[RFC,4/9] pinctrl: Add Multifunction USB Device pinctrl driver
diff mbox series

Message ID 20200216172117.49832-5-noralf@tronnes.org
State New
Headers show
Series
  • Regmap over USB for Multifunction USB Device (gpio, display, ...)
Related show

Commit Message

Noralf Trønnes Feb. 16, 2020, 5:21 p.m. UTC
The Multifunction USB Device has optional support for gpio and pin
configuration. Interrupts are supported if the device supports it.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/pinctrl/Kconfig       |   9 +
 drivers/pinctrl/Makefile      |   1 +
 drivers/pinctrl/pinctrl-mud.c | 657 ++++++++++++++++++++++++++++++++++
 drivers/pinctrl/pinctrl-mud.h |  89 +++++
 4 files changed, 756 insertions(+)
 create mode 100644 drivers/pinctrl/pinctrl-mud.c
 create mode 100644 drivers/pinctrl/pinctrl-mud.h

Patch
diff mbox series

diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index df0ef69dd474..ee3532c64411 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -384,6 +384,15 @@  config PINCTRL_OCELOT
 	select OF_GPIO
 	select REGMAP_MMIO
 
+config PINCTRL_MUD
+	tristate "Multifunction USB Device pinctrl driver"
+	depends on MFD_MUD
+	select GENERIC_PINCONF
+	select GPIOLIB
+	select GPIOLIB_IRQCHIP
+	help
+	  Support for GPIOs on Multifunction USB Devices.
+
 source "drivers/pinctrl/actions/Kconfig"
 source "drivers/pinctrl/aspeed/Kconfig"
 source "drivers/pinctrl/bcm/Kconfig"
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index 879f312bfb75..782cc7f286b7 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -47,6 +47,7 @@  obj-$(CONFIG_PINCTRL_INGENIC)	+= pinctrl-ingenic.o
 obj-$(CONFIG_PINCTRL_RK805)	+= pinctrl-rk805.o
 obj-$(CONFIG_PINCTRL_OCELOT)	+= pinctrl-ocelot.o
 obj-$(CONFIG_PINCTRL_EQUILIBRIUM)   += pinctrl-equilibrium.o
+obj-$(CONFIG_PINCTRL_MUD)	+= pinctrl-mud.o
 
 obj-y				+= actions/
 obj-$(CONFIG_ARCH_ASPEED)	+= aspeed/
