All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/2] i2c: Add Loongson-2K0300 I2C controller support
@ 2025-11-13  8:48 Binbin Zhou
  2025-11-13  8:48 ` [PATCH 1/2] dt-bindings: i2c: loongson,ls2x: Add ls2k0300-i2c compatible Binbin Zhou
                   ` (2 more replies)
  0 siblings, 3 replies; 6+ messages in thread
From: Binbin Zhou @ 2025-11-13  8:48 UTC (permalink / raw)
  To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Andi Shyti, Wolfram Sang, linux-i2c
  Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, Binbin Zhou

Hi all:

This patch set describes the I2C controller integrated into the Loongson-2K0300 chip.

It has a significantly different design from the previous I2C controller (i2c-ls2x),
such as support for master-slave transfer mode, and DMA transfers (implementation
in progress), etc. Therefore, we try to name it i2c-ls2x-v2.

Therefore we try to name it i2c-ls2x-v2.

Thanks.

Binbin Zhou (2):
  dt-bindings: i2c: loongson,ls2x: Add ls2k0300-i2c compatible
  i2c: ls2x-v2: Add driver for Loongson-2K0300 I2C controller

 .../bindings/i2c/loongson,ls2x-i2c.yaml       |   1 +
 MAINTAINERS                                   |   1 +
 drivers/i2c/busses/Kconfig                    |  10 +
 drivers/i2c/busses/Makefile                   |   1 +
 drivers/i2c/busses/i2c-ls2x-v2.c              | 513 ++++++++++++++++++
 5 files changed, 526 insertions(+)
 create mode 100644 drivers/i2c/busses/i2c-ls2x-v2.c


base-commit: e9a6fb0bcdd7609be6969112f3fbfcce3b1d4a7c
-- 
2.47.3


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 1/2] dt-bindings: i2c: loongson,ls2x: Add ls2k0300-i2c compatible
  2025-11-13  8:48 [PATCH 0/2] i2c: Add Loongson-2K0300 I2C controller support Binbin Zhou
@ 2025-11-13  8:48 ` Binbin Zhou
  2025-11-13 19:36   ` Conor Dooley
  2025-11-13  8:48 ` [PATCH 2/2] i2c: ls2x-v2: Add driver for Loongson-2K0300 I2C controller Binbin Zhou
  2025-12-16  8:31 ` [PATCH 0/2] i2c: Add Loongson-2K0300 I2C controller support Binbin Zhou
  2 siblings, 1 reply; 6+ messages in thread
From: Binbin Zhou @ 2025-11-13  8:48 UTC (permalink / raw)
  To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Andi Shyti, Wolfram Sang, linux-i2c
  Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, Binbin Zhou

Add "loongson,ls2k0300-i2c" dedicated compatible for representing I2C of
Loongson-2K0300 chip, because its HW integration is quiet different from
others.

Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
 Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml b/Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml
index 67882ec6e06a..ac2bfb21b5b2 100644
--- a/Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml
+++ b/Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml
@@ -16,6 +16,7 @@ properties:
   compatible:
     enum:
       - loongson,ls2k-i2c
+      - loongson,ls2k0300-i2c
       - loongson,ls7a-i2c
 
   reg:
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 2/2] i2c: ls2x-v2: Add driver for Loongson-2K0300 I2C controller
  2025-11-13  8:48 [PATCH 0/2] i2c: Add Loongson-2K0300 I2C controller support Binbin Zhou
  2025-11-13  8:48 ` [PATCH 1/2] dt-bindings: i2c: loongson,ls2x: Add ls2k0300-i2c compatible Binbin Zhou
@ 2025-11-13  8:48 ` Binbin Zhou
  2025-11-14  7:06   ` Huacai Chen
  2025-12-16  8:31 ` [PATCH 0/2] i2c: Add Loongson-2K0300 I2C controller support Binbin Zhou
  2 siblings, 1 reply; 6+ messages in thread
From: Binbin Zhou @ 2025-11-13  8:48 UTC (permalink / raw)
  To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Andi Shyti, Wolfram Sang, linux-i2c
  Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, Binbin Zhou

This I2C module is integrated into the Loongson-2K0300 SoCs.

It provides multi-master functionality and controls all I2C bus-specific
timing, protocols, arbitration, and timing. It supports both standard
and fast modes.

Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
 MAINTAINERS                      |   1 +
 drivers/i2c/busses/Kconfig       |  10 +
 drivers/i2c/busses/Makefile      |   1 +
 drivers/i2c/busses/i2c-ls2x-v2.c | 513 +++++++++++++++++++++++++++++++
 4 files changed, 525 insertions(+)
 create mode 100644 drivers/i2c/busses/i2c-ls2x-v2.c

diff --git a/MAINTAINERS b/MAINTAINERS
index ddecf1ef3bed..8badab5d774d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14601,6 +14601,7 @@ M:	Binbin Zhou <zhoubinbin@loongson.cn>
 L:	linux-i2c@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml
+F:	drivers/i2c/busses/i2c-ls2x-v2.c
 F:	drivers/i2c/busses/i2c-ls2x.c
 
 LOONGSON PWM DRIVER
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index fd81e49638aa..f52abbe20ce5 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -855,6 +855,16 @@ config I2C_LS2X
 	  This driver can also be built as a module. If so, the module
 	  will be called i2c-ls2x.
 
+config I2C_LS2X_V2
+	tristate "Loongson-2 Fast Speed I2C adapter"
+	depends on LOONGARCH || COMPILE_TEST
+	help
+	  If you say yes to this option, support will be included for the
+	  I2C interface on the Loongson-2K0300 SoCs.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called i2c-ls2x-v2.
+
 config I2C_MLXBF
         tristate "Mellanox BlueField I2C controller"
         depends on (MELLANOX_PLATFORM && ARM64) || COMPILE_TEST
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index fb985769f5ff..8cdfc30b79e9 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -80,6 +80,7 @@ obj-$(CONFIG_I2C_KEBA)		+= i2c-keba.o
 obj-$(CONFIG_I2C_KEMPLD)	+= i2c-kempld.o
 obj-$(CONFIG_I2C_LPC2K)		+= i2c-lpc2k.o
 obj-$(CONFIG_I2C_LS2X)		+= i2c-ls2x.o
+obj-$(CONFIG_I2C_LS2X_V2)	+= i2c-ls2x-v2.o
 obj-$(CONFIG_I2C_MESON)		+= i2c-meson.o
 obj-$(CONFIG_I2C_MICROCHIP_CORE)	+= i2c-microchip-corei2c.o
 obj-$(CONFIG_I2C_MPC)		+= i2c-mpc.o
