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