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 2009 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26/*
27 * tod driver module for Mostek M48T59 part
28 */
29
30#include <sys/types.h>
31#include <sys/param.h>
32#include <sys/sysmacros.h>
33#include <sys/systm.h>
34#include <sys/errno.h>
35#include <sys/modctl.h>
36#include <sys/autoconf.h>
37#include <sys/debug.h>
38#include <sys/clock.h>
39#include <sys/todmostek.h>
40#include <sys/reboot.h>
41#include <sys/cmn_err.h>
42#include <sys/cpuvar.h>
43
44static timestruc_t	todm_get(void);
45static void		todm_set(timestruc_t);
46static uint_t		todm_set_watchdog_timer(uint_t);
47static uint_t		todm_clear_watchdog_timer(void);
48static void		todm_set_power_alarm(timestruc_t);
49static void		todm_clear_power_alarm(void);
50static uint64_t		todm_get_cpufrequency(void);
51
52static uchar_t watchdog_bits = 0;
53static uint_t watchdog_timeout;
54
55extern uint64_t find_cpufrequency(volatile uchar_t *);
56
57/*
58 * Module linkage information for the kernel.
59 */
60static struct modlmisc modlmisc = {
61	&mod_miscops, "tod module for Mostek M48T59"
62};
63
64static struct modlinkage modlinkage = {
65	MODREV_1, (void *)&modlmisc, NULL
66};
67
68int
69_init(void)
70{
71	if (strcmp(tod_module_name, "todmostek") == 0) {
72		tod_ops.tod_get = todm_get;
73		tod_ops.tod_set = todm_set;
74		tod_ops.tod_set_watchdog_timer = todm_set_watchdog_timer;
75		tod_ops.tod_clear_watchdog_timer = todm_clear_watchdog_timer;
76		tod_ops.tod_set_power_alarm = todm_set_power_alarm;
77		tod_ops.tod_clear_power_alarm = todm_clear_power_alarm;
78		tod_ops.tod_get_cpufrequency = todm_get_cpufrequency;
79
80		/*
81		 * check if hardware watchdog timer is available and user
82		 * enabled it.
83		 */
84		if (watchdog_enable) {
85			if (!watchdog_available) {
86				cmn_err(CE_WARN,
87				    "Hardware watchdog unavailable");
88			} else if (boothowto & RB_DEBUG) {
89				cmn_err(CE_WARN, "Hardware watchdog disabled"
90				    " [debugger]");
91			}
92		}
93	}
94
95	return (mod_install(&modlinkage));
96}
97
98int
99_fini(void)
100{
101	if (strcmp(tod_module_name, "todmostek") == 0)
102		return (EBUSY);
103	else
104		return (mod_remove(&modlinkage));
105}
106
107int
108_info(struct modinfo *modinfop)
109{
110	return (mod_info(&modlinkage, modinfop));
111}
112
113/*
114 * Read the current time from the clock chip and convert to UNIX form.
115 * Assumes that the year in the clock chip is valid.
116 * Must be called with tod_lock held.
117 */
118static timestruc_t
119todm_get(void)
120{
121	timestruc_t ts;
122	todinfo_t tod;
123	int s;
124
125	ASSERT(MUTEX_HELD(&tod_lock));
126
127	s = splhi();
128
129	CLOCK->clk_ctrl |= CLK_CTRL_READ;
130	tod.tod_year	= BCD_TO_BYTE(CLOCK->clk_year) + YRBASE;
131	tod.tod_month	= BCD_TO_BYTE(CLOCK->clk_month & 0x1f);
132	tod.tod_day	= BCD_TO_BYTE(CLOCK->clk_day & 0x3f);
133	tod.tod_dow	= BCD_TO_BYTE(CLOCK->clk_weekday & 0x7);
134	tod.tod_hour	= BCD_TO_BYTE(CLOCK->clk_hour & 0x3f);
135	tod.tod_min	= BCD_TO_BYTE(CLOCK->clk_min & 0x7f);
136	tod.tod_sec	= BCD_TO_BYTE(CLOCK->clk_sec & 0x7f);
137	CLOCK->clk_ctrl &= ~CLK_CTRL_READ;
138
139	splx(s);
140
141	/*
142	 * Apparently the m48t59 doesn't quite do what the spec sheet says.
143	 * The spec says reading WRD will reset the timer but that doesn't work.
144	 * So we need to reload timeout each time we want to reset the timer.
145	 */
146	CLOCK->clk_watchdog = watchdog_bits;
147
148	ts.tv_sec = tod_to_utc(tod);
149	ts.tv_nsec = 0;
150	return (ts);
151}
152
153/*
154 * Write the specified time into the clock chip.
155 * Must be called with tod_lock held.
156 */
157/* ARGSUSED */
158static void
159todm_set(timestruc_t ts)
160{
161	todinfo_t tod = utc_to_tod(ts.tv_sec);
162
163	ASSERT(MUTEX_HELD(&tod_lock));
164
165	CLOCK->clk_ctrl |= CLK_CTRL_WRITE;	/* allow writes */
166	CLOCK->clk_year		= BYTE_TO_BCD(tod.tod_year - YRBASE);
167	CLOCK->clk_month	= BYTE_TO_BCD(tod.tod_month);
168	CLOCK->clk_day		= BYTE_TO_BCD(tod.tod_day);
169	CLOCK->clk_weekday	= BYTE_TO_BCD(tod.tod_dow);
170	CLOCK->clk_hour		= BYTE_TO_BCD(tod.tod_hour);
171	CLOCK->clk_min		= BYTE_TO_BCD(tod.tod_min);
172	CLOCK->clk_sec		= BYTE_TO_BCD(tod.tod_sec);
173	CLOCK->clk_ctrl &= ~CLK_CTRL_WRITE;	/* load values */
174}
175
176
177/*
178 * Program the watchdog timer shadow register with the specified value.
179 * Setting the timer to zero value means no watchdog timeout.
180 */
181static uint_t
182todm_set_watchdog_timer(uint_t timeoutval)
183{
184	ASSERT(MUTEX_HELD(&tod_lock));
185
186	if (watchdog_enable == 0 || watchdog_available == 0 ||
187	    (boothowto & RB_DEBUG))
188		return (0);
189
190	watchdog_timeout = timeoutval;
191	watchdog_bits = CLK_WATCHDOG_BITS(timeoutval);
192	watchdog_activated = 1;
193
194	return (timeoutval);
195}
196
197/*
198 * Clear the hardware timer register. Also zero out the watchdog timer
199 * shadow register.
200 */
201static uint_t
202todm_clear_watchdog_timer(void)
203{
204	ASSERT(MUTEX_HELD(&tod_lock));
205
206	if (watchdog_activated == 0)
207		return (0);
208
209	CLOCK->clk_watchdog = 0;
210
211	watchdog_bits = 0;
212	watchdog_activated = 0;
213	return (watchdog_timeout);
214}
215
216/*
217 * program the tod registers for alarm to go off at the specified time
218 */
219static void
220todm_set_power_alarm(timestruc_t ts)
221{
222	todinfo_t	tod;
223	uchar_t	c;
224
225	ASSERT(MUTEX_HELD(&tod_lock));
226	tod = utc_to_tod(ts.tv_sec);
227
228	c = CLOCK->clk_flags; /* clear alarm intr flag by reading the reg */
229#ifdef lint
230	CLOCK->clk_flags = c;
231#endif
232	CLOCK->clk_interrupts &= ~CLK_ALARM_ENABLE; /* disable alarm intr */
233
234	CLOCK->clk_day &= ~CLK_FREQT; /* keep Freqency Test bit cleared */
235
236	CLOCK->clk_alm_day = BYTE_TO_BCD(tod.tod_day);
237	CLOCK->clk_alm_hours = BYTE_TO_BCD(tod.tod_hour);
238	CLOCK->clk_alm_mins = BYTE_TO_BCD(tod.tod_min);
239	CLOCK->clk_alm_secs = BYTE_TO_BCD(tod.tod_sec);
240
241	CLOCK->clk_interrupts |= CLK_ALARM_ENABLE; /* enable alarm intr */
242}
243
244/*
245 * clear alarm interrupt
246 */
247static void
248todm_clear_power_alarm()
249{
250	uchar_t	c;
251
252	ASSERT(MUTEX_HELD(&tod_lock));
253
254	c = CLOCK->clk_flags; /* clear alarm intr flag by reading the reg */
255
256#ifdef lint
257	CLOCK->clk_flags = c;
258#endif
259
260	CLOCK->clk_interrupts &= ~CLK_ALARM_ENABLE; /* disable alarm intr */
261}
262
263/*
264 * Determine the cpu frequency by watching the TOD chip rollover twice.
265 * Cpu clock rate is determined by computing the ticks added (in tick register)
266 * during one second interval on TOD.
267 */
268uint64_t
269todm_get_cpufrequency(void)
270{
271	ASSERT(MUTEX_HELD(&tod_lock));
272
273	return (find_cpufrequency(&(TIMECHECK_CLOCK->clk_sec)));
274}
275