diff --git a/drivers/i2c/busses/i2c-ls2x-v2.c b/drivers/i2c/busses/i2c-ls2x-v2.c
new file mode 100644
index 000000000000..e3b2a7ffe67e
--- /dev/null
+++ b/drivers/i2c/busses/i2c-ls2x-v2.c
@@ -0,0 +1,513 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Loongson-2K fast I2C controller driver
+ *
+ * Copyright (C) 2025 Loongson Technology Corporation Limited
+ *
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/iopoll.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/units.h>
+
+/* Loongson-2 fast I2C offset registers */
+#define LOONGSON2_I2C_CR1	0x00	/* I2C control 1 register */
+#define LOONGSON2_I2C_CR2	0x04	/* I2C control 2 register */
+#define LOONGSON2_I2C_OAR	0x08	/* I2C slave address register */
+#define LOONGSON2_I2C_DR	0x10	/* I2C data register */
+#define LOONGSON2_I2C_SR1	0x14	/* I2C status 1 register */
+#define LOONGSON2_I2C_SR2	0x18	/* I2C status 2 register */
+#define LOONGSON2_I2C_CCR	0x1C	/* I2C clock control register */
+#define LOONGSON2_I2C_TRISE	0x20	/* I2C trise register */
+#define LOONGSON2_I2C_FLTR	0x24
+
+/* Bitfields of I2C control 1 register */
+#define LOONGSON2_I2C_CR1_PE		BIT(0)
+#define LOONGSON2_I2C_CR1_START		BIT(8)
+#define LOONGSON2_I2C_CR1_STOP		BIT(9)
+#define LOONGSON2_I2C_CR1_ACK		BIT(10)
+#define LOONGSON2_I2C_CR1_POS		BIT(11)
+
+#define LOONGSON2_I2C_CR1_OP_MASK	(LOONGSON2_I2C_CR1_START | LOONGSON2_I2C_CR1_STOP)
+
+/* Bitfields of I2C control 2 register */
+#define LOONGSON2_I2C_CR2_FREQ		GENMASK(5, 0)
+#define LOONGSON2_I2C_CR2_ITERREN	BIT(8)
+#define LOONGSON2_I2C_CR2_ITEVTEN	BIT(9)
+#define LOONGSON2_I2C_CR2_ITBUFEN	BIT(10)
+
+#define LOONGSON2_I2C_CR2_IRQ_MASK	(LOONGSON2_I2C_CR2_ITBUFEN | \
+					 LOONGSON2_I2C_CR2_ITEVTEN | \
+					 LOONGSON2_I2C_CR2_ITERREN)
+
+/* Bitfields of I2C status 1 register */
+#define LOONGSON2_I2C_SR1_SB		BIT(0)
+#define LOONGSON2_I2C_SR1_ADDR		BIT(1)
+#define LOONGSON2_I2C_SR1_BTF		BIT(2)
+#define LOONGSON2_I2C_SR1_RXNE		BIT(6)
+#define LOONGSON2_I2C_SR1_TXE		BIT(7)
+#define LOONGSON2_I2C_SR1_BERR		BIT(8)
+#define LOONGSON2_I2C_SR1_ARLO		BIT(9)
+#define LOONGSON2_I2C_SR1_AF		BIT(10)
+
+#define LOONGSON2_I2C_SR1_ITEVTEN_MASK	(LOONGSON2_I2C_SR1_BTF | \
+					 LOONGSON2_I2C_SR1_ADDR | \
+					 LOONGSON2_I2C_SR1_SB)
+#define LOONGSON2_I2C_SR1_ITBUFEN_MASK	(LOONGSON2_I2C_SR1_TXE | LOONGSON2_I2C_SR1_RXNE)
+#define LOONGSON2_I2C_SR1_ITERREN_MASK	(LOONGSON2_I2C_SR1_AF | \
+					 LOONGSON2_I2C_SR1_ARLO | \
+					 LOONGSON2_I2C_SR1_BERR)
+
+/* Bitfields of I2C status 2 register */
+#define LOONGSON2_I2C_SR2_BUSY		BIT(1)
+
+/* Bitfields of I2C clock control register */
+#define LOONGSON2_I2C_CCR_CCR		GENMASK(11, 0)
+#define LOONGSON2_I2C_CCR_DUTY		BIT(14)
+#define LOONGSON2_I2C_CCR_FS		BIT(15)
+
+/* Bitfields of I2C trise register */
+#define LOONGSON2_I2C_TRISE_SCL		GENMASK(5, 0)
+
+#define LOONGSON2_I2C_FREE_SLEEP_US	1000
+#define LOONGSON2_I2C_FREE_TIMEOUT_US	5000
+
+/*
+ * struct loongson2_i2c_msg - client specific data
+ * @addr: 8-bit slave addr, including r/w bit
+ * @count: number of bytes to be transferred
+ * @buf: data buffer
+ * @stop: last I2C msg to be sent, i.e. STOP to be generated
+ * @result: result of the transfer
+ */
+struct loongson2_i2c_msg {
+	u8 addr;
+	u32 count;
+	u8 *buf;
+	bool stop;
+	int result;
+};
+
+/*
+ * struct loongson2_i2c_priv - private data of the controller
+ * @adapter: I2C adapter for this controller
+ * @dev: device for this controller
+ * @complete: completion of I2C message
+ * @regmap: regmap of the I2C device
+ * @i2c_t: I2C timing information
+ * @msg: I2C transfer information
+ */
+struct loongson2_i2c_priv {
+	struct i2c_adapter adapter;
+	struct device *dev;
+	struct completion complete;
+	struct regmap *regmap;
+	struct i2c_timings i2c_t;
+	struct loongson2_i2c_msg msg;
+};
+
+static void loongson2_i2c_disable_irq(struct loongson2_i2c_priv *priv)
+{
+	regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2, LOONGSON2_I2C_CR2_IRQ_MASK, 0);
+}
+
+static int loongson2_i2c_wait_free_bus(struct loongson2_i2c_priv *priv)
+{
+	u32 status;
+	int ret;
+
+	ret = regmap_read_poll_timeout(priv->regmap, LOONGSON2_I2C_SR2, status,
+				       !(status & LOONGSON2_I2C_SR2_BUSY),
+				       LOONGSON2_I2C_FREE_SLEEP_US,
+				       LOONGSON2_I2C_FREE_TIMEOUT_US);
+	if (ret) {
+		dev_dbg(priv->dev, "I2C bus free failed.\n");
+		ret = -EBUSY;
+	}
+
+	return ret;
+}
+
+static void loongson2_i2c_write_byte(struct loongson2_i2c_priv *priv, u8 byte)
+{
+	regmap_write(priv->regmap, LOONGSON2_I2C_DR, byte);
+}
+
+static void loongson2_i2c_read_msg(struct loongson2_i2c_priv *priv)
+{
+	struct loongson2_i2c_msg *msg = &priv->msg;
+	u32 rbuf;
+
+	regmap_read(priv->regmap, LOONGSON2_I2C_DR, &rbuf);
+	*msg->buf++ = rbuf;
+	msg->count--;
+}
+
+static void loongson2_i2c_terminate_xfer(struct loongson2_i2c_priv *priv)
+{
+	struct loongson2_i2c_msg *msg = &priv->msg;
+
+	loongson2_i2c_disable_irq(priv);
+	regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_OP_MASK,
+			   msg->stop ? LOONGSON2_I2C_CR1_STOP : LOONGSON2_I2C_CR1_START);
+	complete(&priv->complete);
+}
+
+static void loongson2_i2c_handle_write(struct loongson2_i2c_priv *priv)
+{
+	struct loongson2_i2c_msg *msg = &priv->msg;
+
+	if (msg->count) {
+		loongson2_i2c_write_byte(priv, *msg->buf++);
+		msg->count--;
+		if (!msg->count)
+			regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2,
+					   LOONGSON2_I2C_CR2_ITBUFEN, 0);
+	} else {
+		loongson2_i2c_terminate_xfer(priv);
+	}
+}
+
+static void loongson2_i2c_handle_read(struct loongson2_i2c_priv *priv, int flag)
+{
+	struct loongson2_i2c_msg *msg = &priv->msg;
+	bool changed;
+	int i;
+
+	switch (msg->count) {
+	case 1:
+		/* only transmit 1 bytes condition */
+		loongson2_i2c_disable_irq(priv);
+		loongson2_i2c_read_msg(priv);
+		complete(&priv->complete);
+		break;
+	case 2:
+		if (flag != 1) {
+			/* ensure only transmit 2 bytes condition */
+			regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2,
+					   LOONGSON2_I2C_CR2_ITBUFEN, 0);
+			break;
+		}
+		regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_OP_MASK,
+				   msg->stop ? LOONGSON2_I2C_CR1_STOP : LOONGSON2_I2C_CR1_START);
+
+		loongson2_i2c_disable_irq(priv);
+
+		for (i = 2; i > 0; i--)
+			loongson2_i2c_read_msg(priv);
+
+		regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_POS, 0);
+		complete(&priv->complete);
+		break;
+	case 3:
+		regmap_update_bits_check(priv->regmap, LOONGSON2_I2C_CR2, LOONGSON2_I2C_CR2_ITBUFEN,
+					 0, &changed);
+		if (changed)
+			break;
+		regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_ACK, 0);
+		fallthrough;
+	default:
+		loongson2_i2c_read_msg(priv);
+	}
+}
+
+static void loongson2_i2c_handle_rx_addr(struct loongson2_i2c_priv *priv)
+{
+	struct loongson2_i2c_msg *msg = &priv->msg;
+
+	switch (msg->count) {
+	case 0:
+		loongson2_i2c_terminate_xfer(priv);
+		break;
+	case 1:
+		regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1,
+				   LOONGSON2_I2C_CR1_ACK | LOONGSON2_I2C_CR1_POS, 0);
+		/* start or stop */
+		regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_OP_MASK,
+				   msg->stop ? LOONGSON2_I2C_CR1_STOP : LOONGSON2_I2C_CR1_START);
+		break;
+	case 2:
+		regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_ACK, 0);
+		regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_POS,
+				   LOONGSON2_I2C_CR1_POS);
+		break;
+
+	default:
+		regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_ACK,
+				   LOONGSON2_I2C_CR1_ACK);
+		regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_POS, 0);
+	}
+}
+
+static irqreturn_t loongson2_i2c_isr_error(u32 status, void *data)
+{
+	struct loongson2_i2c_priv *priv = data;
+	struct loongson2_i2c_msg *msg = &priv->msg;
+
+	/* Arbitration lost */
+	if (status & LOONGSON2_I2C_SR1_ARLO) {
+		regmap_update_bits(priv->regmap, LOONGSON2_I2C_SR1, LOONGSON2_I2C_SR1_ARLO, 0);
+		msg->result = -EAGAIN;
+	}
+
+	/*
+	 * Acknowledge failure:
+	 * In master transmitter mode a Stop must be generated by software
+	 */
+	if (status & LOONGSON2_I2C_SR1_AF) {
+		regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_STOP,
+				   LOONGSON2_I2C_CR1_STOP);
+		regmap_update_bits(priv->regmap, LOONGSON2_I2C_SR1, LOONGSON2_I2C_SR1_AF, 0);
+		msg->result = -EIO;
+	}
+
+	/* Bus error */
+	if (status & LOONGSON2_I2C_SR1_BERR) {
+		regmap_update_bits(priv->regmap, LOONGSON2_I2C_SR1, LOONGSON2_I2C_SR1_BERR, 0);
+		msg->result = -EIO;
+	}
+
+	loongson2_i2c_disable_irq(priv);
+	complete(&priv->complete);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t loongson2_i2c_isr_event(int irq, void *data)
+{
+	u32 possible_status = LOONGSON2_I2C_SR1_ITEVTEN_MASK;
+	struct loongson2_i2c_priv *priv = data;
+	struct loongson2_i2c_msg *msg = &priv->msg;
+	u32 status, ien, event, cr2;
+
+	regmap_read(priv->regmap, LOONGSON2_I2C_SR1, &status);
+	if (status & LOONGSON2_I2C_SR1_ITERREN_MASK)
+		return loongson2_i2c_isr_error(status, data);
+
+	regmap_read(priv->regmap, LOONGSON2_I2C_CR2, &cr2);
+	ien = cr2 & LOONGSON2_I2C_CR2_IRQ_MASK;
+
+	/* Update possible_status if buffer interrupt is enabled */
+	if (ien & LOONGSON2_I2C_CR2_ITBUFEN)
+		possible_status |= LOONGSON2_I2C_SR1_ITBUFEN_MASK;
+
+	event = status & possible_status;
+	if (!event) {
+		dev_dbg(priv->dev, "spurious evt irq (status=0x%08x, ien=0x%08x)\n", status, ien);
+		return IRQ_NONE;
+	}
+
+	/* Start condition generated */
+	if (event & LOONGSON2_I2C_SR1_SB)
+		loongson2_i2c_write_byte(priv, msg->addr);
+
+	/* I2C Address sent */
+	if (event & LOONGSON2_I2C_SR1_ADDR) {
+		if (msg->addr & I2C_M_RD)
+			loongson2_i2c_handle_rx_addr(priv);
+		/* Clear ADDR flag */
+		regmap_read(priv->regmap, LOONGSON2_I2C_SR2, &status);
+		/* Enable buffer interrupts for RX/TX not empty events */
+		regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2, LOONGSON2_I2C_CR2_ITBUFEN,
+				   LOONGSON2_I2C_CR2_ITBUFEN);
+	}
+
+	if (msg->addr & I2C_M_RD) {
+		/* RX not empty */
+		if (event & LOONGSON2_I2C_SR1_RXNE)
+			loongson2_i2c_handle_read(priv, 0);
+
+		if (event & LOONGSON2_I2C_SR1_BTF)
+			loongson2_i2c_handle_read(priv, 1);
+	} else {
+		/* TX empty */
+		if (event & LOONGSON2_I2C_SR1_TXE)
+			loongson2_i2c_handle_write(priv);
+
+		if (event & LOONGSON2_I2C_SR1_BTF)
+			loongson2_i2c_handle_write(priv);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int loongson2_i2c_xfer_msg(struct loongson2_i2c_priv *priv, struct i2c_msg *msg,
+				  bool is_stop)
+{
+	struct loongson2_i2c_msg *l_msg = &priv->msg;
+	unsigned long timeout;
+	int ret;
+
+	l_msg->addr   = i2c_8bit_addr_from_msg(msg);
+	l_msg->buf    = msg->buf;
+	l_msg->count  = msg->len;
+	l_msg->stop   = is_stop;
+	l_msg->result = 0;
+
+	reinit_completion(&priv->complete);
+
+	/* Enable events and errors interrupts */
+	regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2,
+			   LOONGSON2_I2C_CR2_ITEVTEN | LOONGSON2_I2C_CR2_ITERREN,
+			   LOONGSON2_I2C_CR2_ITEVTEN | LOONGSON2_I2C_CR2_ITERREN);
+
+	timeout = wait_for_completion_timeout(&priv->complete, priv->adapter.timeout);
+	ret = l_msg->result;
+
+	if (!timeout)
+		ret = -ETIMEDOUT;
+
+	return ret;
+}
+
+static int loongson2_i2c_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg msgs[], int num)
+{
+	struct loongson2_i2c_priv *priv = i2c_get_adapdata(i2c_adap);
+	int ret = 0, i;
+
+	ret = loongson2_i2c_wait_free_bus(priv);
+	if (ret)
+		return ret;
+
+	/* START generation */
+	regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_START,
+			   LOONGSON2_I2C_CR1_START);
+
+	for (i = 0; i < num && !ret; i++)
+		ret = loongson2_i2c_xfer_msg(priv, &msgs[i], i == num - 1);
+
+	return (ret < 0) ? ret : num;
+}
+
+static u32 loongson2_i2c_func(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm loongson2_i2c_algo = {
+	.master_xfer = loongson2_i2c_xfer,
+	.functionality = loongson2_i2c_func,
+};
+
+static void loongson2_i2c_adjust_bus_speed(struct loongson2_i2c_priv *priv)
+{
+	struct device *dev = priv->adapter.dev.parent;
+	struct i2c_timings *t = &priv->i2c_t;
+	u32 val, ccr = 0;
+
+	t->bus_freq_hz = I2C_MAX_STANDARD_MODE_FREQ;
+
+	i2c_parse_fw_timings(dev, t, false);
+
+	if (t->bus_freq_hz >= I2C_MAX_FAST_MODE_FREQ) {
+		val = DIV_ROUND_UP(t->bus_freq_hz, I2C_MAX_FAST_MODE_FREQ * 3);
+
+		/* Select Fast mode */
+		ccr |= LOONGSON2_I2C_CCR_FS;
+	} else {
+		val = DIV_ROUND_UP(t->bus_freq_hz, I2C_MAX_STANDARD_MODE_FREQ * 2);
+	}
+
+	ccr |= FIELD_GET(LOONGSON2_I2C_CCR_CCR, val);
+	regmap_write(priv->regmap, LOONGSON2_I2C_CCR, ccr);
+
+	/* reference clock determination the configure val(0x3f) */
+	regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2, LOONGSON2_I2C_CR2_FREQ,
+			   LOONGSON2_I2C_CR2_FREQ);
+	regmap_update_bits(priv->regmap, LOONGSON2_I2C_TRISE, LOONGSON2_I2C_TRISE_SCL,
+			   LOONGSON2_I2C_TRISE_SCL);
+
+	/* Enable I2C */
+	regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_PE,
+			   LOONGSON2_I2C_CR1_PE);
+}
+
+static const struct regmap_config loongson2_i2c_regmap_config = {
+	.reg_bits = 32,
+	.val_bits = 32,
+	.reg_stride = 4,
+	.max_register = LOONGSON2_I2C_TRISE,
+};
+
+static int loongson2_i2c_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct loongson2_i2c_priv *priv;
+	struct i2c_adapter *adap;
+	void __iomem *base;
+	int irq, ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(base))
+		return dev_err_probe(dev, PTR_ERR(base),
+				     "devm_platform_ioremap_resource failed\n");
+
+	priv->regmap = devm_regmap_init_mmio(dev, base,
+					     &loongson2_i2c_regmap_config);
+	if (IS_ERR(priv->regmap))
+		return dev_err_probe(dev, PTR_ERR(priv->regmap),
+				     "devm_regmap_init_mmio failed\n");
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return -EINVAL;
+
+	priv->dev = dev;
+
+	adap = &priv->adapter;
+	adap->retries = 5;
+	adap->nr = pdev->id;
+	adap->dev.parent = dev;
+	adap->owner = THIS_MODULE;
+	adap->algo = &loongson2_i2c_algo;
+	adap->timeout = 2 * HZ;
+	device_set_node(&adap->dev, dev_fwnode(dev));
+	i2c_set_adapdata(adap, priv);
+	strscpy(adap->name, pdev->name, sizeof(adap->name));
+	init_completion(&priv->complete);
+	platform_set_drvdata(pdev, priv);
+
+	loongson2_i2c_adjust_bus_speed(priv);
+
+	ret = devm_request_irq(dev, irq,  loongson2_i2c_isr_event, IRQF_SHARED, pdev->name, priv);
+	if (ret)
+		return dev_err_probe(dev, ret, "Unable to request irq %d\n", irq);
+
+	return devm_i2c_add_adapter(dev, adap);
+}
+
+static const struct of_device_id loongson2_i2c_id_table[] = {
+	{ .compatible = "loongson,ls2k0300-i2c" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, loongson2_i2c_id_table);
+
+static struct platform_driver loongson2_i2c_driver = {
+	.driver = {
+		.name = "loongson2-i2c-v2",
+		.of_match_table = loongson2_i2c_id_table,
+	},
+	.probe = loongson2_i2c_probe,
+};
+
+module_platform_driver(loongson2_i2c_driver);
+
+MODULE_DESCRIPTION("Loongson-2K0300 I2C bus driver");
+MODULE_AUTHOR("Loongson Technology Corporation Limited");
+MODULE_LICENSE("GPL");
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH 1/2] dt-bindings: i2c: loongson,ls2x: Add ls2k0300-i2c compatible
  2025-11-13  8:48 ` [PATCH 1/2] dt-bindings: i2c: loongson,ls2x: Add ls2k0300-i2c compatible Binbin Zhou
@ 2025-11-13 19:36   ` Conor Dooley
  0 siblings, 0 replies; 6+ messages in thread
