All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Noralf Trønnes" <noralf@tronnes.org>
To: broonie@kernel.org, balbi@kernel.org, lee.jones@linaro.org
Cc: linux-usb@vger.kernel.org, dri-devel@lists.freedesktop.org,
	"Noralf Trønnes" <noralf@tronnes.org>
Subject: [RFC 5/9] usb: gadget: function: mud: Add gpio support
Date: Sun, 16 Feb 2020 18:21:13 +0100	[thread overview]
Message-ID: <20200216172117.49832-6-noralf@tronnes.org> (raw)
In-Reply-To: <20200216172117.49832-1-noralf@tronnes.org>

Add optional gpio functionality to the Multifunction USB Device.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/usb/gadget/Kconfig               |  14 +
 drivers/usb/gadget/function/Makefile     |   2 +
 drivers/usb/gadget/function/f_mud_pins.c | 962 +++++++++++++++++++++++
 3 files changed, 978 insertions(+)
 create mode 100644 drivers/usb/gadget/function/f_mud_pins.c

diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index 9551876ffe08..d6285146ec76 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -219,6 +219,9 @@ config USB_F_TCM
 config USB_F_MUD
 	tristate
 
+config USB_F_MUD_PINS
+	tristate
+
 # this first set of drivers all depend on bulk-capable hardware.
 
 config USB_CONFIGFS
@@ -493,6 +496,17 @@ menuconfig USB_CONFIGFS_F_MUD
 	help
 	  Core support for the Multifunction USB Device.
 
+if USB_F_MUD
+
+config USB_CONFIGFS_F_MUD_PINS
+	bool "Multifunction USB Device GPIO"
+	depends on PINCTRL
+	select USB_F_MUD_PINS
+	help
+	  GPIO support for the Multifunction USB Device.
+
+endif # USB_F_MUD
+
 choice
 	tristate "USB Gadget precomposed configurations"
 	default USB_ETH
diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile
index b6e31b511521..2e24227fcc12 100644
--- a/drivers/usb/gadget/function/Makefile
+++ b/drivers/usb/gadget/function/Makefile
@@ -52,3 +52,5 @@ usb_f_tcm-y			:= f_tcm.o
 obj-$(CONFIG_USB_F_TCM)		+= usb_f_tcm.o
 usb_f_mud-y			:= f_mud.o mud_regmap.o
 obj-$(CONFIG_USB_F_MUD)		+= usb_f_mud.o
