diff options
Diffstat (limited to 'drivers/gpio')
-rw-r--r-- | drivers/gpio/Kconfig | 13 | ||||
-rw-r--r-- | drivers/gpio/Makefile | 2 | ||||
-rw-r--r-- | drivers/gpio/gpio-bt8xx.c | 2 | ||||
-rw-r--r-- | drivers/gpio/gpio-ich.c | 79 | ||||
-rw-r--r-- | drivers/gpio/gpio-mvebu.c | 679 | ||||
-rw-r--r-- | drivers/gpio/gpio-stp-xway.c | 2 | ||||
-rw-r--r-- | drivers/gpio/gpio-twl6040.c | 137 |
7 files changed, 902 insertions, 12 deletions
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 8382dc832929..d055cee36942 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -150,6 +150,12 @@ config GPIO_MSM_V2 Qualcomm MSM chips. Most of the pins on the MSM can be selected for GPIO, and are controlled by this driver. +config GPIO_MVEBU + def_bool y + depends on ARCH_MVEBU + select GPIO_GENERIC + select GENERIC_IRQ_CHIP + config GPIO_MXC def_bool y depends on ARCH_MXC @@ -409,6 +415,13 @@ config GPIO_TWL4030 Say yes here to access the GPIO signals of various multi-function power management chips from Texas Instruments. +config GPIO_TWL6040 + tristate "TWL6040 GPO" + depends on TWL6040_CORE + help + Say yes here to access the GPO signals of twl6040 + audio chip from Texas Instruments. + config GPIO_WM831X tristate "WM831x GPIOs" depends on MFD_WM831X diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 0ffaa8423e87..9aeed6707326 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -42,6 +42,7 @@ obj-$(CONFIG_GPIO_MPC8XXX) += gpio-mpc8xxx.o obj-$(CONFIG_GPIO_MSIC) += gpio-msic.o obj-$(CONFIG_GPIO_MSM_V1) += gpio-msm-v1.o obj-$(CONFIG_GPIO_MSM_V2) += gpio-msm-v2.o +obj-$(CONFIG_GPIO_MVEBU) += gpio-mvebu.o obj-$(CONFIG_GPIO_MXC) += gpio-mxc.o obj-$(CONFIG_GPIO_MXS) += gpio-mxs.o obj-$(CONFIG_ARCH_OMAP) += gpio-omap.o @@ -68,6 +69,7 @@ obj-$(CONFIG_GPIO_TPS6586X) += gpio-tps6586x.o obj-$(CONFIG_GPIO_TPS65910) += gpio-tps65910.o obj-$(CONFIG_GPIO_TPS65912) += gpio-tps65912.o obj-$(CONFIG_GPIO_TWL4030) += gpio-twl4030.o +obj-$(CONFIG_GPIO_TWL6040) += gpio-twl6040.o obj-$(CONFIG_GPIO_UCB1400) += gpio-ucb1400.o obj-$(CONFIG_GPIO_VR41XX) += gpio-vr41xx.o obj-$(CONFIG_GPIO_VT8500) += gpio-vt8500.o diff --git a/drivers/gpio/gpio-bt8xx.c b/drivers/gpio/gpio-bt8xx.c index aba97abda77c..7d9d7cb35f28 100644 --- a/drivers/gpio/gpio-bt8xx.c +++ b/drivers/gpio/gpio-bt8xx.c @@ -50,7 +50,7 @@ #include <linux/slab.h> /* Steal the hardware definitions from the bttv driver. */ -#include "../media/video/bt8xx/bt848.h" +#include "../media/pci/bt8xx/bt848.h" #define BT8XXGPIO_NR_GPIOS 24 /* We have 24 GPIO pins */ diff --git a/drivers/gpio/gpio-ich.c b/drivers/gpio/gpio-ich.c index b7c06517403d..d4d617966696 100644 --- a/drivers/gpio/gpio-ich.c +++ b/drivers/gpio/gpio-ich.c @@ -49,6 +49,10 @@ static const u8 ichx_regs[3][3] = { {0x0c, 0x38, 0x48}, /* LVL[1-3] offsets */ }; +static const u8 ichx_reglen[3] = { + 0x30, 0x10, 0x10, +}; + #define ICHX_WRITE(val, reg, base_res) outl(val, (reg) + (base_res)->start) #define ICHX_READ(reg, base_res) inl((reg) + (base_res)->start) @@ -75,6 +79,7 @@ static struct { struct resource *pm_base; /* Power Mangagment IO base */ struct ichx_desc *desc; /* Pointer to chipset-specific description */ u32 orig_gpio_ctrl; /* Orig CTRL value, used to restore on exit */ + u8 use_gpio; /* Which GPIO groups are usable */ } ichx_priv; static int modparam_gpiobase = -1; /* dynamic */ @@ -123,8 +128,16 @@ static int ichx_read_bit(int reg, unsigned nr) return data & (1 << bit) ? 1 : 0; } +static int ichx_gpio_check_available(struct gpio_chip *gpio, unsigned nr) +{ + return (ichx_priv.use_gpio & (1 << (nr / 32))) ? 0 : -ENXIO; +} + static int ichx_gpio_direction_input(struct gpio_chip *gpio, unsigned nr) { + if (!ichx_gpio_check_available(gpio, nr)) + return -ENXIO; + /* * Try setting pin as an input and verify it worked since many pins * are output-only. @@ -138,6 +151,9 @@ static int ichx_gpio_direction_input(struct gpio_chip *gpio, unsigned nr) static int ichx_gpio_direction_output(struct gpio_chip *gpio, unsigned nr, int val) { + if (!ichx_gpio_check_available(gpio, nr)) + return -ENXIO; + /* Set GPIO output value. */ ichx_write_bit(GPIO_LVL, nr, val, 0); @@ -153,6 +169,9 @@ static int ichx_gpio_direction_output(struct gpio_chip *gpio, unsigned nr, static int ichx_gpio_get(struct gpio_chip *chip, unsigned nr) { + if (!ichx_gpio_check_available(chip, nr)) + return -ENXIO; + return ichx_read_bit(GPIO_LVL, nr); } @@ -161,6 +180,9 @@ static int ich6_gpio_get(struct gpio_chip *chip, unsigned nr) unsigned long flags; u32 data; + if (!ichx_gpio_check_available(chip, nr)) + return -ENXIO; + /* * GPI 0 - 15 need to be read from the power management registers on * a ICH6/3100 bridge. @@ -291,6 +313,46 @@ static struct ichx_desc intel5_desc = { .ngpio = 76, }; +static int __devinit ichx_gpio_request_regions(struct resource *res_base, + const char *name, u8 use_gpio) +{ + int i; + + if (!res_base || !res_base->start || !res_base->end) + return -ENODEV; + + for (i = 0; i < ARRAY_SIZE(ichx_regs[0]); i++) { + if (!(use_gpio & (1 << i))) + continue; + if (!request_region(res_base->start + ichx_regs[0][i], + ichx_reglen[i], name)) + goto request_err; + } + return 0; + +request_err: + /* Clean up: release already requested regions, if any */ + for (i--; i >= 0; i--) { + if (!(use_gpio & (1 << i))) + continue; + release_region(res_base->start + ichx_regs[0][i], + ichx_reglen[i]); + } + return -EBUSY; +} + +static void ichx_gpio_release_regions(struct resource *res_base, u8 use_gpio) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ichx_regs[0]); i++) { + if (!(use_gpio & (1 << i))) + continue; + release_region(res_base->start + ichx_regs[0][i], + ichx_reglen[i]); + } +} + static int __devinit ichx_gpio_probe(struct platform_device *pdev) { struct resource *res_base, *res_pm; @@ -329,12 +391,11 @@ static int __devinit ichx_gpio_probe(struct platform_device *pdev) } res_base = platform_get_resource(pdev, IORESOURCE_IO, ICH_RES_GPIO); - if (!res_base || !res_base->start || !res_base->end) - return -ENODEV; - - if (!request_region(res_base->start, resource_size(res_base), - pdev->name)) - return -EBUSY; + ichx_priv.use_gpio = ich_info->use_gpio; + err = ichx_gpio_request_regions(res_base, pdev->name, + ichx_priv.use_gpio); + if (err) + return err; ichx_priv.gpio_base = res_base; @@ -374,8 +435,7 @@ init: return 0; add_err: - release_region(ichx_priv.gpio_base->start, - resource_size(ichx_priv.gpio_base)); + ichx_gpio_release_regions(ichx_priv.gpio_base, ichx_priv.use_gpio); if (ichx_priv.pm_base) release_region(ichx_priv.pm_base->start, resource_size(ichx_priv.pm_base)); @@ -393,8 +453,7 @@ static int __devexit ichx_gpio_remove(struct platform_device *pdev) return err; } - release_region(ichx_priv.gpio_base->start, - resource_size(ichx_priv.gpio_base)); + ichx_gpio_release_regions(ichx_priv.gpio_base, ichx_priv.use_gpio); if (ichx_priv.pm_base) release_region(ichx_priv.pm_base->start, resource_size(ichx_priv.pm_base)); diff --git a/drivers/gpio/gpio-mvebu.c b/drivers/gpio/gpio-mvebu.c new file mode 100644 index 000000000000..902af437eaf2 --- /dev/null +++ b/drivers/gpio/gpio-mvebu.c @@ -0,0 +1,679 @@ +/* + * GPIO driver for Marvell SoCs + * + * Copyright (C) 2012 Marvell + * + * Thomas Petazzoni <thomas.petazzoni@free-electrons.com> + * Andrew Lunn <andrew@lunn.ch> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + * + * This driver is a fairly straightforward GPIO driver for the + * complete family of Marvell EBU SoC platforms (Orion, Dove, + * Kirkwood, Discovery, Armada 370/XP). The only complexity of this + * driver is the different register layout that exists between the + * non-SMP platforms (Orion, Dove, Kirkwood, Armada 370) and the SMP + * platforms (MV78200 from the Discovery family and the Armada + * XP). Therefore, this driver handles three variants of the GPIO + * block: + * - the basic variant, called "orion-gpio", with the simplest + * register set. Used on Orion, Dove, Kirkwoord, Armada 370 and + * non-SMP Discovery systems + * - the mv78200 variant for MV78200 Discovery systems. This variant + * turns the edge mask and level mask registers into CPU0 edge + * mask/level mask registers, and adds CPU1 edge mask/level mask + * registers. + * - the armadaxp variant for Armada XP systems. This variant keeps + * the normal cause/edge mask/level mask registers when the global + * interrupts are used, but adds per-CPU cause/edge mask/level mask + * registers n a separate memory area for the per-CPU GPIO + * interrupts. + */ + +#include <linux/module.h> +#include <linux/gpio.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/irqdomain.h> +#include <linux/io.h> +#include <linux/of_irq.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pinctrl/consumer.h> + +/* + * GPIO unit register offsets. + */ +#define GPIO_OUT_OFF 0x0000 +#define GPIO_IO_CONF_OFF 0x0004 +#define GPIO_BLINK_EN_OFF 0x0008 +#define GPIO_IN_POL_OFF 0x000c +#define GPIO_DATA_IN_OFF 0x0010 +#define GPIO_EDGE_CAUSE_OFF 0x0014 +#define GPIO_EDGE_MASK_OFF 0x0018 +#define GPIO_LEVEL_MASK_OFF 0x001c + +/* The MV78200 has per-CPU registers for edge mask and level mask */ +#define GPIO_EDGE_MASK_MV78200_OFF(cpu) ((cpu) ? 0x30 : 0x18) +#define GPIO_LEVEL_MASK_MV78200_OFF(cpu) ((cpu) ? 0x34 : 0x1C) + +/* The Armada XP has per-CPU registers for interrupt cause, interrupt + * mask and interrupt level mask. Those are relative to the + * percpu_membase. */ +#define GPIO_EDGE_CAUSE_ARMADAXP_OFF(cpu) ((cpu) * 0x4) +#define GPIO_EDGE_MASK_ARMADAXP_OFF(cpu) (0x10 + (cpu) * 0x4) +#define GPIO_LEVEL_MASK_ARMADAXP_OFF(cpu) (0x20 + (cpu) * 0x4) + +#define MVEBU_GPIO_SOC_VARIANT_ORION 0x1 +#define MVEBU_GPIO_SOC_VARIANT_MV78200 0x2 +#define MVEBU_GPIO_SOC_VARIANT_ARMADAXP 0x3 + +#define MVEBU_MAX_GPIO_PER_BANK 32 + +struct mvebu_gpio_chip { + struct gpio_chip chip; + spinlock_t lock; + void __iomem *membase; + void __iomem *percpu_membase; + unsigned int irqbase; + struct irq_domain *domain; + int soc_variant; +}; + +/* + * Functions returning addresses of individual registers for a given + * GPIO controller. + */ +static inline void __iomem *mvebu_gpioreg_out(struct mvebu_gpio_chip *mvchip) +{ + return mvchip->membase + GPIO_OUT_OFF; +} + +static inline void __iomem *mvebu_gpioreg_io_conf(struct mvebu_gpio_chip *mvchip) +{ + return mvchip->membase + GPIO_IO_CONF_OFF; +} + +static inline void __iomem *mvebu_gpioreg_in_pol(struct mvebu_gpio_chip *mvchip) +{ + return mvchip->membase + GPIO_IN_POL_OFF; +} + +static inline void __iomem *mvebu_gpioreg_data_in(struct mvebu_gpio_chip *mvchip) +{ + return mvchip->membase + GPIO_DATA_IN_OFF; +} + +static inline void __iomem *mvebu_gpioreg_edge_cause(struct mvebu_gpio_chip *mvchip) +{ + int cpu; + + switch(mvchip->soc_variant) { + case MVEBU_GPIO_SOC_VARIANT_ORION: + case MVEBU_GPIO_SOC_VARIANT_MV78200: + return mvchip->membase + GPIO_EDGE_CAUSE_OFF; + case MVEBU_GPIO_SOC_VARIANT_ARMADAXP: + cpu = smp_processor_id(); + return mvchip->percpu_membase + GPIO_EDGE_CAUSE_ARMADAXP_OFF(cpu); + default: + BUG(); + } +} + +static inline void __iomem *mvebu_gpioreg_edge_mask(struct mvebu_gpio_chip *mvchip) +{ + int cpu; + + switch(mvchip->soc_variant) { + case MVEBU_GPIO_SOC_VARIANT_ORION: + return mvchip->membase + GPIO_EDGE_MASK_OFF; + case MVEBU_GPIO_SOC_VARIANT_MV78200: + cpu = smp_processor_id(); + return mvchip->membase + GPIO_EDGE_MASK_MV78200_OFF(cpu); + case MVEBU_GPIO_SOC_VARIANT_ARMADAXP: + cpu = smp_processor_id(); + return mvchip->percpu_membase + GPIO_EDGE_MASK_ARMADAXP_OFF(cpu); + default: + BUG(); + } +} + +static void __iomem *mvebu_gpioreg_level_mask(struct mvebu_gpio_chip *mvchip) +{ + int cpu; + + switch(mvchip->soc_variant) { + case MVEBU_GPIO_SOC_VARIANT_ORION: + return mvchip->membase + GPIO_LEVEL_MASK_OFF; + case MVEBU_GPIO_SOC_VARIANT_MV78200: + cpu = smp_processor_id(); + return mvchip->membase + GPIO_LEVEL_MASK_MV78200_OFF(cpu); + case MVEBU_GPIO_SOC_VARIANT_ARMADAXP: + cpu = smp_processor_id(); + return mvchip->percpu_membase + GPIO_LEVEL_MASK_ARMADAXP_OFF(cpu); + default: + BUG(); + } +} + +/* + * Functions implementing the gpio_chip methods + */ + +int mvebu_gpio_request(struct gpio_chip *chip, unsigned pin) +{ + return pinctrl_request_gpio(chip->base + pin); +} + +void mvebu_gpio_free(struct gpio_chip *chip, unsigned pin) +{ + pinctrl_free_gpio(chip->base + pin); +} + +static void mvebu_gpio_set(struct gpio_chip *chip, unsigned pin, int value) +{ + struct mvebu_gpio_chip *mvchip = + container_of(chip, struct mvebu_gpio_chip, chip); + unsigned long flags; + u32 u; + + spin_lock_irqsave(&mvchip->lock, flags); + u = readl_relaxed(mvebu_gpioreg_out(mvchip)); + if (value) + u |= 1 << pin; + else + u &= ~(1 << pin); + writel_relaxed(u, mvebu_gpioreg_out(mvchip)); + spin_unlock_irqrestore(&mvchip->lock, flags); +} + +static int mvebu_gpio_get(struct gpio_chip *chip, unsigned pin) +{ + struct mvebu_gpio_chip *mvchip = + container_of(chip, struct mvebu_gpio_chip, chip); + u32 u; + + if (readl_relaxed(mvebu_gpioreg_io_conf(mvchip)) & (1 << pin)) { + u = readl_relaxed(mvebu_gpioreg_data_in(mvchip)) ^ + readl_relaxed(mvebu_gpioreg_in_pol(mvchip)); + } else { + u = readl_relaxed(mvebu_gpioreg_out(mvchip)); + } + + return (u >> pin) & 1; +} + +static int mvebu_gpio_direction_input(struct gpio_chip *chip, unsigned pin) +{ + struct mvebu_gpio_chip *mvchip = + container_of(chip, struct mvebu_gpio_chip, chip); + unsigned long flags; + int ret; + u32 u; + + /* Check with the pinctrl driver whether this pin is usable as + * an input GPIO */ + ret = pinctrl_gpio_direction_input(chip->base + pin); + if (ret) + return ret; + + spin_lock_irqsave(&mvchip->lock, flags); + u = readl_relaxed(mvebu_gpioreg_io_conf(mvchip)); + u |= 1 << pin; + writel_relaxed(u, mvebu_gpioreg_io_conf(mvchip)); + spin_unlock_irqrestore(&mvchip->lock, flags); + + return 0; +} + +static int mvebu_gpio_direction_output(struct gpio_chip *chip, unsigned pin, + int value) +{ + struct mvebu_gpio_chip *mvchip = + container_of(chip, struct mvebu_gpio_chip, chip); + unsigned long flags; + int ret; + u32 u; + + /* Check with the pinctrl driver whether this pin is usable as + * an output GPIO */ + ret = pinctrl_gpio_direction_output(chip->base + pin); + if (ret) + return ret; + + spin_lock_irqsave(&mvchip->lock, flags); + u = readl_relaxed(mvebu_gpioreg_io_conf(mvchip)); + u &= ~(1 << pin); + writel_relaxed(u, mvebu_gpioreg_io_conf(mvchip)); + spin_unlock_irqrestore(&mvchip->lock, flags); + + return 0; +} + +static int mvebu_gpio_to_irq(struct gpio_chip *chip, unsigned pin) +{ + struct mvebu_gpio_chip *mvchip = + container_of(chip, struct mvebu_gpio_chip, chip); + return irq_create_mapping(mvchip->domain, pin); +} + +/* + * Functions implementing the irq_chip methods + */ +static void mvebu_gpio_irq_ack(struct irq_data *d) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct mvebu_gpio_chip *mvchip = gc->private; + u32 mask = ~(1 << (d->irq - gc->irq_base)); + + irq_gc_lock(gc); + writel_relaxed(mask, mvebu_gpioreg_edge_cause(mvchip)); + irq_gc_unlock(gc); +} + +static void mvebu_gpio_edge_irq_mask(struct irq_data *d) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct mvebu_gpio_chip *mvchip = gc->private; + u32 mask = 1 << (d->irq - gc->irq_base); + + irq_gc_lock(gc); + gc->mask_cache &= ~mask; + writel_relaxed(gc->mask_cache, mvebu_gpioreg_edge_mask(mvchip)); + irq_gc_unlock(gc); +} + +static void mvebu_gpio_edge_irq_unmask(struct irq_data *d) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct mvebu_gpio_chip *mvchip = gc->private; + u32 mask = 1 << (d->irq - gc->irq_base); + + irq_gc_lock(gc); + gc->mask_cache |= mask; + writel_relaxed(gc->mask_cache, mvebu_gpioreg_edge_mask(mvchip)); + irq_gc_unlock(gc); +} + +static void mvebu_gpio_level_irq_mask(struct irq_data *d) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct mvebu_gpio_chip *mvchip = gc->private; + u32 mask = 1 << (d->irq - gc->irq_base); + + irq_gc_lock(gc); + gc->mask_cache &= ~mask; + writel_relaxed(gc->mask_cache, mvebu_gpioreg_level_mask(mvchip)); + irq_gc_unlock(gc); +} + +static void mvebu_gpio_level_irq_unmask(struct irq_data *d) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct mvebu_gpio_chip *mvchip = gc->private; + u32 mask = 1 << (d->irq - gc->irq_base); + + irq_gc_lock(gc); + gc->mask_cache |= mask; + writel_relaxed(gc->mask_cache, mvebu_gpioreg_level_mask(mvchip)); + irq_gc_unlock(gc); +} + +/***************************************************************************** + * MVEBU GPIO IRQ + * + * GPIO_IN_POL register controls whether GPIO_DATA_IN will hold the same + * value of the line or the opposite value. + * + * Level IRQ handlers: DATA_IN is used directly as cause register. + * Interrupt are masked by LEVEL_MASK registers. + * Edge IRQ handlers: Change in DATA_IN are latched in EDGE_CAUSE. + * Interrupt are masked by EDGE_MASK registers. + * Both-edge handlers: Similar to regular Edge handlers, but also swaps + * the polarity to catch the next line transaction. + * This is a race condition that might not perfectly + * work on some use cases. + * + * Every eight GPIO lines are grouped (OR'ed) before going up to main + * cause register. + * + * EDGE cause mask + * data-in /--------| |-----| |----\ + * -----| |----- ---- to main cause reg + * X \----------------| |----/ + * polarity LEVEL mask + * + ****************************************************************************/ + +static int mvebu_gpio_irq_set_type(struct irq_data *d, unsigned int type) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct irq_chip_type *ct = irq_data_get_chip_type(d); + struct mvebu_gpio_chip *mvchip = gc->private; + int pin; + u32 u; + + pin = d->hwirq; + + u = readl_relaxed(mvebu_gpioreg_io_conf(mvchip)) & (1 << pin); + if (!u) { + return -EINVAL; + } + + type &= IRQ_TYPE_SENSE_MASK; + if (type == IRQ_TYPE_NONE) + return -EINVAL; + + /* Check if we need to change chip and handler */ + if (!(ct->type & type)) + if (irq_setup_alt_chip(d, type)) + return -EINVAL; + + /* + * Configure interrupt polarity. + */ + switch(type) { + case IRQ_TYPE_EDGE_RISING: + case IRQ_TYPE_LEVEL_HIGH: + u = readl_relaxed(mvebu_gpioreg_in_pol(mvchip)); + u &= ~(1 << pin); + writel_relaxed(u, mvebu_gpioreg_in_pol(mvchip)); + case IRQ_TYPE_EDGE_FALLING: + case IRQ_TYPE_LEVEL_LOW: + u = readl_relaxed(mvebu_gpioreg_in_pol(mvchip)); + u |= 1 << pin; + writel_relaxed(u, mvebu_gpioreg_in_pol(mvchip)); + case IRQ_TYPE_EDGE_BOTH: { + u32 v; + + v = readl_relaxed(mvebu_gpioreg_in_pol(mvchip)) ^ + readl_relaxed(mvebu_gpioreg_data_in(mvchip)); + + /* + * set initial polarity based on current input level + */ + u = readl_relaxed(mvebu_gpioreg_in_pol(mvchip)); + if (v & (1 << pin)) + u |= 1 << pin; /* falling */ + else + u &= ~(1 << pin); /* rising */ + writel_relaxed(u, mvebu_gpioreg_in_pol(mvchip)); + } + } + return 0; +} + +static void mvebu_gpio_irq_handler(unsigned int irq, struct irq_desc *desc) +{ + struct mvebu_gpio_chip *mvchip = irq_get_handler_data(irq); + u32 cause, type; + int i; + + if (mvchip == NULL) + return; + + cause = readl_relaxed(mvebu_gpioreg_data_in(mvchip)) & + readl_relaxed(mvebu_gpioreg_level_mask(mvchip)); + cause |= readl_relaxed(mvebu_gpioreg_edge_cause(mvchip)) & + readl_relaxed(mvebu_gpioreg_edge_mask(mvchip)); + + for (i = 0; i < mvchip->chip.ngpio; i++) { + int irq; + + irq = mvchip->irqbase + i; + + if (!(cause & (1 << i))) + continue; + + type = irqd_get_trigger_type(irq_get_irq_data(irq)); + if ((type & IRQ_TYPE_SENSE_MASK) == IRQ_TYPE_EDGE_BOTH) { + /* Swap polarity (race with GPIO line) */ + u32 polarity; + + polarity = readl_relaxed(mvebu_gpioreg_in_pol(mvchip)); + polarity ^= 1 << i; + writel_relaxed(polarity, mvebu_gpioreg_in_pol(mvchip)); + } + generic_handle_irq(irq); + } +} + +static struct platform_device_id mvebu_gpio_ids[] = { + { + .name = "orion-gpio", + }, { + .name = "mv78200-gpio", + }, { + .name = "armadaxp-gpio", + }, { + /* sentinel */ + }, +}; +MODULE_DEVICE_TABLE(platform, mvebu_gpio_ids); + +static struct of_device_id mvebu_gpio_of_match[] __devinitdata = { + { + .compatible = "marvell,orion-gpio", + .data = (void*) MVEBU_GPIO_SOC_VARIANT_ORION, + }, + { + .compatible = "marvell,mv78200-gpio", + .data = (void*) MVEBU_GPIO_SOC_VARIANT_MV78200, + }, + { + .compatible = "marvell,armadaxp-gpio", + .data = (void*) MVEBU_GPIO_SOC_VARIANT_ARMADAXP, + }, + { + /* sentinel */ + }, +}; +MODULE_DEVICE_TABLE(of, mvebu_gpio_of_match); + +static int __devinit mvebu_gpio_probe(struct platform_device *pdev) +{ + struct mvebu_gpio_chip *mvchip; + const struct of_device_id *match; + struct device_node *np = pdev->dev.of_node; + struct resource *res; + struct irq_chip_generic *gc; + struct irq_chip_type *ct; + unsigned int ngpios; + int soc_variant; + int i, cpu, id; + + match = of_match_device(mvebu_gpio_of_match, &pdev->dev); + if (match) + soc_variant = (int) match->data; + else + soc_variant = MVEBU_GPIO_SOC_VARIANT_ORION; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (! res) { + dev_err(&pdev->dev, "Cannot get memory resource\n"); + return -ENODEV; + } + + mvchip = devm_kzalloc(&pdev->dev, sizeof(struct mvebu_gpio_chip), GFP_KERNEL); + if (! mvchip){ + dev_err(&pdev->dev, "Cannot allocate memory\n"); + return -ENOMEM; + } + + if (of_property_read_u32(pdev->dev.of_node, "ngpios", &ngpios)) { + dev_err(&pdev->dev, "Missing ngpios OF property\n"); + return -ENODEV; + } + + id = of_alias_get_id(pdev->dev.of_node, "gpio"); + if (id < 0) { + dev_err(&pdev->dev, "Couldn't get OF id\n"); + return id; + } + + mvchip->soc_variant = soc_variant; + mvchip->chip.label = dev_name(&pdev->dev); + mvchip->chip.dev = &pdev->dev; + mvchip->chip.request = mvebu_gpio_request; + mvchip->chip.direction_input = mvebu_gpio_direction_input; + mvchip->chip.get = mvebu_gpio_get; + mvchip->chip.direction_output = mvebu_gpio_direction_output; + mvchip->chip.set = mvebu_gpio_set; + mvchip->chip.to_irq = mvebu_gpio_to_irq; + mvchip->chip.base = id * MVEBU_MAX_GPIO_PER_BANK; + mvchip->chip.ngpio = ngpios; + mvchip->chip.can_sleep = 0; +#ifdef CONFIG_OF + mvchip->chip.of_node = np; +#endif + + spin_lock_init(&mvchip->lock); + mvchip->membase = devm_request_and_ioremap(&pdev->dev, res); + if (! mvchip->membase) { + dev_err(&pdev->dev, "Cannot ioremap\n"); + kfree(mvchip->chip.label); + return -ENOMEM; + } + + /* The Armada XP has a second range of registers for the + * per-CPU registers */ + if (soc_variant == MVEBU_GPIO_SOC_VARIANT_ARMADAXP) { + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (! res) { + dev_err(&pdev->dev, "Cannot get memory resource\n"); + kfree(mvchip->chip.label); + return -ENODEV; + } + + mvchip->percpu_membase = devm_request_and_ioremap(&pdev->dev, res); + if (! mvchip->percpu_membase) { + dev_err(&pdev->dev, "Cannot ioremap\n"); + kfree(mvchip->chip.label); + return -ENOMEM; + } + } + + /* + * Mask and clear GPIO interrupts. + */ + switch(soc_variant) { + case MVEBU_GPIO_SOC_VARIANT_ORION: + writel_relaxed(0, mvchip->membase + GPIO_EDGE_CAUSE_OFF); + writel_relaxed(0, mvchip->membase + GPIO_EDGE_MASK_OFF); + writel_relaxed(0, mvchip->membase + GPIO_LEVEL_MASK_OFF); + break; + case MVEBU_GPIO_SOC_VARIANT_MV78200: + writel_relaxed(0, mvchip->membase + GPIO_EDGE_CAUSE_OFF); + for (cpu = 0; cpu < 2; cpu++) { + writel_relaxed(0, mvchip->membase + + GPIO_EDGE_MASK_MV78200_OFF(cpu)); + writel_relaxed(0, mvchip->membase + + GPIO_LEVEL_MASK_MV78200_OFF(cpu)); + } + break; + case MVEBU_GPIO_SOC_VARIANT_ARMADAXP: + writel_relaxed(0, mvchip->membase + GPIO_EDGE_CAUSE_OFF); + writel_relaxed(0, mvchip->membase + GPIO_EDGE_MASK_OFF); + writel_relaxed(0, mvchip->membase + GPIO_LEVEL_MASK_OFF); + for (cpu = 0; cpu < 4; cpu++) { + writel_relaxed(0, mvchip->percpu_membase + + GPIO_EDGE_CAUSE_ARMADAXP_OFF(cpu)); + writel_relaxed(0, mvchip->percpu_membase + + GPIO_EDGE_MASK_ARMADAXP_OFF(cpu)); + writel_relaxed(0, mvchip->percpu_membase + + GPIO_LEVEL_MASK_ARMADAXP_OFF(cpu)); + } + break; + default: + BUG(); + } + + gpiochip_add(&mvchip->chip); + + /* Some gpio controllers do not provide irq support */ + if (!of_irq_count(np)) + return 0; + + /* Setup the interrupt handlers. Each chip can have up to 4 + * interrupt handlers, with each handler dealing with 8 GPIO + * pins. */ + for (i = 0; i < 4; i++) { + int irq; + irq = platform_get_irq(pdev, i); + if (irq < 0) + continue; + irq_set_handler_data(irq, mvchip); + irq_set_chained_handler(irq, mvebu_gpio_irq_handler); + } + + mvchip->irqbase = irq_alloc_descs(-1, 0, ngpios, -1); + if (mvchip->irqbase < 0) { + dev_err(&pdev->dev, "no irqs\n"); + kfree(mvchip->chip.label); + return -ENOMEM; + } + + gc = irq_alloc_generic_chip("mvebu_gpio_irq", 2, mvchip->irqbase, + mvchip->membase, handle_level_irq); + if (! gc) { + dev_err(&pdev->dev, "Cannot allocate generic irq_chip\n"); + kfree(mvchip->chip.label); + return -ENOMEM; + } + + gc->private = mvchip; + ct = &gc->chip_types[0]; + ct->type = IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW; + ct->chip.irq_mask = mvebu_gpio_level_irq_mask; + ct->chip.irq_unmask = mvebu_gpio_level_irq_unmask; + ct->chip.irq_set_type = mvebu_gpio_irq_set_type; + ct->chip.name = mvchip->chip.label; + + ct = &gc->chip_types[1]; + ct->type = IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING; + ct->chip.irq_ack = mvebu_gpio_irq_ack; + ct->chip.irq_mask = mvebu_gpio_edge_irq_mask; + ct->chip.irq_unmask = mvebu_gpio_edge_irq_unmask; + ct->chip.irq_set_type = mvebu_gpio_irq_set_type; + ct->handler = handle_edge_irq; + ct->chip.name = mvchip->chip.label; + + irq_setup_generic_chip(gc, IRQ_MSK(ngpios), IRQ_GC_INIT_MASK_CACHE, + IRQ_NOREQUEST, IRQ_LEVEL | IRQ_NOPROBE); + + /* Setup irq domain on top of the generic chip. */ + mvchip->domain = irq_domain_add_legacy(np, mvchip->chip.ngpio, + mvchip->irqbase, 0, + &irq_domain_simple_ops, + mvchip); + if (!mvchip->domain) { + dev_err(&pdev->dev, "couldn't allocate irq domain %s (DT).\n", + mvchip->chip.label); + irq_remove_generic_chip(gc, IRQ_MSK(ngpios), IRQ_NOREQUEST, + IRQ_LEVEL | IRQ_NOPROBE); + kfree(gc); + kfree(mvchip->chip.label); + return -ENODEV; + } + + return 0; +} + +static struct platform_driver mvebu_gpio_driver = { + .driver = { + .name = "mvebu-gpio", + .owner = THIS_MODULE, + .of_match_table = mvebu_gpio_of_match, + }, + .probe = mvebu_gpio_probe, + .id_table = mvebu_gpio_ids, +}; + +static int __init mvebu_gpio_init(void) +{ + return platform_driver_register(&mvebu_gpio_driver); +} +postcore_initcall(mvebu_gpio_init); diff --git a/drivers/gpio/gpio-stp-xway.c b/drivers/gpio/gpio-stp-xway.c index e35096bf3cfb..8bead0bb6459 100644 --- a/drivers/gpio/gpio-stp-xway.c +++ b/drivers/gpio/gpio-stp-xway.c @@ -82,7 +82,7 @@ struct xway_stp { struct gpio_chip gc; void __iomem *virt; u32 edge; /* rising or falling edge triggered shift register */ - u16 shadow; /* shadow the shift registers state */ + u32 shadow; /* shadow the shift registers state */ u8 groups; /* we can drive 1-3 groups of 8bit each */ u8 dsl; /* the 2 LSBs can be driven by the dsl core */ u8 phy1; /* 3 bits can be driven by phy1 */ diff --git a/drivers/gpio/gpio-twl6040.c b/drivers/gpio/gpio-twl6040.c new file mode 100644 index 000000000000..dd58e8b25043 --- /dev/null +++ b/drivers/gpio/gpio-twl6040.c @@ -0,0 +1,137 @@ +/* + * Access to GPOs on TWL6040 chip + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * Authors: + * Sergio Aguirre <saaguirre@ti.com> + * Peter Ujfalusi <peter.ujfalusi@ti.com> + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kthread.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/platform_device.h> +#include <linux/of.h> + +#include <linux/mfd/twl6040.h> + +static struct gpio_chip twl6040gpo_chip; + +static int twl6040gpo_get(struct gpio_chip *chip, unsigned offset) +{ + struct twl6040 *twl6040 = dev_get_drvdata(chip->dev->parent); + int ret = 0; + + ret = twl6040_reg_read(twl6040, TWL6040_REG_GPOCTL); + if (ret < 0) + return ret; + + return (ret >> offset) & 1; +} + +static int twl6040gpo_direction_out(struct gpio_chip *chip, unsigned offset, + int value) +{ + /* This only drives GPOs, and can't change direction */ + return 0; +} + +static void twl6040gpo_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct twl6040 *twl6040 = dev_get_drvdata(chip->dev->parent); + int ret; + u8 gpoctl; + + ret = twl6040_reg_read(twl6040, TWL6040_REG_GPOCTL); + if (ret < 0) + return; + + if (value) + gpoctl = ret | (1 << offset); + else + gpoctl = ret & ~(1 << offset); + + twl6040_reg_write(twl6040, TWL6040_REG_GPOCTL, gpoctl); +} + +static struct gpio_chip twl6040gpo_chip = { + .label = "twl6040", + .owner = THIS_MODULE, + .get = twl6040gpo_get, + .direction_output = twl6040gpo_direction_out, + .set = twl6040gpo_set, + .can_sleep = 1, +}; + +/*----------------------------------------------------------------------*/ + +static int __devinit gpo_twl6040_probe(struct platform_device *pdev) +{ + struct twl6040_gpo_data *pdata = pdev->dev.platform_data; + struct device *twl6040_core_dev = pdev->dev.parent; + struct twl6040 *twl6040 = dev_get_drvdata(twl6040_core_dev); + int ret; + + if (pdata) + twl6040gpo_chip.base = pdata->gpio_base; + else + twl6040gpo_chip.base = -1; + + if (twl6040_get_revid(twl6040) < TWL6041_REV_ES2_0) + twl6040gpo_chip.ngpio = 3; /* twl6040 have 3 GPO */ + else + twl6040gpo_chip.ngpio = 1; /* twl6041 have 1 GPO */ + + twl6040gpo_chip.dev = &pdev->dev; +#ifdef CONFIG_OF_GPIO + twl6040gpo_chip.of_node = twl6040_core_dev->of_node; +#endif + + ret = gpiochip_add(&twl6040gpo_chip); + if (ret < 0) { + dev_err(&pdev->dev, "could not register gpiochip, %d\n", ret); + twl6040gpo_chip.ngpio = 0; + } + + return ret; +} + +static int __devexit gpo_twl6040_remove(struct platform_device *pdev) +{ + return gpiochip_remove(&twl6040gpo_chip); +} + +/* Note: this hardware lives inside an I2C-based multi-function device. */ +MODULE_ALIAS("platform:twl6040-gpo"); + +static struct platform_driver gpo_twl6040_driver = { + .driver = { + .name = "twl6040-gpo", + .owner = THIS_MODULE, + }, + .probe = gpo_twl6040_probe, + .remove = gpo_twl6040_remove, +}; + +module_platform_driver(gpo_twl6040_driver); + +MODULE_AUTHOR("Texas Instruments, Inc."); +MODULE_DESCRIPTION("GPO interface for TWL6040"); +MODULE_LICENSE("GPL"); |