From: Conor Dooley @ 2025-11-13 19:36 UTC (permalink / raw)
  To: Binbin Zhou
  Cc: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Andi Shyti, Wolfram Sang, linux-i2c, Huacai Chen,
	Xuerui Wang, loongarch, devicetree

[-- Attachment #1: Type: text/plain, Size: 75 bytes --]

Acked-by: Conor Dooley <conor.dooley@microchip.com>
pw-bot: not-applicable

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 2/2] i2c: ls2x-v2: Add driver for Loongson-2K0300 I2C controller
  2025-11-13  8:48 ` [PATCH 2/2] i2c: ls2x-v2: Add driver for Loongson-2K0300 I2C controller Binbin Zhou
@ 2025-11-14  7:06   ` Huacai Chen
  0 siblings, 0 replies; 6+ messages in thread
From: Huacai Chen @ 2025-11-14  7:06 UTC (permalink / raw)
  To: Binbin Zhou
  Cc: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Andi Shyti, Wolfram Sang, linux-i2c, Xuerui Wang,
	loongarch, devicetree

Hi, Binbin,

On Thu, Nov 13, 2025 at 4:48 PM Binbin Zhou <zhoubinbin@loongson.cn> wrote:
>
> This I2C module is integrated into the Loongson-2K0300 SoCs.
>
> It provides multi-master functionality and controls all I2C bus-specific
> timing, protocols, arbitration, and timing. It supports both standard
> and fast modes.
>
> Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> ---
>  MAINTAINERS                      |   1 +
>  drivers/i2c/busses/Kconfig       |  10 +
>  drivers/i2c/busses/Makefile      |   1 +
>  drivers/i2c/busses/i2c-ls2x-v2.c | 513 +++++++++++++++++++++++++++++++
>  4 files changed, 525 insertions(+)
>  create mode 100644 drivers/i2c/busses/i2c-ls2x-v2.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index ddecf1ef3bed..8badab5d774d 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14601,6 +14601,7 @@ M:      Binbin Zhou <zhoubinbin@loongson.cn>
>  L:     linux-i2c@vger.kernel.org
>  S:     Maintained
>  F:     Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml
> +F:     drivers/i2c/busses/i2c-ls2x-v2.c
Move this line after i2c-ls2x.c?

