git: 137b58e4d204 - main - i2c: Add cadence iic driver
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Tue, 03 Oct 2023 07:56:41 UTC
The branch main has been updated by manu: URL: https://cgit.FreeBSD.org/src/commit/?id=137b58e4d2044adc200d13c8989d3746a0a4bd7f commit 137b58e4d2044adc200d13c8989d3746a0a4bd7f Author: Emmanuel Vadot <manu@FreeBSD.org> AuthorDate: 2023-09-27 05:34:38 +0000 Commit: Emmanuel Vadot <manu@FreeBSD.org> CommitDate: 2023-10-03 07:56:20 +0000 i2c: Add cadence iic driver This IP is found in Xilinx SoC, it only been tested on ZynqMP (arm64) so only enable it there for now. Differential Revision: https://reviews.freebsd.org/D41994 --- sys/arm64/conf/std.xilinx | 3 + sys/conf/files.arm64 | 1 + sys/dev/iicbus/controller/cadence/cdnc_i2c.c | 707 +++++++++++++++++++++++++++ 3 files changed, 711 insertions(+) diff --git a/sys/arm64/conf/std.xilinx b/sys/arm64/conf/std.xilinx index cc89e8575336..50ebf5ade53b 100644 --- a/sys/arm64/conf/std.xilinx +++ b/sys/arm64/conf/std.xilinx @@ -16,4 +16,7 @@ device cgem # Cadence GEM Gigabit Ethernet device # MMC/SD/SDIO Card slot support device sdhci +# IICBUS +device cdnc_i2c + options FDT diff --git a/sys/conf/files.arm64 b/sys/conf/files.arm64 index d8adc54763fc..baf8734fb38f 100644 --- a/sys/conf/files.arm64 +++ b/sys/conf/files.arm64 @@ -696,6 +696,7 @@ arm64/rockchip/clk/rk3568_pmucru.c optional fdt soc_rockchip_rk3568 # Xilinx arm/xilinx/uart_dev_cdnc.c optional uart soc_xilinx_zynq fdt arm/xilinx/zy7_gpio.c optional gpio soc_xilinx_zynq fdt +dev/iicbus/controller/cadence/cdnc_i2c.c optional cdnc_i2c iicbus soc_xilinx_zynq fdt dev/usb/controller/xlnx_dwc3.c optional xhci soc_xilinx_zynq fdt dev/firmware/xilinx/zynqmp_firmware.c optional fdt soc_xilinx_zynq dev/firmware/xilinx/zynqmp_firmware_if.m optional fdt soc_xilinx_zynq diff --git a/sys/dev/iicbus/controller/cadence/cdnc_i2c.c b/sys/dev/iicbus/controller/cadence/cdnc_i2c.c new file mode 100644 index 000000000000..dd525071caa2 --- /dev/null +++ b/sys/dev/iicbus/controller/cadence/cdnc_i2c.c @@ -0,0 +1,707 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2019-2020 Thomas Skibo <thomasskibo@yahoo.com> + * + * 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 AUTHOR 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 AUTHOR 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. + */ + +/* Cadence / Zynq i2c driver. + * + * Reference: Zynq-7000 All Programmable SoC Technical Reference Manual. + * (v1.12.2) July 1, 2018. Xilinx doc UG585. I2C Controller is documented + * in Chapter 20. + */ + +#include <sys/cdefs.h> +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/conf.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/sysctl.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/resource.h> +#include <sys/rman.h> +#include <sys/uio.h> + +#include <machine/bus.h> +#include <machine/resource.h> +#include <machine/stdarg.h> + +#include <dev/fdt/fdt_common.h> +#include <dev/ofw/ofw_bus.h> +#include <dev/ofw/ofw_bus_subr.h> + +#include <dev/extres/clk/clk.h> + +#include <dev/iicbus/iiconf.h> +#include <dev/iicbus/iicbus.h> + +#include "iicbus_if.h" + +#ifdef I2CDEBUG +#define DPRINTF(...) do { printf(__VA_ARGS__); } while (0) +#else +#define DPRINTF(...) do { } while (0) +#endif + +#if 0 +#define HWTYPE_CDNS_R1P10 1 +#endif +#define HWTYPE_CDNS_R1P14 2 + +static struct ofw_compat_data compat_data[] = { +#if 0 + {"cdns,i2c-r1p10", HWTYPE_CDNS_R1P10}, +#endif + {"cdns,i2c-r1p14", HWTYPE_CDNS_R1P14}, + {NULL, 0} +}; + +struct cdnc_i2c_softc { + device_t dev; + device_t iicbus; + struct mtx sc_mtx; + struct resource *mem_res; + struct resource *irq_res; + void *intrhandle; + + uint16_t cfg_reg_shadow; + uint16_t istat; + clk_t ref_clk; + uint32_t ref_clock_freq; + uint32_t i2c_clock_freq; + + int hwtype; + int hold; + + /* sysctls */ + unsigned int i2c_clk_real_freq; + unsigned int interrupts; + unsigned int timeout_ints; +}; + +#define I2C_SC_LOCK(sc) mtx_lock(&(sc)->sc_mtx) +#define I2C_SC_UNLOCK(sc) mtx_unlock(&(sc)->sc_mtx) +#define I2C_SC_LOCK_INIT(sc) \ + mtx_init(&(sc)->sc_mtx, device_get_nameunit((sc)->dev), NULL, MTX_DEF) +#define I2C_SC_LOCK_DESTROY(sc) mtx_destroy(&(sc)->sc_mtx) +#define I2C_SC_ASSERT_LOCKED(sc) mtx_assert(&(sc)->sc_mtx, MA_OWNED) + +#define RD2(sc, off) (bus_read_2((sc)->mem_res, (off))) +#define WR2(sc, off, val) (bus_write_2((sc)->mem_res, (off), (val))) +#define RD1(sc, off) (bus_read_1((sc)->mem_res, (off))) +#define WR1(sc, off, val) (bus_write_1((sc)->mem_res, (off), (val))) + +/* Cadence I2C controller device registers. */ +#define CDNC_I2C_CR 0x0000 /* Config register. */ +#define CDNC_I2C_CR_DIV_A_MASK (3 << 14) +#define CDNC_I2C_CR_DIV_A_SHIFT 14 +#define CDNC_I2C_CR_DIV_A(a) ((a) << 14) +#define CDNC_I2C_CR_DIV_A_MAX 3 +#define CDNC_I2C_CR_DIV_B_MASK (0x3f << 8) +#define CDNC_I2C_CR_DIV_B_SHIFT 8 +#define CDNC_I2C_CR_DIV_B(b) ((b) << 8) +#define CDNC_I2C_CR_DIV_B_MAX 63 +#define CDNC_I2C_CR_CLR_FIFO (1 << 6) +#define CDNC_I2C_CR_SLVMON_MODE (1 << 5) +#define CDNC_I2C_CR_HOLD (1 << 4) +#define CDNC_I2C_CR_ACKEN (1 << 3) +#define CDNC_I2C_CR_NEA (1 << 2) +#define CDNC_I2C_CR_MAST (1 << 1) +#define CDNC_I2C_CR_RNW (1 << 0) + +#define CDNC_I2C_SR 0x0004 /* Status register. */ +#define CDNC_I2C_SR_BUS_ACTIVE (1 << 8) +#define CDNC_I2C_SR_RX_OVF (1 << 7) +#define CDNC_I2C_SR_TX_VALID (1 << 6) +#define CDNC_I2C_SR_RX_VALID (1 << 5) +#define CDNC_I2C_SR_RXRW (1 << 3) + +#define CDNC_I2C_ADDR 0x0008 /* i2c address register. */ +#define CDNC_I2C_DATA 0x000C /* i2c data register. */ + +#define CDNC_I2C_ISR 0x0010 /* Int status register. */ +#define CDNC_I2C_ISR_ARB_LOST (1 << 9) +#define CDNC_I2C_ISR_RX_UNDF (1 << 7) +#define CDNC_I2C_ISR_TX_OVF (1 << 6) +#define CDNC_I2C_ISR_RX_OVF (1 << 5) +#define CDNC_I2C_ISR_SLV_RDY (1 << 4) +#define CDNC_I2C_ISR_XFER_TMOUT (1 << 3) +#define CDNC_I2C_ISR_XFER_NACK (1 << 2) +#define CDNC_I2C_ISR_XFER_DATA (1 << 1) +#define CDNC_I2C_ISR_XFER_DONE (1 << 0) +#define CDNC_I2C_ISR_ALL 0x2ff +#define CDNC_I2C_TRANS_SIZE 0x0014 /* Transfer size. */ +#define CDNC_I2C_PAUSE 0x0018 /* Slv Monitor Pause reg. */ +#define CDNC_I2C_TIME_OUT 0x001C /* Time-out register. */ +#define CDNC_I2C_TIME_OUT_MIN 31 +#define CDNC_I2C_TIME_OUT_MAX 255 +#define CDNC_I2C_IMR 0x0020 /* Int mask register. */ +#define CDNC_I2C_IER 0x0024 /* Int enable register. */ +#define CDNC_I2C_IDR 0x0028 /* Int disable register. */ + +#define CDNC_I2C_FIFO_SIZE 16 +#define CDNC_I2C_DEFAULT_I2C_CLOCK 400000 /* 400Khz default */ + +#define CDNC_I2C_ISR_ERRS (CDNC_I2C_ISR_ARB_LOST | CDNC_I2C_ISR_RX_UNDF | \ + CDNC_I2C_ISR_TX_OVF | CDNC_I2C_ISR_RX_OVF | CDNC_I2C_ISR_XFER_TMOUT | \ + CDNC_I2C_ISR_XFER_NACK) + +/* Configure clock dividers. */ +static int +cdnc_i2c_set_freq(struct cdnc_i2c_softc *sc) +{ + uint32_t div_a, div_b, err, clk_out; + uint32_t best_div_a, best_div_b, best_err; + + best_div_a = 0; + best_div_b = 0; + best_err = ~0U; + + /* + * The i2c controller has a two-stage clock divider to create + * the "clock enable" signal used to sample the incoming SCL and + * SDA signals. The Clock Enable signal is divided by 22 to create + * the outgoing SCL signal. + * + * Try all div_a values and pick best match. + */ + for (div_a = 0; div_a <= CDNC_I2C_CR_DIV_A_MAX; div_a++) { + div_b = sc->ref_clock_freq / (22 * sc->i2c_clock_freq * + (div_a + 1)); + if (div_b > CDNC_I2C_CR_DIV_B_MAX) + continue; + clk_out = sc->ref_clock_freq / (22 * (div_a + 1) * + (div_b + 1)); + err = clk_out > sc->i2c_clock_freq ? + clk_out - sc->i2c_clock_freq : + sc->i2c_clock_freq - clk_out; + if (err < best_err) { + best_err = err; + best_div_a = div_a; + best_div_b = div_b; + } + } + + if (best_err == ~0U) { + device_printf(sc->dev, "cannot configure clock divider.\n"); + return (EINVAL); /* out of range */ + } + + clk_out = sc->ref_clock_freq / (22 * (best_div_a + 1) * + (best_div_b + 1)); + + DPRINTF("%s: ref_clock_freq=%d i2c_clock_freq=%d\n", __func__, + sc->ref_clock_freq, sc->i2c_clock_freq); + DPRINTF("%s: div_a=%d div_b=%d real-freq=%d\n", __func__, best_div_a, + best_div_b, clk_out); + + sc->cfg_reg_shadow &= ~(CDNC_I2C_CR_DIV_A_MASK | + CDNC_I2C_CR_DIV_B_MASK); + sc->cfg_reg_shadow |= CDNC_I2C_CR_DIV_A(best_div_a) | + CDNC_I2C_CR_DIV_B(best_div_b); + WR2(sc, CDNC_I2C_CR, sc->cfg_reg_shadow); + + sc->i2c_clk_real_freq = clk_out; + + return (0); +} + +/* Initialize hardware. */ +static int +cdnc_i2c_init_hw(struct cdnc_i2c_softc *sc) +{ + + /* Reset config register and clear FIFO. */ + sc->cfg_reg_shadow = 0; + WR2(sc, CDNC_I2C_CR, CDNC_I2C_CR_CLR_FIFO); + sc->hold = 0; + + /* Clear and disable all interrupts. */ + WR2(sc, CDNC_I2C_ISR, CDNC_I2C_ISR_ALL); + WR2(sc, CDNC_I2C_IDR, CDNC_I2C_ISR_ALL); + + /* Max out bogus time-out register. */ + WR1(sc, CDNC_I2C_TIME_OUT, CDNC_I2C_TIME_OUT_MAX); + + /* Set up clock dividers. */ + return (cdnc_i2c_set_freq(sc)); +} + +static int +cdnc_i2c_errs(struct cdnc_i2c_softc *sc, uint16_t istat) +{ + + DPRINTF("%s: istat=0x%x\n", __func__, istat); + + /* XXX: clean up after errors. */ + + /* Reset config register and clear FIFO. */ + sc->cfg_reg_shadow &= CDNC_I2C_CR_DIV_A_MASK | CDNC_I2C_CR_DIV_B_MASK; + WR2(sc, CDNC_I2C_CR, sc->cfg_reg_shadow | CDNC_I2C_CR_CLR_FIFO); + sc->hold = 0; + + if (istat & CDNC_I2C_ISR_XFER_TMOUT) + return (IIC_ETIMEOUT); + else if (istat & CDNC_I2C_ISR_RX_UNDF) + return (IIC_EUNDERFLOW); + else if (istat & (CDNC_I2C_ISR_RX_OVF | CDNC_I2C_ISR_TX_OVF)) + return (IIC_EOVERFLOW); + else if (istat & CDNC_I2C_ISR_XFER_NACK) + return (IIC_ENOACK); + else if (istat & CDNC_I2C_ISR_ARB_LOST) + return (IIC_EBUSERR); /* XXX: ???? */ + else + /* Should not happen */ + return (IIC_NOERR); +} + +static int +cdnc_i2c_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr) +{ + struct cdnc_i2c_softc *sc = device_get_softc(dev); + int error; + + DPRINTF("%s: speed=%d addr=0x%x\n", __func__, speed, addr); + + I2C_SC_LOCK(sc); + + sc->i2c_clock_freq = IICBUS_GET_FREQUENCY(sc->iicbus, speed); + + error = cdnc_i2c_init_hw(sc); + + I2C_SC_UNLOCK(sc); + + return (error ? IIC_ENOTSUPP : IIC_NOERR); +} + +static void +cdnc_i2c_intr(void *arg) +{ + struct cdnc_i2c_softc *sc = (struct cdnc_i2c_softc *)arg; + uint16_t status; + + I2C_SC_LOCK(sc); + + sc->interrupts++; + + /* Read active interrupts. */ + status = RD2(sc, CDNC_I2C_ISR) & ~RD2(sc, CDNC_I2C_IMR); + + /* Clear interrupts. */ + WR2(sc, CDNC_I2C_ISR, status); + + if (status & CDNC_I2C_ISR_XFER_TMOUT) + sc->timeout_ints++; + + sc->istat |= status; + + if (status) + wakeup(sc); + + I2C_SC_UNLOCK(sc); +} + +static int +cdnc_i2c_xfer_rd(struct cdnc_i2c_softc *sc, struct iic_msg *msg) +{ + int error = IIC_NOERR; + uint16_t flags = msg->flags; + uint16_t len = msg->len; + int idx = 0, nbytes, last, first = 1; + uint16_t statr; + + DPRINTF("%s: flags=0x%x len=%d\n", __func__, flags, len); + +#if 0 + if (sc->hwtype == HWTYPE_CDNS_R1P10 && (flags & IIC_M_NOSTOP)) + return (IIC_ENOTSUPP); +#endif + + I2C_SC_ASSERT_LOCKED(sc); + + /* Program config register. */ + sc->cfg_reg_shadow &= CDNC_I2C_CR_DIV_A_MASK | CDNC_I2C_CR_DIV_B_MASK; + sc->cfg_reg_shadow |= CDNC_I2C_CR_HOLD | CDNC_I2C_CR_ACKEN | + CDNC_I2C_CR_NEA | CDNC_I2C_CR_MAST | CDNC_I2C_CR_RNW; + WR2(sc, CDNC_I2C_CR, sc->cfg_reg_shadow | CDNC_I2C_CR_CLR_FIFO); + sc->hold = 1; + + while (len > 0) { + nbytes = MIN(CDNC_I2C_FIFO_SIZE - 2, len); + WR1(sc, CDNC_I2C_TRANS_SIZE, nbytes); + + last = nbytes == len && !(flags & IIC_M_NOSTOP); + if (last) { + /* Clear HOLD bit on last transfer. */ + sc->cfg_reg_shadow &= ~CDNC_I2C_CR_HOLD; + WR2(sc, CDNC_I2C_CR, sc->cfg_reg_shadow); + sc->hold = 0; + } + + /* Writing slv address for a start or repeated start. */ + if (first && !(flags & IIC_M_NOSTART)) + WR2(sc, CDNC_I2C_ADDR, msg->slave >> 1); + first = 0; + + /* Enable FIFO interrupts and wait. */ + if (last) + WR2(sc, CDNC_I2C_IER, CDNC_I2C_ISR_XFER_DONE | + CDNC_I2C_ISR_ERRS); + else + WR2(sc, CDNC_I2C_IER, CDNC_I2C_ISR_XFER_DATA | + CDNC_I2C_ISR_ERRS); + + error = mtx_sleep(sc, &sc->sc_mtx, 0, "cdi2c", hz); + + /* Disable FIFO interrupts. */ + WR2(sc, CDNC_I2C_IDR, CDNC_I2C_ISR_XFER_DATA | + CDNC_I2C_ISR_XFER_DONE | CDNC_I2C_ISR_ERRS); + + if (error == EWOULDBLOCK) + error = cdnc_i2c_errs(sc, CDNC_I2C_ISR_XFER_TMOUT); + else if (sc->istat & CDNC_I2C_ISR_ERRS) + error = cdnc_i2c_errs(sc, sc->istat); + sc->istat = 0; + + if (error != IIC_NOERR) + break; + + /* Read nbytes from FIFO. */ + while (nbytes-- > 0) { + statr = RD2(sc, CDNC_I2C_SR); + if (!(statr & CDNC_I2C_SR_RX_VALID)) { + printf("%s: RX FIFO underflow?\n", __func__); + break; + } + msg->buf[idx++] = RD2(sc, CDNC_I2C_DATA); + len--; + } + } + + return (error); +} + +static int +cdnc_i2c_xfer_wr(struct cdnc_i2c_softc *sc, struct iic_msg *msg) +{ + int error = IIC_NOERR; + uint16_t flags = msg->flags; + uint16_t len = msg->len; + int idx = 0, nbytes, last, first = 1; + + DPRINTF("%s: flags=0x%x len=%d\n", __func__, flags, len); + + I2C_SC_ASSERT_LOCKED(sc); + + /* Program config register. */ + sc->cfg_reg_shadow &= CDNC_I2C_CR_DIV_A_MASK | CDNC_I2C_CR_DIV_B_MASK; + sc->cfg_reg_shadow |= CDNC_I2C_CR_HOLD | CDNC_I2C_CR_ACKEN | + CDNC_I2C_CR_NEA | CDNC_I2C_CR_MAST; + WR2(sc, CDNC_I2C_CR, sc->cfg_reg_shadow | CDNC_I2C_CR_CLR_FIFO); + sc->hold = 1; + + while (len > 0) { + /* Put as much data into fifo as you can. */ + nbytes = MIN(len, CDNC_I2C_FIFO_SIZE - + RD1(sc, CDNC_I2C_TRANS_SIZE) - 1); + len -= nbytes; + while (nbytes-- > 0) + WR2(sc, CDNC_I2C_DATA, msg->buf[idx++]); + + last = len == 0 && !(flags & IIC_M_NOSTOP); + if (last) { + /* Clear HOLD bit on last transfer. */ + sc->cfg_reg_shadow &= ~CDNC_I2C_CR_HOLD; + WR2(sc, CDNC_I2C_CR, sc->cfg_reg_shadow); + sc->hold = 0; + } + + /* Perform START if this is start or repeated start. */ + if (first && !(flags & IIC_M_NOSTART)) + WR2(sc, CDNC_I2C_ADDR, msg->slave >> 1); + first = 0; + + /* Enable FIFO interrupts. */ + WR2(sc, CDNC_I2C_IER, CDNC_I2C_ISR_XFER_DONE | + CDNC_I2C_ISR_ERRS); + + /* Wait for end of data transfer. */ + error = mtx_sleep(sc, &sc->sc_mtx, 0, "cdi2c", hz); + + /* Disable FIFO interrupts. */ + WR2(sc, CDNC_I2C_IDR, CDNC_I2C_ISR_XFER_DONE | + CDNC_I2C_ISR_ERRS); + + if (error == EWOULDBLOCK) + error = cdnc_i2c_errs(sc, CDNC_I2C_ISR_XFER_TMOUT); + else if (sc->istat & CDNC_I2C_ISR_ERRS) + error = cdnc_i2c_errs(sc, sc->istat); + sc->istat = 0; + if (error) + break; + } + + return (error); +} + +static int +cdnc_i2c_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs) +{ + struct cdnc_i2c_softc *sc = device_get_softc(dev); + int i, error = IIC_NOERR; + + DPRINTF("%s: nmsgs=%d\n", __func__, nmsgs); + + I2C_SC_LOCK(sc); + + for (i = 0; i < nmsgs; i++) { + DPRINTF("%s: msg[%d]: hold=%d slv=0x%x flags=0x%x len=%d\n", + __func__, i, sc->hold, msgs[i].slave, msgs[i].flags, + msgs[i].len); + + if (!sc->hold && (msgs[i].flags & IIC_M_NOSTART)) + return (IIC_ENOTSUPP); + + if (msgs[i].flags & IIC_M_RD) { + error = cdnc_i2c_xfer_rd(sc, &msgs[i]); + if (error != IIC_NOERR) + break; + } else { + error = cdnc_i2c_xfer_wr(sc, &msgs[i]); + if (error != IIC_NOERR) + break; + } + } + + I2C_SC_UNLOCK(sc); + + return (error); +} + +static void +cdnc_i2c_add_sysctls(device_t dev) +{ + struct cdnc_i2c_softc *sc = device_get_softc(dev); + struct sysctl_ctx_list *ctx; + struct sysctl_oid_list *child; + + ctx = device_get_sysctl_ctx(dev); + child = SYSCTL_CHILDREN(device_get_sysctl_tree(dev)); + + SYSCTL_ADD_UINT(ctx, child, OID_AUTO, "i2c_clk_real_freq", CTLFLAG_RD, + &sc->i2c_clk_real_freq, 0, "i2c clock real frequency"); + + SYSCTL_ADD_UINT(ctx, child, OID_AUTO, "_interrupts", CTLFLAG_RD, + &sc->interrupts, 0, "interrupt calls"); + SYSCTL_ADD_UINT(ctx, child, OID_AUTO, "_timeouts", CTLFLAG_RD, + &sc->timeout_ints, 0, "hardware timeout interrupts"); +} + + +static int +cdnc_i2c_probe(device_t dev) +{ + + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) + return (ENXIO); + + device_set_desc(dev, "Cadence I2C Controller"); + + return (BUS_PROBE_DEFAULT); +} + + +static int cdnc_i2c_detach(device_t); + +static int +cdnc_i2c_attach(device_t dev) +{ + struct cdnc_i2c_softc *sc; + int rid, err; + phandle_t node; + pcell_t cell; + uint64_t freq; + + sc = device_get_softc(dev); + sc->dev = dev; + sc->hwtype = ofw_bus_search_compatible(dev, compat_data)->ocd_data; + + I2C_SC_LOCK_INIT(sc); + + /* Get ref-clock and i2c-clock properties. */ + node = ofw_bus_get_node(dev); + if (OF_getprop(node, "ref-clock", &cell, sizeof(cell)) > 0) + sc->ref_clock_freq = fdt32_to_cpu(cell); + else if (clk_get_by_ofw_index(dev, node, 0, &sc->ref_clk) == 0) { + if ((err = clk_enable(sc->ref_clk)) != 0) + device_printf(dev, "Cannot enable clock. err=%d\n", + err); + else if ((err = clk_get_freq(sc->ref_clk, &freq)) != 0) + device_printf(dev, + "Cannot get clock frequency. err=%d\n", err); + else + sc->ref_clock_freq = freq; + } + else { + device_printf(dev, "must have ref-clock property\n"); + return (ENXIO); + } + if (OF_getprop(node, "clock-frequency", &cell, sizeof(cell)) > 0) + sc->i2c_clock_freq = fdt32_to_cpu(cell); + else + sc->i2c_clock_freq = CDNC_I2C_DEFAULT_I2C_CLOCK; + + /* Get memory resource. */ + rid = 0; + sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, + RF_ACTIVE); + if (sc->mem_res == NULL) { + device_printf(dev, "could not allocate memory resources.\n"); + cdnc_i2c_detach(dev); + return (ENOMEM); + } + + /* Allocate IRQ. */ + rid = 0; + sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, + RF_ACTIVE); + if (sc->irq_res == NULL) { + device_printf(dev, "could not allocate IRQ resource.\n"); + cdnc_i2c_detach(dev); + return (ENOMEM); + } + + /* Activate the interrupt. */ + err = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_MISC | INTR_MPSAFE, + NULL, cdnc_i2c_intr, sc, &sc->intrhandle); + if (err) { + device_printf(dev, "could not setup IRQ.\n"); + cdnc_i2c_detach(dev); + return (err); + } + + /* Configure the device. */ + err = cdnc_i2c_init_hw(sc); + if (err) { + cdnc_i2c_detach(dev); + return (err); + } + + sc->iicbus = device_add_child(dev, "iicbus", -1); + + cdnc_i2c_add_sysctls(dev); + + /* Probe and attach iicbus when interrupts work. */ + return (bus_delayed_attach_children(dev)); +} + +static int +cdnc_i2c_detach(device_t dev) +{ + struct cdnc_i2c_softc *sc = device_get_softc(dev); + + if (device_is_attached(dev)) + bus_generic_detach(dev); + + if (sc->ref_clk != NULL) { + clk_release(sc->ref_clk); + sc->ref_clk = NULL; + } + + /* Delete iic bus. */ + if (sc->iicbus) + device_delete_child(dev, sc->iicbus); + + /* Disable hardware. */ + if (sc->mem_res != NULL) { + sc->cfg_reg_shadow = 0; + WR2(sc, CDNC_I2C_CR, CDNC_I2C_CR_CLR_FIFO); + + /* Clear and disable all interrupts. */ + WR2(sc, CDNC_I2C_ISR, CDNC_I2C_ISR_ALL); + WR2(sc, CDNC_I2C_IDR, CDNC_I2C_ISR_ALL); + } + + /* Teardown and release interrupt. */ + if (sc->irq_res != NULL) { + if (sc->intrhandle) + bus_teardown_intr(dev, sc->irq_res, sc->intrhandle); + bus_release_resource(dev, SYS_RES_IRQ, + rman_get_rid(sc->irq_res), sc->irq_res); + sc->irq_res = NULL; + } + + /* Release memory resource. */ + if (sc->mem_res != NULL) { + bus_release_resource(dev, SYS_RES_MEMORY, + rman_get_rid(sc->mem_res), sc->mem_res); + sc->mem_res = NULL; + } + + I2C_SC_LOCK_DESTROY(sc); + + return (0); +} + + +static phandle_t +cdnc_i2c_get_node(device_t bus, device_t dev) +{ + + return (ofw_bus_get_node(bus)); +} + +static device_method_t cdnc_i2c_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, cdnc_i2c_probe), + DEVMETHOD(device_attach, cdnc_i2c_attach), + DEVMETHOD(device_detach, cdnc_i2c_detach), + + /* ofw_bus interface */ + DEVMETHOD(ofw_bus_get_node, cdnc_i2c_get_node), + + /* iicbus methods */ + DEVMETHOD(iicbus_callback, iicbus_null_callback), + DEVMETHOD(iicbus_reset, cdnc_i2c_reset), + DEVMETHOD(iicbus_transfer, cdnc_i2c_transfer), + + DEVMETHOD_END +}; + +static driver_t cdnc_i2c_driver = { + "cdnc_i2c", + cdnc_i2c_methods, + sizeof(struct cdnc_i2c_softc), +}; + +DRIVER_MODULE(cdnc_i2c, simplebus, cdnc_i2c_driver, NULL, NULL); +DRIVER_MODULE(ofw_iicbus, cdnc_i2c, ofw_iicbus_driver, NULL, NULL); +MODULE_DEPEND(cdnc_i2c, iicbus, 1, 1, 1); +MODULE_DEPEND(cdnc_i2c, ofw_iicbus, 1, 1, 1); +SIMPLEBUS_PNP_INFO(compat_data);