Re: [PATCH 2/2] gpio: Add xgs-iproc driver

From: Linus Walleij
Date: Fri Oct 11 2019 - 03:43:43 EST


Hi Chris!

Thanks for your patch!

On Fri, Oct 4, 2019 at 3:25 AM Chris Packham
<chris.packham@xxxxxxxxxxxxxxxxxxx> wrote:

> This driver supports the Chip Common A GPIO controller present on a
> number of Broadcom switch ASICs with integrated SoCs. The controller is
> similar to the pinctrl-nsp-gpio and pinctrl-iproc-gpio blocks but
> different enough that a separate driver is required.
>
> This has been ported from Broadcom's XLDK 5.0.3 retaining only the CCA
> support (pinctrl-iproc-gpio covers CCB).
>
> Signed-off-by: Chris Packham <chris.packham@xxxxxxxxxxxxxxxxxxx>

(...)

> +config GPIO_XGS_IPROC
> + tristate "BRCM XGS iProc GPIO support"
> + depends on OF_GPIO && (ARCH_BCM_IPROC || COMPILE_TEST)
> + select GPIO_GENERIC
> + select GPIOLIB_IRQCHIP

Nice reuse of abstractions.

> +static u32 iproc_gpio_readl(struct iproc_gpio_chip *chip, int reg)
> +{
> + return readl(chip->base + reg);
> +}
> +
> +static void iproc_gpio_writel(struct iproc_gpio_chip *chip, u32 val, int reg)
> +{
> + writel(val, chip->base + reg);
> +}

These wrappers don't really add anything do they? Just inline the
direct readl()/writel() to base + reg everywhere instead.

> +/* returns the corresponding gpio register bit */
> +static int iproc_irq_to_gpio(struct iproc_gpio_chip *chip, u32 irq)
> +{
> + struct irq_data *data = irq_domain_get_irq_data(chip->irq_domain, irq);
> +
> + return data->hwirq;
> +}

I would name it something clearer since "gpio" is pretty ambigous.

Like iproc_irq_to_gpio_offset()

Maybe this is also a bit of unnecessary wrapper?

> +static irqreturn_t iproc_gpio_irq_handler(int irq, void *data)
> +{
> + struct iproc_gpio_chip *chip = (struct iproc_gpio_chip *)data;
> + struct gpio_chip gc = chip->gc;
> + int bit;
> + unsigned long int_bits = 0;
> + u32 int_status;
> +
> + /* go through the entire GPIOs and handle all interrupts */
> + int_status = readl(chip->intr + CCA_INT_STS);
> + if (int_status & CCA_INT_F_GPIOINT) {
> + u32 event, level;
> +
> + /* Get level and edge interrupts */
> + event = readl(chip->base + GPIO_CCA_INT_EVENT_MASK);
> + event &= readl(chip->base + GPIO_CCA_INT_EVENT);
> + level = readl(chip->base + GPIO_CCA_DIN);
> + level ^= readl(chip->base + GPIO_CCA_INT_LEVEL);
> + level &= readl(chip->base + GPIO_CCA_INT_LEVEL_MASK);
> + int_bits = level | event;
> +
> + for_each_set_bit(bit, &int_bits, gc.ngpio)
> + generic_handle_irq(
> + irq_linear_revmap(chip->irq_domain, bit));
> + }
> +
> + return int_bits ? IRQ_HANDLED : IRQ_NONE;
> +}

I think this should be a chained interrupt handler (see below how to
register it).

See e.g. drivers/gpio/gpio-ftgpio010.c for an example:
change function prototype, no return value, use
chained_irq_enter/exit(irqchip, desc); etc.

> +static int iproc_gpiolib_input(struct gpio_chip *gc, u32 gpio)
> +static int iproc_gpiolib_output(struct gpio_chip *gc, u32 gpio, int value)
> +static void iproc_gpiolib_set(struct gpio_chip *gc, u32 gpio, int value)
> +static int iproc_gpiolib_get(struct gpio_chip *gc, u32 gpio)

These callbacks seems to reimplement parts of GPIO_GENERIC
that you should already be using.

Again look at drivers/gpio/gpio-ftgpio010.c() use bgpio_init()
to set up the library callbacks, look in
drivers/gpio/gpio-mmio.c for kerneldoc on the function.

> +static int iproc_gpiolib_to_irq(struct gpio_chip *gc, u32 offset)
> +{
> + struct iproc_gpio_chip *chip = gpiochip_get_data(gc);
> +
> + return irq_linear_revmap(chip->irq_domain, offset);
> +}

