* [PATCH 0/2] i2c: Add Loongson-2K0300 I2C controller support
@ 2025-11-13 8:48 Binbin Zhou
2025-11-13 8:48 ` [PATCH 1/2] dt-bindings: i2c: loongson,ls2x: Add ls2k0300-i2c compatible Binbin Zhou
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: Binbin Zhou @ 2025-11-13 8:48 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Andi Shyti, Wolfram Sang, linux-i2c
Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, Binbin Zhou
Hi all:
This patch set describes the I2C controller integrated into the Loongson-2K0300 chip.
It has a significantly different design from the previous I2C controller (i2c-ls2x),
such as support for master-slave transfer mode, and DMA transfers (implementation
in progress), etc. Therefore, we try to name it i2c-ls2x-v2.
Therefore we try to name it i2c-ls2x-v2.
Thanks.
Binbin Zhou (2):
dt-bindings: i2c: loongson,ls2x: Add ls2k0300-i2c compatible
i2c: ls2x-v2: Add driver for Loongson-2K0300 I2C controller
.../bindings/i2c/loongson,ls2x-i2c.yaml | 1 +
MAINTAINERS | 1 +
drivers/i2c/busses/Kconfig | 10 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-ls2x-v2.c | 513 ++++++++++++++++++
5 files changed, 526 insertions(+)
create mode 100644 drivers/i2c/busses/i2c-ls2x-v2.c
base-commit: e9a6fb0bcdd7609be6969112f3fbfcce3b1d4a7c
--
2.47.3
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 1/2] dt-bindings: i2c: loongson,ls2x: Add ls2k0300-i2c compatible
2025-11-13 8:48 [PATCH 0/2] i2c: Add Loongson-2K0300 I2C controller support Binbin Zhou
@ 2025-11-13 8:48 ` Binbin Zhou
2025-11-13 19:36 ` Conor Dooley
2025-11-13 8:48 ` [PATCH 2/2] i2c: ls2x-v2: Add driver for Loongson-2K0300 I2C controller Binbin Zhou
2025-12-16 8:31 ` [PATCH 0/2] i2c: Add Loongson-2K0300 I2C controller support Binbin Zhou
2 siblings, 1 reply; 6+ messages in thread
From: Binbin Zhou @ 2025-11-13 8:48 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Andi Shyti, Wolfram Sang, linux-i2c
Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, Binbin Zhou
Add "loongson,ls2k0300-i2c" dedicated compatible for representing I2C of
Loongson-2K0300 chip, because its HW integration is quiet different from
others.
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml b/Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml
index 67882ec6e06a..ac2bfb21b5b2 100644
--- a/Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml
+++ b/Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml
@@ -16,6 +16,7 @@ properties:
compatible:
enum:
- loongson,ls2k-i2c
+ - loongson,ls2k0300-i2c
- loongson,ls7a-i2c
reg:
--
2.47.3
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 2/2] i2c: ls2x-v2: Add driver for Loongson-2K0300 I2C controller
2025-11-13 8:48 [PATCH 0/2] i2c: Add Loongson-2K0300 I2C controller support Binbin Zhou
2025-11-13 8:48 ` [PATCH 1/2] dt-bindings: i2c: loongson,ls2x: Add ls2k0300-i2c compatible Binbin Zhou
@ 2025-11-13 8:48 ` Binbin Zhou
2025-11-14 7:06 ` Huacai Chen
2025-12-16 8:31 ` [PATCH 0/2] i2c: Add Loongson-2K0300 I2C controller support Binbin Zhou
2 siblings, 1 reply; 6+ messages in thread
From: Binbin Zhou @ 2025-11-13 8:48 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Andi Shyti, Wolfram Sang, linux-i2c
Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, Binbin Zhou
This I2C module is integrated into the Loongson-2K0300 SoCs.
It provides multi-master functionality and controls all I2C bus-specific
timing, protocols, arbitration, and timing. It supports both standard
and fast modes.
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
MAINTAINERS | 1 +
drivers/i2c/busses/Kconfig | 10 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-ls2x-v2.c | 513 +++++++++++++++++++++++++++++++
4 files changed, 525 insertions(+)
create mode 100644 drivers/i2c/busses/i2c-ls2x-v2.c
diff --git a/MAINTAINERS b/MAINTAINERS
index ddecf1ef3bed..8badab5d774d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14601,6 +14601,7 @@ M: Binbin Zhou <zhoubinbin@loongson.cn>
L: linux-i2c@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml
+F: drivers/i2c/busses/i2c-ls2x-v2.c
F: drivers/i2c/busses/i2c-ls2x.c
LOONGSON PWM DRIVER
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index fd81e49638aa..f52abbe20ce5 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -855,6 +855,16 @@ config I2C_LS2X
This driver can also be built as a module. If so, the module
will be called i2c-ls2x.
+config I2C_LS2X_V2
+ tristate "Loongson-2 Fast Speed I2C adapter"
+ depends on LOONGARCH || COMPILE_TEST
+ help
+ If you say yes to this option, support will be included for the
+ I2C interface on the Loongson-2K0300 SoCs.
+
+ This driver can also be built as a module. If so, the module
+ will be called i2c-ls2x-v2.
+
config I2C_MLXBF
tristate "Mellanox BlueField I2C controller"
depends on (MELLANOX_PLATFORM && ARM64) || COMPILE_TEST
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index fb985769f5ff..8cdfc30b79e9 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -80,6 +80,7 @@ obj-$(CONFIG_I2C_KEBA) += i2c-keba.o
obj-$(CONFIG_I2C_KEMPLD) += i2c-kempld.o
obj-$(CONFIG_I2C_LPC2K) += i2c-lpc2k.o
obj-$(CONFIG_I2C_LS2X) += i2c-ls2x.o
+obj-$(CONFIG_I2C_LS2X_V2) += i2c-ls2x-v2.o
obj-$(CONFIG_I2C_MESON) += i2c-meson.o
obj-$(CONFIG_I2C_MICROCHIP_CORE) += i2c-microchip-corei2c.o
obj-$(CONFIG_I2C_MPC) += i2c-mpc.o
diff --git a/drivers/i2c/busses/i2c-ls2x-v2.c b/drivers/i2c/busses/i2c-ls2x-v2.c
new file mode 100644
index 000000000000..e3b2a7ffe67e
--- /dev/null
+++ b/drivers/i2c/busses/i2c-ls2x-v2.c
@@ -0,0 +1,513 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Loongson-2K fast I2C controller driver
+ *
+ * Copyright (C) 2025 Loongson Technology Corporation Limited
+ *
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/iopoll.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/units.h>
+
+/* Loongson-2 fast I2C offset registers */
+#define LOONGSON2_I2C_CR1 0x00 /* I2C control 1 register */
+#define LOONGSON2_I2C_CR2 0x04 /* I2C control 2 register */
+#define LOONGSON2_I2C_OAR 0x08 /* I2C slave address register */
+#define LOONGSON2_I2C_DR 0x10 /* I2C data register */
+#define LOONGSON2_I2C_SR1 0x14 /* I2C status 1 register */
+#define LOONGSON2_I2C_SR2 0x18 /* I2C status 2 register */
+#define LOONGSON2_I2C_CCR 0x1C /* I2C clock control register */
+#define LOONGSON2_I2C_TRISE 0x20 /* I2C trise register */
+#define LOONGSON2_I2C_FLTR 0x24
+
+/* Bitfields of I2C control 1 register */
+#define LOONGSON2_I2C_CR1_PE BIT(0)
+#define LOONGSON2_I2C_CR1_START BIT(8)
+#define LOONGSON2_I2C_CR1_STOP BIT(9)
+#define LOONGSON2_I2C_CR1_ACK BIT(10)
+#define LOONGSON2_I2C_CR1_POS BIT(11)
+
+#define LOONGSON2_I2C_CR1_OP_MASK (LOONGSON2_I2C_CR1_START | LOONGSON2_I2C_CR1_STOP)
+
+/* Bitfields of I2C control 2 register */
+#define LOONGSON2_I2C_CR2_FREQ GENMASK(5, 0)
+#define LOONGSON2_I2C_CR2_ITERREN BIT(8)
+#define LOONGSON2_I2C_CR2_ITEVTEN BIT(9)
+#define LOONGSON2_I2C_CR2_ITBUFEN BIT(10)
+
+#define LOONGSON2_I2C_CR2_IRQ_MASK (LOONGSON2_I2C_CR2_ITBUFEN | \
+ LOONGSON2_I2C_CR2_ITEVTEN | \
+ LOONGSON2_I2C_CR2_ITERREN)
+
+/* Bitfields of I2C status 1 register */
+#define LOONGSON2_I2C_SR1_SB BIT(0)
+#define LOONGSON2_I2C_SR1_ADDR BIT(1)
+#define LOONGSON2_I2C_SR1_BTF BIT(2)
+#define LOONGSON2_I2C_SR1_RXNE BIT(6)
+#define LOONGSON2_I2C_SR1_TXE BIT(7)
+#define LOONGSON2_I2C_SR1_BERR BIT(8)
+#define LOONGSON2_I2C_SR1_ARLO BIT(9)
+#define LOONGSON2_I2C_SR1_AF BIT(10)
+
+#define LOONGSON2_I2C_SR1_ITEVTEN_MASK (LOONGSON2_I2C_SR1_BTF | \
+ LOONGSON2_I2C_SR1_ADDR | \
+ LOONGSON2_I2C_SR1_SB)
+#define LOONGSON2_I2C_SR1_ITBUFEN_MASK (LOONGSON2_I2C_SR1_TXE | LOONGSON2_I2C_SR1_RXNE)
+#define LOONGSON2_I2C_SR1_ITERREN_MASK (LOONGSON2_I2C_SR1_AF | \
+ LOONGSON2_I2C_SR1_ARLO | \
+ LOONGSON2_I2C_SR1_BERR)
+
+/* Bitfields of I2C status 2 register */
+#define LOONGSON2_I2C_SR2_BUSY BIT(1)
+
+/* Bitfields of I2C clock control register */
+#define LOONGSON2_I2C_CCR_CCR GENMASK(11, 0)
+#define LOONGSON2_I2C_CCR_DUTY BIT(14)
+#define LOONGSON2_I2C_CCR_FS BIT(15)
+
+/* Bitfields of I2C trise register */
+#define LOONGSON2_I2C_TRISE_SCL GENMASK(5, 0)
+
+#define LOONGSON2_I2C_FREE_SLEEP_US 1000
+#define LOONGSON2_I2C_FREE_TIMEOUT_US 5000
+
+/*
+ * struct loongson2_i2c_msg - client specific data
+ * @addr: 8-bit slave addr, including r/w bit
+ * @count: number of bytes to be transferred
+ * @buf: data buffer
+ * @stop: last I2C msg to be sent, i.e. STOP to be generated
+ * @result: result of the transfer
+ */
+struct loongson2_i2c_msg {
+ u8 addr;
+ u32 count;
+ u8 *buf;
+ bool stop;
+ int result;
+};
+
+/*
+ * struct loongson2_i2c_priv - private data of the controller
+ * @adapter: I2C adapter for this controller
+ * @dev: device for this controller
+ * @complete: completion of I2C message
+ * @regmap: regmap of the I2C device
+ * @i2c_t: I2C timing information
+ * @msg: I2C transfer information
+ */
+struct loongson2_i2c_priv {
+ struct i2c_adapter adapter;
+ struct device *dev;
+ struct completion complete;
+ struct regmap *regmap;
+ struct i2c_timings i2c_t;
+ struct loongson2_i2c_msg msg;
+};
+
+static void loongson2_i2c_disable_irq(struct loongson2_i2c_priv *priv)
+{
+ regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2, LOONGSON2_I2C_CR2_IRQ_MASK, 0);
+}
+
+static int loongson2_i2c_wait_free_bus(struct loongson2_i2c_priv *priv)
+{
+ u32 status;
+ int ret;
+
+ ret = regmap_read_poll_timeout(priv->regmap, LOONGSON2_I2C_SR2, status,
+ !(status & LOONGSON2_I2C_SR2_BUSY),
+ LOONGSON2_I2C_FREE_SLEEP_US,
+ LOONGSON2_I2C_FREE_TIMEOUT_US);
+ if (ret) {
+ dev_dbg(priv->dev, "I2C bus free failed.\n");
+ ret = -EBUSY;
+ }
+
+ return ret;
+}
+
+static void loongson2_i2c_write_byte(struct loongson2_i2c_priv *priv, u8 byte)
+{
+ regmap_write(priv->regmap, LOONGSON2_I2C_DR, byte);
+}
+
+static void loongson2_i2c_read_msg(struct loongson2_i2c_priv *priv)
+{
+ struct loongson2_i2c_msg *msg = &priv->msg;
+ u32 rbuf;
+
+ regmap_read(priv->regmap, LOONGSON2_I2C_DR, &rbuf);
+ *msg->buf++ = rbuf;
+ msg->count--;
+}
+
+static void loongson2_i2c_terminate_xfer(struct loongson2_i2c_priv *priv)
+{
+ struct loongson2_i2c_msg *msg = &priv->msg;
+
+ loongson2_i2c_disable_irq(priv);
+ regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_OP_MASK,
+ msg->stop ? LOONGSON2_I2C_CR1_STOP : LOONGSON2_I2C_CR1_START);
+ complete(&priv->complete);
+}
+
+static void loongson2_i2c_handle_write(struct loongson2_i2c_priv *priv)
+{
+ struct loongson2_i2c_msg *msg = &priv->msg;
+
+ if (msg->count) {
+ loongson2_i2c_write_byte(priv, *msg->buf++);
+ msg->count--;
+ if (!msg->count)
+ regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2,
+ LOONGSON2_I2C_CR2_ITBUFEN, 0);
+ } else {
+ loongson2_i2c_terminate_xfer(priv);
+ }
+}
+
+static void loongson2_i2c_handle_read(struct loongson2_i2c_priv *priv, int flag)
+{
+ struct loongson2_i2c_msg *msg = &priv->msg;
+ bool changed;
+ int i;
+
+ switch (msg->count) {
+ case 1:
+ /* only transmit 1 bytes condition */
+ loongson2_i2c_disable_irq(priv);
+ loongson2_i2c_read_msg(priv);
+ complete(&priv->complete);
+ break;
+ case 2:
+ if (flag != 1) {
+ /* ensure only transmit 2 bytes condition */
+ regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2,
+ LOONGSON2_I2C_CR2_ITBUFEN, 0);
+ break;
+ }
+ regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_OP_MASK,
+ msg->stop ? LOONGSON2_I2C_CR1_STOP : LOONGSON2_I2C_CR1_START);
+
+ loongson2_i2c_disable_irq(priv);
+
+ for (i = 2; i > 0; i--)
+ loongson2_i2c_read_msg(priv);
+
+ regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_POS, 0);
+ complete(&priv->complete);
+ break;
+ case 3:
+ regmap_update_bits_check(priv->regmap, LOONGSON2_I2C_CR2, LOONGSON2_I2C_CR2_ITBUFEN,
+ 0, &changed);
+ if (changed)
+ break;
+ regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_ACK, 0);
+ fallthrough;
+ default:
+ loongson2_i2c_read_msg(priv);
+ }
+}
+
+static void loongson2_i2c_handle_rx_addr(struct loongson2_i2c_priv *priv)
+{
+ struct loongson2_i2c_msg *msg = &priv->msg;
+
+ switch (msg->count) {
+ case 0:
+ loongson2_i2c_terminate_xfer(priv);
+ break;
+ case 1:
+ regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1,
+ LOONGSON2_I2C_CR1_ACK | LOONGSON2_I2C_CR1_POS, 0);
+ /* start or stop */
+ regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_OP_MASK,
+ msg->stop ? LOONGSON2_I2C_CR1_STOP : LOONGSON2_I2C_CR1_START);
+ break;
+ case 2:
+ regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_ACK, 0);
+ regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_POS,
+ LOONGSON2_I2C_CR1_POS);
+ break;
+
+ default:
+ regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_ACK,
+ LOONGSON2_I2C_CR1_ACK);
+ regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_POS, 0);
+ }
+}
+
+static irqreturn_t loongson2_i2c_isr_error(u32 status, void *data)
+{
+ struct loongson2_i2c_priv *priv = data;
+ struct loongson2_i2c_msg *msg = &priv->msg;
+
+ /* Arbitration lost */
+ if (status & LOONGSON2_I2C_SR1_ARLO) {
+ regmap_update_bits(priv->regmap, LOONGSON2_I2C_SR1, LOONGSON2_I2C_SR1_ARLO, 0);
+ msg->result = -EAGAIN;
+ }
+
+ /*
+ * Acknowledge failure:
+ * In master transmitter mode a Stop must be generated by software
+ */
+ if (status & LOONGSON2_I2C_SR1_AF) {
+ regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_STOP,
+ LOONGSON2_I2C_CR1_STOP);
+ regmap_update_bits(priv->regmap, LOONGSON2_I2C_SR1, LOONGSON2_I2C_SR1_AF, 0);
+ msg->result = -EIO;
+ }
+
+ /* Bus error */
+ if (status & LOONGSON2_I2C_SR1_BERR) {
+ regmap_update_bits(priv->regmap, LOONGSON2_I2C_SR1, LOONGSON2_I2C_SR1_BERR, 0);
+ msg->result = -EIO;
+ }
+
+ loongson2_i2c_disable_irq(priv);
+ complete(&priv->complete);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t loongson2_i2c_isr_event(int irq, void *data)
+{
+ u32 possible_status = LOONGSON2_I2C_SR1_ITEVTEN_MASK;
+ struct loongson2_i2c_priv *priv = data;
+ struct loongson2_i2c_msg *msg = &priv->msg;
+ u32 status, ien, event, cr2;
+
+ regmap_read(priv->regmap, LOONGSON2_I2C_SR1, &status);
+ if (status & LOONGSON2_I2C_SR1_ITERREN_MASK)
+ return loongson2_i2c_isr_error(status, data);
+
+ regmap_read(priv->regmap, LOONGSON2_I2C_CR2, &cr2);
+ ien = cr2 & LOONGSON2_I2C_CR2_IRQ_MASK;
+
+ /* Update possible_status if buffer interrupt is enabled */
+ if (ien & LOONGSON2_I2C_CR2_ITBUFEN)
+ possible_status |= LOONGSON2_I2C_SR1_ITBUFEN_MASK;
+
+ event = status & possible_status;
+ if (!event) {
+ dev_dbg(priv->dev, "spurious evt irq (status=0x%08x, ien=0x%08x)\n", status, ien);
+ return IRQ_NONE;
+ }
+
+ /* Start condition generated */
+ if (event & LOONGSON2_I2C_SR1_SB)
+ loongson2_i2c_write_byte(priv, msg->addr);
+
+ /* I2C Address sent */
+ if (event & LOONGSON2_I2C_SR1_ADDR) {
+ if (msg->addr & I2C_M_RD)
+ loongson2_i2c_handle_rx_addr(priv);
+ /* Clear ADDR flag */
+ regmap_read(priv->regmap, LOONGSON2_I2C_SR2, &status);
+ /* Enable buffer interrupts for RX/TX not empty events */
+ regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2, LOONGSON2_I2C_CR2_ITBUFEN,
+ LOONGSON2_I2C_CR2_ITBUFEN);
+ }
+
+ if (msg->addr & I2C_M_RD) {
+ /* RX not empty */
+ if (event & LOONGSON2_I2C_SR1_RXNE)
+ loongson2_i2c_handle_read(priv, 0);
+
+ if (event & LOONGSON2_I2C_SR1_BTF)
+ loongson2_i2c_handle_read(priv, 1);
+ } else {
+ /* TX empty */
+ if (event & LOONGSON2_I2C_SR1_TXE)
+ loongson2_i2c_handle_write(priv);
+
+ if (event & LOONGSON2_I2C_SR1_BTF)
+ loongson2_i2c_handle_write(priv);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int loongson2_i2c_xfer_msg(struct loongson2_i2c_priv *priv, struct i2c_msg *msg,
+ bool is_stop)
+{
+ struct loongson2_i2c_msg *l_msg = &priv->msg;
+ unsigned long timeout;
+ int ret;
+
+ l_msg->addr = i2c_8bit_addr_from_msg(msg);
+ l_msg->buf = msg->buf;
+ l_msg->count = msg->len;
+ l_msg->stop = is_stop;
+ l_msg->result = 0;
+
+ reinit_completion(&priv->complete);
+
+ /* Enable events and errors interrupts */
+ regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2,
+ LOONGSON2_I2C_CR2_ITEVTEN | LOONGSON2_I2C_CR2_ITERREN,
+ LOONGSON2_I2C_CR2_ITEVTEN | LOONGSON2_I2C_CR2_ITERREN);
+
+ timeout = wait_for_completion_timeout(&priv->complete, priv->adapter.timeout);
+ ret = l_msg->result;
+
+ if (!timeout)
+ ret = -ETIMEDOUT;
+
+ return ret;
+}
+
+static int loongson2_i2c_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg msgs[], int num)
+{
+ struct loongson2_i2c_priv *priv = i2c_get_adapdata(i2c_adap);
+ int ret = 0, i;
+
+ ret = loongson2_i2c_wait_free_bus(priv);
+ if (ret)
+ return ret;
+
+ /* START generation */
+ regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_START,
+ LOONGSON2_I2C_CR1_START);
+
+ for (i = 0; i < num && !ret; i++)
+ ret = loongson2_i2c_xfer_msg(priv, &msgs[i], i == num - 1);
+
+ return (ret < 0) ? ret : num;
+}
+
+static u32 loongson2_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm loongson2_i2c_algo = {
+ .master_xfer = loongson2_i2c_xfer,
+ .functionality = loongson2_i2c_func,
+};
+
+static void loongson2_i2c_adjust_bus_speed(struct loongson2_i2c_priv *priv)
+{
+ struct device *dev = priv->adapter.dev.parent;
+ struct i2c_timings *t = &priv->i2c_t;
+ u32 val, ccr = 0;
+
+ t->bus_freq_hz = I2C_MAX_STANDARD_MODE_FREQ;
+
+ i2c_parse_fw_timings(dev, t, false);
+
+ if (t->bus_freq_hz >= I2C_MAX_FAST_MODE_FREQ) {
+ val = DIV_ROUND_UP(t->bus_freq_hz, I2C_MAX_FAST_MODE_FREQ * 3);
+
+ /* Select Fast mode */
+ ccr |= LOONGSON2_I2C_CCR_FS;
+ } else {
+ val = DIV_ROUND_UP(t->bus_freq_hz, I2C_MAX_STANDARD_MODE_FREQ * 2);
+ }
+
+ ccr |= FIELD_GET(LOONGSON2_I2C_CCR_CCR, val);
+ regmap_write(priv->regmap, LOONGSON2_I2C_CCR, ccr);
+
+ /* reference clock determination the configure val(0x3f) */
+ regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2, LOONGSON2_I2C_CR2_FREQ,
+ LOONGSON2_I2C_CR2_FREQ);
+ regmap_update_bits(priv->regmap, LOONGSON2_I2C_TRISE, LOONGSON2_I2C_TRISE_SCL,
+ LOONGSON2_I2C_TRISE_SCL);
+
+ /* Enable I2C */
+ regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_PE,
+ LOONGSON2_I2C_CR1_PE);
+}
+
+static const struct regmap_config loongson2_i2c_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = LOONGSON2_I2C_TRISE,
+};
+
+static int loongson2_i2c_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct loongson2_i2c_priv *priv;
+ struct i2c_adapter *adap;
+ void __iomem *base;
+ int irq, ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return dev_err_probe(dev, PTR_ERR(base),
+ "devm_platform_ioremap_resource failed\n");
+
+ priv->regmap = devm_regmap_init_mmio(dev, base,
+ &loongson2_i2c_regmap_config);
+ if (IS_ERR(priv->regmap))
+ return dev_err_probe(dev, PTR_ERR(priv->regmap),
+ "devm_regmap_init_mmio failed\n");
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return -EINVAL;
+
+ priv->dev = dev;
+
+ adap = &priv->adapter;
+ adap->retries = 5;
+ adap->nr = pdev->id;
+ adap->dev.parent = dev;
+ adap->owner = THIS_MODULE;
+ adap->algo = &loongson2_i2c_algo;
+ adap->timeout = 2 * HZ;
+ device_set_node(&adap->dev, dev_fwnode(dev));
+ i2c_set_adapdata(adap, priv);
+ strscpy(adap->name, pdev->name, sizeof(adap->name));
+ init_completion(&priv->complete);
+ platform_set_drvdata(pdev, priv);
+
+ loongson2_i2c_adjust_bus_speed(priv);
+
+ ret = devm_request_irq(dev, irq, loongson2_i2c_isr_event, IRQF_SHARED, pdev->name, priv);
+ if (ret)
+ return dev_err_probe(dev, ret, "Unable to request irq %d\n", irq);
+
+ return devm_i2c_add_adapter(dev, adap);
+}
+
+static const struct of_device_id loongson2_i2c_id_table[] = {
+ { .compatible = "loongson,ls2k0300-i2c" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, loongson2_i2c_id_table);
+
+static struct platform_driver loongson2_i2c_driver = {
+ .driver = {
+ .name = "loongson2-i2c-v2",
+ .of_match_table = loongson2_i2c_id_table,
+ },
+ .probe = loongson2_i2c_probe,
+};
+
+module_platform_driver(loongson2_i2c_driver);
+
+MODULE_DESCRIPTION("Loongson-2K0300 I2C bus driver");
+MODULE_AUTHOR("Loongson Technology Corporation Limited");
+MODULE_LICENSE("GPL");
--
2.47.3
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH 1/2] dt-bindings: i2c: loongson,ls2x: Add ls2k0300-i2c compatible
2025-11-13 8:48 ` [PATCH 1/2] dt-bindings: i2c: loongson,ls2x: Add ls2k0300-i2c compatible Binbin Zhou
@ 2025-11-13 19:36 ` Conor Dooley
0 siblings, 0 replies; 6+ messages in thread
From: Conor Dooley @ 2025-11-13 19:36 UTC (permalink / raw)
To: Binbin Zhou
Cc: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Andi Shyti, Wolfram Sang, linux-i2c, Huacai Chen,
Xuerui Wang, loongarch, devicetree
[-- Attachment #1: Type: text/plain, Size: 75 bytes --]
Acked-by: Conor Dooley <conor.dooley@microchip.com>
pw-bot: not-applicable
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH 2/2] i2c: ls2x-v2: Add driver for Loongson-2K0300 I2C controller
2025-11-13 8:48 ` [PATCH 2/2] i2c: ls2x-v2: Add driver for Loongson-2K0300 I2C controller Binbin Zhou
@ 2025-11-14 7:06 ` Huacai Chen
0 siblings, 0 replies; 6+ messages in thread
From: Huacai Chen @ 2025-11-14 7:06 UTC (permalink / raw)
To: Binbin Zhou
Cc: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Andi Shyti, Wolfram Sang, linux-i2c, Xuerui Wang,
loongarch, devicetree
Hi, Binbin,
On Thu, Nov 13, 2025 at 4:48 PM Binbin Zhou <zhoubinbin@loongson.cn> wrote:
>
> This I2C module is integrated into the Loongson-2K0300 SoCs.
>
> It provides multi-master functionality and controls all I2C bus-specific
> timing, protocols, arbitration, and timing. It supports both standard
> and fast modes.
>
> Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> ---
> MAINTAINERS | 1 +
> drivers/i2c/busses/Kconfig | 10 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-ls2x-v2.c | 513 +++++++++++++++++++++++++++++++
> 4 files changed, 525 insertions(+)
> create mode 100644 drivers/i2c/busses/i2c-ls2x-v2.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index ddecf1ef3bed..8badab5d774d 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14601,6 +14601,7 @@ M: Binbin Zhou <zhoubinbin@loongson.cn>
> L: linux-i2c@vger.kernel.org
> S: Maintained
> F: Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml
> +F: drivers/i2c/busses/i2c-ls2x-v2.c
Move this line after i2c-ls2x.c?
> F: drivers/i2c/busses/i2c-ls2x.c
>
> LOONGSON PWM DRIVER
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index fd81e49638aa..f52abbe20ce5 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -855,6 +855,16 @@ config I2C_LS2X
> This driver can also be built as a module. If so, the module
> will be called i2c-ls2x.
>
> +config I2C_LS2X_V2
> + tristate "Loongson-2 Fast Speed I2C adapter"
> + depends on LOONGARCH || COMPILE_TEST
> + help
> + If you say yes to this option, support will be included for the
> + I2C interface on the Loongson-2K0300 SoCs.
> +
> + This driver can also be built as a module. If so, the module
> + will be called i2c-ls2x-v2.
> +
> config I2C_MLXBF
> tristate "Mellanox BlueField I2C controller"
> depends on (MELLANOX_PLATFORM && ARM64) || COMPILE_TEST
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index fb985769f5ff..8cdfc30b79e9 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -80,6 +80,7 @@ obj-$(CONFIG_I2C_KEBA) += i2c-keba.o
> obj-$(CONFIG_I2C_KEMPLD) += i2c-kempld.o
> obj-$(CONFIG_I2C_LPC2K) += i2c-lpc2k.o
> obj-$(CONFIG_I2C_LS2X) += i2c-ls2x.o
> +obj-$(CONFIG_I2C_LS2X_V2) += i2c-ls2x-v2.o
> obj-$(CONFIG_I2C_MESON) += i2c-meson.o
> obj-$(CONFIG_I2C_MICROCHIP_CORE) += i2c-microchip-corei2c.o
> obj-$(CONFIG_I2C_MPC) += i2c-mpc.o
> diff --git a/drivers/i2c/busses/i2c-ls2x-v2.c b/drivers/i2c/busses/i2c-ls2x-v2.c
> new file mode 100644
> index 000000000000..e3b2a7ffe67e
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-ls2x-v2.c
> @@ -0,0 +1,513 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Loongson-2K fast I2C controller driver
> + *
> + * Copyright (C) 2025 Loongson Technology Corporation Limited
> + *
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/bits.h>
> +#include <linux/clk.h>
> +#include <linux/device.h>
> +#include <linux/iopoll.h>
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/property.h>
> +#include <linux/regmap.h>
> +#include <linux/units.h>
> +
> +/* Loongson-2 fast I2C offset registers */
> +#define LOONGSON2_I2C_CR1 0x00 /* I2C control 1 register */
> +#define LOONGSON2_I2C_CR2 0x04 /* I2C control 2 register */
> +#define LOONGSON2_I2C_OAR 0x08 /* I2C slave address register */
> +#define LOONGSON2_I2C_DR 0x10 /* I2C data register */
> +#define LOONGSON2_I2C_SR1 0x14 /* I2C status 1 register */
> +#define LOONGSON2_I2C_SR2 0x18 /* I2C status 2 register */
> +#define LOONGSON2_I2C_CCR 0x1C /* I2C clock control register */
> +#define LOONGSON2_I2C_TRISE 0x20 /* I2C trise register */
> +#define LOONGSON2_I2C_FLTR 0x24
> +
> +/* Bitfields of I2C control 1 register */
> +#define LOONGSON2_I2C_CR1_PE BIT(0)
> +#define LOONGSON2_I2C_CR1_START BIT(8)
> +#define LOONGSON2_I2C_CR1_STOP BIT(9)
> +#define LOONGSON2_I2C_CR1_ACK BIT(10)
> +#define LOONGSON2_I2C_CR1_POS BIT(11)
> +
> +#define LOONGSON2_I2C_CR1_OP_MASK (LOONGSON2_I2C_CR1_START | LOONGSON2_I2C_CR1_STOP)
> +
> +/* Bitfields of I2C control 2 register */
> +#define LOONGSON2_I2C_CR2_FREQ GENMASK(5, 0)
> +#define LOONGSON2_I2C_CR2_ITERREN BIT(8)
> +#define LOONGSON2_I2C_CR2_ITEVTEN BIT(9)
> +#define LOONGSON2_I2C_CR2_ITBUFEN BIT(10)
> +
> +#define LOONGSON2_I2C_CR2_IRQ_MASK (LOONGSON2_I2C_CR2_ITBUFEN | \
> + LOONGSON2_I2C_CR2_ITEVTEN | \
> + LOONGSON2_I2C_CR2_ITERREN)
> +
> +/* Bitfields of I2C status 1 register */
> +#define LOONGSON2_I2C_SR1_SB BIT(0)
> +#define LOONGSON2_I2C_SR1_ADDR BIT(1)
> +#define LOONGSON2_I2C_SR1_BTF BIT(2)
> +#define LOONGSON2_I2C_SR1_RXNE BIT(6)
> +#define LOONGSON2_I2C_SR1_TXE BIT(7)
> +#define LOONGSON2_I2C_SR1_BERR BIT(8)
> +#define LOONGSON2_I2C_SR1_ARLO BIT(9)
> +#define LOONGSON2_I2C_SR1_AF BIT(10)
> +
> +#define LOONGSON2_I2C_SR1_ITEVTEN_MASK (LOONGSON2_I2C_SR1_BTF | \
> + LOONGSON2_I2C_SR1_ADDR | \
> + LOONGSON2_I2C_SR1_SB)
> +#define LOONGSON2_I2C_SR1_ITBUFEN_MASK (LOONGSON2_I2C_SR1_TXE | LOONGSON2_I2C_SR1_RXNE)
> +#define LOONGSON2_I2C_SR1_ITERREN_MASK (LOONGSON2_I2C_SR1_AF | \
> + LOONGSON2_I2C_SR1_ARLO | \
> + LOONGSON2_I2C_SR1_BERR)
> +
> +/* Bitfields of I2C status 2 register */
> +#define LOONGSON2_I2C_SR2_BUSY BIT(1)
> +
> +/* Bitfields of I2C clock control register */
> +#define LOONGSON2_I2C_CCR_CCR GENMASK(11, 0)
> +#define LOONGSON2_I2C_CCR_DUTY BIT(14)
> +#define LOONGSON2_I2C_CCR_FS BIT(15)
> +
> +/* Bitfields of I2C trise register */
> +#define LOONGSON2_I2C_TRISE_SCL GENMASK(5, 0)
> +
> +#define LOONGSON2_I2C_FREE_SLEEP_US 1000
> +#define LOONGSON2_I2C_FREE_TIMEOUT_US 5000
> +
> +/*
> + * struct loongson2_i2c_msg - client specific data
> + * @addr: 8-bit slave addr, including r/w bit
> + * @count: number of bytes to be transferred
> + * @buf: data buffer
> + * @stop: last I2C msg to be sent, i.e. STOP to be generated
> + * @result: result of the transfer
> + */
> +struct loongson2_i2c_msg {
> + u8 addr;
> + u32 count;
> + u8 *buf;
> + bool stop;
> + int result;
> +};
> +
> +/*
> + * struct loongson2_i2c_priv - private data of the controller
> + * @adapter: I2C adapter for this controller
> + * @dev: device for this controller
> + * @complete: completion of I2C message
> + * @regmap: regmap of the I2C device
> + * @i2c_t: I2C timing information
> + * @msg: I2C transfer information
> + */
> +struct loongson2_i2c_priv {
> + struct i2c_adapter adapter;
> + struct device *dev;
> + struct completion complete;
> + struct regmap *regmap;
> + struct i2c_timings i2c_t;
> + struct loongson2_i2c_msg msg;
> +};
> +
> +static void loongson2_i2c_disable_irq(struct loongson2_i2c_priv *priv)
> +{
> + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2, LOONGSON2_I2C_CR2_IRQ_MASK, 0);
> +}
> +
> +static int loongson2_i2c_wait_free_bus(struct loongson2_i2c_priv *priv)
> +{
> + u32 status;
> + int ret;
> +
> + ret = regmap_read_poll_timeout(priv->regmap, LOONGSON2_I2C_SR2, status,
> + !(status & LOONGSON2_I2C_SR2_BUSY),
> + LOONGSON2_I2C_FREE_SLEEP_US,
> + LOONGSON2_I2C_FREE_TIMEOUT_US);
> + if (ret) {
> + dev_dbg(priv->dev, "I2C bus free failed.\n");
> + ret = -EBUSY;
> + }
> +
> + return ret;
> +}
> +
> +static void loongson2_i2c_write_byte(struct loongson2_i2c_priv *priv, u8 byte)
> +{
> + regmap_write(priv->regmap, LOONGSON2_I2C_DR, byte);
> +}
We usually put the write function after read, which means
loongson2_i2c_write_byte() should be after loongson2_i2c_read_msg().
> +
> +static void loongson2_i2c_read_msg(struct loongson2_i2c_priv *priv)
> +{
> + struct loongson2_i2c_msg *msg = &priv->msg;
> + u32 rbuf;
> +
> + regmap_read(priv->regmap, LOONGSON2_I2C_DR, &rbuf);
> + *msg->buf++ = rbuf;
> + msg->count--;
> +}
> +
> +static void loongson2_i2c_terminate_xfer(struct loongson2_i2c_priv *priv)
> +{
> + struct loongson2_i2c_msg *msg = &priv->msg;
> +
> + loongson2_i2c_disable_irq(priv);
> + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_OP_MASK,
> + msg->stop ? LOONGSON2_I2C_CR1_STOP : LOONGSON2_I2C_CR1_START);
> + complete(&priv->complete);
> +}
> +
> +static void loongson2_i2c_handle_write(struct loongson2_i2c_priv *priv)
> +{
> + struct loongson2_i2c_msg *msg = &priv->msg;
> +
> + if (msg->count) {
> + loongson2_i2c_write_byte(priv, *msg->buf++);
> + msg->count--;
> + if (!msg->count)
> + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2,
> + LOONGSON2_I2C_CR2_ITBUFEN, 0);
> + } else {
> + loongson2_i2c_terminate_xfer(priv);
> + }
> +}
The same, loongson2_i2c_handle_write() should be after
loongson2_i2c_handle_read(), you have already done this in
loongson2_i2c_isr_event().
Huacai
> +
> +static void loongson2_i2c_handle_read(struct loongson2_i2c_priv *priv, int flag)
> +{
> + struct loongson2_i2c_msg *msg = &priv->msg;
> + bool changed;
> + int i;
> +
> + switch (msg->count) {
> + case 1:
> + /* only transmit 1 bytes condition */
> + loongson2_i2c_disable_irq(priv);
> + loongson2_i2c_read_msg(priv);
> + complete(&priv->complete);
> + break;
> + case 2:
> + if (flag != 1) {
> + /* ensure only transmit 2 bytes condition */
> + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2,
> + LOONGSON2_I2C_CR2_ITBUFEN, 0);
> + break;
> + }
> + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_OP_MASK,
> + msg->stop ? LOONGSON2_I2C_CR1_STOP : LOONGSON2_I2C_CR1_START);
> +
> + loongson2_i2c_disable_irq(priv);
> +
> + for (i = 2; i > 0; i--)
> + loongson2_i2c_read_msg(priv);
> +
> + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_POS, 0);
> + complete(&priv->complete);
> + break;
> + case 3:
> + regmap_update_bits_check(priv->regmap, LOONGSON2_I2C_CR2, LOONGSON2_I2C_CR2_ITBUFEN,
> + 0, &changed);
> + if (changed)
> + break;
> + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_ACK, 0);
> + fallthrough;
> + default:
> + loongson2_i2c_read_msg(priv);
> + }
> +}
> +
> +static void loongson2_i2c_handle_rx_addr(struct loongson2_i2c_priv *priv)
> +{
> + struct loongson2_i2c_msg *msg = &priv->msg;
> +
> + switch (msg->count) {
> + case 0:
> + loongson2_i2c_terminate_xfer(priv);
> + break;
> + case 1:
> + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1,
> + LOONGSON2_I2C_CR1_ACK | LOONGSON2_I2C_CR1_POS, 0);
> + /* start or stop */
> + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_OP_MASK,
> + msg->stop ? LOONGSON2_I2C_CR1_STOP : LOONGSON2_I2C_CR1_START);
> + break;
> + case 2:
> + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_ACK, 0);
> + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_POS,
> + LOONGSON2_I2C_CR1_POS);
> + break;
> +
> + default:
> + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_ACK,
> + LOONGSON2_I2C_CR1_ACK);
> + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_POS, 0);
> + }
> +}
> +
> +static irqreturn_t loongson2_i2c_isr_error(u32 status, void *data)
> +{
> + struct loongson2_i2c_priv *priv = data;
> + struct loongson2_i2c_msg *msg = &priv->msg;
> +
> + /* Arbitration lost */
> + if (status & LOONGSON2_I2C_SR1_ARLO) {
> + regmap_update_bits(priv->regmap, LOONGSON2_I2C_SR1, LOONGSON2_I2C_SR1_ARLO, 0);
> + msg->result = -EAGAIN;
> + }
> +
> + /*
> + * Acknowledge failure:
> + * In master transmitter mode a Stop must be generated by software
> + */
> + if (status & LOONGSON2_I2C_SR1_AF) {
> + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_STOP,
> + LOONGSON2_I2C_CR1_STOP);
> + regmap_update_bits(priv->regmap, LOONGSON2_I2C_SR1, LOONGSON2_I2C_SR1_AF, 0);
> + msg->result = -EIO;
> + }
> +
> + /* Bus error */
> + if (status & LOONGSON2_I2C_SR1_BERR) {
> + regmap_update_bits(priv->regmap, LOONGSON2_I2C_SR1, LOONGSON2_I2C_SR1_BERR, 0);
> + msg->result = -EIO;
> + }
> +
> + loongson2_i2c_disable_irq(priv);
> + complete(&priv->complete);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t loongson2_i2c_isr_event(int irq, void *data)
> +{
> + u32 possible_status = LOONGSON2_I2C_SR1_ITEVTEN_MASK;
> + struct loongson2_i2c_priv *priv = data;
> + struct loongson2_i2c_msg *msg = &priv->msg;
> + u32 status, ien, event, cr2;
> +
> + regmap_read(priv->regmap, LOONGSON2_I2C_SR1, &status);
> + if (status & LOONGSON2_I2C_SR1_ITERREN_MASK)
> + return loongson2_i2c_isr_error(status, data);
> +
> + regmap_read(priv->regmap, LOONGSON2_I2C_CR2, &cr2);
> + ien = cr2 & LOONGSON2_I2C_CR2_IRQ_MASK;
> +
> + /* Update possible_status if buffer interrupt is enabled */
> + if (ien & LOONGSON2_I2C_CR2_ITBUFEN)
> + possible_status |= LOONGSON2_I2C_SR1_ITBUFEN_MASK;
> +
> + event = status & possible_status;
> + if (!event) {
> + dev_dbg(priv->dev, "spurious evt irq (status=0x%08x, ien=0x%08x)\n", status, ien);
> + return IRQ_NONE;
> + }
> +
> + /* Start condition generated */
> + if (event & LOONGSON2_I2C_SR1_SB)
> + loongson2_i2c_write_byte(priv, msg->addr);
> +
> + /* I2C Address sent */
> + if (event & LOONGSON2_I2C_SR1_ADDR) {
> + if (msg->addr & I2C_M_RD)
> + loongson2_i2c_handle_rx_addr(priv);
> + /* Clear ADDR flag */
> + regmap_read(priv->regmap, LOONGSON2_I2C_SR2, &status);
> + /* Enable buffer interrupts for RX/TX not empty events */
> + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2, LOONGSON2_I2C_CR2_ITBUFEN,
> + LOONGSON2_I2C_CR2_ITBUFEN);
> + }
> +
> + if (msg->addr & I2C_M_RD) {
> + /* RX not empty */
> + if (event & LOONGSON2_I2C_SR1_RXNE)
> + loongson2_i2c_handle_read(priv, 0);
> +
> + if (event & LOONGSON2_I2C_SR1_BTF)
> + loongson2_i2c_handle_read(priv, 1);
> + } else {
> + /* TX empty */
> + if (event & LOONGSON2_I2C_SR1_TXE)
> + loongson2_i2c_handle_write(priv);
> +
> + if (event & LOONGSON2_I2C_SR1_BTF)
> + loongson2_i2c_handle_write(priv);
> + }
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int loongson2_i2c_xfer_msg(struct loongson2_i2c_priv *priv, struct i2c_msg *msg,
> + bool is_stop)
> +{
> + struct loongson2_i2c_msg *l_msg = &priv->msg;
> + unsigned long timeout;
> + int ret;
> +
> + l_msg->addr = i2c_8bit_addr_from_msg(msg);
> + l_msg->buf = msg->buf;
> + l_msg->count = msg->len;
> + l_msg->stop = is_stop;
> + l_msg->result = 0;
> +
> + reinit_completion(&priv->complete);
> +
> + /* Enable events and errors interrupts */
> + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2,
> + LOONGSON2_I2C_CR2_ITEVTEN | LOONGSON2_I2C_CR2_ITERREN,
> + LOONGSON2_I2C_CR2_ITEVTEN | LOONGSON2_I2C_CR2_ITERREN);
> +
> + timeout = wait_for_completion_timeout(&priv->complete, priv->adapter.timeout);
> + ret = l_msg->result;
> +
> + if (!timeout)
> + ret = -ETIMEDOUT;
> +
> + return ret;
> +}
> +
> +static int loongson2_i2c_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg msgs[], int num)
> +{
> + struct loongson2_i2c_priv *priv = i2c_get_adapdata(i2c_adap);
> + int ret = 0, i;
> +
> + ret = loongson2_i2c_wait_free_bus(priv);
> + if (ret)
> + return ret;
> +
> + /* START generation */
> + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_START,
> + LOONGSON2_I2C_CR1_START);
> +
> + for (i = 0; i < num && !ret; i++)
> + ret = loongson2_i2c_xfer_msg(priv, &msgs[i], i == num - 1);
> +
> + return (ret < 0) ? ret : num;
> +}
> +
> +static u32 loongson2_i2c_func(struct i2c_adapter *adap)
> +{
> + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
> +}
> +
> +static const struct i2c_algorithm loongson2_i2c_algo = {
> + .master_xfer = loongson2_i2c_xfer,
> + .functionality = loongson2_i2c_func,
> +};
> +
> +static void loongson2_i2c_adjust_bus_speed(struct loongson2_i2c_priv *priv)
> +{
> + struct device *dev = priv->adapter.dev.parent;
> + struct i2c_timings *t = &priv->i2c_t;
> + u32 val, ccr = 0;
> +
> + t->bus_freq_hz = I2C_MAX_STANDARD_MODE_FREQ;
> +
> + i2c_parse_fw_timings(dev, t, false);
> +
> + if (t->bus_freq_hz >= I2C_MAX_FAST_MODE_FREQ) {
> + val = DIV_ROUND_UP(t->bus_freq_hz, I2C_MAX_FAST_MODE_FREQ * 3);
> +
> + /* Select Fast mode */
> + ccr |= LOONGSON2_I2C_CCR_FS;
> + } else {
> + val = DIV_ROUND_UP(t->bus_freq_hz, I2C_MAX_STANDARD_MODE_FREQ * 2);
> + }
> +
> + ccr |= FIELD_GET(LOONGSON2_I2C_CCR_CCR, val);
> + regmap_write(priv->regmap, LOONGSON2_I2C_CCR, ccr);
> +
> + /* reference clock determination the configure val(0x3f) */
> + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR2, LOONGSON2_I2C_CR2_FREQ,
> + LOONGSON2_I2C_CR2_FREQ);
> + regmap_update_bits(priv->regmap, LOONGSON2_I2C_TRISE, LOONGSON2_I2C_TRISE_SCL,
> + LOONGSON2_I2C_TRISE_SCL);
> +
> + /* Enable I2C */
> + regmap_update_bits(priv->regmap, LOONGSON2_I2C_CR1, LOONGSON2_I2C_CR1_PE,
> + LOONGSON2_I2C_CR1_PE);
> +}
> +
> +static const struct regmap_config loongson2_i2c_regmap_config = {
> + .reg_bits = 32,
> + .val_bits = 32,
> + .reg_stride = 4,
> + .max_register = LOONGSON2_I2C_TRISE,
> +};
> +
> +static int loongson2_i2c_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct loongson2_i2c_priv *priv;
> + struct i2c_adapter *adap;
> + void __iomem *base;
> + int irq, ret;
> +
> + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + base = devm_platform_ioremap_resource(pdev, 0);
> + if (IS_ERR(base))
> + return dev_err_probe(dev, PTR_ERR(base),
> + "devm_platform_ioremap_resource failed\n");
> +
> + priv->regmap = devm_regmap_init_mmio(dev, base,
> + &loongson2_i2c_regmap_config);
> + if (IS_ERR(priv->regmap))
> + return dev_err_probe(dev, PTR_ERR(priv->regmap),
> + "devm_regmap_init_mmio failed\n");
> +
> + irq = platform_get_irq(pdev, 0);
> + if (irq < 0)
> + return -EINVAL;
> +
> + priv->dev = dev;
> +
> + adap = &priv->adapter;
> + adap->retries = 5;
> + adap->nr = pdev->id;
> + adap->dev.parent = dev;
> + adap->owner = THIS_MODULE;
> + adap->algo = &loongson2_i2c_algo;
> + adap->timeout = 2 * HZ;
> + device_set_node(&adap->dev, dev_fwnode(dev));
> + i2c_set_adapdata(adap, priv);
> + strscpy(adap->name, pdev->name, sizeof(adap->name));
> + init_completion(&priv->complete);
> + platform_set_drvdata(pdev, priv);
> +
> + loongson2_i2c_adjust_bus_speed(priv);
> +
> + ret = devm_request_irq(dev, irq, loongson2_i2c_isr_event, IRQF_SHARED, pdev->name, priv);
> + if (ret)
> + return dev_err_probe(dev, ret, "Unable to request irq %d\n", irq);
> +
> + return devm_i2c_add_adapter(dev, adap);
> +}
> +
> +static const struct of_device_id loongson2_i2c_id_table[] = {
> + { .compatible = "loongson,ls2k0300-i2c" },
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, loongson2_i2c_id_table);
> +
> +static struct platform_driver loongson2_i2c_driver = {
> + .driver = {
> + .name = "loongson2-i2c-v2",
> + .of_match_table = loongson2_i2c_id_table,
> + },
> + .probe = loongson2_i2c_probe,
> +};
> +
> +module_platform_driver(loongson2_i2c_driver);
> +
> +MODULE_DESCRIPTION("Loongson-2K0300 I2C bus driver");
> +MODULE_AUTHOR("Loongson Technology Corporation Limited");
> +MODULE_LICENSE("GPL");
> --
> 2.47.3
>
>
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH 0/2] i2c: Add Loongson-2K0300 I2C controller support
2025-11-13 8:48 [PATCH 0/2] i2c: Add Loongson-2K0300 I2C controller support Binbin Zhou
2025-11-13 8:48 ` [PATCH 1/2] dt-bindings: i2c: loongson,ls2x: Add ls2k0300-i2c compatible Binbin Zhou
2025-11-13 8:48 ` [PATCH 2/2] i2c: ls2x-v2: Add driver for Loongson-2K0300 I2C controller Binbin Zhou
@ 2025-12-16 8:31 ` Binbin Zhou
2 siblings, 0 replies; 6+ messages in thread
From: Binbin Zhou @ 2025-12-16 8:31 UTC (permalink / raw)
To: Binbin Zhou
Cc: Huacai Chen, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Andi Shyti, Wolfram Sang, linux-i2c, Huacai Chen, Xuerui Wang,
loongarch, devicetree
Hi all:
On Thu, Nov 13, 2025 at 4:48 PM Binbin Zhou <zhoubinbin@loongson.cn> wrote:
>
> Hi all:
>
> This patch set describes the I2C controller integrated into the Loongson-2K0300 chip.
>
> It has a significantly different design from the previous I2C controller (i2c-ls2x),
> such as support for master-slave transfer mode, and DMA transfers (implementation
> in progress), etc. Therefore, we try to name it i2c-ls2x-v2.
>
> Therefore we try to name it i2c-ls2x-v2.
>
> Thanks.
>
> Binbin Zhou (2):
> dt-bindings: i2c: loongson,ls2x: Add ls2k0300-i2c compatible
> i2c: ls2x-v2: Add driver for Loongson-2K0300 I2C controller
Gentle ping.
Any comments about this patchset ?
>
> .../bindings/i2c/loongson,ls2x-i2c.yaml | 1 +
> MAINTAINERS | 1 +
> drivers/i2c/busses/Kconfig | 10 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-ls2x-v2.c | 513 ++++++++++++++++++
> 5 files changed, 526 insertions(+)
> create mode 100644 drivers/i2c/busses/i2c-ls2x-v2.c
>
>
> base-commit: e9a6fb0bcdd7609be6969112f3fbfcce3b1d4a7c
> --
> 2.47.3
>
--
Thanks.
Binbin
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2025-12-16 8:31 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-11-13 8:48 [PATCH 0/2] i2c: Add Loongson-2K0300 I2C controller support Binbin Zhou
2025-11-13 8:48 ` [PATCH 1/2] dt-bindings: i2c: loongson,ls2x: Add ls2k0300-i2c compatible Binbin Zhou
2025-11-13 19:36 ` Conor Dooley
2025-11-13 8:48 ` [PATCH 2/2] i2c: ls2x-v2: Add driver for Loongson-2K0300 I2C controller Binbin Zhou
2025-11-14 7:06 ` Huacai Chen
2025-12-16 8:31 ` [PATCH 0/2] i2c: Add Loongson-2K0300 I2C controller support Binbin Zhou
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.