From: Conor Dooley <conor.dooley@microchip.com>
On Polarfire SoC, the Bank 2 and Bank 4 IOs connected to the
Multiprocessor Subsystem (MSS) are controlled by IOMUX_CRs 1 through 6,
which determine what function in routed to them, and
MSSIO_BANK#_IO_CFG_CRs, which determine the configuration of each pin.
Add a driver for this pin controller, including several custom
properties that reflect aspects of the MSS's configuration.
Reuse the Kconfig option for iomux0, since controlling MSSIOs without
iomux0 routing a function to the MSSIOs in question is pointless, and
routing a function to the MSSIOs is equally unhelpful if none of them
are configured to make use of that function.
Signed-off-by: Conor Dooley <conor.dooley@microchip.com>
---
drivers/pinctrl/Kconfig | 7 +-
drivers/pinctrl/Makefile | 1 +
drivers/pinctrl/pinctrl-mpfs-mssio.c | 750 +++++++++++++++++++++++++++
3 files changed, 756 insertions(+), 2 deletions(-)
create mode 100644 drivers/pinctrl/pinctrl-mpfs-mssio.c
diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index b43d4d7d25a8..0e32f91b69ec 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -512,12 +512,15 @@ config PINCTRL_PISTACHIO
This support pinctrl and GPIO driver for IMG Pistachio SoC.
config PINCTRL_POLARFIRE_SOC
- bool "Polarfire SoC pinctrl driver"
+ bool "Polarfire SoC pinctrl drivers"
depends on ARCH_MICROCHIP || COMPILE_TEST
select GENERIC_PINCONF
+ select GENERIC_PINCTRL_GROUPS
+ select GENERIC_PINMUX_FUNCTIONS
+ select GENERIC_PINCTRL_BELLS_AND_WHISTLES
default y
help
- This selects the pinctrl driver for Microchip Polarfire SoC.
+ This selects the pinctrl drivers for Microchip Polarfire SoC.
config PINCTRL_RK805
tristate "Pinctrl and GPIO driver for RK805 PMIC"
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index ea679ca84d85..7d7dc1f4873c 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -51,6 +51,7 @@ obj-$(CONFIG_PINCTRL_PEF2256) += pinctrl-pef2256.o
obj-$(CONFIG_PINCTRL_PIC32) += pinctrl-pic32.o
obj-$(CONFIG_PINCTRL_PIC64GX) += pinctrl-pic64gx-gpio2.o
obj-$(CONFIG_PINCTRL_PISTACHIO) += pinctrl-pistachio.o
+obj-$(CONFIG_PINCTRL_POLARFIRE_SOC) += pinctrl-mpfs-mssio.o
obj-$(CONFIG_PINCTRL_POLARFIRE_SOC) += pinctrl-mpfs-iomux0.o
obj-$(CONFIG_PINCTRL_RK805) += pinctrl-rk805.o
obj-$(CONFIG_PINCTRL_ROCKCHIP) += pinctrl-rockchip.o
diff --git a/drivers/pinctrl/pinctrl-mpfs-mssio.c b/drivers/pinctrl/pinctrl-mpfs-mssio.c
new file mode 100644
index 000000000000..eab74d337922
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-mpfs-mssio.c
@@ -0,0 +1,750 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/bitfield.h>
+#include <linux/module.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mod_devicetable.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.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>
+
+#include "core.h"
+#include "pinctrl-utils.h"
+#include "pinconf.h"
+#include "pinmux.h"
+
+#define MPFS_PINCTRL_PAD_MUX_MASK GENMASK(3, 0)
+
+#define MPFS_PINCTRL_IOCFG_MASK GENMASK(14, 0)
+#define MPFS_PINCTRL_IBUFMD_MASK GENMASK(2, 0)
+#define MPFS_PINCTRL_DRV_MASK GENMASK(6, 3)
+#define MPFS_PINCTRL_CLAMP BIT(7)
+#define MPFS_PINCTRL_ENHYST BIT(8)
+#define MPFS_PINCTRL_LOCKDN BIT(9)
+#define MPFS_PINCTRL_WPD BIT(10)
+#define MPFS_PINCTRL_WPU BIT(11)
+#define MPFS_PINCTRL_PULL_MASK GENMASK(11, 10)
+#define MPFS_PINCTRL_LP_PERSIST_EN BIT(12)
+#define MPFS_PINCTRL_LP_BYPASS_EN BIT(13)
+
+#define MPFS_PINCTRL_MSSIO_BANK2_CFG_CR 0x1c4
+#define MPFS_PINCTRL_MSSIO_BANK4_CFG_CR 0x1c8
+#define MPFS_PINCTRL_BANK_VOLTAGE_MASK GENMASK(19, 16)
+
+#define MPFS_PINCTRL_IOCFG01_REG 0x234
+
+#define MPFS_PINCTRL_INTER_BANK_GAP 0x4
+
+#define MPFS_PINCTRL_BANK2_START 14
+
+#define MPFS_PINCTRL_LOCKDOWN (PIN_CONFIG_END + 1)
+#define MPFS_PINCTRL_CLAMP_DIODE (PIN_CONFIG_END + 2)
+#define MPFS_PINCTRL_IBUFMD (PIN_CONFIG_END + 3)
+#define MPFS_PINCTRL_BANK_VOLTAGE (PIN_CONFIG_END + 4)
+
+struct mpfs_pinctrl_mux_config {
+ u8 pin;
+ u8 function;
+};
+
+struct mpfs_pinctrl {
+ struct pinctrl_dev *pctrl;
+ struct device *dev;
+ struct regmap *regmap;
+ struct regmap *sysreg_regmap;
+ struct mutex mutex;
+ struct pinctrl_desc desc;
+};
+
+struct mpfs_pinctrl_drive_strength {
+ u8 ma;
+ u8 val;
+};
+
+struct mpfs_pinctrl_bank_voltage {
+ u32 uv;
+ u8 val;
+};
+
+static struct mpfs_pinctrl_drive_strength mpfs_pinctrl_drive_strengths[8] = {
+ { .ma = 2, .val = 2 },
+ { .ma = 4, .val = 3 },
+ { .ma = 6, .val = 4 },
+ { .ma = 8, .val = 5 },
+ { .ma = 10, .val = 6 },
+ { .ma = 12, .val = 7 },
+ { .ma = 16, .val = 10 },
+ { .ma = 20, .val = 12 },
+};
+static struct mpfs_pinctrl_bank_voltage mpfs_pinctrl_bank_voltages[8] = {
+ { .uv = 1200000, .val = 0 },
+ { .uv = 1500000, .val = 2 },
+ { .uv = 1800000, .val = 4 },
+ { .uv = 2500000, .val = 6 },
+ { .uv = 3300000, .val = 8 },
+ { .uv = 0, .val = 0x3f }, // pin unused
+};
+
+static int mpfs_pinctrl_get_drive_strength_ma(u32 drive_strength)
+{
+ size_t num = ARRAY_SIZE(mpfs_pinctrl_drive_strengths);
+
+ for (int i = 0; i < num; i++)
+ if (drive_strength == mpfs_pinctrl_drive_strengths[i].val)
+ return mpfs_pinctrl_drive_strengths[i].ma;
+
+ return -EINVAL;
+}
+
+static int mpfs_pinctrl_get_drive_strength_val(u32 drive_strength_ma)
+{
+ size_t num = ARRAY_SIZE(mpfs_pinctrl_drive_strengths);
+
+ if (!drive_strength_ma)
+ return -EINVAL;
+
+ for (int i = 0; i < num; i++)
+ if (drive_strength_ma <= mpfs_pinctrl_drive_strengths[i].ma)
+ return mpfs_pinctrl_drive_strengths[i].val;
+
+ return mpfs_pinctrl_drive_strengths[num - 1].val;
+}
+
+static int mpfs_pinctrl_get_bank_voltage_uv(u32 bank_voltage)
+{
+ size_t num = ARRAY_SIZE(mpfs_pinctrl_bank_voltages);
+
+ for (int i = 0; i < num; i++)
+ if (bank_voltage == mpfs_pinctrl_bank_voltages[i].val)
+ return mpfs_pinctrl_bank_voltages[i].uv;
+
+ return -EINVAL;
+}
+
+static int mpfs_pinctrl_get_bank_voltage_val(u32 bank_voltage_uv)
+{
+ size_t num = ARRAY_SIZE(mpfs_pinctrl_bank_voltages);
+
+ for (int i = 0; i < num; i++)
+ if (bank_voltage_uv <= mpfs_pinctrl_bank_voltages[i].uv)
+ return mpfs_pinctrl_bank_voltages[i].val;
+
+ return -EINVAL;
+}
+
+static u32 mpfs_pinctrl_pin_to_bank_voltage(struct mpfs_pinctrl *pctrl, unsigned int pin)
+{
+ u32 bank_voltage, val;
+
+ if (pin < MPFS_PINCTRL_BANK2_START)
+ regmap_read(pctrl->sysreg_regmap, MPFS_PINCTRL_MSSIO_BANK4_CFG_CR, &val);
+ else
+ regmap_read(pctrl->sysreg_regmap, MPFS_PINCTRL_MSSIO_BANK2_CFG_CR, &val);
+
+ bank_voltage = FIELD_GET(MPFS_PINCTRL_BANK_VOLTAGE_MASK, val);
+
+ return mpfs_pinctrl_get_bank_voltage_uv(bank_voltage);
+}
+
+static void mpfs_pinctrl_set_bank_voltage(struct mpfs_pinctrl *pctrl, unsigned int pin,
+ u32 bank_voltage)
+{
+ u32 val = FIELD_PREP(MPFS_PINCTRL_BANK_VOLTAGE_MASK, bank_voltage);
+
+ if (pin < MPFS_PINCTRL_BANK2_START)
+ regmap_assign_bits(pctrl->sysreg_regmap, MPFS_PINCTRL_MSSIO_BANK4_CFG_CR,
+ MPFS_PINCTRL_BANK_VOLTAGE_MASK, val);
+ else
+ regmap_assign_bits(pctrl->sysreg_regmap, MPFS_PINCTRL_MSSIO_BANK2_CFG_CR,
+ MPFS_PINCTRL_BANK_VOLTAGE_MASK, val);
+}
+
+static char *mpfs_pinctrl_function_names[] = {
+ "sd",
+ "emmc",
+ "qspi",
+ "spi",
+ "usb",
+ "uart",
+ "i2c",
+ "can",
+ "mdio",
+ "misc",
+ "reserved",
+ "gpio",
+ "fabric test",
+ "tied-low",
+ "tied-high",
+ "tristate"
+};
+
+static int mpfs_pinctrl_function_map(const char *function)
+{
+ size_t num = ARRAY_SIZE(mpfs_pinctrl_function_names);
+
+ for (int i = 0; i < num; i++)
+ if (!strcmp(function, mpfs_pinctrl_function_names[i]))
+ return i;
+
+ return -EINVAL;
+}
+
+static const struct pinconf_generic_params mpfs_pinctrl_custom_bindings[] = {
+ { "microchip,clamp-diode", MPFS_PINCTRL_CLAMP_DIODE, 1 },
+ { "microchip,ibufmd", MPFS_PINCTRL_IBUFMD, 0x0 },
+ { "microchip,bank-voltage-microvolt", MPFS_PINCTRL_BANK_VOLTAGE, 0 },
+};
+
+static int mpfs_pinctrl_pin_to_iomux_offset(unsigned int pin)
+{
+ int offset;
+
+ switch (pin) {
+ case 0 ... 7:
+ offset = pin * 4;
+ break;
+ case 8 ... 13:
+ offset = (pin - 8) * 4;
+ break;
+ case 14 ... 21:
+ offset = (pin - 14) * 4;
+ break;
+ case 22 ... 29:
+ offset = (pin - 22) * 4;
+ break;
+ case 30 ... 37:
+ offset = (pin - 30) * 4;
+ break;
+ default:
+ offset = -EINVAL;
+ }
+
+ return offset;
+}
+
+static int mpfs_pinctrl_pin_to_iomux_reg(unsigned int pin)
+{
+ int reg;
+
+ switch (pin) {
+ case 0 ... 7:
+ reg = 0x204;
+ break;
+ case 8 ... 13:
+ reg = 0x208;
+ break;
+ case 14 ... 21:
+ reg = 0x20c;
+ break;
+ case 22 ... 29:
+ reg = 0x210;
+ break;
+ case 30 ... 37:
+ reg = 0x214;
+ break;
+ default:
+ reg = -EINVAL;
+ }
+
+ return reg;
+}
+
+static int mpfs_pinctrl_pin_to_iocfg_reg(unsigned int pin)
+{
+ u32 reg = MPFS_PINCTRL_IOCFG01_REG;
+
+ if (pin >= MPFS_PINCTRL_BANK2_START)
+ reg += MPFS_PINCTRL_INTER_BANK_GAP;
+
+ // 2 pins per 32-bit register
+ reg += (pin / 2) * 0x4;
+
+ return reg;
+}
+
+static int mpfs_pinctrl_pin_to_iocfg_offset(unsigned int pin)
+{
+ return 16 * (pin % 2);
+}
+
+static void mpfs_pinctrl_dbg_show(struct pinctrl_dev *pctrl_dev, struct seq_file *seq,
+ unsigned int pin)
+{
+ struct mpfs_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctrl_dev);
+ u32 func;
+ int reg, offset;
+
+ reg = mpfs_pinctrl_pin_to_iomux_reg(pin);
+ offset = mpfs_pinctrl_pin_to_iomux_offset(pin);
+
+ seq_printf(seq, "reg: %x, offset: %u ", reg, offset);
+ seq_printf(seq, "pin: %u ", pin);
+
+ if (reg < 0 || offset < 0)
+ return;
+
+ regmap_read(pctrl->regmap, reg, &func);
+ func = (func >> offset) & MPFS_PINCTRL_PAD_MUX_MASK;
+ seq_printf(seq, "func: %s (%x)\n", mpfs_pinctrl_function_names[func], func);
+}
+
+static const struct pinctrl_ops mpfs_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,
+ .pin_dbg_show = mpfs_pinctrl_dbg_show,
+ .dt_node_to_map = pinctrl_generic_pins_function_dt_node_to_map,
+ .dt_free_map = pinctrl_utils_free_map,
+};
+
+static int mpfs_pinctrl_set_pin_func(struct mpfs_pinctrl *pctrl, u8 pin, u8 function)
+{
+ struct device *dev = pctrl->dev;
+ int reg, offset;
+ u32 func, mask;
+
+ reg = mpfs_pinctrl_pin_to_iomux_reg(pin);
+ offset = mpfs_pinctrl_pin_to_iomux_offset(pin);
+
+ func = function << offset;
+ mask = MPFS_PINCTRL_PAD_MUX_MASK << offset;
+
+ dev_dbg(dev, "Setting pin %u. reg: %x offset %u func %x\n", pin, reg, offset, func);
+
+ if (reg < 0 || offset < 0)
+ return -EINVAL;
+
+ regmap_update_bits(pctrl->regmap, reg, mask, func);
+
+ return 0;
+}
+
+static int mpfs_pinctrl_set_mux(struct pinctrl_dev *pctrl_dev, unsigned int fsel,
+ unsigned int gsel)
+{
+ struct mpfs_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctrl_dev);
+ const struct group_desc *group;
+ const char **functions;
+
+ group = pinctrl_generic_get_group(pctrl_dev, gsel);
+ if (!group)
+ return -EINVAL;
+
+ functions = group->data;
+
+ for (int i = 0; i < group->grp.npins; i++) {
+ u32 function;
+
+ //TODO @Linus my new function being actually generic means that
+ // the mapping of function string to something the hardware
+ // understands only happens at this point.
+ // I think this is fine, because dt validation would whinge
+ // about something invalid, but it's the "catch" with my approach.
+ // The other option I considered was to provide a mapping
+ // function pointer that the driver can populate, but I think
+ // that's overkill.
+ function = mpfs_pinctrl_function_map(functions[i]);
+ if (function < 0) {
+ dev_err(pctrl->dev, "invalid function %s\n", functions[i]);
+ return function;
+ }
+
+ mpfs_pinctrl_set_pin_func(pctrl, group->grp.pins[i], function);
+ }
+
+ return 0;
+}
+
+static const struct pinmux_ops mpfs_pinctrl_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,
+ .set_mux = mpfs_pinctrl_set_mux,
+};
+
+static int mpfs_pinctrl_pinconf_get(struct pinctrl_dev *pctrl_dev, unsigned int pin,
+ unsigned long *config)
+{
+ struct mpfs_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctrl_dev);
+ int param = pinconf_to_config_param(*config);
+ int reg = mpfs_pinctrl_pin_to_iocfg_reg(pin);
+ int val;
+ u32 arg;
+ u8 str;
+
+ regmap_read(pctrl->regmap, reg, &val);
+
+ val = val >> mpfs_pinctrl_pin_to_iocfg_offset(pin);
+ val = val & MPFS_PINCTRL_IOCFG_MASK;
+
+ switch (param) {
+ case PIN_CONFIG_BIAS_BUS_HOLD:
+ if (!(val & MPFS_PINCTRL_WPD))
+ return -EINVAL;
+
+ if (!(val & MPFS_PINCTRL_WPU))
+ return -EINVAL;
+
+ arg = 1;
+ break;
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ if (!(val & MPFS_PINCTRL_WPD))
+ return -EINVAL;
+
+ if (val & MPFS_PINCTRL_WPU)
+ return -EINVAL;
+
+ arg = 1;
+ break;
+ case PIN_CONFIG_BIAS_PULL_UP:
+ if (!(val & MPFS_PINCTRL_WPU))
+ return -EINVAL;
+
+ if (val & MPFS_PINCTRL_WPD)
+ return -EINVAL;
+
+ arg = 1;
+ break;
+ case PIN_CONFIG_BIAS_DISABLE:
+ if (val & MPFS_PINCTRL_PULL_MASK)
+ return -EINVAL;
+
+ arg = 1;
+ break;
+ case PIN_CONFIG_DRIVE_STRENGTH:
+ str = FIELD_GET(MPFS_PINCTRL_DRV_MASK, val);
+ if (!str)
+ return -EINVAL;
+
+ arg = mpfs_pinctrl_get_drive_strength_ma(str);
+ break;
+ case PIN_CONFIG_INPUT_SCHMITT_ENABLE:
+ if (!FIELD_GET(MPFS_PINCTRL_ENHYST, val))
+ return -EINVAL;
+
+ arg = 1;
+ break;
+ case PIN_CONFIG_PERSIST_STATE:
+ if (!FIELD_GET(MPFS_PINCTRL_LP_PERSIST_EN, val))
+ return -EINVAL;
+
+ arg = 1;
+ break;
+ case PIN_CONFIG_MODE_LOW_POWER:
+ if (!FIELD_GET(MPFS_PINCTRL_LP_BYPASS_EN, val))
+ return -EINVAL;
+
+ arg = 1;
+ break;
+ case MPFS_PINCTRL_BANK_VOLTAGE:
+ arg = mpfs_pinctrl_pin_to_bank_voltage(pctrl, pin);
+ break;
+ case MPFS_PINCTRL_CLAMP_DIODE:
+ if (!FIELD_GET(MPFS_PINCTRL_CLAMP, val))
+ return -EINVAL;
+
+ arg = 1;
+ break;
+ case MPFS_PINCTRL_LOCKDOWN:
+ /*
+ * Lockdown is a read-only configuration, it'll get set if the
+ * tamper unit triggers global lockdown and lockdown has been
+ * set in the MSS Configurator for the bank a pin belongs to.
+ */
+ if (!FIELD_GET(MPFS_PINCTRL_LOCKDN, val))
+ return -EINVAL;
+
+ arg = 1;
+ break;
+ case MPFS_PINCTRL_IBUFMD:
+ arg = FIELD_GET(MPFS_PINCTRL_IBUFMD_MASK, val);
+ break;
+ default:
+ return -ENOTSUPP;
+ }
+
+ *config = pinconf_to_config_packed(param, arg);
+
+ return 0;
+}
+
+static int mpfs_pinctrl_pinconf_generate_config(struct mpfs_pinctrl *pctrl, unsigned int pin,
+ unsigned long *configs, unsigned int num_configs,
+ u32 *value, u32 *bank_voltage)
+{
+ u32 val = 0;
+
+ for (int i = 0; i < num_configs; i++) {
+ int param, tmp;
+ u32 arg;
+
+ param = pinconf_to_config_param(configs[i]);
+ arg = pinconf_to_config_argument(configs[i]);
+
+ switch (param) {
+ case PIN_CONFIG_BIAS_BUS_HOLD:
+ val |= MPFS_PINCTRL_PULL_MASK;
+ break;
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ //TODO mutual exclusion stuff
+ val &= ~MPFS_PINCTRL_PULL_MASK;
+ val |= MPFS_PINCTRL_WPD;
+ break;
+ case PIN_CONFIG_BIAS_PULL_UP:
+ val &= ~MPFS_PINCTRL_PULL_MASK;
+ val |= MPFS_PINCTRL_WPU;
+ break;
+ case PIN_CONFIG_BIAS_DISABLE:
+ val &= ~MPFS_PINCTRL_PULL_MASK;
+ break;
+ case PIN_CONFIG_DRIVE_STRENGTH:
+ tmp = mpfs_pinctrl_get_drive_strength_val(arg);
+ if (tmp < 0)
+ return tmp;
+
+ val |= FIELD_PREP(MPFS_PINCTRL_DRV_MASK, tmp);
+ break;
+ case PIN_CONFIG_INPUT_SCHMITT_ENABLE:
+ if (!arg)
+ break;
+ val |= MPFS_PINCTRL_ENHYST;
+ break;
+ case PIN_CONFIG_PERSIST_STATE:
+ val |= MPFS_PINCTRL_LP_PERSIST_EN;
+ break;
+ case PIN_CONFIG_MODE_LOW_POWER:
+ if (arg)
+ val |= MPFS_PINCTRL_LP_BYPASS_EN;
+ break;
+ case MPFS_PINCTRL_BANK_VOLTAGE:
+ tmp = mpfs_pinctrl_get_bank_voltage_val(arg);
+ if (tmp < 0)
+ return tmp;
+
+ *bank_voltage = tmp;
+ break;
+ case MPFS_PINCTRL_CLAMP_DIODE:
+ val |= MPFS_PINCTRL_CLAMP;
+ break;
+ case MPFS_PINCTRL_IBUFMD:
+ val |= FIELD_PREP(MPFS_PINCTRL_IBUFMD_MASK, arg);
+ break;
+ default:
+ dev_err(pctrl->dev, "config %u not supported\n", param);
+ return -ENOTSUPP;
+ }
+ }
+
+ *value = val;
+ return 0;
+}
+
+static int mpfs_pinctrl_pin_set_config(struct mpfs_pinctrl *pctrl, unsigned int pin, u32 config)
+{
+ int reg = mpfs_pinctrl_pin_to_iocfg_reg(pin);
+ int offset = mpfs_pinctrl_pin_to_iocfg_offset(pin);
+ u32 val, mask;
+
+ mask = MPFS_PINCTRL_IOCFG_MASK << offset;
+ val = config << offset;
+
+ regmap_update_bits(pctrl->regmap, reg, mask, val);
+
+ return 0;
+}
+
+static int mpfs_pinctrl_pinconf_set(struct pinctrl_dev *pctrl_dev, unsigned int pin,
+ unsigned long *configs, unsigned int num_configs)
+{
+ struct mpfs_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctrl_dev);
+ u32 val, bank_voltage = 0;
+ int ret;
+
+ ret = mpfs_pinctrl_pinconf_generate_config(pctrl, pin, configs, num_configs, &val,
+ &bank_voltage);
+ if (ret)
+ return ret;
+
+ ret = mpfs_pinctrl_pin_set_config(pctrl, pin, val);
+ if (ret)
+ return ret;
+
+ if (bank_voltage)
+ mpfs_pinctrl_set_bank_voltage(pctrl, pin, bank_voltage);
+
+ return 0;
+}
+
+static int mpfs_pinctrl_pinconf_group_set(struct pinctrl_dev *pctrl_dev, unsigned int gsel,
+ unsigned long *configs, unsigned int num_configs)
+{
+ struct mpfs_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctrl_dev);
+ const struct group_desc *group;
+ unsigned int pin;
+ u32 val, bank_voltage = 0;
+ int ret;
+
+ group = pinctrl_generic_get_group(pctrl_dev, gsel);
+ if (!group)
+ return -EINVAL;
+
+ /*
+ * Assume that the first pin in a group is representative, as the mss
+ * configurator doesn't allow splitting a function between two banks.
+ */
+ pin = group->grp.pins[0];
+
+ ret = mpfs_pinctrl_pinconf_generate_config(pctrl, pin, configs, num_configs, &val,
+ &bank_voltage);
+ if (ret)
+ return ret;
+
+ for (int i = 0; i < group->grp.npins; i++)
+ mpfs_pinctrl_pin_set_config(pctrl, group->grp.pins[i], val);
+
+ if (bank_voltage)
+ mpfs_pinctrl_set_bank_voltage(pctrl, group->grp.pins[0], bank_voltage);
+
+ return 0;
+}
+
+static void mpfs_pinctrl_pinconf_dbg_show(struct pinctrl_dev *pctrl_dev, struct seq_file *seq,
+ unsigned int pin)
+{
+ struct mpfs_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctrl_dev);
+ u32 val;
+ int reg, offset;
+
+ seq_printf(seq, ", bank voltage: %u, ", mpfs_pinctrl_pin_to_bank_voltage(pctrl, pin));
+
+ reg = mpfs_pinctrl_pin_to_iocfg_reg(pin);
+ offset = mpfs_pinctrl_pin_to_iocfg_offset(pin);
+
+ seq_printf(seq, "pin: %u ", pin);
+ seq_printf(seq, "reg: %x offset: %u ", reg, offset);
+
+ if (reg < 0 || offset < 0)
+ return;
+
+ regmap_read(pctrl->regmap, reg, &val);
+ val = (val & (MPFS_PINCTRL_IOCFG_MASK << offset)) >> offset;
+ seq_printf(seq, "val: %x\n", val);
+}
+
+static const struct pinconf_ops mpfs_pinctrl_pinconf_ops = {
+ .pin_config_get = mpfs_pinctrl_pinconf_get,
+ .pin_config_set = mpfs_pinctrl_pinconf_set,
+ .pin_config_group_set = mpfs_pinctrl_pinconf_group_set,
+ .pin_config_dbg_show = mpfs_pinctrl_pinconf_dbg_show,
+ .is_generic = true,
+};
+
+static const struct pinctrl_pin_desc mpfs_pinctrl_pins[] = {
+ PINCTRL_PIN(0, "bank 4 0"),
+ PINCTRL_PIN(1, "bank 4 1"),
+ PINCTRL_PIN(2, "bank 4 2"),
+ PINCTRL_PIN(3, "bank 4 3"),
+ PINCTRL_PIN(4, "bank 4 4"),
+ PINCTRL_PIN(5, "bank 4 5"),
+ PINCTRL_PIN(6, "bank 4 6"),
+ PINCTRL_PIN(7, "bank 4 7"),
+ PINCTRL_PIN(8, "bank 4 8"),
+ PINCTRL_PIN(9, "bank 4 9"),
+ PINCTRL_PIN(10, "bank 4 10"),
+ PINCTRL_PIN(11, "bank 4 11"),
+ PINCTRL_PIN(12, "bank 4 12"),
+ PINCTRL_PIN(13, "bank 4 13"),
+
+ PINCTRL_PIN(14, "bank 2 0"),
+ PINCTRL_PIN(15, "bank 2 1"),
+ PINCTRL_PIN(16, "bank 2 2"),
+ PINCTRL_PIN(17, "bank 2 3"),
+ PINCTRL_PIN(18, "bank 2 4"),
+ PINCTRL_PIN(19, "bank 2 5"),
+ PINCTRL_PIN(20, "bank 2 6"),
+ PINCTRL_PIN(21, "bank 2 7"),
+ PINCTRL_PIN(22, "bank 2 8"),
+ PINCTRL_PIN(23, "bank 2 9"),
+ PINCTRL_PIN(24, "bank 2 10"),
+ PINCTRL_PIN(25, "bank 2 11"),
+ PINCTRL_PIN(26, "bank 2 12"),
+ PINCTRL_PIN(27, "bank 2 13"),
+ PINCTRL_PIN(28, "bank 2 14"),
+ PINCTRL_PIN(29, "bank 2 15"),
+ PINCTRL_PIN(30, "bank 2 16"),
+ PINCTRL_PIN(31, "bank 2 17"),
+ PINCTRL_PIN(32, "bank 2 18"),
+ PINCTRL_PIN(33, "bank 2 19"),
+ PINCTRL_PIN(34, "bank 2 20"),
+ PINCTRL_PIN(35, "bank 2 21"),
+ PINCTRL_PIN(36, "bank 2 22"),
+ PINCTRL_PIN(37, "bank 2 23"),
+};
+
+static int mpfs_pinctrl_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mpfs_pinctrl *pctrl;
+ int ret;
+
+ pctrl = devm_kzalloc(dev, sizeof(*pctrl), GFP_KERNEL);
+ if (!pctrl)
+ return -ENOMEM;
+
+ pctrl->regmap = device_node_to_regmap(pdev->dev.parent->of_node);
+ if (IS_ERR(pctrl->regmap))
+ dev_err_probe(dev, PTR_ERR(pctrl->regmap), "Failed to find syscon regmap\n");
+
+ pctrl->sysreg_regmap = syscon_regmap_lookup_by_compatible("microchip,mpfs-sysreg-scb");
+ if (IS_ERR(pctrl->sysreg_regmap))
+ return PTR_ERR(pctrl->sysreg_regmap);
+
+ pctrl->desc.name = dev_name(dev);
+ pctrl->desc.pins = mpfs_pinctrl_pins;
+ pctrl->desc.npins = ARRAY_SIZE(mpfs_pinctrl_pins);
+ pctrl->desc.pctlops = &mpfs_pinctrl_ops;
+ pctrl->desc.pmxops = &mpfs_pinctrl_pinmux_ops;
+ pctrl->desc.confops = &mpfs_pinctrl_pinconf_ops;
+ pctrl->desc.owner = THIS_MODULE;
+ pctrl->desc.num_custom_params = ARRAY_SIZE(mpfs_pinctrl_custom_bindings);
+ pctrl->desc.custom_params = mpfs_pinctrl_custom_bindings;
+
+ pctrl->dev = dev;
+
+ ret = devm_mutex_init(dev, &pctrl->mutex);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, pctrl);
+
+ pctrl->pctrl = devm_pinctrl_register(&pdev->dev, &pctrl->desc, pctrl);
+ if (IS_ERR(pctrl->pctrl))
+ return PTR_ERR(pctrl->pctrl);
+
+ return 0;
+}
+
+static const struct of_device_id mpfs_pinctrl_of_match[] = {
+ { .compatible = "microchip,mpfs-pinctrl-mssio" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mpfs_pinctrl_of_match);
+
+static struct platform_driver mpfs_pinctrl_driver = {
+ .driver = {
+ .name = "mpfs-pinctrl",
+ .of_match_table = mpfs_pinctrl_of_match,
+ },
+ .probe = mpfs_pinctrl_probe,
+};
+module_platform_driver(mpfs_pinctrl_driver);
+
+MODULE_AUTHOR("Conor Dooley <conor.dooley@microchip.com>");
+MODULE_DESCRIPTION("Polarfire SoC mssio pinctrl driver");
+MODULE_LICENSE("GPL");
--
2.51.0
On Thu, Nov 27, 2025 at 11:58 AM Conor Dooley <conor@kernel.org> wrote:
> drivers/pinctrl/Kconfig | 7 +-
> drivers/pinctrl/Makefile | 1 +
> drivers/pinctrl/pinctrl-mpfs-mssio.c | 750 +++++++++++++++++++++++++++
Time to move the drivers to drivers/pinctrl/microchip
before it becomes an overpopulation problem?
(The previous drivers can be moved in a separate patch.)
> + select GENERIC_PINCTRL_GROUPS
> + select GENERIC_PINMUX_FUNCTIONS
> + select GENERIC_PINCTRL_BELLS_AND_WHISTLES
Just the bottom select will bring it all in, right?
> +static int mpfs_pinctrl_pin_to_iocfg_reg(unsigned int pin)
> +{
> + u32 reg = MPFS_PINCTRL_IOCFG01_REG;
> +
> + if (pin >= MPFS_PINCTRL_BANK2_START)
> + reg += MPFS_PINCTRL_INTER_BANK_GAP;
> +
> + // 2 pins per 32-bit register
> + reg += (pin / 2) * 0x4;
It's helpful with these nice comments that ease the reading of the code
quite a bit.
> +static int mpfs_pinctrl_set_mux(struct pinctrl_dev *pctrl_dev, unsigned int fsel,
> + unsigned int gsel)
> +{
> + struct mpfs_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctrl_dev);
> + const struct group_desc *group;
> + const char **functions;
> +
> + group = pinctrl_generic_get_group(pctrl_dev, gsel);
> + if (!group)
> + return -EINVAL;
> +
> + functions = group->data;
> +
> + for (int i = 0; i < group->grp.npins; i++) {
> + u32 function;
> +
> + //TODO @Linus my new function being actually generic means that
> + // the mapping of function string to something the hardware
> + // understands only happens at this point.
> + // I think this is fine, because dt validation would whinge
> + // about something invalid, but it's the "catch" with my approach.
> + // The other option I considered was to provide a mapping
> + // function pointer that the driver can populate, but I think
> + // that's overkill.
> + function = mpfs_pinctrl_function_map(functions[i]);
> + if (function < 0) {
> + dev_err(pctrl->dev, "invalid function %s\n", functions[i]);
> + return function;
> + }
This is fine with me.
Ideally I would like code that does a lot of string stacking and comparing
to be using Rust, but we cannot yet use that in core code so that is for
another day.
Yours,
Linus Walleij
On Fri, Dec 26, 2025 at 10:40:07AM +0100, Linus Walleij wrote:
> On Thu, Nov 27, 2025 at 11:58 AM Conor Dooley <conor@kernel.org> wrote:
>
> > drivers/pinctrl/Kconfig | 7 +-
> > drivers/pinctrl/Makefile | 1 +
> > drivers/pinctrl/pinctrl-mpfs-mssio.c | 750 +++++++++++++++++++++++++++
>
> Time to move the drivers to drivers/pinctrl/microchip
> before it becomes an overpopulation problem?
Sure, no problem.
>
> (The previous drivers can be moved in a separate patch.)
>
>
> > + select GENERIC_PINCTRL_GROUPS
> > + select GENERIC_PINMUX_FUNCTIONS
> > + select GENERIC_PINCTRL_BELLS_AND_WHISTLES
>
> Just the bottom select will bring it all in, right?
I'll make it do that if it's not already. Just didn't know if you were a
"select everything you use" kinda guy or didn't mind selects selecting.
> > +static int mpfs_pinctrl_pin_to_iocfg_reg(unsigned int pin)
> > +{
> > + u32 reg = MPFS_PINCTRL_IOCFG01_REG;
> > +
> > + if (pin >= MPFS_PINCTRL_BANK2_START)
> > + reg += MPFS_PINCTRL_INTER_BANK_GAP;
> > +
> > + // 2 pins per 32-bit register
> > + reg += (pin / 2) * 0x4;
>
> It's helpful with these nice comments that ease the reading of the code
> quite a bit.
Eh, I feel like sometimes a comment like this is just better than trying
to insert silly defines to unmagic the numbers.
>
> > +static int mpfs_pinctrl_set_mux(struct pinctrl_dev *pctrl_dev, unsigned int fsel,
> > + unsigned int gsel)
> > +{
> > + struct mpfs_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctrl_dev);
> > + const struct group_desc *group;
> > + const char **functions;
> > +
> > + group = pinctrl_generic_get_group(pctrl_dev, gsel);
> > + if (!group)
> > + return -EINVAL;
> > +
> > + functions = group->data;
> > +
> > + for (int i = 0; i < group->grp.npins; i++) {
> > + u32 function;
> > +
> > + //TODO @Linus my new function being actually generic means that
> > + // the mapping of function string to something the hardware
> > + // understands only happens at this point.
> > + // I think this is fine, because dt validation would whinge
> > + // about something invalid, but it's the "catch" with my approach.
> > + // The other option I considered was to provide a mapping
> > + // function pointer that the driver can populate, but I think
> > + // that's overkill.
> > + function = mpfs_pinctrl_function_map(functions[i]);
> > + if (function < 0) {
> > + dev_err(pctrl->dev, "invalid function %s\n", functions[i]);
> > + return function;
> > + }
>
> This is fine with me.
>
> Ideally I would like code that does a lot of string stacking and comparing
> to be using Rust, but we cannot yet use that in core code so that is for
> another day.
Yeah, would be nice. My problem with it was more about the point in time
where this happens rather than doing it in the first place though.
© 2016 - 2026 Red Hat, Inc.