-
Notifications
You must be signed in to change notification settings - Fork 4
/
rtc.c
212 lines (191 loc) · 5.85 KB
/
rtc.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
/*
Simulation of the clock logic of the MC146818 RTC chip.
*/
/*
SPDX-License-Identifier: MIT
Copyright (c) 2024 Sprite_tm <[email protected]>
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <time.h>
#include "emu.h"
#include "log.h"
#include "rtc.h"
// Debug logging
#define RTC_LOG(msg_level, format_and_args...) \
log_printf(LOG_SRC_RTC, msg_level, format_and_args)
#define RTC_LOG_DEBUG(format_and_args...) RTC_LOG(LOG_DEBUG, format_and_args)
#define RTC_LOG_NOTICE(format_and_args...) RTC_LOG(LOG_NOTICE, format_and_args)
#define CALSECS 0x00
#define CALSECALARM 0x01
#define CALMINS 0x02
#define CALMINALARM 0x03
#define CALHRS 0x04
#define CALHRALARM 0x05
#define CALDAY 0x06
#define CALDATE 0x07
#define CALMONTH 0x08
#define CALYEAR 0x09
#define CALREGA 0x0A /* REGA: r/w register */
#define CALREGB 0x0B /* REGB: r/w register */
#define CALREGC 0x0C /* REGC: read only register */
#define CALREGD 0x0D /* REGD: read only register */
#define BIT_REGB_SET (1<<7)
#define BIT_REGB_PIE (1<<6)
#define BIT_REGB_AIE (1<<5)
#define BIT_REGB_UIE (1<<4)
#define BIT_REGB_SQWE (1<<3)
#define BIT_REGB_DM (1<<2)
#define BIT_REGB_TWOFOUR (1<<1)
#define BIT_REGB_DSE (1<<0)
#define BIT_REGC_IRQF (1<<7)
#define BIT_REGC_PF (1<<6)
#define BIT_REGC_AF (1<<5)
#define BIT_REGC_UF (1<<4)
//Note we always keep time in binary and convert it when needed. (though I'm not sure
//if the Plexus ever uses bcd time)
struct rtc_t {
uint8_t reg[14];
int us; //amount of us we are in. At 100000, the seconds tick over.
int intr_us; //time in us since a square wave output interrupt
int intr_us_max; //period in us of square wave output
};
//Convert value binary->bcd
static int tobcd(int i) {
return (i/10)*16+(i%10);
}
//Convert value bcd->binary
static int tobin(int i) {
return (i>>4)*10+(i&0xf);
}
//Note: This is dead code for the Plexus-20. Turns out the interrupt pin is connected
//to the square wave output...
static void handle_irq(rtc_t *r) {
int irq;
irq =((r->reg[CALREGB]&BIT_REGB_PIE) && (r->reg[CALREGC]&BIT_REGC_PF));
irq|=((r->reg[CALREGB]&BIT_REGB_AIE) && (r->reg[CALREGC]&BIT_REGC_AF));
irq|=((r->reg[CALREGB]&BIT_REGB_UIE) && (r->reg[CALREGC]&BIT_REGC_UF));
// if (irq) emu_raise_rtc_int();
}
//Make sure the rtc values are sane, as in fall in the ranges specified by
//the datasheet.
void rtc_sanitize_vals(rtc_t *r) {
if (r->reg[CALSECS]>=60) r->reg[CALSECS]=0;
if (r->reg[CALMINS]>=60) r->reg[CALMINS]=0;
if (r->reg[CALHRS]>=24) r->reg[CALHRS]=0;
if (r->reg[CALDAY]==0) r->reg[CALDAY]=1;
if (r->reg[CALDAY]>7) r->reg[CALDAY]=7;
if (r->reg[CALDATE]==0) r->reg[CALDATE]=1;
if (r->reg[CALDATE]>31) r->reg[CALDATE]=31;
if (r->reg[CALMONTH]==0) r->reg[CALMONTH]=1;
if (r->reg[CALMONTH]>12) r->reg[CALMONTH]=12;
if (r->reg[CALYEAR]>99) r->reg[CALYEAR]=0;
}
void rtc_write8(void *obj, unsigned int a, unsigned int val) {
a=a/2; //rtc is on odd addresses
rtc_t *r=(rtc_t*)obj;
static const char *regs[]={"SEC", "SECALRM", "MIN", "MINALRM", "HRS", "HRSALARM", "DAY", "DATE", "MONTH", "YEAR", "A", "B", "C", "D"};
RTC_LOG_DEBUG("RTC: set %s to 0x%02X\n", regs[a], val&0xff);
if (a<=CALYEAR) {
int bcd=r->reg[CALREGB]&1;
if (bcd) val=tobin(val);
r->reg[a]=val;
}
if (a==CALREGA) {
val&=0x7F; //no update in progress
if (((val&0x70)!=0x20) && ((val&0x70)!=0x0)) {
RTC_LOG_NOTICE("RTC: Warning: unsupported input clock setting\n");
} else {
if ((val&0x70)==0x20) {
static const int tpi_us[16]={
0, 3906, 7812, 122, 244, 488, 976, 1953, 3906, 7812, 15625, 31250, 62500, 125000, 250000, 500000};
r->intr_us_max=tpi_us[val&15];
} else {
r->intr_us_max=0;
}
}
}
handle_irq(r);
if (a!=CALREGC && a!=CALREGD) r->reg[a]=val;
//Sanitize values if clock is running
if (!(r->reg[CALREGB]&0x80)) rtc_sanitize_vals(r);
}
void rtc_write16(void *obj, unsigned int a, unsigned int val) {
rtc_write8(obj, a+1, val);
}
unsigned int rtc_read8(void *obj, unsigned int a) {
a=a/2; //rtc is on odd addresses
rtc_t *r=(rtc_t*)obj;
int ret=r->reg[a];
if (a<=CALYEAR) {
int bcd=r->reg[CALREGB]&1;
if (bcd) ret=tobcd(r->reg[a]); else ret=r->reg[a];
}
if (a==CALREGC) r->reg[a]=0; //clears on read
if (a==CALREGD) r->reg[a]=0x80; //set VRT on read
RTC_LOG_DEBUG("RTC: read reg %x -> 0x%x (=%d)\n", a, ret, ret);
return ret;
}
unsigned int rtc_read16(void *obj, unsigned int a) {
return rtc_read8(obj, a+1);
}
rtc_t *rtc_new() {
rtc_t *ret=calloc(sizeof(rtc_t), 1);
rtc_sanitize_vals(ret);
return ret;
}
void rtc_tick(rtc_t *r, int ticklen_us) {
r->us+=ticklen_us;
while (r->us>1000000) {
//Update clock for next second.
if ((r->reg[CALREGB]&BIT_REGB_SET)==0) {
r->reg[CALREGC]|=BIT_REGC_UF;
r->reg[CALSECS]++;
if (r->reg[CALSECS]>=60) {
r->reg[CALSECS]=0;
r->reg[CALMINS]++;
}
if (r->reg[CALMINS]>=60) {
r->reg[CALMINS]=0;
r->reg[CALHRS]++;
}
if (r->reg[CALHRS]>=24) {
r->reg[CALHRS]=0;
r->reg[CALDAY]++;
r->reg[CALDATE]++;
}
if (r->reg[CALDAY]>=8) { //day is 1-7
r->reg[CALDAY]=1;
}
int month=r->reg[CALMONTH]; //month is 1-12
if (month>13) month=13;
const int dim[12]={31,28,31,30,31,30,31,31,30,31,30,31};
if (r->reg[CALDATE]>dim[month-1]) {
r->reg[CALDATE]=1;
r->reg[CALMONTH]++;
}
if (r->reg[CALMONTH]>=13) {
r->reg[CALMONTH]=1;
r->reg[CALYEAR]++;
}
if (r->reg[CALSECS]==r->reg[CALSECALARM] &&
r->reg[CALMINS]==r->reg[CALMINALARM] &&
r->reg[CALHRS]==r->reg[CALHRALARM]) {
r->reg[CALREGC]|=BIT_REGC_AF;
}
handle_irq(r);
}
//One second of simulation time passed.
r->us-=1000000;
}
//Handle square wave interrupt output
r->intr_us+=ticklen_us;
if (r->intr_us_max && r->intr_us>r->intr_us_max) {
r->intr_us=r->intr_us%r->intr_us_max;
r->reg[CALREGC]|=BIT_REGC_PF;
handle_irq(r);
//handle square wave output, which generates the int on the Plexus-20
if (r->reg[CALREGB]&BIT_REGB_SQWE) emu_raise_rtc_int();
}
}