public inbox for linux-i2c@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 0/2] i2c: Add Loongson-2K0300 I2C controller support
@ 2026-01-27  2:47 Binbin Zhou
  2026-01-27  2:47 ` [PATCH v2 1/2] dt-bindings: i2c: loongson,ls2x: Add ls2k0300-i2c compatible Binbin Zhou
  2026-01-27  2:47 ` [PATCH v2 2/2] i2c: ls2x-v2: Add driver for Loongson-2K0300 I2C controller Binbin Zhou
  0 siblings, 2 replies; 10+ messages in thread
From: Binbin Zhou @ 2026-01-27  2:47 UTC (permalink / raw)
  To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Andi Shyti, Wolfram Sang, Andy Shevchenko,
	linux-i2c
  Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, Binbin Zhou

Hi all:

This patch set describes the I2C controller integrated 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.

Thanks.

======
V2:
Patch (1/2):
 - Add Acked-by tag from Conor, thanks.

Patch (2/2):
 - Reorder the definitions of read() and write();
 - Adjust the calculation method for bus speed.

Link to V1:
https://lore.kernel.org/all/cover.1763018288.git.zhoubinbin@loongson.cn/

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       |  15 +
 MAINTAINERS                                   |   1 +
 drivers/i2c/busses/Kconfig                    |  10 +
 drivers/i2c/busses/Makefile                   |   1 +
 drivers/i2c/busses/i2c-ls2x-v2.c              | 545 ++++++++++++++++++
 5 files changed, 572 insertions(+)
 create mode 100644 drivers/i2c/busses/i2c-ls2x-v2.c


base-commit: 880977fdc7f67923d1904ee23ca75fa1e375ea46
-- 
2.47.3


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

* [PATCH v2 1/2] dt-bindings: i2c: loongson,ls2x: Add ls2k0300-i2c compatible
  2026-01-27  2:47 [PATCH v2 0/2] i2c: Add Loongson-2K0300 I2C controller support Binbin Zhou
@ 2026-01-27  2:47 ` Binbin Zhou
  2026-01-27  2:47 ` [PATCH v2 2/2] i2c: ls2x-v2: Add driver for Loongson-2K0300 I2C controller Binbin Zhou
  1 sibling, 0 replies; 10+ messages in thread
From: Binbin Zhou @ 2026-01-27  2:47 UTC (permalink / raw)
  To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Andi Shyti, Wolfram Sang, Andy Shevchenko,
	linux-i2c
  Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, Binbin Zhou,
	Conor Dooley

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

Acked-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
 .../bindings/i2c/loongson,ls2x-i2c.yaml           | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml b/Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml
index 67882ec6e06a..4c381559f7f0 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:
@@ -24,6 +25,9 @@ properties:
   interrupts:
     maxItems: 1
 
+  clocks:
+    maxItems: 1
+
 required:
   - compatible
   - reg
@@ -31,6 +35,17 @@ required:
 
 unevaluatedProperties: false
 
+if:
+  properties:
+    compatible:
+      contains:
+        enum:
+          - loongson,ls2k0300-i2c
+
+then:
+  required:
+    - clocks
+
 examples:
   - |
     #include <dt-bindings/interrupt-controller/irq.h>
-- 
2.47.3


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

* [PATCH v2 2/2] i2c: ls2x-v2: Add driver for Loongson-2K0300 I2C controller
  2026-01-27  2:47 [PATCH v2 0/2] i2c: Add Loongson-2K0300 I2C controller support Binbin Zhou
  2026-01-27  2:47 ` [PATCH v2 1/2] dt-bindings: i2c: loongson,ls2x: Add ls2k0300-i2c compatible Binbin Zhou
@ 2026-01-27  2:47 ` Binbin Zhou
  2026-01-27  8:18   ` Andy Shevchenko
  2026-01-28  4:15   ` Huacai Chen
  1 sibling, 2 replies; 10+ messages in thread
From: Binbin Zhou @ 2026-01-27  2:47 UTC (permalink / raw)
  To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Andi Shyti, Wolfram Sang, Andy Shevchenko,
	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 | 545 +++++++++++++++++++++++++++++++
 4 files changed, 557 insertions(+)
 create mode 100644 drivers/i2c/busses/i2c-ls2x-v2.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 5b11839cba9d..01fd37fdd29b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14783,6 +14783,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 09ba55bae1fa..d409401c9c94 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -857,6 +857,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..93d24c9482f0
