Home | History | Annotate | Line # | Download | only in dev
powsw.c revision 1.1
      1 /*	$NetBSD: powsw.c,v 1.1 2011/11/27 09:00:32 isaki Exp $	*/
      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: powsw.c,v 1.1 2011/11/27 09:00:32 isaki Exp $");
     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 
     51 extern 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 
     77 struct 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 
     92 extern struct cfdriver powsw_cd;
     93 
     94 static int  powsw_match(device_t, cfdata_t, void *);
     95 static void powsw_attach(device_t, device_t, void *);
     96 static int  powsw_intr(void *);
     97 static void powsw_softintr(void *);
     98 static void powsw_pswitch_event(void *);
     99 static void powsw_shutdown_check(void *);
    100 static void powsw_reset_counter(struct powsw_softc *);
    101 static void powsw_set_aer(struct powsw_softc *, int);
    102 
    103 CFATTACH_DECL_NEW(powsw, sizeof(struct powsw_softc),
    104     powsw_match, powsw_attach, NULL, NULL);
    105 
    106 
    107 typedef const struct {
    108 	int vector;			/* interrupt vector */
    109 	int mask;			/* mask bit for MFP GPIP */
    110 	const char *name;
    111 } powsw_desc_t;
    112 
    113 static 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 
    120 static int
    121 powsw_match(device_t parent, cfdata_t cf, void *aux)
    122 {
    123 	return 1;
    124 }
    125 
    126 static void
    127 powsw_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 
    168 static int
    169 powsw_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 
    188 void
    189 powsw_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 
    240 static void
    241 powsw_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 
    255 static void
    256 powsw_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 
    268 static void
    269 powsw_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 
    279 static void
    280 powsw_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