GPIOLIB_IRQCHIP provides a .to_irq() implementation
so drop this and let the library handle this.

> +static int iproc_gpio_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct device_node *dn = pdev->dev.of_node;
> + struct iproc_gpio_chip *chip;
> + u32 num_gpios;
> + int irq, ret;
> +
> + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
> + if (!chip)
> + return -ENOMEM;
> +
> + chip->dev = dev;
> + platform_set_drvdata(pdev, chip);
> +
> + chip->gc.label = dev_name(dev);
> +
> + chip->base = devm_platform_ioremap_resource(pdev, 0);
> + if (IS_ERR(chip->base))
> + return PTR_ERR(chip->base);
> +
> + /* Get number of GPIO pin */
> + if (of_property_read_u32(dn, "ngpios", &num_gpios)) {
> + dev_err(dev, "missing ngpios DT property\n");
> + return -EINVAL;
> + }

Maybe provide a sensible default?

> + chip->gc.ngpio = num_gpios;
> + chip->gc.parent = dev;
> + chip->gc.of_node = dn;
> + chip->gc.direction_input = iproc_gpiolib_input;
> + chip->gc.direction_output = iproc_gpiolib_output;
> + chip->gc.set = iproc_gpiolib_set;
> + chip->gc.get = iproc_gpiolib_get;

Drop this and call bgpio_init() to set up the callbacks
instead. However, set up .ngpio *after* calling
bgpio_init() so users can't access the nonexisting
gpios.

> + chip->gc.to_irq = iproc_gpiolib_to_irq;

Drop this and use the GPIOLIB_IRQCHIP.

> + ret = gpiochip_add_data(&chip->gc, chip);
> + if (ret) {
> + dev_err(dev, "unable to add GPIO chip\n");
> + return ret;
> + }

Why not use devm_gpiochip_add_data()?

> + irq = platform_get_irq(pdev, 0);
> + if (irq > 0) {
> + u32 val, count;
> + struct irq_chip *irqc;
> +
> + irqc = &chip->irqchip;
> + irqc->name = dev_name(dev);
> + irqc->irq_ack = iproc_gpio_irq_ack;
> + irqc->irq_mask = iproc_gpio_irq_mask;
> + irqc->irq_unmask = iproc_gpio_irq_unmask;
> + irqc->irq_set_type = iproc_gpio_irq_set_type;
> + irqc->irq_enable = iproc_gpio_irq_unmask;
> + irqc->irq_disable = iproc_gpio_irq_mask;
> + chip->intr = devm_platform_ioremap_resource(pdev, 1);
> + if (IS_ERR(chip->intr))
> + return PTR_ERR(chip->intr);

Move all this above the [devm_]gpiochip_add_data()
call and add:

struct gpio_irq_chip *girq;

girq = &chip->gc.irq;
girq->chip = irqc;

etc follow the pattern from other drivers like the
mentioned ftgpio010.c.

> + /* Create irq domain */
> + chip->irq_domain = irq_domain_add_linear(dn, num_gpios,
> + &irq_domain_simple_ops, chip);
> +
> + if (!chip->irq_domain) {
> + dev_err(dev, "Couldn't allocate IRQ domain\n");
> + ret = -ENODEV;
> + goto err_irq_domain;
> + }
> +
> + /* Map each gpio pin to an IRQ and set the handler */
> + for (count = 0; count < num_gpios; count++) {
> + int irq;
> +
> + irq = irq_create_mapping(chip->irq_domain, count);
> + irq_set_chip_and_handler(irq, irqc, handle_simple_irq);
> + irq_set_chip_data(irq, chip);
> + }

Drop this and let the generic GPIO irqchip handle
the domain.

> + /* Enable GPIO interrupts for CCA GPIO */
> + val = readl(chip->intr + CCA_INT_MASK);
> + val |= CCA_INT_F_GPIOINT;
> + writel(val, chip->intr + CCA_INT_MASK);

Move this before registering the chip.

> + /* Install ISR for this GPIO controller */
> + ret = devm_request_irq(dev, irq, iproc_gpio_irq_handler,
> + IRQF_SHARED, chip->gc.label, chip);
> + if (ret) {
> + dev_err(dev, "Fail to request IRQ%d: %d\n", irq, ret);
> + goto err_irq_request;
> + }

Drop this and use the gpiolib irqchip library.

Yours,
Linus Walleij