--- /dev/null
+++ b/drivers/i2c/busses/i2c-ls2x-v2.c
@@ -0,0 +1,545 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * Loongson-2K fast I2C controller driver
+ *
+ * Copyright (C) 2025-2026 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
+
+#define LOONGSON2_I2C_MIN_STD_FREQ	2U
+#define LOONGSON2_I2C_MIN_FAST_FREQ	6U
+#define LOONGSON2_I2C_MAX_FREQ		46U
+#define HZ_TO_MHZ			1000000
+
+enum loongson2_i2c_speed {
+	LOONGSON2_I2C_SPEED_STANDARD, /* 100 kHz */
+	LOONGSON2_I2C_SPEED_FAST, /* 400 kHz */
+	LOONGSON2_I2C_SPEED_FAST_PLUS, /* 1 MHz */
+	LOONGSON2_I2C_SPEED_END,
+};
+
+/*
+ * 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
+ * @clk: hw i2c clock
+ * @complete: completion of I2C message
+ * @regmap: regmap of the I2C device
+ * @speed: I2C clock frequency of the controller. Standard or Fast are supported
+ * @parent_rate: I2C clock parent rate in MHz
+ * @msg: I2C transfer information
+ */
+struct loongson2_i2c_priv {
+	struct i2c_adapter adapter;
+	struct device *dev;
+	struct clk *clk;
+	struct completion complete;
+	struct regmap *regmap;
+	int speed;
+	int parent_rate;
+	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_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_write_msg(struct loongson2_i2c_priv *priv, u8 byte)
+{
+	regmap_write(priv->regmap, LOONGSON2_I2C_DR, byte);
+}
+
+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_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_write(struct loongson2_i2c_priv *priv)
+{
+	struct loongson2_i2c_msg *msg = &priv->msg;
+
+	if (msg->count) {
+		loongson2_i2c_write_msg(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_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_msg(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 int loongson2_i2c_adjust_bus_speed(struct loongson2_i2c_priv *priv)
+{
+	u32 val, freq, ccr = 0, cr2 = 0;
+
+	priv->parent_rate = clk_get_rate(priv->clk);
+	freq = DIV_ROUND_UP(priv->parent_rate, HZ_TO_MHZ);
+
+	cr2 |= FIELD_GET(LOONGSON2_I2C_CR2_FREQ, freq);
+	regmap_write(priv->regmap, LOONGSON2_I2C_CR2, cr2);
+
+	if (priv->speed == LOONGSON2_I2C_SPEED_STANDARD) {
+		val = DIV_ROUND_UP(priv->parent_rate, I2C_MAX_STANDARD_MODE_FREQ * 2);
+	} else {
+		val = DIV_ROUND_UP(priv->parent_rate, I2C_MAX_FAST_MODE_FREQ * 3);
+
+		/* Select Fast mode */
+		ccr |= LOONGSON2_I2C_CCR_FS;
+	}
+
+	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);
+
+	return 0;
+}
+
+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;
+	u32 clk_rate;
+	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),
+				     "Failed to ioremap resource.\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),
+				     "Failed to init regmap.\n");
+
+	priv->clk = devm_clk_get_enabled(dev, NULL);
+	if (IS_ERR(priv->clk))
+		return dev_err_probe(dev, PTR_ERR(priv->clk),
+				     "Failed  to enable clock.\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);
+
+	priv->speed = LOONGSON2_I2C_SPEED_STANDARD;
+	ret = of_property_read_u32(dev->of_node, "clock-frequency", &clk_rate);
+	if (!ret && clk_rate >= I2C_MAX_FAST_MODE_FREQ)
+		priv->speed = LOONGSON2_I2C_SPEED_FAST;
+
+	ret = loongson2_i2c_adjust_bus_speed(priv);
+	if (ret)
+		return ret;
+
+	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] 10+ messages in thread

* Re: [PATCH v2 2/2] i2c: ls2x-v2: Add driver for Loongson-2K0300 I2C controller
  2026-01-27  2:47 ` [PATCH v2 2/2] i2c: ls2x-v2: Add driver for Loongson-2K0300 I2C controller Binbin Zhou
@ 2026-01-27  8:18   ` Andy Shevchenko
  2026-01-29  8:07     ` Binbin Zhou
  2026-01-28  4:15   ` Huacai Chen
  1 sibling, 1 reply; 10+ messages in thread
From: Andy Shevchenko @ 2026-01-27  8:18 UTC (permalink / raw)
  To: Binbin Zhou
  Cc: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Andi Shyti, Wolfram Sang, Andy Shevchenko,
	linux-i2c, Huacai Chen, Xuerui Wang, loongarch, devicetree

On Tue, Jan 27, 2026 at 10:47:57AM +0800, Binbin Zhou 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.

Thanks for the update, but either v1 was not reviewed properly (if reviewed
at all), or this missed comments from there. This driver needs much more work.
Since it misses v6.20 anyway, you have about 2 month to put this into a shape.

...

> +/*
> + * Loongson-2K fast I2C controller driver
> + *
> + * Copyright (C) 2025-2026 Loongson Technology Corporation Limited

> + *

Redundant blank line.

> + */

...

> +#include <linux/bitfield.h>
> +#include <linux/bits.h>
> +#include <linux/clk.h>

> +#include <linux/device.h>

Is this being used?

> +#include <linux/iopoll.h>
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>

> +#include <linux/kernel.h>

No way you should include this one.

> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/property.h>
> +#include <linux/regmap.h>

Please, follow IWYU principle, exempli gratia here the types.h is missing.

> +#include <linux/units.h>

...

> +#define LOONGSON2_I2C_CR2_IRQ_MASK	(LOONGSON2_I2C_CR2_ITBUFEN | \
> +					 LOONGSON2_I2C_CR2_ITEVTEN | \
> +					 LOONGSON2_I2C_CR2_ITERREN)

Better to indent differently:

#define LOONGSON2_I2C_CR2_IRQ_MASK	\
	(LOONGSON2_I2C_CR2_ITBUFEN | LOONGSON2_I2C_CR2_ITEVTEN | LOONGSON2_I2C_CR2_ITERREN)


...

> +#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)

As per above.

...

> +#define LOONGSON2_I2C_FREE_SLEEP_US	1000
> +#define LOONGSON2_I2C_FREE_TIMEOUT_US	5000

1 * USEC_PER_MSEC
5 * USEC_PER_MSEC

...

> +#define LOONGSON2_I2C_MIN_STD_FREQ	2U
> +#define LOONGSON2_I2C_MIN_FAST_FREQ	6U
> +#define LOONGSON2_I2C_MAX_FREQ		46U

The three are unused, what are they for?


> +#define HZ_TO_MHZ			1000000

Dup. Use units.h.

...

> +enum loongson2_i2c_speed {
> +	LOONGSON2_I2C_SPEED_STANDARD, /* 100 kHz */
> +	LOONGSON2_I2C_SPEED_FAST, /* 400 kHz */
> +	LOONGSON2_I2C_SPEED_FAST_PLUS, /* 1 MHz */

Use existing speed definitions and drop this enum.

> +	LOONGSON2_I2C_SPEED_END,

Besides comma in the terminator, that has not to be there, this is unused.

> +};

...

> +/*

It's not marked as kernel-doc, but looks very much like that. Why?
Same Q for *all* cases like this.

> + * 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;

Run `pahole` and amend *all* data types accordingly.

> +};

...

> +struct loongson2_i2c_priv {
> +	struct i2c_adapter adapter;

> +	struct device *dev;

Isn't this a dup? Can't it be derived from regmap and/or adapter?

> +	struct clk *clk;
> +	struct completion complete;
> +	struct regmap *regmap;
> +	int speed;
> +	int parent_rate;
> +	struct loongson2_i2c_msg msg;
> +};

...

> +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;

Why?! What's wrong with the error code returned in ret?

> +	}
> +
> +	return ret;
> +}

...

> +static void loongson2_i2c_handle_read(struct loongson2_i2c_priv *priv, int flag)
> +{
> +	struct loongson2_i2c_msg *msg = &priv->msg;
> +	bool changed;

> +	int i;

Why signed?

> +	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);

Just unroll the loop and put a comment on top explaining the magic 2. But I
think it's just a msg->count.

> +		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 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;
> +	}

Even if the interrupt is spurious you still Ack it, why?

Ah, it seems it's a helper. Please, return the error code from it as int and
not irqreturn_t, this will make things clearer.

> +	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;

Split assignment...

> +	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;

...to be here, which improves readability (no need to go somewhere up in
the code to see what this is about.

> +	/* 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_msg(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;

Seems unneeded.

> +
> +	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;

	timeout = wait_for_completion_timeout(&priv->complete, priv->adapter.timeout);
	if (!timeout)
		return -ETIMEDOUT;

	return l_msg->result;

> +}

> +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;

Why is 'i' signed?
And redundant assignment for 'ret'.

> +	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;

	for (i = 0; i < num; i++) {
		ret = loongson2_i2c_xfer_msg(priv, &msgs[i], i == num - 1);
		if (ret < 0)
			return ret;
	}

	return num;


> +}

...

> +static int loongson2_i2c_adjust_bus_speed(struct loongson2_i2c_priv *priv)
> +{
> +	u32 val, freq, ccr = 0, cr2 = 0;

Redundant assignment(s), see below why.

> +	priv->parent_rate = clk_get_rate(priv->clk);
> +	freq = DIV_ROUND_UP(priv->parent_rate, HZ_TO_MHZ);

Use unit suffix:

	freq_mhz = ...

(if I am not mistaken).

> +	cr2 |= FIELD_GET(LOONGSON2_I2C_CR2_FREQ, freq);

	cr2 = FIELD_GET(LOONGSON2_I2C_CR2_FREQ, freq);

> +	regmap_write(priv->regmap, LOONGSON2_I2C_CR2, cr2);
> +
> +	if (priv->speed == LOONGSON2_I2C_SPEED_STANDARD) {
> +		val = DIV_ROUND_UP(priv->parent_rate, I2C_MAX_STANDARD_MODE_FREQ * 2);

		/* Select Standard mode */
		ccr = 0;

> +	} else {
> +		val = DIV_ROUND_UP(priv->parent_rate, I2C_MAX_FAST_MODE_FREQ * 3);
> +
> +		/* Select Fast mode */
> +		ccr |= LOONGSON2_I2C_CCR_FS;

		ccr = LOONGSON2_I2C_CCR_FS;

> +	}

