[PATCH 3/6] gpio: Add GPIO support for the ST Multi-Function eXpander

Amelie Delaunay amelie.delaunay at st.com
Thu Feb 8 06:27:34 PST 2018


ST Multi-Function eXpander (MFX) can be used as GPIO expander.
It has 16 fast GPIOs and can have 8 extra alternate GPIOs
when other MFX features are not enabled.

Signed-off-by: Amelie Delaunay <amelie.delaunay at st.com>
---
 drivers/gpio/Kconfig                   |  10 +
 drivers/gpio/Makefile                  |   1 +
 drivers/gpio/gpio-st-mfx.c             | 589 +++++++++++++++++++++++++++++++++
 include/dt-bindings/gpio/st-mfx-gpio.h |  24 ++
 4 files changed, 624 insertions(+)
 create mode 100644 drivers/gpio/gpio-st-mfx.c
 create mode 100644 include/dt-bindings/gpio/st-mfx-gpio.h

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index d6a8e85..7827af3 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1034,6 +1034,16 @@ config GPIO_STMPE
 	  This enables support for the GPIOs found on the STMPE I/O
 	  Expanders.
 
+config GPIO_ST_MFX
+	bool "ST-MFX GPIOs"
+	depends on MFD_ST_MFX
+	depends on OF_GPIO
+	select GPIOLIB_IRQCHIP
+	help
+	  GPIO driver for STMicroelectronics Multi-Function eXpander.
+
+	  This provides GPIO interface supporting inputs and outputs.
+
 config GPIO_TC3589X
 	bool "TC3589X GPIOs"
 	depends on MFD_TC3589X
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 4bc24fe..7013bfa 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -110,6 +110,7 @@ obj-$(CONFIG_GPIO_SODAVILLE)	+= gpio-sodaville.o
 obj-$(CONFIG_GPIO_SPEAR_SPICS)	+= gpio-spear-spics.o
 obj-$(CONFIG_GPIO_STA2X11)	+= gpio-sta2x11.o
 obj-$(CONFIG_GPIO_STMPE)	+= gpio-stmpe.o
+obj-$(CONFIG_GPIO_ST_MFX) 	+= gpio-st-mfx.o
 obj-$(CONFIG_GPIO_STP_XWAY)	+= gpio-stp-xway.o
 obj-$(CONFIG_GPIO_SYSCON)	+= gpio-syscon.o
 obj-$(CONFIG_GPIO_TB10X)	+= gpio-tb10x.o