>  F:     drivers/i2c/busses/i2c-ls2x.c
>
>  LOONGSON PWM DRIVER
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index fd81e49638aa..f52abbe20ce5 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -855,6 +855,16 @@ config I2C_LS2X
>           This driver can also be built as a module. If so, the module
>           will be called i2c-ls2x.
>
> +config I2C_LS2X_V2
> +       tristate "Loongson-2 Fast Speed I2C adapter"
> +       depends on LOONGARCH || COMPILE_TEST
> +       help
> +         If you say yes to this option, support will be included for the
> +         I2C interface on the Loongson-2K0300 SoCs.
> +
> +         This driver can also be built as a module. If so, the module
> +         will be called i2c-ls2x-v2.
> +
>  config I2C_MLXBF
>          tristate "Mellanox BlueField I2C controller"
>          depends on (MELLANOX_PLATFORM && ARM64) || COMPILE_TEST
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index fb985769f5ff..8cdfc30b79e9 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -80,6 +80,7 @@ obj-$(CONFIG_I2C_KEBA)                += i2c-keba.o
>  obj-$(CONFIG_I2C_KEMPLD)       += i2c-kempld.o
>  obj-$(CONFIG_I2C_LPC2K)                += i2c-lpc2k.o
>  obj-$(CONFIG_I2C_LS2X)         += i2c-ls2x.o
> +obj-$(CONFIG_I2C_LS2X_V2)      += i2c-ls2x-v2.o
>  obj-$(CONFIG_I2C_MESON)                += i2c-meson.o
>  obj-$(CONFIG_I2C_MICROCHIP_CORE)       += i2c-microchip-corei2c.o
>  obj-$(CONFIG_I2C_MPC)          += i2c-mpc.o
> diff --git a/drivers/i2c/busses/i2c-ls2x-v2.c b/drivers/i2c/busses/i2c-ls2x-v2.c
> new file mode 100644
> index 000000000000..e3b2a7ffe67e
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-ls2x-v2.c
> @@ -0,0 +1,513 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Loongson-2K fast I2C controller driver
> + *
> + * Copyright (C) 2025 Loongson Technology Corporation Limited
> + *
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/bits.h>
> +#include <linux/clk.h>
> +#include <linux/device.h>
> +#include <linux/iopoll.h>
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/property.h>
> +#include <linux/regmap.h>
> +#include <linux/units.h>
> +
> +/* Loongson-2 fast I2C offset registers */
> +#define LOONGSON2_I2C_CR1      0x00    /* I2C control 1 register */
> +#define LOONGSON2_I2C_CR2      0x04    /* I2C control 2 register */
> +#define LOONGSON2_I2C_OAR      0x08    /* I2C slave address register */
> +#define LOONGSON2_I2C_DR       0x10    /* I2C data register */
> +#define LOONGSON2_I2C_SR1      0x14    /* I2C status 1 register */
> +#define LOONGSON2_I2C_SR2      0x18    /* I2C status 2 register */
> +#define LOONGSON2_I2C_CCR      0x1C    /* I2C clock control register */
> +#define LOONGSON2_I2C_TRISE    0x20    /* I2C trise register */
> +#define LOONGSON2_I2C_FLTR     0x24
> +
> +/* Bitfields of I2C control 1 register */
> +#define LOONGSON2_I2C_CR1_PE           BIT(0)
> +#define LOONGSON2_I2C_CR1_START                BIT(8)
> +#define LOONGSON2_I2C_CR1_STOP         BIT(9)
> +#define LOONGSON2_I2C_CR1_ACK          BIT(10)
> +#define LOONGSON2_I2C_CR1_POS          BIT(11)
> +
> +#define LOONGSON2_I2C_CR1_OP_MASK      (LOONGSON2_I2C_CR1_START | LOONGSON2_I2C_CR1_STOP)
> +
> +/* Bitfields of I2C control 2 register */
> +#define LOONGSON2_I2C_CR2_FREQ         GENMASK(5, 0)
> +#define LOONGSON2_I2C_CR2_ITERREN      BIT(8)
> +#define LOONGSON2_I2C_CR2_ITEVTEN      BIT(9)
> +#define LOONGSON2_I2C_CR2_ITBUFEN      BIT(10)
> +
> +#define LOONGSON2_I2C_CR2_IRQ_MASK     (LOONGSON2_I2C_CR2_ITBUFEN | \
> +                                        LOONGSON2_I2C_CR2_ITEVTEN | \
> +                                        LOONGSON2_I2C_CR2_ITERREN)
> +
> +/* Bitfields of I2C status 1 register */
> +#define LOONGSON2_I2C_SR1_SB           BIT(0)
> +#define LOONGSON2_I2C_SR1_ADDR         BIT(1)
> +#define LOONGSON2_I2C_SR1_BTF          BIT(2)
> +#define LOONGSON2_I2C_SR1_RXNE         BIT(6)
> +#define LOONGSON2_I2C_SR1_TXE          BIT(7)
> +#define LOONGSON2_I2C_SR1_BERR         BIT(8)
> +#define LOONGSON2_I2C_SR1_ARLO         BIT(9)
> +#define LOONGSON2_I2C_SR1_AF           BIT(10)
> +
> +#define LOONGSON2_I2C_SR1_ITEVTEN_MASK (LOONGSON2_I2C_SR1_BTF | \
> +                                        LOONGSON2_I2C_SR1_ADDR | \
> +                                        LOONGSON2_I2C_SR1_SB)
> +#define LOONGSON2_I2C_SR1_ITBUFEN_MASK (LOONGSON2_I2C_SR1_TXE | LOONGSON2_I2C_SR1_RXNE)
> +#define LOONGSON2_I2C_SR1_ITERREN_MASK (LOONGSON2_I2C_SR1_AF | \
> +                                        LOONGSON2_I2C_SR1_ARLO | \
> +                                        LOONGSON2_I2C_SR1_BERR)
> +
> +/* Bitfields of I2C status 2 register */
> +#define LOONGSON2_I2C_SR2_BUSY         BIT(1)
> +
> +/* Bitfields of I2C clock control register */
> +#define LOONGSON2_I2C_CCR_CCR          GENMASK(11, 0)
> +#define LOONGSON2_I2C_CCR_DUTY         BIT(14)
> +#define LOONGSON2_I2C_CCR_FS           BIT(15)
> +
> +/* Bitfields of I2C trise register */
> +#define LOONGSON2_I2C_TRISE_SCL                GENMASK(5, 0)
> +
> +#define LOONGSON2_I2C_FREE_SLEEP_US    1000
> +#define LOONGSON2_I2C_FREE_TIMEOUT_US  5000
> +
> +/*
> + * struct loongson2_i2c_msg - client specific data
> + * @addr: 8-bit slave addr, including r/w bit
> + * @count: number of bytes to be transferred
> + * @buf: data buffer
> + * @stop: last I2C msg to be sent, i.e. STOP to be generated
> + * @result: result of the transfer
> + */
> +struct loongson2_i2c_msg {
> +       u8 addr;
> +       u32 count;
> +       u8 *buf;
> +       bool stop;
> +       int result;
> +};
> +
> +/*
> + * struct loongson2_i2c_priv - private data of the controller
> + * @adapter: I2C adapter for this controller
> + * @dev: device for this controller
> + * @complete: completion of I2C message
> + * @regmap: regmap of the I2C device
> + * @i2c_t: I2C timing information
> + * @msg: I2C transfer information
> + */
> +struct loongson2_i2c_priv {
> +       struct i2c_adapter adapter;
> +       struct device *dev;
> +       struct completion complete;
> +       struct regmap *regmap;
> +       struct i2c_timings i2c_t;
> +       struct loongson2_i2c_msg msg;
> +};
> +
> +static void loongson2_i2c_disable_irq(struct loongson2_i2c_priv *priv)
> +{
> +       regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2, LOONGSON2_I2C_CR2_IRQ_MASK, 0);
> +}
> +
> +static int loongson2_i2c_wait_free_bus(struct loongson2_i2c_priv *priv)
> +{
> +       u32 status;
> +       int ret;
> +
> +       ret = regmap_read_poll_timeout(priv->regmap, LOONGSON2_I2C_SR2, status,
> +                                      !(status & LOONGSON2_I2C_SR2_BUSY),
> +                                      LOONGSON2_I2C_FREE_SLEEP_US,
> +                                      LOONGSON2_I2C_FREE_TIMEOUT_US);
> +       if (ret) {
> +               dev_dbg(priv->dev, "I2C bus free failed.\n");
> +               ret = -EBUSY;
> +       }
> +
> +       return ret;
> +}
> +
> +static void loongson2_i2c_write_byte(struct loongson2_i2c_priv *priv, u8 byte)
> +{
> +       regmap_write(priv->regmap, LOONGSON2_I2C_DR, byte);
> +}
We usually put the write function after read, which means
loongson2_i2c_write_byte() should be after loongson2_i2c_read_msg().

