From: Vincent Jardin <[email protected]>

The LX2160A muxes its IIC, XSPI, SDHC, UART, IRQ and EVT pads from
fields of the RCW, read-only in RCWSR12..14 of DCFG and changed at
runtime through the same fields of the override registers in its DCSR
view, which read 0 until written.

Since commit 8a1365c7bbc1 ("arm64: dts: lx2160a: add pinmux and i2c
gpio to support bus recovery") Linux drives them with pinctrl-single on
the override registers, so its first pin state clears every other field
of the word: a board whose RCW programs a non-zero pad mux loses those
pads at the first I2C probe.

Add a driver for the pad mux as a child of the DCFG node: the fields
of RCWSR12..14 and their encoding.
Then pin states select them with the generic function and groups
properties, and every change goes through the RCW override
of fsl-guts, which reads the status register, writes the override and
waits for the status register to follow.

Signed-off-by: Vincent Jardin <[email protected]>
---
 MAINTAINERS                                 |   7 +
 drivers/pinctrl/freescale/Kconfig           |  15 ++
 drivers/pinctrl/freescale/Makefile          |   1 +
 drivers/pinctrl/freescale/pinctrl-lx2160a.c | 355 ++++++++++++++++++++++++++++
 4 files changed, 378 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 93abbb315065..6c488605f5da 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20207,6 +20207,13 @@ F:     Documentation/devicetree/bindings/clock/*imx*
 F:     drivers/clk/imx/
 F:     include/dt-bindings/clock/*imx*
 
+NXP LX2160A RCW PAD-MUX DRIVER
+M:     Vincent Jardin <[email protected]>
+L:     [email protected]
+S:     Maintained
+F:     Documentation/devicetree/bindings/pinctrl/fsl,lx2160a-rcw-pinmux.yaml
+F:     drivers/pinctrl/freescale/pinctrl-lx2160a.c
+
 NXP NETC ETHERNET SWITCH DRIVER
 M:     Wei Fang <[email protected]>
 R:     Clark Wang <[email protected]>
diff --git a/drivers/pinctrl/freescale/Kconfig 
b/drivers/pinctrl/freescale/Kconfig
index fd53cf5bb843..a3222714c970 100644
--- a/drivers/pinctrl/freescale/Kconfig
+++ b/drivers/pinctrl/freescale/Kconfig
@@ -283,3 +283,18 @@ config PINCTRL_IMXRT1170
        select PINCTRL_IMX
        help
          Say Y here to enable the imxrt1170 pinctrl driver
+
+config PINCTRL_LX2160A
+       bool "NXP LX2160A RCW pad-mux driver"
+       depends on OF
+       depends on ARCH_LAYERSCAPE || COMPILE_TEST
+       default ARCH_LAYERSCAPE
+       select FSL_GUTS
+       select GENERIC_PINCTRL_GROUPS
+       select GENERIC_PINMUX_FUNCTIONS
+       select GENERIC_PINCONF
+       help
+         Say Y here to mux the IIC, XSPI, SDHC, UART, IRQ and EVT pads of the
+         LX2160A at runtime. The pads are muxed by fields of the RCW:
+         the driver changes it in the DCSR view of the DCFG that fsl-guts
+         drives, and is what the I2C bus recovery of the LX2160A needs.
diff --git a/drivers/pinctrl/freescale/Makefile 
b/drivers/pinctrl/freescale/Makefile
index d27085c2b4c4..5ec0d1710ad5 100644
--- a/drivers/pinctrl/freescale/Makefile
+++ b/drivers/pinctrl/freescale/Makefile
@@ -35,3 +35,4 @@ obj-$(CONFIG_PINCTRL_IMX25)   += pinctrl-imx25.o
 obj-$(CONFIG_PINCTRL_IMX28)    += pinctrl-imx28.o
 obj-$(CONFIG_PINCTRL_IMXRT1050)        += pinctrl-imxrt1050.o
 obj-$(CONFIG_PINCTRL_IMXRT1170)        += pinctrl-imxrt1170.o
+obj-$(CONFIG_PINCTRL_LX2160A)  += pinctrl-lx2160a.o
diff --git a/drivers/pinctrl/freescale/pinctrl-lx2160a.c 
b/drivers/pinctrl/freescale/pinctrl-lx2160a.c
new file mode 100644
index 000000000000..ba5bbcfab9fd
--- /dev/null
+++ b/drivers/pinctrl/freescale/pinctrl-lx2160a.c
@@ -0,0 +1,355 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * LX2160A pad muxes: fields of the RCW, changed at
+ * runtime.
+ *
+ * Copyright 2026 Free Mobile - Vincent Jardin
+ */
+
+#include <linux/bits.h>
+#include <linux/fsl/guts.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/platform_device.h>
+#include <linux/seq_file.h>
+
+#include "../core.h"
+#include "../pinmux.h"
+
+struct lx2160a_pmux_func {
+       const char *name;
+       u8 val;
+};
+
+struct lx2160a_pmux_group {
+       const char *name;
+       u8 rcwsr; /* 12..14 */
+       u8 shift;
+       u8 width;
+       const struct lx2160a_pmux_func *funcs;
+       u8 nfuncs;
+};
+
+#define FUNC(_name, _val)      { .name = _name, .val = _val }
+#define GROUP(_name, _rcwsr, _shift, _width, _funcs)                   \
+       {                                                               \
+               .name = _name, .rcwsr = _rcwsr,                         \
+               .shift = _shift, .width = _width,                       \
+               .funcs = _funcs, .nfuncs = ARRAY_SIZE(_funcs),          \
+       }
+
+static const struct lx2160a_pmux_func iic2_funcs[] = {
+       FUNC("i2c", 0), FUNC("gpio", 1), FUNC("flextimer", 2), FUNC("sdhc", 6),
+};
+
+static const struct lx2160a_pmux_func iic3_funcs[] = {
+       FUNC("i2c", 0), FUNC("gpio", 1), FUNC("can", 2), FUNC("evt", 6),
+};
+
+static const struct lx2160a_pmux_func iic5_funcs[] = {
+       FUNC("i2c", 0), FUNC("gpio", 1), FUNC("sdhc-clk", 2), FUNC("spi", 3),
+};
+
+static const struct lx2160a_pmux_func iic6_funcs[] = {
+       FUNC("i2c", 0), FUNC("gpio", 1), FUNC("sdhc-clk", 2),
+};
+
+static const struct lx2160a_pmux_func xspi1_funcs[] = {
+       FUNC("flexspi", 0), FUNC("gpio", 1),
+};
+
+static const struct lx2160a_pmux_func sdhc1_base_funcs[] = {
+       FUNC("sdhc", 0), FUNC("gpio", 1), FUNC("spi", 2), FUNC("sdhc-spi", 3),
+       FUNC("sdhc-data4", 4),
+};
+
+static const struct lx2160a_pmux_func sdhc1_dir_funcs[] = {
+       FUNC("sdhc", 0), FUNC("gpio", 1), FUNC("spi", 3), FUNC("sdhc-data", 4),
+};
+
+static const struct lx2160a_pmux_func sdhc2_data74_funcs[] = {
+       FUNC("sdhc", 0), FUNC("gpio", 1), FUNC("i2c", 2), FUNC("flexspi", 6),
+};
+
+static const struct lx2160a_pmux_func sdhc2_base_funcs[] = {
+       FUNC("sdhc", 0), FUNC("gpio", 1), FUNC("spi", 2), FUNC("flexspi", 6),
+};
+
+static const struct lx2160a_pmux_func uart_funcs[] = {
+       FUNC("uart", 0), FUNC("gpio", 1),
+};
+
+static const struct lx2160a_pmux_func uart1_rtscts_funcs[] = {
+       FUNC("uart", 0), FUNC("gpio", 1), FUNC("uart3", 2),
+};
+
+static const struct lx2160a_pmux_func uart2_rtscts_funcs[] = {
+       FUNC("uart", 0), FUNC("gpio", 1), FUNC("uart4", 2),
+};
+
+static const struct lx2160a_pmux_func irq_funcs[] = {
+       FUNC("irq", 0), FUNC("gpio", 1),
+};
+
+static const struct lx2160a_pmux_func irq_flextimer_funcs[] = {
+       FUNC("irq", 0), FUNC("gpio", 1), FUNC("flextimer", 2),
+};
+
+static const struct lx2160a_pmux_func evt_funcs[] = {
+       FUNC("evt", 0), FUNC("gpio", 1), FUNC("flextimer", 2),
+};
+
+static const struct lx2160a_pmux_func clk_out_funcs[] = {
+       FUNC("gpio", 1), FUNC("flextimer", 2),
+};
+
+static const struct lx2160a_pmux_func asleep_funcs[] = {
+       FUNC("asleep", 0), FUNC("gpio", 1), FUNC("evt", 2),
+};
+
+static const struct lx2160a_pmux_func reset_req_funcs[] = {
+       FUNC("reset-req", 0), FUNC("gpio", 1),
+};
+
+static const struct lx2160a_pmux_func iic1_funcs[] = {
+       FUNC("i2c", 0), FUNC("gpio", 1),
+};
+
+static const struct lx2160a_pmux_group lx2160a_pmux_groups[] = {
+       /* RCWSR12 */
+       GROUP("iic2", 12, 0, 3, iic2_funcs),
+       GROUP("iic3", 12, 3, 3, iic3_funcs),
+       GROUP("iic4", 12, 6, 3, iic3_funcs),
+       GROUP("iic5", 12, 9, 3, iic5_funcs),
+       GROUP("iic6", 12, 12, 3, iic6_funcs),
+       GROUP("xspi1-data74", 12, 15, 3, xspi1_funcs),
+       GROUP("xspi1-data30", 12, 18, 3, xspi1_funcs),
+       GROUP("xspi1-base", 12, 21, 3, xspi1_funcs),
+       GROUP("sdhc1-base", 12, 24, 3, sdhc1_base_funcs),
+       GROUP("sdhc1-dir", 12, 27, 3, sdhc1_dir_funcs),
+       /* RCWSR13 */
+       GROUP("sdhc2-data74", 13, 0, 3, sdhc2_data74_funcs),
+       GROUP("sdhc2-base", 13, 3, 3, sdhc2_base_funcs),
+       GROUP("uart1-soutsin", 13, 6, 3, uart_funcs),
+       GROUP("uart1-rtscts", 13, 9, 3, uart1_rtscts_funcs),
+       GROUP("uart2-soutsin", 13, 12, 3, uart_funcs),
+       GROUP("uart2-rtscts", 13, 15, 3, uart2_rtscts_funcs),
+       GROUP("irq11-8", 13, 18, 3, irq_funcs),
+       GROUP("irq7-4", 13, 21, 3, irq_flextimer_funcs),
+       GROUP("irq3-0", 13, 24, 3, irq_flextimer_funcs),
+       GROUP("evt4-3", 13, 27, 3, evt_funcs),
+       /* RCWSR14 */
+       GROUP("evt2-0", 14, 0, 3, evt_funcs),
+       GROUP("clk-out", 14, 3, 3, clk_out_funcs),
+       GROUP("asleep", 14, 6, 3, asleep_funcs),
+       GROUP("reset-req", 14, 9, 1, reset_req_funcs),
+       GROUP("iic1", 14, 10, 1, iic1_funcs),
+};
+
+struct lx2160a_pmux {
+       struct device *dev;
+       struct pinctrl_dev *pctl;
+       struct pinctrl_desc desc;
+       struct pinctrl_pin_desc pins[ARRAY_SIZE(lx2160a_pmux_groups)];
+};
+
+static u32 lx2160a_pmux_group_mask(const struct lx2160a_pmux_group *group)
+{
+       return GENMASK(group->shift + group->width - 1, group->shift);
+}
+
+static void lx2160a_pmux_pin_dbg_show(struct pinctrl_dev *pctldev,
+                                     struct seq_file *s, unsigned int pin)
+{
+       const struct lx2160a_pmux_group *group = &lx2160a_pmux_groups[pin];
+       unsigned int i;
+       u32 val;
+
+       if (fsl_guts_rcw_read(group->rcwsr, &val))
+               return;
+
+       val = (val & lx2160a_pmux_group_mask(group)) >> group->shift;
+       for (i = 0; i < group->nfuncs; i++) {
+               if (group->funcs[i].val == val) {
+                       seq_puts(s, group->funcs[i].name);
+                       return;
+               }
+       }
+       seq_printf(s, "0x%x", val);
+}
+
+static const struct pinctrl_ops lx2160a_pmux_pctlops = {
+       .get_groups_count = pinctrl_generic_get_group_count,
+       .get_group_name = pinctrl_generic_get_group_name,
+       .get_group_pins = pinctrl_generic_get_group_pins,
+       .pin_dbg_show = lx2160a_pmux_pin_dbg_show,
+       .dt_node_to_map = pinconf_generic_dt_node_to_map_group,
+       .dt_free_map = pinconf_generic_dt_free_map,
+};
+
+static int lx2160a_pmux_set_mux(struct pinctrl_dev *pctldev,
+                               unsigned int fsel, unsigned int gsel)
+{
+       struct lx2160a_pmux *pmux = pinctrl_dev_get_drvdata(pctldev);
+       const struct function_desc *func;
+       const struct lx2160a_pmux_group *group;
+       const struct group_desc *grp;
+       unsigned int i;
+
+       func = pinmux_generic_get_function(pctldev, fsel);
+       grp = pinctrl_generic_get_group(pctldev, gsel);
+       if (!func || !grp)
+               return -EINVAL;
+       group = grp->data;
+
+       for (i = 0; i < group->nfuncs; i++) {
+               if (!strcmp(group->funcs[i].name, func->func->name))
+                       break;
+       }
+       if (i == group->nfuncs) {
+               dev_err(pmux->dev, "%s has no %s function\n",
+                       group->name, func->func->name);
+               return -EINVAL;
+       }
+
+       return fsl_guts_rcw_override(group->rcwsr,
+                                    group->funcs[i].val << group->shift,
+                                    lx2160a_pmux_group_mask(group));
+}
+
+static const struct pinmux_ops lx2160a_pmux_pmxops = {
+       .get_functions_count = pinmux_generic_get_function_count,
+       .get_function_name = pinmux_generic_get_function_name,
+       .get_function_groups = pinmux_generic_get_function_groups,
+       .set_mux = lx2160a_pmux_set_mux,
+       .strict = true,
+};
+
+static int lx2160a_pmux_add_functions(struct lx2160a_pmux *pmux)
+{
+       const struct lx2160a_pmux_group *group;
+       const char **groups;
+       unsigned int i, j, k, n;
+       int ret;
+
+       for (i = 0; i < ARRAY_SIZE(lx2160a_pmux_groups); i++) {
+               group = &lx2160a_pmux_groups[i];
+               for (j = 0; j < group->nfuncs; j++) {
+                       const char *name = group->funcs[j].name;
+                       bool seen = false;
+
+                       /* already added from an earlier group? */
+                       for (k = 0; k < i && !seen; k++) {
+                               const struct lx2160a_pmux_group *g =
+                                       &lx2160a_pmux_groups[k];
+                               unsigned int l;
+
+                               for (l = 0; l < g->nfuncs; l++)
+                                       if (!strcmp(g->funcs[l].name, name))
+                                               seen = true;
+                       }
+                       if (seen)
+                               continue;
+
+                       groups = devm_kcalloc(pmux->dev,
+                                             ARRAY_SIZE(lx2160a_pmux_groups),
+                                             sizeof(*groups), GFP_KERNEL);
+                       if (!groups)
+                               return -ENOMEM;
+                       n = 0;
+                       for (k = i; k < ARRAY_SIZE(lx2160a_pmux_groups); k++) {
+                               const struct lx2160a_pmux_group *g =
+                                       &lx2160a_pmux_groups[k];
+                               unsigned int l;
+
+                               for (l = 0; l < g->nfuncs; l++)
+                                       if (!strcmp(g->funcs[l].name, name))
+                                               groups[n++] = g->name;
+                       }
+                       ret = pinmux_generic_add_function(pmux->pctl, name,
+                                                         groups, n, NULL);
+                       if (ret < 0)
+                               return ret;
+               }
+       }
+
+       return 0;
+}
+
+static int lx2160a_pmux_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct lx2160a_pmux *pmux;
+       unsigned int i;
+       u32 val;
+       int ret;
+
+       /* The override needs the DCSR view of DCFG, mapped by fsl-guts */
+       if (!fsl_guts_rcw_override_supported())
+               return dev_err_probe(dev, -ENODEV,
+                                    "no RCW override through fsl-guts: the 
dcfg node needs its DCSR reg\n");
+
+       pmux = devm_kzalloc(dev, sizeof(*pmux), GFP_KERNEL);
+       if (!pmux)
+               return -ENOMEM;
+       pmux->dev = dev;
+
+       for (i = 0; i < ARRAY_SIZE(lx2160a_pmux_groups); i++) {
+               pmux->pins[i].number = i;
+               pmux->pins[i].name = lx2160a_pmux_groups[i].name;
+       }
+       pmux->desc.name = dev_name(dev);
+       pmux->desc.pins = pmux->pins;
+       pmux->desc.npins = ARRAY_SIZE(pmux->pins);
+       pmux->desc.pctlops = &lx2160a_pmux_pctlops;
+       pmux->desc.pmxops = &lx2160a_pmux_pmxops;
+       pmux->desc.owner = THIS_MODULE;
+
+       ret = devm_pinctrl_register_and_init(dev, &pmux->desc, pmux,
+                                            &pmux->pctl);
+       if (ret)
+               return dev_err_probe(dev, ret, "pinctrl registration failed\n");
+
+       for (i = 0; i < ARRAY_SIZE(lx2160a_pmux_groups); i++) {
+               ret = pinctrl_generic_add_group(pmux->pctl,
+                                               lx2160a_pmux_groups[i].name,
+                                               &pmux->pins[i].number, 1,
+                                               (void 
*)&lx2160a_pmux_groups[i]);
+               if (ret < 0)
+                       return ret;
+       }
+
+       ret = lx2160a_pmux_add_functions(pmux);
+       if (ret)
+               return ret;
+
+       for (i = 12; i <= 14; i++) {
+               fsl_guts_rcw_read(i, &val);
+               dev_dbg(dev, "RCWSR%u = 0x%08x\n", i, val);
+       }
+
+       return pinctrl_enable(pmux->pctl);
+}
+
+static const struct of_device_id lx2160a_pmux_of_match[] = {
+       { .compatible = "fsl,lx2160a-rcw-pinmux" },
+       { }
+};
+MODULE_DEVICE_TABLE(of, lx2160a_pmux_of_match);
+
+static struct platform_driver lx2160a_pmux_driver = {
+       .driver = {
+               .name = "lx2160a-rcw-pinmux",
+               .of_match_table = lx2160a_pmux_of_match,
+       },
+       .probe = lx2160a_pmux_probe,
+};
+module_platform_driver(lx2160a_pmux_driver);
+
+MODULE_AUTHOR("Vincent Jardin <[email protected]>");
+MODULE_DESCRIPTION("NXP LX2160A RCW pad-mux driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("FSL_GUTS");

-- 
2.43.0



Reply via email to