git: 137b58e4d204 - main - i2c: Add cadence iic driver

From: Emmanuel Vadot <manu_at_FreeBSD.org>
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);