1 /* $NetBSD: nhpow.c,v 1.4 2021/08/07 16:19:04 thorpej Exp $ */
2
3 /*-
4 * Copyright (c) 2012 Frank Wille.
5 * All rights reserved.
6 *
7 * Written by Frank Wille for The NetBSD Project.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
19 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
20 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
22 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 * POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 /*
32 * NH230/231 power and LED control, button handling
33 */
34 #include <sys/cdefs.h>
35 __KERNEL_RCSID(0, "$NetBSD: nhpow.c,v 1.4 2021/08/07 16:19:04 thorpej Exp $");
36 #include "gpio.h"
37
38 #include <sys/param.h>
39 #include <sys/device.h>
40 #if NGPIO > 0
41 #include <sys/gpio.h>
42 #endif
43 #include <sys/callout.h>
44 #include <sys/kernel.h>
45 #include <sys/proc.h>
46 #include <sys/reboot.h>
47 #include <sys/sysctl.h>
48
49 #if NGPIO > 0
50 #include <dev/gpio/gpiovar.h>
51 #endif
52 #include <dev/sysmon/sysmonvar.h>
53 #include <dev/sysmon/sysmon_taskq.h>
54
55 #include <machine/autoconf.h>
56
57 static int nhpow_match(device_t, cfdata_t, void *);
58 static void nhpow_attach(device_t, device_t, void *);
59 static void nhpow_reboot(int);
60 static int nhpow_sysctl_fan(SYSCTLFN_PROTO);
61 static int hwintr(void *);
62 static void guarded_pbutton(void *);
63 static void sched_sysmon_pbutton(void *);
64
65 static void nhgpio_pin_write(void *, int, int);
66 #if NGPIO > 0
67 static int nhgpio_pin_read(void *, int);
68 static void nhgpio_pin_ctl(void *, int, int);
69 #endif
70
71 #define NHGPIO_PINS 8
72
73 /* write gpio */
74 #define NHGPIO_WRITE(sc,x) bus_space_write_1(sc->sc_iot, sc->sc_ioh, 0, x)
75 #define NHGPIO_POWEROFF 0x01
76 #define NHGPIO_RESET 0x02
77 #define NHGPIO_STATUS_LED_OFF 0x04
78 #define NHGPIO_FAN_HIGH 0x08
79 #define NHGPIO_USB1_LED_OFF 0x40
80 #define NHGPIO_USB2_LED_OFF 0x80
81
82 /* read gpio */
83 #define NHGPIO_READ(sc) bus_space_read_1(sc->sc_iot, sc->sc_ioh, 0)
84 #define NHGPIO_POWERBUTTON 0x01
85 #define NHGPIO_RESETBUTTON 0x02
86 #define NHGPIO_VERSIONMASK 0xf0
87 #define NHGPIO_VERSIONSHIFT 4
88
89 struct nhpow_softc {
90 device_t sc_dev;
91 bus_space_tag_t sc_iot;
92 bus_space_handle_t sc_ioh;
93 callout_t sc_ch_pbutton;
94 struct sysmon_pswitch sc_sm_pbutton;
95 int sc_sysctl_fan;
96 #if NGPIO > 0
97 struct gpio_chipset_tag sc_gpio_gc;
98 gpio_pin_t sc_gpio_pins[8];
99 #endif
100 uint8_t sc_gpio_wrstate;
101 };
102
103 CFATTACH_DECL_NEW(nhpow, sizeof(struct nhpow_softc),
104 nhpow_match, nhpow_attach, NULL, NULL);
105
106 static int found = 0;
107
108 extern struct cfdriver nhpow_cd;
109 extern void (*md_reboot)(int);
110
111 static int
nhpow_match(device_t parent,cfdata_t cf,void * aux)112 nhpow_match(device_t parent, cfdata_t cf, void *aux)
113 {
114 struct mainbus_attach_args *ma = aux;
115
116 if (found != 0 || strcmp(ma->ma_name, nhpow_cd.cd_name) != 0)
117 return 0;
118
119 return 1;
120 }
121
122 static void
nhpow_attach(device_t parent,device_t self,void * aux)123 nhpow_attach(device_t parent, device_t self, void *aux)
124 {
125 struct mainbus_attach_args *ma = aux;
126 #if NGPIO > 0
127 struct gpiobus_attach_args gba;
128 #endif
129 const struct sysctlnode *rnode;
130 struct sysctllog *clog;
131 struct nhpow_softc *sc;
132 int i;
133
134 found = 1;
135 sc = device_private(self);
136 sc->sc_dev = self;
137
138 /* map the first byte for GPIO */
139 KASSERT(ma->ma_bst != NULL);
140 sc->sc_iot = ma->ma_bst;
141 i = bus_space_map(sc->sc_iot, 0, 1, 0, &sc->sc_ioh);
142 if (i != 0) {
143 aprint_error(": could not map error %d\n", i);
144 return;
145 }
146
147 aprint_naive(": Power Button Manager\n");
148 aprint_normal(": NH230/231 gpio board control, version %u\n",
149 (NHGPIO_READ(sc) & NHGPIO_VERSIONMASK) >> NHGPIO_VERSIONSHIFT);
150
151 md_reboot = nhpow_reboot; /* cpu_reboot() hook */
152 callout_init(&sc->sc_ch_pbutton, 0); /* power-button callout */
153
154 /* establish button interrupt handler */
155 intr_establish_xname(I8259_ICU + 4, IST_EDGE_RISING, IPL_SCHED, hwintr,
156 sc, device_xname(self));
157 aprint_normal_dev(self, "interrupting at irq %d\n", I8259_ICU + 4);
158
159 /* register power button with sysmon */
160 sysmon_task_queue_init();
161 memset(&sc->sc_sm_pbutton, 0, sizeof(struct sysmon_pswitch));
162 sc->sc_sm_pbutton.smpsw_name = device_xname(sc->sc_dev);
163 sc->sc_sm_pbutton.smpsw_type = PSWITCH_TYPE_POWER;
164 if (sysmon_pswitch_register(&sc->sc_sm_pbutton) != 0)
165 aprint_error_dev(sc->sc_dev,
166 "unable to register power button with sysmon\n");
167
168 /* create machdep.nhpow subtree for fan control */
169 clog = NULL;
170 sysctl_createv(&clog, 0, NULL, &rnode,
171 CTLFLAG_PERMANENT,
172 CTLTYPE_NODE, "machdep", NULL,
173 NULL, 0, NULL, 0,
174 CTL_MACHDEP, CTL_EOL);
175 sysctl_createv(&clog, 0, &rnode, &rnode,
176 CTLFLAG_PERMANENT,
177 CTLTYPE_NODE, "nhpow", NULL,
178 NULL, 0, NULL, 0,
179 CTL_CREATE, CTL_EOL);
180 sysctl_createv(&clog, 0, &rnode, NULL,
181 CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
182 CTLTYPE_INT, "fan",
183 SYSCTL_DESCR("Toggle high(1)/low(0) fan speed"),
184 nhpow_sysctl_fan, 0, NULL, 0,
185 CTL_CREATE, CTL_EOL);
186
187 /* define initial output state */
188 sc->sc_sysctl_fan = 0;
189 sc->sc_gpio_wrstate = NHGPIO_USB1_LED_OFF | NHGPIO_USB2_LED_OFF;
190 NHGPIO_WRITE(sc, sc->sc_gpio_wrstate);
191
192 #if NGPIO > 0
193 /* initialize gpio pin array */
194 for (i = 0; i < NHGPIO_PINS; i++) {
195 sc->sc_gpio_pins[i].pin_num = i;
196 sc->sc_gpio_pins[i].pin_caps = GPIO_PIN_INOUT;
197 sc->sc_gpio_pins[i].pin_flags = GPIO_PIN_INOUT;
198 sc->sc_gpio_pins[i].pin_state =
199 (sc->sc_gpio_wrstate & (1 << i)) ?
200 GPIO_PIN_HIGH : GPIO_PIN_LOW;
201 }
202
203 /* create controller tag and attach GPIO framework */
204 sc->sc_gpio_gc.gp_cookie = sc;
205 sc->sc_gpio_gc.gp_pin_read = nhgpio_pin_read;
206 sc->sc_gpio_gc.gp_pin_write = nhgpio_pin_write;
207 sc->sc_gpio_gc.gp_pin_ctl = nhgpio_pin_ctl;
208 gba.gba_gc = &sc->sc_gpio_gc;
209 gba.gba_pins = sc->sc_gpio_pins;
210 gba.gba_npins = NHGPIO_PINS;
211 config_found(self, &gba, gpiobus_print, CFARGS_NONE);
212 #endif
213 }
214
215 static void
nhgpio_pin_write(void * arg,int pin,int value)216 nhgpio_pin_write(void *arg, int pin, int value)
217 {
218 struct nhpow_softc *sc = arg;
219 int p;
220
221 KASSERT(sc != NULL);
222 p = pin % NHGPIO_PINS;
223 if (value)
224 sc->sc_gpio_wrstate |= (1 << p);
225 else
226 sc->sc_gpio_wrstate &= ~(1 << p);
227 NHGPIO_WRITE(sc, sc->sc_gpio_wrstate);
228 }
229
230 #if NGPIO > 0
231 static int
nhgpio_pin_read(void * arg,int pin)232 nhgpio_pin_read(void *arg, int pin)
233 {
234 struct nhpow_softc *sc = arg;
235 int p;
236
237 KASSERT(sc != NULL);
238 p = pin % NHGPIO_PINS;
239 return (NHGPIO_READ(sc) >> p) & 1;
240 }
241
242 static void
nhgpio_pin_ctl(void * arg,int pin,int flags)243 nhgpio_pin_ctl(void *arg, int pin, int flags)
244 {
245
246 /* nothing to control */
247 }
248 #endif
249
250 static void
nhpow_reboot(int howto)251 nhpow_reboot(int howto)
252 {
253 struct nhpow_softc *sc = device_lookup_private(&nhpow_cd, 0);
254
255 if ((howto & RB_POWERDOWN) == RB_AUTOBOOT)
256 NHGPIO_WRITE(sc, NHGPIO_STATUS_LED_OFF | NHGPIO_RESET);
257 else
258 NHGPIO_WRITE(sc, NHGPIO_STATUS_LED_OFF | NHGPIO_POWEROFF);
259
260 tsleep(nhpow_reboot, PWAIT, "reboot", 0);
261 /*NOTREACHED*/
262 }
263
264 static int
nhpow_sysctl_fan(SYSCTLFN_ARGS)265 nhpow_sysctl_fan(SYSCTLFN_ARGS)
266 {
267 struct sysctlnode node;
268 struct nhpow_softc *sc;
269 int error, t;
270
271 sc = device_lookup_private(&nhpow_cd, 0);
272 node = *rnode;
273 t = sc->sc_sysctl_fan;
274 node.sysctl_data = &t;
275 error = sysctl_lookup(SYSCTLFN_CALL(&node));
276 if (error || newp == NULL)
277 return error;
278 if (t < 0 || t > 1)
279 return EINVAL;
280
281 if (sc->sc_sysctl_fan != t) {
282 sc->sc_sysctl_fan = t;
283 nhgpio_pin_write(sc, 3, t); /* set new fan speed */
284 }
285 return 0;
286 }
287
288 static int
hwintr(void * arg)289 hwintr(void *arg)
290 {
291 struct nhpow_softc *sc = arg;
292 uint8_t buttons;
293
294 callout_stop(&sc->sc_ch_pbutton);
295
296 buttons = NHGPIO_READ(sc);
297 if (!(buttons & NHGPIO_POWERBUTTON)) {
298 /* power button, schedule 3 seconds poweroff guard time */
299 callout_reset(&sc->sc_ch_pbutton, 3 * hz, guarded_pbutton, sc);
300 }
301 if (!(buttons & NHGPIO_RESETBUTTON)) {
302 /* reset/setup button */
303 }
304
305 return 1;
306 }
307
308 static void
guarded_pbutton(void * arg)309 guarded_pbutton(void *arg)
310 {
311 struct nhpow_softc *sc = arg;
312
313 /* we're now in callout(9) context */
314 if (!(NHGPIO_READ(sc) & NHGPIO_POWERBUTTON))
315 sysmon_task_queue_sched(0, sched_sysmon_pbutton, sc);
316 }
317
318 static void
sched_sysmon_pbutton(void * arg)319 sched_sysmon_pbutton(void *arg)
320 {
321 struct nhpow_softc *sc = arg;
322
323 /* we're now in kthread(9) context */
324 sysmon_pswitch_event(&sc->sc_sm_pbutton, PSWITCH_EVENT_PRESSED);
325 }
326