1 // license:BSD-3-Clause
2 // copyright-holders:Ryan Holtz
3 /***************************************************************************
4 
5     DP8573 Real Time Clock (RTC)
6 
7 ***************************************************************************/
8 
9 #include "emu.h"
10 #include "machine/dp8573.h"
11 #include "machine/timehelp.h"
12 
13 #define LOG_GENERAL (1 << 0)
14 #define LOG_TICKS   (1 << 1)
15 #define LOG_ALL     (LOG_GENERAL | LOG_TICKS)
16 
17 #define VERBOSE (0)
18 #include "logmacro.h"
19 
20 // device type definition
21 DEFINE_DEVICE_TYPE(DP8573, dp8573_device, "dp8573", "DP8573 Real-Time Clock")
22 
dp8573_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)23 dp8573_device::dp8573_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
24 	: device_t(mconfig, DP8573, tag, owner, clock)
25 	, device_nvram_interface(mconfig, *this)
26 	, m_intr_cb(*this)
27 	, m_mfo_cb(*this)
28 {
29 }
30 
device_start()31 void dp8573_device::device_start()
32 {
33 	save_item(NAME(m_ram));
34 	save_item(NAME(m_tscr));
35 	save_item(NAME(m_pfr));
36 	save_item(NAME(m_millis));
37 
38 	m_timer = timer_alloc(TIMER_ID);
39 	m_timer->adjust(attotime::never);
40 
41 	m_intr_cb.resolve_safe();
42 	m_mfo_cb.resolve_safe();
43 
44 	memset(m_ram, 0, 32);
45 	sync_time();
46 
47 	m_tscr = 0;
48 
49 	m_timer->adjust(attotime::from_msec(1), 0, attotime::from_msec(1));
50 }
51 
sync_time()52 void dp8573_device::sync_time()
53 {
54 	system_time systime;
55 	machine().base_datetime(systime);
56 
57 	m_millis = 0;
58 	m_ram[REG_HUNDREDTH] = 0;
59 	m_ram[REG_SECOND] = time_helper::make_bcd(systime.utc_time.second);
60 	m_ram[REG_MINUTE] = time_helper::make_bcd(systime.utc_time.minute);
61 	m_ram[REG_HOUR] = time_helper::make_bcd(systime.utc_time.hour);
62 	m_ram[REG_DAY] = time_helper::make_bcd(systime.utc_time.mday);
63 	m_ram[REG_MONTH] = time_helper::make_bcd(systime.utc_time.month + 1);
64 	m_ram[REG_YEAR] = time_helper::make_bcd(systime.utc_time.year % 100);
65 	m_ram[REG_DAYOFWEEK] = time_helper::make_bcd(systime.utc_time.weekday + 1);
66 
67 	m_pfr = 0;
68 
69 	// FIXME: should probably rely on nvram start/stop state
70 	m_ram[REG_RTMR] = RTMR_CSS;
71 }
72 
save_registers()73 void dp8573_device::save_registers()
74 {
75 	m_ram[REG_SAVE_SECOND] = m_ram[REG_SECOND];
76 	m_ram[REG_SAVE_MINUTE] = m_ram[REG_MINUTE];
77 	m_ram[REG_SAVE_HOUR]   = m_ram[REG_HOUR];
78 	m_ram[REG_SAVE_DAY]    = m_ram[REG_DAY];
79 	m_ram[REG_SAVE_MONTH]  = m_ram[REG_MONTH];
80 }
81 
device_timer(emu_timer & timer,device_timer_id id,int param,void * ptr)82 void dp8573_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
83 {
84 	if ((m_pfr & PFR_OSF) || !(m_ram[REG_RTMR] & RTMR_CSS))
85 	{
86 		LOGMASKED(LOG_TICKS, "Tick suppressed due to OSF or !CSS\n");
87 		return;
88 	}
89 
90 	m_pfr |= PFR_1MS;
91 
92 	bool carry = false;
93 	bool tens_carry = false;
94 	time_helper::inc_bcd(&m_millis, 0xff, 0x00, 0x09, &tens_carry);
95 	if (tens_carry)
96 	{
97 		m_pfr |= PFR_10MS;
98 		carry = time_helper::inc_bcd(&m_ram[REG_HUNDREDTH], 0xff, 0x00, 0x99, &tens_carry);
99 		if (tens_carry)
100 			m_pfr |= PFR_100MS;
101 	}
102 	if (carry)
103 	{
104 		m_pfr |= PFR_1S;
105 		carry = time_helper::inc_bcd(&m_ram[REG_SECOND], 0xff, 0x00, 0x59, &tens_carry);
106 		if (tens_carry)
107 			m_pfr |= PFR_10S;
108 	}
109 	if (carry)
110 	{
111 		m_pfr |= PFR_1MIN;
112 		carry = time_helper::inc_bcd(&m_ram[REG_MINUTE], 0xff, 0x00, 0x59);
113 	}
114 	if (carry)
115 	{
116 		if (m_ram[REG_RTMR] & RTMR_12H)
117 		{
118 			carry = time_helper::inc_bcd(&m_ram[REG_HOUR], 0xff, 0x01, 0x12);
119 			if (carry)
120 			{
121 				m_ram[REG_HOUR] |= 0x20;
122 				carry = !(m_ram[REG_HOUR] & 0x20);
123 			}
124 		}
125 		else
126 		{
127 			carry = time_helper::inc_bcd(&m_ram[REG_HOUR], 0xff, 0x00, 0x23);
128 		}
129 	}
130 	if (carry)
131 	{
132 		static const uint8_t daysinmonth[] = { 0x31, 0x28, 0x31, 0x30, 0x31, 0x30, 0x31, 0x31, 0x30, 0x31, 0x30, 0x31 };
133 
134 		time_helper::inc_bcd(&m_ram[REG_DAYOFWEEK], 0xff, 0x01, 0x07);
135 
136 		uint8_t month = time_helper::from_bcd(m_ram[REG_MONTH]);
137 
138 		uint8_t maxdays;
139 		if (month == 2 && (m_ram[REG_RTMR] & RTMR_LY) == 0)
140 		{
141 			maxdays = 0x29;
142 		}
143 		else if (month >= 1 && month <= 12)
144 		{
145 			maxdays = daysinmonth[month - 1];
146 		}
147 		else
148 		{
149 			maxdays = 0x31;
150 		}
151 
152 		carry = time_helper::inc_bcd(&m_ram[REG_DAY], 0xff, 0x01, maxdays);
153 	}
154 	if (carry)
155 	{
156 		carry = time_helper::inc_bcd(&m_ram[REG_MONTH], 0xff, 0x01, 0x12);
157 	}
158 	if (carry)
159 	{
160 		carry = time_helper::inc_bcd(&m_ram[REG_YEAR], 0xff, 0x00, 0x99);
161 	}
162 	if (carry)
163 	{
164 		// Advance the leap-year counter
165 		uint8_t leap = m_ram[REG_RTMR] & RTMR_LY;
166 		leap = (leap + 1) & RTMR_LY;
167 		m_ram[REG_RTMR] &= ~RTMR_LY;
168 		m_ram[REG_RTMR] |= leap;
169 	}
170 
171 	// Check for Time Save mode
172 	if (m_tscr & TSCR_TS)
173 	{
174 		save_registers();
175 	}
176 
177 	// Check for periodic interrupts
178 	const uint8_t icr0 = m_ram[REG_PFR_ICR0] & ~ICR0_RAM_MASK;
179 	const uint8_t pfr = m_pfr & ~ICR0_RAM_MASK;
180 	if (icr0 & pfr)
181 	{
182 		set_interrupt(MSR_PER);
183 	}
184 
185 	const uint8_t icr1 = m_ram[REG_TSCR_ICR1] & ICR1_COMPARE_MASK;
186 	if (icr1)
187 	{
188 		if (m_ram[REG_SECOND] == m_ram[REG_COMP_SECOND] ||
189 			m_ram[REG_MINUTE] == m_ram[REG_COMP_MINUTE] ||
190 			m_ram[REG_HOUR] == m_ram[REG_COMP_HOUR] ||
191 			m_ram[REG_DAY] == m_ram[REG_COMP_DAY] ||
192 			m_ram[REG_MONTH] == m_ram[REG_COMP_MONTH] ||
193 			m_ram[REG_DAYOFWEEK] == m_ram[REG_COMP_DAYOFWEEK])
194 		{
195 			set_interrupt(MSR_AL);
196 		}
197 	}
198 }
199 
set_interrupt(uint8_t mask)200 void dp8573_device::set_interrupt(uint8_t mask)
201 {
202 	bool was_intr = m_ram[REG_MSR] & MSR_INT;
203 	m_ram[REG_MSR] |= mask;
204 
205 	if (m_ram[REG_MSR] & MSR_INT_MASK)
206 		m_ram[REG_MSR] |= MSR_INT;
207 
208 	if (!was_intr && (m_ram[REG_MSR] & MSR_INT))
209 		m_intr_cb(0);
210 }
211 
clear_interrupt(uint8_t mask)212 void dp8573_device::clear_interrupt(uint8_t mask)
213 {
214 	bool was_intr = m_ram[REG_MSR] & MSR_INT;
215 	m_ram[REG_MSR] &= ~mask;
216 
217 	if (was_intr && !(m_ram[REG_MSR] & MSR_INT))
218 		m_intr_cb(1);
219 }
220 
write(offs_t offset,u8 data)221 void dp8573_device::write(offs_t offset, u8 data)
222 {
223 	LOGMASKED(LOG_GENERAL, "%s: DP8573 - Register Write: %02x = %02x\n", machine().describe_context(), offset, data);
224 
225 	switch (offset)
226 	{
227 		case REG_MSR: // Main Status Register
228 			m_ram[offset] &= ~MSR_RAM_MASK;
229 			m_ram[offset] |= data & MSR_RAM_MASK;
230 			if (data & MSR_CLEARABLE_MASK)
231 				clear_interrupt(data & MSR_CLEARABLE_MASK);
232 			break;
233 
234 		case REG_RTMR: // Not Applicable / Real-Time Mode Register
235 			if (m_ram[REG_MSR] & MSR_RS)
236 			{
237 				const uint8_t old = m_ram[offset];
238 				m_ram[offset] = data;
239 				if ((old ^ data) & RTMR_12H)
240 				{
241 					uint8_t hour;
242 					if (old & RTMR_12H)
243 						hour = time_helper::from_bcd(m_ram[REG_HOUR] & 0x1f) + (BIT(m_ram[REG_HOUR], 5) ? 12 : 0);
244 					else
245 						hour = time_helper::from_bcd(m_ram[REG_HOUR]);
246 
247 					if (data & RTMR_12H)
248 					{
249 						m_ram[REG_HOUR] = time_helper::make_bcd(hour % 12);
250 						m_ram[REG_HOUR] |= (hour > 11) ? 0x20 : 0;
251 					}
252 					else
253 					{
254 						m_ram[REG_HOUR] = time_helper::make_bcd(hour);
255 					}
256 				}
257 			}
258 			break;
259 
260 		case REG_OMR: // Not Applicable / Output Mode Register
261 			if (m_ram[REG_MSR] & MSR_RS)
262 			{
263 				// Not yet implemented: Buffered Crystal Oscillator output on MFO pin
264 				m_ram[offset] = data;
265 			}
266 			break;
267 
268 		case REG_PFR_ICR0: // Periodic Flag Register / Interrupt Control Register 0
269 			if (m_ram[REG_MSR] & MSR_RS)
270 			{
271 				m_ram[offset] = data;
272 			}
273 			else
274 			{
275 				m_pfr &= ~PFR_TM;
276 				m_pfr |= data & PFR_TM;
277 			}
278 			break;
279 
280 		case REG_TSCR_ICR1: // Time Save Control Register / Interrupt Control Register 1
281 			if (m_ram[REG_MSR] & MSR_RS)
282 			{
283 				m_ram[offset] = data;
284 			}
285 			else
286 			{
287 				m_tscr = data & ~TSCR_NA;
288 				if (data & TSCR_TS)
289 					save_registers();
290 			}
291 			break;
292 
293 		case REG_RAM_D1D0: // RAM, D1/D0 bits only
294 			m_ram[offset] = data & 3;
295 			break;
296 
297 		case REG_NA_0FH:
298 		case REG_NA_10H:
299 		case REG_NA_11H:
300 		case REG_NA_12H:
301 			break;
302 
303 		default:
304 			m_ram[offset] = data;
305 			break;
306 	}
307 }
308 
read(offs_t offset)309 u8 dp8573_device::read(offs_t offset)
310 {
311 	uint8_t ret = m_ram[offset];
312 
313 	if (offset >= REG_RTMR && offset <= REG_TSCR_ICR1)
314 	{
315 		if (m_ram[REG_MSR] & MSR_RS)
316 		{
317 		}
318 		else
319 		{
320 			switch (offset)
321 			{
322 				case REG_RTMR:
323 				case REG_OMR:
324 					ret = 0;
325 					break;
326 				case REG_PFR_ICR0:
327 					ret = m_pfr;
328 					m_pfr &= ~PFR_READ_CLEAR_MASK;
329 					break;
330 				case REG_TSCR_ICR1:
331 					ret = m_tscr;
332 					break;
333 				default:
334 					break;
335 			}
336 		}
337 	}
338 
339 	LOGMASKED(LOG_GENERAL, "%s: DP8573 - Register Read: %02x = %02x\n", machine().describe_context(), offset, ret);
340 	return ret;
341 }
342 
nvram_default()343 void dp8573_device::nvram_default()
344 {
345 	memset(m_ram, 0, 32);
346 	sync_time();
347 }
348 
nvram_read(emu_file & file)349 void dp8573_device::nvram_read(emu_file &file)
350 {
351 	file.read(m_ram, 32);
352 	sync_time();
353 }
354 
nvram_write(emu_file & file)355 void dp8573_device::nvram_write(emu_file &file)
356 {
357 	file.write(m_ram, 32);
358 }
359