OXYGENHUB
◆ main enhanced-x16-emulator / src / rtc.c
4.6 KiB · C · last changed chore(): initial commit 25 days ago source blame history raw
1
// Commander X16 Emulator
2
// Copyright (c) 2021 Michael Steil
3
// All rights reserved. License: 2-clause BSD
4
5
// MCP7940N RTC
6
// * RTC
7
//   * 24h and AM/PM supported
8
//   * oscillator can be turned off
9
//   * alarms etc. not supported
10
11
#include <stdio.h>
12
#include <stdbool.h>
13
#include <time.h>
14
#include "rtc.h"
15
#include "glue.h"
16
17
bool nvram_dirty = false;
18
uint8_t nvram[0x40];
19
20
static bool running;
21
static bool vbaten;
22
static bool h24;
23
24
static unsigned int clocks;
25
static int seconds;
26
static int minutes;
27
static int hours;
28
static int day_of_week;
29
static int day;
30
static int month;
31
static int year;
32
33
#define I2C_DATA_LEN 16
34
static uint8_t i2c_data[I2C_DATA_LEN];
35
static uint8_t i2c_data_pos = 0;
36
37
void
38
rtc_i2c_data(uint8_t v) {
39
	if (i2c_data_pos < I2C_DATA_LEN) {
40
		i2c_data[i2c_data_pos] = v;
41
		i2c_data_pos++;
42
	}
43
}
44
45
46
47
#define BCD(a) (((a) / 10) << 4 | ((a) % 10))
48
#define UNBCD(a) (((a) >> 4) * 10 + ((a) & 0xf))
49
50
void
51
rtc_init(bool set_system_time)
52
{
53
	vbaten = true;
54
	h24 = true;
55
	clocks = 0;
56
57
	if (set_system_time) {
58
		running = true;
59
		time_t t = time(NULL);
60
		struct tm tm = *localtime(&t);
61
		seconds = tm.tm_sec;
62
		minutes = tm.tm_min;
63
		hours = tm.tm_hour;
64
		day_of_week = (tm.tm_wday == 0 ? 7 : tm.tm_wday);
65
		day = tm.tm_mday;
66
		month = tm.tm_mon + 1;
67
		year = tm.tm_year - 100;
68
	} else {
69
		running = false; // yes, the MCP7940N starts out this way!
70
		seconds = 0;
71
		minutes = 0;
72
		hours = 0;
73
		day_of_week = 1;
74
		day = 1;
75
		month = 1;
76
		year = 0;
77
	}
78
}
79
80
static uint8_t days_per_month[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
81
82
bool
83
is_leap_year() {
84
	// the clock does 2000-2099, where the "% 4 == 0" rule applies
85
	return !(year & 3);
86
}
87
88
void
89
rtc_step(int c)
90
{
91
	if (!running) {
92
		return;
93
	}
94
95
	clocks += c;
96
	if (clocks < (MHZ * 1000000)) {
97
		return;
98
	}
99
100
	clocks -= (MHZ * 1000000);
101
	seconds++;
102
	if (seconds < 60) {
103
		return;
104
	}
105
106
	seconds = 0;
107
	minutes++;
108
	if (minutes < 60) {
109
		return;
110
	}
111
112
	minutes = 0;
113
	hours++;
114
	if (hours < 24) {
115
		return;
116
	}
117
118
	hours = 0;
119
	day_of_week++;
120
	if (day_of_week > 7) {
121
		day_of_week = 1;
122
	}
123
	day++;
124
	uint8_t dpm = days_per_month[month - 1];
125
	if (month == 2 && is_leap_year()) {
126
		dpm++;
127
	}
128
	if (day <= dpm) {
129
		return;
130
	}
131
132
	day = 1;
133
	month++;
134
	if (month <= 12) {
135
		return;
136
	}
137
138
	month = 1;
139
	year++;
140
	if (year == 100) {
141
		year = 0; // Y2.1K problem! ;-)
142
	}
143
}
144
145
146
uint8_t
147
rtc_read() {
148
	//    printf("RTC READ $%02X\n", a);
149
	uint8_t ret;
150
	switch (i2c_data[0]) {
151
		case 0:
152
			ret = BCD(seconds) | (running << 7);
153
			break;
154
		case 1:
155
			ret = BCD(minutes);
156
			break;
157
		case 2: {
158
			uint8_t h = hours;
159
			bool pm = false;
160
			if (!h24) {
161
				// AM/PM
162
				if (h >= 12) {
163
					pm = true;
164
					h -= 12;
165
				}
166
				if (h == 0) {
167
					h = 12;
168
				}
169
			}
170
			h = BCD(h);
171
			h |= pm << 5;
172
			h |= (!h24) << 6;
173
			ret = h;
174
			break;
175
		}
176
		case 3: {
177
			uint8_t v = day_of_week;
178
			v |= vbaten << 3;
179
			v |= running << 5;
180
			ret = v;
181
			break;
182
		}
183
		case 4:
184
			ret = BCD(day);
185
			break;
186
		case 5:
187
			ret = BCD(month) | is_leap_year() << 5;
188
			break;
189
		case 6:
190
			ret = BCD(year);
191
			break;
192
		default:
193
			if (i2c_data[0] >= 0x20 && i2c_data[0] < 0x60) {
194
				ret = nvram[i2c_data[0] - 0x20];
195
			} else if (i2c_data[0] >= 0x60) {
196
				ret = 0xff;
197
			} else {
198
				ret = 0;
199
			}
200
			break;
201
	}
202
203
	i2c_data_pos = 0;
204
	return ret;
205
}
206
207
void
208
rtc_write() {
209
	//    printf("RTC WRITE $%02X, $%02X\n", a, v);
210
	switch (i2c_data[0]) {
211
		case 0:
212
			running = !!(i2c_data[1] & 0x80);
213
			seconds = UNBCD(i2c_data[1] & 0x7f);
214
			break;
215
		case 1:
216
			minutes = UNBCD(i2c_data[1]);
217
			break;
218
		case 2: {
219
			h24 = !(i2c_data[1] & 0x40);
220
			uint8_t h = i2c_data[1] & 0x3f;
221
			bool pm = false;
222
			if (!h24) {
223
				pm = i2c_data[1] & 0x20;
224
				h &= 0x1f;
225
			}
226
			h = UNBCD(h);
227
			if (!h24 && h == 12) {
228
				h = 0;
229
			}
230
			if (pm) {
231
				h += 12;
232
			}
233
			hours = h;
234
			break;
235
		}
236
		case 3: {
237
			day_of_week = i2c_data[1] & 7;
238
			vbaten = !!(i2c_data[1] & 0x20);
239
			break;
240
		}
241
		case 4:
242
			day = UNBCD(i2c_data[1]);
243
			break;
244
		case 5:
245
			month = UNBCD(i2c_data[1]);
246
			break;
247
		case 6:
248
			year = UNBCD(i2c_data[1]);
249
			break;
250
		default:
251
			if (i2c_data[0] >= 0x20 && i2c_data[0] < 0x60) {
252
				nvram[i2c_data[0]- 0x20] = i2c_data[1];
253
				nvram_dirty = true;
254
			}
255
	}
256
257
	i2c_data_pos = 0;
258
}
259
260
// Side-effect-free snapshot of RTC state for the ImGui debugger.
261
void
262
rtc_debug_get_state(rtc_debug_state_t *out)
263
{
264
	out->running      = running;
265
	out->h24          = h24;
266
	out->seconds      = seconds;
267
	out->minutes      = minutes;
268
	out->hours        = hours;
269
	out->day_of_week  = day_of_week;
270
	out->day          = day;
271
	out->month        = month;
272
	out->year         = year;
273
	out->i2c_data_pos = i2c_data_pos;
274
	out->nvram_dirty  = nvram_dirty;
275
	for (int i = 0; i < 0x40; i++) {
276
		out->nvram[i] = nvram[i];
277
	}
278
}