On Wed, Nov 04, 2026 at 02:21:18AM -0500, Joseph Telaak via B4 Relay wrote: > From: Joseph Telaak > > Add a driver for the pin controller of the Siflower SF19A2890. Each pin > is a group of its own. The functions are named after the peripheral > they connect, and each lists the pins it can use with the mux mode that > selects it there. The function tables cover what the vendor firmware and > the OpenWrt port use; the rest of the modes are undocumented. > > Based on the OpenWrt driver by Chuanhong Guo, which exposed the mux > modes as functions "func0" to "func3". That driver was itself based on > the BCM2835 pin controller driver. > > Signed-off-by: Joseph Telaak > --- > drivers/pinctrl/Kconfig | 14 + > drivers/pinctrl/Makefile | 1 + > drivers/pinctrl/pinctrl-sf19a2890.c | 536 ++++++++++++++++++++++++++++++++++++ > 3 files changed, 551 insertions(+) > > diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig > index dc7138302..1321eb679 100644 > --- a/drivers/pinctrl/Kconfig > +++ b/drivers/pinctrl/Kconfig > @@ -562,6 +562,20 @@ config PINCTRL_SCMI > It uses SCMI Message Protocol to interact with the > firmware providing all the pinctrl controls. > > +config PINCTRL_SF19A2890 > + tristate "Siflower SF19A2890 pin controller" > + depends on MACH_SIFLOWER_MIPS || COMPILE_TEST > + depends on OF > + default MACH_SIFLOWER_MIPS > + select PINMUX > + select PINCONF > + select GENERIC_PINCONF > + help > + Support for the pin controller of the Siflower SF19A2890 SoC. It > + selects the function of each pin and sets its bias, input enable, > + Schmitt trigger and drive strength. The SoC's GPIO controller uses > + it to switch pins to GPIO. > + > config PINCTRL_SINGLE > tristate "One-register-per-pin type device tree based pinctrl driver" > depends on OF > diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile > index ce0825f4f..2cb2c229c 100644 > --- a/drivers/pinctrl/Makefile > +++ b/drivers/pinctrl/Makefile > @@ -57,6 +57,7 @@ obj-$(CONFIG_PINCTRL_RK805) += pinctrl-rk805.o > obj-$(CONFIG_PINCTRL_ROCKCHIP) += pinctrl-rockchip.o > obj-$(CONFIG_PINCTRL_RP1) += pinctrl-rp1.o > obj-$(CONFIG_PINCTRL_SCMI) += pinctrl-scmi.o > +obj-$(CONFIG_PINCTRL_SF19A2890) += pinctrl-sf19a2890.o > obj-$(CONFIG_PINCTRL_SINGLE) += pinctrl-single.o > obj-$(CONFIG_PINCTRL_ST) += pinctrl-st.o > obj-$(CONFIG_PINCTRL_STMFX) += pinctrl-stmfx.o > diff --git a/drivers/pinctrl/pinctrl-sf19a2890.c b/drivers/pinctrl/pinctrl-sf19a2890.c > new file mode 100644 > index 000000000..dbc8ac34b > --- /dev/null > +++ b/drivers/pinctrl/pinctrl-sf19a2890.c > @@ -0,0 +1,536 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * Siflower SF19A2890 pin controller > + * > + * Based on the OpenWrt driver by Chuanhong Guo , > + * which was based on the Broadcom BCM2835 GPIO unit driver: > + * Copyright (C) 2012 Chris Boot, Simon Arlott, Stephen Warren > + */ > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include > +#include > +#include > +#include > + > +#define SF19A2890_NUM_PINS 49 > + > +/* Pad configuration */ > +#define REG_PC(pin) ((pin) * 0x8) > +#define PC_OEN BIT(7) > +#define PC_ST BIT(6) > +#define PC_IE BIT(5) > +#define PC_PD BIT(4) > +#define PC_PU BIT(3) > +#define PC_DS GENMASK(2, 0) > + > +#define DRIVE_MIN_MA 6 > +#define DRIVE_STEP_MA 3 > + > +/* > + * Pin mux. With FUNC_SW clear, the pad's output enable is forced by > + * PC_OEN; with it set, the GPIO or the function drives it. FMUX_SEL picks > + * the GPIO, and MODE the function, when FMUX_SEL is clear. > + */ > +#define REG_PMX(pin) ((pin) * 0x8 + 0x4) > +#define PMX_FUNC_SW BIT(3) > +#define PMX_FMUX_SEL BIT(2) > +#define PMX_MODE GENMASK(1, 0) > + > +struct sf19a2890_pinctrl { > + struct device *dev; > + void __iomem *base; > + struct pinctrl_dev *pctl; > +}; > + > +static const struct pinctrl_pin_desc sf19a2890_pins[] = { > + PINCTRL_PIN(0, "JTAG_TDO"), > + PINCTRL_PIN(1, "JTAG_TDI"), > + PINCTRL_PIN(2, "JTAG_TMS"), > + PINCTRL_PIN(3, "JTAG_TCK"), > + PINCTRL_PIN(4, "JTAG_RST"), > + PINCTRL_PIN(5, "SPI_TXD"), > + PINCTRL_PIN(6, "SPI_RXD"), > + PINCTRL_PIN(7, "SPI_CLK"), > + PINCTRL_PIN(8, "SPI_CSN"), > + PINCTRL_PIN(9, "UART_TX"), > + PINCTRL_PIN(10, "UART_RX"), > + PINCTRL_PIN(11, "I2C_DAT"), > + PINCTRL_PIN(12, "I2C_CLK"), > + PINCTRL_PIN(13, "RGMII_GTX_CLK"), > + PINCTRL_PIN(14, "RGMII_TXCLK"), > + PINCTRL_PIN(15, "RGMII_TXD0"), > + PINCTRL_PIN(16, "RGMII_TXD1"), > + PINCTRL_PIN(17, "RGMII_TXD2"), > + PINCTRL_PIN(18, "RGMII_TXD3"), > + PINCTRL_PIN(19, "RGMII_TXCTL"), > + PINCTRL_PIN(20, "RGMII_RXCLK"), > + PINCTRL_PIN(21, "RGMII_RXD0"), > + PINCTRL_PIN(22, "RGMII_RXD1"), > + PINCTRL_PIN(23, "RGMII_RXD2"), > + PINCTRL_PIN(24, "RGMII_RXD3"), > + PINCTRL_PIN(25, "RGMII_RXCTL"), > + PINCTRL_PIN(26, "RGMII_COL"), > + PINCTRL_PIN(27, "RGMII_CRS"), > + PINCTRL_PIN(28, "RGMII_MDC"), > + PINCTRL_PIN(29, "RGMII_MDIO"), > + PINCTRL_PIN(30, "HB0_PA_EN"), > + PINCTRL_PIN(31, "HB0_LNA_EN"), > + PINCTRL_PIN(32, "HB0_SW_CTRL0"), > + PINCTRL_PIN(33, "HB0_SW_CTRL1"), > + PINCTRL_PIN(34, "HB1_PA_EN"), > + PINCTRL_PIN(35, "HB1_LNA_EN"), > + PINCTRL_PIN(36, "HB1_SW_CTRL0"), > + PINCTRL_PIN(37, "HB1_SW_CTRL1"), > + PINCTRL_PIN(38, "LB0_PA_EN"), > + PINCTRL_PIN(39, "LB0_LNA_EN"), > + PINCTRL_PIN(40, "LB0_SW_CTRL0"), > + PINCTRL_PIN(41, "LB0_SW_CTRL1"), > + PINCTRL_PIN(42, "LB1_PA_EN"), > + PINCTRL_PIN(43, "LB1_LNA_EN"), > + PINCTRL_PIN(44, "LB1_SW_CTRL0"), > + PINCTRL_PIN(45, "LB1_SW_CTRL1"), > + PINCTRL_PIN(46, "CLK_OUT"), > + PINCTRL_PIN(47, "EXT_CLK_IN"), > + PINCTRL_PIN(48, "DRVVBUS0"), > +}; > + > +static_assert(ARRAY_SIZE(sf19a2890_pins) == SF19A2890_NUM_PINS); > + > +/* A pin that a function can use, and the mux mode that selects it */ > +struct sf19a2890_func_pin { > + unsigned int pin; > + u8 mode; > +}; > + > +struct sf19a2890_function { > + const char *name; > + const char * const *groups; > + const struct sf19a2890_func_pin *pins; > + unsigned int npins; > +}; > + > +/* > + * Each pin is a group of its own, named after the pin. A function's group > + * list names the pins it can use, in the same order as its pin table. > + */ > +#define SF19A2890_FUNCTION(_name, ...) \ > + static const struct sf19a2890_func_pin sf19a2890_##_name##_pins[] = { \ > + __VA_ARGS__ \ > + } > + > +#define PIN_MODE(_pin, _mode) { .pin = (_pin), .mode = (_mode) } > + > +SF19A2890_FUNCTION(jtag, PIN_MODE(0, 0), PIN_MODE(1, 0), PIN_MODE(2, 0), > + PIN_MODE(3, 0), PIN_MODE(4, 0)); > +static const char * const sf19a2890_jtag_groups[] = { > + "JTAG_TDO", "JTAG_TDI", "JTAG_TMS", "JTAG_TCK", "JTAG_RST", > +}; > + > +SF19A2890_FUNCTION(uart1, PIN_MODE(0, 1), PIN_MODE(1, 1), PIN_MODE(2, 1), > + PIN_MODE(3, 1)); > +static const char * const sf19a2890_uart1_groups[] = { > + "JTAG_TDO", "JTAG_TDI", "JTAG_TMS", "JTAG_TCK", > +}; > + > +SF19A2890_FUNCTION(pwm0, PIN_MODE(3, 3)); > +static const char * const sf19a2890_pwm0_groups[] = { "JTAG_TCK" }; > + > +SF19A2890_FUNCTION(pwm1, PIN_MODE(4, 2)); > +static const char * const sf19a2890_pwm1_groups[] = { "JTAG_RST" }; > + > +SF19A2890_FUNCTION(spi, PIN_MODE(5, 0), PIN_MODE(6, 0), PIN_MODE(7, 0), > + PIN_MODE(8, 0)); > +static const char * const sf19a2890_spi_groups[] = { > + "SPI_TXD", "SPI_RXD", "SPI_CLK", "SPI_CSN", > +}; > + > +/* UART0's CTS and RTS are on the I2C pins */ > +SF19A2890_FUNCTION(uart0, PIN_MODE(9, 0), PIN_MODE(10, 0), PIN_MODE(11, 0), > + PIN_MODE(12, 0)); > +static const char * const sf19a2890_uart0_groups[] = { > + "UART_TX", "UART_RX", "I2C_DAT", "I2C_CLK", > +}; > + > +SF19A2890_FUNCTION(uart2, PIN_MODE(11, 1), PIN_MODE(12, 1)); > +static const char * const sf19a2890_uart2_groups[] = { "I2C_DAT", "I2C_CLK" }; > + > +SF19A2890_FUNCTION(i2c0, PIN_MODE(11, 2), PIN_MODE(12, 2)); > +static const char * const sf19a2890_i2c0_groups[] = { "I2C_DAT", "I2C_CLK" }; > + > +SF19A2890_FUNCTION(rgmii, PIN_MODE(14, 0), PIN_MODE(15, 0), PIN_MODE(16, 0), > + PIN_MODE(17, 0), PIN_MODE(18, 0), PIN_MODE(19, 0), > + PIN_MODE(20, 0), PIN_MODE(21, 0), PIN_MODE(22, 0), > + PIN_MODE(23, 0), PIN_MODE(24, 0), PIN_MODE(25, 0)); > +static const char * const sf19a2890_rgmii_groups[] = { > + "RGMII_TXCLK", "RGMII_TXD0", "RGMII_TXD1", "RGMII_TXD2", > + "RGMII_TXD3", "RGMII_TXCTL", "RGMII_RXCLK", "RGMII_RXD0", > + "RGMII_RXD1", "RGMII_RXD2", "RGMII_RXD3", "RGMII_RXCTL", > +}; > + > +SF19A2890_FUNCTION(mdio, PIN_MODE(28, 0), PIN_MODE(29, 0)); > +static const char * const sf19a2890_mdio_groups[] = { > + "RGMII_MDC", "RGMII_MDIO", > +}; > + > +/* The power amplifier, LNA and antenna switch controls of the radios */ > +SF19A2890_FUNCTION(wlan, PIN_MODE(30, 0), PIN_MODE(31, 0), PIN_MODE(32, 0), > + PIN_MODE(33, 0), PIN_MODE(34, 0), PIN_MODE(35, 0), > + PIN_MODE(36, 0), PIN_MODE(37, 0), PIN_MODE(38, 0), > + PIN_MODE(39, 0), PIN_MODE(40, 0), PIN_MODE(41, 0), > + PIN_MODE(42, 0), PIN_MODE(43, 0), PIN_MODE(44, 0), > + PIN_MODE(45, 0)); > +static const char * const sf19a2890_wlan_groups[] = { > + "HB0_PA_EN", "HB0_LNA_EN", "HB0_SW_CTRL0", "HB0_SW_CTRL1", > + "HB1_PA_EN", "HB1_LNA_EN", "HB1_SW_CTRL0", "HB1_SW_CTRL1", > + "LB0_PA_EN", "LB0_LNA_EN", "LB0_SW_CTRL0", "LB0_SW_CTRL1", > + "LB1_PA_EN", "LB1_LNA_EN", "LB1_SW_CTRL0", "LB1_SW_CTRL1", > +}; > + > +#define FUNCTION(_name) \ > + { \ > + .name = #_name, \ > + .groups = sf19a2890_##_name##_groups, \ > + .pins = sf19a2890_##_name##_pins, \ > + .npins = ARRAY_SIZE(sf19a2890_##_name##_pins), \ > + } > + > +static const struct sf19a2890_function sf19a2890_functions[] = { > + FUNCTION(i2c0), > + FUNCTION(jtag), > + FUNCTION(mdio), > + FUNCTION(pwm0), > + FUNCTION(pwm1), > + FUNCTION(rgmii), > + FUNCTION(spi), > + FUNCTION(uart0), > + FUNCTION(uart1), > + FUNCTION(uart2), > + FUNCTION(wlan), > +}; > + > +static u32 sf19a2890_pinctrl_read(struct sf19a2890_pinctrl *pc, u32 reg) > +{ > + return readl(pc->base + reg); > +} > + > +static void sf19a2890_pinctrl_write(struct sf19a2890_pinctrl *pc, u32 reg, > + u32 val) > +{ > + writel(val, pc->base + reg); > +} > + > +static void sf19a2890_pinctrl_rmw(struct sf19a2890_pinctrl *pc, u32 reg, > + u32 clr, u32 set) > +{ > + u32 val = sf19a2890_pinctrl_read(pc, reg); > + > + sf19a2890_pinctrl_write(pc, reg, (val & ~clr) | set); > +} > + > +static int sf19a2890_get_groups_count(struct pinctrl_dev *pctldev) > +{ > + return SF19A2890_NUM_PINS; > +} > + > +static const char *sf19a2890_get_group_name(struct pinctrl_dev *pctldev, > + unsigned int selector) > +{ > + return sf19a2890_pins[selector].name; > +} > + > +static int sf19a2890_get_group_pins(struct pinctrl_dev *pctldev, > + unsigned int selector, > + const unsigned int **pins, > + unsigned int *num_pins) > +{ > + *pins = &sf19a2890_pins[selector].number; > + *num_pins = 1; > + > + return 0; > +} > + > +static void sf19a2890_pin_dbg_show(struct pinctrl_dev *pctldev, > + struct seq_file *s, unsigned int offset) > +{ > + struct sf19a2890_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev); > + u32 conf = sf19a2890_pinctrl_read(pc, REG_PC(offset)); > + u32 mux = sf19a2890_pinctrl_read(pc, REG_PMX(offset)); > + > + if (!(mux & PMX_FUNC_SW)) > + seq_printf(s, "forced %s", conf & PC_OEN ? "input" : "output"); > + else if (mux & PMX_FMUX_SEL) > + seq_puts(s, "gpio"); > + else > + seq_printf(s, "mode %lu", FIELD_GET(PMX_MODE, mux)); > + > + seq_printf(s, "%s%s%s%s drive %lu mA", > + conf & PC_IE ? " input" : "", > + conf & PC_ST ? " schmitt" : "", > + conf & PC_PU ? " pull-up" : "", > + conf & PC_PD ? " pull-down" : "", > + DRIVE_MIN_MA + FIELD_GET(PC_DS, conf) * DRIVE_STEP_MA); > +} > + > +static const struct pinctrl_ops sf19a2890_pctl_ops = { > + .get_groups_count = sf19a2890_get_groups_count, > + .get_group_name = sf19a2890_get_group_name, > + .get_group_pins = sf19a2890_get_group_pins, > + .pin_dbg_show = sf19a2890_pin_dbg_show, > + .dt_node_to_map = pinconf_generic_dt_node_to_map_all, > + .dt_free_map = pinconf_generic_dt_free_map, > +}; Can you just use pinctrl_generic_pins_function_dt_node_to_map here and dynamically create the groups at runtime (and also make use of the generic get_groups_* implementations)?