1 /* $NetBSD: j6x0pwr.c,v 1.7 2005/12/11 12:17:36 christos Exp $ */ 2 3 /* 4 * Copyright (c) 2003 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.7 2005/12/11 12:17:36 christos 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 #ifdef GPROF 40 #include <sys/gmon.h> 41 #endif 42 43 #include <machine/platid.h> 44 #include <machine/platid_mask.h> 45 #include <machine/config_hook.h> 46 47 #include <sh3/exception.h> 48 #include <sh3/intcreg.h> 49 #include <sh3/pfcreg.h> 50 51 #include <sh3/dev/adcvar.h> 52 53 54 /* SH7709_PGDR bits pertinent to Jornada 6x0 power */ 55 #define PGDR_MAIN_BATTERY_OUT 0x04 56 #define PGDR_LID_OPEN 0x01 57 58 /* A/D covnerter channels to get power stats from */ 59 #define ADC_CHANNEL_BATTERY 3 60 #define ADC_CHANNEL_BACKUP 4 61 #define ADC_CHANNEL_CHARGE 5 62 63 /* warn that main battery is low after drops below this value */ 64 #define J6X0PWR_BATTERY_WARNING_THRESHOLD 200 65 66 67 struct j6x0pwr_softc { 68 struct device sc_dev; 69 70 struct callout sc_poll_ch; 71 void *sc_ih; 72 volatile int sc_poweroff; 73 }; 74 75 static int j6x0pwr_match(struct device *, struct cfdata *, void *); 76 static void j6x0pwr_attach(struct device *, struct device *, void *); 77 78 CFATTACH_DECL(j6x0pwr, sizeof(struct j6x0pwr_softc), 79 j6x0pwr_match, j6x0pwr_attach, NULL, NULL); 80 81 82 static int j6x0pwr_intr(void *); 83 static void j6x0pwr_poll_callout(void *); 84 static void j6x0pwr_sleep(void *); 85 static int j6x0pwr_clear_interrupt(void); 86 87 static int 88 j6x0pwr_match(struct device *parent, struct cfdata *cfp, void *aux) 89 { 90 91 /* 92 * XXX: does platid_mask_MACH_HP_LX matches _JORNADA_6XX too? 93 * Is 620 wired similarly? 94 */ 95 if (!platid_match(&platid, &platid_mask_MACH_HP_JORNADA_6XX)) 96 return (0); 97 98 if (strcmp(cfp->cf_name, "j6x0pwr") != 0) 99 return (0); 100 101 return (1); 102 } 103 104 105 static void 106 j6x0pwr_attach(struct device *parent, struct device *self, void *aux) 107 { 108 extern void (*__sleep_func)(void *); 109 extern void *__sleep_ctx; 110 struct j6x0pwr_softc *sc = (struct j6x0pwr_softc *)self; 111 112 /* regsiter sleep function to APM */ 113 __sleep_func = j6x0pwr_sleep; 114 __sleep_ctx = self; 115 sc->sc_poweroff = 0; 116 117 /* drain the old interrupt */ 118 j6x0pwr_clear_interrupt(); 119 120 sc->sc_ih = intc_intr_establish(SH7709_INTEVT2_IRQ0, IST_EDGE, IPL_TTY, 121 j6x0pwr_intr, sc); 122 123 callout_init(&sc->sc_poll_ch); 124 callout_reset(&sc->sc_poll_ch, 5 * hz, 125 j6x0pwr_poll_callout, sc); 126 127 _reg_write_1(SH7709_PKDR, 0); /* Green LED on */ 128 printf("\n"); 129 } 130 131 132 /* 133 * Triggered when the On/Off button is pressed or the lid is closed. 134 * The state of the lid is reflected in the bit PGDR[0]. 135 * Closing the lid can trigger several consecutive interrupts. 136 * 137 * XXX: Since we don't put the machine to sleep, I have no idea how 138 * wakeup interrupt(s) look like. Need to revisit when software 139 * suspend is added to the kernel (which we need to support ACPI). 140 */ 141 static int 142 j6x0pwr_intr(void *self) 143 { 144 struct j6x0pwr_softc *sc = (struct j6x0pwr_softc *)self; 145 uint8_t irr0; 146 uint8_t pgdr; 147 148 if (((irr0 = j6x0pwr_clear_interrupt()) & IRR0_IRQ0) == 0) { 149 #ifdef DIAGNOSTIC 150 printf_nolog("%s: irr0=0x%02x?\n", sc->sc_dev.dv_xname, irr0); 151 #endif 152 return (0); 153 } 154 155 pgdr = _reg_read_1(SH7709_PGDR); 156 if ((pgdr & PGDR_LID_OPEN) == 0) { 157 printf("%s: lid closed %d\n", sc->sc_dev.dv_xname, 158 sc->sc_poweroff); 159 if (sc->sc_poweroff) 160 return 1; 161 } else { 162 printf("%s: ON/OFF %d\n", sc->sc_dev.dv_xname, sc->sc_poweroff); 163 if (sc->sc_poweroff) 164 sc->sc_poweroff = 0; 165 } 166 167 /* push */ 168 config_hook_call(CONFIG_HOOK_BUTTONEVENT, CONFIG_HOOK_BUTTONEVENT_POWER, 169 (void *)1); 170 /* release (fake) */ 171 config_hook_call(CONFIG_HOOK_BUTTONEVENT, CONFIG_HOOK_BUTTONEVENT_POWER, 172 (void *)0); 173 174 return (1); 175 } 176 177 static int 178 j6x0pwr_clear_interrupt() 179 { 180 uint8_t irr0; 181 182 irr0 = _reg_read_1(SH7709_IRR0); 183 if (irr0 & IRR0_IRQ0) 184 _reg_write_1(SH7709_IRR0, irr0 & ~IRR0_IRQ0); 185 186 return irr0; 187 } 188 189 void 190 j6x0pwr_sleep(void *self) 191 { 192 /* splhigh on entry */ 193 struct j6x0pwr_softc *sc = self; 194 int s; 195 196 /* Reinstall j6x0pwr_intr as a wakeup handler */ 197 intc_intr_disestablish(sc->sc_ih); 198 sc->sc_ih = intc_intr_establish(SH7709_INTEVT2_IRQ0, IST_EDGE, IPL_HIGH, 199 j6x0pwr_intr, sc); 200 sc->sc_poweroff = 1; 201 do { 202 /* Disable interrupt except for power button. */ 203 s = _cpu_intr_resume(IPL_CLOCK << 4); 204 _reg_write_1(SH7709_PKDR, 0xff); /* Green LED off */ 205 __asm__ __volatile__("sleep"); 206 _reg_write_1(SH7709_PKDR, 0); /* Green LED on */ 207 _cpu_intr_resume(s); 208 } while (sc->sc_poweroff); 209 210 /* Return to normal power button */ 211 intc_intr_disestablish(sc->sc_ih); 212 sc->sc_ih = intc_intr_establish(SH7709_INTEVT2_IRQ0, IST_EDGE, IPL_TTY, 213 j6x0pwr_intr, sc); 214 /* splhigh on exit */ 215 } 216 217 volatile int j6x0pwr_poll_verbose = 0; /* XXX: tweak from ddb */ 218 219 static void 220 j6x0pwr_poll_callout(void *self) 221 { 222 struct j6x0pwr_softc *sc = (struct j6x0pwr_softc *)self; 223 int battery, backup, charging; 224 uint8_t pgdr; 225 int s; 226 227 pgdr = _reg_read_1(SH7709_PGDR); 228 229 /* just check main battery charge if not verbose and battery is in */ 230 if (!j6x0pwr_poll_verbose) { 231 if (pgdr & PGDR_MAIN_BATTERY_OUT) 232 goto reschedule; 233 else { 234 s = spltty(); 235 battery = adc_sample_channel(ADC_CHANNEL_BATTERY); 236 splx(s); 237 goto check_battery; 238 } 239 } 240 241 s = spltty(); 242 battery = adc_sample_channel(ADC_CHANNEL_BATTERY); 243 splx(s); 244 245 s = spltty(); 246 backup = adc_sample_channel(ADC_CHANNEL_BACKUP); 247 splx(s); 248 249 s = spltty(); 250 charging = adc_sample_channel(ADC_CHANNEL_CHARGE); 251 splx(s); 252 253 printf_nolog("%s: main=", sc->sc_dev.dv_xname); 254 if (pgdr & PGDR_MAIN_BATTERY_OUT) 255 printf_nolog("<no>"); 256 else 257 printf_nolog("%-4d", battery); 258 259 printf_nolog(" bkup=%-4d", backup); 260 261 /* 262 * When main battery is being charged, ADC_CHANNEL_CHARGE 263 * samples at almost zero. It samples at 2^10 otherwise. 264 */ 265 if (charging < 8) 266 printf_nolog("charging"); 267 268 printf_nolog("\n"); 269 270 check_battery: 271 if (!(pgdr & PGDR_MAIN_BATTERY_OUT) 272 && (battery < J6X0PWR_BATTERY_WARNING_THRESHOLD)) 273 printf("%s: WARNING: main battery %d is low!\n", 274 sc->sc_dev.dv_xname, battery); 275 276 reschedule: 277 callout_schedule(&sc->sc_poll_ch, 5*hz); 278 } 279