From c34f16b70a52e348a62944fe0d5c7c1eb9ad5b72 Mon Sep 17 00:00:00 2001 From: Gregory Bean Date: Tue, 10 Aug 2010 18:02:27 -0700 Subject: gpio: sx150x: add Semtech I2C sx150x gpio expander driver Add support for Semtech SX150-series I2C GPIO expanders. Compatible models include: 8 bits: sx1508q 16 bits: sx1509q Signed-off-by: Gregory Bean Cc: David Brownell Cc: Jean Delvare Cc: Trilok Soni Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/gpio/Makefile | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/gpio/Makefile') diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index e53dcff49b4f..a69e0609ff7f 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -35,3 +35,4 @@ obj-$(CONFIG_GPIO_WM8994) += wm8994-gpio.o obj-$(CONFIG_GPIO_SCH) += sch_gpio.o obj-$(CONFIG_GPIO_RDC321X) += rdc321x-gpio.o obj-$(CONFIG_GPIO_JANZ_TTL) += janz-ttl.o +obj-$(CONFIG_GPIO_SX150X) += sx150x.o -- cgit v1.2.3-55-g7522 From 03f822f5e5f5924f4ad372d3e698855c6a9275e0 Mon Sep 17 00:00:00 2001 From: Rabin Vincent Date: Fri, 2 Jul 2010 16:52:09 +0530 Subject: gpio: Add STMPE GPIO driver Add support for the GPIOs on STMPE I/O Expanders. [l.fu@pengutronix.de: fix set direction input] [l.fu@pengutronix.de: set GPIO alternate function while requesting] Acked-by: Luotao Fu Acked-by: Linus Walleij Signed-off-by: Rabin Vincent Signed-off-by: Samuel Ortiz --- drivers/gpio/Kconfig | 7 + drivers/gpio/Makefile | 1 + drivers/gpio/stmpe-gpio.c | 399 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 407 insertions(+) create mode 100644 drivers/gpio/stmpe-gpio.c (limited to 'drivers/gpio/Makefile') diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index f623953b5797..510aa2054544 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -206,6 +206,13 @@ config GPIO_SX150X 8 bits: sx1508q 16 bits: sx1509q +config GPIO_STMPE + bool "STMPE GPIOs" + depends on MFD_STMPE + help + This enables support for the GPIOs found on the STMPE I/O + Expanders. + config GPIO_TC35892 bool "TC35892 GPIOs" depends on MFD_TC35892 diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index a69e0609ff7f..fc6019d93720 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_GPIO_MCP23S08) += mcp23s08.o obj-$(CONFIG_GPIO_PCA953X) += pca953x.o obj-$(CONFIG_GPIO_PCF857X) += pcf857x.o obj-$(CONFIG_GPIO_PL061) += pl061.o +obj-$(CONFIG_GPIO_STMPE) += stmpe-gpio.o obj-$(CONFIG_GPIO_TC35892) += tc35892-gpio.o obj-$(CONFIG_GPIO_TIMBERDALE) += timbgpio.o obj-$(CONFIG_GPIO_TWL4030) += twl4030-gpio.o diff --git a/drivers/gpio/stmpe-gpio.c b/drivers/gpio/stmpe-gpio.c new file mode 100644 index 000000000000..4e1f1b9d5e67 --- /dev/null +++ b/drivers/gpio/stmpe-gpio.c @@ -0,0 +1,399 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License, version 2 + * Author: Rabin Vincent for ST-Ericsson + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * These registers are modified under the irq bus lock and cached to avoid + * unnecessary writes in bus_sync_unlock. + */ +enum { REG_RE, REG_FE, REG_IE }; + +#define CACHE_NR_REGS 3 +#define CACHE_NR_BANKS (STMPE_NR_GPIOS / 8) + +struct stmpe_gpio { + struct gpio_chip chip; + struct stmpe *stmpe; + struct device *dev; + struct mutex irq_lock; + + int irq_base; + + /* Caches of interrupt control registers for bus_lock */ + u8 regs[CACHE_NR_REGS][CACHE_NR_BANKS]; + u8 oldregs[CACHE_NR_REGS][CACHE_NR_BANKS]; +}; + +static inline struct stmpe_gpio *to_stmpe_gpio(struct gpio_chip *chip) +{ + return container_of(chip, struct stmpe_gpio, chip); +} + +static int stmpe_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct stmpe_gpio *stmpe_gpio = to_stmpe_gpio(chip); + struct stmpe *stmpe = stmpe_gpio->stmpe; + u8 reg = stmpe->regs[STMPE_IDX_GPMR_LSB] - (offset / 8); + u8 mask = 1 << (offset % 8); + int ret; + + ret = stmpe_reg_read(stmpe, reg); + if (ret < 0) + return ret; + + return ret & mask; +} + +static void stmpe_gpio_set(struct gpio_chip *chip, unsigned offset, int val) +{ + struct stmpe_gpio *stmpe_gpio = to_stmpe_gpio(chip); + struct stmpe *stmpe = stmpe_gpio->stmpe; + int which = val ? STMPE_IDX_GPSR_LSB : STMPE_IDX_GPCR_LSB; + u8 reg = stmpe->regs[which] - (offset / 8); + u8 mask = 1 << (offset % 8); + + stmpe_reg_write(stmpe, reg, mask); +} + +static int stmpe_gpio_direction_output(struct gpio_chip *chip, + unsigned offset, int val) +{ + struct stmpe_gpio *stmpe_gpio = to_stmpe_gpio(chip); + struct stmpe *stmpe = stmpe_gpio->stmpe; + u8 reg = stmpe->regs[STMPE_IDX_GPDR_LSB] - (offset / 8); + u8 mask = 1 << (offset % 8); + + stmpe_gpio_set(chip, offset, val); + + return stmpe_set_bits(stmpe, reg, mask, mask); +} + +static int stmpe_gpio_direction_input(struct gpio_chip *chip, + unsigned offset) +{ + struct stmpe_gpio *stmpe_gpio = to_stmpe_gpio(chip); + struct stmpe *stmpe = stmpe_gpio->stmpe; + u8 reg = stmpe->regs[STMPE_IDX_GPDR_LSB] - (offset / 8); + u8 mask = 1 << (offset % 8); + + return stmpe_set_bits(stmpe, reg, mask, 0); +} + +static int stmpe_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct stmpe_gpio *stmpe_gpio = to_stmpe_gpio(chip); + + return stmpe_gpio->irq_base + offset; +} + +static int stmpe_gpio_request(struct gpio_chip *chip, unsigned offset) +{ + struct stmpe_gpio *stmpe_gpio = to_stmpe_gpio(chip); + struct stmpe *stmpe = stmpe_gpio->stmpe; + + return stmpe_set_altfunc(stmpe, 1 << offset, STMPE_BLOCK_GPIO); +} + +static struct gpio_chip template_chip = { + .label = "stmpe", + .owner = THIS_MODULE, + .direction_input = stmpe_gpio_direction_input, + .get = stmpe_gpio_get, + .direction_output = stmpe_gpio_direction_output, + .set = stmpe_gpio_set, + .to_irq = stmpe_gpio_to_irq, + .request = stmpe_gpio_request, + .can_sleep = 1, +}; + +static int stmpe_gpio_irq_set_type(unsigned int irq, unsigned int type) +{ + struct stmpe_gpio *stmpe_gpio = get_irq_chip_data(irq); + int offset = irq - stmpe_gpio->irq_base; + int regoffset = offset / 8; + int mask = 1 << (offset % 8); + + if (type == IRQ_TYPE_LEVEL_LOW || type == IRQ_TYPE_LEVEL_HIGH) + return -EINVAL; + + if (type == IRQ_TYPE_EDGE_RISING) + stmpe_gpio->regs[REG_RE][regoffset] |= mask; + else + stmpe_gpio->regs[REG_RE][regoffset] &= ~mask; + + if (type == IRQ_TYPE_EDGE_FALLING) + stmpe_gpio->regs[REG_FE][regoffset] |= mask; + else + stmpe_gpio->regs[REG_FE][regoffset] &= ~mask; + + return 0; +} + +static void stmpe_gpio_irq_lock(unsigned int irq) +{ + struct stmpe_gpio *stmpe_gpio = get_irq_chip_data(irq); + + mutex_lock(&stmpe_gpio->irq_lock); +} + +static void stmpe_gpio_irq_sync_unlock(unsigned int irq) +{ + struct stmpe_gpio *stmpe_gpio = get_irq_chip_data(irq); + struct stmpe *stmpe = stmpe_gpio->stmpe; + int num_banks = DIV_ROUND_UP(stmpe->num_gpios, 8); + static const u8 regmap[] = { + [REG_RE] = STMPE_IDX_GPRER_LSB, + [REG_FE] = STMPE_IDX_GPFER_LSB, + [REG_IE] = STMPE_IDX_IEGPIOR_LSB, + }; + int i, j; + + for (i = 0; i < CACHE_NR_REGS; i++) { + for (j = 0; j < num_banks; j++) { + u8 old = stmpe_gpio->oldregs[i][j]; + u8 new = stmpe_gpio->regs[i][j]; + + if (new == old) + continue; + + stmpe_gpio->oldregs[i][j] = new; + stmpe_reg_write(stmpe, stmpe->regs[regmap[i]] - j, new); + } + } + + mutex_unlock(&stmpe_gpio->irq_lock); +} + +static void stmpe_gpio_irq_mask(unsigned int irq) +{ + struct stmpe_gpio *stmpe_gpio = get_irq_chip_data(irq); + int offset = irq - stmpe_gpio->irq_base; + int regoffset = offset / 8; + int mask = 1 << (offset % 8); + + stmpe_gpio->regs[REG_IE][regoffset] &= ~mask; +} + +static void stmpe_gpio_irq_unmask(unsigned int irq) +{ + struct stmpe_gpio *stmpe_gpio = get_irq_chip_data(irq); + int offset = irq - stmpe_gpio->irq_base; + int regoffset = offset / 8; + int mask = 1 << (offset % 8); + + stmpe_gpio->regs[REG_IE][regoffset] |= mask; +} + +static struct irq_chip stmpe_gpio_irq_chip = { + .name = "stmpe-gpio", + .bus_lock = stmpe_gpio_irq_lock, + .bus_sync_unlock = stmpe_gpio_irq_sync_unlock, + .mask = stmpe_gpio_irq_mask, + .unmask = stmpe_gpio_irq_unmask, + .set_type = stmpe_gpio_irq_set_type, +}; + +static irqreturn_t stmpe_gpio_irq(int irq, void *dev) +{ + struct stmpe_gpio *stmpe_gpio = dev; + struct stmpe *stmpe = stmpe_gpio->stmpe; + u8 statmsbreg = stmpe->regs[STMPE_IDX_ISGPIOR_MSB]; + int num_banks = DIV_ROUND_UP(stmpe->num_gpios, 8); + u8 status[num_banks]; + int ret; + int i; + + ret = stmpe_block_read(stmpe, statmsbreg, num_banks, status); + if (ret < 0) + return IRQ_NONE; + + for (i = 0; i < num_banks; i++) { + int bank = num_banks - i - 1; + unsigned int enabled = stmpe_gpio->regs[REG_IE][bank]; + unsigned int stat = status[i]; + + stat &= enabled; + if (!stat) + continue; + + while (stat) { + int bit = __ffs(stat); + int line = bank * 8 + bit; + + handle_nested_irq(stmpe_gpio->irq_base + line); + stat &= ~(1 << bit); + } + + stmpe_reg_write(stmpe, statmsbreg + i, status[i]); + stmpe_reg_write(stmpe, stmpe->regs[STMPE_IDX_GPEDR_MSB] + i, + status[i]); + } + + return IRQ_HANDLED; +} + +static int __devinit stmpe_gpio_irq_init(struct stmpe_gpio *stmpe_gpio) +{ + int base = stmpe_gpio->irq_base; + int irq; + + for (irq = base; irq < base + stmpe_gpio->chip.ngpio; irq++) { + set_irq_chip_data(irq, stmpe_gpio); + set_irq_chip_and_handler(irq, &stmpe_gpio_irq_chip, + handle_simple_irq); + set_irq_nested_thread(irq, 1); +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID); +#else + set_irq_noprobe(irq); +#endif + } + + return 0; +} + +static void stmpe_gpio_irq_remove(struct stmpe_gpio *stmpe_gpio) +{ + int base = stmpe_gpio->irq_base; + int irq; + + for (irq = base; irq < base + stmpe_gpio->chip.ngpio; irq++) { +#ifdef CONFIG_ARM + set_irq_flags(irq, 0); +#endif + set_irq_chip_and_handler(irq, NULL, NULL); + set_irq_chip_data(irq, NULL); + } +} + +static int __devinit stmpe_gpio_probe(struct platform_device *pdev) +{ + struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent); + struct stmpe_gpio_platform_data *pdata; + struct stmpe_gpio *stmpe_gpio; + int ret; + int irq; + + pdata = stmpe->pdata->gpio; + if (!pdata) + return -ENODEV; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + stmpe_gpio = kzalloc(sizeof(struct stmpe_gpio), GFP_KERNEL); + if (!stmpe_gpio) + return -ENOMEM; + + mutex_init(&stmpe_gpio->irq_lock); + + stmpe_gpio->dev = &pdev->dev; + stmpe_gpio->stmpe = stmpe; + + stmpe_gpio->chip = template_chip; + stmpe_gpio->chip.ngpio = stmpe->num_gpios; + stmpe_gpio->chip.dev = &pdev->dev; + stmpe_gpio->chip.base = pdata ? pdata->gpio_base : -1; + + stmpe_gpio->irq_base = stmpe->irq_base + STMPE_INT_GPIO(0); + + ret = stmpe_enable(stmpe, STMPE_BLOCK_GPIO); + if (ret) + return ret; + + ret = stmpe_gpio_irq_init(stmpe_gpio); + if (ret) + goto out_free; + + ret = request_threaded_irq(irq, NULL, stmpe_gpio_irq, IRQF_ONESHOT, + "stmpe-gpio", stmpe_gpio); + if (ret) { + dev_err(&pdev->dev, "unable to get irq: %d\n", ret); + goto out_removeirq; + } + + ret = gpiochip_add(&stmpe_gpio->chip); + if (ret) { + dev_err(&pdev->dev, "unable to add gpiochip: %d\n", ret); + goto out_freeirq; + } + + if (pdata && pdata->setup) + pdata->setup(stmpe, stmpe_gpio->chip.base); + + platform_set_drvdata(pdev, stmpe_gpio); + + return 0; + +out_freeirq: + free_irq(irq, stmpe_gpio); +out_removeirq: + stmpe_gpio_irq_remove(stmpe_gpio); +out_free: + kfree(stmpe_gpio); + return ret; +} + +static int __devexit stmpe_gpio_remove(struct platform_device *pdev) +{ + struct stmpe_gpio *stmpe_gpio = platform_get_drvdata(pdev); + struct stmpe *stmpe = stmpe_gpio->stmpe; + struct stmpe_gpio_platform_data *pdata = stmpe->pdata->gpio; + int irq = platform_get_irq(pdev, 0); + int ret; + + if (pdata && pdata->remove) + pdata->remove(stmpe, stmpe_gpio->chip.base); + + ret = gpiochip_remove(&stmpe_gpio->chip); + if (ret < 0) { + dev_err(stmpe_gpio->dev, + "unable to remove gpiochip: %d\n", ret); + return ret; + } + + stmpe_disable(stmpe, STMPE_BLOCK_GPIO); + + free_irq(irq, stmpe_gpio); + stmpe_gpio_irq_remove(stmpe_gpio); + platform_set_drvdata(pdev, NULL); + kfree(stmpe_gpio); + + return 0; +} + +static struct platform_driver stmpe_gpio_driver = { + .driver.name = "stmpe-gpio", + .driver.owner = THIS_MODULE, + .probe = stmpe_gpio_probe, + .remove = __devexit_p(stmpe_gpio_remove), +}; + +static int __init stmpe_gpio_init(void) +{ + return platform_driver_register(&stmpe_gpio_driver); +} +subsys_initcall(stmpe_gpio_init); + +static void __exit stmpe_gpio_exit(void) +{ + platform_driver_unregister(&stmpe_gpio_driver); +} +module_exit(stmpe_gpio_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("STMPExxxx GPIO driver"); +MODULE_AUTHOR("Rabin Vincent "); -- cgit v1.2.3-55-g7522 From aeec56e331c6d2750de02ef34b305338305ca690 Mon Sep 17 00:00:00 2001 From: Anton Vorontsov Date: Wed, 27 Oct 2010 15:33:15 -0700 Subject: gpio: add driver for basic memory-mapped GPIO controllers The basic GPIO controllers may be found in various on-board FPGA and ASIC solutions that are used to control board's switches, LEDs, chip-selects, Ethernet/USB PHY power, etc. These controllers may not provide any means of pin setup (in/out/open drain). The driver supports: - 8/16/32/64 bits registers; - GPIO controllers with clear/set registers; - GPIO controllers with a single "data" register; - Big endian bits/GPIOs ordering (mostly used on PowerPC). Signed-off-by: Anton Vorontsov Reviewed-by: Mark Brown Cc: David Brownell Cc: Samuel Ortiz , Cc: Alan Cox Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/gpio/Kconfig | 5 + drivers/gpio/Makefile | 1 + drivers/gpio/basic_mmio_gpio.c | 297 ++++++++++++++++++++++++++++++++++++++++ include/linux/basic_mmio_gpio.h | 20 +++ 4 files changed, 323 insertions(+) create mode 100644 drivers/gpio/basic_mmio_gpio.c create mode 100644 include/linux/basic_mmio_gpio.h (limited to 'drivers/gpio/Makefile') diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 510aa2054544..e47ef94be379 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -70,6 +70,11 @@ config GPIO_MAX730X comment "Memory mapped GPIO expanders:" +config GPIO_BASIC_MMIO + tristate "Basic memory-mapped GPIO controllers support" + help + Say yes here to support basic memory-mapped GPIO controllers. + config GPIO_IT8761E tristate "IT8761E GPIO support" depends on GPIOLIB diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index fc6019d93720..3ff0651fd173 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_GPIOLIB) += gpiolib.o obj-$(CONFIG_GPIO_ADP5520) += adp5520-gpio.o obj-$(CONFIG_GPIO_ADP5588) += adp5588-gpio.o +obj-$(CONFIG_GPIO_BASIC_MMIO) += basic_mmio_gpio.o obj-$(CONFIG_GPIO_LANGWELL) += langwell_gpio.o obj-$(CONFIG_GPIO_MAX730X) += max730x.o obj-$(CONFIG_GPIO_MAX7300) += max7300.o diff --git a/drivers/gpio/basic_mmio_gpio.c b/drivers/gpio/basic_mmio_gpio.c new file mode 100644 index 000000000000..3addea65894e --- /dev/null +++ b/drivers/gpio/basic_mmio_gpio.c @@ -0,0 +1,297 @@ +/* + * Driver for basic memory-mapped GPIO controllers. + * + * Copyright 2008 MontaVista Software, Inc. + * Copyright 2008,2010 Anton Vorontsov + * + * 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. + * + * ....``.```~~~~````.`.`.`.`.```````'',,,.........`````......`....... + * ...`` ```````.. + * ..The simplest form of a GPIO controller that the driver supports is`` + * `.just a single "data" register, where GPIO state can be read and/or ` + * `,..written. ,,..``~~~~ .....``.`.`.~~.```.`.........``````.``````` + * ````````` + ___ +_/~~|___/~| . ```~~~~~~ ___/___\___ ,~.`.`.`.`````.~~...,,,,... +__________|~$@~~~ %~ /o*o*o*o*o*o\ .. Implementing such a GPIO . +o ` ~~~~\___/~~~~ ` controller in FPGA is ,.` + `....trivial..'~`.```.``` + * ``````` + * .```````~~~~`..`.``.``. + * . The driver supports `... ,..```.`~~~```````````````....````.``,, + * . big-endian notation, just`. .. A bit more sophisticated controllers , + * . register the device with -be`. .with a pair of set/clear-bit registers , + * `.. suffix. ```~~`````....`.` . affecting the data register and the .` + * ``.`.``...``` ```.. output pins are also supported.` + * ^^ `````.`````````.,``~``~``~~`````` + * . ^^ + * ,..`.`.`...````````````......`.`.`.`.`.`..`.`.`.. + * .. The expectation is that in at least some cases . ,-~~~-, + * .this will be used with roll-your-own ASIC/FPGA .` \ / + * .logic in Verilog or VHDL. ~~~`````````..`````~~` \ / + * ..````````......``````````` \o_ + * | + * ^^ / \ + * + * ...`````~~`.....``.`..........``````.`.``.```........``. + * ` 8, 16, 32 and 64 bits registers are supported, and``. + * . the number of GPIOs is determined by the width of ~ + * .. the registers. ,............```.`.`..`.`.~~~.`.`.`~ + * `.......````.``` + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct bgpio_chip { + struct gpio_chip gc; + void __iomem *reg_dat; + void __iomem *reg_set; + void __iomem *reg_clr; + + /* Number of bits (GPIOs): * 8. */ + int bits; + + /* + * Some GPIO controllers work with the big-endian bits notation, + * e.g. in a 8-bits register, GPIO7 is the least significant bit. + */ + int big_endian_bits; + + /* + * Used to lock bgpio_chip->data. Also, this is needed to keep + * shadowed and real data registers writes together. + */ + spinlock_t lock; + + /* Shadowed data register to clear/set bits safely. */ + unsigned long data; +}; + +static struct bgpio_chip *to_bgpio_chip(struct gpio_chip *gc) +{ + return container_of(gc, struct bgpio_chip, gc); +} + +static unsigned long bgpio_in(struct bgpio_chip *bgc) +{ + switch (bgc->bits) { + case 8: + return __raw_readb(bgc->reg_dat); + case 16: + return __raw_readw(bgc->reg_dat); + case 32: + return __raw_readl(bgc->reg_dat); +#if BITS_PER_LONG >= 64 + case 64: + return __raw_readq(bgc->reg_dat); +#endif + } + return -EINVAL; +} + +static void bgpio_out(struct bgpio_chip *bgc, void __iomem *reg, + unsigned long data) +{ + switch (bgc->bits) { + case 8: + __raw_writeb(data, reg); + return; + case 16: + __raw_writew(data, reg); + return; + case 32: + __raw_writel(data, reg); + return; +#if BITS_PER_LONG >= 64 + case 64: + __raw_writeq(data, reg); + return; +#endif + } +} + +static unsigned long bgpio_pin2mask(struct bgpio_chip *bgc, unsigned int pin) +{ + if (bgc->big_endian_bits) + return 1 << (bgc->bits - 1 - pin); + else + return 1 << pin; +} + +static int bgpio_get(struct gpio_chip *gc, unsigned int gpio) +{ + struct bgpio_chip *bgc = to_bgpio_chip(gc); + + return bgpio_in(bgc) & bgpio_pin2mask(bgc, gpio); +} + +static void bgpio_set(struct gpio_chip *gc, unsigned int gpio, int val) +{ + struct bgpio_chip *bgc = to_bgpio_chip(gc); + unsigned long mask = bgpio_pin2mask(bgc, gpio); + unsigned long flags; + + if (bgc->reg_set) { + if (val) + bgpio_out(bgc, bgc->reg_set, mask); + else + bgpio_out(bgc, bgc->reg_clr, mask); + return; + } + + spin_lock_irqsave(&bgc->lock, flags); + + if (val) + bgc->data |= mask; + else + bgc->data &= ~mask; + + bgpio_out(bgc, bgc->reg_dat, bgc->data); + + spin_unlock_irqrestore(&bgc->lock, flags); +} + +static int bgpio_dir_in(struct gpio_chip *gc, unsigned int gpio) +{ + return 0; +} + +static int bgpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val) +{ + bgpio_set(gc, gpio, val); + return 0; +} + +static int __devinit bgpio_probe(struct platform_device *pdev) +{ + const struct platform_device_id *platid = platform_get_device_id(pdev); + struct device *dev = &pdev->dev; + struct bgpio_pdata *pdata = dev_get_platdata(dev); + struct bgpio_chip *bgc; + struct resource *res_dat; + struct resource *res_set; + struct resource *res_clr; + resource_size_t dat_sz; + int bits; + int ret; + + res_dat = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dat"); + if (!res_dat) + return -EINVAL; + + dat_sz = resource_size(res_dat); + if (!is_power_of_2(dat_sz)) + return -EINVAL; + + bits = dat_sz * 8; + if (bits > BITS_PER_LONG) + return -EINVAL; + + bgc = devm_kzalloc(dev, sizeof(*bgc), GFP_KERNEL); + if (!bgc) + return -ENOMEM; + + bgc->reg_dat = devm_ioremap(dev, res_dat->start, dat_sz); + if (!bgc->reg_dat) + return -ENOMEM; + + res_set = platform_get_resource_byname(pdev, IORESOURCE_MEM, "set"); + res_clr = platform_get_resource_byname(pdev, IORESOURCE_MEM, "clr"); + if (res_set && res_clr) { + if (resource_size(res_set) != resource_size(res_clr) || + resource_size(res_set) != dat_sz) + return -EINVAL; + + bgc->reg_set = devm_ioremap(dev, res_set->start, dat_sz); + bgc->reg_clr = devm_ioremap(dev, res_clr->start, dat_sz); + if (!bgc->reg_set || !bgc->reg_clr) + return -ENOMEM; + } else if (res_set || res_clr) { + return -EINVAL; + } + + spin_lock_init(&bgc->lock); + + bgc->bits = bits; + bgc->big_endian_bits = !strcmp(platid->name, "basic-mmio-gpio-be"); + bgc->data = bgpio_in(bgc); + + bgc->gc.ngpio = bits; + bgc->gc.direction_input = bgpio_dir_in; + bgc->gc.direction_output = bgpio_dir_out; + bgc->gc.get = bgpio_get; + bgc->gc.set = bgpio_set; + bgc->gc.dev = dev; + bgc->gc.label = dev_name(dev); + + if (pdata) + bgc->gc.base = pdata->base; + else + bgc->gc.base = -1; + + dev_set_drvdata(dev, bgc); + + ret = gpiochip_add(&bgc->gc); + if (ret) + dev_err(dev, "gpiochip_add() failed: %d\n", ret); + + return ret; +} + +static int __devexit bgpio_remove(struct platform_device *pdev) +{ + struct bgpio_chip *bgc = dev_get_drvdata(&pdev->dev); + + return gpiochip_remove(&bgc->gc); +} + +static const struct platform_device_id bgpio_id_table[] = { + { "basic-mmio-gpio", }, + { "basic-mmio-gpio-be", }, + {}, +}; +MODULE_DEVICE_TABLE(platform, bgpio_id_table); + +static struct platform_driver bgpio_driver = { + .driver = { + .name = "basic-mmio-gpio", + }, + .id_table = bgpio_id_table, + .probe = bgpio_probe, + .remove = __devexit_p(bgpio_remove), +}; + +static int __init bgpio_init(void) +{ + return platform_driver_register(&bgpio_driver); +} +module_init(bgpio_init); + +static void __exit bgpio_exit(void) +{ + platform_driver_unregister(&bgpio_driver); +} +module_exit(bgpio_exit); + +MODULE_DESCRIPTION("Driver for basic memory-mapped GPIO controllers"); +MODULE_AUTHOR("Anton Vorontsov "); +MODULE_LICENSE("GPL"); diff --git a/include/linux/basic_mmio_gpio.h b/include/linux/basic_mmio_gpio.h new file mode 100644 index 000000000000..198087a16fc4 --- /dev/null +++ b/include/linux/basic_mmio_gpio.h @@ -0,0 +1,20 @@ +/* + * Basic memory-mapped GPIO controllers. + * + * Copyright 2008 MontaVista Software, Inc. + * Copyright 2008,2010 Anton Vorontsov + * + * 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. + */ + +#ifndef __BASIC_MMIO_GPIO_H +#define __BASIC_MMIO_GPIO_H + +struct bgpio_pdata { + int base; +}; + +#endif /* __BASIC_MMIO_GPIO_H */ -- cgit v1.2.3-55-g7522 From ead6db084392349ad33323b1bb2916058dd7e82b Mon Sep 17 00:00:00 2001 From: Miguel Gaio Date: Wed, 27 Oct 2010 15:33:18 -0700 Subject: gpio: add support for 74x164 serial-in/parallel-out 8-bit shift register Add support for generic 74x164 serial-in/parallel-out 8-bits shift register. This driver can be used as a GPIO output expander. [akpm@linux-foundation.org: remove unused local `refresh'] Signed-off-by: Miguel Gaio Signed-off-by: Juhos Gabor Signed-off-by: Florian Fainelli Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/gpio/74x164.c | 182 +++++++++++++++++++++++++++++++++++++++++++++ drivers/gpio/Kconfig | 8 ++ drivers/gpio/Makefile | 1 + include/linux/spi/74x164.h | 11 +++ 4 files changed, 202 insertions(+) create mode 100644 drivers/gpio/74x164.c create mode 100644 include/linux/spi/74x164.h (limited to 'drivers/gpio/Makefile') diff --git a/drivers/gpio/74x164.c b/drivers/gpio/74x164.c new file mode 100644 index 000000000000..d91ff4c282e9 --- /dev/null +++ b/drivers/gpio/74x164.c @@ -0,0 +1,182 @@ +/* + * 74Hx164 - Generic serial-in/parallel-out 8-bits shift register GPIO driver + * + * Copyright (C) 2010 Gabor Juhos + * Copyright (C) 2010 Miguel Gaio + * + * 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. + */ + +#include +#include +#include +#include +#include +#include + +#define GEN_74X164_GPIO_COUNT 8 + + +struct gen_74x164_chip { + struct spi_device *spi; + struct gpio_chip gpio_chip; + struct mutex lock; + u8 port_config; +}; + +static void gen_74x164_set_value(struct gpio_chip *, unsigned, int); + +static struct gen_74x164_chip *gpio_to_chip(struct gpio_chip *gc) +{ + return container_of(gc, struct gen_74x164_chip, gpio_chip); +} + +static int __gen_74x164_write_config(struct gen_74x164_chip *chip) +{ + return spi_write(chip->spi, + &chip->port_config, sizeof(chip->port_config)); +} + +static int gen_74x164_direction_output(struct gpio_chip *gc, + unsigned offset, int val) +{ + gen_74x164_set_value(gc, offset, val); + return 0; +} + +static int gen_74x164_get_value(struct gpio_chip *gc, unsigned offset) +{ + struct gen_74x164_chip *chip = gpio_to_chip(gc); + int ret; + + mutex_lock(&chip->lock); + ret = (chip->port_config >> offset) & 0x1; + mutex_unlock(&chip->lock); + + return ret; +} + +static void gen_74x164_set_value(struct gpio_chip *gc, + unsigned offset, int val) +{ + struct gen_74x164_chip *chip = gpio_to_chip(gc); + + mutex_lock(&chip->lock); + if (val) + chip->port_config |= (1 << offset); + else + chip->port_config &= ~(1 << offset); + + __gen_74x164_write_config(chip); + mutex_unlock(&chip->lock); +} + +static int __devinit gen_74x164_probe(struct spi_device *spi) +{ + struct gen_74x164_chip *chip; + struct gen_74x164_chip_platform_data *pdata; + int ret; + + pdata = spi->dev.platform_data; + if (!pdata || !pdata->base) { + dev_dbg(&spi->dev, "incorrect or missing platform data\n"); + return -EINVAL; + } + + /* + * bits_per_word cannot be configured in platform data + */ + spi->bits_per_word = 8; + + ret = spi_setup(spi); + if (ret < 0) + return ret; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + mutex_init(&chip->lock); + + dev_set_drvdata(&spi->dev, chip); + + chip->spi = spi; + + chip->gpio_chip.label = GEN_74X164_DRIVER_NAME, + chip->gpio_chip.direction_output = gen_74x164_direction_output; + chip->gpio_chip.get = gen_74x164_get_value; + chip->gpio_chip.set = gen_74x164_set_value; + chip->gpio_chip.base = pdata->base; + chip->gpio_chip.ngpio = GEN_74X164_GPIO_COUNT; + chip->gpio_chip.can_sleep = 1; + chip->gpio_chip.dev = &spi->dev; + chip->gpio_chip.owner = THIS_MODULE; + + ret = __gen_74x164_write_config(chip); + if (ret) { + dev_err(&spi->dev, "Failed writing: %d\n", ret); + goto exit_destroy; + } + + ret = gpiochip_add(&chip->gpio_chip); + if (ret) + goto exit_destroy; + + return ret; + +exit_destroy: + dev_set_drvdata(&spi->dev, NULL); + mutex_destroy(&chip->lock); + kfree(chip); + return ret; +} + +static int gen_74x164_remove(struct spi_device *spi) +{ + struct gen_74x164_chip *chip; + int ret; + + chip = dev_get_drvdata(&spi->dev); + if (chip == NULL) + return -ENODEV; + + dev_set_drvdata(&spi->dev, NULL); + + ret = gpiochip_remove(&chip->gpio_chip); + if (!ret) { + mutex_destroy(&chip->lock); + kfree(chip); + } else + dev_err(&spi->dev, "Failed to remove the GPIO controller: %d\n", + ret); + + return ret; +} + +static struct spi_driver gen_74x164_driver = { + .driver = { + .name = GEN_74X164_DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = gen_74x164_probe, + .remove = __devexit_p(gen_74x164_remove), +}; + +static int __init gen_74x164_init(void) +{ + return spi_register_driver(&gen_74x164_driver); +} +subsys_initcall(gen_74x164_init); + +static void __exit gen_74x164_exit(void) +{ + spi_unregister_driver(&gen_74x164_driver); +} +module_exit(gen_74x164_exit); + +MODULE_AUTHOR("Gabor Juhos "); +MODULE_AUTHOR("Miguel Gaio "); +MODULE_DESCRIPTION("GPIO expander driver for 74X164 8-bits shift register"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index e47ef94be379..bc7b0fca6415 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -344,6 +344,14 @@ config GPIO_MC33880 SPI driver for Freescale MC33880 high-side/low-side switch. This provides GPIO interface supporting inputs and outputs. +config GPIO_74X164 + tristate "74x164 serial-in/parallel-out 8-bits shift register" + depends on SPI_MASTER + help + Platform driver for 74x164 compatible serial-in/parallel-out + 8-outputs shift registers. This driver can be used to provide access + to more gpio outputs. + comment "AC97 GPIO expanders:" config GPIO_UCB1400 diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 3ff0651fd173..0c23a4dd45e5 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_GPIO_MAX7301) += max7301.o obj-$(CONFIG_GPIO_MAX732X) += max732x.o obj-$(CONFIG_GPIO_MC33880) += mc33880.o obj-$(CONFIG_GPIO_MCP23S08) += mcp23s08.o +obj-$(CONFIG_GPIO_74X164) += 74x164.o obj-$(CONFIG_GPIO_PCA953X) += pca953x.o obj-$(CONFIG_GPIO_PCF857X) += pcf857x.o obj-$(CONFIG_GPIO_PL061) += pl061.o diff --git a/include/linux/spi/74x164.h b/include/linux/spi/74x164.h new file mode 100644 index 000000000000..d85c52f294a0 --- /dev/null +++ b/include/linux/spi/74x164.h @@ -0,0 +1,11 @@ +#ifndef LINUX_SPI_74X164_H +#define LINUX_SPI_74X164_H + +#define GEN_74X164_DRIVER_NAME "74x164" + +struct gen_74x164_chip_platform_data { + /* number assigned to the first GPIO */ + unsigned base; +}; + +#endif -- cgit v1.2.3-55-g7522 From 04c17aa89380addf8d7df6f0fd269fc2bd87796c Mon Sep 17 00:00:00 2001 From: Tomoya MORINAGA Date: Wed, 27 Oct 2010 15:33:21 -0700 Subject: gpio: add Topcliff PCH GPIO driver Topcliff PCH is the platform controller hub that is going to be used in Intel's upcoming general embedded platform. All IO peripherals in Topcliff PCH are actually devices sitting on AMBA bus. Topcliff PCH has GPIO I/F. Using this I/F, it is able to access system devices connected to GPIO. [akpm@linux-foundation.org: ese DEFINE_PCI_DEVICE_TABLE (per Joe Perches)] Signed-off-by: Tomoya MORINAGA Reviewed-by: Mark Brown Cc: Rabin Vincent Cc: Samuel Ortiz Cc: Linus Walleij Cc: Tomoya MORINAGA Cc: Joe Perches Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/gpio/Kconfig | 8 ++ drivers/gpio/Makefile | 1 + drivers/gpio/pch_gpio.c | 312 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 321 insertions(+) create mode 100644 drivers/gpio/pch_gpio.c (limited to 'drivers/gpio/Makefile') diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 3f3181dac8d7..dd9b4ba8d32d 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -313,6 +313,14 @@ config GPIO_LANGWELL help Say Y here to support Intel Langwell/Penwell GPIO. +config GPIO_PCH + tristate "PCH GPIO of Intel Topcliff" + depends on PCI + help + This driver is for PCH(Platform controller Hub) GPIO of Intel Topcliff + which is an IOH(Input/Output Hub) for x86 embedded processor. + This driver can access PCH GPIO device. + config GPIO_TIMBERDALE bool "Support for timberdale GPIO IP" depends on MFD_TIMBERDALE && GPIOLIB && HAS_IOMEM diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 0c23a4dd45e5..da2ecde5abdd 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_GPIO_MCP23S08) += mcp23s08.o obj-$(CONFIG_GPIO_74X164) += 74x164.o obj-$(CONFIG_GPIO_PCA953X) += pca953x.o obj-$(CONFIG_GPIO_PCF857X) += pcf857x.o +obj-$(CONFIG_GPIO_PCH) += pch_gpio.o obj-$(CONFIG_GPIO_PL061) += pl061.o obj-$(CONFIG_GPIO_STMPE) += stmpe-gpio.o obj-$(CONFIG_GPIO_TC35892) += tc35892-gpio.o diff --git a/drivers/gpio/pch_gpio.c b/drivers/gpio/pch_gpio.c new file mode 100644 index 000000000000..0eba0a75c804 --- /dev/null +++ b/drivers/gpio/pch_gpio.c @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2010 OKI SEMICONDUCTOR Co., LTD. + * + * 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 of the License. + * + * 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 +#include +#include + +#define PCH_GPIO_ALL_PINS 0xfff /* Mask for GPIO pins 0 to 11 */ +#define GPIO_NUM_PINS 12 /* Specifies number of GPIO PINS GPIO0-GPIO11 */ + +struct pch_regs { + u32 ien; + u32 istatus; + u32 idisp; + u32 iclr; + u32 imask; + u32 imaskclr; + u32 po; + u32 pi; + u32 pm; + u32 im0; + u32 im1; + u32 reserved[4]; + u32 reset; +}; + +/** + * struct pch_gpio_reg_data - The register store data. + * @po_reg: To store contents of PO register. + * @pm_reg: To store contents of PM register. + */ +struct pch_gpio_reg_data { + u32 po_reg; + u32 pm_reg; +}; + +/** + * struct pch_gpio - GPIO private data structure. + * @base: PCI base address of Memory mapped I/O register. + * @reg: Memory mapped PCH GPIO register list. + * @dev: Pointer to device structure. + * @gpio: Data for GPIO infrastructure. + * @pch_gpio_reg: Memory mapped Register data is saved here + * when suspend. + */ +struct pch_gpio { + void __iomem *base; + struct pch_regs __iomem *reg; + struct device *dev; + struct gpio_chip gpio; + struct pch_gpio_reg_data pch_gpio_reg; + struct mutex lock; +}; + +static void pch_gpio_set(struct gpio_chip *gpio, unsigned nr, int val) +{ + u32 reg_val; + struct pch_gpio *chip = container_of(gpio, struct pch_gpio, gpio); + + mutex_lock(&chip->lock); + reg_val = ioread32(&chip->reg->po); + if (val) + reg_val |= (1 << nr); + else + reg_val &= ~(1 << nr); + + iowrite32(reg_val, &chip->reg->po); + mutex_unlock(&chip->lock); +} + +static int pch_gpio_get(struct gpio_chip *gpio, unsigned nr) +{ + struct pch_gpio *chip = container_of(gpio, struct pch_gpio, gpio); + + return ioread32(&chip->reg->pi) & (1 << nr); +} + +static int pch_gpio_direction_output(struct gpio_chip *gpio, unsigned nr, + int val) +{ + struct pch_gpio *chip = container_of(gpio, struct pch_gpio, gpio); + u32 pm; + u32 reg_val; + + mutex_lock(&chip->lock); + pm = ioread32(&chip->reg->pm) & PCH_GPIO_ALL_PINS; + pm |= (1 << nr); + iowrite32(pm, &chip->reg->pm); + + reg_val = ioread32(&chip->reg->po); + if (val) + reg_val |= (1 << nr); + else + reg_val &= ~(1 << nr); + + mutex_unlock(&chip->lock); + + return 0; +} + +static int pch_gpio_direction_input(struct gpio_chip *gpio, unsigned nr) +{ + struct pch_gpio *chip = container_of(gpio, struct pch_gpio, gpio); + u32 pm; + + mutex_lock(&chip->lock); + pm = ioread32(&chip->reg->pm) & PCH_GPIO_ALL_PINS; /*bits 0-11*/ + pm &= ~(1 << nr); + iowrite32(pm, &chip->reg->pm); + mutex_unlock(&chip->lock); + + return 0; +} + +/* + * Save register configuration and disable interrupts. + */ +static void pch_gpio_save_reg_conf(struct pch_gpio *chip) +{ + chip->pch_gpio_reg.po_reg = ioread32(&chip->reg->po); + chip->pch_gpio_reg.pm_reg = ioread32(&chip->reg->pm); +} + +/* + * This function restores the register configuration of the GPIO device. + */ +static void pch_gpio_restore_reg_conf(struct pch_gpio *chip) +{ + /* to store contents of PO register */ + iowrite32(chip->pch_gpio_reg.po_reg, &chip->reg->po); + /* to store contents of PM register */ + iowrite32(chip->pch_gpio_reg.pm_reg, &chip->reg->pm); +} + +static void pch_gpio_setup(struct pch_gpio *chip) +{ + struct gpio_chip *gpio = &chip->gpio; + + gpio->label = dev_name(chip->dev); + gpio->owner = THIS_MODULE; + gpio->direction_input = pch_gpio_direction_input; + gpio->get = pch_gpio_get; + gpio->direction_output = pch_gpio_direction_output; + gpio->set = pch_gpio_set; + gpio->dbg_show = NULL; + gpio->base = -1; + gpio->ngpio = GPIO_NUM_PINS; + gpio->can_sleep = 0; +} + +static int __devinit pch_gpio_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + s32 ret; + struct pch_gpio *chip; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + + chip->dev = &pdev->dev; + ret = pci_enable_device(pdev); + if (ret) { + dev_err(&pdev->dev, "%s : pci_enable_device FAILED", __func__); + goto err_pci_enable; + } + + ret = pci_request_regions(pdev, KBUILD_MODNAME); + if (ret) { + dev_err(&pdev->dev, "pci_request_regions FAILED-%d", ret); + goto err_request_regions; + } + + chip->base = pci_iomap(pdev, 1, 0); + if (chip->base == 0) { + dev_err(&pdev->dev, "%s : pci_iomap FAILED", __func__); + ret = -ENOMEM; + goto err_iomap; + } + + chip->reg = chip->base; + pci_set_drvdata(pdev, chip); + mutex_init(&chip->lock); + pch_gpio_setup(chip); + ret = gpiochip_add(&chip->gpio); + if (ret) { + dev_err(&pdev->dev, "PCH gpio: Failed to register GPIO\n"); + goto err_gpiochip_add; + } + + return 0; + +err_gpiochip_add: + pci_iounmap(pdev, chip->base); + +err_iomap: + pci_release_regions(pdev); + +err_request_regions: + pci_disable_device(pdev); + +err_pci_enable: + kfree(chip); + dev_err(&pdev->dev, "%s Failed returns %d\n", __func__, ret); + return ret; +} + +static void __devexit pch_gpio_remove(struct pci_dev *pdev) +{ + int err; + struct pch_gpio *chip = pci_get_drvdata(pdev); + + err = gpiochip_remove(&chip->gpio); + if (err) + dev_err(&pdev->dev, "Failed gpiochip_remove\n"); + + pci_iounmap(pdev, chip->base); + pci_release_regions(pdev); + pci_disable_device(pdev); + kfree(chip); +} + +#ifdef CONFIG_PM +static int pch_gpio_suspend(struct pci_dev *pdev, pm_message_t state) +{ + s32 ret; + struct pch_gpio *chip = pci_get_drvdata(pdev); + + pch_gpio_save_reg_conf(chip); + pch_gpio_restore_reg_conf(chip); + + ret = pci_save_state(pdev); + if (ret) { + dev_err(&pdev->dev, "pci_save_state Failed-%d\n", ret); + return ret; + } + pci_disable_device(pdev); + pci_set_power_state(pdev, PCI_D0); + ret = pci_enable_wake(pdev, PCI_D0, 1); + if (ret) + dev_err(&pdev->dev, "pci_enable_wake Failed -%d\n", ret); + + return 0; +} + +static int pch_gpio_resume(struct pci_dev *pdev) +{ + s32 ret; + struct pch_gpio *chip = pci_get_drvdata(pdev); + + ret = pci_enable_wake(pdev, PCI_D0, 0); + + pci_set_power_state(pdev, PCI_D0); + ret = pci_enable_device(pdev); + if (ret) { + dev_err(&pdev->dev, "pci_enable_device Failed-%d ", ret); + return ret; + } + pci_restore_state(pdev); + + iowrite32(0x01, &chip->reg->reset); + iowrite32(0x00, &chip->reg->reset); + pch_gpio_restore_reg_conf(chip); + + return 0; +} +#else +#define pch_gpio_suspend NULL +#define pch_gpio_resume NULL +#endif + +static DEFINE_PCI_DEVICE_TABLE(pch_gpio_pcidev_id) = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x8803) }, + { 0, } +}; + +static struct pci_driver pch_gpio_driver = { + .name = "pch_gpio", + .id_table = pch_gpio_pcidev_id, + .probe = pch_gpio_probe, + .remove = __devexit_p(pch_gpio_remove), + .suspend = pch_gpio_suspend, + .resume = pch_gpio_resume +}; + +static int __init pch_gpio_pci_init(void) +{ + return pci_register_driver(&pch_gpio_driver); +} +module_init(pch_gpio_pci_init); + +static void __exit pch_gpio_pci_exit(void) +{ + pci_unregister_driver(&pch_gpio_driver); +} +module_exit(pch_gpio_pci_exit); + +MODULE_DESCRIPTION("PCH GPIO PCI Driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-55-g7522 From 99ea2626a75e13ce926af69d96e9ae5bfb62b7ba Mon Sep 17 00:00:00 2001 From: Daniel Drake Date: Thu, 30 Sep 2010 21:55:48 +0100 Subject: gpio: Add VIA VX855 GPIO driver This is needed for supporting the upcoming VX855 camera and OLPC DCON drivers, as well as the advanced viafb features on non-OLPC hardware based on this chip. Based on earlier work by Harald Welte. Signed-off-by: Daniel Drake Signed-off-by: Samuel Ortiz --- drivers/gpio/Kconfig | 12 ++ drivers/gpio/Makefile | 1 + drivers/gpio/vx855_gpio.c | 332 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 345 insertions(+) create mode 100644 drivers/gpio/vx855_gpio.c (limited to 'drivers/gpio/Makefile') diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index dd9b4ba8d32d..3143ac795eb0 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -116,6 +116,18 @@ config GPIO_SCH This driver can also be built as a module. If so, the module will be called sch-gpio. +config GPIO_VX855 + tristate "VIA VX855/VX875 GPIO" + depends on GPIOLIB + select MFD_CORE + select MFD_VX855 + help + Support access to the VX855/VX875 GPIO lines through the gpio library. + + This driver provides common support for accessing the device, + additional drivers must be enabled in order to use the + functionality of the device. + comment "I2C GPIO expanders:" config GPIO_MAX7300 diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index da2ecde5abdd..bdf3ddec0652 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -40,3 +40,4 @@ obj-$(CONFIG_GPIO_SCH) += sch_gpio.o obj-$(CONFIG_GPIO_RDC321X) += rdc321x-gpio.o obj-$(CONFIG_GPIO_JANZ_TTL) += janz-ttl.o obj-$(CONFIG_GPIO_SX150X) += sx150x.o +obj-$(CONFIG_GPIO_VX855) += vx855_gpio.o diff --git a/drivers/gpio/vx855_gpio.c b/drivers/gpio/vx855_gpio.c new file mode 100644 index 000000000000..8a98ee5d5f6c --- /dev/null +++ b/drivers/gpio/vx855_gpio.c @@ -0,0 +1,332 @@ +/* + * Linux GPIOlib driver for the VIA VX855 integrated southbridge GPIO + * + * Copyright (C) 2009 VIA Technologies, Inc. + * Copyright (C) 2010 One Laptop per Child + * Author: Harald Welte + * All rights reserved. + * + * 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 +#include +#include +#include +#include +#include +#include + +#define MODULE_NAME "vx855_gpio" + +/* The VX855 south bridge has the following GPIO pins: + * GPI 0...13 General Purpose Input + * GPO 0...12 General Purpose Output + * GPIO 0...14 General Purpose I/O (Open-Drain) + */ + +#define NR_VX855_GPI 14 +#define NR_VX855_GPO 13 +#define NR_VX855_GPIO 15 + +#define NR_VX855_GPInO (NR_VX855_GPI + NR_VX855_GPO) +#define NR_VX855_GP (NR_VX855_GPI + NR_VX855_GPO + NR_VX855_GPIO) + +struct vx855_gpio { + struct gpio_chip gpio; + spinlock_t lock; + u32 io_gpi; + u32 io_gpo; + bool gpi_reserved; + bool gpo_reserved; +}; + +/* resolve a GPIx into the corresponding bit position */ +static inline u_int32_t gpi_i_bit(int i) +{ + if (i < 10) + return 1 << i; + else + return 1 << (i + 14); +} + +static inline u_int32_t gpo_o_bit(int i) +{ + if (i < 11) + return 1 << i; + else + return 1 << (i + 14); +} + +static inline u_int32_t gpio_i_bit(int i) +{ + if (i < 14) + return 1 << (i + 10); + else + return 1 << (i + 14); +} + +static inline u_int32_t gpio_o_bit(int i) +{ + if (i < 14) + return 1 << (i + 11); + else + return 1 << (i + 13); +} + +/* Mapping betwee numeric GPIO ID and the actual GPIO hardware numbering: + * 0..13 GPI 0..13 + * 14..26 GPO 0..12 + * 27..41 GPIO 0..14 + */ + +static int vx855gpio_direction_input(struct gpio_chip *gpio, + unsigned int nr) +{ + struct vx855_gpio *vg = container_of(gpio, struct vx855_gpio, gpio); + unsigned long flags; + u_int32_t reg_out; + + /* Real GPI bits are always in input direction */ + if (nr < NR_VX855_GPI) + return 0; + + /* Real GPO bits cannot be put in output direction */ + if (nr < NR_VX855_GPInO) + return -EINVAL; + + /* Open Drain GPIO have to be set to one */ + spin_lock_irqsave(&vg->lock, flags); + reg_out = inl(vg->io_gpo); + reg_out |= gpio_o_bit(nr - NR_VX855_GPInO); + outl(reg_out, vg->io_gpo); + spin_unlock_irqrestore(&vg->lock, flags); + + return 0; +} + +static int vx855gpio_get(struct gpio_chip *gpio, unsigned int nr) +{ + struct vx855_gpio *vg = container_of(gpio, struct vx855_gpio, gpio); + u_int32_t reg_in; + int ret = 0; + + if (nr < NR_VX855_GPI) { + reg_in = inl(vg->io_gpi); + if (reg_in & gpi_i_bit(nr)) + ret = 1; + } else if (nr < NR_VX855_GPInO) { + /* GPO don't have an input bit, we need to read it + * back from the output register */ + reg_in = inl(vg->io_gpo); + if (reg_in & gpo_o_bit(nr - NR_VX855_GPI)) + ret = 1; + } else { + reg_in = inl(vg->io_gpi); + if (reg_in & gpio_i_bit(nr - NR_VX855_GPInO)) + ret = 1; + } + + return ret; +} + +static void vx855gpio_set(struct gpio_chip *gpio, unsigned int nr, + int val) +{ + struct vx855_gpio *vg = container_of(gpio, struct vx855_gpio, gpio); + unsigned long flags; + u_int32_t reg_out; + + /* True GPI cannot be switched to output mode */ + if (nr < NR_VX855_GPI) + return; + + spin_lock_irqsave(&vg->lock, flags); + reg_out = inl(vg->io_gpo); + if (nr < NR_VX855_GPInO) { + if (val) + reg_out |= gpo_o_bit(nr - NR_VX855_GPI); + else + reg_out &= ~gpo_o_bit(nr - NR_VX855_GPI); + } else { + if (val) + reg_out |= gpio_o_bit(nr - NR_VX855_GPInO); + else + reg_out &= ~gpio_o_bit(nr - NR_VX855_GPInO); + } + outl(reg_out, vg->io_gpo); + spin_unlock_irqrestore(&vg->lock, flags); +} + +static int vx855gpio_direction_output(struct gpio_chip *gpio, + unsigned int nr, int val) +{ + /* True GPI cannot be switched to output mode */ + if (nr < NR_VX855_GPI) + return -EINVAL; + + /* True GPO don't need to be switched to output mode, + * and GPIO are open-drain, i.e. also need no switching, + * so all we do is set the level */ + vx855gpio_set(gpio, nr, val); + + return 0; +} + +static const char *vx855gpio_names[NR_VX855_GP] = { + "VX855_GPI0", "VX855_GPI1", "VX855_GPI2", "VX855_GPI3", "VX855_GPI4", + "VX855_GPI5", "VX855_GPI6", "VX855_GPI7", "VX855_GPI8", "VX855_GPI9", + "VX855_GPI10", "VX855_GPI11", "VX855_GPI12", "VX855_GPI13", + "VX855_GPO0", "VX855_GPO1", "VX855_GPO2", "VX855_GPO3", "VX855_GPO4", + "VX855_GPO5", "VX855_GPO6", "VX855_GPO7", "VX855_GPO8", "VX855_GPO9", + "VX855_GPO10", "VX855_GPO11", "VX855_GPO12", + "VX855_GPIO0", "VX855_GPIO1", "VX855_GPIO2", "VX855_GPIO3", + "VX855_GPIO4", "VX855_GPIO5", "VX855_GPIO6", "VX855_GPIO7", + "VX855_GPIO8", "VX855_GPIO9", "VX855_GPIO10", "VX855_GPIO11", + "VX855_GPIO12", "VX855_GPIO13", "VX855_GPIO14" +}; + +static void vx855gpio_gpio_setup(struct vx855_gpio *vg) +{ + struct gpio_chip *c = &vg->gpio; + + c->label = "VX855 South Bridge"; + c->owner = THIS_MODULE; + c->direction_input = vx855gpio_direction_input; + c->direction_output = vx855gpio_direction_output; + c->get = vx855gpio_get; + c->set = vx855gpio_set; + c->dbg_show = NULL; + c->base = 0; + c->ngpio = NR_VX855_GP; + c->can_sleep = 0; + c->names = vx855gpio_names; +} + +/* This platform device is ordinarily registered by the vx855 mfd driver */ +static __devinit int vx855gpio_probe(struct platform_device *pdev) +{ + struct resource *res_gpi; + struct resource *res_gpo; + struct vx855_gpio *vg; + int ret; + + res_gpi = platform_get_resource(pdev, IORESOURCE_IO, 0); + res_gpo = platform_get_resource(pdev, IORESOURCE_IO, 1); + if (!res_gpi || !res_gpo) + return -EBUSY; + + vg = kzalloc(sizeof(*vg), GFP_KERNEL); + if (!vg) + return -ENOMEM; + + platform_set_drvdata(pdev, vg); + + dev_info(&pdev->dev, "found VX855 GPIO controller\n"); + vg->io_gpi = res_gpi->start; + vg->io_gpo = res_gpo->start; + spin_lock_init(&vg->lock); + + /* + * A single byte is used to control various GPIO ports on the VX855, + * and in the case of the OLPC XO-1.5, some of those ports are used + * for switches that are interpreted and exposed through ACPI. ACPI + * will have reserved the region, so our own reservation will not + * succeed. Ignore and continue. + */ + + if (!request_region(res_gpi->start, resource_size(res_gpi), + MODULE_NAME "_gpi")) + dev_warn(&pdev->dev, + "GPI I/O resource busy, probably claimed by ACPI\n"); + else + vg->gpi_reserved = true; + + if (!request_region(res_gpo->start, resource_size(res_gpo), + MODULE_NAME "_gpo")) + dev_warn(&pdev->dev, + "GPO I/O resource busy, probably claimed by ACPI\n"); + else + vg->gpo_reserved = true; + + vx855gpio_gpio_setup(vg); + + ret = gpiochip_add(&vg->gpio); + if (ret) { + dev_err(&pdev->dev, "failed to register GPIOs\n"); + goto out_release; + } + + return 0; + +out_release: + if (vg->gpi_reserved) + release_region(res_gpi->start, resource_size(res_gpi)); + if (vg->gpo_reserved) + release_region(res_gpi->start, resource_size(res_gpo)); + platform_set_drvdata(pdev, NULL); + kfree(vg); + return ret; +} + +static int __devexit vx855gpio_remove(struct platform_device *pdev) +{ + struct vx855_gpio *vg = platform_get_drvdata(pdev); + struct resource *res; + + if (gpiochip_remove(&vg->gpio)) + dev_err(&pdev->dev, "unable to remove gpio_chip?\n"); + + if (vg->gpi_reserved) { + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + release_region(res->start, resource_size(res)); + } + if (vg->gpo_reserved) { + res = platform_get_resource(pdev, IORESOURCE_IO, 1); + release_region(res->start, resource_size(res)); + } + + platform_set_drvdata(pdev, NULL); + kfree(vg); + return 0; +} + +static struct platform_driver vx855gpio_driver = { + .driver = { + .name = MODULE_NAME, + .owner = THIS_MODULE, + }, + .probe = vx855gpio_probe, + .remove = __devexit_p(vx855gpio_remove), +}; + +static int vx855gpio_init(void) +{ + return platform_driver_register(&vx855gpio_driver); +} +module_init(vx855gpio_init); + +static void vx855gpio_exit(void) +{ + platform_driver_unregister(&vx855gpio_driver); +} +module_exit(vx855gpio_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Harald Welte "); +MODULE_DESCRIPTION("GPIO driver for the VIA VX855 chipset"); +MODULE_ALIAS("platform:vx855_gpio"); -- cgit v1.2.3-55-g7522