From: Jia Wang Date: Wed, 10 Jun 2026 05:29:56 +0000 (+0800) Subject: pinctrl: ultrarisc: Add UltraRISC DP1000 pinctrl driver X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=cb7037924836a352e767f69f1aa65b82f3e815f4;p=thirdparty%2Flinux.git pinctrl: ultrarisc: Add UltraRISC DP1000 pinctrl driver Add support for the pin controller on the UltraRISC DP1000 SoC. The controller provides mux selection for pins in ports A, B, C, D, and LPC. Ports A-D default to GPIO and support peripheral muxing. LPC pins can be switched to eSPI, but are not available as GPIOs. Basic pin configuration controls such as drive strength, pull-up, and pull-down are also supported. Signed-off-by: Jia Wang Reviewed-by: Bartosz Golaszewski Signed-off-by: Linus Walleij --- diff --git a/MAINTAINERS b/MAINTAINERS index fe5c274f69471..0617efbbd882f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -27318,6 +27318,7 @@ M: Jia Wang L: linux-gpio@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/pinctrl/ultrarisc,dp1000-pinctrl.yaml +F: drivers/pinctrl/ultrarisc/* ULTRATRONIK BOARD SUPPORT M: Goran Rađenović diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig index 3624cfe4f2a2e..c2cdd7b2c49b0 100644 --- a/drivers/pinctrl/Kconfig +++ b/drivers/pinctrl/Kconfig @@ -720,6 +720,7 @@ source "drivers/pinctrl/sunplus/Kconfig" source "drivers/pinctrl/sunxi/Kconfig" source "drivers/pinctrl/tegra/Kconfig" source "drivers/pinctrl/ti/Kconfig" +source "drivers/pinctrl/ultrarisc/Kconfig" source "drivers/pinctrl/uniphier/Kconfig" source "drivers/pinctrl/visconti/Kconfig" source "drivers/pinctrl/vt8500/Kconfig" diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile index 5536e05e5edc0..a35d71135abfb 100644 --- a/drivers/pinctrl/Makefile +++ b/drivers/pinctrl/Makefile @@ -97,6 +97,7 @@ obj-y += sunplus/ obj-$(CONFIG_PINCTRL_SUNXI) += sunxi/ obj-y += tegra/ obj-y += ti/ +obj-$(CONFIG_PINCTRL_ULTRARISC) += ultrarisc/ obj-$(CONFIG_PINCTRL_UNIPHIER) += uniphier/ obj-$(CONFIG_PINCTRL_VISCONTI) += visconti/ obj-$(CONFIG_PINCTRL_WMT) += vt8500/ diff --git a/drivers/pinctrl/ultrarisc/Kconfig b/drivers/pinctrl/ultrarisc/Kconfig new file mode 100644 index 0000000000000..80ac997f6c513 --- /dev/null +++ b/drivers/pinctrl/ultrarisc/Kconfig @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config PINCTRL_ULTRARISC + tristate + depends on OF + depends on ARCH_ULTRARISC || COMPILE_TEST + select GENERIC_PINCTRL + select PINMUX + +config PINCTRL_ULTRARISC_DP1000 + tristate "UltraRISC DP1000 SoC Pinctrl driver" + select PINCTRL_ULTRARISC + depends on OF && HAS_IOMEM + depends on ARCH_ULTRARISC || COMPILE_TEST + 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 0000000000000..5d49ce1c0af9e --- /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 0000000000000..f9c85c8c44330 --- /dev/null +++ b/drivers/pinctrl/ultrarisc/pinctrl-dp1000.c @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2026 UltraRISC Technology (Shanghai) Co., Ltd. + * + * Author: Jia Wang + */ + +#include +#include +#include + +#include "pinctrl-ultrarisc.h" + +/* Port indices. */ +#define UR_DP1000_PORT_A_IDX 0 +#define UR_DP1000_PORT_B_IDX 1 +#define UR_DP1000_PORT_C_IDX 2 +#define UR_DP1000_PORT_D_IDX 3 +#define UR_DP1000_PORT_LPC_IDX 4 + +/* Port mux register offsets. */ +#define UR_DP1000_PORTA_FUNC_OFFSET 0x2c0 +#define UR_DP1000_PORTB_FUNC_OFFSET 0x2c4 +#define UR_DP1000_PORTC_FUNC_OFFSET 0x2c8 +#define UR_DP1000_PORTD_FUNC_OFFSET 0x2cc +#define UR_DP1000_PORTLPC_FUNC_OFFSET 0x2d0 + +/* Port pinconf register offsets. */ +#define UR_DP1000_PORTA_CONF_OFFSET 0x310 +#define UR_DP1000_PORTB_CONF_OFFSET 0x318 +#define UR_DP1000_PORTC_CONF_OFFSET 0x31c +#define UR_DP1000_PORTD_CONF_OFFSET 0x320 +#define UR_DP1000_PORTLPC_CONF_OFFSET 0x324 + +/* Pin ranges for function descriptors. */ +#define UR_DP1000_PINS_ABCD GENMASK_ULL(39, 0) +#define UR_DP1000_PINS_LPC GENMASK_ULL(52, 40) + +/* Static table entry helpers. */ +#define UR_DP1000_PORT(_base, _npins, _func, _conf, _modes, _gpio) \ + { .pin_base = (_base), .npins = (_npins), .func_offset = (_func), \ + .conf_offset = (_conf), .supported_modes = (_modes), \ + .supports_gpio = (_gpio) } + +#define UR_DP1000_PIN(_nr, _name, _port) \ + { .number = (_nr), .name = (_name), .drv_data = (void *)&ur_dp1000_ports[_port] } + +static const struct ur_func_route ur_dp1000_routes[] = { + { "gpio", UR_FUNC_DEFAULT, UR_DP1000_PINS_ABCD }, + { "i2c", UR_FUNC_0, GENMASK_ULL(13, 12) }, + { "i2c", UR_FUNC_0, GENMASK_ULL(23, 22) }, + { "i2c", UR_FUNC_0, GENMASK_ULL(25, 24) }, + { "i2c", UR_FUNC_0, GENMASK_ULL(27, 26) }, + { "pwm", UR_FUNC_0, GENMASK_ULL(19, 16) }, + { "spi", UR_FUNC_1, GENMASK_ULL(39, 32) }, + { "spi", UR_FUNC_0, GENMASK_ULL(6, 0) }, + { "uart", UR_FUNC_1, GENMASK_ULL(9, 8) }, + { "uart", UR_FUNC_0, GENMASK_ULL(21, 20) }, + { "uart", UR_FUNC_0, GENMASK_ULL(29, 28) }, + { "uart", UR_FUNC_0, GENMASK_ULL(31, 30) }, + { "lpc", UR_FUNC_DEFAULT, UR_DP1000_PINS_LPC }, + { "espi", UR_FUNC_0, UR_DP1000_PINS_LPC }, +}; + +static const struct ur_port_desc ur_dp1000_ports[] = { + UR_DP1000_PORT(0, 16, UR_DP1000_PORTA_FUNC_OFFSET, + UR_DP1000_PORTA_CONF_OFFSET, + UR_FUNC_0 | UR_FUNC_1, true), + UR_DP1000_PORT(16, 8, UR_DP1000_PORTB_FUNC_OFFSET, + UR_DP1000_PORTB_CONF_OFFSET, + UR_FUNC_0 | UR_FUNC_1, true), + UR_DP1000_PORT(24, 8, UR_DP1000_PORTC_FUNC_OFFSET, + UR_DP1000_PORTC_CONF_OFFSET, + UR_FUNC_0 | UR_FUNC_1, true), + UR_DP1000_PORT(32, 8, UR_DP1000_PORTD_FUNC_OFFSET, + UR_DP1000_PORTD_CONF_OFFSET, + UR_FUNC_0 | UR_FUNC_1, true), + UR_DP1000_PORT(40, 13, UR_DP1000_PORTLPC_FUNC_OFFSET, + UR_DP1000_PORTLPC_CONF_OFFSET, + UR_FUNC_0, false), +}; + +static const struct pinctrl_pin_desc ur_dp1000_pins[] = { + UR_DP1000_PIN(0, "PA0", UR_DP1000_PORT_A_IDX), + UR_DP1000_PIN(1, "PA1", UR_DP1000_PORT_A_IDX), + UR_DP1000_PIN(2, "PA2", UR_DP1000_PORT_A_IDX), + UR_DP1000_PIN(3, "PA3", UR_DP1000_PORT_A_IDX), + UR_DP1000_PIN(4, "PA4", UR_DP1000_PORT_A_IDX), + UR_DP1000_PIN(5, "PA5", UR_DP1000_PORT_A_IDX), + UR_DP1000_PIN(6, "PA6", UR_DP1000_PORT_A_IDX), + UR_DP1000_PIN(7, "PA7", UR_DP1000_PORT_A_IDX), + UR_DP1000_PIN(8, "PA8", UR_DP1000_PORT_A_IDX), + UR_DP1000_PIN(9, "PA9", UR_DP1000_PORT_A_IDX), + UR_DP1000_PIN(10, "PA10", UR_DP1000_PORT_A_IDX), + UR_DP1000_PIN(11, "PA11", UR_DP1000_PORT_A_IDX), + UR_DP1000_PIN(12, "PA12", UR_DP1000_PORT_A_IDX), + UR_DP1000_PIN(13, "PA13", UR_DP1000_PORT_A_IDX), + UR_DP1000_PIN(14, "PA14", UR_DP1000_PORT_A_IDX), + UR_DP1000_PIN(15, "PA15", UR_DP1000_PORT_A_IDX), + UR_DP1000_PIN(16, "PB0", UR_DP1000_PORT_B_IDX), + UR_DP1000_PIN(17, "PB1", UR_DP1000_PORT_B_IDX), + UR_DP1000_PIN(18, "PB2", UR_DP1000_PORT_B_IDX), + UR_DP1000_PIN(19, "PB3", UR_DP1000_PORT_B_IDX), + UR_DP1000_PIN(20, "PB4", UR_DP1000_PORT_B_IDX), + UR_DP1000_PIN(21, "PB5", UR_DP1000_PORT_B_IDX), + UR_DP1000_PIN(22, "PB6", UR_DP1000_PORT_B_IDX), + UR_DP1000_PIN(23, "PB7", UR_DP1000_PORT_B_IDX), + UR_DP1000_PIN(24, "PC0", UR_DP1000_PORT_C_IDX), + UR_DP1000_PIN(25, "PC1", UR_DP1000_PORT_C_IDX), + UR_DP1000_PIN(26, "PC2", UR_DP1000_PORT_C_IDX), + UR_DP1000_PIN(27, "PC3", UR_DP1000_PORT_C_IDX), + UR_DP1000_PIN(28, "PC4", UR_DP1000_PORT_C_IDX), + UR_DP1000_PIN(29, "PC5", UR_DP1000_PORT_C_IDX), + UR_DP1000_PIN(30, "PC6", UR_DP1000_PORT_C_IDX), + UR_DP1000_PIN(31, "PC7", UR_DP1000_PORT_C_IDX), + UR_DP1000_PIN(32, "PD0", UR_DP1000_PORT_D_IDX), + UR_DP1000_PIN(33, "PD1", UR_DP1000_PORT_D_IDX), + UR_DP1000_PIN(34, "PD2", UR_DP1000_PORT_D_IDX), + UR_DP1000_PIN(35, "PD3", UR_DP1000_PORT_D_IDX), + UR_DP1000_PIN(36, "PD4", UR_DP1000_PORT_D_IDX), + UR_DP1000_PIN(37, "PD5", UR_DP1000_PORT_D_IDX), + UR_DP1000_PIN(38, "PD6", UR_DP1000_PORT_D_IDX), + UR_DP1000_PIN(39, "PD7", UR_DP1000_PORT_D_IDX), + UR_DP1000_PIN(40, "LPC0", UR_DP1000_PORT_LPC_IDX), + UR_DP1000_PIN(41, "LPC1", UR_DP1000_PORT_LPC_IDX), + UR_DP1000_PIN(42, "LPC2", UR_DP1000_PORT_LPC_IDX), + UR_DP1000_PIN(43, "LPC3", UR_DP1000_PORT_LPC_IDX), + UR_DP1000_PIN(44, "LPC4", UR_DP1000_PORT_LPC_IDX), + UR_DP1000_PIN(45, "LPC5", UR_DP1000_PORT_LPC_IDX), + UR_DP1000_PIN(46, "LPC6", UR_DP1000_PORT_LPC_IDX), + UR_DP1000_PIN(47, "LPC7", UR_DP1000_PORT_LPC_IDX), + UR_DP1000_PIN(48, "LPC8", UR_DP1000_PORT_LPC_IDX), + UR_DP1000_PIN(49, "LPC9", UR_DP1000_PORT_LPC_IDX), + UR_DP1000_PIN(50, "LPC10", UR_DP1000_PORT_LPC_IDX), + UR_DP1000_PIN(51, "LPC11", UR_DP1000_PORT_LPC_IDX), + UR_DP1000_PIN(52, "LPC12", UR_DP1000_PORT_LPC_IDX), +}; + +static const struct ur_pinctrl_data ur_dp1000_pinctrl_data = { + .pins = ur_dp1000_pins, + .npins = ARRAY_SIZE(ur_dp1000_pins), + .routes = ur_dp1000_routes, + .num_routes = ARRAY_SIZE(ur_dp1000_routes), +}; + +static const struct of_device_id ur_pinctrl_of_match[] = { + { .compatible = "ultrarisc,dp1000-pinctrl" }, + { } +}; +MODULE_DEVICE_TABLE(of, ur_pinctrl_of_match); + +static int ur_dp1000_pinctrl_probe(struct platform_device *pdev) +{ + return ur_pinctrl_probe(pdev, &ur_dp1000_pinctrl_data); +} + +static struct platform_driver ur_pinctrl_driver = { + .driver = { + .name = "ultrarisc-pinctrl-dp1000", + .of_match_table = ur_pinctrl_of_match, + }, + .probe = ur_dp1000_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 0000000000000..8fb5b0ea5b936 --- /dev/null +++ b/drivers/pinctrl/ultrarisc/pinctrl-ultrarisc.c @@ -0,0 +1,517 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2026 UltraRISC Technology (Shanghai) Co., Ltd. + * + * Author: Jia Wang + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../core.h" +#include "../devicetree.h" +#include "../pinconf.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 u32 ur_drive_strengths[] = { 20, 27, 33, 40 }; + +static const struct ur_port_desc *ur_get_pin_port(struct pinctrl_dev *pctldev, + unsigned int pin) +{ + const struct pin_desc *desc = pin_desc_get(pctldev, pin); + + if (!desc || !desc->drv_data) + return NULL; + + return desc->drv_data; +} + +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 int ur_read_pin_conf(struct ur_pinctrl *pctrl, unsigned int pin, u32 *conf) +{ + const struct ur_port_desc *port_desc; + u32 pin_offset; + u32 reg_offset; + u32 shift; + u32 mask; + + port_desc = ur_get_pin_port(pctrl->pctl_dev, pin); + if (!port_desc) + return -EINVAL; + + pin_offset = pin - port_desc->pin_base; + reg_offset = ur_get_pin_conf_offset(port_desc, pin_offset); + shift = (pin_offset % 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(pctrl->base + reg_offset)); + + return 0; +} + +static int ur_write_pin_conf(struct ur_pinctrl *pctrl, unsigned int pin, u32 conf) +{ + const struct ur_port_desc *port_desc; + void __iomem *reg; + u32 pin_offset; + u32 reg_offset; + u32 shift; + u32 mask; + u32 val; + + port_desc = ur_get_pin_port(pctrl->pctl_dev, pin); + if (!port_desc) + return -EINVAL; + + pin_offset = pin - port_desc->pin_base; + reg_offset = ur_get_pin_conf_offset(port_desc, pin_offset); + reg = pctrl->base + reg_offset; + shift = (pin_offset % UR_CONF_PIN_PER_REG) * UR_CONF_BIT_PER_PIN; + mask = GENMASK(UR_CONF_BIT_PER_PIN - 1, 0) << shift; + + scoped_guard(raw_spinlock_irqsave, &pctrl->lock) { + val = readl_relaxed(reg); + val = (val & ~mask) | field_prep(mask, conf); + writel_relaxed(val, reg); + } + + return 0; +} + +static int ur_set_pin_mux(struct ur_pinctrl *pctrl, + const struct ur_port_desc *port_desc, + u32 pin_offset, u32 mode) +{ + void __iomem *reg = pctrl->base + port_desc->func_offset; + u32 val; + + if (WARN_ON(pin_offset >= UR_MAX_PINS_PER_PORT)) + return -EINVAL; + + scoped_guard(raw_spinlock_irqsave, &pctrl->lock) { + val = readl_relaxed(reg); + val &= ~((UR_FUNC_0 | UR_FUNC_1) << pin_offset); + val |= mode << pin_offset; + writel_relaxed(val, reg); + } + + return 0; +} + +static int ur_set_pin_mux_by_num(struct ur_pinctrl *pctrl, unsigned int pin, u32 mode) +{ + const struct ur_port_desc *port_desc = ur_get_pin_port(pctrl->pctl_dev, pin); + u32 pin_offset; + + if (!port_desc) + return -EINVAL; + + if (mode != UR_FUNC_DEFAULT && !(port_desc->supported_modes & mode)) + return -EINVAL; + + pin_offset = pin - port_desc->pin_base; + + return ur_set_pin_mux(pctrl, port_desc, pin_offset, mode); +} + +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 -EINVAL; + } +} + +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_PERSIST_STATE: + /* + * For PIN_CONFIG_PERSIST_STATE, gpiolib only treats + * -ENOTSUPP as an optional unsupported result. + * Do not use -EOPNOTSUPP here. + */ + return -ENOTSUPP; + default: + return -EOPNOTSUPP; + } +} + +/** + * ur_find_group_route() - Find the route matching a function and pin set + * @pctrl: pin controller instance + * @function: mux function name + * @group_mask: bitmap of pins in the selected group + * @route_out: returned route entry on success + * + * A mux function may be associated with multiple hardware routing options, + * each valid for a specific set of pins. This helper finds the unique route + * whose valid_pins mask covers all pins in @group_mask. + * + * The routing table must guarantee that a given function plus pin set + * resolves to exactly one route. If multiple routes match, the routing + * description is considered ambiguous and the lookup fails. + * + * Return: 0 on success, or -EINVAL if no matching route exists or if the + * routing table contains ambiguous entries. + */ +static int ur_find_group_route(struct ur_pinctrl *pctrl, + const char *function, + u64 group_mask, + const struct ur_func_route **route_out) +{ + const struct ur_func_route *match = NULL; + + for (u32 i = 0; i < pctrl->data->num_routes; i++) { + const struct ur_func_route *route = &pctrl->data->routes[i]; + + if (strcmp(route->function, function)) + continue; + + if ((route->valid_pins & group_mask) != group_mask) + continue; + + if (match) { + dev_err(pctrl->dev, + "ambiguous route for function %s group_mask=%#llx\n", + function, (unsigned long long)group_mask); + return -EINVAL; + } + + match = route; + } + + if (match) { + *route_out = match; + return 0; + } + + return -EINVAL; +} + +static const char *ur_get_group_function(struct pinctrl_dev *pctldev, + unsigned int group_selector, + unsigned int pin_index) +{ + const struct group_desc *group; + const char * const *functions; + + group = pinctrl_generic_get_group(pctldev, group_selector); + if (!group || pin_index >= group->grp.npins || !group->data) + return NULL; + + functions = group->data; + + return functions[pin_index]; +} + +static int ur_resolve_group_mux(struct pinctrl_dev *pctldev, + struct ur_pinctrl *pctrl, + unsigned int group_selector, + const unsigned int *pins, + unsigned int npins, + const struct ur_func_route **route_out) +{ + const char *function; + u64 group_mask = 0; + + if (!npins) + return -EINVAL; + + function = ur_get_group_function(pctldev, group_selector, 0); + if (!function) + return -EINVAL; + + for (u32 i = 0; i < npins; i++) + group_mask |= BIT_ULL(pins[i]); + + return ur_find_group_route(pctrl, function, group_mask, route_out); +} + +static bool ur_function_is_gpio(struct pinctrl_dev *pctldev, + unsigned int selector) +{ + const struct function_desc *function; + + function = pinmux_generic_get_function(pctldev, selector); + if (!function) + return false; + + for (u32 i = 0; i < function->func->ngroups; i++) { + const char *func_name; + int group_selector; + + group_selector = pinctrl_get_group_selector(pctldev, + function->func->groups[i]); + if (group_selector < 0) + return false; + + func_name = ur_get_group_function(pctldev, group_selector, 0); + if (!func_name || strcmp(func_name, "gpio")) + return false; + } + + return true; +} + +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 = pinctrl_generic_pins_function_dt_node_to_map, + .dt_free_map = pinconf_generic_dt_free_map, +}; + +static int ur_gpio_request_enable(struct pinctrl_dev *pctldev, + struct pinctrl_gpio_range *range, + unsigned int offset) +{ + struct ur_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev); + const struct ur_port_desc *port_desc; + const struct ur_func_route *route; + int ret; + + (void)range; + + port_desc = ur_get_pin_port(pctldev, offset); + if (!port_desc || !port_desc->supports_gpio) + return -EINVAL; + + ret = ur_find_group_route(pctrl, "gpio", BIT_ULL(offset), &route); + if (ret) + return ret; + + return ur_set_pin_mux_by_num(pctrl, offset, route->mode); +} + +static int ur_set_mux(struct pinctrl_dev *pctldev, unsigned int func_selector, + unsigned int group_selector) +{ + struct ur_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev); + const struct ur_func_route *route; + const unsigned int *pins; + unsigned int npins; + int ret; + + (void)func_selector; + + ret = pinctrl_generic_get_group_pins(pctldev, group_selector, &pins, &npins); + if (ret) + return ret; + + ret = ur_resolve_group_mux(pctldev, pctrl, group_selector, pins, npins, + &route); + if (ret) + return ret; + + for (u32 i = 0; i < npins; i++) { + ret = ur_set_pin_mux_by_num(pctrl, pins[i], route->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 = ur_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 *pctrl = pinctrl_dev_get_drvdata(pctldev); + u32 conf; + int ret; + + ret = ur_read_pin_conf(pctrl, pin, &conf); + if (ret) + return ret; + + return ur_hw_to_config(config, conf); +} + +static int ur_pin_config_set(struct pinctrl_dev *pctldev, + unsigned int pin, + unsigned long *configs, + unsigned int num_configs) +{ + struct ur_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev); + u32 conf; + int ret; + + ret = ur_read_pin_conf(pctrl, pin, &conf); + if (ret) + return 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(pctrl, 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, + .is_generic = true, + .pin_config_config_dbg_show = pinconf_generic_dump_config, +}; + +int ur_pinctrl_probe(struct platform_device *pdev, + const struct ur_pinctrl_data *data) +{ + struct pinctrl_desc *desc; + struct ur_pinctrl *pctrl; + int ret; + + if (!data) + return -ENODEV; + + desc = devm_kzalloc(&pdev->dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + + pctrl = devm_kzalloc(&pdev->dev, sizeof(*pctrl), GFP_KERNEL); + if (!pctrl) + return -ENOMEM; + + pctrl->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(pctrl->base)) + return PTR_ERR(pctrl->base); + pctrl->dev = &pdev->dev; + pctrl->data = data; + + raw_spin_lock_init(&pctrl->lock); + + desc->name = dev_name(&pdev->dev); + desc->owner = THIS_MODULE; + desc->pins = data->pins; + desc->npins = 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, pctrl, &pctrl->pctl_dev); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to register pinctrl\n"); + + platform_set_drvdata(pdev, pctrl); + + return pinctrl_enable(pctrl->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 0000000000000..c874688aafca9 --- /dev/null +++ b/drivers/pinctrl/ultrarisc/pinctrl-ultrarisc.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2026 UltraRISC Technology (Shanghai) Co., Ltd. + * + * Author: Jia Wang + */ + +#ifndef __PINCTRL_ULTRARISC_H__ +#define __PINCTRL_ULTRARISC_H__ + +#include +#include +#include + +struct platform_device; + +#define UR_FUNC_DEFAULT 0U +#define UR_FUNC_0 1U +#define UR_FUNC_1 0x10000U + +#define UR_MAX_PINS_PER_PORT 16 + +#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 { + u32 pin_base; + u32 npins; + u32 func_offset; + u32 conf_offset; + u32 supported_modes; + bool supports_gpio; +}; + +struct ur_func_route { + const char *function; + u32 mode; + u64 valid_pins; +}; + +struct ur_pinctrl_data { + const struct pinctrl_pin_desc *pins; + u32 npins; + const struct ur_func_route *routes; + u32 num_routes; +}; + +struct ur_pinctrl { + struct device *dev; + struct pinctrl_dev *pctl_dev; + const struct ur_pinctrl_data *data; + void __iomem *base; + raw_spinlock_t lock; /* Protects mux and conf registers */ +}; + +int ur_pinctrl_probe(struct platform_device *pdev, + const struct ur_pinctrl_data *data); + +#endif