[PATCH v5 09/13] i2c: synquacer: SNI Synquacer I2C controller

Masami Hiramatsu masami.hiramatsu at linaro.org
Fri Jun 4 11:44:48 CEST 2021


From: Jassi Brar <jaswinder.singh at linaro.org>

Add driver for class of I2C controllers found on
Socionext Synquacer platform.

Signed-off-by: Jassi Brar <jaswinder.singh at linaro.org>
---
 drivers/i2c/Kconfig         |    7 +
 drivers/i2c/Makefile        |    1 
 drivers/i2c/synquacer_i2c.c |  338 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 346 insertions(+)
 create mode 100644 drivers/i2c/synquacer_i2c.c

diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig
index 57a4efb88e..ce0d796d09 100644
--- a/drivers/i2c/Kconfig
+++ b/drivers/i2c/Kconfig
@@ -455,6 +455,13 @@ config SYS_I2C_STM32F7
 	   _ Optional clock stretching
 	   _ Software reset
 
+config SYS_I2C_SYNQUACER
+	bool "Socionext SynQuacer I2C controller"
+	depends on ARCH_SYNQUACER && DM_I2C
+	help
+	  Support for Socionext Synquacer I2C controller. This I2C controller
+	  will be used for RTC and LS-connector on DeveloperBox.
+
 config SYS_I2C_TEGRA
 	bool "NVIDIA Tegra internal I2C controller"
 	depends on ARCH_TEGRA
diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile
index 8c9f1fcd8b..06a1150f03 100644
--- a/drivers/i2c/Makefile
+++ b/drivers/i2c/Makefile
@@ -43,6 +43,7 @@ obj-$(CONFIG_SYS_I2C_SANDBOX) += sandbox_i2c.o i2c-emul-uclass.o
 obj-$(CONFIG_SYS_I2C_SH) += sh_i2c.o
 obj-$(CONFIG_SYS_I2C_SOFT) += soft_i2c.o
 obj-$(CONFIG_SYS_I2C_STM32F7) += stm32f7_i2c.o
+obj-$(CONFIG_SYS_I2C_SYNQUACER) += synquacer_i2c.o
 obj-$(CONFIG_SYS_I2C_TEGRA) += tegra_i2c.o
 obj-$(CONFIG_SYS_I2C_UNIPHIER) += i2c-uniphier.o
 obj-$(CONFIG_SYS_I2C_UNIPHIER_F) += i2c-uniphier-f.o
