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 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 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 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 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 243 nhgpio_pin_ctl(void *arg, int pin, int flags) 244 { 245 246 /* nothing to control */ 247 } 248 #endif 249 250 static void 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 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 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 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 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