xref: /freebsd/sys/dev/atopcase/atopcase.c (revision 64fbda90)
164fbda90SVal Packett /*-
264fbda90SVal Packett  * SPDX-License-Identifier: BSD-2-Clause
364fbda90SVal Packett  *
464fbda90SVal Packett  * Copyright (c) 2021-2023 Val Packett <val@packett.cool>
564fbda90SVal Packett  * Copyright (c) 2023 Vladimir Kondratyev <wulf@FreeBSD.org>
664fbda90SVal Packett  *
764fbda90SVal Packett  * Redistribution and use in source and binary forms, with or without
864fbda90SVal Packett  * modification, are permitted provided that the following conditions
964fbda90SVal Packett  * are met:
1064fbda90SVal Packett  * 1. Redistributions of source code must retain the above copyright
1164fbda90SVal Packett  *    notice, this list of conditions and the following disclaimer.
1264fbda90SVal Packett  * 2. Redistributions in binary form must reproduce the above copyright
1364fbda90SVal Packett  *    notice, this list of conditions and the following disclaimer in the
1464fbda90SVal Packett  *    documentation and/or other materials provided with the distribution.
1564fbda90SVal Packett  *
1664fbda90SVal Packett  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1764fbda90SVal Packett  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1864fbda90SVal Packett  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1964fbda90SVal Packett  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
2064fbda90SVal Packett  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2164fbda90SVal Packett  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2264fbda90SVal Packett  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2364fbda90SVal Packett  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2464fbda90SVal Packett  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2564fbda90SVal Packett  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2664fbda90SVal Packett  * SUCH DAMAGE.
2764fbda90SVal Packett  */
2864fbda90SVal Packett 
2964fbda90SVal Packett #include "opt_hid.h"
3064fbda90SVal Packett #include "opt_spi.h"
3164fbda90SVal Packett 
3264fbda90SVal Packett #include <sys/param.h>
3364fbda90SVal Packett #include <sys/bus.h>
3464fbda90SVal Packett #include <sys/crc16.h>
3564fbda90SVal Packett #include <sys/endian.h>
3664fbda90SVal Packett #include <sys/kdb.h>
3764fbda90SVal Packett #include <sys/kernel.h>
3864fbda90SVal Packett #include <sys/malloc.h>
3964fbda90SVal Packett #include <sys/mutex.h>
4064fbda90SVal Packett #include <sys/module.h>
4164fbda90SVal Packett #include <sys/proc.h>
4264fbda90SVal Packett #include <sys/rman.h>
4364fbda90SVal Packett #include <sys/sysctl.h>
4464fbda90SVal Packett #include <sys/sx.h>
4564fbda90SVal Packett #include <sys/taskqueue.h>
4664fbda90SVal Packett 
4764fbda90SVal Packett #include <dev/backlight/backlight.h>
4864fbda90SVal Packett 
4964fbda90SVal Packett #include <dev/evdev/input.h>
5064fbda90SVal Packett 
5164fbda90SVal Packett #define	HID_DEBUG_VAR atopcase_debug
5264fbda90SVal Packett #include <dev/hid/hid.h>
5364fbda90SVal Packett #include <dev/hid/hidquirk.h>
5464fbda90SVal Packett 
5564fbda90SVal Packett #include <dev/spibus/spi.h>
5664fbda90SVal Packett #include <dev/spibus/spibusvar.h>
5764fbda90SVal Packett 
5864fbda90SVal Packett #include "spibus_if.h"
5964fbda90SVal Packett 
6064fbda90SVal Packett #include "atopcase_reg.h"
6164fbda90SVal Packett #include "atopcase_var.h"
6264fbda90SVal Packett 
6364fbda90SVal Packett #define	ATOPCASE_IN_KDB()	(SCHEDULER_STOPPED() || kdb_active)
6464fbda90SVal Packett #define	ATOPCASE_IN_POLLING_MODE(sc)					\
6564fbda90SVal Packett 	(((sc)->sc_gpe_bit == 0 && ((sc)->sc_irq_ih == NULL)) || cold ||\
6664fbda90SVal Packett 	ATOPCASE_IN_KDB())
6764fbda90SVal Packett #define	ATOPCASE_WAKEUP(sc, chan) do {					\
6864fbda90SVal Packett 	if (!ATOPCASE_IN_POLLING_MODE(sc)) {				\
6964fbda90SVal Packett 		DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "wakeup: %p\n", chan);	\
7064fbda90SVal Packett 		wakeup(chan);						\
7164fbda90SVal Packett 	}								\
7264fbda90SVal Packett } while (0)
7364fbda90SVal Packett #define	ATOPCASE_SPI_PAUSE()	DELAY(100)
7464fbda90SVal Packett #define	ATOPCASE_SPI_NO_SLEEP_FLAG(sc)					\
7564fbda90SVal Packett 	((sc)->sc_irq_ih != NULL ? SPI_FLAG_NO_SLEEP : 0)
7664fbda90SVal Packett 
7764fbda90SVal Packett /* Tunables */
7864fbda90SVal Packett static SYSCTL_NODE(_hw_hid, OID_AUTO, atopcase, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
7964fbda90SVal Packett     "Apple MacBook Topcase HID driver");
8064fbda90SVal Packett 
8164fbda90SVal Packett #ifdef HID_DEBUG
8264fbda90SVal Packett enum atopcase_log_level atopcase_debug = ATOPCASE_LLEVEL_DISABLED;
8364fbda90SVal Packett 
8464fbda90SVal Packett SYSCTL_INT(_hw_hid_atopcase, OID_AUTO, debug, CTLFLAG_RWTUN,
8564fbda90SVal Packett     &atopcase_debug, ATOPCASE_LLEVEL_DISABLED, "atopcase log level");
8664fbda90SVal Packett #endif /* !HID_DEBUG */
8764fbda90SVal Packett 
8864fbda90SVal Packett static const uint8_t booted[] = { 0xa0, 0x80, 0x00, 0x00 };
8964fbda90SVal Packett static const uint8_t status_ok[] = { 0xac, 0x27, 0x68, 0xd5 };
9064fbda90SVal Packett 
9164fbda90SVal Packett static inline struct atopcase_child *
atopcase_get_child_by_device(struct atopcase_softc * sc,uint8_t device)9264fbda90SVal Packett atopcase_get_child_by_device(struct atopcase_softc *sc, uint8_t device)
9364fbda90SVal Packett {
9464fbda90SVal Packett 	switch (device) {
9564fbda90SVal Packett 	case ATOPCASE_DEV_KBRD:
9664fbda90SVal Packett 		return (&sc->sc_kb);
9764fbda90SVal Packett 	case ATOPCASE_DEV_TPAD:
9864fbda90SVal Packett 		return (&sc->sc_tp);
9964fbda90SVal Packett 	default:
10064fbda90SVal Packett 		return (NULL);
10164fbda90SVal Packett 	}
10264fbda90SVal Packett }
10364fbda90SVal Packett 
10464fbda90SVal Packett static int
atopcase_receive_status(struct atopcase_softc * sc)10564fbda90SVal Packett atopcase_receive_status(struct atopcase_softc *sc)
10664fbda90SVal Packett {
10764fbda90SVal Packett 	struct spi_command cmd = SPI_COMMAND_INITIALIZER;
10864fbda90SVal Packett 	uint8_t dummy_buffer[4] = { 0 };
10964fbda90SVal Packett 	uint8_t status_buffer[4] = { 0 };
11064fbda90SVal Packett 	int err;
11164fbda90SVal Packett 
11264fbda90SVal Packett 	cmd.tx_cmd = dummy_buffer;
11364fbda90SVal Packett 	cmd.tx_cmd_sz = sizeof(dummy_buffer);
11464fbda90SVal Packett 	cmd.rx_cmd = status_buffer;
11564fbda90SVal Packett 	cmd.rx_cmd_sz = sizeof(status_buffer);
11664fbda90SVal Packett 	cmd.flags = ATOPCASE_SPI_NO_SLEEP_FLAG(sc);
11764fbda90SVal Packett 
11864fbda90SVal Packett 	err = SPIBUS_TRANSFER(device_get_parent(sc->sc_dev), sc->sc_dev, &cmd);
11964fbda90SVal Packett 	ATOPCASE_SPI_PAUSE();
12064fbda90SVal Packett 	if (err) {
12164fbda90SVal Packett 		device_printf(sc->sc_dev, "SPI error: %d\n", err);
12264fbda90SVal Packett 		return (err);
12364fbda90SVal Packett 	}
12464fbda90SVal Packett 
12564fbda90SVal Packett 	DPRINTFN(ATOPCASE_LLEVEL_TRACE, "Status: %*D\n", 4, status_buffer, " ");
12664fbda90SVal Packett 
12764fbda90SVal Packett 	if (memcmp(status_buffer, status_ok, sizeof(status_ok)) == 0) {
12864fbda90SVal Packett 		DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "Wrote command\n");
12964fbda90SVal Packett 		ATOPCASE_WAKEUP(sc, sc->sc_dev);
13064fbda90SVal Packett 	} else {
13164fbda90SVal Packett 		device_printf(sc->sc_dev, "Failed to write command\n");
13264fbda90SVal Packett 		return (EIO);
13364fbda90SVal Packett 	}
13464fbda90SVal Packett 
13564fbda90SVal Packett 	return (0);
13664fbda90SVal Packett }
13764fbda90SVal Packett 
13864fbda90SVal Packett static int
atopcase_process_message(struct atopcase_softc * sc,uint8_t device,void * msg,uint16_t msg_len)13964fbda90SVal Packett atopcase_process_message(struct atopcase_softc *sc, uint8_t device, void *msg,
14064fbda90SVal Packett     uint16_t msg_len)
14164fbda90SVal Packett {
14264fbda90SVal Packett 	struct atopcase_header *hdr = msg;
14364fbda90SVal Packett 	struct atopcase_child *ac;
14464fbda90SVal Packett 	void *payload;
14564fbda90SVal Packett 	uint16_t pl_len, crc;
14664fbda90SVal Packett 
14764fbda90SVal Packett 	payload = (uint8_t *)msg + sizeof(*hdr);
14864fbda90SVal Packett 	pl_len = le16toh(hdr->len);
14964fbda90SVal Packett 
15064fbda90SVal Packett 	if (pl_len + sizeof(*hdr) + sizeof(crc) != msg_len) {
15164fbda90SVal Packett 		DPRINTFN(ATOPCASE_LLEVEL_DEBUG,
15264fbda90SVal Packett 		    "message with length overflow\n");
15364fbda90SVal Packett 		return (EIO);
15464fbda90SVal Packett 	}
15564fbda90SVal Packett 
15664fbda90SVal Packett 	crc = le16toh(*(uint16_t *)((uint8_t *)payload + pl_len));
15764fbda90SVal Packett 	if (crc != crc16(0, msg, msg_len - sizeof(crc))) {
15864fbda90SVal Packett 		DPRINTFN(ATOPCASE_LLEVEL_DEBUG,
15964fbda90SVal Packett 		    "message with failed checksum\n");
16064fbda90SVal Packett 		return (EIO);
16164fbda90SVal Packett 	}
16264fbda90SVal Packett 
16364fbda90SVal Packett #define CPOFF(dst, len, off)	do {					\
16464fbda90SVal Packett 	unsigned _len = le16toh(len);					\
16564fbda90SVal Packett 	unsigned _off = le16toh(off);					\
16664fbda90SVal Packett 	if (pl_len >= _len + _off) {					\
16764fbda90SVal Packett 		memcpy(dst, (uint8_t*)payload + _off, MIN(_len, sizeof(dst)));\
16864fbda90SVal Packett 		(dst)[MIN(_len, sizeof(dst) - 1)] = '\0';		\
16964fbda90SVal Packett 	}} while (0);
17064fbda90SVal Packett 
17164fbda90SVal Packett 	if ((ac = atopcase_get_child_by_device(sc, device)) != NULL
17264fbda90SVal Packett 	    && hdr->type == ATOPCASE_MSG_TYPE_REPORT(device)) {
17364fbda90SVal Packett 		if (ac->open)
17464fbda90SVal Packett 			ac->intr_handler(ac->intr_ctx, payload, pl_len);
17564fbda90SVal Packett 	} else if (device == ATOPCASE_DEV_INFO
17664fbda90SVal Packett 	    && hdr->type == ATOPCASE_MSG_TYPE_INFO(ATOPCASE_INFO_IFACE)
17764fbda90SVal Packett 	    && (ac = atopcase_get_child_by_device(sc, hdr->type_arg)) != NULL) {
17864fbda90SVal Packett 		struct atopcase_iface_info_payload *iface = payload;
17964fbda90SVal Packett 		CPOFF(ac->name, iface->name_len, iface->name_off);
18064fbda90SVal Packett 		DPRINTF("Interface #%d name: %s\n", ac->device, ac->name);
18164fbda90SVal Packett 	} else if (device == ATOPCASE_DEV_INFO
18264fbda90SVal Packett 	    && hdr->type == ATOPCASE_MSG_TYPE_INFO(ATOPCASE_INFO_DESCRIPTOR)
18364fbda90SVal Packett 	    && (ac = atopcase_get_child_by_device(sc, hdr->type_arg)) != NULL) {
18464fbda90SVal Packett 		memcpy(ac->rdesc, payload, pl_len);
18564fbda90SVal Packett 		ac->rdesc_len = ac->hw.rdescsize = pl_len;
18664fbda90SVal Packett 		DPRINTF("%s HID report descriptor: %*D\n", ac->name,
18764fbda90SVal Packett 		    (int) ac->hw.rdescsize, ac->rdesc, " ");
18864fbda90SVal Packett 	} else if (device == ATOPCASE_DEV_INFO
18964fbda90SVal Packett 	    && hdr->type == ATOPCASE_MSG_TYPE_INFO(ATOPCASE_INFO_DEVICE)
19064fbda90SVal Packett 	    && hdr->type_arg == ATOPCASE_INFO_DEVICE) {
19164fbda90SVal Packett 		struct atopcase_device_info_payload *dev = payload;
19264fbda90SVal Packett 		sc->sc_vid = le16toh(dev->vid);
19364fbda90SVal Packett 		sc->sc_pid = le16toh(dev->pid);
19464fbda90SVal Packett 		sc->sc_ver = le16toh(dev->ver);
19564fbda90SVal Packett 		CPOFF(sc->sc_vendor, dev->vendor_len, dev->vendor_off);
19664fbda90SVal Packett 		CPOFF(sc->sc_product, dev->product_len, dev->product_off);
19764fbda90SVal Packett 		CPOFF(sc->sc_serial, dev->serial_len, dev->serial_off);
19864fbda90SVal Packett 		if (bootverbose) {
19964fbda90SVal Packett 			device_printf(sc->sc_dev, "Device info descriptor:\n");
20064fbda90SVal Packett 			printf("  Vendor:  %s\n", sc->sc_vendor);
20164fbda90SVal Packett 			printf("  Product: %s\n", sc->sc_product);
20264fbda90SVal Packett 			printf("  Serial:  %s\n", sc->sc_serial);
20364fbda90SVal Packett 		}
20464fbda90SVal Packett 	}
20564fbda90SVal Packett 
20664fbda90SVal Packett 	return (0);
20764fbda90SVal Packett }
20864fbda90SVal Packett 
20964fbda90SVal Packett int
atopcase_receive_packet(struct atopcase_softc * sc)21064fbda90SVal Packett atopcase_receive_packet(struct atopcase_softc *sc)
21164fbda90SVal Packett {
21264fbda90SVal Packett 	struct atopcase_packet pkt = { 0 };
21364fbda90SVal Packett 	struct spi_command cmd = SPI_COMMAND_INITIALIZER;
21464fbda90SVal Packett 	void *msg;
21564fbda90SVal Packett 	int err;
21664fbda90SVal Packett 	uint16_t length, remaining, offset, msg_len;
21764fbda90SVal Packett 
21864fbda90SVal Packett 	bzero(&sc->sc_junk, sizeof(struct atopcase_packet));
21964fbda90SVal Packett 	cmd.tx_cmd = &sc->sc_junk;
22064fbda90SVal Packett 	cmd.tx_cmd_sz = sizeof(struct atopcase_packet);
22164fbda90SVal Packett 	cmd.rx_cmd = &pkt;
22264fbda90SVal Packett 	cmd.rx_cmd_sz = sizeof(struct atopcase_packet);
22364fbda90SVal Packett 	cmd.flags = ATOPCASE_SPI_NO_SLEEP_FLAG(sc);
22464fbda90SVal Packett 	err = SPIBUS_TRANSFER(device_get_parent(sc->sc_dev), sc->sc_dev, &cmd);
22564fbda90SVal Packett 	ATOPCASE_SPI_PAUSE();
22664fbda90SVal Packett 	if (err) {
22764fbda90SVal Packett 		device_printf(sc->sc_dev, "SPI error: %d\n", err);
22864fbda90SVal Packett 		return (err);
22964fbda90SVal Packett 	}
23064fbda90SVal Packett 
23164fbda90SVal Packett 	DPRINTFN(ATOPCASE_LLEVEL_TRACE, "Response: %*D\n", 256, &pkt, " ");
23264fbda90SVal Packett 
23364fbda90SVal Packett 	if (le16toh(pkt.checksum) != crc16(0, &pkt, sizeof(pkt) - 2)) {
23464fbda90SVal Packett 		DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "packet with failed checksum\n");
23564fbda90SVal Packett 		return (EIO);
23664fbda90SVal Packett 	}
23764fbda90SVal Packett 
23864fbda90SVal Packett 	/*
23964fbda90SVal Packett 	 * When we poll and nothing has arrived we get a particular packet
24064fbda90SVal Packett 	 * starting with '80 11 00 01'
24164fbda90SVal Packett 	 */
24264fbda90SVal Packett 	if (pkt.direction == ATOPCASE_DIR_NOTHING) {
24364fbda90SVal Packett 		DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "'Nothing' packet: %*D\n", 4,
24464fbda90SVal Packett 		    &pkt, " ");
24564fbda90SVal Packett 		return (EAGAIN);
24664fbda90SVal Packett 	}
24764fbda90SVal Packett 
24864fbda90SVal Packett 	if (pkt.direction != ATOPCASE_DIR_READ &&
24964fbda90SVal Packett 	    pkt.direction != ATOPCASE_DIR_WRITE) {
25064fbda90SVal Packett 		DPRINTFN(ATOPCASE_LLEVEL_DEBUG,
25164fbda90SVal Packett 		         "unknown message direction 0x%x\n", pkt.direction);
25264fbda90SVal Packett 		return (EIO);
25364fbda90SVal Packett 	}
25464fbda90SVal Packett 
25564fbda90SVal Packett 	length = le16toh(pkt.length);
25664fbda90SVal Packett 	remaining = le16toh(pkt.remaining);
25764fbda90SVal Packett 	offset = le16toh(pkt.offset);
25864fbda90SVal Packett 
25964fbda90SVal Packett 	if (length > sizeof(pkt.data)) {
26064fbda90SVal Packett 		DPRINTFN(ATOPCASE_LLEVEL_DEBUG,
26164fbda90SVal Packett 		    "packet with length overflow: %u\n", length);
26264fbda90SVal Packett 		return (EIO);
26364fbda90SVal Packett 	}
26464fbda90SVal Packett 
26564fbda90SVal Packett 	if (pkt.direction == ATOPCASE_DIR_READ &&
26664fbda90SVal Packett 	    pkt.device == ATOPCASE_DEV_INFO &&
26764fbda90SVal Packett 	    length == sizeof(booted) &&
26864fbda90SVal Packett 	    memcmp(pkt.data, booted, length) == 0) {
26964fbda90SVal Packett 		DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "GPE boot packet\n");
27064fbda90SVal Packett 		sc->sc_booted = true;
27164fbda90SVal Packett 		ATOPCASE_WAKEUP(sc, sc);
27264fbda90SVal Packett 		return (0);
27364fbda90SVal Packett 	}
27464fbda90SVal Packett 
27564fbda90SVal Packett 	/* handle multi-packet messages */
27664fbda90SVal Packett 	if (remaining != 0 || offset != 0) {
27764fbda90SVal Packett 		if (offset != sc->sc_msg_len) {
27864fbda90SVal Packett 			DPRINTFN(ATOPCASE_LLEVEL_DEBUG,
27964fbda90SVal Packett 			    "Unexpected offset (got %u, expected %u)\n",
28064fbda90SVal Packett 			    offset, sc->sc_msg_len);
28164fbda90SVal Packett 			sc->sc_msg_len = 0;
28264fbda90SVal Packett 			return (EIO);
28364fbda90SVal Packett 		}
28464fbda90SVal Packett 
28564fbda90SVal Packett 		if ((size_t)remaining + length + offset > sizeof(sc->sc_msg)) {
28664fbda90SVal Packett 			DPRINTFN(ATOPCASE_LLEVEL_DEBUG,
28764fbda90SVal Packett 			    "Message with length overflow: %zu\n",
28864fbda90SVal Packett 			    (size_t)remaining + length + offset);
28964fbda90SVal Packett 			sc->sc_msg_len = 0;
29064fbda90SVal Packett 			return (EIO);
29164fbda90SVal Packett 		}
29264fbda90SVal Packett 
29364fbda90SVal Packett 		memcpy(sc->sc_msg + offset, &pkt.data, length);
29464fbda90SVal Packett 		sc->sc_msg_len += length;
29564fbda90SVal Packett 
29664fbda90SVal Packett 		if (remaining != 0)
29764fbda90SVal Packett 			return (0);
29864fbda90SVal Packett 
29964fbda90SVal Packett 		msg = sc->sc_msg;
30064fbda90SVal Packett 		msg_len = sc->sc_msg_len;
30164fbda90SVal Packett 	} else {
30264fbda90SVal Packett 		msg = pkt.data;
30364fbda90SVal Packett 		msg_len = length;
30464fbda90SVal Packett 	}
30564fbda90SVal Packett 	sc->sc_msg_len = 0;
30664fbda90SVal Packett 
30764fbda90SVal Packett 	err = atopcase_process_message(sc, pkt.device, msg, msg_len);
30864fbda90SVal Packett 	if (err == 0 && pkt.direction == ATOPCASE_DIR_WRITE) {
30964fbda90SVal Packett 		DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "Write ack\n");
31064fbda90SVal Packett 		ATOPCASE_WAKEUP(sc, sc);
31164fbda90SVal Packett 	}
31264fbda90SVal Packett 
31364fbda90SVal Packett 	return (err);
31464fbda90SVal Packett }
31564fbda90SVal Packett 
31664fbda90SVal Packett static int
atopcase_send(struct atopcase_softc * sc,struct atopcase_packet * pkt)31764fbda90SVal Packett atopcase_send(struct atopcase_softc *sc, struct atopcase_packet *pkt)
31864fbda90SVal Packett {
31964fbda90SVal Packett 	struct spi_command cmd = SPI_COMMAND_INITIALIZER;
32064fbda90SVal Packett 	int err, retries;
32164fbda90SVal Packett 
32264fbda90SVal Packett 	cmd.tx_cmd = pkt;
32364fbda90SVal Packett 	cmd.tx_cmd_sz = sizeof(struct atopcase_packet);
32464fbda90SVal Packett 	cmd.rx_cmd = &sc->sc_junk;
32564fbda90SVal Packett 	cmd.rx_cmd_sz = sizeof(struct atopcase_packet);
32664fbda90SVal Packett 	cmd.flags = SPI_FLAG_KEEP_CS | ATOPCASE_SPI_NO_SLEEP_FLAG(sc);
32764fbda90SVal Packett 
32864fbda90SVal Packett 	DPRINTFN(ATOPCASE_LLEVEL_TRACE, "Request: %*D\n",
32964fbda90SVal Packett 	    (int)sizeof(struct atopcase_packet), cmd.tx_cmd, " ");
33064fbda90SVal Packett 
33164fbda90SVal Packett 	if (!ATOPCASE_IN_POLLING_MODE(sc)) {
33264fbda90SVal Packett 		if (sc->sc_irq_ih != NULL)
33364fbda90SVal Packett 			mtx_lock(&sc->sc_mtx);
33464fbda90SVal Packett 		else
33564fbda90SVal Packett 			sx_xlock(&sc->sc_sx);
33664fbda90SVal Packett 	}
33764fbda90SVal Packett 	sc->sc_wait_for_status = true;
33864fbda90SVal Packett 	err = SPIBUS_TRANSFER(device_get_parent(sc->sc_dev), sc->sc_dev, &cmd);
33964fbda90SVal Packett 	ATOPCASE_SPI_PAUSE();
34064fbda90SVal Packett 	if (!ATOPCASE_IN_POLLING_MODE(sc)) {
34164fbda90SVal Packett 		if (sc->sc_irq_ih != NULL)
34264fbda90SVal Packett 			mtx_unlock(&sc->sc_mtx);
34364fbda90SVal Packett 		else
34464fbda90SVal Packett 			sx_xunlock(&sc->sc_sx);
34564fbda90SVal Packett 	}
34664fbda90SVal Packett 	if (err != 0) {
34764fbda90SVal Packett 		device_printf(sc->sc_dev, "SPI error: %d\n", err);
34864fbda90SVal Packett 		goto exit;
34964fbda90SVal Packett 	}
35064fbda90SVal Packett 
35164fbda90SVal Packett 	if (ATOPCASE_IN_POLLING_MODE(sc)) {
35264fbda90SVal Packett 		err = atopcase_receive_status(sc);
35364fbda90SVal Packett 	} else {
35464fbda90SVal Packett 		DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "wait for: %p\n", sc->sc_dev);
35564fbda90SVal Packett 		err = tsleep(sc->sc_dev, 0, "atcstat", hz / 10);
35664fbda90SVal Packett 	}
35764fbda90SVal Packett 	sc->sc_wait_for_status = false;
35864fbda90SVal Packett 	if (err != 0) {
35964fbda90SVal Packett 		DPRINTF("Write status read failed: %d\n", err);
36064fbda90SVal Packett 		goto exit;
36164fbda90SVal Packett 	}
36264fbda90SVal Packett 
36364fbda90SVal Packett 	if (ATOPCASE_IN_POLLING_MODE(sc)) {
36464fbda90SVal Packett 		/* Backlight setting may require a lot of time */
36564fbda90SVal Packett 		retries = 20;
36664fbda90SVal Packett 		while ((err = atopcase_receive_packet(sc)) == EAGAIN &&
36764fbda90SVal Packett 		    --retries != 0)
36864fbda90SVal Packett 			DELAY(1000);
36964fbda90SVal Packett 	} else {
37064fbda90SVal Packett 		DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "wait for: %p\n", sc);
37164fbda90SVal Packett 		err = tsleep(sc, 0, "atcack", hz / 10);
37264fbda90SVal Packett 	}
37364fbda90SVal Packett 	if (err != 0)
37464fbda90SVal Packett 		DPRINTF("Write ack read failed: %d\n", err);
37564fbda90SVal Packett 
37664fbda90SVal Packett exit:
37764fbda90SVal Packett 	if (err == EWOULDBLOCK)
37864fbda90SVal Packett 		err = EIO;
37964fbda90SVal Packett 
38064fbda90SVal Packett 	return (err);
38164fbda90SVal Packett }
38264fbda90SVal Packett 
38364fbda90SVal Packett 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)38464fbda90SVal Packett atopcase_create_message(struct atopcase_packet *pkt, uint8_t device,
38564fbda90SVal Packett     uint16_t type, uint8_t type_arg, const void *payload, uint8_t len,
38664fbda90SVal Packett     uint16_t resp_len)
38764fbda90SVal Packett {
38864fbda90SVal Packett 	struct atopcase_header *hdr = (struct atopcase_header *)pkt->data;
38964fbda90SVal Packett 	uint16_t msg_checksum;
39064fbda90SVal Packett 	static uint8_t seq_no;
39164fbda90SVal Packett 
39264fbda90SVal Packett 	KASSERT(len <= ATOPCASE_DATA_SIZE - sizeof(struct atopcase_header),
39364fbda90SVal Packett 	    ("outgoing msg must be 1 packet"));
39464fbda90SVal Packett 
39564fbda90SVal Packett 	bzero(pkt, sizeof(struct atopcase_packet));
39664fbda90SVal Packett 	pkt->direction = ATOPCASE_DIR_WRITE;
39764fbda90SVal Packett 	pkt->device = device;
39864fbda90SVal Packett 	pkt->length = htole16(sizeof(*hdr) + len + 2);
39964fbda90SVal Packett 
40064fbda90SVal Packett 	hdr->type = htole16(type);
40164fbda90SVal Packett 	hdr->type_arg = type_arg;
40264fbda90SVal Packett 	hdr->seq_no = seq_no++;
40364fbda90SVal Packett 	hdr->resp_len = htole16((resp_len == 0) ? len : resp_len);
40464fbda90SVal Packett 	hdr->len = htole16(len);
40564fbda90SVal Packett 
40664fbda90SVal Packett 	memcpy(pkt->data + sizeof(*hdr), payload, len);
40764fbda90SVal Packett 	msg_checksum = htole16(crc16(0, pkt->data, pkt->length - 2));
40864fbda90SVal Packett 	memcpy(pkt->data + sizeof(*hdr) + len, &msg_checksum, 2);
40964fbda90SVal Packett 	pkt->checksum = htole16(crc16(0, (uint8_t*)pkt, sizeof(*pkt) - 2));
41064fbda90SVal Packett 
41164fbda90SVal Packett 	return;
41264fbda90SVal Packett }
41364fbda90SVal Packett 
41464fbda90SVal Packett static int
atopcase_request_desc(struct atopcase_softc * sc,uint16_t type,uint8_t device)41564fbda90SVal Packett atopcase_request_desc(struct atopcase_softc *sc, uint16_t type, uint8_t device)
41664fbda90SVal Packett {
41764fbda90SVal Packett 	atopcase_create_message(
41864fbda90SVal Packett 	   &sc->sc_buf, ATOPCASE_DEV_INFO, type, device, NULL, 0, 0x200);
41964fbda90SVal Packett 	return (atopcase_send(sc, &sc->sc_buf));
42064fbda90SVal Packett }
42164fbda90SVal Packett 
42264fbda90SVal Packett int
atopcase_intr(struct atopcase_softc * sc)42364fbda90SVal Packett atopcase_intr(struct atopcase_softc *sc)
42464fbda90SVal Packett {
42564fbda90SVal Packett 	int err;
42664fbda90SVal Packett 
42764fbda90SVal Packett 	DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "Interrupt event\n");
42864fbda90SVal Packett 
42964fbda90SVal Packett 	if (sc->sc_wait_for_status) {
43064fbda90SVal Packett 		err = atopcase_receive_status(sc);
43164fbda90SVal Packett 		sc->sc_wait_for_status = false;
43264fbda90SVal Packett 	} else
43364fbda90SVal Packett 		err = atopcase_receive_packet(sc);
43464fbda90SVal Packett 
43564fbda90SVal Packett 	return (err);
43664fbda90SVal Packett }
43764fbda90SVal Packett 
43864fbda90SVal Packett static int
atopcase_add_child(struct atopcase_softc * sc,struct atopcase_child * ac,uint8_t device)43964fbda90SVal Packett atopcase_add_child(struct atopcase_softc *sc, struct atopcase_child *ac,
44064fbda90SVal Packett     uint8_t device)
44164fbda90SVal Packett {
44264fbda90SVal Packett 	device_t hidbus;
44364fbda90SVal Packett 	int err = 0;
44464fbda90SVal Packett 
44564fbda90SVal Packett 	ac->device = device;
44664fbda90SVal Packett 
44764fbda90SVal Packett 	/* fill device info */
44864fbda90SVal Packett 	strlcpy(ac->hw.name, "Apple MacBook", sizeof(ac->hw.name));
44964fbda90SVal Packett 	ac->hw.idBus = BUS_SPI;
45064fbda90SVal Packett 	ac->hw.idVendor = sc->sc_vid;
45164fbda90SVal Packett 	ac->hw.idProduct = sc->sc_pid;
45264fbda90SVal Packett 	ac->hw.idVersion = sc->sc_ver;
45364fbda90SVal Packett 	strlcpy(ac->hw.idPnP, sc->sc_hid, sizeof(ac->hw.idPnP));
45464fbda90SVal Packett 	strlcpy(ac->hw.serial, sc->sc_serial, sizeof(ac->hw.serial));
45564fbda90SVal Packett 	/*
45664fbda90SVal Packett 	 * HID write and set_report methods executed on Apple SPI topcase
45764fbda90SVal Packett 	 * hardware do the same request on SPI layer. Set HQ_NOWRITE quirk to
45864fbda90SVal Packett 	 * force hidmap to convert writes to set_reports. That makes HID bus
45964fbda90SVal Packett 	 * write handler unnecessary and reduces code duplication.
46064fbda90SVal Packett 	 */
46164fbda90SVal Packett 	hid_add_dynamic_quirk(&ac->hw, HQ_NOWRITE);
46264fbda90SVal Packett 
46364fbda90SVal Packett 	DPRINTF("Get the interface #%d descriptor\n", device);
46464fbda90SVal Packett 	err = atopcase_request_desc(sc,
46564fbda90SVal Packett 	    ATOPCASE_MSG_TYPE_INFO(ATOPCASE_INFO_IFACE), device);
46664fbda90SVal Packett 	if (err) {
46764fbda90SVal Packett 		device_printf(sc->sc_dev, "can't receive iface descriptor\n");
46864fbda90SVal Packett 		goto exit;
46964fbda90SVal Packett 	}
47064fbda90SVal Packett 
47164fbda90SVal Packett 	DPRINTF("Get the \"%s\" HID report descriptor\n", ac->name);
47264fbda90SVal Packett 	err = atopcase_request_desc(sc,
47364fbda90SVal Packett 	    ATOPCASE_MSG_TYPE_INFO(ATOPCASE_INFO_DESCRIPTOR), device);
47464fbda90SVal Packett 	if (err) {
47564fbda90SVal Packett 		device_printf(sc->sc_dev, "can't receive report descriptor\n");
47664fbda90SVal Packett 		goto exit;
47764fbda90SVal Packett 	}
47864fbda90SVal Packett 
47964fbda90SVal Packett 	hidbus = device_add_child(sc->sc_dev, "hidbus", -1);
48064fbda90SVal Packett 	if (hidbus == NULL) {
48164fbda90SVal Packett 		device_printf(sc->sc_dev, "can't add child\n");
48264fbda90SVal Packett 		err = ENOMEM;
48364fbda90SVal Packett 		goto exit;
48464fbda90SVal Packett 	}
48564fbda90SVal Packett 	device_set_ivars(hidbus, &ac->hw);
48664fbda90SVal Packett 	ac->hidbus = hidbus;
48764fbda90SVal Packett 
48864fbda90SVal Packett exit:
48964fbda90SVal Packett 	return (err);
49064fbda90SVal Packett }
49164fbda90SVal Packett 
49264fbda90SVal Packett int
atopcase_init(struct atopcase_softc * sc)49364fbda90SVal Packett atopcase_init(struct atopcase_softc *sc)
49464fbda90SVal Packett {
49564fbda90SVal Packett 	int err;
49664fbda90SVal Packett 
49764fbda90SVal Packett 	/* Wait until we know we're getting reasonable responses */
49864fbda90SVal Packett 	if(!sc->sc_booted && tsleep(sc, 0, "atcboot", hz / 20) != 0) {
49964fbda90SVal Packett 		device_printf(sc->sc_dev, "can't establish communication\n");
50064fbda90SVal Packett 		err = EIO;
50164fbda90SVal Packett 		goto err;
50264fbda90SVal Packett 	}
50364fbda90SVal Packett 
50464fbda90SVal Packett 	/*
50564fbda90SVal Packett 	 * Management device may send a message on first boot after power off.
50664fbda90SVal Packett 	 * Let interrupt handler to read and discard it.
50764fbda90SVal Packett 	 */
50864fbda90SVal Packett 	DELAY(2000);
50964fbda90SVal Packett 
51064fbda90SVal Packett 	DPRINTF("Get the device descriptor\n");
51164fbda90SVal Packett 	err = atopcase_request_desc(sc,
51264fbda90SVal Packett 	    ATOPCASE_MSG_TYPE_INFO(ATOPCASE_INFO_DEVICE),
51364fbda90SVal Packett 	    ATOPCASE_INFO_DEVICE);
51464fbda90SVal Packett 	if (err) {
51564fbda90SVal Packett 		device_printf(sc->sc_dev, "can't receive device descriptor\n");
51664fbda90SVal Packett 		goto err;
51764fbda90SVal Packett 	}
51864fbda90SVal Packett 
51964fbda90SVal Packett 	err = atopcase_add_child(sc, &sc->sc_kb, ATOPCASE_DEV_KBRD);
52064fbda90SVal Packett 	if (err != 0)
52164fbda90SVal Packett 		goto err;
52264fbda90SVal Packett 	err = atopcase_add_child(sc, &sc->sc_tp, ATOPCASE_DEV_TPAD);
52364fbda90SVal Packett 	if (err != 0)
52464fbda90SVal Packett 		goto err;
52564fbda90SVal Packett 
52664fbda90SVal Packett 	/* TODO: skip on 2015 models where it's controlled by asmc */
52764fbda90SVal Packett 	sc->sc_backlight = backlight_register("atopcase", sc->sc_dev);
52864fbda90SVal Packett 	if (!sc->sc_backlight) {
52964fbda90SVal Packett 		device_printf(sc->sc_dev, "can't register backlight\n");
53064fbda90SVal Packett 		err = ENOMEM;
53164fbda90SVal Packett 	}
53264fbda90SVal Packett 
53364fbda90SVal Packett 	if (sc->sc_tq != NULL)
53464fbda90SVal Packett 		taskqueue_enqueue_timeout(sc->sc_tq, &sc->sc_task, hz / 120);
53564fbda90SVal Packett 
53664fbda90SVal Packett 	return (bus_generic_attach(sc->sc_dev));
53764fbda90SVal Packett 
53864fbda90SVal Packett err:
53964fbda90SVal Packett 	return (err);
54064fbda90SVal Packett }
54164fbda90SVal Packett 
54264fbda90SVal Packett int
atopcase_destroy(struct atopcase_softc * sc)54364fbda90SVal Packett atopcase_destroy(struct atopcase_softc *sc)
54464fbda90SVal Packett {
54564fbda90SVal Packett 	int err;
54664fbda90SVal Packett 
54764fbda90SVal Packett 	err = device_delete_children(sc->sc_dev);
54864fbda90SVal Packett 	if (err)
54964fbda90SVal Packett 		return (err);
55064fbda90SVal Packett 
55164fbda90SVal Packett 	if (sc->sc_backlight)
55264fbda90SVal Packett 		backlight_destroy(sc->sc_backlight);
55364fbda90SVal Packett 
55464fbda90SVal Packett 	return (0);
55564fbda90SVal Packett }
55664fbda90SVal Packett 
55764fbda90SVal Packett static struct atopcase_child *
atopcase_get_child_by_hidbus(device_t child)55864fbda90SVal Packett atopcase_get_child_by_hidbus(device_t child)
55964fbda90SVal Packett {
56064fbda90SVal Packett 	device_t parent = device_get_parent(child);
56164fbda90SVal Packett 	struct atopcase_softc *sc = device_get_softc(parent);
56264fbda90SVal Packett 
56364fbda90SVal Packett 	if (child == sc->sc_kb.hidbus)
56464fbda90SVal Packett 		return (&sc->sc_kb);
56564fbda90SVal Packett 	if (child == sc->sc_tp.hidbus)
56664fbda90SVal Packett 		return (&sc->sc_tp);
56764fbda90SVal Packett 	panic("unknown child");
56864fbda90SVal Packett }
56964fbda90SVal Packett 
57064fbda90SVal Packett void
atopcase_intr_setup(device_t dev,device_t child,hid_intr_t intr,void * context,struct hid_rdesc_info * rdesc)57164fbda90SVal Packett atopcase_intr_setup(device_t dev, device_t child, hid_intr_t intr,
57264fbda90SVal Packett     void *context, struct hid_rdesc_info *rdesc)
57364fbda90SVal Packett {
57464fbda90SVal Packett 	struct atopcase_child *ac = atopcase_get_child_by_hidbus(child);
57564fbda90SVal Packett 
57664fbda90SVal Packett 	if (intr == NULL)
57764fbda90SVal Packett 		return;
57864fbda90SVal Packett 
57964fbda90SVal Packett 	rdesc->rdsize = ATOPCASE_MSG_SIZE - sizeof(struct atopcase_header) - 2;
58064fbda90SVal Packett 	rdesc->grsize = 0;
58164fbda90SVal Packett 	rdesc->srsize = ATOPCASE_DATA_SIZE - sizeof(struct atopcase_header) - 2;
58264fbda90SVal Packett 	rdesc->wrsize = 0;
58364fbda90SVal Packett 
58464fbda90SVal Packett 	ac->intr_handler = intr;
58564fbda90SVal Packett 	ac->intr_ctx = context;
58664fbda90SVal Packett }
58764fbda90SVal Packett 
58864fbda90SVal Packett void
atopcase_intr_unsetup(device_t dev,device_t child)58964fbda90SVal Packett atopcase_intr_unsetup(device_t dev, device_t child)
59064fbda90SVal Packett {
59164fbda90SVal Packett }
59264fbda90SVal Packett 
59364fbda90SVal Packett int
atopcase_intr_start(device_t dev,device_t child)59464fbda90SVal Packett atopcase_intr_start(device_t dev, device_t child)
59564fbda90SVal Packett {
59664fbda90SVal Packett 	struct atopcase_softc *sc = device_get_softc(dev);
59764fbda90SVal Packett 	struct atopcase_child *ac = atopcase_get_child_by_hidbus(child);
59864fbda90SVal Packett 
59964fbda90SVal Packett 	if (ATOPCASE_IN_POLLING_MODE(sc))
60064fbda90SVal Packett 		sx_xlock(&sc->sc_write_sx);
60164fbda90SVal Packett 	else if (sc->sc_irq_ih != NULL)
60264fbda90SVal Packett 		mtx_lock(&sc->sc_mtx);
60364fbda90SVal Packett 	else
60464fbda90SVal Packett 		sx_xlock(&sc->sc_sx);
60564fbda90SVal Packett 	ac->open = true;
60664fbda90SVal Packett 	if (ATOPCASE_IN_POLLING_MODE(sc))
60764fbda90SVal Packett 		sx_xunlock(&sc->sc_write_sx);
60864fbda90SVal Packett 	else if (sc->sc_irq_ih != NULL)
60964fbda90SVal Packett 		mtx_unlock(&sc->sc_mtx);
61064fbda90SVal Packett 	else
61164fbda90SVal Packett 		sx_xunlock(&sc->sc_sx);
61264fbda90SVal Packett 
61364fbda90SVal Packett 	return (0);
61464fbda90SVal Packett }
61564fbda90SVal Packett 
61664fbda90SVal Packett int
atopcase_intr_stop(device_t dev,device_t child)61764fbda90SVal Packett atopcase_intr_stop(device_t dev, device_t child)
61864fbda90SVal Packett {
61964fbda90SVal Packett 	struct atopcase_softc *sc = device_get_softc(dev);
62064fbda90SVal Packett 	struct atopcase_child *ac = atopcase_get_child_by_hidbus(child);
62164fbda90SVal Packett 
62264fbda90SVal Packett 	if (ATOPCASE_IN_POLLING_MODE(sc))
62364fbda90SVal Packett 		sx_xlock(&sc->sc_write_sx);
62464fbda90SVal Packett 	else if (sc->sc_irq_ih != NULL)
62564fbda90SVal Packett 		mtx_lock(&sc->sc_mtx);
62664fbda90SVal Packett 	else
62764fbda90SVal Packett 		sx_xlock(&sc->sc_sx);
62864fbda90SVal Packett 	ac->open = false;
62964fbda90SVal Packett 	if (ATOPCASE_IN_POLLING_MODE(sc))
63064fbda90SVal Packett 		sx_xunlock(&sc->sc_write_sx);
63164fbda90SVal Packett 	else if (sc->sc_irq_ih != NULL)
63264fbda90SVal Packett 		mtx_unlock(&sc->sc_mtx);
63364fbda90SVal Packett 	else
63464fbda90SVal Packett 		sx_xunlock(&sc->sc_sx);
63564fbda90SVal Packett 
63664fbda90SVal Packett 	return (0);
63764fbda90SVal Packett }
63864fbda90SVal Packett 
63964fbda90SVal Packett void
atopcase_intr_poll(device_t dev,device_t child)64064fbda90SVal Packett atopcase_intr_poll(device_t dev, device_t child)
64164fbda90SVal Packett {
64264fbda90SVal Packett 	struct atopcase_softc *sc = device_get_softc(dev);
64364fbda90SVal Packett 
64464fbda90SVal Packett 	(void)atopcase_receive_packet(sc);
64564fbda90SVal Packett }
64664fbda90SVal Packett 
64764fbda90SVal Packett int
atopcase_get_rdesc(device_t dev,device_t child,void * buf,hid_size_t len)64864fbda90SVal Packett atopcase_get_rdesc(device_t dev, device_t child, void *buf, hid_size_t len)
64964fbda90SVal Packett {
65064fbda90SVal Packett 	struct atopcase_child *ac = atopcase_get_child_by_hidbus(child);
65164fbda90SVal Packett 
65264fbda90SVal Packett 	if (ac->rdesc_len != len)
65364fbda90SVal Packett 		return (ENXIO);
65464fbda90SVal Packett 	memcpy(buf, ac->rdesc, len);
65564fbda90SVal Packett 
65664fbda90SVal Packett 	return (0);
65764fbda90SVal Packett }
65864fbda90SVal Packett 
65964fbda90SVal Packett int
atopcase_set_report(device_t dev,device_t child,const void * buf,hid_size_t len,uint8_t type __unused,uint8_t id)66064fbda90SVal Packett atopcase_set_report(device_t dev, device_t child, const void *buf,
66164fbda90SVal Packett     hid_size_t len, uint8_t type __unused, uint8_t id)
66264fbda90SVal Packett {
66364fbda90SVal Packett 	struct atopcase_softc *sc = device_get_softc(dev);
66464fbda90SVal Packett 	struct atopcase_child *ac = atopcase_get_child_by_hidbus(child);
66564fbda90SVal Packett 	int err;
66664fbda90SVal Packett 
66764fbda90SVal Packett 	if (len >= ATOPCASE_DATA_SIZE - sizeof(struct atopcase_header) - 2)
66864fbda90SVal Packett 		return (EINVAL);
66964fbda90SVal Packett 
67064fbda90SVal Packett 	DPRINTF("%s HID command SET_REPORT %d (len %d): %*D\n",
67164fbda90SVal Packett 	    ac->name, id, len, len, buf, " ");
67264fbda90SVal Packett 
67364fbda90SVal Packett 	if (!ATOPCASE_IN_KDB())
67464fbda90SVal Packett 		sx_xlock(&sc->sc_write_sx);
67564fbda90SVal Packett 	atopcase_create_message(&sc->sc_buf, ac->device,
67664fbda90SVal Packett 	    ATOPCASE_MSG_TYPE_SET_REPORT(ac->device, id), 0, buf, len, 0);
67764fbda90SVal Packett 	err = atopcase_send(sc, &sc->sc_buf);
67864fbda90SVal Packett 	if (!ATOPCASE_IN_KDB())
67964fbda90SVal Packett 		sx_xunlock(&sc->sc_write_sx);
68064fbda90SVal Packett 
68164fbda90SVal Packett 	return (err);
68264fbda90SVal Packett }
68364fbda90SVal Packett 
68464fbda90SVal Packett int
atopcase_backlight_update_status(device_t dev,struct backlight_props * props)68564fbda90SVal Packett atopcase_backlight_update_status(device_t dev, struct backlight_props *props)
68664fbda90SVal Packett {
68764fbda90SVal Packett 	struct atopcase_softc *sc = device_get_softc(dev);
68864fbda90SVal Packett 	struct atopcase_bl_payload payload = { 0 };
68964fbda90SVal Packett 
69064fbda90SVal Packett 	payload.report_id = ATOPCASE_BKL_REPORT_ID;
69164fbda90SVal Packett 	payload.device = ATOPCASE_DEV_KBRD;
69264fbda90SVal Packett 	/*
69364fbda90SVal Packett 	 * Hardware range is 32-255 for visible backlight,
69464fbda90SVal Packett 	 * convert from percentages
69564fbda90SVal Packett 	 */
69664fbda90SVal Packett 	payload.level = (props->brightness == 0) ? 0 :
69764fbda90SVal Packett 		(32 + (223 * props->brightness / 100));
69864fbda90SVal Packett 	payload.status = (payload.level > 0) ? 0x01F4 : 0x1;
69964fbda90SVal Packett 
70064fbda90SVal Packett 	return (atopcase_set_report(dev, sc->sc_kb.hidbus, &payload,
70164fbda90SVal Packett 	    sizeof(payload), HID_OUTPUT_REPORT, ATOPCASE_BKL_REPORT_ID));
70264fbda90SVal Packett }
70364fbda90SVal Packett 
70464fbda90SVal Packett int
atopcase_backlight_get_status(device_t dev,struct backlight_props * props)70564fbda90SVal Packett atopcase_backlight_get_status(device_t dev, struct backlight_props *props)
70664fbda90SVal Packett {
70764fbda90SVal Packett 	struct atopcase_softc *sc = device_get_softc(dev);
70864fbda90SVal Packett 
70964fbda90SVal Packett 	props->brightness = sc->sc_backlight_level;
71064fbda90SVal Packett 	props->nlevels = 0;
71164fbda90SVal Packett 
71264fbda90SVal Packett 	return (0);
71364fbda90SVal Packett }
71464fbda90SVal Packett 
71564fbda90SVal Packett int
atopcase_backlight_get_info(device_t dev,struct backlight_info * info)71664fbda90SVal Packett atopcase_backlight_get_info(device_t dev, struct backlight_info *info)
71764fbda90SVal Packett {
71864fbda90SVal Packett 	info->type = BACKLIGHT_TYPE_KEYBOARD;
71964fbda90SVal Packett 	strlcpy(info->name, "Apple MacBook Keyboard", BACKLIGHTMAXNAMELENGTH);
72064fbda90SVal Packett 
72164fbda90SVal Packett 	return (0);
72264fbda90SVal Packett }
723