1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 
27 /*
28  * tod driver module for ALI M5819P part
29  */
30 
31 #include <sys/types.h>
32 #include <sys/conf.h>
33 #include <sys/kmem.h>
34 #include <sys/open.h>
35 #include <sys/ddi.h>
36 #include <sys/sunddi.h>
37 
38 #include <sys/todm5819p.h>
39 #include <sys/rmc_comm_dp.h>
40 #include <sys/rmc_comm_drvintf.h>
41 #include <sys/modctl.h>
42 #include <sys/stat.h>
43 #include <sys/clock.h>
44 #include <sys/reboot.h>
45 #include <sys/machsystm.h>
46 
47 static timestruc_t	todm5819p_rmc_get(void);
48 static void		todm5819p_rmc_set(timestruc_t);
49 static uint_t		todm5819p_rmc_set_watchdog_timer(uint_t);
50 static uint_t		todm5819p_rmc_clear_watchdog_timer(void);
51 static void		todm5819p_rmc_set_power_alarm(timestruc_t);
52 static void		todm5819p_rmc_clear_power_alarm(void);
53 static uint64_t		todm5819p_rmc_get_cpufrequency(void);
54 
55 extern uint64_t		find_cpufrequency(volatile uint8_t *);
56 
57 /*
58  * External variables
59  */
60 extern int	watchdog_enable;
61 extern int	watchdog_available;
62 extern int	boothowto;
63 
64 /*
65  * Global variables
66  */
67 int m5819p_debug_flags;
68 
69 /*
70  * Module linkage information for the kernel.
71  */
72 static struct modlmisc modlmisc = {
73 	&mod_miscops, "tod module for ALI M5819P"
74 };
75 
76 static struct modlinkage modlinkage = {
77 	MODREV_1, (void *)&modlmisc, NULL
78 };
79 
80 static todinfo_t rtc_to_tod(struct rtc_t *);
81 static void read_rtc(struct rtc_t *);
82 static void write_rtc_time(struct rtc_t *);
83 static void write_rtc_alarm(struct rtc_t *);
84 
85 
86 int
87 _init(void)
88 {
89 	if (strcmp(tod_module_name, "todm5819p_rmc") == 0) {
90 		M5819P_ADDR_REG = RTC_B;
91 		M5819P_DATA_REG = (RTC_DM | RTC_HM);
92 
93 		tod_ops.tod_get = todm5819p_rmc_get;
94 		tod_ops.tod_set = todm5819p_rmc_set;
95 
96 		tod_ops.tod_set_watchdog_timer =
97 		    todm5819p_rmc_set_watchdog_timer;
98 		tod_ops.tod_clear_watchdog_timer =
99 		    todm5819p_rmc_clear_watchdog_timer;
100 		tod_ops.tod_set_power_alarm = todm5819p_rmc_set_power_alarm;
101 		tod_ops.tod_clear_power_alarm = todm5819p_rmc_clear_power_alarm;
102 		tod_ops.tod_get_cpufrequency = todm5819p_rmc_get_cpufrequency;
103 		if (boothowto & RB_DEBUG) {
104 			cmn_err(CE_WARN, "todm5819p_rmc: kernel debugger "
105 			    "detected: hardware watchdog disabled");
106 		}
107 	}
108 
109 	return (mod_install(&modlinkage));
110 }
111 
112 int
113 _fini(void)
114 {
115 	if (strcmp(tod_module_name, "todm5819p_rmc") == 0)
116 		return (EBUSY);
117 
118 	return (mod_remove(&modlinkage));
119 }
120 
121 /*
122  * The loadable-module _info(9E) entry point
123  */
124 int
125 _info(struct modinfo *modinfop)
126 {
127 	return (mod_info(&modlinkage, modinfop));
128 }
129 
130 
131 /*
132  * todm5819p_rmc is normally called once a second, from the clock thread.
133  * It may also be infrequently called from other contexts (eg. ddi framework),
134  * in which case our counting to NBAD_READ_LIMIT may be a few seconds short
135  * of the desired 15-minute timeframe; this slight inaccuracy is acceptable.
136  */
137 #define	NBAD_READ_LIMIT	(900)   /* 15 minutes, in seconds */
138 /*
139  * Read the current time from the clock chip and convert to UNIX form.
140  * Checks the century, but otherwise assumes that the values in the clock
141  * chip are valid.
142  * Must be called with tod_lock held.
143  */
144 static timestruc_t
145 todm5819p_rmc_get(void)
146 {
147 	int i;
148 	int s;
149 	timestruc_t ts;
150 	struct rtc_t rtc;
151 	static int nbad_reads = 0;
152 
153 	ASSERT(MUTEX_HELD(&tod_lock));
154 
155 	/* set the hw watchdog timer if it's been activated */
156 	if (watchdog_activated) {
157 		int ret = 0;
158 		ret = tod_ops.tod_set_watchdog_timer(0);
159 		/*
160 		 * The empty set_watchdog routine returns a 0. So if a
161 		 * coded routine fails we will look for a -1 for a failure.
162 		 */
163 		if (ret == -1)
164 			cmn_err(CE_WARN, "todm5819p: failed to set hardware "
165 			    "watchdog timer.");
166 	}
167 
168 	/*
169 	 * Read current time from the tod. If the tod isn't accessible, wait and
170 	 * retry.
171 	 * Run critical in the time critical section to avoid being interrupted
172 	 */
173 	for (i = 0; i < TODM5819_UIP_RETRY_THRESH; i++) {
174 		s = ddi_enter_critical();
175 		M5819P_ADDR_REG = RTC_A;
176 		if (!(M5819P_DATA_REG & RTC_UIP)) {
177 			read_rtc(&rtc);
178 			ddi_exit_critical(s);
179 			break;
180 		}
181 		ddi_exit_critical(s);
182 		drv_usecwait(TODM5819_UIP_WAIT_USEC);
183 	}
184 	if (i == TODM5819_UIP_RETRY_THRESH) {
185 		/*
186 		 * tod is inaccessible: just return current software time
187 		 */
188 		tod_fault_reset();
189 		return (hrestime);
190 	}
191 
192 	DPRINTF("todm5819p_rmc_get: century=%d year=%d dom=%d hrs=%d\n",
193 	    (int)rtc.rtc_century, (int)rtc.rtc_year, (int)rtc.rtc_dom,
194 	    (int)rtc.rtc_hrs);
195 
196 	/* detect and correct invalid century register data */
197 	if (rtc.rtc_century < 19) {
198 		DPRINTF(
199 		    "todm5819p_rmc_get: century invalid (%d), returning 20\n",
200 		    (int)rtc.rtc_century);
201 		rtc.rtc_century = 20;
202 		if (++nbad_reads == NBAD_READ_LIMIT) {
203 			nbad_reads = 0;
204 			cmn_err(CE_WARN, "todm5819p: realtime clock century "
205 			    "register appears to be defective.");
206 		}
207 	}
208 
209 	ts.tv_sec = tod_to_utc(rtc_to_tod(&rtc));
210 	ts.tv_nsec = 0;
211 	return (ts);
212 }
213 
214 static todinfo_t
215 rtc_to_tod(struct rtc_t *rtc)
216 {
217 	todinfo_t tod;
218 
219 	/*
220 	 * tod_year is base 1900 so this code needs to adjust the true year
221 	 * retrieved from the rtc's century and year fields.
222 	 */
223 	tod.tod_year	= rtc->rtc_year + (rtc->rtc_century * 100) - 1900;
224 	tod.tod_month	= rtc->rtc_mon;
225 	tod.tod_day	= rtc->rtc_dom;
226 	tod.tod_dow	= rtc->rtc_dow;
227 	tod.tod_hour	= rtc->rtc_hrs;
228 	tod.tod_min	= rtc->rtc_min;
229 	tod.tod_sec	= rtc->rtc_sec;
230 
231 	return (tod);
232 }
233 
234 static void
235 read_rtc(struct rtc_t *rtc)
236 {
237 	M5819P_ADDR_REG = RTC_SEC;
238 	rtc->rtc_sec = M5819P_DATA_REG;
239 	M5819P_ADDR_REG = RTC_ASEC;
240 	rtc->rtc_asec = M5819P_DATA_REG;
241 	M5819P_ADDR_REG = RTC_MIN;
242 	rtc->rtc_min = M5819P_DATA_REG;
243 	M5819P_ADDR_REG = RTC_AMIN;
244 	rtc->rtc_amin = M5819P_DATA_REG;
245 	M5819P_ADDR_REG = RTC_HRS;
246 	rtc->rtc_hrs = M5819P_DATA_REG;
247 	M5819P_ADDR_REG = RTC_AHRS;
248 	rtc->rtc_ahrs = M5819P_DATA_REG;
249 	M5819P_ADDR_REG = RTC_DOW;
250 	rtc->rtc_dow = M5819P_DATA_REG;
251 	M5819P_ADDR_REG = RTC_DOM;
252 	rtc->rtc_dom = M5819P_DATA_REG;
253 	M5819P_ADDR_REG = RTC_MON;
254 	rtc->rtc_mon = M5819P_DATA_REG;
255 	M5819P_ADDR_REG = RTC_YEAR;
256 	rtc->rtc_year = M5819P_DATA_REG;
257 	M5819P_ADDR_REG = RTC_CENTURY;
258 	rtc->rtc_century = M5819P_DATA_REG;
259 
260 	/* Read date alarm */
261 	M5819P_ADDR_REG = RTC_ADOM_REG;
262 	rtc->rtc_adom = (M5819P_DATA_REG) & RTC_ADOM;
263 }
264 
265 /*
266  * Write the specified time into the clock chip.
267  * Must be called with tod_lock held.
268  */
269 static void
270 todm5819p_rmc_set(timestruc_t ts)
271 {
272 	struct rtc_t	rtc;
273 	todinfo_t tod = utc_to_tod(ts.tv_sec);
274 	int year;
275 	rmc_comm_msg_t request;
276 	dp_set_date_time_t set_time_msg;
277 
278 	ASSERT(MUTEX_HELD(&tod_lock));
279 
280 	/* tod_year is base 1900 so this code needs to adjust */
281 	year = 1900 + tod.tod_year;
282 	rtc.rtc_year	= year % 100;
283 	rtc.rtc_century = year / 100;
284 	rtc.rtc_mon	= (uint8_t)tod.tod_month;
285 	rtc.rtc_dom	= (uint8_t)tod.tod_day;
286 	rtc.rtc_dow	= (uint8_t)tod.tod_dow;
287 	rtc.rtc_hrs	= (uint8_t)tod.tod_hour;
288 	rtc.rtc_min	= (uint8_t)tod.tod_min;
289 	rtc.rtc_sec	= (uint8_t)tod.tod_sec;
290 
291 	DPRINTF("todm5819p_rmc_set: century=%d year=%d dom=%d hrs=%d\n",
292 	    (int)rtc.rtc_century, (int)rtc.rtc_year, (int)rtc.rtc_dom,
293 	    (int)rtc.rtc_hrs);
294 
295 	write_rtc_time(&rtc);
296 
297 	set_time_msg.year	= year - 1900;
298 	set_time_msg.month	= tod.tod_month - 1;
299 	set_time_msg.day	= tod.tod_day;
300 	set_time_msg.hour	= tod.tod_hour;
301 	set_time_msg.minute	= tod.tod_min;
302 	set_time_msg.second	= tod.tod_sec;
303 
304 	request.msg_type = DP_SET_DATE_TIME;
305 	request.msg_len = sizeof (set_time_msg);
306 	request.msg_buf = (caddr_t)&set_time_msg;
307 
308 	(void) rmc_comm_request_nowait(&request, 0);
309 }
310 
311 void
312 write_rtc_time(struct rtc_t *rtc)
313 {
314 	uint8_t	regb;
315 
316 	/*
317 	 * Freeze
318 	 */
319 	M5819P_ADDR_REG = RTC_B;
320 	regb = M5819P_DATA_REG;
321 	M5819P_DATA_REG = (regb | RTC_SET);
322 
323 	M5819P_ADDR_REG = RTC_SEC;
324 	M5819P_DATA_REG = rtc->rtc_sec;
325 	M5819P_ADDR_REG = RTC_MIN;
326 	M5819P_DATA_REG = rtc->rtc_min;
327 	M5819P_ADDR_REG = RTC_HRS;
328 	M5819P_DATA_REG = rtc->rtc_hrs;
329 	M5819P_ADDR_REG = RTC_DOW;
330 	M5819P_DATA_REG = rtc->rtc_dow;
331 	M5819P_ADDR_REG = RTC_DOM;
332 	M5819P_DATA_REG = rtc->rtc_dom;
333 	M5819P_ADDR_REG = RTC_MON;
334 	M5819P_DATA_REG = rtc->rtc_mon;
335 	M5819P_ADDR_REG = RTC_YEAR;
336 	M5819P_DATA_REG = rtc->rtc_year;
337 	M5819P_ADDR_REG = RTC_CENTURY;
338 	M5819P_DATA_REG = rtc->rtc_century;
339 
340 	/*
341 	 * Unfreeze
342 	 */
343 	M5819P_ADDR_REG = RTC_B;
344 	M5819P_DATA_REG = regb;
345 }
346 
347 void
348 write_rtc_alarm(struct rtc_t *rtc)
349 {
350 	M5819P_ADDR_REG = RTC_ASEC;
351 	M5819P_DATA_REG = rtc->rtc_asec;
352 	M5819P_ADDR_REG = RTC_AMIN;
353 	M5819P_DATA_REG = rtc->rtc_amin;
354 	M5819P_ADDR_REG = RTC_AHRS;
355 	M5819P_DATA_REG = rtc->rtc_ahrs;
356 
357 	M5819P_ADDR_REG = RTC_ADOM_REG;
358 	M5819P_DATA_REG = rtc->rtc_adom;
359 }
360 
361 /*
362  * program the rtc registers for alarm to go off at the specified time
363  */
364 static void
365 todm5819p_rmc_set_power_alarm(timestruc_t ts)
366 {
367 	todinfo_t	tod;
368 	uint8_t		regb;
369 	struct rtc_t	rtc;
370 
371 	ASSERT(MUTEX_HELD(&tod_lock));
372 	tod = utc_to_tod(ts.tv_sec);
373 
374 	/*
375 	 * disable alarms and clear AF flag by reading reg C
376 	 */
377 	M5819P_ADDR_REG = RTC_B;
378 	regb = M5819P_DATA_REG;
379 	M5819P_DATA_REG = regb & ~RTC_AIE;
380 	M5819P_ADDR_REG = RTC_C;
381 	(void) M5819P_DATA_REG;
382 
383 	rtc.rtc_asec = (uint8_t)tod.tod_sec;
384 	rtc.rtc_amin = (uint8_t)tod.tod_min;
385 	rtc.rtc_ahrs = (uint8_t)tod.tod_hour;
386 	rtc.rtc_adom = (uint8_t)tod.tod_day;
387 
388 	/*
389 	 * Write alarm values and enable alarm
390 	 */
391 	write_rtc_alarm(&rtc);
392 
393 	M5819P_ADDR_REG = RTC_B;
394 	M5819P_DATA_REG = regb | RTC_AIE;
395 }
396 
397 /*
398  * clear alarm interrupt
399  */
400 static void
401 todm5819p_rmc_clear_power_alarm(void)
402 {
403 	uint8_t regb;
404 
405 	ASSERT(MUTEX_HELD(&tod_lock));
406 
407 	M5819P_ADDR_REG = RTC_B;
408 	regb = M5819P_DATA_REG;
409 	M5819P_DATA_REG = regb & ~RTC_AIE;
410 }
411 
412 /*
413  * Determine the cpu frequency by watching the TOD chip rollover twice.
414  * Cpu clock rate is determined by computing the ticks added (in tick register)
415  * during one second interval on TOD.
416  */
417 uint64_t
418 todm5819p_rmc_get_cpufrequency(void)
419 {
420 	ASSERT(MUTEX_HELD(&tod_lock));
421 	M5819P_ADDR_REG = RTC_SEC;
422 	return (find_cpufrequency(v_rtc_data_reg));
423 }
424 
425 /*ARGSUSED*/
426 static uint_t
427 todm5819p_rmc_set_watchdog_timer(uint_t timeoutval)
428 {
429 	ASSERT(MUTEX_HELD(&tod_lock));
430 	return (0);
431 }
432 
433 static uint_t
434 todm5819p_rmc_clear_watchdog_timer(void)
435 {
436 	ASSERT(MUTEX_HELD(&tod_lock));
437 	return (0);
438 }
439