Thread (12 messages) 12 messages, 4 authors, 2015-01-20

[PATCH v6 2/3] gpio: Cygnus: add GPIO driver

From: Linus Walleij <hidden>
Date: 2015-01-13 08:53:15
Also in: linux-devicetree, linux-gpio, lkml

On Tue, Dec 16, 2014 at 3:18 AM, Ray Jui [off-list ref] wrote:
This GPIO driver supports all 3 GPIO controllers in the Broadcom Cygnus
SoC. The 3 GPIO controllers are 1) the ASIU GPIO controller, 2) the
chipCommonG GPIO controller, and 3) the ALWAYS-ON GPIO controller

Signed-off-by: Ray Jui <rjui@broadcom.com>
Reviewed-by: Scott Branden <sbranden@broadcom.com>
(Big thanks to Alexandre for doing the major part of the review,
good work with following up so far!)

(...)
+config GPIO_BCM_CYGNUS
+       bool "Broadcom Cygnus GPIO support"
+       depends on ARCH_BCM_CYGNUS && OF_GPIO
select GPIOLIB_IRQCHIP

See more about this below.
quoted hunk ↗ jump to hunk
+++ b/drivers/gpio/gpio-bcm-cygnus.c
@@ -0,0 +1,607 @@
+/*
+ * Copyright (C) 2014 Broadcom Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/ioport.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/irqchip/chained_irq.h>
Skip <linux/irq.h> and <linux/irqchip/chained_irq.h>
as these move to the core with GPIOLIB_IRQCHIP
+#define CYGNUS_GPIO_DATA_IN_OFFSET   0x00
+#define CYGNUS_GPIO_DATA_OUT_OFFSET  0x04
+#define CYGNUS_GPIO_OUT_EN_OFFSET    0x08
+#define CYGNUS_GPIO_IN_TYPE_OFFSET   0x0c
+#define CYGNUS_GPIO_INT_DE_OFFSET    0x10
+#define CYGNUS_GPIO_INT_EDGE_OFFSET  0x14
+#define CYGNUS_GPIO_INT_MSK_OFFSET   0x18
+#define CYGNUS_GPIO_INT_STAT_OFFSET  0x1c
+#define CYGNUS_GPIO_INT_MSTAT_OFFSET 0x20
+#define CYGNUS_GPIO_INT_CLR_OFFSET   0x24
+#define CYGNUS_GPIO_PAD_RES_OFFSET   0x34
+#define CYGNUS_GPIO_RES_EN_OFFSET    0x38
+
+/* drive strength control for ASIU GPIO */
+#define CYGNUS_GPIO_ASIU_DRV0_CTRL_OFFSET 0x58
+
+/* drive strength control for CCM GPIO */
+#define CYGNUS_GPIO_CCM_DRV0_CTRL_OFFSET  0x00
This stuff (drive strength) is pin control, pin config.
It does not belong in a pure GPIO driver. If you're
making a combined pin control + GPIO driver, it
shall be put in drivers/pinctrl/*
+#define GPIO_BANK_SIZE 0x200
+#define NGPIOS_PER_BANK 32
+#define GPIO_BANK(pin) ((pin) / NGPIOS_PER_BANK)
+
+#define CYGNUS_GPIO_REG(pin, reg) (GPIO_BANK(pin) * GPIO_BANK_SIZE + (reg))
+#define CYGNUS_GPIO_SHIFT(pin) ((pin) % NGPIOS_PER_BANK)
+
+#define GPIO_FLAG_BIT_MASK           0xffff
+#define GPIO_PULL_BIT_SHIFT          16
+#define GPIO_PULL_BIT_MASK           0x3
+
+#define GPIO_DRV_STRENGTH_BIT_SHIFT  20
+#define GPIO_DRV_STRENGTH_BITS       3
+#define GPIO_DRV_STRENGTH_BIT_MASK   ((1 << GPIO_DRV_STRENGTH_BITS) - 1)
+
+/*
+ * For GPIO internal pull up/down registers
+ */
+enum gpio_pull {
+       GPIO_PULL_NONE = 0,
+       GPIO_PULL_UP,
+       GPIO_PULL_DOWN,
+       GPIO_PULL_INVALID,
+};
+
+/*
+ * GPIO drive strength
+ */
+enum gpio_drv_strength {
+       GPIO_DRV_STRENGTH_2MA = 0,
+       GPIO_DRV_STRENGTH_4MA,
+       GPIO_DRV_STRENGTH_6MA,
+       GPIO_DRV_STRENGTH_8MA,
+       GPIO_DRV_STRENGTH_10MA,
+       GPIO_DRV_STRENGTH_12MA,
+       GPIO_DRV_STRENGTH_14MA,
+       GPIO_DRV_STRENGTH_16MA,
+       GPIO_DRV_STRENGTH_INVALID,
+};

