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
46 int 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 */
53 typedef struct pmc {
54 dev_info_t *dip;
55 ddi_acc_handle_t pmc_handle;
56 } pmc_t;
57
58 static void *pmc_soft_state;
59 static int instance = -1;
60
61 /* dev_ops and cb_ops entry point function declarations */
62 static int pmc_attach(dev_info_t *, ddi_attach_cmd_t);
63 static int pmc_detach(dev_info_t *, ddi_detach_cmd_t);
64 static int pmc_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
65
66 /* hardware watchdog parameters */
67 static uint_t pmc_set_watchdog_timer(uint_t);
68 static uint_t pmc_clear_watchdog_timer(void);
69
70 extern volatile uint8_t *v_pmc_addr_reg;
71 extern volatile uint8_t *v_pmc_data_reg;
72 extern int watchdog_enable;
73 extern int watchdog_available;
74 extern int watchdog_activated;
75 extern int boothowto;
76 extern 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
84 struct 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
105 static 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 */
120 extern struct mod_ops mod_driverops;
121 static struct modldrv modldrv = {
122 &mod_driverops,
123 "pmc driver",
124 &pmc_dev_ops
125 };
126 static struct modlinkage modlinkage = {
127 MODREV_1,
128 &modldrv,
129 0
130 };
131
132
133 int
_init(void)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
173 int
_fini(void)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
191 int
_info(struct modinfo * modinfop)192 _info(struct modinfo *modinfop)
193 {
194 return (mod_info(&modlinkage, modinfop));
195 }
196
197 static int
pmc_getinfo(dev_info_t * dip,ddi_info_cmd_t cmd,void * arg,void ** result)198 pmc_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
226 static int
pmc_attach(dev_info_t * dip,ddi_attach_cmd_t cmd)227 pmc_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
268 static int
pmc_detach(dev_info_t * dip,ddi_detach_cmd_t cmd)269 pmc_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 */
303 static uint_t
pmc_set_watchdog_timer(uint_t timeoutval)304 pmc_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 */
334 static uint_t
pmc_clear_watchdog_timer(void)335 pmc_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