> +
> +static void loongson2_i2c_read_msg(struct loongson2_i2c_priv *priv)
> +{
> +       struct loongson2_i2c_msg *msg = &priv->msg;
> +       u32 rbuf;
> +
> +       regmap_read(priv->regmap, LOONGSON2_I2C_DR, &rbuf);
> +       *msg->buf++ = rbuf;
> +       msg->count--;
> +}
> +
> +static void loongson2_i2c_terminate_xfer(struct loongson2_i2c_priv *priv)
> +{
> +       struct loongson2_i2c_msg *msg = &priv->msg;
> +
> +       loongson2_i2c_disable_irq(priv);
> +       regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_OP_MASK,
> +                          msg->stop ? LOONGSON2_I2C_CR1_STOP : LOONGSON2_I2C_CR1_START);
> +       complete(&priv->complete);
> +}
> +
> +static void loongson2_i2c_handle_write(struct loongson2_i2c_priv *priv)
> +{
> +       struct loongson2_i2c_msg *msg = &priv->msg;
> +
> +       if (msg->count) {
> +               loongson2_i2c_write_byte(priv, *msg->buf++);
> +               msg->count--;
> +               if (!msg->count)
> +                       regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2,
> +                                          LOONGSON2_I2C_CR2_ITBUFEN, 0);
> +       } else {
> +               loongson2_i2c_terminate_xfer(priv);
> +       }
> +}
The same, loongson2_i2c_handle_write() should be after
loongson2_i2c_handle_read(), you have already done this in
loongson2_i2c_isr_event().