All this pull up/down and drive strength is pin config for
the pin control subsystem.
+struct cygnus_gpio {
+       struct device *dev;
+       void __iomem *base;
+       void __iomem *io_ctrl;
+       spinlock_t lock;
+       struct gpio_chip gc;
+       unsigned num_banks;
+       int irq;
+       struct irq_domain *irq_domain;
Skip irq and irqdomain and use GPIOLIB_IRQCHIP
+static u32 cygnus_readl(struct cygnus_gpio *cygnus_gpio, unsigned int offset)
+{
+       return readl(cygnus_gpio->base + offset);
+}
+
+static void cygnus_writel(struct cygnus_gpio *cygnus_gpio,
+                         unsigned int offset, u32 val)
+{
+       writel(val, cygnus_gpio->base + offset);
+}
I don't see the value of using these accessors over just inlining
your readl/writel stuff.

(...)
+static int cygnus_gpio_to_irq(struct gpio_chip *gc, unsigned offset)
+{
+       struct cygnus_gpio *cygnus_gpio = to_cygnus_gpio(gc);
+
+       return irq_find_mapping(cygnus_gpio->irq_domain, offset);
+}
This goes away to the core with GPIOLIB_IRQCHIP
+static void cygnus_gpio_irq_handler(unsigned int irq, struct irq_desc *desc)
+{
+       struct cygnus_gpio *cygnus_gpio;
+       struct irq_chip *chip = irq_desc_get_chip(desc);
+       int i, bit;
+
+       chained_irq_enter(chip, desc);
+
+       cygnus_gpio = irq_get_handler_data(irq);
+
+       /* go through the entire GPIO banks and handle all interrupts */
+       for (i = 0; i < cygnus_gpio->num_banks; i++) {
+               unsigned long val = cygnus_readl(cygnus_gpio,
+                               (i * GPIO_BANK_SIZE) +
+                               CYGNUS_GPIO_INT_MSTAT_OFFSET);
+
+               for_each_set_bit(bit, &val, NGPIOS_PER_BANK) {
+                       unsigned pin = NGPIOS_PER_BANK * i + bit;
+                       int child_irq =
+                               cygnus_gpio_to_irq(&cygnus_gpio->gc, pin);
+
+                       /*
+                        * Clear the interrupt before invoking the
+                        * handler, so we do not leave any window
+                        */
+                       cygnus_writel(cygnus_gpio, (i * GPIO_BANK_SIZE) +
+                               CYGNUS_GPIO_INT_CLR_OFFSET, BIT(bit));
+
+                       generic_handle_irq(child_irq);
+               }
+       }
+
+       chained_irq_exit(chip, desc);
+}
Looks good, but you will need to have the struct gpio_chip * as
handler data to use GPIOLIB_IRQCHIP, so get from there to
the struct cygnus_gpio something like:

struct gpio_chip *gc = irq_desc_get_handler_data(desc);
struct cygnus_gpio *cyg = to_cygnus_gpio(gc);
+static int cygnus_gpio_get(struct gpio_chip *gc, unsigned gpio)
+{
+       struct cygnus_gpio *cygnus_gpio = to_cygnus_gpio(gc);
+       unsigned int offset = CYGNUS_GPIO_REG(gpio,
+                       CYGNUS_GPIO_DATA_IN_OFFSET);
+       unsigned int shift = CYGNUS_GPIO_SHIFT(gpio);
+       u32 val;
+
+       val = cygnus_readl(cygnus_gpio, offset);
+       val = (val >> shift) & 1;
No, do this:

return !!(cygnus_readl(cygnus_gpio, offset) & BIT(shift));

Maybe rename the "shift" variable to "bit" or just use the macro
directly in the readl().
+static int cygnus_gpio_irq_map(struct irq_domain *d, unsigned int irq,
+                              irq_hw_number_t hwirq)
+{
+       int ret;
+
+       ret = irq_set_chip_data(irq, d->host_data);
+       if (ret < 0)
+               return ret;
+       irq_set_lockdep_class(irq, &gpio_lock_class);
+       irq_set_chip_and_handler(irq, &cygnus_gpio_irq_chip,
+                       handle_simple_irq);
+       set_irq_flags(irq, IRQF_VALID);
+
+       return 0;
+}
+
+static void cygnus_gpio_irq_unmap(struct irq_domain *d, unsigned int irq)
+{
+       irq_set_chip_and_handler(irq, NULL, NULL);
+       irq_set_chip_data(irq, NULL);
+}
+
+static struct irq_domain_ops cygnus_irq_ops = {
+       .map = cygnus_gpio_irq_map,
+       .unmap = cygnus_gpio_irq_unmap,
+       .xlate = irq_domain_xlate_twocell,
+};
All this goes away with GPIOLIB_IRQCHIP (that is what is good about it).
+#ifdef CONFIG_OF_GPIO
What, that should be defined all the time, you depend on it in
Kconfig!
+static void cygnus_gpio_set_pull(struct cygnus_gpio *cygnus_gpio,
+                                unsigned gpio, enum gpio_pull pull)
(...)
+static void cygnus_gpio_set_strength(struct cygnus_gpio *cygnus_gpio,
+               unsigned gpio, enum gpio_drv_strength strength)
(...)
+static int cygnus_gpio_of_xlate(struct gpio_chip *gc,
+               const struct of_phandle_args *gpiospec, u32 *flags)
NAK. This is pin control, put this in the pin control driver.

I guess the same that is part of this patch series.

(...)
+static int cygnus_gpio_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct resource *res;
+       struct cygnus_gpio *cygnus_gpio;
+       struct gpio_chip *gc;
+       u32 i, ngpios;
+       int ret;
+
+       cygnus_gpio = devm_kzalloc(dev, sizeof(*cygnus_gpio), GFP_KERNEL);
+       if (!cygnus_gpio)
+               return -ENOMEM;
+
+       cygnus_gpio->dev = dev;
+       platform_set_drvdata(pdev, cygnus_gpio);
+
+       if (of_property_read_u32(dev->of_node, "ngpios", &ngpios)) {
+               dev_err(&pdev->dev, "missing ngpios DT property\n");
+               return -ENODEV;
+       }
+       cygnus_gpio->num_banks = (ngpios + NGPIOS_PER_BANK - 1) /
+               NGPIOS_PER_BANK;
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       cygnus_gpio->base = devm_ioremap_resource(dev, res);
+       if (IS_ERR(cygnus_gpio->base)) {
+               dev_err(&pdev->dev, "unable to map I/O memory\n");
+               return PTR_ERR(cygnus_gpio->base);
+       }
+
+       /*
+        * Only certain types of Cygnus GPIO interfaces have I/O control
+        * registers
+        */
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+       if (res) {
+               cygnus_gpio->io_ctrl = devm_ioremap_resource(dev, res);
+               if (IS_ERR(cygnus_gpio->io_ctrl)) {
+                       dev_err(&pdev->dev, "unable to map I/O memory\n");
+                       return PTR_ERR(cygnus_gpio->io_ctrl);
+               }
+       }
This is a good indication that it's a separate piece of HW and should
be a separate pin control driver.
+
+       spin_lock_init(&cygnus_gpio->lock);
+
+       gc = &cygnus_gpio->gc;
+       gc->base = -1;
+       gc->ngpio = ngpios;
+       gc->label = dev_name(dev);
+       gc->dev = dev;
+#ifdef CONFIG_OF_GPIO
You depend on this symbol.
+       gc->of_node = dev->of_node;
+       gc->of_gpio_n_cells = 2;
+       gc->of_xlate = cygnus_gpio_of_xlate;
+#endif
+       gc->direction_input = cygnus_gpio_direction_input;
+       gc->direction_output = cygnus_gpio_direction_output;
+       gc->set = cygnus_gpio_set;
+       gc->get = cygnus_gpio_get;
+       gc->to_irq = cygnus_gpio_to_irq;
+
+       ret = gpiochip_add(gc);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "unable to add GPIO chip\n");
+               return ret;
+       }
+
+       /*
+        * Some of the GPIO interfaces do not have interrupt wired to the main
+        * processor
+        */
+       cygnus_gpio->irq = platform_get_irq(pdev, 0);
+       if (cygnus_gpio->irq < 0) {
+               ret = cygnus_gpio->irq;
+               if (ret == -EPROBE_DEFER)
+                       goto err_rm_gpiochip;
+
+               dev_info(&pdev->dev, "no interrupt hook\n");
+       }
From here:
+       cygnus_gpio->irq_domain = irq_domain_add_linear(dev->of_node,
+                       gc->ngpio, &cygnus_irq_ops, cygnus_gpio);
+       if (!cygnus_gpio->irq_domain) {
+               dev_err(&pdev->dev, "unable to allocate IRQ domain\n");
+               ret = -ENXIO;
+               goto err_rm_gpiochip;
+       }
+
+       for (i = 0; i < gc->ngpio; i++) {
+               int irq = irq_create_mapping(cygnus_gpio->irq_domain, i);
+
+               irq_set_lockdep_class(irq, &gpio_lock_class);
+               irq_set_chip_data(irq, cygnus_gpio);
+               irq_set_chip_and_handler(irq, &cygnus_gpio_irq_chip,
+                               handle_simple_irq);
+               set_irq_flags(irq, IRQF_VALID);
+       }
+
+       irq_set_chained_handler(cygnus_gpio->irq, cygnus_gpio_irq_handler);
+       irq_set_handler_data(cygnus_gpio->irq, cygnus_gpio);
To here, replace with a single call to
gpiochip_set_chained_irqchip(chip *, irq_chip *, irq, handler)...

Look at other drivers using GPIOLIB_IRQCHIP for inspiration.

Yours,
Linus Walleij
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help