diff --git a/drivers/gpio/gpio-st-mfx.c b/drivers/gpio/gpio-st-mfx.c
new file mode 100644
index 0000000..4e5235f
--- /dev/null
+++ b/drivers/gpio/gpio-st-mfx.c
@@ -0,0 +1,589 @@
+/*
+ * STMicroelectronics Multi-Function eXpander (ST-MFX) - GPIO expander driver.
+ *
+ * Copyright (C) 2017, STMicroelectronics - All Rights Reserved
+ * Author(s): Amelie Delaunay <amelie.delaunay at st.com> for STMicroelectronics.
+ *
+ * License terms: GPL V2.0.
+ *
+ * st-mfx-gpio is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * st-mfx-gpio is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * st-mfx-gpio. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/mfd/st-mfx.h>
+#include <linux/module.h>
+#include <linux/of_gpio.h>
+#include <linux/platform_device.h>
+#include <linux/seq_file.h>
+
+/* MFX has a maximum of 24 gpios, with 8 gpios per bank, so 3 banks maximum */
+#define NR_MAX_GPIOS		24
+#define NR_GPIOS_PER_BANK	8
+#define NR_MAX_BANKS		(NR_MAX_GPIOS / NR_GPIOS_PER_BANK)
+#define get_bank(offset)	((u8)((offset) / NR_GPIOS_PER_BANK))
+#define get_mask(offset)	((u8)BIT((offset) % NR_GPIOS_PER_BANK))
+
+/*
+ * These registers are modified under the .irq_bus_lock and cached to avoid
+ * unnecessary writes in .irq_bus_sync_unlock.
+ */
+enum { REG_IRQ_SRC, REG_IRQ_EVT, REG_IRQ_TYPE, NR_CACHE_IRQ_REGS };
+
+/*
+ * This is MFX-specific flags, overloading Linux-specific of_gpio_flags,
+ * needed in of_xlate callback.
+ * on MFX: - if GPIO is output:
+ *		* (0) means PUSH_PULL
+ *		* OF_GPIO_SINGLE_ENDED (=2) means OPEN-DRAIN
+ *	   - if GPIO is input:
+ *		* (0) means NO_PULL
+ *		* OF_MFX_GPI_PUSH_PULL (=2) means PUSH_PULL
+ *
+ *		* OF_MFX_GPIO_PULL_UP programs pull up resistor on the GPIO,
+ *		  whatever its direction, without this flag, depending on
+ *		  GPIO type and direction, it programs either no pull or
+ *		  pull down resistor.
+ */
+enum of_mfx_gpio_flags {
+	OF_MFX_GPI_PUSH_PULL = 0x2,
+	OF_MFX_GPIO_PULL_UP = 0x4,
+};
+
+struct mfx_gpio {
+	struct gpio_chip gc;
+	struct mfx *mfx;
+	struct device *dev;
+	struct mutex irq_lock; /* IRQ bus lock */
+	u32 flags[NR_MAX_GPIOS];
+	/* Caches of interrupt control registers for bus_lock */
+	u8 regs[NR_CACHE_IRQ_REGS][NR_MAX_BANKS];
+	u8 oldregs[NR_CACHE_IRQ_REGS][NR_MAX_BANKS];
+};
+
+static int mfx_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+	struct mfx_gpio *mfx_gpio = gpiochip_get_data(gc);
+	struct mfx *mfx = mfx_gpio->mfx;
+	u8 reg = MFX_REG_GPIO_STATE + get_bank(offset);
+	u8 mask = get_mask(offset);
+	int ret;
+
+	ret = mfx_reg_read(mfx, reg);
+	if (ret < 0)
+		return ret;
+
+	return !!(ret & mask);
+}
+
+static void mfx_gpio_set(struct gpio_chip *gc, unsigned int offset, int val)
+{
+	struct mfx_gpio *mfx_gpio = gpiochip_get_data(gc);
+	struct mfx *mfx = mfx_gpio->mfx;
+	int which = val ? MFX_REG_GPO_SET : MFX_REG_GPO_CLR;
+	u8 reg = which + get_bank(offset);
+	u8 mask = get_mask(offset);
+
+	mfx_reg_write(mfx, reg, mask);
+}
+
+static int mfx_gpio_get_direction(struct gpio_chip *gc,
+				  unsigned int offset)
+{
+	struct mfx_gpio *mfx_gpio = gpiochip_get_data(gc);
+	struct mfx *mfx = mfx_gpio->mfx;
+	u8 reg = MFX_REG_GPIO_DIR + get_bank(offset);
+	u8 mask = get_mask(offset);
+	int ret;
+
+	ret = mfx_reg_read(mfx, reg);
+	if (ret < 0)
+		return ret;
+
+	return !(ret & mask);
+}
+
+static int mfx_gpio_direction_input(struct gpio_chip *gc,
+				    unsigned int offset)
+{
+	struct mfx_gpio *mfx_gpio = gpiochip_get_data(gc);
+	struct mfx *mfx = mfx_gpio->mfx;
+	u8 reg_type = MFX_REG_GPIO_TYPE + get_bank(offset);
+	u8 reg_pupd = MFX_REG_GPIO_PUPD + get_bank(offset);
+	u8 reg_dir = MFX_REG_GPIO_DIR + get_bank(offset);
+	u8 mask = get_mask(offset);
+	int ret;
+
+	/*
+	 * In case of input direction, there is actually no way to apply some
+	 * configuration in hardware, as it can be done with
+	 * .set_config in case of output direction. That's why we apply
+	 * here the MFX specific-flags.
+	 */
+	if (mfx_gpio->flags[offset] & OF_MFX_GPI_PUSH_PULL)
+		ret = mfx_set_bits(mfx, reg_type, mask, mask);
+	else
+		ret = mfx_set_bits(mfx, reg_type, mask, 0);
+
+	if (ret)
+		return ret;
+
+	if (mfx_gpio->flags[offset] & OF_MFX_GPIO_PULL_UP)
+		ret = mfx_set_bits(mfx, reg_pupd, mask, mask);
+	else
+		ret = mfx_set_bits(mfx, reg_pupd, mask, 0);
+
+	if (ret)
+		return ret;
+
+	return mfx_set_bits(mfx, reg_dir, mask, 0);
+}
+
+static int mfx_gpio_direction_output(struct gpio_chip *gc,
+				     unsigned int offset, int val)
+{
+	struct mfx_gpio *mfx_gpio = gpiochip_get_data(gc);
+	struct mfx *mfx = mfx_gpio->mfx;
+	u8 reg = MFX_REG_GPIO_DIR + get_bank(offset);
+	u8 mask = get_mask(offset);
+
+	mfx_gpio_set(gc, offset, val);
+
+	return mfx_set_bits(mfx, reg, mask, mask);
+}
+
+static int mfx_gpio_set_config(struct gpio_chip *gc, unsigned int offset,
+			       unsigned long config)
+{
+	struct mfx_gpio *mfx_gpio = gpiochip_get_data(gc);
+	struct mfx *mfx = mfx_gpio->mfx;
+	u8 reg_type = MFX_REG_GPIO_TYPE + get_bank(offset);
+	u8 reg_pupd = MFX_REG_GPIO_PUPD + get_bank(offset);
+	u8 mask = get_mask(offset);
+	int ret;
+
+	switch (pinconf_to_config_param(config)) {
+	case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+		ret = mfx_set_bits(mfx, reg_type, mask, mask);
+		if (ret)
+			return ret;
+		return mfx_set_bits(mfx, reg_pupd, mask, 0);
+	case PIN_CONFIG_DRIVE_OPEN_SOURCE:
+		ret = mfx_set_bits(mfx, reg_type, mask, mask);
+		if (ret)
+			return ret;
+		return mfx_set_bits(mfx, reg_pupd, mask, mask);
+	case PIN_CONFIG_DRIVE_PUSH_PULL:
+		ret = mfx_set_bits(mfx, reg_type, mask, 0);
+		if (ret)
+			return ret;
+		return mfx_set_bits(mfx, reg_pupd, mask, 0);
+	default:
+		break;
+	}
+
+	return -ENOTSUPP;
+}
+
+static void mfx_gpio_dbg_show(struct seq_file *s, struct gpio_chip *gc)
+{
+	struct mfx_gpio *mfx_gpio = gpiochip_get_data(gc);
+	struct mfx *mfx = mfx_gpio->mfx;
+	u16 i;
+
+	for (i = 0; i < gc->ngpio; i++) {
+		int gpio = i + gc->base;
+		const char *label = gpiochip_is_requested(gc, i);
+		int dir = mfx_gpio_get_direction(gc, i);
+		int val = mfx_gpio_get(gc, i);
+		u8 mask = get_mask(i);
+		u8 reg;
+		int type, pupd;
+		int irqsrc, irqevt, irqtype, irqpending;
+
+		if (!label)
+			label = "Unrequested";
+
+		seq_printf(s, " gpio-%-3d (%-20.20s) ", gpio, label);
+
+		reg = MFX_REG_GPIO_TYPE + get_bank(i);
+		type = mfx_reg_read(mfx, reg);
+		if (type < 0)
+			return;
+		type = !!(type & mask);
+
+		reg = MFX_REG_GPIO_PUPD + get_bank(i);
+		pupd = mfx_reg_read(mfx, reg);
+		if (pupd < 0)
+			return;
+		pupd = !!(pupd & mask);
+
+		reg = MFX_REG_IRQ_GPI_SRC + get_bank(i);
+		irqsrc = mfx_reg_read(mfx, reg);
+		if (irqsrc < 0)
+			return;
+		irqsrc = !!(irqsrc & mask);
+
+		reg = MFX_REG_IRQ_GPI_EVT + get_bank(i);
+		irqevt = mfx_reg_read(mfx, reg);
+		if (irqevt < 0)
+			return;
+		irqevt = !!(irqevt & mask);
+
+		reg = MFX_REG_IRQ_GPI_TYPE + get_bank(i);
+		irqtype = mfx_reg_read(mfx, reg);
+		if (irqtype < 0)
+			return;
+		irqtype = !!(irqtype & mask);
+
+		reg = MFX_REG_IRQ_GPI_PENDING + get_bank(i);
+		irqpending = mfx_reg_read(mfx, reg);
+		if (irqpending < 0)
+			return;
+		irqpending = !!(irqpending & mask);
+
+		if (!dir) {
+			seq_printf(s, "out %s ", val ? "high" : "low");
+			if (type)
+				seq_puts(s, "open drain ");
+			else
+				seq_puts(s, "push pull ");
+			if (pupd && type)
+				seq_puts(s, "with internal pull-up ");
+			else
+				seq_puts(s, "without internal pull-up ");
+		} else {
+			seq_printf(s, "in %s ", val ? "high" : "low");
+			if (type)
+				seq_printf(s, "with internal pull-%s ",
+					   pupd ? "up" : "down");
+			else
+				seq_printf(s, "%s ",
+					   pupd ? "floating" : "analog");
+		}
+
+		if (irqsrc) {
+			seq_printf(s, "IRQ generated on %s %s",
+				   !irqevt ?
+				   (!irqtype ? "Low level" : "High level") :
+				   (!irqtype ? "Falling edge" : "Rising edge"),
+				   irqpending ? "(pending)" : "");
+		}
+
+		seq_puts(s, "\n");
+	}
+}
+
+int mfx_gpio_of_xlate(struct gpio_chip *gc,
+		      const struct of_phandle_args *gpiospec, u32 *flags)
+{
+	struct mfx_gpio *mfx_gpio = gpiochip_get_data(gc);
+	int ret;
+
+	ret = of_gpio_simple_xlate(gc, gpiospec, flags);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * In atomic context here, we can't access registers over I2C,
+	 * that's why gpio flags are stored to be applied later.
+	 */
+	mfx_gpio->flags[gpiospec->args[0]] = gpiospec->args[1];
+
+	return ret;
+}
+
+static const struct gpio_chip mfx_gpio_chip = {
+	.label			= "MFX_GPIO",
+	.owner			= THIS_MODULE,
+	.get_direction		= mfx_gpio_get_direction,
+	.direction_input	= mfx_gpio_direction_input,
+	.direction_output	= mfx_gpio_direction_output,
+	.get			= mfx_gpio_get,
+	.set			= mfx_gpio_set,
+	.set_config		= mfx_gpio_set_config,
+	.dbg_show		= mfx_gpio_dbg_show,
+	.can_sleep		= true,
+	.of_gpio_n_cells	= 2,
+	.of_xlate		= mfx_gpio_of_xlate,
+};
+
+static void mfx_gpio_irq_toggle_trigger(struct gpio_chip *gc, int offset)
+{
+	struct mfx_gpio *mfx_gpio = gpiochip_get_data(gc);
+	struct mfx *mfx = mfx_gpio->mfx;
+	u8 bank = get_bank(offset);
+	u8 mask = get_mask(offset);
+	int val = mfx_gpio_get(gc, offset);
+
+	if (val < 0) {
+		dev_err(mfx_gpio->dev, "can't get value of gpio%d: ret=%d\n",
+			offset, val);
+		return;
+	}
+
+	if (val) {
+		mfx_gpio->regs[REG_IRQ_TYPE][bank] &= ~mask;
+		mfx_set_bits(mfx, MFX_REG_IRQ_GPI_TYPE + bank, mask, 0);
+	} else {
+		mfx_gpio->regs[REG_IRQ_TYPE][bank] |= mask;
+		mfx_set_bits(mfx, MFX_REG_IRQ_GPI_TYPE + bank, mask, mask);
+	}
+}
+
+static int mfx_gpio_irq_set_type(struct irq_data *d, unsigned int type)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+	struct mfx_gpio *mfx_gpio = gpiochip_get_data(gc);
+	int bank = get_bank(d->hwirq);
+	int mask = get_mask(d->hwirq);
+
+	if ((type & IRQ_TYPE_LEVEL_LOW) || (type & IRQ_TYPE_LEVEL_HIGH))
+		mfx_gpio->regs[REG_IRQ_EVT][bank] &= ~mask;
+	else
+		mfx_gpio->regs[REG_IRQ_EVT][bank] |= mask;
+
+	if ((type & IRQ_TYPE_EDGE_RISING) || (type & IRQ_TYPE_LEVEL_HIGH))
+		mfx_gpio->regs[REG_IRQ_TYPE][bank] |= mask;
+	else
+		mfx_gpio->regs[REG_IRQ_TYPE][bank] &= ~mask;
+
+	/*
+	 * In case of (type & IRQ_TYPE_EDGE_BOTH), we need to know current
+	 * GPIO value to set the right edge trigger. But in atomic context
+	 * here we can't access registers over I2C. That's why (type &
+	 * IRQ_TYPE_EDGE_BOTH) will be managed in .irq_sync_unlock.
+	 */
+
+	return 0;
+}
+
+static void mfx_gpio_irq_lock(struct irq_data *d)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+	struct mfx_gpio *mfx_gpio = gpiochip_get_data(gc);
+
+	mutex_lock(&mfx_gpio->irq_lock);
+}
+
+static void mfx_gpio_irq_sync_unlock(struct irq_data *d)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+	struct mfx_gpio *mfx_gpio = gpiochip_get_data(gc);
+	struct mfx *mfx = mfx_gpio->mfx;
+	static const u8 cache_regs[] = {
+		[REG_IRQ_SRC] = MFX_REG_IRQ_GPI_SRC,
+		[REG_IRQ_EVT] = MFX_REG_IRQ_GPI_EVT,
+		[REG_IRQ_TYPE] = MFX_REG_IRQ_GPI_TYPE,
+	};
+	u8 i, bank;
+
+	/*
+	 * In case of (type & IRQ_TYPE_EDGE_BOTH), read the current GPIO value
+	 * (this couldn't be done in .irq_set_type because of atomic context)
+	 * to set the right irq trigger type.
+	 */
+	if (irqd_get_trigger_type(d) & IRQ_TYPE_EDGE_BOTH) {
+		int type;
+
+		if (mfx_gpio_get(gc, d->hwirq))
+			type = IRQ_TYPE_EDGE_FALLING;
+		else
+			type = IRQ_TYPE_EDGE_RISING;
+
+		mfx_gpio_irq_set_type(d, type);
+	}
+
+	for (i = 0; i < NR_CACHE_IRQ_REGS; i++) {
+		for (bank = 0; bank < NR_MAX_BANKS; bank++) {
+			u8 old = mfx_gpio->oldregs[i][bank];
+			u8 new = mfx_gpio->regs[i][bank];
+
+			if (new == old)
+				continue;
+
+			mfx_gpio->oldregs[i][bank] = new;
+			mfx_reg_write(mfx, cache_regs[i] + bank, new);
+		}
+	}
+
+	mutex_unlock(&mfx_gpio->irq_lock);
+}
+
+static void mfx_gpio_irq_mask(struct irq_data *d)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+	struct mfx_gpio *mfx_gpio = gpiochip_get_data(gc);
+	int offset = d->hwirq;
+	u8 bank = get_bank(offset);
+	u8 mask = get_mask(offset);
+
+	mfx_gpio->regs[REG_IRQ_SRC][bank] &= ~mask;
+}
+
+static void mfx_gpio_irq_unmask(struct irq_data *d)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+	struct mfx_gpio *mfx_gpio = gpiochip_get_data(gc);
+	int offset = d->hwirq;
+	u8 bank = get_bank(offset);
+	u8 mask = get_mask(offset);
+
+	mfx_gpio->regs[REG_IRQ_SRC][bank] |= mask;
+}
+
+static struct irq_chip mfx_gpio_irq_chip = {
+	.name			= "mfx-gpio",
+	.irq_bus_lock		= mfx_gpio_irq_lock,
+	.irq_bus_sync_unlock	= mfx_gpio_irq_sync_unlock,
+	.irq_mask		= mfx_gpio_irq_mask,
+	.irq_unmask		= mfx_gpio_irq_unmask,
+	.irq_set_type		= mfx_gpio_irq_set_type,
+};
+
+static irqreturn_t mfx_gpio_irq(int irq, void *dev)
+{
+	struct mfx_gpio *mfx_gpio = dev;
+	struct mfx *mfx = mfx_gpio->mfx;
+	int num_banks = mfx->num_gpio / 8;
+	u8 status[num_banks];
+	int ret;
+	u8 bank;
+
+	ret = mfx_block_read(mfx, MFX_REG_IRQ_GPI_PENDING, ARRAY_SIZE(status),
+			     status);
+	if (ret < 0) {
+		dev_err(mfx_gpio->dev, "can't get IRQ_GPI_PENDING: %d\n", ret);
+		return IRQ_NONE;
+	}
+
+	for (bank = 0; bank < ARRAY_SIZE(status); bank++) {
+		u8 stat = status[bank];
+
+		if (!stat)
+			continue;
+
+		while (stat) {
+			int bit = __ffs(stat);
+			int offset = bank * NR_GPIOS_PER_BANK + bit;
+			int gpio_irq = irq_find_mapping(mfx_gpio->gc.irq.domain,
+							offset);
+			int irq_trigger = irq_get_trigger_type(gpio_irq);
+
+			handle_nested_irq(gpio_irq);
+			stat &= ~(BIT(bit));
+
+			if (irq_trigger & IRQ_TYPE_EDGE_BOTH)
+				mfx_gpio_irq_toggle_trigger(&mfx_gpio->gc,
+							    offset);
+		}
+
+		mfx_reg_write(mfx, MFX_REG_IRQ_GPI_ACK + bank, status[bank]);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int mfx_gpio_probe(struct platform_device *pdev)
+{
+	struct mfx *mfx = dev_get_drvdata(pdev->dev.parent);
+	struct mfx_gpio *mfx_gpio;
+	int irq;
+	int ret;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(&pdev->dev, "failed to get irq: %d\n", irq);
+
+		return irq;
+	}
+
+	mfx_gpio = devm_kzalloc(&pdev->dev, sizeof(struct mfx_gpio),
+				GFP_KERNEL);
+	if (!mfx_gpio)
+		return -ENOMEM;
+
+	mutex_init(&mfx_gpio->irq_lock);
+
+	mfx_gpio->dev = &pdev->dev;
+	mfx_gpio->mfx = mfx;
+
+	mfx_gpio->gc = mfx_gpio_chip;
+	mfx_gpio->gc.ngpio = mfx->num_gpio;
+	mfx_gpio->gc.parent = &pdev->dev;
+	mfx_gpio->gc.base = -1;
+	mfx_gpio->gc.of_node = pdev->dev.of_node;
+
+	if (mfx->blocks & MFX_BLOCK_ALTGPIO)
+		ret = mfx_enable(mfx, MFX_BLOCK_GPIO | MFX_BLOCK_ALTGPIO);
+	else
+		ret = mfx_enable(mfx, MFX_BLOCK_GPIO);
+
+	if (ret < 0)
+		return ret;
+
+	ret = devm_request_threaded_irq(&pdev->dev, irq,
+					NULL, mfx_gpio_irq, IRQF_ONESHOT,
+					"mfx-gpio", mfx_gpio);
+	if (ret) {
+		dev_err(&pdev->dev, "unable to request irq: %d\n", ret);
+		return ret;
+	}
+
+	ret = devm_gpiochip_add_data(&pdev->dev, &mfx_gpio->gc, mfx_gpio);
+	if (ret) {
+		dev_err(&pdev->dev, "unable to add gpiochip: %d\n", ret);
+		return ret;
+	}
+
+	ret = gpiochip_irqchip_add_nested(&mfx_gpio->gc, &mfx_gpio_irq_chip, 0,
+					  handle_simple_irq, IRQ_TYPE_NONE);
+	if (ret) {
+		dev_err(&pdev->dev, "could not connect irqchip to gpiochip\n");
+		return ret;
+	}
+
+	gpiochip_set_nested_irqchip(&mfx_gpio->gc, &mfx_gpio_irq_chip, irq);
+
+	platform_set_drvdata(pdev, mfx_gpio);
+
+	dev_info(&pdev->dev, "ST-MFX (GPIO) initialized\n");
+
+	return 0;
+}
+
+static int mfx_gpio_remove(struct platform_device *pdev)
+{
+	struct mfx *mfx = dev_get_drvdata(pdev->dev.parent);
+
+	return mfx_disable(mfx, MFX_BLOCK_GPIO | MFX_BLOCK_ALTGPIO);
+}
+
+static const struct of_device_id mfx_gpio_of_match[] = {
+	{ .compatible = "st,mfx-gpio" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, mfx_of_match);
+
+static struct platform_driver mfx_gpio_driver = {
+	.driver = {
+		.name	= "st-mfx-gpio",
+		.of_match_table = mfx_gpio_of_match,
+	},
+	.probe		= mfx_gpio_probe,
+	.remove		= mfx_gpio_remove,
+};
+module_platform_driver(mfx_gpio_driver);
+
+MODULE_DESCRIPTION("STMicroelectronics Multi-Function eXpander GPIO driver");
+MODULE_AUTHOR("Amelie Delaunay <amelie.delaunay at st.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/include/dt-bindings/gpio/st-mfx-gpio.h b/include/dt-bindings/gpio/st-mfx-gpio.h
new file mode 100644
index 0000000..78dc7c5
--- /dev/null
+++ b/include/dt-bindings/gpio/st-mfx-gpio.h
@@ -0,0 +1,24 @@
+/*
+ * This header provides constants for binding st,mfx-gpio.
+ *
+ * The first cell in ST MFX GPIO specifier is the gpio number.
+ *
+ * The second cell contains standard flag values specified in gpio.h and
+ * specific flag values described here.
+ */
+
+#ifndef _DT_BINDINGS_GPIO_ST_MFX_GPIO_H
+#define _DT_BINDINGS_GPIO_ST_MFX_GPIO_H
+
+#include <dt-bindings/gpio/gpio.h>
+
+#define GPIO_IN_NO_PULL		0
+#define GPIO_IN_PUSH_PULL	2
+#define GPIO_OUT_PUSH_PULL	GPIO_PUSH_PULL
+#define GPIO_OUT_OPEN_DRAIN	GPIO_SINGLE_ENDED
+
+#define GPIO_NO_PULL		0
+#define GPIO_PULL_DOWN		0
+#define GPIO_PULL_UP		4
+
+#endif
-- 
2.7.4




More information about the linux-arm-kernel mailing list