> +	ccr |= FIELD_GET(LOONGSON2_I2C_CCR_CCR, val);

FIELD_MODIFY()?

> +	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);
> +
> +	return 0;
> +}

...

> +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;
> +	u32 clk_rate;
> +	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),
> +				     "Failed to ioremap resource.\n");

You utilise 100, put this on a single line.

But, this is a dup, devm_platform_*() call above takes care already about error
message.

> +
> +	priv->regmap = devm_regmap_init_mmio(dev, base,
> +					     &loongson2_i2c_regmap_config);

Single line.

> +	if (IS_ERR(priv->regmap))
> +		return dev_err_probe(dev, PTR_ERR(priv->regmap),
> +				     "Failed to init regmap.\n");

Single line.

> +	priv->clk = devm_clk_get_enabled(dev, NULL);
> +	if (IS_ERR(priv->clk))
> +		return dev_err_probe(dev, PTR_ERR(priv->clk),
> +				     "Failed  to enable clock.\n");

Single line.

> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0)
> +		return -EINVAL;

Why?! What's wrong with the error code in 'irq'?

> +	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));

2-arguments version is even better.

> +	init_completion(&priv->complete);
> +	platform_set_drvdata(pdev, priv);
> +
> +	priv->speed = LOONGSON2_I2C_SPEED_STANDARD;

> +	ret = of_property_read_u32(dev->of_node, "clock-frequency", &clk_rate);

device_property_read_u32()

> +	if (!ret && clk_rate >= I2C_MAX_FAST_MODE_FREQ)
> +		priv->speed = LOONGSON2_I2C_SPEED_FAST;
> +
> +	ret = loongson2_i2c_adjust_bus_speed(priv);
> +	if (ret)
> +		return ret;
> +
> +	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);

Dup message.

		return ret;

> +	return devm_i2c_add_adapter(dev, adap);
> +}

...

> +static struct platform_driver loongson2_i2c_driver = {
> +	.driver = {
> +		.name = "loongson2-i2c-v2",
> +		.of_match_table = loongson2_i2c_id_table,
> +	},
> +	.probe = loongson2_i2c_probe,
> +};

> +

Remove this blank line.

> +module_platform_driver(loongson2_i2c_driver);

-- 
With Best Regards,
Andy Shevchenko



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

* Re: [PATCH v2 2/2] i2c: ls2x-v2: Add driver for Loongson-2K0300 I2C controller
  2026-01-27  2:47 ` [PATCH v2 2/2] i2c: ls2x-v2: Add driver for Loongson-2K0300 I2C controller Binbin Zhou
  2026-01-27  8:18   ` Andy Shevchenko
@ 2026-01-28  4:15   ` Huacai Chen
  2026-01-28  9:08     ` Andy Shevchenko
  1 sibling, 1 reply; 10+ messages in thread
From: Huacai Chen @ 2026-01-28  4:15 UTC (permalink / raw)
  To: Binbin Zhou
  Cc: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Andi Shyti, Wolfram Sang, Andy Shevchenko,
	linux-i2c, Xuerui Wang, loongarch, devicetree

Hi, Binbin,

On Tue, Jan 27, 2026 at 10:48 AM 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 | 545 +++++++++++++++++++++++++++++++
>  4 files changed, 557 insertions(+)
>  create mode 100644 drivers/i2c/busses/i2c-ls2x-v2.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 5b11839cba9d..01fd37fdd29b 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14783,6 +14783,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
I  still think it is better to move this line below. Though it may
cause a checkpatch warning, but for checkpatch only errors must be
fixed, whether warnings need to be eliminated depends on the fact.

Huacai

