summaryrefslogtreecommitdiff
path: root/sys
diff options
context:
space:
mode:
authorMark Kettenis <kettenis@cvs.openbsd.org>2021-11-01 09:02:47 +0000
committerMark Kettenis <kettenis@cvs.openbsd.org>2021-11-01 09:02:47 +0000
commit5821488658df337700d73fe07c946addee6b59ab (patch)
tree486057f3842f77e3bcdb1da3413adc9ae92b3fb1 /sys
parent63d0de34df5d43d981410566371b39415bd5a77b (diff)
Add support for the keyboard/touchpad on Apple M1 laptops.
ok patrick@
Diffstat (limited to 'sys')
-rw-r--r--sys/arch/arm64/conf/GENERIC7
-rw-r--r--sys/arch/arm64/conf/RAMDISK5
-rw-r--r--sys/arch/arm64/conf/files.arm6410
-rw-r--r--sys/arch/arm64/dev/aplhidev.c550
4 files changed, 569 insertions, 3 deletions
diff --git a/sys/arch/arm64/conf/GENERIC b/sys/arch/arm64/conf/GENERIC
index 45b6d1d6425..809ece860c0 100644
--- a/sys/arch/arm64/conf/GENERIC
+++ b/sys/arch/arm64/conf/GENERIC
@@ -1,4 +1,4 @@
-# $OpenBSD: GENERIC,v 1.210 2021/10/31 16:38:12 kettenis Exp $
+# $OpenBSD: GENERIC,v 1.211 2021/11/01 09:02:46 kettenis Exp $
#
# GENERIC machine description file
#
@@ -143,6 +143,11 @@ aplpcie* at fdt?
pci* at aplpcie?
aplpinctrl* at fdt? early 1
aplspi* at fdt?
+aplhidev* at spi?
+aplkbd* at aplhidev?
+wskbd* at aplkbd? mux 1
+aplms* at aplhidev?
+wsmouse* at aplms? mux 0
aplspmi* at fdt?
aplpmu* at aplspmi?
exuart* at fdt?
diff --git a/sys/arch/arm64/conf/RAMDISK b/sys/arch/arm64/conf/RAMDISK
index 1f01f178d07..201a31a432d 100644
--- a/sys/arch/arm64/conf/RAMDISK
+++ b/sys/arch/arm64/conf/RAMDISK
@@ -1,4 +1,4 @@
-# $OpenBSD: RAMDISK,v 1.159 2021/10/31 16:38:12 kettenis Exp $
+# $OpenBSD: RAMDISK,v 1.160 2021/11/01 09:02:46 kettenis Exp $
machine arm64
maxusers 4
@@ -108,6 +108,9 @@ aplpcie* at fdt?
pci* at aplpcie?
aplpinctrl* at fdt? early 1
aplspi* at fdt?
+aplhidev* at spi?
+aplkbd* at aplhidev?
+wskbd* at aplkbd? mux 1
aplspmi* at fdt?
aplpmu* at aplspmi?
exuart* at fdt?
diff --git a/sys/arch/arm64/conf/files.arm64 b/sys/arch/arm64/conf/files.arm64
index b58002c2c9e..139df931cca 100644
--- a/sys/arch/arm64/conf/files.arm64
+++ b/sys/arch/arm64/conf/files.arm64
@@ -1,4 +1,4 @@
-# $OpenBSD: files.arm64,v 1.45 2021/10/31 16:38:12 kettenis Exp $
+# $OpenBSD: files.arm64,v 1.46 2021/11/01 09:02:46 kettenis Exp $
maxpartitions 16
maxusers 2 8 128
@@ -152,6 +152,14 @@ device aplintc
attach aplintc at fdt
file arch/arm64/dev/aplintc.c aplintc
+device aplhidev {}
+attach aplhidev at spi
+device aplkbd: hid, hidkbd, wskbddev
+attach aplkbd at aplhidev
+device aplms: hid, hidms, wsmousedev
+attach aplms at aplhidev
+file arch/arm64/dev/aplhidev.c aplhidev | aplkbd | aplms needs-flag
+
device aplpcie: pcibus
attach aplpcie at fdt
file arch/arm64/dev/aplpcie.c aplpcie
diff --git a/sys/arch/arm64/dev/aplhidev.c b/sys/arch/arm64/dev/aplhidev.c
new file mode 100644
index 00000000000..4a70a3773d9
--- /dev/null
+++ b/sys/arch/arm64/dev/aplhidev.c
@@ -0,0 +1,550 @@
+/* $OpenBSD: aplhidev.c,v 1.1 2021/11/01 09:02:46 kettenis Exp $ */
+/*
+ * Copyright (c) 2021 Mark Kettenis <kettenis@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/device.h>
+#include <sys/malloc.h>
+#include <sys/timeout.h>
+
+#include <lib/libkern/crc16.h>
+
+#include <machine/fdt.h>
+
+#include <dev/spi/spivar.h>
+
+#include <dev/ofw/openfirm.h>
+#include <dev/ofw/ofw_gpio.h>
+#include <dev/ofw/ofw_pinctrl.h>
+
+#include <dev/wscons/wsconsio.h>
+#include <dev/wscons/wskbdvar.h>
+#include <dev/wscons/wsksymdef.h>
+#include <dev/wscons/wsmousevar.h>
+
+#include <dev/hid/hid.h>
+#include <dev/hid/hidkbdsc.h>
+#include <dev/hid/hidmsvar.h>
+
+#include "aplhidev.h"
+
+#define APLHIDEV_READ_PACKET 0x20
+#define APLHIDEV_WRITE_PACKET 0x40
+
+#define APLHIDEV_KBD_DEVICE 1
+#define APLHIDEV_TP_DEVICE 2
+#define APLHIDEV_INFO_DEVICE 208
+
+#define APLHIDEV_GET_DESCRIPTOR 0x1020
+#define APLHIDEV_DESC_MAX 512
+#define APLHIDEV_KBD_REPORT 0x0110
+#define APLHIDEV_TP_REPORT 0x0210
+
+struct aplhidev_attach_args {
+ uint8_t aa_reportid;
+ void *aa_desc;
+ size_t aa_desclen;
+};
+
+struct aplhidev_spi_packet {
+ uint8_t flags;
+ uint8_t device;
+ uint16_t offset;
+ uint16_t remaining;
+ uint16_t len;
+ uint8_t data[246];
+ uint16_t crc;
+};
+
+struct aplhidev_spi_status {
+ uint8_t status[4];
+};
+
+struct aplhidev_msghdr {
+ uint16_t type;
+ uint8_t device;
+ uint8_t msgid;
+ uint16_t rsplen;
+ uint16_t cmdlen;
+};
+
+struct aplhidev_get_desc {
+ struct aplhidev_msghdr hdr;
+ uint16_t crc;
+};
+
+struct aplhidev_softc {
+ struct device sc_dev;
+ int sc_node;
+
+ spi_tag_t sc_spi_tag;
+ struct spi_config sc_spi_conf;
+
+ uint8_t sc_msgid;
+
+ uint32_t *sc_gpio;
+ size_t sc_gpiolen;
+
+ struct device *sc_kbd;
+ uint8_t sc_kbddesc[APLHIDEV_DESC_MAX];
+ size_t sc_kbddesclen;
+
+ struct device *sc_ms;
+ uint8_t sc_tpdesc[APLHIDEV_DESC_MAX];
+ size_t sc_tpdesclen;
+};
+
+int aplhidev_match(struct device *, void *, void *);
+void aplhidev_attach(struct device *, struct device *, void *);
+
+struct cfattach aplhidev_ca = {
+ sizeof(struct aplhidev_softc), aplhidev_match, aplhidev_attach
+};
+
+struct cfdriver aplhidev_cd = {
+ NULL, "aplhidev", DV_DULL
+};
+
+void aplhidev_get_descriptor(struct aplhidev_softc *, uint8_t);
+
+int aplhidev_intr(void *);
+void aplkbd_intr(struct device *, void *, size_t);
+void aplms_intr(struct device *, void *, size_t);
+
+int
+aplhidev_match(struct device *parent, void *match, void *aux)
+{
+ struct spi_attach_args *sa = aux;
+
+ if (strcmp(sa->sa_name, "apple,keyboard") == 0)
+ return 1;
+
+ return 0;
+}
+
+void
+aplhidev_attach(struct device *parent, struct device *self, void *aux)
+{
+ struct aplhidev_softc *sc = (struct aplhidev_softc *)self;
+ struct spi_attach_args *sa = aux;
+ struct aplhidev_attach_args aa;
+ int retry;
+
+ sc->sc_spi_tag = sa->sa_tag;
+ sc->sc_node = *(int *)sa->sa_cookie;
+
+ sc->sc_gpiolen = OF_getproplen(sc->sc_node, "spien-gpios");
+ if (sc->sc_gpiolen > 0) {
+ sc->sc_gpio = malloc(sc->sc_gpiolen, M_TEMP, M_WAITOK);
+ OF_getpropintarray(sc->sc_node, "spien-gpios",
+ sc->sc_gpio, sc->sc_gpiolen);
+ gpio_controller_config_pin(sc->sc_gpio, GPIO_CONFIG_OUTPUT);
+
+ /* Reset */
+ gpio_controller_set_pin(sc->sc_gpio, 1);
+ delay(5000);
+ gpio_controller_set_pin(sc->sc_gpio, 0);
+ delay(5000);
+
+ /* Enable. */
+ gpio_controller_set_pin(sc->sc_gpio, 1);
+ delay(50000);
+ }
+
+ sc->sc_spi_conf.sc_bpw = 8;
+ sc->sc_spi_conf.sc_freq = OF_getpropint(sc->sc_node,
+ "spi-max-frequency", 0);
+ sc->sc_spi_conf.sc_cs = OF_getpropint(sc->sc_node, "reg", 0);
+ sc->sc_spi_conf.sc_cs_delay = 100;
+
+ fdt_intr_establish(sc->sc_node, IPL_TTY,
+ aplhidev_intr, sc, sc->sc_dev.dv_xname);
+
+ aplhidev_get_descriptor(sc, APLHIDEV_KBD_DEVICE);
+ for (retry = 10; retry > 0; retry--) {
+ aplhidev_intr(sc);
+ delay(1000);
+ if (sc->sc_kbddesclen > 0)
+ break;
+ }
+
+ aplhidev_get_descriptor(sc, APLHIDEV_TP_DEVICE);
+ for (retry = 10; retry > 0; retry--) {
+ aplhidev_intr(sc);
+ delay(1000);
+ if (sc->sc_tpdesclen > 0)
+ break;
+ }
+
+ printf("\n");
+
+ if (sc->sc_kbddesclen > 0) {
+ aa.aa_reportid = APLHIDEV_KBD_DEVICE;
+ aa.aa_desc = sc->sc_kbddesc;
+ aa.aa_desclen = sc->sc_kbddesclen;
+ sc->sc_kbd = config_found(self, &aa, NULL);
+ }
+
+ if (sc->sc_tpdesclen > 0) {
+ aa.aa_reportid = APLHIDEV_TP_DEVICE;
+ aa.aa_desc = sc->sc_tpdesc;
+ aa.aa_desclen = sc->sc_tpdesclen;
+ sc->sc_ms = config_found(self, &aa, NULL);
+ }
+}
+
+void
+aplhidev_get_descriptor(struct aplhidev_softc *sc, uint8_t device)
+{
+ struct aplhidev_spi_packet packet;
+ struct aplhidev_get_desc *msg;
+ struct aplhidev_spi_status status;
+
+ memset(&packet, 0, sizeof(packet));
+ packet.flags = APLHIDEV_WRITE_PACKET;
+ packet.device = APLHIDEV_INFO_DEVICE;
+ packet.len = sizeof(*msg);
+
+ msg = (void *)&packet.data[0];
+ msg->hdr.type = APLHIDEV_GET_DESCRIPTOR;
+ msg->hdr.device = device;
+ msg->hdr.msgid = sc->sc_msgid++;
+ msg->hdr.cmdlen = 0;
+ msg->hdr.rsplen = APLHIDEV_DESC_MAX;
+ msg->crc = crc16(0, (void *)msg, sizeof(*msg) - 2);
+
+ packet.crc = crc16(0, (void *)&packet, sizeof(packet) - 2);
+
+ spi_acquire_bus(sc->sc_spi_tag, 0);
+ spi_config(sc->sc_spi_tag, &sc->sc_spi_conf);
+ spi_transfer(sc->sc_spi_tag, (char *)&packet, NULL, sizeof(packet),
+ SPI_KEEP_CS);
+ delay(100);
+ spi_read(sc->sc_spi_tag, (char *)&status, sizeof(status));
+ spi_release_bus(sc->sc_spi_tag, 0);
+
+ delay(1000);
+}
+
+int
+aplhidev_intr(void *arg)
+{
+ struct aplhidev_softc *sc = arg;
+ struct aplhidev_spi_packet packet;
+ struct aplhidev_msghdr *hdr = (struct aplhidev_msghdr *)&packet.data[0];
+
+ memset(&packet, 0, sizeof(packet));
+ spi_acquire_bus(sc->sc_spi_tag, 0);
+ spi_config(sc->sc_spi_tag, &sc->sc_spi_conf);
+ spi_read(sc->sc_spi_tag, (char *)&packet, sizeof(packet));
+ spi_release_bus(sc->sc_spi_tag, 0);
+
+ /* Treat empty packets as spurious interrupts. */
+ if (packet.flags == 0 && packet.device == 0 && packet.crc == 0)
+ return 0;
+
+ if (crc16(0, (uint8_t *)&packet, sizeof(packet)))
+ return 1;
+
+ /* Keyboard input. */
+ if (packet.flags == APLHIDEV_READ_PACKET &&
+ packet.device == APLHIDEV_KBD_DEVICE &&
+ hdr->type == APLHIDEV_KBD_REPORT) {
+ if (sc->sc_kbd)
+ aplkbd_intr(sc->sc_kbd, &packet.data[9], hdr->cmdlen - 1);
+ return 1;
+ }
+
+ /* Touchpad input. */
+ if (packet.flags == APLHIDEV_READ_PACKET &&
+ packet.device == APLHIDEV_TP_DEVICE &&
+ hdr->type == APLHIDEV_TP_REPORT) {
+ if (sc->sc_ms)
+ aplms_intr(sc->sc_ms, &packet.data[9], hdr->cmdlen - 1);
+ return 1;
+ }
+
+ /* Replies to commands we sent. */
+ if (packet.flags == APLHIDEV_WRITE_PACKET &&
+ packet.device == APLHIDEV_INFO_DEVICE &&
+ hdr->type == APLHIDEV_GET_DESCRIPTOR) {
+ switch (hdr->device) {
+ case APLHIDEV_KBD_DEVICE:
+ memcpy(sc->sc_kbddesc, &packet.data[8], hdr->cmdlen);
+ sc->sc_kbddesclen = hdr->cmdlen;
+ break;
+ case APLHIDEV_TP_DEVICE:
+ memcpy(sc->sc_tpdesc, &packet.data[8], hdr->cmdlen);
+ sc->sc_tpdesclen = hdr->cmdlen;
+ break;
+ }
+
+ return 1;
+ }
+
+ /* Valid, but unrecognized packet; ignore for now. */
+ return 1;
+}
+
+/* Keyboard */
+
+struct aplkbd_softc {
+ struct device sc_dev;
+ struct hidkbd sc_kbd;
+ int sc_spl;
+};
+
+void aplkbd_cngetc(void *, u_int *, int *);
+void aplkbd_cnpollc(void *, int);
+void aplkbd_cnbell(void *, u_int, u_int, u_int);
+
+const struct wskbd_consops aplkbd_consops = {
+ aplkbd_cngetc,
+ aplkbd_cnpollc,
+ aplkbd_cnbell,
+};
+
+int aplkbd_enable(void *, int);
+void aplkbd_set_leds(void *, int);
+int aplkbd_ioctl(void *, u_long, caddr_t, int, struct proc *);
+
+const struct wskbd_accessops aplkbd_accessops = {
+ .enable = aplkbd_enable,
+ .ioctl = aplkbd_ioctl,
+ .set_leds = aplkbd_set_leds,
+};
+
+int aplkbd_match(struct device *, void *, void *);
+void aplkbd_attach(struct device *, struct device *, void *);
+
+struct cfattach aplkbd_ca = {
+ sizeof(struct aplkbd_softc), aplkbd_match, aplkbd_attach
+};
+
+struct cfdriver aplkbd_cd = {
+ NULL, "aplkbd", DV_DULL
+};
+
+int
+aplkbd_match(struct device *parent, void *match, void *aux)
+{
+ struct aplhidev_attach_args *aa = (struct aplhidev_attach_args *)aux;
+
+ return (aa->aa_reportid == APLHIDEV_KBD_DEVICE);
+}
+
+void
+aplkbd_attach(struct device *parent, struct device *self, void *aux)
+{
+ struct aplkbd_softc *sc = (struct aplkbd_softc *)self;
+ struct aplhidev_attach_args *aa = (struct aplhidev_attach_args *)aux;
+ struct hidkbd *kbd = &sc->sc_kbd;
+
+ if (hidkbd_attach(self, kbd, 1, 0, APLHIDEV_KBD_DEVICE,
+ aa->aa_desc, aa->aa_desclen))
+ return;
+
+ printf("\n");
+
+ if (kbd->sc_console_keyboard) {
+ extern struct wskbd_mapdata ukbd_keymapdata;
+
+ ukbd_keymapdata.layout = KB_US | KB_DEFAULT;
+ wskbd_cnattach(&aplkbd_consops, sc, &ukbd_keymapdata);
+ aplkbd_enable(sc, 1);
+ }
+
+ hidkbd_attach_wskbd(kbd, KB_US | KB_DEFAULT, &aplkbd_accessops);
+}
+
+void
+aplkbd_intr(struct device *self, void *report, size_t reportlen)
+{
+ struct aplkbd_softc *sc = (struct aplkbd_softc *)self;
+ struct hidkbd *kbd = &sc->sc_kbd;
+
+ if (kbd->sc_enabled)
+ hidkbd_input(kbd, report, reportlen);
+}
+
+int
+aplkbd_enable(void *v, int on)
+{
+ struct aplkbd_softc *sc = v;
+ struct hidkbd *kbd = &sc->sc_kbd;
+
+ return hidkbd_enable(kbd, on);
+}
+
+int
+aplkbd_ioctl(void *v, u_long cmd, caddr_t data, int flag, struct proc *p)
+{
+ struct aplkbd_softc *sc = v;
+ struct hidkbd *kbd = &sc->sc_kbd;
+
+ switch (cmd) {
+ case WSKBDIO_GTYPE:
+ /* XXX: should we set something else? */
+ *(u_int *)data = WSKBD_TYPE_USB;
+ return 0;
+ default:
+ return hidkbd_ioctl(kbd, cmd, data, flag, p);
+ }
+}
+
+void
+aplkbd_set_leds(void *v, int leds)
+{
+}
+
+/* Console interface. */
+void
+aplkbd_cngetc(void *v, u_int *type, int *data)
+{
+ struct aplkbd_softc *sc = v;
+ struct hidkbd *kbd = &sc->sc_kbd;
+
+ kbd->sc_polling = 1;
+ while (kbd->sc_npollchar <= 0) {
+ aplhidev_intr(sc->sc_dev.dv_parent);
+ delay(1000);
+ }
+ kbd->sc_polling = 0;
+ hidkbd_cngetc(kbd, type, data);
+}
+
+void
+aplkbd_cnpollc(void *v, int on)
+{
+ struct aplkbd_softc *sc = v;
+
+ if (on)
+ sc->sc_spl = spltty();
+ else
+ splx(sc->sc_spl);
+}
+
+void
+aplkbd_cnbell(void *v, u_int pitch, u_int period, u_int volume)
+{
+ hidkbd_bell(pitch, period, volume, 1);
+}
+
+#if NAPLMS > 0
+
+/* Touchpad */
+
+struct aplms_softc {
+ struct device sc_dev;
+ struct hidms sc_ms;
+};
+
+int aplms_enable(void *);
+void aplms_disable(void *);
+int aplms_ioctl(void *, u_long, caddr_t, int, struct proc *);
+
+const struct wsmouse_accessops aplms_accessops = {
+ .enable = aplms_enable,
+ .disable = aplms_disable,
+ .ioctl = aplms_ioctl,
+};
+
+int aplms_match(struct device *, void *, void *);
+void aplms_attach(struct device *, struct device *, void *);
+
+struct cfattach aplms_ca = {
+ sizeof(struct aplms_softc), aplms_match, aplms_attach
+};
+
+struct cfdriver aplms_cd = {
+ NULL, "aplms", DV_DULL
+};
+
+int
+aplms_match(struct device *parent, void *match, void *aux)
+{
+ struct aplhidev_attach_args *aa = (struct aplhidev_attach_args *)aux;
+
+ return (aa->aa_reportid == APLHIDEV_TP_DEVICE);
+}
+
+void
+aplms_attach(struct device *parent, struct device *self, void *aux)
+{
+ struct aplms_softc *sc = (struct aplms_softc *)self;
+ struct aplhidev_attach_args *aa = (struct aplhidev_attach_args *)aux;
+ struct hidms *ms = &sc->sc_ms;
+
+ if (hidms_setup(self, ms, 0, APLHIDEV_TP_DEVICE,
+ aa->aa_desc, aa->aa_desclen))
+ return;
+
+ hidms_attach(ms, &aplms_accessops);
+}
+
+void
+aplms_intr(struct device *self, void *report, size_t reportlen)
+{
+ struct aplms_softc *sc = (struct aplms_softc *)self;
+ struct hidms *ms = &sc->sc_ms;
+
+ if (ms->sc_enabled)
+ hidms_input(ms, report, reportlen);
+}
+
+int
+aplms_enable(void *v)
+{
+ struct aplms_softc *sc = v;
+ struct hidms *ms = &sc->sc_ms;
+
+ return hidms_enable(ms);
+}
+
+void
+aplms_disable(void *v)
+{
+ struct aplms_softc *sc = v;
+ struct hidms *ms = &sc->sc_ms;
+
+ hidms_disable(ms);
+}
+
+int
+aplms_ioctl(void *v, u_long cmd, caddr_t data, int flag, struct proc *p)
+{
+ struct aplms_softc *sc = v;
+ struct hidms *ms = &sc->sc_ms;
+
+ switch (cmd) {
+ case WSMOUSEIO_GTYPE:
+ *(u_int *)data = WSMOUSE_TYPE_TOUCHPAD;
+ return 0;
+ default:
+ return hidms_ioctl(ms, cmd, data, flag, p);
+ }
+}
+
+#else
+
+void
+aplms_intr(struct device *self, void *report, size_t reportlen)
+{
+}
+
+#endif