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 4/9] pinctrl: Add Multifunction USB Device pinctrl driver
Date: Sun, 16 Feb 2020 18:21:12 +0100	[thread overview]
Message-ID: <20200216172117.49832-5-noralf@tronnes.org> (raw)
In-Reply-To: <20200216172117.49832-1-noralf@tronnes.org>

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

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
-- 
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 4/9] pinctrl: Add Multifunction USB Device pinctrl driver
Date: Sun, 16 Feb 2020 18:21:12 +0100	[thread overview]
Message-ID: <20200216172117.49832-5-noralf@tronnes.org> (raw)
In-Reply-To: <20200216172117.49832-1-noralf@tronnes.org>

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

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
-- 
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 ` Noralf Trønnes [this message]
2020-02-16 17:21   ` [RFC 4/9] pinctrl: Add Multifunction USB Device pinctrl driver Noralf Trønnes
2020-02-16 17:21 ` [RFC 5/9] usb: gadget: function: mud: Add gpio support Noralf Trønnes
2020-02-16 17:21   ` 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-5-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.