>  F:     drivers/i2c/busses/i2c-ls2x.c
>
>  LOONGSON PWM DRIVER
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index 09ba55bae1fa..d409401c9c94 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -857,6 +857,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..93d24c9482f0
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-ls2x-v2.c
> @@ -0,0 +1,545 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +/*
> + * Loongson-2K fast I2C controller driver
> + *
> + * Copyright (C) 2025-2026 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
> +
> +#define LOONGSON2_I2C_MIN_STD_FREQ     2U
> +#define LOONGSON2_I2C_MIN_FAST_FREQ    6U
> +#define LOONGSON2_I2C_MAX_FREQ         46U
> +#define HZ_TO_MHZ                      1000000
> +
> +enum loongson2_i2c_speed {
> +       LOONGSON2_I2C_SPEED_STANDARD, /* 100 kHz */
> +       LOONGSON2_I2C_SPEED_FAST, /* 400 kHz */
> +       LOONGSON2_I2C_SPEED_FAST_PLUS, /* 1 MHz */
> +       LOONGSON2_I2C_SPEED_END,
> +};
> +
> +/*
> + * 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
> + * @clk: hw i2c clock
> + * @complete: completion of I2C message
> + * @regmap: regmap of the I2C device
> + * @speed: I2C clock frequency of the controller. Standard or Fast are supported
> + * @parent_rate: I2C clock parent rate in MHz
> + * @msg: I2C transfer information
> + */
> +struct loongson2_i2c_priv {
> +       struct i2c_adapter adapter;
> +       struct device *dev;
> +       struct clk *clk;
> +       struct completion complete;
> +       struct regmap *regmap;
> +       int speed;
> +       int parent_rate;
> +       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_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_write_msg(struct loongson2_i2c_priv *priv, u8 byte)
> +{
> +       regmap_write(priv->regmap, LOONGSON2_I2C_DR, byte);
> +}
> +
> +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_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_write(struct loongson2_i2c_priv *priv)
> +{
> +       struct loongson2_i2c_msg *msg = &priv->msg;
> +
> +       if (msg->count) {
> +               loongson2_i2c_write_msg(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_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_msg(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 int loongson2_i2c_adjust_bus_speed(struct loongson2_i2c_priv *priv)
> +{
> +       u32 val, freq, ccr = 0, cr2 = 0;
> +
> +       priv->parent_rate = clk_get_rate(priv->clk);
> +       freq = DIV_ROUND_UP(priv->parent_rate, HZ_TO_MHZ);
> +
> +       cr2 |= FIELD_GET(LOONGSON2_I2C_CR2_FREQ, freq);
> +       regmap_write(priv->regmap, LOONGSON2_I2C_CR2, cr2);
> +
> +       if (priv->speed == LOONGSON2_I2C_SPEED_STANDARD) {
> +               val = DIV_ROUND_UP(priv->parent_rate, I2C_MAX_STANDARD_MODE_FREQ * 2);
> +       } else {
> +               val = DIV_ROUND_UP(priv->parent_rate, I2C_MAX_FAST_MODE_FREQ * 3);
> +
> +               /* Select Fast mode */
> +               ccr |= LOONGSON2_I2C_CCR_FS;
> +       }
> +
> +       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);
> +
> +       return 0;
> +}
> +
> +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;
> +       u32 clk_rate;
> +       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),
> +                                    "Failed to ioremap resource.\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),
> +                                    "Failed to init regmap.\n");
> +
> +       priv->clk = devm_clk_get_enabled(dev, NULL);
> +       if (IS_ERR(priv->clk))
> +               return dev_err_probe(dev, PTR_ERR(priv->clk),
> +                                    "Failed  to enable clock.\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);
> +
> +       priv->speed = LOONGSON2_I2C_SPEED_STANDARD;
> +       ret = of_property_read_u32(dev->of_node, "clock-frequency", &clk_rate);
> +       if (!ret && clk_rate >= I2C_MAX_FAST_MODE_FREQ)
> +               priv->speed = LOONGSON2_I2C_SPEED_FAST;
> +
> +       ret = loongson2_i2c_adjust_bus_speed(priv);
> +       if (ret)
> +               return ret;
> +
> +       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] 10+ messages in thread

* Re: [PATCH v2 2/2] i2c: ls2x-v2: Add driver for Loongson-2K0300 I2C controller
  2026-01-28  4:15   ` Huacai Chen
@ 2026-01-28  9:08     ` Andy Shevchenko
  0 siblings, 0 replies; 10+ messages in thread
From: Andy Shevchenko @ 2026-01-28  9:08 UTC (permalink / raw)
  To: Huacai Chen
  Cc: Binbin Zhou, Binbin Zhou, Huacai Chen, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Andi Shyti, Wolfram Sang,
	Andy Shevchenko, linux-i2c, Xuerui Wang, loongarch, devicetree

On Wed, Jan 28, 2026 at 12:15:08PM +0800, Huacai Chen wrote:
> On Tue, Jan 27, 2026 at 10:48 AM Binbin Zhou <zhoubinbin@loongson.cn> wrote:

...

> >  F:     Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml
> > +F:     drivers/i2c/busses/i2c-ls2x-v2.c
> I  still think it is better to move this line below. Though it may
> cause a checkpatch warning, but for checkpatch only errors must be
> fixed, whether warnings need to be eliminated depends on the fact.
> 
> >  F:     drivers/i2c/busses/i2c-ls2x.c

This should be sorted, and

$ cat << EOF | LC_ALL=C sort
> Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml
> drivers/i2c/busses/i2c-ls2x-v2.c
> drivers/i2c/busses/i2c-ls2x.c

Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml
drivers/i2c/busses/i2c-ls2x-v2.c
drivers/i2c/busses/i2c-ls2x.c

The patch is correct AFAICS.

P.S> Remove the unneeded context when you do not comment on it,
do not make others to waste time on reading that.

-- 
With Best Regards,
Andy Shevchenko



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

* Re: [PATCH v2 2/2] i2c: ls2x-v2: Add driver for Loongson-2K0300 I2C controller
  2026-01-27  8:18   ` Andy Shevchenko
@ 2026-01-29  8:07     ` Binbin Zhou
  2026-01-29 14:38       ` Andy Shevchenko
  0 siblings, 1 reply; 10+ messages in thread
From: Binbin Zhou @ 2026-01-29  8:07 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Andi Shyti, Wolfram Sang, Andy Shevchenko,
	linux-i2c, Huacai Chen, Xuerui Wang, loongarch, devicetree

Hi Andy:

Thanks for your detailed review.

On Tue, Jan 27, 2026 at 4:18 PM Andy Shevchenko
<andriy.shevchenko@intel.com> wrote:
>
> On Tue, Jan 27, 2026 at 10:47:57AM +0800, Binbin Zhou 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.
>
> Thanks for the update, but either v1 was not reviewed properly (if reviewed
> at all), or this missed comments from there. This driver needs much more work.
> Since it misses v6.20 anyway, you have about 2 month to put this into a shape.
>
> ...
>
> > +/*
> > + * Loongson-2K fast I2C controller driver
> > + *
> > + * Copyright (C) 2025-2026 Loongson Technology Corporation Limited
>
> > + *
>
> Redundant blank line.

ok..
>
> > + */
>
> ...
>
> > +#include <linux/bitfield.h>
> > +#include <linux/bits.h>
> > +#include <linux/clk.h>
>
> > +#include <linux/device.h>
>
> Is this being used?
>
> > +#include <linux/iopoll.h>
> > +#include <linux/i2c.h>
> > +#include <linux/init.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/io.h>
>
> > +#include <linux/kernel.h>
>
> No way you should include this one.
>
> > +#include <linux/module.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/property.h>
> > +#include <linux/regmap.h>
>
> Please, follow IWYU principle, exempli gratia here the types.h is missing.

I will check the included header files.
>
> > +#include <linux/units.h>
>
> ...
>
> > +#define LOONGSON2_I2C_CR2_IRQ_MASK   (LOONGSON2_I2C_CR2_ITBUFEN | \
> > +                                      LOONGSON2_I2C_CR2_ITEVTEN | \
> > +                                      LOONGSON2_I2C_CR2_ITERREN)
>
> Better to indent differently:
>
> #define LOONGSON2_I2C_CR2_IRQ_MASK      \
>         (LOONGSON2_I2C_CR2_ITBUFEN | LOONGSON2_I2C_CR2_ITEVTEN | LOONGSON2_I2C_CR2_ITERREN)

ok. I will check.
>
>
> ...
>
> > +#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)
>
> As per above.
>
> ...
>
> > +#define LOONGSON2_I2C_FREE_SLEEP_US  1000
> > +#define LOONGSON2_I2C_FREE_TIMEOUT_US        5000
>
> 1 * USEC_PER_MSEC
> 5 * USEC_PER_MSEC

ok...
>
> ...
>
> > +#define LOONGSON2_I2C_MIN_STD_FREQ   2U
> > +#define LOONGSON2_I2C_MIN_FAST_FREQ  6U
> > +#define LOONGSON2_I2C_MAX_FREQ               46U
>
> The three are unused, what are they for?

I will drop it.
>
>
> > +#define HZ_TO_MHZ                    1000000
>
> Dup. Use units.h.
>
> ...
>
> > +enum loongson2_i2c_speed {
> > +     LOONGSON2_I2C_SPEED_STANDARD, /* 100 kHz */
> > +     LOONGSON2_I2C_SPEED_FAST, /* 400 kHz */
> > +     LOONGSON2_I2C_SPEED_FAST_PLUS, /* 1 MHz */
>
> Use existing speed definitions and drop this enum.

ok....
>
> > +     LOONGSON2_I2C_SPEED_END,
>
> Besides comma in the terminator, that has not to be there, this is unused.
>
> > +};
>
> ...
>
> > +/*
>
> It's not marked as kernel-doc, but looks very much like that. Why?
> Same Q for *all* cases like this.

I didn't intend to mark it in kernel-doc; I just wanted to comment on
the variable.
Is removing the `@` symbol sufficient?

>
> > + * 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;
>
> Run `pahole` and amend *all* data types accordingly.

 Moving 'stop' from after 'buf' to after 'addr'.
>
> > +};
>
> ...
>
> > +struct loongson2_i2c_priv {
> > +     struct i2c_adapter adapter;
>
> > +     struct device *dev;
>
> Isn't this a dup? Can't it be derived from regmap and/or adapter?

 it can be converted using `struct device *dev = priv->adapter.dev.parent;`
>
> > +     struct clk *clk;
> > +     struct completion complete;
> > +     struct regmap *regmap;
> > +     int speed;
> > +     int parent_rate;
> > +     struct loongson2_i2c_msg msg;
> > +};
>
> ...

After run `pahole`, the structs are reorganized as follow:

pahole --show_reorg_steps --reorganize --sort -C loongson2_i2c_priv
i2c-ls2x-v2.o
struct loongson2_i2c_priv {
        struct i2c_adapter         adapter
__attribute__((__aligned__(8))); /*     0  1064 */
        /* --- cacheline 16 boundary (1024 bytes) was 40 bytes ago ---
*/
        struct clk *               clk;                  /*  1064
8 */
        struct completion          complete;             /*  1072
32 */
        /* --- cacheline 17 boundary (1088 bytes) was 16 bytes ago ---
*/
        struct regmap *            regmap;               /*  1104
8 */
        int                        speed;                /*  1112
4 */
        int                        parent_rate;          /*  1116
4 */
        struct loongson2_i2c_msg   msg;                  /*  1120
24 */

        /* XXX last struct has 4 bytes of padding */

        /* size: 1144, cachelines: 18, members: 7 */
        /* paddings: 1, sum paddings: 4 */
        /* forced alignments: 1 */
        /* last cacheline: 56 bytes */
} __attribute__((__aligned__(8)));

pahole --show_reorg_steps --reorganize --sort -C loongson2_i2c_msg
i2c-ls2x-v2.o
struct loongson2_i2c_msg {
        u8                         addr;                 /*     0
1 */
        bool                       stop;                 /*     1
1 */

        /* XXX 2 bytes hole, try to pack */

        u32                        count;                /*     4
4 */
        u8 *                       buf;                  /*     8
8 */
        int                        result;               /*    16
4 */

        /* size: 24, cachelines: 1, members: 5 */
        /* sum members: 18, holes: 1, sum holes: 2 */
        /* padding: 4 */
        /* last cacheline: 24 bytes */
};

>
> > +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;
>
> Why?! What's wrong with the error code returned in ret?

I want to indicate the bus busy state if it times out.
>
> > +     }
> > +
> > +     return ret;
> > +}
>
> ...
>
> > +static void loongson2_i2c_handle_read(struct loongson2_i2c_priv *priv, int flag)
> > +{
> > +     struct loongson2_i2c_msg *msg = &priv->msg;
> > +     bool changed;
>
> > +     int i;
>
> Why signed?

unsigned 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);
>
> Just unroll the loop and put a comment on top explaining the magic 2. But I
> think it's just a msg->count.

Yes it's  msg->count.
>
> > +             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 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;
> > +     }
>
> Even if the interrupt is spurious you still Ack it, why?
>
> Ah, it seems it's a helper. Please, return the error code from it as int and
> not irqreturn_t, this will make things clearer.

How about just define it as void:

static void loongson2_i2c_isr_error(u32 status, void *data)
{
.........
        if (status & LOONGSON2_I2C_SR1_ARLO) {
         ........
                msg->result = -EAGAIN;
                goto out;
        }
        if (status & LOONGSON2_I2C_SR1_AF) {
           .......
                msg->result = -EIO;
                goto out;
        }
        if (status & LOONGSON2_I2C_SR1_BERR) {
         .........
                msg->result = -EIO;
                goto out;
        }

out:
        loongson2_i2c_disable_irq(priv);
        complete(&priv->complete);
}

and in loongson2_i2c_isr_event(), I reference it as:

        if (status & LOONGSON2_I2C_SR1_ITERREN_MASK) {
                loongson2_i2c_isr_error(status, data);
                return IRQ_NONE;
        }

Its return value is meaningless for loongson2_i2c_isr_event().

>
> > +     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;
>
> Split assignment...
>
> > +     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;
>
> ...to be here, which improves readability (no need to go somewhere up in
> the code to see what this is about.

ok, I will put  `possible_status = LOONGSON2_I2C_SR1_ITEVTEN_MASK;` here.

>
> > +     /* 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_msg(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;
>
> Seems unneeded.

emm, I will drop it.
>
> > +
> > +     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;
>
>         timeout = wait_for_completion_timeout(&priv->complete, priv->adapter.timeout);
>         if (!timeout)
>                 return -ETIMEDOUT;
>
>         return l_msg->result;
>
> > +}
>
> > +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;
>
> Why is 'i' signed?
> And redundant assignment for 'ret'.

unsigned int i;
int ret;
>
> > +     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;
>
>         for (i = 0; i < num; i++) {
>                 ret = loongson2_i2c_xfer_msg(priv, &msgs[i], i == num - 1);
>                 if (ret < 0)
>                         return ret;
>         }
>
>         return num;

Yes, this way will be more readable.
>
>
> > +}
>
> ...
>
> > +static int loongson2_i2c_adjust_bus_speed(struct loongson2_i2c_priv *priv)
> > +{
> > +     u32 val, freq, ccr = 0, cr2 = 0;
>
> Redundant assignment(s), see below why.
>
> > +     priv->parent_rate = clk_get_rate(priv->clk);
> > +     freq = DIV_ROUND_UP(priv->parent_rate, HZ_TO_MHZ);
>
> Use unit suffix:
>
>         freq_mhz = ...
>
> (if I am not mistaken).

Yes, `freq_mhz` is more appropriate.
>
> > +     cr2 |= FIELD_GET(LOONGSON2_I2C_CR2_FREQ, freq);
>
>         cr2 = FIELD_GET(LOONGSON2_I2C_CR2_FREQ, freq);

ok..
>
> > +     regmap_write(priv->regmap, LOONGSON2_I2C_CR2, cr2);
> > +
> > +     if (priv->speed == LOONGSON2_I2C_SPEED_STANDARD) {
> > +             val = DIV_ROUND_UP(priv->parent_rate, I2C_MAX_STANDARD_MODE_FREQ * 2);
>
>                 /* Select Standard mode */
>                 ccr = 0;
>
> > +     } else {
> > +             val = DIV_ROUND_UP(priv->parent_rate, I2C_MAX_FAST_MODE_FREQ * 3);
> > +
> > +             /* Select Fast mode */
> > +             ccr |= LOONGSON2_I2C_CCR_FS;
>
>                 ccr = LOONGSON2_I2C_CCR_FS;
>
> > +     }
>
> > +     ccr |= FIELD_GET(LOONGSON2_I2C_CCR_CCR, val);
>
> FIELD_MODIFY()?

FIELD_MODIFY(LOONGSON2_I2C_CCR_CCR, &ccr, val);  instead.
>
> > +     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);
> > +
> > +     return 0;
> > +}
>
> ...
>
> > +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;
> > +     u32 clk_rate;
> > +     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),
> > +                                  "Failed to ioremap resource.\n");
>
> You utilise 100, put this on a single line.

ok. I will check the issue again.
>
> But, this is a dup, devm_platform_*() call above takes care already about error
> message.

emm. I will use `return PTR_ERR(base);` instead.
>
> > +
> > +     priv->regmap = devm_regmap_init_mmio(dev, base,
> > +                                          &loongson2_i2c_regmap_config);
>
> Single line.
>
> > +     if (IS_ERR(priv->regmap))
> > +             return dev_err_probe(dev, PTR_ERR(priv->regmap),
> > +                                  "Failed to init regmap.\n");
>
> Single line.
>
> > +     priv->clk = devm_clk_get_enabled(dev, NULL);
> > +     if (IS_ERR(priv->clk))
> > +             return dev_err_probe(dev, PTR_ERR(priv->clk),
> > +                                  "Failed  to enable clock.\n");
>
> Single line.
>
> > +     irq = platform_get_irq(pdev, 0);
> > +     if (irq < 0)
> > +             return -EINVAL;
>
> Why?! What's wrong with the error code in 'irq'?

I will use `return irq` instead.
>
> > +     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));
>
> 2-arguments version is even better.

Sorry, I'm not quite sure what you mean by `2-arguments version.`

>
> > +     init_completion(&priv->complete);
> > +     platform_set_drvdata(pdev, priv);
> > +
> > +     priv->speed = LOONGSON2_I2C_SPEED_STANDARD;
>
> > +     ret = of_property_read_u32(dev->of_node, "clock-frequency", &clk_rate);
>
> device_property_read_u32()

ok...
>
> > +     if (!ret && clk_rate >= I2C_MAX_FAST_MODE_FREQ)
> > +             priv->speed = LOONGSON2_I2C_SPEED_FAST;
> > +
> > +     ret = loongson2_i2c_adjust_bus_speed(priv);
> > +     if (ret)
> > +             return ret;
> > +
> > +     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);
>
> Dup message.

ok...
>
>                 return ret;
>
> > +     return devm_i2c_add_adapter(dev, adap);
> > +}
>
> ...
>
> > +static struct platform_driver loongson2_i2c_driver = {
> > +     .driver = {
> > +             .name = "loongson2-i2c-v2",
> > +             .of_match_table = loongson2_i2c_id_table,
> > +     },
> > +     .probe = loongson2_i2c_probe,
> > +};
>
> > +
>
> Remove this blank line.

ok...
>
> > +module_platform_driver(loongson2_i2c_driver);
>
> --
> With Best Regards,
> Andy Shevchenko
>
>


--
Thanks.
Binbin

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

* Re: [PATCH v2 2/2] i2c: ls2x-v2: Add driver for Loongson-2K0300 I2C controller
  2026-01-29  8:07     ` Binbin Zhou
@ 2026-01-29 14:38       ` Andy Shevchenko
  2026-02-02  6:39         ` Binbin Zhou
  0 siblings, 1 reply; 10+ messages in thread
From: Andy Shevchenko @ 2026-01-29 14:38 UTC (permalink / raw)
  To: Binbin Zhou
  Cc: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Andi Shyti, Wolfram Sang, Andy Shevchenko,
	linux-i2c, Huacai Chen, Xuerui Wang, loongarch, devicetree

On Thu, Jan 29, 2026 at 04:07:06PM +0800, Binbin Zhou wrote:
> On Tue, Jan 27, 2026 at 4:18 PM Andy Shevchenko
> <andriy.shevchenko@intel.com> wrote:
> > On Tue, Jan 27, 2026 at 10:47:57AM +0800, Binbin Zhou wrote:

...

> > > +/*
> >
> > It's not marked as kernel-doc, but looks very much like that. Why?
> > Same Q for *all* cases like this.
> 
> I didn't intend to mark it in kernel-doc; I just wanted to comment on
> the variable.
> Is removing the `@` symbol sufficient?

But why? If you want it being properly documented, make sure it follows
the regular format.

> > > + * 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;
> >
> > Run `pahole` and amend *all* data types accordingly.
> 
>  Moving 'stop' from after 'buf' to after 'addr'.
> >
> > > +};

...

> After run `pahole`, the structs are reorganized as follow:
> 
> pahole --show_reorg_steps --reorganize --sort -C loongson2_i2c_priv
> i2c-ls2x-v2.o
> struct loongson2_i2c_priv {
>         struct i2c_adapter         adapter
> __attribute__((__aligned__(8))); /*     0  1064 */
>         /* --- cacheline 16 boundary (1024 bytes) was 40 bytes ago ---
> */
>         struct clk *               clk;                  /*  1064
> 8 */
>         struct completion          complete;             /*  1072
> 32 */
>         /* --- cacheline 17 boundary (1088 bytes) was 16 bytes ago ---
> */
>         struct regmap *            regmap;               /*  1104

It's better to keep pointers like clk and regmap next to each other.
They are semantically coupled as "resources".

> 8 */
>         int                        speed;                /*  1112
> 4 */
>         int                        parent_rate;          /*  1116
> 4 */
>         struct loongson2_i2c_msg   msg;                  /*  1120
> 24 */
> 
>         /* XXX last struct has 4 bytes of padding */
> 
>         /* size: 1144, cachelines: 18, members: 7 */
>         /* paddings: 1, sum paddings: 4 */
>         /* forced alignments: 1 */
>         /* last cacheline: 56 bytes */
> } __attribute__((__aligned__(8)));

...

> pahole --show_reorg_steps --reorganize --sort -C loongson2_i2c_msg
> i2c-ls2x-v2.o
> struct loongson2_i2c_msg {
>         u8                         addr;                 /*     0
> 1 */
>         bool                       stop;                 /*     1
> 1 */
> 
>         /* XXX 2 bytes hole, try to pack */

>         u32                        count;                /*     4
> 4 */
>         u8 *                       buf;                  /*     8
> 8 */

Also think about it (don't blindly follow the `pahole` automatic mode).
This is much better if you move the pointer to be the first, followed
by a count, result, and others.

>         int                        result;               /*    16
> 4 */
> 
>         /* size: 24, cachelines: 1, members: 5 */
>         /* sum members: 18, holes: 1, sum holes: 2 */
>         /* padding: 4 */
>         /* last cacheline: 24 bytes */
> };

TL;DR: don't use `pahole` blindly. Use the common sense.

...

> > > +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;
> >
> > Why?! What's wrong with the error code returned in ret?
> 
> I want to indicate the bus busy state if it times out.

This is not an answer. So, why do you remap error code. What's wrong with
the callee's one?

> > > +     }
> > > +
> > > +     return ret;
> > > +}

