From: Boris Brezillon <boris.brezillon@bootlin.com>
To: Wolfram Sang <wsa@the-dreams.de>,
linux-i2c@vger.kernel.org, Jonathan Corbet <corbet@lwn.net>,
linux-doc@vger.kernel.org,
Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
Arnd Bergmann <arnd@arndb.de>
Cc: Przemyslaw Sroka <psroka@cadence.com>,
Arkadiusz Golec <agolec@cadence.com>,
Alan Douglas <adouglas@cadence.com>,
Bartosz Folta <bfolta@cadence.com>, Damian Kos <dkos@cadence.com>,
Alicja Jurasik-Urbaniak <alicja@cadence.com>,
Cyprian Wronka <cwronka@cadence.com>,
Suresh Punnoose <sureshp@cadence.com>,
Rafal Ciepiela <rafalc@cadence.com>,
Thomas Petazzoni <thomas.petazzoni@free-electrons.com>,
Nishanth Menon <nm@ti.com>, Rob Herring <robh+dt@kernel.org>,
Pawel Moll <pawel.moll@arm.com>,
Mark Rutland <mark.rutland@arm.com>,
Ian Campbell <ijc+devicetree@hellion.org.uk>,
Kumar Gala <galak@codeaurora.org>,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
Vitor Soares <Vitor.Soares@synopsys.com>,
Geert Uytterhoeven <geert@linux-m68k.org>,
Linus Walleij <linus.walleij@linaro.org>,
Xiang Lin <Xiang.Lin@synaptics.com>,
linux-gpio@vger.kernel.org,
Boris Brezillon <boris.brezillon@bootlin.com>
Subject: [PATCH v3 10/11] gpio: Add a driver for Cadence I3C GPIO expander
Date: Fri, 23 Mar 2018 12:00:19 +0100 [thread overview]
Message-ID: <20180323110020.19080-11-boris.brezillon@bootlin.com> (raw)
In-Reply-To: <20180323110020.19080-1-boris.brezillon@bootlin.com>
Add a driver for Cadence I3C GPIO expander.
Signed-off-by: Boris Brezillon <boris.brezillon@bootlin.com>
---
drivers/gpio/Kconfig | 11 ++
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-cdns-i3c.c | 380 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 392 insertions(+)
create mode 100644 drivers/gpio/gpio-cdns-i3c.c
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 8dbb2280538d..87b7083179ff 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -862,6 +862,17 @@ config GPIO_TS4900
endmenu
+menu "I3C GPIO expanders"
+ depends on I3C
+
+config GPIO_CDNS_I3C
+ tristate "Cadence I3C GPIO expander"
+ select GPIOLIB_IRQCHIP
+ help
+ Say yes here to enabled the driver for Cadence I3C GPIO expander.
+
+endmenu
+
menu "MFD GPIO expanders"
config GPIO_ADP5520
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index cccb0d40846c..22a7151fc565 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -36,6 +36,7 @@ obj-$(CONFIG_GPIO_BCM_KONA) += gpio-bcm-kona.o
obj-$(CONFIG_GPIO_BD9571MWV) += gpio-bd9571mwv.o
obj-$(CONFIG_GPIO_BRCMSTB) += gpio-brcmstb.o
obj-$(CONFIG_GPIO_BT8XX) += gpio-bt8xx.o
+obj-$(CONFIG_GPIO_CDNS_I3C) += gpio-cdns-i3c.o
obj-$(CONFIG_GPIO_CLPS711X) += gpio-clps711x.o
obj-$(CONFIG_GPIO_CS5535) += gpio-cs5535.o
obj-$(CONFIG_GPIO_CRYSTAL_COVE) += gpio-crystalcove.o
diff --git a/drivers/gpio/gpio-cdns-i3c.c b/drivers/gpio/gpio-cdns-i3c.c
new file mode 100644
index 000000000000..5a75891b47fe
--- /dev/null
+++ b/drivers/gpio/gpio-cdns-i3c.c
@@ -0,0 +1,380 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Cadence Design Systems Inc.
+ *
+ * Author: Boris Brezillon <boris.brezillon@bootlin.com>
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/i3c/device.h>
+#include <linux/module.h>
+
+#define OVR 0x0
+#define IVR 0x1
+#define DIR_MODE 0x2
+#define IMR 0x3
+#define ISR 0x4
+#define ITR(x) (0x5 + (x))
+
+struct cdns_i3c_gpio {
+ struct gpio_chip gpioc;
+ struct irq_chip irqc;
+ struct i3c_device *i3cdev;
+ struct mutex irq_lock;
+ u8 dir;
+ u8 ovr;
+ u8 imr;
+ u8 itr[3];
+};
+
+static struct cdns_i3c_gpio *gpioc_to_cdns_gpioc(struct gpio_chip *gpioc)
+{
+ return container_of(gpioc, struct cdns_i3c_gpio, gpioc);
+}
+
+static int cdns_i3c_gpio_read_reg(struct cdns_i3c_gpio *gpioc, u8 reg,
+ u8 *val)
+{
+ struct i3c_priv_xfer xfers[] = {
+ {
+ .len = sizeof(reg),
+ .data.out = ®,
+ },
+ {
+ .rnw = true,
+ .len = sizeof(*val),
+ .data.in = val,
+ },
+ };
+
+ return i3c_device_do_priv_xfers(gpioc->i3cdev, xfers,
+ ARRAY_SIZE(xfers));
+}
+
+static int cdns_i3c_gpio_write_reg(struct cdns_i3c_gpio *gpioc, u8 reg,
+ u8 val)
+{
+ struct i3c_priv_xfer xfers[] = {
+ {
+ .len = sizeof(reg),
+ .data.out = ®,
+ },
+ {
+ .len = sizeof(val),
+ .data.out = &val,
+ },
+ };
+
+ return i3c_device_do_priv_xfers(gpioc->i3cdev, xfers,
+ ARRAY_SIZE(xfers));
+}
+
+static int cdns_i3c_gpio_get_direction(struct gpio_chip *g, unsigned offset)
+{
+ struct cdns_i3c_gpio *gpioc = gpioc_to_cdns_gpioc(g);
+
+ return gpioc->dir & BIT(offset);
+}
+
+static void cdns_i3c_gpio_set_multiple(struct gpio_chip *g,
+ unsigned long *mask,
+ unsigned long *bits)
+{
+ struct cdns_i3c_gpio *gpioc = gpioc_to_cdns_gpioc(g);
+ u8 newovr;
+ int ret;
+
+ newovr = (gpioc->ovr & ~(*mask)) | (*bits & *mask);
+ if (newovr == gpioc->ovr)
+ return;
+
+ ret = cdns_i3c_gpio_write_reg(gpioc, OVR, newovr);
+ if (!ret)
+ gpioc->ovr = newovr;
+}
+
+static void cdns_i3c_gpio_set(struct gpio_chip *g, unsigned offset, int value)
+{
+ unsigned long mask = BIT(offset), bits = value ? BIT(offset) : 0;
+
+ cdns_i3c_gpio_set_multiple(g, &mask, &bits);
+}
+
+static int cdns_i3c_gpio_set_dir(struct cdns_i3c_gpio *gpioc, unsigned pin,
+ bool in)
+{
+ u8 newdir;
+ int ret;
+
+ newdir = gpioc->dir;
+ if (in)
+ newdir |= BIT(pin);
+ else
+ newdir &= ~BIT(pin);
+
+ if (newdir == gpioc->dir)
+ return 0;
+
+ gpioc->dir = newdir;
+ ret = cdns_i3c_gpio_write_reg(gpioc, DIR_MODE, newdir);
+ if (!ret)
+ gpioc->dir = newdir;
+
+ return ret;
+}
+
+static int cdns_i3c_gpio_dir_input(struct gpio_chip *g, unsigned offset)
+{
+ struct cdns_i3c_gpio *gpioc = gpioc_to_cdns_gpioc(g);
+
+ return cdns_i3c_gpio_set_dir(gpioc, offset, true);
+}
+
+static int cdns_i3c_gpio_dir_output(struct gpio_chip *g, unsigned offset,
+ int val)
+{
+ struct cdns_i3c_gpio *gpioc = gpioc_to_cdns_gpioc(g);
+
+ cdns_i3c_gpio_set(g, offset, val);
+
+ return cdns_i3c_gpio_set_dir(gpioc, offset, true);
+}
+
+static int cdns_i3c_gpio_get_multiple(struct gpio_chip *g,
+ unsigned long *mask,
+ unsigned long *bits)
+{
+ struct cdns_i3c_gpio *gpioc = gpioc_to_cdns_gpioc(g);
+ int ret;
+ u8 ivr;
+
+ ret = cdns_i3c_gpio_read_reg(gpioc, IVR, &ivr);
+ if (ret)
+ return ret;
+
+ *bits = ivr & *mask & gpioc->dir;
+ *bits |= gpioc->ovr & *mask & ~gpioc->dir;
+
+ return 0;
+}
+
+static int cdns_i3c_gpio_get(struct gpio_chip *g, unsigned offset)
+{
+ unsigned long mask = BIT(offset), bits = 0;
+ int ret;
+
+ ret = cdns_i3c_gpio_get_multiple(g, &mask, &bits);
+ if (ret)
+ return ret;
+
+ return mask & bits;
+}
+
+static void cdns_i3c_gpio_ibi_handler(struct i3c_device *i3cdev,
+ const struct i3c_ibi_payload *payload)
+{
+ struct cdns_i3c_gpio *gpioc = i3cdev_get_drvdata(i3cdev);
+ u8 isr = 0;
+ int i;
+
+ cdns_i3c_gpio_read_reg(gpioc, ISR, &isr);
+ for (i = 0; i < 8; i++) {
+ unsigned int irq;
+
+ if (!(BIT(i) & isr & gpioc->imr))
+ continue;
+
+ irq = irq_find_mapping(gpioc->gpioc.irq.domain, i);
+ handle_nested_irq(irq);
+ }
+}
+
+static void cdns_i3c_gpio_irq_lock(struct irq_data *data)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+ struct cdns_i3c_gpio *gpioc = gpiochip_get_data(gc);
+
+ mutex_lock(&gpioc->irq_lock);
+}
+
+static void cdns_i3c_gpio_irq_sync_unlock(struct irq_data *data)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+ struct cdns_i3c_gpio *gpioc = gpiochip_get_data(gc);
+ int i;
+
+ cdns_i3c_gpio_write_reg(gpioc, IMR, gpioc->imr);
+ for (i = 0; i < 3; i++)
+ cdns_i3c_gpio_write_reg(gpioc, ITR(i), gpioc->itr[i]);
+
+ mutex_unlock(&gpioc->irq_lock);
+}
+
+static void cdns_i3c_gpio_irq_unmask(struct irq_data *data)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+ struct cdns_i3c_gpio *gpioc = gpiochip_get_data(gc);
+
+ gpioc->imr |= BIT(data->hwirq);
+}
+
+static void cdns_i3c_gpio_irq_mask(struct irq_data *data)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+ struct cdns_i3c_gpio *gpioc = gpiochip_get_data(gc);
+
+ gpioc->imr &= ~BIT(data->hwirq);
+}
+
+static int cdns_i3c_gpio_irq_set_type(struct irq_data *data, unsigned type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+ struct cdns_i3c_gpio *gpioc = gpiochip_get_data(gc);
+
+ switch (type) {
+ case IRQ_TYPE_LEVEL_HIGH:
+ gpioc->itr[0] |= BIT(data->hwirq);
+ gpioc->itr[1] |= BIT(data->hwirq);
+ break;
+
+ case IRQ_TYPE_LEVEL_LOW:
+ gpioc->itr[0] |= BIT(data->hwirq);
+ gpioc->itr[1] &= ~BIT(data->hwirq);
+ break;
+
+ case IRQ_TYPE_EDGE_BOTH:
+ gpioc->itr[0] &= ~BIT(data->hwirq);
+ gpioc->itr[2] |= BIT(data->hwirq);
+ break;
+
+ case IRQ_TYPE_EDGE_RISING:
+ gpioc->itr[0] &= ~BIT(data->hwirq);
+ gpioc->itr[1] |= BIT(data->hwirq);
+ gpioc->itr[2] &= ~BIT(data->hwirq);
+ break;
+
+ case IRQ_TYPE_EDGE_FALLING:
+ gpioc->itr[0] &= ~BIT(data->hwirq);
+ gpioc->itr[1] &= ~BIT(data->hwirq);
+ gpioc->itr[2] &= ~BIT(data->hwirq);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int cdns_i3c_gpio_probe(struct i3c_device *i3cdev)
+{
+ struct cdns_i3c_gpio *gpioc;
+ struct device *parent = i3cdev_to_dev(i3cdev);
+ struct i3c_ibi_setup ibisetup = {
+ .max_payload_len = 2,
+ .num_slots = 1,
+ .handler = cdns_i3c_gpio_ibi_handler,
+ };
+ int ret;
+
+ gpioc = devm_kzalloc(parent, sizeof(*gpioc), GFP_KERNEL);
+ if (!gpioc)
+ return -ENOMEM;
+
+ gpioc->i3cdev = i3cdev;
+ i3cdev_set_drvdata(i3cdev, gpioc);
+
+ /* Mask all interrupts. */
+ ret = cdns_i3c_gpio_write_reg(gpioc, IMR, 0);
+ if (ret)
+ return ret;
+
+ /*
+ * Clear the ISR after reading it, not when the IBI is is Acked by the
+ * I3C master. This way we make sure we don't lose events.
+ */
+ ret = cdns_i3c_gpio_write_reg(gpioc, ITR(3), 0xff);
+ if (ret)
+ return ret;
+
+ ret = cdns_i3c_gpio_read_reg(gpioc, DIR_MODE, &gpioc->dir);
+ if (ret)
+ return ret;
+
+ ret = cdns_i3c_gpio_read_reg(gpioc, OVR, &gpioc->ovr);
+ if (ret)
+ return ret;
+
+ ret = i3c_device_request_ibi(i3cdev, &ibisetup);
+ if (ret)
+ return ret;
+
+ gpioc->gpioc.label = dev_name(parent);
+ gpioc->gpioc.owner = THIS_MODULE;
+ gpioc->gpioc.parent = parent;
+ gpioc->gpioc.base = -1;
+ gpioc->gpioc.ngpio = 8;
+ gpioc->gpioc.can_sleep = true;
+ gpioc->gpioc.get_direction = cdns_i3c_gpio_get_direction;
+ gpioc->gpioc.direction_input = cdns_i3c_gpio_dir_input;
+ gpioc->gpioc.direction_output = cdns_i3c_gpio_dir_output;
+ gpioc->gpioc.get = cdns_i3c_gpio_get;
+ gpioc->gpioc.get_multiple = cdns_i3c_gpio_get_multiple;
+ gpioc->gpioc.set = cdns_i3c_gpio_set;
+ gpioc->gpioc.set_multiple = cdns_i3c_gpio_set_multiple;
+
+ ret = devm_gpiochip_add_data(parent, &gpioc->gpioc, gpioc);
+ if (ret)
+ return ret;
+
+ gpioc->irqc.name = dev_name(parent);
+ gpioc->irqc.parent_device = parent;
+ gpioc->irqc.irq_unmask = cdns_i3c_gpio_irq_unmask;
+ gpioc->irqc.irq_mask = cdns_i3c_gpio_irq_mask;
+ gpioc->irqc.irq_bus_lock = cdns_i3c_gpio_irq_lock;
+ gpioc->irqc.irq_bus_sync_unlock = cdns_i3c_gpio_irq_sync_unlock;
+ gpioc->irqc.irq_set_type = cdns_i3c_gpio_irq_set_type;
+ gpioc->irqc.flags = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_MASK_ON_SUSPEND;
+
+ ret = gpiochip_irqchip_add_nested(&gpioc->gpioc, &gpioc->irqc, 0,
+ handle_simple_irq, IRQ_TYPE_NONE);
+ if (ret)
+ goto err_free_ibi;
+
+ ret = i3c_device_enable_ibi(i3cdev);
+ if (ret)
+ goto err_free_ibi;
+
+ return 0;
+
+err_free_ibi:
+ i3c_device_free_ibi(i3cdev);
+
+ return ret;
+}
+
+static int cdns_i3c_gpio_remove(struct i3c_device *i3cdev)
+{
+ i3c_device_disable_ibi(i3cdev);
+ i3c_device_free_ibi(i3cdev);
+
+ return 0;
+}
+
+static const struct i3c_device_id cdns_i3c_gpio_ids[] = {
+ I3C_DEVICE(0x1c9, 0x0, NULL),
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(i3c, cdns_i3c_gpio_ids);
+
+static struct i3c_driver cdns_i3c_gpio = {
+ .driver.name = "cdns-i3c-gpio",
+ .id_table = cdns_i3c_gpio_ids,
+ .probe = cdns_i3c_gpio_probe,
+ .remove = cdns_i3c_gpio_remove,
+};
+module_i3c_driver(cdns_i3c_gpio);
+
+MODULE_AUTHOR("Boris Brezillon <boris.brezillon@bootlin.com.com>");
+MODULE_DESCRIPTION("Driver for Cadence I3C GPIO expander");
+MODULE_LICENSE("GPL v2");
--
2.14.1
next prev parent reply other threads:[~2018-03-23 11:00 UTC|newest]
Thread overview: 34+ messages / expand[flat|nested] mbox.gz Atom feed top
2018-03-23 11:00 [PATCH v3 00/11] Add the I3C subsystem Boris Brezillon
2018-03-23 11:00 ` [PATCH v3 01/11] i2c: Export of_i2c_get_board_info() Boris Brezillon
2018-03-24 22:35 ` Wolfram Sang
2018-03-24 22:38 ` Wolfram Sang
2018-03-25 10:21 ` Boris Brezillon
2018-03-25 10:19 ` Boris Brezillon
2018-03-23 11:00 ` [PATCH v3 02/11] i3c: Add core I3C infrastructure Boris Brezillon
2018-03-23 11:00 ` [PATCH v3 03/11] docs: driver-api: Add I3C documentation Boris Brezillon
2018-03-23 11:00 ` [PATCH v3 04/11] i3c: Add sysfs ABI spec Boris Brezillon
2018-03-23 11:00 ` [PATCH v3 05/11] dt-bindings: i3c: Document core bindings Boris Brezillon
2018-03-23 12:47 ` Peter Rosin
2018-03-23 13:58 ` Boris Brezillon
2018-03-26 10:22 ` Geert Uytterhoeven
2018-03-26 11:19 ` Boris Brezillon
2018-03-26 22:24 ` Rob Herring
2018-03-28 8:19 ` Boris Brezillon
2018-03-28 16:42 ` Rob Herring
2018-03-28 17:28 ` Boris Brezillon
2018-03-23 11:00 ` [PATCH v3 06/11] dt-bindings: i3c: Add macros to help fill I3C/I2C device's reg property Boris Brezillon
2018-03-26 22:25 ` Rob Herring
2018-03-23 11:00 ` [PATCH v3 07/11] MAINTAINERS: Add myself as the I3C subsystem maintainer Boris Brezillon
2018-03-23 11:00 ` [PATCH v3 08/11] i3c: master: Add driver for Cadence IP Boris Brezillon
2018-03-23 11:00 ` [PATCH v3 09/11] dt-bindings: i3c: Document Cadence I3C master bindings Boris Brezillon
2018-03-23 11:10 ` Thomas Petazzoni
2018-03-23 13:59 ` Boris Brezillon
2018-03-23 11:00 ` Boris Brezillon [this message]
2018-03-23 11:00 ` [PATCH v3 11/11] dt-bindings: gpio: Add bindings for Cadence I3C gpio expander Boris Brezillon
2018-03-26 10:12 ` Geert Uytterhoeven
2018-03-26 11:25 ` Boris Brezillon
2018-03-26 11:35 ` Geert Uytterhoeven
2018-03-26 10:17 ` Geert Uytterhoeven
2018-03-26 11:21 ` Boris Brezillon
2018-03-26 22:25 ` Rob Herring
2018-03-23 11:03 ` [PATCH v3 00/11] Add the I3C subsystem Boris Brezillon
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=20180323110020.19080-11-boris.brezillon@bootlin.com \
--to=boris.brezillon@bootlin.com \
--cc=Vitor.Soares@synopsys.com \
--cc=Xiang.Lin@synaptics.com \
--cc=adouglas@cadence.com \
--cc=agolec@cadence.com \
--cc=alicja@cadence.com \
--cc=arnd@arndb.de \
--cc=bfolta@cadence.com \
--cc=corbet@lwn.net \
--cc=cwronka@cadence.com \
--cc=devicetree@vger.kernel.org \
--cc=dkos@cadence.com \
--cc=galak@codeaurora.org \
--cc=geert@linux-m68k.org \
--cc=gregkh@linuxfoundation.org \
--cc=ijc+devicetree@hellion.org.uk \
--cc=linus.walleij@linaro.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-gpio@vger.kernel.org \
--cc=linux-i2c@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=mark.rutland@arm.com \
--cc=nm@ti.com \
--cc=pawel.moll@arm.com \
--cc=psroka@cadence.com \
--cc=rafalc@cadence.com \
--cc=robh+dt@kernel.org \
--cc=sureshp@cadence.com \
--cc=thomas.petazzoni@free-electrons.com \
--cc=wsa@the-dreams.de \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).