From aed4423c7d742eb53456cd5f7cfd25b67f684521 Mon Sep 17 00:00:00 2001 From: Joshua Stein Date: Tue, 12 Jan 2016 01:11:16 +0000 Subject: Add dwiic, a driver for the Synopsys DesignWare i2c controller found on the Samsung ATIV Book 9 laptop. This initial version only supports ACPI config/attachment. Add ihidev, a HID-over-i2c driver largely based on uhidev. dwiic handles attaching ihidev devices found in ACPI. Add ims, a HID-over-i2c mouse/trackpad driver to get basic cursor and button functionality from HID-compliant i2c trackpads. ok kettenis deraadt --- sys/arch/amd64/conf/GENERIC | 7 +- sys/dev/acpi/acpi.c | 68 ++-- sys/dev/acpi/acpireg.h | 10 +- sys/dev/acpi/acpivar.h | 5 +- sys/dev/acpi/dwiic.c | 925 ++++++++++++++++++++++++++++++++++++++++++++ sys/dev/acpi/files.acpi | 7 +- sys/dev/i2c/files.i2c | 15 +- sys/dev/i2c/i2cvar.h | 4 +- sys/dev/i2c/ihidev.c | 574 +++++++++++++++++++++++++++ sys/dev/i2c/ihidev.h | 120 ++++++ sys/dev/i2c/ims.c | 183 +++++++++ 11 files changed, 1888 insertions(+), 30 deletions(-) create mode 100644 sys/dev/acpi/dwiic.c create mode 100644 sys/dev/i2c/ihidev.c create mode 100644 sys/dev/i2c/ihidev.h create mode 100644 sys/dev/i2c/ims.c (limited to 'sys') diff --git a/sys/arch/amd64/conf/GENERIC b/sys/arch/amd64/conf/GENERIC index 42c4ac31ac6..27fb72585fa 100644 --- a/sys/arch/amd64/conf/GENERIC +++ b/sys/arch/amd64/conf/GENERIC @@ -1,4 +1,4 @@ -# $OpenBSD: GENERIC,v 1.410 2016/01/11 22:06:50 kettenis Exp $ +# $OpenBSD: GENERIC,v 1.411 2016/01/12 01:11:15 jcs Exp $ # # For further information on compiling OpenBSD kernels, see the config(8) # man page. @@ -125,6 +125,8 @@ nviic* at pci? # NVIDIA nForce2/3/4 SMBus controller iic* at nviic? amdpm* at pci? # AMD-7xx/8111 and NForce SMBus controller iic* at amdpm? +dwiic* at acpi? # DesignWare Synopsys i2c controller +iic* at dwiic? itherm* at pci? # Intel 3400 Thermal Sensor adc* at iic? # Analog Devices AD7416/AD7417/7418 @@ -148,6 +150,9 @@ spdmem* at iic? # SPD memory eeproms sdtemp* at iic? # SO-DIMM (JC-42.4) temperature wbng* at iic? # Winbond W83793G nvt* at iic? # Novoton W83795G +ihidev* at iic? # HID-over-i2c +ims* at ihidev? # i2c mouse/trackpad +wsmouse* at ims? mux 0 skgpio0 at isa? port 0x680 # Soekris net6501 GPIO and LEDs gpio* at skgpio? diff --git a/sys/dev/acpi/acpi.c b/sys/dev/acpi/acpi.c index 6c464102fa8..a808501fe62 100644 --- a/sys/dev/acpi/acpi.c +++ b/sys/dev/acpi/acpi.c @@ -1,4 +1,4 @@ -/* $OpenBSD: acpi.c,v 1.300 2016/01/11 22:06:00 kettenis Exp $ */ +/* $OpenBSD: acpi.c,v 1.301 2016/01/12 01:11:15 jcs Exp $ */ /* * Copyright (c) 2005 Thorsten Lockert * Copyright (c) 2005 Jordan Hargrave @@ -2659,15 +2659,12 @@ acpi_foundsony(struct aml_node *node, void *arg) #ifndef SMALL_KERNEL int -acpi_foundhid(struct aml_node *node, void *arg) +acpi_parsehid(struct aml_node *node, void *arg, char *outcdev, char *outdev, + size_t devlen) { struct acpi_softc *sc = (struct acpi_softc *)arg; - struct device *self = (struct device *)arg; - const char *dev; - char cdev[16]; struct aml_value res; - struct acpi_attach_args aaa; - int i; + const char *dev; /* NB aml_eisaid returns a static buffer, this must come first */ if (aml_evalname(acpi_softc, node->parent, "_CID", 0, NULL, &res) == 0) { @@ -2682,17 +2679,17 @@ acpi_foundhid(struct aml_node *node, void *arg) dev = "unknown"; break; } - strlcpy(cdev, dev, sizeof(cdev)); + strlcpy(outcdev, dev, devlen); aml_freevalue(&res); - - dnprintf(10, "compatible with device: %s\n", cdev); + + dnprintf(10, "compatible with device: %s\n", outcdev); } else { - cdev[0] = '\0'; + outcdev[0] = '\0'; } dnprintf(10, "found hid device: %s ", node->parent->name); if (aml_evalnode(sc, node, 0, NULL, &res) != 0) - return 0; + return (1); switch (res.type) { case AML_OBJTYPE_STRING: @@ -2707,36 +2704,63 @@ acpi_foundhid(struct aml_node *node, void *arg) } dnprintf(10, " device: %s\n", dev); + strlcpy(outdev, dev, devlen); + + aml_freevalue(&res); + + return (0); +} + +int +acpi_foundhid(struct aml_node *node, void *arg) +{ + struct acpi_softc *sc = (struct acpi_softc *)arg; + struct device *self = (struct device *)arg; + char cdev[16]; + char dev[16]; + struct acpi_attach_args aaa; + int i; + + if (acpi_parsehid(node, arg, cdev, dev, 16) != 0) + return (0); + memset(&aaa, 0, sizeof(aaa)); aaa.aaa_iot = sc->sc_iot; aaa.aaa_memt = sc->sc_memt; aaa.aaa_node = node->parent; aaa.aaa_dev = dev; - if (!strcmp(dev, ACPI_DEV_AC)) + if (!strcmp(dev, ACPI_DEV_AC)) { aaa.aaa_name = "acpiac"; - else if (!strcmp(dev, ACPI_DEV_CMB)) + } else if (!strcmp(dev, ACPI_DEV_CMB)) { aaa.aaa_name = "acpibat"; - else if (!strcmp(dev, ACPI_DEV_LD) || + } else if (!strcmp(dev, ACPI_DEV_LD) || !strcmp(dev, ACPI_DEV_PBD) || - !strcmp(dev, ACPI_DEV_SBD)) + !strcmp(dev, ACPI_DEV_SBD)) { aaa.aaa_name = "acpibtn"; - else if (!strcmp(dev, ACPI_DEV_ASUS) || !strcmp(dev, ACPI_DEV_ASUS1)) { + } else if (!strcmp(dev, ACPI_DEV_ASUS) || + !strcmp(dev, ACPI_DEV_ASUS1)) { aaa.aaa_name = "acpiasus"; acpi_asus_enabled = 1; } else if (!strcmp(dev, ACPI_DEV_IBM) || !strcmp(dev, ACPI_DEV_LENOVO)) { aaa.aaa_name = "acpithinkpad"; acpi_thinkpad_enabled = 1; - } else if (!strcmp(dev, ACPI_DEV_ASUSAIBOOSTER)) + } else if (!strcmp(dev, ACPI_DEV_ASUSAIBOOSTER)) { aaa.aaa_name = "aibs"; - else if (!strcmp(dev, ACPI_DEV_TOSHIBA_LIBRETTO) || + } else if (!strcmp(dev, ACPI_DEV_TOSHIBA_LIBRETTO) || !strcmp(dev, ACPI_DEV_TOSHIBA_DYNABOOK) || !strcmp(dev, ACPI_DEV_TOSHIBA_SPA40)) { aaa.aaa_name = "acpitoshiba"; acpi_toshiba_enabled = 1; - } else if (!strcmp(dev, "80860F14") || !strcmp(dev, "PNP0FFF")) + } else if (!strcmp(dev, "80860F14") || !strcmp(dev, "PNP0FFF")) { aaa.aaa_name = "sdhc"; + } else if (!strcmp(dev, ACPI_DEV_DWIIC1) || + !strcmp(dev, ACPI_DEV_DWIIC2) || + !strcmp(dev, ACPI_DEV_DWIIC3) || + !strcmp(dev, ACPI_DEV_DWIIC4)) { + aaa.aaa_name = "dwiic"; + } if (!strcmp(cdev, ACPI_DEV_MOUSE)) { for (i = 0; i < nitems(sbtn_pnp); i++) { @@ -2750,9 +2774,7 @@ acpi_foundhid(struct aml_node *node, void *arg) if (aaa.aaa_name) config_found(self, &aaa, acpi_print); - aml_freevalue(&res); - - return 0; + return (0); } int diff --git a/sys/dev/acpi/acpireg.h b/sys/dev/acpi/acpireg.h index 0048cf9c4d7..0fc3a78f749 100644 --- a/sys/dev/acpi/acpireg.h +++ b/sys/dev/acpi/acpireg.h @@ -1,4 +1,4 @@ -/* $OpenBSD: acpireg.h,v 1.31 2016/01/09 11:00:01 kettenis Exp $ */ +/* $OpenBSD: acpireg.h,v 1.32 2016/01/12 01:11:15 jcs Exp $ */ /* * Copyright (c) 2005 Thorsten Lockert * Copyright (c) 2005 Marco Peereboom @@ -730,6 +730,8 @@ struct acpi_ivrs { #define ACPI_DEV_LD "PNP0C0D" /* Lid Device */ #define ACPI_DEV_SBD "PNP0C0E" /* Sleep Button Device */ #define ACPI_DEV_PILD "PNP0C0F" /* PCI Interrupt Link Device */ +#define ACPI_DEV_HIDI2C "PNP0C50" /* HID over I2C device */ +#define ACPI_DEV_HIDI2C2 "ACPI0C50" /* HID over I2C device */ #define ACPI_DEV_MEMD "PNP0C80" /* Memory Device */ #define ACPI_DEV_MOUSE "PNP0F13" /* PS/2 Mouse */ #define ACPI_DEV_SHC "ACPI0001" /* SMBus 1.0 Host Controller */ @@ -754,4 +756,10 @@ struct acpi_ivrs { #define ACPI_DEV_TOSHIBA_DYNABOOK "TOS6207" /* Toshiba Dynabook support */ #define ACPI_DEV_TOSHIBA_SPA40 "TOS6208" /* Toshiba SPA40 support */ +/* Synopsys DesignWare I2C controllers */ +#define ACPI_DEV_DWIIC1 "INT33C2" +#define ACPI_DEV_DWIIC2 "INT33C3" +#define ACPI_DEV_DWIIC3 "INT3432" +#define ACPI_DEV_DWIIC4 "INT3433" + #endif /* !_DEV_ACPI_ACPIREG_H_ */ diff --git a/sys/dev/acpi/acpivar.h b/sys/dev/acpi/acpivar.h index 979ad54c0a2..6b84fe1f1a6 100644 --- a/sys/dev/acpi/acpivar.h +++ b/sys/dev/acpi/acpivar.h @@ -1,4 +1,4 @@ -/* $OpenBSD: acpivar.h,v 1.81 2016/01/10 16:59:41 kettenis Exp $ */ +/* $OpenBSD: acpivar.h,v 1.82 2016/01/12 01:11:15 jcs Exp $ */ /* * Copyright (c) 2005 Thorsten Lockert * @@ -341,7 +341,8 @@ void acpi_write_pmreg(struct acpi_softc *, int, int, int); void acpi_poll(void *); void acpi_sleep(int, char *); -int acpi_matchhids(struct acpi_attach_args *, const char *[], const char *); +int acpi_matchhids(struct acpi_attach_args *, const char *[], const char *); +int acpi_parsehid(struct aml_node *, void *, char *, char *, size_t); int acpi_record_event(struct acpi_softc *, u_int); diff --git a/sys/dev/acpi/dwiic.c b/sys/dev/acpi/dwiic.c new file mode 100644 index 00000000000..45591c84bbb --- /dev/null +++ b/sys/dev/acpi/dwiic.c @@ -0,0 +1,925 @@ +/* $OpenBSD: dwiic.c,v 1.1 2016/01/12 01:11:15 jcs Exp $ */ +/* + * Synopsys DesignWare I2C controller + * + * Copyright (c) 2015, 2016 joshua stein + * + * Permission to use, copy, modify, and/or 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 +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +/* #define DWIIC_DEBUG */ + +#ifdef DWIIC_DEBUG +#define DPRINTF(x) printf x +#else +#define DPRINTF(x) +#endif + +/* register offsets */ +#define DW_IC_CON 0x0 +#define DW_IC_TAR 0x4 +#define DW_IC_DATA_CMD 0x10 +#define DW_IC_SS_SCL_HCNT 0x14 +#define DW_IC_SS_SCL_LCNT 0x18 +#define DW_IC_FS_SCL_HCNT 0x1c +#define DW_IC_FS_SCL_LCNT 0x20 +#define DW_IC_INTR_STAT 0x2c +#define DW_IC_INTR_MASK 0x30 +#define DW_IC_RAW_INTR_STAT 0x34 +#define DW_IC_RX_TL 0x38 +#define DW_IC_TX_TL 0x3c +#define DW_IC_CLR_INTR 0x40 +#define DW_IC_CLR_RX_UNDER 0x44 +#define DW_IC_CLR_RX_OVER 0x48 +#define DW_IC_CLR_TX_OVER 0x4c +#define DW_IC_CLR_RD_REQ 0x50 +#define DW_IC_CLR_TX_ABRT 0x54 +#define DW_IC_CLR_RX_DONE 0x58 +#define DW_IC_CLR_ACTIVITY 0x5c +#define DW_IC_CLR_STOP_DET 0x60 +#define DW_IC_CLR_START_DET 0x64 +#define DW_IC_CLR_GEN_CALL 0x68 +#define DW_IC_ENABLE 0x6c +#define DW_IC_STATUS 0x70 +#define DW_IC_TXFLR 0x74 +#define DW_IC_RXFLR 0x78 +#define DW_IC_SDA_HOLD 0x7c +#define DW_IC_TX_ABRT_SOURCE 0x80 +#define DW_IC_ENABLE_STATUS 0x9c +#define DW_IC_COMP_PARAM_1 0xf4 +#define DW_IC_COMP_VERSION 0xf8 +#define DW_IC_SDA_HOLD_MIN_VERS 0x3131312A +#define DW_IC_COMP_TYPE 0xfc +#define DW_IC_COMP_TYPE_VALUE 0x44570140 + +#define DW_IC_CON_MASTER 0x1 +#define DW_IC_CON_SPEED_STD 0x2 +#define DW_IC_CON_SPEED_FAST 0x4 +#define DW_IC_CON_10BITADDR_MASTER 0x10 +#define DW_IC_CON_RESTART_EN 0x20 +#define DW_IC_CON_SLAVE_DISABLE 0x40 + +#define DW_IC_INTR_RX_UNDER 0x001 +#define DW_IC_INTR_RX_OVER 0x002 +#define DW_IC_INTR_RX_FULL 0x004 +#define DW_IC_INTR_TX_OVER 0x008 +#define DW_IC_INTR_TX_EMPTY 0x010 +#define DW_IC_INTR_RD_REQ 0x020 +#define DW_IC_INTR_TX_ABRT 0x040 +#define DW_IC_INTR_RX_DONE 0x080 +#define DW_IC_INTR_ACTIVITY 0x100 +#define DW_IC_INTR_STOP_DET 0x200 +#define DW_IC_INTR_START_DET 0x400 +#define DW_IC_INTR_GEN_CALL 0x800 + +#define DW_IC_STATUS_ACTIVITY 0x1 + +#define DW_IC_ERR_TX_ABRT 0x1 + +/* hardware abort codes from the DW_IC_TX_ABRT_SOURCE register */ +#define ABRT_7B_ADDR_NOACK 0 +#define ABRT_10ADDR1_NOACK 1 +#define ABRT_10ADDR2_NOACK 2 +#define ABRT_TXDATA_NOACK 3 +#define ABRT_GCALL_NOACK 4 +#define ABRT_GCALL_READ 5 +#define ABRT_SBYTE_ACKDET 7 +#define ABRT_SBYTE_NORSTRT 9 +#define ABRT_10B_RD_NORSTRT 10 +#define ABRT_MASTER_DIS 11 +#define ARB_LOST 12 + +struct dwiic_crs { + int irq_int; + uint8_t irq_flags; + uint32_t addr_min; + uint32_t addr_bas; + uint32_t addr_len; +}; + +struct dwiic_softc { + struct device sc_dev; + + bus_space_tag_t sc_iot; + bus_space_handle_t sc_ioh; + + struct acpi_softc *sc_acpi; + struct aml_node *sc_devnode; + void *sc_ih; + + struct i2cbus_attach_args sc_iba; + struct device *sc_iic; + + int sc_poll; + int sc_busy; + int sc_readwait; + int sc_writewait; + + uint32_t master_cfg; + uint16_t ss_hcnt, ss_lcnt, fs_hcnt, fs_lcnt; + uint32_t sda_hold_time; + int tx_fifo_depth; + int rx_fifo_depth; + + struct i2c_controller sc_i2c_tag; + struct rwlock sc_i2c_lock; + struct { + i2c_op_t op; + void * buf; + size_t len; + int flags; + volatile int error; + } sc_i2c_xfer; +}; + +int dwiic_match(struct device *, void *, void *); +void dwiic_attach(struct device *, struct device *, void *); +int dwiic_detach(struct device *, int); +int dwiic_activate(struct device *, int); + +int dwiic_init(struct dwiic_softc *); +void dwiic_enable(struct dwiic_softc *, int); +int dwiic_intr(void *); + +int dwiic_acpi_parse_crs(union acpi_resource *, void *); +int dwiic_acpi_foundhid(struct aml_node *, void *); +void dwiic_acpi_get_params(struct dwiic_softc *, char *, uint16_t *, + uint16_t *, uint32_t *); +void dwiic_bus_scan(struct device *, struct i2cbus_attach_args *, + void *); + +int dwiic_i2c_acquire_bus(void *, int); +void dwiic_i2c_release_bus(void *, int); +uint32_t dwiic_i2c_read(struct dwiic_softc *, int); +void dwiic_i2c_write(struct dwiic_softc *, int, uint32_t); +int dwiic_i2c_exec(void *, i2c_op_t, i2c_addr_t, const void *, + size_t, void *, size_t, int); +void dwiic_xfer_msg(struct dwiic_softc *); + +struct cfattach dwiic_ca = { + sizeof(struct dwiic_softc), + dwiic_match, + dwiic_attach, + NULL, + dwiic_activate +}; + +struct cfdriver dwiic_cd = { + NULL, "dwiic", DV_DULL +}; + +int +dwiic_match(struct device *parent, void *match, void *aux) +{ + struct acpi_attach_args *aaa = aux; + struct cfdata *cf = match; + + if (aaa->aaa_name == NULL || + strcmp(aaa->aaa_name, cf->cf_driver->cd_name) != 0 || + aaa->aaa_table != NULL) + return 0; + + return 1; +} + +void +dwiic_attach(struct device *parent, struct device *self, void *aux) +{ + struct dwiic_softc *sc = (struct dwiic_softc *)self; + struct acpi_attach_args *aa = aux; + struct aml_value res; + struct dwiic_crs crs; + + sc->sc_acpi = (struct acpi_softc *)parent; + sc->sc_devnode = aa->aaa_node; + + printf(": %s", sc->sc_devnode->name); + + if (aml_evalname(sc->sc_acpi, sc->sc_devnode, "_CRS", 0, NULL, &res)) { + printf(", no _CRS method\n"); + return; + } + if (res.type != AML_OBJTYPE_BUFFER || res.length < 5) { + printf(", invalid _CRS object (type %d len %d)\n", + res.type, res.length); + aml_freevalue(&res); + return; + } + aml_parse_resource(&res, dwiic_acpi_parse_crs, &crs); + aml_freevalue(&res); + + if (crs.addr_bas == 0) { + printf(", can't find address\n"); + return; + } + + printf(", addr 0x%x len 0x%x", crs.addr_bas, crs.addr_len); + + sc->sc_iot = X86_BUS_SPACE_MEM; + if (bus_space_map(sc->sc_iot, crs.addr_bas, crs.addr_len, + BUS_SPACE_MAP_PREFETCHABLE | BUS_SPACE_MAP_LINEAR, + &sc->sc_ioh)) { + printf(", failed mapping at 0x%x\n", crs.addr_bas); + return; + } + + /* fetch timing parameters */ + dwiic_acpi_get_params(sc, "SSCN", &sc->ss_hcnt, &sc->ss_lcnt, NULL); + dwiic_acpi_get_params(sc, "FMCN", &sc->fs_hcnt, &sc->fs_lcnt, + &sc->sda_hold_time); + + /* power up the controller */ + if (!aml_searchname(sc->sc_devnode, "_PS0") || + aml_evalname(sc->sc_acpi, sc->sc_devnode, "_PS0", 0, NULL, NULL)) { + printf(", failed powering on with _PS0\n"); + return; + } + + if (dwiic_init(sc)) { + printf(", failed initializing\n"); + bus_space_unmap(sc->sc_iot, sc->sc_ioh, crs.addr_len); + return; + } + + /* leave the controller disabled */ + dwiic_i2c_write(sc, DW_IC_INTR_MASK, 0); + dwiic_enable(sc, 0); + dwiic_i2c_read(sc, DW_IC_CLR_INTR); + + /* try to register interrupt with apic, but not fatal without it */ + if (crs.irq_int > 0) { + sc->sc_ih = acpi_intr_establish(crs.irq_int, crs.irq_flags, + IPL_BIO, dwiic_intr, sc, sc->sc_dev.dv_xname); + if (sc->sc_ih == NULL) + printf(", failed establishing acpi int %d", + crs.irq_int); + else { + printf(", apic int %d", crs.irq_int); + sc->sc_poll = 0; + } + } + + printf("\n"); + + rw_init(&sc->sc_i2c_lock, "iiclk"); + + /* setup and attach iic bus */ + sc->sc_i2c_tag.ic_cookie = sc; + sc->sc_i2c_tag.ic_acquire_bus = dwiic_i2c_acquire_bus; + sc->sc_i2c_tag.ic_release_bus = dwiic_i2c_release_bus; + sc->sc_i2c_tag.ic_exec = dwiic_i2c_exec; + + bzero(&sc->sc_iba, sizeof(sc->sc_iba)); + sc->sc_iba.iba_name = "iic"; + sc->sc_iba.iba_tag = &sc->sc_i2c_tag; + sc->sc_iba.iba_bus_scan = dwiic_bus_scan; + sc->sc_iba.iba_bus_scan_arg = sc; + + config_found((struct device *)sc, &sc->sc_iba, iicbus_print); + + return; +} + +int +dwiic_detach(struct device *self, int flags) +{ + struct dwiic_softc *sc = (struct dwiic_softc *)self; + + if (sc->sc_ih != NULL) { + intr_disestablish(sc->sc_ih); + sc->sc_ih = NULL; + } + + return 0; +} + +int +dwiic_activate(struct device *self, int act) +{ + struct dwiic_softc *sc = (struct dwiic_softc *)self; + + switch (act) { + case DVACT_SUSPEND: + /* disable controller */ + dwiic_enable(sc, 0); + + /* disable interrupts */ + dwiic_i2c_write(sc, DW_IC_INTR_MASK, 0); + + /* power down the controller */ + if (!aml_searchname(sc->sc_devnode, "_PS3") || + aml_evalname(sc->sc_acpi, sc->sc_devnode, "_PS3", 0, NULL, + NULL)) { + printf("%s: failed powering down with _PS3\n", + sc->sc_dev.dv_xname); + return (1); + } + + break; + case DVACT_WAKEUP: + /* power up the controller */ + if (!aml_searchname(sc->sc_devnode, "_PS0") || + aml_evalname(sc->sc_acpi, sc->sc_devnode, "_PS0", 0, NULL, + NULL)) { + printf("%s: failed powering up with _PS0\n", + sc->sc_dev.dv_xname); + return (1); + } + + dwiic_init(sc); + + break; + } + + config_activate_children(self, act); + + return 0; +} + +int +dwiic_acpi_parse_crs(union acpi_resource *crs, void *arg) +{ + struct dwiic_crs *sc_crs = arg; + + switch (AML_CRSTYPE(crs)) { + case SR_IRQ: + sc_crs->irq_int = ffs(letoh16(crs->sr_irq.irq_mask)) - 1; + sc_crs->irq_flags = crs->sr_irq.irq_flags; + break; + + case LR_EXTIRQ: + sc_crs->irq_int = letoh32(crs->lr_extirq.irq[0]); + sc_crs->irq_flags = crs->lr_extirq.flags; + break; + + case LR_MEM32: + sc_crs->addr_min = letoh32(crs->lr_m32._min); + sc_crs->addr_len = letoh32(crs->lr_m32._len); + break; + + case LR_MEM32FIXED: + sc_crs->addr_bas = letoh32(crs->lr_m32fixed._bas); + sc_crs->addr_len = letoh32(crs->lr_m32fixed._len); + break; + + default: + DPRINTF(("%s: unknown resource type %d\n", __func__, + AML_CRSTYPE(crs))); + } + + return 0; +} + +void +dwiic_acpi_get_params(struct dwiic_softc *sc, char *method, uint16_t *hcnt, + uint16_t *lcnt, uint32_t *sda_hold_time) +{ + struct aml_value res; + + if (!aml_searchname(sc->sc_devnode, method)) { + printf(": no %s method", method); + return; + } + + if (aml_evalname(sc->sc_acpi, sc->sc_devnode, method, 0, NULL, &res)) { + printf(": eval of %s at %s failed", method, + aml_nodename(sc->sc_devnode)); + return; + } + + if (res.type != AML_OBJTYPE_PACKAGE) { + printf(": %s is not a package (%d)", method, res.type); + return; + } + + if (res.length <= 2) { + printf(": %s returned package of len %d", method, res.length); + return; + } + + *hcnt = aml_val2int(res.v_package[0]); + *lcnt = aml_val2int(res.v_package[1]); + if (sda_hold_time) + *sda_hold_time = aml_val2int(res.v_package[2]); +} + +void +dwiic_bus_scan(struct device *iic, struct i2cbus_attach_args *iba, void *aux) +{ + struct dwiic_softc *sc = (struct dwiic_softc *)aux; + + /* just to pass through to dwiic_acpi_foundhid */ + sc->sc_iic = iic; + + /* find i2c hid devices */ + aml_find_node(sc->sc_devnode, "_HID", dwiic_acpi_foundhid, sc); +} + +int +dwiic_i2c_print(void *aux, const char *pnp) +{ + struct i2c_attach_args *ia = aux; + + if (pnp != NULL) + printf("%s at %s", ia->ia_name, pnp); + + printf(" addr 0x%x", ia->ia_addr); + + return UNCONF; +} + +int +dwiic_acpi_foundhid(struct aml_node *node, void *arg) +{ + struct dwiic_softc *sc = (struct dwiic_softc *)arg; + struct i2c_attach_args ia; + struct aml_value cmd[4], res; + struct dwiic_crs crs; + char cdev[16], dev[16]; + int64_t sta; + + /* 3cdff6f7-4267-4555-ad05-b30a3d8938de */ + static uint8_t i2c_hid_guid[] = { + 0xF7, 0xF6, 0xDF, 0x3C, 0x67, 0x42, 0x55, 0x45, + 0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE, + }; + + if (acpi_parsehid(node, arg, cdev, dev, 16) != 0) + return 0; + + if (strcmp(cdev, ACPI_DEV_HIDI2C) != 0 && + strcmp(cdev, ACPI_DEV_HIDI2C2) != 0) + return 0; + + DPRINTF(("%s: found HID %s at %s\n", sc->sc_dev.dv_xname, dev, + aml_nodename(node))); + + if (aml_evalinteger(acpi_softc, node->parent, "_STA", 0, NULL, &sta) || + !sta) { + printf("%s: _sta failed at %s\n", sc->sc_dev.dv_xname, + aml_nodename(node->parent)); + return 0; + } + + if (!aml_searchname(node->parent, "_DSM")) { + printf("%s: couldn't find _DSM at %s\n", sc->sc_dev.dv_xname, + aml_nodename(node->parent)); + return 0; + } + + bzero(&cmd, sizeof(cmd)); + cmd[0].type = AML_OBJTYPE_BUFFER; + cmd[0].v_buffer = (uint8_t *)&i2c_hid_guid; + cmd[0].length = sizeof(i2c_hid_guid); + /* rev */ + cmd[1].type = AML_OBJTYPE_INTEGER; + cmd[1].v_integer = 1; + cmd[1].length = 1; + /* func */ + cmd[2].type = AML_OBJTYPE_INTEGER; + cmd[2].v_integer = 1; /* HID */ + cmd[2].length = 1; + /* not used */ + cmd[3].type = AML_OBJTYPE_PACKAGE; + cmd[3].length = 0; + + if (aml_evalname(acpi_softc, node->parent, "_DSM", 4, cmd, &res)) { + printf("%s: eval of _DSM at %s failed\n", + sc->sc_dev.dv_xname, aml_nodename(node->parent)); + return 0; + } + + if (res.type != AML_OBJTYPE_INTEGER) { + printf("%s: bad _DSM result at %s: %d\n", + sc->sc_dev.dv_xname, aml_nodename(node->parent), res.type); + aml_freevalue(&res); + return 0; + } + + memset(&ia, 0, sizeof(ia)); + ia.ia_tag = sc->sc_iba.iba_tag; + ia.ia_size = 1; + ia.ia_name = "ihidev"; + ia.ia_addr = aml_val2int(&res); /* hid descriptor address */ + ia.ia_cookie = dev; + + aml_freevalue(&res); + + if (aml_evalname(acpi_softc, node->parent, "_CRS", 0, NULL, &res)) { + printf("%s: no _CRS method at %s\n", sc->sc_dev.dv_xname, + aml_nodename(node->parent)); + return (0); + } + if (res.type != AML_OBJTYPE_BUFFER || res.length < 5) { + printf("%s: invalid _CRS object (type %d len %d)\n", + sc->sc_dev.dv_xname, res.type, res.length); + aml_freevalue(&res); + return (0); + } + aml_parse_resource(&res, dwiic_acpi_parse_crs, &crs); + aml_freevalue(&res); + + if (crs.irq_int <= 0) { + printf("%s: couldn't find irq for %s\n", sc->sc_dev.dv_xname, + aml_nodename(node->parent)); + return 0; + } + + ia.ia_irq = crs.irq_int; + ia.ia_irq_flags = crs.irq_flags; + + if (config_found(sc->sc_iic, &ia, dwiic_i2c_print)) + return 0; + + return 1; +} + +uint32_t +dwiic_i2c_read(struct dwiic_softc *sc, int offset) +{ + u_int32_t b = bus_space_read_4(sc->sc_iot, sc->sc_ioh, offset); + + DPRINTF(("%s: read at 0x%x = 0x%x\n", sc->sc_dev.dv_xname, offset, b)); + + return b; +} + +void +dwiic_i2c_write(struct dwiic_softc *sc, int offset, uint32_t val) +{ + bus_space_write_4(sc->sc_iot, sc->sc_ioh, offset, val); + + DPRINTF(("%s: write at 0x%x: 0x%x\n", sc->sc_dev.dv_xname, offset, + val)); +} + +int +dwiic_i2c_acquire_bus(void *cookie, int flags) +{ + struct dwiic_softc *sc = cookie; + + if (cold || sc->sc_poll || (flags & I2C_F_POLL)) + return (0); + + return rw_enter(&sc->sc_i2c_lock, RW_WRITE | RW_INTR); +} + +void +dwiic_i2c_release_bus(void *cookie, int flags) +{ + struct dwiic_softc *sc = cookie; + + if (cold || sc->sc_poll || (flags & I2C_F_POLL)) + return; + + rw_exit(&sc->sc_i2c_lock); +} + +int +dwiic_init(struct dwiic_softc *sc) +{ + uint32_t reg; + + /* make sure we're talking to a device we know */ + reg = dwiic_i2c_read(sc, DW_IC_COMP_TYPE); + if (reg != DW_IC_COMP_TYPE_VALUE) { + DPRINTF(("%s: invalid component type 0x%x\n", + sc->sc_dev.dv_xname, reg)); + return 1; + } + + /* disable the adapter */ + dwiic_enable(sc, 0); + + /* write standard-mode SCL timing parameters */ + dwiic_i2c_write(sc, DW_IC_SS_SCL_HCNT, sc->ss_hcnt); + dwiic_i2c_write(sc, DW_IC_SS_SCL_LCNT, sc->ss_lcnt); + + /* and fast-mode SCL timing parameters */ + dwiic_i2c_write(sc, DW_IC_FS_SCL_HCNT, sc->fs_hcnt); + dwiic_i2c_write(sc, DW_IC_FS_SCL_LCNT, sc->fs_lcnt); + + /* SDA hold time */ + reg = dwiic_i2c_read(sc, DW_IC_COMP_VERSION); + if (reg >= DW_IC_SDA_HOLD_MIN_VERS) + dwiic_i2c_write(sc, DW_IC_SDA_HOLD, sc->sda_hold_time); + + /* FIFO threshold levels */ + sc->tx_fifo_depth = 16; //32; + sc->rx_fifo_depth = 32; + dwiic_i2c_write(sc, DW_IC_TX_TL, sc->tx_fifo_depth /* / 2 */); + dwiic_i2c_write(sc, DW_IC_RX_TL, 0); + + /* configure as i2c master with fast speed */ + sc->master_cfg = DW_IC_CON_MASTER | DW_IC_CON_SLAVE_DISABLE | + DW_IC_CON_RESTART_EN | DW_IC_CON_SPEED_FAST; + dwiic_i2c_write(sc, DW_IC_CON, sc->master_cfg); + + return 0; +} + +void +dwiic_enable(struct dwiic_softc *sc, int enable) +{ + int retries; + + for (retries = 100; retries > 0; retries--) { + dwiic_i2c_write(sc, DW_IC_ENABLE, enable); + if ((dwiic_i2c_read(sc, DW_IC_ENABLE_STATUS) & 1) == enable) + return; + + DELAY(25); + } + + printf("%s: failed to %sable\n", sc->sc_dev.dv_xname, + (enable ? "en" : "dis")); +} + +int +dwiic_i2c_exec(void *cookie, i2c_op_t op, i2c_addr_t addr, const void *cmdbuf, + size_t cmdlen, void *buf, size_t len, int flags) +{ + struct dwiic_softc *sc = cookie; + u_int32_t ic_con, st, cmd, resp; + int retries, tx_limit, rx_avail, x, readpos; + uint8_t *b; + + if (sc->sc_busy) + return 1; + + sc->sc_busy++; + + DPRINTF(("%s: %s: op %d, addr 0x%02x, cmdlen %zu, len %zu, " + "flags 0x%02x\n", sc->sc_dev.dv_xname, __func__, op, addr, cmdlen, + len, flags)); + + /* setup transfer */ + sc->sc_i2c_xfer.op = op; + sc->sc_i2c_xfer.buf = buf; + sc->sc_i2c_xfer.len = len; + sc->sc_i2c_xfer.flags = flags; + sc->sc_i2c_xfer.error = 0; + + /* wait for bus to be idle */ + for (retries = 100; retries > 0; retries--) { + st = dwiic_i2c_read(sc, DW_IC_STATUS); + if (!(st & DW_IC_STATUS_ACTIVITY)) + break; + DELAY(1000); + } + DPRINTF(("%s: %s: status 0x%x\n", sc->sc_dev.dv_xname, __func__, st)); + if (st & DW_IC_STATUS_ACTIVITY) { + sc->sc_busy = 0; + return (1); + } + + if (cold || sc->sc_poll) + flags |= I2C_F_POLL; + + /* disable controller */ + dwiic_enable(sc, 0); + + /* set slave address */ + ic_con = dwiic_i2c_read(sc, DW_IC_CON); + ic_con &= ~DW_IC_CON_10BITADDR_MASTER; + dwiic_i2c_write(sc, DW_IC_CON, ic_con); + dwiic_i2c_write(sc, DW_IC_TAR, addr); + + /* disable interrupts */ + dwiic_i2c_write(sc, DW_IC_INTR_MASK, 0); + + /* enable controller */ + dwiic_enable(sc, 1); + + /* wait until the controller is ready for commands */ + if (flags & I2C_F_POLL) + DELAY(200); + else { + dwiic_i2c_read(sc, DW_IC_CLR_INTR); + dwiic_i2c_write(sc, DW_IC_INTR_MASK, DW_IC_INTR_TX_EMPTY); + + if (tsleep(&sc->sc_writewait, PRIBIO, "dwiic", hz / 2) != 0) + printf("%s: timed out waiting for tx_empty intr\n", + sc->sc_dev.dv_xname); + } + + /* send our command, one byte at a time */ + if (op == I2C_OP_WRITE) { + b = (void *)cmdbuf; + + DPRINTF(("%s: %s: sending cmd (len %zu):", sc->sc_dev.dv_xname, + __func__, cmdlen)); + for (x = 0; x < cmdlen; x++) + DPRINTF((" %02x", b[x])); + DPRINTF(("\n")); + + tx_limit = sc->tx_fifo_depth - dwiic_i2c_read(sc, DW_IC_TXFLR); + if (cmdlen > tx_limit) { + /* TODO */ + printf("%s: can't write %zu (> %d)\n", + sc->sc_dev.dv_xname, cmdlen, tx_limit); + sc->sc_i2c_xfer.error = 1; + sc->sc_busy = 0; + return (1); + } + + for (x = 0; x < cmdlen; x++) { + if (x > 0) + DELAY(50); + + dwiic_i2c_write(sc, DW_IC_DATA_CMD, b[x]); + } + + if (len == 0) { + /* not expecting any response, but still need to send + * final command byte */ + DELAY(50); + dwiic_i2c_write(sc, DW_IC_DATA_CMD, 0x100 | (1 << 9)); + sc->sc_busy = 0; + return (0); + } + } + + b = (void *)buf; + x = readpos = 0; + retries = 5; + tx_limit = sc->tx_fifo_depth - dwiic_i2c_read(sc, DW_IC_TXFLR); + + DPRINTF(("%s: %s: need to read %zu bytes, can send %d read reqs\n", + sc->sc_dev.dv_xname, __func__, len, tx_limit)); + + while (x < len) { + cmd = 0x100; + + /* + * For each byte we want to read as input, send a 0x100 + * command. On the first read request after sending a write + * command, set bit 10. On the last read request, set bit 9. + */ + if (x == 0 && I2C_OP_WRITE_P(op)) + cmd |= (1L << 10); + else if (x == len - 1) + cmd |= (1L << 9); + + dwiic_i2c_write(sc, DW_IC_DATA_CMD, cmd); + DELAY(50); + + tx_limit--; + x++; + + /* + * As TXFLR fills up, we need to clear it out by reading all + * available data. + */ + while (tx_limit == 0 || x == len) { + DPRINTF(("%s: %s: tx_limit %d, sent %d read reqs\n", + sc->sc_dev.dv_xname, __func__, tx_limit, x)); + + if (flags & I2C_F_POLL) + DELAY(200); + else { + dwiic_i2c_read(sc, DW_IC_CLR_INTR); + dwiic_i2c_write(sc, DW_IC_INTR_MASK, + DW_IC_INTR_RX_FULL); + + if (tsleep(&sc->sc_readwait, PRIBIO, "dwiic", + hz / 2) != 0) + printf("%s: timed out waiting for " + "rx_full intr\n", + sc->sc_dev.dv_xname); + } + + rx_avail = dwiic_i2c_read(sc, DW_IC_RXFLR); + + if (rx_avail == 0 && --retries <= 0) { + printf("%s: timed out reading remaining %d\n", + sc->sc_dev.dv_xname, + (int)(len - 1 - readpos)); + sc->sc_i2c_xfer.error = 1; + sc->sc_busy = 0; + return (1); + } + + DPRINTF(("%s: %s: %d avail to read (%zu remaining)\n", + sc->sc_dev.dv_xname, __func__, rx_avail, + len - readpos)); + + while (rx_avail > 0) { + resp = dwiic_i2c_read(sc, DW_IC_DATA_CMD); + if (readpos < len) { + b[readpos] = resp; + readpos++; + } + rx_avail--; + } + + if (readpos >= len - 1) + break; + + DPRINTF(("%s: still need to read %d bytes\n", + sc->sc_dev.dv_xname, (int)(len - 1 - readpos))); + tx_limit = sc->tx_fifo_depth - + dwiic_i2c_read(sc, DW_IC_TXFLR); + } + } + + sc->sc_busy = 0; + + return 0; +} + +uint32_t +dwiic_read_clear_intrbits(struct dwiic_softc *sc) +{ + uint32_t stat; + + stat = dwiic_i2c_read(sc, DW_IC_INTR_STAT); + + if (stat & DW_IC_INTR_RX_UNDER) + dwiic_i2c_read(sc, DW_IC_CLR_RX_UNDER); + if (stat & DW_IC_INTR_RX_OVER) + dwiic_i2c_read(sc, DW_IC_CLR_RX_OVER); + if (stat & DW_IC_INTR_TX_OVER) + dwiic_i2c_read(sc, DW_IC_CLR_TX_OVER); + if (stat & DW_IC_INTR_RD_REQ) + dwiic_i2c_read(sc, DW_IC_CLR_RD_REQ); + if (stat & DW_IC_INTR_TX_ABRT) + dwiic_i2c_read(sc, DW_IC_CLR_TX_ABRT); + if (stat & DW_IC_INTR_RX_DONE) + dwiic_i2c_read(sc, DW_IC_CLR_RX_DONE); + if (stat & DW_IC_INTR_ACTIVITY) + dwiic_i2c_read(sc, DW_IC_CLR_ACTIVITY); + if (stat & DW_IC_INTR_STOP_DET) + dwiic_i2c_read(sc, DW_IC_CLR_STOP_DET); + if (stat & DW_IC_INTR_START_DET) + dwiic_i2c_read(sc, DW_IC_CLR_START_DET); + if (stat & DW_IC_INTR_GEN_CALL) + dwiic_i2c_read(sc, DW_IC_CLR_GEN_CALL); + + return stat; +} + + +int +dwiic_intr(void *arg) +{ + struct dwiic_softc *sc = arg; + uint32_t en, stat; + + en = dwiic_i2c_read(sc, DW_IC_ENABLE); + /* probably for the other controller */ + if (!en) + return 0; + + stat = dwiic_read_clear_intrbits(sc); + DPRINTF(("%s: %s: enabled=0x%x stat=0x%x\n", sc->sc_dev.dv_xname, + __func__, en, stat)); + if (!(stat & ~DW_IC_INTR_ACTIVITY)) + return 1; + + if (stat & DW_IC_INTR_TX_ABRT) + sc->sc_i2c_xfer.error = 1; + + if (sc->sc_i2c_xfer.flags & I2C_F_POLL) + DPRINTF(("%s: %s: intr in poll mode?\n", sc->sc_dev.dv_xname, + __func__)); + else { + if (stat & DW_IC_INTR_RX_FULL) { + dwiic_i2c_write(sc, DW_IC_INTR_MASK, 0); + DPRINTF(("%s: %s: waking up reader\n", + sc->sc_dev.dv_xname, __func__)); + wakeup(&sc->sc_readwait); + } + if (stat & DW_IC_INTR_TX_EMPTY) { + dwiic_i2c_write(sc, DW_IC_INTR_MASK, 0); + DPRINTF(("%s: %s: waking up writer\n", + sc->sc_dev.dv_xname, __func__)); + wakeup(&sc->sc_writewait); + } + } + + return 1; +} diff --git a/sys/dev/acpi/files.acpi b/sys/dev/acpi/files.acpi index efda1b0f7e9..4cad71a0730 100644 --- a/sys/dev/acpi/files.acpi +++ b/sys/dev/acpi/files.acpi @@ -1,4 +1,4 @@ -# $OpenBSD: files.acpi,v 1.29 2016/01/11 14:09:37 kettenis Exp $ +# $OpenBSD: files.acpi,v 1.30 2016/01/12 01:11:15 jcs Exp $ # # Config file and device description for machine-independent ACPI code. # Included by ports that need it. @@ -115,3 +115,8 @@ file dev/acpi/atk0110.c aibs # SD Host Controller attach sdhc at acpi with sdhc_acpi file dev/acpi/sdhc_acpi.c sdhc_acpi + +# Synopsys DesignWare I2C controller +device dwiic: i2cbus +attach dwiic at acpi +file dev/acpi/dwiic.c dwiic diff --git a/sys/dev/i2c/files.i2c b/sys/dev/i2c/files.i2c index aec54a21a73..566a829edbd 100644 --- a/sys/dev/i2c/files.i2c +++ b/sys/dev/i2c/files.i2c @@ -1,4 +1,4 @@ -# $OpenBSD: files.i2c,v 1.51 2013/03/31 13:30:24 kettenis Exp $ +# $OpenBSD: files.i2c,v 1.52 2016/01/12 01:11:15 jcs Exp $ # $NetBSD: files.i2c,v 1.3 2003/10/20 16:24:10 briggs Exp $ define i2c {[addr = -1], [size = -1]} @@ -171,3 +171,16 @@ device lisa attach lisa at i2c file dev/i2c/lis331dl.c lisa +# HID +# HID "bus" +define ihidbus {[reportid = -1]} + +# HID root device +device ihidev: hid, ihidbus +attach ihidev at i2c +file dev/i2c/ihidev.c ihidev + +# HID Mouse/Trackpad +device ims: hid, hidms, wsmousedev +attach ims at ihidbus +file dev/i2c/ims.c ims diff --git a/sys/dev/i2c/i2cvar.h b/sys/dev/i2c/i2cvar.h index d08de1edc60..86829b0af82 100644 --- a/sys/dev/i2c/i2cvar.h +++ b/sys/dev/i2c/i2cvar.h @@ -1,4 +1,4 @@ -/* $OpenBSD: i2cvar.h,v 1.11 2013/07/05 09:32:14 kettenis Exp $ */ +/* $OpenBSD: i2cvar.h,v 1.12 2016/01/12 01:11:15 jcs Exp $ */ /* $NetBSD: i2cvar.h,v 1.1 2003/09/30 00:35:31 thorpej Exp $ */ /* @@ -106,6 +106,8 @@ struct i2c_attach_args { i2c_tag_t ia_tag; /* our controller */ i2c_addr_t ia_addr; /* address of device */ int ia_size; /* size (for EEPROMs) */ + int ia_irq; /* IRQ */ + int ia_irq_flags; /* IRQ flags */ char *ia_name; /* chip name */ void *ia_cookie; /* pass extra info from bus to dev */ }; diff --git a/sys/dev/i2c/ihidev.c b/sys/dev/i2c/ihidev.c new file mode 100644 index 00000000000..e86ab922bff --- /dev/null +++ b/sys/dev/i2c/ihidev.c @@ -0,0 +1,574 @@ +/* $OpenBSD: ihidev.c,v 1.1 2016/01/12 01:11:15 jcs Exp $ */ +/* + * HID-over-i2c driver + * + * http://download.microsoft.com/download/7/d/d/7dd44bb7-2a7a-4505-ac1c-7227d3d96d5b/hid-over-i2c-protocol-spec-v1-0.docx + * + * Copyright (c) 2015, 2016 joshua stein + * + * 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 +#include +#include +#include +#include + +#include +#include + +#include + +/* XXX */ +#include + +/* #define IHIDEV_DEBUG */ + +#ifdef IHIDEV_DEBUG +#define DPRINTF(x) printf x +#else +#define DPRINTF(x) +#endif + +/* 7.2 */ +enum { + I2C_HID_CMD_DESCR = 0x0, + I2C_HID_CMD_RESET = 0x1, + I2C_HID_CMD_GET_REPORT = 0x2, + I2C_HID_CMD_SET_REPORT = 0x3, + I2C_HID_CMD_GET_IDLE = 0x4, + I2C_HID_CMD_SET_IDLE = 0x5, + I2C_HID_CMD_GET_PROTO = 0x6, + I2C_HID_CMD_SET_PROTO = 0x7, + I2C_HID_CMD_SET_POWER = 0x8, + + /* pseudo commands */ + I2C_HID_REPORT_DESCR = 0x100, +}; + +static int I2C_HID_POWER_ON = 0x0; +static int I2C_HID_POWER_OFF = 0x1; + +union i2c_hid_cmd { + uint8_t data[0]; + struct cmd { + uint16_t reg; + uint8_t reportTypeId; + uint8_t opcode; + } __packed c; +}; + +int ihidev_match(struct device *, void *, void *); +void ihidev_attach(struct device *, struct device *, void *); +int ihidev_detach(struct device *, int); + +int ihidev_hid_command(struct ihidev_softc *, int, void *); +int ihidev_intr(void *); +int ihidev_reset(struct ihidev_softc *); +int ihidev_hid_desc_parse(struct ihidev_softc *); + +int ihidev_maxrepid(void *buf, int len); +int ihidev_print(void *aux, const char *pnp); +int ihidev_submatch(struct device *parent, void *cf, void *aux); + +struct cfattach ihidev_ca = { + sizeof(struct ihidev_softc), + ihidev_match, + ihidev_attach, + ihidev_detach, + NULL +}; + +struct cfdriver ihidev_cd = { + NULL, "ihidev", DV_DULL +}; + +int +ihidev_match(struct device *parent, void *match, void *aux) +{ + struct i2c_attach_args *ia = aux; + + if (strcmp(ia->ia_name, "ihidev") == 0) + return (1); + + return (0); +} + +void +ihidev_attach(struct device *parent, struct device *self, void *aux) +{ + struct ihidev_softc *sc = (struct ihidev_softc *)self; + struct i2c_attach_args *ia = aux; + struct ihidev_attach_arg iha; + struct device *dev; + int repid, repsz; + int repsizes[256]; + + sc->sc_tag = ia->ia_tag; + sc->sc_addr = ia->ia_addr; + + printf(": int %d", ia->ia_irq); + + if (ihidev_hid_command(sc, I2C_HID_CMD_DESCR, NULL) || + ihidev_hid_desc_parse(sc)) { + printf(", failed fetching initial HID descriptor\n"); + return; + } + + printf(", vendor 0x%x product 0x%x, %s\n", + letoh16(sc->hid_desc.wVendorID), letoh16(sc->hid_desc.wProductID), + (char *)ia->ia_cookie); + + sc->sc_nrepid = ihidev_maxrepid(sc->sc_report, sc->sc_reportlen); + if (sc->sc_nrepid < 0) + return; + + printf("%s: %d report id%s\n", sc->sc_dev.dv_xname, sc->sc_nrepid, + sc->sc_nrepid > 1 ? "s" : ""); + + sc->sc_nrepid++; + sc->sc_subdevs = mallocarray(sc->sc_nrepid, sizeof(struct ihidev *), + M_DEVBUF, M_NOWAIT | M_ZERO); + if (sc->sc_subdevs == NULL) { + printf("%s: failed allocating memory\n", sc->sc_dev.dv_xname); + return; + } + + iha.iaa = ia; + iha.parent = sc; + iha.reportid = IHIDEV_CLAIM_ALLREPORTID; + + /* Look for a driver claiming all report IDs first. */ + dev = config_found_sm((struct device *)sc, &iha, NULL, + ihidev_submatch); + if (dev != NULL) { + for (repid = 0; repid < sc->sc_nrepid; repid++) + sc->sc_subdevs[repid] = (struct ihidev *)dev; + return; + } + + sc->sc_isize = 0; + for (repid = 0; repid < sc->sc_nrepid; repid++) { + repsz = hid_report_size(sc->sc_report, sc->sc_reportlen, + hid_input, repid); + repsizes[repid] = repsz; + if (repsz > sc->sc_isize) + sc->sc_isize = repsz; + + DPRINTF(("%s: repid %d size %d\n", sc->sc_dev.dv_xname, repid, + repsz)); + + if (hid_report_size(sc->sc_report, sc->sc_reportlen, hid_input, + repid) == 0 && + hid_report_size(sc->sc_report, sc->sc_reportlen, + hid_output, repid) == 0 && + hid_report_size(sc->sc_report, sc->sc_reportlen, + hid_feature, repid) == 0) + continue; + + iha.reportid = repid; + dev = config_found_sm(self, &iha, ihidev_print, + ihidev_submatch); + sc->sc_subdevs[repid] = (struct ihidev *)dev; + } + sc->sc_isize += (sc->sc_nrepid != 1); /* one byte for the report ID */ + + sc->sc_ibuf = malloc(sc->sc_isize, M_USBDEV, M_WAITOK); + + /* register interrupt with system */ + if (ia->ia_irq > 0) { + /* XXX: don't assume this uses acpi_intr_establish */ + sc->sc_ih = acpi_intr_establish(ia->ia_irq, ia->ia_irq_flags, + IPL_BIO, ihidev_intr, sc, sc->sc_dev.dv_xname); + if (sc->sc_ih == NULL) { + printf(", failed establishing intr\n"); + return; + } + } + + /* power down until we're opened */ + if (ihidev_hid_command(sc, I2C_HID_CMD_SET_POWER, &I2C_HID_POWER_OFF)) { + printf("%s: failed to power down\n", sc->sc_dev.dv_xname); + return; + } +} + +int +ihidev_detach(struct device *self, int flags) +{ + struct ihidev_softc *sc = (struct ihidev_softc *)self; + + if (sc->sc_ih != NULL) { + intr_disestablish(sc->sc_ih); + sc->sc_ih = NULL; + } + + if (sc->sc_ibuf != NULL) { + free(sc->sc_ibuf, M_DEVBUF, 0); + sc->sc_ibuf = NULL; + } + + if (sc->sc_report != NULL) + free(sc->sc_report, M_DEVBUF, sc->sc_reportlen); + + return (0); +} + +int +ihidev_hid_command(struct ihidev_softc *sc, int hidcmd, void *arg) +{ + int i, res = 1; + + iic_acquire_bus(sc->sc_tag, 0); + + switch (hidcmd) { + case I2C_HID_CMD_DESCR: { + /* + * 5.2.2 - HID Descriptor Retrieval + * register is passed from the controller, and is probably just + * the address of the device + */ + uint8_t cmdbuf[] = { htole16(sc->sc_addr), 0x0 }; + + DPRINTF(("%s: HID command I2C_HID_CMD_DESCR at 0x%x\n", + sc->sc_dev.dv_xname, htole16(sc->sc_addr))); + + /* 20 00 */ + res = iic_exec(sc->sc_tag, I2C_OP_WRITE, sc->sc_addr, &cmdbuf, + sizeof(cmdbuf), &sc->hid_desc_buf, + sizeof(struct i2c_hid_desc), 0); + + DPRINTF(("%s: HID descriptor:", sc->sc_dev.dv_xname)); + for (i = 0; i < sizeof(struct i2c_hid_desc); i++) + DPRINTF((" %.2x", sc->hid_desc_buf[i])); + DPRINTF(("\n")); + + break; + } + case I2C_HID_CMD_RESET: { + uint8_t cmdbuf[4] = { 0 }; + union i2c_hid_cmd *cmd = (union i2c_hid_cmd *)cmdbuf; + + DPRINTF(("%s: HID command I2C_HID_CMD_RESET\n", + sc->sc_dev.dv_xname)); + + cmd->data[0] = sc->hid_desc_buf[offsetof(struct i2c_hid_desc, + wCommandRegister)]; + cmd->data[1] = sc->hid_desc_buf[offsetof(struct i2c_hid_desc, + wCommandRegister) + 1]; + cmd->c.opcode = I2C_HID_CMD_RESET; + + /* 22 00 00 01 */ + res = iic_exec(sc->sc_tag, I2C_OP_WRITE, sc->sc_addr, &cmdbuf, + sizeof(cmdbuf), NULL, 0, 0); + + break; + } + case I2C_HID_CMD_SET_POWER: { + uint8_t cmdbuf[4] = { 0 }; + union i2c_hid_cmd *cmd = (union i2c_hid_cmd *)cmdbuf; + int power = *(int *)arg; + + DPRINTF(("%s: HID command I2C_HID_CMD_SET_POWER(%d)\n", + sc->sc_dev.dv_xname, power)); + + cmd->data[0] = sc->hid_desc_buf[offsetof(struct i2c_hid_desc, + wCommandRegister)]; + cmd->data[1] = sc->hid_desc_buf[offsetof(struct i2c_hid_desc, + wCommandRegister) + 1]; + cmd->c.opcode = I2C_HID_CMD_SET_POWER; + cmd->c.reportTypeId = power; + + /* 22 00 00 08 */ + res = iic_exec(sc->sc_tag, I2C_OP_WRITE, sc->sc_addr, &cmdbuf, + sizeof(cmdbuf), NULL, 0, 0); + + break; + } + case I2C_HID_REPORT_DESCR: { + uint8_t cmdbuf[] = { + sc->hid_desc_buf[offsetof(struct i2c_hid_desc, + wReportDescRegister)], 0 }; + + DPRINTF(("%s: HID command I2C_HID_REPORT_DESCR at 0x%x with " + "size %d\n", sc->sc_dev.dv_xname, cmdbuf[0], + sc->sc_reportlen)); + + /* 20 00 */ + res = iic_exec(sc->sc_tag, I2C_OP_WRITE, sc->sc_addr, &cmdbuf, + sizeof(cmdbuf), sc->sc_report, sc->sc_reportlen, 0); + + DPRINTF(("%s: HID report descriptor:", sc->sc_dev.dv_xname)); + for (i = 0; i < sc->sc_reportlen; i++) + DPRINTF((" %.2x", sc->sc_report[i])); + DPRINTF(("\n")); + + break; + } + default: + printf("%s: unknown command %d\n", sc->sc_dev.dv_xname, + hidcmd); + } + + iic_release_bus(sc->sc_tag, 0); + + return (res); +} + +int +ihidev_reset(struct ihidev_softc *sc) +{ + DPRINTF(("%s: resetting\n", sc->sc_dev.dv_xname)); + + if (ihidev_hid_command(sc, I2C_HID_CMD_SET_POWER, &I2C_HID_POWER_ON)) { + printf("%s: failed to power on\n", sc->sc_dev.dv_xname); + return (1); + } + + DELAY(1000); + + if (ihidev_hid_command(sc, I2C_HID_CMD_RESET, 0)) { + printf("%s: failed to reset hardware\n", sc->sc_dev.dv_xname); + + ihidev_hid_command(sc, I2C_HID_CMD_SET_POWER, + &I2C_HID_POWER_OFF); + + return (1); + } + + DELAY(1000); + + return (0); +} + +/* + * 5.2.2 - HID Descriptor Retrieval + * + * parse HID Descriptor that has already been read into hid_desc with + * I2C_HID_CMD_DESCR + */ +int +ihidev_hid_desc_parse(struct ihidev_softc *sc) +{ + int retries = 3; + + /* must be v01.00 */ + if (letoh16(sc->hid_desc.bcdVersion) != 0x0100) { + printf("%s: bad HID descriptor bcdVersion (0x%x)\n", + sc->sc_dev.dv_xname, + letoh16(sc->hid_desc.bcdVersion)); + return (1); + } + + /* must be 30 bytes for v1.00 */ + if (letoh16(sc->hid_desc.wHIDDescLength != + sizeof(struct i2c_hid_desc))) { + printf("%s: bad HID descriptor size (%d != %zu)\n", + sc->sc_dev.dv_xname, + letoh16(sc->hid_desc.wHIDDescLength), + sizeof(struct i2c_hid_desc)); + return (1); + } + + if (letoh16(sc->hid_desc.wReportDescLength) <= 0) { + printf("%s: bad HID report descriptor size (%d)\n", + sc->sc_dev.dv_xname, + letoh16(sc->hid_desc.wReportDescLength)); + return (1); + } + + while (retries-- > 0) { + if (ihidev_reset(sc)) { + if (retries == 0) + return(1); + + DELAY(1000); + } + else + break; + } + + sc->sc_reportlen = letoh16(sc->hid_desc.wReportDescLength); + sc->sc_report = malloc(sc->sc_reportlen, M_DEVBUF, M_NOWAIT | M_ZERO); + + if (ihidev_hid_command(sc, I2C_HID_REPORT_DESCR, 0)) { + printf("%s: failed fetching HID report\n", + sc->sc_dev.dv_xname); + return (1); + } + + return (0); +} + +int +ihidev_intr(void *arg) +{ + struct ihidev_softc *sc = arg; + struct ihidev *scd; + size_t size, psize; + u_int32_t cc; + int res, i; + u_char *p; + u_int rep = 0; + + size = letoh16(sc->hid_desc.wMaxInputLength); + if (size > sc->sc_isize); + size = sc->sc_isize; + + iic_acquire_bus(sc->sc_tag, 0); + + /* XXX: force I2C_F_POLL for now to avoid dwiic interrupting while we + * are interrupting */ + res = iic_exec(sc->sc_tag, I2C_OP_READ, sc->sc_addr, NULL, 0, + sc->sc_ibuf, size, I2C_F_POLL); + + iic_release_bus(sc->sc_tag, 0); + + DPRINTF(("%s: ihidev_intr: hid input:", sc->sc_dev.dv_xname)); + for (i = 0; i < size; i++) + DPRINTF((" %.2x", sc->sc_ibuf[i])); + DPRINTF(("\n")); + + psize = sc->sc_ibuf[0] | sc->sc_ibuf[1] << 8; + if (!psize) { + DPRINTF(("%s: %s: invalid packet size\n", sc->sc_dev.dv_xname, + __func__)); + return (1); + } + + if (psize > size) { + DPRINTF(("%s: %s: truncated packet (%zu > %zu)\n", + sc->sc_dev.dv_xname, __func__, psize, size)); + return (1); + } + + /* report id is 3rd byte */ + p = sc->sc_ibuf + 2; + if (sc->sc_nrepid != 1) + rep = *p++, cc--; + + if (rep >= sc->sc_nrepid) { + printf("%s: %s: bad repid %d\n", sc->sc_dev.dv_xname, __func__, + rep); + return (1); + } + + scd = sc->sc_subdevs[rep]; + if (scd == NULL || !(scd->sc_state & IHIDEV_OPEN)) + return (1); + + scd->sc_intr(scd, p, cc); + + return (1); +} + +int +ihidev_maxrepid(void *buf, int len) +{ + struct hid_data *d; + struct hid_item h; + int maxid; + + maxid = -1; + h.report_ID = 0; + for (d = hid_start_parse(buf, len, hid_none); hid_get_item(d, &h); ) + if (h.report_ID > maxid) + maxid = h.report_ID; + hid_end_parse(d); + + return (maxid); +} + +int +ihidev_print(void *aux, const char *pnp) +{ + struct ihidev_attach_arg *iha = aux; + + if (pnp) + printf("hid at %s", pnp); + + if (iha->reportid != 0 && iha->reportid != IHIDEV_CLAIM_ALLREPORTID) + printf(" reportid %d", iha->reportid); + + return (UNCONF); +} + +int +ihidev_submatch(struct device *parent, void *match, void *aux) +{ + struct ihidev_attach_arg *iha = aux; + struct cfdata *cf = match; + + if (cf->ihidevcf_reportid != IHIDEV_UNK_REPORTID && + cf->ihidevcf_reportid != iha->reportid) + return (0); + + return ((*cf->cf_attach->ca_match)(parent, cf, aux)); +} + +int +ihidev_open(struct ihidev *scd) +{ + struct ihidev_softc *sc = scd->sc_parent; + + DPRINTF(("%s: %s: state=%d refcnt=%d\n", sc->sc_dev.dv_xname, + __func__, scd->sc_state, sc->sc_refcnt)); + + if (scd->sc_state & IHIDEV_OPEN) + return (EBUSY); + + scd->sc_state |= IHIDEV_OPEN; + + if (sc->sc_refcnt++ || sc->sc_isize == 0) + return (0); + + /* power on */ + ihidev_reset(sc); + + return (0); +} + +void +ihidev_close(struct ihidev *scd) +{ + struct ihidev_softc *sc = scd->sc_parent; + + DPRINTF(("%s: %s: state=%d refcnt=%d\n", sc->sc_dev.dv_xname, + __func__, scd->sc_state, sc->sc_refcnt)); + + if (!(scd->sc_state & IHIDEV_OPEN)) + return; + + scd->sc_state &= ~IHIDEV_OPEN; + + if (--sc->sc_refcnt) + return; + + if (ihidev_hid_command(sc, I2C_HID_CMD_SET_POWER, &I2C_HID_POWER_OFF)) + printf("%s: failed to power down\n", sc->sc_dev.dv_xname); +} + +int +ihidev_ioctl(struct ihidev *sc, u_long cmd, caddr_t addr, int flag, + struct proc *p) +{ + return -1; +} + +void +ihidev_get_report_desc(struct ihidev_softc *sc, void **desc, int *size) +{ + *desc = sc->sc_report; + *size = sc->sc_reportlen; +} diff --git a/sys/dev/i2c/ihidev.h b/sys/dev/i2c/ihidev.h new file mode 100644 index 00000000000..d65498735cd --- /dev/null +++ b/sys/dev/i2c/ihidev.h @@ -0,0 +1,120 @@ +/* $OpenBSD: ihidev.h,v 1.1 2016/01/12 01:11:15 jcs Exp $ */ +/* + * HID-over-i2c driver + * + * http://download.microsoft.com/download/7/d/d/7dd44bb7-2a7a-4505-ac1c-7227d3d96d5b/hid-over-i2c-protocol-spec-v1-0.docx + * + * Copyright (c) 2015 joshua stein + * + * 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. + */ + +/* from usbdi.h: Match codes. */ +/* First five codes is for a whole device. */ +#define IMATCH_VENDOR_PRODUCT_REV 14 +#define IMATCH_VENDOR_PRODUCT 13 +#define IMATCH_VENDOR_DEVCLASS_DEVPROTO 12 +#define IMATCH_DEVCLASS_DEVSUBCLASS_DEVPROTO 11 +#define IMATCH_DEVCLASS_DEVSUBCLASS 10 +/* Next six codes are for interfaces. */ +#define IMATCH_VENDOR_PRODUCT_REV_CONF_IFACE 9 +#define IMATCH_VENDOR_PRODUCT_CONF_IFACE 8 +#define IMATCH_VENDOR_IFACESUBCLASS_IFACEPROTO 7 +#define IMATCH_VENDOR_IFACESUBCLASS 6 +#define IMATCH_IFACECLASS_IFACESUBCLASS_IFACEPROTO 5 +#define IMATCH_IFACECLASS_IFACESUBCLASS 4 +#define IMATCH_IFACECLASS 3 +#define IMATCH_IFACECLASS_GENERIC 2 +/* Generic driver */ +#define IMATCH_GENERIC 1 +/* No match */ +#define IMATCH_NONE 0 + +#define IHIDBUSCF_REPORTID 0 +#define IHIDBUSCF_REPORTID_DEFAULT -1 + +#define ihidevcf_reportid cf_loc[IHIDBUSCF_REPORTID] +#define IHIDEV_UNK_REPORTID IHIDBUSCF_REPORTID_DEFAULT + +/* 5.1.1 - HID Descriptor Format */ +struct i2c_hid_desc { + uint16_t wHIDDescLength; + uint16_t bcdVersion; + uint16_t wReportDescLength; + uint16_t wReportDescRegister; + uint16_t wInputRegister; + uint16_t wMaxInputLength; + uint16_t wOutputRegister; + uint16_t wMaxOutputLength; + uint16_t wCommandRegister; + uint16_t wDataRegister; + uint16_t wVendorID; + uint16_t wProductID; + uint16_t wVersionID; + uint32_t reserved; +} __packed; + +struct ihidev_softc { + struct device sc_dev; + i2c_tag_t sc_tag; + + i2c_addr_t sc_addr; + void *sc_ih; + + union { + uint8_t hid_desc_buf[sizeof(struct i2c_hid_desc)]; + struct i2c_hid_desc hid_desc; + }; + + uint8_t *sc_report; + int sc_reportlen; + + u_int sc_nrepid; + struct ihidev **sc_subdevs; + + u_int sc_isize; + u_char *sc_ibuf; + + int sc_refcnt; +}; + +struct ihidev { + struct device sc_idev; + struct ihidev_softc *sc_parent; + uint8_t sc_report_id; + uint8_t sc_state; +#define IHIDEV_OPEN 0x01 /* device is open */ + void (*sc_intr)(struct ihidev *, void *, u_int); + + int sc_isize; + int sc_osize; + int sc_fsize; +}; + +struct ihidev_attach_arg { + struct i2c_attach_args *iaa; + struct ihidev_softc *parent; + uint8_t reportid; +#define IHIDEV_CLAIM_ALLREPORTID 255 +}; + +void ihidev_get_report_desc(struct ihidev_softc *, void **, int *); +int ihidev_open(struct ihidev *); +void ihidev_close(struct ihidev *); +int ihidev_ioctl(struct ihidev *, u_long, caddr_t, int, struct proc *); + +int ihidev_set_report(struct ihidev_softc *, int, int, void *, int); +int ihidev_set_report_async(struct ihidev_softc *, int, int, void *, int); +int ihidev_get_report(struct ihidev_softc *, int, int, void *, int); +int ihidev_get_report_async(struct ihidev_softc *, int, int, void *, int, + void *, void (*)(void *, int, void *, int)); diff --git a/sys/dev/i2c/ims.c b/sys/dev/i2c/ims.c new file mode 100644 index 00000000000..c3bc81ed84b --- /dev/null +++ b/sys/dev/i2c/ims.c @@ -0,0 +1,183 @@ +/* $OpenBSD: ims.c,v 1.1 2016/01/12 01:11:15 jcs Exp $ */ +/* + * HID-over-i2c mouse/trackpad driver + * + * Copyright (c) 2015, 2016 joshua stein + * + * 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 +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include + +struct ims_softc { + struct ihidev sc_hdev; + struct hidms sc_ms; +}; + +void ims_intr(struct ihidev *addr, void *ibuf, u_int len); + +int ims_enable(void *); +void ims_disable(void *); +int ims_ioctl(void *, u_long, caddr_t, int, struct proc *); + +const struct wsmouse_accessops ims_accessops = { + ims_enable, + ims_ioctl, + ims_disable, +}; + +int ims_match(struct device *, void *, void *); +void ims_attach(struct device *, struct device *, void *); +int ims_detach(struct device *, int); + +struct cfdriver ims_cd = { + NULL, "ims", DV_DULL +}; + +const struct cfattach ims_ca = { + sizeof(struct ims_softc), + ims_match, + ims_attach, + ims_detach +}; + +int +ims_match(struct device *parent, void *match, void *aux) +{ + struct ihidev_attach_arg *iha = (struct ihidev_attach_arg *)aux; + int size; + void *desc; + + ihidev_get_report_desc(iha->parent, &desc, &size); + + if (hid_is_collection(desc, size, iha->reportid, + HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_POINTER))) + return (IMATCH_IFACECLASS); + + if (hid_is_collection(desc, size, iha->reportid, + HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_MOUSE))) + return (IMATCH_IFACECLASS); + + if (hid_is_collection(desc, size, iha->reportid, + HID_USAGE2(HUP_DIGITIZERS, HUD_PEN))) + return (IMATCH_IFACECLASS); + + return (IMATCH_NONE); +} + +void +ims_attach(struct device *parent, struct device *self, void *aux) +{ + struct ims_softc *sc = (struct ims_softc *)self; + struct hidms *ms = &sc->sc_ms; + struct ihidev_attach_arg *iha = (struct ihidev_attach_arg *)aux; + int size, repid; + void *desc; + + sc->sc_hdev.sc_intr = ims_intr; + sc->sc_hdev.sc_parent = iha->parent; + sc->sc_hdev.sc_report_id = iha->reportid; + + ihidev_get_report_desc(iha->parent, &desc, &size); + repid = iha->reportid; + sc->sc_hdev.sc_isize = hid_report_size(desc, size, hid_input, repid); + sc->sc_hdev.sc_osize = hid_report_size(desc, size, hid_output, repid); + sc->sc_hdev.sc_fsize = hid_report_size(desc, size, hid_feature, repid); + + if (hidms_setup(self, ms, 0, iha->reportid, desc, size) != 0) + return; + + hidms_attach(ms, &ims_accessops); +} + +int +ims_detach(struct device *self, int flags) +{ + struct ims_softc *sc = (struct ims_softc *)self; + struct hidms *ms = &sc->sc_ms; + + return hidms_detach(ms, flags); +} + +void +ims_intr(struct ihidev *addr, void *buf, u_int len) +{ + struct ims_softc *sc = (struct ims_softc *)addr; + struct hidms *ms = &sc->sc_ms; + + if (ms->sc_enabled != 0) + hidms_input(ms, (uint8_t *)buf, len); +} + +int +ims_enable(void *v) +{ + struct ims_softc *sc = v; + struct hidms *ms = &sc->sc_ms; + int rv; + + if ((rv = hidms_enable(ms)) != 0) + return rv; + + return ihidev_open(&sc->sc_hdev); +} + +void +ims_disable(void *v) +{ + struct ims_softc *sc = v; + struct hidms *ms = &sc->sc_ms; + + hidms_disable(ms); + ihidev_close(&sc->sc_hdev); +} + +int +ims_ioctl(void *v, u_long cmd, caddr_t data, int flag, struct proc *p) +{ + struct ims_softc *sc = v; + struct hidms *ms = &sc->sc_ms; + int rc; + +#if 0 + rc = ihidev_ioctl(&sc->sc_hdev, cmd, data, flag, p); + if (rc != -1) + return rc; +#endif + + rc = hidms_ioctl(ms, cmd, data, flag, p); + if (rc != -1) + return rc; + + switch (cmd) { + case WSMOUSEIO_GTYPE: + /* XXX: should we set something else? */ + *(u_int *)data = WSMOUSE_TYPE_USB; + return 0; + default: + return -1; + } +} -- cgit v1.2.3