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