...

> > Ah, it seems it's a helper. Please, return the error code from it as int and
> > not irqreturn_t, this will make things clearer.
> 
> How about just define it as void:
> 
> static void loongson2_i2c_isr_error(u32 status, void *data)
> {
> .........
>         if (status & LOONGSON2_I2C_SR1_ARLO) {
>          ........
>                 msg->result = -EAGAIN;
>                 goto out;
>         }
>         if (status & LOONGSON2_I2C_SR1_AF) {
>            .......
>                 msg->result = -EIO;
>                 goto out;
>         }
>         if (status & LOONGSON2_I2C_SR1_BERR) {
>          .........
>                 msg->result = -EIO;
>                 goto out;
>         }
> 
> out:
>         loongson2_i2c_disable_irq(priv);
>         complete(&priv->complete);
> }
> 
> and in loongson2_i2c_isr_event(), I reference it as:
> 
>         if (status & LOONGSON2_I2C_SR1_ITERREN_MASK) {
>                 loongson2_i2c_isr_error(status, data);
>                 return IRQ_NONE;
>         }

> Its return value is meaningless for loongson2_i2c_isr_event().

Works for me.

...

> > > +     u32 possible_status = LOONGSON2_I2C_SR1_ITEVTEN_MASK;
> >
> > Split assignment...

