1 /* $NetBSD: j6x0pwr.c,v 1.13 2006/10/27 00:08:32 uwe Exp $ */ 2 3 /* 4 * Copyright (c) 2003, 2006 Valeriy E. Ushakov 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. The name of the author may not be used to endorse or promote products 16 * derived from this software without specific prior written permission 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 #include <sys/cdefs.h> 31 __KERNEL_RCSID(0, "$NetBSD: j6x0pwr.c,v 1.13 2006/10/27 00:08:32 uwe Exp $"); 32 33 #include <sys/param.h> 34 #include <sys/kernel.h> 35 #include <sys/device.h> 36 #include <sys/malloc.h> 37 #include <sys/systm.h> 38 #include <sys/callout.h> 39 40 #include <dev/apm/apmbios.h> 41 42 #include <machine/platid.h> 43 #include <machine/platid_mask.h> 44 #include <machine/config_hook.h> 45 46 #include <sh3/exception.h> 47 #include <sh3/intcreg.h> 48 #include <sh3/pfcreg.h> 49 50 #include <sh3/dev/adcvar.h> 51 52 53 /* SH7709 PFC bits pertinent to Jornada 6x0 power */ 54 #define PGDR_MAIN_BATTERY_OUT 0x04 55 #define PGDR_LID_OPEN 0x01 56 #define PJDR_AC_POWER_OUT 0x10 57 #define PLDR_BATTERY_CHARGED 0x20 58 59 60 static inline int __attribute__((__always_inline__)) 61 j6x0pwr_battery_is_absent(void) 62 { 63 64 return _reg_read_1(SH7709_PGDR) & PGDR_MAIN_BATTERY_OUT; 65 } 66 67 static inline int __attribute__((__always_inline__)) 68 j6x0pwr_ac_is_off(void) 69 { 70 71 return _reg_read_1(SH7709_PJDR) & PJDR_AC_POWER_OUT; 72 } 73 74 75 static inline int __attribute__((__always_inline__)) 76 j6x0pwr_is_not_charging(void) 77 { 78 79 return _reg_read_1(SH7709_PLDR) & PLDR_BATTERY_CHARGED; 80 } 81 82 83 /* A/D converter channels to get power stats from */ 84 #define ADC_CHANNEL_BATTERY 3 /* main battery */ 85 #define ADC_CHANNEL_BACKUP 4 /* backup battery - we don't report it */ 86 #define ADC_CHANNEL_CHARGE 5 /* we use PLDR_BATTERY_CHARGED instead */ 87 88 /* 89 * Empirical range of battery values. 90 * Thanks to Joseph Heenan for measurements. 91 */ 92 #define J6X0PWR_BATTERY_MIN 630 93 #define J6X0PWR_BATTERY_CRITICAL 635 94 #define J6X0PWR_BATTERY_LOW 645 95 #define J6X0PWR_BATTERY_FULL 910 /* can be slightly more */ 96 97 98 /* Convenience aliases (XXX: should be provided by config_hook.h) */ 99 #define CONFIG_HOOK_BUTTON_PRESSED ((void *)1) 100 #define CONFIG_HOOK_BUTTON_RELEASED ((void *)0) 101 102 103 104 /* allow only one instance */ 105 static int j6x0pwr_attached = 0; 106 107 struct j6x0pwr_softc { 108 struct device sc_dev; 109 110 void *sc_ih; 111 volatile int sc_poweroff; 112 }; 113 114 static int j6x0pwr_match(struct device *, struct cfdata *, void *); 115 static void j6x0pwr_attach(struct device *, struct device *, void *); 116 117 CFATTACH_DECL(j6x0pwr, sizeof(struct j6x0pwr_softc), 118 j6x0pwr_match, j6x0pwr_attach, NULL, NULL); 119 120 121 static int j6x0pwr_apm_getpower_hook(void *, int, long, void *); 122 static int j6x0pwr_get_battery(void); /* from ADC */ 123 124 static int j6x0pwr_intr(void *); 125 static void j6x0pwr_sleep(void *); 126 static int j6x0pwr_clear_interrupt(void); 127 128 129 static int 130 j6x0pwr_match(struct device *parent, struct cfdata *cfp, void *aux) 131 { 132 133 /* 134 * XXX: does platid_mask_MACH_HP_LX matches _JORNADA_6XX too? 135 * Is 620 wired similarly? 136 */ 137 if (!platid_match(&platid, &platid_mask_MACH_HP_JORNADA_6XX)) 138 return 0; 139 140 if (strcmp(cfp->cf_name, "j6x0pwr") != 0) 141 return 0; 142 143 /* allow only one instance */ 144 return !j6x0pwr_attached; 145 } 146 147 148 static void 149 j6x0pwr_attach(struct device *parent, struct device *self, void *aux) 150 { 151 struct j6x0pwr_softc *sc = (void *)self; 152 153 /* XXX: in machdep.c */ 154 extern void (*__sleep_func)(void *); 155 extern void *__sleep_ctx; 156 157 /* allow only one instance */ 158 j6x0pwr_attached = 1; 159 160 /* arrange for hpcapm to call us when power status is requested */ 161 config_hook(CONFIG_HOOK_GET, CONFIG_HOOK_ACADAPTER, 162 CONFIG_HOOK_EXCLUSIVE, 163 j6x0pwr_apm_getpower_hook, sc); 164 config_hook(CONFIG_HOOK_GET, CONFIG_HOOK_CHARGE, 165 CONFIG_HOOK_EXCLUSIVE, 166 j6x0pwr_apm_getpower_hook, sc); 167 config_hook(CONFIG_HOOK_GET, CONFIG_HOOK_BATTERYVAL, 168 CONFIG_HOOK_EXCLUSIVE, 169 j6x0pwr_apm_getpower_hook, sc); 170 171 /* register sleep function to APM */ 172 __sleep_func = j6x0pwr_sleep; 173 __sleep_ctx = self; 174 175 sc->sc_poweroff = 0; 176 177 /* drain the old interrupt */ 178 j6x0pwr_clear_interrupt(); 179 180 sc->sc_ih = intc_intr_establish(SH7709_INTEVT2_IRQ0, IST_EDGE, IPL_TTY, 181 j6x0pwr_intr, sc); 182 183 _reg_write_1(SH7709_PKDR, 0); /* Green LED on */ 184 printf("\n"); 185 } 186 187 188 /* 189 * Triggered when the On/Off button is pressed or the lid is closed. 190 * The state of the lid is reflected in the bit PGDR[0]. 191 * Closing the lid can trigger several consecutive interrupts. 192 * 193 * XXX: Since we don't put the machine to sleep, I have no idea how 194 * wakeup interrupt(s) look like. Need to revisit when software 195 * suspend is added to the kernel (which we need to support ACPI). 196 */ 197 static int 198 j6x0pwr_intr(void *self) 199 { 200 struct j6x0pwr_softc *sc = (struct j6x0pwr_softc *)self; 201 uint8_t irr0; 202 uint8_t pgdr; 203 204 irr0 = j6x0pwr_clear_interrupt(); 205 if ((irr0 & IRR0_IRQ0) == 0) { 206 #ifdef DIAGNOSTIC 207 printf_nolog("%s: irr0=0x%02x?\n", sc->sc_dev.dv_xname, irr0); 208 #endif 209 return 0; 210 } 211 212 pgdr = _reg_read_1(SH7709_PGDR); 213 if ((pgdr & PGDR_LID_OPEN) == 0) { 214 printf("%s: lid closed %d\n", sc->sc_dev.dv_xname, 215 sc->sc_poweroff); 216 if (sc->sc_poweroff) 217 return 1; 218 } else { 219 printf("%s: ON/OFF %d\n", sc->sc_dev.dv_xname, sc->sc_poweroff); 220 if (sc->sc_poweroff) 221 sc->sc_poweroff = 0; 222 } 223 224 config_hook_call(CONFIG_HOOK_BUTTONEVENT, 225 CONFIG_HOOK_BUTTONEVENT_POWER, 226 CONFIG_HOOK_BUTTON_PRESSED); 227 /* fake release */ 228 config_hook_call(CONFIG_HOOK_BUTTONEVENT, 229 CONFIG_HOOK_BUTTONEVENT_POWER, 230 CONFIG_HOOK_BUTTON_RELEASED); 231 232 return 1; 233 } 234 235 236 static int 237 j6x0pwr_clear_interrupt(void) 238 { 239 uint8_t irr0; 240 241 irr0 = _reg_read_1(SH7709_IRR0); 242 if (irr0 & IRR0_IRQ0) 243 _reg_write_1(SH7709_IRR0, irr0 & ~IRR0_IRQ0); 244 245 return irr0; 246 } 247 248 249 void 250 j6x0pwr_sleep(void *self) 251 { 252 /* splhigh on entry */ 253 struct j6x0pwr_softc *sc = self; 254 int s; 255 256 /* Reinstall j6x0pwr_intr as a wakeup handler */ 257 intc_intr_disestablish(sc->sc_ih); 258 sc->sc_ih = intc_intr_establish(SH7709_INTEVT2_IRQ0, IST_EDGE, IPL_HIGH, 259 j6x0pwr_intr, sc); 260 sc->sc_poweroff = 1; 261 do { 262 /* Disable interrupt except for power button. */ 263 s = _cpu_intr_resume(IPL_CLOCK << 4); 264 _reg_write_1(SH7709_PKDR, 0xff); /* Green LED off */ 265 266 __asm volatile("sleep"); 267 268 _reg_write_1(SH7709_PKDR, 0); /* Green LED on */ 269 _cpu_intr_resume(s); 270 } while (sc->sc_poweroff); 271 272 /* Return to normal power button */ 273 intc_intr_disestablish(sc->sc_ih); 274 sc->sc_ih = intc_intr_establish(SH7709_INTEVT2_IRQ0, IST_EDGE, IPL_TTY, 275 j6x0pwr_intr, sc); 276 /* splhigh on exit */ 277 } 278 279 280 static int 281 j6x0pwr_apm_getpower_hook(void *ctx, int type, long id, void *msg) 282 { 283 /* struct j6x0pwr_softc * const sc = ctx; */ 284 int * const pval = msg; 285 int battery, state; 286 287 if (type != CONFIG_HOOK_GET) 288 return EINVAL; 289 290 switch (id) { 291 292 case CONFIG_HOOK_ACADAPTER: 293 *pval = j6x0pwr_ac_is_off() ? APM_AC_OFF : APM_AC_ON; 294 return 0; 295 296 case CONFIG_HOOK_CHARGE: 297 if (j6x0pwr_battery_is_absent()) 298 state = APM_BATT_FLAG_NO_SYSTEM_BATTERY; 299 else { 300 battery = j6x0pwr_get_battery(); 301 if (battery < J6X0PWR_BATTERY_CRITICAL) 302 state = APM_BATT_FLAG_CRITICAL; 303 else if (battery < J6X0PWR_BATTERY_LOW) 304 state = APM_BATT_FLAG_LOW; 305 else 306 state = APM_BATT_FLAG_HIGH; /* XXX? */ 307 308 if (!j6x0pwr_is_not_charging()) 309 state |= APM_BATT_FLAG_CHARGING; 310 } 311 *pval = state; 312 return 0; 313 314 case CONFIG_HOOK_BATTERYVAL: 315 if (j6x0pwr_battery_is_absent()) 316 state = 0; 317 else { 318 battery = j6x0pwr_get_battery(); 319 if (battery > J6X0PWR_BATTERY_FULL) 320 state = 100; 321 else 322 state = 100 * (battery - J6X0PWR_BATTERY_MIN) 323 / (J6X0PWR_BATTERY_FULL 324 - J6X0PWR_BATTERY_MIN); 325 } 326 *pval = state; 327 return 0; 328 } 329 330 return EINVAL; 331 } 332 333 334 static int 335 j6x0pwr_get_battery(void) 336 { 337 int battery; 338 int s; 339 340 if (j6x0pwr_battery_is_absent()) 341 return -1; 342 343 s = spltty(); 344 battery = adc_sample_channel(ADC_CHANNEL_BATTERY); 345 splx(s); 346 347 return battery; 348 } 349