+usb_f_mud_pins-y		:= f_mud_pins.o
+obj-$(CONFIG_USB_F_MUD_PINS)	+= usb_f_mud_pins.o
diff --git a/drivers/usb/gadget/function/f_mud_pins.c b/drivers/usb/gadget/function/f_mud_pins.c
new file mode 100644
index 000000000000..b3466804ad5e
--- /dev/null
+++ b/drivers/usb/gadget/function/f_mud_pins.c
@@ -0,0 +1,962 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2020 Noralf Trønnes
+ */
+
+#include <linux/bitmap.h>
+#include <linux/configfs.h>
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/gpio/machine.h>
+#include <linux/idr.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include "f_mud.h"
+#include "../../../pinctrl/pinctrl-mud.h"
+
+/*
+ * Even though the host side is a pinctrl driver, the device side is a gpio consumer.
+ * That's because not all boards have a pin controller.
+ */
+
+/* Temporary debugging aid */
+#define fmdebug(fmt, ...)				\
+do {							\
+	if (1)						\
+		printk(KERN_DEBUG fmt, ##__VA_ARGS__);	\
+} while (0)
+
+static DEFINE_IDA(f_mud_pins_ida);
+
+struct f_mud_pins_cell_item {
+	struct list_head node;
+	unsigned int index;
+	struct config_group group;
+
+	struct mutex lock; /* Protect the values below */
+	int refcnt;
+
+	const char *name;
+	const char *chip;
+	int offset;
+};
+
+static inline struct f_mud_pins_cell_item *ci_to_f_mud_pins_cell_item(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct f_mud_pins_cell_item, group);
+}
+
+struct f_mud_pins_lookup_device {
+	struct device dev;
+	int id;
+	struct f_mud_cell *cell;
+	struct gpiod_lookup_table *lookup;
+	const char **names;
+	unsigned int count;
+};
+
+struct f_mud_pins_pin {
+	struct f_mud_pins_cell *parent;
+	unsigned int index;
+	struct gpio_desc *gpio;
+	unsigned long dflags;
+	unsigned int debounce;
+#define DEBOUNCE_NOT_SET UINT_MAX
+	bool config_requested;
+	int irq;
+	int irqflags;
+};
+
+struct f_mud_pins_cell {
+	struct f_mud_cell cell;
+
+	struct mutex lock; /* Protect refcnt and items */
+	int refcnt;
+	struct list_head items;
+
+	struct f_mud_pins_lookup_device *ldev;
+	struct f_mud_pins_pin *pins;
+	unsigned int count;
+	spinlock_t irq_status_lock;
+	unsigned long *irq_status;
+};
+
+static inline struct f_mud_pins_cell *ci_to_f_mud_pins_cell(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct f_mud_pins_cell, cell.group);
+}
+
+static inline struct f_mud_pins_cell *cell_to_pcell(struct f_mud_cell *cell)
+{
+	return container_of(cell, struct f_mud_pins_cell, cell);
+}
+
+static irqreturn_t f_mud_pins_gpio_irq_thread(int irq, void *p)
+{
+	struct f_mud_pins_pin *pin = p;
+	struct f_mud_pins_cell *pcell = pin->parent;
+
+	spin_lock(&pcell->irq_status_lock);
+	set_bit(pin->index, pcell->irq_status);
+	spin_unlock(&pcell->irq_status_lock);
+
+	fmdebug("%s(index=%u): irq_status=%*pb\n", __func__, pin->index,
+		pcell->count, pcell->irq_status);
+
+	f_mud_irq(pcell->ldev->cell);
+
+	return IRQ_HANDLED;
+}
+
+static int f_mud_pins_gpio_irq_request(struct f_mud_pins_cell *pcell, unsigned int index)
+{
+	struct f_mud_pins_pin *pin = &pcell->pins[index];
+	int ret, irq;
+
+	fmdebug("%s(index=%u)\n", __func__, index);
+
+	if (pin->irq)
+		return 0;
+
+	if (!pin->gpio || !pin->irqflags)
+		return -EINVAL;
+
+	ret = gpiod_get_direction(pin->gpio);
+	if (ret < 0)
+		return ret;
+	if (ret != 1)
+		return -EINVAL;
+
+	irq = gpiod_to_irq(pin->gpio);
+	fmdebug("    irq=%d\n", irq);
+	if (irq <= 0)
+		return -ENODEV;
+
+	ret = request_threaded_irq(irq, NULL, f_mud_pins_gpio_irq_thread,
+				   pin->irqflags | IRQF_ONESHOT,
+				   dev_name(&pcell->ldev->dev), pin);
+	if (ret)
+		return ret;
+
+	pin->irq = irq;
+
+	return 0;
+}
+
+static void f_mud_pins_gpio_irq_free(struct f_mud_pins_cell *pcell, unsigned int index)
+{
+	struct f_mud_pins_pin *pin = &pcell->pins[index];
+
+	fmdebug("%s(index=%u): irq=%d\n", __func__, index, pin->irq);
+
+	if (pin->irq) {
+		free_irq(pin->irq, pin);
+		pin->irq = 0;
+	}
+}
+
+static void f_mud_pins_gpio_irq_free_all(struct f_mud_pins_cell *pcell)
+{
+	unsigned int i;
+
+	for (i = 0; i < pcell->count; i++)
+		f_mud_pins_gpio_irq_free(pcell, i);
+}
+
+static int f_mud_pins_gpio_irq_type(struct f_mud_pins_cell *pcell, unsigned int index,
+				    unsigned int val)
+{
+	struct f_mud_pins_pin *pin = &pcell->pins[index];
+
+	fmdebug("%s(index=%u, val=%u)\n", __func__, index, val);
+
+	switch (val) {
+	case MUD_PIN_IRQ_TYPE_EDGE_RISING:
+		pin->irqflags = IRQF_TRIGGER_RISING;
+		break;
+	case MUD_PIN_IRQ_TYPE_EDGE_FALLING:
+		pin->irqflags = IRQF_TRIGGER_FALLING;
+		break;
+	case MUD_PIN_IRQ_TYPE_EDGE_BOTH:
+		pin->irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
+		break;
+	case MUD_PIN_IRQ_TYPE_NONE:
+		pin->irqflags = IRQF_TRIGGER_NONE;
+		break;
+	default:
+		return -EINVAL;
+	};
+
+	return 0;
+}
+
+static void f_mud_pins_gpio_free(struct f_mud_pins_cell *pcell, unsigned int index)
+{
+	struct f_mud_pins_pin *pin = &pcell->pins[index];
+
+	fmdebug("%s(index=%u): gpio=%d, config_requested=%u\n", __func__, index,
+		pin->gpio ? desc_to_gpio(pin->gpio) : -1, pin->config_requested);
+
+	if (pin->config_requested)
+		return;
+
+	if (pin->gpio)
+		gpiod_put(pin->gpio);
+	pin->gpio = NULL;
+}
+
+/* When flags change re-request the gpio to enable the settings */
+static int f_mud_pins_gpio_request_do(struct f_mud_pins_cell *pcell, unsigned int index,
+				      bool config, unsigned int debounce)
+{
+	struct f_mud_pins_pin *pin = &pcell->pins[index];
+	struct gpio_desc *gpio;
+	int ret;
+
+	fmdebug("%s(index=%u): lflags=%lu dflags=%lu\n", __func__, index,
+		pcell->ldev->lookup->table[index].flags, pin->dflags);
+
+	if (!pcell->pins[index].gpio && config)
+		pin->config_requested = true;
+
+	if (pcell->pins[index].gpio) {
+		gpiod_put(pcell->pins[index].gpio);
+		pcell->pins[index].gpio = NULL;
+	}
+
+	gpio = gpiod_get_index(&pcell->ldev->dev, NULL, index, pin->dflags);
+	if (IS_ERR(gpio)) {
+		ret = PTR_ERR(gpio);
+		fmdebug("failed to get gpio %u: ret=%d\n", index, ret);
+		return ret;
+	}
+	pin->gpio = gpio;
+	fmdebug("    gpios[%u]: gpionr %d\n", index, desc_to_gpio(gpio));
+
+	/* Debounce can be set through pinconf so it must be stored */
+	if (debounce == DEBOUNCE_NOT_SET)
+		debounce = pin->debounce;
+
+	if (debounce != DEBOUNCE_NOT_SET) {
+		ret = gpiod_set_debounce(pin->gpio, debounce);
+		if (ret)
+			return ret;
+
+		pin->debounce = debounce ? debounce : DEBOUNCE_NOT_SET;
+	}
+
+	return 0;
+}
+
+static int f_mud_pins_gpio_request_debounce(struct f_mud_pins_cell *pcell,
+					    unsigned int index, unsigned int debounce)
+{
+	struct f_mud_pins_pin *pin = &pcell->pins[index];
+
+	if (pin->debounce == debounce)
+		return 0;
+
+	return f_mud_pins_gpio_request_do(pcell, index, true, debounce);
+}
+
+static int f_mud_pins_gpio_request_dflag(struct f_mud_pins_cell *pcell, unsigned int index,
+					 enum gpiod_flags dflag, bool dval, bool config)
+{
+	struct f_mud_pins_pin *pin = &pcell->pins[index];
+	bool curr_dflag = pin->dflags & dflag;
+	bool changed = false;
+
+	fmdebug("%s(index=%u): dflag=%u dval=%u\n", __func__, index, dflag, dval);
+
+	if (curr_dflag != dval) {
+		if (dval)
+			pin->dflags |= dflag;
+		else
+			pin->dflags &= ~dflag;
+		changed = true;
+	}
+
+	if (pin->gpio && !changed)
+		return 0;
+
+	return f_mud_pins_gpio_request_do(pcell, index, config, DEBOUNCE_NOT_SET);
+}
+
+static int f_mud_pins_gpio_request_lflag(struct f_mud_pins_cell *pcell, unsigned int index,
+					 enum gpio_lookup_flags lflag, bool lval,
+					 bool config)
+{
+	struct gpiod_lookup *lentry = &pcell->ldev->lookup->table[index];
+	struct f_mud_pins_pin *pin = &pcell->pins[index];
+	bool curr_lflag = lentry->flags & lflag;
+	bool changed = false;
+
+	fmdebug("%s(index=%u): lflag=%u lval=%u\n", __func__, index, lflag, lval);
+
+	if (curr_lflag != lval) {
+		if (lval)
+			lentry->flags |= lflag;
+		else
+			lentry->flags &= ~lflag;
+		changed = true;
+	}
+
+	if (pin->gpio && !changed)
+		return 0;
+
+	return f_mud_pins_gpio_request_do(pcell, index, config, DEBOUNCE_NOT_SET);
+}
+
+static int f_mud_pins_write_gpio_config(struct f_mud_pins_cell *pcell, unsigned int index,
+					unsigned int offset, unsigned int val)
+{
+	struct f_mud_pins_pin *pin;
+	int ret = -ENOTSUPP;
+
+	fmdebug("%s(index=%u, offset=0x%02x, val=%u)\n", __func__, index, offset, val);
+
+	pin = &pcell->pins[index];
+
+	switch (offset) {
+	case MUD_PIN_CONFIG_BIAS_PULL_DOWN:
+		if (!val)
+			return -ENOTSUPP;
+		ret = f_mud_pins_gpio_request_lflag(pcell, index, GPIO_PULL_DOWN, val, true);
+		break;
+	case MUD_PIN_CONFIG_BIAS_PULL_UP:
+		if (!val)
+			return -ENOTSUPP;
+		ret = f_mud_pins_gpio_request_lflag(pcell, index, GPIO_PULL_UP, val, true);
+		break;
+	case MUD_PIN_CONFIG_DRIVE_OPEN_DRAIN:
+		ret = f_mud_pins_gpio_request_lflag(pcell, index, GPIO_OPEN_DRAIN, 1, true);
+		break;
+	case MUD_PIN_CONFIG_DRIVE_OPEN_SOURCE:
+		ret = f_mud_pins_gpio_request_lflag(pcell, index, GPIO_OPEN_SOURCE, 1, true);
+		break;
+	case MUD_PIN_CONFIG_DRIVE_PUSH_PULL:
+		ret = f_mud_pins_gpio_request_dflag(pcell, index, GPIOD_IN, 0, true);
+		break;
+	case MUD_PIN_CONFIG_INPUT_DEBOUNCE:
+		ret = f_mud_pins_gpio_request_debounce(pcell, index, val);
+		break;
+	case MUD_PIN_CONFIG_INPUT_ENABLE:
+		ret = f_mud_pins_gpio_request_dflag(pcell, index, GPIOD_IN, val, true);
+		break;
+	case MUD_PIN_CONFIG_OUTPUT:
+		val = val ? GPIOD_OUT_HIGH : GPIOD_OUT_LOW;
+		ret = f_mud_pins_gpio_request_dflag(pcell, index, val, 1, true);
+		break;
+	case MUD_PIN_CONFIG_PERSIST_STATE:
+		ret = f_mud_pins_gpio_request_lflag(pcell, index, GPIO_TRANSITORY, val, true);
+		break;
+	}
+
+	return ret;
+}
+
+static int f_mud_pins_write_gpio(struct f_mud_pins_cell *pcell, unsigned int regnr,
+				 const void *buf, size_t len)
+{
+	unsigned int regbase = regnr - MUD_PINCTRL_REG_PIN_BASE;
+	unsigned int offset = regbase % MUD_PINCTRL_PIN_BLOCK_SIZE;
+	unsigned int index = regbase / MUD_PINCTRL_PIN_BLOCK_SIZE;
+	size_t count = len / sizeof(u32);
+	struct f_mud_pins_pin *pin;
+	const __le32 *buf32 = buf;
+	u32 val;
+	int ret;
+
+	fmdebug("%s(len=%zu): offset=0x%02x index=%u\n", __func__, len, offset, index);
+
+	if (index >= pcell->count) {
+		fmdebug("%s: gpio index out of bounds: %u\n", __func__, index);
+		return -EINVAL;
+	}
+
+	while (count--) {
+		pin = &pcell->pins[index];
+		val = le32_to_cpup(buf32++);
+		ret = 0;
+
+		switch (offset++) {
+		case MUD_PIN_CONFIG_BIAS_BUS_HOLD ... MUD_PIN_CONFIG_END:
+			return f_mud_pins_write_gpio_config(pcell, index, offset - 1, val);
+
+		case MUD_PIN_GPIO_REQUEST:
+			if (val != 1)
+				return -EINVAL;
+			/* Persistence is the default so set it from the start */
+			ret = f_mud_pins_gpio_request_lflag(pcell, index, GPIO_TRANSITORY,
+							    0, false);
+			break;
+		case MUD_PIN_GPIO_FREE:
+			if (val != 1)
+				return -EINVAL;
+			f_mud_pins_gpio_free(pcell, index);
+			break;
+
+		case MUD_PIN_DIRECTION:
+			if (!pin)
+				return -EINVAL;
+
+			if (val == MUD_PIN_DIRECTION_INPUT) {
+				ret = gpiod_direction_input(pin->gpio);
+			} else {
+				int value = !!(val & MUD_PIN_DIRECTION_OUTPUT_HIGH);
+
+				ret = gpiod_direction_output(pin->gpio, value);
+			}
+			break;
+		case MUD_PIN_LEVEL:
+			if (pin)
+				gpiod_set_value_cansleep(pin->gpio, val);
+			break;
+		case MUD_PIN_IRQ_TYPE:
+			ret = f_mud_pins_gpio_irq_type(pcell, index, val);
+			break;
+		case MUD_PIN_IRQ_ENABLED:
+			if (val)
+				ret = f_mud_pins_gpio_irq_request(pcell, index);
+			else
+				f_mud_pins_gpio_irq_free(pcell, index);
+			break;
+		default:
+			pr_err("%s: unknown register: 0x%x\n", __func__, regnr);
+			return -EINVAL;
+		}
+
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int f_mud_pins_writereg(struct f_mud_cell *cell, unsigned int regnr,
+			       const void *buf, size_t len, u8 compression)
+{
+	struct f_mud_pins_cell *pcell = cell_to_pcell(cell);
+
+	fmdebug("%s(regnr=0x%02x, len=%zu)\n", __func__, regnr, len);
+
+	if (regnr >= MUD_PINCTRL_REG_PIN_BASE)
+		return f_mud_pins_write_gpio(pcell, regnr, buf, len);
+
+	return -EINVAL;
+}
+
+static int f_mud_pins_read_gpio(struct f_mud_pins_cell *pcell, unsigned int regnr,
+				void *buf, size_t len)
+{
+	unsigned int regbase = regnr - MUD_PINCTRL_REG_PIN_BASE;
+	unsigned int offset = regbase % MUD_PINCTRL_PIN_BLOCK_SIZE;
+	unsigned int index = regbase / MUD_PINCTRL_PIN_BLOCK_SIZE;
+	size_t count = len / sizeof(u32);
+	struct f_mud_pins_pin *pin;
+	struct gpio_desc *gpio;
+	__le32 *buf32 = buf;
+	u32 val;
+	int ret;
+
+	fmdebug("%s(len=%zu): offset=0x%02x index=%u\n", __func__, len, offset, index);
+
+	if (index >= pcell->count) {
+		fmdebug("%s: gpio index out of bounds: %u\n", __func__, index);
+		return -EINVAL;
+	}
+
+	if (offset >= MUD_PIN_NAME && (offset + count - 1) <= MUD_PIN_NAME_END) {
+		size_t start = (offset - MUD_PIN_NAME) * sizeof(u32);
+		char name[MUD_PIN_NAME_LEN];
+
+		strscpy_pad(name, pcell->ldev->names[index], MUD_PIN_NAME_LEN);
+		fmdebug("    name=%*ph\n", MUD_PIN_NAME_LEN, name);
+		memcpy(buf, name + start, len);
+
+		return 0;
+	}
+
+	pin = &pcell->pins[index];
+
+	while (count--) {
+		switch (offset++) {
+		case MUD_PIN_DIRECTION:
+			/*
+			 * Host side gpiochip_add_data_with_key() calls ->get_direction
+			 * without requesting the gpio first.
+			 */
+			gpio = pin->gpio;
+			if (!gpio) {
+				/* non gpio pins will fail here, but that's fine */
+				ret = f_mud_pins_gpio_request_lflag(pcell, index,
+								    0, 0, false);
+				if (ret)
+					return ret;
+			}
+			ret = gpiod_get_direction(pin->gpio);
+			if (!gpio)
+				f_mud_pins_gpio_free(pcell, index);
+			if (ret < 0)
+				return ret;
+			val = ret ? MUD_PIN_DIRECTION_INPUT : MUD_PIN_DIRECTION_OUTPUT;
+			break;
+		case MUD_PIN_LEVEL:
+			if (!pin->gpio)
+				return -EINVAL;
+			ret = gpiod_get_value_cansleep(pin->gpio);
+			if (ret < 0)
+				return ret;
+			val = ret;
+			break;
+		case MUD_PIN_IRQ_TYPES:
+			/* FIXME: Is it possible to get this info somewhere? */
+			val = MUD_PIN_IRQ_TYPE_EDGE_BOTH;
+			break;
+		default:
+			pr_err("%s: unknown register: 0x%x\n", __func__, regnr);
+			return -EINVAL;
+		}
+
+		*(buf32++) = cpu_to_le32(val);
+	}
+
+	return 0;
+}
+
+static int f_mud_pins_readreg(struct f_mud_cell *cell, unsigned int regnr,
+			      void *buf, size_t *len, u8 compression)
+{
+	struct f_mud_pins_cell *pcell = cell_to_pcell(cell);
+	size_t count = *len / sizeof(u32);
+	__le32 *buf32 = buf;
+
+	fmdebug("%s(regnr=0x%02x, len=%zu)\n", __func__, regnr, *len);
+
+	if (regnr >= MUD_PINCTRL_REG_PIN_BASE)
+		return f_mud_pins_read_gpio(pcell, regnr, buf, *len);
+
+	if (regnr >= MUD_PINCTRL_REG_IRQ_STATUS &&
+	    regnr <= MUD_PINCTRL_REG_IRQ_STATUS_END) {
+		unsigned int nregs = DIV_ROUND_UP(pcell->count, sizeof(u32));
+		unsigned int offset = regnr - MUD_PINCTRL_REG_IRQ_STATUS;
+		unsigned int i, end = min_t(unsigned int, count, nregs);
+		u32 irqvals[MUD_PINCTRL_MAX_NUM_PINS / sizeof(u32)];
+
+		if (count > (nregs - offset))
+			return -EINVAL;
+
+		bitmap_to_arr32(irqvals, pcell->irq_status, pcell->count);
+
+		fmdebug("    irq_status=%*pb  irqvals=%*ph\n", pcell->count,
+			pcell->irq_status, nregs, irqvals);
+
+		for (i = offset; i < end; i++)
+			*buf32++ = cpu_to_le32(irqvals[i]);
+
+		return 0;
+	}
+
+	if (regnr == MUD_PINCTRL_REG_NUM_PINS && count == 1) {
+		*buf32 = cpu_to_le32(pcell->count);
+		return 0;
+	}
+
+	fmdebug("%s: unknown register 0x%x\n", __func__, regnr);
+
+	return -EINVAL;
+}
+
+static void f_mud_pins_disable(struct f_mud_cell *cell)
+{
+	struct f_mud_pins_cell *pcell = cell_to_pcell(cell);
+
+	f_mud_pins_gpio_irq_free_all(pcell);
+	/*
+	 * FIXME: Free requested gpios as well?
+	 *        Or should they survive on unplug on a powered board?
+	 */
+}
+
+static void f_mud_pins_lookup_device_release(struct device *dev)
+{
+	struct f_mud_pins_lookup_device *ldev =
+				container_of(dev, struct f_mud_pins_lookup_device, dev);
+
+	fmdebug("%s: ldev=%px\n", __func__, ldev);
+
+	if (ldev->lookup) {
+		struct gpiod_lookup *p;
+
+		if (ldev->lookup->dev_id)
+			gpiod_remove_lookup_table(ldev->lookup);
+
+		for (p = &ldev->lookup->table[0]; p->chip_label; p++)
+			kfree(p->chip_label);
+		kfree(ldev->lookup);
+	}
+
+	if (ldev->names) {
+		unsigned int i;
+
+		for (i = 0; i < ldev->count; i++)
+			kfree(ldev->names[i]);
+		kfree(ldev->names);
+	}
+
+	ida_free(&f_mud_pins_ida, ldev->id);
+	kfree(ldev);
+}
+
+static int f_mud_pins_looup_device_create(struct f_mud_pins_cell *pcell)
+{
+	struct f_mud_pins_lookup_device *ldev;
+	struct f_mud_pins_cell_item *fitem;
+	unsigned int max_index = 0;
+	int ret;
+
+	fmdebug("%s: %px\n", __func__, pcell);
+
+	ldev = kzalloc(sizeof(*ldev), GFP_KERNEL);
+	if (!ldev)
+		return -ENOMEM;
+
+	fmdebug("%s: ldev=%px\n", __func__, ldev);
+
+	ldev->id = ida_alloc(&f_mud_pins_ida, GFP_KERNEL);
+	if (ldev->id < 0) {
+		kfree(ldev);
+		return ldev->id;
+	}
+
+	ldev->cell = &pcell->cell;
+	ldev->dev.release = f_mud_pins_lookup_device_release;
+	dev_set_name(&ldev->dev, "f_mud_pins-%d", ldev->id);
+
+	ret = device_register(&ldev->dev);
+	if (ret) {
+		put_device(&ldev->dev);
+		return ret;
+	}
+
+	mutex_lock(&pcell->lock);
+
+	list_for_each_entry(fitem, &pcell->items, node) {
+		max_index = max(max_index, fitem->index);
+		ldev->count++;
+	}
+
+	if (!ldev->count) {
+		ret = -ENOENT;
+		goto out_unlock;
+	}
+
+	if (ldev->count != max_index + 1) {
+		pr_err("Pin indices are not continuous\n");
+		ret = -EINVAL;
+		goto out_unlock;
+	}
+
+	ldev->names = kcalloc(ldev->count, sizeof(*ldev->names), GFP_KERNEL);
+	ldev->lookup = kzalloc(struct_size(ldev->lookup, table, ldev->count + 1),
+			       GFP_KERNEL);
+	if (!ldev->lookup || !ldev->names) {
+		ret = -ENOMEM;
+		goto out_unlock;
+	}
+
+	ret = 0;
+	list_for_each_entry(fitem, &pcell->items, node) {
+		struct gpiod_lookup *entry = &ldev->lookup->table[fitem->index];
+
+		mutex_lock(&fitem->lock);
+
+		if (!fitem->name) {
+			pr_err("Missing name for pin %u\n", fitem->index);
+			ret = -EINVAL;
+			goto out_unlock_pin;
+		}
+
+		ldev->names[fitem->index] = kstrdup(fitem->name, GFP_KERNEL);
+		if (!ldev->names[fitem->index]) {
+			ret = -ENOMEM;
+			goto out_unlock_pin;
+		}
+
+		/* Skip adding to lookup if the pin has no gpio function */
+		if (!fitem->chip)
+			goto out_unlock_pin;
+
+		entry->idx = fitem->index;
+		entry->chip_hwnum = fitem->offset;
+
+		entry->chip_label = kstrdup(fitem->chip, GFP_KERNEL);
+		if (!entry->chip_label) {
+			ret = -ENOMEM;
+			goto out_unlock_pin;
+		}
+
+		fmdebug("    %u: chip=%s hwnum=%u name=%s\n", entry->idx,
+			entry->chip_label, entry->chip_hwnum, ldev->names[fitem->index]);
+out_unlock_pin:
+		mutex_unlock(&fitem->lock);
+		if (ret)
+			goto out_unlock;
+	}
+
+	ldev->lookup->dev_id = dev_name(&ldev->dev);
+	gpiod_add_lookup_table(ldev->lookup);
+
+	pcell->ldev = ldev;
+	pcell->count = ldev->count;
+
+out_unlock:
+	mutex_unlock(&pcell->lock);
+
+	if (ret)
+		device_unregister(&ldev->dev);
+
+	return ret;
+}
+
+static void f_mud_pins_lookup_device_destroy(struct f_mud_pins_cell *pcell)
+{
+	if (pcell->ldev) {
+		device_unregister(&pcell->ldev->dev);
+		pcell->ldev = NULL;
+	}
+}
+
+static int f_mud_pins_bind(struct f_mud_cell *cell)
+{
+	struct f_mud_pins_cell *pcell = cell_to_pcell(cell);
+	unsigned int i;
+	int ret;
+
+	fmdebug("%s: %px\n", __func__, pcell);
+
+	ret = f_mud_pins_looup_device_create(pcell);
+	if (ret)
+		return ret;
+
+	spin_lock_init(&pcell->irq_status_lock);
+	pcell->irq_status = bitmap_zalloc(pcell->count, GFP_KERNEL);
+	if (!pcell->irq_status) {
+		ret = -ENOMEM;
+		goto error_free;
+	}
+
+	pcell->pins = kcalloc(pcell->count, sizeof(*pcell->pins), GFP_KERNEL);
+	if (!pcell->pins) {
+		ret = -ENOMEM;
+		goto error_free;
+	}
+
+	for (i = 0; i < pcell->count; i++) {
+		struct f_mud_pins_pin *pin = &pcell->pins[i];
+
+		pin->parent = pcell;
+		pin->index = i;
+		pin->debounce = DEBOUNCE_NOT_SET;
+	}
+
+	return 0;
+
+error_free:
+	kfree(pcell->pins);
+	f_mud_pins_lookup_device_destroy(pcell);
+
+	return ret;
+}
+
+static void f_mud_pins_unbind(struct f_mud_cell *cell)
+{
+	struct f_mud_pins_cell *pcell = cell_to_pcell(cell);
+	unsigned int i;
+
+	fmdebug("%s:\n", __func__);
+
+	if (pcell->pins) {
+		for (i = 0; i < pcell->count; i++) {
+			if (pcell->pins[i].gpio) {
+				fmdebug("    gpiod_put: %u\n", i);
+				gpiod_put(pcell->pins[i].gpio);
+			}
+		}
+
+		kfree(pcell->pins);
+	}
+
+	bitmap_free(pcell->irq_status);
+
+	f_mud_pins_lookup_device_destroy(pcell);
+}
+
+static void f_mud_pins_cell_item_item_release(struct config_item *item)
+{
+	struct f_mud_pins_cell_item *fitem = ci_to_f_mud_pins_cell_item(item);
+
+	fmdebug("%s: fitem=%px\n", __func__, fitem);
+
+	kfree(fitem->name);
+	kfree(fitem->chip);
+	kfree(fitem);
+}
+
+F_MUD_OPT_STR(f_mud_pins_cell_item, name);
+F_MUD_OPT_STR(f_mud_pins_cell_item, chip);
+F_MUD_OPT_INT(f_mud_pins_cell_item, offset, 0, INT_MAX);
+
+static struct configfs_attribute *f_mud_pins_cell_item_attrs[] = {
+	&f_mud_pins_cell_item_attr_name,
+	&f_mud_pins_cell_item_attr_chip,
+	&f_mud_pins_cell_item_attr_offset,
+	NULL,
+};
+
+static struct configfs_item_operations f_mud_pins_cell_item_item_ops = {
+	.release = f_mud_pins_cell_item_item_release,
+};
+
+static const struct config_item_type f_mud_pins_cell_item_func_type = {
+	.ct_item_ops	= &f_mud_pins_cell_item_item_ops,
+	.ct_attrs	= f_mud_pins_cell_item_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
+static struct config_group *f_mud_pins_make_group(struct config_group *group,
+						  const char *name)
+{
+	struct f_mud_pins_cell *pcell = ci_to_f_mud_pins_cell(&group->cg_item);
+	struct f_mud_pins_cell_item *fitem;
+	const char *prefix = "pin.";
+	struct config_group *grp;
+	int ret, index;
+
+	fmdebug("%s: name=%s\n", __func__, name);
+
+	mutex_lock(&pcell->lock);
+	fmdebug("%s: pcell=%px pcell->refcnt=%d\n", __func__, pcell, pcell->refcnt);
+	if (pcell->refcnt) {
+		grp = ERR_PTR(-EBUSY);
+		goto out_unlock;
+	}
+
+	if (strstr(name, prefix) != name) {
+		pr_err("Missing prefix '%s' in name: '%s'\n", prefix, name);
+		grp = ERR_PTR(-EINVAL);
+		goto out_unlock;
+	}
+
+	ret = kstrtoint(name + strlen(prefix), 10, &index);
+	if (ret) {
+		pr_err("Failed to parse index in name: '%s'\n", name);
+		grp = ERR_PTR(ret);
+		goto out_unlock;
+	}
+
+	fitem = kzalloc(sizeof(*fitem), GFP_KERNEL);
+	fmdebug("    pin=%px\n", fitem);
+	if (!fitem) {
+		grp = ERR_PTR(-ENOMEM);
+		goto out_unlock;
+	}
+
+	fitem->index = index;
+	grp = &fitem->group;
+
+	config_group_init_type_name(grp, "", &f_mud_pins_cell_item_func_type);
+
+	list_add(&fitem->node, &pcell->items);
+out_unlock:
+	mutex_unlock(&pcell->lock);
+
+	return grp;
+}
+
+static void f_mud_pins_drop_item(struct config_group *group, struct config_item *item)
+{
+	struct f_mud_pins_cell *pcell = ci_to_f_mud_pins_cell(&group->cg_item);
+	struct f_mud_pins_cell_item *fitem = ci_to_f_mud_pins_cell_item(item);
+
+	fmdebug("%s: pcell=%px fitem=%px\n", __func__, pcell, fitem);
+
+	mutex_lock(&pcell->lock);
+	list_del(&fitem->node);
+	mutex_unlock(&pcell->lock);
+
+	config_item_put(item);
+}
+
+static struct configfs_group_operations f_mud_pins_group_ops = {
+	.make_group	= f_mud_pins_make_group,
+	.drop_item	= f_mud_pins_drop_item,
+};
+
+static struct configfs_item_operations f_mud_pins_item_ops = {
+	.release = f_mud_cell_item_release,
+};
+
+static const struct config_item_type f_mud_pins_func_type = {
+	.ct_item_ops	= &f_mud_pins_item_ops,
+	.ct_group_ops	= &f_mud_pins_group_ops,
+	.ct_owner	= THIS_MODULE,
+};
+
+static void f_mud_pins_free(struct f_mud_cell *cell)
+{
+	struct f_mud_pins_cell *pcell = container_of(cell, struct f_mud_pins_cell, cell);
+
+	fmdebug("%s: pcell=%px\n", __func__, pcell);
+
+	mutex_destroy(&pcell->lock);
+	kfree(pcell);
+}
+
+static struct f_mud_cell *f_mud_pins_alloc(void)
+{
+	struct f_mud_pins_cell *pcell;
+
+	pcell = kzalloc(sizeof(*pcell), GFP_KERNEL);
+	fmdebug("%s: pcell=%px\n", __func__, pcell);
+	if (!pcell)
+		return ERR_PTR(-ENOMEM);
+
+	mutex_init(&pcell->lock);
+	INIT_LIST_HEAD(&pcell->items);
+	config_group_init_type_name(&pcell->cell.group, "", &f_mud_pins_func_type);
+
+	fmdebug("%s: cell=%px\n", __func__, &pcell->cell);
+
+	return &pcell->cell;
+}
+
+static const struct f_mud_cell_ops f_mud_pins_ops = {
+	.name = "mud-pins",
+	.owner = THIS_MODULE,
+
+	.interrupt_interval_ms = 100,
+
+	.alloc = f_mud_pins_alloc,
+	.free = f_mud_pins_free,
+	.bind = f_mud_pins_bind,
+	.unbind = f_mud_pins_unbind,
+
+	.regval_bytes = 4,
+	.max_transfer_size = 64,
+
+	.disable = f_mud_pins_disable,
+	.readreg = f_mud_pins_readreg,
+	.writereg = f_mud_pins_writereg,
+};
+
+DECLARE_F_MUD_CELL_INIT(f_mud_pins_ops);
+
+MODULE_AUTHOR("Noralf Trønnes");
+MODULE_LICENSE("GPL");
-- 
2.23.0


WARNING: multiple messages have this Message-ID (diff)
From: "Noralf Trønnes" <noralf@tronnes.org>
To: broonie@kernel.org, balbi@kernel.org, lee.jones@linaro.org
Cc: linux-usb@vger.kernel.org, dri-devel@lists.freedesktop.org
Subject: [RFC 5/9] usb: gadget: function: mud: Add gpio support
Date: Sun, 16 Feb 2020 18:21:13 +0100	[thread overview]
Message-ID: <20200216172117.49832-6-noralf@tronnes.org> (raw)
In-Reply-To: <20200216172117.49832-1-noralf@tronnes.org>

Add optional gpio functionality to the Multifunction USB Device.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/usb/gadget/Kconfig               |  14 +
 drivers/usb/gadget/function/Makefile     |   2 +
 drivers/usb/gadget/function/f_mud_pins.c | 962 +++++++++++++++++++++++
 3 files changed, 978 insertions(+)
 create mode 100644 drivers/usb/gadget/function/f_mud_pins.c

diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index 9551876ffe08..d6285146ec76 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -219,6 +219,9 @@ config USB_F_TCM
 config USB_F_MUD
 	tristate
 
+config USB_F_MUD_PINS
+	tristate
+
 # this first set of drivers all depend on bulk-capable hardware.
 
 config USB_CONFIGFS
@@ -493,6 +496,17 @@ menuconfig USB_CONFIGFS_F_MUD
 	help
 	  Core support for the Multifunction USB Device.
 
+if USB_F_MUD
+
+config USB_CONFIGFS_F_MUD_PINS
+	bool "Multifunction USB Device GPIO"
+	depends on PINCTRL
+	select USB_F_MUD_PINS
+	help
+	  GPIO support for the Multifunction USB Device.
+
+endif # USB_F_MUD
+
 choice
 	tristate "USB Gadget precomposed configurations"
 	default USB_ETH
diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile
index b6e31b511521..2e24227fcc12 100644
--- a/drivers/usb/gadget/function/Makefile
+++ b/drivers/usb/gadget/function/Makefile
@@ -52,3 +52,5 @@ usb_f_tcm-y			:= f_tcm.o
 obj-$(CONFIG_USB_F_TCM)		+= usb_f_tcm.o
 usb_f_mud-y			:= f_mud.o mud_regmap.o
 obj-$(CONFIG_USB_F_MUD)		+= usb_f_mud.o
+usb_f_mud_pins-y		:= f_mud_pins.o
+obj-$(CONFIG_USB_F_MUD_PINS)	+= usb_f_mud_pins.o
diff --git a/drivers/usb/gadget/function/f_mud_pins.c b/drivers/usb/gadget/function/f_mud_pins.c
new file mode 100644
index 000000000000..b3466804ad5e
--- /dev/null
+++ b/drivers/usb/gadget/function/f_mud_pins.c
@@ -0,0 +1,962 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2020 Noralf Trønnes
+ */
+
+#include <linux/bitmap.h>
+#include <linux/configfs.h>
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/gpio/machine.h>
+#include <linux/idr.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include "f_mud.h"
+#include "../../../pinctrl/pinctrl-mud.h"
+
+/*
+ * Even though the host side is a pinctrl driver, the device side is a gpio consumer.
+ * That's because not all boards have a pin controller.
+ */
+
+/* Temporary debugging aid */
+#define fmdebug(fmt, ...)				\
+do {							\
+	if (1)						\
+		printk(KERN_DEBUG fmt, ##__VA_ARGS__);	\
+} while (0)
+
+static DEFINE_IDA(f_mud_pins_ida);
+
+struct f_mud_pins_cell_item {
+	struct list_head node;
+	unsigned int index;
+	struct config_group group;
+
+	struct mutex lock; /* Protect the values below */
+	int refcnt;
+
+	const char *name;
+	const char *chip;
+	int offset;
+};
+
+static inline struct f_mud_pins_cell_item *ci_to_f_mud_pins_cell_item(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct f_mud_pins_cell_item, group);
+}
+
+struct f_mud_pins_lookup_device {
+	struct device dev;
+	int id;
+	struct f_mud_cell *cell;
+	struct gpiod_lookup_table *lookup;
+	const char **names;
+	unsigned int count;
+};
+
+struct f_mud_pins_pin {
+	struct f_mud_pins_cell *parent;
+	unsigned int index;
+	struct gpio_desc *gpio;
+	unsigned long dflags;
+	unsigned int debounce;
+#define DEBOUNCE_NOT_SET UINT_MAX
+	bool config_requested;
+	int irq;
+	int irqflags;
+};
+
+struct f_mud_pins_cell {
+	struct f_mud_cell cell;
+
+	struct mutex lock; /* Protect refcnt and items */
+	int refcnt;
+	struct list_head items;
+
+	struct f_mud_pins_lookup_device *ldev;
+	struct f_mud_pins_pin *pins;
+	unsigned int count;
+	spinlock_t irq_status_lock;
+	unsigned long *irq_status;
+};
+
+static inline struct f_mud_pins_cell *ci_to_f_mud_pins_cell(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct f_mud_pins_cell, cell.group);
+}
+
+static inline struct f_mud_pins_cell *cell_to_pcell(struct f_mud_cell *cell)
+{
+	return container_of(cell, struct f_mud_pins_cell, cell);
+}
+
+static irqreturn_t f_mud_pins_gpio_irq_thread(int irq, void *p)
+{
+	struct f_mud_pins_pin *pin = p;
+	struct f_mud_pins_cell *pcell = pin->parent;
+
+	spin_lock(&pcell->irq_status_lock);
+	set_bit(pin->index, pcell->irq_status);
+	spin_unlock(&pcell->irq_status_lock);
+
+	fmdebug("%s(index=%u): irq_status=%*pb\n", __func__, pin->index,
+		pcell->count, pcell->irq_status);
+
+	f_mud_irq(pcell->ldev->cell);
+
+	return IRQ_HANDLED;
+}
+
+static int f_mud_pins_gpio_irq_request(struct f_mud_pins_cell *pcell, unsigned int index)
+{
+	struct f_mud_pins_pin *pin = &pcell->pins[index];
+	int ret, irq;
+
+	fmdebug("%s(index=%u)\n", __func__, index);
+
+	if (pin->irq)
+		return 0;
+
+	if (!pin->gpio || !pin->irqflags)
+		return -EINVAL;
+
+	ret = gpiod_get_direction(pin->gpio);
+	if (ret < 0)
+		return ret;
+	if (ret != 1)
+		return -EINVAL;
+
+	irq = gpiod_to_irq(pin->gpio);
+	fmdebug("    irq=%d\n", irq);
+	if (irq <= 0)
+		return -ENODEV;
+
+	ret = request_threaded_irq(irq, NULL, f_mud_pins_gpio_irq_thread,
+				   pin->irqflags | IRQF_ONESHOT,
+				   dev_name(&pcell->ldev->dev), pin);
+	if (ret)
+		return ret;
+
+	pin->irq = irq;
+
+	return 0;
+}
+
+static void f_mud_pins_gpio_irq_free(struct f_mud_pins_cell *pcell, unsigned int index)
+{
+	struct f_mud_pins_pin *pin = &pcell->pins[index];
+
+	fmdebug("%s(index=%u): irq=%d\n", __func__, index, pin->irq);
+
+	if (pin->irq) {
+		free_irq(pin->irq, pin);
+		pin->irq = 0;
+	}
+}
+
+static void f_mud_pins_gpio_irq_free_all(struct f_mud_pins_cell *pcell)
+{
+	unsigned int i;
+
+	for (i = 0; i < pcell->count; i++)
+		f_mud_pins_gpio_irq_free(pcell, i);
+}
+
+static int f_mud_pins_gpio_irq_type(struct f_mud_pins_cell *pcell, unsigned int index,
+				    unsigned int val)
+{
+	struct f_mud_pins_pin *pin = &pcell->pins[index];
+
+	fmdebug("%s(index=%u, val=%u)\n", __func__, index, val);
+
+	switch (val) {
+	case MUD_PIN_IRQ_TYPE_EDGE_RISING:
+		pin->irqflags = IRQF_TRIGGER_RISING;
+		break;
+	case MUD_PIN_IRQ_TYPE_EDGE_FALLING:
+		pin->irqflags = IRQF_TRIGGER_FALLING;
+		break;
+	case MUD_PIN_IRQ_TYPE_EDGE_BOTH:
+		pin->irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
+		break;
+	case MUD_PIN_IRQ_TYPE_NONE:
+		pin->irqflags = IRQF_TRIGGER_NONE;
+		break;
+	default:
+		return -EINVAL;
+	};
+
+	return 0;
+}
+
+static void f_mud_pins_gpio_free(struct f_mud_pins_cell *pcell, unsigned int index)
+{
+	struct f_mud_pins_pin *pin = &pcell->pins[index];
+
+	fmdebug("%s(index=%u): gpio=%d, config_requested=%u\n", __func__, index,
+		pin->gpio ? desc_to_gpio(pin->gpio) : -1, pin->config_requested);
+
+	if (pin->config_requested)
+		return;
+
+	if (pin->gpio)
+		gpiod_put(pin->gpio);
+	pin->gpio = NULL;
+}
+
+/* When flags change re-request the gpio to enable the settings */
+static int f_mud_pins_gpio_request_do(struct f_mud_pins_cell *pcell, unsigned int index,
+				      bool config, unsigned int debounce)
+{
+	struct f_mud_pins_pin *pin = &pcell->pins[index];
+	struct gpio_desc *gpio;
+	int ret;
+
+	fmdebug("%s(index=%u): lflags=%lu dflags=%lu\n", __func__, index,
+		pcell->ldev->lookup->table[index].flags, pin->dflags);
+
+	if (!pcell->pins[index].gpio && config)
+		pin->config_requested = true;
+
+	if (pcell->pins[index].gpio) {
+		gpiod_put(pcell->pins[index].gpio);
+		pcell->pins[index].gpio = NULL;
+	}
+
+	gpio = gpiod_get_index(&pcell->ldev->dev, NULL, index, pin->dflags);
+	if (IS_ERR(gpio)) {
+		ret = PTR_ERR(gpio);
+		fmdebug("failed to get gpio %u: ret=%d\n", index, ret);
+		return ret;
+	}
+	pin->gpio = gpio;
+	fmdebug("    gpios[%u]: gpionr %d\n", index, desc_to_gpio(gpio));
+
+	/* Debounce can be set through pinconf so it must be stored */
+	if (debounce == DEBOUNCE_NOT_SET)
+		debounce = pin->debounce;
+
+	if (debounce != DEBOUNCE_NOT_SET) {
+		ret = gpiod_set_debounce(pin->gpio, debounce);
+		if (ret)
+			return ret;
+
+		pin->debounce = debounce ? debounce : DEBOUNCE_NOT_SET;
+	}
+
+	return 0;
+}
+
+static int f_mud_pins_gpio_request_debounce(struct f_mud_pins_cell *pcell,
+					    unsigned int index, unsigned int debounce)
+{
+	struct f_mud_pins_pin *pin = &pcell->pins[index];
+
+	if (pin->debounce == debounce)
+		return 0;
+
+	return f_mud_pins_gpio_request_do(pcell, index, true, debounce);
+}
+
+static int f_mud_pins_gpio_request_dflag(struct f_mud_pins_cell *pcell, unsigned int index,
+					 enum gpiod_flags dflag, bool dval, bool config)
+{
+	struct f_mud_pins_pin *pin = &pcell->pins[index];
+	bool curr_dflag = pin->dflags & dflag;
+	bool changed = false;
+
+	fmdebug("%s(index=%u): dflag=%u dval=%u\n", __func__, index, dflag, dval);
+
+	if (curr_dflag != dval) {
+		if (dval)
+			pin->dflags |= dflag;
+		else
+			pin->dflags &= ~dflag;
+		changed = true;
+	}
+
+	if (pin->gpio && !changed)
+		return 0;
+
+	return f_mud_pins_gpio_request_do(pcell, index, config, DEBOUNCE_NOT_SET);
+}
+
+static int f_mud_pins_gpio_request_lflag(struct f_mud_pins_cell *pcell, unsigned int index,
+					 enum gpio_lookup_flags lflag, bool lval,
+					 bool config)
+{
+	struct gpiod_lookup *lentry = &pcell->ldev->lookup->table[index];
+	struct f_mud_pins_pin *pin = &pcell->pins[index];
+	bool curr_lflag = lentry->flags & lflag;
+	bool changed = false;
+
+	fmdebug("%s(index=%u): lflag=%u lval=%u\n", __func__, index, lflag, lval);
+
+	if (curr_lflag != lval) {
+		if (lval)
+			lentry->flags |= lflag;
+		else
+			lentry->flags &= ~lflag;
+		changed = true;
+	}
+
+	if (pin->gpio && !changed)
+		return 0;
+
+	return f_mud_pins_gpio_request_do(pcell, index, config, DEBOUNCE_NOT_SET);
+}
+
+static int f_mud_pins_write_gpio_config(struct f_mud_pins_cell *pcell, unsigned int index,
+					unsigned int offset, unsigned int val)
+{
+	struct f_mud_pins_pin *pin;
+	int ret = -ENOTSUPP;
+
+	fmdebug("%s(index=%u, offset=0x%02x, val=%u)\n", __func__, index, offset, val);
+
+	pin = &pcell->pins[index];
+
+	switch (offset) {
+	case MUD_PIN_CONFIG_BIAS_PULL_DOWN:
+		if (!val)
+			return -ENOTSUPP;
+		ret = f_mud_pins_gpio_request_lflag(pcell, index, GPIO_PULL_DOWN, val, true);
+		break;
+	case MUD_PIN_CONFIG_BIAS_PULL_UP:
+		if (!val)
+			return -ENOTSUPP;
+		ret = f_mud_pins_gpio_request_lflag(pcell, index, GPIO_PULL_UP, val, true);
+		break;
+	case MUD_PIN_CONFIG_DRIVE_OPEN_DRAIN:
+		ret = f_mud_pins_gpio_request_lflag(pcell, index, GPIO_OPEN_DRAIN, 1, true);
+		break;
+	case MUD_PIN_CONFIG_DRIVE_OPEN_SOURCE:
+		ret = f_mud_pins_gpio_request_lflag(pcell, index, GPIO_OPEN_SOURCE, 1, true);
+		break;
+	case MUD_PIN_CONFIG_DRIVE_PUSH_PULL:
+		ret = f_mud_pins_gpio_request_dflag(pcell, index, GPIOD_IN, 0, true);
+		break;
+	case MUD_PIN_CONFIG_INPUT_DEBOUNCE:
+		ret = f_mud_pins_gpio_request_debounce(pcell, index, val);
+		break;
+	case MUD_PIN_CONFIG_INPUT_ENABLE:
+		ret = f_mud_pins_gpio_request_dflag(pcell, index, GPIOD_IN, val, true);
+		break;
+	case MUD_PIN_CONFIG_OUTPUT:
+		val = val ? GPIOD_OUT_HIGH : GPIOD_OUT_LOW;
+		ret = f_mud_pins_gpio_request_dflag(pcell, index, val, 1, true);
+		break;
+	case MUD_PIN_CONFIG_PERSIST_STATE:
+		ret = f_mud_pins_gpio_request_lflag(pcell, index, GPIO_TRANSITORY, val, true);
+		break;
+	}
+
+	return ret;
+}
+
+static int f_mud_pins_write_gpio(struct f_mud_pins_cell *pcell, unsigned int regnr,
+				 const void *buf, size_t len)
+{
+	unsigned int regbase = regnr - MUD_PINCTRL_REG_PIN_BASE;
+	unsigned int offset = regbase % MUD_PINCTRL_PIN_BLOCK_SIZE;
+	unsigned int index = regbase / MUD_PINCTRL_PIN_BLOCK_SIZE;
+	size_t count = len / sizeof(u32);
+	struct f_mud_pins_pin *pin;
+	const __le32 *buf32 = buf;
+	u32 val;
+	int ret;
+
+	fmdebug("%s(len=%zu): offset=0x%02x index=%u\n", __func__, len, offset, index);
+
+	if (index >= pcell->count) {
+		fmdebug("%s: gpio index out of bounds: %u\n", __func__, index);
+		return -EINVAL;
+	}
+
+	while (count--) {
+		pin = &pcell->pins[index];
+		val = le32_to_cpup(buf32++);
+		ret = 0;
+
+		switch (offset++) {
+		case MUD_PIN_CONFIG_BIAS_BUS_HOLD ... MUD_PIN_CONFIG_END:
+			return f_mud_pins_write_gpio_config(pcell, index, offset - 1, val);
+
+		case MUD_PIN_GPIO_REQUEST:
+			if (val != 1)
+				return -EINVAL;
+			/* Persistence is the default so set it from the start */
+			ret = f_mud_pins_gpio_request_lflag(pcell, index, GPIO_TRANSITORY,
+							    0, false);
+			break;
+		case MUD_PIN_GPIO_FREE:
+			if (val != 1)
+				return -EINVAL;
+			f_mud_pins_gpio_free(pcell, index);
+			break;
+
+		case MUD_PIN_DIRECTION:
+			if (!pin)
+				return -EINVAL;
+
+			if (val == MUD_PIN_DIRECTION_INPUT) {
+				ret = gpiod_direction_input(pin->gpio);
+			} else {
+				int value = !!(val & MUD_PIN_DIRECTION_OUTPUT_HIGH);
+
+				ret = gpiod_direction_output(pin->gpio, value);
+			}
+			break;
+		case MUD_PIN_LEVEL:
+			if (pin)
+				gpiod_set_value_cansleep(pin->gpio, val);
+			break;
+		case MUD_PIN_IRQ_TYPE:
+			ret = f_mud_pins_gpio_irq_type(pcell, index, val);
+			break;
+		case MUD_PIN_IRQ_ENABLED:
+			if (val)
+				ret = f_mud_pins_gpio_irq_request(pcell, index);
+			else
+				f_mud_pins_gpio_irq_free(pcell, index);
+			break;
+		default:
+			pr_err("%s: unknown register: 0x%x\n", __func__, regnr);
+			return -EINVAL;
+		}
+
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int f_mud_pins_writereg(struct f_mud_cell *cell, unsigned int regnr,
+			       const void *buf, size_t len, u8 compression)
+{
+	struct f_mud_pins_cell *pcell = cell_to_pcell(cell);
+
+	fmdebug("%s(regnr=0x%02x, len=%zu)\n", __func__, regnr, len);
+
+	if (regnr >= MUD_PINCTRL_REG_PIN_BASE)
+		return f_mud_pins_write_gpio(pcell, regnr, buf, len);
+
+	return -EINVAL;
+}
+
+static int f_mud_pins_read_gpio(struct f_mud_pins_cell *pcell, unsigned int regnr,
+				void *buf, size_t len)
+{
+	unsigned int regbase = regnr - MUD_PINCTRL_REG_PIN_BASE;
+	unsigned int offset = regbase % MUD_PINCTRL_PIN_BLOCK_SIZE;
+	unsigned int index = regbase / MUD_PINCTRL_PIN_BLOCK_SIZE;
+	size_t count = len / sizeof(u32);
+	struct f_mud_pins_pin *pin;
+	struct gpio_desc *gpio;
+	__le32 *buf32 = buf;
+	u32 val;
+	int ret;
+
+	fmdebug("%s(len=%zu): offset=0x%02x index=%u\n", __func__, len, offset, index);
+
+	if (index >= pcell->count) {
+		fmdebug("%s: gpio index out of bounds: %u\n", __func__, index);
+		return -EINVAL;
+	}
+
+	if (offset >= MUD_PIN_NAME && (offset + count - 1) <= MUD_PIN_NAME_END) {
+		size_t start = (offset - MUD_PIN_NAME) * sizeof(u32);
+		char name[MUD_PIN_NAME_LEN];
+
+		strscpy_pad(name, pcell->ldev->names[index], MUD_PIN_NAME_LEN);
+		fmdebug("    name=%*ph\n", MUD_PIN_NAME_LEN, name);
+		memcpy(buf, name + start, len);
+
+		return 0;
+	}
+
+	pin = &pcell->pins[index];
+
+	while (count--) {
+		switch (offset++) {
+		case MUD_PIN_DIRECTION:
+			/*
+			 * Host side gpiochip_add_data_with_key() calls ->get_direction
+			 * without requesting the gpio first.
+			 */
+			gpio = pin->gpio;
+			if (!gpio) {
+				/* non gpio pins will fail here, but that's fine */
+				ret = f_mud_pins_gpio_request_lflag(pcell, index,
+								    0, 0, false);
+				if (ret)
+					return ret;
+			}
+			ret = gpiod_get_direction(pin->gpio);
+			if (!gpio)
+				f_mud_pins_gpio_free(pcell, index);
+			if (ret < 0)
+				return ret;
+			val = ret ? MUD_PIN_DIRECTION_INPUT : MUD_PIN_DIRECTION_OUTPUT;
+			break;
+		case MUD_PIN_LEVEL:
+			if (!pin->gpio)
+				return -EINVAL;
+			ret = gpiod_get_value_cansleep(pin->gpio);
+			if (ret < 0)
+				return ret;
+			val = ret;
+			break;
+		case MUD_PIN_IRQ_TYPES:
+			/* FIXME: Is it possible to get this info somewhere? */
+			val = MUD_PIN_IRQ_TYPE_EDGE_BOTH;
+			break;
+		default:
+			pr_err("%s: unknown register: 0x%x\n", __func__, regnr);
+			return -EINVAL;
+		}
+
+		*(buf32++) = cpu_to_le32(val);
+	}
+
+	return 0;
+}
+
+static int f_mud_pins_readreg(struct f_mud_cell *cell, unsigned int regnr,
+			      void *buf, size_t *len, u8 compression)
+{
+	struct f_mud_pins_cell *pcell = cell_to_pcell(cell);
+	size_t count = *len / sizeof(u32);
+	__le32 *buf32 = buf;
+
+	fmdebug("%s(regnr=0x%02x, len=%zu)\n", __func__, regnr, *len);
+
+	if (regnr >= MUD_PINCTRL_REG_PIN_BASE)
+		return f_mud_pins_read_gpio(pcell, regnr, buf, *len);
+
+	if (regnr >= MUD_PINCTRL_REG_IRQ_STATUS &&
+	    regnr <= MUD_PINCTRL_REG_IRQ_STATUS_END) {
+		unsigned int nregs = DIV_ROUND_UP(pcell->count, sizeof(u32));
+		unsigned int offset = regnr - MUD_PINCTRL_REG_IRQ_STATUS;
+		unsigned int i, end = min_t(unsigned int, count, nregs);
+		u32 irqvals[MUD_PINCTRL_MAX_NUM_PINS / sizeof(u32)];
+
+		if (count > (nregs - offset))
+			return -EINVAL;
+
+		bitmap_to_arr32(irqvals, pcell->irq_status, pcell->count);
+
+		fmdebug("    irq_status=%*pb  irqvals=%*ph\n", pcell->count,
+			pcell->irq_status, nregs, irqvals);
+
+		for (i = offset; i < end; i++)
+			*buf32++ = cpu_to_le32(irqvals[i]);
+
+		return 0;
+	}
+
+	if (regnr == MUD_PINCTRL_REG_NUM_PINS && count == 1) {
+		*buf32 = cpu_to_le32(pcell->count);
+		return 0;
+	}
+
+	fmdebug("%s: unknown register 0x%x\n", __func__, regnr);
+
+	return -EINVAL;
+}
+
+static void f_mud_pins_disable(struct f_mud_cell *cell)
+{
+	struct f_mud_pins_cell *pcell = cell_to_pcell(cell);
+
+	f_mud_pins_gpio_irq_free_all(pcell);
+	/*
+	 * FIXME: Free requested gpios as well?
+	 *        Or should they survive on unplug on a powered board?
+	 */
+}
+
+static void f_mud_pins_lookup_device_release(struct device *dev)
+{
+	struct f_mud_pins_lookup_device *ldev =
+				container_of(dev, struct f_mud_pins_lookup_device, dev);
+
+	fmdebug("%s: ldev=%px\n", __func__, ldev);
+
+	if (ldev->lookup) {
+		struct gpiod_lookup *p;
+
+		if (ldev->lookup->dev_id)
+			gpiod_remove_lookup_table(ldev->lookup);
+
+		for (p = &ldev->lookup->table[0]; p->chip_label; p++)
+			kfree(p->chip_label);
+		kfree(ldev->lookup);
+	}
+
+	if (ldev->names) {
+		unsigned int i;
+
+		for (i = 0; i < ldev->count; i++)
+			kfree(ldev->names[i]);
+		kfree(ldev->names);
+	}
+
+	ida_free(&f_mud_pins_ida, ldev->id);
+	kfree(ldev);
+}
+
+static int f_mud_pins_looup_device_create(struct f_mud_pins_cell *pcell)
+{
+	struct f_mud_pins_lookup_device *ldev;
+	struct f_mud_pins_cell_item *fitem;
+	unsigned int max_index = 0;
+	int ret;
+
+	fmdebug("%s: %px\n", __func__, pcell);
+
+	ldev = kzalloc(sizeof(*ldev), GFP_KERNEL);
+	if (!ldev)
+		return -ENOMEM;
+
+	fmdebug("%s: ldev=%px\n", __func__, ldev);
+
+	ldev->id = ida_alloc(&f_mud_pins_ida, GFP_KERNEL);
+	if (ldev->id < 0) {
+		kfree(ldev);
+		return ldev->id;
+	}
+
+	ldev->cell = &pcell->cell;
+	ldev->dev.release = f_mud_pins_lookup_device_release;
+	dev_set_name(&ldev->dev, "f_mud_pins-%d", ldev->id);
+
+	ret = device_register(&ldev->dev);
+	if (ret) {
+		put_device(&ldev->dev);
+		return ret;
+	}
+
+	mutex_lock(&pcell->lock);
+
+	list_for_each_entry(fitem, &pcell->items, node) {
+		max_index = max(max_index, fitem->index);
+		ldev->count++;
+	}
+
+	if (!ldev->count) {
+		ret = -ENOENT;
+		goto out_unlock;
+	}
+
+	if (ldev->count != max_index + 1) {
+		pr_err("Pin indices are not continuous\n");
+		ret = -EINVAL;
+		goto out_unlock;
+	}
+
+	ldev->names = kcalloc(ldev->count, sizeof(*ldev->names), GFP_KERNEL);
+	ldev->lookup = kzalloc(struct_size(ldev->lookup, table, ldev->count + 1),
+			       GFP_KERNEL);
+	if (!ldev->lookup || !ldev->names) {
+		ret = -ENOMEM;
+		goto out_unlock;
+	}
+
+	ret = 0;
+	list_for_each_entry(fitem, &pcell->items, node) {
+		struct gpiod_lookup *entry = &ldev->lookup->table[fitem->index];
+
+		mutex_lock(&fitem->lock);
+
+		if (!fitem->name) {
+			pr_err("Missing name for pin %u\n", fitem->index);
+			ret = -EINVAL;
+			goto out_unlock_pin;
+		}
+
+		ldev->names[fitem->index] = kstrdup(fitem->name, GFP_KERNEL);
+		if (!ldev->names[fitem->index]) {
+			ret = -ENOMEM;
+			goto out_unlock_pin;
+		}
+
+		/* Skip adding to lookup if the pin has no gpio function */
+		if (!fitem->chip)
+			goto out_unlock_pin;
+
+		entry->idx = fitem->index;
+		entry->chip_hwnum = fitem->offset;
+
+		entry->chip_label = kstrdup(fitem->chip, GFP_KERNEL);
+		if (!entry->chip_label) {
+			ret = -ENOMEM;
+			goto out_unlock_pin;
+		}
+
+		fmdebug("    %u: chip=%s hwnum=%u name=%s\n", entry->idx,
+			entry->chip_label, entry->chip_hwnum, ldev->names[fitem->index]);
+out_unlock_pin:
+		mutex_unlock(&fitem->lock);
+		if (ret)
+			goto out_unlock;
+	}
+
+	ldev->lookup->dev_id = dev_name(&ldev->dev);
+	gpiod_add_lookup_table(ldev->lookup);
+
+	pcell->ldev = ldev;
+	pcell->count = ldev->count;
+
+out_unlock:
+	mutex_unlock(&pcell->lock);
+
+	if (ret)
+		device_unregister(&ldev->dev);
+
+	return ret;
+}
+
+static void f_mud_pins_lookup_device_destroy(struct f_mud_pins_cell *pcell)
+{
+	if (pcell->ldev) {
+		device_unregister(&pcell->ldev->dev);
+		pcell->ldev = NULL;
+	}
+}
+
+static int f_mud_pins_bind(struct f_mud_cell *cell)
+{
+	struct f_mud_pins_cell *pcell = cell_to_pcell(cell);
+	unsigned int i;
+	int ret;
+
+	fmdebug("%s: %px\n", __func__, pcell);
+
+	ret = f_mud_pins_looup_device_create(pcell);
+	if (ret)
+		return ret;
+
+	spin_lock_init(&pcell->irq_status_lock);
+	pcell->irq_status = bitmap_zalloc(pcell->count, GFP_KERNEL);
+	if (!pcell->irq_status) {
+		ret = -ENOMEM;
+		goto error_free;
+	}
+
+	pcell->pins = kcalloc(pcell->count, sizeof(*pcell->pins), GFP_KERNEL);
+	if (!pcell->pins) {
+		ret = -ENOMEM;
+		goto error_free;
+	}
+
+	for (i = 0; i < pcell->count; i++) {
+		struct f_mud_pins_pin *pin = &pcell->pins[i];
+
+		pin->parent = pcell;
+		pin->index = i;
+		pin->debounce = DEBOUNCE_NOT_SET;
+	}
+
+	return 0;
+
+error_free:
+	kfree(pcell->pins);
+	f_mud_pins_lookup_device_destroy(pcell);
+
+	return ret;
+}
+
+static void f_mud_pins_unbind(struct f_mud_cell *cell)
+{
+	struct f_mud_pins_cell *pcell = cell_to_pcell(cell);
+	unsigned int i;
+
+	fmdebug("%s:\n", __func__);
+
+	if (pcell->pins) {
+		for (i = 0; i < pcell->count; i++) {
+			if (pcell->pins[i].gpio) {
+				fmdebug("    gpiod_put: %u\n", i);
+				gpiod_put(pcell->pins[i].gpio);
+			}
+		}
+
+		kfree(pcell->pins);
+	}
+
+	bitmap_free(pcell->irq_status);
+
+	f_mud_pins_lookup_device_destroy(pcell);
+}
+
+static void f_mud_pins_cell_item_item_release(struct config_item *item)
+{
+	struct f_mud_pins_cell_item *fitem = ci_to_f_mud_pins_cell_item(item);
+
+	fmdebug("%s: fitem=%px\n", __func__, fitem);
+
+	kfree(fitem->name);
+	kfree(fitem->chip);
+	kfree(fitem);
+}
+
+F_MUD_OPT_STR(f_mud_pins_cell_item, name);
+F_MUD_OPT_STR(f_mud_pins_cell_item, chip);
+F_MUD_OPT_INT(f_mud_pins_cell_item, offset, 0, INT_MAX);
+
+static struct configfs_attribute *f_mud_pins_cell_item_attrs[] = {
+	&f_mud_pins_cell_item_attr_name,
+	&f_mud_pins_cell_item_attr_chip,
+	&f_mud_pins_cell_item_attr_offset,
+	NULL,
+};
+
+static struct configfs_item_operations f_mud_pins_cell_item_item_ops = {
+	.release = f_mud_pins_cell_item_item_release,
+};
+
+static const struct config_item_type f_mud_pins_cell_item_func_type = {
+	.ct_item_ops	= &f_mud_pins_cell_item_item_ops,
+	.ct_attrs	= f_mud_pins_cell_item_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
+static struct config_group *f_mud_pins_make_group(struct config_group *group,
+						  const char *name)
+{
+	struct f_mud_pins_cell *pcell = ci_to_f_mud_pins_cell(&group->cg_item);
+	struct f_mud_pins_cell_item *fitem;
+	const char *prefix = "pin.";
+	struct config_group *grp;
+	int ret, index;
+
+	fmdebug("%s: name=%s\n", __func__, name);
+
+	mutex_lock(&pcell->lock);
+	fmdebug("%s: pcell=%px pcell->refcnt=%d\n", __func__, pcell, pcell->refcnt);
+	if (pcell->refcnt) {
+		grp = ERR_PTR(-EBUSY);
+		goto out_unlock;
+	}
+
+	if (strstr(name, prefix) != name) {
+		pr_err("Missing prefix '%s' in name: '%s'\n", prefix, name);
+		grp = ERR_PTR(-EINVAL);
+		goto out_unlock;
+	}
+
+	ret = kstrtoint(name + strlen(prefix), 10, &index);
+	if (ret) {
+		pr_err("Failed to parse index in name: '%s'\n", name);
+		grp = ERR_PTR(ret);
+		goto out_unlock;
+	}
+
+	fitem = kzalloc(sizeof(*fitem), GFP_KERNEL);
+	fmdebug("    pin=%px\n", fitem);
+	if (!fitem) {
+		grp = ERR_PTR(-ENOMEM);
+		goto out_unlock;
+	}
+
+	fitem->index = index;
+	grp = &fitem->group;
+
+	config_group_init_type_name(grp, "", &f_mud_pins_cell_item_func_type);
+
+	list_add(&fitem->node, &pcell->items);
+out_unlock:
+	mutex_unlock(&pcell->lock);
+
+	return grp;
+}
+
+static void f_mud_pins_drop_item(struct config_group *group, struct config_item *item)
+{
+	struct f_mud_pins_cell *pcell = ci_to_f_mud_pins_cell(&group->cg_item);
+	struct f_mud_pins_cell_item *fitem = ci_to_f_mud_pins_cell_item(item);
+
+	fmdebug("%s: pcell=%px fitem=%px\n", __func__, pcell, fitem);
+
+	mutex_lock(&pcell->lock);
+	list_del(&fitem->node);
+	mutex_unlock(&pcell->lock);
+
+	config_item_put(item);
+}
+
+static struct configfs_group_operations f_mud_pins_group_ops = {
+	.make_group	= f_mud_pins_make_group,
+	.drop_item	= f_mud_pins_drop_item,
+};
+
+static struct configfs_item_operations f_mud_pins_item_ops = {
+	.release = f_mud_cell_item_release,
+};
+
+static const struct config_item_type f_mud_pins_func_type = {
+	.ct_item_ops	= &f_mud_pins_item_ops,
+	.ct_group_ops	= &f_mud_pins_group_ops,
+	.ct_owner	= THIS_MODULE,
+};
+
+static void f_mud_pins_free(struct f_mud_cell *cell)
+{
+	struct f_mud_pins_cell *pcell = container_of(cell, struct f_mud_pins_cell, cell);
+
+	fmdebug("%s: pcell=%px\n", __func__, pcell);
+
+	mutex_destroy(&pcell->lock);
+	kfree(pcell);
+}
+
+static struct f_mud_cell *f_mud_pins_alloc(void)
+{
+	struct f_mud_pins_cell *pcell;
+
+	pcell = kzalloc(sizeof(*pcell), GFP_KERNEL);
+	fmdebug("%s: pcell=%px\n", __func__, pcell);
+	if (!pcell)
+		return ERR_PTR(-ENOMEM);
+
+	mutex_init(&pcell->lock);
+	INIT_LIST_HEAD(&pcell->items);
+	config_group_init_type_name(&pcell->cell.group, "", &f_mud_pins_func_type);
+
+	fmdebug("%s: cell=%px\n", __func__, &pcell->cell);
+
+	return &pcell->cell;
+}
+
+static const struct f_mud_cell_ops f_mud_pins_ops = {
+	.name = "mud-pins",
+	.owner = THIS_MODULE,
+
+	.interrupt_interval_ms = 100,
+
+	.alloc = f_mud_pins_alloc,
+	.free = f_mud_pins_free,
+	.bind = f_mud_pins_bind,
+	.unbind = f_mud_pins_unbind,
+
+	.regval_bytes = 4,
+	.max_transfer_size = 64,
+
+	.disable = f_mud_pins_disable,
+	.readreg = f_mud_pins_readreg,
+	.writereg = f_mud_pins_writereg,
+};
+
+DECLARE_F_MUD_CELL_INIT(f_mud_pins_ops);
+
+MODULE_AUTHOR("Noralf Trønnes");
+MODULE_LICENSE("GPL");
-- 
2.23.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

  parent reply	other threads:[~2020-02-16 17:27 UTC|newest]

Thread overview: 54+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-02-16 17:21 [RFC 0/9] Regmap over USB for Multifunction USB Device (gpio, display, ...) Noralf Trønnes
2020-02-16 17:21 ` Noralf Trønnes
2020-02-16 17:21 ` [RFC 1/9] regmap: Add USB support Noralf Trønnes
2020-02-16 17:21   ` Noralf Trønnes
2020-02-17 12:11   ` Mark Brown
2020-02-17 12:11     ` Mark Brown
2020-02-17 21:33     ` Noralf Trønnes
2020-02-17 21:33       ` Noralf Trønnes
2020-02-17 21:39       ` Mark Brown
2020-02-17 21:39         ` Mark Brown
2020-02-17 22:15         ` Noralf Trønnes
2020-02-17 22:15           ` Noralf Trønnes
2020-02-17 22:44           ` Mark Brown
2020-02-17 22:44             ` Mark Brown
2020-02-16 17:21 ` [RFC 2/9] mfd: Add driver for Multifunction USB Device Noralf Trønnes
2020-02-16 17:21   ` Noralf Trønnes
2020-02-27  9:09   ` Lee Jones
2020-02-27  9:09     ` Lee Jones
2020-02-29 13:26     ` Noralf Trønnes
2020-02-29 13:26       ` Noralf Trønnes
2020-02-29 16:02       ` Alan Stern
2020-02-29 16:02         ` Alan Stern
2020-02-16 17:21 ` [RFC 3/9] usb: gadget: function: Add Multifunction USB Device support Noralf Trønnes
2020-02-16 17:21   ` Noralf Trønnes
2020-02-16 17:21 ` [RFC 4/9] pinctrl: Add Multifunction USB Device pinctrl driver Noralf Trønnes
2020-02-16 17:21   ` Noralf Trønnes
2020-02-16 17:21 ` Noralf Trønnes [this message]
2020-02-16 17:21   ` [RFC 5/9] usb: gadget: function: mud: Add gpio support Noralf Trønnes
2020-02-16 17:21 ` [RFC 6/9] regmap: Speed up _regmap_raw_write_impl() for large buffers Noralf Trønnes
2020-02-16 17:21   ` Noralf Trønnes
2020-02-17 12:15   ` Mark Brown
2020-02-17 12:15     ` Mark Brown
2020-02-16 17:21 ` [RFC 7/9] drm: Add Multifunction USB Device display driver Noralf Trønnes
2020-02-16 17:21   ` Noralf Trønnes
2020-02-16 17:21 ` [RFC 8/9] drm/client: Add drm_client_init_from_id() and drm_client_modeset_set() Noralf Trønnes
2020-02-16 17:21   ` Noralf Trønnes
2020-02-17  9:38   ` Daniel Vetter
2020-02-17  9:38     ` Daniel Vetter
2020-02-23 17:43     ` Noralf Trønnes
2020-02-23 17:43       ` Noralf Trønnes
2020-02-23 20:59       ` Daniel Vetter
2020-02-23 20:59         ` Daniel Vetter
2020-02-16 17:21 ` [RFC 9/9] usb: gadget: function: mud: Add display support Noralf Trønnes
2020-02-16 17:21   ` Noralf Trønnes
2020-02-17  9:40 ` [RFC 0/9] Regmap over USB for Multifunction USB Device (gpio, display, ...) Daniel Vetter
2020-02-17  9:40   ` Daniel Vetter
2020-02-17 10:32 ` Neil Armstrong
2020-02-17 10:32   ` Neil Armstrong
2020-02-17 14:05   ` Noralf Trønnes
2020-02-17 14:05     ` Noralf Trønnes
2020-02-18 20:57 ` Andy Shevchenko
2020-02-18 20:57   ` Andy Shevchenko
2020-02-18 21:31   ` Noralf Trønnes
2020-02-18 21:31     ` Noralf Trønnes

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20200216172117.49832-6-noralf@tronnes.org \
    --to=noralf@tronnes.org \
    --cc=balbi@kernel.org \
    --cc=broonie@kernel.org \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=lee.jones@linaro.org \
    --cc=linux-usb@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.