mirror of
https://github.com/opnsense/src.git
synced 2026-06-08 16:22:46 -04:00
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
This commit is contained in:
parent
125f5c5b48
commit
137b58e4d2
3 changed files with 711 additions and 0 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
707
sys/dev/iicbus/controller/cadence/cdnc_i2c.c
Normal file
707
sys/dev/iicbus/controller/cadence/cdnc_i2c.c
Normal file
|
|
@ -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);
|
||||
Loading…
Reference in a new issue