gpiokeys.c revision 1.5 1 /* $NetBSD: gpiokeys.c,v 1.5 2017/09/23 23:54:30 jmcneill Exp $ */
2
3 /*-
4 * Copyright (c) 2015 Jared D. McNeill <jmcneill (at) invisible.ca>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29 #include <sys/cdefs.h>
30 __KERNEL_RCSID(0, "$NetBSD: gpiokeys.c,v 1.5 2017/09/23 23:54:30 jmcneill Exp $");
31
32 #include <sys/param.h>
33 #include <sys/kernel.h>
34 #include <sys/systm.h>
35 #include <sys/device.h>
36 #include <sys/kmem.h>
37 #include <sys/bus.h>
38 #include <sys/gpio.h>
39
40 #include <dev/sysmon/sysmonvar.h>
41 #include <dev/sysmon/sysmon_taskq.h>
42
43 #include <dev/wscons/wsconsio.h>
44 #include <dev/wscons/wskbdvar.h>
45 #include <dev/wscons/wsksymdef.h>
46 #include <dev/wscons/wsksymvar.h>
47 #include <dev/wscons/linux_keymap.h>
48
49 #include <dev/fdt/fdtvar.h>
50
51 #define GPIOKEYS_POLL_INTERVAL mstohz(200)
52
53 #define KEY_POWER 116
54 #define KEY_SLEEP 142
55
56 static int gpiokeys_match(device_t, cfdata_t, void *);
57 static void gpiokeys_attach(device_t, device_t, void *);
58
59 static void gpiokeys_tick(void *);
60 static void gpiokeys_task(void *);
61
62 extern const struct wscons_keydesc ukbd_keydesctab[];
63 static const struct wskbd_mapdata gpiokeys_keymapdata = {
64 ukbd_keydesctab,
65 KB_US,
66 };
67
68 struct gpiokeys_softc;
69
70 struct gpiokeys_key {
71 struct gpiokeys_softc *key_sc;
72 int key_phandle;
73 char *key_label;
74 struct fdtbus_gpio_pin *key_pin;
75 u_int key_debounce;
76 u_int key_code;
77 struct sysmon_pswitch key_pswitch;
78 uint8_t key_usbcode;
79 u_int key_state;
80
81 struct gpiokeys_key *key_next;
82 };
83
84 struct gpiokeys_softc {
85 device_t sc_dev;
86 int sc_phandle;
87
88 struct fdtbus_gpio_pin *sc_pin;
89 bool sc_always_on;
90 bool sc_enable_val;
91
92 struct gpiokeys_key *sc_keys;
93 callout_t sc_tick;
94
95 device_t sc_wskbddev;
96 int sc_enabled;
97 };
98
99 CFATTACH_DECL_NEW(gpiokeys, sizeof(struct gpiokeys_softc),
100 gpiokeys_match, gpiokeys_attach, NULL, NULL);
101
102 static int
103 gpiokeys_enable(void *v, int on)
104 {
105 struct gpiokeys_softc * const sc = v;
106
107 sc->sc_enabled = on;
108
109 return 0;
110 }
111
112 static void
113 gpiokeys_set_leds(void *v, int leds)
114 {
115 }
116
117 static int
118 gpiokeys_ioctl(void *v, u_long cmd, void *data, int flag, lwp_t *l)
119 {
120 switch (cmd) {
121 case WSKBDIO_GTYPE:
122 *(int *)data = WSKBD_TYPE_USB;
123 return 0;
124 }
125
126 return EPASSTHROUGH;
127 }
128
129 static const struct wskbd_accessops gpiokeys_accessops = {
130 .enable = gpiokeys_enable,
131 .set_leds = gpiokeys_set_leds,
132 .ioctl = gpiokeys_ioctl
133 };
134
135 static int
136 gpiokeys_match(device_t parent, cfdata_t cf, void *aux)
137 {
138 const char * const compatible[] = { "gpio-keys", NULL };
139 const struct fdt_attach_args *faa = aux;
140
141 return of_match_compatible(faa->faa_phandle, compatible);
142 }
143
144 static void
145 gpiokeys_attach(device_t parent, device_t self, void *aux)
146 {
147 struct gpiokeys_softc * const sc = device_private(self);
148 const struct fdt_attach_args *faa = aux;
149 const int phandle = faa->faa_phandle;
150 struct gpiokeys_key *key;
151 u_int debounce, code;
152 int use_wskbddev = 0;
153 int child, len;
154
155 sc->sc_dev = self;
156 sc->sc_phandle = phandle;
157
158 aprint_naive("\n");
159 aprint_normal(":");
160
161 for (child = OF_child(phandle); child; child = OF_peer(child)) {
162 if (of_getprop_uint32(child, "linux,code", &code))
163 continue;
164 if (of_getprop_uint32(child, "debounce-interval", &debounce))
165 debounce = 5; /* default */
166 len = OF_getproplen(child, "label");
167 if (len <= 0) {
168 continue;
169 }
170 key = kmem_zalloc(sizeof(*key), KM_SLEEP);
171 key->key_sc = sc;
172 key->key_phandle = child;
173 key->key_code = code;
174 key->key_label = kmem_zalloc(len, KM_SLEEP);
175 if (OF_getprop(child, "label", key->key_label, len) != len) {
176 kmem_free(key->key_label, len);
177 kmem_free(key, sizeof(*key));
178 continue;
179 }
180 key->key_debounce = debounce;
181 key->key_pin = fdtbus_gpio_acquire(child, "gpios",
182 GPIO_PIN_INPUT);
183 if (key->key_pin)
184 key->key_state = fdtbus_gpio_read(key->key_pin);
185
186 switch (code) {
187 case KEY_POWER:
188 key->key_pswitch.smpsw_name = key->key_label;
189 key->key_pswitch.smpsw_type = PSWITCH_TYPE_POWER;
190 break;
191 case KEY_SLEEP:
192 key->key_pswitch.smpsw_name = key->key_label;
193 key->key_pswitch.smpsw_type = PSWITCH_TYPE_SLEEP;
194 break;
195 default:
196 key->key_usbcode = linux_key_to_usb(code);
197 if (key->key_usbcode != 0) {
198 use_wskbddev++;
199 } else {
200 key->key_pswitch.smpsw_name = key->key_label;
201 key->key_pswitch.smpsw_type = PSWITCH_TYPE_HOTKEY;
202 }
203 break;
204 }
205
206 if (key->key_pswitch.smpsw_name != NULL &&
207 sysmon_pswitch_register(&key->key_pswitch) != 0) {
208 aprint_error(" %s:ERROR", key->key_label);
209 kmem_free(key->key_label, len);
210 kmem_free(key, sizeof(*key));
211 continue;
212 }
213
214 if (sc->sc_keys) {
215 aprint_normal(", %s", key->key_label);
216 } else {
217 aprint_normal(" %s", key->key_label);
218 }
219
220 key->key_next = sc->sc_keys;
221 sc->sc_keys = key;
222 }
223
224 if (sc->sc_keys == NULL) {
225 aprint_normal(" no keys configured\n");
226 return;
227 }
228
229 aprint_normal("\n");
230
231 if (use_wskbddev > 0) {
232 struct wskbddev_attach_args a;
233 memset(&a, 0, sizeof(a));
234 a.console = false;
235 a.keymap = &gpiokeys_keymapdata;
236 a.accessops = &gpiokeys_accessops;
237 a.accesscookie = sc;
238 sc->sc_wskbddev = config_found_ia(self, "wskbddev", &a,
239 wskbddevprint);
240 }
241
242 callout_init(&sc->sc_tick, CALLOUT_MPSAFE);
243 callout_setfunc(&sc->sc_tick, gpiokeys_tick, sc);
244
245 gpiokeys_tick(sc);
246 }
247
248 static void
249 gpiokeys_tick(void *priv)
250 {
251 struct gpiokeys_softc * const sc = priv;
252 struct gpiokeys_key *key;
253
254 for (key = sc->sc_keys; key; key = key->key_next) {
255 if (key->key_pin == NULL) {
256 continue;
257 }
258 const int new_state = fdtbus_gpio_read(key->key_pin);
259 if (new_state != key->key_state) {
260 key->key_state = new_state;
261 sysmon_task_queue_sched(0, gpiokeys_task, key);
262 }
263 }
264 callout_schedule(&sc->sc_tick, GPIOKEYS_POLL_INTERVAL);
265 }
266
267 static void
268 gpiokeys_task(void *priv)
269 {
270 struct gpiokeys_key *key = priv;
271 struct gpiokeys_softc *sc = key->key_sc;
272
273 if (key->key_pswitch.smpsw_name) {
274 sysmon_pswitch_event(&key->key_pswitch,
275 key->key_state ? PSWITCH_EVENT_PRESSED : PSWITCH_EVENT_RELEASED);
276 } else if (sc->sc_enabled && sc->sc_wskbddev != NULL && key->key_usbcode != 0) {
277 wskbd_input(sc->sc_wskbddev,
278 key->key_state ? WSCONS_EVENT_KEY_DOWN : WSCONS_EVENT_KEY_UP,
279 key->key_usbcode);
280 }
281 }
282