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 * Driver for the Power Management Controller (logical unit 8) of the
28 * PC87317 SuperI/O chip. The PMC contains the hardware watchdog timer.
29 */
30
31#include <sys/types.h>
32#include <sys/time.h>
33#include <sys/cmn_err.h>
34#include <sys/param.h>
35#include <sys/modctl.h>
36#include <sys/conf.h>
37#include <sys/stat.h>
38#include <sys/clock.h>
39#include <sys/reboot.h>
40#include <sys/ddi.h>
41#include <sys/sunddi.h>
42#include <sys/file.h>
43#include <sys/note.h>
44
45#ifdef	DEBUG
46int pmc_debug_flag = 0;
47#define	DPRINTF(ARGLIST) if (pmc_debug_flag) printf ARGLIST;
48#else
49#define	DPRINTF(ARGLIST)
50#endif /* DEBUG */
51
52/* Driver soft state structure */
53typedef struct pmc {
54	dev_info_t		*dip;
55	ddi_acc_handle_t	pmc_handle;
56} pmc_t;
57
58static void *pmc_soft_state;
59static int instance = -1;
60
61/* dev_ops and cb_ops entry point function declarations */
62static int pmc_attach(dev_info_t *, ddi_attach_cmd_t);
63static int pmc_detach(dev_info_t *, ddi_detach_cmd_t);
64static int pmc_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
65
66/* hardware watchdog parameters */
67static uint_t pmc_set_watchdog_timer(uint_t);
68static uint_t pmc_clear_watchdog_timer(void);
69
70extern volatile uint8_t	*v_pmc_addr_reg;
71extern volatile uint8_t	*v_pmc_data_reg;
72extern int		watchdog_enable;
73extern int		watchdog_available;
74extern int		watchdog_activated;
75extern int		boothowto;
76extern uint_t		watchdog_timeout_seconds;
77
78/*
79 * Power Management Registers and values
80 */
81#define	PMC_WDTO	0x05	/* Watchdog Time Out */
82#define	PMC_CLEAR_WDTO	0x00
83
84struct cb_ops pmc_cb_ops = {
85	nodev,
86	nodev,
87	nodev,
88	nodev,
89	nodev,			/* dump */
90	nodev,
91	nodev,
92	nodev,
93	nodev,			/* devmap */
94	nodev,
95	nodev,
96	nochpoll,
97	ddi_prop_op,
98	NULL,			/* for STREAMS drivers */
99	D_NEW | D_MP,		/* driver compatibility flag */
100	CB_REV,
101	nodev,
102	nodev
103};
104
105static struct dev_ops pmc_dev_ops = {
106	DEVO_REV,			/* driver build version */
107	0,				/* device reference count */
108	pmc_getinfo,
109	nulldev,
110	nulldev,			/* probe */
111	pmc_attach,
112	pmc_detach,
113	nulldev,			/* reset */
114	&pmc_cb_ops,
115	(struct bus_ops *)NULL,
116	nulldev				/* power */
117};
118
119/* module configuration stuff */
120extern struct mod_ops mod_driverops;
121static struct modldrv modldrv = {
122	&mod_driverops,
123	"pmc driver",
124	&pmc_dev_ops
125};
126static struct modlinkage modlinkage = {
127	MODREV_1,
128	&modldrv,
129	0
130};
131
132
133int
134_init(void)
135{
136	int e;
137
138	e = ddi_soft_state_init(&pmc_soft_state, sizeof (pmc_t), 1);
139	if (e != 0) {
140		DPRINTF(("_init: ddi_soft_state_init failed\n"));
141		return (e);
142	}
143
144	e = mod_install(&modlinkage);
145	if (e != 0) {
146		DPRINTF(("_init: mod_install failed\n"));
147		ddi_soft_state_fini(&pmc_soft_state);
148		return (e);
149	}
150
151	if (v_pmc_addr_reg != NULL) {
152		tod_ops.tod_set_watchdog_timer = pmc_set_watchdog_timer;
153		tod_ops.tod_clear_watchdog_timer = pmc_clear_watchdog_timer;
154
155		/*
156		 * See if the user has enabled the watchdog timer, and if
157		 * it's available.
158		 */
159		if (watchdog_enable) {
160			if (!watchdog_available) {
161				cmn_err(CE_WARN, "pmc: Hardware watchdog "
162					"unavailable");
163			} else if (boothowto & RB_DEBUG) {
164				watchdog_available = 0;
165				cmn_err(CE_WARN, "pmc: kernel debugger "
166					"detected: hardware watchdog disabled");
167			}
168		}
169	}
170	return (e);
171}
172
173int
174_fini(void)
175{
176	int e;
177
178	if (v_pmc_addr_reg != NULL)
179		return (DDI_FAILURE);
180	else {
181		e = mod_remove(&modlinkage);
182		if (e != 0)
183			return (e);
184
185		ddi_soft_state_fini(&pmc_soft_state);
186		return (DDI_SUCCESS);
187	}
188}
189
190
191int
192_info(struct modinfo *modinfop)
193{
194	return (mod_info(&modlinkage, modinfop));
195}
196
197static int
198pmc_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result)
199{
200	_NOTE(ARGUNUSED(dip))
201
202	pmc_t	*pmcp;
203	int	instance;
204
205	switch (cmd) {
206	case DDI_INFO_DEVT2DEVINFO:
207		instance = getminor((dev_t)arg);
208		pmcp = (pmc_t *)ddi_get_soft_state(pmc_soft_state, instance);
209		if (pmcp == NULL) {
210			*result = (void *)NULL;
211			return (DDI_FAILURE);
212		}
213		*result = (void *)pmcp->dip;
214		return (DDI_SUCCESS);
215
216	case DDI_INFO_DEVT2INSTANCE:
217		*result = (void *)(uintptr_t)getminor((dev_t)arg);
218		return (DDI_SUCCESS);
219
220	default:
221		return (DDI_FAILURE);
222	}
223}
224
225
226static int
227pmc_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
228{
229	pmc_t	*pmcp;
230	uint_t	wd_timout;
231
232	switch (cmd) {
233	case DDI_ATTACH:
234		break;
235	case DDI_RESUME:
236		if (v_pmc_addr_reg != NULL && watchdog_enable) {
237			int ret = 0;
238			wd_timout = watchdog_timeout_seconds;
239			mutex_enter(&tod_lock);
240			ret = tod_ops.tod_set_watchdog_timer(wd_timout);
241			mutex_exit(&tod_lock);
242			if (ret == 0)
243				return (DDI_FAILURE);
244		}
245		return (DDI_SUCCESS);
246	default:
247		return (DDI_FAILURE);
248	}
249
250	if (instance != -1) {
251		DPRINTF(("pmc_attach: Another instance is already attached."));
252		return (DDI_FAILURE);
253	}
254
255	instance = ddi_get_instance(dip);
256
257	if (ddi_soft_state_zalloc(pmc_soft_state, instance) != DDI_SUCCESS) {
258		DPRINTF(("pmc_attach: Failed to allocate soft state."));
259		return (DDI_FAILURE);
260	}
261
262	pmcp = (pmc_t *)ddi_get_soft_state(pmc_soft_state, instance);
263	pmcp->dip = dip;
264
265	return (DDI_SUCCESS);
266}
267
268static int
269pmc_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
270{
271	_NOTE(ARGUNUSED(dip))
272
273	pmc_t	*pmcp;
274
275	switch (cmd) {
276	case DDI_DETACH:
277		/* allow detach if no hardware watchdog */
278		if (v_pmc_addr_reg == NULL || !watchdog_activated) {
279			pmcp = (pmc_t *)ddi_get_soft_state(pmc_soft_state,
280				instance);
281			if (pmcp == NULL)
282				return (ENXIO);
283			ddi_soft_state_free(pmc_soft_state, instance);
284			return (DDI_SUCCESS);
285		} else
286			return (DDI_FAILURE);
287	case DDI_SUSPEND:
288		if (v_pmc_addr_reg != NULL && watchdog_activated) {
289			mutex_enter(&tod_lock);
290			(void) tod_ops.tod_clear_watchdog_timer();
291			mutex_exit(&tod_lock);
292		}
293		return (DDI_SUCCESS);
294	default:
295		return (DDI_FAILURE);
296	}
297
298}
299
300/*
301 * Set the hardware watchdog timer; returning what we set it to.
302 */
303static uint_t
304pmc_set_watchdog_timer(uint_t timeoutval)
305{
306	uint_t timeoutval_minutes;
307	ASSERT(MUTEX_HELD(&tod_lock));
308
309	/* sanity checks */
310	if (watchdog_enable == 0 || watchdog_available == 0 ||
311	    timeoutval == 0)
312		return (0);
313
314	/*
315	 * Historically the timer has been counted out in seconds.
316	 * The PC87317 counts the timeout in minutes. The default
317	 * timeout is 10 seconds; the least we can do is one minute.
318	 */
319	timeoutval_minutes = (timeoutval + 59) / 60;
320	if (timeoutval_minutes > UINT8_MAX)
321		return (0);
322
323	*v_pmc_addr_reg = (uint8_t)PMC_WDTO;
324	*v_pmc_data_reg = (uint8_t)timeoutval_minutes;
325	watchdog_activated = 1;
326
327	/* we'll still return seconds */
328	return (timeoutval_minutes * 60);
329}
330
331/*
332 * Clear the hardware watchdog timer; returning what it was set to.
333 */
334static uint_t
335pmc_clear_watchdog_timer(void)
336{
337	uint_t	wd_timeout;
338
339	ASSERT(MUTEX_HELD(&tod_lock));
340	if (watchdog_activated == 0)
341		return (0);
342
343	*v_pmc_addr_reg = (uint8_t)PMC_WDTO;
344	wd_timeout = (uint_t)*v_pmc_data_reg;
345	*v_pmc_data_reg = (uint8_t)PMC_CLEAR_WDTO;
346	watchdog_activated = 0;
347
348	/* return seconds */
349	return (wd_timeout * 60);
350}
351