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 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 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 192 _info(struct modinfo *modinfop) 193 { 194 return (mod_info(&modlinkage, modinfop)); 195 } 196 197 static int 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 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 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 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 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