realtek: 6.18: remove obsolete patches
Remove patches which are accepted upstream and were backported to pre-6.18. Those are obsolete now. Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com> Link: https://github.com/openwrt/openwrt/pull/21181 Signed-off-by: Robert Marko <robimarko@gmail.com>
This commit is contained in:
parent
a7e63b4dac
commit
c3d998f147
@ -1,481 +0,0 @@
|
||||
From 42d20a6a61b8fccbb57d80df1ccde7dd82d5bbd6 Mon Sep 17 00:00:00 2001
|
||||
From: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Date: Wed, 16 Oct 2024 11:54:34 +1300
|
||||
Subject: [PATCH] spi: spi-mem: Add Realtek SPI-NAND controller
|
||||
|
||||
Add a driver for the SPI-NAND controller on the RTL9300 family of
|
||||
devices.
|
||||
|
||||
The controller supports
|
||||
* Serial/Dual/Quad data with
|
||||
* PIO and DMA data read/write operation
|
||||
* Configurable flash access timing
|
||||
|
||||
There is a separate ECC controller on the RTL9300 which isn't currently
|
||||
supported (instead we rely on the on-die ECC supported by most SPI-NAND
|
||||
chips).
|
||||
|
||||
Signed-off-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Link: https://patch.msgid.link/20241015225434.3970360-4-chris.packham@alliedtelesis.co.nz
|
||||
Signed-off-by: Mark Brown <broonie@kernel.org>
|
||||
---
|
||||
MAINTAINERS | 6 +
|
||||
drivers/spi/Kconfig | 11 +
|
||||
drivers/spi/Makefile | 1 +
|
||||
drivers/spi/spi-realtek-rtl-snand.c | 405 ++++++++++++++++++++++++++++
|
||||
4 files changed, 423 insertions(+)
|
||||
create mode 100644 drivers/spi/spi-realtek-rtl-snand.c
|
||||
|
||||
--- a/MAINTAINERS
|
||||
+++ b/MAINTAINERS
|
||||
@@ -19507,6 +19507,12 @@ S: Maintained
|
||||
F: Documentation/devicetree/bindings/net/dsa/realtek.yaml
|
||||
F: drivers/net/dsa/realtek/*
|
||||
|
||||
+REALTEK SPI-NAND
|
||||
+M: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
+S: Maintained
|
||||
+F: Documentation/devicetree/bindings/spi/realtek,rtl9301-snand.yaml
|
||||
+F: drivers/spi/spi-realtek-rtl-snand.c
|
||||
+
|
||||
REALTEK WIRELESS DRIVER (rtlwifi family)
|
||||
M: Ping-Ke Shih <pkshih@realtek.com>
|
||||
L: linux-wireless@vger.kernel.org
|
||||
--- a/drivers/spi/Kconfig
|
||||
+++ b/drivers/spi/Kconfig
|
||||
@@ -843,6 +843,17 @@ config SPI_PXA2XX
|
||||
config SPI_PXA2XX_PCI
|
||||
def_tristate SPI_PXA2XX && PCI && COMMON_CLK
|
||||
|
||||
+config SPI_REALTEK_SNAND
|
||||
+ tristate "Realtek SPI-NAND Flash Controller"
|
||||
+ depends on MACH_REALTEK_RTL || COMPILE_TEST
|
||||
+ select REGMAP
|
||||
+ help
|
||||
+ This enables support for the SPI-NAND Flash controller on
|
||||
+ Realtek SoCs.
|
||||
+
|
||||
+ This driver does not support generic SPI. The implementation
|
||||
+ only supports the spi-mem interface.
|
||||
+
|
||||
config SPI_ROCKCHIP
|
||||
tristate "Rockchip SPI controller driver"
|
||||
depends on ARCH_ROCKCHIP || COMPILE_TEST
|
||||
--- a/drivers/spi/Makefile
|
||||
+++ b/drivers/spi/Makefile
|
||||
@@ -120,6 +120,7 @@ obj-$(CONFIG_SPI_ROCKCHIP) += spi-rockc
|
||||
obj-$(CONFIG_SPI_ROCKCHIP_SFC) += spi-rockchip-sfc.o
|
||||
obj-$(CONFIG_SPI_RB4XX) += spi-rb4xx.o
|
||||
obj-$(CONFIG_MACH_REALTEK_RTL) += spi-realtek-rtl.o
|
||||
+obj-$(CONFIG_SPI_REALTEK_SNAND) += spi-realtek-rtl-snand.o
|
||||
obj-$(CONFIG_SPI_RPCIF) += spi-rpc-if.o
|
||||
obj-$(CONFIG_SPI_RSPI) += spi-rspi.o
|
||||
obj-$(CONFIG_SPI_RZV2M_CSI) += spi-rzv2m-csi.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/spi/spi-realtek-rtl-snand.c
|
||||
@@ -0,0 +1,405 @@
|
||||
+// SPDX-License-Identifier: GPL-2.0-only
|
||||
+
|
||||
+#include <linux/completion.h>
|
||||
+#include <linux/dma-mapping.h>
|
||||
+#include <linux/interrupt.h>
|
||||
+#include <linux/mod_devicetable.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/regmap.h>
|
||||
+#include <linux/spi/spi.h>
|
||||
+#include <linux/spi/spi-mem.h>
|
||||
+
|
||||
+#define SNAFCFR 0x00
|
||||
+#define SNAFCFR_DMA_IE BIT(20)
|
||||
+#define SNAFCCR 0x04
|
||||
+#define SNAFWCMR 0x08
|
||||
+#define SNAFRCMR 0x0c
|
||||
+#define SNAFRDR 0x10
|
||||
+#define SNAFWDR 0x14
|
||||
+#define SNAFDTR 0x18
|
||||
+#define SNAFDRSAR 0x1c
|
||||
+#define SNAFDIR 0x20
|
||||
+#define SNAFDIR_DMA_IP BIT(0)
|
||||
+#define SNAFDLR 0x24
|
||||
+#define SNAFSR 0x40
|
||||
+#define SNAFSR_NFCOS BIT(3)
|
||||
+#define SNAFSR_NFDRS BIT(2)
|
||||
+#define SNAFSR_NFDWS BIT(1)
|
||||
+
|
||||
+#define CMR_LEN(len) ((len) - 1)
|
||||
+#define CMR_WID(width) (((width) >> 1) << 28)
|
||||
+
|
||||
+struct rtl_snand {
|
||||
+ struct device *dev;
|
||||
+ struct regmap *regmap;
|
||||
+ struct completion comp;
|
||||
+};
|
||||
+
|
||||
+static irqreturn_t rtl_snand_irq(int irq, void *data)
|
||||
+{
|
||||
+ struct rtl_snand *snand = data;
|
||||
+ u32 val = 0;
|
||||
+
|
||||
+ regmap_read(snand->regmap, SNAFSR, &val);
|
||||
+ if (val & (SNAFSR_NFCOS | SNAFSR_NFDRS | SNAFSR_NFDWS))
|
||||
+ return IRQ_NONE;
|
||||
+
|
||||
+ regmap_write(snand->regmap, SNAFDIR, SNAFDIR_DMA_IP);
|
||||
+ complete(&snand->comp);
|
||||
+
|
||||
+ return IRQ_HANDLED;
|
||||
+}
|
||||
+
|
||||
+static bool rtl_snand_supports_op(struct spi_mem *mem,
|
||||
+ const struct spi_mem_op *op)
|
||||
+{
|
||||
+ if (!spi_mem_default_supports_op(mem, op))
|
||||
+ return false;
|
||||
+ if (op->cmd.nbytes != 1 || op->cmd.buswidth != 1)
|
||||
+ return false;
|
||||
+ return true;
|
||||
+}
|
||||
+
|
||||
+static void rtl_snand_set_cs(struct rtl_snand *snand, int cs, bool active)
|
||||
+{
|
||||
+ u32 val;
|
||||
+
|
||||
+ if (active)
|
||||
+ val = ~(1 << (4 * cs));
|
||||
+ else
|
||||
+ val = ~0;
|
||||
+
|
||||
+ regmap_write(snand->regmap, SNAFCCR, val);
|
||||
+}
|
||||
+
|
||||
+static int rtl_snand_wait_ready(struct rtl_snand *snand)
|
||||
+{
|
||||
+ u32 val;
|
||||
+
|
||||
+ return regmap_read_poll_timeout(snand->regmap, SNAFSR, val, !(val & SNAFSR_NFCOS),
|
||||
+ 0, 2 * USEC_PER_MSEC);
|
||||
+}
|
||||
+
|
||||
+static int rtl_snand_xfer_head(struct rtl_snand *snand, int cs, const struct spi_mem_op *op)
|
||||
+{
|
||||
+ int ret;
|
||||
+ u32 val, len = 0;
|
||||
+
|
||||
+ rtl_snand_set_cs(snand, cs, true);
|
||||
+
|
||||
+ val = op->cmd.opcode << 24;
|
||||
+ len = 1;
|
||||
+ if (op->addr.nbytes && op->addr.buswidth == 1) {
|
||||
+ val |= op->addr.val << ((3 - op->addr.nbytes) * 8);
|
||||
+ len += op->addr.nbytes;
|
||||
+ }
|
||||
+
|
||||
+ ret = rtl_snand_wait_ready(snand);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = regmap_write(snand->regmap, SNAFWCMR, CMR_LEN(len));
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = regmap_write(snand->regmap, SNAFWDR, val);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = rtl_snand_wait_ready(snand);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ if (op->addr.buswidth > 1) {
|
||||
+ val = op->addr.val << ((3 - op->addr.nbytes) * 8);
|
||||
+ len = op->addr.nbytes;
|
||||
+
|
||||
+ ret = regmap_write(snand->regmap, SNAFWCMR,
|
||||
+ CMR_WID(op->addr.buswidth) | CMR_LEN(len));
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = regmap_write(snand->regmap, SNAFWDR, val);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = rtl_snand_wait_ready(snand);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ if (op->dummy.nbytes) {
|
||||
+ val = 0;
|
||||
+
|
||||
+ ret = regmap_write(snand->regmap, SNAFWCMR,
|
||||
+ CMR_WID(op->dummy.buswidth) | CMR_LEN(op->dummy.nbytes));
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = regmap_write(snand->regmap, SNAFWDR, val);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = rtl_snand_wait_ready(snand);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static void rtl_snand_xfer_tail(struct rtl_snand *snand, int cs)
|
||||
+{
|
||||
+ rtl_snand_set_cs(snand, cs, false);
|
||||
+}
|
||||
+
|
||||
+static int rtl_snand_xfer(struct rtl_snand *snand, int cs, const struct spi_mem_op *op)
|
||||
+{
|
||||
+ unsigned int pos, nbytes;
|
||||
+ int ret;
|
||||
+ u32 val, len = 0;
|
||||
+
|
||||
+ ret = rtl_snand_xfer_head(snand, cs, op);
|
||||
+ if (ret)
|
||||
+ goto out_deselect;
|
||||
+
|
||||
+ if (op->data.dir == SPI_MEM_DATA_IN) {
|
||||
+ pos = 0;
|
||||
+ len = op->data.nbytes;
|
||||
+
|
||||
+ while (pos < len) {
|
||||
+ nbytes = len - pos;
|
||||
+ if (nbytes > 4)
|
||||
+ nbytes = 4;
|
||||
+
|
||||
+ ret = rtl_snand_wait_ready(snand);
|
||||
+ if (ret)
|
||||
+ goto out_deselect;
|
||||
+
|
||||
+ ret = regmap_write(snand->regmap, SNAFRCMR,
|
||||
+ CMR_WID(op->data.buswidth) | CMR_LEN(nbytes));
|
||||
+ if (ret)
|
||||
+ goto out_deselect;
|
||||
+
|
||||
+ ret = rtl_snand_wait_ready(snand);
|
||||
+ if (ret)
|
||||
+ goto out_deselect;
|
||||
+
|
||||
+ ret = regmap_read(snand->regmap, SNAFRDR, &val);
|
||||
+ if (ret)
|
||||
+ goto out_deselect;
|
||||
+
|
||||
+ memcpy(op->data.buf.in + pos, &val, nbytes);
|
||||
+
|
||||
+ pos += nbytes;
|
||||
+ }
|
||||
+ } else if (op->data.dir == SPI_MEM_DATA_OUT) {
|
||||
+ pos = 0;
|
||||
+ len = op->data.nbytes;
|
||||
+
|
||||
+ while (pos < len) {
|
||||
+ nbytes = len - pos;
|
||||
+ if (nbytes > 4)
|
||||
+ nbytes = 4;
|
||||
+
|
||||
+ memcpy(&val, op->data.buf.out + pos, nbytes);
|
||||
+
|
||||
+ pos += nbytes;
|
||||
+
|
||||
+ ret = regmap_write(snand->regmap, SNAFWCMR, CMR_LEN(nbytes));
|
||||
+ if (ret)
|
||||
+ goto out_deselect;
|
||||
+
|
||||
+ ret = regmap_write(snand->regmap, SNAFWDR, val);
|
||||
+ if (ret)
|
||||
+ goto out_deselect;
|
||||
+
|
||||
+ ret = rtl_snand_wait_ready(snand);
|
||||
+ if (ret)
|
||||
+ goto out_deselect;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+out_deselect:
|
||||
+ rtl_snand_xfer_tail(snand, cs);
|
||||
+
|
||||
+ if (ret)
|
||||
+ dev_err(snand->dev, "transfer failed %d\n", ret);
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static int rtl_snand_dma_xfer(struct rtl_snand *snand, int cs, const struct spi_mem_op *op)
|
||||
+{
|
||||
+ int ret;
|
||||
+ dma_addr_t buf_dma;
|
||||
+ enum dma_data_direction dir;
|
||||
+ u32 trig;
|
||||
+
|
||||
+ ret = rtl_snand_xfer_head(snand, cs, op);
|
||||
+ if (ret)
|
||||
+ goto out_deselect;
|
||||
+
|
||||
+ if (op->data.dir == SPI_MEM_DATA_IN) {
|
||||
+ dir = DMA_FROM_DEVICE;
|
||||
+ trig = 0;
|
||||
+ } else if (op->data.dir == SPI_MEM_DATA_OUT) {
|
||||
+ dir = DMA_TO_DEVICE;
|
||||
+ trig = 1;
|
||||
+ } else {
|
||||
+ ret = -EOPNOTSUPP;
|
||||
+ goto out_deselect;
|
||||
+ }
|
||||
+
|
||||
+ buf_dma = dma_map_single(snand->dev, op->data.buf.in, op->data.nbytes, dir);
|
||||
+ ret = dma_mapping_error(snand->dev, buf_dma);
|
||||
+ if (ret)
|
||||
+ goto out_deselect;
|
||||
+
|
||||
+ ret = regmap_write(snand->regmap, SNAFDIR, SNAFDIR_DMA_IP);
|
||||
+ if (ret)
|
||||
+ goto out_unmap;
|
||||
+
|
||||
+ ret = regmap_update_bits(snand->regmap, SNAFCFR, SNAFCFR_DMA_IE, SNAFCFR_DMA_IE);
|
||||
+ if (ret)
|
||||
+ goto out_unmap;
|
||||
+
|
||||
+ reinit_completion(&snand->comp);
|
||||
+
|
||||
+ ret = regmap_write(snand->regmap, SNAFDRSAR, buf_dma);
|
||||
+ if (ret)
|
||||
+ goto out_disable_int;
|
||||
+
|
||||
+ ret = regmap_write(snand->regmap, SNAFDLR,
|
||||
+ CMR_WID(op->data.buswidth) | (op->data.nbytes & 0xffff));
|
||||
+ if (ret)
|
||||
+ goto out_disable_int;
|
||||
+
|
||||
+ ret = regmap_write(snand->regmap, SNAFDTR, trig);
|
||||
+ if (ret)
|
||||
+ goto out_disable_int;
|
||||
+
|
||||
+ if (!wait_for_completion_timeout(&snand->comp, usecs_to_jiffies(20000)))
|
||||
+ ret = -ETIMEDOUT;
|
||||
+
|
||||
+ if (ret)
|
||||
+ goto out_disable_int;
|
||||
+
|
||||
+out_disable_int:
|
||||
+ regmap_update_bits(snand->regmap, SNAFCFR, SNAFCFR_DMA_IE, 0);
|
||||
+out_unmap:
|
||||
+ dma_unmap_single(snand->dev, buf_dma, op->data.nbytes, dir);
|
||||
+out_deselect:
|
||||
+ rtl_snand_xfer_tail(snand, cs);
|
||||
+
|
||||
+ if (ret)
|
||||
+ dev_err(snand->dev, "transfer failed %d\n", ret);
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static bool rtl_snand_dma_op(const struct spi_mem_op *op)
|
||||
+{
|
||||
+ switch (op->data.dir) {
|
||||
+ case SPI_MEM_DATA_IN:
|
||||
+ case SPI_MEM_DATA_OUT:
|
||||
+ return op->data.nbytes > 32;
|
||||
+ default:
|
||||
+ return false;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+static int rtl_snand_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
|
||||
+{
|
||||
+ struct rtl_snand *snand = spi_controller_get_devdata(mem->spi->controller);
|
||||
+ int cs = spi_get_chipselect(mem->spi, 0);
|
||||
+
|
||||
+ dev_dbg(snand->dev, "cs %d op cmd %02x %d:%d, dummy %d:%d, addr %08llx@%d:%d, data %d:%d\n",
|
||||
+ cs, op->cmd.opcode,
|
||||
+ op->cmd.buswidth, op->cmd.nbytes, op->dummy.buswidth,
|
||||
+ op->dummy.nbytes, op->addr.val, op->addr.buswidth,
|
||||
+ op->addr.nbytes, op->data.buswidth, op->data.nbytes);
|
||||
+
|
||||
+ if (rtl_snand_dma_op(op))
|
||||
+ return rtl_snand_dma_xfer(snand, cs, op);
|
||||
+ else
|
||||
+ return rtl_snand_xfer(snand, cs, op);
|
||||
+}
|
||||
+
|
||||
+static const struct spi_controller_mem_ops rtl_snand_mem_ops = {
|
||||
+ .supports_op = rtl_snand_supports_op,
|
||||
+ .exec_op = rtl_snand_exec_op,
|
||||
+};
|
||||
+
|
||||
+static const struct of_device_id rtl_snand_match[] = {
|
||||
+ { .compatible = "realtek,rtl9301-snand" },
|
||||
+ { .compatible = "realtek,rtl9302b-snand" },
|
||||
+ { .compatible = "realtek,rtl9302c-snand" },
|
||||
+ { .compatible = "realtek,rtl9303-snand" },
|
||||
+ {},
|
||||
+};
|
||||
+MODULE_DEVICE_TABLE(of, rtl_snand_match);
|
||||
+
|
||||
+static int rtl_snand_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct rtl_snand *snand;
|
||||
+ struct device *dev = &pdev->dev;
|
||||
+ struct spi_controller *ctrl;
|
||||
+ void __iomem *base;
|
||||
+ const struct regmap_config rc = {
|
||||
+ .reg_bits = 32,
|
||||
+ .val_bits = 32,
|
||||
+ .reg_stride = 4,
|
||||
+ .cache_type = REGCACHE_NONE,
|
||||
+ };
|
||||
+ int irq, ret;
|
||||
+
|
||||
+ ctrl = devm_spi_alloc_host(dev, sizeof(*snand));
|
||||
+ if (!ctrl)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ snand = spi_controller_get_devdata(ctrl);
|
||||
+ snand->dev = dev;
|
||||
+
|
||||
+ base = devm_platform_ioremap_resource(pdev, 0);
|
||||
+ if (IS_ERR(base))
|
||||
+ return PTR_ERR(base);
|
||||
+
|
||||
+ snand->regmap = devm_regmap_init_mmio(dev, base, &rc);
|
||||
+ if (IS_ERR(snand->regmap))
|
||||
+ return PTR_ERR(snand->regmap);
|
||||
+
|
||||
+ init_completion(&snand->comp);
|
||||
+
|
||||
+ irq = platform_get_irq(pdev, 0);
|
||||
+ if (irq < 0)
|
||||
+ return irq;
|
||||
+
|
||||
+ ret = dma_set_mask(snand->dev, DMA_BIT_MASK(32));
|
||||
+ if (ret)
|
||||
+ return dev_err_probe(dev, ret, "failed to set DMA mask\n");
|
||||
+
|
||||
+ ret = devm_request_irq(dev, irq, rtl_snand_irq, 0, "rtl-snand", snand);
|
||||
+ if (ret)
|
||||
+ return dev_err_probe(dev, ret, "failed to request irq\n");
|
||||
+
|
||||
+ ctrl->num_chipselect = 2;
|
||||
+ ctrl->mem_ops = &rtl_snand_mem_ops;
|
||||
+ ctrl->bits_per_word_mask = SPI_BPW_MASK(8);
|
||||
+ ctrl->mode_bits = SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD;
|
||||
+ device_set_node(&ctrl->dev, dev_fwnode(dev));
|
||||
+
|
||||
+ return devm_spi_register_controller(dev, ctrl);
|
||||
+}
|
||||
+
|
||||
+static struct platform_driver rtl_snand_driver = {
|
||||
+ .driver = {
|
||||
+ .name = "realtek-rtl-snand",
|
||||
+ .of_match_table = rtl_snand_match,
|
||||
+ },
|
||||
+ .probe = rtl_snand_probe,
|
||||
+};
|
||||
+module_platform_driver(rtl_snand_driver);
|
||||
+
|
||||
+MODULE_DESCRIPTION("Realtek SPI-NAND Flash Controller Driver");
|
||||
+MODULE_LICENSE("GPL");
|
||||
@ -1,96 +0,0 @@
|
||||
From 25d284715845a465a1a3693a09cf8b6ab8bd9caf Mon Sep 17 00:00:00 2001
|
||||
From: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Date: Thu, 31 Oct 2024 08:49:20 +1300
|
||||
Subject: [PATCH] spi: spi-mem: rtl-snand: Correctly handle DMA transfers
|
||||
|
||||
The RTL9300 has some limitations on the maximum DMA transfers possible.
|
||||
For reads this is 2080 bytes (520*4) for writes this is 520 bytes. Deal
|
||||
with this by splitting transfers into appropriately sized parts.
|
||||
|
||||
Fixes: 42d20a6a61b8 ("spi: spi-mem: Add Realtek SPI-NAND controller")
|
||||
Signed-off-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Link: https://patch.msgid.link/20241030194920.3202282-1-chris.packham@alliedtelesis.co.nz
|
||||
Signed-off-by: Mark Brown <broonie@kernel.org>
|
||||
---
|
||||
drivers/spi/spi-realtek-rtl-snand.c | 46 +++++++++++++++++++----------
|
||||
1 file changed, 30 insertions(+), 16 deletions(-)
|
||||
|
||||
--- a/drivers/spi/spi-realtek-rtl-snand.c
|
||||
+++ b/drivers/spi/spi-realtek-rtl-snand.c
|
||||
@@ -231,19 +231,22 @@ out_deselect:
|
||||
|
||||
static int rtl_snand_dma_xfer(struct rtl_snand *snand, int cs, const struct spi_mem_op *op)
|
||||
{
|
||||
+ unsigned int pos, nbytes;
|
||||
int ret;
|
||||
dma_addr_t buf_dma;
|
||||
enum dma_data_direction dir;
|
||||
- u32 trig;
|
||||
+ u32 trig, len, maxlen;
|
||||
|
||||
ret = rtl_snand_xfer_head(snand, cs, op);
|
||||
if (ret)
|
||||
goto out_deselect;
|
||||
|
||||
if (op->data.dir == SPI_MEM_DATA_IN) {
|
||||
+ maxlen = 2080;
|
||||
dir = DMA_FROM_DEVICE;
|
||||
trig = 0;
|
||||
} else if (op->data.dir == SPI_MEM_DATA_OUT) {
|
||||
+ maxlen = 520;
|
||||
dir = DMA_TO_DEVICE;
|
||||
trig = 1;
|
||||
} else {
|
||||
@@ -264,26 +267,37 @@ static int rtl_snand_dma_xfer(struct rtl
|
||||
if (ret)
|
||||
goto out_unmap;
|
||||
|
||||
- reinit_completion(&snand->comp);
|
||||
+ pos = 0;
|
||||
+ len = op->data.nbytes;
|
||||
|
||||
- ret = regmap_write(snand->regmap, SNAFDRSAR, buf_dma);
|
||||
- if (ret)
|
||||
- goto out_disable_int;
|
||||
+ while (pos < len) {
|
||||
+ nbytes = len - pos;
|
||||
+ if (nbytes > maxlen)
|
||||
+ nbytes = maxlen;
|
||||
|
||||
- ret = regmap_write(snand->regmap, SNAFDLR,
|
||||
- CMR_WID(op->data.buswidth) | (op->data.nbytes & 0xffff));
|
||||
- if (ret)
|
||||
- goto out_disable_int;
|
||||
+ reinit_completion(&snand->comp);
|
||||
|
||||
- ret = regmap_write(snand->regmap, SNAFDTR, trig);
|
||||
- if (ret)
|
||||
- goto out_disable_int;
|
||||
+ ret = regmap_write(snand->regmap, SNAFDRSAR, buf_dma + pos);
|
||||
+ if (ret)
|
||||
+ goto out_disable_int;
|
||||
|
||||
- if (!wait_for_completion_timeout(&snand->comp, usecs_to_jiffies(20000)))
|
||||
- ret = -ETIMEDOUT;
|
||||
+ pos += nbytes;
|
||||
|
||||
- if (ret)
|
||||
- goto out_disable_int;
|
||||
+ ret = regmap_write(snand->regmap, SNAFDLR,
|
||||
+ CMR_WID(op->data.buswidth) | nbytes);
|
||||
+ if (ret)
|
||||
+ goto out_disable_int;
|
||||
+
|
||||
+ ret = regmap_write(snand->regmap, SNAFDTR, trig);
|
||||
+ if (ret)
|
||||
+ goto out_disable_int;
|
||||
+
|
||||
+ if (!wait_for_completion_timeout(&snand->comp, usecs_to_jiffies(20000)))
|
||||
+ ret = -ETIMEDOUT;
|
||||
+
|
||||
+ if (ret)
|
||||
+ goto out_disable_int;
|
||||
+ }
|
||||
|
||||
out_disable_int:
|
||||
regmap_update_bits(snand->regmap, SNAFCFR, SNAFCFR_DMA_IE, 0);
|
||||
@ -1,84 +0,0 @@
|
||||
From c5eda0333076e031197816454998a918f1de0831 Mon Sep 17 00:00:00 2001
|
||||
From: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Date: Fri, 1 Nov 2024 09:03:46 +1300
|
||||
Subject: [PATCH] dt-bindings: i2c: Add Realtek RTL I2C Controller
|
||||
|
||||
Add dt-schema for the I2C controller on the RTL9300 Ethernet switch
|
||||
with integrated SoC.
|
||||
|
||||
Signed-off-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
|
||||
--- /dev/null
|
||||
+++ b/Documentation/devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml
|
||||
@@ -0,0 +1,69 @@
|
||||
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
+%YAML 1.2
|
||||
+---
|
||||
+$id: http://devicetree.org/schemas/i2c/realtek,rtl9301-i2c.yaml#
|
||||
+$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
+
|
||||
+title: Realtek RTL I2C Controller
|
||||
+
|
||||
+maintainers:
|
||||
+ - Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
+
|
||||
+description:
|
||||
+ The RTL9300 SoC has two I2C controllers. Each of these has an SCL line (which
|
||||
+ if not-used for SCL can be a GPIO). There are 8 common SDA lines that can be
|
||||
+ assigned to either I2C controller.
|
||||
+
|
||||
+properties:
|
||||
+ compatible:
|
||||
+ oneOf:
|
||||
+ - items:
|
||||
+ - enum:
|
||||
+ - realtek,rtl9302b-i2c
|
||||
+ - realtek,rtl9302c-i2c
|
||||
+ - realtek,rtl9303-i2c
|
||||
+ - const: realtek,rtl9301-i2c
|
||||
+ - const: realtek,rtl9301-i2c
|
||||
+
|
||||
+ reg:
|
||||
+ description: Register offset and size this I2C controller.
|
||||
+
|
||||
+ "#address-cells":
|
||||
+ const: 1
|
||||
+
|
||||
+ "#size-cells":
|
||||
+ const: 0
|
||||
+
|
||||
+patternProperties:
|
||||
+ '^i2c@[0-7]$':
|
||||
+ $ref: /schemas/i2c/i2c-controller.yaml
|
||||
+ unevaluatedProperties: false
|
||||
+
|
||||
+ properties:
|
||||
+ reg:
|
||||
+ description: The SDA pin associated with the I2C bus.
|
||||
+ maxItems: 1
|
||||
+
|
||||
+ required:
|
||||
+ - reg
|
||||
+
|
||||
+required:
|
||||
+ - compatible
|
||||
+ - reg
|
||||
+
|
||||
+additionalProperties: false
|
||||
+
|
||||
+examples:
|
||||
+ - |
|
||||
+ i2c@36c {
|
||||
+ compatible = "realtek,rtl9301-i2c";
|
||||
+ reg = <0x36c 0x14>;
|
||||
+ #address-cells = <1>;
|
||||
+ #size-cells = <0>;
|
||||
+
|
||||
+ i2c@2 {
|
||||
+ reg = <2>;
|
||||
+ #address-cells = <1>;
|
||||
+ #size-cells = <0>;
|
||||
+ };
|
||||
+ };
|
||||
@ -1,493 +0,0 @@
|
||||
From c366be720235301fdadf67e6f1ea6ff32669c074 Mon Sep 17 00:00:00 2001
|
||||
From: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Date: Wed, 6 Nov 2024 13:18:35 +1300
|
||||
Subject: [PATCH] i2c: Add driver for the RTL9300 I2C controller
|
||||
|
||||
Add support for the I2C controller on the RTL9300 SoC. There are two I2C
|
||||
controllers in the RTL9300 that are part of the Ethernet switch register
|
||||
block. Each of these controllers owns a SCL pin (GPIO8 for the fiorst
|
||||
I2C controller, GPIO17 for the second). There are 8 possible SDA pins
|
||||
(GPIO9-16) that can be assigned to either I2C controller. This
|
||||
relationship is represented in the device tree with a child node for
|
||||
each SDA line in use.
|
||||
|
||||
This is based on the openwrt implementation[1] but has been
|
||||
significantly modified
|
||||
|
||||
[1] - https://git.openwrt.org/?p=openwrt/openwrt.git;a=blob;f=target/linux/realtek/files-5.15/drivers/i2c/busses/i2c-rtl9300.c
|
||||
|
||||
Signed-off-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Reviewed-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
|
||||
--- a/MAINTAINERS
|
||||
+++ b/MAINTAINERS
|
||||
@@ -20164,6 +20164,13 @@ S: Maintained
|
||||
T: git https://github.com/pkshih/rtw.git
|
||||
F: drivers/net/wireless/realtek/rtl8xxxu/
|
||||
|
||||
+RTL9300 I2C DRIVER (rtl9300-i2c)
|
||||
+M: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
+L: linux-i2c@vger.kernel.org
|
||||
+S: Maintained
|
||||
+F: Documentation/devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml
|
||||
+F: drivers/i2c/busses/i2c-rtl9300.c
|
||||
+
|
||||
RTRS TRANSPORT DRIVERS
|
||||
M: Md. Haris Iqbal <haris.iqbal@ionos.com>
|
||||
M: Jack Wang <jinpu.wang@ionos.com>
|
||||
--- a/drivers/i2c/busses/Kconfig
|
||||
+++ b/drivers/i2c/busses/Kconfig
|
||||
@@ -1062,6 +1062,16 @@ config I2C_RK3X
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called i2c-rk3x.
|
||||
|
||||
+config I2C_RTL9300
|
||||
+ tristate "Realtek RTL9300 I2C controller"
|
||||
+ depends on MACH_REALTEK_RTL || COMPILE_TEST
|
||||
+ help
|
||||
+ Say Y here to include support for the I2C controller in Realtek
|
||||
+ RTL9300 SoCs.
|
||||
+
|
||||
+ This driver can also be built as a module. If so, the module will
|
||||
+ be called i2c-rtl9300.
|
||||
+
|
||||
config I2C_RZV2M
|
||||
tristate "Renesas RZ/V2M adapter"
|
||||
depends on ARCH_RENESAS || COMPILE_TEST
|
||||
--- a/drivers/i2c/busses/Makefile
|
||||
+++ b/drivers/i2c/busses/Makefile
|
||||
@@ -103,6 +103,7 @@ obj-$(CONFIG_I2C_QCOM_GENI) += i2c-qcom-
|
||||
obj-$(CONFIG_I2C_QUP) += i2c-qup.o
|
||||
obj-$(CONFIG_I2C_RIIC) += i2c-riic.o
|
||||
obj-$(CONFIG_I2C_RK3X) += i2c-rk3x.o
|
||||
+obj-$(CONFIG_I2C_RTL9300) += i2c-rtl9300.o
|
||||
obj-$(CONFIG_I2C_RZV2M) += i2c-rzv2m.o
|
||||
obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
|
||||
obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -0,0 +1,423 @@
|
||||
+// SPDX-License-Identifier: GPL-2.0-only
|
||||
+
|
||||
+#include <linux/bits.h>
|
||||
+#include <linux/i2c.h>
|
||||
+#include <linux/i2c-mux.h>
|
||||
+#include <linux/mod_devicetable.h>
|
||||
+#include <linux/mfd/syscon.h>
|
||||
+#include <linux/mutex.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/regmap.h>
|
||||
+
|
||||
+enum rtl9300_bus_freq {
|
||||
+ RTL9300_I2C_STD_FREQ,
|
||||
+ RTL9300_I2C_FAST_FREQ,
|
||||
+};
|
||||
+
|
||||
+struct rtl9300_i2c;
|
||||
+
|
||||
+struct rtl9300_i2c_chan {
|
||||
+ struct i2c_adapter adap;
|
||||
+ struct rtl9300_i2c *i2c;
|
||||
+ enum rtl9300_bus_freq bus_freq;
|
||||
+ u8 sda_pin;
|
||||
+};
|
||||
+
|
||||
+#define RTL9300_I2C_MUX_NCHAN 8
|
||||
+
|
||||
+struct rtl9300_i2c {
|
||||
+ struct regmap *regmap;
|
||||
+ struct device *dev;
|
||||
+ struct rtl9300_i2c_chan chans[RTL9300_I2C_MUX_NCHAN];
|
||||
+ u32 reg_base;
|
||||
+ u8 sda_pin;
|
||||
+ struct mutex lock;
|
||||
+};
|
||||
+
|
||||
+#define RTL9300_I2C_MST_CTRL1 0x0
|
||||
+#define RTL9300_I2C_MST_CTRL1_MEM_ADDR_OFS 8
|
||||
+#define RTL9300_I2C_MST_CTRL1_MEM_ADDR_MASK GENMASK(31, 8)
|
||||
+#define RTL9300_I2C_MST_CTRL1_SDA_OUT_SEL_OFS 4
|
||||
+#define RTL9300_I2C_MST_CTRL1_SDA_OUT_SEL_MASK GENMASK(6, 4)
|
||||
+#define RTL9300_I2C_MST_CTRL1_GPIO_SCL_SEL BIT(3)
|
||||
+#define RTL9300_I2C_MST_CTRL1_RWOP BIT(2)
|
||||
+#define RTL9300_I2C_MST_CTRL1_I2C_FAIL BIT(1)
|
||||
+#define RTL9300_I2C_MST_CTRL1_I2C_TRIG BIT(0)
|
||||
+#define RTL9300_I2C_MST_CTRL2 0x4
|
||||
+#define RTL9300_I2C_MST_CTRL2_RD_MODE BIT(15)
|
||||
+#define RTL9300_I2C_MST_CTRL2_DEV_ADDR_OFS 8
|
||||
+#define RTL9300_I2C_MST_CTRL2_DEV_ADDR_MASK GENMASK(14, 8)
|
||||
+#define RTL9300_I2C_MST_CTRL2_DATA_WIDTH_OFS 4
|
||||
+#define RTL9300_I2C_MST_CTRL2_DATA_WIDTH_MASK GENMASK(7, 4)
|
||||
+#define RTL9300_I2C_MST_CTRL2_MEM_ADDR_WIDTH_OFS 2
|
||||
+#define RTL9300_I2C_MST_CTRL2_MEM_ADDR_WIDTH_MASK GENMASK(3, 2)
|
||||
+#define RTL9300_I2C_MST_CTRL2_SCL_FREQ_OFS 0
|
||||
+#define RTL9300_I2C_MST_CTRL2_SCL_FREQ_MASK GENMASK(1, 0)
|
||||
+#define RTL9300_I2C_MST_DATA_WORD0 0x8
|
||||
+#define RTL9300_I2C_MST_DATA_WORD1 0xc
|
||||
+#define RTL9300_I2C_MST_DATA_WORD2 0x10
|
||||
+#define RTL9300_I2C_MST_DATA_WORD3 0x14
|
||||
+
|
||||
+#define RTL9300_I2C_MST_GLB_CTRL 0x384
|
||||
+
|
||||
+static int rtl9300_i2c_reg_addr_set(struct rtl9300_i2c *i2c, u32 reg, u16 len)
|
||||
+{
|
||||
+ u32 val, mask;
|
||||
+ int ret;
|
||||
+
|
||||
+ val = len << RTL9300_I2C_MST_CTRL2_MEM_ADDR_WIDTH_OFS;
|
||||
+ mask = RTL9300_I2C_MST_CTRL2_MEM_ADDR_WIDTH_MASK;
|
||||
+
|
||||
+ ret = regmap_update_bits(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL2, mask, val);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ val = reg << RTL9300_I2C_MST_CTRL1_MEM_ADDR_OFS;
|
||||
+ mask = RTL9300_I2C_MST_CTRL1_MEM_ADDR_MASK;
|
||||
+
|
||||
+ return regmap_update_bits(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL1, mask, val);
|
||||
+}
|
||||
+
|
||||
+static int rtl9300_i2c_config_io(struct rtl9300_i2c *i2c, u8 sda_pin)
|
||||
+{
|
||||
+ int ret;
|
||||
+ u32 val, mask;
|
||||
+
|
||||
+ ret = regmap_update_bits(i2c->regmap, RTL9300_I2C_MST_GLB_CTRL, BIT(sda_pin), BIT(sda_pin));
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ val = (sda_pin << RTL9300_I2C_MST_CTRL1_SDA_OUT_SEL_OFS) |
|
||||
+ RTL9300_I2C_MST_CTRL1_GPIO_SCL_SEL;
|
||||
+ mask = RTL9300_I2C_MST_CTRL1_SDA_OUT_SEL_MASK | RTL9300_I2C_MST_CTRL1_GPIO_SCL_SEL;
|
||||
+
|
||||
+ return regmap_update_bits(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL1, mask, val);
|
||||
+}
|
||||
+
|
||||
+static int rtl9300_i2c_config_xfer(struct rtl9300_i2c *i2c, struct rtl9300_i2c_chan *chan,
|
||||
+ u16 addr, u16 len)
|
||||
+{
|
||||
+ u32 val, mask;
|
||||
+
|
||||
+ val = chan->bus_freq << RTL9300_I2C_MST_CTRL2_SCL_FREQ_OFS;
|
||||
+ mask = RTL9300_I2C_MST_CTRL2_SCL_FREQ_MASK;
|
||||
+
|
||||
+ val |= addr << RTL9300_I2C_MST_CTRL2_DEV_ADDR_OFS;
|
||||
+ mask |= RTL9300_I2C_MST_CTRL2_DEV_ADDR_MASK;
|
||||
+
|
||||
+ val |= ((len - 1) & 0xf) << RTL9300_I2C_MST_CTRL2_DATA_WIDTH_OFS;
|
||||
+ mask |= RTL9300_I2C_MST_CTRL2_DATA_WIDTH_MASK;
|
||||
+
|
||||
+ mask |= RTL9300_I2C_MST_CTRL2_RD_MODE;
|
||||
+
|
||||
+ return regmap_update_bits(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL2, mask, val);
|
||||
+}
|
||||
+
|
||||
+static int rtl9300_i2c_read(struct rtl9300_i2c *i2c, u8 *buf, int len)
|
||||
+{
|
||||
+ u32 vals[4] = {};
|
||||
+ int i, ret;
|
||||
+
|
||||
+ if (len > 16)
|
||||
+ return -EIO;
|
||||
+
|
||||
+ ret = regmap_bulk_read(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0,
|
||||
+ vals, ARRAY_SIZE(vals));
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ for (i = 0; i < len; i++) {
|
||||
+ buf[i] = vals[i/4] & 0xff;
|
||||
+ vals[i/4] >>= 8;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int rtl9300_i2c_write(struct rtl9300_i2c *i2c, u8 *buf, int len)
|
||||
+{
|
||||
+ u32 vals[4] = {};
|
||||
+ int i;
|
||||
+
|
||||
+ if (len > 16)
|
||||
+ return -EIO;
|
||||
+
|
||||
+ for (i = 0; i < len; i++) {
|
||||
+ if (i % 4 == 0)
|
||||
+ vals[i/4] = 0;
|
||||
+ vals[i/4] <<= 8;
|
||||
+ vals[i/4] |= buf[i];
|
||||
+ }
|
||||
+
|
||||
+ return regmap_bulk_write(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0,
|
||||
+ vals, ARRAY_SIZE(vals));
|
||||
+}
|
||||
+
|
||||
+static int rtl9300_i2c_writel(struct rtl9300_i2c *i2c, u32 data)
|
||||
+{
|
||||
+ return regmap_write(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0, data);
|
||||
+}
|
||||
+
|
||||
+static int rtl9300_i2c_execute_xfer(struct rtl9300_i2c *i2c, char read_write,
|
||||
+ int size, union i2c_smbus_data *data, int len)
|
||||
+{
|
||||
+ u32 val, mask;
|
||||
+ int ret;
|
||||
+
|
||||
+ val = read_write == I2C_SMBUS_WRITE ? RTL9300_I2C_MST_CTRL1_RWOP : 0;
|
||||
+ mask = RTL9300_I2C_MST_CTRL1_RWOP;
|
||||
+
|
||||
+ val |= RTL9300_I2C_MST_CTRL1_I2C_TRIG;
|
||||
+ mask |= RTL9300_I2C_MST_CTRL1_I2C_TRIG;
|
||||
+
|
||||
+ ret = regmap_update_bits(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL1, mask, val);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = regmap_read_poll_timeout(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL1,
|
||||
+ val, !(val & RTL9300_I2C_MST_CTRL1_I2C_TRIG), 100, 2000);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ if (val & RTL9300_I2C_MST_CTRL1_I2C_FAIL)
|
||||
+ return -EIO;
|
||||
+
|
||||
+ if (read_write == I2C_SMBUS_READ) {
|
||||
+ if (size == I2C_SMBUS_BYTE || size == I2C_SMBUS_BYTE_DATA) {
|
||||
+ ret = regmap_read(i2c->regmap,
|
||||
+ i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0, &val);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ data->byte = val & 0xff;
|
||||
+ } else if (size == I2C_SMBUS_WORD_DATA) {
|
||||
+ ret = regmap_read(i2c->regmap,
|
||||
+ i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0, &val);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ data->word = val & 0xffff;
|
||||
+ } else {
|
||||
+ ret = rtl9300_i2c_read(i2c, &data->block[0], len);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int rtl9300_i2c_smbus_xfer(struct i2c_adapter *adap, u16 addr, unsigned short flags,
|
||||
+ char read_write, u8 command, int size,
|
||||
+ union i2c_smbus_data *data)
|
||||
+{
|
||||
+ struct rtl9300_i2c_chan *chan = i2c_get_adapdata(adap);
|
||||
+ struct rtl9300_i2c *i2c = chan->i2c;
|
||||
+ int len = 0, ret;
|
||||
+
|
||||
+ mutex_lock(&i2c->lock);
|
||||
+ if (chan->sda_pin != i2c->sda_pin) {
|
||||
+ ret = rtl9300_i2c_config_io(i2c, chan->sda_pin);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ i2c->sda_pin = chan->sda_pin;
|
||||
+ }
|
||||
+
|
||||
+ switch (size) {
|
||||
+ case I2C_SMBUS_QUICK:
|
||||
+ ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 0);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ ret = rtl9300_i2c_reg_addr_set(i2c, 0, 0);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ break;
|
||||
+
|
||||
+ case I2C_SMBUS_BYTE:
|
||||
+ if (read_write == I2C_SMBUS_WRITE) {
|
||||
+ ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 0);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ } else {
|
||||
+ ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 1);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ ret = rtl9300_i2c_reg_addr_set(i2c, 0, 0);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ }
|
||||
+ break;
|
||||
+
|
||||
+ case I2C_SMBUS_BYTE_DATA:
|
||||
+ ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 1);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ if (read_write == I2C_SMBUS_WRITE) {
|
||||
+ ret = rtl9300_i2c_writel(i2c, data->byte);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ }
|
||||
+ break;
|
||||
+
|
||||
+ case I2C_SMBUS_WORD_DATA:
|
||||
+ ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 2);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ if (read_write == I2C_SMBUS_WRITE) {
|
||||
+ ret = rtl9300_i2c_writel(i2c, data->word);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ }
|
||||
+ break;
|
||||
+
|
||||
+ case I2C_SMBUS_BLOCK_DATA:
|
||||
+ ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ ret = rtl9300_i2c_config_xfer(i2c, chan, addr, data->block[0]);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ if (read_write == I2C_SMBUS_WRITE) {
|
||||
+ ret = rtl9300_i2c_write(i2c, &data->block[1], data->block[0]);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ }
|
||||
+ len = data->block[0];
|
||||
+ break;
|
||||
+
|
||||
+ default:
|
||||
+ dev_err(&adap->dev, "Unsupported transaction %d\n", size);
|
||||
+ ret = -EOPNOTSUPP;
|
||||
+ goto out_unlock;
|
||||
+ }
|
||||
+
|
||||
+ ret = rtl9300_i2c_execute_xfer(i2c, read_write, size, data, len);
|
||||
+
|
||||
+out_unlock:
|
||||
+ mutex_unlock(&i2c->lock);
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static u32 rtl9300_i2c_func(struct i2c_adapter *a)
|
||||
+{
|
||||
+ return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
|
||||
+ I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
|
||||
+ I2C_FUNC_SMBUS_BLOCK_DATA;
|
||||
+}
|
||||
+
|
||||
+static const struct i2c_algorithm rtl9300_i2c_algo = {
|
||||
+ .smbus_xfer = rtl9300_i2c_smbus_xfer,
|
||||
+ .functionality = rtl9300_i2c_func,
|
||||
+};
|
||||
+
|
||||
+static struct i2c_adapter_quirks rtl9300_i2c_quirks = {
|
||||
+ .flags = I2C_AQ_NO_CLK_STRETCH,
|
||||
+ .max_read_len = 16,
|
||||
+ .max_write_len = 16,
|
||||
+};
|
||||
+
|
||||
+static int rtl9300_i2c_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct device *dev = &pdev->dev;
|
||||
+ struct rtl9300_i2c *i2c;
|
||||
+ u32 clock_freq, sda_pin;
|
||||
+ int ret, i = 0;
|
||||
+ struct fwnode_handle *child;
|
||||
+
|
||||
+ i2c = devm_kzalloc(dev, sizeof(*i2c), GFP_KERNEL);
|
||||
+ if (!i2c)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ i2c->regmap = syscon_node_to_regmap(dev->parent->of_node);
|
||||
+ if (IS_ERR(i2c->regmap))
|
||||
+ return PTR_ERR(i2c->regmap);
|
||||
+ i2c->dev = dev;
|
||||
+
|
||||
+ mutex_init(&i2c->lock);
|
||||
+
|
||||
+ ret = device_property_read_u32(dev, "reg", &i2c->reg_base);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ platform_set_drvdata(pdev, i2c);
|
||||
+
|
||||
+ if (device_get_child_node_count(dev) >= RTL9300_I2C_MUX_NCHAN)
|
||||
+ return dev_err_probe(dev, -EINVAL, "Too many channels\n");
|
||||
+
|
||||
+ device_for_each_child_node(dev, child) {
|
||||
+ struct rtl9300_i2c_chan *chan = &i2c->chans[i];
|
||||
+ struct i2c_adapter *adap = &chan->adap;
|
||||
+
|
||||
+ ret = fwnode_property_read_u32(child, "reg", &sda_pin);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = fwnode_property_read_u32(child, "clock-frequency", &clock_freq);
|
||||
+ if (ret)
|
||||
+ clock_freq = I2C_MAX_STANDARD_MODE_FREQ;
|
||||
+
|
||||
+ switch (clock_freq) {
|
||||
+ case I2C_MAX_STANDARD_MODE_FREQ:
|
||||
+ chan->bus_freq = RTL9300_I2C_STD_FREQ;
|
||||
+ break;
|
||||
+
|
||||
+ case I2C_MAX_FAST_MODE_FREQ:
|
||||
+ chan->bus_freq = RTL9300_I2C_FAST_FREQ;
|
||||
+ break;
|
||||
+ default:
|
||||
+ dev_warn(i2c->dev, "SDA%d clock-frequency %d not supported using default\n",
|
||||
+ sda_pin, clock_freq);
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ chan->sda_pin = sda_pin;
|
||||
+ chan->i2c = i2c;
|
||||
+ adap = &i2c->chans[i].adap;
|
||||
+ adap->owner = THIS_MODULE;
|
||||
+ adap->algo = &rtl9300_i2c_algo;
|
||||
+ adap->quirks = &rtl9300_i2c_quirks;
|
||||
+ adap->retries = 3;
|
||||
+ adap->dev.parent = dev;
|
||||
+ i2c_set_adapdata(adap, chan);
|
||||
+ adap->dev.of_node = to_of_node(child);
|
||||
+ snprintf(adap->name, sizeof(adap->name), "%s SDA%d\n", dev_name(dev), sda_pin);
|
||||
+ i++;
|
||||
+
|
||||
+ ret = devm_i2c_add_adapter(dev, adap);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ }
|
||||
+ i2c->sda_pin = 0xff;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static const struct of_device_id i2c_rtl9300_dt_ids[] = {
|
||||
+ { .compatible = "realtek,rtl9301-i2c" },
|
||||
+ { .compatible = "realtek,rtl9302b-i2c" },
|
||||
+ { .compatible = "realtek,rtl9302c-i2c" },
|
||||
+ { .compatible = "realtek,rtl9303-i2c" },
|
||||
+ {}
|
||||
+};
|
||||
+MODULE_DEVICE_TABLE(of, i2c_rtl9300_dt_ids);
|
||||
+
|
||||
+static struct platform_driver rtl9300_i2c_driver = {
|
||||
+ .probe = rtl9300_i2c_probe,
|
||||
+ .driver = {
|
||||
+ .name = "i2c-rtl9300",
|
||||
+ .of_match_table = i2c_rtl9300_dt_ids,
|
||||
+ },
|
||||
+};
|
||||
+
|
||||
+module_platform_driver(rtl9300_i2c_driver);
|
||||
+
|
||||
+MODULE_DESCRIPTION("RTL9300 I2C controller driver");
|
||||
+MODULE_LICENSE("GPL");
|
||||
@ -1,27 +0,0 @@
|
||||
From 5f05fc6e2218db7ecc52c60eb34b707fe69262c2 Mon Sep 17 00:00:00 2001
|
||||
From: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
|
||||
Date: Wed, 2 Jul 2025 08:15:31 +0200
|
||||
Subject: [PATCH] dt-bindings: i2c: realtek,rtl9301: Fix missing 'reg'
|
||||
constraint
|
||||
|
||||
Lists should have fixed amount if items, so add missing constraint to
|
||||
the 'reg' property (only one address space entry).
|
||||
|
||||
Fixes: c5eda0333076 ("dt-bindings: i2c: Add Realtek RTL I2C Controller")
|
||||
Cc: <stable@vger.kernel.org> # v6.13+
|
||||
Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250702061530.6940-2-krzysztof.kozlowski@linaro.org
|
||||
|
||||
--- a/Documentation/devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml
|
||||
+++ b/Documentation/devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml
|
||||
@@ -26,7 +26,8 @@ properties:
|
||||
- const: realtek,rtl9301-i2c
|
||||
|
||||
reg:
|
||||
- description: Register offset and size this I2C controller.
|
||||
+ items:
|
||||
+ - description: Register offset and size this I2C controller.
|
||||
|
||||
"#address-cells":
|
||||
const: 1
|
||||
@ -1,38 +0,0 @@
|
||||
From 57f312b955938fc4663f430cb57a71f2414f601b Mon Sep 17 00:00:00 2001
|
||||
From: Alex Guo <alexguo1023@gmail.com>
|
||||
Date: Sun, 10 Aug 2025 20:05:13 +0200
|
||||
Subject: [PATCH] i2c: rtl9300: Fix out-of-bounds bug in rtl9300_i2c_smbus_xfer
|
||||
|
||||
The data->block[0] variable comes from user. Without proper check,
|
||||
the variable may be very large to cause an out-of-bounds bug.
|
||||
|
||||
Fix this bug by checking the value of data->block[0] first.
|
||||
|
||||
1. commit 39244cc75482 ("i2c: ismt: Fix an out-of-bounds bug in
|
||||
ismt_access()")
|
||||
2. commit 92fbb6d1296f ("i2c: xgene-slimpro: Fix out-of-bounds bug in
|
||||
xgene_slimpro_i2c_xfer()")
|
||||
|
||||
Fixes: c366be720235 ("i2c: Add driver for the RTL9300 I2C controller")
|
||||
Signed-off-by: Alex Guo <alexguo1023@gmail.com>
|
||||
Cc: <stable@vger.kernel.org> # v6.13+
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Reviewed-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
|
||||
Signed-off-by: Sven Eckelmann <sven@narfation.org>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250810-i2c-rtl9300-multi-byte-v5-1-cd9dca0db722@narfation.org
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -281,6 +281,10 @@ static int rtl9300_i2c_smbus_xfer(struct
|
||||
ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
+ if (data->block[0] < 1 || data->block[0] > I2C_SMBUS_BLOCK_MAX) {
|
||||
+ ret = -EINVAL;
|
||||
+ goto out_unlock;
|
||||
+ }
|
||||
ret = rtl9300_i2c_config_xfer(i2c, chan, addr, data->block[0]);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
@ -1,75 +0,0 @@
|
||||
From d67b740b9edfa46310355e2b68050f79ebf05a4c Mon Sep 17 00:00:00 2001
|
||||
From: Harshal Gohel <hg@simonwunderlich.de>
|
||||
Date: Sun, 10 Aug 2025 20:05:14 +0200
|
||||
Subject: [PATCH] i2c: rtl9300: Fix multi-byte I2C write
|
||||
|
||||
The RTL93xx I2C controller has 4 32 bit registers to store the bytes for
|
||||
the upcoming I2C transmission. The first byte is stored in the
|
||||
least-significant byte of the first register. And the last byte in the most
|
||||
significant byte of the last register. A map of the transferred bytes to
|
||||
their order in the registers is:
|
||||
|
||||
reg 0: 0x04_03_02_01
|
||||
reg 1: 0x08_07_06_05
|
||||
reg 2: 0x0c_0b_0a_09
|
||||
reg 3: 0x10_0f_0e_0d
|
||||
|
||||
The i2c_read() function basically demonstrates how the hardware would pick
|
||||
up bytes from this register set. But the i2c_write() function was just
|
||||
pushing bytes one after another to the least significant byte of a register
|
||||
AFTER shifting the last one to the next more significant byte position.
|
||||
|
||||
If you would then have tried to send a buffer with numbers 1-11 using
|
||||
i2c_write(), you would have ended up with following register content:
|
||||
|
||||
reg 0: 0x01_02_03_04
|
||||
reg 1: 0x05_06_07_08
|
||||
reg 2: 0x00_09_0a_0b
|
||||
reg 3: 0x00_00_00_00
|
||||
|
||||
On the wire, you would then have seen:
|
||||
|
||||
Sr Addr Wr [A] 04 A 03 A 02 A 01 A 08 A 07 A 06 A 05 A 0b A 0a A 09 A P
|
||||
|
||||
But the correct data transmission was expected to be
|
||||
|
||||
Sr Addr Wr [A] 01 A 02 A 03 A 04 A 05 A 06 A 07 A 08 A 09 A 0a A 0b A P
|
||||
|
||||
Because of this multi-byte ordering problem, only single byte i2c_write()
|
||||
operations were executed correctly (on the wire).
|
||||
|
||||
By shifting the byte directly to the correct end position in the register,
|
||||
it is possible to avoid this incorrect byte ordering and fix multi-byte
|
||||
transmissions.
|
||||
|
||||
The second initialization (to 0) of vals was also be dropped because this
|
||||
array is initialized to 0 on the stack by using `= {};`. This makes the
|
||||
fix a lot more readable.
|
||||
|
||||
Fixes: c366be720235 ("i2c: Add driver for the RTL9300 I2C controller")
|
||||
Signed-off-by: Harshal Gohel <hg@simonwunderlich.de>
|
||||
Cc: <stable@vger.kernel.org> # v6.13+
|
||||
Co-developed-by: Sven Eckelmann <sven@narfation.org>
|
||||
Signed-off-by: Sven Eckelmann <sven@narfation.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250810-i2c-rtl9300-multi-byte-v5-2-cd9dca0db722@narfation.org
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -143,10 +143,10 @@ static int rtl9300_i2c_write(struct rtl9
|
||||
return -EIO;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
- if (i % 4 == 0)
|
||||
- vals[i/4] = 0;
|
||||
- vals[i/4] <<= 8;
|
||||
- vals[i/4] |= buf[i];
|
||||
+ unsigned int shift = (i % 4) * 8;
|
||||
+ unsigned int reg = i / 4;
|
||||
+
|
||||
+ vals[reg] |= buf[i] << shift;
|
||||
}
|
||||
|
||||
return regmap_bulk_write(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0,
|
||||
@ -1,37 +0,0 @@
|
||||
From ceee7776c010c5f09d30985c9e5223b363a6172a Mon Sep 17 00:00:00 2001
|
||||
From: Sven Eckelmann <sven@narfation.org>
|
||||
Date: Sun, 10 Aug 2025 20:05:15 +0200
|
||||
Subject: [PATCH] i2c: rtl9300: Increase timeout for transfer polling
|
||||
|
||||
The timeout for transfers was only set to 2ms. Because of this relatively
|
||||
low limit, 12-byte read operations to the frontend MCU of a RTL8239 POE PSE
|
||||
chip cluster was consistently resulting in a timeout.
|
||||
|
||||
The original OpenWrt downstream driver [1] was not using any timeout limit
|
||||
at all. This is also possible by setting the timeout_us parameter of
|
||||
regmap_read_poll_timeout() to 0. But since the driver currently implements
|
||||
the ETIMEDOUT error, it is more sensible to increase the timeout in such a
|
||||
way that communication with the (quite common) Realtek I2C-connected POE
|
||||
management solution is possible.
|
||||
|
||||
[1] https://git.openwrt.org/?p=openwrt/openwrt.git;a=blob;f=target/linux/realtek/files-6.12/drivers/i2c/busses/i2c-rtl9300.c;h=c4d973195ef39dc56d6207e665d279745525fcac#l202
|
||||
|
||||
Fixes: c366be720235 ("i2c: Add driver for the RTL9300 I2C controller")
|
||||
Signed-off-by: Sven Eckelmann <sven@narfation.org>
|
||||
Cc: <stable@vger.kernel.org> # v6.13+
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250810-i2c-rtl9300-multi-byte-v5-3-cd9dca0db722@narfation.org
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -175,7 +175,7 @@ static int rtl9300_i2c_execute_xfer(stru
|
||||
return ret;
|
||||
|
||||
ret = regmap_read_poll_timeout(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL1,
|
||||
- val, !(val & RTL9300_I2C_MST_CTRL1_I2C_TRIG), 100, 2000);
|
||||
+ val, !(val & RTL9300_I2C_MST_CTRL1_I2C_TRIG), 100, 100000);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@ -1,53 +0,0 @@
|
||||
From 82b350dd8185ce790e61555c436f90b6501af23c Mon Sep 17 00:00:00 2001
|
||||
From: Sven Eckelmann <sven@narfation.org>
|
||||
Date: Sun, 10 Aug 2025 20:05:16 +0200
|
||||
Subject: [PATCH] i2c: rtl9300: Add missing count byte for SMBus Block Ops
|
||||
|
||||
The expected on-wire format of an SMBus Block Write is
|
||||
|
||||
S Addr Wr [A] Comm [A] Count [A] Data [A] Data [A] ... [A] Data [A] P
|
||||
|
||||
Everything starting from the Count byte is provided by the I2C subsystem in
|
||||
the array data->block. But the driver was skipping the Count byte
|
||||
(data->block[0]) when sending it to the RTL93xx I2C controller.
|
||||
|
||||
Only the actual data could be seen on the wire:
|
||||
|
||||
S Addr Wr [A] Comm [A] Data [A] Data [A] ... [A] Data [A] P
|
||||
|
||||
This wire format is not SMBus Block Write compatible but matches the format
|
||||
of an I2C Block Write. Simply adding the count byte to the buffer for the
|
||||
I2C controller is enough to fix the transmission.
|
||||
|
||||
This also affects read because the I2C controller must receive the count
|
||||
byte + $count * data bytes.
|
||||
|
||||
Fixes: c366be720235 ("i2c: Add driver for the RTL9300 I2C controller")
|
||||
Signed-off-by: Sven Eckelmann <sven@narfation.org>
|
||||
Cc: <stable@vger.kernel.org> # v6.13+
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250810-i2c-rtl9300-multi-byte-v5-4-cd9dca0db722@narfation.org
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -285,15 +285,15 @@ static int rtl9300_i2c_smbus_xfer(struct
|
||||
ret = -EINVAL;
|
||||
goto out_unlock;
|
||||
}
|
||||
- ret = rtl9300_i2c_config_xfer(i2c, chan, addr, data->block[0]);
|
||||
+ ret = rtl9300_i2c_config_xfer(i2c, chan, addr, data->block[0] + 1);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
if (read_write == I2C_SMBUS_WRITE) {
|
||||
- ret = rtl9300_i2c_write(i2c, &data->block[1], data->block[0]);
|
||||
+ ret = rtl9300_i2c_write(i2c, &data->block[0], data->block[0] + 1);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
}
|
||||
- len = data->block[0];
|
||||
+ len = data->block[0] + 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -1,40 +0,0 @@
|
||||
From cd6c956fbc13156bcbcca084b46a8380caebc2a8 Mon Sep 17 00:00:00 2001
|
||||
From: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Date: Sun, 31 Aug 2025 10:04:46 +0000
|
||||
Subject: [PATCH] i2c: rtl9300: fix channel number bound check
|
||||
|
||||
Fix the current check for number of channels (child nodes in the device
|
||||
tree). Before, this was:
|
||||
|
||||
if (device_get_child_node_count(dev) >= RTL9300_I2C_MUX_NCHAN)
|
||||
|
||||
RTL9300_I2C_MUX_NCHAN gives the maximum number of channels so checking
|
||||
with '>=' isn't correct because it doesn't allow the last channel
|
||||
number. Thus, fix it to:
|
||||
|
||||
if (device_get_child_node_count(dev) > RTL9300_I2C_MUX_NCHAN)
|
||||
|
||||
Issue occured on a TP-Link TL-ST1008F v2.0 device (8 SFP+ ports) and fix
|
||||
is tested there.
|
||||
|
||||
Fixes: c366be720235 ("i2c: Add driver for the RTL9300 I2C controller")
|
||||
Cc: stable@vger.kernel.org # v6.13+
|
||||
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Tested-by: Sven Eckelmann <sven@narfation.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302C based board
|
||||
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250831100457.3114-2-jelonek.jonas@gmail.com
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -353,7 +353,7 @@ static int rtl9300_i2c_probe(struct plat
|
||||
|
||||
platform_set_drvdata(pdev, i2c);
|
||||
|
||||
- if (device_get_child_node_count(dev) >= RTL9300_I2C_MUX_NCHAN)
|
||||
+ if (device_get_child_node_count(dev) > RTL9300_I2C_MUX_NCHAN)
|
||||
return dev_err_probe(dev, -EINVAL, "Too many channels\n");
|
||||
|
||||
device_for_each_child_node(dev, child) {
|
||||
@ -1,61 +0,0 @@
|
||||
From 06418cb5a1a542a003fdb4ad8e76ea542d57cfba Mon Sep 17 00:00:00 2001
|
||||
From: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Date: Sun, 31 Aug 2025 10:04:47 +0000
|
||||
Subject: [PATCH] i2c: rtl9300: ensure data length is within supported range
|
||||
|
||||
Add an explicit check for the xfer length to 'rtl9300_i2c_config_xfer'
|
||||
to ensure the data length isn't within the supported range. In
|
||||
particular a data length of 0 is not supported by the hardware and
|
||||
causes unintended or destructive behaviour.
|
||||
|
||||
This limitation becomes obvious when looking at the register
|
||||
documentation [1]. 4 bits are reserved for DATA_WIDTH and the value
|
||||
of these 4 bits is used as N + 1, allowing a data length range of
|
||||
1 <= len <= 16.
|
||||
|
||||
Affected by this is the SMBus Quick Operation which works with a data
|
||||
length of 0. Passing 0 as the length causes an underflow of the value
|
||||
due to:
|
||||
|
||||
(len - 1) & 0xf
|
||||
|
||||
and effectively specifying a transfer length of 16 via the registers.
|
||||
This causes a 16-byte write operation instead of a Quick Write. For
|
||||
example, on SFP modules without write-protected EEPROM this soft-bricks
|
||||
them by overwriting some initial bytes.
|
||||
|
||||
For completeness, also add a quirk for the zero length.
|
||||
|
||||
[1] https://svanheule.net/realtek/longan/register/i2c_mst1_ctrl2
|
||||
|
||||
Fixes: c366be720235 ("i2c: Add driver for the RTL9300 I2C controller")
|
||||
Cc: stable@vger.kernel.org # v6.13+
|
||||
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Tested-by: Sven Eckelmann <sven@narfation.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302C based board
|
||||
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250831100457.3114-3-jelonek.jonas@gmail.com
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -99,6 +99,9 @@ static int rtl9300_i2c_config_xfer(struc
|
||||
{
|
||||
u32 val, mask;
|
||||
|
||||
+ if (len < 1 || len > 16)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
val = chan->bus_freq << RTL9300_I2C_MST_CTRL2_SCL_FREQ_OFS;
|
||||
mask = RTL9300_I2C_MST_CTRL2_SCL_FREQ_MASK;
|
||||
|
||||
@@ -323,7 +326,7 @@ static const struct i2c_algorithm rtl930
|
||||
};
|
||||
|
||||
static struct i2c_adapter_quirks rtl9300_i2c_quirks = {
|
||||
- .flags = I2C_AQ_NO_CLK_STRETCH,
|
||||
+ .flags = I2C_AQ_NO_CLK_STRETCH | I2C_AQ_NO_ZERO_LEN,
|
||||
.max_read_len = 16,
|
||||
.max_write_len = 16,
|
||||
};
|
||||
@ -1,77 +0,0 @@
|
||||
From ede965fd555ac2536cf651893a998dbfd8e57b86 Mon Sep 17 00:00:00 2001
|
||||
From: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Date: Sun, 31 Aug 2025 10:04:48 +0000
|
||||
Subject: [PATCH] i2c: rtl9300: remove broken SMBus Quick operation support
|
||||
|
||||
Remove the SMBus Quick operation from this driver because it is not
|
||||
natively supported by the hardware and is wrongly implemented in the
|
||||
driver.
|
||||
|
||||
The I2C controllers in Realtek RTL9300 and RTL9310 are SMBus-compliant
|
||||
but there doesn't seem to be native support for the SMBus Quick
|
||||
operation. It is not explicitly mentioned in the documentation but
|
||||
looking at the registers which configure an SMBus transaction, one can
|
||||
see that the data length cannot be set to 0. This suggests that the
|
||||
hardware doesn't allow any SMBus message without data bytes (except for
|
||||
those it does on it's own, see SMBus Block Read).
|
||||
|
||||
The current implementation of SMBus Quick operation passes a length of
|
||||
0 (which is actually invalid). Before the fix of a bug in a previous
|
||||
commit, this led to a read operation of 16 bytes from any register (the
|
||||
one of a former transaction or any other value.
|
||||
|
||||
This caused issues like soft-bricked SFP modules after a simple probe
|
||||
with i2cdetect which uses Quick by default. Running this with SFP
|
||||
modules whose EEPROM isn't write-protected, some of the initial bytes
|
||||
are overwritten because a 16-byte write operation is executed instead of
|
||||
a Quick Write. (This temporarily soft-bricked one of my DAC cables.)
|
||||
|
||||
Because SMBus Quick operation is obviously not supported on these
|
||||
controllers (because a length of 0 cannot be set, even when no register
|
||||
address is set), remove that instead of claiming there is support. There
|
||||
also shouldn't be any kind of emulated 'Quick' which just does another
|
||||
kind of operation in the background. Otherwise, specific issues occur
|
||||
in case of a 'Quick' Write which actually writes unknown data to an
|
||||
unknown register.
|
||||
|
||||
Fixes: c366be720235 ("i2c: Add driver for the RTL9300 I2C controller")
|
||||
Cc: stable@vger.kernel.org # v6.13+
|
||||
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Tested-by: Sven Eckelmann <sven@narfation.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302C based board
|
||||
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250831100457.3114-4-jelonek.jonas@gmail.com
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -225,15 +225,6 @@ static int rtl9300_i2c_smbus_xfer(struct
|
||||
}
|
||||
|
||||
switch (size) {
|
||||
- case I2C_SMBUS_QUICK:
|
||||
- ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 0);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- ret = rtl9300_i2c_reg_addr_set(i2c, 0, 0);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- break;
|
||||
-
|
||||
case I2C_SMBUS_BYTE:
|
||||
if (read_write == I2C_SMBUS_WRITE) {
|
||||
ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 0);
|
||||
@@ -315,9 +306,9 @@ out_unlock:
|
||||
|
||||
static u32 rtl9300_i2c_func(struct i2c_adapter *a)
|
||||
{
|
||||
- return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
|
||||
- I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
|
||||
- I2C_FUNC_SMBUS_BLOCK_DATA;
|
||||
+ return I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA |
|
||||
+ I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_DATA |
|
||||
+ I2C_FUNC_SMBUS_I2C_BLOCK;
|
||||
}
|
||||
|
||||
static const struct i2c_algorithm rtl9300_i2c_algo = {
|
||||
@ -1,26 +0,0 @@
|
||||
From 095530512152e6811278de9c30f170f0ac9705eb Mon Sep 17 00:00:00 2001
|
||||
From: Sven Eckelmann <sven@narfation.org>
|
||||
Date: Sat, 27 Sep 2025 11:52:16 +0200
|
||||
Subject: [PATCH] i2c: rtl9300: Drop unsupported I2C_FUNC_SMBUS_I2C_BLOCK
|
||||
|
||||
While applying the patch for commit ede965fd555a ("i2c: rtl9300: remove
|
||||
broken SMBus Quick operation support"), a conflict was incorrectly solved
|
||||
by adding the I2C_FUNC_SMBUS_I2C_BLOCK feature flag. But the code to handle
|
||||
I2C_SMBUS_I2C_BLOCK_DATA requests will be added by a separate commit.
|
||||
|
||||
Fixes: ede965fd555a ("i2c: rtl9300: remove broken SMBus Quick operation support")
|
||||
Signed-off-by: Sven Eckelmann <sven@narfation.org>
|
||||
Signed-off-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -307,8 +307,7 @@ out_unlock:
|
||||
static u32 rtl9300_i2c_func(struct i2c_adapter *a)
|
||||
{
|
||||
return I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA |
|
||||
- I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_DATA |
|
||||
- I2C_FUNC_SMBUS_I2C_BLOCK;
|
||||
+ I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_DATA;
|
||||
}
|
||||
|
||||
static const struct i2c_algorithm rtl9300_i2c_algo = {
|
||||
@ -1,119 +0,0 @@
|
||||
From 415216ae3196e67bdb9515519f219d553bd38d3a Mon Sep 17 00:00:00 2001
|
||||
From: Harshal Gohel <hg@simonwunderlich.de>
|
||||
Date: Sat, 27 Sep 2025 11:52:17 +0200
|
||||
Subject: [PATCH] i2c: rtl9300: Implement I2C block read and write
|
||||
|
||||
It was noticed that the original implementation of SMBus Block Write in the
|
||||
driver was actually an I2C Block Write. Both differ only in the Count byte
|
||||
before the actual data:
|
||||
|
||||
S Addr Wr [A] Comm [A] Count [A] Data [A] Data [A] ... [A] Data [A] P
|
||||
|
||||
The I2C Block Write is just skipping this Count byte and starts directly
|
||||
with the data:
|
||||
|
||||
S Addr Wr [A] Comm [A] Data [A] Data [A] ... [A] Data [A] P
|
||||
|
||||
The I2C controller of RTL93xx doesn't handle this Count byte special and it
|
||||
is simply another one of (16 possible) data bytes. Adding support for the
|
||||
I2C Block Write therefore only requires skipping the count byte (0) in
|
||||
data->block.
|
||||
|
||||
It is similar for reads. The SMBUS Block read is having a Count byte before
|
||||
the data:
|
||||
|
||||
S Addr Wr [A] Comm [A]
|
||||
Sr Addr Rd [A] [Count] A [Data] A [Data] A ... A [Data] NA P
|
||||
|
||||
And the I2C Block Read is directly starting with the actual data:
|
||||
|
||||
S Addr Wr [A] Comm [A]
|
||||
Sr Addr Rd [A] [Data] A [Data] A ... A [Data] NA P
|
||||
|
||||
The I2C controller is also not handling this byte in a special way. It
|
||||
simply provides every byte after the Rd marker + Ack as part of the 16 byte
|
||||
receive buffer (registers). The content of this buffer just has to be
|
||||
copied to the right position in the receive data->block.
|
||||
|
||||
Signed-off-by: Harshal Gohel <hg@simonwunderlich.de>
|
||||
Co-developed-by: Sven Eckelmann <sven@narfation.org>
|
||||
Signed-off-by: Sven Eckelmann <sven@narfation.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Reviewed-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Tested-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -186,22 +186,32 @@ static int rtl9300_i2c_execute_xfer(stru
|
||||
return -EIO;
|
||||
|
||||
if (read_write == I2C_SMBUS_READ) {
|
||||
- if (size == I2C_SMBUS_BYTE || size == I2C_SMBUS_BYTE_DATA) {
|
||||
+ switch (size) {
|
||||
+ case I2C_SMBUS_BYTE:
|
||||
+ case I2C_SMBUS_BYTE_DATA:
|
||||
ret = regmap_read(i2c->regmap,
|
||||
i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
data->byte = val & 0xff;
|
||||
- } else if (size == I2C_SMBUS_WORD_DATA) {
|
||||
+ break;
|
||||
+ case I2C_SMBUS_WORD_DATA:
|
||||
ret = regmap_read(i2c->regmap,
|
||||
i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
data->word = val & 0xffff;
|
||||
- } else {
|
||||
+ break;
|
||||
+ case I2C_SMBUS_I2C_BLOCK_DATA:
|
||||
+ ret = rtl9300_i2c_read(i2c, &data->block[1], len);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ break;
|
||||
+ default:
|
||||
ret = rtl9300_i2c_read(i2c, &data->block[0], len);
|
||||
if (ret)
|
||||
return ret;
|
||||
+ break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,6 +300,25 @@ static int rtl9300_i2c_smbus_xfer(struct
|
||||
len = data->block[0] + 1;
|
||||
break;
|
||||
|
||||
+ case I2C_SMBUS_I2C_BLOCK_DATA:
|
||||
+ ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ if (data->block[0] < 1 || data->block[0] > I2C_SMBUS_BLOCK_MAX) {
|
||||
+ ret = -EINVAL;
|
||||
+ goto out_unlock;
|
||||
+ }
|
||||
+ ret = rtl9300_i2c_config_xfer(i2c, chan, addr, data->block[0]);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ if (read_write == I2C_SMBUS_WRITE) {
|
||||
+ ret = rtl9300_i2c_write(i2c, &data->block[1], data->block[0]);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ }
|
||||
+ len = data->block[0];
|
||||
+ break;
|
||||
+
|
||||
default:
|
||||
dev_err(&adap->dev, "Unsupported transaction %d\n", size);
|
||||
ret = -EOPNOTSUPP;
|
||||
@@ -307,7 +336,8 @@ out_unlock:
|
||||
static u32 rtl9300_i2c_func(struct i2c_adapter *a)
|
||||
{
|
||||
return I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA |
|
||||
- I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_DATA;
|
||||
+ I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_DATA |
|
||||
+ I2C_FUNC_SMBUS_I2C_BLOCK;
|
||||
}
|
||||
|
||||
static const struct i2c_algorithm rtl9300_i2c_algo = {
|
||||
@ -1,354 +0,0 @@
|
||||
From 5a6ecb27435ef7a67d7bec4543f0c6303f34e8a6 Mon Sep 17 00:00:00 2001
|
||||
From: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Date: Sat, 27 Sep 2025 10:19:23 +0000
|
||||
Subject: [PATCH] i2c: rtl9300: use regmap fields and API for registers
|
||||
|
||||
Adapt the RTL9300 I2C controller driver to use more of the regmap
|
||||
API, especially make use of reg_field and regmap_field instead of macros
|
||||
to represent registers. Most register operations are performed through
|
||||
regmap_field_* API then.
|
||||
|
||||
Handle SCL selection using separate chip-specific functions since this
|
||||
is already known to differ between the Realtek SoC families in such a
|
||||
way that this cannot be properly handled using just a different
|
||||
reg_field.
|
||||
|
||||
This makes it easier to add support for newer generations or to handle
|
||||
differences between specific revisions within a series. Just by
|
||||
defining a separate driver data structure with the corresponding
|
||||
register field definitions and linking it to a new compatible.
|
||||
|
||||
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Tested-by: Sven Eckelmann <sven@narfation.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302C based board
|
||||
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250927101931.71575-2-jelonek.jonas@gmail.com
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -23,97 +23,117 @@ struct rtl9300_i2c_chan {
|
||||
u8 sda_pin;
|
||||
};
|
||||
|
||||
+enum rtl9300_i2c_reg_scope {
|
||||
+ REG_SCOPE_GLOBAL,
|
||||
+ REG_SCOPE_MASTER,
|
||||
+};
|
||||
+
|
||||
+struct rtl9300_i2c_reg_field {
|
||||
+ struct reg_field field;
|
||||
+ enum rtl9300_i2c_reg_scope scope;
|
||||
+};
|
||||
+
|
||||
+enum rtl9300_i2c_reg_fields {
|
||||
+ F_DATA_WIDTH = 0,
|
||||
+ F_DEV_ADDR,
|
||||
+ F_I2C_FAIL,
|
||||
+ F_I2C_TRIG,
|
||||
+ F_MEM_ADDR,
|
||||
+ F_MEM_ADDR_WIDTH,
|
||||
+ F_RD_MODE,
|
||||
+ F_RWOP,
|
||||
+ F_SCL_FREQ,
|
||||
+ F_SCL_SEL,
|
||||
+ F_SDA_OUT_SEL,
|
||||
+ F_SDA_SEL,
|
||||
+
|
||||
+ /* keep last */
|
||||
+ F_NUM_FIELDS
|
||||
+};
|
||||
+
|
||||
+struct rtl9300_i2c_drv_data {
|
||||
+ struct rtl9300_i2c_reg_field field_desc[F_NUM_FIELDS];
|
||||
+ int (*select_scl)(struct rtl9300_i2c *i2c, u8 scl);
|
||||
+ u32 data_reg;
|
||||
+ u8 max_nchan;
|
||||
+};
|
||||
+
|
||||
#define RTL9300_I2C_MUX_NCHAN 8
|
||||
|
||||
struct rtl9300_i2c {
|
||||
struct regmap *regmap;
|
||||
struct device *dev;
|
||||
struct rtl9300_i2c_chan chans[RTL9300_I2C_MUX_NCHAN];
|
||||
+ struct regmap_field *fields[F_NUM_FIELDS];
|
||||
u32 reg_base;
|
||||
+ u32 data_reg;
|
||||
u8 sda_pin;
|
||||
struct mutex lock;
|
||||
};
|
||||
|
||||
#define RTL9300_I2C_MST_CTRL1 0x0
|
||||
-#define RTL9300_I2C_MST_CTRL1_MEM_ADDR_OFS 8
|
||||
-#define RTL9300_I2C_MST_CTRL1_MEM_ADDR_MASK GENMASK(31, 8)
|
||||
-#define RTL9300_I2C_MST_CTRL1_SDA_OUT_SEL_OFS 4
|
||||
-#define RTL9300_I2C_MST_CTRL1_SDA_OUT_SEL_MASK GENMASK(6, 4)
|
||||
-#define RTL9300_I2C_MST_CTRL1_GPIO_SCL_SEL BIT(3)
|
||||
-#define RTL9300_I2C_MST_CTRL1_RWOP BIT(2)
|
||||
-#define RTL9300_I2C_MST_CTRL1_I2C_FAIL BIT(1)
|
||||
-#define RTL9300_I2C_MST_CTRL1_I2C_TRIG BIT(0)
|
||||
#define RTL9300_I2C_MST_CTRL2 0x4
|
||||
-#define RTL9300_I2C_MST_CTRL2_RD_MODE BIT(15)
|
||||
-#define RTL9300_I2C_MST_CTRL2_DEV_ADDR_OFS 8
|
||||
-#define RTL9300_I2C_MST_CTRL2_DEV_ADDR_MASK GENMASK(14, 8)
|
||||
-#define RTL9300_I2C_MST_CTRL2_DATA_WIDTH_OFS 4
|
||||
-#define RTL9300_I2C_MST_CTRL2_DATA_WIDTH_MASK GENMASK(7, 4)
|
||||
-#define RTL9300_I2C_MST_CTRL2_MEM_ADDR_WIDTH_OFS 2
|
||||
-#define RTL9300_I2C_MST_CTRL2_MEM_ADDR_WIDTH_MASK GENMASK(3, 2)
|
||||
-#define RTL9300_I2C_MST_CTRL2_SCL_FREQ_OFS 0
|
||||
-#define RTL9300_I2C_MST_CTRL2_SCL_FREQ_MASK GENMASK(1, 0)
|
||||
#define RTL9300_I2C_MST_DATA_WORD0 0x8
|
||||
#define RTL9300_I2C_MST_DATA_WORD1 0xc
|
||||
#define RTL9300_I2C_MST_DATA_WORD2 0x10
|
||||
#define RTL9300_I2C_MST_DATA_WORD3 0x14
|
||||
-
|
||||
#define RTL9300_I2C_MST_GLB_CTRL 0x384
|
||||
|
||||
static int rtl9300_i2c_reg_addr_set(struct rtl9300_i2c *i2c, u32 reg, u16 len)
|
||||
{
|
||||
- u32 val, mask;
|
||||
int ret;
|
||||
|
||||
- val = len << RTL9300_I2C_MST_CTRL2_MEM_ADDR_WIDTH_OFS;
|
||||
- mask = RTL9300_I2C_MST_CTRL2_MEM_ADDR_WIDTH_MASK;
|
||||
-
|
||||
- ret = regmap_update_bits(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL2, mask, val);
|
||||
+ ret = regmap_field_write(i2c->fields[F_MEM_ADDR_WIDTH], len);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- val = reg << RTL9300_I2C_MST_CTRL1_MEM_ADDR_OFS;
|
||||
- mask = RTL9300_I2C_MST_CTRL1_MEM_ADDR_MASK;
|
||||
+ return regmap_field_write(i2c->fields[F_MEM_ADDR], reg);
|
||||
+}
|
||||
|
||||
- return regmap_update_bits(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL1, mask, val);
|
||||
+static int rtl9300_i2c_select_scl(struct rtl9300_i2c *i2c, u8 scl)
|
||||
+{
|
||||
+ return regmap_field_write(i2c->fields[F_SCL_SEL], 1);
|
||||
}
|
||||
|
||||
static int rtl9300_i2c_config_io(struct rtl9300_i2c *i2c, u8 sda_pin)
|
||||
{
|
||||
+ struct rtl9300_i2c_drv_data *drv_data;
|
||||
int ret;
|
||||
- u32 val, mask;
|
||||
|
||||
- ret = regmap_update_bits(i2c->regmap, RTL9300_I2C_MST_GLB_CTRL, BIT(sda_pin), BIT(sda_pin));
|
||||
+ drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev);
|
||||
+
|
||||
+ ret = regmap_field_update_bits(i2c->fields[F_SDA_SEL], BIT(sda_pin), BIT(sda_pin));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- val = (sda_pin << RTL9300_I2C_MST_CTRL1_SDA_OUT_SEL_OFS) |
|
||||
- RTL9300_I2C_MST_CTRL1_GPIO_SCL_SEL;
|
||||
- mask = RTL9300_I2C_MST_CTRL1_SDA_OUT_SEL_MASK | RTL9300_I2C_MST_CTRL1_GPIO_SCL_SEL;
|
||||
+ ret = regmap_field_write(i2c->fields[F_SDA_OUT_SEL], sda_pin);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
|
||||
- return regmap_update_bits(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL1, mask, val);
|
||||
+ return drv_data->select_scl(i2c, 0);
|
||||
}
|
||||
|
||||
static int rtl9300_i2c_config_xfer(struct rtl9300_i2c *i2c, struct rtl9300_i2c_chan *chan,
|
||||
u16 addr, u16 len)
|
||||
{
|
||||
- u32 val, mask;
|
||||
+ int ret;
|
||||
|
||||
if (len < 1 || len > 16)
|
||||
return -EINVAL;
|
||||
|
||||
- val = chan->bus_freq << RTL9300_I2C_MST_CTRL2_SCL_FREQ_OFS;
|
||||
- mask = RTL9300_I2C_MST_CTRL2_SCL_FREQ_MASK;
|
||||
-
|
||||
- val |= addr << RTL9300_I2C_MST_CTRL2_DEV_ADDR_OFS;
|
||||
- mask |= RTL9300_I2C_MST_CTRL2_DEV_ADDR_MASK;
|
||||
+ ret = regmap_field_write(i2c->fields[F_SCL_FREQ], chan->bus_freq);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
|
||||
- val |= ((len - 1) & 0xf) << RTL9300_I2C_MST_CTRL2_DATA_WIDTH_OFS;
|
||||
- mask |= RTL9300_I2C_MST_CTRL2_DATA_WIDTH_MASK;
|
||||
+ ret = regmap_field_write(i2c->fields[F_DEV_ADDR], addr);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
|
||||
- mask |= RTL9300_I2C_MST_CTRL2_RD_MODE;
|
||||
+ ret = regmap_field_write(i2c->fields[F_DATA_WIDTH], (len - 1) & 0xf);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
|
||||
- return regmap_update_bits(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL2, mask, val);
|
||||
+ return regmap_field_write(i2c->fields[F_RD_MODE], 0);
|
||||
}
|
||||
|
||||
static int rtl9300_i2c_read(struct rtl9300_i2c *i2c, u8 *buf, int len)
|
||||
@@ -124,8 +144,7 @@ static int rtl9300_i2c_read(struct rtl93
|
||||
if (len > 16)
|
||||
return -EIO;
|
||||
|
||||
- ret = regmap_bulk_read(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0,
|
||||
- vals, ARRAY_SIZE(vals));
|
||||
+ ret = regmap_bulk_read(i2c->regmap, i2c->data_reg, vals, ARRAY_SIZE(vals));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -152,52 +171,49 @@ static int rtl9300_i2c_write(struct rtl9
|
||||
vals[reg] |= buf[i] << shift;
|
||||
}
|
||||
|
||||
- return regmap_bulk_write(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0,
|
||||
- vals, ARRAY_SIZE(vals));
|
||||
+ return regmap_bulk_write(i2c->regmap, i2c->data_reg, vals, ARRAY_SIZE(vals));
|
||||
}
|
||||
|
||||
static int rtl9300_i2c_writel(struct rtl9300_i2c *i2c, u32 data)
|
||||
{
|
||||
- return regmap_write(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0, data);
|
||||
+ return regmap_write(i2c->regmap, i2c->data_reg, data);
|
||||
}
|
||||
|
||||
static int rtl9300_i2c_execute_xfer(struct rtl9300_i2c *i2c, char read_write,
|
||||
int size, union i2c_smbus_data *data, int len)
|
||||
{
|
||||
- u32 val, mask;
|
||||
+ u32 val;
|
||||
int ret;
|
||||
|
||||
- val = read_write == I2C_SMBUS_WRITE ? RTL9300_I2C_MST_CTRL1_RWOP : 0;
|
||||
- mask = RTL9300_I2C_MST_CTRL1_RWOP;
|
||||
-
|
||||
- val |= RTL9300_I2C_MST_CTRL1_I2C_TRIG;
|
||||
- mask |= RTL9300_I2C_MST_CTRL1_I2C_TRIG;
|
||||
+ ret = regmap_field_write(i2c->fields[F_RWOP], read_write == I2C_SMBUS_WRITE);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
|
||||
- ret = regmap_update_bits(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL1, mask, val);
|
||||
+ ret = regmap_field_write(i2c->fields[F_I2C_TRIG], 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- ret = regmap_read_poll_timeout(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL1,
|
||||
- val, !(val & RTL9300_I2C_MST_CTRL1_I2C_TRIG), 100, 100000);
|
||||
+ ret = regmap_field_read_poll_timeout(i2c->fields[F_I2C_TRIG], val, !val, 100, 100000);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- if (val & RTL9300_I2C_MST_CTRL1_I2C_FAIL)
|
||||
+ ret = regmap_field_read(i2c->fields[F_I2C_FAIL], &val);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ if (val)
|
||||
return -EIO;
|
||||
|
||||
if (read_write == I2C_SMBUS_READ) {
|
||||
switch (size) {
|
||||
case I2C_SMBUS_BYTE:
|
||||
case I2C_SMBUS_BYTE_DATA:
|
||||
- ret = regmap_read(i2c->regmap,
|
||||
- i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0, &val);
|
||||
+ ret = regmap_read(i2c->regmap, i2c->data_reg, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
data->byte = val & 0xff;
|
||||
break;
|
||||
case I2C_SMBUS_WORD_DATA:
|
||||
- ret = regmap_read(i2c->regmap,
|
||||
- i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0, &val);
|
||||
+ ret = regmap_read(i2c->regmap, i2c->data_reg, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
data->word = val & 0xffff;
|
||||
@@ -355,9 +371,11 @@ static int rtl9300_i2c_probe(struct plat
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct rtl9300_i2c *i2c;
|
||||
+ struct fwnode_handle *child;
|
||||
+ struct rtl9300_i2c_drv_data *drv_data;
|
||||
+ struct reg_field fields[F_NUM_FIELDS];
|
||||
u32 clock_freq, sda_pin;
|
||||
int ret, i = 0;
|
||||
- struct fwnode_handle *child;
|
||||
|
||||
i2c = devm_kzalloc(dev, sizeof(*i2c), GFP_KERNEL);
|
||||
if (!i2c)
|
||||
@@ -376,9 +394,22 @@ static int rtl9300_i2c_probe(struct plat
|
||||
|
||||
platform_set_drvdata(pdev, i2c);
|
||||
|
||||
- if (device_get_child_node_count(dev) > RTL9300_I2C_MUX_NCHAN)
|
||||
+ drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev);
|
||||
+ if (device_get_child_node_count(dev) > drv_data->max_nchan)
|
||||
return dev_err_probe(dev, -EINVAL, "Too many channels\n");
|
||||
|
||||
+ i2c->data_reg = i2c->reg_base + drv_data->data_reg;
|
||||
+ for (i = 0; i < F_NUM_FIELDS; i++) {
|
||||
+ fields[i] = drv_data->field_desc[i].field;
|
||||
+ if (drv_data->field_desc[i].scope == REG_SCOPE_MASTER)
|
||||
+ fields[i].reg += i2c->reg_base;
|
||||
+ }
|
||||
+ ret = devm_regmap_field_bulk_alloc(dev, i2c->regmap, i2c->fields,
|
||||
+ fields, F_NUM_FIELDS);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ i = 0;
|
||||
device_for_each_child_node(dev, child) {
|
||||
struct rtl9300_i2c_chan *chan = &i2c->chans[i];
|
||||
struct i2c_adapter *adap = &chan->adap;
|
||||
@@ -395,7 +426,6 @@ static int rtl9300_i2c_probe(struct plat
|
||||
case I2C_MAX_STANDARD_MODE_FREQ:
|
||||
chan->bus_freq = RTL9300_I2C_STD_FREQ;
|
||||
break;
|
||||
-
|
||||
case I2C_MAX_FAST_MODE_FREQ:
|
||||
chan->bus_freq = RTL9300_I2C_FAST_FREQ;
|
||||
break;
|
||||
@@ -427,11 +457,37 @@ static int rtl9300_i2c_probe(struct plat
|
||||
return 0;
|
||||
}
|
||||
|
||||
+#define GLB_REG_FIELD(reg, msb, lsb) \
|
||||
+ { .field = REG_FIELD(reg, msb, lsb), .scope = REG_SCOPE_GLOBAL }
|
||||
+#define MST_REG_FIELD(reg, msb, lsb) \
|
||||
+ { .field = REG_FIELD(reg, msb, lsb), .scope = REG_SCOPE_MASTER }
|
||||
+
|
||||
+static const struct rtl9300_i2c_drv_data rtl9300_i2c_drv_data = {
|
||||
+ .field_desc = {
|
||||
+ [F_MEM_ADDR] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL1, 8, 31),
|
||||
+ [F_SDA_OUT_SEL] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL1, 4, 6),
|
||||
+ [F_SCL_SEL] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL1, 3, 3),
|
||||
+ [F_RWOP] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL1, 2, 2),
|
||||
+ [F_I2C_FAIL] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL1, 1, 1),
|
||||
+ [F_I2C_TRIG] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL1, 0, 0),
|
||||
+ [F_RD_MODE] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL2, 15, 15),
|
||||
+ [F_DEV_ADDR] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL2, 8, 14),
|
||||
+ [F_DATA_WIDTH] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL2, 4, 7),
|
||||
+ [F_MEM_ADDR_WIDTH] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL2, 2, 3),
|
||||
+ [F_SCL_FREQ] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL2, 0, 1),
|
||||
+ [F_SDA_SEL] = GLB_REG_FIELD(RTL9300_I2C_MST_GLB_CTRL, 0, 7),
|
||||
+ },
|
||||
+ .select_scl = rtl9300_i2c_select_scl,
|
||||
+ .data_reg = RTL9300_I2C_MST_DATA_WORD0,
|
||||
+ .max_nchan = RTL9300_I2C_MUX_NCHAN,
|
||||
+};
|
||||
+
|
||||
+
|
||||
static const struct of_device_id i2c_rtl9300_dt_ids[] = {
|
||||
- { .compatible = "realtek,rtl9301-i2c" },
|
||||
- { .compatible = "realtek,rtl9302b-i2c" },
|
||||
- { .compatible = "realtek,rtl9302c-i2c" },
|
||||
- { .compatible = "realtek,rtl9303-i2c" },
|
||||
+ { .compatible = "realtek,rtl9301-i2c", .data = (void *) &rtl9300_i2c_drv_data },
|
||||
+ { .compatible = "realtek,rtl9302b-i2c", .data = (void *) &rtl9300_i2c_drv_data },
|
||||
+ { .compatible = "realtek,rtl9302c-i2c", .data = (void *) &rtl9300_i2c_drv_data },
|
||||
+ { .compatible = "realtek,rtl9303-i2c", .data = (void *) &rtl9300_i2c_drv_data },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, i2c_rtl9300_dt_ids);
|
||||
@ -1,51 +0,0 @@
|
||||
From 80f3e37d5e734bbfe891592bb669ceb5e8b314dc Mon Sep 17 00:00:00 2001
|
||||
From: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Date: Sat, 27 Sep 2025 10:19:24 +0000
|
||||
Subject: [PATCH] dt-bindings: i2c: realtek,rtl9301-i2c: fix wording and typos
|
||||
|
||||
Fix wording of binding description to use plural because there is not
|
||||
only a single RTL9300 SoC. RTL9300 describes a whole family of Realtek
|
||||
SoCs.
|
||||
|
||||
Add missing word 'of' in description of reg property.
|
||||
|
||||
Change 'SDA pin' to 'SDA line number' because the property must contain
|
||||
the SDA (channel) number ranging from 0-7 instead of a real pin number.
|
||||
|
||||
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302C based board
|
||||
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250927101931.71575-3-jelonek.jonas@gmail.com
|
||||
|
||||
--- a/Documentation/devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml
|
||||
+++ b/Documentation/devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml
|
||||
@@ -10,7 +10,7 @@ maintainers:
|
||||
- Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
|
||||
description:
|
||||
- The RTL9300 SoC has two I2C controllers. Each of these has an SCL line (which
|
||||
+ RTL9300 SoCs have two I2C controllers. Each of these has an SCL line (which
|
||||
if not-used for SCL can be a GPIO). There are 8 common SDA lines that can be
|
||||
assigned to either I2C controller.
|
||||
|
||||
@@ -27,7 +27,7 @@ properties:
|
||||
|
||||
reg:
|
||||
items:
|
||||
- - description: Register offset and size this I2C controller.
|
||||
+ - description: Register offset and size of this I2C controller.
|
||||
|
||||
"#address-cells":
|
||||
const: 1
|
||||
@@ -42,7 +42,7 @@ patternProperties:
|
||||
|
||||
properties:
|
||||
reg:
|
||||
- description: The SDA pin associated with the I2C bus.
|
||||
+ description: The SDA line number associated with the I2C bus.
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
@ -1,118 +0,0 @@
|
||||
From 8ff3819d7edcd56e4c533b9391a156cd607048fa Mon Sep 17 00:00:00 2001
|
||||
From: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Date: Sat, 27 Sep 2025 10:19:25 +0000
|
||||
Subject: [PATCH] i2c: rtl9300: rename internal sda_pin to sda_num
|
||||
|
||||
Rename the internally used 'sda_pin' to 'sda_num' to make it clear that
|
||||
this is NOT the actual pin number of the GPIO pin but rather the logical
|
||||
SDA channel number. Although the alternate function SDA_Y is sometimes
|
||||
given with the GPIO number, this is not always the case. Thus, avoid any
|
||||
confusion or misconfiguration by giving the variable the correct name.
|
||||
|
||||
This follows the description change in the devicetree bindings.
|
||||
|
||||
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Tested-by: Sven Eckelmann <sven@narfation.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302C based board
|
||||
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250927101931.71575-4-jelonek.jonas@gmail.com
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -20,7 +20,7 @@ struct rtl9300_i2c_chan {
|
||||
struct i2c_adapter adap;
|
||||
struct rtl9300_i2c *i2c;
|
||||
enum rtl9300_bus_freq bus_freq;
|
||||
- u8 sda_pin;
|
||||
+ u8 sda_num;
|
||||
};
|
||||
|
||||
enum rtl9300_i2c_reg_scope {
|
||||
@@ -67,7 +67,7 @@ struct rtl9300_i2c {
|
||||
struct regmap_field *fields[F_NUM_FIELDS];
|
||||
u32 reg_base;
|
||||
u32 data_reg;
|
||||
- u8 sda_pin;
|
||||
+ u8 sda_num;
|
||||
struct mutex lock;
|
||||
};
|
||||
|
||||
@@ -102,11 +102,11 @@ static int rtl9300_i2c_config_io(struct
|
||||
|
||||
drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev);
|
||||
|
||||
- ret = regmap_field_update_bits(i2c->fields[F_SDA_SEL], BIT(sda_pin), BIT(sda_pin));
|
||||
+ ret = regmap_field_update_bits(i2c->fields[F_SDA_SEL], BIT(sda_num), BIT(sda_num));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- ret = regmap_field_write(i2c->fields[F_SDA_OUT_SEL], sda_pin);
|
||||
+ ret = regmap_field_write(i2c->fields[F_SDA_OUT_SEL], sda_num);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -243,11 +243,11 @@ static int rtl9300_i2c_smbus_xfer(struct
|
||||
int len = 0, ret;
|
||||
|
||||
mutex_lock(&i2c->lock);
|
||||
- if (chan->sda_pin != i2c->sda_pin) {
|
||||
+ if (chan->sda_num != i2c->sda_num) {
|
||||
ret = rtl9300_i2c_config_io(i2c, chan->sda_pin);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
- i2c->sda_pin = chan->sda_pin;
|
||||
+ i2c->sda_num = chan->sda_num;
|
||||
}
|
||||
|
||||
switch (size) {
|
||||
@@ -374,7 +374,7 @@ static int rtl9300_i2c_probe(struct plat
|
||||
struct fwnode_handle *child;
|
||||
struct rtl9300_i2c_drv_data *drv_data;
|
||||
struct reg_field fields[F_NUM_FIELDS];
|
||||
- u32 clock_freq, sda_pin;
|
||||
+ u32 clock_freq, sda_num;
|
||||
int ret, i = 0;
|
||||
|
||||
i2c = devm_kzalloc(dev, sizeof(*i2c), GFP_KERNEL);
|
||||
@@ -414,7 +414,7 @@ static int rtl9300_i2c_probe(struct plat
|
||||
struct rtl9300_i2c_chan *chan = &i2c->chans[i];
|
||||
struct i2c_adapter *adap = &chan->adap;
|
||||
|
||||
- ret = fwnode_property_read_u32(child, "reg", &sda_pin);
|
||||
+ ret = fwnode_property_read_u32(child, "reg", &sda_num);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -431,11 +431,11 @@ static int rtl9300_i2c_probe(struct plat
|
||||
break;
|
||||
default:
|
||||
dev_warn(i2c->dev, "SDA%d clock-frequency %d not supported using default\n",
|
||||
- sda_pin, clock_freq);
|
||||
+ sda_num, clock_freq);
|
||||
break;
|
||||
}
|
||||
|
||||
- chan->sda_pin = sda_pin;
|
||||
+ chan->sda_num = sda_num;
|
||||
chan->i2c = i2c;
|
||||
adap = &i2c->chans[i].adap;
|
||||
adap->owner = THIS_MODULE;
|
||||
@@ -445,14 +445,14 @@ static int rtl9300_i2c_probe(struct plat
|
||||
adap->dev.parent = dev;
|
||||
i2c_set_adapdata(adap, chan);
|
||||
adap->dev.of_node = to_of_node(child);
|
||||
- snprintf(adap->name, sizeof(adap->name), "%s SDA%d\n", dev_name(dev), sda_pin);
|
||||
+ snprintf(adap->name, sizeof(adap->name), "%s SDA%d\n", dev_name(dev), sda_num);
|
||||
i++;
|
||||
|
||||
ret = devm_i2c_add_adapter(dev, adap);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
- i2c->sda_pin = 0xff;
|
||||
+ i2c->sda_num = 0xff;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,67 +0,0 @@
|
||||
From 6b0549abc8582a81425f89a436def8e28d8d7dce Mon Sep 17 00:00:00 2001
|
||||
From: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Date: Sat, 27 Sep 2025 10:19:26 +0000
|
||||
Subject: [PATCH] i2c: rtl9300: move setting SCL frequency to config_io
|
||||
|
||||
Move the register operation to set the SCL frequency to the
|
||||
rtl9300_i2c_config_io function instead of the rtl9300_i2c_config_xfer
|
||||
function. This rather belongs there next to selecting the current SDA
|
||||
output line.
|
||||
|
||||
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Tested-by: Sven Eckelmann <sven@narfation.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302C based board
|
||||
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250927101931.71575-5-jelonek.jonas@gmail.com
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -95,18 +95,23 @@ static int rtl9300_i2c_select_scl(struct
|
||||
return regmap_field_write(i2c->fields[F_SCL_SEL], 1);
|
||||
}
|
||||
|
||||
-static int rtl9300_i2c_config_io(struct rtl9300_i2c *i2c, u8 sda_pin)
|
||||
+static int rtl9300_i2c_config_io(struct rtl9300_i2c *i2c, struct rtl9300_i2c_chan *chan)
|
||||
{
|
||||
struct rtl9300_i2c_drv_data *drv_data;
|
||||
int ret;
|
||||
|
||||
drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev);
|
||||
|
||||
- ret = regmap_field_update_bits(i2c->fields[F_SDA_SEL], BIT(sda_num), BIT(sda_num));
|
||||
+ ret = regmap_field_update_bits(i2c->fields[F_SDA_SEL], BIT(chan->sda_num),
|
||||
+ BIT(chan->sda_num));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- ret = regmap_field_write(i2c->fields[F_SDA_OUT_SEL], sda_num);
|
||||
+ ret = regmap_field_write(i2c->fields[F_SDA_OUT_SEL], chan->sda_num);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = regmap_field_write(i2c->fields[F_SCL_FREQ], chan->bus_freq);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -121,10 +126,6 @@ static int rtl9300_i2c_config_xfer(struc
|
||||
if (len < 1 || len > 16)
|
||||
return -EINVAL;
|
||||
|
||||
- ret = regmap_field_write(i2c->fields[F_SCL_FREQ], chan->bus_freq);
|
||||
- if (ret)
|
||||
- return ret;
|
||||
-
|
||||
ret = regmap_field_write(i2c->fields[F_DEV_ADDR], addr);
|
||||
if (ret)
|
||||
return ret;
|
||||
@@ -244,7 +245,7 @@ static int rtl9300_i2c_smbus_xfer(struct
|
||||
|
||||
mutex_lock(&i2c->lock);
|
||||
if (chan->sda_num != i2c->sda_num) {
|
||||
- ret = rtl9300_i2c_config_io(i2c, chan->sda_pin);
|
||||
+ ret = rtl9300_i2c_config_io(i2c, chan);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
i2c->sda_num = chan->sda_num;
|
||||
@ -1,49 +0,0 @@
|
||||
From 0cb24186d0ebd7dd12c070fed9d782bf7c6dfb1e Mon Sep 17 00:00:00 2001
|
||||
From: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Date: Sat, 27 Sep 2025 10:19:27 +0000
|
||||
Subject: [PATCH] i2c: rtl9300: do not set read mode on every transfer
|
||||
|
||||
Move the operation to set the read mode from config_xfer to probe.
|
||||
|
||||
The I2C controller of RTL9300 and RTL9310 support a legacy message mode
|
||||
for READs with 'Read Address Data' instead of the standard format 'Write
|
||||
Address ; Read Data'. There is no way to pass that via smbus_xfer, thus
|
||||
there is no point in supported this in the driver and moreover no point
|
||||
in setting this on every transaction. Setting this once in the probe
|
||||
call is sufficient.
|
||||
|
||||
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Tested-by: Sven Eckelmann <sven@narfation.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302C based board
|
||||
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250927101931.71575-6-jelonek.jonas@gmail.com
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -130,11 +130,7 @@ static int rtl9300_i2c_config_xfer(struc
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- ret = regmap_field_write(i2c->fields[F_DATA_WIDTH], (len - 1) & 0xf);
|
||||
- if (ret)
|
||||
- return ret;
|
||||
-
|
||||
- return regmap_field_write(i2c->fields[F_RD_MODE], 0);
|
||||
+ return regmap_field_write(i2c->fields[F_DATA_WIDTH], (len - 1) & 0xf);
|
||||
}
|
||||
|
||||
static int rtl9300_i2c_read(struct rtl9300_i2c *i2c, u8 *buf, int len)
|
||||
@@ -455,6 +451,11 @@ static int rtl9300_i2c_probe(struct plat
|
||||
}
|
||||
i2c->sda_num = 0xff;
|
||||
|
||||
+ /* only use standard read format */
|
||||
+ ret = regmap_field_write(i2c->fields[F_RD_MODE], 0);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1,382 +0,0 @@
|
||||
From 447dd46f95014eb4ea94f6164963bf23ce05b927 Mon Sep 17 00:00:00 2001
|
||||
From: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Date: Sat, 27 Sep 2025 10:19:28 +0000
|
||||
Subject: [PATCH] i2c: rtl9300: separate xfer configuration and execution
|
||||
|
||||
So far, the rtl9300_i2c_smbus_xfer code is quite a mess with function
|
||||
calls distributed over the whole function setting different values in
|
||||
different cases. Calls to rtl9300_i2c_config_xfer and
|
||||
rtl9300_i2c_reg_addr_set are used in every case-block with varying
|
||||
values whose meaning is not instantly obvious. In some cases, there are
|
||||
additional calls within these case-blocks doing more things.
|
||||
|
||||
This is in general a bad design and especially really bad for
|
||||
readability and maintainability because it distributes changes or
|
||||
issues to multiple locations due to the same function being called with
|
||||
different hardcoded values in different places.
|
||||
|
||||
To have a good structure, setting different parameters based on the
|
||||
desired operation should not be interleaved with applying these
|
||||
parameters to the hardware registers. Or in different words, the
|
||||
parameter site should be mixed with the call site.
|
||||
|
||||
Thus, separate configuration and execution of an SMBus xfer within
|
||||
rtl9300_i2c_smbus_xfer to improve readability and maintainability. Add a
|
||||
new 'struct rtl9300_i2c_xfer' to carry the required parameters for an
|
||||
xfer which are configured based on the input parameters within a single
|
||||
switch-case block, without having any function calls within this block.
|
||||
|
||||
The function calls to actually apply these values to the hardware
|
||||
registers then appear below in a single place and just operate on the
|
||||
passed instance of 'struct rtl9300_i2c_xfer'. These are
|
||||
'rtl9300_i2c_prepare_xfer' which combines applying all parameters of the
|
||||
xfer to the corresponding register, and 'rtl9300_i2c_do_xfer' which
|
||||
actually executes the xfer and does post-processing if needed.
|
||||
|
||||
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Tested-by: Sven Eckelmann <sven@narfation.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302C based board
|
||||
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250927101931.71575-7-jelonek.jonas@gmail.com
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
+#include <linux/unaligned.h>
|
||||
|
||||
enum rtl9300_bus_freq {
|
||||
RTL9300_I2C_STD_FREQ,
|
||||
@@ -71,6 +72,22 @@ struct rtl9300_i2c {
|
||||
struct mutex lock;
|
||||
};
|
||||
|
||||
+enum rtl9300_i2c_xfer_type {
|
||||
+ RTL9300_I2C_XFER_BYTE,
|
||||
+ RTL9300_I2C_XFER_WORD,
|
||||
+ RTL9300_I2C_XFER_BLOCK,
|
||||
+};
|
||||
+
|
||||
+struct rtl9300_i2c_xfer {
|
||||
+ enum rtl9300_i2c_xfer_type type;
|
||||
+ u16 dev_addr;
|
||||
+ u8 reg_addr;
|
||||
+ u8 reg_addr_len;
|
||||
+ u8 *data;
|
||||
+ u8 data_len;
|
||||
+ bool write;
|
||||
+};
|
||||
+
|
||||
#define RTL9300_I2C_MST_CTRL1 0x0
|
||||
#define RTL9300_I2C_MST_CTRL2 0x4
|
||||
#define RTL9300_I2C_MST_DATA_WORD0 0x8
|
||||
@@ -95,45 +112,37 @@ static int rtl9300_i2c_select_scl(struct
|
||||
return regmap_field_write(i2c->fields[F_SCL_SEL], 1);
|
||||
}
|
||||
|
||||
-static int rtl9300_i2c_config_io(struct rtl9300_i2c *i2c, struct rtl9300_i2c_chan *chan)
|
||||
+static int rtl9300_i2c_config_chan(struct rtl9300_i2c *i2c, struct rtl9300_i2c_chan *chan)
|
||||
{
|
||||
struct rtl9300_i2c_drv_data *drv_data;
|
||||
int ret;
|
||||
|
||||
- drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev);
|
||||
+ if (i2c->sda_num == chan->sda_num)
|
||||
+ return 0;
|
||||
|
||||
- ret = regmap_field_update_bits(i2c->fields[F_SDA_SEL], BIT(chan->sda_num),
|
||||
- BIT(chan->sda_num));
|
||||
+ ret = regmap_field_write(i2c->fields[F_SCL_FREQ], chan->bus_freq);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- ret = regmap_field_write(i2c->fields[F_SDA_OUT_SEL], chan->sda_num);
|
||||
+ drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev);
|
||||
+ ret = drv_data->select_scl(i2c, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- ret = regmap_field_write(i2c->fields[F_SCL_FREQ], chan->bus_freq);
|
||||
+ ret = regmap_field_update_bits(i2c->fields[F_SDA_SEL], BIT(chan->sda_num),
|
||||
+ BIT(chan->sda_num));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- return drv_data->select_scl(i2c, 0);
|
||||
-}
|
||||
-
|
||||
-static int rtl9300_i2c_config_xfer(struct rtl9300_i2c *i2c, struct rtl9300_i2c_chan *chan,
|
||||
- u16 addr, u16 len)
|
||||
-{
|
||||
- int ret;
|
||||
-
|
||||
- if (len < 1 || len > 16)
|
||||
- return -EINVAL;
|
||||
-
|
||||
- ret = regmap_field_write(i2c->fields[F_DEV_ADDR], addr);
|
||||
+ ret = regmap_field_write(i2c->fields[F_SDA_OUT_SEL], chan->sda_num);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- return regmap_field_write(i2c->fields[F_DATA_WIDTH], (len - 1) & 0xf);
|
||||
+ i2c->sda_num = chan->sda_num;
|
||||
+ return 0;
|
||||
}
|
||||
|
||||
-static int rtl9300_i2c_read(struct rtl9300_i2c *i2c, u8 *buf, int len)
|
||||
+static int rtl9300_i2c_read(struct rtl9300_i2c *i2c, u8 *buf, u8 len)
|
||||
{
|
||||
u32 vals[4] = {};
|
||||
int i, ret;
|
||||
@@ -153,7 +162,7 @@ static int rtl9300_i2c_read(struct rtl93
|
||||
return 0;
|
||||
}
|
||||
|
||||
-static int rtl9300_i2c_write(struct rtl9300_i2c *i2c, u8 *buf, int len)
|
||||
+static int rtl9300_i2c_write(struct rtl9300_i2c *i2c, u8 *buf, u8 len)
|
||||
{
|
||||
u32 vals[4] = {};
|
||||
int i;
|
||||
@@ -176,16 +185,51 @@ static int rtl9300_i2c_writel(struct rtl
|
||||
return regmap_write(i2c->regmap, i2c->data_reg, data);
|
||||
}
|
||||
|
||||
-static int rtl9300_i2c_execute_xfer(struct rtl9300_i2c *i2c, char read_write,
|
||||
- int size, union i2c_smbus_data *data, int len)
|
||||
+static int rtl9300_i2c_prepare_xfer(struct rtl9300_i2c *i2c, struct rtl9300_i2c_xfer *xfer)
|
||||
{
|
||||
- u32 val;
|
||||
int ret;
|
||||
|
||||
- ret = regmap_field_write(i2c->fields[F_RWOP], read_write == I2C_SMBUS_WRITE);
|
||||
+ if (xfer->data_len < 1 || xfer->data_len > 16)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ ret = regmap_field_write(i2c->fields[F_DEV_ADDR], xfer->dev_addr);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
+ ret = rtl9300_i2c_reg_addr_set(i2c, xfer->reg_addr, xfer->reg_addr_len);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = regmap_field_write(i2c->fields[F_RWOP], xfer->write);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = regmap_field_write(i2c->fields[F_DATA_WIDTH], (xfer->data_len - 1) & 0xf);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ if (xfer->write) {
|
||||
+ switch (xfer->type) {
|
||||
+ case RTL9300_I2C_XFER_BYTE:
|
||||
+ ret = rtl9300_i2c_writel(i2c, *xfer->data);
|
||||
+ break;
|
||||
+ case RTL9300_I2C_XFER_WORD:
|
||||
+ ret = rtl9300_i2c_writel(i2c, get_unaligned((const u16 *)xfer->data));
|
||||
+ break;
|
||||
+ default:
|
||||
+ ret = rtl9300_i2c_write(i2c, xfer->data, xfer->data_len);
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static int rtl9300_i2c_do_xfer(struct rtl9300_i2c *i2c, struct rtl9300_i2c_xfer *xfer)
|
||||
+{
|
||||
+ u32 val;
|
||||
+ int ret;
|
||||
+
|
||||
ret = regmap_field_write(i2c->fields[F_I2C_TRIG], 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
@@ -200,28 +244,24 @@ static int rtl9300_i2c_execute_xfer(stru
|
||||
if (val)
|
||||
return -EIO;
|
||||
|
||||
- if (read_write == I2C_SMBUS_READ) {
|
||||
- switch (size) {
|
||||
- case I2C_SMBUS_BYTE:
|
||||
- case I2C_SMBUS_BYTE_DATA:
|
||||
+ if (!xfer->write) {
|
||||
+ switch (xfer->type) {
|
||||
+ case RTL9300_I2C_XFER_BYTE:
|
||||
ret = regmap_read(i2c->regmap, i2c->data_reg, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
- data->byte = val & 0xff;
|
||||
+
|
||||
+ *xfer->data = val & 0xff;
|
||||
break;
|
||||
- case I2C_SMBUS_WORD_DATA:
|
||||
+ case RTL9300_I2C_XFER_WORD:
|
||||
ret = regmap_read(i2c->regmap, i2c->data_reg, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
- data->word = val & 0xffff;
|
||||
- break;
|
||||
- case I2C_SMBUS_I2C_BLOCK_DATA:
|
||||
- ret = rtl9300_i2c_read(i2c, &data->block[1], len);
|
||||
- if (ret)
|
||||
- return ret;
|
||||
+
|
||||
+ put_unaligned(val & 0xffff, (u16*)xfer->data);
|
||||
break;
|
||||
default:
|
||||
- ret = rtl9300_i2c_read(i2c, &data->block[0], len);
|
||||
+ ret = rtl9300_i2c_read(i2c, xfer->data, xfer->data_len);
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
@@ -237,108 +277,62 @@ static int rtl9300_i2c_smbus_xfer(struct
|
||||
{
|
||||
struct rtl9300_i2c_chan *chan = i2c_get_adapdata(adap);
|
||||
struct rtl9300_i2c *i2c = chan->i2c;
|
||||
- int len = 0, ret;
|
||||
+ struct rtl9300_i2c_xfer xfer = {0};
|
||||
+ int ret;
|
||||
+
|
||||
+ if (addr > 0x7f)
|
||||
+ return -EINVAL;
|
||||
|
||||
mutex_lock(&i2c->lock);
|
||||
- if (chan->sda_num != i2c->sda_num) {
|
||||
- ret = rtl9300_i2c_config_io(i2c, chan);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- i2c->sda_num = chan->sda_num;
|
||||
- }
|
||||
+
|
||||
+ ret = rtl9300_i2c_config_chan(i2c, chan);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+
|
||||
+ xfer.dev_addr = addr & 0x7f;
|
||||
+ xfer.write = (read_write == I2C_SMBUS_WRITE);
|
||||
+ xfer.reg_addr = command;
|
||||
+ xfer.reg_addr_len = 1;
|
||||
|
||||
switch (size) {
|
||||
case I2C_SMBUS_BYTE:
|
||||
- if (read_write == I2C_SMBUS_WRITE) {
|
||||
- ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 0);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- } else {
|
||||
- ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 1);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- ret = rtl9300_i2c_reg_addr_set(i2c, 0, 0);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- }
|
||||
+ xfer.data = (read_write == I2C_SMBUS_READ) ? &data->byte : &command;
|
||||
+ xfer.data_len = 1;
|
||||
+ xfer.reg_addr = 0;
|
||||
+ xfer.reg_addr_len = 0;
|
||||
+ xfer.type = RTL9300_I2C_XFER_BYTE;
|
||||
break;
|
||||
-
|
||||
case I2C_SMBUS_BYTE_DATA:
|
||||
- ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 1);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- if (read_write == I2C_SMBUS_WRITE) {
|
||||
- ret = rtl9300_i2c_writel(i2c, data->byte);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- }
|
||||
+ xfer.data = &data->byte;
|
||||
+ xfer.data_len = 1;
|
||||
+ xfer.type = RTL9300_I2C_XFER_BYTE;
|
||||
break;
|
||||
-
|
||||
case I2C_SMBUS_WORD_DATA:
|
||||
- ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 2);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- if (read_write == I2C_SMBUS_WRITE) {
|
||||
- ret = rtl9300_i2c_writel(i2c, data->word);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- }
|
||||
+ xfer.data = (u8 *)&data->word;
|
||||
+ xfer.data_len = 2;
|
||||
+ xfer.type = RTL9300_I2C_XFER_WORD;
|
||||
break;
|
||||
-
|
||||
case I2C_SMBUS_BLOCK_DATA:
|
||||
- ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- if (data->block[0] < 1 || data->block[0] > I2C_SMBUS_BLOCK_MAX) {
|
||||
- ret = -EINVAL;
|
||||
- goto out_unlock;
|
||||
- }
|
||||
- ret = rtl9300_i2c_config_xfer(i2c, chan, addr, data->block[0] + 1);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- if (read_write == I2C_SMBUS_WRITE) {
|
||||
- ret = rtl9300_i2c_write(i2c, &data->block[0], data->block[0] + 1);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- }
|
||||
- len = data->block[0] + 1;
|
||||
+ xfer.data = &data->block[0];
|
||||
+ xfer.data_len = data->block[0] + 1;
|
||||
+ xfer.type = RTL9300_I2C_XFER_BLOCK;
|
||||
break;
|
||||
-
|
||||
case I2C_SMBUS_I2C_BLOCK_DATA:
|
||||
- ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- if (data->block[0] < 1 || data->block[0] > I2C_SMBUS_BLOCK_MAX) {
|
||||
- ret = -EINVAL;
|
||||
- goto out_unlock;
|
||||
- }
|
||||
- ret = rtl9300_i2c_config_xfer(i2c, chan, addr, data->block[0]);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- if (read_write == I2C_SMBUS_WRITE) {
|
||||
- ret = rtl9300_i2c_write(i2c, &data->block[1], data->block[0]);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- }
|
||||
- len = data->block[0];
|
||||
+ xfer.data = &data->block[1];
|
||||
+ xfer.data_len = data->block[0];
|
||||
+ xfer.type = RTL9300_I2C_XFER_BLOCK;
|
||||
break;
|
||||
-
|
||||
default:
|
||||
dev_err(&adap->dev, "Unsupported transaction %d\n", size);
|
||||
ret = -EOPNOTSUPP;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
- ret = rtl9300_i2c_execute_xfer(i2c, read_write, size, data, len);
|
||||
+ ret = rtl9300_i2c_prepare_xfer(i2c, &xfer);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+
|
||||
+ ret = rtl9300_i2c_do_xfer(i2c, &xfer);
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&i2c->lock);
|
||||
@ -1,68 +0,0 @@
|
||||
From bcd5f0da57e6c47a884dcad94ad6b0e32cce8705 Mon Sep 17 00:00:00 2001
|
||||
From: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Date: Sat, 27 Sep 2025 10:19:29 +0000
|
||||
Subject: [PATCH] i2c: rtl9300: use scoped guard instead of explicit
|
||||
lock/unlock
|
||||
|
||||
Use the scoped guard infrastructure which unlocks a mutex automatically
|
||||
when the guard goes out of scope, instead of explicit lock and unlock.
|
||||
This simplifies the code and control flow in rtl9300_i2c_smbus_xfer and
|
||||
removes the need of using goto in error cases to unlock before
|
||||
returning.
|
||||
|
||||
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302C based board
|
||||
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250927101931.71575-8-jelonek.jonas@gmail.com
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -72,6 +72,8 @@ struct rtl9300_i2c {
|
||||
struct mutex lock;
|
||||
};
|
||||
|
||||
+DEFINE_GUARD(rtl9300_i2c, struct rtl9300_i2c *, mutex_lock(&_T->lock), mutex_unlock(&_T->lock))
|
||||
+
|
||||
enum rtl9300_i2c_xfer_type {
|
||||
RTL9300_I2C_XFER_BYTE,
|
||||
RTL9300_I2C_XFER_WORD,
|
||||
@@ -283,11 +285,11 @@ static int rtl9300_i2c_smbus_xfer(struct
|
||||
if (addr > 0x7f)
|
||||
return -EINVAL;
|
||||
|
||||
- mutex_lock(&i2c->lock);
|
||||
+ guard(rtl9300_i2c)(i2c);
|
||||
|
||||
ret = rtl9300_i2c_config_chan(i2c, chan);
|
||||
if (ret)
|
||||
- goto out_unlock;
|
||||
+ return ret;
|
||||
|
||||
xfer.dev_addr = addr & 0x7f;
|
||||
xfer.write = (read_write == I2C_SMBUS_WRITE);
|
||||
@@ -324,20 +326,14 @@ static int rtl9300_i2c_smbus_xfer(struct
|
||||
break;
|
||||
default:
|
||||
dev_err(&adap->dev, "Unsupported transaction %d\n", size);
|
||||
- ret = -EOPNOTSUPP;
|
||||
- goto out_unlock;
|
||||
+ return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
ret = rtl9300_i2c_prepare_xfer(i2c, &xfer);
|
||||
if (ret)
|
||||
- goto out_unlock;
|
||||
-
|
||||
- ret = rtl9300_i2c_do_xfer(i2c, &xfer);
|
||||
-
|
||||
-out_unlock:
|
||||
- mutex_unlock(&i2c->lock);
|
||||
+ return ret;
|
||||
|
||||
- return ret;
|
||||
+ return rtl9300_i2c_do_xfer(i2c, &xfer);
|
||||
}
|
||||
|
||||
static u32 rtl9300_i2c_func(struct i2c_adapter *a)
|
||||
@ -1,94 +0,0 @@
|
||||
From 17689aafb793599a862617a127429dd3d6f675c9 Mon Sep 17 00:00:00 2001
|
||||
From: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Date: Sat, 27 Sep 2025 10:19:30 +0000
|
||||
Subject: [PATCH] dt-bindings: i2c: realtek,rtl9301-i2c: extend for RTL9310
|
||||
support
|
||||
|
||||
Adjust the regex for child-node address to account for the fact that
|
||||
RTL9310 supports 12 instead of only 8 SDA lines. Also, narrow this per
|
||||
variant.
|
||||
|
||||
Add a vendor-specific property to explicitly specify the SCL line number
|
||||
of the defined I2C controller/master. This is required, in particular
|
||||
for RTL9310, to operate on the correct SCL for each controller. Require
|
||||
this property to be specified for RTL9310.
|
||||
|
||||
Add compatibles for known SoC variants RTL9311, RTL9312 and RTL9313.
|
||||
|
||||
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250927101931.71575-9-jelonek.jonas@gmail.com
|
||||
|
||||
--- a/Documentation/devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml
|
||||
+++ b/Documentation/devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml
|
||||
@@ -13,6 +13,8 @@ description:
|
||||
RTL9300 SoCs have two I2C controllers. Each of these has an SCL line (which
|
||||
if not-used for SCL can be a GPIO). There are 8 common SDA lines that can be
|
||||
assigned to either I2C controller.
|
||||
+ RTL9310 SoCs have equal capabilities but support 12 common SDA lines which
|
||||
+ can be assigned to either I2C controller.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
@@ -23,7 +25,15 @@ properties:
|
||||
- realtek,rtl9302c-i2c
|
||||
- realtek,rtl9303-i2c
|
||||
- const: realtek,rtl9301-i2c
|
||||
- - const: realtek,rtl9301-i2c
|
||||
+ - items:
|
||||
+ - enum:
|
||||
+ - realtek,rtl9311-i2c
|
||||
+ - realtek,rtl9312-i2c
|
||||
+ - realtek,rtl9313-i2c
|
||||
+ - const: realtek,rtl9310-i2c
|
||||
+ - enum:
|
||||
+ - realtek,rtl9301-i2c
|
||||
+ - realtek,rtl9310-i2c
|
||||
|
||||
reg:
|
||||
items:
|
||||
@@ -35,8 +45,14 @@ properties:
|
||||
"#size-cells":
|
||||
const: 0
|
||||
|
||||
+ realtek,scl:
|
||||
+ $ref: /schemas/types.yaml#/definitions/uint32
|
||||
+ description:
|
||||
+ The SCL line number of this I2C controller.
|
||||
+ enum: [ 0, 1 ]
|
||||
+
|
||||
patternProperties:
|
||||
- '^i2c@[0-7]$':
|
||||
+ '^i2c@[0-9ab]$':
|
||||
$ref: /schemas/i2c/i2c-controller.yaml
|
||||
unevaluatedProperties: false
|
||||
|
||||
@@ -48,6 +64,25 @@ patternProperties:
|
||||
required:
|
||||
- reg
|
||||
|
||||
+
|
||||
+allOf:
|
||||
+ - if:
|
||||
+ properties:
|
||||
+ compatible:
|
||||
+ contains:
|
||||
+ const: realtek,rtl9310-i2c
|
||||
+ then:
|
||||
+ required:
|
||||
+ - realtek,scl
|
||||
+ - if:
|
||||
+ properties:
|
||||
+ compatible:
|
||||
+ contains:
|
||||
+ const: realtek,rtl9301-i2c
|
||||
+ then:
|
||||
+ patternProperties:
|
||||
+ '^i2c@[89ab]$': false
|
||||
+
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
@ -1,134 +0,0 @@
|
||||
From 8d43287120ce6437e7a77e735d99137f3fdb3ae9 Mon Sep 17 00:00:00 2001
|
||||
From: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Date: Sat, 27 Sep 2025 10:19:31 +0000
|
||||
Subject: [PATCH] i2c: rtl9300: add support for RTL9310 I2C controller
|
||||
|
||||
Add support for the internal I2C controllers of RTL9310 series based
|
||||
SoCs to the driver for RTL9300. Add register definitions, chip-specific
|
||||
functions and compatible strings for known RTL9310-based SoCs RTL9311,
|
||||
RTL9312 and RTL9313.
|
||||
|
||||
Make use of a new device tree property 'realtek,scl' which needs to be
|
||||
specified in case both or only the second master is used. This is
|
||||
required due how the register layout changed in contrast to RTL9300,
|
||||
which has SCL selection in a global register instead of a
|
||||
master-specific one.
|
||||
|
||||
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Tested-by: Sven Eckelmann <sven@narfation.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250927101931.71575-10-jelonek.jonas@gmail.com
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -60,14 +60,16 @@ struct rtl9300_i2c_drv_data {
|
||||
};
|
||||
|
||||
#define RTL9300_I2C_MUX_NCHAN 8
|
||||
+#define RTL9310_I2C_MUX_NCHAN 12
|
||||
|
||||
struct rtl9300_i2c {
|
||||
struct regmap *regmap;
|
||||
struct device *dev;
|
||||
- struct rtl9300_i2c_chan chans[RTL9300_I2C_MUX_NCHAN];
|
||||
+ struct rtl9300_i2c_chan chans[RTL9310_I2C_MUX_NCHAN];
|
||||
struct regmap_field *fields[F_NUM_FIELDS];
|
||||
u32 reg_base;
|
||||
u32 data_reg;
|
||||
+ u8 scl_num;
|
||||
u8 sda_num;
|
||||
struct mutex lock;
|
||||
};
|
||||
@@ -98,6 +100,12 @@ struct rtl9300_i2c_xfer {
|
||||
#define RTL9300_I2C_MST_DATA_WORD3 0x14
|
||||
#define RTL9300_I2C_MST_GLB_CTRL 0x384
|
||||
|
||||
+#define RTL9310_I2C_MST_IF_CTRL 0x1004
|
||||
+#define RTL9310_I2C_MST_IF_SEL 0x1008
|
||||
+#define RTL9310_I2C_MST_CTRL 0x0
|
||||
+#define RTL9310_I2C_MST_MEMADDR_CTRL 0x4
|
||||
+#define RTL9310_I2C_MST_DATA_CTRL 0x8
|
||||
+
|
||||
static int rtl9300_i2c_reg_addr_set(struct rtl9300_i2c *i2c, u32 reg, u16 len)
|
||||
{
|
||||
int ret;
|
||||
@@ -114,6 +122,11 @@ static int rtl9300_i2c_select_scl(struct
|
||||
return regmap_field_write(i2c->fields[F_SCL_SEL], 1);
|
||||
}
|
||||
|
||||
+static int rtl9310_i2c_select_scl(struct rtl9300_i2c *i2c, u8 scl)
|
||||
+{
|
||||
+ return regmap_field_update_bits(i2c->fields[F_SCL_SEL], BIT(scl), BIT(scl));
|
||||
+}
|
||||
+
|
||||
static int rtl9300_i2c_config_chan(struct rtl9300_i2c *i2c, struct rtl9300_i2c_chan *chan)
|
||||
{
|
||||
struct rtl9300_i2c_drv_data *drv_data;
|
||||
@@ -127,7 +140,7 @@ static int rtl9300_i2c_config_chan(struc
|
||||
return ret;
|
||||
|
||||
drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev);
|
||||
- ret = drv_data->select_scl(i2c, 0);
|
||||
+ ret = drv_data->select_scl(i2c, i2c->scl_num);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -361,7 +374,7 @@ static int rtl9300_i2c_probe(struct plat
|
||||
struct fwnode_handle *child;
|
||||
struct rtl9300_i2c_drv_data *drv_data;
|
||||
struct reg_field fields[F_NUM_FIELDS];
|
||||
- u32 clock_freq, sda_num;
|
||||
+ u32 clock_freq, scl_num, sda_num;
|
||||
int ret, i = 0;
|
||||
|
||||
i2c = devm_kzalloc(dev, sizeof(*i2c), GFP_KERNEL);
|
||||
@@ -379,6 +392,11 @@ static int rtl9300_i2c_probe(struct plat
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
+ ret = device_property_read_u32(dev, "realtek,scl", &scl_num);
|
||||
+ if (ret || scl_num != 1)
|
||||
+ scl_num = 0;
|
||||
+ i2c->scl_num = (u8)scl_num;
|
||||
+
|
||||
platform_set_drvdata(pdev, i2c);
|
||||
|
||||
drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev);
|
||||
@@ -474,12 +492,35 @@ static const struct rtl9300_i2c_drv_data
|
||||
.max_nchan = RTL9300_I2C_MUX_NCHAN,
|
||||
};
|
||||
|
||||
+static const struct rtl9300_i2c_drv_data rtl9310_i2c_drv_data = {
|
||||
+ .field_desc = {
|
||||
+ [F_SCL_SEL] = GLB_REG_FIELD(RTL9310_I2C_MST_IF_SEL, 12, 13),
|
||||
+ [F_SDA_SEL] = GLB_REG_FIELD(RTL9310_I2C_MST_IF_SEL, 0, 11),
|
||||
+ [F_SCL_FREQ] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 30, 31),
|
||||
+ [F_DEV_ADDR] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 11, 17),
|
||||
+ [F_SDA_OUT_SEL] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 18, 21),
|
||||
+ [F_MEM_ADDR_WIDTH] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 9, 10),
|
||||
+ [F_DATA_WIDTH] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 5, 8),
|
||||
+ [F_RD_MODE] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 4, 4),
|
||||
+ [F_RWOP] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 2, 2),
|
||||
+ [F_I2C_FAIL] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 1, 1),
|
||||
+ [F_I2C_TRIG] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 0, 0),
|
||||
+ [F_MEM_ADDR] = MST_REG_FIELD(RTL9310_I2C_MST_MEMADDR_CTRL, 0, 23),
|
||||
+ },
|
||||
+ .select_scl = rtl9310_i2c_select_scl,
|
||||
+ .data_reg = RTL9310_I2C_MST_DATA_CTRL,
|
||||
+ .max_nchan = RTL9310_I2C_MUX_NCHAN,
|
||||
+};
|
||||
|
||||
static const struct of_device_id i2c_rtl9300_dt_ids[] = {
|
||||
{ .compatible = "realtek,rtl9301-i2c", .data = (void *) &rtl9300_i2c_drv_data },
|
||||
{ .compatible = "realtek,rtl9302b-i2c", .data = (void *) &rtl9300_i2c_drv_data },
|
||||
{ .compatible = "realtek,rtl9302c-i2c", .data = (void *) &rtl9300_i2c_drv_data },
|
||||
{ .compatible = "realtek,rtl9303-i2c", .data = (void *) &rtl9300_i2c_drv_data },
|
||||
+ { .compatible = "realtek,rtl9310-i2c", .data = (void *) &rtl9310_i2c_drv_data },
|
||||
+ { .compatible = "realtek,rtl9311-i2c", .data = (void *) &rtl9310_i2c_drv_data },
|
||||
+ { .compatible = "realtek,rtl9312-i2c", .data = (void *) &rtl9310_i2c_drv_data },
|
||||
+ { .compatible = "realtek,rtl9313-i2c", .data = (void *) &rtl9310_i2c_drv_data },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, i2c_rtl9300_dt_ids);
|
||||
@ -1,51 +0,0 @@
|
||||
From 85e27f218121bdaa8e8afd68674262aa154d2cb4 Mon Sep 17 00:00:00 2001
|
||||
From: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Date: Mon, 4 Aug 2025 04:03:26 -0400
|
||||
Subject: clocksource/drivers/timer-rtl-otto: Drop set_counter function
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
The current counter value is a read only register. It will be
|
||||
reset when writing a new target timer value with rttm_set_period().
|
||||
rttm_set_counter() is essentially a noop. Drop it.
|
||||
|
||||
While this makes rttm_start_timer() and rttm_enable_timer() the
|
||||
same functions keep both to make the established abstraction layers
|
||||
for register and control functions active.
|
||||
|
||||
Downstream has already tested and confirmed a patch. See
|
||||
https://github.com/openwrt/openwrt/pull/19468
|
||||
https://forum.openwrt.org/t/support-for-rtl838x-based-managed-switches/57875/3788
|
||||
|
||||
Tested-by: Stephen Howell <howels@allthatwemight.be>
|
||||
Tested-by: Bjørn Mork <bjorn@mork.no>
|
||||
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Link: https://lore.kernel.org/r/20250804080328.2609287-3-markus.stockhausen@gmx.de
|
||||
Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
|
||||
---
|
||||
drivers/clocksource/timer-rtl-otto.c | 6 ------
|
||||
1 file changed, 6 deletions(-)
|
||||
|
||||
--- a/drivers/clocksource/timer-rtl-otto.c
|
||||
+++ b/drivers/clocksource/timer-rtl-otto.c
|
||||
@@ -56,11 +56,6 @@ struct rttm_cs {
|
||||
};
|
||||
|
||||
/* Simple internal register functions */
|
||||
-static inline void rttm_set_counter(void __iomem *base, unsigned int counter)
|
||||
-{
|
||||
- iowrite32(counter, base + RTTM_CNT);
|
||||
-}
|
||||
-
|
||||
static inline unsigned int rttm_get_counter(void __iomem *base)
|
||||
{
|
||||
return ioread32(base + RTTM_CNT);
|
||||
@@ -137,7 +132,6 @@ static void rttm_stop_timer(void __iomem
|
||||
|
||||
static void rttm_start_timer(struct timer_of *to, u32 mode)
|
||||
{
|
||||
- rttm_set_counter(to->of_base.base, 0);
|
||||
rttm_enable_timer(to->of_base.base, mode, to->of_clk.rate / RTTM_TICKS_PER_SEC);
|
||||
}
|
||||
|
||||
@ -1,521 +0,0 @@
|
||||
From 3148d0e5b1c5733d69ec51b70c8280e46488750a Mon Sep 17 00:00:00 2001
|
||||
From: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Date: Fri, 19 Sep 2025 03:52:01 -0400
|
||||
Subject: mtd: nand: realtek-ecc: Add Realtek external ECC engine support
|
||||
|
||||
The Realtek RTl93xx switch SoC series has a built in ECC controller
|
||||
that can provide BCH6 or BCH12 over 512 data and 6 tag bytes. It
|
||||
generates 10 (BCH6) or 20 (BCH12) bytes of parity.
|
||||
|
||||
This engine will most likely work in conjunction with the Realtek
|
||||
spi-mem based NAND controller but can work on its own. Therefore
|
||||
the initial implementation will be of type external.
|
||||
|
||||
Remark! The engine can support any data blocks that are multiples
|
||||
of 512 bytes. For now limit it to data+oob layouts that have been
|
||||
analyzed from existing devices. This way it keeps compatibility
|
||||
and pre-existing vendor data can be read.
|
||||
|
||||
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
|
||||
---
|
||||
drivers/mtd/nand/Kconfig | 8 +
|
||||
drivers/mtd/nand/Makefile | 1 +
|
||||
drivers/mtd/nand/ecc-realtek.c | 464 +++++++++++++++++++++++++++++++++++++++++
|
||||
3 files changed, 473 insertions(+)
|
||||
create mode 100644 drivers/mtd/nand/ecc-realtek.c
|
||||
|
||||
--- a/drivers/mtd/nand/Kconfig
|
||||
+++ b/drivers/mtd/nand/Kconfig
|
||||
@@ -65,6 +65,14 @@ config MTD_NAND_ECC_MEDIATEK
|
||||
help
|
||||
This enables support for the hardware ECC engine from Mediatek.
|
||||
|
||||
+config MTD_NAND_ECC_REALTEK
|
||||
+ tristate "Realtek RTL93xx hardware ECC engine"
|
||||
+ depends on HAS_IOMEM
|
||||
+ depends on MACH_REALTEK_RTL || COMPILE_TEST
|
||||
+ select MTD_NAND_ECC
|
||||
+ help
|
||||
+ This enables support for the hardware ECC engine from Realtek.
|
||||
+
|
||||
endmenu
|
||||
|
||||
endmenu
|
||||
--- a/drivers/mtd/nand/Makefile
|
||||
+++ b/drivers/mtd/nand/Makefile
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
nandcore-objs := core.o bbt.o
|
||||
obj-$(CONFIG_MTD_NAND_CORE) += nandcore.o
|
||||
+obj-$(CONFIG_MTD_NAND_ECC_REALTEK) += ecc-realtek.o
|
||||
obj-$(CONFIG_MTD_NAND_ECC_MEDIATEK) += ecc-mtk.o
|
||||
obj-$(CONFIG_MTD_NAND_MTK_BMT) += mtk_bmt.o mtk_bmt_v2.o mtk_bmt_bbt.o mtk_bmt_nmbm.o
|
||||
obj-$(CONFIG_SPI_QPIC_SNAND) += qpic_common.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/mtd/nand/ecc-realtek.c
|
||||
@@ -0,0 +1,464 @@
|
||||
+// SPDX-License-Identifier: GPL-2.0
|
||||
+/*
|
||||
+ * Support for Realtek hardware ECC engine in RTL93xx SoCs
|
||||
+ */
|
||||
+
|
||||
+#include <linux/bitfield.h>
|
||||
+#include <linux/dma-mapping.h>
|
||||
+#include <linux/mtd/nand.h>
|
||||
+#include <linux/mutex.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/regmap.h>
|
||||
+
|
||||
+/*
|
||||
+ * The Realtek ECC engine has two operation modes.
|
||||
+ *
|
||||
+ * - BCH6 : Generate 10 ECC bytes from 512 data bytes plus 6 free bytes
|
||||
+ * - BCH12 : Generate 20 ECC bytes from 512 data bytes plus 6 free bytes
|
||||
+ *
|
||||
+ * It can run for arbitrary NAND flash chips with different block and OOB sizes. Currently there
|
||||
+ * are only two known devices in the wild that have NAND flash and make use of this ECC engine
|
||||
+ * (Linksys LGS328C & LGS352C). To keep compatibility with vendor firmware, new modes can only
|
||||
+ * be added when new data layouts have been analyzed. For now allow BCH6 on flash with 2048 byte
|
||||
+ * blocks and 64 bytes oob.
|
||||
+ *
|
||||
+ * This driver aligns with kernel ECC naming conventions. Neverthless a short notice on the
|
||||
+ * Realtek naming conventions for the different structures in the OOB area.
|
||||
+ *
|
||||
+ * - BBI : Bad block indicator. The first two bytes of OOB. Protected by ECC!
|
||||
+ * - tag : 6 User/free bytes. First tag "contains" 2 bytes BBI. Protected by ECC!
|
||||
+ * - syndrome : ECC/parity bytes
|
||||
+ *
|
||||
+ * Altogether this gives currently the following block layout.
|
||||
+ *
|
||||
+ * +------+------+------+------+-----+------+------+------+------+-----+-----+-----+-----+
|
||||
+ * | 512 | 512 | 512 | 512 | 2 | 4 | 6 | 6 | 6 | 10 | 10 | 10 | 10 |
|
||||
+ * +------+------+------+------+-----+------+------+------+------+-----+-----+-----+-----+
|
||||
+ * | data | data | data | data | BBI | free | free | free | free | ECC | ECC | ECC | ECC |
|
||||
+ * +------+------+------+------+-----+------+------+------+------+-----+-----+-----+-----+
|
||||
+ */
|
||||
+
|
||||
+#define RTL_ECC_ALLOWED_PAGE_SIZE 2048
|
||||
+#define RTL_ECC_ALLOWED_OOB_SIZE 64
|
||||
+#define RTL_ECC_ALLOWED_STRENGTH 6
|
||||
+
|
||||
+#define RTL_ECC_BLOCK_SIZE 512
|
||||
+#define RTL_ECC_FREE_SIZE 6
|
||||
+#define RTL_ECC_PARITY_SIZE_BCH6 10
|
||||
+#define RTL_ECC_PARITY_SIZE_BCH12 20
|
||||
+
|
||||
+/*
|
||||
+ * The engine is fed with two DMA regions. One for data (always 512 bytes) and one for free bytes
|
||||
+ * and parity (either 16 bytes for BCH6 or 26 bytes for BCH12). Start and length of each must be
|
||||
+ * aligned to a multiple of 4.
|
||||
+ */
|
||||
+
|
||||
+#define RTL_ECC_DMA_FREE_PARITY_SIZE ALIGN(RTL_ECC_FREE_SIZE + RTL_ECC_PARITY_SIZE_BCH12, 4)
|
||||
+#define RTL_ECC_DMA_SIZE (RTL_ECC_BLOCK_SIZE + RTL_ECC_DMA_FREE_PARITY_SIZE)
|
||||
+
|
||||
+#define RTL_ECC_CFG 0x00
|
||||
+#define RTL_ECC_BCH6 0
|
||||
+#define RTL_ECC_BCH12 BIT(28)
|
||||
+#define RTL_ECC_DMA_PRECISE BIT(12)
|
||||
+#define RTL_ECC_BURST_128 GENMASK(1, 0)
|
||||
+#define RTL_ECC_DMA_TRIGGER 0x08
|
||||
+#define RTL_ECC_OP_DECODE 0
|
||||
+#define RTL_ECC_OP_ENCODE BIT(0)
|
||||
+#define RTL_ECC_DMA_START 0x0c
|
||||
+#define RTL_ECC_DMA_TAG 0x10
|
||||
+#define RTL_ECC_STATUS 0x14
|
||||
+#define RTL_ECC_CORR_COUNT GENMASK(19, 12)
|
||||
+#define RTL_ECC_RESULT BIT(8)
|
||||
+#define RTL_ECC_ALL_ONE BIT(4)
|
||||
+#define RTL_ECC_OP_STATUS BIT(0)
|
||||
+
|
||||
+struct rtl_ecc_engine {
|
||||
+ struct device *dev;
|
||||
+ struct nand_ecc_engine engine;
|
||||
+ struct mutex lock;
|
||||
+ char *buf;
|
||||
+ dma_addr_t buf_dma;
|
||||
+ struct regmap *regmap;
|
||||
+};
|
||||
+
|
||||
+struct rtl_ecc_ctx {
|
||||
+ struct rtl_ecc_engine * rtlc;
|
||||
+ struct nand_ecc_req_tweak_ctx req_ctx;
|
||||
+ int steps;
|
||||
+ int bch_mode;
|
||||
+ int strength;
|
||||
+ int parity_size;
|
||||
+};
|
||||
+
|
||||
+static const struct regmap_config rtl_ecc_regmap_config = {
|
||||
+ .reg_bits = 32,
|
||||
+ .val_bits = 32,
|
||||
+ .reg_stride = 4,
|
||||
+};
|
||||
+
|
||||
+static inline void *nand_to_ctx(struct nand_device *nand)
|
||||
+{
|
||||
+ return nand->ecc.ctx.priv;
|
||||
+}
|
||||
+
|
||||
+static inline struct rtl_ecc_engine *nand_to_rtlc(struct nand_device *nand)
|
||||
+{
|
||||
+ struct nand_ecc_engine *eng = nand->ecc.engine;
|
||||
+
|
||||
+ return container_of(eng, struct rtl_ecc_engine, engine);
|
||||
+}
|
||||
+
|
||||
+static int rtl_ecc_ooblayout_ecc(struct mtd_info *mtd, int section,
|
||||
+ struct mtd_oob_region *oobregion)
|
||||
+{
|
||||
+ struct nand_device *nand = mtd_to_nanddev(mtd);
|
||||
+ struct rtl_ecc_ctx *ctx = nand_to_ctx(nand);
|
||||
+
|
||||
+ if (section < 0 || section >= ctx->steps)
|
||||
+ return -ERANGE;
|
||||
+
|
||||
+ oobregion->offset = ctx->steps * RTL_ECC_FREE_SIZE + section * ctx->parity_size;
|
||||
+ oobregion->length = ctx->parity_size;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int rtl_ecc_ooblayout_free(struct mtd_info *mtd, int section,
|
||||
+ struct mtd_oob_region *oobregion)
|
||||
+{
|
||||
+ struct nand_device *nand = mtd_to_nanddev(mtd);
|
||||
+ struct rtl_ecc_ctx *ctx = nand_to_ctx(nand);
|
||||
+ int bbm;
|
||||
+
|
||||
+ if (section < 0 || section >= ctx->steps)
|
||||
+ return -ERANGE;
|
||||
+
|
||||
+ /* reserve 2 BBM bytes in first block */
|
||||
+ bbm = section ? 0 : 2;
|
||||
+ oobregion->offset = section * RTL_ECC_FREE_SIZE + bbm;
|
||||
+ oobregion->length = RTL_ECC_FREE_SIZE - bbm;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static const struct mtd_ooblayout_ops rtl_ecc_ooblayout_ops = {
|
||||
+ .ecc = rtl_ecc_ooblayout_ecc,
|
||||
+ .free = rtl_ecc_ooblayout_free,
|
||||
+};
|
||||
+
|
||||
+static void rtl_ecc_kick_engine(struct rtl_ecc_ctx *ctx, int operation)
|
||||
+{
|
||||
+ struct rtl_ecc_engine *rtlc = ctx->rtlc;
|
||||
+
|
||||
+ regmap_write(rtlc->regmap, RTL_ECC_CFG,
|
||||
+ ctx->bch_mode | RTL_ECC_BURST_128 | RTL_ECC_DMA_PRECISE);
|
||||
+
|
||||
+ regmap_write(rtlc->regmap, RTL_ECC_DMA_START, rtlc->buf_dma);
|
||||
+ regmap_write(rtlc->regmap, RTL_ECC_DMA_TAG, rtlc->buf_dma + RTL_ECC_BLOCK_SIZE);
|
||||
+ regmap_write(rtlc->regmap, RTL_ECC_DMA_TRIGGER, operation);
|
||||
+}
|
||||
+
|
||||
+static int rtl_ecc_wait_for_engine(struct rtl_ecc_ctx *ctx)
|
||||
+{
|
||||
+ struct rtl_ecc_engine *rtlc = ctx->rtlc;
|
||||
+ int ret, status, bitflips;
|
||||
+ bool all_one;
|
||||
+
|
||||
+ /*
|
||||
+ * The ECC engine needs 6-8 us to encode/decode a BCH6 syndrome for 512 bytes of data
|
||||
+ * and 6 free bytes. In case the NAND area has been erased and all data and oob is
|
||||
+ * set to 0xff, decoding takes 30us (reason unknown). Although the engine can trigger
|
||||
+ * interrupts when finished, use active polling for now. 12 us maximum wait time has
|
||||
+ * proven to be a good tradeoff between performance and overhead.
|
||||
+ */
|
||||
+
|
||||
+ ret = regmap_read_poll_timeout(rtlc->regmap, RTL_ECC_STATUS, status,
|
||||
+ !(status & RTL_ECC_OP_STATUS), 12, 1000000);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = FIELD_GET(RTL_ECC_RESULT, status);
|
||||
+ all_one = FIELD_GET(RTL_ECC_ALL_ONE, status);
|
||||
+ bitflips = FIELD_GET(RTL_ECC_CORR_COUNT, status);
|
||||
+
|
||||
+ /* For erased blocks (all bits one) error status can be ignored */
|
||||
+ if (all_one)
|
||||
+ ret = 0;
|
||||
+
|
||||
+ return ret ? -EBADMSG : bitflips;
|
||||
+}
|
||||
+
|
||||
+static int rtl_ecc_run_engine(struct rtl_ecc_ctx *ctx, char *data, char *free,
|
||||
+ char *parity, int operation)
|
||||
+{
|
||||
+ struct rtl_ecc_engine *rtlc = ctx->rtlc;
|
||||
+ char *buf_parity = rtlc->buf + RTL_ECC_BLOCK_SIZE + RTL_ECC_FREE_SIZE;
|
||||
+ char *buf_free = rtlc->buf + RTL_ECC_BLOCK_SIZE;
|
||||
+ char *buf_data = rtlc->buf;
|
||||
+ int ret;
|
||||
+
|
||||
+ mutex_lock(&rtlc->lock);
|
||||
+
|
||||
+ memcpy(buf_data, data, RTL_ECC_BLOCK_SIZE);
|
||||
+ memcpy(buf_free, free, RTL_ECC_FREE_SIZE);
|
||||
+ memcpy(buf_parity, parity, ctx->parity_size);
|
||||
+
|
||||
+ dma_sync_single_for_device(rtlc->dev, rtlc->buf_dma, RTL_ECC_DMA_SIZE, DMA_TO_DEVICE);
|
||||
+ rtl_ecc_kick_engine(ctx, operation);
|
||||
+ ret = rtl_ecc_wait_for_engine(ctx);
|
||||
+ dma_sync_single_for_cpu(rtlc->dev, rtlc->buf_dma, RTL_ECC_DMA_SIZE, DMA_FROM_DEVICE);
|
||||
+
|
||||
+ if (ret >= 0) {
|
||||
+ memcpy(data, buf_data, RTL_ECC_BLOCK_SIZE);
|
||||
+ memcpy(free, buf_free, RTL_ECC_FREE_SIZE);
|
||||
+ memcpy(parity, buf_parity, ctx->parity_size);
|
||||
+ }
|
||||
+
|
||||
+ mutex_unlock(&rtlc->lock);
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static int rtl_ecc_prepare_io_req(struct nand_device *nand, struct nand_page_io_req *req)
|
||||
+{
|
||||
+ struct rtl_ecc_engine *rtlc = nand_to_rtlc(nand);
|
||||
+ struct rtl_ecc_ctx *ctx = nand_to_ctx(nand);
|
||||
+ char *data, *free, *parity;
|
||||
+ int ret = 0;
|
||||
+
|
||||
+ if (req->mode == MTD_OPS_RAW)
|
||||
+ return 0;
|
||||
+
|
||||
+ nand_ecc_tweak_req(&ctx->req_ctx, req);
|
||||
+
|
||||
+ if (req->type == NAND_PAGE_READ)
|
||||
+ return 0;
|
||||
+
|
||||
+ free = req->oobbuf.in;
|
||||
+ data = req->databuf.in;
|
||||
+ parity = req->oobbuf.in + ctx->steps * RTL_ECC_FREE_SIZE;
|
||||
+
|
||||
+ for (int i = 0; i < ctx->steps; i++) {
|
||||
+ ret |= rtl_ecc_run_engine(ctx, data, free, parity, RTL_ECC_OP_ENCODE);
|
||||
+
|
||||
+ free += RTL_ECC_FREE_SIZE;
|
||||
+ data += RTL_ECC_BLOCK_SIZE;
|
||||
+ parity += ctx->parity_size;
|
||||
+ }
|
||||
+
|
||||
+ if (unlikely(ret))
|
||||
+ dev_dbg(rtlc->dev, "ECC calculation failed\n");
|
||||
+
|
||||
+ return ret ? -EBADMSG : 0;
|
||||
+}
|
||||
+
|
||||
+static int rtl_ecc_finish_io_req(struct nand_device *nand, struct nand_page_io_req *req)
|
||||
+{
|
||||
+ struct rtl_ecc_engine *rtlc = nand_to_rtlc(nand);
|
||||
+ struct rtl_ecc_ctx *ctx = nand_to_ctx(nand);
|
||||
+ struct mtd_info *mtd = nanddev_to_mtd(nand);
|
||||
+ char *data, *free, *parity;
|
||||
+ bool failure = false;
|
||||
+ int bitflips = 0;
|
||||
+
|
||||
+ if (req->mode == MTD_OPS_RAW)
|
||||
+ return 0;
|
||||
+
|
||||
+ if (req->type == NAND_PAGE_WRITE) {
|
||||
+ nand_ecc_restore_req(&ctx->req_ctx, req);
|
||||
+ return 0;
|
||||
+ }
|
||||
+
|
||||
+ free = req->oobbuf.in;
|
||||
+ data = req->databuf.in;
|
||||
+ parity = req->oobbuf.in + ctx->steps * RTL_ECC_FREE_SIZE;
|
||||
+
|
||||
+ for (int i = 0 ; i < ctx->steps; i++) {
|
||||
+ int ret = rtl_ecc_run_engine(ctx, data, free, parity, RTL_ECC_OP_DECODE);
|
||||
+
|
||||
+ if (unlikely(ret < 0))
|
||||
+ /* ECC totally fails for bitflips in erased blocks */
|
||||
+ ret = nand_check_erased_ecc_chunk(data, RTL_ECC_BLOCK_SIZE,
|
||||
+ parity, ctx->parity_size,
|
||||
+ free, RTL_ECC_FREE_SIZE,
|
||||
+ ctx->strength);
|
||||
+ if (unlikely(ret < 0)) {
|
||||
+ failure = true;
|
||||
+ mtd->ecc_stats.failed++;
|
||||
+ } else {
|
||||
+ mtd->ecc_stats.corrected += ret;
|
||||
+ bitflips = max_t(unsigned int, bitflips, ret);
|
||||
+ }
|
||||
+
|
||||
+ free += RTL_ECC_FREE_SIZE;
|
||||
+ data += RTL_ECC_BLOCK_SIZE;
|
||||
+ parity += ctx->parity_size;
|
||||
+ }
|
||||
+
|
||||
+ nand_ecc_restore_req(&ctx->req_ctx, req);
|
||||
+
|
||||
+ if (unlikely(failure))
|
||||
+ dev_dbg(rtlc->dev, "ECC correction failed\n");
|
||||
+ else if (unlikely(bitflips > 2))
|
||||
+ dev_dbg(rtlc->dev, "%d bitflips detected\n", bitflips);
|
||||
+
|
||||
+ return failure ? -EBADMSG : bitflips;
|
||||
+}
|
||||
+
|
||||
+static int rtl_ecc_check_support(struct nand_device *nand)
|
||||
+{
|
||||
+ struct mtd_info *mtd = nanddev_to_mtd(nand);
|
||||
+ struct device *dev = nand->ecc.engine->dev;
|
||||
+
|
||||
+ if (mtd->oobsize != RTL_ECC_ALLOWED_OOB_SIZE ||
|
||||
+ mtd->writesize != RTL_ECC_ALLOWED_PAGE_SIZE) {
|
||||
+ dev_err(dev, "only flash geometry data=%d, oob=%d supported\n",
|
||||
+ RTL_ECC_ALLOWED_PAGE_SIZE, RTL_ECC_ALLOWED_OOB_SIZE);
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+
|
||||
+ if (nand->ecc.user_conf.algo != NAND_ECC_ALGO_BCH ||
|
||||
+ nand->ecc.user_conf.strength != RTL_ECC_ALLOWED_STRENGTH ||
|
||||
+ nand->ecc.user_conf.placement != NAND_ECC_PLACEMENT_OOB ||
|
||||
+ nand->ecc.user_conf.step_size != RTL_ECC_BLOCK_SIZE) {
|
||||
+ dev_err(dev, "only algo=bch, strength=%d, placement=oob, step=%d supported\n",
|
||||
+ RTL_ECC_ALLOWED_STRENGTH, RTL_ECC_BLOCK_SIZE);
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int rtl_ecc_init_ctx(struct nand_device *nand)
|
||||
+{
|
||||
+ struct nand_ecc_props *conf = &nand->ecc.ctx.conf;
|
||||
+ struct rtl_ecc_engine *rtlc = nand_to_rtlc(nand);
|
||||
+ struct mtd_info *mtd = nanddev_to_mtd(nand);
|
||||
+ int strength = nand->ecc.user_conf.strength;
|
||||
+ struct device *dev = nand->ecc.engine->dev;
|
||||
+ struct rtl_ecc_ctx *ctx;
|
||||
+ int ret;
|
||||
+
|
||||
+ ret = rtl_ecc_check_support(nand);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
|
||||
+ if (!ctx)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ nand->ecc.ctx.priv = ctx;
|
||||
+ mtd_set_ooblayout(mtd, &rtl_ecc_ooblayout_ops);
|
||||
+
|
||||
+ conf->algo = NAND_ECC_ALGO_BCH;
|
||||
+ conf->strength = strength;
|
||||
+ conf->step_size = RTL_ECC_BLOCK_SIZE;
|
||||
+ conf->engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST;
|
||||
+
|
||||
+ ctx->rtlc = rtlc;
|
||||
+ ctx->steps = mtd->writesize / RTL_ECC_BLOCK_SIZE;
|
||||
+ ctx->strength = strength;
|
||||
+ ctx->bch_mode = strength == 6 ? RTL_ECC_BCH6 : RTL_ECC_BCH12;
|
||||
+ ctx->parity_size = strength == 6 ? RTL_ECC_PARITY_SIZE_BCH6 : RTL_ECC_PARITY_SIZE_BCH12;
|
||||
+
|
||||
+ ret = nand_ecc_init_req_tweaking(&ctx->req_ctx, nand);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ dev_dbg(dev, "using bch%d with geometry data=%dx%d, free=%dx6, parity=%dx%d",
|
||||
+ conf->strength, ctx->steps, conf->step_size,
|
||||
+ ctx->steps, ctx->steps, ctx->parity_size);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static void rtl_ecc_cleanup_ctx(struct nand_device *nand)
|
||||
+{
|
||||
+ struct rtl_ecc_ctx *ctx = nand_to_ctx(nand);
|
||||
+
|
||||
+ if (ctx)
|
||||
+ nand_ecc_cleanup_req_tweaking(&ctx->req_ctx);
|
||||
+}
|
||||
+
|
||||
+static struct nand_ecc_engine_ops rtl_ecc_engine_ops = {
|
||||
+ .init_ctx = rtl_ecc_init_ctx,
|
||||
+ .cleanup_ctx = rtl_ecc_cleanup_ctx,
|
||||
+ .prepare_io_req = rtl_ecc_prepare_io_req,
|
||||
+ .finish_io_req = rtl_ecc_finish_io_req,
|
||||
+};
|
||||
+
|
||||
+static int rtl_ecc_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct device *dev = &pdev->dev;
|
||||
+ struct rtl_ecc_engine *rtlc;
|
||||
+ void __iomem *base;
|
||||
+ int ret;
|
||||
+
|
||||
+ rtlc = devm_kzalloc(dev, sizeof(*rtlc), GFP_KERNEL);
|
||||
+ if (!rtlc)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ base = devm_platform_ioremap_resource(pdev, 0);
|
||||
+ if (IS_ERR(base))
|
||||
+ return PTR_ERR(base);
|
||||
+
|
||||
+ ret = devm_mutex_init(dev, &rtlc->lock);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ rtlc->regmap = devm_regmap_init_mmio(dev, base, &rtl_ecc_regmap_config);
|
||||
+ if (IS_ERR(rtlc->regmap))
|
||||
+ return PTR_ERR(rtlc->regmap);
|
||||
+
|
||||
+ /*
|
||||
+ * Focus on simplicity and use a preallocated DMA buffer for data exchange with the
|
||||
+ * engine. For now make it a noncoherent memory model as invalidating/flushing caches
|
||||
+ * is faster than reading/writing uncached memory on the known architectures.
|
||||
+ */
|
||||
+
|
||||
+ rtlc->buf = dma_alloc_noncoherent(dev, RTL_ECC_DMA_SIZE, &rtlc->buf_dma,
|
||||
+ DMA_BIDIRECTIONAL, GFP_KERNEL);
|
||||
+ if (IS_ERR(rtlc->buf))
|
||||
+ return PTR_ERR(rtlc->buf);
|
||||
+
|
||||
+ rtlc->dev = dev;
|
||||
+ rtlc->engine.dev = dev;
|
||||
+ rtlc->engine.ops = &rtl_ecc_engine_ops;
|
||||
+ rtlc->engine.integration = NAND_ECC_ENGINE_INTEGRATION_EXTERNAL;
|
||||
+
|
||||
+ nand_ecc_register_on_host_hw_engine(&rtlc->engine);
|
||||
+
|
||||
+ platform_set_drvdata(pdev, rtlc);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static void rtl_ecc_remove(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct rtl_ecc_engine *rtlc = platform_get_drvdata(pdev);
|
||||
+
|
||||
+ nand_ecc_unregister_on_host_hw_engine(&rtlc->engine);
|
||||
+ dma_free_noncoherent(rtlc->dev, RTL_ECC_DMA_SIZE, rtlc->buf, rtlc->buf_dma,
|
||||
+ DMA_BIDIRECTIONAL);
|
||||
+}
|
||||
+
|
||||
+static const struct of_device_id rtl_ecc_of_ids[] = {
|
||||
+ {
|
||||
+ .compatible = "realtek,rtl9301-ecc",
|
||||
+ },
|
||||
+ { /* sentinel */ },
|
||||
+};
|
||||
+
|
||||
+static struct platform_driver rtl_ecc_driver = {
|
||||
+ .driver = {
|
||||
+ .name = "rtl-nand-ecc-engine",
|
||||
+ .of_match_table = rtl_ecc_of_ids,
|
||||
+ },
|
||||
+ .probe = rtl_ecc_probe,
|
||||
+ .remove = rtl_ecc_remove,
|
||||
+};
|
||||
+module_platform_driver(rtl_ecc_driver);
|
||||
+
|
||||
+MODULE_LICENSE("GPL");
|
||||
+MODULE_AUTHOR("Markus Stockhausen <markus.stockhausen@gmx.de>");
|
||||
+MODULE_DESCRIPTION("Realtek NAND hardware ECC controller");
|
||||
@ -1,47 +0,0 @@
|
||||
From 2250db8628a0d8293ad2e0671138b848a185fba1 Mon Sep 17 00:00:00 2001
|
||||
From: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Date: Sat, 21 Jun 2025 01:49:51 -0400
|
||||
Subject: irqchip/mips-gic: Allow forced affinity
|
||||
|
||||
Devices of the Realtek MIPS Otto platform use the official rtl-otto-timer
|
||||
as clock event generator and CPU clocksource. It is registered for each CPU
|
||||
startup via cpuhp_setup_state() and forces the affinity of the clockevent
|
||||
interrupts to the appropriate CPU via irq_force_affinity().
|
||||
|
||||
On the "smaller" devices with a vendor specific interrupt controller
|
||||
(supported by irq-realtek-rtl) the registration works fine. The "larger"
|
||||
RTL931x series is based on a MIPS interAptiv dual core with a MIPS GIC
|
||||
controller. Interrupt routing setup is cancelled because gic_set_affinity()
|
||||
does not accept the current (not yet online) CPU as a target.
|
||||
|
||||
Relax the checks by evaluating the force parameter that is provided for
|
||||
exactly this purpose like in other drivers. With this the affinity can be
|
||||
set as follows:
|
||||
|
||||
- force = false: allow to set affinity to any online cpu
|
||||
- force = true: allow to set affinity to any cpu
|
||||
|
||||
Co-developed-by: Sebastian Gottschall <s.gottschall@dd-wrt.com>
|
||||
Signed-off-by: Sebastian Gottschall <s.gottschall@dd-wrt.com>
|
||||
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
|
||||
Link: https://lore.kernel.org/all/20250621054952.380374-1-markus.stockhausen@gmx.de
|
||||
---
|
||||
drivers/irqchip/irq-mips-gic.c | 8 ++++++--
|
||||
1 file changed, 6 insertions(+), 2 deletions(-)
|
||||
|
||||
--- a/drivers/irqchip/irq-mips-gic.c
|
||||
+++ b/drivers/irqchip/irq-mips-gic.c
|
||||
@@ -263,7 +263,11 @@ static int gic_set_affinity(struct irq_d
|
||||
unsigned long flags;
|
||||
unsigned int cpu;
|
||||
|
||||
- cpu = cpumask_first_and(cpumask, cpu_online_mask);
|
||||
+ if (force)
|
||||
+ cpu = cpumask_first(cpumask);
|
||||
+ else
|
||||
+ cpu = cpumask_first_and(cpumask, cpu_online_mask);
|
||||
+
|
||||
if (cpu >= NR_CPUS)
|
||||
return -EINVAL;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user