diff options
Diffstat (limited to 'drivers/bcma/driver_gpio.c')
| -rw-r--r-- | drivers/bcma/driver_gpio.c | 254 | 
1 files changed, 254 insertions, 0 deletions
diff --git a/drivers/bcma/driver_gpio.c b/drivers/bcma/driver_gpio.c new file mode 100644 index 00000000000..d7f81ad56b8 --- /dev/null +++ b/drivers/bcma/driver_gpio.c @@ -0,0 +1,254 @@ +/* + * Broadcom specific AMBA + * GPIO driver + * + * Copyright 2011, Broadcom Corporation + * Copyright 2012, Hauke Mehrtens <hauke@hauke-m.de> + * + * Licensed under the GNU/GPL. See COPYING for details. + */ + +#include <linux/gpio.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/irqdomain.h> +#include <linux/export.h> +#include <linux/bcma/bcma.h> + +#include "bcma_private.h" + +static inline struct bcma_drv_cc *bcma_gpio_get_cc(struct gpio_chip *chip) +{ +	return container_of(chip, struct bcma_drv_cc, gpio); +} + +static int bcma_gpio_get_value(struct gpio_chip *chip, unsigned gpio) +{ +	struct bcma_drv_cc *cc = bcma_gpio_get_cc(chip); + +	return !!bcma_chipco_gpio_in(cc, 1 << gpio); +} + +static void bcma_gpio_set_value(struct gpio_chip *chip, unsigned gpio, +				int value) +{ +	struct bcma_drv_cc *cc = bcma_gpio_get_cc(chip); + +	bcma_chipco_gpio_out(cc, 1 << gpio, value ? 1 << gpio : 0); +} + +static int bcma_gpio_direction_input(struct gpio_chip *chip, unsigned gpio) +{ +	struct bcma_drv_cc *cc = bcma_gpio_get_cc(chip); + +	bcma_chipco_gpio_outen(cc, 1 << gpio, 0); +	return 0; +} + +static int bcma_gpio_direction_output(struct gpio_chip *chip, unsigned gpio, +				      int value) +{ +	struct bcma_drv_cc *cc = bcma_gpio_get_cc(chip); + +	bcma_chipco_gpio_outen(cc, 1 << gpio, 1 << gpio); +	bcma_chipco_gpio_out(cc, 1 << gpio, value ? 1 << gpio : 0); +	return 0; +} + +static int bcma_gpio_request(struct gpio_chip *chip, unsigned gpio) +{ +	struct bcma_drv_cc *cc = bcma_gpio_get_cc(chip); + +	bcma_chipco_gpio_control(cc, 1 << gpio, 0); +	/* clear pulldown */ +	bcma_chipco_gpio_pulldown(cc, 1 << gpio, 0); +	/* Set pullup */ +	bcma_chipco_gpio_pullup(cc, 1 << gpio, 1 << gpio); + +	return 0; +} + +static void bcma_gpio_free(struct gpio_chip *chip, unsigned gpio) +{ +	struct bcma_drv_cc *cc = bcma_gpio_get_cc(chip); + +	/* clear pullup */ +	bcma_chipco_gpio_pullup(cc, 1 << gpio, 0); +} + +#if IS_BUILTIN(CONFIG_BCMA_HOST_SOC) +static int bcma_gpio_to_irq(struct gpio_chip *chip, unsigned gpio) +{ +	struct bcma_drv_cc *cc = bcma_gpio_get_cc(chip); + +	if (cc->core->bus->hosttype == BCMA_HOSTTYPE_SOC) +		return irq_find_mapping(cc->irq_domain, gpio); +	else +		return -EINVAL; +} + +static void bcma_gpio_irq_unmask(struct irq_data *d) +{ +	struct bcma_drv_cc *cc = irq_data_get_irq_chip_data(d); +	int gpio = irqd_to_hwirq(d); +	u32 val = bcma_chipco_gpio_in(cc, BIT(gpio)); + +	bcma_chipco_gpio_polarity(cc, BIT(gpio), val); +	bcma_chipco_gpio_intmask(cc, BIT(gpio), BIT(gpio)); +} + +static void bcma_gpio_irq_mask(struct irq_data *d) +{ +	struct bcma_drv_cc *cc = irq_data_get_irq_chip_data(d); +	int gpio = irqd_to_hwirq(d); + +	bcma_chipco_gpio_intmask(cc, BIT(gpio), 0); +} + +static struct irq_chip bcma_gpio_irq_chip = { +	.name		= "BCMA-GPIO", +	.irq_mask	= bcma_gpio_irq_mask, +	.irq_unmask	= bcma_gpio_irq_unmask, +}; + +static irqreturn_t bcma_gpio_irq_handler(int irq, void *dev_id) +{ +	struct bcma_drv_cc *cc = dev_id; +	u32 val = bcma_cc_read32(cc, BCMA_CC_GPIOIN); +	u32 mask = bcma_cc_read32(cc, BCMA_CC_GPIOIRQ); +	u32 pol = bcma_cc_read32(cc, BCMA_CC_GPIOPOL); +	unsigned long irqs = (val ^ pol) & mask; +	int gpio; + +	if (!irqs) +		return IRQ_NONE; + +	for_each_set_bit(gpio, &irqs, cc->gpio.ngpio) +		generic_handle_irq(bcma_gpio_to_irq(&cc->gpio, gpio)); +	bcma_chipco_gpio_polarity(cc, irqs, val & irqs); + +	return IRQ_HANDLED; +} + +static int bcma_gpio_irq_domain_init(struct bcma_drv_cc *cc) +{ +	struct gpio_chip *chip = &cc->gpio; +	int gpio, hwirq, err; + +	if (cc->core->bus->hosttype != BCMA_HOSTTYPE_SOC) +		return 0; + +	cc->irq_domain = irq_domain_add_linear(NULL, chip->ngpio, +					       &irq_domain_simple_ops, cc); +	if (!cc->irq_domain) { +		err = -ENODEV; +		goto err_irq_domain; +	} +	for (gpio = 0; gpio < chip->ngpio; gpio++) { +		int irq = irq_create_mapping(cc->irq_domain, gpio); + +		irq_set_chip_data(irq, cc); +		irq_set_chip_and_handler(irq, &bcma_gpio_irq_chip, +					 handle_simple_irq); +	} + +	hwirq = bcma_core_irq(cc->core); +	err = request_irq(hwirq, bcma_gpio_irq_handler, IRQF_SHARED, "gpio", +			  cc); +	if (err) +		goto err_req_irq; + +	bcma_chipco_gpio_intmask(cc, ~0, 0); +	bcma_cc_set32(cc, BCMA_CC_IRQMASK, BCMA_CC_IRQ_GPIO); + +	return 0; + +err_req_irq: +	for (gpio = 0; gpio < chip->ngpio; gpio++) { +		int irq = irq_find_mapping(cc->irq_domain, gpio); + +		irq_dispose_mapping(irq); +	} +	irq_domain_remove(cc->irq_domain); +err_irq_domain: +	return err; +} + +static void bcma_gpio_irq_domain_exit(struct bcma_drv_cc *cc) +{ +	struct gpio_chip *chip = &cc->gpio; +	int gpio; + +	if (cc->core->bus->hosttype != BCMA_HOSTTYPE_SOC) +		return; + +	bcma_cc_mask32(cc, BCMA_CC_IRQMASK, ~BCMA_CC_IRQ_GPIO); +	free_irq(bcma_core_irq(cc->core), cc); +	for (gpio = 0; gpio < chip->ngpio; gpio++) { +		int irq = irq_find_mapping(cc->irq_domain, gpio); + +		irq_dispose_mapping(irq); +	} +	irq_domain_remove(cc->irq_domain); +} +#else +static int bcma_gpio_irq_domain_init(struct bcma_drv_cc *cc) +{ +	return 0; +} + +static void bcma_gpio_irq_domain_exit(struct bcma_drv_cc *cc) +{ +} +#endif + +int bcma_gpio_init(struct bcma_drv_cc *cc) +{ +	struct gpio_chip *chip = &cc->gpio; +	int err; + +	chip->label		= "bcma_gpio"; +	chip->owner		= THIS_MODULE; +	chip->request		= bcma_gpio_request; +	chip->free		= bcma_gpio_free; +	chip->get		= bcma_gpio_get_value; +	chip->set		= bcma_gpio_set_value; +	chip->direction_input	= bcma_gpio_direction_input; +	chip->direction_output	= bcma_gpio_direction_output; +#if IS_BUILTIN(CONFIG_BCMA_HOST_SOC) +	chip->to_irq		= bcma_gpio_to_irq; +#endif +	switch (cc->core->bus->chipinfo.id) { +	case BCMA_CHIP_ID_BCM5357: +		chip->ngpio	= 32; +		break; +	default: +		chip->ngpio	= 16; +	} + +	/* There is just one SoC in one device and its GPIO addresses should be +	 * deterministic to address them more easily. The other buses could get +	 * a random base number. */ +	if (cc->core->bus->hosttype == BCMA_HOSTTYPE_SOC) +		chip->base		= 0; +	else +		chip->base		= -1; + +	err = bcma_gpio_irq_domain_init(cc); +	if (err) +		return err; + +	err = gpiochip_add(chip); +	if (err) { +		bcma_gpio_irq_domain_exit(cc); +		return err; +	} + +	return 0; +} + +int bcma_gpio_unregister(struct bcma_drv_cc *cc) +{ +	bcma_gpio_irq_domain_exit(cc); +	return gpiochip_remove(&cc->gpio); +}  | 
