Module Name: src Committed By: bouyer Date: Sun Dec 10 17:12:54 UTC 2017
Modified Files: src/sys/arch/x86/pci: files.pci src/sys/conf: files Added Files: src/sys/arch/x86/pci: dwiic_pci.c lpssreg.h src/sys/dev/ic: dwiic.c dwiic_var.h Log Message: Add support for I2C designware controllers (as found in Intel PCH devices), with a pci front-end. The pci front-end is tied to ACPI and Intel-specific, so it's in arch/x86/pci and not dev/pci. Core driver from OpenBSD, PCI front-end by me. To generate a diff of this commit: cvs rdiff -u -r0 -r1.1 src/sys/arch/x86/pci/dwiic_pci.c \ src/sys/arch/x86/pci/lpssreg.h cvs rdiff -u -r1.20 -r1.21 src/sys/arch/x86/pci/files.pci cvs rdiff -u -r1.1186 -r1.1187 src/sys/conf/files cvs rdiff -u -r0 -r1.1 src/sys/dev/ic/dwiic.c src/sys/dev/ic/dwiic_var.h Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/arch/x86/pci/files.pci diff -u src/sys/arch/x86/pci/files.pci:1.20 src/sys/arch/x86/pci/files.pci:1.21 --- src/sys/arch/x86/pci/files.pci:1.20 Sun May 3 02:50:59 2015 +++ src/sys/arch/x86/pci/files.pci Sun Dec 10 17:12:54 2017 @@ -1,4 +1,4 @@ -# $NetBSD: files.pci,v 1.20 2015/05/03 02:50:59 pgoyette Exp $ +# $NetBSD: files.pci,v 1.21 2017/12/10 17:12:54 bouyer Exp $ device aapic attach aapic at pci @@ -39,6 +39,9 @@ device amdtemp: sysmon_envsys attach amdtemp at amdnb_miscbus file arch/x86/pci/amdtemp.c amdtemp +attach dwiic at pci with pcidwiic +file arch/x86/pci/dwiic_pci.c pcidwiic + # PCI-LPC bridges device rdcpcib: isabus, sysmon_wdog attach rdcpcib at pci Index: src/sys/conf/files diff -u src/sys/conf/files:1.1186 src/sys/conf/files:1.1187 --- src/sys/conf/files:1.1186 Sun Dec 10 17:03:07 2017 +++ src/sys/conf/files Sun Dec 10 17:12:54 2017 @@ -1,4 +1,4 @@ -# $NetBSD: files,v 1.1186 2017/12/10 17:03:07 bouyer Exp $ +# $NetBSD: files,v 1.1187 2017/12/10 17:12:54 bouyer Exp $ # @(#)files.newconf 7.5 (Berkeley) 5/10/93 version 20171118 @@ -1128,6 +1128,11 @@ file dev/ic/pcf8584.c pcf8584 define pca9564 file dev/ic/pca9564.c pca9564 +# Synopsys DesignWare I2C controller +define dwiic +device dwiic: dwiic, i2cbus +file dev/ic/dwiic.c dwiic + # ACPI power management timer (hardware access, independent of ACPI) # define acpipmtimer Added files: Index: src/sys/arch/x86/pci/dwiic_pci.c diff -u /dev/null src/sys/arch/x86/pci/dwiic_pci.c:1.1 --- /dev/null Sun Dec 10 17:12:54 2017 +++ src/sys/arch/x86/pci/dwiic_pci.c Sun Dec 10 17:12:54 2017 @@ -0,0 +1,213 @@ +/* $NetBSD: dwiic_pci.c,v 1.1 2017/12/10 17:12:54 bouyer Exp $ */ + +/*- + * Copyright (c) 2017 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Manuel Bouyer. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * Synopsys DesignWare I2C controller, PCI front-end + */ + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: dwiic_pci.c,v 1.1 2017/12/10 17:12:54 bouyer Exp $"); + +#include <sys/param.h> +#include <sys/systm.h> + +#include <dev/pci/pcireg.h> +#include <dev/pci/pcivar.h> +#include <dev/pci/pcidevs.h> + +#include <dev/acpi/acpivar.h> +#include <dev/acpi/acpi_pci.h> +#include <dev/acpi/acpi_util.h> +#include <dev/acpi/acpi_i2c.h> + +#include <dev/ic/dwiic_var.h> +#include <arch/x86/pci/lpssreg.h> + +//#define DWIIC_DEBUG + +#ifdef DWIIC_DEBUG +#define DPRINTF(x) printf x +#else +#define DPRINTF(x) +#endif + +struct pci_dwiic_softc { + struct dwiic_softc sc_dwiic; + pci_chipset_tag_t sc_pc; + pcitag_t sc_ptag; + struct acpi_devnode *sc_acpinode; +}; + +static uint32_t +lpss_read(struct pci_dwiic_softc *sc, int offset) +{ + u_int32_t b = bus_space_read_4(sc->sc_dwiic.sc_iot, sc->sc_dwiic.sc_ioh, + offset); + return b; +} + +static void +lpss_write(struct pci_dwiic_softc *sc, int offset, uint32_t val) +{ + bus_space_write_4(sc->sc_dwiic.sc_iot, sc->sc_dwiic.sc_ioh, + offset, val); +} + +static int pci_dwiic_match(device_t, cfdata_t, void *); +static void pci_dwiic_attach(device_t, device_t, void *); +static bool dwiic_pci_power(struct dwiic_softc *, bool); + +CFATTACH_DECL_NEW(pcidwiic, sizeof(struct pci_dwiic_softc), + pci_dwiic_match, pci_dwiic_attach, dwiic_detach, NULL); + + +int +pci_dwiic_match(device_t parent, cfdata_t match, void *aux) +{ + struct pci_attach_args *pa = aux; + + if (PCI_VENDOR(pa->pa_id) != PCI_VENDOR_INTEL) + return 0; + + if (PCI_PRODUCT(pa->pa_id) < PCI_PRODUCT_INTEL_100SERIES_LP_I2C_0 || + PCI_PRODUCT(pa->pa_id) > PCI_PRODUCT_INTEL_100SERIES_LP_I2C_3) + return 0; + + return 1; +} + +void +pci_dwiic_attach(device_t parent, device_t self, void *aux) +{ + struct pci_dwiic_softc *sc = device_private(self); + struct pci_attach_args *pa = aux; + const char *intrstr; + pci_intr_handle_t intrhandle; + char intrbuf[PCI_INTRSTR_LEN]; + pcireg_t memtype; + pcireg_t csr; + uint32_t caps; + + sc->sc_dwiic.sc_dev = self; + sc->sc_dwiic.sc_power = dwiic_pci_power; + sc->sc_dwiic.sc_type = dwiic_type_sunrisepoint; + + sc->sc_pc = pa->pa_pc; + sc->sc_ptag = pa->pa_tag; + + /* register access not enabled by BIOS */ + csr = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_COMMAND_STATUS_REG); + pci_conf_write(pa->pa_pc, pa->pa_tag, PCI_COMMAND_STATUS_REG, + csr | PCI_COMMAND_MEM_ENABLE); + + memtype = pci_mapreg_type(pa->pa_pc, pa->pa_tag, PCI_BAR0); + if (pci_mapreg_map(pa, PCI_BAR0, memtype, 0, &sc->sc_dwiic.sc_iot, + &sc->sc_dwiic.sc_ioh, NULL, NULL) != 0) { + aprint_error(": can't map register space\n"); + goto out; + } + dwiic_pci_power(&sc->sc_dwiic, 1); + + caps = lpss_read(sc, LPSS_CAP); + + aprint_naive(": I2C controller\n"); + aprint_normal(": I2C controller instance %d\n", + (int)(caps & LPSS_CAP_INSTANCE)); + + if (pci_intr_map(pa, &intrhandle)) { + aprint_error_dev(self, "can't map interrupt\n"); + goto out; + } + intrstr = pci_intr_string(pa->pa_pc, intrhandle, + intrbuf, sizeof(intrbuf)); + + sc->sc_dwiic.sc_ih = pci_intr_establish(pa->pa_pc, intrhandle, + IPL_VM, dwiic_intr, sc); + if (sc->sc_dwiic.sc_ih == NULL) { + aprint_error_dev(self, "couldn't establish interrupt"); + if (intrstr != NULL) + aprint_error(" at %s", intrstr); + aprint_error("\n"); + goto out; + } + aprint_normal_dev(self, "interrupting at %s\n", intrstr); + + lpss_write(sc, LPSS_RESET, LPSS_RESET_CTRL_REL); + lpss_write(sc, LPSS_REMAP_LO, + pci_conf_read(sc->sc_pc, sc->sc_ptag, PCI_BAR0)); + lpss_write(sc, LPSS_REMAP_HI, + pci_conf_read(sc->sc_pc, sc->sc_ptag, PCI_BAR0 + 0x4)); + + sc->sc_acpinode = acpi_pcidev_find(0 /*XXX segment*/, + pa->pa_bus, pa->pa_device, pa->pa_function); + + if (sc->sc_acpinode) { + sc->sc_dwiic.sc_iba.iba_child_devices = + acpi_enter_i2c_devs(sc->sc_acpinode); + } else { + aprint_verbose_dev(self, "no matching ACPI node\n"); + } + + dwiic_attach(&sc->sc_dwiic); + + pmf_device_register(self, dwiic_suspend, dwiic_resume); + +out: + return; +} + +static bool +dwiic_pci_power(struct dwiic_softc *dwsc, bool power) +{ + struct pci_dwiic_softc *sc = (void *)dwsc; + pcireg_t pmreg; + + printf("status 0x%x\n", pci_conf_read(sc->sc_pc, sc->sc_ptag, PCI_COMMAND_STATUS_REG)); + printf("reset 0x%x\n", lpss_read(sc, LPSS_RESET)); + printf("rlo 0x%x\n", lpss_read(sc, LPSS_REMAP_LO)); + printf("rho 0x%x\n", lpss_read(sc, LPSS_REMAP_HI)); + + if (!power) + lpss_write(sc, LPSS_CLKGATE, LPSS_CLKGATE_CTRL_OFF); + if (pci_get_capability(sc->sc_pc, sc->sc_ptag, PCI_CAP_PWRMGMT, + &pmreg, NULL)) { + DPRINTF(("%s: power status 0x%x", device_xname(dwsc->sc_dev), + pci_conf_read(sc->sc_pc, sc->sc_ptag, pmreg + PCI_PMCSR))); + pci_conf_write(sc->sc_pc, sc->sc_ptag, pmreg + PCI_PMCSR, + power ? PCI_PMCSR_STATE_D0 : PCI_PMCSR_STATE_D3); + DELAY(10000); /* 10 milliseconds */ + DPRINTF((" -> 0x%x\n", + pci_conf_read(sc->sc_pc, sc->sc_ptag, pmreg + PCI_PMCSR))); + } + if (power) { + lpss_write(sc, LPSS_CLKGATE, LPSS_CLKGATE_CTRL_ON); + } + return true; +} Index: src/sys/arch/x86/pci/lpssreg.h diff -u /dev/null src/sys/arch/x86/pci/lpssreg.h:1.1 --- /dev/null Sun Dec 10 17:12:54 2017 +++ src/sys/arch/x86/pci/lpssreg.h Sun Dec 10 17:12:54 2017 @@ -0,0 +1,92 @@ +/* $NetBSD: lpssreg.h,v 1.1 2017/12/10 17:12:54 bouyer Exp $ */ + +/*- + * Copyright (c) 2017 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Manuel Bouyer. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* LPSS registers, as found in several functions of Ivy Lake bridge */ +#define LPSS_RESET 0x204 +#define LPSS_RESET_DMA __BIT(2) +#define LPSS_RESET_CTRL __BITS(1,0) +#define LPSS_RESET_CTRL_ASSERT 0 +#define LPSS_RESET_CTRL_REL 0x3 + +#define LPSS_ACTIVELTR 0x210 +#define LPSS_ACTIVELTR_SSCALE __BITS(12,10) +#define LPSS_ACTIVELTR_SSCALE_1 (0x2 << 10) +#define LPSS_ACTIVELTR_SSCALE_32 (0x3 << 10) +#define LPSS_ACTIVELTR_SVALUE __BITS(9,0) + +#define LPSS_IDLELTR 0x214 +#define LPSS_IDLELTR_SSCALE __BITS(12,10) +#define LPSS_IDLELTR_SSCALE_1 (0x2 << 10) +#define LPSS_IDLELTR_SSCALE_32 (0x3 << 10) +#define LPSS_IDLELTR_SVALUE __BITS(9,0) + +#define LPSS_TXACK 0x218 +#define LPSS_TXACK_OVF __BIT(31) +#define LPSS_TXACK_CNT __BITS(23,0) + +#define LPSS_RXACK 0x21C +#define LPSS_RXACK_OVF __BIT(31) +#define LPSS_RXACK_CNT __BITS(23,0) + +#define LPSS_TX_IRQ 0x220 +#define LPSS_TX_IRQ_MSK __BIT(1) +#define LPSS_TX_IRQ_I __BIT(0) + +#define LPSS_TX_IRQ_CLR 0x224 +#define LPSS_TX_IRQ_CLRI __BIT(0) + +#define LPSS_CLKGATE 0x238 +#define LPSS_CLKGATE_DMA __BITS(3,2) +#define LPSS_CLKGATE_DMA_AUTO (0x0 << 2) +#define LPSS_CLKGATE_DMA_OFF (0x2 << 2) +#define LPSS_CLKGATE_DMA_ON (0x3 << 2) +#define LPSS_CLKGATE_CTRL __BITS(1,0) +#define LPSS_CLKGATE_CTRL_AUTO (0x0 << 0) +#define LPSS_CLKGATE_CTRL_OFF (0x2 << 0) +#define LPSS_CLKGATE_CTRL_ON (0x3 << 0) + +#define LPSS_REMAP_LO 0x240 + +#define LPSS_REMAP_HI 0x244 + +#define LPSS_DEVIDLE 0x24c +#define LPSS_DEVIDLE_IRQ __BIT(4) +#define LPSS_DEVIDLE_RESTORE __BIT(3) +#define LPSS_DEVIDLE_IDLE __BIT(2) +#define LPSS_DEVIDLE_INPROG __BIT(0) + +#define LPSS_CAP 0x2fc +#define LPSS_CAP_DMA __BIT(8) +#define LPSS_CAP_TYPE __BITS(7,4) +#define LPSS_CAP_TYPE_I2C (0x0 << 4) +#define LPSS_CAP_TYPE_UART (0x1 << 4) +#define LPSS_CAP_TYPE_SPI (0x2 << 4) +#define LPSS_CAP_INSTANCE __BITS(3,0) Index: src/sys/dev/ic/dwiic.c diff -u /dev/null src/sys/dev/ic/dwiic.c:1.1 --- /dev/null Sun Dec 10 17:12:54 2017 +++ src/sys/dev/ic/dwiic.c Sun Dec 10 17:12:54 2017 @@ -0,0 +1,623 @@ +/* $NetBSD: dwiic.c,v 1.1 2017/12/10 17:12:54 bouyer Exp $ */ + +/* $OpenBSD dwiic.c,v 1.24 2017/08/17 20:41:16 kettenis Exp $ */ + +/*- + * Copyright (c) 2017 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Manuel Bouyer. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * Synopsys DesignWare I2C controller + * + * Copyright (c) 2015, 2016 joshua stein <j...@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/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: dwiic.c,v 1.1 2017/12/10 17:12:54 bouyer Exp $"); + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/device.h> +#include <sys/kernel.h> +#include <sys/systm.h> + +#include <dev/ic/dwiic_var.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_DATA_CMD_READ 0x100 +#define DW_IC_DATA_CMD_STOP 0x200 +#define DW_IC_DATA_CMD_RESTART 0x400 + +#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 + +/* 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 + +static int dwiic_init(struct dwiic_softc *); +static void dwiic_enable(struct dwiic_softc *, bool); +static int dwiic_i2c_acquire_bus(void *, int); +static void dwiic_i2c_release_bus(void *, int); +static uint32_t dwiic_read(struct dwiic_softc *, int); +static void dwiic_write(struct dwiic_softc *, int, uint32_t); +static int dwiic_i2c_exec(void *, i2c_op_t, i2c_addr_t, const void *, + size_t, void *, size_t, int); + +bool +dwiic_attach(struct dwiic_softc *sc) +{ + if (sc->sc_power != NULL) { + if (!sc->sc_power(sc, 1)) { + aprint_error_dev(sc->sc_dev, "failed to power up\n"); + return 0; + } + } + + /* fetch timing parameters */ + if (sc->ss_hcnt == 0) + sc->ss_hcnt = dwiic_read(sc, DW_IC_SS_SCL_HCNT); + if (sc->ss_lcnt == 0) + sc->ss_lcnt = dwiic_read(sc, DW_IC_SS_SCL_LCNT); + if (sc->fs_hcnt == 0) + sc->fs_hcnt = dwiic_read(sc, DW_IC_FS_SCL_HCNT); + if (sc->fs_lcnt == 0) + sc->fs_lcnt = dwiic_read(sc, DW_IC_FS_SCL_LCNT); + if (sc->sda_hold_time == 0) + sc->sda_hold_time = dwiic_read(sc, DW_IC_SDA_HOLD); + + if (dwiic_init(sc)) { + aprint_error_dev(sc->sc_dev, "failed initializing\n"); + return 0; + } + + /* leave the controller disabled */ + dwiic_write(sc, DW_IC_INTR_MASK, 0); + dwiic_enable(sc, 0); + dwiic_read(sc, DW_IC_CLR_INTR); + + mutex_init(&sc->sc_i2c_lock, MUTEX_DEFAULT, IPL_NONE); + mutex_init(&sc->sc_int_lock, MUTEX_DEFAULT, IPL_VM); + cv_init(&sc->sc_int_readwait, "dwiicr"); + cv_init(&sc->sc_int_writewait, "dwiicw"); + + /* 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; + + sc->sc_iba.iba_tag = &sc->sc_i2c_tag; + + config_found_ia(sc->sc_dev, "i2cbus", &sc->sc_iba, iicbus_print); + return 1; +} + +int +dwiic_detach(device_t self, int flags) +{ + struct dwiic_softc *sc = device_private(self); + + dwiic_enable(sc, 0); + if (sc->sc_ih != NULL) { + intr_disestablish(sc->sc_ih); + sc->sc_ih = NULL; + } + + return 0; +} + +bool +dwiic_suspend(device_t self, const pmf_qual_t *qual) +{ + struct dwiic_softc *sc = device_private(self); + + /* disable controller */ + dwiic_enable(sc, 0); + + /* disable interrupts */ + dwiic_write(sc, DW_IC_INTR_MASK, 0); + dwiic_read(sc, DW_IC_CLR_INTR); + if (sc->sc_power != NULL) { + if (!sc->sc_power(sc, 0)) { + aprint_error_dev(sc->sc_dev, "failed to power off\n"); + } + } + return true; +} + +bool +dwiic_resume(device_t self, const pmf_qual_t *qual) +{ + struct dwiic_softc *sc = device_private(self); + if (sc->sc_power != NULL) { + if (!sc->sc_power(sc, 1)) { + aprint_error_dev(sc->sc_dev, "failed to power up\n"); + return false; + } + } + dwiic_init(sc); + return true; +} + +static uint32_t +dwiic_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", device_xname(sc->sc_dev), offset, b)); + + return b; +} + +static void +dwiic_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", device_xname(sc->sc_dev), offset, + val)); +} + +static 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); + + mutex_enter(&sc->sc_i2c_lock); + return 1; +} + +void +dwiic_i2c_release_bus(void *cookie, int flags) +{ + struct dwiic_softc *sc = cookie; + + if (cold || sc->sc_poll || (flags & I2C_F_POLL)) + return; + + mutex_exit(&sc->sc_i2c_lock); +} + +static int +dwiic_init(struct dwiic_softc *sc) +{ + uint32_t reg; + + /* make sure we're talking to a device we know */ + reg = dwiic_read(sc, DW_IC_COMP_TYPE); + if (reg != DW_IC_COMP_TYPE_VALUE) { + DPRINTF(("%s: invalid component type 0x%x\n", + device_xname(sc->sc_dev), reg)); + return 1; + } + + /* disable the adapter */ + dwiic_enable(sc, 0); + + /* write standard-mode SCL timing parameters */ + dwiic_write(sc, DW_IC_SS_SCL_HCNT, sc->ss_hcnt); + dwiic_write(sc, DW_IC_SS_SCL_LCNT, sc->ss_lcnt); + + /* and fast-mode SCL timing parameters */ + dwiic_write(sc, DW_IC_FS_SCL_HCNT, sc->fs_hcnt); + dwiic_write(sc, DW_IC_FS_SCL_LCNT, sc->fs_lcnt); + + /* SDA hold time */ + reg = dwiic_read(sc, DW_IC_COMP_VERSION); + if (reg >= DW_IC_SDA_HOLD_MIN_VERS) + dwiic_write(sc, DW_IC_SDA_HOLD, sc->sda_hold_time); + + /* FIFO threshold levels */ + sc->tx_fifo_depth = 32; + sc->rx_fifo_depth = 32; + dwiic_write(sc, DW_IC_TX_TL, sc->tx_fifo_depth / 2); + dwiic_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_write(sc, DW_IC_CON, sc->master_cfg); + + return 0; +} + +static void +dwiic_enable(struct dwiic_softc *sc, bool enable) +{ + int retries; + + for (retries = 100; retries > 0; retries--) { + dwiic_write(sc, DW_IC_ENABLE, enable); + if ((dwiic_read(sc, DW_IC_ENABLE_STATUS) & 1) == enable) + return; + + DELAY(25); + } + + aprint_error_dev(sc->sc_dev, "failed to %sable\n", + (enable ? "en" : "dis")); +} + +static 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; + const uint8_t *bcmd; + uint8_t *bdata; + + if (cold || sc->sc_poll) + flags |= I2C_F_POLL; + + DPRINTF(("%s: %s: op %d, addr 0x%02x, cmdlen %zu, len %zu, " + "flags 0x%02x\n", device_xname(sc->sc_dev), __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_read(sc, DW_IC_STATUS); + if (!(st & DW_IC_STATUS_ACTIVITY)) + break; + DELAY(1000); + } + DPRINTF(("%s: %s: status 0x%x\n", device_xname(sc->sc_dev), __func__, st)); + if (st & DW_IC_STATUS_ACTIVITY) { + return (1); + } + + /* disable controller */ + dwiic_enable(sc, 0); + + /* set slave address */ + ic_con = dwiic_read(sc, DW_IC_CON); + ic_con &= ~DW_IC_CON_10BITADDR_MASTER; + dwiic_write(sc, DW_IC_CON, ic_con); + dwiic_write(sc, DW_IC_TAR, addr); + + /* disable interrupts */ + dwiic_write(sc, DW_IC_INTR_MASK, 0); + dwiic_read(sc, DW_IC_CLR_INTR); + + /* enable controller */ + dwiic_enable(sc, 1); + + /* wait until the controller is ready for commands */ + if (flags & I2C_F_POLL) + DELAY(200); + else { + mutex_enter(&sc->sc_int_lock); + dwiic_read(sc, DW_IC_CLR_INTR); + dwiic_write(sc, DW_IC_INTR_MASK, DW_IC_INTR_TX_EMPTY); + + if (cv_timedwait(&sc->sc_int_writewait, + &sc->sc_int_lock, hz / 2) != 0) + aprint_error_dev(sc->sc_dev, + "timed out waiting for tx_empty intr\n"); + dwiic_write(sc, DW_IC_INTR_MASK, 0); + dwiic_read(sc, DW_IC_CLR_INTR); + mutex_exit(&sc->sc_int_lock); + } + + /* send our command, one byte at a time */ + if (cmdlen > 0) { + bcmd = (const void *)cmdbuf; + + DPRINTF(("%s: %s: sending cmd (len %zu):", device_xname(sc->sc_dev), + __func__, cmdlen)); + for (x = 0; x < cmdlen; x++) + DPRINTF((" %02x", bcmd[x])); + DPRINTF(("\n")); + + tx_limit = sc->tx_fifo_depth - dwiic_read(sc, DW_IC_TXFLR); + if (cmdlen > tx_limit) { + /* TODO */ + aprint_error_dev(sc->sc_dev, "can't write %zu (> %d)\n", + cmdlen, tx_limit); + sc->sc_i2c_xfer.error = 1; + return (1); + } + + for (x = 0; x < cmdlen; x++) { + cmd = bcmd[x]; + /* + * Generate STOP condition if this is the last + * byte of the transfer. + */ + if (x == (cmdlen - 1) && len == 0 && I2C_OP_STOP_P(op)) + cmd |= DW_IC_DATA_CMD_STOP; + dwiic_write(sc, DW_IC_DATA_CMD, cmd); + } + } + + bdata = (void *)buf; + x = readpos = 0; + tx_limit = sc->tx_fifo_depth - dwiic_read(sc, DW_IC_TXFLR); + + DPRINTF(("%s: %s: need to read %zu bytes, can send %d read reqs\n", + device_xname(sc->sc_dev), __func__, len, tx_limit)); + + while (x < len) { + if (I2C_OP_WRITE_P(op)) + cmd = bdata[x]; + else + cmd = DW_IC_DATA_CMD_READ; + + /* + * Generate RESTART condition if we're reversing + * direction. + */ + if (x == 0 && cmdlen > 0 && I2C_OP_READ_P(op)) + cmd |= DW_IC_DATA_CMD_RESTART; + /* + * Generate STOP conditon on the last byte of the + * transfer. + */ + if (x == (len - 1) && I2C_OP_STOP_P(op)) + cmd |= DW_IC_DATA_CMD_STOP; + + dwiic_write(sc, DW_IC_DATA_CMD, cmd); + + 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", + device_xname(sc->sc_dev), __func__, tx_limit, x)); + + if (flags & I2C_F_POLL) { + for (retries = 100; retries > 0; retries--) { + rx_avail = dwiic_read(sc, DW_IC_RXFLR); + if (rx_avail > 0) + break; + DELAY(50); + } + } else { + mutex_enter(&sc->sc_int_lock); + dwiic_read(sc, DW_IC_CLR_INTR); + dwiic_write(sc, DW_IC_INTR_MASK, + DW_IC_INTR_RX_FULL); + + if (cv_timedwait(&sc->sc_int_readwait, + &sc->sc_int_lock, hz / 2) != 0) + aprint_error_dev(sc->sc_dev, + "timed out waiting for " + "rx_full intr\n"); + + dwiic_write(sc, DW_IC_INTR_MASK, 0); + dwiic_read(sc, DW_IC_CLR_INTR); + mutex_exit(&sc->sc_int_lock); + rx_avail = dwiic_read(sc, DW_IC_RXFLR); + } + + if (rx_avail == 0) { + aprint_error_dev(sc->sc_dev, + "timed out reading remaining %d\n", + (int)(len - 1 - readpos)); + sc->sc_i2c_xfer.error = 1; + return (1); + } + + DPRINTF(("%s: %s: %d avail to read (%zu remaining)\n", + device_xname(sc->sc_dev), __func__, rx_avail, + len - readpos)); + + while (rx_avail > 0) { + resp = dwiic_read(sc, DW_IC_DATA_CMD); + if (readpos < len) { + bdata[readpos] = resp; + readpos++; + } + rx_avail--; + } + + if (readpos >= len) + break; + + DPRINTF(("%s: still need to read %d bytes\n", + device_xname(sc->sc_dev), (int)(len - readpos))); + tx_limit = sc->tx_fifo_depth - + dwiic_read(sc, DW_IC_TXFLR); + } + } + + return 0; +} + +static uint32_t +dwiic_read_clear_intrbits(struct dwiic_softc *sc) +{ + uint32_t stat; + + stat = dwiic_read(sc, DW_IC_INTR_STAT); + + if (stat & DW_IC_INTR_RX_UNDER) + dwiic_read(sc, DW_IC_CLR_RX_UNDER); + if (stat & DW_IC_INTR_RX_OVER) + dwiic_read(sc, DW_IC_CLR_RX_OVER); + if (stat & DW_IC_INTR_TX_OVER) + dwiic_read(sc, DW_IC_CLR_TX_OVER); + if (stat & DW_IC_INTR_RD_REQ) + dwiic_read(sc, DW_IC_CLR_RD_REQ); + if (stat & DW_IC_INTR_TX_ABRT) + dwiic_read(sc, DW_IC_CLR_TX_ABRT); + if (stat & DW_IC_INTR_RX_DONE) + dwiic_read(sc, DW_IC_CLR_RX_DONE); + if (stat & DW_IC_INTR_ACTIVITY) + dwiic_read(sc, DW_IC_CLR_ACTIVITY); + if (stat & DW_IC_INTR_STOP_DET) + dwiic_read(sc, DW_IC_CLR_STOP_DET); + if (stat & DW_IC_INTR_START_DET) + dwiic_read(sc, DW_IC_CLR_START_DET); + if (stat & DW_IC_INTR_GEN_CALL) + dwiic_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_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", device_xname(sc->sc_dev), + __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", device_xname(sc->sc_dev), + __func__)); + else { + mutex_enter(&sc->sc_int_lock); + if (stat & DW_IC_INTR_RX_FULL) { + dwiic_write(sc, DW_IC_INTR_MASK, 0); + DPRINTF(("%s: %s: waking up reader\n", + device_xname(sc->sc_dev), __func__)); + cv_signal(&sc->sc_int_readwait); + } + if (stat & DW_IC_INTR_TX_EMPTY) { + dwiic_write(sc, DW_IC_INTR_MASK, 0); + DPRINTF(("%s: %s: waking up writer\n", + device_xname(sc->sc_dev), __func__)); + cv_signal(&sc->sc_int_writewait); + } + mutex_exit(&sc->sc_int_lock); + } + + return 1; +} Index: src/sys/dev/ic/dwiic_var.h diff -u /dev/null src/sys/dev/ic/dwiic_var.h:1.1 --- /dev/null Sun Dec 10 17:12:54 2017 +++ src/sys/dev/ic/dwiic_var.h Sun Dec 10 17:12:54 2017 @@ -0,0 +1,80 @@ +/* $NetBSD: dwiic_var.h,v 1.1 2017/12/10 17:12:54 bouyer Exp $ */ + +/*- + * Copyright (c) 2017 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Manuel Bouyer. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <dev/i2c/i2cvar.h> + +enum dwiic_type { + dwiic_type_generic, + dwiic_type_sunrisepoint +}; + +struct dwiic_softc { + device_t sc_dev; + + bus_space_tag_t sc_iot; + bus_space_handle_t sc_ioh; + void *sc_ih; + + enum dwiic_type sc_type; + + bool (*sc_power)(struct dwiic_softc *, bool); + + struct i2cbus_attach_args sc_iba; + device_t sc_iic; + + int sc_poll; + kmutex_t sc_int_lock; + kcondvar_t sc_int_readwait; + kcondvar_t sc_int_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; + kmutex_t sc_i2c_lock; + struct { + i2c_op_t op; + void *buf; + size_t len; + int flags; + volatile int error; + } sc_i2c_xfer; +}; + +bool dwiic_attach(struct dwiic_softc *); +int dwiic_detach(device_t, int); +bool dwiic_suspend(device_t, const pmf_qual_t *); +bool dwiic_resume(device_t, const pmf_qual_t *); + +int dwiic_intr(void *);