All of lore.kernel.org
 help / color / mirror / Atom feed
From: Marek Vasut <marek.vasut@gmail.com>
To: u-boot@lists.denx.de
Subject: [U-Boot] [PATCH 2/4] i2c: rcar_i2c: Add DM and DT capable I2C driver
Date: Tue,  1 May 2018 09:03:33 +0200	[thread overview]
Message-ID: <20180501070335.1815-2-marek.vasut+renesas@gmail.com> (raw)
In-Reply-To: <20180501070335.1815-1-marek.vasut+renesas@gmail.com>

Add derivative of the rcar_i2c driver which is capable of
probing itself from DM and uses DT.

Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
Cc: Heiko Schocher <hs@denx.de>
Cc: Nobuhiro Iwamatsu <iwamatsu@nigauri.org>
---
 drivers/i2c/Kconfig    |   6 +
 drivers/i2c/Makefile   |   1 +
 drivers/i2c/rcar_i2c.c | 355 +++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 362 insertions(+)
 create mode 100644 drivers/i2c/rcar_i2c.c

diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig
index 7fb201d8e6..5eceab9ea8 100644
--- a/drivers/i2c/Kconfig
+++ b/drivers/i2c/Kconfig
@@ -339,6 +339,12 @@ config SYS_OMAP24_I2C_SPEED
 	  OMAP24xx Slave speed channel 0
 endif
 
+config SYS_I2C_RCAR_I2C
+	bool "Renesas RCar I2C driver"
+	depends on (RCAR_GEN3 || RCAR_GEN2) && DM_I2C
+	help
+	  Support for Renesas RCar I2C controller.
+
 config SYS_I2C_RCAR_IIC
 	bool "Renesas RCar Gen3 IIC driver"
 	depends on (RCAR_GEN3 || RCAR_GEN2) && DM_I2C
diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile
index 4a6e06fbc5..4e9f233cda 100644
--- a/drivers/i2c/Makefile
+++ b/drivers/i2c/Makefile
@@ -30,6 +30,7 @@ obj-$(CONFIG_SYS_I2C_MVTWSI) += mvtwsi.o
 obj-$(CONFIG_SYS_I2C_MXC) += mxc_i2c.o
 obj-$(CONFIG_SYS_I2C_MXS) += mxs_i2c.o
 obj-$(CONFIG_SYS_I2C_OMAP24XX) += omap24xx_i2c.o
+obj-$(CONFIG_SYS_I2C_RCAR_I2C) += rcar_i2c.o
 obj-$(CONFIG_SYS_I2C_RCAR_IIC) += rcar_iic.o
 obj-$(CONFIG_SYS_I2C_ROCKCHIP) += rk_i2c.o
 obj-$(CONFIG_SYS_I2C_S3C24X0) += s3c24x0_i2c.o exynos_hs_i2c.o
