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
57static int  nhpow_match(device_t, cfdata_t, void *);
58static void nhpow_attach(device_t, device_t, void *);
59static void nhpow_reboot(int);
60static int nhpow_sysctl_fan(SYSCTLFN_PROTO);
61static int hwintr(void *);
62static void guarded_pbutton(void *);
63static void sched_sysmon_pbutton(void *);
64
65static void nhgpio_pin_write(void *, int, int);
66#if NGPIO > 0
67static int nhgpio_pin_read(void *, int);
68static 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
89struct 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
103CFATTACH_DECL_NEW(nhpow, sizeof(struct nhpow_softc),
104    nhpow_match, nhpow_attach, NULL, NULL);
105
106static int found = 0;
107
108extern struct cfdriver nhpow_cd;
109extern void (*md_reboot)(int);
110
111static int
112nhpow_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
122static void
123nhpow_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
215static void
216nhgpio_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
231static int
232nhgpio_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
242static void
243nhgpio_pin_ctl(void *arg, int pin, int flags)
244{
245
246	/* nothing to control */
247}
248#endif
249
250static void
251nhpow_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
264static int
265nhpow_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
288static int
289hwintr(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
308static void
309guarded_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
318static void
319sched_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