diff --git a/drivers/i2c/synquacer_i2c.c b/drivers/i2c/synquacer_i2c.c
new file mode 100644
index 0000000000..6672d9435e
--- /dev/null
+++ b/drivers/i2c/synquacer_i2c.c
@@ -0,0 +1,338 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ */
+
+#include <dm/device_compat.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/io.h>
+#include <linux/sizes.h>
+#include <linux/types.h>
+#include <dm.h>
+#include <fdtdec.h>
+#include <i2c.h>
+#include <clk.h>
+
+#define REG_BSR		0x0
+#define REG_BCR		0x4
+#define REG_CCR		0x8
+#define REG_ADR		0xc
+#define REG_DAR		0x10
+#define REG_CSR		0x14
+#define REG_FSR		0x18
+#define REG_BC2R	0x1c
+
+/* I2C register bit definitions */
+#define BSR_FBT		BIT(0)	// First Byte Transfer
+#define BSR_GCA		BIT(1)	// General Call Address
+#define BSR_AAS		BIT(2)	// Address as Slave
+#define BSR_TRX		BIT(3)	// Transfer/Receive
+#define BSR_LRB		BIT(4)	// Last Received Bit
+#define BSR_AL		BIT(5)	// Arbitration Lost
+#define BSR_RSC		BIT(6)	// Repeated Start Cond.
+#define BSR_BB		BIT(7)	// Bus Busy
+
+#define BCR_INT		BIT(0)	// Interrupt
+#define BCR_INTE		BIT(1)	// Interrupt Enable
+#define BCR_GCAA		BIT(2)	// Gen. Call Access Ack.
+#define BCR_ACK		BIT(3)	// Acknowledge
+#define BCR_MSS		BIT(4)	// Master Slave Select
+#define BCR_SCC		BIT(5)	// Start Condition Cont.
+#define BCR_BEIE		BIT(6)	// Bus Error Int Enable
+#define BCR_BER		BIT(7)	// Bus Error
+
+#define CCR_CS_MASK	(0x1f)	// CCR Clock Period Sel.
+#define CCR_EN		BIT(5)	// Enable
+#define CCR_FM		BIT(6)	// Speed Mode Select
+
+#define CSR_CS_MASK	(0x3f)	// CSR Clock Period Sel.
+
+#define BC2R_SCLL		BIT(0)	// SCL Low Drive
+#define BC2R_SDAL		BIT(1)	// SDA Low Drive
+#define BC2R_SCLS		BIT(4)	// SCL Status
+#define BC2R_SDAS		BIT(5)	// SDA Status
+
+/* PCLK frequency */
+#define BUS_CLK_FR(rate)	(((rate) / 20000000) + 1)
+
+#define I2C_CLK_DEF		62500000
+
+/* STANDARD MODE frequency */
+#define CLK_MASTER_STD(rate)			\
+	DIV_ROUND_UP(DIV_ROUND_UP((rate), I2C_SPEED_STANDARD_RATE) - 2, 2)
+/* FAST MODE frequency */
+#define CLK_MASTER_FAST(rate)			\
+	DIV_ROUND_UP((DIV_ROUND_UP((rate), I2C_SPEED_FAST_RATE) - 2) * 2, 3)
+
+/* (clkrate <= 18000000) */
+/* calculate the value of CS bits in CCR register on standard mode */
+#define CCR_CS_STD_MAX_18M(rate)			\
+	   ((CLK_MASTER_STD(rate) - 65)		\
+					& CCR_CS_MASK)
+
+/* calculate the value of CS bits in CSR register on standard mode */
+#define CSR_CS_STD_MAX_18M(rate)		0x00
+
+/* calculate the value of CS bits in CCR register on fast mode */
+#define CCR_CS_FAST_MAX_18M(rate)			\
+	   ((CLK_MASTER_FAST(rate) - 1)		\
+					& CCR_CS_MASK)
+
+/* calculate the value of CS bits in CSR register on fast mode */
+#define CSR_CS_FAST_MAX_18M(rate)		0x00
+
+/* (clkrate > 18000000) */
+/* calculate the value of CS bits in CCR register on standard mode */
+#define CCR_CS_STD_MIN_18M(rate)			\
+	   ((CLK_MASTER_STD(rate) - 1)		\
+					& CCR_CS_MASK)
+
+/* calculate the value of CS bits in CSR register on standard mode */
+#define CSR_CS_STD_MIN_18M(rate)			\
+	   (((CLK_MASTER_STD(rate) - 1) >> 5)	\
+					& CSR_CS_MASK)
+
+/* calculate the value of CS bits in CCR register on fast mode */
+#define CCR_CS_FAST_MIN_18M(rate)			\
+	   ((CLK_MASTER_FAST(rate) - 1)		\
+					& CCR_CS_MASK)
+
+/* calculate the value of CS bits in CSR register on fast mode */
+#define CSR_CS_FAST_MIN_18M(rate)			\
+	   (((CLK_MASTER_FAST(rate) - 1) >> 5)	\
+					& CSR_CS_MASK)
+
+/* min I2C clock frequency 14M */
+#define MIN_CLK_RATE	(14 * 1000000)
+/* max I2C clock frequency 200M */
+#define MAX_CLK_RATE	(200 * 1000000)
+/* I2C clock frequency 18M */
+#define CLK_RATE_18M	(18 * 1000000)
+
+#define SPEED_FM		400	// Fast Mode
+#define SPEED_SM		100	// Standard Mode
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct synquacer_i2c {
+	void __iomem *base;
+	unsigned long pclkrate;
+	unsigned long speed_khz;
+};
+
+static int wait_irq(struct udevice *dev)
+{
+	struct synquacer_i2c *i2c = dev_get_priv(dev);
+	int timeout = 500000;
+
+	do {
+		if (readb(i2c->base + REG_BCR) & BCR_INT)
+			return 0;
+	} while (timeout--);
+
+	pr_err("%s: timeout\n", __func__);
+	return -1;
+}
+
+static int synquacer_i2c_xfer_start(struct synquacer_i2c *i2c,
+				    int addr, int read)
+{
+	u8 bsr, bcr;
+
+	writeb((addr << 1) | (read ? 1 : 0), i2c->base + REG_DAR);
+
+	bsr = readb(i2c->base + REG_BSR);
+	bcr = readb(i2c->base + REG_BCR);
+
+	if ((bsr & BSR_BB) && !(bcr & BCR_MSS))
+		return -EBUSY;
+
+	if (bsr & BSR_BB) {
+		writeb(bcr | BCR_SCC, i2c->base + REG_BCR);
+	} else {
+		if (bcr & BCR_MSS)
+			return -EAGAIN;
+		/* Start Condition + Enable Interrupts */
+		writeb(bcr | BCR_MSS | BCR_INTE | BCR_BEIE, i2c->base + REG_BCR);
+	}
+
+	udelay(100);
+	return 0;
+}
+
+static int synquacer_i2c_xfer(struct udevice *bus,
+			      struct i2c_msg *msg, int nmsgs)
+{
+	struct synquacer_i2c *i2c = dev_get_priv(bus);
+	u8 bsr, bcr;
+	int idx;
+
+	for (; nmsgs > 0; nmsgs--, msg++) {
+		synquacer_i2c_xfer_start(i2c, msg->addr, msg->flags & I2C_M_RD);
+		if (wait_irq(bus))
+			return -EREMOTEIO;
+
+		bsr = readb(i2c->base + REG_BSR);
+		if (bsr & BSR_LRB) {
+			debug("%s: No ack received\n", __func__);
+			return -EREMOTEIO;
+		}
+
+		idx = 0;
+		do {
+			bsr = readb(i2c->base + REG_BSR);
+			bcr = readb(i2c->base + REG_BCR);
+			if (bcr & BCR_BER) {
+				debug("%s: Bus error detected\n", __func__);
+				return -EREMOTEIO;
+			}
+			if ((bsr & BSR_AL) || !(bcr & BCR_MSS)) {
+				debug("%s: Arbitration lost\n", __func__);
+				return -EREMOTEIO;
+			}
+
+			if (msg->flags & I2C_M_RD) {
+				bcr = BCR_MSS | BCR_INTE | BCR_BEIE;
+				if (idx < msg->len - 1)
+					bcr |= BCR_ACK;
+				writeb(bcr, i2c->base + REG_BCR);
+				if (wait_irq(bus))
+					return -EREMOTEIO;
+				bsr = readb(i2c->base + REG_BSR);
+				if (!(bsr & BSR_FBT))
+					msg->buf[idx++] = readb(i2c->base + REG_DAR);
+			} else {
+				writeb(msg->buf[idx++], i2c->base + REG_DAR);
+				bcr = BCR_MSS | BCR_INTE | BCR_BEIE;
+				writeb(bcr, i2c->base + REG_BCR);
+				if (wait_irq(bus))
+					return -EREMOTEIO;
+				bsr = readb(i2c->base + REG_BSR);
+				if (bsr & BSR_LRB) {
+					debug("%s: no ack\n", __func__);
+					return -EREMOTEIO;
+				}
+			}
+		} while (idx < msg->len);
+	}
+
+	/* Force bus state to idle, terminating any ongoing transfer */
+	writeb(0, i2c->base + REG_BCR);
+	udelay(100);
+
+	return 0;
+}
+
+static void synquacer_i2c_hw_reset(struct synquacer_i2c *i2c)
+{
+	/* Disable clock */
+	writeb(0, i2c->base + REG_CCR);
+	writeb(0, i2c->base + REG_CSR);
+
+	/* Set own Address */
+	writeb(0, i2c->base + REG_ADR);
+
+	/* Set PCLK frequency */
+	writeb(BUS_CLK_FR(i2c->pclkrate), i2c->base + REG_FSR);
+
+	/* clear IRQ (INT=0, BER=0), Interrupt Disable */
+	writeb(0, i2c->base + REG_BCR);
+	writeb(0, i2c->base + REG_BC2R);
+}
+
+static int synquacer_i2c_get_bus_speed(struct udevice *bus)
+{
+	struct synquacer_i2c *i2c = dev_get_priv(bus);
+
+	return i2c->speed_khz * 1000;
+}
+
+static int synquacer_i2c_set_bus_speed(struct udevice *bus, unsigned int speed)
+{
+	struct synquacer_i2c *i2c = dev_get_priv(bus);
+	u32 rt = i2c->pclkrate;
+	u8 ccr_cs, csr_cs;
+
+	/* Set PCLK frequency */
+	writeb(BUS_CLK_FR(i2c->pclkrate), i2c->base + REG_FSR);
+
+	if (speed >= SPEED_FM * 1000) {
+		i2c->speed_khz = SPEED_FM;
+		if (i2c->pclkrate <= CLK_RATE_18M) {
+			ccr_cs = CCR_CS_FAST_MAX_18M(rt);
+			csr_cs = CSR_CS_FAST_MAX_18M(rt);
+		} else {
+			ccr_cs = CCR_CS_FAST_MIN_18M(rt);
+			csr_cs = CSR_CS_FAST_MIN_18M(rt);
+		}
+
+		/* Set Clock and enable, Set fast mode */
+		writeb(ccr_cs | CCR_FM | CCR_EN, i2c->base + REG_CCR);
+		writeb(csr_cs, i2c->base + REG_CSR);
+	} else {
+		i2c->speed_khz = SPEED_SM;
+		if (i2c->pclkrate <= CLK_RATE_18M) {
+			ccr_cs = CCR_CS_STD_MAX_18M(rt);
+			csr_cs = CSR_CS_STD_MAX_18M(rt);
+		} else {
+			ccr_cs = CCR_CS_STD_MIN_18M(rt);
+			csr_cs = CSR_CS_STD_MIN_18M(rt);
+		}
+
+		/* Set Clock and enable, Set standard mode */
+		writeb(ccr_cs | CCR_EN, i2c->base + REG_CCR);
+		writeb(csr_cs, i2c->base + REG_CSR);
+	}
+
+	return 0;
+}
+
+static int synquacer_i2c_of_to_plat(struct udevice *bus)
+{
+	struct synquacer_i2c *priv = dev_get_priv(bus);
+	struct clk ck;
+	int ret;
+
+	ret = clk_get_by_index(bus, 0, &ck);
+	if (ret < 0) {
+		priv->pclkrate = I2C_CLK_DEF;
+	} else {
+		clk_enable(&ck);
+		priv->pclkrate = clk_get_rate(&ck);
+	}
+
+	return 0;
+}
+
+static int synquacer_i2c_probe(struct udevice *bus)
+{
+	struct synquacer_i2c *i2c = dev_get_priv(bus);
+
+	i2c->base = dev_read_addr_ptr(bus);
+	synquacer_i2c_hw_reset(i2c);
+	synquacer_i2c_set_bus_speed(bus, 400000); /* set default speed */
+	return 0;
+}
+
+static const struct dm_i2c_ops synquacer_i2c_ops = {
+	.xfer = synquacer_i2c_xfer,
+	.set_bus_speed = synquacer_i2c_set_bus_speed,
+	.get_bus_speed = synquacer_i2c_get_bus_speed,
+};
+
+static const struct udevice_id synquacer_i2c_ids[] = {
+	{
+		.compatible = "socionext,synquacer-i2c",
+	},
+	{ }
+};
+
+U_BOOT_DRIVER(sni_synquacer_i2c) = {
+	.name	= "sni_synquacer_i2c",
+	.id	= UCLASS_I2C,
+	.of_match = synquacer_i2c_ids,
+	.of_to_plat = synquacer_i2c_of_to_plat,
+	.probe	= synquacer_i2c_probe,
+	.priv_auto	= sizeof(struct synquacer_i2c),
+	.ops	= &synquacer_i2c_ops,
+};



More information about the U-Boot mailing list