Page MenuHomeFreeBSD

D39863.diff
No OneTemporary

D39863.diff

diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -57,6 +57,7 @@
ath_hal.4 \
atkbd.4 \
atkbdc.4 \
+ ${_atopcase.4} \
atp.4 \
${_atf_test_case.4} \
${_atrtc.4} \
@@ -794,6 +795,7 @@
_amdsmn.4= amdsmn.4
_amdtemp.4= amdtemp.4
_asmc.4= asmc.4
+_atopcase.4= atopcase.4
_bxe.4= bxe.4
_bytgpio.4= bytgpio.4
_chvgpio.4= chvgpio.4
diff --git a/share/man/man4/atopcase.4 b/share/man/man4/atopcase.4
new file mode 100644
--- /dev/null
+++ b/share/man/man4/atopcase.4
@@ -0,0 +1,134 @@
+.\" Copyright (c) 2023 Vladimir Kondratyev <wulf@FreeBSD.org>
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd August 17, 2023
+.Dt ATOPCASE 4
+.Os
+.Sh NAME
+.Nm atopcase
+.Nd Apple HID-over-SPI transport driver
+.Sh SYNOPSIS
+To compile this driver into the kernel,
+place the following lines in your
+kernel configuration file:
+.Bd -ragged -offset indent
+.Cd "device atopcase"
+.Cd "device intelspi"
+.Cd "device spibus"
+.Cd "device hidbus"
+.Cd "device hkbd"
+.Ed
+.Pp
+Alternatively, to load the driver as a
+module at boot time, place the following line in
+.Xr loader.conf 5 :
+.Bd -literal -offset indent
+atopcase_load="YES"
+hkbd_load="YES"
+.Ed
+.Sh DESCRIPTION
+The
+.Nm
+driver provides support for Human Interface Devices (HID) on
+Serial Peripheral Interface (SPI) buses on Apple Intel Macs.
+.Sh HARDWARE
+The
+.Nm
+driver supports the following MacBooks produced in 2015-2018 years:
+.Pp
+.Bl -bullet -compact
+.It
+Macbook8,1
+.It
+Macbook9,1
+.It
+Macbook10,1
+.It
+MacbookPro11,4
+.It
+MacbookPro12,1
+.It
+MacbookPro13,1
+.It
+MacbookPro13,2
+.It
+MacbookPro13,3
+.It
+MacbookPro14,1
+.It
+MacbookPro14,2
+.It
+MacbookPro14,3
+.El
+.Sh SYSCTL VARIABLES
+The following variables are available as both
+.Xr sysctl 8
+variables and
+.Xr loader 8
+tunables:
+.Bl -tag -width indent
+.It Va hw.hid.atopcase.debug
+Debug output level, where 0 is debugging disabled and larger values increase
+debug message verbosity.
+Default is 0.
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /dev/backlight/atopcase0" -compact
+.It Pa /dev/backlight/atopcase0
+Keyboard
+.Xr backlight 8
+device node.
+.El
+.Sh SEE ALSO
+.Xr acpi 4 ,
+.Xr backlight 8 ,
+.Xr loader 8 ,
+.Xr loader.conf 5 .
+.Sh HISTORY
+The
+.Nm
+driver first appeared in
+.Fx 14.0.
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+driver was originally written by
+.An Val Packett Aq Mt val@packett.cool
+and marginally improved upon by
+.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org .
+.Pp
+This manual page was written by
+.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org .
+.Sh BUGS
+Device interrupts are not acknowledged on some hardware that results in
+interrupt storm.
+Installation of Darwin OSI in
+.Xr acpi 4
+driver fixes the issue.
+To install Darwin OSI add following lines to
+.Xr loader.conf 5 :
+.Bl -tag -width indent
+.It Va hw.acpi.install_interface="Darwin"
+.It Va hw.acpi.remove_interface="Windows 2009, Windows 2012"
+.El
diff --git a/sys/conf/files.x86 b/sys/conf/files.x86
--- a/sys/conf/files.x86
+++ b/sys/conf/files.x86
@@ -69,6 +69,8 @@
dev/atkbdc/atkbdc_isa.c optional atkbdc isa
dev/atkbdc/atkbdc_subr.c optional atkbdc
dev/atkbdc/psm.c optional psm atkbdc
+dev/atopcase/atopcase.c optional atopcase acpi hid spibus
+dev/atopcase/atopcase_acpi.c optional atopcase acpi hid spibus
dev/bxe/bxe.c optional bxe pci
dev/bxe/bxe_stats.c optional bxe pci
dev/bxe/bxe_debug.c optional bxe pci
diff --git a/sys/dev/atopcase/atopcase.c b/sys/dev/atopcase/atopcase.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/atopcase/atopcase.c
@@ -0,0 +1,722 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2021-2023 Val Packett <val@packett.cool>
+ * Copyright (c) 2023 Vladimir Kondratyev <wulf@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "opt_hid.h"
+#include "opt_spi.h"
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/crc16.h>
+#include <sys/endian.h>
+#include <sys/kdb.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/mutex.h>
+#include <sys/module.h>
+#include <sys/proc.h>
+#include <sys/rman.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/taskqueue.h>
+
+#include <dev/backlight/backlight.h>
+
+#include <dev/evdev/input.h>
+
+#define HID_DEBUG_VAR atopcase_debug
+#include <dev/hid/hid.h>
+#include <dev/hid/hidquirk.h>
+
+#include <dev/spibus/spi.h>
+#include <dev/spibus/spibusvar.h>
+
+#include "spibus_if.h"
+
+#include "atopcase_reg.h"
+#include "atopcase_var.h"
+
+#define ATOPCASE_IN_KDB() (SCHEDULER_STOPPED() || kdb_active)
+#define ATOPCASE_IN_POLLING_MODE(sc) \
+ (((sc)->sc_gpe_bit == 0 && ((sc)->sc_irq_ih == NULL)) || cold ||\
+ ATOPCASE_IN_KDB())
+#define ATOPCASE_WAKEUP(sc, chan) do { \
+ if (!ATOPCASE_IN_POLLING_MODE(sc)) { \
+ DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "wakeup: %p\n", chan); \
+ wakeup(chan); \
+ } \
+} while (0)
+#define ATOPCASE_SPI_PAUSE() DELAY(100)
+#define ATOPCASE_SPI_NO_SLEEP_FLAG(sc) \
+ ((sc)->sc_irq_ih != NULL ? SPI_FLAG_NO_SLEEP : 0)
+
+/* Tunables */
+static SYSCTL_NODE(_hw_hid, OID_AUTO, atopcase, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "Apple MacBook Topcase HID driver");
+
+#ifdef HID_DEBUG
+enum atopcase_log_level atopcase_debug = ATOPCASE_LLEVEL_DISABLED;
+
+SYSCTL_INT(_hw_hid_atopcase, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &atopcase_debug, ATOPCASE_LLEVEL_DISABLED, "atopcase log level");
+#endif /* !HID_DEBUG */
+
+static const uint8_t booted[] = { 0xa0, 0x80, 0x00, 0x00 };
+static const uint8_t status_ok[] = { 0xac, 0x27, 0x68, 0xd5 };
+
+static inline struct atopcase_child *
+atopcase_get_child_by_device(struct atopcase_softc *sc, uint8_t device)
+{
+ switch (device) {
+ case ATOPCASE_DEV_KBRD:
+ return (&sc->sc_kb);
+ case ATOPCASE_DEV_TPAD:
+ return (&sc->sc_tp);
+ default:
+ return (NULL);
+ }
+}
+
+static int
+atopcase_receive_status(struct atopcase_softc *sc)
+{
+ struct spi_command cmd = SPI_COMMAND_INITIALIZER;
+ uint8_t dummy_buffer[4] = { 0 };
+ uint8_t status_buffer[4] = { 0 };
+ int err;
+
+ cmd.tx_cmd = dummy_buffer;
+ cmd.tx_cmd_sz = sizeof(dummy_buffer);
+ cmd.rx_cmd = status_buffer;
+ cmd.rx_cmd_sz = sizeof(status_buffer);
+ cmd.flags = ATOPCASE_SPI_NO_SLEEP_FLAG(sc);
+
+ err = SPIBUS_TRANSFER(device_get_parent(sc->sc_dev), sc->sc_dev, &cmd);
+ ATOPCASE_SPI_PAUSE();
+ if (err) {
+ device_printf(sc->sc_dev, "SPI error: %d\n", err);
+ return (err);
+ }
+
+ DPRINTFN(ATOPCASE_LLEVEL_TRACE, "Status: %*D\n", 4, status_buffer, " ");
+
+ if (memcmp(status_buffer, status_ok, sizeof(status_ok)) == 0) {
+ DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "Wrote command\n");
+ ATOPCASE_WAKEUP(sc, sc->sc_dev);
+ } else {
+ device_printf(sc->sc_dev, "Failed to write command\n");
+ return (EIO);
+ }
+
+ return (0);
+}
+
+static int
+atopcase_process_message(struct atopcase_softc *sc, uint8_t device, void *msg,
+ uint16_t msg_len)
+{
+ struct atopcase_header *hdr = msg;
+ struct atopcase_child *ac;
+ void *payload;
+ uint16_t pl_len, crc;
+
+ payload = (uint8_t *)msg + sizeof(*hdr);
+ pl_len = le16toh(hdr->len);
+
+ if (pl_len + sizeof(*hdr) + sizeof(crc) != msg_len) {
+ DPRINTFN(ATOPCASE_LLEVEL_DEBUG,
+ "message with length overflow\n");
+ return (EIO);
+ }
+
+ crc = le16toh(*(uint16_t *)((uint8_t *)payload + pl_len));
+ if (crc != crc16(0, msg, msg_len - sizeof(crc))) {
+ DPRINTFN(ATOPCASE_LLEVEL_DEBUG,
+ "message with failed checksum\n");
+ return (EIO);
+ }
+
+#define CPOFF(dst, len, off) do { \
+ unsigned _len = le16toh(len); \
+ unsigned _off = le16toh(off); \
+ if (pl_len >= _len + _off) { \
+ memcpy(dst, (uint8_t*)payload + _off, MIN(_len, sizeof(dst)));\
+ (dst)[MIN(_len, sizeof(dst) - 1)] = '\0'; \
+ }} while (0);
+
+ if ((ac = atopcase_get_child_by_device(sc, device)) != NULL
+ && hdr->type == ATOPCASE_MSG_TYPE_REPORT(device)) {
+ if (ac->open)
+ ac->intr_handler(ac->intr_ctx, payload, pl_len);
+ } else if (device == ATOPCASE_DEV_INFO
+ && hdr->type == ATOPCASE_MSG_TYPE_INFO(ATOPCASE_INFO_IFACE)
+ && (ac = atopcase_get_child_by_device(sc, hdr->type_arg)) != NULL) {
+ struct atopcase_iface_info_payload *iface = payload;
+ CPOFF(ac->name, iface->name_len, iface->name_off);
+ DPRINTF("Interface #%d name: %s\n", ac->device, ac->name);
+ } else if (device == ATOPCASE_DEV_INFO
+ && hdr->type == ATOPCASE_MSG_TYPE_INFO(ATOPCASE_INFO_DESCRIPTOR)
+ && (ac = atopcase_get_child_by_device(sc, hdr->type_arg)) != NULL) {
+ memcpy(ac->rdesc, payload, pl_len);
+ ac->rdesc_len = ac->hw.rdescsize = pl_len;
+ DPRINTF("%s HID report descriptor: %*D\n", ac->name,
+ (int) ac->hw.rdescsize, ac->rdesc, " ");
+ } else if (device == ATOPCASE_DEV_INFO
+ && hdr->type == ATOPCASE_MSG_TYPE_INFO(ATOPCASE_INFO_DEVICE)
+ && hdr->type_arg == ATOPCASE_INFO_DEVICE) {
+ struct atopcase_device_info_payload *dev = payload;
+ sc->sc_vid = le16toh(dev->vid);
+ sc->sc_pid = le16toh(dev->pid);
+ sc->sc_ver = le16toh(dev->ver);
+ CPOFF(sc->sc_vendor, dev->vendor_len, dev->vendor_off);
+ CPOFF(sc->sc_product, dev->product_len, dev->product_off);
+ CPOFF(sc->sc_serial, dev->serial_len, dev->serial_off);
+ if (bootverbose) {
+ device_printf(sc->sc_dev, "Device info descriptor:\n");
+ printf(" Vendor: %s\n", sc->sc_vendor);
+ printf(" Product: %s\n", sc->sc_product);
+ printf(" Serial: %s\n", sc->sc_serial);
+ }
+ }
+
+ return (0);
+}
+
+int
+atopcase_receive_packet(struct atopcase_softc *sc)
+{
+ struct atopcase_packet pkt = { 0 };
+ struct spi_command cmd = SPI_COMMAND_INITIALIZER;
+ void *msg;
+ int err;
+ uint16_t length, remaining, offset, msg_len;
+
+ bzero(&sc->sc_junk, sizeof(struct atopcase_packet));
+ cmd.tx_cmd = &sc->sc_junk;
+ cmd.tx_cmd_sz = sizeof(struct atopcase_packet);
+ cmd.rx_cmd = &pkt;
+ cmd.rx_cmd_sz = sizeof(struct atopcase_packet);
+ cmd.flags = ATOPCASE_SPI_NO_SLEEP_FLAG(sc);
+ err = SPIBUS_TRANSFER(device_get_parent(sc->sc_dev), sc->sc_dev, &cmd);
+ ATOPCASE_SPI_PAUSE();
+ if (err) {
+ device_printf(sc->sc_dev, "SPI error: %d\n", err);
+ return (err);
+ }
+
+ DPRINTFN(ATOPCASE_LLEVEL_TRACE, "Response: %*D\n", 256, &pkt, " ");
+
+ if (le16toh(pkt.checksum) != crc16(0, &pkt, sizeof(pkt) - 2)) {
+ DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "packet with failed checksum\n");
+ return (EIO);
+ }
+
+ /*
+ * When we poll and nothing has arrived we get a particular packet
+ * starting with '80 11 00 01'
+ */
+ if (pkt.direction == ATOPCASE_DIR_NOTHING) {
+ DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "'Nothing' packet: %*D\n", 4,
+ &pkt, " ");
+ return (EAGAIN);
+ }
+
+ if (pkt.direction != ATOPCASE_DIR_READ &&
+ pkt.direction != ATOPCASE_DIR_WRITE) {
+ DPRINTFN(ATOPCASE_LLEVEL_DEBUG,
+ "unknown message direction 0x%x\n", pkt.direction);
+ return (EIO);
+ }
+
+ length = le16toh(pkt.length);
+ remaining = le16toh(pkt.remaining);
+ offset = le16toh(pkt.offset);
+
+ if (length > sizeof(pkt.data)) {
+ DPRINTFN(ATOPCASE_LLEVEL_DEBUG,
+ "packet with length overflow: %u\n", length);
+ return (EIO);
+ }
+
+ if (pkt.direction == ATOPCASE_DIR_READ &&
+ pkt.device == ATOPCASE_DEV_INFO &&
+ length == sizeof(booted) &&
+ memcmp(pkt.data, booted, length) == 0) {
+ DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "GPE boot packet\n");
+ sc->sc_booted = true;
+ ATOPCASE_WAKEUP(sc, sc);
+ return (0);
+ }
+
+ /* handle multi-packet messages */
+ if (remaining != 0 || offset != 0) {
+ if (offset != sc->sc_msg_len) {
+ DPRINTFN(ATOPCASE_LLEVEL_DEBUG,
+ "Unexpected offset (got %u, expected %u)\n",
+ offset, sc->sc_msg_len);
+ sc->sc_msg_len = 0;
+ return (EIO);
+ }
+
+ if ((size_t)remaining + length + offset > sizeof(sc->sc_msg)) {
+ DPRINTFN(ATOPCASE_LLEVEL_DEBUG,
+ "Message with length overflow: %zu\n",
+ (size_t)remaining + length + offset);
+ sc->sc_msg_len = 0;
+ return (EIO);
+ }
+
+ memcpy(sc->sc_msg + offset, &pkt.data, length);
+ sc->sc_msg_len += length;
+
+ if (remaining != 0)
+ return (0);
+
+ msg = sc->sc_msg;
+ msg_len = sc->sc_msg_len;
+ } else {
+ msg = pkt.data;
+ msg_len = length;
+ }
+ sc->sc_msg_len = 0;
+
+ err = atopcase_process_message(sc, pkt.device, msg, msg_len);
+ if (err == 0 && pkt.direction == ATOPCASE_DIR_WRITE) {
+ DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "Write ack\n");
+ ATOPCASE_WAKEUP(sc, sc);
+ }
+
+ return (err);
+}
+
+static int
+atopcase_send(struct atopcase_softc *sc, struct atopcase_packet *pkt)
+{
+ struct spi_command cmd = SPI_COMMAND_INITIALIZER;
+ int err, retries;
+
+ cmd.tx_cmd = pkt;
+ cmd.tx_cmd_sz = sizeof(struct atopcase_packet);
+ cmd.rx_cmd = &sc->sc_junk;
+ cmd.rx_cmd_sz = sizeof(struct atopcase_packet);
+ cmd.flags = SPI_FLAG_KEEP_CS | ATOPCASE_SPI_NO_SLEEP_FLAG(sc);
+
+ DPRINTFN(ATOPCASE_LLEVEL_TRACE, "Request: %*D\n",
+ (int)sizeof(struct atopcase_packet), cmd.tx_cmd, " ");
+
+ if (!ATOPCASE_IN_POLLING_MODE(sc)) {
+ if (sc->sc_irq_ih != NULL)
+ mtx_lock(&sc->sc_mtx);
+ else
+ sx_xlock(&sc->sc_sx);
+ }
+ sc->sc_wait_for_status = true;
+ err = SPIBUS_TRANSFER(device_get_parent(sc->sc_dev), sc->sc_dev, &cmd);
+ ATOPCASE_SPI_PAUSE();
+ if (!ATOPCASE_IN_POLLING_MODE(sc)) {
+ if (sc->sc_irq_ih != NULL)
+ mtx_unlock(&sc->sc_mtx);
+ else
+ sx_xunlock(&sc->sc_sx);
+ }
+ if (err != 0) {
+ device_printf(sc->sc_dev, "SPI error: %d\n", err);
+ goto exit;
+ }
+
+ if (ATOPCASE_IN_POLLING_MODE(sc)) {
+ err = atopcase_receive_status(sc);
+ } else {
+ DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "wait for: %p\n", sc->sc_dev);
+ err = tsleep(sc->sc_dev, 0, "atcstat", hz / 10);
+ }
+ sc->sc_wait_for_status = false;
+ if (err != 0) {
+ DPRINTF("Write status read failed: %d\n", err);
+ goto exit;
+ }
+
+ if (ATOPCASE_IN_POLLING_MODE(sc)) {
+ /* Backlight setting may require a lot of time */
+ retries = 20;
+ while ((err = atopcase_receive_packet(sc)) == EAGAIN &&
+ --retries != 0)
+ DELAY(1000);
+ } else {
+ DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "wait for: %p\n", sc);
+ err = tsleep(sc, 0, "atcack", hz / 10);
+ }
+ if (err != 0)
+ DPRINTF("Write ack read failed: %d\n", err);
+
+exit:
+ if (err == EWOULDBLOCK)
+ err = EIO;
+
+ return (err);
+}
+
+static void
+atopcase_create_message(struct atopcase_packet *pkt, uint8_t device,
+ uint16_t type, uint8_t type_arg, const void *payload, uint8_t len,
+ uint16_t resp_len)
+{
+ struct atopcase_header *hdr = (struct atopcase_header *)pkt->data;
+ uint16_t msg_checksum;
+ static uint8_t seq_no;
+
+ KASSERT(len <= ATOPCASE_DATA_SIZE - sizeof(struct atopcase_header),
+ ("outgoing msg must be 1 packet"));
+
+ bzero(pkt, sizeof(struct atopcase_packet));
+ pkt->direction = ATOPCASE_DIR_WRITE;
+ pkt->device = device;
+ pkt->length = htole16(sizeof(*hdr) + len + 2);
+
+ hdr->type = htole16(type);
+ hdr->type_arg = type_arg;
+ hdr->seq_no = seq_no++;
+ hdr->resp_len = htole16((resp_len == 0) ? len : resp_len);
+ hdr->len = htole16(len);
+
+ memcpy(pkt->data + sizeof(*hdr), payload, len);
+ msg_checksum = htole16(crc16(0, pkt->data, pkt->length - 2));
+ memcpy(pkt->data + sizeof(*hdr) + len, &msg_checksum, 2);
+ pkt->checksum = htole16(crc16(0, (uint8_t*)pkt, sizeof(*pkt) - 2));
+
+ return;
+}
+
+static int
+atopcase_request_desc(struct atopcase_softc *sc, uint16_t type, uint8_t device)
+{
+ atopcase_create_message(
+ &sc->sc_buf, ATOPCASE_DEV_INFO, type, device, NULL, 0, 0x200);
+ return (atopcase_send(sc, &sc->sc_buf));
+}
+
+int
+atopcase_intr(struct atopcase_softc *sc)
+{
+ int err;
+
+ DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "Interrupt event\n");
+
+ if (sc->sc_wait_for_status) {
+ err = atopcase_receive_status(sc);
+ sc->sc_wait_for_status = false;
+ } else
+ err = atopcase_receive_packet(sc);
+
+ return (err);
+}
+
+static int
+atopcase_add_child(struct atopcase_softc *sc, struct atopcase_child *ac,
+ uint8_t device)
+{
+ device_t hidbus;
+ int err = 0;
+
+ ac->device = device;
+
+ /* fill device info */
+ strlcpy(ac->hw.name, "Apple MacBook", sizeof(ac->hw.name));
+ ac->hw.idBus = BUS_SPI;
+ ac->hw.idVendor = sc->sc_vid;
+ ac->hw.idProduct = sc->sc_pid;
+ ac->hw.idVersion = sc->sc_ver;
+ strlcpy(ac->hw.idPnP, sc->sc_hid, sizeof(ac->hw.idPnP));
+ strlcpy(ac->hw.serial, sc->sc_serial, sizeof(ac->hw.serial));
+ /*
+ * HID write and set_report methods executed on Apple SPI topcase
+ * hardware do the same request on SPI layer. Set HQ_NOWRITE quirk to
+ * force hidmap to convert writes to set_reports. That makes HID bus
+ * write handler unnecessary and reduces code duplication.
+ */
+ hid_add_dynamic_quirk(&ac->hw, HQ_NOWRITE);
+
+ DPRINTF("Get the interface #%d descriptor\n", device);
+ err = atopcase_request_desc(sc,
+ ATOPCASE_MSG_TYPE_INFO(ATOPCASE_INFO_IFACE), device);
+ if (err) {
+ device_printf(sc->sc_dev, "can't receive iface descriptor\n");
+ goto exit;
+ }
+
+ DPRINTF("Get the \"%s\" HID report descriptor\n", ac->name);
+ err = atopcase_request_desc(sc,
+ ATOPCASE_MSG_TYPE_INFO(ATOPCASE_INFO_DESCRIPTOR), device);
+ if (err) {
+ device_printf(sc->sc_dev, "can't receive report descriptor\n");
+ goto exit;
+ }
+
+ hidbus = device_add_child(sc->sc_dev, "hidbus", -1);
+ if (hidbus == NULL) {
+ device_printf(sc->sc_dev, "can't add child\n");
+ err = ENOMEM;
+ goto exit;
+ }
+ device_set_ivars(hidbus, &ac->hw);
+ ac->hidbus = hidbus;
+
+exit:
+ return (err);
+}
+
+int
+atopcase_init(struct atopcase_softc *sc)
+{
+ int err;
+
+ /* Wait until we know we're getting reasonable responses */
+ if(!sc->sc_booted && tsleep(sc, 0, "atcboot", hz / 20) != 0) {
+ device_printf(sc->sc_dev, "can't establish communication\n");
+ err = EIO;
+ goto err;
+ }
+
+ /*
+ * Management device may send a message on first boot after power off.
+ * Let interrupt handler to read and discard it.
+ */
+ DELAY(2000);
+
+ DPRINTF("Get the device descriptor\n");
+ err = atopcase_request_desc(sc,
+ ATOPCASE_MSG_TYPE_INFO(ATOPCASE_INFO_DEVICE),
+ ATOPCASE_INFO_DEVICE);
+ if (err) {
+ device_printf(sc->sc_dev, "can't receive device descriptor\n");
+ goto err;
+ }
+
+ err = atopcase_add_child(sc, &sc->sc_kb, ATOPCASE_DEV_KBRD);
+ if (err != 0)
+ goto err;
+ err = atopcase_add_child(sc, &sc->sc_tp, ATOPCASE_DEV_TPAD);
+ if (err != 0)
+ goto err;
+
+ /* TODO: skip on 2015 models where it's controlled by asmc */
+ sc->sc_backlight = backlight_register("atopcase", sc->sc_dev);
+ if (!sc->sc_backlight) {
+ device_printf(sc->sc_dev, "can't register backlight\n");
+ err = ENOMEM;
+ }
+
+ if (sc->sc_tq != NULL)
+ taskqueue_enqueue_timeout(sc->sc_tq, &sc->sc_task, hz / 120);
+
+ return (bus_generic_attach(sc->sc_dev));
+
+err:
+ return (err);
+}
+
+int
+atopcase_destroy(struct atopcase_softc *sc)
+{
+ int err;
+
+ err = device_delete_children(sc->sc_dev);
+ if (err)
+ return (err);
+
+ if (sc->sc_backlight)
+ backlight_destroy(sc->sc_backlight);
+
+ return (0);
+}
+
+static struct atopcase_child *
+atopcase_get_child_by_hidbus(device_t child)
+{
+ device_t parent = device_get_parent(child);
+ struct atopcase_softc *sc = device_get_softc(parent);
+
+ if (child == sc->sc_kb.hidbus)
+ return (&sc->sc_kb);
+ if (child == sc->sc_tp.hidbus)
+ return (&sc->sc_tp);
+ panic("unknown child");
+}
+
+void
+atopcase_intr_setup(device_t dev, device_t child, hid_intr_t intr,
+ void *context, struct hid_rdesc_info *rdesc)
+{
+ struct atopcase_child *ac = atopcase_get_child_by_hidbus(child);
+
+ if (intr == NULL)
+ return;
+
+ rdesc->rdsize = ATOPCASE_MSG_SIZE - sizeof(struct atopcase_header) - 2;
+ rdesc->grsize = 0;
+ rdesc->srsize = ATOPCASE_DATA_SIZE - sizeof(struct atopcase_header) - 2;
+ rdesc->wrsize = 0;
+
+ ac->intr_handler = intr;
+ ac->intr_ctx = context;
+}
+
+void
+atopcase_intr_unsetup(device_t dev, device_t child)
+{
+}
+
+int
+atopcase_intr_start(device_t dev, device_t child)
+{
+ struct atopcase_softc *sc = device_get_softc(dev);
+ struct atopcase_child *ac = atopcase_get_child_by_hidbus(child);
+
+ if (ATOPCASE_IN_POLLING_MODE(sc))
+ sx_xlock(&sc->sc_write_sx);
+ else if (sc->sc_irq_ih != NULL)
+ mtx_lock(&sc->sc_mtx);
+ else
+ sx_xlock(&sc->sc_sx);
+ ac->open = true;
+ if (ATOPCASE_IN_POLLING_MODE(sc))
+ sx_xunlock(&sc->sc_write_sx);
+ else if (sc->sc_irq_ih != NULL)
+ mtx_unlock(&sc->sc_mtx);
+ else
+ sx_xunlock(&sc->sc_sx);
+
+ return (0);
+}
+
+int
+atopcase_intr_stop(device_t dev, device_t child)
+{
+ struct atopcase_softc *sc = device_get_softc(dev);
+ struct atopcase_child *ac = atopcase_get_child_by_hidbus(child);
+
+ if (ATOPCASE_IN_POLLING_MODE(sc))
+ sx_xlock(&sc->sc_write_sx);
+ else if (sc->sc_irq_ih != NULL)
+ mtx_lock(&sc->sc_mtx);
+ else
+ sx_xlock(&sc->sc_sx);
+ ac->open = false;
+ if (ATOPCASE_IN_POLLING_MODE(sc))
+ sx_xunlock(&sc->sc_write_sx);
+ else if (sc->sc_irq_ih != NULL)
+ mtx_unlock(&sc->sc_mtx);
+ else
+ sx_xunlock(&sc->sc_sx);
+
+ return (0);
+}
+
+void
+atopcase_intr_poll(device_t dev, device_t child)
+{
+ struct atopcase_softc *sc = device_get_softc(dev);
+
+ (void)atopcase_receive_packet(sc);
+}
+
+int
+atopcase_get_rdesc(device_t dev, device_t child, void *buf, hid_size_t len)
+{
+ struct atopcase_child *ac = atopcase_get_child_by_hidbus(child);
+
+ if (ac->rdesc_len != len)
+ return (ENXIO);
+ memcpy(buf, ac->rdesc, len);
+
+ return (0);
+}
+
+int
+atopcase_set_report(device_t dev, device_t child, const void *buf,
+ hid_size_t len, uint8_t type __unused, uint8_t id)
+{
+ struct atopcase_softc *sc = device_get_softc(dev);
+ struct atopcase_child *ac = atopcase_get_child_by_hidbus(child);
+ int err;
+
+ if (len >= ATOPCASE_DATA_SIZE - sizeof(struct atopcase_header) - 2)
+ return (EINVAL);
+
+ DPRINTF("%s HID command SET_REPORT %d (len %d): %*D\n",
+ ac->name, id, len, len, buf, " ");
+
+ if (!ATOPCASE_IN_KDB())
+ sx_xlock(&sc->sc_write_sx);
+ atopcase_create_message(&sc->sc_buf, ac->device,
+ ATOPCASE_MSG_TYPE_SET_REPORT(ac->device, id), 0, buf, len, 0);
+ err = atopcase_send(sc, &sc->sc_buf);
+ if (!ATOPCASE_IN_KDB())
+ sx_xunlock(&sc->sc_write_sx);
+
+ return (err);
+}
+
+int
+atopcase_backlight_update_status(device_t dev, struct backlight_props *props)
+{
+ struct atopcase_softc *sc = device_get_softc(dev);
+ struct atopcase_bl_payload payload = { 0 };
+
+ payload.report_id = ATOPCASE_BKL_REPORT_ID;
+ payload.device = ATOPCASE_DEV_KBRD;
+ /*
+ * Hardware range is 32-255 for visible backlight,
+ * convert from percentages
+ */
+ payload.level = (props->brightness == 0) ? 0 :
+ (32 + (223 * props->brightness / 100));
+ payload.status = (payload.level > 0) ? 0x01F4 : 0x1;
+
+ return (atopcase_set_report(dev, sc->sc_kb.hidbus, &payload,
+ sizeof(payload), HID_OUTPUT_REPORT, ATOPCASE_BKL_REPORT_ID));
+}
+
+int
+atopcase_backlight_get_status(device_t dev, struct backlight_props *props)
+{
+ struct atopcase_softc *sc = device_get_softc(dev);
+
+ props->brightness = sc->sc_backlight_level;
+ props->nlevels = 0;
+
+ return (0);
+}
+
+int
+atopcase_backlight_get_info(device_t dev, struct backlight_info *info)
+{
+ info->type = BACKLIGHT_TYPE_KEYBOARD;
+ strlcpy(info->name, "Apple MacBook Keyboard", BACKLIGHTMAXNAMELENGTH);
+
+ return (0);
+}
diff --git a/sys/dev/atopcase/atopcase_acpi.c b/sys/dev/atopcase/atopcase_acpi.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/atopcase/atopcase_acpi.c
@@ -0,0 +1,458 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2021-2023 Val Packett <val@packett.cool>
+ * Copyright (c) 2023 Vladimir Kondratyev <wulf@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "opt_acpi.h"
+#include "opt_hid.h"
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/mutex.h>
+#include <sys/module.h>
+#include <sys/rman.h>
+#include <sys/sx.h>
+#include <sys/taskqueue.h>
+
+#include <contrib/dev/acpica/include/acpi.h>
+#include <contrib/dev/acpica/include/accommon.h>
+#include <contrib/dev/acpica/include/acevents.h>
+#include <dev/acpica/acpivar.h>
+#include <dev/acpica/acpiio.h>
+
+#include <dev/backlight/backlight.h>
+
+#include <dev/evdev/input.h>
+
+#define HID_DEBUG_VAR atopcase_debug
+#include <dev/hid/hid.h>
+#include <dev/hid/hidquirk.h>
+
+#include <dev/spibus/spi.h>
+#include <dev/spibus/spibusvar.h>
+
+#include "backlight_if.h"
+#include "hid_if.h"
+
+#include "atopcase_reg.h"
+#include "atopcase_var.h"
+
+/*
+ * XXX: The Linux driver only supports ACPI GPEs, but we only receive
+ * interrupts in this driver on a MacBookPro 12,1 and 14,1. This is because
+ * Linux responds to _OSI("Darwin") while we don't!
+ *
+ * ACPI GPE is enabled on FreeBSD by addition of following lines to
+ * /boot/loader.conf:
+ * hw.acpi.install_interface="Darwin"
+ * hw.acpi.remove_interface="Windows 2009, Windows 2012"
+ */
+
+static const char *atopcase_ids[] = { "APP000D", NULL };
+
+static device_probe_t atopcase_acpi_probe;
+static device_attach_t atopcase_acpi_attach;
+static device_detach_t atopcase_acpi_detach;
+static device_suspend_t atopcase_acpi_suspend;
+static device_resume_t atopcase_acpi_resume;
+
+static bool
+acpi_is_atopcase(ACPI_HANDLE handle)
+{
+ const char **ids;
+ UINT32 sta;
+
+ for (ids = atopcase_ids; *ids != NULL; ids++) {
+ if (acpi_MatchHid(handle, *ids))
+ break;
+ }
+ if (*ids == NULL)
+ return (false);
+
+ /*
+ * If no _STA method or if it failed, then assume that
+ * the device is present.
+ */
+ if (ACPI_FAILURE(acpi_GetInteger(handle, "_STA", &sta)) ||
+ ACPI_DEVICE_PRESENT(sta))
+ return (true);
+
+ return (false);
+}
+
+static int
+atopcase_acpi_set_comm_enabled(struct atopcase_softc *sc, char *prop,
+ const bool on)
+{
+ ACPI_OBJECT argobj;
+ ACPI_OBJECT_LIST args;
+
+ argobj.Type = ACPI_TYPE_INTEGER;
+ argobj.Integer.Value = on;
+ args.Count = 1;
+ args.Pointer = &argobj;
+
+ if (ACPI_FAILURE(
+ AcpiEvaluateObject(sc->sc_handle, prop, &args, NULL)))
+ return (ENXIO);
+
+ DELAY(100);
+
+ return (0);
+}
+
+static int
+atopcase_acpi_test_comm_enabled(ACPI_HANDLE handle, char *prop, int *enabled)
+{
+ if (ACPI_FAILURE(acpi_GetInteger(handle, prop, enabled)))
+ return (ENXIO);
+
+ return (0);
+}
+
+static void
+atopcase_acpi_task(void *ctx, int pending __unused)
+{
+ struct atopcase_softc *sc = ctx;
+ int err = EAGAIN;
+
+ DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "Timer event\n");
+
+ sx_xlock(&sc->sc_write_sx);
+ err = atopcase_receive_packet(sc);
+ sx_xunlock(&sc->sc_write_sx);
+
+ /* Rearm timer */
+ taskqueue_enqueue_timeout(sc->sc_tq, &sc->sc_task,
+ hz / (err == EAGAIN ? 10 : 120));
+}
+
+static void
+atopcase_acpi_gpe_task(void *ctx)
+{
+ struct atopcase_softc *sc = ctx;
+
+ DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "GPE event\n");
+
+ sx_xlock(&sc->sc_sx);
+ (void)atopcase_intr(sc);
+ sx_xunlock(&sc->sc_sx);
+
+ /* Rearm GPE */
+ if (ACPI_FAILURE(AcpiFinishGpe(NULL, sc->sc_gpe_bit)))
+ device_printf(sc->sc_dev, "GPE rearm failed\n");
+}
+
+static UINT32
+atopcase_acpi_notify(ACPI_HANDLE h __unused, UINT32 notify __unused, void *ctx)
+{
+ AcpiOsExecute(OSL_GPE_HANDLER, atopcase_acpi_gpe_task, ctx);
+ return (0);
+}
+
+static void
+atopcase_acpi_intr(void *ctx)
+{
+ struct atopcase_softc *sc = ctx;
+
+ DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "Interrupt event\n");
+
+ mtx_lock(&sc->sc_mtx);
+ sc->sc_intr_cnt++;
+ (void)atopcase_intr(sc);
+ mtx_unlock(&sc->sc_mtx);
+}
+
+static int
+atopcase_acpi_probe(device_t dev)
+{
+ ACPI_HANDLE handle;
+ int usb_enabled;
+
+ if (acpi_disabled("atopcase"))
+ return (ENXIO);
+
+ handle = acpi_get_handle(dev);
+ if (handle == NULL)
+ return (ENXIO);
+
+ if (!acpi_is_atopcase(handle))
+ return (ENXIO);
+
+ /* If USB interface exists and is enabled, use USB driver */
+ if (atopcase_acpi_test_comm_enabled(handle, "UIST", &usb_enabled) == 0
+ && usb_enabled != 0)
+ return (ENXIO);
+
+ device_set_desc(dev, "Apple MacBook SPI Topcase");
+
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+atopcase_acpi_attach(device_t dev)
+{
+ struct atopcase_softc *sc = device_get_softc(dev);
+ ACPI_DEVICE_INFO *device_info;
+ uint32_t cs_delay;
+ int spi_enabled, err;
+
+ sc->sc_dev = dev;
+ sc->sc_handle = acpi_get_handle(dev);
+
+ if (atopcase_acpi_test_comm_enabled(sc->sc_handle, "SIST",
+ &spi_enabled) != 0) {
+ device_printf(dev, "can't test SPI communication\n");
+ return (ENXIO);
+ }
+
+ /* Turn SPI off if enabled to force "booted" packet to appear */
+ if (spi_enabled != 0 &&
+ atopcase_acpi_set_comm_enabled(sc, "SIEN", false) != 0) {
+ device_printf(dev, "can't disable SPI communication\n");
+ return (ENXIO);
+ }
+
+ if (atopcase_acpi_set_comm_enabled(sc, "SIEN", true) != 0) {
+ device_printf(dev, "can't enable SPI communication\n");
+ return (ENXIO);
+ }
+
+ /*
+ * Apple encodes a CS delay in ACPI properties, but
+ * - they're encoded in a non-standard way that predates _DSD, and
+ * - they're only exported if you respond to _OSI(Darwin) which we don't
+ * - because that has more side effects than we're prepared to handle
+ * - Apple makes a Windows driver and Windows is not Darwin
+ * - so presumably that one uses hardcoded values too
+ */
+ spibus_get_cs_delay(sc->sc_dev, &cs_delay);
+ if (cs_delay == 0)
+ spibus_set_cs_delay(sc->sc_dev, 10);
+
+ /* Retrieve ACPI _HID */
+ if (ACPI_FAILURE(AcpiGetObjectInfo(sc->sc_handle, &device_info)))
+ return (ENXIO);
+ if (device_info->Valid & ACPI_VALID_HID)
+ strlcpy(sc->sc_hid, device_info->HardwareId.String,
+ sizeof(sc->sc_hid));
+ AcpiOsFree(device_info);
+
+ sx_init(&sc->sc_write_sx, "atc_wr");
+ sx_init(&sc->sc_sx, "atc_sx");
+ mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
+
+ err = ENXIO;
+ sc->sc_irq_res = bus_alloc_resource_any(sc->sc_dev,
+ SYS_RES_IRQ, &sc->sc_irq_rid, RF_ACTIVE);
+ if (sc->sc_irq_res != NULL) {
+ if (bus_setup_intr(dev, sc->sc_irq_res,
+ INTR_TYPE_MISC | INTR_MPSAFE, NULL,
+ atopcase_acpi_intr, sc, &sc->sc_irq_ih) != 0) {
+ device_printf(dev, "can't setup interrupt handler\n");
+ goto err;
+ }
+ device_printf(dev, "Using interrupts.\n");
+ /*
+ * On some hardware interrupts are not acked by SPI read for
+ * unknown reasons that leads to interrupt storm due to level
+ * triggering. GPE does not suffer from this problem.
+ *
+ * TODO: Find out what Windows driver does to ack IRQ.
+ */
+ pause("atopcase", hz / 5);
+ DPRINTF("interrupts asserted: %u\n", sc->sc_intr_cnt);
+ if (sc->sc_intr_cnt > 2 || sc->sc_intr_cnt == 0) {
+ bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_irq_ih);
+ sc->sc_irq_ih = NULL;
+ device_printf(dev, "Interrupt storm detected. "
+ "Falling back to polling\n");
+ sc->sc_tq = taskqueue_create("atc_tq", M_WAITOK|M_ZERO,
+ taskqueue_thread_enqueue, &sc->sc_tq);
+ TIMEOUT_TASK_INIT(sc->sc_tq, &sc->sc_task, 0,
+ atopcase_acpi_task, sc);
+ taskqueue_start_threads(&sc->sc_tq, 1, PI_TTY,
+ "%s taskq", device_get_nameunit(dev));
+ }
+ /*
+ * Interrupts does not work at all. It may happen if kernel
+ * erroneously detected stray irq at bus_teardown_intr() and
+ * completelly disabled it after than.
+ * Fetch "booted" packet manually to pass communication check.
+ */
+ if (sc->sc_intr_cnt == 0)
+ atopcase_receive_packet(sc);
+ } else {
+ if (bootverbose)
+ device_printf(dev, "can't allocate IRQ resource\n");
+ if (ACPI_FAILURE(acpi_GetInteger(sc->sc_handle, "_GPE",
+ &sc->sc_gpe_bit))) {
+ device_printf(dev, "can't allocate nor IRQ nor GPE\n");
+ goto err;
+ }
+ if (ACPI_FAILURE(AcpiInstallGpeHandler(NULL, sc->sc_gpe_bit,
+ ACPI_GPE_LEVEL_TRIGGERED, atopcase_acpi_notify, sc))) {
+ device_printf(dev, "can't install ACPI GPE handler\n");
+ goto err;
+ }
+ if (ACPI_FAILURE(AcpiEnableGpe(NULL, sc->sc_gpe_bit))) {
+ device_printf(dev, "can't enable ACPI notification\n");
+ goto err;
+ }
+ device_printf(dev, "Using ACPI GPE.\n");
+ if (bootverbose)
+ device_printf(dev, "GPE int %d\n", sc->sc_gpe_bit);
+ }
+
+ err = atopcase_init(sc);
+
+err:
+ if (err != 0)
+ atopcase_acpi_detach(dev);
+ return (err);
+}
+
+static int
+atopcase_acpi_detach(device_t dev)
+{
+ struct atopcase_softc *sc = device_get_softc(dev);
+ int err;
+
+ err = atopcase_destroy(sc);
+ if (err != 0)
+ return (err);
+
+ if (sc->sc_irq_ih)
+ bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_irq_ih);
+ if (sc->sc_irq_res != NULL)
+ bus_release_resource(dev, SYS_RES_IRQ,
+ sc->sc_irq_rid, sc->sc_irq_res);
+
+ if (sc->sc_tq != NULL) {
+ while (taskqueue_cancel_timeout(sc->sc_tq, &sc->sc_task, NULL))
+ taskqueue_drain_timeout(sc->sc_tq, &sc->sc_task);
+ taskqueue_free(sc->sc_tq);
+ }
+
+ if (sc->sc_gpe_bit != 0 && ACPI_FAILURE(AcpiRemoveGpeHandler(NULL,
+ sc->sc_gpe_bit, atopcase_acpi_notify)))
+ device_printf(dev, "can't remove ACPI GPE handler\n");
+
+ if (atopcase_acpi_set_comm_enabled(sc, "SIEN", false) != 0)
+ device_printf(dev, "can't disable SPI communication\n");
+
+ mtx_destroy(&sc->sc_mtx);
+ sx_destroy(&sc->sc_sx);
+ sx_destroy(&sc->sc_write_sx);
+
+ return (0);
+}
+
+static int
+atopcase_acpi_suspend(device_t dev)
+{
+ struct atopcase_softc *sc = device_get_softc(dev);
+ int err;
+
+ err = bus_generic_suspend(dev);
+ if (err)
+ return (err);
+
+ if (sc->sc_gpe_bit != 0)
+ AcpiDisableGpe(NULL, sc->sc_gpe_bit);
+
+ if (sc->sc_tq != NULL)
+ while (taskqueue_cancel_timeout(sc->sc_tq, &sc->sc_task, NULL))
+ taskqueue_drain_timeout(sc->sc_tq, &sc->sc_task);
+
+ if (atopcase_acpi_set_comm_enabled(sc, "SIEN", false) != 0)
+ device_printf(dev, "can't disable SPI communication\n");
+
+ return (0);
+}
+
+static int
+atopcase_acpi_resume(device_t dev)
+{
+ struct atopcase_softc *sc = device_get_softc(dev);
+
+ if (sc->sc_gpe_bit != 0)
+ AcpiEnableGpe(NULL, sc->sc_gpe_bit);
+
+ if (sc->sc_tq != NULL)
+ taskqueue_enqueue_timeout(sc->sc_tq, &sc->sc_task, hz / 120);
+
+ if (atopcase_acpi_set_comm_enabled(sc, "SIEN", true) != 0) {
+ device_printf(dev, "can't enable SPI communication\n");
+ return (ENXIO);
+ }
+
+ return (bus_generic_resume(dev));
+}
+
+static device_method_t atopcase_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, atopcase_acpi_probe),
+ DEVMETHOD(device_attach, atopcase_acpi_attach),
+ DEVMETHOD(device_detach, atopcase_acpi_detach),
+ DEVMETHOD(device_suspend, atopcase_acpi_suspend),
+ DEVMETHOD(device_resume, atopcase_acpi_resume),
+
+ /* HID interrupt interface */
+ DEVMETHOD(hid_intr_setup, atopcase_intr_setup),
+ DEVMETHOD(hid_intr_unsetup, atopcase_intr_unsetup),
+ DEVMETHOD(hid_intr_start, atopcase_intr_start),
+ DEVMETHOD(hid_intr_stop, atopcase_intr_stop),
+ DEVMETHOD(hid_intr_poll, atopcase_intr_poll),
+
+ /* HID interface */
+ DEVMETHOD(hid_get_rdesc, atopcase_get_rdesc),
+ DEVMETHOD(hid_set_report, atopcase_set_report),
+
+ /* Backlight interface */
+ DEVMETHOD(backlight_update_status, atopcase_backlight_update_status),
+ DEVMETHOD(backlight_get_status, atopcase_backlight_get_status),
+ DEVMETHOD(backlight_get_info, atopcase_backlight_get_info),
+
+ DEVMETHOD_END
+};
+
+static driver_t atopcase_driver = {
+ "atopcase",
+ atopcase_methods,
+ sizeof(struct atopcase_softc),
+};
+
+DRIVER_MODULE(atopcase, spibus, atopcase_driver, 0, 0);
+MODULE_DEPEND(atopcase, acpi, 1, 1, 1);
+MODULE_DEPEND(atopcase, backlight, 1, 1, 1);
+MODULE_DEPEND(atopcase, hid, 1, 1, 1);
+MODULE_DEPEND(atopcase, hidbus, 1, 1, 1);
+MODULE_DEPEND(atopcase, spibus, 1, 1, 1);
+MODULE_VERSION(atopcase, 1);
+SPIBUS_ACPI_PNP_INFO(atopcase_ids);
diff --git a/sys/dev/atopcase/atopcase_reg.h b/sys/dev/atopcase/atopcase_reg.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/atopcase/atopcase_reg.h
@@ -0,0 +1,110 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2021-2023 Val Packett <val@packett.cool>
+ * Copyright (c) 2023 Vladimir Kondratyev <wulf@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _ATOPCASE_REG_H_
+#define _ATOPCASE_REG_H_
+
+#include <sys/types.h>
+
+#define ATOPCASE_PACKET_SIZE 256
+#define ATOPCASE_DATA_SIZE 246
+#define ATOPCASE_PKT_PER_MSG 2
+#define ATOPCASE_MSG_SIZE (ATOPCASE_DATA_SIZE * ATOPCASE_PKT_PER_MSG)
+
+/* Read == device-initiated, Write == host-initiated or reply to that */
+#define ATOPCASE_DIR_READ 0x20
+#define ATOPCASE_DIR_WRITE 0x40
+#define ATOPCASE_DIR_NOTHING 0x80
+
+#define ATOPCASE_DEV_MGMT 0x00
+#define ATOPCASE_DEV_KBRD 0x01
+#define ATOPCASE_DEV_TPAD 0x02
+#define ATOPCASE_DEV_INFO 0xD0
+
+#define ATOPCASE_BKL_REPORT_ID 0xB0
+
+#define ATOPCASE_INFO_DEVICE 0x01
+#define ATOPCASE_INFO_IFACE 0x02
+#define ATOPCASE_INFO_DESCRIPTOR 0x10
+
+#define ATOPCASE_MSG_TYPE_SET_REPORT(dev,rid) ((rid << 8) | 0x50 | dev)
+#define ATOPCASE_MSG_TYPE_REPORT(dev) ((dev << 8) | 0x10)
+#define ATOPCASE_MSG_TYPE_INFO(inf) ((inf << 8) | 0x20)
+
+struct atopcase_bl_payload {
+ uint8_t report_id;
+ uint8_t device;
+ uint16_t level;
+ uint16_t status;
+} __packed;
+
+struct atopcase_device_info_payload {
+ uint16_t unknown[2];
+ uint16_t num_devs;
+ uint16_t vid;
+ uint16_t pid;
+ uint16_t ver;
+ uint16_t vendor_off;
+ uint16_t vendor_len;
+ uint16_t product_off;
+ uint16_t product_len;
+ uint16_t serial_off;
+ uint16_t serial_len;
+} __packed;
+
+struct atopcase_iface_info_payload {
+ uint8_t unknown0;
+ uint8_t iface_num;
+ uint8_t unknown1[3];
+ uint8_t country_code;
+ uint16_t max_input_report_len;
+ uint16_t max_output_report_len;
+ uint16_t max_control_report_len;
+ uint16_t name_off;
+ uint16_t name_len;
+} __packed;
+
+struct atopcase_header {
+ uint16_t type;
+ uint8_t type_arg; /* means "device" for ATOPCASE_MSG_TYPE_DESCRIPTOR */
+ uint8_t seq_no;
+ uint16_t resp_len;
+ uint16_t len;
+} __packed;
+
+struct atopcase_packet {
+ uint8_t direction;
+ uint8_t device;
+ uint16_t offset;
+ uint16_t remaining;
+ uint16_t length;
+ uint8_t data[ATOPCASE_DATA_SIZE];
+ uint16_t checksum;
+} __packed;
+
+#endif /* _ATOPCASE_REG_H_ */
diff --git a/sys/dev/atopcase/atopcase_var.h b/sys/dev/atopcase/atopcase_var.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/atopcase/atopcase_var.h
@@ -0,0 +1,142 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2021-2023 Val Packett <val@packett.cool>
+ * Copyright (c) 2023 Vladimir Kondratyev <wulf@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _ATOPCASE_VAR_H_
+#define _ATOPCASE_VAR_H_
+
+#include "opt_hid.h"
+
+#include <sys/types.h>
+#include <sys/bus.h>
+#include <sys/taskqueue.h>
+
+#include <contrib/dev/acpica/include/acpi.h>
+#include <dev/acpica/acpivar.h>
+
+#include <dev/backlight/backlight.h>
+
+#include <dev/hid/hid.h>
+
+struct atopcase_child {
+ device_t hidbus;
+
+ struct hid_device_info hw;
+
+ uint8_t device;
+ uint8_t name[80];
+
+ uint8_t rdesc[ATOPCASE_MSG_SIZE];
+ size_t rdesc_len;
+
+ hid_intr_t *intr_handler;
+ void *intr_ctx;
+
+ bool open;
+};
+
+struct atopcase_softc {
+ device_t sc_dev;
+
+ ACPI_HANDLE sc_handle;
+ int sc_gpe_bit;
+
+ int sc_irq_rid;
+ struct resource *sc_irq_res;
+ void *sc_irq_ih;
+ volatile unsigned int sc_intr_cnt;
+
+ struct timeout_task sc_task;
+ struct taskqueue *sc_tq;
+
+ bool sc_booted;
+ bool sc_wait_for_status;
+
+ uint8_t sc_hid[HID_PNP_ID_SIZE];
+ uint8_t sc_vendor[80];
+ uint8_t sc_product[80];
+ uint8_t sc_serial[80];
+ uint16_t sc_vid;
+ uint16_t sc_pid;
+ uint16_t sc_ver;
+
+ /*
+ * Writes are complex and async (i.e. 2 responses arrive via interrupt)
+ * and cannot be interleaved (no new writes until responses arrive).
+ * they are serialized with sc_write_sx lock.
+ */
+ struct sx sc_write_sx;
+ /*
+ * SPI transfers must be separated by a small pause. As they can be
+ * initiated by both interrupts and users, do ATOPCASE_SPI_PAUSE()
+ * after each transfer and serialize them with sc_sx or sc_mtx locks
+ * depending on interupt source (GPE or PIC). Still use sc_write_sx
+ * lock while polling.
+ */
+ struct sx sc_sx;
+ struct mtx sc_mtx;
+
+ struct atopcase_child sc_kb;
+ struct atopcase_child sc_tp;
+
+ struct cdev *sc_backlight;
+ uint32_t sc_backlight_level;
+
+ uint16_t sc_msg_len;
+ uint8_t sc_msg[ATOPCASE_MSG_SIZE];
+ struct atopcase_packet sc_buf;
+ struct atopcase_packet sc_junk;
+};
+
+#ifdef HID_DEBUG
+enum atopcase_log_level {
+ ATOPCASE_LLEVEL_DISABLED = 0,
+ ATOPCASE_LLEVEL_INFO,
+ ATOPCASE_LLEVEL_DEBUG, /* for troubleshooting */
+ ATOPCASE_LLEVEL_TRACE, /* log every packet */
+};
+extern enum atopcase_log_level atopcase_debug;
+#endif
+
+int atopcase_receive_packet(struct atopcase_softc *);
+int atopcase_init(struct atopcase_softc *);
+int atopcase_destroy(struct atopcase_softc *sc);
+int atopcase_intr(struct atopcase_softc *);
+void atopcase_intr_setup(device_t, device_t, hid_intr_t, void *,
+ struct hid_rdesc_info *);
+void atopcase_intr_unsetup(device_t, device_t);
+int atopcase_intr_start(device_t, device_t);
+int atopcase_intr_stop(device_t, device_t);
+void atopcase_intr_poll(device_t, device_t);
+int atopcase_get_rdesc(device_t, device_t, void *, hid_size_t);
+int atopcase_set_report(device_t, device_t, const void *, hid_size_t, uint8_t,
+ uint8_t);
+int atopcase_backlight_update_status(device_t, struct backlight_props *);
+int atopcase_backlight_get_status(device_t, struct backlight_props *);
+int atopcase_backlight_get_info(device_t, struct backlight_info *);
+
+#endif /* _ATOPCASE_VAR_H_ */
diff --git a/sys/dev/hid/hidbus.c b/sys/dev/hid/hidbus.c
--- a/sys/dev/hid/hidbus.c
+++ b/sys/dev/hid/hidbus.c
@@ -973,6 +973,7 @@
MODULE_DEPEND(hidbus, hid, 1, 1, 1);
MODULE_VERSION(hidbus, 1);
+DRIVER_MODULE(hidbus, atopcase, hidbus_driver, 0, 0);
DRIVER_MODULE(hidbus, hvhid, hidbus_driver, 0, 0);
DRIVER_MODULE(hidbus, iichid, hidbus_driver, 0, 0);
DRIVER_MODULE(hidbus, usbhid, hidbus_driver, 0, 0);
diff --git a/sys/modules/spi/Makefile b/sys/modules/spi/Makefile
--- a/sys/modules/spi/Makefile
+++ b/sys/modules/spi/Makefile
@@ -2,7 +2,12 @@
SUBDIR = \
../spigen \
at45d \
+ ${_atopcase} \
mx25l \
spibus \
+.if ${MACHINE_CPUARCH} == "i386" || ${MACHINE_CPUARCH} == "amd64"
+_atopcase=atopcase
+.endif
+
.include <bsd.subdir.mk>
diff --git a/sys/modules/spi/atopcase/Makefile b/sys/modules/spi/atopcase/Makefile
new file mode 100644
--- /dev/null
+++ b/sys/modules/spi/atopcase/Makefile
@@ -0,0 +1,7 @@
+.PATH: ${SRCTOP}/sys/dev/atopcase
+KMOD= atopcase
+SRCS= atopcase.c atopcase_acpi.c
+SRCS+= acpi_if.h backlight_if.h bus_if.h device_if.h hid_if.h opt_acpi.h \
+ opt_hid.h opt_spi.h spibus_if.h usbdevs.h
+
+.include <bsd.kmod.mk>

File Metadata

Mime Type
text/plain
Expires
Wed, Feb 5, 12:30 PM (20 h, 29 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
16474340
Default Alt Text
D39863.diff (47 KB)

Event Timeline