[PATCH 6/9] pinctrl: ultrarisc: Add UltraRISC DP1000 pinctrl driver
Jia Wang via B4 Relay
devnull+wangjia.ultrarisc.com at kernel.org
Thu May 14 18:18:02 PDT 2026
From: Jia Wang <wangjia at ultrarisc.com>
Add pinctrl driver for UltraRISC DP1000 pinctrl controller.
Signed-off-by: Jia Wang <wangjia at ultrarisc.com>
---
MAINTAINERS | 1 +
drivers/pinctrl/Kconfig | 1 +
drivers/pinctrl/Makefile | 1 +
drivers/pinctrl/ultrarisc/Kconfig | 23 +
drivers/pinctrl/ultrarisc/Makefile | 4 +
drivers/pinctrl/ultrarisc/pinctrl-dp1000.c | 112 ++++
drivers/pinctrl/ultrarisc/pinctrl-ultrarisc.c | 746 ++++++++++++++++++++++++++
drivers/pinctrl/ultrarisc/pinctrl-ultrarisc.h | 71 +++
8 files changed, 959 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 832e01898ae5..ecd87d58f28c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27364,6 +27364,7 @@ M: Jia Wang <wangjia at ultrarisc.com>
L: linux-gpio at vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/pinctrl/ultrarisc,dp1000-pinctrl.yaml
+F: drivers/pinctrl/ultrarisc/*
F: include/dt-bindings/pinctrl/ultrarisc,dp1000-pinctrl.h
ULTRATRONIK BOARD SUPPORT
diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index 03f2e3ee065f..76105be8b395 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -711,5 +711,6 @@ source "drivers/pinctrl/ti/Kconfig"
source "drivers/pinctrl/uniphier/Kconfig"
source "drivers/pinctrl/visconti/Kconfig"
source "drivers/pinctrl/vt8500/Kconfig"
+source "drivers/pinctrl/ultrarisc/Kconfig"
endif
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index f7d5d5f76d0c..4df3e52518ea 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -98,3 +98,4 @@ obj-y += ti/
obj-$(CONFIG_PINCTRL_UNIPHIER) += uniphier/
obj-$(CONFIG_PINCTRL_VISCONTI) += visconti/
obj-$(CONFIG_ARCH_VT8500) += vt8500/
+obj-$(CONFIG_ARCH_ULTRARISC) += ultrarisc/
diff --git a/drivers/pinctrl/ultrarisc/Kconfig b/drivers/pinctrl/ultrarisc/Kconfig
new file mode 100644
index 000000000000..ba8747b90127
--- /dev/null
+++ b/drivers/pinctrl/ultrarisc/Kconfig
@@ -0,0 +1,23 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config PINCTRL_ULTRARISC
+ tristate
+ depends on OF
+ select PINMUX
+ select GENERIC_PINCTRL_GROUPS
+ select GENERIC_PINCONF
+ select GENERIC_PINMUX_FUNCTIONS
+ select GPIOLIB
+ select IRQ_DOMAIN_HIERARCHY
+ select MFD_SYSCON
+
+config PINCTRL_ULTRARISC_DP1000
+ tristate "UltraRISC DP1000 SoC Pinctrl driver"
+ select PINCTRL_ULTRARISC
+ depends on OF && HAS_IOMEM
+ default ARCH_ULTRARISC
+ help
+ Say Y to select the pinctrl driver for UltraRISC DP1000 SoC.
+ This pin controller allows selecting the mux function for
+ each pin. This driver can also be built as a module called
+ pinctrl-dp1000.
diff --git a/drivers/pinctrl/ultrarisc/Makefile b/drivers/pinctrl/ultrarisc/Makefile
new file mode 100644
index 000000000000..5d49ce1c0af9
--- /dev/null
+++ b/drivers/pinctrl/ultrarisc/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+obj-$(CONFIG_PINCTRL_ULTRARISC) += pinctrl-ultrarisc.o
+obj-$(CONFIG_PINCTRL_ULTRARISC_DP1000) += pinctrl-dp1000.o
diff --git a/drivers/pinctrl/ultrarisc/pinctrl-dp1000.c b/drivers/pinctrl/ultrarisc/pinctrl-dp1000.c
new file mode 100644
index 000000000000..23b6cc512031
--- /dev/null
+++ b/drivers/pinctrl/ultrarisc/pinctrl-dp1000.c
@@ -0,0 +1,112 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 UltraRISC Technology (Shanghai) Co., Ltd.
+ *
+ * Author: Jia Wang <wangjia at ultrarisc.com>
+ */
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+#include "pinctrl-ultrarisc.h"
+
+static const struct pinctrl_pin_desc ur_dp1000_pins[] = {
+ PINCTRL_PIN(0, "PA0"),
+ PINCTRL_PIN(1, "PA1"),
+ PINCTRL_PIN(2, "PA2"),
+ PINCTRL_PIN(3, "PA3"),
+ PINCTRL_PIN(4, "PA4"),
+ PINCTRL_PIN(5, "PA5"),
+ PINCTRL_PIN(6, "PA6"),
+ PINCTRL_PIN(7, "PA7"),
+ PINCTRL_PIN(8, "PA8"),
+ PINCTRL_PIN(9, "PA9"),
+ PINCTRL_PIN(10, "PA10"),
+ PINCTRL_PIN(11, "PA11"),
+ PINCTRL_PIN(12, "PA12"),
+ PINCTRL_PIN(13, "PA13"),
+ PINCTRL_PIN(14, "PA14"),
+ PINCTRL_PIN(15, "PA15"),
+ PINCTRL_PIN(16, "PB0"),
+ PINCTRL_PIN(17, "PB1"),
+ PINCTRL_PIN(18, "PB2"),
+ PINCTRL_PIN(19, "PB3"),
+ PINCTRL_PIN(20, "PB4"),
+ PINCTRL_PIN(21, "PB5"),
+ PINCTRL_PIN(22, "PB6"),
+ PINCTRL_PIN(23, "PB7"),
+ PINCTRL_PIN(24, "PC0"),
+ PINCTRL_PIN(25, "PC1"),
+ PINCTRL_PIN(26, "PC2"),
+ PINCTRL_PIN(27, "PC3"),
+ PINCTRL_PIN(28, "PC4"),
+ PINCTRL_PIN(29, "PC5"),
+ PINCTRL_PIN(30, "PC6"),
+ PINCTRL_PIN(31, "PC7"),
+ PINCTRL_PIN(32, "PD0"),
+ PINCTRL_PIN(33, "PD1"),
+ PINCTRL_PIN(34, "PD2"),
+ PINCTRL_PIN(35, "PD3"),
+ PINCTRL_PIN(36, "PD4"),
+ PINCTRL_PIN(37, "PD5"),
+ PINCTRL_PIN(38, "PD6"),
+ PINCTRL_PIN(39, "PD7"),
+ PINCTRL_PIN(40, "LPC0"),
+ PINCTRL_PIN(41, "LPC1"),
+ PINCTRL_PIN(42, "LPC2"),
+ PINCTRL_PIN(43, "LPC3"),
+ PINCTRL_PIN(44, "LPC4"),
+ PINCTRL_PIN(45, "LPC5"),
+ PINCTRL_PIN(46, "LPC6"),
+ PINCTRL_PIN(47, "LPC7"),
+ PINCTRL_PIN(48, "LPC8"),
+ PINCTRL_PIN(49, "LPC9"),
+ PINCTRL_PIN(50, "LPC10"),
+ PINCTRL_PIN(51, "LPC11"),
+ PINCTRL_PIN(52, "LPC12"),
+};
+
+static const struct ur_function_desc ur_dp1000_functions[] = {
+ { "gpio", UR_FUNC_DEF, true },
+ { "func0", UR_FUNC0, false },
+ { "func1", UR_FUNC1, false },
+};
+
+#define UR_DP1000_PORT(_name, _npins, _func, _conf, _modes) \
+ { .name = (_name), .npins = (_npins), .func_offset = (_func), \
+ .conf_offset = (_conf), .supported_modes = (_modes) }
+
+static const struct ur_pinctrl_match_data ur_dp1000_match_data = {
+ .pins = ur_dp1000_pins,
+ .npins = ARRAY_SIZE(ur_dp1000_pins),
+ .functions = ur_dp1000_functions,
+ .num_functions = ARRAY_SIZE(ur_dp1000_functions),
+ .num_ports = 5,
+ .ports = {
+ UR_DP1000_PORT("A", 16, 0x2c0, 0x310, UR_FUNC0 | UR_FUNC1),
+ UR_DP1000_PORT("B", 8, 0x2c4, 0x318, UR_FUNC0 | UR_FUNC1),
+ UR_DP1000_PORT("C", 8, 0x2c8, 0x31c, UR_FUNC0 | UR_FUNC1),
+ UR_DP1000_PORT("D", 8, 0x2cc, 0x320, UR_FUNC0 | UR_FUNC1),
+ UR_DP1000_PORT("LPC", 13, 0x2d0, 0x324, UR_FUNC0),
+ },
+};
+
+static const struct of_device_id ur_pinctrl_of_match[] = {
+ { .compatible = "ultrarisc,dp1000-pinctrl", .data = &ur_dp1000_match_data, },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ur_pinctrl_of_match);
+
+static struct platform_driver ur_pinctrl_driver = {
+ .driver = {
+ .name = "ultrarisc-pinctrl-dp1000",
+ .of_match_table = ur_pinctrl_of_match,
+ },
+ .probe = ur_pinctrl_probe,
+};
+
+module_platform_driver(ur_pinctrl_driver);
+
+MODULE_DESCRIPTION("UltraRISC DP1000 pinctrl driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pinctrl/ultrarisc/pinctrl-ultrarisc.c b/drivers/pinctrl/ultrarisc/pinctrl-ultrarisc.c
new file mode 100644
index 000000000000..774746943e28
--- /dev/null
+++ b/drivers/pinctrl/ultrarisc/pinctrl-ultrarisc.c
@@ -0,0 +1,746 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 UltraRISC Technology (Shanghai) Co., Ltd.
+ *
+ * Author: Jia Wang <wangjia at ultrarisc.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pinctrl/pinconf.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/platform_device.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+
+#include "../core.h"
+#include "../devicetree.h"
+#include "../pinconf.h"
+#include "../pinctrl-utils.h"
+#include "../pinmux.h"
+
+#include "pinctrl-ultrarisc.h"
+
+#define UR_CONF_BIT_PER_PIN 4
+#define UR_CONF_PIN_PER_REG (32 / UR_CONF_BIT_PER_PIN)
+static const int ur_drive_strengths[] = { 20, 27, 33, 40 };
+
+static int ur_pin_num_to_port_pin(const struct ur_pinctrl_match_data *match_data,
+ struct ur_pin_val *pin_val, u32 pin_num)
+{
+ for (u32 i = 0; i < match_data->num_ports; i++) {
+ if (pin_num < match_data->ports[i].npins) {
+ pin_val->port = i;
+ pin_val->pin = pin_num;
+ return 0;
+ }
+ pin_num -= match_data->ports[i].npins;
+ }
+
+ return -EINVAL;
+}
+
+static int ur_pin_to_desc(struct pinctrl_dev *pctldev, struct ur_pin_val *pin_val)
+{
+ struct ur_pinctrl *ur_pinctrl = pinctrl_dev_get_drvdata(pctldev);
+ int index = 0;
+
+ for (u32 i = 0; i < pin_val->port; i++)
+ index += ur_pinctrl->match_data->ports[i].npins;
+
+ return index + pin_val->pin;
+}
+
+static u32 ur_get_pin_conf_offset(const struct ur_port_desc *port_desc, u32 pin)
+{
+ return port_desc->conf_offset +
+ (pin / UR_CONF_PIN_PER_REG) * sizeof(u32);
+}
+
+static u32 ur_read_pin_conf(struct ur_pinctrl *pctldata, unsigned int pin)
+{
+ const struct ur_port_desc *port_desc;
+ struct ur_pin_val pin_val;
+ u32 reg_offset;
+ u32 shift;
+ u32 conf;
+ u32 mask;
+
+ if (ur_pin_num_to_port_pin(pctldata->match_data, &pin_val, pin))
+ return 0;
+
+ port_desc = &pctldata->match_data->ports[pin_val.port];
+ reg_offset = ur_get_pin_conf_offset(port_desc, pin_val.pin);
+ shift = (pin_val.pin % UR_CONF_PIN_PER_REG) * UR_CONF_BIT_PER_PIN;
+ mask = GENMASK(UR_CONF_BIT_PER_PIN - 1, 0) << shift;
+ conf = field_get(mask, readl_relaxed(pctldata->base + reg_offset));
+
+ return conf;
+}
+
+static int ur_write_pin_conf(struct ur_pinctrl *pctldata, unsigned int pin, u32 conf)
+{
+ const struct ur_port_desc *port_desc;
+ struct ur_pin_val pin_val;
+ unsigned long flags;
+ void __iomem *reg;
+ u32 reg_offset;
+ u32 val;
+ u32 shift;
+ u32 mask;
+
+ if (ur_pin_num_to_port_pin(pctldata->match_data, &pin_val, pin))
+ return -EINVAL;
+
+ port_desc = &pctldata->match_data->ports[pin_val.port];
+ reg_offset = ur_get_pin_conf_offset(port_desc, pin_val.pin);
+ reg = pctldata->base + reg_offset;
+ shift = (pin_val.pin % UR_CONF_PIN_PER_REG) * UR_CONF_BIT_PER_PIN;
+ mask = GENMASK(UR_CONF_BIT_PER_PIN - 1, 0) << shift;
+
+ raw_spin_lock_irqsave(&pctldata->lock, flags);
+ val = readl_relaxed(reg);
+ val = (val & ~mask) | field_prep(mask, conf);
+ writel_relaxed(val, reg);
+ raw_spin_unlock_irqrestore(&pctldata->lock, flags);
+
+ return 0;
+}
+
+static int ur_set_pin_mux(struct ur_pinctrl *pctldata, struct ur_pin_val *pin_val)
+{
+ const struct ur_port_desc *port_desc = &pctldata->match_data->ports[pin_val->port];
+ void __iomem *reg = pctldata->base + port_desc->func_offset;
+ unsigned long flags;
+ u32 val;
+
+ raw_spin_lock_irqsave(&pctldata->lock, flags);
+ val = readl_relaxed(reg);
+ val &= ~((UR_FUNC0 | UR_FUNC1) << pin_val->pin);
+ val |= pin_val->mode << pin_val->pin;
+ writel_relaxed(val, reg);
+ raw_spin_unlock_irqrestore(&pctldata->lock, flags);
+
+ return 0;
+}
+
+static int ur_set_pin_mux_by_num(struct ur_pinctrl *pctldata, unsigned int pin, u32 mode)
+{
+ struct ur_pin_val pin_val = { .mode = mode };
+ const struct ur_port_desc *port_desc;
+ int ret;
+
+ ret = ur_pin_num_to_port_pin(pctldata->match_data, &pin_val, pin);
+ if (ret)
+ return ret;
+
+ port_desc = &pctldata->match_data->ports[pin_val.port];
+ if (mode != UR_FUNC_DEF && !(port_desc->supported_modes & mode))
+ return -EINVAL;
+
+ return ur_set_pin_mux(pctldata, &pin_val);
+}
+
+static int ur_hw_to_config(unsigned long *config, u32 conf)
+{
+ enum pin_config_param param = pinconf_to_config_param(*config);
+ u32 drive = FIELD_GET(UR_DRIVE_MASK, conf);
+ u32 pull = FIELD_GET(UR_PULL_MASK, conf);
+
+ switch (param) {
+ case PIN_CONFIG_BIAS_DISABLE:
+ case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
+ if (pull != UR_PULL_DIS)
+ return -EINVAL;
+ *config = pinconf_to_config_packed(param, 1);
+ return 0;
+ case PIN_CONFIG_BIAS_PULL_UP:
+ if (pull != UR_PULL_UP)
+ return -EINVAL;
+ *config = pinconf_to_config_packed(param, 1);
+ return 0;
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ case PIN_CONFIG_BIAS_PULL_PIN_DEFAULT:
+ if (pull != UR_PULL_DOWN)
+ return -EINVAL;
+ *config = pinconf_to_config_packed(param, 1);
+ return 0;
+ case PIN_CONFIG_DRIVE_STRENGTH:
+ if (drive >= ARRAY_SIZE(ur_drive_strengths))
+ return -EINVAL;
+ *config = pinconf_to_config_packed(param, ur_drive_strengths[drive]);
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int ur_config_to_hw(unsigned long config, u32 *conf)
+{
+ enum pin_config_param param = pinconf_to_config_param(config);
+ u32 arg = pinconf_to_config_argument(config);
+
+ switch (param) {
+ case PIN_CONFIG_BIAS_DISABLE:
+ case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
+ FIELD_MODIFY(UR_PULL_MASK, conf, UR_PULL_DIS);
+ return 0;
+ case PIN_CONFIG_BIAS_PULL_UP:
+ FIELD_MODIFY(UR_PULL_MASK, conf, UR_PULL_UP);
+ return 0;
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ case PIN_CONFIG_BIAS_PULL_PIN_DEFAULT:
+ FIELD_MODIFY(UR_PULL_MASK, conf, UR_PULL_DOWN);
+ return 0;
+ case PIN_CONFIG_DRIVE_STRENGTH:
+ for (u32 i = 0; i < ARRAY_SIZE(ur_drive_strengths); i++) {
+ if (ur_drive_strengths[i] != arg)
+ continue;
+ FIELD_MODIFY(UR_DRIVE_MASK, conf, i);
+ return 0;
+ }
+ return -EINVAL;
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ case PIN_CONFIG_INPUT_ENABLE:
+ case PIN_CONFIG_OUTPUT_ENABLE:
+ case PIN_CONFIG_PERSIST_STATE:
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+struct ur_legacy_prop_data {
+ struct ur_pin_val *pin_vals;
+ unsigned int *group_pins;
+ unsigned int num_pins;
+};
+
+static int ur_legacy_parse_prop(struct pinctrl_dev *pctldev,
+ struct device_node *np,
+ const char *propname,
+ struct ur_legacy_prop_data *prop)
+{
+ struct ur_pinctrl *pctldata = pinctrl_dev_get_drvdata(pctldev);
+ int rows;
+
+ rows = pinctrl_count_index_with_args(np, propname);
+ if (rows < 0)
+ return dev_err_probe(pctldev->dev, rows, "%pOF: invalid %s count\n",
+ np, propname);
+
+ prop->pin_vals = devm_kcalloc(pctldev->dev, rows, sizeof(*prop->pin_vals),
+ GFP_KERNEL);
+ if (!prop->pin_vals)
+ return -ENOMEM;
+
+ prop->group_pins = devm_kcalloc(pctldev->dev, rows, sizeof(*prop->group_pins),
+ GFP_KERNEL);
+ if (!prop->group_pins)
+ return -ENOMEM;
+
+ prop->num_pins = rows;
+
+ for (int i = 0; i < rows; i++) {
+ struct of_phandle_args pin_args;
+ int ret;
+
+ ret = pinctrl_parse_index_with_args(np, propname, i, &pin_args);
+ if (ret)
+ return dev_err_probe(pctldev->dev, ret,
+ "%pOF: failed to parse %s[%d]\n",
+ np, propname, i);
+
+ if (pin_args.args_count != 3)
+ return dev_err_probe(pctldev->dev, -EINVAL,
+ "%pOF: invalid %s[%d] args_count=%d\n",
+ np, propname, i, pin_args.args_count);
+
+ prop->pin_vals[i].port = pin_args.args[0];
+ prop->pin_vals[i].pin = pin_args.args[1];
+ prop->pin_vals[i].mode = pin_args.args[2];
+
+ if (prop->pin_vals[i].port >= pctldata->match_data->num_ports)
+ return dev_err_probe(pctldev->dev, -EINVAL,
+ "%pOF: invalid %s[%d] port=%u\n",
+ np, propname, i, prop->pin_vals[i].port);
+
+ if (prop->pin_vals[i].pin >=
+ pctldata->match_data->ports[prop->pin_vals[i].port].npins)
+ return dev_err_probe(pctldev->dev, -EINVAL,
+ "%pOF: invalid %s[%d] pin=%u\n",
+ np, propname, i, prop->pin_vals[i].pin);
+
+ prop->group_pins[i] = ur_pin_to_desc(pctldev, &prop->pin_vals[i]);
+ }
+
+ return 0;
+}
+
+static const char *ur_legacy_get_function_name(const struct ur_pinctrl_match_data *match_data,
+ u32 mode)
+{
+ for (u32 i = 0; i < match_data->num_functions; i++) {
+ if (match_data->functions[i].mode == mode)
+ return match_data->functions[i].name;
+ }
+
+ return NULL;
+}
+
+static int ur_legacy_conf_to_configs(struct pinctrl_dev *pctldev, u32 conf,
+ unsigned long **configs,
+ unsigned int *num_configs)
+{
+ u32 drive = FIELD_GET(UR_DRIVE_MASK, conf);
+ u32 pull = FIELD_GET(UR_PULL_MASK, conf);
+ unsigned long config;
+ int ret;
+
+ switch (pull) {
+ case UR_PULL_DIS:
+ config = pinconf_to_config_packed(PIN_CONFIG_BIAS_DISABLE, 1);
+ break;
+ case UR_PULL_UP:
+ config = pinconf_to_config_packed(PIN_CONFIG_BIAS_PULL_UP, 1);
+ break;
+ case UR_PULL_DOWN:
+ config = pinconf_to_config_packed(PIN_CONFIG_BIAS_PULL_DOWN, 1);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = pinctrl_utils_add_config(pctldev, configs, num_configs, config);
+ if (ret)
+ return ret;
+
+ if (drive >= ARRAY_SIZE(ur_drive_strengths))
+ return -EINVAL;
+
+ config = pinconf_to_config_packed(PIN_CONFIG_DRIVE_STRENGTH,
+ ur_drive_strengths[drive]);
+
+ return pinctrl_utils_add_config(pctldev, configs, num_configs, config);
+}
+
+static int ur_legacy_add_mux_maps(struct pinctrl_dev *pctldev,
+ struct pinctrl_map **map,
+ unsigned int *reserved_maps,
+ unsigned int *num_maps,
+ const struct ur_legacy_prop_data *prop)
+{
+ struct ur_pinctrl *pctldata = pinctrl_dev_get_drvdata(pctldev);
+
+ for (u32 i = 0; i < prop->num_pins; i++) {
+ const char *function;
+ const char *group;
+ int ret;
+
+ function = ur_legacy_get_function_name(pctldata->match_data,
+ prop->pin_vals[i].mode);
+ if (!function)
+ return -EINVAL;
+
+ group = pctldata->match_data->pins[prop->group_pins[i]].name;
+ if (!group)
+ return -EINVAL;
+
+ ret = pinctrl_utils_add_map_mux(pctldev, map, reserved_maps,
+ num_maps, group, function);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ur_legacy_add_pinconf_maps(struct pinctrl_dev *pctldev,
+ struct pinctrl_map **map,
+ unsigned int *reserved_maps,
+ unsigned int *num_maps,
+ const struct ur_legacy_prop_data *prop)
+{
+ struct ur_pinctrl *pctldata = pinctrl_dev_get_drvdata(pctldev);
+
+ for (u32 i = 0; i < prop->num_pins; i++) {
+ unsigned long *configs = NULL;
+ unsigned int num_configs = 0;
+ const char *group;
+ int ret;
+
+ ret = ur_legacy_conf_to_configs(pctldev, prop->pin_vals[i].conf,
+ &configs, &num_configs);
+ if (ret)
+ goto err;
+
+ group = pctldata->match_data->pins[prop->group_pins[i]].name;
+ if (!group) {
+ ret = -EINVAL;
+ goto err;
+ }
+
+ ret = pinctrl_utils_add_map_configs(pctldev, map, reserved_maps,
+ num_maps, group, configs,
+ num_configs,
+ PIN_MAP_TYPE_CONFIGS_PIN);
+err:
+ kfree(configs);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ur_legacy_dt_node_to_map(struct pinctrl_dev *pctldev,
+ struct device_node *np,
+ struct pinctrl_map **map,
+ unsigned int *num_maps)
+{
+ struct ur_legacy_prop_data conf_prop = {};
+ struct ur_legacy_prop_data mux_prop = {};
+ struct pinctrl_map *new_map = NULL;
+ unsigned int reserved_maps = 0;
+ unsigned int total_maps = 0;
+ bool conf_present = false;
+ bool mux_present = false;
+ unsigned int map_num = 0;
+ int ret;
+
+ if (of_property_present(np, "pinctrl-pins"))
+ mux_present = true;
+ if (of_property_present(np, "pinconf-pins"))
+ conf_present = true;
+ if (!mux_present && !conf_present)
+ return -EINVAL;
+
+ if (mux_present) {
+ ret = ur_legacy_parse_prop(pctldev, np, "pinctrl-pins", &mux_prop);
+ if (ret)
+ goto err;
+ total_maps += mux_prop.num_pins;
+ }
+
+ if (conf_present) {
+ ret = ur_legacy_parse_prop(pctldev, np, "pinconf-pins", &conf_prop);
+ if (ret)
+ goto err;
+ total_maps += conf_prop.num_pins;
+ }
+
+ ret = pinctrl_utils_reserve_map(pctldev, &new_map, &reserved_maps,
+ &map_num, total_maps);
+ if (ret)
+ goto err;
+
+ if (mux_present) {
+ ret = ur_legacy_add_mux_maps(pctldev, &new_map, &reserved_maps,
+ &map_num, &mux_prop);
+ if (ret)
+ goto err;
+ }
+
+ if (conf_present) {
+ ret = ur_legacy_add_pinconf_maps(pctldev, &new_map, &reserved_maps,
+ &map_num, &conf_prop);
+ if (ret)
+ goto err;
+ }
+
+ *map = new_map;
+ *num_maps = map_num;
+
+ return 0;
+
+err:
+ pinctrl_utils_free_map(pctldev, new_map, map_num);
+ return ret;
+}
+
+static int ur_generic_dt_node_to_map(struct pinctrl_dev *pctldev,
+ struct device_node *np_config,
+ struct pinctrl_map **map,
+ unsigned int *num_maps)
+{
+ return pinconf_generic_dt_node_to_map(pctldev, np_config, map, num_maps,
+ PIN_MAP_TYPE_INVALID);
+}
+
+static int ur_dt_node_to_map(struct pinctrl_dev *pctldev,
+ struct device_node *np,
+ struct pinctrl_map **map,
+ unsigned int *num_maps)
+{
+ bool legacy = of_property_present(np, "pinctrl-pins") ||
+ of_property_present(np, "pinconf-pins");
+ bool generic = of_property_present(np, "pins");
+
+ if (legacy && generic) {
+ dev_err(pctldev->dev,
+ "%pOF: mixed legacy and generic pinctrl properties are not supported\n",
+ np);
+ return -EINVAL;
+ }
+
+ if (generic)
+ return ur_generic_dt_node_to_map(pctldev, np, map, num_maps);
+
+ if (legacy)
+ return ur_legacy_dt_node_to_map(pctldev, np, map, num_maps);
+
+ return -EINVAL;
+}
+
+static void ur_dt_free_map(struct pinctrl_dev *pctldev,
+ struct pinctrl_map *map,
+ unsigned int num_maps)
+{
+ pinctrl_utils_free_map(pctldev, map, num_maps);
+}
+
+static void ur_pin_dbg_show(struct pinctrl_dev *pctldev,
+ struct seq_file *s, unsigned int offset)
+{
+ seq_printf(s, "%s", dev_name(pctldev->dev));
+}
+
+static const struct pinctrl_ops ur_pinctrl_ops = {
+ .get_groups_count = pinctrl_generic_get_group_count,
+ .get_group_name = pinctrl_generic_get_group_name,
+ .get_group_pins = pinctrl_generic_get_group_pins,
+ .dt_node_to_map = ur_dt_node_to_map,
+ .dt_free_map = ur_dt_free_map,
+ .pin_dbg_show = ur_pin_dbg_show,
+};
+
+static int ur_gpio_request_enable(struct pinctrl_dev *pctldev,
+ struct pinctrl_gpio_range *range,
+ unsigned int offset)
+{
+ struct ur_pinctrl *pctldata = pinctrl_dev_get_drvdata(pctldev);
+
+ (void)range;
+
+ return ur_set_pin_mux_by_num(pctldata, offset, UR_FUNC_DEF);
+}
+
+static int ur_set_mux(struct pinctrl_dev *pctldev, unsigned int func_selector,
+ unsigned int group_selector)
+{
+ struct ur_pinctrl *pctldata = pinctrl_dev_get_drvdata(pctldev);
+ const struct ur_function_desc *desc;
+ const struct function_desc *func;
+ const unsigned int *pins;
+ unsigned int npins;
+ int ret;
+
+ func = pinmux_generic_get_function(pctldev, func_selector);
+ if (!func || !func->data)
+ return -EINVAL;
+
+ desc = func->data;
+ ret = pinctrl_generic_get_group_pins(pctldev, group_selector, &pins, &npins);
+ if (ret)
+ return ret;
+
+ for (u32 i = 0; i < npins; i++) {
+ ret = ur_set_pin_mux_by_num(pctldata, pins[i], desc->mode);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct pinmux_ops ur_pinmux_ops = {
+ .get_functions_count = pinmux_generic_get_function_count,
+ .get_function_name = pinmux_generic_get_function_name,
+ .get_function_groups = pinmux_generic_get_function_groups,
+ .function_is_gpio = pinmux_generic_function_is_gpio,
+ .set_mux = ur_set_mux,
+ .gpio_request_enable = ur_gpio_request_enable,
+ .strict = true,
+};
+
+static int ur_pin_config_get(struct pinctrl_dev *pctldev,
+ unsigned int pin,
+ unsigned long *config)
+{
+ struct ur_pinctrl *pctldata = pinctrl_dev_get_drvdata(pctldev);
+
+ return ur_hw_to_config(config, ur_read_pin_conf(pctldata, pin));
+}
+
+static int ur_pin_config_set(struct pinctrl_dev *pctldev,
+ unsigned int pin,
+ unsigned long *configs,
+ unsigned int num_configs)
+{
+ struct ur_pinctrl *pctldata = pinctrl_dev_get_drvdata(pctldev);
+ u32 conf = ur_read_pin_conf(pctldata, pin);
+ int ret;
+
+ for (u32 i = 0; i < num_configs; i++) {
+ ret = ur_config_to_hw(configs[i], &conf);
+ if (ret)
+ return ret;
+ }
+
+ return ur_write_pin_conf(pctldata, pin, conf);
+}
+
+static int ur_pin_config_group_get(struct pinctrl_dev *pctldev,
+ unsigned int selector,
+ unsigned long *config)
+{
+ const unsigned int *pins;
+ unsigned int npins;
+ int ret;
+
+ ret = pinctrl_generic_get_group_pins(pctldev, selector, &pins, &npins);
+ if (ret || !npins)
+ return ret ?: -EINVAL;
+
+ return ur_pin_config_get(pctldev, pins[0], config);
+}
+
+static int ur_pin_config_group_set(struct pinctrl_dev *pctldev,
+ unsigned int selector,
+ unsigned long *configs,
+ unsigned int num_configs)
+{
+ const unsigned int *pins;
+ unsigned int npins;
+ int ret;
+
+ ret = pinctrl_generic_get_group_pins(pctldev, selector, &pins, &npins);
+ if (ret)
+ return ret;
+
+ for (u32 i = 0; i < npins; i++) {
+ ret = ur_pin_config_set(pctldev, pins[i], configs, num_configs);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct pinconf_ops ur_pinconf_ops = {
+ .pin_config_get = ur_pin_config_get,
+ .pin_config_set = ur_pin_config_set,
+ .pin_config_group_get = ur_pin_config_group_get,
+ .pin_config_group_set = ur_pin_config_group_set,
+#ifdef CONFIG_GENERIC_PINCONF
+ .is_generic = true,
+#endif
+};
+
+static int ur_add_pin_groups(struct ur_pinctrl *pctldata)
+{
+ for (u32 i = 0; i < pctldata->match_data->npins; i++) {
+ int ret;
+
+ pctldata->group_names[i] = pctldata->match_data->pins[i].name;
+ pctldata->group_pins[i] = pctldata->match_data->pins[i].number;
+
+ ret = pinctrl_generic_add_group(pctldata->pctl_dev, pctldata->group_names[i],
+ &pctldata->group_pins[i], 1, NULL);
+ if (ret < 0)
+ return dev_err_probe(pctldata->dev, ret,
+ "failed to add pin group %s\n",
+ pctldata->group_names[i]);
+ }
+
+ return 0;
+}
+
+static int ur_add_functions(struct ur_pinctrl *pctldata)
+{
+ for (u32 i = 0; i < pctldata->match_data->num_functions; i++) {
+ const struct ur_function_desc *desc = &pctldata->match_data->functions[i];
+ struct pinfunction func = desc->gpio ?
+ PINCTRL_GPIO_PINFUNCTION(desc->name, pctldata->group_names,
+ pctldata->match_data->npins) :
+ PINCTRL_PINFUNCTION(desc->name, pctldata->group_names,
+ pctldata->match_data->npins);
+ int ret;
+
+ ret = pinmux_generic_add_pinfunction(pctldata->pctl_dev, &func, (void *)desc);
+ if (ret < 0)
+ return dev_err_probe(pctldata->dev, ret,
+ "failed to add function %s\n",
+ desc->name);
+ }
+
+ return 0;
+}
+
+int ur_pinctrl_probe(struct platform_device *pdev)
+{
+ const struct ur_pinctrl_match_data *match_data;
+ struct ur_pinctrl *pctldata;
+ struct pinctrl_desc *desc;
+ int ret;
+
+ match_data = of_device_get_match_data(&pdev->dev);
+ if (!match_data)
+ return -ENODEV;
+
+ desc = devm_kzalloc(&pdev->dev, sizeof(*desc), GFP_KERNEL);
+ if (!desc)
+ return -ENOMEM;
+
+ pctldata = devm_kzalloc(&pdev->dev, sizeof(*pctldata), GFP_KERNEL);
+ if (!pctldata)
+ return -ENOMEM;
+
+ pctldata->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(pctldata->base))
+ return PTR_ERR(pctldata->base);
+ pctldata->dev = &pdev->dev;
+ pctldata->match_data = match_data;
+ pctldata->group_names = devm_kcalloc(&pdev->dev, match_data->npins,
+ sizeof(*pctldata->group_names), GFP_KERNEL);
+ if (!pctldata->group_names)
+ return -ENOMEM;
+
+ pctldata->group_pins = devm_kcalloc(&pdev->dev, match_data->npins,
+ sizeof(*pctldata->group_pins), GFP_KERNEL);
+ if (!pctldata->group_pins)
+ return -ENOMEM;
+
+ raw_spin_lock_init(&pctldata->lock);
+
+ desc->name = dev_name(&pdev->dev);
+ desc->owner = THIS_MODULE;
+ desc->pins = match_data->pins;
+ desc->npins = match_data->npins;
+ desc->pctlops = &ur_pinctrl_ops;
+ desc->pmxops = &ur_pinmux_ops;
+ desc->confops = &ur_pinconf_ops;
+
+ ret = devm_pinctrl_register_and_init(&pdev->dev, desc, pctldata, &pctldata->pctl_dev);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "failed to register pinctrl\n");
+
+ ret = ur_add_pin_groups(pctldata);
+ if (ret)
+ return ret;
+
+ ret = ur_add_functions(pctldata);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, pctldata);
+
+ return pinctrl_enable(pctldata->pctl_dev);
+}
+EXPORT_SYMBOL_GPL(ur_pinctrl_probe);
+
+MODULE_DESCRIPTION("UltraRISC pinctrl core driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pinctrl/ultrarisc/pinctrl-ultrarisc.h b/drivers/pinctrl/ultrarisc/pinctrl-ultrarisc.h
new file mode 100644
index 000000000000..25291f18c950
--- /dev/null
+++ b/drivers/pinctrl/ultrarisc/pinctrl-ultrarisc.h
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2026 UltraRISC Technology (Shanghai) Co., Ltd.
+ *
+ * Author: Jia Wang <wangjia at ultrarisc.com>
+ */
+
+#ifndef __PINCTRL_ULTRARISC_H__
+#define __PINCTRL_ULTRARISC_H__
+
+#include <linux/io.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/spinlock.h>
+
+struct platform_device;
+
+struct ur_pin_val {
+ u32 port;
+ u32 pin;
+ union {
+ u32 mode;
+ u32 conf;
+ };
+#define UR_FUNC_DEF 0
+#define UR_FUNC0 1
+#define UR_FUNC1 0x10000
+
+#define UR_BIAS_MASK 0x0000000F
+#define UR_PULL_MASK 0x0C
+#define UR_PULL_DIS 0
+#define UR_PULL_UP 1
+#define UR_PULL_DOWN 2
+#define UR_DRIVE_MASK 0x03
+};
+
+struct ur_port_desc {
+ const char *name;
+ u32 npins;
+ u32 func_offset;
+ u32 conf_offset;
+ u32 supported_modes;
+};
+
+struct ur_function_desc {
+ const char *name;
+ u32 mode;
+ bool gpio;
+};
+
+struct ur_pinctrl_match_data {
+ const struct pinctrl_pin_desc *pins;
+ u32 npins;
+ const struct ur_function_desc *functions;
+ u32 num_functions;
+ u32 num_ports;
+ struct ur_port_desc ports[];
+};
+
+struct ur_pinctrl {
+ struct device *dev;
+ struct pinctrl_dev *pctl_dev;
+ void __iomem *base;
+ const struct ur_pinctrl_match_data *match_data;
+ raw_spinlock_t lock;
+ const char **group_names;
+ unsigned int *group_pins;
+};
+
+int ur_pinctrl_probe(struct platform_device *pdev);
+
+#endif
--
2.34.1
More information about the linux-riscv
mailing list