Huacai

> +
> +static void loongson2_i2c_handle_read(struct loongson2_i2c_priv *priv, int flag)
> +{
> +       struct loongson2_i2c_msg *msg = &priv->msg;
> +       bool changed;
> +       int i;
> +
> +       switch (msg->count) {
> +       case 1:
> +               /* only transmit 1 bytes condition */
> +               loongson2_i2c_disable_irq(priv);
> +               loongson2_i2c_read_msg(priv);
> +               complete(&priv->complete);
> +               break;
> +       case 2:
> +               if (flag != 1) {
> +                       /* ensure only transmit 2 bytes condition */
> +                       regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2,
> +                                          LOONGSON2_I2C_CR2_ITBUFEN, 0);
> +                       break;
> +               }
> +               regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_OP_MASK,
> +                                  msg->stop ? LOONGSON2_I2C_CR1_STOP : LOONGSON2_I2C_CR1_START);
> +
> +               loongson2_i2c_disable_irq(priv);
> +
> +               for (i = 2; i > 0; i--)
> +                       loongson2_i2c_read_msg(priv);
> +
> +               regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_POS, 0);
> +               complete(&priv->complete);
> +               break;
> +       case 3:
> +               regmap_update_bits_check(priv->regmap, LOONGSON2_I2C_CR2, LOONGSON2_I2C_CR2_ITBUFEN,
> +                                        0, &changed);
> +               if (changed)
> +                       break;
> +               regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_ACK, 0);
> +               fallthrough;
> +       default:
> +               loongson2_i2c_read_msg(priv);
> +       }
> +}
> +
> +static void loongson2_i2c_handle_rx_addr(struct loongson2_i2c_priv *priv)
> +{
> +       struct loongson2_i2c_msg *msg = &priv->msg;
> +
> +       switch (msg->count) {
> +       case 0:
> +               loongson2_i2c_terminate_xfer(priv);
> +               break;
> +       case 1:
> +               regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1,
> +                                  LOONGSON2_I2C_CR1_ACK | LOONGSON2_I2C_CR1_POS, 0);
> +               /* start or stop */
> +               regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_OP_MASK,
> +                                  msg->stop ? LOONGSON2_I2C_CR1_STOP : LOONGSON2_I2C_CR1_START);
> +               break;
> +       case 2:
> +               regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_ACK, 0);
> +               regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_POS,
> +                                  LOONGSON2_I2C_CR1_POS);
> +               break;
> +
> +       default:
> +               regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_ACK,
> +                                  LOONGSON2_I2C_CR1_ACK);
> +               regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_POS, 0);
> +       }
> +}
> +
> +static irqreturn_t loongson2_i2c_isr_error(u32 status, void *data)
> +{
> +       struct loongson2_i2c_priv *priv = data;
> +       struct loongson2_i2c_msg *msg = &priv->msg;
> +
> +       /* Arbitration lost */
> +       if (status & LOONGSON2_I2C_SR1_ARLO) {
> +               regmap_update_bits(priv->regmap, LOONGSON2_I2C_SR1, LOONGSON2_I2C_SR1_ARLO, 0);
> +               msg->result = -EAGAIN;
> +       }
> +
> +       /*
> +        * Acknowledge failure:
> +        * In master transmitter mode a Stop must be generated by software
> +        */
> +       if (status & LOONGSON2_I2C_SR1_AF) {
> +               regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_STOP,
> +                                  LOONGSON2_I2C_CR1_STOP);
> +               regmap_update_bits(priv->regmap, LOONGSON2_I2C_SR1, LOONGSON2_I2C_SR1_AF, 0);
> +               msg->result = -EIO;
> +       }
> +
> +       /* Bus error */
> +       if (status & LOONGSON2_I2C_SR1_BERR) {
> +               regmap_update_bits(priv->regmap, LOONGSON2_I2C_SR1, LOONGSON2_I2C_SR1_BERR, 0);
> +               msg->result = -EIO;
> +       }
> +
> +       loongson2_i2c_disable_irq(priv);
> +       complete(&priv->complete);
> +
> +       return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t loongson2_i2c_isr_event(int irq, void *data)
> +{
> +       u32 possible_status = LOONGSON2_I2C_SR1_ITEVTEN_MASK;
> +       struct loongson2_i2c_priv *priv = data;
> +       struct loongson2_i2c_msg *msg = &priv->msg;
> +       u32 status, ien, event, cr2;
> +
> +       regmap_read(priv->regmap, LOONGSON2_I2C_SR1, &status);
> +       if (status & LOONGSON2_I2C_SR1_ITERREN_MASK)
> +               return loongson2_i2c_isr_error(status, data);
> +
> +       regmap_read(priv->regmap, LOONGSON2_I2C_CR2, &cr2);
> +       ien = cr2 & LOONGSON2_I2C_CR2_IRQ_MASK;
> +
> +       /* Update possible_status if buffer interrupt is enabled */
> +       if (ien & LOONGSON2_I2C_CR2_ITBUFEN)
> +               possible_status |= LOONGSON2_I2C_SR1_ITBUFEN_MASK;
> +
> +       event = status & possible_status;
> +       if (!event) {
> +               dev_dbg(priv->dev, "spurious evt irq (status=0x%08x, ien=0x%08x)\n", status, ien);
> +               return IRQ_NONE;
> +       }
> +
> +       /* Start condition generated */
> +       if (event & LOONGSON2_I2C_SR1_SB)
> +               loongson2_i2c_write_byte(priv, msg->addr);
> +
> +       /* I2C Address sent */
> +       if (event & LOONGSON2_I2C_SR1_ADDR) {
> +               if (msg->addr & I2C_M_RD)
> +                       loongson2_i2c_handle_rx_addr(priv);
> +               /* Clear ADDR flag */
> +               regmap_read(priv->regmap, LOONGSON2_I2C_SR2, &status);
> +               /* Enable buffer interrupts for RX/TX not empty events */
> +               regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2, LOONGSON2_I2C_CR2_ITBUFEN,
> +                                  LOONGSON2_I2C_CR2_ITBUFEN);
> +       }
> +
> +       if (msg->addr & I2C_M_RD) {
> +               /* RX not empty */
> +               if (event & LOONGSON2_I2C_SR1_RXNE)
> +                       loongson2_i2c_handle_read(priv, 0);
> +
> +               if (event & LOONGSON2_I2C_SR1_BTF)
> +                       loongson2_i2c_handle_read(priv, 1);
> +       } else {
> +               /* TX empty */
> +               if (event & LOONGSON2_I2C_SR1_TXE)
> +                       loongson2_i2c_handle_write(priv);
> +
> +               if (event & LOONGSON2_I2C_SR1_BTF)
> +                       loongson2_i2c_handle_write(priv);
> +       }
> +
> +       return IRQ_HANDLED;
> +}
> +
> +static int loongson2_i2c_xfer_msg(struct loongson2_i2c_priv *priv, struct i2c_msg *msg,
> +                                 bool is_stop)
> +{
> +       struct loongson2_i2c_msg *l_msg = &priv->msg;
> +       unsigned long timeout;
> +       int ret;
> +
> +       l_msg->addr   = i2c_8bit_addr_from_msg(msg);
> +       l_msg->buf    = msg->buf;
> +       l_msg->count  = msg->len;
> +       l_msg->stop   = is_stop;
> +       l_msg->result = 0;
> +
> +       reinit_completion(&priv->complete);
> +
> +       /* Enable events and errors interrupts */
> +       regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2,
> +                          LOONGSON2_I2C_CR2_ITEVTEN | LOONGSON2_I2C_CR2_ITERREN,
> +                          LOONGSON2_I2C_CR2_ITEVTEN | LOONGSON2_I2C_CR2_ITERREN);
> +
> +       timeout = wait_for_completion_timeout(&priv->complete, priv->adapter.timeout);
> +       ret = l_msg->result;
> +
> +       if (!timeout)
> +               ret = -ETIMEDOUT;
> +
> +       return ret;
> +}
> +
> +static int loongson2_i2c_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg msgs[], int num)
> +{
> +       struct loongson2_i2c_priv *priv = i2c_get_adapdata(i2c_adap);
> +       int ret = 0, i;
> +
> +       ret = loongson2_i2c_wait_free_bus(priv);
> +       if (ret)
> +               return ret;
> +
> +       /* START generation */
> +       regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_START,
> +                          LOONGSON2_I2C_CR1_START);
> +
> +       for (i = 0; i < num && !ret; i++)
> +               ret = loongson2_i2c_xfer_msg(priv, &msgs[i], i == num - 1);
> +
> +       return (ret < 0) ? ret : num;
> +}
> +
> +static u32 loongson2_i2c_func(struct i2c_adapter *adap)
> +{
> +       return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
> +}
> +
> +static const struct i2c_algorithm loongson2_i2c_algo = {
> +       .master_xfer = loongson2_i2c_xfer,
> +       .functionality = loongson2_i2c_func,
> +};
> +
> +static void loongson2_i2c_adjust_bus_speed(struct loongson2_i2c_priv *priv)
> +{
> +       struct device *dev = priv->adapter.dev.parent;
> +       struct i2c_timings *t = &priv->i2c_t;
> +       u32 val, ccr = 0;
> +
> +       t->bus_freq_hz = I2C_MAX_STANDARD_MODE_FREQ;
> +
> +       i2c_parse_fw_timings(dev, t, false);
> +
> +       if (t->bus_freq_hz >= I2C_MAX_FAST_MODE_FREQ) {
> +               val = DIV_ROUND_UP(t->bus_freq_hz, I2C_MAX_FAST_MODE_FREQ * 3);
> +
> +               /* Select Fast mode */
> +               ccr |= LOONGSON2_I2C_CCR_FS;
> +       } else {
> +               val = DIV_ROUND_UP(t->bus_freq_hz, I2C_MAX_STANDARD_MODE_FREQ * 2);
> +       }
> +
> +       ccr |= FIELD_GET(LOONGSON2_I2C_CCR_CCR, val);
> +       regmap_write(priv->regmap, LOONGSON2_I2C_CCR, ccr);
> +
> +       /* reference clock determination the configure val(0x3f) */
> +       regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2, LOONGSON2_I2C_CR2_FREQ,
> +                          LOONGSON2_I2C_CR2_FREQ);
> +       regmap_update_bits(priv->regmap, LOONGSON2_I2C_TRISE, LOONGSON2_I2C_TRISE_SCL,
> +                          LOONGSON2_I2C_TRISE_SCL);
> +
> +       /* Enable I2C */
> +       regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_PE,
> +                          LOONGSON2_I2C_CR1_PE);
> +}
> +
> +static const struct regmap_config loongson2_i2c_regmap_config = {
> +       .reg_bits = 32,
> +       .val_bits = 32,
> +       .reg_stride = 4,
> +       .max_register = LOONGSON2_I2C_TRISE,
> +};
> +
> +static int loongson2_i2c_probe(struct platform_device *pdev)
> +{
> +       struct device *dev = &pdev->dev;
> +       struct loongson2_i2c_priv *priv;
> +       struct i2c_adapter *adap;
> +       void __iomem *base;
> +       int irq, ret;
> +
> +       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +       if (!priv)
> +               return -ENOMEM;
> +
> +       base = devm_platform_ioremap_resource(pdev, 0);
> +       if (IS_ERR(base))
> +               return dev_err_probe(dev, PTR_ERR(base),
> +                                    "devm_platform_ioremap_resource failed\n");
> +
> +       priv->regmap = devm_regmap_init_mmio(dev, base,
> +                                            &loongson2_i2c_regmap_config);
> +       if (IS_ERR(priv->regmap))
> +               return dev_err_probe(dev, PTR_ERR(priv->regmap),
> +                                    "devm_regmap_init_mmio failed\n");
> +
> +       irq = platform_get_irq(pdev, 0);
> +       if (irq < 0)
> +               return -EINVAL;
> +
> +       priv->dev = dev;
> +
> +       adap = &priv->adapter;
> +       adap->retries = 5;
> +       adap->nr = pdev->id;
> +       adap->dev.parent = dev;
> +       adap->owner = THIS_MODULE;
> +       adap->algo = &loongson2_i2c_algo;
> +       adap->timeout = 2 * HZ;
> +       device_set_node(&adap->dev, dev_fwnode(dev));
> +       i2c_set_adapdata(adap, priv);
> +       strscpy(adap->name, pdev->name, sizeof(adap->name));
> +       init_completion(&priv->complete);
> +       platform_set_drvdata(pdev, priv);
> +
> +       loongson2_i2c_adjust_bus_speed(priv);
> +
> +       ret = devm_request_irq(dev, irq,  loongson2_i2c_isr_event, IRQF_SHARED, pdev->name, priv);
> +       if (ret)
> +               return dev_err_probe(dev, ret, "Unable to request irq %d\n", irq);
> +
> +       return devm_i2c_add_adapter(dev, adap);
> +}
> +
> +static const struct of_device_id loongson2_i2c_id_table[] = {
> +       { .compatible = "loongson,ls2k0300-i2c" },
> +       { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, loongson2_i2c_id_table);
> +
> +static struct platform_driver loongson2_i2c_driver = {
> +       .driver = {
> +               .name = "loongson2-i2c-v2",
> +               .of_match_table = loongson2_i2c_id_table,
> +       },
> +       .probe = loongson2_i2c_probe,
> +};
> +
> +module_platform_driver(loongson2_i2c_driver);
> +
> +MODULE_DESCRIPTION("Loongson-2K0300 I2C bus driver");
> +MODULE_AUTHOR("Loongson Technology Corporation Limited");
> +MODULE_LICENSE("GPL");
> --
> 2.47.3
>
>

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 0/2] i2c: Add Loongson-2K0300 I2C controller support
  2025-11-13  8:48 [PATCH 0/2] i2c: Add Loongson-2K0300 I2C controller support Binbin Zhou
  2025-11-13  8:48 ` [PATCH 1/2] dt-bindings: i2c: loongson,ls2x: Add ls2k0300-i2c compatible Binbin Zhou
  2025-11-13  8:48 ` [PATCH 2/2] i2c: ls2x-v2: Add driver for Loongson-2K0300 I2C controller Binbin Zhou
