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

Linus Walleij linus.walleij at linaro.org
Tue Jan 13 00:53:12 PST 2015


On Tue, Dec 16, 2014 at 3:18 AM, Ray Jui <rjui at broadcom.com> 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 at broadcom.com>
> Reviewed-by: Scott Branden <sbranden at 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.

> +++ 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



More information about the linux-arm-kernel mailing list