diff --git a/drivers/pinctrl/pinctrl-mud.c b/drivers/pinctrl/pinctrl-mud.c
new file mode 100644
index 000000000000..f890c8e68755
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-mud.c
@@ -0,0 +1,657 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2020 Noralf Trønnes
+ */
+
+#include <linux/bitmap.h>
+#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/mfd/mud.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pinctrl/pinconf.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/regmap.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+
+#include "core.h"
+#include "pinconf.h"
+#include "pinmux.h"
+#include "pinctrl-utils.h"
+
+#include "pinctrl-mud.h"
+
+/* Temporary debugging aid */
+static unsigned int debug = 8;
+
+#define pdebug(level, fmt, ...)				\
+do {							\
+	if ((level) <= debug)				\
+		printk(KERN_DEBUG fmt, ##__VA_ARGS__);	\
+} while (0)
+
+struct mud_pinctrl_pin {
+	unsigned int irq_types;
+	unsigned int irq_type;
+	bool irq_enabled;
+};
+
+struct mud_pinctrl {
+	struct device *dev;
+	struct regmap *regmap;
+	struct mud_pinctrl_pin *pins;
+	struct pinctrl_dev *pctl_dev;
+	struct pinctrl_desc pctl_desc;
+	struct gpio_chip gpio_chip;
+	struct irq_chip irq_chip;
+	struct mutex irqlock; /* IRQ bus lock */
+};
+
+static unsigned int mud_pinctrl_pin_reg(unsigned int pin, unsigned int offset)
+{
+	return MUD_PINCTRL_REG_PIN_BASE + (pin * MUD_PINCTRL_PIN_BLOCK_SIZE) + offset;
+}
+
+static int mud_pinctrl_pin_read_reg(struct mud_pinctrl *pctl, unsigned int pin,
+				    unsigned int offset, unsigned int *val)
+{
+	return regmap_read(pctl->regmap, mud_pinctrl_pin_reg(pin, offset), val);
+}
+
+static int mud_pinctrl_pin_write_reg(struct mud_pinctrl *pctl, unsigned int pin,
+				     unsigned int offset, unsigned int val)
+{
+	return regmap_write(pctl->regmap, mud_pinctrl_pin_reg(pin, offset), val);
+}
+
+static int mud_pinctrl_read_bitmap(struct mud_pinctrl *pctl, unsigned int reg,
+				   unsigned long *bitmap, unsigned int nbits)
+{
+	unsigned int nregs = DIV_ROUND_UP(nbits, 32);
+	u32 *vals;
+	int ret;
+
+	vals = kmalloc_array(nregs, sizeof(*vals), GFP_KERNEL);
+	if (!vals)
+		return -ENOMEM;
+
+	ret = regmap_bulk_read(pctl->regmap, reg, vals, nregs);
+	if (ret)
+		goto free;
+
+	bitmap_from_arr32(bitmap, vals, nbits);
+free:
+	kfree(vals);
+
+	return ret;
+}
+
+static int mud_pinctrl_gpio_request(struct gpio_chip *gc, unsigned int offset)
+{
+	struct mud_pinctrl *pctl = gpiochip_get_data(gc);
+	int ret;
+
+	pdebug(1, "%s: offset=%u\n", __func__, offset);
+
+	ret = mud_pinctrl_pin_write_reg(pctl, offset, MUD_PIN_GPIO_REQUEST, 1);
+	if (ret == -EBUSY) {
+		dev_err(pctl->dev,
+			"pin %u is claimed by another function on the USB device\n",
+			offset);
+		ret = -EINVAL; /* follow pinmux.c:pin_request() */
+	}
+
+	return ret;
+}
+
+static void mud_pinctrl_gpio_free(struct gpio_chip *gc, unsigned int offset)
+{
+	struct mud_pinctrl *pctl = gpiochip_get_data(gc);
+
+	pdebug(1, "%s: offset=%u\n", __func__, offset);
+
+	mud_pinctrl_pin_write_reg(pctl, offset, MUD_PIN_GPIO_FREE, 1);
+}
+
+static int mud_pinctrl_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+	struct mud_pinctrl *pctl = gpiochip_get_data(gc);
+	unsigned int value;
+	int ret;
+
+	ret = mud_pinctrl_pin_read_reg(pctl, offset, MUD_PIN_LEVEL, &value);
+
+	return ret ? ret : value;
+}
+
+static void mud_pinctrl_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
+{
+	struct mud_pinctrl *pctl = gpiochip_get_data(gc);
+	int ret;
+
+	ret = mud_pinctrl_pin_write_reg(pctl, offset, MUD_PIN_LEVEL, value);
+	if (ret)
+		dev_err_once(pctl->dev, "Failed to set gpio output, error=%d\n", ret);
+}
+
+/* FIXME: Remove this comment when settled: .get_direction returns 0=out, 1=in */
+static int mud_pinctrl_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+	struct mud_pinctrl *pctl = gpiochip_get_data(gc);
+	unsigned int val;
+	int ret;
+
+	ret = mud_pinctrl_pin_read_reg(pctl, offset, MUD_PIN_DIRECTION, &val);
+
+	return ret ? ret : val;
+}
+
+static int mud_pinctrl_gpio_direction_input(struct gpio_chip *gc, unsigned int offset)
+{
+	struct mud_pinctrl *pctl = gpiochip_get_data(gc);
+	int ret;
+
+	ret = mud_pinctrl_pin_write_reg(pctl, offset, MUD_PIN_DIRECTION,
+					MUD_PIN_DIRECTION_INPUT);
+	if (ret == -ENOTSUPP) {
+		dev_err(pctl->dev, "pin %u can't be used as an input\n", offset);
+		ret = -EIO; /* gpiolib uses this error code */
+	}
+
+	return ret;
+}
+
+static int mud_pinctrl_gpio_direction_output(struct gpio_chip *gc, unsigned int offset, int value)
+{
+	struct mud_pinctrl *pctl = gpiochip_get_data(gc);
+	unsigned int regval;
+	int ret;
+
+	regval = value ? MUD_PIN_DIRECTION_OUTPUT_HIGH : MUD_PIN_DIRECTION_OUTPUT_LOW;
+
+	ret = mud_pinctrl_pin_write_reg(pctl, offset, MUD_PIN_DIRECTION, regval);
+	if (ret == -ENOTSUPP) {
+		dev_err(pctl->dev, "pin %u can't be used as an output\n", offset);
+		ret = -EIO;
+	}
+
+	return ret;
+}
+
+/* The pinctrl pin number space and the gpio hwpin (offset) numberspace are identical. */
+static int mud_pinctrl_gpio_add_pin_ranges(struct gpio_chip *gc)
+{
+	struct mud_pinctrl *pctl = gpiochip_get_data(gc);
+
+	return gpiochip_add_pin_range(&pctl->gpio_chip, dev_name(pctl->dev),
+				      pctl->pctl_desc.pins->number,
+				      pctl->pctl_desc.pins->number,
+				      pctl->pctl_desc.npins);
+}
+
+static const struct gpio_chip mud_pinctrl_gpio_chip = {
+	.label			= "mud-pins",
+	.request		= mud_pinctrl_gpio_request,
+	.free			= mud_pinctrl_gpio_free,
+	.get_direction		= mud_pinctrl_gpio_get_direction,
+	.direction_input	= mud_pinctrl_gpio_direction_input,
+	.direction_output	= mud_pinctrl_gpio_direction_output,
+	.get			= mud_pinctrl_gpio_get,
+	.set			= mud_pinctrl_gpio_set,
+	.set_config		= gpiochip_generic_config,
+	.add_pin_ranges		= mud_pinctrl_gpio_add_pin_ranges,
+	.base			= -1,
+	.can_sleep		= true,
+};
+
+/* enum pin_config_param is not ABI so: */
+static const u8 mud_pin_config_param_table[] = {
+	[PIN_CONFIG_BIAS_BUS_HOLD] = MUD_PIN_CONFIG_BIAS_BUS_HOLD,
+	[PIN_CONFIG_BIAS_DISABLE] = MUD_PIN_CONFIG_BIAS_DISABLE,
+	[PIN_CONFIG_BIAS_HIGH_IMPEDANCE] = MUD_PIN_CONFIG_BIAS_HIGH_IMPEDANCE,
+	[PIN_CONFIG_BIAS_PULL_DOWN] = MUD_PIN_CONFIG_BIAS_PULL_DOWN,
+	[PIN_CONFIG_BIAS_PULL_PIN_DEFAULT] = MUD_PIN_CONFIG_BIAS_PULL_PIN_DEFAULT,
+	[PIN_CONFIG_BIAS_PULL_UP] = MUD_PIN_CONFIG_BIAS_PULL_UP,
+	[PIN_CONFIG_DRIVE_OPEN_DRAIN] = MUD_PIN_CONFIG_DRIVE_OPEN_DRAIN,
+	[PIN_CONFIG_DRIVE_OPEN_SOURCE] = MUD_PIN_CONFIG_DRIVE_OPEN_SOURCE,
+	[PIN_CONFIG_DRIVE_PUSH_PULL] = MUD_PIN_CONFIG_DRIVE_PUSH_PULL,
+	[PIN_CONFIG_DRIVE_STRENGTH] = MUD_PIN_CONFIG_DRIVE_STRENGTH,
+	[PIN_CONFIG_DRIVE_STRENGTH_UA] = MUD_PIN_CONFIG_DRIVE_STRENGTH_UA,
+	[PIN_CONFIG_INPUT_DEBOUNCE] = MUD_PIN_CONFIG_INPUT_DEBOUNCE,
+	[PIN_CONFIG_INPUT_ENABLE] = MUD_PIN_CONFIG_INPUT_ENABLE,
+	[PIN_CONFIG_INPUT_SCHMITT] = MUD_PIN_CONFIG_INPUT_SCHMITT,
+	[PIN_CONFIG_INPUT_SCHMITT_ENABLE] = MUD_PIN_CONFIG_INPUT_SCHMITT_ENABLE,
+	[PIN_CONFIG_LOW_POWER_MODE] = MUD_PIN_CONFIG_LOW_POWER_MODE,
+	[PIN_CONFIG_OUTPUT_ENABLE] = MUD_PIN_CONFIG_OUTPUT_ENABLE,
+	[PIN_CONFIG_OUTPUT] = MUD_PIN_CONFIG_OUTPUT,
+	[PIN_CONFIG_POWER_SOURCE] = MUD_PIN_CONFIG_POWER_SOURCE,
+	[PIN_CONFIG_SLEEP_HARDWARE_STATE] = MUD_PIN_CONFIG_SLEEP_HARDWARE_STATE,
+	[PIN_CONFIG_SLEW_RATE] = MUD_PIN_CONFIG_SLEW_RATE,
+	[PIN_CONFIG_SKEW_DELAY] = MUD_PIN_CONFIG_SKEW_DELAY,
+	[PIN_CONFIG_PERSIST_STATE] = MUD_PIN_CONFIG_PERSIST_STATE,
+};
+
+static int mud_pinctrl_pinconf_get(struct pinctrl_dev *pctldev,
+				   unsigned int pin, unsigned long *config)
+{
+	enum pin_config_param param = pinconf_to_config_param(*config);
+	struct mud_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
+	unsigned int arg, offset = mud_pin_config_param_table[param];
+	int ret;
+
+	ret = mud_pinctrl_pin_read_reg(pctl, pin, offset, &arg);
+	if (ret)
+		return ret;
+
+	*config = pinconf_to_config_packed(param, arg);
+
+	return 0;
+}
+
+static int mud_pinctrl_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin,
+				   unsigned long *configs, unsigned int num_configs)
+{
+	struct mud_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
+	enum pin_config_param param;
+	unsigned int i, offset, arg;
+	int ret;
+
+	for (i = 0; i < num_configs; i++) {
+		param = pinconf_to_config_param(configs[i]);
+		arg = pinconf_to_config_argument(configs[i]);
+		offset = mud_pin_config_param_table[param];
+
+		ret = mud_pinctrl_pin_write_reg(pctl, pin, offset, arg);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static const struct pinconf_ops mud_pinconf_ops = {
+	.is_generic			= true,
+	.pin_config_get			= mud_pinctrl_pinconf_get,
+	.pin_config_set			= mud_pinctrl_pinconf_set,
+	.pin_config_config_dbg_show	= pinconf_generic_dump_config,
+};
+
+static int mud_pinctrl_get_groups_count(struct pinctrl_dev *pctldev)
+{
+	return 0;
+}
+
+static const char *mud_pinctrl_get_group_name(struct pinctrl_dev *pctldev,
+					      unsigned int selector)
+{
+	return NULL;
+}
+
+static int mud_pinctrl_get_group_pins(struct pinctrl_dev *pctldev,
+				      unsigned int selector,
+				      const unsigned int **pins,
+				      unsigned int *num_pins)
+{
+	return -ENOTSUPP;
+}
+
+static void mud_pinctrl_pin_dbg_show(struct pinctrl_dev *pctldev,
+				     struct seq_file *s, unsigned int offset)
+{
+	struct mud_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
+	struct pinctrl_gpio_range *range;
+	int dir, val;
+
+	range = pinctrl_find_gpio_range_from_pin_nolock(pctldev, offset);
+	if (!range)
+		return;
+
+	dir = mud_pinctrl_gpio_get_direction(&pctl->gpio_chip, offset);
+	if (dir < 0)
+		return;
+
+	val = mud_pinctrl_gpio_get(&pctl->gpio_chip, offset);
+	if (val < 0)
+		return;
+
+	seq_printf(s, "function gpio_%s %s", dir ? "in" : "out", val ? "hi" : "lo");
+}
+
+static const struct pinctrl_ops mud_pinctrl_ops = {
+	.get_groups_count	= mud_pinctrl_get_groups_count,
+	.get_group_name		= mud_pinctrl_get_group_name,
+	.get_group_pins		= mud_pinctrl_get_group_pins,
+	.pin_dbg_show		= mud_pinctrl_pin_dbg_show,
+	.dt_node_to_map		= pinconf_generic_dt_node_to_map_pin,
+	.dt_free_map		= pinconf_generic_dt_free_map,
+};
+
+static const struct pinctrl_desc mud_pinctrl_pinctrl_desc = {
+	.owner		= THIS_MODULE,
+	.name		= "mud-pins",
+	.pctlops	= &mud_pinctrl_ops,
+	.confops	= &mud_pinconf_ops,
+};
+
+static void mud_pinctrl_irq_init_valid_mask(struct gpio_chip *gc,
+					    unsigned long *valid_mask,
+					    unsigned int ngpios)
+{
+	struct mud_pinctrl *pctl = gpiochip_get_data(gc);
+	unsigned int i;
+
+	pdebug(1, "%s: valid_mask: %*pb\n", __func__, ngpios, valid_mask);
+
+	for (i = 0; i < ngpios; i++) {
+		if (!pctl->pins[i].irq_types)
+			clear_bit(i, valid_mask);
+	}
+
+	pdebug(1, "%s: valid_mask: %*pb\n", __func__, ngpios, valid_mask);
+}
+
+static void mud_pinctrl_irq_enable(struct irq_data *data)
+{
+	struct gpio_chip *gpio_chip = irq_data_get_irq_chip_data(data);
+	struct mud_pinctrl *pctl = gpiochip_get_data(gpio_chip);
+
+	pdebug(2, "%s: hwirq=%lu\n", __func__, data->hwirq);
+
+	pctl->pins[data->hwirq].irq_enabled = true;
+}
+
+static void mud_pinctrl_irq_disable(struct irq_data *data)
+{
+	struct gpio_chip *gpio_chip = irq_data_get_irq_chip_data(data);
+	struct mud_pinctrl *pctl = gpiochip_get_data(gpio_chip);
+
+	pdebug(2, "%s: hwirq=%lu\n", __func__, data->hwirq);
+
+	pctl->pins[data->hwirq].irq_enabled = false;
+}
+
+static int mud_pinctrl_irq_set_type(struct irq_data *data, unsigned int type)
+{
+	struct gpio_chip *gpio_chip = irq_data_get_irq_chip_data(data);
+	struct mud_pinctrl *pctl = gpiochip_get_data(gpio_chip);
+
+	pdebug(1, "%s: hwirq=%lu, type=%u\n", __func__, data->hwirq, type);
+
+	if (type == IRQ_TYPE_NONE)
+		return -EINVAL;
+
+	if ((pctl->pins[data->hwirq].irq_types & type) == type)
+		pctl->pins[data->hwirq].irq_type = type;
+
+	return 0;
+}
+
+static void mud_pinctrl_irq_bus_lock(struct irq_data *data)
+{
+	struct gpio_chip *gpio_chip = irq_data_get_irq_chip_data(data);
+	struct mud_pinctrl *pctl = gpiochip_get_data(gpio_chip);
+
+	pdebug(3, "%s: hwirq=%lu\n", __func__, data->hwirq);
+
+	mutex_lock(&pctl->irqlock);
+}
+
+static void mud_pinctrl_irq_bus_sync_unlock(struct irq_data *data)
+{
+	struct gpio_chip *gpio_chip = irq_data_get_irq_chip_data(data);
+	struct mud_pinctrl *pctl = gpiochip_get_data(gpio_chip);
+	unsigned int reg;
+	u32 vals[2];
+	int ret;
+
+	pdebug(3, "%s: hwirq=%lu: irq_enabled=%u irq_type=%u\n", __func__,
+	       data->hwirq, pctl->pins[data->hwirq].irq_enabled,
+	       pctl->pins[data->hwirq].irq_type);
+
+	switch (pctl->pins[data->hwirq].irq_type) {
+	case IRQ_TYPE_EDGE_RISING:
+		vals[0] = MUD_PIN_IRQ_TYPE_EDGE_RISING;
+		break;
+	case IRQ_TYPE_EDGE_FALLING:
+		vals[0] = MUD_PIN_IRQ_TYPE_EDGE_FALLING;
+		break;
+	case IRQ_TYPE_EDGE_BOTH:
+		vals[0] = MUD_PIN_IRQ_TYPE_EDGE_BOTH;
+		break;
+	default:
+		vals[0] = MUD_PIN_IRQ_TYPE_NONE;
+		break;
+	};
+
+	vals[1] = pctl->pins[data->hwirq].irq_enabled;
+	reg = mud_pinctrl_pin_reg(data->hwirq, MUD_PIN_IRQ_TYPE);
+
+	/* It's safe to use a stack allocated array because bulk_write does kmemdup */
+	ret = regmap_bulk_write(pctl->regmap, reg, vals, 2);
+	if (ret)
+		dev_err_once(pctl->dev, "Failed to sync irq data, error=%d\n", ret);
+
+	mutex_unlock(&pctl->irqlock);
+}
+
+static const struct irq_chip mud_pinctrl_irq_chip = {
+	.name			= "mud-pins",
+	.irq_enable		= mud_pinctrl_irq_enable,
+	.irq_disable		= mud_pinctrl_irq_disable,
+	.irq_set_type		= mud_pinctrl_irq_set_type,
+	.irq_bus_lock		= mud_pinctrl_irq_bus_lock,
+	.irq_bus_sync_unlock	= mud_pinctrl_irq_bus_sync_unlock,
+};
+
+static irqreturn_t mud_pinctrl_irq_thread_fn(int irq, void *dev_id)
+{
+	struct mud_pinctrl *pctl = (struct mud_pinctrl *)dev_id;
+	DECLARE_BITMAP(status, MUD_PINCTRL_MAX_NUM_PINS);
+	struct gpio_chip *gc = &pctl->gpio_chip;
+	unsigned long n;
+	int ret;
+
+	ret = mud_pinctrl_read_bitmap(pctl, MUD_PINCTRL_REG_IRQ_STATUS,
+				      status, gc->ngpio);
+	if (ret)
+		return IRQ_NONE;
+
+	pdebug(3, "%s: STATUS: %*pb\n", __func__, gc->ngpio, status);
+
+	for_each_set_bit(n, status, gc->ngpio) {
+		unsigned int irq = irq_find_mapping(gc->irq.domain, n);
+
+		pdebug(2, "%s: IRQ on pin %lu irq=%u enabled=%u\n", __func__,
+		       n, irq, pctl->pins[n].irq_enabled);
+
+		if (irq && pctl->pins[n].irq_enabled)
+			handle_nested_irq(irq);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static const struct regmap_config mud_pinctrl_regmap_config = {
+	.reg_bits = 32,
+	.val_bits = 32,
+	/* FIXME: Setup caching? */
+	.cache_type = REGCACHE_NONE,
+};
+
+static int mud_pinctrl_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct mud_cell_pdata *pdata = dev_get_platdata(dev);
+	struct pinctrl_pin_desc *pdesc;
+	struct mud_pinctrl *pctl;
+	struct regmap *regmap;
+	unsigned int i, reg, npins;
+	const char **names;
+	int ret, irq;
+
+	pdebug(1, "%s: dev->of_node=%px\n", __func__, dev->of_node);
+
+	pctl = devm_kzalloc(dev, sizeof(*pctl), GFP_KERNEL);
+	if (!pctl)
+		return -ENOMEM;
+
+	pctl->dev = dev;
+
+	mutex_init(&pctl->irqlock);
+
+	regmap = devm_regmap_init_usb(dev, pdata->interface, pdata->index,
+				      &mud_pinctrl_regmap_config);
+	if (IS_ERR(regmap))
+		return PTR_ERR(regmap);
+
+	pctl->regmap = regmap;
+
+	ret = regmap_read(regmap, MUD_PINCTRL_REG_NUM_PINS, &npins);
+	if (ret) {
+		dev_err(pctl->dev, "Failed to read from device\n");
+		return ret;
+	}
+	if (!npins || npins > MUD_PINCTRL_MAX_NUM_PINS)
+		return -EINVAL;
+
+	pctl->pins = devm_kcalloc(dev, npins, sizeof(*pctl->pins), GFP_KERNEL);
+	if (!pctl->pins)
+		return -ENOMEM;
+
+	pdesc = devm_kcalloc(dev, npins, sizeof(*pdesc), GFP_KERNEL);
+	if (!pdesc)
+		return -ENOMEM;
+
+	pctl->pctl_desc = mud_pinctrl_pinctrl_desc;
+	pctl->pctl_desc.pins = pdesc;
+	pctl->pctl_desc.npins = npins;
+
+	for (i = 0; i < npins; i++, pdesc++) {
+		char *name;
+
+		name = devm_kmalloc(dev, MUD_PIN_NAME_LEN, GFP_KERNEL);
+		if (!name)
+			return -ENOMEM;
+
+		pdesc->number = i;
+		pdesc->name = name;
+		reg = mud_pinctrl_pin_reg(i, MUD_PIN_NAME);
+		ret = regmap_raw_read(regmap, reg, name, MUD_PIN_NAME_LEN);
+		if (ret) {
+			dev_err(pctl->dev, "Failed to read name for pin %u\n", i);
+			return ret;
+		}
+		if (!name[0] || name[MUD_PIN_NAME_LEN - 1]) {
+			dev_err(pctl->dev, "Illegal name for pin %u\n", i);
+			return -EINVAL;
+		}
+	}
+
+	ret = devm_pinctrl_register_and_init(dev, &pctl->pctl_desc,
+					     pctl, &pctl->pctl_dev);
+	if (ret) {
+		dev_err(pctl->dev, "pinctrl registration failed\n");
+		return ret;
+	}
+
+	ret = pinctrl_enable(pctl->pctl_dev);
+	if (ret) {
+		dev_err(pctl->dev, "pinctrl enable failed\n");
+		return ret;
+	}
+
+	irq = platform_get_irq_optional(pdev, 0);
+	pdebug(1, "%s: irq=%d\n", __func__, irq);
+	if (irq > 0) {
+		bool use_irq = false;
+
+		for (i = 0; i < npins; i++) {
+			reg = mud_pinctrl_pin_reg(i, MUD_PIN_IRQ_TYPES);
+			ret = regmap_read(regmap, reg, &pctl->pins[i].irq_types);
+			if (ret) {
+				dev_err(dev, "Failed to read irq type for pin %u\n", i);
+				return ret;
+			}
+			pdebug(1, "%s: pctl->pins[%u].irq_types=%u\n", __func__,
+			       i, pctl->pins[i].irq_types);
+			if (pctl->pins[i].irq_types)
+				use_irq = true;
+		}
+
+		if (!use_irq)
+			irq = 0;
+	} else {
+		irq = 0;
+	}
+
+	pctl->gpio_chip = mud_pinctrl_gpio_chip;
+	pctl->gpio_chip.parent = dev;
+	pctl->gpio_chip.ngpio = npins;
+	if (irq)
+		pctl->gpio_chip.irq.init_valid_mask = mud_pinctrl_irq_init_valid_mask;
+
+	names = devm_kcalloc(dev, npins, sizeof(*names), GFP_KERNEL);
+	if (!names)
+		return -ENOMEM;
+
+	pctl->gpio_chip.names = names;
+
+	for (i = 0; i < npins; i++)
+		names[i] = pctl->pctl_desc.pins[i].name;
+
+	ret = devm_gpiochip_add_data(dev, &pctl->gpio_chip, pctl);
+	if (ret) {
+		dev_err(dev, "Could not register gpiochip, %d\n", ret);
+		return ret;
+	}
+
+	if (irq) {
+		pctl->irq_chip = mud_pinctrl_irq_chip;
+		ret = gpiochip_irqchip_add_nested(&pctl->gpio_chip, &pctl->irq_chip,
+						  0, handle_bad_irq, IRQ_TYPE_NONE);
+		if (ret) {
+			dev_err(dev, "Cannot add irqchip to gpiochip\n");
+			return ret;
+		}
+
+		ret = devm_request_threaded_irq(dev, irq, NULL,
+						mud_pinctrl_irq_thread_fn,
+						IRQF_ONESHOT, "mud-pins", pctl);
+		if (ret) {
+			dev_err(dev, "Cannot request irq%d\n", irq);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static const struct of_device_id mud_pinctrl_of_match[] = {
+	{ .compatible = "mud-pins", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, mud_pinctrl_of_match);
+
+static const struct platform_device_id mud_pinctrl_id_table[] = {
+	{ "mud-pins", },
+	{ },
+};
+MODULE_DEVICE_TABLE(platform, mud_pinctrl_id_table);
+
+static struct platform_driver mud_pinctrl_driver = {
+	.driver = {
+		.name = "mud-pins",
+		.of_match_table = mud_pinctrl_of_match,
+	},
+	.probe = mud_pinctrl_probe,
+	.id_table = mud_pinctrl_id_table,
+};
+
+module_platform_driver(mud_pinctrl_driver);
+
+MODULE_AUTHOR("Noralf Trønnes");
+MODULE_DESCRIPTION("Pin control interface for Multifunction USB Device");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pinctrl/pinctrl-mud.h b/drivers/pinctrl/pinctrl-mud.h
new file mode 100644
index 000000000000..f4839da46bda
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-mud.h
@@ -0,0 +1,89 @@ 
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright 2020 Noralf Trønnes
+ */
+
+#ifndef __LINUX_PINCTRL_MUD_H
+#define __LINUX_PINCTRL_MUD_H
+
+#define MUD_PINCTRL_MAX_NUM_PINS		512
+
+#define MUD_PINCTRL_REG_NUM_PINS		0x0000
+
+#define MUD_PINCTRL_REG_IRQ_STATUS		0x0020
+#define MUD_PINCTRL_REG_IRQ_STATUS_END		0x002f
+
+#define MUD_PINCTRL_REG_PIN_BASE		0x0100
+#define MUD_PINCTRL_PIN_BLOCK_SIZE		256
+
+  /*
+   * The following are offsets into the pin register block.
+   *
+   * The first part is identical to enum pin_config_param except for:
+   * - No room for custom configurations.
+   *
+   * See the enum declaration docs in include/linux/pinctrl/pinconf-generic.h
+   * for details about behaviour and arguments.
+   *
+   * Device should return -ENOTSUPP if the config is not supported.
+   */
+  #define MUD_PIN_CONFIG_BIAS_BUS_HOLD		0x00
+  #define MUD_PIN_CONFIG_BIAS_DISABLE		0x01
+  #define MUD_PIN_CONFIG_BIAS_HIGH_IMPEDANCE	0x02
+  #define MUD_PIN_CONFIG_BIAS_PULL_DOWN		0x03
+  #define MUD_PIN_CONFIG_BIAS_PULL_PIN_DEFAULT	0x04
+  #define MUD_PIN_CONFIG_BIAS_PULL_UP		0x05
+  #define MUD_PIN_CONFIG_DRIVE_OPEN_DRAIN	0x06
+  #define MUD_PIN_CONFIG_DRIVE_OPEN_SOURCE	0x07
+  #define MUD_PIN_CONFIG_DRIVE_PUSH_PULL	0x08
+  #define MUD_PIN_CONFIG_DRIVE_STRENGTH		0x09
+  #define MUD_PIN_CONFIG_DRIVE_STRENGTH_UA	0x0a
+  #define MUD_PIN_CONFIG_INPUT_DEBOUNCE		0x0b
+  #define MUD_PIN_CONFIG_INPUT_ENABLE		0x0c
+  #define MUD_PIN_CONFIG_INPUT_SCHMITT		0x0d
+  #define MUD_PIN_CONFIG_INPUT_SCHMITT_ENABLE	0x0e
+  #define MUD_PIN_CONFIG_LOW_POWER_MODE		0x0f
+  #define MUD_PIN_CONFIG_OUTPUT_ENABLE		0x10
+  #define MUD_PIN_CONFIG_OUTPUT			0x11
+  #define MUD_PIN_CONFIG_POWER_SOURCE		0x12
+  #define MUD_PIN_CONFIG_SLEEP_HARDWARE_STATE	0x13
+  #define MUD_PIN_CONFIG_SLEW_RATE		0x14
+  #define MUD_PIN_CONFIG_SKEW_DELAY		0x15
+  #define MUD_PIN_CONFIG_PERSIST_STATE		0x16
+  #define MUD_PIN_CONFIG_END			0x7f
+
+  /* Must be NUL terminated */
+  #define MUD_PIN_NAME				0x80
+    #define MUD_PIN_NAME_LEN			16
+  #define MUD_PIN_NAME_END			0x83
+
+  /*
+   * Device should return:
+   *   -EBUSY if pin is in use by another function (i2c, spi, ...)
+   *   -ENOENT if there is no gpio function on the pin.
+   */
+  #define MUD_PIN_GPIO_REQUEST			0x84
+  #define MUD_PIN_GPIO_FREE			0x85
+
+  /* Device should return -ENOTSUPP if the direction is not supported */
+  #define MUD_PIN_DIRECTION			0x86
+    #define MUD_PIN_DIRECTION_OUTPUT		0x0
+      #define MUD_PIN_DIRECTION_OUTPUT_LOW	0x0
+      #define MUD_PIN_DIRECTION_OUTPUT_HIGH	0x2
+    #define MUD_PIN_DIRECTION_INPUT		0x1
+
+  #define MUD_PIN_LEVEL				0x87
+
+  /*
+   * Set _TYPES to _NONE is the pin doesn't have interrupt support.
+   * If all pins are _NONE, then interrupts are disabled in the host driver.
+   */
+  #define MUD_PIN_IRQ_TYPES			0x90
+    #define MUD_PIN_IRQ_TYPE_NONE		0x00
+    #define MUD_PIN_IRQ_TYPE_EDGE_RISING	0x01
+    #define MUD_PIN_IRQ_TYPE_EDGE_FALLING	0x02
+    #define MUD_PIN_IRQ_TYPE_EDGE_BOTH		0x03
+  #define MUD_PIN_IRQ_TYPE			0x91
+  #define MUD_PIN_IRQ_ENABLED			0x92
+
+#endif