@ 2025-12-16  8:31 ` Binbin Zhou
  2 siblings, 0 replies; 6+ messages in thread
From: Binbin Zhou @ 2025-12-16  8:31 UTC (permalink / raw)
  To: Binbin Zhou
  Cc: Huacai Chen, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Andi Shyti, Wolfram Sang, linux-i2c, Huacai Chen, Xuerui Wang,
	loongarch, devicetree

Hi all:

On Thu, Nov 13, 2025 at 4:48 PM Binbin Zhou <zhoubinbin@loongson.cn> wrote:
>
> Hi all:
>
> This patch set describes the I2C controller integrated into the Loongson-2K0300 chip.
>
> It has a significantly different design from the previous I2C controller (i2c-ls2x),
> such as support for master-slave transfer mode, and DMA transfers (implementation
> in progress), etc. Therefore, we try to name it i2c-ls2x-v2.
>
> Therefore we try to name it i2c-ls2x-v2.
>
> Thanks.
>
> Binbin Zhou (2):
>   dt-bindings: i2c: loongson,ls2x: Add ls2k0300-i2c compatible
>   i2c: ls2x-v2: Add driver for Loongson-2K0300 I2C controller

Gentle ping.
Any comments about this patchset ?

>
>  .../bindings/i2c/loongson,ls2x-i2c.yaml       |   1 +
>  MAINTAINERS                                   |   1 +
>  drivers/i2c/busses/Kconfig                    |  10 +
>  drivers/i2c/busses/Makefile                   |   1 +
>  drivers/i2c/busses/i2c-ls2x-v2.c              | 513 ++++++++++++++++++
>  5 files changed, 526 insertions(+)
>  create mode 100644 drivers/i2c/busses/i2c-ls2x-v2.c
>
>
> base-commit: e9a6fb0bcdd7609be6969112f3fbfcce3b1d4a7c
> --
> 2.47.3
>

--
Thanks.
Binbin

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2025-12-16  8:31 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-11-13  8:48 [PATCH 0/2] i2c: Add Loongson-2K0300 I2C controller support Binbin Zhou
2025-11-13  8:48 ` [PATCH 1/2] dt-bindings: i2c: loongson,ls2x: Add ls2k0300-i2c compatible Binbin Zhou
2025-11-13 19:36   ` Conor Dooley
2025-11-13  8:48 ` [PATCH 2/2] i2c: ls2x-v2: Add driver for Loongson-2K0300 I2C controller Binbin Zhou
2025-11-14  7:06   ` Huacai Chen
2025-12-16  8:31 ` [PATCH 0/2] i2c: Add Loongson-2K0300 I2C controller support Binbin Zhou

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.