@@ -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 <vjardin@free.fr>");
+MODULE_DESCRIPTION("NXP LX2160A RCW pad-mux driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("FSL_GUTS");