1/* $NetBSD$ */ 2 3/* 4 * Copyright (c) 2011 Tetsuya Isaki. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 20 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 22 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 */ 27 28/* 29 * Power switch monitor 30 */ 31 32#include <sys/cdefs.h> 33__KERNEL_RCSID(0, "$NetBSD$"); 34 35#include <sys/param.h> 36#include <sys/systm.h> 37#include <sys/conf.h> 38#include <sys/device.h> 39#include <sys/intr.h> 40#include <sys/callout.h> 41 42#include <machine/bus.h> 43#include <machine/cpu.h> 44 45#include <arch/x68k/dev/intiovar.h> 46#include <arch/x68k/dev/mfp.h> 47 48#include <dev/sysmon/sysmonvar.h> 49#include <dev/sysmon/sysmon_taskq.h> 50 51extern int power_switch_is_off; /* XXX should be in .h */ 52 53//#define POWSW_DEBUG 54 55#if defined(POWSW_DEBUG) 56#define DPRINTF(fmt...) printf(fmt) 57#define DEBUG_LOG_ADD(c) sc->sc_log[sc->sc_loglen++] = (c) 58#define DEBUG_LOG_PRINT() do { \ 59 sc->sc_log[sc->sc_loglen] = '\0'; \ 60 printf("%s", sc->sc_log); \ 61} while (0) 62#else 63#define DPRINTF(fmt...) 64#define DEBUG_LOG_ADD(c) 65#define DEBUG_LOG_PRINT() 66#endif 67 68/* mask */ 69#define POWSW_ALARM (0x01) 70#define POWSW_EXTERNAL (0x02) 71#define POWSW_FRONT (0x04) 72 73/* parameter */ 74#define POWSW_MAX_TICK (30) 75#define POWSW_THRESHOLD (10) 76 77struct powsw_softc { 78 device_t sc_dev; 79 struct sysmon_pswitch sc_smpsw; 80 callout_t sc_callout; 81 int sc_mask; 82 int sc_prev; 83 int sc_last_sw; 84 int sc_tick; 85 int sc_count; 86#if defined(POWSW_DEBUG) 87 char sc_log[100]; 88 int sc_loglen; 89#endif 90}; 91 92extern struct cfdriver powsw_cd; 93 94static int powsw_match(device_t, cfdata_t, void *); 95static void powsw_attach(device_t, device_t, void *); 96static int powsw_intr(void *); 97static void powsw_softintr(void *); 98static void powsw_pswitch_event(void *); 99static void powsw_shutdown_check(void *); 100static void powsw_reset_counter(struct powsw_softc *); 101static void powsw_set_aer(struct powsw_softc *, int); 102 103CFATTACH_DECL_NEW(powsw, sizeof(struct powsw_softc), 104 powsw_match, powsw_attach, NULL, NULL); 105 106 107typedef const struct { 108 int vector; /* interrupt vector */ 109 int mask; /* mask bit for MFP GPIP */ 110 const char *name; 111} powsw_desc_t; 112 113static powsw_desc_t powsw_desc[2] = { 114 { 66, POWSW_FRONT, "Front Switch", }, 115 { 65, POWSW_EXTERNAL, "External Power Switch", }, 116 /* XXX I'm not sure about alarm bit */ 117}; 118 119 120static int 121powsw_match(device_t parent, cfdata_t cf, void *aux) 122{ 123 return 1; 124} 125 126static void 127powsw_attach(device_t parent, device_t self, void *aux) 128{ 129 struct powsw_softc *sc = device_private(self); 130 powsw_desc_t *desc; 131 const char *xname; 132 int unit; 133 int sw; 134 135 unit = device_unit(self); 136 xname = device_xname(self); 137 desc = &powsw_desc[unit]; 138 139 memset(sc, 0, sizeof(*sc)); 140 sc->sc_dev = self; 141 sc->sc_mask = desc->mask; 142 sc->sc_prev = -1; 143 powsw_reset_counter(sc); 144 145 sysmon_task_queue_init(); 146 sc->sc_smpsw.smpsw_name = xname; 147 sc->sc_smpsw.smpsw_type = PSWITCH_TYPE_POWER; 148 if (sysmon_pswitch_register(&sc->sc_smpsw) != 0) 149 panic("can't register with sysmon"); 150 151 callout_init(&sc->sc_callout, 0); 152 callout_setfunc(&sc->sc_callout, powsw_softintr, sc); 153 154 if (shutdownhook_establish(powsw_shutdown_check, sc) == NULL) 155 panic("%s: can't establish shutdown hook", xname); 156 157 if (intio_intr_establish(desc->vector, xname, powsw_intr, sc) < 0) 158 panic("%s: can't establish interrupt", xname); 159 160 /* Set AER and enable interrupt */ 161 sw = (mfp_get_gpip() & sc->sc_mask); 162 powsw_set_aer(sc, sw ? 0 : 1); 163 mfp_bit_set_ierb(sc->sc_mask); 164 165 aprint_normal(": %s\n", desc->name); 166} 167 168static int 169powsw_intr(void *arg) 170{ 171 struct powsw_softc *sc = arg; 172 173 if (sc->sc_tick == 0) { 174 mfp_bit_clear_ierb(sc->sc_mask); 175 sc->sc_tick++; 176 DEBUG_LOG_ADD('i'); 177 /* 178 * The button state seems unstable for few ticks, 179 * so wait a bit to settle. 180 */ 181 callout_schedule(&sc->sc_callout, 1); 182 } else { 183 DEBUG_LOG_ADD('x'); 184 } 185 return 0; 186} 187 188void 189powsw_softintr(void *arg) 190{ 191 struct powsw_softc *sc = arg; 192 int sw; 193 int s; 194 195 s = spl6(); 196 197 if (sc->sc_tick++ >= POWSW_MAX_TICK) { 198 /* tick is over, broken switch? */ 199 printf("%s: unstable power switch?, ignored\n", 200 device_xname(sc->sc_dev)); 201 powsw_reset_counter(sc); 202 203 mfp_bit_set_ierb(sc->sc_mask); 204 splx(s); 205 return; 206 } 207 208 sw = (mfp_get_gpip() & sc->sc_mask) ? 1 : 0; 209 DEBUG_LOG_ADD('0' + sw); 210 211 if (sw == sc->sc_last_sw) { 212 sc->sc_count++; 213 } else { 214 sc->sc_last_sw = sw; 215 sc->sc_count = 1; 216 } 217 218 if (sc->sc_count < POWSW_THRESHOLD) { 219 callout_schedule(&sc->sc_callout, 1); 220 } else { 221 /* switch seems stable */ 222 DEBUG_LOG_PRINT(); 223 224 if (sc->sc_last_sw == sc->sc_prev) { 225 /* switch state is not changed, it was a noise */ 226 DPRINTF(" ignore(sw=%d,prev=%d)\n", sc->sc_last_sw, sc->sc_prev); 227 } else { 228 /* switch state has been changed */ 229 sc->sc_prev = sc->sc_last_sw; 230 powsw_set_aer(sc, 1 - sc->sc_prev); 231 sysmon_task_queue_sched(0, powsw_pswitch_event, sc); 232 } 233 powsw_reset_counter(sc); 234 mfp_bit_set_ierb(sc->sc_mask); // enable interrupt 235 } 236 237 splx(s); 238} 239 240static void 241powsw_pswitch_event(void *arg) 242{ 243 struct powsw_softc *sc = arg; 244 int poweroff; 245 246 poweroff = sc->sc_prev; 247 248 DPRINTF(" %s is %s\n", device_xname(sc->sc_dev), 249 poweroff ? "off(PRESS)" : "on(RELEASE)"); 250 251 sysmon_pswitch_event(&sc->sc_smpsw, 252 poweroff ? PSWITCH_EVENT_PRESSED : PSWITCH_EVENT_RELEASED); 253} 254 255static void 256powsw_shutdown_check(void *arg) 257{ 258 struct powsw_softc *sc = arg; 259 int poweroff; 260 261 poweroff = sc->sc_prev; 262 if (poweroff) 263 power_switch_is_off = 1; 264 DPRINTF("powsw_shutdown_check %s = %d\n", 265 device_xname(sc->sc_dev), power_switch_is_off); 266} 267 268static void 269powsw_reset_counter(struct powsw_softc *sc) 270{ 271 sc->sc_last_sw = -1; 272 sc->sc_tick = 0; 273 sc->sc_count = 0; 274#if defined(POWSW_DEBUG) 275 sc->sc_loglen = 0; 276#endif 277} 278 279static void 280powsw_set_aer(struct powsw_softc *sc, int aer) 281{ 282 KASSERT(aer == 0 || aer == 1); 283 284 if (aer == 0) { 285 mfp_bit_clear_aer(sc->sc_mask); 286 } else { 287 mfp_bit_set_aer(sc->sc_mask); 288 } 289 DPRINTF(" SetAER=%d", aer); 290} 291