[PATCH RFC 2/2] pinctrl: add syscon-backed packed-field pin controller driver

Billy Tsai billy_tsai at aspeedtech.com
Fri Feb 13 00:17:43 PST 2026


Introduce a new pinctrl driver for controllers where pinmux and/or pin
configuration fields are represented as fixed-width packed fields within
shared registers.

The driver is designed to be used as a child of a syscon node and
obtains a regmap from the parent, avoiding direct MMIO ownership and
allowing shared access to SCU-style register blocks.

This driver is derived from pinctrl-single but reworked as a standalone
implementation focused on packed-field controllers.

Signed-off-by: Billy Tsai <billy_tsai at aspeedtech.com>
---
 drivers/pinctrl/Kconfig          |   13 +
 drivers/pinctrl/Makefile         |    1 +
 drivers/pinctrl/pinctrl-packed.c | 1168 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 1182 insertions(+)

diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index 7b9f792acb0e..d1fd6a2e767c 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -443,6 +443,19 @@ config PINCTRL_OCELOT
 
 	  If conpiled as a module, the module name will be pinctrl-ocelot.
 
+config PINCTRL_PACKED
+	tristate "Packed-field type device tree based pinctrl driver"
+	depends on OF
+	depends on HAS_IOMEM
+	select GENERIC_PINCTRL_GROUPS
+	select GENERIC_PINMUX_FUNCTIONS
+	select GENERIC_PINCONF
+	help
+	  This selects the device tree based generic pinctrl driver with
+	  fixed-width packed-field register layout.
+	  The driver is derived from pinctrl-single but reworked as a standalone
+	  implementation focused on packed-field controllers.
+
 config PINCTRL_PALMAS
 	tristate "Pinctrl driver for the PALMAS Series MFD devices"
 	depends on OF && MFD_PALMAS
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index be5200c23e60..fd71da9901b9 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -45,6 +45,7 @@ obj-$(CONFIG_PINCTRL_MCP23S08)	+= pinctrl-mcp23s08.o
 obj-$(CONFIG_PINCTRL_MICROCHIP_SGPIO)	+= pinctrl-microchip-sgpio.o
 obj-$(CONFIG_PINCTRL_MLXBF3)	+= pinctrl-mlxbf3.o
 obj-$(CONFIG_PINCTRL_OCELOT)	+= pinctrl-ocelot.o
+obj-$(CONFIG_PINCTRL_PACKED)	+= pinctrl-packed.o
 obj-$(CONFIG_PINCTRL_PALMAS)	+= pinctrl-palmas.o
 obj-$(CONFIG_PINCTRL_PEF2256)	+= pinctrl-pef2256.o
 obj-$(CONFIG_PINCTRL_PIC32)	+= pinctrl-pic32.o
