src/sys/arch/arm64/dev/apldc.c

1446 lines
33 KiB
C
Raw Normal View History

/* $OpenBSD: apldc.c,v 1.7 2023/04/10 15:14:04 tobhe Exp $ */
/*
* Copyright (c) 2022 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/device.h>
#include <sys/evcount.h>
#include <sys/malloc.h>
#include <sys/task.h>
#include <sys/timeout.h>
#include <machine/bus.h>
#include <machine/fdt.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_gpio.h>
#include <dev/ofw/fdt.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 <arm64/dev/rtkit.h>
#include <arm64/dev/simplebusvar.h>
#include "apldc.h"
#define DC_IRQ_MASK 0x0000
#define DC_IRQ_STAT 0x0004
#define DC_CONFIG_TX_THRESH 0x0000
#define DC_CONFIG_RX_THRESH 0x0004
#define DC_DATA_TX8 0x0004
#define DC_DATA_TX32 0x0010
#define DC_DATA_TX_FREE 0x0014
#define DC_DATA_RX8 0x001c
#define DC_DATA_RX8_COUNT(d) ((d) & 0x7f)
#define DC_DATA_RX8_DATA(d) (((d) >> 8) & 0xff)
#define DC_DATA_RX32 0x0028
#define DC_DATA_RX_COUNT 0x002c
#define APLDC_MAX_INTR 32
#define HREAD4(sc, reg) \
(bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)))
#define HWRITE4(sc, reg, val) \
bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val))
#define HSET4(sc, reg, bits) \
HWRITE4((sc), (reg), HREAD4((sc), (reg)) | (bits))
#define HCLR4(sc, reg, bits) \
HWRITE4((sc), (reg), HREAD4((sc), (reg)) & ~(bits))
struct apldchidev_attach_args {
const char *aa_name;
void *aa_desc;
size_t aa_desclen;
};
struct intrhand {
int (*ih_func)(void *);
void *ih_arg;
int ih_ipl;
int ih_irq;
int ih_level;
struct evcount ih_count;
char *ih_name;
void *ih_sc;
};
struct apldc_softc {
struct simplebus_softc sc_sbus;
bus_space_tag_t sc_iot;
bus_space_handle_t sc_ioh;
void *sc_ih;
struct intrhand *sc_handlers[APLDC_MAX_INTR];
struct interrupt_controller sc_ic;
};
int apldc_match(struct device *, void *, void *);
void apldc_attach(struct device *, struct device *, void *);
const struct cfattach apldc_ca = {
sizeof (struct apldc_softc), apldc_match, apldc_attach
};
struct cfdriver apldc_cd = {
NULL, "apldc", DV_DULL
};
int apldc_intr(void *);
void *apldc_intr_establish(void *, int *, int, struct cpu_info *,
int (*)(void *), void *, char *);
void apldc_intr_enable(void *);
void apldc_intr_disable(void *);
void apldc_intr_barrier(void *);
int
apldc_match(struct device *parent, void *match, void *aux)
{
struct fdt_attach_args *faa = aux;
return OF_is_compatible(faa->fa_node, "apple,dockchannel");
}
void
apldc_attach(struct device *parent, struct device *self, void *aux)
{
struct apldc_softc *sc = (struct apldc_softc *)self;
struct fdt_attach_args *faa = aux;
if (faa->fa_nreg < 1) {
printf(": no registers\n");
return;
}
sc->sc_iot = faa->fa_iot;
if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
faa->fa_reg[0].size, 0, &sc->sc_ioh)) {
printf(": can't map registers\n");
return;
}
/* Disable and clear all interrupts. */
HWRITE4(sc, DC_IRQ_MASK, 0);
HWRITE4(sc, DC_IRQ_STAT, 0xffffffff);
sc->sc_ih = fdt_intr_establish(faa->fa_node, IPL_TTY,
apldc_intr, sc, sc->sc_sbus.sc_dev.dv_xname);
sc->sc_ic.ic_node = faa->fa_node;
sc->sc_ic.ic_cookie = sc;
sc->sc_ic.ic_establish = apldc_intr_establish;
sc->sc_ic.ic_enable = apldc_intr_enable;
sc->sc_ic.ic_disable = apldc_intr_disable;
sc->sc_ic.ic_barrier = apldc_intr_barrier;
fdt_intr_register(&sc->sc_ic);
simplebus_attach(parent, &sc->sc_sbus.sc_dev, faa);
}
int
apldc_intr(void *arg)
{
struct apldc_softc *sc = arg;
struct intrhand *ih;
uint32_t stat, pending;
int irq, s;
stat = HREAD4(sc, DC_IRQ_STAT);
pending = stat;
while (pending) {
irq = ffs(pending) - 1;
ih = sc->sc_handlers[irq];
if (ih) {
s = splraise(ih->ih_ipl);
if (ih->ih_func(ih->ih_arg))
ih->ih_count.ec_count++;
splx(s);
}
pending &= ~(1 << irq);
}
HWRITE4(sc, DC_IRQ_STAT, stat);
return 1;
}
void *
apldc_intr_establish(void *cookie, int *cells, int ipl,
struct cpu_info *ci, int (*func)(void *), void *arg, char *name)
{
struct apldc_softc *sc = cookie;
struct intrhand *ih;
int irq = cells[0];
int level = cells[1];
if (irq < 0 || irq >= APLDC_MAX_INTR)
return NULL;
if (ipl != IPL_TTY)
return NULL;
if (ci != NULL && !CPU_IS_PRIMARY(ci))
return NULL;
if (sc->sc_handlers[irq])
return NULL;
ih = malloc(sizeof(*ih), M_DEVBUF, M_WAITOK);
ih->ih_func = func;
ih->ih_arg = arg;
ih->ih_ipl = ipl & IPL_IRQMASK;
ih->ih_irq = irq;
ih->ih_name = name;
ih->ih_level = level;
ih->ih_sc = sc;
sc->sc_handlers[irq] = ih;
if (name != NULL)
evcount_attach(&ih->ih_count, name, &ih->ih_irq);
return ih;
}
void
apldc_intr_enable(void *cookie)
{
struct intrhand *ih = cookie;
struct apldc_softc *sc = ih->ih_sc;
HSET4(sc, DC_IRQ_MASK, 1 << ih->ih_irq);
}
void
apldc_intr_disable(void *cookie)
{
struct intrhand *ih = cookie;
struct apldc_softc *sc = ih->ih_sc;
HCLR4(sc, DC_IRQ_MASK, 1 << ih->ih_irq);
}
void
apldc_intr_barrier(void *cookie)
{
struct intrhand *ih = cookie;
struct apldc_softc *sc = ih->ih_sc;
intr_barrier(sc->sc_ih);
}
#define APLDCHIDEV_DESC_MAX 512
#define APLDCHIDEV_PKT_MAX 1024
#define APLDCHIDEV_GPIO_MAX 4
#define APLDCHIDEV_NUM_GPIOS 16
struct apldchidev_gpio {
struct apldchidev_softc *ag_sc;
uint8_t ag_id;
uint8_t ag_iface;
uint32_t ag_gpio[APLDCHIDEV_GPIO_MAX];
struct task ag_task;
};
struct apldchidev_softc {
struct device sc_dev;
bus_space_tag_t sc_iot;
bus_space_handle_t sc_cfg_ioh;
bus_space_handle_t sc_data_ioh;
bus_dma_tag_t sc_dmat;
int sc_node;
void *sc_rx_ih;
uint8_t sc_seq_comm;
uint8_t sc_iface_stm;
uint8_t sc_seq_stm;
uint8_t sc_stmdesc[APLDCHIDEV_DESC_MAX];
size_t sc_stmdesclen;
int sc_stm_ready;
uint8_t sc_iface_kbd;
uint8_t sc_seq_kbd;
struct device *sc_kbd;
uint8_t sc_kbddesc[APLDCHIDEV_DESC_MAX];
size_t sc_kbddesclen;
int sc_kbd_ready;
uint8_t sc_iface_mt;
uint8_t sc_seq_mt;
struct device *sc_mt;
uint8_t sc_mtdesc[APLDCHIDEV_DESC_MAX];
size_t sc_mtdesclen;
int sc_mt_ready;
struct apldchidev_gpio sc_gpio[APLDCHIDEV_NUM_GPIOS];
u_int sc_ngpios;
uint8_t sc_gpio_cmd[APLDCHIDEV_PKT_MAX];
size_t sc_gpio_cmd_len;
uint8_t sc_cmd_iface;
uint8_t sc_cmd_seq;
uint32_t sc_retcode;
int sc_busy;
};
int apldchidev_match(struct device *, void *, void *);
void apldchidev_attach(struct device *, struct device *, void *);
const struct cfattach apldchidev_ca = {
sizeof(struct apldchidev_softc), apldchidev_match, apldchidev_attach
};
struct cfdriver apldchidev_cd = {
NULL, "apldchidev", DV_DULL
};
void apldchidev_attachhook(struct device *);
void apldchidev_cmd(struct apldchidev_softc *, uint8_t, uint8_t,
void *, size_t);
void apldchidev_wait(struct apldchidev_softc *);
int apldchidev_send_firmware(struct apldchidev_softc *, int,
void *, size_t);
void apldchidev_enable(struct apldchidev_softc *, uint8_t);
void apldchidev_reset(struct apldchidev_softc *, uint8_t, uint8_t);
int apldchidev_rx_intr(void *);
void apldchidev_gpio_task(void *);
int
apldchidev_match(struct device *parent, void *cfdata, void *aux)
{
struct fdt_attach_args *faa = aux;
return OF_is_compatible(faa->fa_node, "apple,dockchannel-hid");
}
void
apldchidev_attach(struct device *parent, struct device *self, void *aux)
{
struct apldchidev_softc *sc = (struct apldchidev_softc *)self;
struct fdt_attach_args *faa = aux;
struct apldchidev_attach_args aa;
uint32_t phandle;
int error, idx, retry;
if (faa->fa_nreg < 2) {
printf(": no registers\n");
return;
}
sc->sc_iot = faa->fa_iot;
if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
faa->fa_reg[0].size, 0, &sc->sc_cfg_ioh)) {
printf(": can't map registers\n");
return;
}
if (bus_space_map(sc->sc_iot, faa->fa_reg[1].addr,
faa->fa_reg[1].size, 0, &sc->sc_data_ioh)) {
printf(": can't map registers\n");
return;
}
sc->sc_dmat = faa->fa_dmat;
sc->sc_node = faa->fa_node;
idx = OF_getindex(faa->fa_node, "rx", "interrupt-names");
if (idx < 0) {
printf(": no rx interrupt\n");
return;
}
sc->sc_rx_ih = fdt_intr_establish_idx(faa->fa_node, idx, IPL_TTY,
apldchidev_rx_intr, sc, sc->sc_dev.dv_xname);
if (sc->sc_rx_ih == NULL) {
printf(": can't establish interrupt\n");
return;
}
phandle = OF_getpropint(faa->fa_node, "apple,helper-cpu", 0);
if (phandle) {
error = aplrtk_start(phandle);
if (error) {
printf(": can't start helper CPU\n");
return;
}
}
printf("\n");
/* Poll until we have received the STM HID descriptor. */
for (retry = 10; retry > 0; retry--) {
if (sc->sc_stmdesclen > 0)
break;
apldchidev_rx_intr(sc);
delay(1000);
}
if (sc->sc_stmdesclen > 0) {
/* Enable interface. */
apldchidev_enable(sc, sc->sc_iface_stm);
}
/* Poll until we have received the keyboard HID descriptor. */
for (retry = 10; retry > 0; retry--) {
if (sc->sc_kbddesclen > 0)
break;
apldchidev_rx_intr(sc);
delay(1000);
}
if (sc->sc_kbddesclen > 0) {
/* Enable interface. */
apldchidev_enable(sc, sc->sc_iface_kbd);
aa.aa_name = "keyboard";
aa.aa_desc = sc->sc_kbddesc;
aa.aa_desclen = sc->sc_kbddesclen;
sc->sc_kbd = config_found(self, &aa, NULL);
}
bus_space_write_4(sc->sc_iot, sc->sc_cfg_ioh, DC_CONFIG_RX_THRESH, 8);
fdt_intr_enable(sc->sc_rx_ih);
#if NAPLDCMS > 0
config_mountroot(self, apldchidev_attachhook);
#endif
}
int
apldchidev_read(struct apldchidev_softc *sc, void *buf, size_t len,
uint32_t *checksum)
{
uint8_t *dst = buf;
uint32_t data;
int shift = 0;
while (len > 0) {
data = bus_space_read_4(sc->sc_iot, sc->sc_data_ioh,
DC_DATA_RX8);
if (DC_DATA_RX8_COUNT(data) > 0) {
*dst++ = DC_DATA_RX8_DATA(data);
*checksum += (DC_DATA_RX8_DATA(data) << shift);
shift += 8;
if (shift > 24)
shift = 0;
len--;
} else {
delay(10);
}
}
return 0;
}
int
apldchidev_write(struct apldchidev_softc *sc, const void *buf, size_t len,
uint32_t *checksum)
{
const uint8_t *src = buf;
uint32_t free;
int shift = 0;
while (len > 0) {
free = bus_space_read_4(sc->sc_iot, sc->sc_data_ioh,
DC_DATA_TX_FREE);
if (free > 0) {
if (checksum)
*checksum -= *src << shift;
bus_space_write_4(sc->sc_iot, sc->sc_data_ioh,
DC_DATA_TX8, *src++);
shift += 8;
if (shift > 24)
shift = 0;
len--;
} else {
delay(10);
}
}
return 0;
}
struct mtp_hdr {
uint8_t hdr_len;
uint8_t chan;
#define MTP_CHAN_CMD 0x11
#define MTP_CHAN_REPORT 0x12
uint16_t pkt_len;
uint8_t seq;
uint8_t iface;
#define MTP_IFACE_COMM 0
uint16_t pad;
} __packed;
struct mtp_subhdr {
uint8_t flags;
#define MTP_GROUP_SHIFT 6
#define MTP_GROUP(x) ((x >> 6) & 0x3)
#define MTP_GROUP_INPUT 0
#define MTP_GROUP_OUTPUT 1
#define MTP_GROUP_CMD 2
#define MTP_REQ_SHIFT 0
#define MTP_REQ(x) ((x >> 0) & 0x3f)
#define MTP_REQ_SET_REPORT 0
#define MTP_REQ_GET_REPORT 1
uint8_t unk;
uint16_t len;
uint32_t retcode;
} __packed;
struct mtp_init_hdr {
uint8_t type;
#define MTP_EVENT_GPIO_CMD 0xa0
#define MTP_EVENT_INIT 0xf0
#define MTP_EVENT_READY 0xf1
uint8_t unk1;
uint8_t unk2;
uint8_t iface;
char name[16];
} __packed;
struct mtp_init_block_hdr {
uint16_t type;
#define MTP_BLOCK_DESCRIPTOR 0
#define MTP_BLOCK_GPIO_REQ 1
#define MTP_BLOCK_END 2
uint16_t subtype;
uint16_t len;
} __packed;
struct mtp_gpio_req {
uint16_t unk;
uint16_t id;
char name[32];
} __packed;
struct mtp_gpio_cmd {
uint8_t type;
uint8_t iface;
uint8_t id;
uint8_t unk;
uint8_t cmd;
#define MTP_GPIO_CMD_TOGGLE 0x03
} __packed;
struct mtp_gpio_ack {
uint8_t type;
uint32_t retcode;
uint8_t cmd[512];
} __packed;
#define MTP_CMD_RESET_INTERFACE 0x40
#define MTP_CMD_SEND_FIRMWARE 0x95
#define MTP_CMD_ENABLE_INTERFACE 0xb4
#define MTP_CMD_ACK_GPIO_CMD 0xa1
void
apldchidev_handle_gpio_req(struct apldchidev_softc *sc, uint8_t iface,
void *buf, size_t len)
{
struct mtp_gpio_req *req = buf;
uint32_t gpio[APLDCHIDEV_GPIO_MAX];
char name[64];
int node = -1;
if (len < sizeof(*req))
return;
if (sc->sc_ngpios >= APLDCHIDEV_NUM_GPIOS)
return;
if (iface == sc->sc_iface_mt)
node = OF_getnodebyname(sc->sc_node, "multi-touch");
else if (iface == sc->sc_iface_stm)
node = OF_getnodebyname(sc->sc_node, "stm");
if (node == -1)
return;
snprintf(name, sizeof(name), "apple,%s-gpios", req->name);
len = OF_getproplen(node, name);
if (len <= 0 || len > sizeof(gpio))
return;
OF_getpropintarray(node, name, gpio, len);
gpio_controller_config_pin(gpio, GPIO_CONFIG_OUTPUT);
gpio_controller_set_pin(gpio, 0);
sc->sc_gpio[sc->sc_ngpios].ag_sc = sc;
sc->sc_gpio[sc->sc_ngpios].ag_id = req->id;
sc->sc_gpio[sc->sc_ngpios].ag_iface = iface;
memcpy(sc->sc_gpio[sc->sc_ngpios].ag_gpio, gpio, len);
task_set(&sc->sc_gpio[sc->sc_ngpios].ag_task,
apldchidev_gpio_task, &sc->sc_gpio[sc->sc_ngpios]);
sc->sc_ngpios++;
}
void
apldchidev_handle_init(struct apldchidev_softc *sc, uint8_t iface,
void *buf, size_t len)
{
struct mtp_init_block_hdr *bhdr = buf;
for (;;) {
if (len < sizeof(*bhdr))
return;
len -= sizeof(*bhdr);
if (len < bhdr->len)
return;
len -= bhdr->len;
switch (bhdr->type) {
case MTP_BLOCK_DESCRIPTOR:
if (iface == sc->sc_iface_kbd &&
bhdr->len <= sizeof(sc->sc_kbddesc)) {
memcpy(sc->sc_kbddesc, bhdr + 1, bhdr->len);
sc->sc_kbddesclen = bhdr->len;
} else if (iface == sc->sc_iface_mt &&
bhdr->len <= sizeof(sc->sc_mtdesc)) {
memcpy(sc->sc_mtdesc, bhdr + 1, bhdr->len);
sc->sc_mtdesclen = bhdr->len;
} else if (iface == sc->sc_iface_stm &&
bhdr->len <= sizeof(sc->sc_stmdesc)) {
memcpy(sc->sc_stmdesc, bhdr + 1, bhdr->len);
sc->sc_stmdesclen = bhdr->len;
}
break;
case MTP_BLOCK_GPIO_REQ:
apldchidev_handle_gpio_req(sc, iface,
bhdr + 1, bhdr->len);
break;
case MTP_BLOCK_END:
return;
default:
printf("%s: unhandled block type 0x%04x\n",
sc->sc_dev.dv_xname, bhdr->type);
break;
}
bhdr = (struct mtp_init_block_hdr *)
((uint8_t *)(bhdr + 1) + bhdr->len);
}
}
void
apldchidev_handle_comm(struct apldchidev_softc *sc, void *buf, size_t len)
{
struct mtp_init_hdr *ihdr = buf;
struct mtp_gpio_cmd *cmd = buf;
uint8_t iface;
int i;
switch (ihdr->type) {
case MTP_EVENT_INIT:
if (strcmp(ihdr->name, "keyboard") == 0) {
sc->sc_iface_kbd = ihdr->iface;
apldchidev_handle_init(sc, ihdr->iface,
ihdr + 1, len - sizeof(*ihdr));
}
if (strcmp(ihdr->name, "multi-touch") == 0) {
sc->sc_iface_mt = ihdr->iface;
apldchidev_handle_init(sc, ihdr->iface,
ihdr + 1, len - sizeof(*ihdr));
}
if (strcmp(ihdr->name, "stm") == 0) {
sc->sc_iface_stm = ihdr->iface;
apldchidev_handle_init(sc, ihdr->iface,
ihdr + 1, len - sizeof(*ihdr));
}
break;
case MTP_EVENT_READY:
iface = ihdr->unk1;
if (iface == sc->sc_iface_stm)
sc->sc_stm_ready = 1;
if (iface == sc->sc_iface_kbd)
sc->sc_kbd_ready = 1;
if (iface == sc->sc_iface_mt)
sc->sc_mt_ready = 1;
break;
case MTP_EVENT_GPIO_CMD:
for (i =0; i < sc->sc_ngpios; i++) {
if (cmd->id == sc->sc_gpio[i].ag_id &&
cmd->iface == sc->sc_gpio[i].ag_iface &&
cmd->cmd == MTP_GPIO_CMD_TOGGLE) {
/* Stash the command for the reply. */
KASSERT(len < sizeof(sc->sc_gpio_cmd));
memcpy(sc->sc_gpio_cmd, buf, len);
sc->sc_gpio_cmd_len = len;
task_add(systq, &sc->sc_gpio[i].ag_task);
return;
}
}
printf("%s: unhandled gpio id %d iface %d cmd 0x%02x\n",
sc->sc_dev.dv_xname, cmd->id, cmd->iface, cmd->cmd);
break;
default:
printf("%s: unhandled comm event 0x%02x\n",
sc->sc_dev.dv_xname, ihdr->type);
break;
}
}
void
apldchidev_gpio_task(void *arg)
{
struct apldchidev_gpio *ag = arg;
struct apldchidev_softc *sc = ag->ag_sc;
struct mtp_gpio_ack *ack;
uint8_t flags;
size_t len;
gpio_controller_set_pin(ag->ag_gpio, 1);
delay(10000);
gpio_controller_set_pin(ag->ag_gpio, 0);
len = sizeof(*ack) + sc->sc_gpio_cmd_len;
ack = malloc(len, M_TEMP, M_WAITOK);
ack->type = MTP_CMD_ACK_GPIO_CMD;
ack->retcode = 0;
memcpy(ack->cmd, sc->sc_gpio_cmd, sc->sc_gpio_cmd_len);
flags = MTP_GROUP_CMD << MTP_GROUP_SHIFT;
flags |= MTP_REQ_SET_REPORT << MTP_REQ_SHIFT;
apldchidev_cmd(sc, MTP_IFACE_COMM, flags, ack, len);
free(ack, M_TEMP, len);
}
void apldckbd_intr(struct device *, uint8_t *, size_t);
void apldcms_intr(struct device *, uint8_t *, size_t);
int
apldchidev_rx_intr(void *arg)
{
struct apldchidev_softc *sc = arg;
struct mtp_hdr hdr;
struct mtp_subhdr *shdr;
uint32_t checksum = 0;
char buf[APLDCHIDEV_PKT_MAX];
apldchidev_read(sc, &hdr, sizeof(hdr), &checksum);
apldchidev_read(sc, buf, hdr.pkt_len + 4, &checksum);
if (checksum != 0xffffffff) {
printf("%s: packet checksum error\n", sc->sc_dev.dv_xname);
return 1;
}
if (hdr.pkt_len < sizeof(*shdr)) {
printf("%s: packet too small\n", sc->sc_dev.dv_xname);
return 1;
}
shdr = (struct mtp_subhdr *)buf;
if (MTP_GROUP(shdr->flags) == MTP_GROUP_OUTPUT ||
MTP_GROUP(shdr->flags) == MTP_GROUP_CMD) {
if (hdr.iface != sc->sc_cmd_iface) {
printf("%s: got ack for unexpected iface\n",
sc->sc_dev.dv_xname);
}
if (hdr.seq != sc->sc_cmd_seq) {
printf("%s: got ack with unexpected seq\n",
sc->sc_dev.dv_xname);
}
sc->sc_retcode = shdr->retcode;
sc->sc_busy = 0;
wakeup(sc);
return 1;
}
if (MTP_GROUP(shdr->flags) != MTP_GROUP_INPUT) {
printf("%s: unhandled group 0x%02x\n",
sc->sc_dev.dv_xname, shdr->flags);
return 1;
}
if (hdr.iface == MTP_IFACE_COMM)
apldchidev_handle_comm(sc, shdr + 1, shdr->len);
else if (hdr.iface == sc->sc_iface_kbd && sc->sc_kbd)
apldckbd_intr(sc->sc_kbd, (uint8_t *)(shdr + 1), shdr->len);
else if (hdr.iface == sc->sc_iface_mt && sc->sc_mt)
apldcms_intr(sc->sc_mt, (uint8_t *)(shdr + 1), shdr->len);
else {
printf("%s: unhandled iface %d\n",
sc->sc_dev.dv_xname, hdr.iface);
}
wakeup(sc);
return 1;
}
void
apldchidev_cmd(struct apldchidev_softc *sc, uint8_t iface, uint8_t flags,
void *data, size_t len)
{
struct mtp_hdr hdr;
struct mtp_subhdr shdr;
uint32_t checksum = 0xffffffff;
uint8_t pad[4];
KASSERT(sc->sc_busy == 0);
sc->sc_busy = 1;
memset(&hdr, 0, sizeof(hdr));
hdr.hdr_len = sizeof(hdr);
hdr.chan = MTP_CHAN_CMD;
hdr.pkt_len = roundup(len, 4) + sizeof(shdr);
if (iface == MTP_IFACE_COMM)
hdr.seq = sc->sc_seq_comm++;
else if (iface == sc->sc_iface_kbd)
hdr.seq = sc->sc_seq_kbd++;
else if (iface == sc->sc_iface_mt)
hdr.seq = sc->sc_seq_mt++;
else if (iface == sc->sc_iface_stm)
hdr.seq = sc->sc_seq_stm++;
hdr.iface = iface;
sc->sc_cmd_iface = hdr.iface;
sc->sc_cmd_seq = hdr.seq;
memset(&shdr, 0, sizeof(shdr));
shdr.flags = flags;
shdr.len = len;
apldchidev_write(sc, &hdr, sizeof(hdr), &checksum);
apldchidev_write(sc, &shdr, sizeof(shdr), &checksum);
apldchidev_write(sc, data, len & ~3, &checksum);
if (len & 3) {
memset(pad, 0, sizeof(pad));
memcpy(pad, &data[len & ~3], len & 3);
apldchidev_write(sc, pad, sizeof(pad), &checksum);
}
apldchidev_write(sc, &checksum, sizeof(checksum), NULL);
}
void
apldchidev_wait(struct apldchidev_softc *sc)
{
int retry, error;
if (cold) {
for (retry = 10; retry > 0; retry--) {
if (sc->sc_busy == 0)
break;
apldchidev_rx_intr(sc);
delay(1000);
}
return;
}
while (sc->sc_busy) {
error = tsleep_nsec(sc, PZERO, "apldcwt", SEC_TO_NSEC(1));
if (error == EWOULDBLOCK)
return;
}
if (sc->sc_retcode) {
printf("%s: command failed with error 0x%04x\n",
sc->sc_dev.dv_xname, sc->sc_retcode);
}
}
void
apldchidev_enable(struct apldchidev_softc *sc, uint8_t iface)
{
uint8_t cmd[2] = { MTP_CMD_ENABLE_INTERFACE, iface };
uint8_t flags;
flags = MTP_GROUP_CMD << MTP_GROUP_SHIFT;
flags |= MTP_REQ_SET_REPORT << MTP_REQ_SHIFT;
apldchidev_cmd(sc, MTP_IFACE_COMM, flags, cmd, sizeof(cmd));
apldchidev_wait(sc);
}
void
apldchidev_reset(struct apldchidev_softc *sc, uint8_t iface, uint8_t state)
{
uint8_t cmd[4] = { MTP_CMD_RESET_INTERFACE, 1, iface, state };
uint8_t flags;
flags = MTP_GROUP_CMD << MTP_GROUP_SHIFT;
flags |= MTP_REQ_SET_REPORT << MTP_REQ_SHIFT;
apldchidev_cmd(sc, MTP_IFACE_COMM, flags, cmd, sizeof(cmd));
apldchidev_wait(sc);
}
#if NAPLDCMS > 0
int
apldchidev_send_firmware(struct apldchidev_softc *sc, int iface,
void *ucode, size_t ucode_size)
{
bus_dmamap_t map;
bus_dma_segment_t seg;
uint8_t cmd[16] = {};
uint64_t addr;
uint32_t size;
uint8_t flags;
caddr_t buf;
int nsegs;
int error;
error = bus_dmamap_create(sc->sc_dmat, ucode_size, 1, ucode_size, 0,
BUS_DMA_WAITOK, &map);
if (error)
return error;
error = bus_dmamem_alloc(sc->sc_dmat, ucode_size, 4 * PAGE_SIZE, 0,
&seg, 1, &nsegs, BUS_DMA_WAITOK);
if (error) {
bus_dmamap_destroy(sc->sc_dmat, map);
return error;
}
error = bus_dmamem_map(sc->sc_dmat, &seg, 1, ucode_size, &buf,
BUS_DMA_WAITOK);
if (error) {
bus_dmamem_free(sc->sc_dmat, &seg, 1);
bus_dmamap_destroy(sc->sc_dmat, map);
return error;
}
error = bus_dmamap_load_raw(sc->sc_dmat, map, &seg, 1,
ucode_size, BUS_DMA_WAITOK);
if (error) {
bus_dmamem_unmap(sc->sc_dmat, buf, ucode_size);
bus_dmamem_free(sc->sc_dmat, &seg, 1);
bus_dmamap_destroy(sc->sc_dmat, map);
return error;
}
memcpy(buf, ucode, ucode_size);
bus_dmamap_sync(sc->sc_dmat, map, 0, ucode_size, BUS_DMASYNC_PREWRITE);
cmd[0] = MTP_CMD_SEND_FIRMWARE;
cmd[1] = 2;
cmd[2] = 0;
cmd[3] = iface;
addr = map->dm_segs[0].ds_addr;
memcpy(&cmd[4], &addr, sizeof(addr));
size = map->dm_segs[0].ds_len;
memcpy(&cmd[12], &size, sizeof(size));
flags = MTP_GROUP_CMD << MTP_GROUP_SHIFT;
flags |= MTP_REQ_SET_REPORT << MTP_REQ_SHIFT;
apldchidev_cmd(sc, MTP_IFACE_COMM, flags, cmd, sizeof(cmd));
apldchidev_wait(sc);
bus_dmamap_unload(sc->sc_dmat, map);
bus_dmamem_unmap(sc->sc_dmat, buf, ucode_size);
bus_dmamem_free(sc->sc_dmat, &seg, 1);
bus_dmamap_destroy(sc->sc_dmat, map);
return 0;
}
struct mtp_fwhdr {
uint32_t magic;
#define MTP_FW_MAGIC 0x46444948
uint32_t version;
#define MTP_FW_VERSION 1
uint32_t hdr_len;
uint32_t data_len;
uint32_t iface_off;
};
void
apldchidev_attachhook(struct device *self)
{
struct apldchidev_softc *sc = (struct apldchidev_softc *)self;
struct apldchidev_attach_args aa;
uint8_t *ucode;
size_t ucode_size;
uint8_t *data;
size_t size;
int error;
/* Wait until we have received the multi-touch HID descriptor. */
while (sc->sc_mtdesclen == 0) {
error = tsleep_nsec(sc, PZERO, "apldcmt", SEC_TO_NSEC(1));
if (error == EWOULDBLOCK)
return;
}
if (sc->sc_mtdesclen > 0) {
struct mtp_fwhdr *hdr;
char *firmware_name;
int node, len;
/* Enable interface. */
apldchidev_enable(sc, sc->sc_iface_mt);
node = OF_getnodebyname(sc->sc_node, "multi-touch");
if (node == -1)
return;
len = OF_getproplen(node, "firmware-name");
if (len <= 0)
return;
firmware_name = malloc(len, M_TEMP, M_WAITOK);
OF_getprop(node, "firmware-name", firmware_name, len);
error = loadfirmware(firmware_name, &ucode, &ucode_size);
if (error) {
printf("%s: error %d, could not read firmware %s\n",
sc->sc_dev.dv_xname, error, firmware_name);
free(firmware_name, M_TEMP, len);
return;
}
free(firmware_name, M_TEMP, len);
hdr = (struct mtp_fwhdr *)ucode;
if (sizeof(hdr) > ucode_size ||
hdr->hdr_len + hdr->data_len > ucode_size) {
printf("%s: loaded firmware is too small\n",
sc->sc_dev.dv_xname);
return;
}
if (hdr->magic != MTP_FW_MAGIC) {
printf("%s: wrong firmware magic number 0x%08x\n",
sc->sc_dev.dv_xname, hdr->magic);
return;
}
if (hdr->version != MTP_FW_VERSION) {
printf("%s: wrong firmware version %d\n",
sc->sc_dev.dv_xname, hdr->version);
return;
}
data = ucode + hdr->hdr_len;
if (hdr->iface_off)
data[hdr->iface_off] = sc->sc_iface_mt;
size = hdr->data_len;
apldchidev_send_firmware(sc, sc->sc_iface_mt, data, size);
apldchidev_reset(sc, sc->sc_iface_mt, 0);
apldchidev_reset(sc, sc->sc_iface_mt, 2);
/* Wait until ready. */
while (sc->sc_mt_ready == 0) {
error = tsleep_nsec(sc, PZERO, "apldcmt",
SEC_TO_NSEC(10));
if (error == EWOULDBLOCK)
return;
}
aa.aa_name = "multi-touch";
aa.aa_desc = sc->sc_mtdesc;
aa.aa_desclen = sc->sc_mtdesclen;
sc->sc_mt = config_found(self, &aa, NULL);
}
}
#endif
void
apldchidev_set_leds(struct apldchidev_softc *sc, uint8_t leds)
{
uint8_t report[2] = { 1, leds };
uint8_t flags;
flags = MTP_GROUP_OUTPUT << MTP_GROUP_SHIFT;
flags |= MTP_REQ_SET_REPORT << MTP_REQ_SHIFT;
apldchidev_cmd(sc, sc->sc_iface_kbd, flags, report, sizeof(report));
}
/* Keyboard */
struct apldckbd_softc {
struct device sc_dev;
struct apldchidev_softc *sc_hidev;
struct hidkbd sc_kbd;
int sc_spl;
};
void apldckbd_cngetc(void *, u_int *, int *);
void apldckbd_cnpollc(void *, int);
void apldckbd_cnbell(void *, u_int, u_int, u_int);
const struct wskbd_consops apldckbd_consops = {
apldckbd_cngetc,
apldckbd_cnpollc,
apldckbd_cnbell,
};
int apldckbd_enable(void *, int);
void apldckbd_set_leds(void *, int);
int apldckbd_ioctl(void *, u_long, caddr_t, int, struct proc *);
const struct wskbd_accessops apldckbd_accessops = {
.enable = apldckbd_enable,
.ioctl = apldckbd_ioctl,
.set_leds = apldckbd_set_leds,
};
int apldckbd_match(struct device *, void *, void *);
void apldckbd_attach(struct device *, struct device *, void *);
const struct cfattach apldckbd_ca = {
sizeof(struct apldckbd_softc), apldckbd_match, apldckbd_attach
};
struct cfdriver apldckbd_cd = {
NULL, "apldckbd", DV_DULL
};
int
apldckbd_match(struct device *parent, void *match, void *aux)
{
struct apldchidev_attach_args *aa = aux;
return strcmp(aa->aa_name, "keyboard") == 0;
}
void
apldckbd_attach(struct device *parent, struct device *self, void *aux)
{
struct apldckbd_softc *sc = (struct apldckbd_softc *)self;
struct apldchidev_attach_args *aa = aux;
struct hidkbd *kbd = &sc->sc_kbd;
#define APLHIDEV_KBD_DEVICE 1
sc->sc_hidev = (struct apldchidev_softc *)parent;
if (hidkbd_attach(self, kbd, 1, 0, APLHIDEV_KBD_DEVICE,
aa->aa_desc, aa->aa_desclen))
return;
printf("\n");
if (hid_locate(aa->aa_desc, aa->aa_desclen, HID_USAGE2(HUP_APPLE, HUG_FN_KEY),
1, hid_input, &kbd->sc_fn, NULL))
kbd->sc_munge = hidkbd_apple_munge;
if (kbd->sc_console_keyboard) {
extern struct wskbd_mapdata ukbd_keymapdata;
ukbd_keymapdata.layout = KB_US | KB_DEFAULT;
wskbd_cnattach(&apldckbd_consops, sc, &ukbd_keymapdata);
apldckbd_enable(sc, 1);
}
hidkbd_attach_wskbd(kbd, KB_US | KB_DEFAULT, &apldckbd_accessops);
}
void
apldckbd_intr(struct device *self, uint8_t *packet, size_t packetlen)
{
struct apldckbd_softc *sc = (struct apldckbd_softc *)self;
struct hidkbd *kbd = &sc->sc_kbd;
if (kbd->sc_enabled)
hidkbd_input(kbd, &packet[1], packetlen - 1);
}
int
apldckbd_enable(void *v, int on)
{
struct apldckbd_softc *sc = v;
struct hidkbd *kbd = &sc->sc_kbd;
return hidkbd_enable(kbd, on);
}
int
apldckbd_ioctl(void *v, u_long cmd, caddr_t data, int flag, struct proc *p)
{
struct apldckbd_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;
case WSKBDIO_SETLEDS:
apldckbd_set_leds(v, *(int *)data);
return 0;
default:
return hidkbd_ioctl(kbd, cmd, data, flag, p);
}
}
void
apldckbd_set_leds(void *v, int leds)
{
struct apldckbd_softc *sc = v;
struct hidkbd *kbd = &sc->sc_kbd;
uint8_t res;
if (hidkbd_set_leds(kbd, leds, &res))
apldchidev_set_leds(sc->sc_hidev, res);
}
/* Console interface. */
void
apldckbd_cngetc(void *v, u_int *type, int *data)
{
struct apldckbd_softc *sc = v;
struct hidkbd *kbd = &sc->sc_kbd;
kbd->sc_polling = 1;
while (kbd->sc_npollchar <= 0) {
apldchidev_rx_intr(sc->sc_dev.dv_parent);
delay(1000);
}
kbd->sc_polling = 0;
hidkbd_cngetc(kbd, type, data);
}
void
apldckbd_cnpollc(void *v, int on)
{
struct apldckbd_softc *sc = v;
if (on)
sc->sc_spl = spltty();
else
splx(sc->sc_spl);
}
void
apldckbd_cnbell(void *v, u_int pitch, u_int period, u_int volume)
{
hidkbd_bell(pitch, period, volume, 1);
}
#if NAPLDCMS > 0
/* Touchpad */
/*
* The contents of the touchpad event packets is identical to those
* used by the ubcmtp(4) driver. The relevant definitions and the
* code to decode the packets is replicated here.
*/
struct ubcmtp_finger {
uint16_t origin;
uint16_t abs_x;
uint16_t abs_y;
uint16_t rel_x;
uint16_t rel_y;
uint16_t tool_major;
uint16_t tool_minor;
uint16_t orientation;
uint16_t touch_major;
uint16_t touch_minor;
uint16_t unused[2];
uint16_t pressure;
uint16_t multi;
} __packed __attribute((aligned(2)));
#define UBCMTP_MAX_FINGERS 16
#define UBCMTP_TYPE4_TPOFF (20 * sizeof(uint16_t))
#define UBCMTP_TYPE4_BTOFF 23
#define UBCMTP_TYPE4_FINGERPAD (1 * sizeof(uint16_t))
/* Use a constant, synaptics-compatible pressure value for now. */
#define DEFAULT_PRESSURE 40
struct apldcms_softc {
struct device sc_dev;
struct device *sc_wsmousedev;
int sc_enabled;
int tp_offset;
int tp_fingerpad;
struct mtpoint frame[UBCMTP_MAX_FINGERS];
int contacts;
int btn;
};
int apldcms_enable(void *);
void apldcms_disable(void *);
int apldcms_ioctl(void *, u_long, caddr_t, int, struct proc *);
const struct wsmouse_accessops apldcms_accessops = {
.enable = apldcms_enable,
.disable = apldcms_disable,
.ioctl = apldcms_ioctl,
};
int apldcms_match(struct device *, void *, void *);
void apldcms_attach(struct device *, struct device *, void *);
const struct cfattach apldcms_ca = {
sizeof(struct apldcms_softc), apldcms_match, apldcms_attach
};
struct cfdriver apldcms_cd = {
NULL, "apldcms", DV_DULL
};
int apldcms_configure(struct apldcms_softc *);
int
apldcms_match(struct device *parent, void *match, void *aux)
{
struct apldchidev_attach_args *aa = aux;
return strcmp(aa->aa_name, "multi-touch") == 0;
}
void
apldcms_attach(struct device *parent, struct device *self, void *aux)
{
struct apldcms_softc *sc = (struct apldcms_softc *)self;
struct wsmousedev_attach_args aa;
printf("\n");
sc->tp_offset = UBCMTP_TYPE4_TPOFF;
sc->tp_fingerpad = UBCMTP_TYPE4_FINGERPAD;
aa.accessops = &apldcms_accessops;
aa.accesscookie = sc;
sc->sc_wsmousedev = config_found(self, &aa, wsmousedevprint);
if (sc->sc_wsmousedev != NULL && apldcms_configure(sc))
apldcms_disable(sc);
}
int
apldcms_configure(struct apldcms_softc *sc)
{
struct wsmousehw *hw = wsmouse_get_hw(sc->sc_wsmousedev);
/* The values below are for the MacBookPro17,1 */
hw->type = WSMOUSE_TYPE_TOUCHPAD;
hw->hw_type = WSMOUSEHW_CLICKPAD;
hw->x_min = -6046;
hw->x_max = 6536;
hw->y_min = -164;
hw->y_max = 7439;
hw->mt_slots = UBCMTP_MAX_FINGERS;
hw->flags = WSMOUSEHW_MT_TRACKING;
return wsmouse_configure(sc->sc_wsmousedev, NULL, 0);
}
void
apldcms_intr(struct device *self, uint8_t *packet, size_t packetlen)
{
struct apldcms_softc *sc = (struct apldcms_softc *)self;
struct ubcmtp_finger *finger;
int off, s, btn, contacts;
if (!sc->sc_enabled)
return;
contacts = 0;
for (off = sc->tp_offset; off < packetlen;
off += (sizeof(struct ubcmtp_finger) + sc->tp_fingerpad)) {
finger = (struct ubcmtp_finger *)(packet + off);
if ((int16_t)letoh16(finger->touch_major) == 0)
continue; /* finger lifted */
sc->frame[contacts].x = (int16_t)letoh16(finger->abs_x);
sc->frame[contacts].y = (int16_t)letoh16(finger->abs_y);
sc->frame[contacts].pressure = DEFAULT_PRESSURE;
contacts++;
}
btn = sc->btn;
sc->btn = !!((int16_t)letoh16(packet[UBCMTP_TYPE4_BTOFF]));
if (contacts || sc->contacts || sc->btn != btn) {
sc->contacts = contacts;
s = spltty();
wsmouse_buttons(sc->sc_wsmousedev, sc->btn);
wsmouse_mtframe(sc->sc_wsmousedev, sc->frame, contacts);
wsmouse_input_sync(sc->sc_wsmousedev);
splx(s);
}
}
int
apldcms_enable(void *v)
{
struct apldcms_softc *sc = v;
if (sc->sc_enabled)
return EBUSY;
sc->sc_enabled = 1;
return 0;
}
void
apldcms_disable(void *v)
{
struct apldcms_softc *sc = v;
sc->sc_enabled = 0;
}
int
apldcms_ioctl(void *v, u_long cmd, caddr_t data, int flag, struct proc *p)
{
struct apldcms_softc *sc = v;
struct wsmousehw *hw = wsmouse_get_hw(sc->sc_wsmousedev);
struct wsmouse_calibcoords *wsmc = (struct wsmouse_calibcoords *)data;
int wsmode;
switch (cmd) {
case WSMOUSEIO_GTYPE:
*(u_int *)data = hw->type;
break;
case WSMOUSEIO_GCALIBCOORDS:
wsmc->minx = hw->x_min;
wsmc->maxx = hw->x_max;
wsmc->miny = hw->y_min;
wsmc->maxy = hw->y_max;
wsmc->swapxy = 0;
wsmc->resx = 0;
wsmc->resy = 0;
break;
case WSMOUSEIO_SETMODE:
wsmode = *(u_int *)data;
if (wsmode != WSMOUSE_COMPAT && wsmode != WSMOUSE_NATIVE) {
printf("%s: invalid mode %d\n", sc->sc_dev.dv_xname,
wsmode);
return (EINVAL);
}
wsmouse_set_mode(sc->sc_wsmousedev, wsmode);
break;
default:
return -1;
}
return 0;
}
#else
void
apldcms_intr(struct device *self, uint8_t *packet, size_t packetlen)
{
}
#endif