> > ...to be here, which improves readability (no need to go somewhere up in
> > the code to see what this is about.
> 
> ok, I will put  `possible_status = LOONGSON2_I2C_SR1_ITEVTEN_MASK;` here.
> 
> > > +     /* Update possible_status if buffer interrupt is enabled */
> > > +     if (ien & LOONGSON2_I2C_CR2_ITBUFEN)
> > > +             possible_status |= LOONGSON2_I2C_SR1_ITBUFEN_MASK;

I expect something like this:

		/* Update possible_status if buffer interrupt is enabled */
		possible_status = LOONGSON2_I2C_SR1_ITEVTEN_MASK;
		if (ien & LOONGSON2_I2C_CR2_ITBUFEN)
			possible_status |= LOONGSON2_I2C_SR1_ITBUFEN_MASK;

...

> > > +     strscpy(adap->name, pdev->name, sizeof(adap->name));
> >
> > 2-arguments version is even better.
> 
> Sorry, I'm not quite sure what you mean by `2-arguments version.`

Use 2 arguments instead of 3. I.o.w. just drop the third argument.

-- 
With Best Regards,
Andy Shevchenko



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

* Re: [PATCH v2 2/2] i2c: ls2x-v2: Add driver for Loongson-2K0300 I2C controller
  2026-01-29 14:38       ` Andy Shevchenko
@ 2026-02-02  6:39         ` Binbin Zhou
  2026-02-03  9:53           ` Andy Shevchenko
  0 siblings, 1 reply; 10+ messages in thread
From: Binbin Zhou @ 2026-02-02  6:39 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Andi Shyti, Wolfram Sang, Andy Shevchenko,
	linux-i2c, Huacai Chen, Xuerui Wang, loongarch, devicetree

Hi Andy:

Thanks for your patient reply.

On Thu, Jan 29, 2026 at 10:38 PM Andy Shevchenko
<andriy.shevchenko@intel.com> wrote:
>
> On Thu, Jan 29, 2026 at 04:07:06PM +0800, Binbin Zhou wrote:
> > On Tue, Jan 27, 2026 at 4:18 PM Andy Shevchenko
> > <andriy.shevchenko@intel.com> wrote:
> > > On Tue, Jan 27, 2026 at 10:47:57AM +0800, Binbin Zhou wrote:
>
> ...
>
> > > > +/*
> > >
> > > It's not marked as kernel-doc, but looks very much like that. Why?
> > > Same Q for *all* cases like this.
> >
> > I didn't intend to mark it in kernel-doc; I just wanted to comment on
> > the variable.
> > Is removing the `@` symbol sufficient?
>
> But why? If you want it being properly documented, make sure it follows
> the regular format.

I sincerely apologize for not having read `kernel-doc.rst` earlier.
`kernel-doc` comments use `/**` as its opening marker. I will take
this time to learn the documentation.

>
> > > > + * 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;
> > >
> > > Run `pahole` and amend *all* data types accordingly.
> >
> >  Moving 'stop' from after 'buf' to after 'addr'.
> > >
> > > > +};
>
> ...
>
> > After run `pahole`, the structs are reorganized as follow:
> >
> > pahole --show_reorg_steps --reorganize --sort -C loongson2_i2c_priv
> > i2c-ls2x-v2.o
> > struct loongson2_i2c_priv {
> >         struct i2c_adapter         adapter
> > __attribute__((__aligned__(8))); /*     0  1064 */
> >         /* --- cacheline 16 boundary (1024 bytes) was 40 bytes ago ---
> > */
> >         struct clk *               clk;                  /*  1064
> > 8 */
> >         struct completion          complete;             /*  1072
> > 32 */
> >         /* --- cacheline 17 boundary (1088 bytes) was 16 bytes ago ---
> > */
> >         struct regmap *            regmap;               /*  1104
>
> It's better to keep pointers like clk and regmap next to each other.
> They are semantically coupled as "resources".
>
> > 8 */
> >         int                        speed;                /*  1112
> > 4 */
> >         int                        parent_rate;          /*  1116
> > 4 */
> >         struct loongson2_i2c_msg   msg;                  /*  1120
> > 24 */
> >
> >         /* XXX last struct has 4 bytes of padding */
> >
> >         /* size: 1144, cachelines: 18, members: 7 */
> >         /* paddings: 1, sum paddings: 4 */
> >         /* forced alignments: 1 */
> >         /* last cacheline: 56 bytes */
> > } __attribute__((__aligned__(8)));
>
> ...
>
> > pahole --show_reorg_steps --reorganize --sort -C loongson2_i2c_msg
> > i2c-ls2x-v2.o
> > struct loongson2_i2c_msg {
> >         u8                         addr;                 /*     0
> > 1 */
> >         bool                       stop;                 /*     1
> > 1 */
> >
> >         /* XXX 2 bytes hole, try to pack */
>
> >         u32                        count;                /*     4
> > 4 */
> >         u8 *                       buf;                  /*     8
> > 8 */
>
> Also think about it (don't blindly follow the `pahole` automatic mode).
> This is much better if you move the pointer to be the first, followed
> by a count, result, and others.
>
> >         int                        result;               /*    16
> > 4 */
> >
> >         /* size: 24, cachelines: 1, members: 5 */
> >         /* sum members: 18, holes: 1, sum holes: 2 */
> >         /* padding: 4 */
> >         /* last cacheline: 24 bytes */
> > };
>
> TL;DR: don't use `pahole` blindly. Use the common sense.
>

Sorry, I slacked off.
How about the organized data structure as follows:

struct loongson2_i2c_msg {
        u8      *buf;
        u32     count;
        int     result;
        u8      addr;
        bool    stop;
};

struct loongson2_i2c_priv {
        struct i2c_adapter              adapter;
        struct completion               complete;
        struct clk                      *clk;
        struct regmap                   *regmap;
        int                             speed;
        int                             parent_rate;
        struct loongson2_i2c_msg        msg;
};

> ...
>
> > > > +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;
> > >
> > > Why?! What's wrong with the error code returned in ret?
> >
> > I want to indicate the bus busy state if it times out.
>
> This is not an answer. So, why do you remap error code. What's wrong with
> the callee's one?

I examined the existing callee and found no special checks for
`-EBUSY`. Using the return value of `regmap_read_poll_timeout()`
directly is the more appropriate choice.

Additionally, `loongson2_i2c_wait_free_bus()` is only called by
`loongson2_i2c_xfer()`. Its separate definition does not appear
essential. Calling `regmap_read_poll_timeout()` directly within
`loongson2_i2c_xfer()` seems more concise.

As follows:

static int loongson2_i2c_xfer(struct i2c_adapter *i2c_adap, struct
i2c_msg msgs[], int num)
{
............

        /* Wait I2C bus free */
        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(dev, "I2C bus free failed.\n");
                return ret;
        }

        /* Start generation */
        regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1,
LOONGSON2_I2C_CR1_START,
                           LOONGSON2_I2C_CR1_START);

