[PATCH 2/2] gpio: Add Avionic Design N-bit GPIO expander support

Grant Likely grant.likely at secretlab.ca
Sat May 12 09:47:27 EST 2012


On Thu, 12 Apr 2012 13:24:18 +0200, Thierry Reding <thierry.reding at avionic-design.de> wrote:
> This commit adds a driver for the Avionic Design N-bit GPIO expander.
> The expander provides a variable number of GPIO pins with interrupt
> support.

Hi Thierry,  comments below...

> diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
> index 7875c3f..d86c3b7 100644
> --- a/drivers/gpio/Kconfig
> +++ b/drivers/gpio/Kconfig
> @@ -373,6 +373,25 @@ config GPIO_ADP5588_IRQ
>  	  Say yes here to enable the adp5588 to be used as an interrupt
>  	  controller. It requires the driver to be built in the kernel.
>  
> +config GPIO_ADNP
> +	tristate "Avionic Design N-bit GPIO expander"
> +	depends on I2C
> +	help
> +	  This option enables support for N GPIOs found on Avionic Design
> +	  I2C GPIO expanders. The register space will be extended by powers
> +	  of two, so the controller will need to accomodate for that. For
> +	  example: if a controller provides 48 pins, 6 registers will be
> +	  enough to represent all pins, but the driver will assume a
> +	  register layout for 64 pins (8 registers).
> +
> +config GPIO_ADNP_IRQ
> +	bool "Interrupt controller support"
> +	depends on GPIO_ADNP
> +	help
> +	  Say yes here to enable the Avionic Design N-bit GPIO expander to
> +	  be used as an interrupt controller. It requires the driver to be
> +	  built in the kernel.
> +

Why does it require the driver to be built in?

> +struct adnp {
> +	struct i2c_client *client;
> +	struct gpio_chip gpio;
> +	unsigned int reg_shift;
> +
> +	struct mutex i2c_lock;
> +
> +#ifdef CONFIG_GPIO_ADNP_IRQ
> +	struct irq_domain *domain;
> +	struct mutex irq_lock;
> +
> +	unsigned int irq_base;
> +	unsigned int nrirq;

This driver should use the irq_domain linear mapping which makes
irq_base unnecessary.

> +static int adnp_gpio_setup(struct adnp *gpio, struct adnp_platform_data *pdata)
> +{
> +	struct gpio_chip *chip = &gpio->gpio;
> +
> +	gpio->reg_shift = get_count_order(pdata->nr_gpios) - 3;
> +
> +	chip->direction_input = adnp_gpio_direction_input;
> +	chip->direction_output = adnp_gpio_direction_output;
> +	chip->get = adnp_gpio_get;
> +	chip->set = adnp_gpio_set;
> +	chip->dbg_show = adnp_gpio_dbg_show;
> +	chip->can_sleep = 1;
> +
> +	chip->base = pdata->gpio_base;
> +	chip->ngpio = pdata->nr_gpios;
> +	chip->label = gpio->client->name;
> +	chip->dev = &gpio->client->dev;
> +	chip->owner = THIS_MODULE;
> +	chip->names = pdata->names;
> +
> +#ifdef CONFIG_OF_GPIO
> +	chip->of_node = chip->dev->of_node;
> +#endif

Drop the #ifdef protection.  dev->of_node always exists so this
assignment is safe.

> +static int adnp_irq_setup(struct adnp *gpio, struct adnp_platform_data *pdata)
> +{
> +	unsigned int regs = 1 << gpio->reg_shift;
> +	struct gpio_chip *chip = &gpio->gpio;
> +	int pin;
> +	int err;
> +
> +	mutex_init(&gpio->irq_lock);
> +
> +	gpio->irq_mask = devm_kzalloc(gpio->gpio.dev, regs * 4, GFP_KERNEL);
> +	if (!gpio->irq_mask)
> +		return -ENOMEM;
> +
> +	gpio->irq_mask_cur = gpio->irq_mask + (regs * 1);
> +	gpio->irq_rising = gpio->irq_mask + (regs * 2);
> +	gpio->irq_falling = gpio->irq_mask + (regs * 3);
> +
> +	err = irq_alloc_descs(-1, pdata->irq_base, gpio->gpio.ngpio, -1);
> +	if (err < 0) {
> +		dev_err(gpio->gpio.dev, "%s failed: %d\n",
> +				"irq_alloc_descs()", err);
> +		return err;
> +	}
> +
> +	gpio->nrirq = gpio->gpio.ngpio;
> +	gpio->irq_base = err;
> +
> +	gpio->domain = irq_domain_add_legacy(chip->dev->of_node, gpio->nrirq,
> +			gpio->irq_base, 0, &irq_domain_simple_ops, NULL);

Use the linear mapping instead with takes care of allocating irq_descs
for you and simplifies a lot of the driver.

> +
> +	for (pin = 0; pin < gpio->nrirq; pin++) {
> +		int irq = irq_find_mapping(gpio->domain, pin);
> +
> +		irq_clear_status_flags(irq, IRQ_NOREQUEST);
> +		irq_set_chip_data(irq, gpio);
> +		irq_set_chip(irq, &adnp_irq_chip);
> +		irq_set_nested_thread(irq, true);
> +#ifdef CONFIG_ARM
> +		set_irq_flags(irq, IRQF_VALID);
> +#else
> +		irq_set_noprobe(irq);
> +#endif
> +	}

The body of this for loop needs to be performed in the irq_domain map
hook.

> +static const struct i2c_device_id adnp_i2c_id[] = {
> +	{ "gpio-adnp" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, adnp_i2c_id);

Should use an of_device_id table since this is a DT-enabled
driver.

> +
> +static struct i2c_driver adnp_i2c_driver = {
> +	.driver = {
> +		.name = "gpio-adnp",
> +		.owner = THIS_MODULE,
> +	},
> +	.probe = adnp_i2c_probe,
> +	.remove = __devexit_p(adnp_i2c_remove),
> +	.id_table = adnp_i2c_id,
> +};
> +module_i2c_driver(adnp_i2c_driver);
> +
> +MODULE_DESCRIPTION("Avionic Design N-bit GPIO expander");
> +MODULE_AUTHOR("Thierry Reding <thierry.reding at avionic-design.de>");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/i2c/gpio-adnp.h b/include/linux/i2c/gpio-adnp.h
> new file mode 100644
> index 0000000..6e077a3
> --- /dev/null
> +++ b/include/linux/i2c/gpio-adnp.h
> @@ -0,0 +1,19 @@
> +/*
> + * Copyright (C) 2011-2012 Avionic Design GmbH
> + *
> + * This program 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.
> + */
> +
> +#ifndef _LINUX_I2C_ADNP_H
> +#define _LINUX_I2C_ADNP_H 1
> +
> +struct adnp_platform_data {
> +	unsigned gpio_base;
> +	unsigned nr_gpios;
> +	int irq_base;
> +	const char *const *names;
> +};

>From the start this is a DT enabled driver.  There should be no reason
to also include platform_data support.



More information about the devicetree-discuss mailing list