diff options
author | Joshua Stein <jcs@cvs.openbsd.org> | 2016-01-12 01:11:16 +0000 |
---|---|---|
committer | Joshua Stein <jcs@cvs.openbsd.org> | 2016-01-12 01:11:16 +0000 |
commit | aed4423c7d742eb53456cd5f7cfd25b67f684521 (patch) | |
tree | e4105dfd6022fcb1576a79fe91753fa8a5771aeb /sys/dev/acpi | |
parent | 051eb318946cb49493d05cc3d8f3b20d33741552 (diff) |
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
Diffstat (limited to 'sys/dev/acpi')
-rw-r--r-- | sys/dev/acpi/acpi.c | 68 | ||||
-rw-r--r-- | sys/dev/acpi/acpireg.h | 10 | ||||
-rw-r--r-- | sys/dev/acpi/acpivar.h | 5 | ||||
-rw-r--r-- | sys/dev/acpi/dwiic.c | 925 | ||||
-rw-r--r-- | sys/dev/acpi/files.acpi | 7 |
5 files changed, 988 insertions, 27 deletions
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 <tholo@sigmasoft.com> * Copyright (c) 2005 Jordan Hargrave <jordan@openbsd.org> @@ -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 <tholo@sigmasoft.com> * Copyright (c) 2005 Marco Peereboom <marco@openbsd.org> @@ -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 <tholo@sigmasoft.com> * @@ -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 <jcs@openbsd.org> + * + * 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 <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/kthread.h> + +#include <dev/acpi/acpireg.h> +#include <dev/acpi/acpivar.h> +#include <dev/acpi/acpidev.h> +#include <dev/acpi/amltypes.h> +#include <dev/acpi/dsdt.h> + +#include <dev/i2c/i2cvar.h> + +/* #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 |