Re: [PATCH 2/2] pinctrl: Add Siflower SF19A2890 pin controller driver

From: Conor Dooley

Date: Wed Nov 04 2026 - 19:23:55 EST


On Wed, Nov 04, 2026 at 02:21:18AM -0500, Joseph Telaak via B4 Relay wrote:
> From: Joseph Telaak <joseph@xxxxxxxxxx>
>
> 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 <joseph@xxxxxxxxxx>
> ---
> 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 <gch981213@xxxxxxxxx>,
> + * which was based on the Broadcom BCM2835 GPIO unit driver:
> + * Copyright (C) 2012 Chris Boot, Simon Arlott, Stephen Warren
> + */
> +
> +#include <linux/array_size.h>
> +#include <linux/bitfield.h>
> +#include <linux/bits.h>
> +#include <linux/io.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/seq_file.h>
> +
> +#include <linux/pinctrl/pinconf-generic.h>
> +#include <linux/pinctrl/pinconf.h>
> +#include <linux/pinctrl/pinctrl.h>
> +#include <linux/pinctrl/pinmux.h>
> +
> +#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)?

Attachment: signature.asc
Description: PGP signature