All of lore.kernel.org
 help / color / mirror / Atom feed
From: Ben Zong-You Xie <ben717@andestech.com>
To: <linux-i2c@vger.kernel.org>, <devicetree@vger.kernel.org>,
	<linux-kernel@vger.kernel.org>
Cc: <andi.shyti@kernel.org>, <robh@kernel.org>, <krzk+dt@kernel.org>,
	<conor+dt@kernel.org>, Ben Zong-You Xie <ben717@andestech.com>
Subject: [PATCH 2/2] i2c: atciic100: add Andes I2C driver support
Date: Fri, 7 Feb 2025 10:19:23 +0800	[thread overview]
Message-ID: <20250207021923.2912373-3-ben717@andestech.com> (raw)
In-Reply-To: <20250207021923.2912373-1-ben717@andestech.com>

Add support for Andes I2C driver.

Signed-off-by: Ben Zong-You Xie <ben717@andestech.com>
---
 MAINTAINERS                        |   1 +
 drivers/i2c/busses/Kconfig         |  10 +
 drivers/i2c/busses/Makefile        |   1 +
 drivers/i2c/busses/i2c-atciic100.c | 346 +++++++++++++++++++++++++++++
 4 files changed, 358 insertions(+)
 create mode 100644 drivers/i2c/busses/i2c-atciic100.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 544251a03c7d..939ff77eed19 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1639,6 +1639,7 @@ ANDES TECHNOLOGY I2C DRIVER
 M:	Ben Zong-You Xie <ben717@andestech.com>
 S:	Supported
 F:	Documentation/devicetree/bindings/i2c/andestech,atciic100.yaml
+F:	drivers/i2c/busses/i2c-atciic100.c
 
 ANDROID DRIVERS
 M:	Greg Kroah-Hartman <gregkh@linuxfoundation.org>
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index fc438f445771..93edb62c7d3f 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -1422,6 +1422,16 @@ config I2C_ACORN
 
 	  If you don't know, say Y.
 
+config I2C_ATCIIC100
+	tristate "Andes I2C Controller"
+	depends on OF && HAS_IOMEM
+	help
+	  If you say yes to this option, support will be included for the
+	  Andes I2C controller.
+
+	  This support is also available as a module.  If so, the module
+	  will be called i2c-atciic100.
+
 config I2C_ELEKTOR
 	tristate "Elektor ISA card"
 	depends on ISA && HAS_IOPORT_MAP && BROKEN_ON_SMP
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 1c2a4510abe4..58756a80c11f 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -143,6 +143,7 @@ obj-$(CONFIG_I2C_VIPERBOARD)	+= i2c-viperboard.o
 
 # Other I2C/SMBus bus drivers
 obj-$(CONFIG_I2C_ACORN)		+= i2c-acorn.o
+obj-$(CONFIG_I2C_ATCIIC100)	+= i2c-atciic100.o
 obj-$(CONFIG_I2C_BCM_KONA)	+= i2c-bcm-kona.o
 obj-$(CONFIG_I2C_BRCMSTB)	+= i2c-brcmstb.o
 obj-$(CONFIG_I2C_CROS_EC_TUNNEL)	+= i2c-cros-ec-tunnel.o