diff --git a/drivers/pinctrl/pinctrl-packed.c b/drivers/pinctrl/pinctrl-packed.c
new file mode 100644
index 000000000000..cfb43894cfaa
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-packed.c
@@ -0,0 +1,1168 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Generic device tree based pinctrl driver for fixed-width
+ * packed-field pin controllers
+ *
+ * Copyright (C) 2026 ASPEED Technology Inc.
+ *
+ * This driver targets controllers where pinmux and/or pinconf fields
+ * are arranged as fixed-width fields packed sequentially within
+ * shared registers.
+ *
+ * Derived from drivers/pinctrl/pinctrl-single.c
+ *
+ * Original author:
+ *   Tony Lindgren <tony at atomide.com>
+ *
+ * Reworked for packed-field / syscon-based pin controllers:
+ *   Billy Tsai <billy_tsai at aspeedtech.com>
+ */
+
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/list.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/seq_file.h>
+#include <linux/slab.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 "devicetree.h"
+#include "pinconf.h"
+#include "pinmux.h"
+
+#define DRIVER_NAME			"pinctrl-packed"
+
+/**
+ * struct pcp_func_vals - mux function register offset and value pair
+ * @reg:	register offset
+ * @val:	register value
+ * @mask:	mask
+ */
+struct pcp_func_vals {
+	unsigned int reg;
+	unsigned int val;
+	unsigned int mask;
+};
+
+/**
+ * struct pcp_conf_vals - pinconf parameter, pinconf register offset
+ * and value, enable, disable, mask
+ * @param:	config parameter
+ * @val:	user input bits in the pinconf register
+ * @enable:	enable bits in the pinconf register
+ * @disable:	disable bits in the pinconf register
+ * @mask:	mask bits in the register value
+ */
+struct pcp_conf_vals {
+	enum pin_config_param param;
+	unsigned int val;
+	unsigned int enable;
+	unsigned int disable;
+	unsigned int mask;
+};
+
+/**
+ * struct pcp_conf_type - pinconf property name, pinconf param pair
+ * @name:	property name in DTS file
+ * @param:	config parameter
+ */
+struct pcp_conf_type {
+	const char *name;
+	enum pin_config_param param;
+};
+
+/**
+ * struct pcp_function - pinctrl function
+ * @name:	pinctrl function name
+ * @vals:	register and vals array
+ * @nvals:	number of entries in vals array
+ * @conf:	array of pin configurations
+ * @nconfs:	number of pin configurations available
+ * @node:	list node
+ */
+struct pcp_function {
+	const char *name;
+	struct pcp_func_vals *vals;
+	unsigned int nvals;
+	struct pcp_conf_vals *conf;
+	int nconfs;
+	struct list_head node;
+};
+
+/**
+ * struct pcp_gpiofunc_range - pin ranges with same mux value of gpio function
+ * @offset:	offset base of pins
+ * @npins:	number pins with the same mux value of gpio function
+ * @gpiofunc:	mux value of gpio function
+ * @node:	list node
+ */
+struct pcp_gpiofunc_range {
+	unsigned int offset;
+	unsigned int npins;
+	unsigned int gpiofunc;
+	struct list_head node;
+};
+
+/**
+ * struct pcp_data - wrapper for data needed by pinctrl framework
+ * @pa:		pindesc array
+ * @cur:	index to current element
+ *
+ * REVISIT: We should be able to drop this eventually by adding
+ * support for registering pins individually in the pinctrl
+ * framework for those drivers that don't need a static array.
+ */
+struct pcp_data {
+	struct pinctrl_pin_desc *pa;
+	int cur;
+};
+
+/**
+ * struct pcp_soc_data - SoC specific settings
+ * @flags:	initial SoC specific PCP_FEAT_xxx values
+ */
+struct pcp_soc_data {
+	unsigned int flags;
+};
+
+/**
+ * struct pcp_device - pinctrl device instance
+ * @regmap:	regmap for the controller
+ * @base_offset: offset from the regmap base
+ * @size:	size of the area
+ * @dev:	device entry
+ * @np:		device tree node
+ * @pctl:	pin controller device
+ * @flags:	mask of PCP_FEAT_xxx values
+ * @mutex:	mutex protecting the lists
+ * @width:	bits per mux register
+ * @fmask:	function register mask
+ * @bits_per_pin: number of bits per pin
+ * @pins:	physical pins on the SoC
+ * @gpiofuncs:	list of gpio functions
+ * @desc:	pin controller descriptor
+ */
+struct pcp_device {
+	struct regmap *regmap;
+	unsigned int base_offset;
+	unsigned int size;
+	struct device *dev;
+	struct device_node *np;
+	struct pinctrl_dev *pctl;
+	unsigned int flags;
+#define PCP_FEAT_PINCONF	(1 << 0)
+	struct mutex mutex;
+	unsigned int width;
+	unsigned int fmask;
+	unsigned int bits_per_pin;
+	struct pcp_data pins;
+	struct list_head gpiofuncs;
+	struct pinctrl_desc desc;
+};
+
+#define PCP_HAS_PINCONF		(pcp->flags & PCP_FEAT_PINCONF)
+
+static int pcp_pinconf_get(struct pinctrl_dev *pctldev, unsigned int pin,
+			   unsigned long *config);
+static int pcp_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin,
+			   unsigned long *configs, unsigned int num_configs);
+
+static enum pin_config_param pcp_bias[] = {
+	PIN_CONFIG_BIAS_PULL_DOWN,
+	PIN_CONFIG_BIAS_PULL_UP,
+};
+
+static inline void pcp_writel(unsigned int val, struct pcp_device *pcp,
+			      unsigned int reg)
+{
+	regmap_write(pcp->regmap, reg, val);
+}
+
+static inline unsigned int pcp_readl(struct pcp_device *pcp, unsigned int reg)
+{
+	unsigned int val;
+
+	regmap_read(pcp->regmap, reg, &val);
+	return val;
+}
+
+static unsigned int pcp_pin_reg_offset_get(struct pcp_device *pcp,
+					   unsigned int pin)
+{
+	unsigned int mux_bytes = pcp->width / BITS_PER_BYTE;
+	unsigned int pin_offset_bytes;
+
+	pin_offset_bytes = (pcp->bits_per_pin * pin) / BITS_PER_BYTE;
+	return (pin_offset_bytes / mux_bytes) * mux_bytes;
+}
+
+static unsigned int pcp_pin_shift_reg_get(struct pcp_device *pcp,
+					  unsigned int pin)
+{
+	return (pin % (pcp->width / pcp->bits_per_pin)) * pcp->bits_per_pin;
+}
+
+static void pcp_pin_dbg_show(struct pinctrl_dev *pctldev, struct seq_file *s,
+			     unsigned int pin)
+{
+	unsigned int val, offset;
+	struct pcp_device *pcp;
+
+	pcp = pinctrl_dev_get_drvdata(pctldev);
+
+	offset = pcp_pin_reg_offset_get(pcp, pin);
+	val = pcp_readl(pcp, pcp->base_offset + offset);
+
+	val &= pcp->fmask << pcp_pin_shift_reg_get(pcp, pin);
+
+	seq_printf(s, "%08x %08x %s ", pcp->base_offset + offset, val, DRIVER_NAME);
+}
+
+static void pcp_dt_free_map(struct pinctrl_dev *pctldev, struct pinctrl_map *map,
+			    unsigned int num_maps)
+{
+	struct pcp_device *pcp;
+
+	pcp = pinctrl_dev_get_drvdata(pctldev);
+	devm_kfree(pcp->dev, map);
+}
+
+static int pcp_dt_node_to_map(struct pinctrl_dev *pctldev,
+			      struct device_node *np_config,
+			      struct pinctrl_map **map, unsigned int *num_maps);
+
+static const struct pinctrl_ops pcp_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 = pcp_pin_dbg_show,
+	.dt_node_to_map = pcp_dt_node_to_map,
+	.dt_free_map = pcp_dt_free_map,
+};
+
+static int pcp_get_function(struct pinctrl_dev *pctldev, unsigned int pin,
+			    struct pcp_function **func)
+{
+	struct pcp_device *pcp = pinctrl_dev_get_drvdata(pctldev);
+	struct pin_desc *pdesc = pin_desc_get(pctldev, pin);
+	const struct pinctrl_setting_mux *setting;
+	const struct function_desc *function;
+	unsigned int fselector;
+
+	/* If pin is not described in DTS & enabled, mux_setting is NULL. */
+	setting = pdesc->mux_setting;
+	if (!setting)
+		return -EOPNOTSUPP;
+	fselector = setting->func;
+	function = pinmux_generic_get_function(pctldev, fselector);
+	if (!function)
+		return -EINVAL;
+	*func = function->data;
+	if (!(*func)) {
+		dev_err(pcp->dev, "%s could not find function%i\n",
+			__func__, fselector);
+		return -EOPNOTSUPP;
+	}
+	return 0;
+}
+
+static int pcp_set_mux(struct pinctrl_dev *pctldev, unsigned int fselector,
+		       unsigned int group)
+{
+	const struct function_desc *function;
+	struct pcp_function *func;
+	struct pcp_device *pcp;
+	int i;
+
+	pcp = pinctrl_dev_get_drvdata(pctldev);
+	/* If function mask is null, needn't enable it. */
+	if (!pcp->fmask)
+		return 0;
+	function = pinmux_generic_get_function(pctldev, fselector);
+	if (!function)
+		return -EINVAL;
+	func = function->data;
+	if (!func)
+		return -EINVAL;
+
+	dev_dbg(pcp->dev, "enabling %s function%i\n",
+		func->name, fselector);
+
+	for (i = 0; i < func->nvals; i++) {
+		struct pcp_func_vals *vals;
+
+		vals = &func->vals[i];
+		regmap_update_bits(pcp->regmap, vals->reg, vals->mask, vals->val);
+	}
+
+	return 0;
+}
+
+static int pcp_request_gpio(struct pinctrl_dev *pctldev,
+			    struct pinctrl_gpio_range *range, unsigned int pin)
+{
+	struct pcp_device *pcp = pinctrl_dev_get_drvdata(pctldev);
+	struct pcp_gpiofunc_range *frange = NULL;
+	struct list_head *pos, *tmp;
+	int pin_shift;
+
+	/* If function mask is null, return directly. */
+	if (!pcp->fmask)
+		return -EOPNOTSUPP;
+
+	list_for_each_safe(pos, tmp, &pcp->gpiofuncs) {
+		u32 offset;
+
+		frange = list_entry(pos, struct pcp_gpiofunc_range, node);
+		if (pin >= frange->offset + frange->npins
+			|| pin < frange->offset)
+			continue;
+
+		offset = pcp_pin_reg_offset_get(pcp, pin);
+
+		pin_shift = pcp_pin_shift_reg_get(pcp, pin);
+
+		regmap_update_bits(pcp->regmap, pcp->base_offset + offset,
+					pcp->fmask << pin_shift,
+					frange->gpiofunc << pin_shift);
+		break;
+	}
+	return 0;
+}
+
+static const struct pinmux_ops pcp_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 = pcp_set_mux,
+	.gpio_request_enable = pcp_request_gpio,
+};
+
+/* Clear BIAS value */
+static void pcp_pinconf_clear_bias(struct pinctrl_dev *pctldev, unsigned int pin)
+{
+	unsigned long config;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(pcp_bias); i++) {
+		config = pinconf_to_config_packed(pcp_bias[i], 0);
+		pcp_pinconf_set(pctldev, pin, &config, 1);
+	}
+}
+
+/*
+ * Check whether PIN_CONFIG_BIAS_DISABLE is valid.
+ * It's depend on that PULL_DOWN & PULL_UP configs are all invalid.
+ */
+static bool pcp_pinconf_bias_disable(struct pinctrl_dev *pctldev, unsigned int pin)
+{
+	unsigned long config;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(pcp_bias); i++) {
+		config = pinconf_to_config_packed(pcp_bias[i], 0);
+		if (!pcp_pinconf_get(pctldev, pin, &config))
+			return false;
+	}
+	return true;
+}
+
+static int pcp_pinconf_get(struct pinctrl_dev *pctldev, unsigned int pin,
+			   unsigned long *config)
+{
+	struct pcp_device *pcp = pinctrl_dev_get_drvdata(pctldev);
+	unsigned int offset, data, i, j;
+	enum pin_config_param param;
+	struct pcp_function *func;
+	int ret;
+
+	offset = 0;
+	data = 0;
+	ret = pcp_get_function(pctldev, pin, &func);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < func->nconfs; i++) {
+		param = pinconf_to_config_param(*config);
+		if (param == PIN_CONFIG_BIAS_DISABLE) {
+			if (pcp_pinconf_bias_disable(pctldev, pin)) {
+				*config = 0;
+				return 0;
+			} else {
+				return -EOPNOTSUPP;
+			}
+		} else if (param != func->conf[i].param) {
+			continue;
+		}
+
+		offset = pcp_pin_reg_offset_get(pcp, pin);
+		data = pcp_readl(pcp, pcp->base_offset + offset) & func->conf[i].mask;
+		switch (func->conf[i].param) {
+		/* 4 parameters */
+		case PIN_CONFIG_BIAS_PULL_DOWN:
+		case PIN_CONFIG_BIAS_PULL_UP:
+		case PIN_CONFIG_INPUT_SCHMITT_ENABLE:
+			if ((data != func->conf[i].enable) ||
+			    (data == func->conf[i].disable))
+				return -EOPNOTSUPP;
+			*config = 0;
+			break;
+		/* 2 parameters */
+		case PIN_CONFIG_INPUT_SCHMITT:
+			for (j = 0; j < func->nconfs; j++) {
+				switch (func->conf[j].param) {
+				case PIN_CONFIG_INPUT_SCHMITT_ENABLE:
+					if (data != func->conf[j].enable)
+						return -EOPNOTSUPP;
+					break;
+				default:
+					break;
+				}
+			}
+			*config = data;
+			break;
+		case PIN_CONFIG_DRIVE_STRENGTH:
+		case PIN_CONFIG_SLEW_RATE:
+		case PIN_CONFIG_MODE_LOW_POWER:
+		case PIN_CONFIG_INPUT_ENABLE:
+		default:
+			*config = data;
+			break;
+		}
+		return 0;
+	}
+	return -EOPNOTSUPP;
+}
+
+static int pcp_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin,
+			   unsigned long *configs, unsigned int num_configs)
+{
+	struct pcp_device *pcp = pinctrl_dev_get_drvdata(pctldev);
+	unsigned int offset, shift, i, data;
+	enum pin_config_param param;
+	struct pcp_function *func;
+	int j, ret;
+	u32 arg;
+
+	offset = 0;
+	shift = 0;
+	ret = pcp_get_function(pctldev, pin, &func);
+	if (ret)
+		return ret;
+
+	for (j = 0; j < num_configs; j++) {
+		param = pinconf_to_config_param(configs[j]);
+
+		/* BIAS_DISABLE has no entry in the func->conf table */
+		if (param == PIN_CONFIG_BIAS_DISABLE) {
+			/* This just disables all bias entries */
+			pcp_pinconf_clear_bias(pctldev, pin);
+			continue;
+		}
+
+		for (i = 0; i < func->nconfs; i++) {
+			if (param != func->conf[i].param)
+				continue;
+
+			/* Use the same offset mapping as pinmux (handles bit-per-mux) */
+			offset = pcp_pin_reg_offset_get(pcp, pin);
+			data = pcp_readl(pcp, pcp->base_offset + offset);
+			arg = pinconf_to_config_argument(configs[j]);
+			switch (param) {
+			/* 2 parameters */
+			case PIN_CONFIG_INPUT_SCHMITT:
+			case PIN_CONFIG_DRIVE_STRENGTH:
+			case PIN_CONFIG_SLEW_RATE:
+			case PIN_CONFIG_MODE_LOW_POWER:
+			case PIN_CONFIG_INPUT_ENABLE:
+				shift = ffs(func->conf[i].mask) - 1;
+				data &= ~func->conf[i].mask;
+				data |= (arg << shift) & func->conf[i].mask;
+				break;
+			/* 4 parameters */
+			case PIN_CONFIG_BIAS_PULL_DOWN:
+			case PIN_CONFIG_BIAS_PULL_UP:
+				if (arg) {
+					pcp_pinconf_clear_bias(pctldev, pin);
+					data = pcp_readl(pcp, pcp->base_offset + offset);
+				}
+				fallthrough;
+			case PIN_CONFIG_INPUT_SCHMITT_ENABLE:
+				data &= ~func->conf[i].mask;
+				if (arg)
+					data |= func->conf[i].enable;
+				else
+					data |= func->conf[i].disable;
+				break;
+			default:
+				return -EOPNOTSUPP;
+			}
+			pcp_writel(data, pcp, pcp->base_offset + offset);
+
+			break;
+		}
+		if (i >= func->nconfs)
+			return -EOPNOTSUPP;
+	} /* for each config */
+
+	return 0;
+}
+
+static int pcp_pinconf_group_get(struct pinctrl_dev *pctldev,
+				 unsigned int group, unsigned long *config)
+{
+	const unsigned int *pins;
+	unsigned int npins, old;
+	int i, ret;
+
+	old = 0;
+	ret = pinctrl_generic_get_group_pins(pctldev, group, &pins, &npins);
+	if (ret)
+		return ret;
+	for (i = 0; i < npins; i++) {
+		if (pcp_pinconf_get(pctldev, pins[i], config))
+			return -EOPNOTSUPP;
+		/* configs do not match between two pins */
+		if (i && (old != *config))
+			return -EOPNOTSUPP;
+		old = *config;
+	}
+	return 0;
+}
+
+static int pcp_pinconf_group_set(struct pinctrl_dev *pctldev, unsigned int group,
+				 unsigned long *configs, unsigned int num_configs)
+{
+	const unsigned int *pins;
+	unsigned int npins;
+	int i, ret;
+
+	ret = pinctrl_generic_get_group_pins(pctldev, group, &pins, &npins);
+	if (ret)
+		return ret;
+	for (i = 0; i < npins; i++) {
+		if (pcp_pinconf_set(pctldev, pins[i], configs, num_configs))
+			return -EOPNOTSUPP;
+	}
+	return 0;
+}
+
+static void pcp_pinconf_config_dbg_show(struct pinctrl_dev *pctldev,
+					struct seq_file *s, unsigned long config)
+{
+	pinconf_generic_dump_config(pctldev, s, config);
+}
+
+static const struct pinconf_ops pcp_pinconf_ops = {
+	.pin_config_get = pcp_pinconf_get,
+	.pin_config_set = pcp_pinconf_set,
+	.pin_config_group_get = pcp_pinconf_group_get,
+	.pin_config_group_set = pcp_pinconf_group_set,
+	.pin_config_config_dbg_show = pcp_pinconf_config_dbg_show,
+	.is_generic = true,
+};
+
+/**
+ * pcp_add_pin() - add a pin to the static per controller pin array
+ * @pcp: pcp driver instance
+ * @offset: register offset from base
+ */
+static int pcp_add_pin(struct pcp_device *pcp, unsigned int offset)
+{
+	struct pinctrl_pin_desc *pin;
+	int i;
+
+	i = pcp->pins.cur;
+	if (i >= pcp->desc.npins) {
+		dev_err(pcp->dev, "too many pins, max %i\n",
+			pcp->desc.npins);
+		return -ENOMEM;
+	}
+
+	pin = &pcp->pins.pa[i];
+	pin->number = i;
+	pcp->pins.cur++;
+
+	return i;
+}
+
+/**
+ * pcp_allocate_pin_table() - adds all the pins for the pinctrl driver
+ * @pcp: pcp driver instance
+ *
+ * If your hardware needs holes in the address space, then just set
+ * up multiple driver instances.
+ */
+static int pcp_allocate_pin_table(struct pcp_device *pcp)
+{
+	int nr_pins, i;
+
+	if (!pcp->fmask) {
+		dev_err(pcp->dev, "invalid function mask\n");
+		return -EINVAL;
+	}
+	pcp->bits_per_pin = fls(pcp->fmask);
+	nr_pins = (pcp->size * BITS_PER_BYTE) / pcp->bits_per_pin;
+
+	dev_dbg(pcp->dev, "allocating %i pins\n", nr_pins);
+	pcp->pins.pa = devm_kcalloc(pcp->dev,
+				nr_pins, sizeof(*pcp->pins.pa),
+				GFP_KERNEL);
+	if (!pcp->pins.pa)
+		return -ENOMEM;
+
+	pcp->desc.pins = pcp->pins.pa;
+	pcp->desc.npins = nr_pins;
+
+	for (i = 0; i < pcp->desc.npins; i++) {
+		unsigned int offset;
+		int res;
+
+		offset = pcp_pin_reg_offset_get(pcp, i);
+		res = pcp_add_pin(pcp, offset);
+		if (res < 0) {
+			dev_err(pcp->dev, "error adding pins: %i\n", res);
+			return res;
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * pcp_add_function() - adds a new function to the function list
+ * @pcp: pcp driver instance
+ * @fcn: new function allocated
+ * @name: name of the function
+ * @vals: array of mux register value pairs used by the function
+ * @nvals: number of mux register value pairs
+ * @pgnames: array of pingroup names for the function
+ * @npgnames: number of pingroup names
+ *
+ * Caller must take care of locking.
+ */
+static int pcp_add_function(struct pcp_device *pcp, struct pcp_function **fcn,
+			    const char *name, struct pcp_func_vals *vals,
+			    unsigned int nvals, const char **pgnames,
+			    unsigned int npgnames)
+{
+	struct pcp_function *function;
+	int selector;
+
+	function = devm_kzalloc(pcp->dev, sizeof(*function), GFP_KERNEL);
+	if (!function)
+		return -ENOMEM;
+
+	function->vals = vals;
+	function->nvals = nvals;
+	function->name = name;
+
+	selector = pinmux_generic_add_function(pcp->pctl, name,
+					       pgnames, npgnames,
+					       function);
+	if (selector < 0) {
+		devm_kfree(pcp->dev, function);
+		*fcn = NULL;
+	} else {
+		*fcn = function;
+	}
+
+	return selector;
+}
+
+/*
+ * check whether data matches enable bits or disable bits
+ * Return value: 1 for matching enable bits, 0 for matching disable bits,
+ *               and negative value for matching failure.
+ */
+static int pcp_config_match(unsigned int data, unsigned int enable,
+			    unsigned int disable)
+{
+	int ret = -EINVAL;
+
+	if (data == enable)
+		ret = 1;
+	else if (data == disable)
+		ret = 0;
+	return ret;
+}
+
+static void add_config(struct pcp_conf_vals **conf, enum pin_config_param param,
+		       unsigned int value, unsigned int enable,
+		       unsigned int disable, unsigned int mask)
+{
+	(*conf)->param = param;
+	(*conf)->val = value;
+	(*conf)->enable = enable;
+	(*conf)->disable = disable;
+	(*conf)->mask = mask;
+	(*conf)++;
+}
+
+static void add_setting(unsigned long **setting, enum pin_config_param param,
+			unsigned int arg)
+{
+	**setting = pinconf_to_config_packed(param, arg);
+	(*setting)++;
+}
+
+/* add pinconf setting with 2 parameters */
+static void pcp_add_conf2(struct pcp_device *pcp, struct device_node *np,
+			  const char *name, enum pin_config_param param,
+			  struct pcp_conf_vals **conf, unsigned long **settings)
+{
+	unsigned int value[2], shift;
+	int ret;
+
+	ret = of_property_read_u32_array(np, name, value, 2);
+	if (ret)
+		return;
+	/* set value & mask */
+	value[0] &= value[1];
+	shift = ffs(value[1]) - 1;
+	/* skip enable & disable */
+	add_config(conf, param, value[0], 0, 0, value[1]);
+	add_setting(settings, param, value[0] >> shift);
+}
+
+/* add pinconf setting with 4 parameters */
+static void pcp_add_conf4(struct pcp_device *pcp, struct device_node *np,
+			  const char *name, enum pin_config_param param,
+			  struct pcp_conf_vals **conf, unsigned long **settings)
+{
+	unsigned int value[4];
+	int ret;
+
+	/* value to set, enable, disable, mask */
+	ret = of_property_read_u32_array(np, name, value, 4);
+	if (ret)
+		return;
+	if (!value[3]) {
+		dev_err(pcp->dev, "mask field of the property can't be 0\n");
+		return;
+	}
+	value[0] &= value[3];
+	value[1] &= value[3];
+	value[2] &= value[3];
+	ret = pcp_config_match(value[0], value[1], value[2]);
+	if (ret < 0)
+		dev_dbg(pcp->dev, "failed to match enable or disable bits\n");
+	add_config(conf, param, value[0], value[1], value[2], value[3]);
+	add_setting(settings, param, ret);
+}
+
+static int pcp_parse_pinconf(struct pcp_device *pcp, struct device_node *np,
+			     struct pcp_function *func, struct pinctrl_map **map)
+
+{
+	static const struct pcp_conf_type prop2[] = {
+		{ "pinctrl-packed,drive-strength", PIN_CONFIG_DRIVE_STRENGTH, },
+		{ "pinctrl-packed,slew-rate", PIN_CONFIG_SLEW_RATE, },
+		{ "pinctrl-packed,input-enable", PIN_CONFIG_INPUT_ENABLE, },
+		{ "pinctrl-packed,input-schmitt", PIN_CONFIG_INPUT_SCHMITT, },
+		{ "pinctrl-packed,low-power-mode", PIN_CONFIG_MODE_LOW_POWER, },
+	};
+	static const struct pcp_conf_type prop4[] = {
+		{ "pinctrl-packed,bias-pullup", PIN_CONFIG_BIAS_PULL_UP, },
+		{ "pinctrl-packed,bias-pulldown", PIN_CONFIG_BIAS_PULL_DOWN, },
+		{ "pinctrl-packed,input-schmitt-enable",
+			PIN_CONFIG_INPUT_SCHMITT_ENABLE, },
+	};
+
+	unsigned long *settings = NULL, *s = NULL;
+	struct pcp_conf_vals *conf = NULL;
+	struct pinctrl_map *m = *map;
+	int i = 0, nconfs = 0;
+
+	/* If pinconf isn't supported, don't parse properties in below. */
+	if (!PCP_HAS_PINCONF)
+		return -EOPNOTSUPP;
+
+	/* calculate how many properties are supported in the current node */
+	for (i = 0; i < ARRAY_SIZE(prop2); i++) {
+		if (of_property_present(np, prop2[i].name))
+			nconfs++;
+	}
+	for (i = 0; i < ARRAY_SIZE(prop4); i++) {
+		if (of_property_present(np, prop4[i].name))
+			nconfs++;
+	}
+	if (!nconfs)
+		return -EOPNOTSUPP;
+
+	func->conf = devm_kcalloc(pcp->dev,
+				  nconfs, sizeof(struct pcp_conf_vals),
+				  GFP_KERNEL);
+	if (!func->conf)
+		return -ENOMEM;
+	func->nconfs = nconfs;
+	conf = &(func->conf[0]);
+	m++;
+	settings = devm_kcalloc(pcp->dev, nconfs, sizeof(unsigned long),
+				GFP_KERNEL);
+	if (!settings)
+		return -ENOMEM;
+	s = &settings[0];
+
+	for (i = 0; i < ARRAY_SIZE(prop2); i++)
+		pcp_add_conf2(pcp, np, prop2[i].name, prop2[i].param,
+			      &conf, &s);
+	for (i = 0; i < ARRAY_SIZE(prop4); i++)
+		pcp_add_conf4(pcp, np, prop4[i].name, prop4[i].param,
+			      &conf, &s);
+	m->type = PIN_MAP_TYPE_CONFIGS_GROUP;
+	m->data.configs.group_or_pin = np->name;
+	m->data.configs.configs = settings;
+	m->data.configs.num_configs = nconfs;
+	return 0;
+}
+
+/**
+ * pcp_parse_one_pinctrl_entry() - parses a device tree mux entry
+ * @pcp: pinctrl driver instance
+ * @np: device node of the mux entry
+ * @map: map entry
+ * @num_maps: number of map
+ * @pgnames: pingroup names
+ *
+ * Note that this binding currently supports only sets of one register + value.
+ *
+ * Also note that this driver tries to avoid understanding pin and function
+ * names because of the extra bloat they would cause especially in the case of
+ * a large number of pins. This driver just sets what is specified for the board
+ * in the .dts file. Further user space debugging tools can be developed to
+ * decipher the pin and function names using debugfs.
+ *
+ * If you are concerned about the boot time, set up the static pins in
+ * the bootloader, and only set up selected pins as device tree entries.
+ */
+static int pcp_parse_one_pinctrl_entry(struct pcp_device *pcp,
+				       struct device_node *np,
+				       struct pinctrl_map **map,
+				       unsigned int *num_maps,
+				       const char **pgnames)
+{
+	int rows, *pins, found, res, i, fsel, gsel;
+	const char *name = "pinctrl-packed,pins";
+	struct pcp_function *function = NULL;
+	struct pcp_func_vals *vals;
+
+	res = -ENOMEM;
+	found = 0;
+	rows = pinctrl_count_index_with_args(np, name);
+	if (rows <= 0) {
+		dev_err(pcp->dev, "Invalid number of rows: %d\n", rows);
+		return -EINVAL;
+	}
+
+	vals = devm_kcalloc(pcp->dev, rows, sizeof(*vals), GFP_KERNEL);
+	if (!vals)
+		return -ENOMEM;
+
+	pins = devm_kcalloc(pcp->dev, rows, sizeof(*pins), GFP_KERNEL);
+	if (!pins)
+		goto free_vals;
+
+	for (i = 0; i < rows; i++) {
+		struct of_phandle_args pinctrl_spec;
+		unsigned int offset;
+		unsigned int pin_index, func_sel;
+		unsigned int shift, mask, val;
+
+		res = pinctrl_parse_index_with_args(np, name, i, &pinctrl_spec);
+		if (res)
+			return res;
+
+		/* Expect <pin_index func_sel> for bit-per-mux users. */
+		if (pinctrl_spec.args_count < 2) {
+			dev_err(pcp->dev,
+				"invalid args_count for bit-per-mux spec: %i\n",
+				pinctrl_spec.args_count);
+			break;
+		}
+
+		pin_index = pinctrl_spec.args[0];
+		func_sel = pinctrl_spec.args[1];
+
+		if (pin_index >= pcp->desc.npins) {
+			dev_err(pcp->dev,
+				"pin index out of range for %pOFn: %u (npins %u)\n",
+				np, pin_index, pcp->desc.npins);
+			break;
+		}
+
+		offset = pcp_pin_reg_offset_get(pcp, pin_index);
+		shift = pcp_pin_shift_reg_get(pcp, pin_index);
+
+		mask = pcp->fmask << shift;
+		val = (func_sel << shift) & mask;
+
+		vals[found].reg = pcp->base_offset + offset;
+		vals[found].val = val;
+		vals[found].mask = mask;
+
+		dev_dbg(pcp->dev,
+			 "%pOFn pin: %u offset: 0x%x func: 0x%x val: 0x%x mask: 0x%x\n",
+			 pinctrl_spec.np, pin_index, offset,
+			 func_sel, val, mask);
+
+		pins[found++] = pin_index;
+	}
+
+	pgnames[0] = np->name;
+	mutex_lock(&pcp->mutex);
+	fsel = pcp_add_function(pcp, &function, np->name, vals, found,
+				pgnames, 1);
+	if (fsel < 0) {
+		res = fsel;
+		goto free_pins;
+	}
+
+	gsel = pinctrl_generic_add_group(pcp->pctl, np->name, pins, found, pcp);
+	if (gsel < 0) {
+		res = gsel;
+		goto free_function;
+	}
+
+	(*map)->type = PIN_MAP_TYPE_MUX_GROUP;
+	(*map)->data.mux.group = np->name;
+	(*map)->data.mux.function = np->name;
+
+	if (PCP_HAS_PINCONF && function) {
+		res = pcp_parse_pinconf(pcp, np, function, map);
+		if (res == 0)
+			*num_maps = 2;
+		else if (res == -EOPNOTSUPP)
+			*num_maps = 1;
+		else
+			goto free_pingroups;
+	} else {
+		*num_maps = 1;
+	}
+	mutex_unlock(&pcp->mutex);
+
+	return 0;
+
+free_pingroups:
+	pinctrl_generic_remove_group(pcp->pctl, gsel);
+	*num_maps = 1;
+free_function:
+	pinmux_generic_remove_function(pcp->pctl, fsel);
+free_pins:
+	mutex_unlock(&pcp->mutex);
+	devm_kfree(pcp->dev, pins);
+
+free_vals:
+	devm_kfree(pcp->dev, vals);
+
+	return res;
+}
+
+/**
+ * pcp_dt_node_to_map() - allocates and parses pinctrl maps
+ * @pctldev: pinctrl instance
+ * @np_config: device tree pinmux entry
+ * @map: array of map entries
+ * @num_maps: number of maps
+ */
+static int pcp_dt_node_to_map(struct pinctrl_dev *pctldev,
+			      struct device_node *np_config,
+			      struct pinctrl_map **map, unsigned int *num_maps)
+{
+	struct pcp_device *pcp;
+	const char **pgnames;
+	int ret;
+
+	pcp = pinctrl_dev_get_drvdata(pctldev);
+	/* create 2 maps. One is for pinmux, and the other is for pinconf. */
+	*map = devm_kcalloc(pcp->dev, 2, sizeof(**map), GFP_KERNEL);
+	if (!*map)
+		return -ENOMEM;
+
+	*num_maps = 0;
+
+	pgnames = devm_kzalloc(pcp->dev, sizeof(*pgnames), GFP_KERNEL);
+	if (!pgnames) {
+		ret = -ENOMEM;
+		goto free_map;
+	}
+
+	if (of_find_property(np_config, "pinctrl-packed,pins", NULL)) {
+		ret = pcp_parse_one_pinctrl_entry(pcp, np_config, map, num_maps,
+						  pgnames);
+	} else {
+		ret = -EINVAL;
+	}
+
+	if (ret < 0) {
+		dev_err(pcp->dev, "no pins entries for %pOFn\n", np_config);
+		goto free_pgnames;
+	}
+
+	return 0;
+
+free_pgnames:
+	devm_kfree(pcp->dev, pgnames);
+free_map:
+	devm_kfree(pcp->dev, *map);
+	return ret;
+}
+
+static int pcp_add_gpio_func(struct device_node *node, struct pcp_device *pcp)
+{
+	const char *cellname = "#pinctrl-packed,gpio-range-cells";
+	const char *propname = "pinctrl-packed,gpio-range";
+	struct pcp_gpiofunc_range *range;
+	struct of_phandle_args gpiospec;
+	int ret, i;
+
+	for (i = 0; ; i++) {
+		ret = of_parse_phandle_with_args(node, propname, cellname,
+						 i, &gpiospec);
+		/* Do not treat it as error. Only treat it as end condition. */
+		if (ret) {
+			ret = 0;
+			break;
+		}
+		range = devm_kzalloc(pcp->dev, sizeof(*range), GFP_KERNEL);
+		if (!range) {
+			ret = -ENOMEM;
+			break;
+		}
+		range->offset = gpiospec.args[0];
+		range->npins = gpiospec.args[1];
+		range->gpiofunc = gpiospec.args[2];
+		mutex_lock(&pcp->mutex);
+		list_add_tail(&range->node, &pcp->gpiofuncs);
+		mutex_unlock(&pcp->mutex);
+	}
+	return ret;
+}
+
+static int pcp_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct resource res, parent_res;
+	const struct pcp_soc_data *soc;
+	struct pcp_device *pcp;
+	int ret;
+
+	soc = of_device_get_match_data(&pdev->dev);
+	if (WARN_ON(!soc))
+		return -EINVAL;
+
+	pcp = devm_kzalloc(&pdev->dev, sizeof(*pcp), GFP_KERNEL);
+	if (!pcp)
+		return -ENOMEM;
+
+	pcp->dev = &pdev->dev;
+	pcp->np = np;
+	mutex_init(&pcp->mutex);
+	INIT_LIST_HEAD(&pcp->gpiofuncs);
+	pcp->flags = soc->flags;
+
+	pcp->regmap = syscon_node_to_regmap(np->parent);
+	if (IS_ERR(pcp->regmap)) {
+		dev_err(pcp->dev, "could not get regmap from parent\n");
+		return PTR_ERR(pcp->regmap);
+	}
+	pcp->width = regmap_get_reg_stride(pcp->regmap) * BITS_PER_BYTE;
+
+	ret = of_address_to_resource(np, 0, &res);
+	if (ret) {
+		dev_err(pcp->dev, "could not get resource from node\n");
+		return ret;
+	}
+
+	ret = of_address_to_resource(np->parent, 0, &parent_res);
+	if (ret) {
+		dev_err(pcp->dev,
+			"could not get resource from parent syscon\n");
+		return ret;
+	}
+
+	pcp->base_offset = res.start - parent_res.start;
+	pcp->size = resource_size(&res);
+
+	ret = of_property_read_u32(np, "pinctrl-packed,function-mask",
+				   &pcp->fmask);
+	if (ret) {
+		dev_err(pcp->dev, "function mask property not specified\n");
+		return ret;
+	}
+	platform_set_drvdata(pdev, pcp);
+
+	pcp->desc.name = dev_name(&pdev->dev);
+	pcp->desc.pctlops = &pcp_pinctrl_ops;
+	pcp->desc.pmxops = &pcp_pinmux_ops;
+	if (PCP_HAS_PINCONF)
+		pcp->desc.confops = &pcp_pinconf_ops;
+	pcp->desc.owner = THIS_MODULE;
+
+	ret = pcp_allocate_pin_table(pcp);
+	if (ret < 0) {
+		dev_err(pcp->dev, "failed to allocate pin table\n");
+		return ret;
+	}
+
+	ret = devm_pinctrl_register_and_init(pcp->dev, &pcp->desc, pcp, &pcp->pctl);
+	if (ret) {
+		dev_err(pcp->dev, "could not register packed pinctrl driver\n");
+		return ret;
+	}
+
+	ret = pcp_add_gpio_func(np, pcp);
+	if (ret < 0) {
+		dev_err(pcp->dev, "failed to add gpio functions\n");
+		return ret;
+	}
+
+	dev_info(pcp->dev, "%i pins registered\n", pcp->desc.npins);
+
+	ret = pinctrl_enable(pcp->pctl);
+	if (ret) {
+		dev_err(pcp->dev, "failed to enable pinctrl\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct pcp_soc_data pinctrl_data = {
+};
+static const struct pcp_soc_data pinconf_data = {
+	.flags = PCP_FEAT_PINCONF,
+};
+
+static const struct of_device_id pcp_of_match[] = {
+	{ .compatible = "pinctrl-packed", .data = &pinctrl_data },
+	{ .compatible = "pinconf-packed", .data = &pinconf_data },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, pcp_of_match);
+
+static struct platform_driver pcp_driver = {
+	.probe		= pcp_probe,
+	.driver = {
+		.name		= DRIVER_NAME,
+		.of_match_table	= pcp_of_match,
+	},
+};
+
+module_platform_driver(pcp_driver);
+
+MODULE_AUTHOR("Tony Lindgren <tony at atomide.com>");
+MODULE_AUTHOR("Billy Tsai <billy_tsai at aspeedtech.com>");
+MODULE_DESCRIPTION("packed-field type device tree based pinctrl driver");
+MODULE_LICENSE("GPL");

-- 
2.34.1




More information about the linux-arm-kernel mailing list