diff mbox series

[05/11] i2c: Add support for ADI SC5XX-family I2C peripheral

Message ID 20240515215837.14028-6-greg.malysa@timesys.com
State Superseded
Delegated to: Tom Rini
Headers show
Series drivers: Driver support for ADI SC5xx SoCs | expand

Commit Message

Greg Malysa May 15, 2024, 9:57 p.m. UTC
From: Nathan Barrett-Morrison <nathan.morrison@timesys.com>

Co-developed-by: Greg Malysa <greg.malysa@timesys.com>
Signed-off-by: Greg Malysa <greg.malysa@timesys.com>
Co-developed-by: Ian Roberts <ian.roberts@timesys.com>
Signed-off-by: Ian Roberts <ian.roberts@timesys.com>
Co-developed-by: Angelo Dureghello <angelo.dureghello@timesys.com>
Signed-off-by: Angelo Dureghello <angelo.dureghello@timesys.com>
Signed-off-by: Vasileios Bimpikas <vasileios.bimpikas@analog.com>
Signed-off-by: Utsav Agarwal <utsav.agarwal@analog.com>
Signed-off-by: Arturs Artamonovs <arturs.artamonovs@analog.com>
Signed-off-by: Nathan Barrett-Morrison <nathan.morrison@timesys.com>
---

 MAINTAINERS           |   1 +
 drivers/i2c/Kconfig   |   7 +
 drivers/i2c/Makefile  |   1 +
 drivers/i2c/adi_i2c.c | 393 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 402 insertions(+)
 create mode 100644 drivers/i2c/adi_i2c.c

Comments

Heiko Schocher May 16, 2024, 6:44 a.m. UTC | #1
Hello Greg,

On 15.05.24 23:57, Greg Malysa wrote:
> From: Nathan Barrett-Morrison <nathan.morrison@timesys.com>
> 
> Co-developed-by: Greg Malysa <greg.malysa@timesys.com>
> Signed-off-by: Greg Malysa <greg.malysa@timesys.com>
> Co-developed-by: Ian Roberts <ian.roberts@timesys.com>
> Signed-off-by: Ian Roberts <ian.roberts@timesys.com>
> Co-developed-by: Angelo Dureghello <angelo.dureghello@timesys.com>
> Signed-off-by: Angelo Dureghello <angelo.dureghello@timesys.com>
> Signed-off-by: Vasileios Bimpikas <vasileios.bimpikas@analog.com>
> Signed-off-by: Utsav Agarwal <utsav.agarwal@analog.com>
> Signed-off-by: Arturs Artamonovs <arturs.artamonovs@analog.com>
> Signed-off-by: Nathan Barrett-Morrison <nathan.morrison@timesys.com>
> ---
> 
>   MAINTAINERS           |   1 +
>   drivers/i2c/Kconfig   |   7 +
>   drivers/i2c/Makefile  |   1 +
>   drivers/i2c/adi_i2c.c | 393 ++++++++++++++++++++++++++++++++++++++++++
>   4 files changed, 402 insertions(+)
>   create mode 100644 drivers/i2c/adi_i2c.c

Reviewed-by: Heiko Schocher <hs@denx.de>

bye,
Heiko
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 3bcdb73e6e..977233451e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -612,6 +612,7 @@  F:	arch/arm/mach-sc5xx/
 F:	drivers/clk/adi/
 F:	drivers/gpio/adp5588_gpio.c
 F:	drivers/gpio/gpio-adi-adsp.c
+F:	drivers/i2c/adi_i2c.c
 F:	drivers/pinctrl/pinctrl-adi-adsp.c
 F:	drivers/serial/serial_adi_uart4.c
 F:	drivers/timer/adi_sc5xx_timer.c
diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig
index 34b02114dc..efcb0589ca 100644
--- a/drivers/i2c/Kconfig
+++ b/drivers/i2c/Kconfig
@@ -154,6 +154,13 @@  config SPL_DM_I2C_GPIO
 	  bindings are supported.
 	  Binding info: doc/device-tree-bindings/i2c/i2c-gpio.txt
 
+config SYS_I2C_ADI
+	bool "ADI I2C driver"
+	depends on DM_I2C && (SC57X || SC58X || SC59X || SC59X_64)
+	help
+	  Add support for the ADI (Analog Devices) I2C driver as used
+	  in SC57X, SC58X, SC59X, SC59X_64.
+
 config SYS_I2C_AT91
 	bool "Atmel I2C driver"
 	depends on DM_I2C && ARCH_AT91
diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile
index 00b90523c6..30c1a43a57 100644
--- a/drivers/i2c/Makefile
+++ b/drivers/i2c/Makefile
@@ -11,6 +11,7 @@  obj-$(CONFIG_$(SPL_)I2C_CROS_EC_TUNNEL) += cros_ec_tunnel.o
 obj-$(CONFIG_$(SPL_)I2C_CROS_EC_LDO) += cros_ec_ldo.o
 
 obj-$(CONFIG_$(SPL_)SYS_I2C_LEGACY) += i2c_core.o
+obj-$(CONFIG_SYS_I2C_ADI) += adi_i2c.o
 obj-$(CONFIG_SYS_I2C_ASPEED) += ast_i2c.o
 obj-$(CONFIG_SYS_I2C_AST2600) += ast2600_i2c.o
 obj-$(CONFIG_SYS_I2C_AT91) += at91_i2c.o
diff --git a/drivers/i2c/adi_i2c.c b/drivers/i2c/adi_i2c.c
new file mode 100644
index 0000000000..cfc5561299
--- /dev/null
+++ b/drivers/i2c/adi_i2c.c
@@ -0,0 +1,393 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * (C) Copyright 2022 - Analog Devices, Inc.
+ *
+ * Written and/or maintained by Timesys Corporation
+ *
+ * Converted to driver model by Nathan Barrett-Morrison
+ *
+ * Contact: Nathan Barrett-Morrison <nathan.morrison@timesys.com>
+ * Contact: Greg Malysa <greg.malysa@timesys.com>
+ */
+
+#include <clk.h>
+#include <dm.h>
+#include <i2c.h>
+#include <mapmem.h>
+#include <asm/io.h>
+
+#define CLKLOW(x) ((x) & 0xFF)     // Periods Clock Is Held Low
+#define CLKHI(y) (((y) & 0xFF) << 0x8) // Periods Clock Is High
+
+#define PRESCALE        0x007F     // SCLKs Per Internal Time Reference (10MHz)
+#define TWI_ENA         0x0080     // TWI Enable
+#define SCCB            0x0200     // SCCB Compatibility Enable
+
+#define SEN             0x0001     // Slave Enable
+#define SADD_LEN        0x0002     // Slave Address Length
+#define STDVAL          0x0004     // Slave Transmit Data Valid
+#define TSC_NAK         0x0008     // NAK Generated At Conclusion Of Transfer
+#define GEN             0x0010     // General Call Adrress Matching Enabled
+
+#define SDIR            0x0001     // Slave Transfer Direction
+#define GCALL           0x0002     // General Call Indicator
+
+#define MEN             0x0001     // Master Mode Enable
+#define MADD_LEN        0x0002     // Master Address Length
+#define MDIR            0x0004     // Master Transmit Direction (RX/TX*)
+#define FAST            0x0008     // Use Fast Mode Timing Specs
+#define STOP            0x0010     // Issue Stop Condition
+#define RSTART          0x0020     // Repeat Start or Stop* At End Of Transfer
+#define DCNT            0x3FC0     // Data Bytes To Transfer
+#define SDAOVR          0x4000     // Serial Data Override
+#define SCLOVR          0x8000     // Serial Clock Override
+
+#define MPROG           0x0001     // Master Transfer In Progress
+#define LOSTARB         0x0002     // Lost Arbitration Indicator (Xfer Aborted)
+#define ANAK            0x0004     // Address Not Acknowledged
+#define DNAK            0x0008     // Data Not Acknowledged
+#define BUFRDERR        0x0010     // Buffer Read Error
+#define BUFWRERR        0x0020     // Buffer Write Error
+#define SDASEN          0x0040     // Serial Data Sense
+#define SCLSEN          0x0080     // Serial Clock Sense
+#define BUSBUSY         0x0100     // Bus Busy Indicator
+
+#define SINIT           0x0001     // Slave Transfer Initiated
+#define SCOMP           0x0002     // Slave Transfer Complete
+#define SERR            0x0004     // Slave Transfer Error
+#define SOVF            0x0008     // Slave Overflow
+#define MCOMP           0x0010     // Master Transfer Complete
+#define MERR            0x0020     // Master Transfer Error
+#define XMTSERV         0x0040     // Transmit FIFO Service
+#define RCVSERV         0x0080     // Receive FIFO Service
+
+#define XMTFLUSH        0x0001     // Transmit Buffer Flush
+#define RCVFLUSH        0x0002     // Receive Buffer Flush
+#define XMTINTLEN       0x0004     // Transmit Buffer Interrupt Length
+#define RCVINTLEN       0x0008     // Receive Buffer Interrupt Length
+
+#define XMTSTAT         0x0003     // Transmit FIFO Status
+#define XMT_EMPTY       0x0000     // Transmit FIFO Empty
+#define XMT_HALF        0x0001     // Transmit FIFO Has 1 Byte To Write
+#define XMT_FULL        0x0003     // Transmit FIFO Full (2 Bytes To Write)
+
+#define RCVSTAT         0x000C     // Receive FIFO Status
+#define RCV_EMPTY       0x0000     // Receive FIFO Empty
+#define RCV_HALF        0x0004     // Receive FIFO Has 1 Byte To Read
+#define RCV_FULL        0x000C     // Receive FIFO Full (2 Bytes To Read)
+
+/* Every register is 32bit aligned, but only 16bits in size */
+#define ureg(name) u16 name; u16 __pad_##name
+struct twi_regs {
+	ureg(clkdiv);
+	ureg(control);
+	ureg(slave_ctl);
+	ureg(slave_stat);
+	ureg(slave_addr);
+	ureg(master_ctl);
+	ureg(master_stat);
+	ureg(master_addr);
+	ureg(int_stat);
+	ureg(int_mask);
+	ureg(fifo_ctl);
+	ureg(fifo_stat);
+	u8 __pad[0x50];
+
+	ureg(xmt_data8);
+	ureg(xmt_data16);
+	ureg(rcv_data8);
+	ureg(rcv_data16);
+};
+#undef ureg
+
+/*
+ * The way speed is changed into duty often results in integer truncation
+ * with 50% duty, so we'll force rounding up to the next duty by adding 1
+ * to the max. In practice this will get us a speed of something like
+ * 385 KHz. The other limit is easy to handle as it is only 8 bits.
+ */
+#define I2C_SPEED_MAX             400000
+#define I2C_SPEED_TO_DUTY(speed)  (5000000 / (speed))
+#define I2C_DUTY_MAX              (I2C_SPEED_TO_DUTY(I2C_SPEED_MAX) + 1)
+#define I2C_DUTY_MIN              0xff	/* 8 bit limited */
+
+#define I2C_M_COMBO		0x4
+#define I2C_M_STOP		0x2
+#define I2C_M_READ		0x1
+
+/*
+ * All transfers are described by this data structure
+ */
+struct adi_i2c_msg {
+	u8 flags;
+	u32 len;		/* msg length */
+	u8 *buf;		/* pointer to msg data */
+	u32 olen;		/* addr length */
+	u8 *obuf;		/* addr buffer */
+};
+
+struct adi_i2c_dev {
+	struct twi_regs  __iomem *base;
+	u32 i2c_clk;
+	uint speed;
+};
+
+/* Allow msec timeout per ~byte transfer */
+#define I2C_TIMEOUT 10
+
+/**
+ * wait_for_completion - manage the actual i2c transfer
+ *	@msg: the i2c msg
+ */
+static int wait_for_completion(struct twi_regs *twi, struct adi_i2c_msg *msg)
+{
+	u16 int_stat, ctl;
+	ulong timebase = get_timer(0);
+
+	do {
+		int_stat = readw(&twi->int_stat);
+
+		if (int_stat & XMTSERV) {
+			writew(XMTSERV, &twi->int_stat);
+			if (msg->olen) {
+				writew(*(msg->obuf++), &twi->xmt_data8);
+				--msg->olen;
+			} else if (!(msg->flags & I2C_M_COMBO) && msg->len) {
+				writew(*(msg->buf++), &twi->xmt_data8);
+				--msg->len;
+			} else {
+				ctl = readw(&twi->master_ctl);
+				if (msg->flags & I2C_M_COMBO)
+					writew(ctl | RSTART | MDIR,
+					       &twi->master_ctl);
+				else
+					writew(ctl | STOP, &twi->master_ctl);
+			}
+		}
+		if (int_stat & RCVSERV) {
+			writew(RCVSERV, &twi->int_stat);
+			if (msg->len) {
+				*(msg->buf++) = readw(&twi->rcv_data8);
+				--msg->len;
+			} else if (msg->flags & I2C_M_STOP) {
+				ctl = readw(&twi->master_ctl);
+				writew(ctl | STOP, &twi->master_ctl);
+			}
+		}
+		if (int_stat & MERR) {
+			pr_err("%s: master transmit terror: %d\n", __func__,
+			       readw(&twi->master_stat));
+			writew(MERR, &twi->int_stat);
+			return -EIO;
+		}
+		if (int_stat & MCOMP) {
+			writew(MCOMP, &twi->int_stat);
+			if (msg->flags & I2C_M_COMBO && msg->len) {
+				ctl = readw(&twi->master_ctl);
+				ctl = (ctl & ~RSTART) |
+					(min((unsigned int)msg->len,
+					     0xffU) << 6) | MEN | MDIR;
+				writew(ctl, &twi->master_ctl);
+			} else {
+				break;
+			}
+		}
+
+		/* If we were able to do something, reset timeout */
+		if (int_stat)
+			timebase = get_timer(0);
+
+	} while (get_timer(timebase) < I2C_TIMEOUT);
+
+	return 0;
+}
+
+static int i2c_transfer(struct twi_regs *twi, u8 chip, u8 *offset,
+			int olen, u8 *buffer, int len, u8 flags)
+{
+	int ret;
+	u16 ctl;
+
+	struct adi_i2c_msg msg = {
+		.flags = flags | (len >= 0xff ? I2C_M_STOP : 0),
+		.buf   = buffer,
+		.len   = len,
+		.obuf  = offset,
+		.olen  = olen,
+	};
+
+	/* wait for things to settle */
+	while (readw(&twi->master_stat) & BUSBUSY)
+		if (!IS_ENABLED(CONFIG_SPL_BUILD) && ctrlc())
+			return -EINTR;
+
+	/* Set Transmit device address */
+	writew(chip, &twi->master_addr);
+
+	/* Clear the FIFO before starting things */
+	writew(XMTFLUSH | RCVFLUSH, &twi->fifo_ctl);
+	writew(0, &twi->fifo_ctl);
+
+	/* Prime the pump */
+	if (msg.olen) {
+		len = (msg.flags & I2C_M_COMBO) ? msg.olen : msg.olen + len;
+		writew(*(msg.obuf++), &twi->xmt_data8);
+		--msg.olen;
+	} else if (!(msg.flags & I2C_M_READ) && msg.len) {
+		writew(*(msg.buf++), &twi->xmt_data8);
+		--msg.len;
+	}
+
+	/* clear int stat */
+	writew(-1, &twi->master_stat);
+	writew(-1, &twi->int_stat);
+	writew(0, &twi->int_mask);
+
+	/* Master enable */
+	ctl = readw(&twi->master_ctl);
+	ctl = (ctl & FAST) | (min(len, 0xff) << 6) | MEN |
+		((msg.flags & I2C_M_READ) ? MDIR : 0);
+	writew(ctl, &twi->master_ctl);
+
+	/* Process the rest */
+	ret = wait_for_completion(twi, &msg);
+
+	ctl = readw(&twi->master_ctl) & ~MEN;
+	writew(ctl, &twi->master_ctl);
+	ctl = readw(&twi->control) & ~TWI_ENA;
+	writew(ctl, &twi->control);
+	ctl = readw(&twi->control) | TWI_ENA;
+	writew(ctl, &twi->control);
+	return ret;
+}
+
+static int adi_i2c_read(struct twi_regs *twi, u8 chip,
+			u8 *offset, int olen, u8 *buffer, int len)
+{
+	return i2c_transfer(twi, chip, offset, olen, buffer,
+			len, olen ? I2C_M_COMBO : I2C_M_READ);
+}
+
+static int adi_i2c_write(struct twi_regs *twi, u8 chip,
+			 u8 *offset, int olen, u8 *buffer, int len)
+{
+	return i2c_transfer(twi, chip, offset, olen, buffer, len, 0);
+}
+
+static int adi_i2c_set_bus_speed(struct udevice *bus, uint speed)
+{
+	struct adi_i2c_dev *dev = dev_get_priv(bus);
+	struct twi_regs *twi = dev->base;
+	u16 clkdiv = I2C_SPEED_TO_DUTY(speed);
+
+	/* Set TWI interface clock */
+	if (clkdiv < I2C_DUTY_MAX || clkdiv > I2C_DUTY_MIN)
+		return -1;
+	clkdiv = (clkdiv << 8) | (clkdiv & 0xff);
+	writew(clkdiv, &twi->clkdiv);
+
+	/* Don't turn it on */
+	writew(speed > 100000 ? FAST : 0, &twi->master_ctl);
+
+	return 0;
+}
+
+static int adi_i2c_of_to_plat(struct udevice *bus)
+{
+	struct adi_i2c_dev *dev = dev_get_priv(bus);
+	struct clk clock;
+	u32 ret;
+
+	dev->base = map_sysmem(dev_read_addr(bus), sizeof(struct twi_regs));
+
+	if (!dev->base)
+		return -ENOMEM;
+
+	dev->speed = dev_read_u32_default(bus, "clock-frequency",
+					  I2C_SPEED_FAST_RATE);
+
+	ret = clk_get_by_name(bus, "i2c", &clock);
+	if (ret < 0)
+		printf("%s: Can't get I2C clk: %d\n", __func__, ret);
+	else
+		dev->i2c_clk = clk_get_rate(&clock);
+
+	return 0;
+}
+
+static int adi_i2c_probe_chip(struct udevice *bus, u32 chip_addr,
+			      u32 chip_flags)
+{
+	struct adi_i2c_dev *dev = dev_get_priv(bus);
+	u8 byte;
+
+	return adi_i2c_read(dev->base, chip_addr, NULL, 0, &byte, 1);
+}
+
+static int adi_i2c_xfer(struct udevice *bus, struct i2c_msg *msg, int nmsgs)
+{
+	struct adi_i2c_dev *dev = dev_get_priv(bus);
+	struct i2c_msg *dmsg, *omsg, dummy;
+
+	memset(&dummy, 0, sizeof(struct i2c_msg));
+
+	/*
+	 * We expect either two messages (one with an offset and one with the
+	 * actual data) or one message (just data)
+	 */
+	if (nmsgs > 2 || nmsgs == 0) {
+		debug("%s: Only one or two messages are supported.", __func__);
+		return -EINVAL;
+	}
+
+	omsg = nmsgs == 1 ? &dummy : msg;
+	dmsg = nmsgs == 1 ? msg : msg + 1;
+
+	if (dmsg->flags & I2C_M_RD)
+		return adi_i2c_read(dev->base, dmsg->addr, omsg->buf, omsg->len,
+				  dmsg->buf, dmsg->len);
+	else
+		return adi_i2c_write(dev->base, dmsg->addr, omsg->buf, omsg->len,
+				   dmsg->buf, dmsg->len);
+}
+
+int adi_i2c_probe(struct udevice *bus)
+{
+	struct adi_i2c_dev *dev = dev_get_priv(bus);
+	struct twi_regs *twi = dev->base;
+
+	u16 prescale = ((dev->i2c_clk / 1000 / 1000 + 5) / 10) & 0x7F;
+
+	/* Set TWI internal clock as 10MHz */
+	writew(prescale, &twi->control);
+
+	/* Set TWI interface clock as specified */
+	adi_i2c_set_bus_speed(bus, dev->speed);
+
+	/* Enable it */
+	writew(TWI_ENA | prescale, &twi->control);
+
+	return 0;
+}
+
+static const struct dm_i2c_ops adi_i2c_ops = {
+	.xfer           = adi_i2c_xfer,
+	.probe_chip     = adi_i2c_probe_chip,
+	.set_bus_speed  = adi_i2c_set_bus_speed,
+};
+
+static const struct udevice_id adi_i2c_ids[] = {
+	{ .compatible = "adi-i2c", },
+	{ /* sentinel */ }
+};
+
+U_BOOT_DRIVER(i2c_adi) = {
+	.name = "i2c_adi",
+	.id = UCLASS_I2C,
+	.of_match = adi_i2c_ids,
+	.probe = adi_i2c_probe,
+	.of_to_plat = adi_i2c_of_to_plat,
+	.priv_auto = sizeof(struct adi_i2c_dev),
+	.ops = &adi_i2c_ops,
+	.flags = DM_FLAG_PRE_RELOC,
+};