diff --git a/drivers/i2c/busses/i2c-atciic100.c b/drivers/i2c/busses/i2c-atciic100.c
new file mode 100644
index 000000000000..cfb18b3b506e
--- /dev/null
+++ b/drivers/i2c/busses/i2c-atciic100.c
@@ -0,0 +1,346 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * I2C Support for Andes ATCIIC100 two-wire interface (TWI)
+ *
+ * Copyright (C) 2025 Andes Technology Corporation.
+ */
+
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/iopoll.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#define	ATCIIC100_CFG_REG		0x10
+#define ATCIIC100_CFG_FIFOSIZE		GENMASK(1, 0)
+
+#define	ATCIIC100_INTEN_REG		0x14
+#define	ATCIIC100_INTEN_FIFO_EMPTY	BIT(0)
+#define	ATCIIC100_INTEN_FIFO_FULL	BIT(1)
+#define	ATCIIC100_INTEN_CMPL		BIT(9)
+
+#define	ATCIIC100_STATUS_REG		0x18
+#define ATCIIC100_STATUS_FIFO_EMPTY	BIT(0)
+#define ATCIIC100_STATUS_FIFO_FULL	BIT(1)
+#define ATCIIC100_STATUS_ADDR_HIT	BIT(3)
+#define ATCIIC100_STATUS_CMPL		BIT(9)
+#define ATCIIC100_STATUS_W1C		GENMASK(9, 3)
+
+#define	ATCIIC100_ADDR_REG		0x1C
+
+#define	ATCIIC100_DATA_REG		0x20
+
+#define	ATCIIC100_CTRL_REG		0x24
+#define ATCIIC100_CTRL_DATA_CNT		GENMASK(7, 0)
+#define ATCIIC100_CTRL_DIR		BIT(8)
+#define ATCIIC100_CTRL_PHASE		GENMASK(12, 9)
+
+#define	ATCIIC100_CMD_REG		0x28
+#define ATCIIC100_CMD_ACTION		GENMASK(2, 0)
+#define ATCIIC100_CMD_ACTION_TRANS	BIT(0)
+
+#define	ATCIIC100_SETUP_REG		0x2C
+#define ATCIIC100_SETUP_IICEN		BIT(0)
+#define ATCIIC100_SETUP_REQ		BIT(2)
+
+#define ATCIIC100_TIMEOUT_US		400000
+#define ATCIIC100_TIMEOUT		usecs_to_jiffies(ATCIIC100_TIMEOUT_US)
+
+#define ATCIIC100_MAX_DATA_LEN		256
+
+struct atciic100 {
+	struct i2c_adapter	adap;
+	struct completion	complete;
+	spinlock_t		lock;
+	void __iomem		*base;
+	u8			*buf;
+	int			irq;
+	u16			buf_len;
+	u8			fifo_size;
+	bool			addr_hit;
+	bool			xfer_done;
+};
+
+static void atciic100_xfer_common(struct atciic100 *i2c, u32 status)
+{
+	unsigned long flags;
+	u32 val;
+	u8 fsize = i2c->fifo_size;
+
+	spin_lock_irqsave(&i2c->lock, flags);
+
+	if (status & ATCIIC100_STATUS_FIFO_EMPTY) { /* Data transmit */
+		/* The last write to FIFO */
+		if (i2c->buf_len <= fsize) {
+			fsize = i2c->buf_len;
+			val = readl(i2c->base + ATCIIC100_INTEN_REG) &
+			      ~ATCIIC100_INTEN_FIFO_EMPTY;
+			writel(val, i2c->base + ATCIIC100_INTEN_REG);
+		}
+
+		while (fsize--) {
+			writel(*i2c->buf++, i2c->base + ATCIIC100_DATA_REG);
+			i2c->buf_len--;
+		}
+	} else if (status & ATCIIC100_STATUS_FIFO_FULL) { /* Data receive */
+		while (fsize--) {
+			*i2c->buf++ = readl(i2c->base + ATCIIC100_DATA_REG);
+			i2c->buf_len--;
+		}
+	}
+
+	if (status & ATCIIC100_STATUS_CMPL) {
+		/* Write 1 to clear all status */
+		writel(ATCIIC100_STATUS_W1C, i2c->base + ATCIIC100_STATUS_REG);
+
+		i2c->xfer_done = true;
+		if (status & ATCIIC100_STATUS_ADDR_HIT)
+			i2c->addr_hit = true;
+
+		/* For the last read, retrieve all remaining data in FIFO. */
+		while (i2c->buf_len--)
+			*i2c->buf++ = readl(i2c->base + ATCIIC100_DATA_REG);
+
+	}
+
+	spin_unlock_irqrestore(&i2c->lock, flags);
+}
+
+static irqreturn_t atciic100_irq_handler(int irq, void *data)
+{
+	struct atciic100 *i2c = data;
+	u32 status = readl(i2c->base + ATCIIC100_STATUS_REG);
+
+	atciic100_xfer_common(i2c, status);
+	if (i2c->xfer_done) {
+		/* Disable all interrupts */
+		writel(0, i2c->base + ATCIIC100_INTEN_REG);
+		if (i2c->addr_hit)
+			complete(&i2c->complete);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int atciic100_xfer_wait(struct atciic100 *i2c, struct i2c_msg *msg)
+{
+	u32 val;
+
+	/*
+	 * Set the data count.
+	 * If there are 256 bytes to be transmitted/received, write 0 to the
+	 * data count field.
+	 */
+	val = readl(i2c->base + ATCIIC100_CTRL_REG) |
+	      (i2c->buf_len & ATCIIC100_CTRL_DATA_CNT);
+
+	/* Set the transaction direction */
+	if (msg->flags & I2C_M_RD)
+		val |= ATCIIC100_CTRL_DIR;
+	else
+		val &= ~ATCIIC100_CTRL_DIR;
+
+	/* Enable all phases choices */
+	val |= ATCIIC100_CTRL_PHASE;
+	writel(val, i2c->base + ATCIIC100_CTRL_REG);
+
+	/* Write the address of the target device to the address register */
+	writel(msg->addr, i2c->base + ATCIIC100_ADDR_REG);
+
+	/* Enable the completion and FIFO empty/full interrupts */
+	if (i2c->irq > 0) {
+		val = readl(i2c->base + ATCIIC100_INTEN_REG);
+		val |= (msg->flags & I2C_M_RD) ? ATCIIC100_INTEN_FIFO_FULL
+					       : ATCIIC100_INTEN_FIFO_EMPTY;
+		val |= ATCIIC100_INTEN_CMPL;
+		writel(val, i2c->base + ATCIIC100_INTEN_REG);
+	}
+
+	/* Issue the transaction */
+	writel(ATCIIC100_CMD_ACTION_TRANS, i2c->base + ATCIIC100_CMD_REG);
+
+	if (i2c->irq > 0) {
+		unsigned long time_left;
+
+		time_left = wait_for_completion_timeout(&i2c->complete,
+							ATCIIC100_TIMEOUT);
+		if (!time_left)
+			return -ETIMEDOUT;
+
+		reinit_completion(&i2c->complete);
+	} else {
+		u32 cond = ATCIIC100_STATUS_CMPL;
+		unsigned long timeout;
+
+		cond |= (msg->flags & I2C_M_RD) ? ATCIIC100_STATUS_FIFO_FULL
+						: ATCIIC100_STATUS_FIFO_EMPTY;
+		while (!i2c->xfer_done) {
+			timeout = readl_poll_timeout_atomic(
+					i2c->base + ATCIIC100_STATUS_REG,
+					val, val & cond,
+					10, ATCIIC100_TIMEOUT_US);
+
+			if (timeout)
+				return -ETIMEDOUT;
+
+			atciic100_xfer_common(i2c, val);
+		}
+
+		if (!i2c->addr_hit)
+			return -ENXIO;
+	}
+
+	/* Check if all data is successfully transmitted. */
+	if (readl(i2c->base + ATCIIC100_CTRL_REG) & ATCIIC100_CTRL_DATA_CNT)
+		return -EIO;
+
+	return 0;
+}
+
+static int atciic100_xfer(struct i2c_adapter *adap, struct i2c_msg *msg,
+			  int num)
+{
+	int i, ret;
+	struct atciic100 *i2c = i2c_get_adapdata(adap);
+
+	for (i = 0; i < num; i++) {
+		struct i2c_msg *m = &msg[i];
+
+		i2c->addr_hit = false;
+		i2c->buf = m->buf;
+		i2c->buf_len = m->len;
+		i2c->xfer_done = false;
+		ret = atciic100_xfer_wait(i2c, m);
+		if (ret < 0)
+			return ret;
+	}
+
+	return num;
+}
+
+static u32 atciic100_func(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static struct i2c_algorithm atciic100_algo = {
+	.xfer = atciic100_xfer,
+	.functionality = atciic100_func,
+};
+
+static const struct i2c_adapter_quirks atciic100_quirks = {
+	.flags = I2C_AQ_NO_ZERO_LEN,
+	.max_write_len = ATCIIC100_MAX_DATA_LEN,
+	.max_read_len = ATCIIC100_MAX_DATA_LEN,
+};
+
+static int atciic100_probe(struct platform_device *pdev)
+{
+	int ret;
+	u32 val;
+	struct atciic100 *i2c;
+	struct i2c_adapter *adap;
+	struct device *dev = &pdev->dev;
+
+	i2c = devm_kzalloc(dev, sizeof(*i2c), GFP_KERNEL);
+	if (!i2c)
+		return -ENOMEM;
+
+	i2c->base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(i2c->base))
+		return PTR_ERR(i2c->base);
+
+	i2c->irq = platform_get_irq(pdev, 0);
+	if (i2c->irq > 0) {
+		ret = devm_request_irq(dev, i2c->irq, atciic100_irq_handler, 0,
+				       dev_name(dev), i2c);
+		if (ret < 0) {
+			return dev_err_probe(dev, ret,
+					     "unable to request irq %d\n",
+					     i2c->irq);
+		}
+	} else {
+		dev_warn(dev, "no irq resource, falling back to poll mode\n");
+	}
+
+	spin_lock_init(&i2c->lock);
+	init_completion(&i2c->complete);
+
+	adap = &i2c->adap;
+	strscpy(adap->name, pdev->name, sizeof(adap->name));
+	adap->algo = &atciic100_algo;
+	adap->class = I2C_CLASS_HWMON;
+	adap->dev.parent = dev;
+	adap->dev.of_node = dev->of_node;
+	adap->owner = THIS_MODULE;
+	adap->quirks = &atciic100_quirks;
+	adap->retries = 1;
+	adap->timeout = ATCIIC100_TIMEOUT;
+
+	i2c_set_adapdata(adap, i2c);
+	platform_set_drvdata(pdev, i2c);
+	ret = devm_i2c_add_adapter(dev, adap);
+	if (ret)
+		return dev_err_probe(dev, ret, "i2c add adapter failed\n");
+
+	/* Get FIFO size */
+	i2c->fifo_size = 2 << (readl(i2c->base + ATCIIC100_CFG_REG) &
+			       ATCIIC100_CFG_FIFOSIZE);
+
+	/* Configure ATCIIC100 as a controller and enable ATCIIC100 */
+	val = readl(i2c->base + ATCIIC100_SETUP_REG) | ATCIIC100_SETUP_IICEN
+						     | ATCIIC100_SETUP_REQ;
+	writel(val, i2c->base + ATCIIC100_SETUP_REG);
+	return 0;
+}
+
+static void atciic100_remove(struct platform_device *pdev)
+{
+	u32 val;
+	struct atciic100 *i2c = platform_get_drvdata(pdev);
+
+	/* Disable ATCIIC100 */
+	val = readl(i2c->base + ATCIIC100_SETUP_REG) & ~ATCIIC100_SETUP_IICEN;
+	writel(val, i2c->base + ATCIIC100_SETUP_REG);
+
+	i2c_del_adapter(&i2c->adap);
+}
+
+static const struct of_device_id atciic100_dt[] = {
+	{ .compatible = "andestech,atciic100" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, atciic100_dt);
+
+static struct platform_driver atciic100_platform_driver = {
+	.driver		= {
+		.name	= "atciic100",
+		.of_match_table	= of_match_ptr(atciic100_dt),
+	},
+	.probe		= atciic100_probe,
+	.remove		= atciic100_remove,
+};
+
+/* I2C may be needed to bring up other drivers */
+static int __init atciic100_init_driver(void)
+{
+	return platform_driver_register(&atciic100_platform_driver);
+}
+subsys_initcall(atciic100_init_driver);
+
+static void __exit atciic100_exit_driver(void)
+{
+	platform_driver_unregister(&atciic100_platform_driver);
+}
+module_exit(atciic100_exit_driver);
+
+MODULE_AUTHOR("Ben Zong-You Xie <ben717@andestech.com>");
+MODULE_DESCRIPTION("Andes ATCIIC100 I2C bus adapter");
+MODULE_LICENSE("GPL");
+
-- 
2.34.1


  parent reply	other threads:[~2025-02-07  2:19 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-02-07  2:19 [PATCH 0/2] i2c: add I2C driver for Andes atciic100 Ben Zong-You Xie
2025-02-07  2:19 ` [PATCH 1/2] dt-bindings: i2c: add atciic100 Ben Zong-You Xie
2025-02-07  3:23   ` Rob Herring (Arm)
2025-02-07  5:57     ` Ben Zong-You Xie
2025-02-09 12:29   ` Krzysztof Kozlowski
2026-01-22 11:18     ` Ben Zong-You Xie
2026-01-22 11:27       ` Krzysztof Kozlowski
2026-01-22 13:59         ` Ben Zong-You Xie
2025-02-07  2:19 ` Ben Zong-You Xie [this message]
2025-03-20 10:44   ` [PATCH 2/2] i2c: atciic100: add Andes I2C driver support Andi Shyti

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=20250207021923.2912373-3-ben717@andestech.com \
    --to=ben717@andestech.com \
    --cc=andi.shyti@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=krzk+dt@kernel.org \
    --cc=linux-i2c@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=robh@kernel.org \
    /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.