diff --git a/drivers/i2c/rcar_i2c.c b/drivers/i2c/rcar_i2c.c
new file mode 100644
index 0000000000..6010ccde61
--- /dev/null
+++ b/drivers/i2c/rcar_i2c.c
@@ -0,0 +1,355 @@
+/*
+ * drivers/i2c/rcar_i2c.c
+ *
+ * Copyright (C) 2018 Marek Vasut <marek.vasut@gmail.com>
+ *
+ * Clock configuration based on Linux i2c-rcar.c:
+ * Copyright (C) 2014-15 Wolfram Sang <wsa@sang-engineering.com>
+ * Copyright (C) 2011-2015 Renesas Electronics Corporation
+ * Copyright (C) 2012-14 Renesas Solutions Corp.
+ *   Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+#include <clk.h>
+#include <dm.h>
+#include <i2c.h>
+#include <asm/io.h>
+#include <wait_bit.h>
+
+#define RCAR_I2C_ICSCR			0x00
+#define RCAR_I2C_ICMCR			0x04
+#define RCAR_I2C_ICMCR_MDBS		BIT(7)
+#define RCAR_I2C_ICMCR_FSCL		BIT(6)
+#define RCAR_I2C_ICMCR_FSDA		BIT(5)
+#define RCAR_I2C_ICMCR_OBPC		BIT(4)
+#define RCAR_I2C_ICMCR_MIE		BIT(3)
+#define RCAR_I2C_ICMCR_TSBE		BIT(2)
+#define RCAR_I2C_ICMCR_FSB		BIT(1)
+#define RCAR_I2C_ICMCR_ESG		BIT(0)
+#define RCAR_I2C_ICSSR			0x08
+#define RCAR_I2C_ICMSR			0x0c
+#define RCAR_I2C_ICMSR_MASK		0x7f
+#define RCAR_I2C_ICMSR_MNR		BIT(6)
+#define RCAR_I2C_ICMSR_MAL		BIT(5)
+#define RCAR_I2C_ICMSR_MST		BIT(4)
+#define RCAR_I2C_ICMSR_MDE		BIT(3)
+#define RCAR_I2C_ICMSR_MDT		BIT(2)
+#define RCAR_I2C_ICMSR_MDR		BIT(1)
+#define RCAR_I2C_ICMSR_MAT		BIT(0)
+#define RCAR_I2C_ICSIER			0x10
+#define RCAR_I2C_ICMIER			0x14
+#define RCAR_I2C_ICCCR			0x18
+#define RCAR_I2C_ICCCR_SCGD_OFF		3
+#define RCAR_I2C_ICSAR			0x1c
+#define RCAR_I2C_ICMAR			0x20
+#define RCAR_I2C_ICRXD_ICTXD		0x24
+
+struct rcar_i2c_priv {
+	void __iomem		*base;
+	struct clk		clk;
+	u32			intdelay;
+	u32			icccr;
+};
+
+static int rcar_i2c_finish(struct udevice *dev)
+{
+	struct rcar_i2c_priv *priv = dev_get_priv(dev);
+	int ret;
+
+	ret = wait_for_bit_le32(priv->base + RCAR_I2C_ICMSR, RCAR_I2C_ICMSR_MST,
+				true, 10, true);
+
+	writel(0, priv->base + RCAR_I2C_ICSSR);
+	writel(0, priv->base + RCAR_I2C_ICMSR);
+	writel(0, priv->base + RCAR_I2C_ICMCR);
+
+	return ret;
+}
+
+static void rcar_i2c_recover(struct udevice *dev)
+{
+	struct rcar_i2c_priv *priv = dev_get_priv(dev);
+	u32 mcr = RCAR_I2C_ICMCR_MDBS | RCAR_I2C_ICMCR_OBPC;
+	u32 mcra = mcr | RCAR_I2C_ICMCR_FSDA;
+	int i;
+
+	/* Send 9 SCL pulses */
+	for (i = 0; i < 9; i++) {
+		writel(mcra | RCAR_I2C_ICMCR_FSCL, priv->base + RCAR_I2C_ICMCR);
+		udelay(5);
+		writel(mcra, priv->base + RCAR_I2C_ICMCR);
+		udelay(5);
+	}
+
+	/* Send stop condition */
+	udelay(5);
+	writel(mcra, priv->base + RCAR_I2C_ICMCR);
+	udelay(5);
+	writel(mcr, priv->base + RCAR_I2C_ICMCR);
+	udelay(5);
+	writel(mcr | RCAR_I2C_ICMCR_FSCL, priv->base + RCAR_I2C_ICMCR);
+	udelay(5);
+	writel(mcra | RCAR_I2C_ICMCR_FSCL, priv->base + RCAR_I2C_ICMCR);
+	udelay(5);
+}
+
+static int rcar_i2c_set_addr(struct udevice *dev, u8 chip, u8 read)
+{
+	struct rcar_i2c_priv *priv = dev_get_priv(dev);
+	u32 mask = RCAR_I2C_ICMSR_MAT |
+		   (read ? RCAR_I2C_ICMSR_MDR : RCAR_I2C_ICMSR_MDE);
+	u32 val;
+	int ret;
+
+	writel(0, priv->base + RCAR_I2C_ICMIER);
+	writel(RCAR_I2C_ICMCR_MDBS, priv->base + RCAR_I2C_ICMCR);
+	writel(0, priv->base + RCAR_I2C_ICMSR);
+	writel(priv->icccr, priv->base + RCAR_I2C_ICCCR);
+
+	ret = wait_for_bit_le32(priv->base + RCAR_I2C_ICMCR,
+				RCAR_I2C_ICMCR_FSDA, false, 2, true);
+	if (ret) {
+		rcar_i2c_recover(dev);
+		val = readl(priv->base + RCAR_I2C_ICMSR);
+		if (val & RCAR_I2C_ICMCR_FSDA) {
+			dev_err(dev, "Bus busy, aborting\n");
+			return ret;
+		}
+	}
+
+	writel((chip << 1) | read, priv->base + RCAR_I2C_ICMAR);
+	writel(0, priv->base + RCAR_I2C_ICMSR);
+	writel(RCAR_I2C_ICMCR_MDBS | RCAR_I2C_ICMCR_MIE | RCAR_I2C_ICMCR_ESG,
+	       priv->base + RCAR_I2C_ICMCR);
+
+	ret = wait_for_bit_le32(priv->base + RCAR_I2C_ICMSR, mask,
+				true, 100, true);
+	if (ret)
+		return ret;
+
+	/* Check NAK */
+	if (readl(priv->base + RCAR_I2C_ICMSR) & RCAR_I2C_ICMSR_MNR)
+		return -EREMOTEIO;
+
+	return 0;
+}
+
+static int rcar_i2c_read_common(struct udevice *dev, struct i2c_msg *msg)
+{
+	struct rcar_i2c_priv *priv = dev_get_priv(dev);
+	u32 icmcr = RCAR_I2C_ICMCR_MDBS | RCAR_I2C_ICMCR_MIE;
+	int i, ret = -EREMOTEIO;
+
+	ret = rcar_i2c_set_addr(dev, msg->addr, 1);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < msg->len; i++) {
+
+		if (msg->len - 1 == i)
+			icmcr |= RCAR_I2C_ICMCR_FSB;
+
+		writel(icmcr, priv->base + RCAR_I2C_ICMCR);
+		writel(~RCAR_I2C_ICMSR_MDR, priv->base + RCAR_I2C_ICMSR);
+
+		ret = wait_for_bit_le32(priv->base + RCAR_I2C_ICMSR,
+					RCAR_I2C_ICMSR_MDR, true, 100, true);
+		if (ret)
+			return ret;
+
+		msg->buf[i] = readl(priv->base + RCAR_I2C_ICRXD_ICTXD) & 0xff;
+	}
+
+	writel(~RCAR_I2C_ICMSR_MDR, priv->base + RCAR_I2C_ICMSR);
+
+	return rcar_i2c_finish(dev);
+}
+
+static int rcar_i2c_write_common(struct udevice *dev, struct i2c_msg *msg)
+{
+	struct rcar_i2c_priv *priv = dev_get_priv(dev);
+	u32 icmcr = RCAR_I2C_ICMCR_MDBS | RCAR_I2C_ICMCR_MIE;
+	int i, ret = -EREMOTEIO;
+
+	ret = rcar_i2c_set_addr(dev, msg->addr, 0);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < msg->len; i++) {
+		writel(msg->buf[i], priv->base + RCAR_I2C_ICRXD_ICTXD);
+		writel(icmcr, priv->base + RCAR_I2C_ICMCR);
+		writel(~RCAR_I2C_ICMSR_MDE, priv->base + RCAR_I2C_ICMSR);
+
+		ret = wait_for_bit_le32(priv->base + RCAR_I2C_ICMSR,
+					RCAR_I2C_ICMSR_MDE, true, 100, true);
+		if (ret)
+			return ret;
+	}
+
+	writel(~RCAR_I2C_ICMSR_MDE, priv->base + RCAR_I2C_ICMSR);
+	icmcr |= RCAR_I2C_ICMCR_FSB;
+	writel(icmcr, priv->base + RCAR_I2C_ICMCR);
+
+	return rcar_i2c_finish(dev);
+}
+
+static int rcar_i2c_xfer(struct udevice *dev, struct i2c_msg *msg, int nmsgs)
+{
+	int ret;
+
+	for (; nmsgs > 0; nmsgs--, msg++) {
+		if (msg->flags & I2C_M_RD)
+			ret = rcar_i2c_read_common(dev, msg);
+		else
+			ret = rcar_i2c_write_common(dev, msg);
+
+		if (ret)
+			return -EREMOTEIO;
+	}
+
+	return ret;
+}
+
+static int rcar_i2c_probe_chip(struct udevice *dev, uint addr, uint flags)
+{
+	struct rcar_i2c_priv *priv = dev_get_priv(dev);
+	int ret;
+
+	/* Ignore address 0, slave address */
+	if (addr == 0)
+		return -EINVAL;
+
+	ret = rcar_i2c_set_addr(dev, addr, 1);
+	writel(0, priv->base + RCAR_I2C_ICMSR);
+	return ret;
+}
+
+static int rcar_i2c_set_speed(struct udevice *dev, uint bus_freq_hz)
+{
+	struct rcar_i2c_priv *priv = dev_get_priv(dev);
+	u32 scgd, cdf, round, ick, sum, scl;
+	unsigned long rate;
+
+	/*
+	 * calculate SCL clock
+	 * see
+	 *	ICCCR
+	 *
+	 * ick	= clkp / (1 + CDF)
+	 * SCL	= ick / (20 + SCGD * 8 + F[(ticf + tr + intd) * ick])
+	 *
+	 * ick  : I2C internal clock < 20 MHz
+	 * ticf : I2C SCL falling time
+	 * tr   : I2C SCL rising  time
+	 * intd : LSI internal delay
+	 * clkp : peripheral_clk
+	 * F[]  : integer up-valuation
+	 */
+	rate = clk_get_rate(&priv->clk);
+	cdf = rate / 20000000;
+	if (cdf >= 8) {
+		dev_err(dev, "Input clock %lu too high\n", rate);
+		return -EIO;
+	}
+	ick = rate / (cdf + 1);
+
+	/*
+	 * it is impossible to calculate large scale
+	 * number on u32. separate it
+	 *
+	 * F[(ticf + tr + intd) * ick] with sum = (ticf + tr + intd)
+	 *  = F[sum * ick / 1000000000]
+	 *  = F[(ick / 1000000) * sum / 1000]
+	 */
+	sum = 35 + 200 + priv->intdelay;
+	round = (ick + 500000) / 1000000 * sum;
+	round = (round + 500) / 1000;
+
+	/*
+	 * SCL	= ick / (20 + SCGD * 8 + F[(ticf + tr + intd) * ick])
+	 *
+	 * Calculation result (= SCL) should be less than
+	 * bus_speed for hardware safety
+	 *
+	 * We could use something along the lines of
+	 *	div = ick / (bus_speed + 1) + 1;
+	 *	scgd = (div - 20 - round + 7) / 8;
+	 *	scl = ick / (20 + (scgd * 8) + round);
+	 * (not fully verified) but that would get pretty involved
+	 */
+	for (scgd = 0; scgd < 0x40; scgd++) {
+		scl = ick / (20 + (scgd * 8) + round);
+		if (scl <= bus_freq_hz)
+			goto scgd_find;
+	}
+	dev_err(dev, "it is impossible to calculate best SCL\n");
+	return -EIO;
+
+scgd_find:
+	dev_dbg(dev, "clk %d/%d(%lu), round %u, CDF:0x%x, SCGD: 0x%x\n",
+		scl, bus_freq_hz, clk_get_rate(&priv->clk), round, cdf, scgd);
+
+	priv->icccr = (scgd << RCAR_I2C_ICCCR_SCGD_OFF) | cdf;
+	writel(priv->icccr, priv->base + RCAR_I2C_ICCCR);
+
+	return 0;
+}
+
+static int rcar_i2c_probe(struct udevice *dev)
+{
+	struct rcar_i2c_priv *priv = dev_get_priv(dev);
+	int ret;
+
+	priv->base = dev_read_addr_ptr(dev);
+	priv->intdelay = dev_read_u32_default(dev,
+					      "i2c-scl-internal-delay-ns", 5);
+
+	ret = clk_get_by_index(dev, 0, &priv->clk);
+	if (ret)
+		return ret;
+
+	ret = clk_enable(&priv->clk);
+	if (ret)
+		return ret;
+
+	/* reset slave mode */
+	writel(0, priv->base + RCAR_I2C_ICSIER);
+	writel(0, priv->base + RCAR_I2C_ICSAR);
+	writel(0, priv->base + RCAR_I2C_ICSCR);
+	writel(0, priv->base + RCAR_I2C_ICSSR);
+
+	/* reset master mode */
+	writel(0, priv->base + RCAR_I2C_ICMIER);
+	writel(0, priv->base + RCAR_I2C_ICMCR);
+	writel(0, priv->base + RCAR_I2C_ICMSR);
+	writel(0, priv->base + RCAR_I2C_ICMAR);
+
+	ret = rcar_i2c_set_speed(dev, 100000);
+	if (ret)
+		clk_disable(&priv->clk);
+
+	return ret;
+}
+
+static const struct dm_i2c_ops rcar_i2c_ops = {
+	.xfer		= rcar_i2c_xfer,
+	.probe_chip	= rcar_i2c_probe_chip,
+	.set_bus_speed	= rcar_i2c_set_speed,
+};
+
+static const struct udevice_id rcar_i2c_ids[] = {
+	{ .compatible = "renesas,rcar-gen2-i2c" },
+	{ }
+};
+
+U_BOOT_DRIVER(i2c_rcar) = {
+	.name		= "i2c_rcar",
+	.id		= UCLASS_I2C,
+	.of_match	= rcar_i2c_ids,
+	.probe		= rcar_i2c_probe,
+	.priv_auto_alloc_size = sizeof(struct rcar_i2c_priv),
+	.ops		= &rcar_i2c_ops,
+};
-- 
2.16.2

  reply	other threads:[~2018-05-01  7:03 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-05-01  7:03 [U-Boot] [PATCH 1/4] i2c: rcar_i2c: Remove the driver Marek Vasut
2018-05-01  7:03 ` Marek Vasut [this message]
2018-05-10  7:20   ` [U-Boot] [PATCH 2/4] i2c: rcar_i2c: Add DM and DT capable I2C driver Heiko Schocher
2018-05-01  7:03 ` [U-Boot] [PATCH 3/4] ARM: rmobile: Enable DM capable RCar I2C driver on Lager Marek Vasut
2018-05-10  7:23   ` Heiko Schocher
2018-05-10 10:23     ` Marek Vasut
2018-05-01  7:03 ` [U-Boot] [PATCH 4/4] ARM: rmobile: Enable DM capable RCar I2C driver on Silk Marek Vasut
2018-05-08  8:14 ` [U-Boot] [PATCH 1/4] i2c: rcar_i2c: Remove the driver Heiko Schocher
2018-05-08  9:28   ` Marek Vasut

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20180501070335.1815-2-marek.vasut+renesas@gmail.com \
    --to=marek.vasut@gmail.com \
    --cc=u-boot@lists.denx.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.