1/*	$OpenBSD: gpiokeys.c,v 1.3 2023/03/31 12:07:54 kn Exp $	*/
2/*
3 * Copyright (c) 2021 Klemens Nanni <kn@openbsd.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18#include <sys/param.h>
19#include <sys/systm.h>
20#include <sys/device.h>
21#include <sys/gpio.h>
22#include <sys/malloc.h>
23#include <sys/queue.h>
24
25#include <machine/bus.h>
26#include <machine/fdt.h>
27
28#include <dev/gpio/gpiovar.h>
29#include <dev/ofw/ofw_gpio.h>
30#include <dev/ofw/ofw_pinctrl.h>
31#include <dev/ofw/openfirm.h>
32#include <dev/ofw/fdt.h>
33
34#include <sys/sensors.h>
35
36#define	DEVNAME(_s)	((_s)->sc_dev.dv_xname)
37
38/*
39 * Defines from Linux, see:
40 *	Documentation/input/event-codes.rst
41 *	include/dt-bindings/input/linux-event-codes.h
42 */
43enum gpiokeys_event_type {
44	GPIOKEYS_EV_KEY = 1,
45	GPIOKEYS_EV_SW = 5,
46};
47
48enum gpiokeys_switch_event {
49	GPIOKEYS_SW_LID = 0,	/* set = lid closed */
50};
51
52struct gpiokeys_key {
53	uint32_t			*key_pin;
54	uint32_t			 key_input_type;
55	uint32_t			 key_code;
56	struct ksensor			 key_sensor;
57	SLIST_ENTRY(gpiokeys_key)	 entries;
58};
59
60struct gpiokeys_softc {
61	struct device			 sc_dev;
62	int				 sc_node;
63	struct ksensordev		 sc_sensordev;
64	SLIST_HEAD(, gpiokeys_key)	 sc_keys;
65};
66
67int	 gpiokeys_match(struct device *, void *, void *);
68void	 gpiokeys_attach(struct device *, struct device *, void *);
69
70const struct cfattach gpiokeys_ca = {
71	sizeof (struct gpiokeys_softc), gpiokeys_match, gpiokeys_attach
72};
73
74struct cfdriver gpiokeys_cd = {
75	NULL, "gpiokeys", DV_DULL
76};
77
78void	 gpiokeys_update_key(void *);
79
80int
81gpiokeys_match(struct device *parent, void *match, void *aux)
82{
83	const struct fdt_attach_args	*faa = aux;
84
85	return OF_is_compatible(faa->fa_node, "gpio-keys") ||
86	    OF_is_compatible(faa->fa_node, "gpio-keys-polled");
87}
88
89void
90gpiokeys_attach(struct device *parent, struct device *self, void *aux)
91{
92	struct gpiokeys_softc	*sc = (struct gpiokeys_softc *)self;
93	struct fdt_attach_args	*faa = aux;
94	struct gpiokeys_key	*key;
95	char			*label;
96	uint32_t		 code;
97	int			 node, len, gpios_len;
98	int			 have_labels = 0, have_sensors = 0;
99
100	SLIST_INIT(&sc->sc_keys);
101
102	pinctrl_byname(faa->fa_node, "default");
103
104	for (node = OF_child(faa->fa_node); node; node = OF_peer(node)) {
105		if (OF_getprop(node, "linux,code", &code, sizeof(code)) == -1)
106			continue;
107		gpios_len = OF_getproplen(node, "gpios");
108		if (gpios_len <= 0)
109			continue;
110		label = NULL;
111		len = OF_getproplen(node, "label");
112		if (len > 0) {
113			label = malloc(len, M_TEMP, M_WAITOK);
114			if (OF_getprop(node, "label", label, len) != len) {
115				free(label, M_TEMP, len);
116				continue;
117			}
118		}
119		key = malloc(sizeof(*key), M_DEVBUF, M_WAITOK | M_ZERO);
120		key->key_input_type = OF_getpropint(node, "linux,input-type",
121		    GPIOKEYS_EV_KEY);
122		key->key_code = code;
123		key->key_pin = malloc(gpios_len, M_DEVBUF, M_WAITOK);
124		OF_getpropintarray(node, "gpios", key->key_pin, gpios_len);
125		gpio_controller_config_pin(key->key_pin, GPIO_CONFIG_INPUT);
126
127		switch (key->key_input_type) {
128		case GPIOKEYS_EV_SW:
129			switch (key->key_code) {
130			case GPIOKEYS_SW_LID:
131				strlcpy(key->key_sensor.desc, "lid open",
132				    sizeof(key->key_sensor.desc));
133				key->key_sensor.type = SENSOR_INDICATOR;
134				sensor_attach(&sc->sc_sensordev, &key->key_sensor);
135				sensor_task_register(key, gpiokeys_update_key, 1);
136				have_sensors = 1;
137				break;
138			}
139			break;
140		}
141
142		if (label) {
143			printf("%s \"%s\"", have_labels ? "," : ":", label);
144			free(label, M_TEMP, len);
145			have_labels = 1;
146		}
147
148		SLIST_INSERT_HEAD(&sc->sc_keys, key, entries);
149	}
150
151	if (have_sensors) {
152		strlcpy(sc->sc_sensordev.xname, DEVNAME(sc),
153		    sizeof(sc->sc_sensordev.xname));
154		sensordev_install(&sc->sc_sensordev);
155	}
156
157	if (SLIST_EMPTY(&sc->sc_keys))
158		printf(": no keys");
159	printf("\n");
160}
161
162void
163gpiokeys_update_key(void *arg)
164{
165	struct gpiokeys_key	*key = arg;
166	int			 val;
167
168	val = gpio_controller_get_pin(key->key_pin);
169
170	switch (key->key_input_type) {
171	case GPIOKEYS_EV_SW:
172		switch (key->key_code) {
173		case GPIOKEYS_SW_LID:
174			/*
175			 * Match acpibtn(4), i.e. closed ThinkPad lid yields
176			 * hw.sensors.acpibtn1.indicator0=Off (lid open)
177			 */
178			key->key_sensor.value = !val;
179			break;
180		}
181		break;
182	}
183}
184