powsw.c revision 1.3 1 /* $NetBSD: powsw.c,v 1.3 2022/07/16 04:49:07 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.3 2022/07/16 04:49:07 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 #include "ioconf.h"
52
53 extern int power_switch_is_off; /* XXX should be in .h */
54
55 //#define POWSW_DEBUG
56
57 #if defined(POWSW_DEBUG)
58 #define DPRINTF(fmt...) printf(fmt)
59 #define DEBUG_LOG_ADD(c) sc->sc_log[sc->sc_loglen++] = (c)
60 #define DEBUG_LOG_PRINT() do { \
61 sc->sc_log[sc->sc_loglen] = '\0'; \
62 printf("%s", sc->sc_log); \
63 } while (0)
64 #else
65 #define DPRINTF(fmt...)
66 #define DEBUG_LOG_ADD(c)
67 #define DEBUG_LOG_PRINT()
68 #endif
69
70 /* mask */
71 #define POWSW_ALARM (0x01)
72 #define POWSW_EXTERNAL (0x02)
73 #define POWSW_FRONT (0x04)
74
75 /* parameter */
76 #define POWSW_MAX_TICK (30)
77 #define POWSW_THRESHOLD (10)
78
79 struct powsw_softc {
80 device_t sc_dev;
81 struct sysmon_pswitch sc_smpsw;
82 callout_t sc_callout;
83 int sc_mask;
84 int sc_prev;
85 int sc_last_sw;
86 int sc_tick;
87 int sc_count;
88 #if defined(POWSW_DEBUG)
89 char sc_log[100];
90 int sc_loglen;
91 #endif
92 };
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
124 return 1;
125 }
126
127 static void
128 powsw_attach(device_t parent, device_t self, void *aux)
129 {
130 struct powsw_softc *sc = device_private(self);
131 powsw_desc_t *desc;
132 const char *xname;
133 int unit;
134 int sw;
135
136 unit = device_unit(self);
137 xname = device_xname(self);
138 desc = &powsw_desc[unit];
139
140 memset(sc, 0, sizeof(*sc));
141 sc->sc_dev = self;
142 sc->sc_mask = desc->mask;
143 sc->sc_prev = -1;
144 powsw_reset_counter(sc);
145
146 sysmon_task_queue_init();
147 sc->sc_smpsw.smpsw_name = xname;
148 sc->sc_smpsw.smpsw_type = PSWITCH_TYPE_POWER;
149 if (sysmon_pswitch_register(&sc->sc_smpsw) != 0)
150 panic("can't register with sysmon");
151
152 callout_init(&sc->sc_callout, 0);
153 callout_setfunc(&sc->sc_callout, powsw_softintr, sc);
154
155 if (shutdownhook_establish(powsw_shutdown_check, sc) == NULL)
156 panic("%s: can't establish shutdown hook", xname);
157
158 if (intio_intr_establish(desc->vector, xname, powsw_intr, sc) < 0)
159 panic("%s: can't establish interrupt", xname);
160
161 /* Set AER and enable interrupt */
162 sw = (mfp_get_gpip() & sc->sc_mask);
163 powsw_set_aer(sc, sw ? 0 : 1);
164 mfp_bit_set_ierb(sc->sc_mask);
165
166 aprint_normal(": %s\n", desc->name);
167 }
168
169 static int
170 powsw_intr(void *arg)
171 {
172 struct powsw_softc *sc = arg;
173
174 if (sc->sc_tick == 0) {
175 mfp_bit_clear_ierb(sc->sc_mask);
176 sc->sc_tick++;
177 DEBUG_LOG_ADD('i');
178 /*
179 * The button state seems unstable for few ticks,
180 * so wait a bit to settle.
181 */
182 callout_schedule(&sc->sc_callout, 1);
183 } else {
184 DEBUG_LOG_ADD('x');
185 }
186 return 0;
187 }
188
189 void
190 powsw_softintr(void *arg)
191 {
192 struct powsw_softc *sc = arg;
193 int sw;
194 int s;
195
196 s = spl6();
197
198 if (sc->sc_tick++ >= POWSW_MAX_TICK) {
199 /* tick is over, broken switch? */
200 printf("%s: unstable power switch?, ignored\n",
201 device_xname(sc->sc_dev));
202 powsw_reset_counter(sc);
203
204 mfp_bit_set_ierb(sc->sc_mask);
205 splx(s);
206 return;
207 }
208
209 sw = (mfp_get_gpip() & sc->sc_mask) ? 1 : 0;
210 DEBUG_LOG_ADD('0' + sw);
211
212 if (sw == sc->sc_last_sw) {
213 sc->sc_count++;
214 } else {
215 sc->sc_last_sw = sw;
216 sc->sc_count = 1;
217 }
218
219 if (sc->sc_count < POWSW_THRESHOLD) {
220 callout_schedule(&sc->sc_callout, 1);
221 } else {
222 /* switch seems stable */
223 DEBUG_LOG_PRINT();
224
225 if (sc->sc_last_sw == sc->sc_prev) {
226 /* switch state is not changed, it was a noise */
227 DPRINTF(" ignore(sw=%d,prev=%d)\n",
228 sc->sc_last_sw, sc->sc_prev);
229 } else {
230 /* switch state has been changed */
231 sc->sc_prev = sc->sc_last_sw;
232 powsw_set_aer(sc, 1 - sc->sc_prev);
233 sysmon_task_queue_sched(0, powsw_pswitch_event, sc);
234 }
235 powsw_reset_counter(sc);
236 /* enable interrupt */
237 mfp_bit_set_ierb(sc->sc_mask);
238 }
239
240 splx(s);
241 }
242
243 static void
244 powsw_pswitch_event(void *arg)
245 {
246 struct powsw_softc *sc = arg;
247 int poweroff;
248
249 poweroff = sc->sc_prev;
250
251 DPRINTF(" %s is %s\n", device_xname(sc->sc_dev),
252 poweroff ? "off(PRESS)" : "on(RELEASE)");
253
254 sysmon_pswitch_event(&sc->sc_smpsw,
255 poweroff ? PSWITCH_EVENT_PRESSED : PSWITCH_EVENT_RELEASED);
256 }
257
258 static void
259 powsw_shutdown_check(void *arg)
260 {
261 struct powsw_softc *sc = arg;
262 int poweroff;
263
264 poweroff = sc->sc_prev;
265 if (poweroff)
266 power_switch_is_off = 1;
267 DPRINTF("powsw_shutdown_check %s = %d\n",
268 device_xname(sc->sc_dev), power_switch_is_off);
269 }
270
271 static void
272 powsw_reset_counter(struct powsw_softc *sc)
273 {
274
275 sc->sc_last_sw = -1;
276 sc->sc_tick = 0;
277 sc->sc_count = 0;
278 #if defined(POWSW_DEBUG)
279 sc->sc_loglen = 0;
280 #endif
281 }
282
283 static void
284 powsw_set_aer(struct powsw_softc *sc, int aer)
285 {
286
287 KASSERT(aer == 0 || aer == 1);
288
289 if (aer == 0) {
290 mfp_bit_clear_aer(sc->sc_mask);
291 } else {
292 mfp_bit_set_aer(sc->sc_mask);
293 }
294 DPRINTF(" SetAER=%d", aer);
295 }
296