[PATCH] i2c: add Cadence i2c host controller support
Sascha Hauer
sha at pengutronix.de
Tue May 10 23:44:29 PDT 2022
On Tue, May 10, 2022 at 03:04:14PM +0200, Matthias Fend wrote:
> Add a driver to support the Cadence I2C host controller found in Zynq
> UltraScale+ MPSoCs.
>
> Signed-off-by: Matthias Fend <matthias.fend at emfend.at>
> ---
> drivers/i2c/busses/Kconfig | 8 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-cadence.c | 453 +++++++++++++++++++++++++++++++
> 3 files changed, 462 insertions(+)
> create mode 100644 drivers/i2c/busses/i2c-cadence.c
Applied, thanks
Sascha
>
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index d4e74552b..58f865606 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -70,4 +70,12 @@ config I2C_RK3X
> Say Y here to include support for the I2C adapter in Rockchip RK3xxx
> SoCs.
>
> +config I2C_CADENCE
> + bool "Cadence I2C adapter"
> + depends on HAVE_CLK
> + depends on ARCH_ZYNQMP || COMPILE_TEST
> + help
> + Say Y here to include support for the Cadence I2C host controller found
> + in Zynq UltraScale+ MPSoCs.
> +
> endmenu
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index d6273f3d8..a8661f605 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -10,3 +10,4 @@ obj-$(CONFIG_I2C_VERSATILE) += i2c-versatile.o
> obj-$(CONFIG_I2C_DESIGNWARE) += i2c-designware.o
> obj-$(CONFIG_I2C_STM32) += i2c-stm32.o
> obj-$(CONFIG_I2C_RK3X) += i2c-rockchip.o
> +obj-$(CONFIG_I2C_CADENCE) += i2c-cadence.o
> diff --git a/drivers/i2c/busses/i2c-cadence.c b/drivers/i2c/busses/i2c-cadence.c
> new file mode 100644
> index 000000000..5537efff2
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-cadence.c
> @@ -0,0 +1,453 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * I2C bus driver for the Cadence I2C host controller (master only).
> + *
> + * Partly based on the driver in the Linux kernel
> + * Copyright (C) 2009 - 2014 Xilinx, Inc.
> + *
> + * Copyright (C) 2022 Matthias Fend <matthias.fend at emfend.at>
> + */
> +
> +#include <common.h>
> +#include <i2c/i2c.h>
> +#include <linux/iopoll.h>
> +#include <errno.h>
> +#include <linux/err.h>
> +#include <driver.h>
> +#include <io.h>
> +#include <linux/clk.h>
> +#include <regmap.h>
> +
> +struct __packed i2c_regs {
> + u32 control;
> + u32 status;
> + u32 address;
> + u32 data;
> + u32 interrupt_status;
> + u32 transfer_size;
> + u32 slave_mon_pause;
> + u32 time_out;
> + u32 interrupt_mask;
> + u32 interrupt_enable;
> + u32 interrupt_disable;
> + u32 glitch_filter;
> +};
> +
> +/* Control register fields */
> +#define CDNS_I2C_CONTROL_RW BIT(0)
> +#define CDNS_I2C_CONTROL_MS BIT(1)
> +#define CDNS_I2C_CONTROL_NEA BIT(2)
> +#define CDNS_I2C_CONTROL_ACKEN BIT(3)
> +#define CDNS_I2C_CONTROL_HOLD BIT(4)
> +#define CDNS_I2C_CONTROL_SLVMON BIT(5)
> +#define CDNS_I2C_CONTROL_CLR_FIFO BIT(6)
> +#define CDNS_I2C_CONTROL_DIV_B_SHIFT 8
> +#define CDNS_I2C_CONTROL_DIV_B_MASK (0x3F << CDNS_I2C_CONTROL_DIV_B_SHIFT)
> +#define CDNS_I2C_CONTROL_DIV_A_SHIFT 14
> +#define CDNS_I2C_CONTROL_DIV_A_MASK (0x03 << CDNS_I2C_CONTROL_DIV_A_SHIFT)
> +
> +#define CDNS_I2C_CONTROL_DIV_B_MAX 64
> +#define CDNS_I2C_CONTROL_DIV_A_MAX 4
> +
> +/* Status register fields */
> +#define CDNS_I2C_STATUS_RXRW BIT(3)
> +#define CDNS_I2C_STATUS_RXDV BIT(5)
> +#define CDNS_I2C_STATUS_TXDV BIT(6)
> +#define CDNS_I2C_STATUS_RXOVF BIT(7)
> +#define CDNS_I2C_STATUS_BA BIT(8)
> +
> +/* Address register fields */
> +#define CDNS_I2C_ADDRESS_MASK 0x3FF
> +
> +/* Interrupt register fields */
> +#define CDNS_I2C_INTERRUPT_COMP BIT(0)
> +#define CDNS_I2C_INTERRUPT_DATA BIT(1)
> +#define CDNS_I2C_INTERRUPT_NACK BIT(2)
> +#define CDNS_I2C_INTERRUPT_TO BIT(3)
> +#define CDNS_I2C_INTERRUPT_SLVRDY BIT(4)
> +#define CDNS_I2C_INTERRUPT_RXOVF BIT(5)
> +#define CDNS_I2C_INTERRUPT_TXOVF BIT(6)
> +#define CDNS_I2C_INTERRUPT_RXUNF BIT(7)
> +#define CDNS_I2C_INTERRUPT_ARBLOST BIT(9)
> +
> +#define CDNS_I2C_INTERRUPTS_MASK_MASTER (CDNS_I2C_INTERRUPT_COMP | \
> + CDNS_I2C_INTERRUPT_DATA | \
> + CDNS_I2C_INTERRUPT_NACK | \
> + CDNS_I2C_INTERRUPT_RXOVF | \
> + CDNS_I2C_INTERRUPT_TXOVF | \
> + CDNS_I2C_INTERRUPT_RXUNF | \
> + CDNS_I2C_INTERRUPT_ARBLOST)
> +
> +#define CDNS_I2C_INTERRUPTS_MASK_ALL (CDNS_I2C_INTERRUPT_COMP | \
> + CDNS_I2C_INTERRUPT_DATA | \
> + CDNS_I2C_INTERRUPT_NACK | \
> + CDNS_I2C_INTERRUPT_TO | \
> + CDNS_I2C_INTERRUPT_SLVRDY | \
> + CDNS_I2C_INTERRUPT_RXOVF | \
> + CDNS_I2C_INTERRUPT_TXOVF | \
> + CDNS_I2C_INTERRUPT_RXUNF | \
> + CDNS_I2C_INTERRUPT_ARBLOST)
> +
> +#define CDNS_I2C_FIFO_DEPTH 16
> +#define CDNS_I2C_TRANSFER_SIZE_MAX 255
> +#define CDNS_I2C_TRANSFER_SIZE (CDNS_I2C_TRANSFER_SIZE_MAX - 3)
> +
> +#define I2C_TIMEOUT_US (100 * USEC_PER_MSEC)
> +
> +struct cdns_i2c {
> + struct i2c_adapter adapter;
> + struct clk *clk;
> + struct i2c_regs *regs;
> + bool bus_hold_flag;
> +};
> +
> +static void cdns_i2c_reset_hardware(struct cdns_i2c *i2c)
> +{
> + struct i2c_regs *regs = i2c->regs;
> + u32 regval;
> +
> + writel(CDNS_I2C_INTERRUPTS_MASK_ALL, ®s->interrupt_disable);
> +
> + regval = readl(®s->control);
> + regval &= ~CDNS_I2C_CONTROL_HOLD;
> + regval |= CDNS_I2C_CONTROL_CLR_FIFO;
> + writel(regval, ®s->control);
> +
> + writel(0xFF, ®s->time_out);
> +
> + writel(0, ®s->transfer_size);
> +
> + regval = readl(®s->interrupt_status);
> + writel(regval, ®s->interrupt_status);
> +
> + regval = readl(®s->status);
> + writel(regval, ®s->status);
> +
> + writel(0, ®s->control);
> +}
> +
> +static void cdns_i2c_setup_master(struct cdns_i2c *i2c)
> +{
> + u32 control;
> +
> + control = readl(&i2c->regs->control);
> + control |= CDNS_I2C_CONTROL_MS | CDNS_I2C_CONTROL_ACKEN |
> + CDNS_I2C_CONTROL_NEA;
> + writel(control, &i2c->regs->control);
> +
> + writel(CDNS_I2C_INTERRUPTS_MASK_MASTER, &i2c->regs->interrupt_enable);
> +}
> +
> +static void cdns_i2c_clear_hold_flag(struct cdns_i2c *i2c)
> +{
> + u32 control;
> +
> + control = readl(&i2c->regs->control);
> + if (control & CDNS_I2C_CONTROL_HOLD)
> + writel(control & ~CDNS_I2C_CONTROL_HOLD, &i2c->regs->control);
> +}
> +
> +static bool cdns_i2c_is_busy(struct cdns_i2c *i2c)
> +{
> + return readl(&i2c->regs->status) & CDNS_I2C_STATUS_BA;
> +}
> +
> +static int cdns_i2c_hw_error(struct cdns_i2c *i2c)
> +{
> + u32 isr_status;
> +
> + isr_status = readl(&i2c->regs->interrupt_status);
> +
> + if (isr_status & CDNS_I2C_INTERRUPT_NACK)
> + return -EREMOTEIO;
> +
> + if (isr_status &
> + (CDNS_I2C_INTERRUPT_ARBLOST | CDNS_I2C_INTERRUPT_RXOVF))
> + return -EAGAIN;
> +
> + return 0;
> +}
> +
> +static int cdns_i2c_wait_for_completion(struct cdns_i2c *i2c)
> +{
> + int err;
> + u32 isr_status;
> + const u32 isr_mask =
> + (CDNS_I2C_INTERRUPT_COMP | CDNS_I2C_INTERRUPT_NACK |
> + CDNS_I2C_INTERRUPT_ARBLOST);
> +
> + err = readl_poll_timeout(&i2c->regs->interrupt_status, isr_status,
> + isr_status & isr_mask, I2C_TIMEOUT_US);
> +
> + if (err)
> + return -ETIMEDOUT;
> +
> + return cdns_i2c_hw_error(i2c);
> +}
> +
> +/*
> + * Find best clock divisors
> + *
> + * f = finput / (22 x (div_a + 1) x (div_b + 1))
> + */
> +static int cdns_i2c_calc_divs(u32 *f, u32 input_clk, u32 *a, u32 *b)
> +{
> + ulong fscl = *f, best_fscl = *f, actual_fscl, temp;
> + uint div_a, div_b, calc_div_a = 0, calc_div_b = 0;
> + uint last_error, current_error;
> +
> + temp = input_clk / (22 * fscl);
> +
> + if (!temp ||
> + (temp > (CDNS_I2C_CONTROL_DIV_A_MAX * CDNS_I2C_CONTROL_DIV_B_MAX)))
> + return -EINVAL;
> +
> + last_error = -1;
> + for (div_a = 0; div_a < CDNS_I2C_CONTROL_DIV_A_MAX; div_a++) {
> + div_b = DIV_ROUND_UP(input_clk, 22 * fscl * (div_a + 1));
> +
> + if ((div_b < 1) || (div_b > CDNS_I2C_CONTROL_DIV_B_MAX))
> + continue;
> + div_b--;
> +
> + actual_fscl = input_clk / (22 * (div_a + 1) * (div_b + 1));
> +
> + if (actual_fscl > fscl)
> + continue;
> +
> + current_error = ((actual_fscl > fscl) ? (actual_fscl - fscl) :
> + (fscl - actual_fscl));
> +
> + if (last_error > current_error) {
> + calc_div_a = div_a;
> + calc_div_b = div_b;
> + best_fscl = actual_fscl;
> + last_error = current_error;
> + }
> + }
> +
> + *a = calc_div_a;
> + *b = calc_div_b;
> + *f = best_fscl;
> +
> + return 0;
> +}
> +
> +static int cdns_i2c_set_clk(struct cdns_i2c *i2c, u32 scl_rate)
> +{
> + u32 i2c_rate;
> + u32 control;
> + u32 div_a, div_b;
> + int err;
> +
> + i2c_rate = clk_get_rate(i2c->clk);
> +
> + err = cdns_i2c_calc_divs(&scl_rate, i2c_rate, &div_a, &div_b);
> + if (err)
> + return err;
> +
> + control = readl(&i2c->regs->control);
> + control &= ~(CDNS_I2C_CONTROL_DIV_B_MASK | CDNS_I2C_CONTROL_DIV_A_MASK);
> + control |= (div_b << CDNS_I2C_CONTROL_DIV_B_SHIFT) |
> + (div_a << CDNS_I2C_CONTROL_DIV_A_SHIFT);
> + writel(control, &i2c->regs->control);
> +
> + return err;
> +}
> +
> +static int cdns_i2c_read(struct cdns_i2c *i2c, uchar chip, uchar *buf,
> + uint buf_len)
> +{
> + struct i2c_regs *regs = i2c->regs;
> + u32 control;
> + int err;
> +
> + control = readl(®s->control);
> + control |= CDNS_I2C_CONTROL_RW | CDNS_I2C_CONTROL_CLR_FIFO;
> + if (i2c->bus_hold_flag || (buf_len > CDNS_I2C_FIFO_DEPTH))
> + control |= CDNS_I2C_CONTROL_HOLD;
> + writel(control, ®s->control);
> +
> + do {
> + uint bytes_to_receive;
> + u32 isr_status;
> + u64 start_time;
> +
> + isr_status = readl(®s->interrupt_status);
> + writel(isr_status, ®s->interrupt_status);
> +
> + if (buf_len > CDNS_I2C_TRANSFER_SIZE)
> + bytes_to_receive = CDNS_I2C_TRANSFER_SIZE;
> + else
> + bytes_to_receive = buf_len;
> +
> + buf_len -= bytes_to_receive;
> +
> + writel(bytes_to_receive, ®s->transfer_size);
> + writel(chip & CDNS_I2C_ADDRESS_MASK, ®s->address);
> +
> + start_time = get_time_ns();
> + while (bytes_to_receive) {
> + err = cdns_i2c_hw_error(i2c);
> + if (err)
> + goto i2c_exit;
> +
> + if (is_timeout(start_time,
> + (I2C_TIMEOUT_US * USECOND))) {
> + err = -ETIMEDOUT;
> + goto i2c_exit;
> + }
> +
> + if (readl(®s->status) & CDNS_I2C_STATUS_RXDV) {
> + *buf++ = readl(®s->data);
> + bytes_to_receive--;
> + }
> + }
> +
> + } while (buf_len);
> +
> + err = cdns_i2c_wait_for_completion(i2c);
> +
> +i2c_exit:
> + if (!i2c->bus_hold_flag)
> + cdns_i2c_clear_hold_flag(i2c);
> +
> + return err;
> +}
> +
> +static int cdns_i2c_write(struct cdns_i2c *i2c, uchar chip, uchar *buf,
> + uint buf_len)
> +{
> + struct i2c_regs *regs = i2c->regs;
> + u32 control;
> + u32 isr_status;
> + bool start_transfer;
> + int err;
> +
> + control = readl(®s->control);
> + control &= ~CDNS_I2C_CONTROL_RW;
> + control |= CDNS_I2C_CONTROL_CLR_FIFO;
> + if (i2c->bus_hold_flag || (buf_len > CDNS_I2C_FIFO_DEPTH))
> + control |= CDNS_I2C_CONTROL_HOLD;
> + writel(control, ®s->control);
> +
> + isr_status = readl(®s->interrupt_status);
> + writel(isr_status, ®s->interrupt_status);
> +
> + start_transfer = true;
> + do {
> + uint bytes_to_send;
> +
> + bytes_to_send =
> + CDNS_I2C_FIFO_DEPTH - readl(®s->transfer_size);
> +
> + if (buf_len < bytes_to_send)
> + bytes_to_send = buf_len;
> +
> + buf_len -= bytes_to_send;
> +
> + while (bytes_to_send--)
> + writel(*buf++, ®s->data);
> +
> + if (start_transfer) {
> + writel(chip & CDNS_I2C_ADDRESS_MASK, ®s->address);
> + start_transfer = false;
> + }
> +
> + err = cdns_i2c_wait_for_completion(i2c);
> + if (err)
> + goto i2c_exit;
> +
> + } while (buf_len);
> +
> +i2c_exit:
> + if (!i2c->bus_hold_flag)
> + cdns_i2c_clear_hold_flag(i2c);
> +
> + return err;
> +}
> +
> +static int cdns_i2c_xfer(struct i2c_adapter *adapter, struct i2c_msg *msg,
> + int nmsgs)
> +{
> + struct cdns_i2c *i2c = container_of(adapter, struct cdns_i2c, adapter);
> + int i;
> + int err;
> +
> + if (cdns_i2c_is_busy(i2c))
> + return -EBUSY;
> +
> + for (i = 0; i < nmsgs; i++) {
> + i2c->bus_hold_flag = i < (nmsgs - 1);
> +
> + if (msg->flags & I2C_M_RD) {
> + err = cdns_i2c_read(i2c, msg->addr, msg->buf, msg->len);
> + } else {
> + err = cdns_i2c_write(i2c, msg->addr, msg->buf,
> + msg->len);
> + }
> +
> + if (err)
> + return err;
> +
> + msg++;
> + }
> +
> + return nmsgs;
> +}
> +
> +static int cdns_i2c_probe(struct device_d *dev)
> +{
> + struct device_node *np = dev->device_node;
> + struct resource *iores;
> + struct cdns_i2c *i2c;
> + u32 bitrate;
> + int err;
> +
> + iores = dev_request_mem_resource(dev, 0);
> + if (IS_ERR(iores))
> + return PTR_ERR(iores);
> +
> + i2c = xzalloc(sizeof(*i2c));
> +
> + dev->priv = i2c;
> + i2c->regs = IOMEM(iores->start);
> +
> + i2c->clk = clk_get(dev, NULL);
> + if (IS_ERR(i2c->clk))
> + return PTR_ERR(i2c->clk);
> +
> + err = clk_enable(i2c->clk);
> + if (err)
> + return err;
> +
> + i2c->adapter.master_xfer = cdns_i2c_xfer;
> + i2c->adapter.nr = dev->id;
> + i2c->adapter.dev.parent = dev;
> + i2c->adapter.dev.device_node = np;
> +
> + cdns_i2c_reset_hardware(i2c);
> +
> + bitrate = 100000;
> + of_property_read_u32(np, "clock-frequency", &bitrate);
> +
> + err = cdns_i2c_set_clk(i2c, bitrate);
> + if (err)
> + return err;
> +
> + cdns_i2c_setup_master(i2c);
> +
> + return i2c_add_numbered_adapter(&i2c->adapter);
> +}
> +
> +static const struct of_device_id cdns_i2c_match[] = {
> + { .compatible = "cdns,i2c-r1p14" },
> + {},
> +};
> +
> +static struct driver_d cdns_i2c_driver = {
> + .name = "cdns-i2c",
> + .of_compatible = cdns_i2c_match,
> + .probe = cdns_i2c_probe,
> +};
> +coredevice_platform_driver(cdns_i2c_driver);
> --
> 2.25.1
>
>
> _______________________________________________
> barebox mailing list
> barebox at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/barebox
>
--
Pengutronix e.K. | |
Steuerwalder Str. 21 | http://www.pengutronix.de/ |
31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
More information about the barebox
mailing list