.......

        return num;
}

>
> > > > +     }
> > > > +
> > > > +     return ret;
> > > > +}
>
> ...
>
> > > Ah, it seems it's a helper. Please, return the error code from it as int and
> > > not irqreturn_t, this will make things clearer.
> >
> > How about just define it as void:
> >
> > static void loongson2_i2c_isr_error(u32 status, void *data)
> > {
> > .........
> >         if (status & LOONGSON2_I2C_SR1_ARLO) {
> >          ........
> >                 msg->result = -EAGAIN;
> >                 goto out;
> >         }
> >         if (status & LOONGSON2_I2C_SR1_AF) {
> >            .......
> >                 msg->result = -EIO;
> >                 goto out;
> >         }
> >         if (status & LOONGSON2_I2C_SR1_BERR) {
> >          .........
> >                 msg->result = -EIO;
> >                 goto out;
> >         }
> >
> > out:
> >         loongson2_i2c_disable_irq(priv);
> >         complete(&priv->complete);
> > }
> >
> > and in loongson2_i2c_isr_event(), I reference it as:
> >
> >         if (status & LOONGSON2_I2C_SR1_ITERREN_MASK) {
> >                 loongson2_i2c_isr_error(status, data);
> >                 return IRQ_NONE;
> >         }
>
> > Its return value is meaningless for loongson2_i2c_isr_event().
>
> Works for me.
>
> ...
>
> > > > +     u32 possible_status = LOONGSON2_I2C_SR1_ITEVTEN_MASK;
> > >
> > > Split assignment...
>
> > > ...to be here, which improves readability (no need to go somewhere up in
> > > the code to see what this is about.
> >
> > ok, I will put  `possible_status = LOONGSON2_I2C_SR1_ITEVTEN_MASK;` here.
> >
> > > > +     /* Update possible_status if buffer interrupt is enabled */
> > > > +     if (ien & LOONGSON2_I2C_CR2_ITBUFEN)
> > > > +             possible_status |= LOONGSON2_I2C_SR1_ITBUFEN_MASK;
>
> I expect something like this:
>
>                 /* Update possible_status if buffer interrupt is enabled */
>                 possible_status = LOONGSON2_I2C_SR1_ITEVTEN_MASK;
>                 if (ien & LOONGSON2_I2C_CR2_ITBUFEN)
>                         possible_status |= LOONGSON2_I2C_SR1_ITBUFEN_MASK;

ok...
>
> ...
>
> > > > +     strscpy(adap->name, pdev->name, sizeof(adap->name));
> > >
> > > 2-arguments version is even better.
> >
> > Sorry, I'm not quite sure what you mean by `2-arguments version.`
>
> Use 2 arguments instead of 3. I.o.w. just drop the third argument.

Ah, I see, the third argument is optional.

>
> --
> With Best Regards,
> Andy Shevchenko
>
>

--
Thanks.
Binbin

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

* Re: [PATCH v2 2/2] i2c: ls2x-v2: Add driver for Loongson-2K0300 I2C controller
  2026-02-02  6:39         ` Binbin Zhou
@ 2026-02-03  9:53           ` Andy Shevchenko
  0 siblings, 0 replies; 10+ messages in thread
From: Andy Shevchenko @ 2026-02-03  9:53 UTC (permalink / raw)
  To: Binbin Zhou
  Cc: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Andi Shyti, Wolfram Sang, Andy Shevchenko,
	linux-i2c, Huacai Chen, Xuerui Wang, loongarch, devicetree

On Mon, Feb 02, 2026 at 02:39:28PM +0800, Binbin Zhou wrote:
> On Thu, Jan 29, 2026 at 10:38 PM Andy Shevchenko
> <andriy.shevchenko@intel.com> wrote:
> > On Thu, Jan 29, 2026 at 04:07:06PM +0800, Binbin Zhou wrote:
> > > On Tue, Jan 27, 2026 at 4:18 PM Andy Shevchenko
> > > <andriy.shevchenko@intel.com> wrote:
> > > > On Tue, Jan 27, 2026 at 10:47:57AM +0800, Binbin Zhou wrote:

...

> > > After run `pahole`, the structs are reorganized as follow:
> > >
> > > pahole --show_reorg_steps --reorganize --sort -C loongson2_i2c_priv
> > > i2c-ls2x-v2.o
> > > struct loongson2_i2c_priv {
> > >         struct i2c_adapter         adapter
> > > __attribute__((__aligned__(8))); /*     0  1064 */
> > >         /* --- cacheline 16 boundary (1024 bytes) was 40 bytes ago ---
> > > */
> > >         struct clk *               clk;                  /*  1064
> > > 8 */
> > >         struct completion          complete;             /*  1072
> > > 32 */
> > >         /* --- cacheline 17 boundary (1088 bytes) was 16 bytes ago ---
> > > */
> > >         struct regmap *            regmap;               /*  1104
> >
> > It's better to keep pointers like clk and regmap next to each other.
> > They are semantically coupled as "resources".
> >
> > > 8 */
> > >         int                        speed;                /*  1112
> > > 4 */
> > >         int                        parent_rate;          /*  1116
> > > 4 */
> > >         struct loongson2_i2c_msg   msg;                  /*  1120
> > > 24 */
> > >
> > >         /* XXX last struct has 4 bytes of padding */
> > >
> > >         /* size: 1144, cachelines: 18, members: 7 */
> > >         /* paddings: 1, sum paddings: 4 */
> > >         /* forced alignments: 1 */
> > >         /* last cacheline: 56 bytes */
> > > } __attribute__((__aligned__(8)));
> >
> > ...
> >
> > > pahole --show_reorg_steps --reorganize --sort -C loongson2_i2c_msg
> > > i2c-ls2x-v2.o
> > > struct loongson2_i2c_msg {
> > >         u8                         addr;                 /*     0
> > > 1 */
> > >         bool                       stop;                 /*     1
> > > 1 */
> > >
> > >         /* XXX 2 bytes hole, try to pack */
> >
> > >         u32                        count;                /*     4
> > > 4 */
> > >         u8 *                       buf;                  /*     8
> > > 8 */
> >
> > Also think about it (don't blindly follow the `pahole` automatic mode).
> > This is much better if you move the pointer to be the first, followed
> > by a count, result, and others.
> >
> > >         int                        result;               /*    16
> > > 4 */
> > >
> > >         /* size: 24, cachelines: 1, members: 5 */
> > >         /* sum members: 18, holes: 1, sum holes: 2 */
> > >         /* padding: 4 */
> > >         /* last cacheline: 24 bytes */
> > > };
> >
> > TL;DR: don't use `pahole` blindly. Use the common sense.
> 
> Sorry, I slacked off.
> How about the organized data structure as follows:
> 
> struct loongson2_i2c_msg {
>         u8      *buf;
>         u32     count;
>         int     result;
>         u8      addr;
>         bool    stop;
> };
> 
> struct loongson2_i2c_priv {
>         struct i2c_adapter              adapter;
>         struct completion               complete;
>         struct clk                      *clk;
>         struct regmap                   *regmap;
>         int                             speed;
>         int                             parent_rate;
>         struct loongson2_i2c_msg        msg;
> };

At the first glance looks okay to me.

-- 
With Best Regards,
Andy Shevchenko



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

end of thread, other threads:[~2026-02-03  9:53 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-27  2:47 [PATCH v2 0/2] i2c: Add Loongson-2K0300 I2C controller support Binbin Zhou
2026-01-27  2:47 ` [PATCH v2 1/2] dt-bindings: i2c: loongson,ls2x: Add ls2k0300-i2c compatible Binbin Zhou
2026-01-27  2:47 ` [PATCH v2 2/2] i2c: ls2x-v2: Add driver for Loongson-2K0300 I2C controller Binbin Zhou
2026-01-27  8:18   ` Andy Shevchenko
2026-01-29  8:07     ` Binbin Zhou
2026-01-29 14:38       ` Andy Shevchenko
2026-02-02  6:39         ` Binbin Zhou
2026-02-03  9:53           ` Andy Shevchenko
2026-01-28  4:15   ` Huacai Chen
2026-01-28  9:08     ` Andy Shevchenko

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox