diff options
Diffstat (limited to 'drivers/gpio')
62 files changed, 28833 insertions, 0 deletions
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig new file mode 100644 index 00000000..e03653d6 --- /dev/null +++ b/drivers/gpio/Kconfig @@ -0,0 +1,517 @@ +# +# GPIO infrastructure and drivers +# + +config ARCH_WANT_OPTIONAL_GPIOLIB + bool + help + Select this config option from the architecture Kconfig, if + it is possible to use gpiolib on the architecture, but let the + user decide whether to actually build it or not. + Select this instead of ARCH_REQUIRE_GPIOLIB, if your architecture does + not depend on GPIOs being available, but rather let the user + decide whether he needs it or not. + +config ARCH_REQUIRE_GPIOLIB + bool + select GPIOLIB + help + Platforms select gpiolib if they use this infrastructure + for all their GPIOs, usually starting with ones integrated + into SOC processors. + Selecting this from the architecture code will cause the gpiolib + code to always get built in. + + + +menuconfig GPIOLIB + bool "GPIO Support" + depends on ARCH_WANT_OPTIONAL_GPIOLIB || ARCH_REQUIRE_GPIOLIB + select GENERIC_GPIO + help + This enables GPIO support through the generic GPIO library. + You only need to enable this, if you also want to enable + one or more of the GPIO drivers below. + + If unsure, say N. + +if GPIOLIB + +config DEBUG_GPIO + bool "Debug GPIO calls" + depends on DEBUG_KERNEL + help + Say Y here to add some extra checks and diagnostics to GPIO calls. + These checks help ensure that GPIOs have been properly initialized + before they are used, and that sleeping calls are not made from + non-sleeping contexts. They can make bitbanged serial protocols + slower. The diagnostics help catch the type of setup errors + that are most common when setting up new platforms or boards. + +config GPIO_SYSFS + bool "/sys/class/gpio/... (sysfs interface)" + depends on SYSFS && EXPERIMENTAL + help + Say Y here to add a sysfs interface for GPIOs. + + This is mostly useful to work around omissions in a system's + kernel support. Those are common in custom and semicustom + hardware assembled using standard kernels with a minimum of + custom patches. In those cases, userspace code may import + a given GPIO from the kernel, if no kernel driver requested it. + + Kernel drivers may also request that a particular GPIO be + exported to userspace; this can be useful when debugging. + +config GPIO_GENERIC + tristate + +# put drivers in the right section, in alphabetical order + +config GPIO_DA9052 + tristate "Dialog DA9052 GPIO" + depends on PMIC_DA9052 && BROKEN + help + Say yes here to enable the GPIO driver for the DA9052 chip. + +config GPIO_MAX730X + tristate + +comment "Memory mapped GPIO drivers:" + +config GPIO_GENERIC_PLATFORM + tristate "Generic memory-mapped GPIO controller support (MMIO platform device)" + select GPIO_GENERIC + help + Say yes here to support basic platform_device memory-mapped GPIO controllers. + +config GPIO_IT8761E + tristate "IT8761E GPIO support" + depends on X86 # unconditional access to IO space. + help + Say yes here to support GPIO functionality of IT8761E super I/O chip. + +config GPIO_EP93XX + def_bool y + depends on ARCH_EP93XX + select GPIO_GENERIC + +config GPIO_MPC5200 + def_bool y + depends on PPC_MPC52xx + +config GPIO_MPC8XXX + bool "MPC512x/MPC8xxx GPIO support" + depends on PPC_MPC512x || PPC_MPC831x || PPC_MPC834x || PPC_MPC837x || \ + FSL_SOC_BOOKE || PPC_86xx + help + Say Y here if you're going to use hardware that connects to the + MPC512x/831x/834x/837x/8572/8610 GPIOs. + +config GPIO_MSM_V1 + tristate "Qualcomm MSM GPIO v1" + depends on GPIOLIB && ARCH_MSM + help + Say yes here to support the GPIO interface on ARM v6 based + Qualcomm MSM chips. Most of the pins on the MSM can be + selected for GPIO, and are controlled by this driver. + +config GPIO_MSM_V2 + tristate "Qualcomm MSM GPIO v2" + depends on GPIOLIB && ARCH_MSM + help + Say yes here to support the GPIO interface on ARM v7 based + Qualcomm MSM chips. Most of the pins on the MSM can be + selected for GPIO, and are controlled by this driver. + +config GPIO_MXC + def_bool y + depends on ARCH_MXC + select GPIO_GENERIC + select GENERIC_IRQ_CHIP + +config GPIO_MXS + def_bool y + depends on ARCH_MXS + select GPIO_GENERIC + select GENERIC_IRQ_CHIP + +config GPIO_PL061 + bool "PrimeCell PL061 GPIO support" + depends on ARM_AMBA + select GENERIC_IRQ_CHIP + help + Say yes here to support the PrimeCell PL061 GPIO device + +config GPIO_PXA + bool "PXA GPIO support" + depends on ARCH_PXA || ARCH_MMP + help + Say yes here to support the PXA GPIO device + +config GPIO_XILINX + bool "Xilinx GPIO support" + depends on PPC_OF || MICROBLAZE + help + Say yes here to support the Xilinx FPGA GPIO device + +config GPIO_VR41XX + tristate "NEC VR4100 series General-purpose I/O Uint support" + depends on CPU_VR41XX + help + Say yes here to support the NEC VR4100 series General-purpose I/O Uint + +config GPIO_SCH + tristate "Intel SCH/TunnelCreek GPIO" + depends on PCI && X86 + select MFD_CORE + select LPC_SCH + help + Say yes here to support GPIO interface on Intel Poulsbo SCH + or Intel Tunnel Creek processor. + The Intel SCH contains a total of 14 GPIO pins. Ten GPIOs are + powered by the core power rail and are turned off during sleep + modes (S3 and higher). The remaining four GPIOs are powered by + the Intel SCH suspend power supply. These GPIOs remain + active during S3. The suspend powered GPIOs can be used to wake the + system from the Suspend-to-RAM state. + The Intel Tunnel Creek processor has 5 GPIOs powered by the + core power rail and 9 from suspend power supply. + +config GPIO_VX855 + tristate "VIA VX855/VX875 GPIO" + depends on PCI + 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. + +config GPIO_GE_FPGA + bool "GE FPGA based GPIO" + depends on GE_FPGA + help + Support for common GPIO functionality provided on some GE Single Board + Computers. + + This driver provides basic support (configure as input or output, read + and write pin state) for GPIO implemented in a number of GE single + board computers. + +comment "I2C GPIO expanders:" + +config GPIO_MAX7300 + tristate "Maxim MAX7300 GPIO expander" + depends on I2C + select GPIO_MAX730X + help + GPIO driver for Maxim MAX7301 I2C-based GPIO expander. + +config GPIO_MAX732X + tristate "MAX7319, MAX7320-7327 I2C Port Expanders" + depends on I2C + help + Say yes here to support the MAX7319, MAX7320-7327 series of I2C + Port Expanders. Each IO port on these chips has a fixed role of + Input (designated by 'I'), Push-Pull Output ('O'), or Open-Drain + Input and Output (designed by 'P'). The combinations are listed + below: + + 8 bits: max7319 (8I), max7320 (8O), max7321 (8P), + max7322 (4I4O), max7323 (4P4O) + + 16 bits: max7324 (8I8O), max7325 (8P8O), + max7326 (4I12O), max7327 (4P12O) + + Board setup code must specify the model to use, and the start + number for these GPIOs. + +config GPIO_MAX732X_IRQ + bool "Interrupt controller support for MAX732x" + depends on GPIO_MAX732X=y && GENERIC_HARDIRQS + help + Say yes here to enable the max732x to be used as an interrupt + controller. It requires the driver to be built in the kernel. + +config GPIO_MC9S08DZ60 + bool "MX35 3DS BOARD MC9S08DZ60 GPIO functions" + depends on I2C && MACH_MX35_3DS + help + Select this to enable the MC9S08DZ60 GPIO driver + +config GPIO_PCA953X + tristate "PCA953x, PCA955x, TCA64xx, and MAX7310 I/O ports" + depends on I2C + help + Say yes here to provide access to several register-oriented + SMBus I/O expanders, made mostly by NXP or TI. Compatible + models include: + + 4 bits: pca9536, pca9537 + + 8 bits: max7310, pca9534, pca9538, pca9554, pca9557, + tca6408 + + 16 bits: pca9535, pca9539, pca9555, tca6416 + +config GPIO_PCA953X_IRQ + bool "Interrupt controller support for PCA953x" + depends on GPIO_PCA953X=y + help + Say yes here to enable the pca953x to be used as an interrupt + controller. It requires the driver to be built in the kernel. + +config GPIO_PCF857X + tristate "PCF857x, PCA{85,96}7x, and MAX732[89] I2C GPIO expanders" + depends on I2C + help + Say yes here to provide access to most "quasi-bidirectional" I2C + GPIO expanders used for additional digital outputs or inputs. + Most of these parts are from NXP, though TI is a second source for + some of them. Compatible models include: + + 8 bits: pcf8574, pcf8574a, pca8574, pca8574a, + pca9670, pca9672, pca9674, pca9674a, + max7328, max7329 + + 16 bits: pcf8575, pcf8575c, pca8575, + pca9671, pca9673, pca9675 + + Your board setup code will need to declare the expanders in + use, and assign numbers to the GPIOs they expose. Those GPIOs + can then be used from drivers and other kernel code, just like + other GPIOs, but only accessible from task contexts. + + This driver provides an in-kernel interface to those GPIOs using + platform-neutral GPIO calls. + +config GPIO_SX150X + bool "Semtech SX150x I2C GPIO expander" + depends on I2C=y + default n + help + Say yes here to provide support for Semtech SX150-series I2C + GPIO expanders. Compatible models include: + + 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_TC3589X + bool "TC3589X GPIOs" + depends on MFD_TC3589X + help + This enables support for the GPIOs found on the TC3589X + I/O Expander. + +config GPIO_TPS65912 + tristate "TI TPS65912 GPIO" + depends on (MFD_TPS65912_I2C || MFD_TPS65912_SPI) + help + This driver supports TPS65912 gpio chip + +config GPIO_TWL4030 + tristate "TWL4030, TWL5030, and TPS659x0 GPIOs" + depends on TWL4030_CORE + help + Say yes here to access the GPIO signals of various multi-function + power management chips from Texas Instruments. + +config GPIO_WM831X + tristate "WM831x GPIOs" + depends on MFD_WM831X + help + Say yes here to access the GPIO signals of WM831x power management + chips from Wolfson Microelectronics. + +config GPIO_WM8350 + tristate "WM8350 GPIOs" + depends on MFD_WM8350 + help + Say yes here to access the GPIO signals of WM8350 power management + chips from Wolfson Microelectronics. + +config GPIO_WM8994 + tristate "WM8994 GPIOs" + depends on MFD_WM8994 + help + Say yes here to access the GPIO signals of WM8994 audio hub + CODECs from Wolfson Microelectronics. + +config GPIO_ADP5520 + tristate "GPIO Support for ADP5520 PMIC" + depends on PMIC_ADP5520 + help + This option enables support for on-chip GPIO found + on Analog Devices ADP5520 PMICs. + +config GPIO_ADP5588 + tristate "ADP5588 I2C GPIO expander" + depends on I2C + help + This option enables support for 18 GPIOs found + on Analog Devices ADP5588 GPIO Expanders. + +config GPIO_ADP5588_IRQ + bool "Interrupt controller support for ADP5588" + depends on GPIO_ADP5588=y + help + Say yes here to enable the adp5588 to be used as an interrupt + controller. It requires the driver to be built in the kernel. + +comment "PCI GPIO expanders:" + +config GPIO_CS5535 + tristate "AMD CS5535/CS5536 GPIO support" + depends on PCI && X86 && MFD_CS5535 + help + The AMD CS5535 and CS5536 southbridges support 28 GPIO pins that + can be used for quite a number of things. The CS5535/6 is found on + AMD Geode and Lemote Yeeloong devices. + + If unsure, say N. + +config GPIO_BT8XX + tristate "BT8XX GPIO abuser" + depends on PCI && VIDEO_BT848=n + help + The BT8xx frame grabber chip has 24 GPIO pins than can be abused + as a cheap PCI GPIO card. + + This chip can be found on Miro, Hauppauge and STB TV-cards. + + The card needs to be physically altered for using it as a + GPIO card. For more information on how to build a GPIO card + from a BT8xx TV card, see the documentation file at + Documentation/bt8xxgpio.txt + + If unsure, say N. + +config GPIO_LANGWELL + bool "Intel Langwell/Penwell GPIO support" + depends on PCI && X86 + help + Say Y here to support Intel Langwell/Penwell GPIO. + +config GPIO_PCH + tristate "Intel EG20T PCH/LAPIS Semiconductor IOH(ML7223/ML7831) GPIO" + depends on PCI && X86 + select GENERIC_IRQ_CHIP + 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. + + This driver also can be used for LAPIS Semiconductor IOH(Input/ + Output Hub), ML7223 and ML7831. + ML7223 IOH is for MP(Media Phone) use. + ML7831 IOH is for general purpose use. + ML7223/ML7831 is companion chip for Intel Atom E6xx series. + ML7223/ML7831 is completely compatible for Intel EG20T PCH. + +config GPIO_ML_IOH + tristate "OKI SEMICONDUCTOR ML7213 IOH GPIO support" + depends on PCI + select GENERIC_IRQ_CHIP + help + ML7213 is companion chip for Intel Atom E6xx series. + This driver can be used for OKI SEMICONDUCTOR ML7213 IOH(Input/Output + Hub) which is for IVI(In-Vehicle Infotainment) use. + This driver can access the IOH's GPIO device. + +config GPIO_SODAVILLE + bool "Intel Sodaville GPIO support" + depends on X86 && PCI && OF + select GPIO_GENERIC + select GENERIC_IRQ_CHIP + help + Say Y here to support Intel Sodaville GPIO. + +config GPIO_TIMBERDALE + bool "Support for timberdale GPIO IP" + depends on MFD_TIMBERDALE && HAS_IOMEM + ---help--- + Add support for the GPIO IP in the timberdale FPGA. + +config GPIO_RDC321X + tristate "RDC R-321x GPIO support" + depends on PCI + select MFD_CORE + select MFD_RDC321X + help + Support for the RDC R321x SoC GPIOs over southbridge + PCI configuration space. + +comment "SPI GPIO expanders:" + +config GPIO_MAX7301 + tristate "Maxim MAX7301 GPIO expander" + depends on SPI_MASTER + select GPIO_MAX730X + help + GPIO driver for Maxim MAX7301 SPI-based GPIO expander. + +config GPIO_MCP23S08 + tristate "Microchip MCP23xxx I/O expander" + depends on SPI_MASTER || I2C + help + SPI/I2C driver for Microchip MCP23S08/MCP23S17/MCP23008/MCP23017 + I/O expanders. + This provides a GPIO interface supporting inputs and outputs. + +config GPIO_MC33880 + tristate "Freescale MC33880 high-side/low-side switch" + depends on SPI_MASTER + help + 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 + bool "Philips UCB1400 GPIO" + depends on UCB1400_CORE + help + This enables support for the Philips UCB1400 GPIO pins. + The UCB1400 is an AC97 audio codec. + +comment "MODULbus GPIO expanders:" + +config GPIO_JANZ_TTL + tristate "Janz VMOD-TTL Digital IO Module" + depends on MFD_JANZ_CMODIO + help + This enables support for the Janz VMOD-TTL Digital IO module. + This driver provides support for driving the pins in output + mode only. Input mode is not supported. + +config GPIO_AB8500 + bool "ST-Ericsson AB8500 Mixed Signal Circuit gpio functions" + depends on AB8500_CORE && BROKEN + help + Select this to enable the AB8500 IC GPIO driver + +config GPIO_TPS65910 + bool "TPS65910 GPIO" + depends on MFD_TPS65910 + help + Select this option to enable GPIO driver for the TPS65910 + chip family. +endif diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile new file mode 100644 index 00000000..007f54bd --- /dev/null +++ b/drivers/gpio/Makefile @@ -0,0 +1,66 @@ +# generic gpio support: platform drivers, dedicated expander chips, etc + +ccflags-$(CONFIG_DEBUG_GPIO) += -DDEBUG + +obj-$(CONFIG_GPIOLIB) += gpiolib.o devres.o + +# Device drivers. Generally keep list sorted alphabetically +obj-$(CONFIG_GPIO_GENERIC) += gpio-generic.o + +obj-$(CONFIG_GPIO_74X164) += gpio-74x164.o +obj-$(CONFIG_GPIO_AB8500) += gpio-ab8500.o +obj-$(CONFIG_GPIO_ADP5520) += gpio-adp5520.o +obj-$(CONFIG_GPIO_ADP5588) += gpio-adp5588.o +obj-$(CONFIG_GPIO_BT8XX) += gpio-bt8xx.o +obj-$(CONFIG_GPIO_CS5535) += gpio-cs5535.o +obj-$(CONFIG_GPIO_DA9052) += gpio-da9052.o +obj-$(CONFIG_ARCH_DAVINCI) += gpio-davinci.o +obj-$(CONFIG_GPIO_EP93XX) += gpio-ep93xx.o +obj-$(CONFIG_GPIO_GE_FPGA) += gpio-ge.o +obj-$(CONFIG_GPIO_IT8761E) += gpio-it8761e.o +obj-$(CONFIG_GPIO_JANZ_TTL) += gpio-janz-ttl.o +obj-$(CONFIG_ARCH_KS8695) += gpio-ks8695.o +obj-$(CONFIG_GPIO_LANGWELL) += gpio-langwell.o +obj-$(CONFIG_ARCH_LPC32XX) += gpio-lpc32xx.o +obj-$(CONFIG_GPIO_MAX730X) += gpio-max730x.o +obj-$(CONFIG_GPIO_MAX7300) += gpio-max7300.o +obj-$(CONFIG_GPIO_MAX7301) += gpio-max7301.o +obj-$(CONFIG_GPIO_MAX732X) += gpio-max732x.o +obj-$(CONFIG_GPIO_MC33880) += gpio-mc33880.o +obj-$(CONFIG_GPIO_MC9S08DZ60) += gpio-mc9s08dz60.o +obj-$(CONFIG_GPIO_MCP23S08) += gpio-mcp23s08.o +obj-$(CONFIG_GPIO_ML_IOH) += gpio-ml-ioh.o +obj-$(CONFIG_GPIO_MPC5200) += gpio-mpc5200.o +obj-$(CONFIG_GPIO_MPC8XXX) += gpio-mpc8xxx.o +obj-$(CONFIG_GPIO_MSM_V1) += gpio-msm-v1.o +obj-$(CONFIG_GPIO_MSM_V2) += gpio-msm-v2.o +obj-$(CONFIG_GPIO_MXC) += gpio-mxc.o +obj-$(CONFIG_GPIO_MXS) += gpio-mxs.o +obj-$(CONFIG_PLAT_NOMADIK) += gpio-nomadik.o +obj-$(CONFIG_ARCH_OMAP) += gpio-omap.o +obj-$(CONFIG_GPIO_PCA953X) += gpio-pca953x.o +obj-$(CONFIG_GPIO_PCF857X) += gpio-pcf857x.o +obj-$(CONFIG_GPIO_PCH) += gpio-pch.o +obj-$(CONFIG_GPIO_PL061) += gpio-pl061.o +obj-$(CONFIG_GPIO_PXA) += gpio-pxa.o +obj-$(CONFIG_GPIO_RDC321X) += gpio-rdc321x.o +obj-$(CONFIG_PLAT_SAMSUNG) += gpio-samsung.o +obj-$(CONFIG_ARCH_SA1100) += gpio-sa1100.o +obj-$(CONFIG_GPIO_SCH) += gpio-sch.o +obj-$(CONFIG_GPIO_SODAVILLE) += gpio-sodaville.o +obj-$(CONFIG_GPIO_STMPE) += gpio-stmpe.o +obj-$(CONFIG_GPIO_SX150X) += gpio-sx150x.o +obj-$(CONFIG_GPIO_TC3589X) += gpio-tc3589x.o +obj-$(CONFIG_ARCH_TEGRA) += gpio-tegra.o +obj-$(CONFIG_GPIO_TIMBERDALE) += gpio-timberdale.o +obj-$(CONFIG_ARCH_DAVINCI_TNETV107X) += gpio-tnetv107x.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_UCB1400) += gpio-ucb1400.o +obj-$(CONFIG_GPIO_VR41XX) += gpio-vr41xx.o +obj-$(CONFIG_GPIO_VX855) += gpio-vx855.o +obj-$(CONFIG_GPIO_WM831X) += gpio-wm831x.o +obj-$(CONFIG_GPIO_WM8350) += gpio-wm8350.o +obj-$(CONFIG_GPIO_WM8994) += gpio-wm8994.o +obj-$(CONFIG_GPIO_XILINX) += gpio-xilinx.o diff --git a/drivers/gpio/devres.c b/drivers/gpio/devres.c new file mode 100644 index 00000000..3dd29399 --- /dev/null +++ b/drivers/gpio/devres.c @@ -0,0 +1,90 @@ +/* + * drivers/gpio/devres.c - managed gpio resources + * + * 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. + * + * 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 + * + * This file is based on kernel/irq/devres.c + * + * Copyright (c) 2011 John Crispin <blogic@openwrt.org> + */ + +#include <linux/module.h> +#include <linux/gpio.h> +#include <linux/device.h> +#include <linux/gfp.h> + +static void devm_gpio_release(struct device *dev, void *res) +{ + unsigned *gpio = res; + + gpio_free(*gpio); +} + +static int devm_gpio_match(struct device *dev, void *res, void *data) +{ + unsigned *this = res, *gpio = data; + + return *this == *gpio; +} + +/** + * devm_gpio_request - request a gpio for a managed device + * @dev: device to request the gpio for + * @gpio: gpio to allocate + * @label: the name of the requested gpio + * + * Except for the extra @dev argument, this function takes the + * same arguments and performs the same function as + * gpio_request(). GPIOs requested with this function will be + * automatically freed on driver detach. + * + * If an GPIO allocated with this function needs to be freed + * separately, devm_gpio_free() must be used. + */ + +int devm_gpio_request(struct device *dev, unsigned gpio, const char *label) +{ + unsigned *dr; + int rc; + + dr = devres_alloc(devm_gpio_release, sizeof(unsigned), GFP_KERNEL); + if (!dr) + return -ENOMEM; + + rc = gpio_request(gpio, label); + if (rc) { + devres_free(dr); + return rc; + } + + *dr = gpio; + devres_add(dev, dr); + + return 0; +} +EXPORT_SYMBOL(devm_gpio_request); + +/** + * devm_gpio_free - free an interrupt + * @dev: device to free gpio for + * @gpio: gpio to free + * + * Except for the extra @dev argument, this function takes the + * same arguments and performs the same function as gpio_free(). + * This function instead of gpio_free() should be used to manually + * free GPIOs allocated with devm_gpio_request(). + */ +void devm_gpio_free(struct device *dev, unsigned int gpio) +{ + + WARN_ON(devres_destroy(dev, devm_gpio_release, devm_gpio_match, + &gpio)); + gpio_free(gpio); +} +EXPORT_SYMBOL(devm_gpio_free); diff --git a/drivers/gpio/gpio-74x164.c b/drivers/gpio/gpio-74x164.c new file mode 100644 index 00000000..a31ad6f5 --- /dev/null +++ b/drivers/gpio/gpio-74x164.c @@ -0,0 +1,178 @@ +/* + * 74Hx164 - Generic serial-in/parallel-out 8-bits shift register GPIO driver + * + * Copyright (C) 2010 Gabor Juhos <juhosg@openwrt.org> + * Copyright (C) 2010 Miguel Gaio <miguel.gaio@efixo.com> + * + * 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 <linux/init.h> +#include <linux/mutex.h> +#include <linux/spi/spi.h> +#include <linux/spi/74x164.h> +#include <linux/gpio.h> +#include <linux/slab.h> +#include <linux/module.h> + +struct gen_74x164_chip { + struct spi_device *spi; + struct gpio_chip gpio_chip; + struct mutex lock; + u8 port_config; +}; + +static struct gen_74x164_chip *gpio_to_74x164_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_get_value(struct gpio_chip *gc, unsigned offset) +{ + struct gen_74x164_chip *chip = gpio_to_74x164_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_74x164_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 gen_74x164_direction_output(struct gpio_chip *gc, + unsigned offset, int val) +{ + gen_74x164_set_value(gc, offset, val); + return 0; +} + +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 = spi->modalias; + 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 = 8; + 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 __devexit 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 = "74x164", + .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 <juhosg@openwrt.org>"); +MODULE_AUTHOR("Miguel Gaio <miguel.gaio@efixo.com>"); +MODULE_DESCRIPTION("GPIO expander driver for 74X164 8-bits shift register"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpio/gpio-ab8500.c b/drivers/gpio/gpio-ab8500.c new file mode 100644 index 00000000..050c05d9 --- /dev/null +++ b/drivers/gpio/gpio-ab8500.c @@ -0,0 +1,520 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: BIBEK BASU <bibek.basu@stericsson.com> + * License terms: GNU General Public License (GPL) version 2 + * + * 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 <linux/kernel.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/mfd/ab8500.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/ab8500/gpio.h> + +/* + * GPIO registers offset + * Bank: 0x10 + */ +#define AB8500_GPIO_SEL1_REG 0x00 +#define AB8500_GPIO_SEL2_REG 0x01 +#define AB8500_GPIO_SEL3_REG 0x02 +#define AB8500_GPIO_SEL4_REG 0x03 +#define AB8500_GPIO_SEL5_REG 0x04 +#define AB8500_GPIO_SEL6_REG 0x05 + +#define AB8500_GPIO_DIR1_REG 0x10 +#define AB8500_GPIO_DIR2_REG 0x11 +#define AB8500_GPIO_DIR3_REG 0x12 +#define AB8500_GPIO_DIR4_REG 0x13 +#define AB8500_GPIO_DIR5_REG 0x14 +#define AB8500_GPIO_DIR6_REG 0x15 + +#define AB8500_GPIO_OUT1_REG 0x20 +#define AB8500_GPIO_OUT2_REG 0x21 +#define AB8500_GPIO_OUT3_REG 0x22 +#define AB8500_GPIO_OUT4_REG 0x23 +#define AB8500_GPIO_OUT5_REG 0x24 +#define AB8500_GPIO_OUT6_REG 0x25 + +#define AB8500_GPIO_PUD1_REG 0x30 +#define AB8500_GPIO_PUD2_REG 0x31 +#define AB8500_GPIO_PUD3_REG 0x32 +#define AB8500_GPIO_PUD4_REG 0x33 +#define AB8500_GPIO_PUD5_REG 0x34 +#define AB8500_GPIO_PUD6_REG 0x35 + +#define AB8500_GPIO_IN1_REG 0x40 +#define AB8500_GPIO_IN2_REG 0x41 +#define AB8500_GPIO_IN3_REG 0x42 +#define AB8500_GPIO_IN4_REG 0x43 +#define AB8500_GPIO_IN5_REG 0x44 +#define AB8500_GPIO_IN6_REG 0x45 +#define AB8500_GPIO_ALTFUN_REG 0x45 +#define ALTFUN_REG_INDEX 6 +#define AB8500_NUM_GPIO 42 +#define AB8500_NUM_VIR_GPIO_IRQ 16 + +enum ab8500_gpio_action { + NONE, + STARTUP, + SHUTDOWN, + MASK, + UNMASK +}; + +struct ab8500_gpio { + struct gpio_chip chip; + struct ab8500 *parent; + struct device *dev; + struct mutex lock; + u32 irq_base; + enum ab8500_gpio_action irq_action; + u16 rising; + u16 falling; +}; +/** + * to_ab8500_gpio() - get the pointer to ab8500_gpio + * @chip: Member of the structure ab8500_gpio + */ +static inline struct ab8500_gpio *to_ab8500_gpio(struct gpio_chip *chip) +{ + return container_of(chip, struct ab8500_gpio, chip); +} + +static int ab8500_gpio_set_bits(struct gpio_chip *chip, u8 reg, + unsigned offset, int val) +{ + struct ab8500_gpio *ab8500_gpio = to_ab8500_gpio(chip); + u8 pos = offset % 8; + int ret; + + reg = reg + (offset / 8); + ret = abx500_mask_and_set_register_interruptible(ab8500_gpio->dev, + AB8500_MISC, reg, 1 << pos, val << pos); + if (ret < 0) + dev_err(ab8500_gpio->dev, "%s write failed\n", __func__); + return ret; +} +/** + * ab8500_gpio_get() - Get the particular GPIO value + * @chip: Gpio device + * @offset: GPIO number to read + */ +static int ab8500_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct ab8500_gpio *ab8500_gpio = to_ab8500_gpio(chip); + u8 mask = 1 << (offset % 8); + u8 reg = AB8500_GPIO_OUT1_REG + (offset / 8); + int ret; + u8 data; + ret = abx500_get_register_interruptible(ab8500_gpio->dev, AB8500_MISC, + reg, &data); + if (ret < 0) { + dev_err(ab8500_gpio->dev, "%s read failed\n", __func__); + return ret; + } + return (data & mask) >> (offset % 8); +} + +static void ab8500_gpio_set(struct gpio_chip *chip, unsigned offset, int val) +{ + struct ab8500_gpio *ab8500_gpio = to_ab8500_gpio(chip); + int ret; + /* Write the data */ + ret = ab8500_gpio_set_bits(chip, AB8500_GPIO_OUT1_REG, offset, 1); + if (ret < 0) + dev_err(ab8500_gpio->dev, "%s write failed\n", __func__); +} + +static int ab8500_gpio_direction_output(struct gpio_chip *chip, unsigned offset, + int val) +{ + int ret; + /* set direction as output */ + ret = ab8500_gpio_set_bits(chip, AB8500_GPIO_DIR1_REG, offset, 1); + if (ret < 0) + return ret; + /* disable pull down */ + ret = ab8500_gpio_set_bits(chip, AB8500_GPIO_PUD1_REG, offset, 1); + if (ret < 0) + return ret; + /* set the output as 1 or 0 */ + return ab8500_gpio_set_bits(chip, AB8500_GPIO_OUT1_REG, offset, val); + +} + +static int ab8500_gpio_direction_input(struct gpio_chip *chip, unsigned offset) +{ + /* set the register as input */ + return ab8500_gpio_set_bits(chip, AB8500_GPIO_DIR1_REG, offset, 0); +} + +static int ab8500_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + /* + * Only some GPIOs are interrupt capable, and they are + * organized in discontiguous clusters: + * + * GPIO6 to GPIO13 + * GPIO24 and GPIO25 + * GPIO36 to GPIO41 + */ + static struct ab8500_gpio_irq_cluster { + int start; + int end; + } clusters[] = { + {.start = 6, .end = 13}, + {.start = 24, .end = 25}, + {.start = 36, .end = 41}, + }; + struct ab8500_gpio *ab8500_gpio = to_ab8500_gpio(chip); + int base = ab8500_gpio->irq_base; + int i; + + for (i = 0; i < ARRAY_SIZE(clusters); i++) { + struct ab8500_gpio_irq_cluster *cluster = &clusters[i]; + + if (offset >= cluster->start && offset <= cluster->end) + return base + offset - cluster->start; + + /* Advance by the number of gpios in this cluster */ + base += cluster->end - cluster->start + 1; + } + + return -EINVAL; +} + +static struct gpio_chip ab8500gpio_chip = { + .label = "ab8500_gpio", + .owner = THIS_MODULE, + .direction_input = ab8500_gpio_direction_input, + .get = ab8500_gpio_get, + .direction_output = ab8500_gpio_direction_output, + .set = ab8500_gpio_set, + .to_irq = ab8500_gpio_to_irq, +}; + +static unsigned int irq_to_rising(unsigned int irq) +{ + struct ab8500_gpio *ab8500_gpio = get_irq_chip_data(irq); + int offset = irq - ab8500_gpio->irq_base; + int new_irq = offset + AB8500_INT_GPIO6R + + ab8500_gpio->parent->irq_base; + return new_irq; +} + +static unsigned int irq_to_falling(unsigned int irq) +{ + struct ab8500_gpio *ab8500_gpio = get_irq_chip_data(irq); + int offset = irq - ab8500_gpio->irq_base; + int new_irq = offset + AB8500_INT_GPIO6F + + ab8500_gpio->parent->irq_base; + return new_irq; + +} + +static unsigned int rising_to_irq(unsigned int irq, void *dev) +{ + struct ab8500_gpio *ab8500_gpio = dev; + int offset = irq - AB8500_INT_GPIO6R + - ab8500_gpio->parent->irq_base ; + int new_irq = offset + ab8500_gpio->irq_base; + return new_irq; +} + +static unsigned int falling_to_irq(unsigned int irq, void *dev) +{ + struct ab8500_gpio *ab8500_gpio = dev; + int offset = irq - AB8500_INT_GPIO6F + - ab8500_gpio->parent->irq_base ; + int new_irq = offset + ab8500_gpio->irq_base; + return new_irq; + +} + +/* + * IRQ handler + */ + +static irqreturn_t handle_rising(int irq, void *dev) +{ + + handle_nested_irq(rising_to_irq(irq , dev)); + return IRQ_HANDLED; +} + +static irqreturn_t handle_falling(int irq, void *dev) +{ + + handle_nested_irq(falling_to_irq(irq, dev)); + return IRQ_HANDLED; +} + +static void ab8500_gpio_irq_lock(unsigned int irq) +{ + struct ab8500_gpio *ab8500_gpio = get_irq_chip_data(irq); + mutex_lock(&ab8500_gpio->lock); +} + +static void ab8500_gpio_irq_sync_unlock(unsigned int irq) +{ + struct ab8500_gpio *ab8500_gpio = get_irq_chip_data(irq); + int offset = irq - ab8500_gpio->irq_base; + bool rising = ab8500_gpio->rising & BIT(offset); + bool falling = ab8500_gpio->falling & BIT(offset); + int ret; + + switch (ab8500_gpio->irq_action) { + case STARTUP: + if (rising) + ret = request_threaded_irq(irq_to_rising(irq), + NULL, handle_rising, + IRQF_TRIGGER_RISING, + "ab8500-gpio-r", ab8500_gpio); + if (falling) + ret = request_threaded_irq(irq_to_falling(irq), + NULL, handle_falling, + IRQF_TRIGGER_FALLING, + "ab8500-gpio-f", ab8500_gpio); + break; + case SHUTDOWN: + if (rising) + free_irq(irq_to_rising(irq), ab8500_gpio); + if (falling) + free_irq(irq_to_falling(irq), ab8500_gpio); + break; + case MASK: + if (rising) + disable_irq(irq_to_rising(irq)); + if (falling) + disable_irq(irq_to_falling(irq)); + break; + case UNMASK: + if (rising) + enable_irq(irq_to_rising(irq)); + if (falling) + enable_irq(irq_to_falling(irq)); + break; + case NONE: + break; + } + ab8500_gpio->irq_action = NONE; + ab8500_gpio->rising &= ~(BIT(offset)); + ab8500_gpio->falling &= ~(BIT(offset)); + mutex_unlock(&ab8500_gpio->lock); +} + + +static void ab8500_gpio_irq_mask(unsigned int irq) +{ + struct ab8500_gpio *ab8500_gpio = get_irq_chip_data(irq); + ab8500_gpio->irq_action = MASK; +} + +static void ab8500_gpio_irq_unmask(unsigned int irq) +{ + struct ab8500_gpio *ab8500_gpio = get_irq_chip_data(irq); + ab8500_gpio->irq_action = UNMASK; +} + +static int ab8500_gpio_irq_set_type(unsigned int irq, unsigned int type) +{ + struct ab8500_gpio *ab8500_gpio = get_irq_chip_data(irq); + int offset = irq - ab8500_gpio->irq_base; + + if (type == IRQ_TYPE_EDGE_BOTH) { + ab8500_gpio->rising = BIT(offset); + ab8500_gpio->falling = BIT(offset); + } else if (type == IRQ_TYPE_EDGE_RISING) { + ab8500_gpio->rising = BIT(offset); + } else { + ab8500_gpio->falling = BIT(offset); + } + return 0; +} + +unsigned int ab8500_gpio_irq_startup(unsigned int irq) +{ + struct ab8500_gpio *ab8500_gpio = get_irq_chip_data(irq); + ab8500_gpio->irq_action = STARTUP; + return 0; +} + +void ab8500_gpio_irq_shutdown(unsigned int irq) +{ + struct ab8500_gpio *ab8500_gpio = get_irq_chip_data(irq); + ab8500_gpio->irq_action = SHUTDOWN; +} + +static struct irq_chip ab8500_gpio_irq_chip = { + .name = "ab8500-gpio", + .startup = ab8500_gpio_irq_startup, + .shutdown = ab8500_gpio_irq_shutdown, + .bus_lock = ab8500_gpio_irq_lock, + .bus_sync_unlock = ab8500_gpio_irq_sync_unlock, + .mask = ab8500_gpio_irq_mask, + .unmask = ab8500_gpio_irq_unmask, + .set_type = ab8500_gpio_irq_set_type, +}; + +static int ab8500_gpio_irq_init(struct ab8500_gpio *ab8500_gpio) +{ + u32 base = ab8500_gpio->irq_base; + int irq; + + for (irq = base; irq < base + AB8500_NUM_VIR_GPIO_IRQ ; irq++) { + set_irq_chip_data(irq, ab8500_gpio); + set_irq_chip_and_handler(irq, &ab8500_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 ab8500_gpio_irq_remove(struct ab8500_gpio *ab8500_gpio) +{ + int base = ab8500_gpio->irq_base; + int irq; + + for (irq = base; irq < base + AB8500_NUM_VIR_GPIO_IRQ; 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 ab8500_gpio_probe(struct platform_device *pdev) +{ + struct ab8500_platform_data *ab8500_pdata = + dev_get_platdata(pdev->dev.parent); + struct ab8500_gpio_platform_data *pdata; + struct ab8500_gpio *ab8500_gpio; + int ret; + int i; + + pdata = ab8500_pdata->gpio; + if (!pdata) { + dev_err(&pdev->dev, "gpio platform data missing\n"); + return -ENODEV; + } + + ab8500_gpio = kzalloc(sizeof(struct ab8500_gpio), GFP_KERNEL); + if (ab8500_gpio == NULL) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + ab8500_gpio->dev = &pdev->dev; + ab8500_gpio->parent = dev_get_drvdata(pdev->dev.parent); + ab8500_gpio->chip = ab8500gpio_chip; + ab8500_gpio->chip.ngpio = AB8500_NUM_GPIO; + ab8500_gpio->chip.dev = &pdev->dev; + ab8500_gpio->chip.base = pdata->gpio_base; + ab8500_gpio->irq_base = pdata->irq_base; + /* initialize the lock */ + mutex_init(&ab8500_gpio->lock); + /* + * AB8500 core will handle and clear the IRQ + * configre GPIO based on config-reg value. + * These values are for selecting the PINs as + * GPIO or alternate function + */ + for (i = AB8500_GPIO_SEL1_REG; i <= AB8500_GPIO_SEL6_REG; i++) { + ret = abx500_set_register_interruptible(ab8500_gpio->dev, + AB8500_MISC, i, + pdata->config_reg[i]); + if (ret < 0) + goto out_free; + } + ret = abx500_set_register_interruptible(ab8500_gpio->dev, AB8500_MISC, + AB8500_GPIO_ALTFUN_REG, + pdata->config_reg[ALTFUN_REG_INDEX]); + if (ret < 0) + goto out_free; + + ret = ab8500_gpio_irq_init(ab8500_gpio); + if (ret) + goto out_free; + ret = gpiochip_add(&ab8500_gpio->chip); + if (ret) { + dev_err(&pdev->dev, "unable to add gpiochip: %d\n", + ret); + goto out_rem_irq; + } + platform_set_drvdata(pdev, ab8500_gpio); + return 0; + +out_rem_irq: + ab8500_gpio_irq_remove(ab8500_gpio); +out_free: + mutex_destroy(&ab8500_gpio->lock); + kfree(ab8500_gpio); + return ret; +} + +/* + * ab8500_gpio_remove() - remove Ab8500-gpio driver + * @pdev : Platform device registered + */ +static int __devexit ab8500_gpio_remove(struct platform_device *pdev) +{ + struct ab8500_gpio *ab8500_gpio = platform_get_drvdata(pdev); + int ret; + + ret = gpiochip_remove(&ab8500_gpio->chip); + if (ret < 0) { + dev_err(ab8500_gpio->dev, "unable to remove gpiochip: %d\n", + ret); + return ret; + } + + platform_set_drvdata(pdev, NULL); + mutex_destroy(&ab8500_gpio->lock); + kfree(ab8500_gpio); + + return 0; +} + +static struct platform_driver ab8500_gpio_driver = { + .driver = { + .name = "ab8500-gpio", + .owner = THIS_MODULE, + }, + .probe = ab8500_gpio_probe, + .remove = __devexit_p(ab8500_gpio_remove), +}; + +static int __init ab8500_gpio_init(void) +{ + return platform_driver_register(&ab8500_gpio_driver); +} +arch_initcall(ab8500_gpio_init); + +static void __exit ab8500_gpio_exit(void) +{ + platform_driver_unregister(&ab8500_gpio_driver); +} +module_exit(ab8500_gpio_exit); + +MODULE_AUTHOR("BIBEK BASU <bibek.basu@stericsson.com>"); +MODULE_DESCRIPTION("Driver allows to use AB8500 unused pins to be used as GPIO"); +MODULE_ALIAS("platform:ab8500-gpio"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpio/gpio-adp5520.c b/drivers/gpio/gpio-adp5520.c new file mode 100644 index 00000000..2f263cc3 --- /dev/null +++ b/drivers/gpio/gpio-adp5520.c @@ -0,0 +1,201 @@ +/* + * GPIO driver for Analog Devices ADP5520 MFD PMICs + * + * Copyright 2009 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/mfd/adp5520.h> + +#include <linux/gpio.h> + +struct adp5520_gpio { + struct device *master; + struct gpio_chip gpio_chip; + unsigned char lut[ADP5520_MAXGPIOS]; + unsigned long output; +}; + +static int adp5520_gpio_get_value(struct gpio_chip *chip, unsigned off) +{ + struct adp5520_gpio *dev; + uint8_t reg_val; + + dev = container_of(chip, struct adp5520_gpio, gpio_chip); + + /* + * There are dedicated registers for GPIO IN/OUT. + * Make sure we return the right value, even when configured as output + */ + + if (test_bit(off, &dev->output)) + adp5520_read(dev->master, ADP5520_GPIO_OUT, ®_val); + else + adp5520_read(dev->master, ADP5520_GPIO_IN, ®_val); + + return !!(reg_val & dev->lut[off]); +} + +static void adp5520_gpio_set_value(struct gpio_chip *chip, + unsigned off, int val) +{ + struct adp5520_gpio *dev; + dev = container_of(chip, struct adp5520_gpio, gpio_chip); + + if (val) + adp5520_set_bits(dev->master, ADP5520_GPIO_OUT, dev->lut[off]); + else + adp5520_clr_bits(dev->master, ADP5520_GPIO_OUT, dev->lut[off]); +} + +static int adp5520_gpio_direction_input(struct gpio_chip *chip, unsigned off) +{ + struct adp5520_gpio *dev; + dev = container_of(chip, struct adp5520_gpio, gpio_chip); + + clear_bit(off, &dev->output); + + return adp5520_clr_bits(dev->master, ADP5520_GPIO_CFG_2, + dev->lut[off]); +} + +static int adp5520_gpio_direction_output(struct gpio_chip *chip, + unsigned off, int val) +{ + struct adp5520_gpio *dev; + int ret = 0; + dev = container_of(chip, struct adp5520_gpio, gpio_chip); + + set_bit(off, &dev->output); + + if (val) + ret |= adp5520_set_bits(dev->master, ADP5520_GPIO_OUT, + dev->lut[off]); + else + ret |= adp5520_clr_bits(dev->master, ADP5520_GPIO_OUT, + dev->lut[off]); + + ret |= adp5520_set_bits(dev->master, ADP5520_GPIO_CFG_2, + dev->lut[off]); + + return ret; +} + +static int __devinit adp5520_gpio_probe(struct platform_device *pdev) +{ + struct adp5520_gpio_platform_data *pdata = pdev->dev.platform_data; + struct adp5520_gpio *dev; + struct gpio_chip *gc; + int ret, i, gpios; + unsigned char ctl_mask = 0; + + if (pdata == NULL) { + dev_err(&pdev->dev, "missing platform data\n"); + return -ENODEV; + } + + if (pdev->id != ID_ADP5520) { + dev_err(&pdev->dev, "only ADP5520 supports GPIO\n"); + return -ENODEV; + } + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (dev == NULL) { + dev_err(&pdev->dev, "failed to alloc memory\n"); + return -ENOMEM; + } + + dev->master = pdev->dev.parent; + + for (gpios = 0, i = 0; i < ADP5520_MAXGPIOS; i++) + if (pdata->gpio_en_mask & (1 << i)) + dev->lut[gpios++] = 1 << i; + + if (gpios < 1) { + ret = -EINVAL; + goto err; + } + + gc = &dev->gpio_chip; + gc->direction_input = adp5520_gpio_direction_input; + gc->direction_output = adp5520_gpio_direction_output; + gc->get = adp5520_gpio_get_value; + gc->set = adp5520_gpio_set_value; + gc->can_sleep = 1; + + gc->base = pdata->gpio_start; + gc->ngpio = gpios; + gc->label = pdev->name; + gc->owner = THIS_MODULE; + + ret = adp5520_clr_bits(dev->master, ADP5520_GPIO_CFG_1, + pdata->gpio_en_mask); + + if (pdata->gpio_en_mask & ADP5520_GPIO_C3) + ctl_mask |= ADP5520_C3_MODE; + + if (pdata->gpio_en_mask & ADP5520_GPIO_R3) + ctl_mask |= ADP5520_R3_MODE; + + if (ctl_mask) + ret = adp5520_set_bits(dev->master, ADP5520_LED_CONTROL, + ctl_mask); + + ret |= adp5520_set_bits(dev->master, ADP5520_GPIO_PULLUP, + pdata->gpio_pullup_mask); + + if (ret) { + dev_err(&pdev->dev, "failed to write\n"); + goto err; + } + + ret = gpiochip_add(&dev->gpio_chip); + if (ret) + goto err; + + platform_set_drvdata(pdev, dev); + return 0; + +err: + kfree(dev); + return ret; +} + +static int __devexit adp5520_gpio_remove(struct platform_device *pdev) +{ + struct adp5520_gpio *dev; + int ret; + + dev = platform_get_drvdata(pdev); + ret = gpiochip_remove(&dev->gpio_chip); + if (ret) { + dev_err(&pdev->dev, "%s failed, %d\n", + "gpiochip_remove()", ret); + return ret; + } + + kfree(dev); + return 0; +} + +static struct platform_driver adp5520_gpio_driver = { + .driver = { + .name = "adp5520-gpio", + .owner = THIS_MODULE, + }, + .probe = adp5520_gpio_probe, + .remove = __devexit_p(adp5520_gpio_remove), +}; + +module_platform_driver(adp5520_gpio_driver); + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("GPIO ADP5520 Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:adp5520-gpio"); diff --git a/drivers/gpio/gpio-adp5588.c b/drivers/gpio/gpio-adp5588.c new file mode 100644 index 00000000..ae5d7f12 --- /dev/null +++ b/drivers/gpio/gpio-adp5588.c @@ -0,0 +1,502 @@ +/* + * GPIO Chip driver for Analog Devices + * ADP5588/ADP5587 I/O Expander and QWERTY Keypad Controller + * + * Copyright 2009-2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/irq.h> + +#include <linux/i2c/adp5588.h> + +#define DRV_NAME "adp5588-gpio" + +/* + * Early pre 4.0 Silicon required to delay readout by at least 25ms, + * since the Event Counter Register updated 25ms after the interrupt + * asserted. + */ +#define WA_DELAYED_READOUT_REVID(rev) ((rev) < 4) + +struct adp5588_gpio { + struct i2c_client *client; + struct gpio_chip gpio_chip; + struct mutex lock; /* protect cached dir, dat_out */ + /* protect serialized access to the interrupt controller bus */ + struct mutex irq_lock; + unsigned gpio_start; + unsigned irq_base; + uint8_t dat_out[3]; + uint8_t dir[3]; + uint8_t int_lvl[3]; + uint8_t int_en[3]; + uint8_t irq_mask[3]; + uint8_t irq_stat[3]; +}; + +static int adp5588_gpio_read(struct i2c_client *client, u8 reg) +{ + int ret = i2c_smbus_read_byte_data(client, reg); + + if (ret < 0) + dev_err(&client->dev, "Read Error\n"); + + return ret; +} + +static int adp5588_gpio_write(struct i2c_client *client, u8 reg, u8 val) +{ + int ret = i2c_smbus_write_byte_data(client, reg, val); + + if (ret < 0) + dev_err(&client->dev, "Write Error\n"); + + return ret; +} + +static int adp5588_gpio_get_value(struct gpio_chip *chip, unsigned off) +{ + struct adp5588_gpio *dev = + container_of(chip, struct adp5588_gpio, gpio_chip); + + return !!(adp5588_gpio_read(dev->client, + GPIO_DAT_STAT1 + ADP5588_BANK(off)) & ADP5588_BIT(off)); +} + +static void adp5588_gpio_set_value(struct gpio_chip *chip, + unsigned off, int val) +{ + unsigned bank, bit; + struct adp5588_gpio *dev = + container_of(chip, struct adp5588_gpio, gpio_chip); + + bank = ADP5588_BANK(off); + bit = ADP5588_BIT(off); + + mutex_lock(&dev->lock); + if (val) + dev->dat_out[bank] |= bit; + else + dev->dat_out[bank] &= ~bit; + + adp5588_gpio_write(dev->client, GPIO_DAT_OUT1 + bank, + dev->dat_out[bank]); + mutex_unlock(&dev->lock); +} + +static int adp5588_gpio_direction_input(struct gpio_chip *chip, unsigned off) +{ + int ret; + unsigned bank; + struct adp5588_gpio *dev = + container_of(chip, struct adp5588_gpio, gpio_chip); + + bank = ADP5588_BANK(off); + + mutex_lock(&dev->lock); + dev->dir[bank] &= ~ADP5588_BIT(off); + ret = adp5588_gpio_write(dev->client, GPIO_DIR1 + bank, dev->dir[bank]); + mutex_unlock(&dev->lock); + + return ret; +} + +static int adp5588_gpio_direction_output(struct gpio_chip *chip, + unsigned off, int val) +{ + int ret; + unsigned bank, bit; + struct adp5588_gpio *dev = + container_of(chip, struct adp5588_gpio, gpio_chip); + + bank = ADP5588_BANK(off); + bit = ADP5588_BIT(off); + + mutex_lock(&dev->lock); + dev->dir[bank] |= bit; + + if (val) + dev->dat_out[bank] |= bit; + else + dev->dat_out[bank] &= ~bit; + + ret = adp5588_gpio_write(dev->client, GPIO_DAT_OUT1 + bank, + dev->dat_out[bank]); + ret |= adp5588_gpio_write(dev->client, GPIO_DIR1 + bank, + dev->dir[bank]); + mutex_unlock(&dev->lock); + + return ret; +} + +#ifdef CONFIG_GPIO_ADP5588_IRQ +static int adp5588_gpio_to_irq(struct gpio_chip *chip, unsigned off) +{ + struct adp5588_gpio *dev = + container_of(chip, struct adp5588_gpio, gpio_chip); + return dev->irq_base + off; +} + +static void adp5588_irq_bus_lock(struct irq_data *d) +{ + struct adp5588_gpio *dev = irq_data_get_irq_chip_data(d); + + mutex_lock(&dev->irq_lock); +} + + /* + * genirq core code can issue chip->mask/unmask from atomic context. + * This doesn't work for slow busses where an access needs to sleep. + * bus_sync_unlock() is therefore called outside the atomic context, + * syncs the current irq mask state with the slow external controller + * and unlocks the bus. + */ + +static void adp5588_irq_bus_sync_unlock(struct irq_data *d) +{ + struct adp5588_gpio *dev = irq_data_get_irq_chip_data(d); + int i; + + for (i = 0; i <= ADP5588_BANK(ADP5588_MAXGPIO); i++) + if (dev->int_en[i] ^ dev->irq_mask[i]) { + dev->int_en[i] = dev->irq_mask[i]; + adp5588_gpio_write(dev->client, GPIO_INT_EN1 + i, + dev->int_en[i]); + } + + mutex_unlock(&dev->irq_lock); +} + +static void adp5588_irq_mask(struct irq_data *d) +{ + struct adp5588_gpio *dev = irq_data_get_irq_chip_data(d); + unsigned gpio = d->irq - dev->irq_base; + + dev->irq_mask[ADP5588_BANK(gpio)] &= ~ADP5588_BIT(gpio); +} + +static void adp5588_irq_unmask(struct irq_data *d) +{ + struct adp5588_gpio *dev = irq_data_get_irq_chip_data(d); + unsigned gpio = d->irq - dev->irq_base; + + dev->irq_mask[ADP5588_BANK(gpio)] |= ADP5588_BIT(gpio); +} + +static int adp5588_irq_set_type(struct irq_data *d, unsigned int type) +{ + struct adp5588_gpio *dev = irq_data_get_irq_chip_data(d); + uint16_t gpio = d->irq - dev->irq_base; + unsigned bank, bit; + + if ((type & IRQ_TYPE_EDGE_BOTH)) { + dev_err(&dev->client->dev, "irq %d: unsupported type %d\n", + d->irq, type); + return -EINVAL; + } + + bank = ADP5588_BANK(gpio); + bit = ADP5588_BIT(gpio); + + if (type & IRQ_TYPE_LEVEL_HIGH) + dev->int_lvl[bank] |= bit; + else if (type & IRQ_TYPE_LEVEL_LOW) + dev->int_lvl[bank] &= ~bit; + else + return -EINVAL; + + adp5588_gpio_direction_input(&dev->gpio_chip, gpio); + adp5588_gpio_write(dev->client, GPIO_INT_LVL1 + bank, + dev->int_lvl[bank]); + + return 0; +} + +static struct irq_chip adp5588_irq_chip = { + .name = "adp5588", + .irq_mask = adp5588_irq_mask, + .irq_unmask = adp5588_irq_unmask, + .irq_bus_lock = adp5588_irq_bus_lock, + .irq_bus_sync_unlock = adp5588_irq_bus_sync_unlock, + .irq_set_type = adp5588_irq_set_type, +}; + +static int adp5588_gpio_read_intstat(struct i2c_client *client, u8 *buf) +{ + int ret = i2c_smbus_read_i2c_block_data(client, GPIO_INT_STAT1, 3, buf); + + if (ret < 0) + dev_err(&client->dev, "Read INT_STAT Error\n"); + + return ret; +} + +static irqreturn_t adp5588_irq_handler(int irq, void *devid) +{ + struct adp5588_gpio *dev = devid; + unsigned status, bank, bit, pending; + int ret; + status = adp5588_gpio_read(dev->client, INT_STAT); + + if (status & ADP5588_GPI_INT) { + ret = adp5588_gpio_read_intstat(dev->client, dev->irq_stat); + if (ret < 0) + memset(dev->irq_stat, 0, ARRAY_SIZE(dev->irq_stat)); + + for (bank = 0, bit = 0; bank <= ADP5588_BANK(ADP5588_MAXGPIO); + bank++, bit = 0) { + pending = dev->irq_stat[bank] & dev->irq_mask[bank]; + + while (pending) { + if (pending & (1 << bit)) { + handle_nested_irq(dev->irq_base + + (bank << 3) + bit); + pending &= ~(1 << bit); + + } + bit++; + } + } + } + + adp5588_gpio_write(dev->client, INT_STAT, status); /* Status is W1C */ + + return IRQ_HANDLED; +} + +static int adp5588_irq_setup(struct adp5588_gpio *dev) +{ + struct i2c_client *client = dev->client; + struct adp5588_gpio_platform_data *pdata = client->dev.platform_data; + unsigned gpio; + int ret; + + adp5588_gpio_write(client, CFG, ADP5588_AUTO_INC); + adp5588_gpio_write(client, INT_STAT, -1); /* status is W1C */ + adp5588_gpio_read_intstat(client, dev->irq_stat); /* read to clear */ + + dev->irq_base = pdata->irq_base; + mutex_init(&dev->irq_lock); + + for (gpio = 0; gpio < dev->gpio_chip.ngpio; gpio++) { + int irq = gpio + dev->irq_base; + irq_set_chip_data(irq, dev); + irq_set_chip_and_handler(irq, &adp5588_irq_chip, + handle_level_irq); + irq_set_nested_thread(irq, 1); +#ifdef CONFIG_ARM + /* + * ARM needs us to explicitly flag the IRQ as VALID, + * once we do so, it will also set the noprobe. + */ + set_irq_flags(irq, IRQF_VALID); +#else + irq_set_noprobe(irq); +#endif + } + + ret = request_threaded_irq(client->irq, + NULL, + adp5588_irq_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + dev_name(&client->dev), dev); + if (ret) { + dev_err(&client->dev, "failed to request irq %d\n", + client->irq); + goto out; + } + + dev->gpio_chip.to_irq = adp5588_gpio_to_irq; + adp5588_gpio_write(client, CFG, + ADP5588_AUTO_INC | ADP5588_INT_CFG | ADP5588_GPI_INT); + + return 0; + +out: + dev->irq_base = 0; + return ret; +} + +static void adp5588_irq_teardown(struct adp5588_gpio *dev) +{ + if (dev->irq_base) + free_irq(dev->client->irq, dev); +} + +#else +static int adp5588_irq_setup(struct adp5588_gpio *dev) +{ + struct i2c_client *client = dev->client; + dev_warn(&client->dev, "interrupt support not compiled in\n"); + + return 0; +} + +static void adp5588_irq_teardown(struct adp5588_gpio *dev) +{ +} +#endif /* CONFIG_GPIO_ADP5588_IRQ */ + +static int __devinit adp5588_gpio_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct adp5588_gpio_platform_data *pdata = client->dev.platform_data; + struct adp5588_gpio *dev; + struct gpio_chip *gc; + int ret, i, revid; + + if (pdata == NULL) { + dev_err(&client->dev, "missing platform data\n"); + return -ENODEV; + } + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(&client->dev, "SMBUS Byte Data not Supported\n"); + return -EIO; + } + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (dev == NULL) { + dev_err(&client->dev, "failed to alloc memory\n"); + return -ENOMEM; + } + + dev->client = client; + + gc = &dev->gpio_chip; + gc->direction_input = adp5588_gpio_direction_input; + gc->direction_output = adp5588_gpio_direction_output; + gc->get = adp5588_gpio_get_value; + gc->set = adp5588_gpio_set_value; + gc->can_sleep = 1; + + gc->base = pdata->gpio_start; + gc->ngpio = ADP5588_MAXGPIO; + gc->label = client->name; + gc->owner = THIS_MODULE; + + mutex_init(&dev->lock); + + ret = adp5588_gpio_read(dev->client, DEV_ID); + if (ret < 0) + goto err; + + revid = ret & ADP5588_DEVICE_ID_MASK; + + for (i = 0, ret = 0; i <= ADP5588_BANK(ADP5588_MAXGPIO); i++) { + dev->dat_out[i] = adp5588_gpio_read(client, GPIO_DAT_OUT1 + i); + dev->dir[i] = adp5588_gpio_read(client, GPIO_DIR1 + i); + ret |= adp5588_gpio_write(client, KP_GPIO1 + i, 0); + ret |= adp5588_gpio_write(client, GPIO_PULL1 + i, + (pdata->pullup_dis_mask >> (8 * i)) & 0xFF); + ret |= adp5588_gpio_write(client, GPIO_INT_EN1 + i, 0); + if (ret) + goto err; + } + + if (pdata->irq_base) { + if (WA_DELAYED_READOUT_REVID(revid)) { + dev_warn(&client->dev, "GPIO int not supported\n"); + } else { + ret = adp5588_irq_setup(dev); + if (ret) + goto err; + } + } + + ret = gpiochip_add(&dev->gpio_chip); + if (ret) + goto err_irq; + + dev_info(&client->dev, "IRQ Base: %d Rev.: %d\n", + pdata->irq_base, revid); + + if (pdata->setup) { + ret = pdata->setup(client, gc->base, gc->ngpio, pdata->context); + if (ret < 0) + dev_warn(&client->dev, "setup failed, %d\n", ret); + } + + i2c_set_clientdata(client, dev); + + return 0; + +err_irq: + adp5588_irq_teardown(dev); +err: + kfree(dev); + return ret; +} + +static int __devexit adp5588_gpio_remove(struct i2c_client *client) +{ + struct adp5588_gpio_platform_data *pdata = client->dev.platform_data; + struct adp5588_gpio *dev = i2c_get_clientdata(client); + int ret; + + if (pdata->teardown) { + ret = pdata->teardown(client, + dev->gpio_chip.base, dev->gpio_chip.ngpio, + pdata->context); + if (ret < 0) { + dev_err(&client->dev, "teardown failed %d\n", ret); + return ret; + } + } + + if (dev->irq_base) + free_irq(dev->client->irq, dev); + + ret = gpiochip_remove(&dev->gpio_chip); + if (ret) { + dev_err(&client->dev, "gpiochip_remove failed %d\n", ret); + return ret; + } + + kfree(dev); + return 0; +} + +static const struct i2c_device_id adp5588_gpio_id[] = { + {DRV_NAME, 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, adp5588_gpio_id); + +static struct i2c_driver adp5588_gpio_driver = { + .driver = { + .name = DRV_NAME, + }, + .probe = adp5588_gpio_probe, + .remove = __devexit_p(adp5588_gpio_remove), + .id_table = adp5588_gpio_id, +}; + +static int __init adp5588_gpio_init(void) +{ + return i2c_add_driver(&adp5588_gpio_driver); +} + +module_init(adp5588_gpio_init); + +static void __exit adp5588_gpio_exit(void) +{ + i2c_del_driver(&adp5588_gpio_driver); +} + +module_exit(adp5588_gpio_exit); + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("GPIO ADP5588 Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpio/gpio-bt8xx.c b/drivers/gpio/gpio-bt8xx.c new file mode 100644 index 00000000..5ca4098b --- /dev/null +++ b/drivers/gpio/gpio-bt8xx.c @@ -0,0 +1,345 @@ +/* + + bt8xx GPIO abuser + + Copyright (C) 2008 Michael Buesch <m@bues.ch> + + Please do _only_ contact the people listed _above_ with issues related to this driver. + All the other people listed below are not related to this driver. Their names + are only here, because this driver is derived from the bt848 driver. + + + Derived from the bt848 driver: + + Copyright (C) 1996,97,98 Ralph Metzler + & Marcus Metzler + (c) 1999-2002 Gerd Knorr + + some v4l2 code lines are taken from Justin's bttv2 driver which is + (c) 2000 Justin Schoeman + + V4L1 removal from: + (c) 2005-2006 Nickolay V. Shmyrev + + Fixes to be fully V4L2 compliant by + (c) 2006 Mauro Carvalho Chehab + + Cropping and overscan support + Copyright (C) 2005, 2006 Michael H. Schimek + Sponsored by OPQ Systems AB + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/spinlock.h> +#include <linux/gpio.h> +#include <linux/slab.h> + +/* Steal the hardware definitions from the bttv driver. */ +#include "../media/video/bt8xx/bt848.h" + + +#define BT8XXGPIO_NR_GPIOS 24 /* We have 24 GPIO pins */ + + +struct bt8xxgpio { + spinlock_t lock; + + void __iomem *mmio; + struct pci_dev *pdev; + struct gpio_chip gpio; + +#ifdef CONFIG_PM + u32 saved_outen; + u32 saved_data; +#endif +}; + +#define bgwrite(dat, adr) writel((dat), bg->mmio+(adr)) +#define bgread(adr) readl(bg->mmio+(adr)) + + +static int modparam_gpiobase = -1/* dynamic */; +module_param_named(gpiobase, modparam_gpiobase, int, 0444); +MODULE_PARM_DESC(gpiobase, "The GPIO number base. -1 means dynamic, which is the default."); + + +static int bt8xxgpio_gpio_direction_input(struct gpio_chip *gpio, unsigned nr) +{ + struct bt8xxgpio *bg = container_of(gpio, struct bt8xxgpio, gpio); + unsigned long flags; + u32 outen, data; + + spin_lock_irqsave(&bg->lock, flags); + + data = bgread(BT848_GPIO_DATA); + data &= ~(1 << nr); + bgwrite(data, BT848_GPIO_DATA); + + outen = bgread(BT848_GPIO_OUT_EN); + outen &= ~(1 << nr); + bgwrite(outen, BT848_GPIO_OUT_EN); + + spin_unlock_irqrestore(&bg->lock, flags); + + return 0; +} + +static int bt8xxgpio_gpio_get(struct gpio_chip *gpio, unsigned nr) +{ + struct bt8xxgpio *bg = container_of(gpio, struct bt8xxgpio, gpio); + unsigned long flags; + u32 val; + + spin_lock_irqsave(&bg->lock, flags); + val = bgread(BT848_GPIO_DATA); + spin_unlock_irqrestore(&bg->lock, flags); + + return !!(val & (1 << nr)); +} + +static int bt8xxgpio_gpio_direction_output(struct gpio_chip *gpio, + unsigned nr, int val) +{ + struct bt8xxgpio *bg = container_of(gpio, struct bt8xxgpio, gpio); + unsigned long flags; + u32 outen, data; + + spin_lock_irqsave(&bg->lock, flags); + + outen = bgread(BT848_GPIO_OUT_EN); + outen |= (1 << nr); + bgwrite(outen, BT848_GPIO_OUT_EN); + + data = bgread(BT848_GPIO_DATA); + if (val) + data |= (1 << nr); + else + data &= ~(1 << nr); + bgwrite(data, BT848_GPIO_DATA); + + spin_unlock_irqrestore(&bg->lock, flags); + + return 0; +} + +static void bt8xxgpio_gpio_set(struct gpio_chip *gpio, + unsigned nr, int val) +{ + struct bt8xxgpio *bg = container_of(gpio, struct bt8xxgpio, gpio); + unsigned long flags; + u32 data; + + spin_lock_irqsave(&bg->lock, flags); + + data = bgread(BT848_GPIO_DATA); + if (val) + data |= (1 << nr); + else + data &= ~(1 << nr); + bgwrite(data, BT848_GPIO_DATA); + + spin_unlock_irqrestore(&bg->lock, flags); +} + +static void bt8xxgpio_gpio_setup(struct bt8xxgpio *bg) +{ + struct gpio_chip *c = &bg->gpio; + + c->label = dev_name(&bg->pdev->dev); + c->owner = THIS_MODULE; + c->direction_input = bt8xxgpio_gpio_direction_input; + c->get = bt8xxgpio_gpio_get; + c->direction_output = bt8xxgpio_gpio_direction_output; + c->set = bt8xxgpio_gpio_set; + c->dbg_show = NULL; + c->base = modparam_gpiobase; + c->ngpio = BT8XXGPIO_NR_GPIOS; + c->can_sleep = 0; +} + +static int bt8xxgpio_probe(struct pci_dev *dev, + const struct pci_device_id *pci_id) +{ + struct bt8xxgpio *bg; + int err; + + bg = kzalloc(sizeof(*bg), GFP_KERNEL); + if (!bg) + return -ENOMEM; + + bg->pdev = dev; + spin_lock_init(&bg->lock); + + err = pci_enable_device(dev); + if (err) { + printk(KERN_ERR "bt8xxgpio: Can't enable device.\n"); + goto err_freebg; + } + if (!request_mem_region(pci_resource_start(dev, 0), + pci_resource_len(dev, 0), + "bt8xxgpio")) { + printk(KERN_WARNING "bt8xxgpio: Can't request iomem (0x%llx).\n", + (unsigned long long)pci_resource_start(dev, 0)); + err = -EBUSY; + goto err_disable; + } + pci_set_master(dev); + pci_set_drvdata(dev, bg); + + bg->mmio = ioremap(pci_resource_start(dev, 0), 0x1000); + if (!bg->mmio) { + printk(KERN_ERR "bt8xxgpio: ioremap() failed\n"); + err = -EIO; + goto err_release_mem; + } + + /* Disable interrupts */ + bgwrite(0, BT848_INT_MASK); + + /* gpio init */ + bgwrite(0, BT848_GPIO_DMA_CTL); + bgwrite(0, BT848_GPIO_REG_INP); + bgwrite(0, BT848_GPIO_OUT_EN); + + bt8xxgpio_gpio_setup(bg); + err = gpiochip_add(&bg->gpio); + if (err) { + printk(KERN_ERR "bt8xxgpio: Failed to register GPIOs\n"); + goto err_release_mem; + } + + return 0; + +err_release_mem: + release_mem_region(pci_resource_start(dev, 0), + pci_resource_len(dev, 0)); + pci_set_drvdata(dev, NULL); +err_disable: + pci_disable_device(dev); +err_freebg: + kfree(bg); + + return err; +} + +static void bt8xxgpio_remove(struct pci_dev *pdev) +{ + struct bt8xxgpio *bg = pci_get_drvdata(pdev); + + gpiochip_remove(&bg->gpio); + + bgwrite(0, BT848_INT_MASK); + bgwrite(~0x0, BT848_INT_STAT); + bgwrite(0x0, BT848_GPIO_OUT_EN); + + iounmap(bg->mmio); + release_mem_region(pci_resource_start(pdev, 0), + pci_resource_len(pdev, 0)); + pci_disable_device(pdev); + + pci_set_drvdata(pdev, NULL); + kfree(bg); +} + +#ifdef CONFIG_PM +static int bt8xxgpio_suspend(struct pci_dev *pdev, pm_message_t state) +{ + struct bt8xxgpio *bg = pci_get_drvdata(pdev); + unsigned long flags; + + spin_lock_irqsave(&bg->lock, flags); + + bg->saved_outen = bgread(BT848_GPIO_OUT_EN); + bg->saved_data = bgread(BT848_GPIO_DATA); + + bgwrite(0, BT848_INT_MASK); + bgwrite(~0x0, BT848_INT_STAT); + bgwrite(0x0, BT848_GPIO_OUT_EN); + + spin_unlock_irqrestore(&bg->lock, flags); + + pci_save_state(pdev); + pci_disable_device(pdev); + pci_set_power_state(pdev, pci_choose_state(pdev, state)); + + return 0; +} + +static int bt8xxgpio_resume(struct pci_dev *pdev) +{ + struct bt8xxgpio *bg = pci_get_drvdata(pdev); + unsigned long flags; + int err; + + pci_set_power_state(pdev, 0); + err = pci_enable_device(pdev); + if (err) + return err; + pci_restore_state(pdev); + + spin_lock_irqsave(&bg->lock, flags); + + bgwrite(0, BT848_INT_MASK); + bgwrite(0, BT848_GPIO_DMA_CTL); + bgwrite(0, BT848_GPIO_REG_INP); + bgwrite(bg->saved_outen, BT848_GPIO_OUT_EN); + bgwrite(bg->saved_data & bg->saved_outen, + BT848_GPIO_DATA); + + spin_unlock_irqrestore(&bg->lock, flags); + + return 0; +} +#else +#define bt8xxgpio_suspend NULL +#define bt8xxgpio_resume NULL +#endif /* CONFIG_PM */ + +static struct pci_device_id bt8xxgpio_pci_tbl[] = { + { PCI_DEVICE(PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT848) }, + { PCI_DEVICE(PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT849) }, + { PCI_DEVICE(PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT878) }, + { PCI_DEVICE(PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT879) }, + { 0, }, +}; +MODULE_DEVICE_TABLE(pci, bt8xxgpio_pci_tbl); + +static struct pci_driver bt8xxgpio_pci_driver = { + .name = "bt8xxgpio", + .id_table = bt8xxgpio_pci_tbl, + .probe = bt8xxgpio_probe, + .remove = bt8xxgpio_remove, + .suspend = bt8xxgpio_suspend, + .resume = bt8xxgpio_resume, +}; + +static int __init bt8xxgpio_init(void) +{ + return pci_register_driver(&bt8xxgpio_pci_driver); +} +module_init(bt8xxgpio_init) + +static void __exit bt8xxgpio_exit(void) +{ + pci_unregister_driver(&bt8xxgpio_pci_driver); +} +module_exit(bt8xxgpio_exit) + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Michael Buesch"); +MODULE_DESCRIPTION("Abuse a BT8xx framegrabber card as generic GPIO card"); diff --git a/drivers/gpio/gpio-cs5535.c b/drivers/gpio/gpio-cs5535.c new file mode 100644 index 00000000..19eda1bb --- /dev/null +++ b/drivers/gpio/gpio-cs5535.c @@ -0,0 +1,389 @@ +/* + * AMD CS5535/CS5536 GPIO driver + * Copyright (C) 2006 Advanced Micro Devices, Inc. + * Copyright (C) 2007-2009 Andres Salomon <dilinger@collabora.co.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/spinlock.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/io.h> +#include <linux/cs5535.h> +#include <asm/msr.h> + +#define DRV_NAME "cs5535-gpio" + +/* + * Some GPIO pins + * 31-29,23 : reserved (always mask out) + * 28 : Power Button + * 26 : PME# + * 22-16 : LPC + * 14,15 : SMBus + * 9,8 : UART1 + * 7 : PCI INTB + * 3,4 : UART2/DDC + * 2 : IDE_IRQ0 + * 1 : AC_BEEP + * 0 : PCI INTA + * + * If a mask was not specified, allow all except + * reserved and Power Button + */ +#define GPIO_DEFAULT_MASK 0x0F7FFFFF + +static ulong mask = GPIO_DEFAULT_MASK; +module_param_named(mask, mask, ulong, 0444); +MODULE_PARM_DESC(mask, "GPIO channel mask."); + +static struct cs5535_gpio_chip { + struct gpio_chip chip; + resource_size_t base; + + struct platform_device *pdev; + spinlock_t lock; +} cs5535_gpio_chip; + +/* + * The CS5535/CS5536 GPIOs support a number of extra features not defined + * by the gpio_chip API, so these are exported. For a full list of the + * registers, see include/linux/cs5535.h. + */ + +static void errata_outl(struct cs5535_gpio_chip *chip, u32 val, + unsigned int reg) +{ + unsigned long addr = chip->base + 0x80 + reg; + + /* + * According to the CS5536 errata (#36), after suspend + * a write to the high bank GPIO register will clear all + * non-selected bits; the recommended workaround is a + * read-modify-write operation. + * + * Don't apply this errata to the edge status GPIOs, as writing + * to their lower bits will clear them. + */ + if (reg != GPIO_POSITIVE_EDGE_STS && reg != GPIO_NEGATIVE_EDGE_STS) { + if (val & 0xffff) + val |= (inl(addr) & 0xffff); /* ignore the high bits */ + else + val |= (inl(addr) ^ (val >> 16)); + } + outl(val, addr); +} + +static void __cs5535_gpio_set(struct cs5535_gpio_chip *chip, unsigned offset, + unsigned int reg) +{ + if (offset < 16) + /* low bank register */ + outl(1 << offset, chip->base + reg); + else + /* high bank register */ + errata_outl(chip, 1 << (offset - 16), reg); +} + +void cs5535_gpio_set(unsigned offset, unsigned int reg) +{ + struct cs5535_gpio_chip *chip = &cs5535_gpio_chip; + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + __cs5535_gpio_set(chip, offset, reg); + spin_unlock_irqrestore(&chip->lock, flags); +} +EXPORT_SYMBOL_GPL(cs5535_gpio_set); + +static void __cs5535_gpio_clear(struct cs5535_gpio_chip *chip, unsigned offset, + unsigned int reg) +{ + if (offset < 16) + /* low bank register */ + outl(1 << (offset + 16), chip->base + reg); + else + /* high bank register */ + errata_outl(chip, 1 << offset, reg); +} + +void cs5535_gpio_clear(unsigned offset, unsigned int reg) +{ + struct cs5535_gpio_chip *chip = &cs5535_gpio_chip; + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + __cs5535_gpio_clear(chip, offset, reg); + spin_unlock_irqrestore(&chip->lock, flags); +} +EXPORT_SYMBOL_GPL(cs5535_gpio_clear); + +int cs5535_gpio_isset(unsigned offset, unsigned int reg) +{ + struct cs5535_gpio_chip *chip = &cs5535_gpio_chip; + unsigned long flags; + long val; + + spin_lock_irqsave(&chip->lock, flags); + if (offset < 16) + /* low bank register */ + val = inl(chip->base + reg); + else { + /* high bank register */ + val = inl(chip->base + 0x80 + reg); + offset -= 16; + } + spin_unlock_irqrestore(&chip->lock, flags); + + return (val & (1 << offset)) ? 1 : 0; +} +EXPORT_SYMBOL_GPL(cs5535_gpio_isset); + +int cs5535_gpio_set_irq(unsigned group, unsigned irq) +{ + uint32_t lo, hi; + + if (group > 7 || irq > 15) + return -EINVAL; + + rdmsr(MSR_PIC_ZSEL_HIGH, lo, hi); + + lo &= ~(0xF << (group * 4)); + lo |= (irq & 0xF) << (group * 4); + + wrmsr(MSR_PIC_ZSEL_HIGH, lo, hi); + return 0; +} +EXPORT_SYMBOL_GPL(cs5535_gpio_set_irq); + +void cs5535_gpio_setup_event(unsigned offset, int pair, int pme) +{ + struct cs5535_gpio_chip *chip = &cs5535_gpio_chip; + uint32_t shift = (offset % 8) * 4; + unsigned long flags; + uint32_t val; + + if (offset >= 24) + offset = GPIO_MAP_W; + else if (offset >= 16) + offset = GPIO_MAP_Z; + else if (offset >= 8) + offset = GPIO_MAP_Y; + else + offset = GPIO_MAP_X; + + spin_lock_irqsave(&chip->lock, flags); + val = inl(chip->base + offset); + + /* Clear whatever was there before */ + val &= ~(0xF << shift); + + /* Set the new value */ + val |= ((pair & 7) << shift); + + /* Set the PME bit if this is a PME event */ + if (pme) + val |= (1 << (shift + 3)); + + outl(val, chip->base + offset); + spin_unlock_irqrestore(&chip->lock, flags); +} +EXPORT_SYMBOL_GPL(cs5535_gpio_setup_event); + +/* + * Generic gpio_chip API support. + */ + +static int chip_gpio_request(struct gpio_chip *c, unsigned offset) +{ + struct cs5535_gpio_chip *chip = (struct cs5535_gpio_chip *) c; + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + + /* check if this pin is available */ + if ((mask & (1 << offset)) == 0) { + dev_info(&chip->pdev->dev, + "pin %u is not available (check mask)\n", offset); + spin_unlock_irqrestore(&chip->lock, flags); + return -EINVAL; + } + + /* disable output aux 1 & 2 on this pin */ + __cs5535_gpio_clear(chip, offset, GPIO_OUTPUT_AUX1); + __cs5535_gpio_clear(chip, offset, GPIO_OUTPUT_AUX2); + + /* disable input aux 1 on this pin */ + __cs5535_gpio_clear(chip, offset, GPIO_INPUT_AUX1); + + spin_unlock_irqrestore(&chip->lock, flags); + + return 0; +} + +static int chip_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + return cs5535_gpio_isset(offset, GPIO_READ_BACK); +} + +static void chip_gpio_set(struct gpio_chip *chip, unsigned offset, int val) +{ + if (val) + cs5535_gpio_set(offset, GPIO_OUTPUT_VAL); + else + cs5535_gpio_clear(offset, GPIO_OUTPUT_VAL); +} + +static int chip_direction_input(struct gpio_chip *c, unsigned offset) +{ + struct cs5535_gpio_chip *chip = (struct cs5535_gpio_chip *) c; + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + __cs5535_gpio_set(chip, offset, GPIO_INPUT_ENABLE); + __cs5535_gpio_clear(chip, offset, GPIO_OUTPUT_ENABLE); + spin_unlock_irqrestore(&chip->lock, flags); + + return 0; +} + +static int chip_direction_output(struct gpio_chip *c, unsigned offset, int val) +{ + struct cs5535_gpio_chip *chip = (struct cs5535_gpio_chip *) c; + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + + __cs5535_gpio_set(chip, offset, GPIO_INPUT_ENABLE); + __cs5535_gpio_set(chip, offset, GPIO_OUTPUT_ENABLE); + if (val) + __cs5535_gpio_set(chip, offset, GPIO_OUTPUT_VAL); + else + __cs5535_gpio_clear(chip, offset, GPIO_OUTPUT_VAL); + + spin_unlock_irqrestore(&chip->lock, flags); + + return 0; +} + +static const char * const cs5535_gpio_names[] = { + "GPIO0", "GPIO1", "GPIO2", "GPIO3", + "GPIO4", "GPIO5", "GPIO6", "GPIO7", + "GPIO8", "GPIO9", "GPIO10", "GPIO11", + "GPIO12", "GPIO13", "GPIO14", "GPIO15", + "GPIO16", "GPIO17", "GPIO18", "GPIO19", + "GPIO20", "GPIO21", "GPIO22", NULL, + "GPIO24", "GPIO25", "GPIO26", "GPIO27", + "GPIO28", NULL, NULL, NULL, +}; + +static struct cs5535_gpio_chip cs5535_gpio_chip = { + .chip = { + .owner = THIS_MODULE, + .label = DRV_NAME, + + .base = 0, + .ngpio = 32, + .names = cs5535_gpio_names, + .request = chip_gpio_request, + + .get = chip_gpio_get, + .set = chip_gpio_set, + + .direction_input = chip_direction_input, + .direction_output = chip_direction_output, + }, +}; + +static int __devinit cs5535_gpio_probe(struct platform_device *pdev) +{ + struct resource *res; + int err = -EIO; + ulong mask_orig = mask; + + /* There are two ways to get the GPIO base address; one is by + * fetching it from MSR_LBAR_GPIO, the other is by reading the + * PCI BAR info. The latter method is easier (especially across + * different architectures), so we'll stick with that for now. If + * it turns out to be unreliable in the face of crappy BIOSes, we + * can always go back to using MSRs.. */ + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!res) { + dev_err(&pdev->dev, "can't fetch device resource info\n"); + goto done; + } + + if (!request_region(res->start, resource_size(res), pdev->name)) { + dev_err(&pdev->dev, "can't request region\n"); + goto done; + } + + /* set up the driver-specific struct */ + cs5535_gpio_chip.base = res->start; + cs5535_gpio_chip.pdev = pdev; + spin_lock_init(&cs5535_gpio_chip.lock); + + dev_info(&pdev->dev, "reserved resource region %pR\n", res); + + /* mask out reserved pins */ + mask &= 0x1F7FFFFF; + + /* do not allow pin 28, Power Button, as there's special handling + * in the PMC needed. (note 12, p. 48) */ + mask &= ~(1 << 28); + + if (mask_orig != mask) + dev_info(&pdev->dev, "mask changed from 0x%08lX to 0x%08lX\n", + mask_orig, mask); + + /* finally, register with the generic GPIO API */ + err = gpiochip_add(&cs5535_gpio_chip.chip); + if (err) + goto release_region; + + return 0; + +release_region: + release_region(res->start, resource_size(res)); +done: + return err; +} + +static int __devexit cs5535_gpio_remove(struct platform_device *pdev) +{ + struct resource *r; + int err; + + err = gpiochip_remove(&cs5535_gpio_chip.chip); + if (err) { + /* uhh? */ + dev_err(&pdev->dev, "unable to remove gpio_chip?\n"); + return err; + } + + r = platform_get_resource(pdev, IORESOURCE_IO, 0); + release_region(r->start, resource_size(r)); + return 0; +} + +static struct platform_driver cs5535_gpio_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + .probe = cs5535_gpio_probe, + .remove = __devexit_p(cs5535_gpio_remove), +}; + +module_platform_driver(cs5535_gpio_driver); + +MODULE_AUTHOR("Andres Salomon <dilinger@queued.net>"); +MODULE_DESCRIPTION("AMD CS5535/CS5536 GPIO driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/gpio/gpio-da9052.c b/drivers/gpio/gpio-da9052.c new file mode 100644 index 00000000..56dd047d --- /dev/null +++ b/drivers/gpio/gpio-da9052.c @@ -0,0 +1,262 @@ +/* + * GPIO Driver for Dialog DA9052 PMICs. + * + * Copyright(c) 2011 Dialog Semiconductor Ltd. + * + * Author: David Dajun Chen <dchen@diasemi.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. + * + */ +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/uaccess.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/syscalls.h> +#include <linux/seq_file.h> + +#include <linux/mfd/da9052/da9052.h> +#include <linux/mfd/da9052/reg.h> +#include <linux/mfd/da9052/pdata.h> + +#define DA9052_INPUT 1 +#define DA9052_OUTPUT_OPENDRAIN 2 +#define DA9052_OUTPUT_PUSHPULL 3 + +#define DA9052_SUPPLY_VDD_IO1 0 + +#define DA9052_DEBOUNCING_OFF 0 +#define DA9052_DEBOUNCING_ON 1 + +#define DA9052_OUTPUT_LOWLEVEL 0 + +#define DA9052_ACTIVE_LOW 0 +#define DA9052_ACTIVE_HIGH 1 + +#define DA9052_GPIO_MAX_PORTS_PER_REGISTER 8 +#define DA9052_GPIO_SHIFT_COUNT(no) (no%8) +#define DA9052_GPIO_MASK_UPPER_NIBBLE 0xF0 +#define DA9052_GPIO_MASK_LOWER_NIBBLE 0x0F +#define DA9052_GPIO_NIBBLE_SHIFT 4 +#define DA9052_IRQ_GPI0 16 +#define DA9052_GPIO_ODD_SHIFT 7 +#define DA9052_GPIO_EVEN_SHIFT 3 + +struct da9052_gpio { + struct da9052 *da9052; + struct gpio_chip gp; +}; + +static inline struct da9052_gpio *to_da9052_gpio(struct gpio_chip *chip) +{ + return container_of(chip, struct da9052_gpio, gp); +} + +static unsigned char da9052_gpio_port_odd(unsigned offset) +{ + return offset % 2; +} + +static int da9052_gpio_get(struct gpio_chip *gc, unsigned offset) +{ + struct da9052_gpio *gpio = to_da9052_gpio(gc); + int da9052_port_direction = 0; + int ret; + + ret = da9052_reg_read(gpio->da9052, + DA9052_GPIO_0_1_REG + (offset >> 1)); + if (ret < 0) + return ret; + + if (da9052_gpio_port_odd(offset)) { + da9052_port_direction = ret & DA9052_GPIO_ODD_PORT_PIN; + da9052_port_direction >>= 4; + } else { + da9052_port_direction = ret & DA9052_GPIO_EVEN_PORT_PIN; + } + + switch (da9052_port_direction) { + case DA9052_INPUT: + if (offset < DA9052_GPIO_MAX_PORTS_PER_REGISTER) + ret = da9052_reg_read(gpio->da9052, + DA9052_STATUS_C_REG); + else + ret = da9052_reg_read(gpio->da9052, + DA9052_STATUS_D_REG); + if (ret < 0) + return ret; + if (ret & (1 << DA9052_GPIO_SHIFT_COUNT(offset))) + return 1; + else + return 0; + case DA9052_OUTPUT_PUSHPULL: + if (da9052_gpio_port_odd(offset)) + return ret & DA9052_GPIO_ODD_PORT_MODE; + else + return ret & DA9052_GPIO_EVEN_PORT_MODE; + default: + return -EINVAL; + } +} + +static void da9052_gpio_set(struct gpio_chip *gc, unsigned offset, int value) +{ + struct da9052_gpio *gpio = to_da9052_gpio(gc); + int ret; + + if (da9052_gpio_port_odd(offset)) { + ret = da9052_reg_update(gpio->da9052, (offset >> 1) + + DA9052_GPIO_0_1_REG, + DA9052_GPIO_ODD_PORT_MODE, + value << DA9052_GPIO_ODD_SHIFT); + if (ret != 0) + dev_err(gpio->da9052->dev, + "Failed to updated gpio odd reg,%d", + ret); + } else { + ret = da9052_reg_update(gpio->da9052, (offset >> 1) + + DA9052_GPIO_0_1_REG, + DA9052_GPIO_EVEN_PORT_MODE, + value << DA9052_GPIO_EVEN_SHIFT); + if (ret != 0) + dev_err(gpio->da9052->dev, + "Failed to updated gpio even reg,%d", + ret); + } +} + +static int da9052_gpio_direction_input(struct gpio_chip *gc, unsigned offset) +{ + struct da9052_gpio *gpio = to_da9052_gpio(gc); + unsigned char register_value; + int ret; + + /* Format: function - 2 bits type - 1 bit mode - 1 bit */ + register_value = DA9052_INPUT | DA9052_ACTIVE_LOW << 2 | + DA9052_DEBOUNCING_ON << 3; + + if (da9052_gpio_port_odd(offset)) + ret = da9052_reg_update(gpio->da9052, (offset >> 1) + + DA9052_GPIO_0_1_REG, + DA9052_GPIO_MASK_UPPER_NIBBLE, + (register_value << + DA9052_GPIO_NIBBLE_SHIFT)); + else + ret = da9052_reg_update(gpio->da9052, (offset >> 1) + + DA9052_GPIO_0_1_REG, + DA9052_GPIO_MASK_LOWER_NIBBLE, + register_value); + + return ret; +} + +static int da9052_gpio_direction_output(struct gpio_chip *gc, + unsigned offset, int value) +{ + struct da9052_gpio *gpio = to_da9052_gpio(gc); + unsigned char register_value; + int ret; + + /* Format: Function - 2 bits Type - 1 bit Mode - 1 bit */ + register_value = DA9052_OUTPUT_PUSHPULL | DA9052_SUPPLY_VDD_IO1 << 2 | + value << 3; + + if (da9052_gpio_port_odd(offset)) + ret = da9052_reg_update(gpio->da9052, (offset >> 1) + + DA9052_GPIO_0_1_REG, + DA9052_GPIO_MASK_UPPER_NIBBLE, + (register_value << + DA9052_GPIO_NIBBLE_SHIFT)); + else + ret = da9052_reg_update(gpio->da9052, (offset >> 1) + + DA9052_GPIO_0_1_REG, + DA9052_GPIO_MASK_LOWER_NIBBLE, + register_value); + + return ret; +} + +static int da9052_gpio_to_irq(struct gpio_chip *gc, u32 offset) +{ + struct da9052_gpio *gpio = to_da9052_gpio(gc); + struct da9052 *da9052 = gpio->da9052; + + return da9052->irq_base + DA9052_IRQ_GPI0 + offset; +} + +static struct gpio_chip reference_gp __devinitdata = { + .label = "da9052-gpio", + .owner = THIS_MODULE, + .get = da9052_gpio_get, + .set = da9052_gpio_set, + .direction_input = da9052_gpio_direction_input, + .direction_output = da9052_gpio_direction_output, + .to_irq = da9052_gpio_to_irq, + .can_sleep = 1, + .ngpio = 16, + .base = -1, +}; + +static int __devinit da9052_gpio_probe(struct platform_device *pdev) +{ + struct da9052_gpio *gpio; + struct da9052_pdata *pdata; + int ret; + + gpio = kzalloc(sizeof(*gpio), GFP_KERNEL); + if (gpio == NULL) + return -ENOMEM; + + gpio->da9052 = dev_get_drvdata(pdev->dev.parent); + pdata = gpio->da9052->dev->platform_data; + + gpio->gp = reference_gp; + if (pdata && pdata->gpio_base) + gpio->gp.base = pdata->gpio_base; + + ret = gpiochip_add(&gpio->gp); + if (ret < 0) { + dev_err(&pdev->dev, "Could not register gpiochip, %d\n", ret); + goto err_mem; + } + + platform_set_drvdata(pdev, gpio); + + return 0; + +err_mem: + kfree(gpio); + return ret; +} + +static int __devexit da9052_gpio_remove(struct platform_device *pdev) +{ + struct da9052_gpio *gpio = platform_get_drvdata(pdev); + int ret; + + ret = gpiochip_remove(&gpio->gp); + if (ret == 0) + kfree(gpio); + + return ret; +} + +static struct platform_driver da9052_gpio_driver = { + .probe = da9052_gpio_probe, + .remove = __devexit_p(da9052_gpio_remove), + .driver = { + .name = "da9052-gpio", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(da9052_gpio_driver); + +MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>"); +MODULE_DESCRIPTION("DA9052 GPIO Device Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:da9052-gpio"); diff --git a/drivers/gpio/gpio-davinci.c b/drivers/gpio/gpio-davinci.c new file mode 100644 index 00000000..3d000169 --- /dev/null +++ b/drivers/gpio/gpio-davinci.c @@ -0,0 +1,461 @@ +/* + * TI DaVinci GPIO Support + * + * Copyright (c) 2006-2007 David Brownell + * Copyright (c) 2007, MontaVista Software, Inc. <source@mvista.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. + */ +#include <linux/gpio.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> + +#include <asm/mach/irq.h> + +struct davinci_gpio_regs { + u32 dir; + u32 out_data; + u32 set_data; + u32 clr_data; + u32 in_data; + u32 set_rising; + u32 clr_rising; + u32 set_falling; + u32 clr_falling; + u32 intstat; +}; + +#define chip2controller(chip) \ + container_of(chip, struct davinci_gpio_controller, chip) + +static struct davinci_gpio_controller chips[DIV_ROUND_UP(DAVINCI_N_GPIO, 32)]; +static void __iomem *gpio_base; + +static struct davinci_gpio_regs __iomem __init *gpio2regs(unsigned gpio) +{ + void __iomem *ptr; + + if (gpio < 32 * 1) + ptr = gpio_base + 0x10; + else if (gpio < 32 * 2) + ptr = gpio_base + 0x38; + else if (gpio < 32 * 3) + ptr = gpio_base + 0x60; + else if (gpio < 32 * 4) + ptr = gpio_base + 0x88; + else if (gpio < 32 * 5) + ptr = gpio_base + 0xb0; + else + ptr = NULL; + return ptr; +} + +static inline struct davinci_gpio_regs __iomem *irq2regs(int irq) +{ + struct davinci_gpio_regs __iomem *g; + + g = (__force struct davinci_gpio_regs __iomem *)irq_get_chip_data(irq); + + return g; +} + +static int __init davinci_gpio_irq_setup(void); + +/*--------------------------------------------------------------------------*/ + +/* board setup code *MUST* setup pinmux and enable the GPIO clock. */ +static inline int __davinci_direction(struct gpio_chip *chip, + unsigned offset, bool out, int value) +{ + struct davinci_gpio_controller *d = chip2controller(chip); + struct davinci_gpio_regs __iomem *g = d->regs; + unsigned long flags; + u32 temp; + u32 mask = 1 << offset; + + spin_lock_irqsave(&d->lock, flags); + temp = __raw_readl(&g->dir); + if (out) { + temp &= ~mask; + __raw_writel(mask, value ? &g->set_data : &g->clr_data); + } else { + temp |= mask; + } + __raw_writel(temp, &g->dir); + spin_unlock_irqrestore(&d->lock, flags); + + return 0; +} + +static int davinci_direction_in(struct gpio_chip *chip, unsigned offset) +{ + return __davinci_direction(chip, offset, false, 0); +} + +static int +davinci_direction_out(struct gpio_chip *chip, unsigned offset, int value) +{ + return __davinci_direction(chip, offset, true, value); +} + +/* + * Read the pin's value (works even if it's set up as output); + * returns zero/nonzero. + * + * Note that changes are synched to the GPIO clock, so reading values back + * right after you've set them may give old values. + */ +static int davinci_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct davinci_gpio_controller *d = chip2controller(chip); + struct davinci_gpio_regs __iomem *g = d->regs; + + return (1 << offset) & __raw_readl(&g->in_data); +} + +/* + * Assuming the pin is muxed as a gpio output, set its output value. + */ +static void +davinci_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct davinci_gpio_controller *d = chip2controller(chip); + struct davinci_gpio_regs __iomem *g = d->regs; + + __raw_writel((1 << offset), value ? &g->set_data : &g->clr_data); +} + +static int __init davinci_gpio_setup(void) +{ + int i, base; + unsigned ngpio; + struct davinci_soc_info *soc_info = &davinci_soc_info; + struct davinci_gpio_regs *regs; + + if (soc_info->gpio_type != GPIO_TYPE_DAVINCI) + return 0; + + /* + * The gpio banks conceptually expose a segmented bitmap, + * and "ngpio" is one more than the largest zero-based + * bit index that's valid. + */ + ngpio = soc_info->gpio_num; + if (ngpio == 0) { + pr_err("GPIO setup: how many GPIOs?\n"); + return -EINVAL; + } + + if (WARN_ON(DAVINCI_N_GPIO < ngpio)) + ngpio = DAVINCI_N_GPIO; + + gpio_base = ioremap(soc_info->gpio_base, SZ_4K); + if (WARN_ON(!gpio_base)) + return -ENOMEM; + + for (i = 0, base = 0; base < ngpio; i++, base += 32) { + chips[i].chip.label = "DaVinci"; + + chips[i].chip.direction_input = davinci_direction_in; + chips[i].chip.get = davinci_gpio_get; + chips[i].chip.direction_output = davinci_direction_out; + chips[i].chip.set = davinci_gpio_set; + + chips[i].chip.base = base; + chips[i].chip.ngpio = ngpio - base; + if (chips[i].chip.ngpio > 32) + chips[i].chip.ngpio = 32; + + spin_lock_init(&chips[i].lock); + + regs = gpio2regs(base); + chips[i].regs = regs; + chips[i].set_data = ®s->set_data; + chips[i].clr_data = ®s->clr_data; + chips[i].in_data = ®s->in_data; + + gpiochip_add(&chips[i].chip); + } + + soc_info->gpio_ctlrs = chips; + soc_info->gpio_ctlrs_num = DIV_ROUND_UP(ngpio, 32); + + davinci_gpio_irq_setup(); + return 0; +} +pure_initcall(davinci_gpio_setup); + +/*--------------------------------------------------------------------------*/ +/* + * We expect irqs will normally be set up as input pins, but they can also be + * used as output pins ... which is convenient for testing. + * + * NOTE: The first few GPIOs also have direct INTC hookups in addition + * to their GPIOBNK0 irq, with a bit less overhead. + * + * All those INTC hookups (direct, plus several IRQ banks) can also + * serve as EDMA event triggers. + */ + +static void gpio_irq_disable(struct irq_data *d) +{ + struct davinci_gpio_regs __iomem *g = irq2regs(d->irq); + u32 mask = (u32) irq_data_get_irq_handler_data(d); + + __raw_writel(mask, &g->clr_falling); + __raw_writel(mask, &g->clr_rising); +} + +static void gpio_irq_enable(struct irq_data *d) +{ + struct davinci_gpio_regs __iomem *g = irq2regs(d->irq); + u32 mask = (u32) irq_data_get_irq_handler_data(d); + unsigned status = irqd_get_trigger_type(d); + + status &= IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING; + if (!status) + status = IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING; + + if (status & IRQ_TYPE_EDGE_FALLING) + __raw_writel(mask, &g->set_falling); + if (status & IRQ_TYPE_EDGE_RISING) + __raw_writel(mask, &g->set_rising); +} + +static int gpio_irq_type(struct irq_data *d, unsigned trigger) +{ + if (trigger & ~(IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)) + return -EINVAL; + + return 0; +} + +static struct irq_chip gpio_irqchip = { + .name = "GPIO", + .irq_enable = gpio_irq_enable, + .irq_disable = gpio_irq_disable, + .irq_set_type = gpio_irq_type, + .flags = IRQCHIP_SET_TYPE_MASKED, +}; + +static void +gpio_irq_handler(unsigned irq, struct irq_desc *desc) +{ + struct davinci_gpio_regs __iomem *g; + u32 mask = 0xffff; + struct davinci_gpio_controller *d; + + d = (struct davinci_gpio_controller *)irq_desc_get_handler_data(desc); + g = (struct davinci_gpio_regs __iomem *)d->regs; + + /* we only care about one bank */ + if (irq & 1) + mask <<= 16; + + /* temporarily mask (level sensitive) parent IRQ */ + desc->irq_data.chip->irq_mask(&desc->irq_data); + desc->irq_data.chip->irq_ack(&desc->irq_data); + while (1) { + u32 status; + int n; + int res; + + /* ack any irqs */ + status = __raw_readl(&g->intstat) & mask; + if (!status) + break; + __raw_writel(status, &g->intstat); + + /* now demux them to the right lowlevel handler */ + n = d->irq_base; + if (irq & 1) { + n += 16; + status >>= 16; + } + + while (status) { + res = ffs(status); + n += res; + generic_handle_irq(n - 1); + status >>= res; + } + } + desc->irq_data.chip->irq_unmask(&desc->irq_data); + /* now it may re-trigger */ +} + +static int gpio_to_irq_banked(struct gpio_chip *chip, unsigned offset) +{ + struct davinci_gpio_controller *d = chip2controller(chip); + + if (d->irq_base >= 0) + return d->irq_base + offset; + else + return -ENODEV; +} + +static int gpio_to_irq_unbanked(struct gpio_chip *chip, unsigned offset) +{ + struct davinci_soc_info *soc_info = &davinci_soc_info; + + /* NOTE: we assume for now that only irqs in the first gpio_chip + * can provide direct-mapped IRQs to AINTC (up to 32 GPIOs). + */ + if (offset < soc_info->gpio_unbanked) + return soc_info->gpio_irq + offset; + else + return -ENODEV; +} + +static int gpio_irq_type_unbanked(struct irq_data *data, unsigned trigger) +{ + struct davinci_gpio_controller *d; + struct davinci_gpio_regs __iomem *g; + struct davinci_soc_info *soc_info = &davinci_soc_info; + u32 mask; + + d = (struct davinci_gpio_controller *)data->handler_data; + g = (struct davinci_gpio_regs __iomem *)d->regs; + mask = __gpio_mask(data->irq - soc_info->gpio_irq); + + if (trigger & ~(IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)) + return -EINVAL; + + __raw_writel(mask, (trigger & IRQ_TYPE_EDGE_FALLING) + ? &g->set_falling : &g->clr_falling); + __raw_writel(mask, (trigger & IRQ_TYPE_EDGE_RISING) + ? &g->set_rising : &g->clr_rising); + + return 0; +} + +/* + * NOTE: for suspend/resume, probably best to make a platform_device with + * suspend_late/resume_resume calls hooking into results of the set_wake() + * calls ... so if no gpios are wakeup events the clock can be disabled, + * with outputs left at previously set levels, and so that VDD3P3V.IOPWDN0 + * (dm6446) can be set appropriately for GPIOV33 pins. + */ + +static int __init davinci_gpio_irq_setup(void) +{ + unsigned gpio, irq, bank; + struct clk *clk; + u32 binten = 0; + unsigned ngpio, bank_irq; + struct davinci_soc_info *soc_info = &davinci_soc_info; + struct davinci_gpio_regs __iomem *g; + + ngpio = soc_info->gpio_num; + + bank_irq = soc_info->gpio_irq; + if (bank_irq == 0) { + printk(KERN_ERR "Don't know first GPIO bank IRQ.\n"); + return -EINVAL; + } + + clk = clk_get(NULL, "gpio"); + if (IS_ERR(clk)) { + printk(KERN_ERR "Error %ld getting gpio clock?\n", + PTR_ERR(clk)); + return PTR_ERR(clk); + } + clk_enable(clk); + + /* Arrange gpio_to_irq() support, handling either direct IRQs or + * banked IRQs. Having GPIOs in the first GPIO bank use direct + * IRQs, while the others use banked IRQs, would need some setup + * tweaks to recognize hardware which can do that. + */ + for (gpio = 0, bank = 0; gpio < ngpio; bank++, gpio += 32) { + chips[bank].chip.to_irq = gpio_to_irq_banked; + chips[bank].irq_base = soc_info->gpio_unbanked + ? -EINVAL + : (soc_info->intc_irq_num + gpio); + } + + /* + * AINTC can handle direct/unbanked IRQs for GPIOs, with the GPIO + * controller only handling trigger modes. We currently assume no + * IRQ mux conflicts; gpio_irq_type_unbanked() is only for GPIOs. + */ + if (soc_info->gpio_unbanked) { + static struct irq_chip_type gpio_unbanked; + + /* pass "bank 0" GPIO IRQs to AINTC */ + chips[0].chip.to_irq = gpio_to_irq_unbanked; + binten = BIT(0); + + /* AINTC handles mask/unmask; GPIO handles triggering */ + irq = bank_irq; + gpio_unbanked = *container_of(irq_get_chip(irq), + struct irq_chip_type, chip); + gpio_unbanked.chip.name = "GPIO-AINTC"; + gpio_unbanked.chip.irq_set_type = gpio_irq_type_unbanked; + + /* default trigger: both edges */ + g = gpio2regs(0); + __raw_writel(~0, &g->set_falling); + __raw_writel(~0, &g->set_rising); + + /* set the direct IRQs up to use that irqchip */ + for (gpio = 0; gpio < soc_info->gpio_unbanked; gpio++, irq++) { + irq_set_chip(irq, &gpio_unbanked.chip); + irq_set_handler_data(irq, &chips[gpio / 32]); + irq_set_status_flags(irq, IRQ_TYPE_EDGE_BOTH); + } + + goto done; + } + + /* + * Or, AINTC can handle IRQs for banks of 16 GPIO IRQs, which we + * then chain through our own handler. + */ + for (gpio = 0, irq = gpio_to_irq(0), bank = 0; + gpio < ngpio; + bank++, bank_irq++) { + unsigned i; + + /* disabled by default, enabled only as needed */ + g = gpio2regs(gpio); + __raw_writel(~0, &g->clr_falling); + __raw_writel(~0, &g->clr_rising); + + /* set up all irqs in this bank */ + irq_set_chained_handler(bank_irq, gpio_irq_handler); + + /* + * Each chip handles 32 gpios, and each irq bank consists of 16 + * gpio irqs. Pass the irq bank's corresponding controller to + * the chained irq handler. + */ + irq_set_handler_data(bank_irq, &chips[gpio / 32]); + + for (i = 0; i < 16 && gpio < ngpio; i++, irq++, gpio++) { + irq_set_chip(irq, &gpio_irqchip); + irq_set_chip_data(irq, (__force void *)g); + irq_set_handler_data(irq, (void *)__gpio_mask(gpio)); + irq_set_handler(irq, handle_simple_irq); + set_irq_flags(irq, IRQF_VALID); + } + + binten |= BIT(bank); + } + +done: + /* BINTEN -- per-bank interrupt enable. genirq would also let these + * bits be set/cleared dynamically. + */ + __raw_writel(binten, gpio_base + 0x08); + + printk(KERN_INFO "DaVinci: %d gpio irqs\n", irq - gpio_to_irq(0)); + + return 0; +} diff --git a/drivers/gpio/gpio-ep93xx.c b/drivers/gpio/gpio-ep93xx.c new file mode 100644 index 00000000..776b7725 --- /dev/null +++ b/drivers/gpio/gpio-ep93xx.c @@ -0,0 +1,411 @@ +/* + * Generic EP93xx GPIO handling + * + * Copyright (c) 2008 Ryan Mallon + * Copyright (c) 2011 H Hartley Sweeten <hsweeten@visionengravers.com> + * + * Based on code originally from: + * linux/arch/arm/mach-ep93xx/core.c + * + * 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 <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/gpio.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/basic_mmio_gpio.h> + +#include <mach/hardware.h> +#include <mach/gpio-ep93xx.h> + +#define irq_to_gpio(irq) ((irq) - gpio_to_irq(0)) + +struct ep93xx_gpio { + void __iomem *mmio_base; + struct bgpio_chip bgc[8]; +}; + +/************************************************************************* + * Interrupt handling for EP93xx on-chip GPIOs + *************************************************************************/ +static unsigned char gpio_int_unmasked[3]; +static unsigned char gpio_int_enabled[3]; +static unsigned char gpio_int_type1[3]; +static unsigned char gpio_int_type2[3]; +static unsigned char gpio_int_debounce[3]; + +/* Port ordering is: A B F */ +static const u8 int_type1_register_offset[3] = { 0x90, 0xac, 0x4c }; +static const u8 int_type2_register_offset[3] = { 0x94, 0xb0, 0x50 }; +static const u8 eoi_register_offset[3] = { 0x98, 0xb4, 0x54 }; +static const u8 int_en_register_offset[3] = { 0x9c, 0xb8, 0x58 }; +static const u8 int_debounce_register_offset[3] = { 0xa8, 0xc4, 0x64 }; + +static void ep93xx_gpio_update_int_params(unsigned port) +{ + BUG_ON(port > 2); + + __raw_writeb(0, EP93XX_GPIO_REG(int_en_register_offset[port])); + + __raw_writeb(gpio_int_type2[port], + EP93XX_GPIO_REG(int_type2_register_offset[port])); + + __raw_writeb(gpio_int_type1[port], + EP93XX_GPIO_REG(int_type1_register_offset[port])); + + __raw_writeb(gpio_int_unmasked[port] & gpio_int_enabled[port], + EP93XX_GPIO_REG(int_en_register_offset[port])); +} + +static void ep93xx_gpio_int_debounce(unsigned int irq, bool enable) +{ + int line = irq_to_gpio(irq); + int port = line >> 3; + int port_mask = 1 << (line & 7); + + if (enable) + gpio_int_debounce[port] |= port_mask; + else + gpio_int_debounce[port] &= ~port_mask; + + __raw_writeb(gpio_int_debounce[port], + EP93XX_GPIO_REG(int_debounce_register_offset[port])); +} + +static void ep93xx_gpio_ab_irq_handler(unsigned int irq, struct irq_desc *desc) +{ + unsigned char status; + int i; + + status = __raw_readb(EP93XX_GPIO_A_INT_STATUS); + for (i = 0; i < 8; i++) { + if (status & (1 << i)) { + int gpio_irq = gpio_to_irq(EP93XX_GPIO_LINE_A(0)) + i; + generic_handle_irq(gpio_irq); + } + } + + status = __raw_readb(EP93XX_GPIO_B_INT_STATUS); + for (i = 0; i < 8; i++) { + if (status & (1 << i)) { + int gpio_irq = gpio_to_irq(EP93XX_GPIO_LINE_B(0)) + i; + generic_handle_irq(gpio_irq); + } + } +} + +static void ep93xx_gpio_f_irq_handler(unsigned int irq, struct irq_desc *desc) +{ + /* + * map discontiguous hw irq range to continuous sw irq range: + * + * IRQ_EP93XX_GPIO{0..7}MUX -> gpio_to_irq(EP93XX_GPIO_LINE_F({0..7}) + */ + int port_f_idx = ((irq + 1) & 7) ^ 4; /* {19..22,47..50} -> {0..7} */ + int gpio_irq = gpio_to_irq(EP93XX_GPIO_LINE_F(0)) + port_f_idx; + + generic_handle_irq(gpio_irq); +} + +static void ep93xx_gpio_irq_ack(struct irq_data *d) +{ + int line = irq_to_gpio(d->irq); + int port = line >> 3; + int port_mask = 1 << (line & 7); + + if (irqd_get_trigger_type(d) == IRQ_TYPE_EDGE_BOTH) { + gpio_int_type2[port] ^= port_mask; /* switch edge direction */ + ep93xx_gpio_update_int_params(port); + } + + __raw_writeb(port_mask, EP93XX_GPIO_REG(eoi_register_offset[port])); +} + +static void ep93xx_gpio_irq_mask_ack(struct irq_data *d) +{ + int line = irq_to_gpio(d->irq); + int port = line >> 3; + int port_mask = 1 << (line & 7); + + if (irqd_get_trigger_type(d) == IRQ_TYPE_EDGE_BOTH) + gpio_int_type2[port] ^= port_mask; /* switch edge direction */ + + gpio_int_unmasked[port] &= ~port_mask; + ep93xx_gpio_update_int_params(port); + + __raw_writeb(port_mask, EP93XX_GPIO_REG(eoi_register_offset[port])); +} + +static void ep93xx_gpio_irq_mask(struct irq_data *d) +{ + int line = irq_to_gpio(d->irq); + int port = line >> 3; + + gpio_int_unmasked[port] &= ~(1 << (line & 7)); + ep93xx_gpio_update_int_params(port); +} + +static void ep93xx_gpio_irq_unmask(struct irq_data *d) +{ + int line = irq_to_gpio(d->irq); + int port = line >> 3; + + gpio_int_unmasked[port] |= 1 << (line & 7); + ep93xx_gpio_update_int_params(port); +} + +/* + * gpio_int_type1 controls whether the interrupt is level (0) or + * edge (1) triggered, while gpio_int_type2 controls whether it + * triggers on low/falling (0) or high/rising (1). + */ +static int ep93xx_gpio_irq_type(struct irq_data *d, unsigned int type) +{ + const int gpio = irq_to_gpio(d->irq); + const int port = gpio >> 3; + const int port_mask = 1 << (gpio & 7); + irq_flow_handler_t handler; + + gpio_direction_input(gpio); + + switch (type) { + case IRQ_TYPE_EDGE_RISING: + gpio_int_type1[port] |= port_mask; + gpio_int_type2[port] |= port_mask; + handler = handle_edge_irq; + break; + case IRQ_TYPE_EDGE_FALLING: + gpio_int_type1[port] |= port_mask; + gpio_int_type2[port] &= ~port_mask; + handler = handle_edge_irq; + break; + case IRQ_TYPE_LEVEL_HIGH: + gpio_int_type1[port] &= ~port_mask; + gpio_int_type2[port] |= port_mask; + handler = handle_level_irq; + break; + case IRQ_TYPE_LEVEL_LOW: + gpio_int_type1[port] &= ~port_mask; + gpio_int_type2[port] &= ~port_mask; + handler = handle_level_irq; + break; + case IRQ_TYPE_EDGE_BOTH: + gpio_int_type1[port] |= port_mask; + /* set initial polarity based on current input level */ + if (gpio_get_value(gpio)) + gpio_int_type2[port] &= ~port_mask; /* falling */ + else + gpio_int_type2[port] |= port_mask; /* rising */ + handler = handle_edge_irq; + break; + default: + return -EINVAL; + } + + __irq_set_handler_locked(d->irq, handler); + + gpio_int_enabled[port] |= port_mask; + + ep93xx_gpio_update_int_params(port); + + return 0; +} + +static struct irq_chip ep93xx_gpio_irq_chip = { + .name = "GPIO", + .irq_ack = ep93xx_gpio_irq_ack, + .irq_mask_ack = ep93xx_gpio_irq_mask_ack, + .irq_mask = ep93xx_gpio_irq_mask, + .irq_unmask = ep93xx_gpio_irq_unmask, + .irq_set_type = ep93xx_gpio_irq_type, +}; + +static void ep93xx_gpio_init_irq(void) +{ + int gpio_irq; + + for (gpio_irq = gpio_to_irq(0); + gpio_irq <= gpio_to_irq(EP93XX_GPIO_LINE_MAX_IRQ); ++gpio_irq) { + irq_set_chip_and_handler(gpio_irq, &ep93xx_gpio_irq_chip, + handle_level_irq); + set_irq_flags(gpio_irq, IRQF_VALID); + } + + irq_set_chained_handler(IRQ_EP93XX_GPIO_AB, + ep93xx_gpio_ab_irq_handler); + irq_set_chained_handler(IRQ_EP93XX_GPIO0MUX, + ep93xx_gpio_f_irq_handler); + irq_set_chained_handler(IRQ_EP93XX_GPIO1MUX, + ep93xx_gpio_f_irq_handler); + irq_set_chained_handler(IRQ_EP93XX_GPIO2MUX, + ep93xx_gpio_f_irq_handler); + irq_set_chained_handler(IRQ_EP93XX_GPIO3MUX, + ep93xx_gpio_f_irq_handler); + irq_set_chained_handler(IRQ_EP93XX_GPIO4MUX, + ep93xx_gpio_f_irq_handler); + irq_set_chained_handler(IRQ_EP93XX_GPIO5MUX, + ep93xx_gpio_f_irq_handler); + irq_set_chained_handler(IRQ_EP93XX_GPIO6MUX, + ep93xx_gpio_f_irq_handler); + irq_set_chained_handler(IRQ_EP93XX_GPIO7MUX, + ep93xx_gpio_f_irq_handler); +} + + +/************************************************************************* + * gpiolib interface for EP93xx on-chip GPIOs + *************************************************************************/ +struct ep93xx_gpio_bank { + const char *label; + int data; + int dir; + int base; + bool has_debounce; +}; + +#define EP93XX_GPIO_BANK(_label, _data, _dir, _base, _debounce) \ + { \ + .label = _label, \ + .data = _data, \ + .dir = _dir, \ + .base = _base, \ + .has_debounce = _debounce, \ + } + +static struct ep93xx_gpio_bank ep93xx_gpio_banks[] = { + EP93XX_GPIO_BANK("A", 0x00, 0x10, 0, true), + EP93XX_GPIO_BANK("B", 0x04, 0x14, 8, true), + EP93XX_GPIO_BANK("C", 0x08, 0x18, 40, false), + EP93XX_GPIO_BANK("D", 0x0c, 0x1c, 24, false), + EP93XX_GPIO_BANK("E", 0x20, 0x24, 32, false), + EP93XX_GPIO_BANK("F", 0x30, 0x34, 16, true), + EP93XX_GPIO_BANK("G", 0x38, 0x3c, 48, false), + EP93XX_GPIO_BANK("H", 0x40, 0x44, 56, false), +}; + +static int ep93xx_gpio_set_debounce(struct gpio_chip *chip, + unsigned offset, unsigned debounce) +{ + int gpio = chip->base + offset; + int irq = gpio_to_irq(gpio); + + if (irq < 0) + return -EINVAL; + + ep93xx_gpio_int_debounce(irq, debounce ? true : false); + + return 0; +} + +/* + * Map GPIO A0..A7 (0..7) to irq 64..71, + * B0..B7 (7..15) to irq 72..79, and + * F0..F7 (16..24) to irq 80..87. + */ +static int ep93xx_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + int gpio = chip->base + offset; + + if (gpio > EP93XX_GPIO_LINE_MAX_IRQ) + return -EINVAL; + + return 64 + gpio; +} + +static int ep93xx_gpio_add_bank(struct bgpio_chip *bgc, struct device *dev, + void __iomem *mmio_base, struct ep93xx_gpio_bank *bank) +{ + void __iomem *data = mmio_base + bank->data; + void __iomem *dir = mmio_base + bank->dir; + int err; + + err = bgpio_init(bgc, dev, 1, data, NULL, NULL, dir, NULL, false); + if (err) + return err; + + bgc->gc.label = bank->label; + bgc->gc.base = bank->base; + + if (bank->has_debounce) { + bgc->gc.set_debounce = ep93xx_gpio_set_debounce; + bgc->gc.to_irq = ep93xx_gpio_to_irq; + } + + return gpiochip_add(&bgc->gc); +} + +static int __devinit ep93xx_gpio_probe(struct platform_device *pdev) +{ + struct ep93xx_gpio *ep93xx_gpio; + struct resource *res; + void __iomem *mmio; + int i; + int ret; + + ep93xx_gpio = kzalloc(sizeof(*ep93xx_gpio), GFP_KERNEL); + if (!ep93xx_gpio) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENXIO; + goto exit_free; + } + + if (!request_mem_region(res->start, resource_size(res), pdev->name)) { + ret = -EBUSY; + goto exit_free; + } + + mmio = ioremap(res->start, resource_size(res)); + if (!mmio) { + ret = -ENXIO; + goto exit_release; + } + ep93xx_gpio->mmio_base = mmio; + + for (i = 0; i < ARRAY_SIZE(ep93xx_gpio_banks); i++) { + struct bgpio_chip *bgc = &ep93xx_gpio->bgc[i]; + struct ep93xx_gpio_bank *bank = &ep93xx_gpio_banks[i]; + + if (ep93xx_gpio_add_bank(bgc, &pdev->dev, mmio, bank)) + dev_warn(&pdev->dev, "Unable to add gpio bank %s\n", + bank->label); + } + + ep93xx_gpio_init_irq(); + + return 0; + +exit_release: + release_mem_region(res->start, resource_size(res)); +exit_free: + kfree(ep93xx_gpio); + dev_info(&pdev->dev, "%s failed with errno %d\n", __func__, ret); + return ret; +} + +static struct platform_driver ep93xx_gpio_driver = { + .driver = { + .name = "gpio-ep93xx", + .owner = THIS_MODULE, + }, + .probe = ep93xx_gpio_probe, +}; + +static int __init ep93xx_gpio_init(void) +{ + return platform_driver_register(&ep93xx_gpio_driver); +} +postcore_initcall(ep93xx_gpio_init); + +MODULE_AUTHOR("Ryan Mallon <ryan@bluewatersys.com> " + "H Hartley Sweeten <hsweeten@visionengravers.com>"); +MODULE_DESCRIPTION("EP93XX GPIO driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpio/gpio-ge.c b/drivers/gpio/gpio-ge.c new file mode 100644 index 00000000..7b95a4a8 --- /dev/null +++ b/drivers/gpio/gpio-ge.c @@ -0,0 +1,199 @@ +/* + * Driver for GE FPGA based GPIO + * + * Author: Martyn Welch <martyn.welch@ge.com> + * + * 2008 (c) GE Intelligent Platforms Embedded Systems, Inc. + * + * 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. + */ + +/* TODO + * + * Configuration of output modes (totem-pole/open-drain) + * Interrupt configuration - interrupts are always generated the FPGA relies on + * the I/O interrupt controllers mask to stop them propergating + */ + +#include <linux/kernel.h> +#include <linux/compiler.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> +#include <linux/of_gpio.h> +#include <linux/gpio.h> +#include <linux/slab.h> +#include <linux/module.h> + +#define GEF_GPIO_DIRECT 0x00 +#define GEF_GPIO_IN 0x04 +#define GEF_GPIO_OUT 0x08 +#define GEF_GPIO_TRIG 0x0C +#define GEF_GPIO_POLAR_A 0x10 +#define GEF_GPIO_POLAR_B 0x14 +#define GEF_GPIO_INT_STAT 0x18 +#define GEF_GPIO_OVERRUN 0x1C +#define GEF_GPIO_MODE 0x20 + +static void _gef_gpio_set(void __iomem *reg, unsigned int offset, int value) +{ + unsigned int data; + + data = ioread32be(reg); + /* value: 0=low; 1=high */ + if (value & 0x1) + data = data | (0x1 << offset); + else + data = data & ~(0x1 << offset); + + iowrite32be(data, reg); +} + + +static int gef_gpio_dir_in(struct gpio_chip *chip, unsigned offset) +{ + unsigned int data; + struct of_mm_gpio_chip *mmchip = to_of_mm_gpio_chip(chip); + + data = ioread32be(mmchip->regs + GEF_GPIO_DIRECT); + data = data | (0x1 << offset); + iowrite32be(data, mmchip->regs + GEF_GPIO_DIRECT); + + return 0; +} + +static int gef_gpio_dir_out(struct gpio_chip *chip, unsigned offset, int value) +{ + unsigned int data; + struct of_mm_gpio_chip *mmchip = to_of_mm_gpio_chip(chip); + + /* Set direction before switching to input */ + _gef_gpio_set(mmchip->regs + GEF_GPIO_OUT, offset, value); + + data = ioread32be(mmchip->regs + GEF_GPIO_DIRECT); + data = data & ~(0x1 << offset); + iowrite32be(data, mmchip->regs + GEF_GPIO_DIRECT); + + return 0; +} + +static int gef_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + unsigned int data; + int state = 0; + struct of_mm_gpio_chip *mmchip = to_of_mm_gpio_chip(chip); + + data = ioread32be(mmchip->regs + GEF_GPIO_IN); + state = (int)((data >> offset) & 0x1); + + return state; +} + +static void gef_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct of_mm_gpio_chip *mmchip = to_of_mm_gpio_chip(chip); + + _gef_gpio_set(mmchip->regs + GEF_GPIO_OUT, offset, value); +} + +static int __init gef_gpio_init(void) +{ + struct device_node *np; + int retval; + struct of_mm_gpio_chip *gef_gpio_chip; + + for_each_compatible_node(np, NULL, "gef,sbc610-gpio") { + + pr_debug("%s: Initialising GEF GPIO\n", np->full_name); + + /* Allocate chip structure */ + gef_gpio_chip = kzalloc(sizeof(*gef_gpio_chip), GFP_KERNEL); + if (!gef_gpio_chip) { + pr_err("%s: Unable to allocate structure\n", + np->full_name); + continue; + } + + /* Setup pointers to chip functions */ + gef_gpio_chip->gc.of_gpio_n_cells = 2; + gef_gpio_chip->gc.ngpio = 19; + gef_gpio_chip->gc.direction_input = gef_gpio_dir_in; + gef_gpio_chip->gc.direction_output = gef_gpio_dir_out; + gef_gpio_chip->gc.get = gef_gpio_get; + gef_gpio_chip->gc.set = gef_gpio_set; + + /* This function adds a memory mapped GPIO chip */ + retval = of_mm_gpiochip_add(np, gef_gpio_chip); + if (retval) { + kfree(gef_gpio_chip); + pr_err("%s: Unable to add GPIO\n", np->full_name); + } + } + + for_each_compatible_node(np, NULL, "gef,sbc310-gpio") { + + pr_debug("%s: Initialising GEF GPIO\n", np->full_name); + + /* Allocate chip structure */ + gef_gpio_chip = kzalloc(sizeof(*gef_gpio_chip), GFP_KERNEL); + if (!gef_gpio_chip) { + pr_err("%s: Unable to allocate structure\n", + np->full_name); + continue; + } + + /* Setup pointers to chip functions */ + gef_gpio_chip->gc.of_gpio_n_cells = 2; + gef_gpio_chip->gc.ngpio = 6; + gef_gpio_chip->gc.direction_input = gef_gpio_dir_in; + gef_gpio_chip->gc.direction_output = gef_gpio_dir_out; + gef_gpio_chip->gc.get = gef_gpio_get; + gef_gpio_chip->gc.set = gef_gpio_set; + + /* This function adds a memory mapped GPIO chip */ + retval = of_mm_gpiochip_add(np, gef_gpio_chip); + if (retval) { + kfree(gef_gpio_chip); + pr_err("%s: Unable to add GPIO\n", np->full_name); + } + } + + for_each_compatible_node(np, NULL, "ge,imp3a-gpio") { + + pr_debug("%s: Initialising GE GPIO\n", np->full_name); + + /* Allocate chip structure */ + gef_gpio_chip = kzalloc(sizeof(*gef_gpio_chip), GFP_KERNEL); + if (!gef_gpio_chip) { + pr_err("%s: Unable to allocate structure\n", + np->full_name); + continue; + } + + /* Setup pointers to chip functions */ + gef_gpio_chip->gc.of_gpio_n_cells = 2; + gef_gpio_chip->gc.ngpio = 16; + gef_gpio_chip->gc.direction_input = gef_gpio_dir_in; + gef_gpio_chip->gc.direction_output = gef_gpio_dir_out; + gef_gpio_chip->gc.get = gef_gpio_get; + gef_gpio_chip->gc.set = gef_gpio_set; + + /* This function adds a memory mapped GPIO chip */ + retval = of_mm_gpiochip_add(np, gef_gpio_chip); + if (retval) { + kfree(gef_gpio_chip); + pr_err("%s: Unable to add GPIO\n", np->full_name); + } + } + + return 0; +}; +arch_initcall(gef_gpio_init); + +MODULE_DESCRIPTION("GE I/O FPGA GPIO driver"); +MODULE_AUTHOR("Martyn Welch <martyn.welch@ge.com"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpio/gpio-generic.c b/drivers/gpio/gpio-generic.c new file mode 100644 index 00000000..e38dd0c3 --- /dev/null +++ b/drivers/gpio/gpio-generic.c @@ -0,0 +1,533 @@ +/* + * Generic driver for memory-mapped GPIO controllers. + * + * Copyright 2008 MontaVista Software, Inc. + * Copyright 2008,2010 Anton Vorontsov <cbouatmailru@gmail.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. + * + * ....``.```~~~~````.`.`.`.`.```````'',,,.........`````......`....... + * ...`` ```````.. + * ..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 <linux/init.h> +#include <linux/err.h> +#include <linux/bug.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/compiler.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/log2.h> +#include <linux/ioport.h> +#include <linux/io.h> +#include <linux/gpio.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/mod_devicetable.h> +#include <linux/basic_mmio_gpio.h> + +static void bgpio_write8(void __iomem *reg, unsigned long data) +{ + writeb(data, reg); +} + +static unsigned long bgpio_read8(void __iomem *reg) +{ + return readb(reg); +} + +static void bgpio_write16(void __iomem *reg, unsigned long data) +{ + writew(data, reg); +} + +static unsigned long bgpio_read16(void __iomem *reg) +{ + return readw(reg); +} + +static void bgpio_write32(void __iomem *reg, unsigned long data) +{ + writel(data, reg); +} + +static unsigned long bgpio_read32(void __iomem *reg) +{ + return readl(reg); +} + +#if BITS_PER_LONG >= 64 +static void bgpio_write64(void __iomem *reg, unsigned long data) +{ + writeq(data, reg); +} + +static unsigned long bgpio_read64(void __iomem *reg) +{ + return readq(reg); +} +#endif /* BITS_PER_LONG >= 64 */ + +static unsigned long bgpio_pin2mask(struct bgpio_chip *bgc, unsigned int pin) +{ + return 1 << pin; +} + +static unsigned long bgpio_pin2mask_be(struct bgpio_chip *bgc, + unsigned int pin) +{ + return 1 << (bgc->bits - 1 - pin); +} + +static int bgpio_get(struct gpio_chip *gc, unsigned int gpio) +{ + struct bgpio_chip *bgc = to_bgpio_chip(gc); + + return bgc->read_reg(bgc->reg_dat) & bgc->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 = bgc->pin2mask(bgc, gpio); + unsigned long flags; + + spin_lock_irqsave(&bgc->lock, flags); + + if (val) + bgc->data |= mask; + else + bgc->data &= ~mask; + + bgc->write_reg(bgc->reg_dat, bgc->data); + + spin_unlock_irqrestore(&bgc->lock, flags); +} + +static void bgpio_set_with_clear(struct gpio_chip *gc, unsigned int gpio, + int val) +{ + struct bgpio_chip *bgc = to_bgpio_chip(gc); + unsigned long mask = bgc->pin2mask(bgc, gpio); + + if (val) + bgc->write_reg(bgc->reg_set, mask); + else + bgc->write_reg(bgc->reg_clr, mask); +} + +static void bgpio_set_set(struct gpio_chip *gc, unsigned int gpio, int val) +{ + struct bgpio_chip *bgc = to_bgpio_chip(gc); + unsigned long mask = bgc->pin2mask(bgc, gpio); + unsigned long flags; + + spin_lock_irqsave(&bgc->lock, flags); + + if (val) + bgc->data |= mask; + else + bgc->data &= ~mask; + + bgc->write_reg(bgc->reg_set, bgc->data); + + spin_unlock_irqrestore(&bgc->lock, flags); +} + +static int bgpio_simple_dir_in(struct gpio_chip *gc, unsigned int gpio) +{ + return 0; +} + +static int bgpio_simple_dir_out(struct gpio_chip *gc, unsigned int gpio, + int val) +{ + gc->set(gc, gpio, val); + + return 0; +} + +static int bgpio_dir_in(struct gpio_chip *gc, unsigned int gpio) +{ + struct bgpio_chip *bgc = to_bgpio_chip(gc); + unsigned long flags; + + spin_lock_irqsave(&bgc->lock, flags); + + bgc->dir &= ~bgc->pin2mask(bgc, gpio); + bgc->write_reg(bgc->reg_dir, bgc->dir); + + spin_unlock_irqrestore(&bgc->lock, flags); + + return 0; +} + +static int bgpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val) +{ + struct bgpio_chip *bgc = to_bgpio_chip(gc); + unsigned long flags; + + gc->set(gc, gpio, val); + + spin_lock_irqsave(&bgc->lock, flags); + + bgc->dir |= bgc->pin2mask(bgc, gpio); + bgc->write_reg(bgc->reg_dir, bgc->dir); + + spin_unlock_irqrestore(&bgc->lock, flags); + + return 0; +} + +static int bgpio_dir_in_inv(struct gpio_chip *gc, unsigned int gpio) +{ + struct bgpio_chip *bgc = to_bgpio_chip(gc); + unsigned long flags; + + spin_lock_irqsave(&bgc->lock, flags); + + bgc->dir |= bgc->pin2mask(bgc, gpio); + bgc->write_reg(bgc->reg_dir, bgc->dir); + + spin_unlock_irqrestore(&bgc->lock, flags); + + return 0; +} + +static int bgpio_dir_out_inv(struct gpio_chip *gc, unsigned int gpio, int val) +{ + struct bgpio_chip *bgc = to_bgpio_chip(gc); + unsigned long flags; + + gc->set(gc, gpio, val); + + spin_lock_irqsave(&bgc->lock, flags); + + bgc->dir &= ~bgc->pin2mask(bgc, gpio); + bgc->write_reg(bgc->reg_dir, bgc->dir); + + spin_unlock_irqrestore(&bgc->lock, flags); + + return 0; +} + +static int bgpio_setup_accessors(struct device *dev, + struct bgpio_chip *bgc, + bool be) +{ + + switch (bgc->bits) { + case 8: + bgc->read_reg = bgpio_read8; + bgc->write_reg = bgpio_write8; + break; + case 16: + bgc->read_reg = bgpio_read16; + bgc->write_reg = bgpio_write16; + break; + case 32: + bgc->read_reg = bgpio_read32; + bgc->write_reg = bgpio_write32; + break; +#if BITS_PER_LONG >= 64 + case 64: + bgc->read_reg = bgpio_read64; + bgc->write_reg = bgpio_write64; + break; +#endif /* BITS_PER_LONG >= 64 */ + default: + dev_err(dev, "unsupported data width %u bits\n", bgc->bits); + return -EINVAL; + } + + bgc->pin2mask = be ? bgpio_pin2mask_be : bgpio_pin2mask; + + return 0; +} + +/* + * Create the device and allocate the resources. For setting GPIO's there are + * three supported configurations: + * + * - single input/output register resource (named "dat"). + * - set/clear pair (named "set" and "clr"). + * - single output register resource and single input resource ("set" and + * dat"). + * + * For the single output register, this drives a 1 by setting a bit and a zero + * by clearing a bit. For the set clr pair, this drives a 1 by setting a bit + * in the set register and clears it by setting a bit in the clear register. + * The configuration is detected by which resources are present. + * + * For setting the GPIO direction, there are three supported configurations: + * + * - simple bidirection GPIO that requires no configuration. + * - an output direction register (named "dirout") where a 1 bit + * indicates the GPIO is an output. + * - an input direction register (named "dirin") where a 1 bit indicates + * the GPIO is an input. + */ +static int bgpio_setup_io(struct bgpio_chip *bgc, + void __iomem *dat, + void __iomem *set, + void __iomem *clr) +{ + + bgc->reg_dat = dat; + if (!bgc->reg_dat) + return -EINVAL; + + if (set && clr) { + bgc->reg_set = set; + bgc->reg_clr = clr; + bgc->gc.set = bgpio_set_with_clear; + } else if (set && !clr) { + bgc->reg_set = set; + bgc->gc.set = bgpio_set_set; + } else { + bgc->gc.set = bgpio_set; + } + + bgc->gc.get = bgpio_get; + + return 0; +} + +static int bgpio_setup_direction(struct bgpio_chip *bgc, + void __iomem *dirout, + void __iomem *dirin) +{ + if (dirout && dirin) { + return -EINVAL; + } else if (dirout) { + bgc->reg_dir = dirout; + bgc->gc.direction_output = bgpio_dir_out; + bgc->gc.direction_input = bgpio_dir_in; + } else if (dirin) { + bgc->reg_dir = dirin; + bgc->gc.direction_output = bgpio_dir_out_inv; + bgc->gc.direction_input = bgpio_dir_in_inv; + } else { + bgc->gc.direction_output = bgpio_simple_dir_out; + bgc->gc.direction_input = bgpio_simple_dir_in; + } + + return 0; +} + +int bgpio_remove(struct bgpio_chip *bgc) +{ + int err = gpiochip_remove(&bgc->gc); + + kfree(bgc); + + return err; +} +EXPORT_SYMBOL_GPL(bgpio_remove); + +int bgpio_init(struct bgpio_chip *bgc, struct device *dev, + unsigned long sz, void __iomem *dat, void __iomem *set, + void __iomem *clr, void __iomem *dirout, void __iomem *dirin, + bool big_endian) +{ + int ret; + + if (!is_power_of_2(sz)) + return -EINVAL; + + bgc->bits = sz * 8; + if (bgc->bits > BITS_PER_LONG) + return -EINVAL; + + spin_lock_init(&bgc->lock); + bgc->gc.dev = dev; + bgc->gc.label = dev_name(dev); + bgc->gc.base = -1; + bgc->gc.ngpio = bgc->bits; + + ret = bgpio_setup_io(bgc, dat, set, clr); + if (ret) + return ret; + + ret = bgpio_setup_accessors(dev, bgc, big_endian); + if (ret) + return ret; + + ret = bgpio_setup_direction(bgc, dirout, dirin); + if (ret) + return ret; + + bgc->data = bgc->read_reg(bgc->reg_dat); + + return ret; +} +EXPORT_SYMBOL_GPL(bgpio_init); + +#ifdef CONFIG_GPIO_GENERIC_PLATFORM + +static void __iomem *bgpio_map(struct platform_device *pdev, + const char *name, + resource_size_t sane_sz, + int *err) +{ + struct device *dev = &pdev->dev; + struct resource *r; + resource_size_t start; + resource_size_t sz; + void __iomem *ret; + + *err = 0; + + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); + if (!r) + return NULL; + + sz = resource_size(r); + if (sz != sane_sz) { + *err = -EINVAL; + return NULL; + } + + start = r->start; + if (!devm_request_mem_region(dev, start, sz, r->name)) { + *err = -EBUSY; + return NULL; + } + + ret = devm_ioremap(dev, start, sz); + if (!ret) { + *err = -ENOMEM; + return NULL; + } + + return ret; +} + +static int __devinit bgpio_pdev_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *r; + void __iomem *dat; + void __iomem *set; + void __iomem *clr; + void __iomem *dirout; + void __iomem *dirin; + unsigned long sz; + bool be; + int err; + struct bgpio_chip *bgc; + struct bgpio_pdata *pdata = dev_get_platdata(dev); + + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dat"); + if (!r) + return -EINVAL; + + sz = resource_size(r); + + dat = bgpio_map(pdev, "dat", sz, &err); + if (!dat) + return err ? err : -EINVAL; + + set = bgpio_map(pdev, "set", sz, &err); + if (err) + return err; + + clr = bgpio_map(pdev, "clr", sz, &err); + if (err) + return err; + + dirout = bgpio_map(pdev, "dirout", sz, &err); + if (err) + return err; + + dirin = bgpio_map(pdev, "dirin", sz, &err); + if (err) + return err; + + be = !strcmp(platform_get_device_id(pdev)->name, "basic-mmio-gpio-be"); + + bgc = devm_kzalloc(&pdev->dev, sizeof(*bgc), GFP_KERNEL); + if (!bgc) + return -ENOMEM; + + err = bgpio_init(bgc, dev, sz, dat, set, clr, dirout, dirin, be); + if (err) + return err; + + if (pdata) { + bgc->gc.base = pdata->base; + if (pdata->ngpio > 0) + bgc->gc.ngpio = pdata->ngpio; + } + + platform_set_drvdata(pdev, bgc); + + return gpiochip_add(&bgc->gc); +} + +static int __devexit bgpio_pdev_remove(struct platform_device *pdev) +{ + struct bgpio_chip *bgc = platform_get_drvdata(pdev); + + return bgpio_remove(bgc); +} + +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_pdev_probe, + .remove = __devexit_p(bgpio_pdev_remove), +}; + +module_platform_driver(bgpio_driver); + +#endif /* CONFIG_GPIO_GENERIC_PLATFORM */ + +MODULE_DESCRIPTION("Driver for basic memory-mapped GPIO controllers"); +MODULE_AUTHOR("Anton Vorontsov <cbouatmailru@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpio/gpio-it8761e.c b/drivers/gpio/gpio-it8761e.c new file mode 100644 index 00000000..278b8131 --- /dev/null +++ b/drivers/gpio/gpio-it8761e.c @@ -0,0 +1,234 @@ +/* + * GPIO interface for IT8761E Super I/O chip + * + * Author: Denis Turischev <denis@compulab.co.il> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 2 as published + * by the Free Software Foundation. + * + * 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; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/errno.h> +#include <linux/ioport.h> + +#include <linux/gpio.h> + +#define SIO_CHIP_ID 0x8761 +#define CHIP_ID_HIGH_BYTE 0x20 +#define CHIP_ID_LOW_BYTE 0x21 + +static u8 ports[2] = { 0x2e, 0x4e }; +static u8 port; + +static DEFINE_SPINLOCK(sio_lock); + +#define GPIO_NAME "it8761-gpio" +#define GPIO_BA_HIGH_BYTE 0x60 +#define GPIO_BA_LOW_BYTE 0x61 +#define GPIO_IOSIZE 4 +#define GPIO1X_IO 0xf0 +#define GPIO2X_IO 0xf1 + +static u16 gpio_ba; + +static u8 read_reg(u8 addr, u8 port) +{ + outb(addr, port); + return inb(port + 1); +} + +static void write_reg(u8 data, u8 addr, u8 port) +{ + outb(addr, port); + outb(data, port + 1); +} + +static void enter_conf_mode(u8 port) +{ + outb(0x87, port); + outb(0x61, port); + outb(0x55, port); + outb((port == 0x2e) ? 0x55 : 0xaa, port); +} + +static void exit_conf_mode(u8 port) +{ + outb(0x2, port); + outb(0x2, port + 1); +} + +static void enter_gpio_mode(u8 port) +{ + write_reg(0x2, 0x7, port); +} + +static int it8761e_gpio_get(struct gpio_chip *gc, unsigned gpio_num) +{ + u16 reg; + u8 bit; + + bit = gpio_num % 8; + reg = (gpio_num >= 8) ? gpio_ba + 1 : gpio_ba; + + return !!(inb(reg) & (1 << bit)); +} + +static int it8761e_gpio_direction_in(struct gpio_chip *gc, unsigned gpio_num) +{ + u8 curr_dirs; + u8 io_reg, bit; + + bit = gpio_num % 8; + io_reg = (gpio_num >= 8) ? GPIO2X_IO : GPIO1X_IO; + + spin_lock(&sio_lock); + + enter_conf_mode(port); + enter_gpio_mode(port); + + curr_dirs = read_reg(io_reg, port); + + if (curr_dirs & (1 << bit)) + write_reg(curr_dirs & ~(1 << bit), io_reg, port); + + exit_conf_mode(port); + + spin_unlock(&sio_lock); + return 0; +} + +static void it8761e_gpio_set(struct gpio_chip *gc, + unsigned gpio_num, int val) +{ + u8 curr_vals, bit; + u16 reg; + + bit = gpio_num % 8; + reg = (gpio_num >= 8) ? gpio_ba + 1 : gpio_ba; + + spin_lock(&sio_lock); + + curr_vals = inb(reg); + if (val) + outb(curr_vals | (1 << bit) , reg); + else + outb(curr_vals & ~(1 << bit), reg); + + spin_unlock(&sio_lock); +} + +static int it8761e_gpio_direction_out(struct gpio_chip *gc, + unsigned gpio_num, int val) +{ + u8 curr_dirs, io_reg, bit; + + bit = gpio_num % 8; + io_reg = (gpio_num >= 8) ? GPIO2X_IO : GPIO1X_IO; + + it8761e_gpio_set(gc, gpio_num, val); + + spin_lock(&sio_lock); + + enter_conf_mode(port); + enter_gpio_mode(port); + + curr_dirs = read_reg(io_reg, port); + + if (!(curr_dirs & (1 << bit))) + write_reg(curr_dirs | (1 << bit), io_reg, port); + + exit_conf_mode(port); + + spin_unlock(&sio_lock); + return 0; +} + +static struct gpio_chip it8761e_gpio_chip = { + .label = GPIO_NAME, + .owner = THIS_MODULE, + .get = it8761e_gpio_get, + .direction_input = it8761e_gpio_direction_in, + .set = it8761e_gpio_set, + .direction_output = it8761e_gpio_direction_out, +}; + +static int __init it8761e_gpio_init(void) +{ + int i, id, err; + + /* chip and port detection */ + for (i = 0; i < ARRAY_SIZE(ports); i++) { + spin_lock(&sio_lock); + enter_conf_mode(ports[i]); + + id = (read_reg(CHIP_ID_HIGH_BYTE, ports[i]) << 8) + + read_reg(CHIP_ID_LOW_BYTE, ports[i]); + + exit_conf_mode(ports[i]); + spin_unlock(&sio_lock); + + if (id == SIO_CHIP_ID) { + port = ports[i]; + break; + } + } + + if (!port) + return -ENODEV; + + /* fetch GPIO base address */ + enter_conf_mode(port); + enter_gpio_mode(port); + gpio_ba = (read_reg(GPIO_BA_HIGH_BYTE, port) << 8) + + read_reg(GPIO_BA_LOW_BYTE, port); + exit_conf_mode(port); + + if (!request_region(gpio_ba, GPIO_IOSIZE, GPIO_NAME)) + return -EBUSY; + + it8761e_gpio_chip.base = -1; + it8761e_gpio_chip.ngpio = 16; + + err = gpiochip_add(&it8761e_gpio_chip); + if (err < 0) + goto gpiochip_add_err; + + return 0; + +gpiochip_add_err: + release_region(gpio_ba, GPIO_IOSIZE); + gpio_ba = 0; + return err; +} + +static void __exit it8761e_gpio_exit(void) +{ + if (gpio_ba) { + int ret = gpiochip_remove(&it8761e_gpio_chip); + + WARN(ret, "%s(): gpiochip_remove() failed, ret=%d\n", + __func__, ret); + + release_region(gpio_ba, GPIO_IOSIZE); + gpio_ba = 0; + } +} +module_init(it8761e_gpio_init); +module_exit(it8761e_gpio_exit); + +MODULE_AUTHOR("Denis Turischev <denis@compulab.co.il>"); +MODULE_DESCRIPTION("GPIO interface for IT8761E Super I/O chip"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpio/gpio-janz-ttl.c b/drivers/gpio/gpio-janz-ttl.c new file mode 100644 index 00000000..f2f000dd --- /dev/null +++ b/drivers/gpio/gpio-janz-ttl.c @@ -0,0 +1,245 @@ +/* + * Janz MODULbus VMOD-TTL GPIO Driver + * + * Copyright (c) 2010 Ira W. Snyder <iws@ovro.caltech.edu> + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/gpio.h> +#include <linux/slab.h> + +#include <linux/mfd/janz.h> + +#define DRV_NAME "janz-ttl" + +#define PORTA_DIRECTION 0x23 +#define PORTB_DIRECTION 0x2B +#define PORTC_DIRECTION 0x06 +#define PORTA_IOCTL 0x24 +#define PORTB_IOCTL 0x2C +#define PORTC_IOCTL 0x07 + +#define MASTER_INT_CTL 0x00 +#define MASTER_CONF_CTL 0x01 + +#define CONF_PAE (1 << 2) +#define CONF_PBE (1 << 7) +#define CONF_PCE (1 << 4) + +struct ttl_control_regs { + __be16 portc; + __be16 portb; + __be16 porta; + __be16 control; +}; + +struct ttl_module { + struct gpio_chip gpio; + + /* base address of registers */ + struct ttl_control_regs __iomem *regs; + + u8 portc_shadow; + u8 portb_shadow; + u8 porta_shadow; + + spinlock_t lock; +}; + +static int ttl_get_value(struct gpio_chip *gpio, unsigned offset) +{ + struct ttl_module *mod = dev_get_drvdata(gpio->dev); + u8 *shadow; + int ret; + + if (offset < 8) { + shadow = &mod->porta_shadow; + } else if (offset < 16) { + shadow = &mod->portb_shadow; + offset -= 8; + } else { + shadow = &mod->portc_shadow; + offset -= 16; + } + + spin_lock(&mod->lock); + ret = *shadow & (1 << offset); + spin_unlock(&mod->lock); + return ret; +} + +static void ttl_set_value(struct gpio_chip *gpio, unsigned offset, int value) +{ + struct ttl_module *mod = dev_get_drvdata(gpio->dev); + void __iomem *port; + u8 *shadow; + + if (offset < 8) { + port = &mod->regs->porta; + shadow = &mod->porta_shadow; + } else if (offset < 16) { + port = &mod->regs->portb; + shadow = &mod->portb_shadow; + offset -= 8; + } else { + port = &mod->regs->portc; + shadow = &mod->portc_shadow; + offset -= 16; + } + + spin_lock(&mod->lock); + if (value) + *shadow |= (1 << offset); + else + *shadow &= ~(1 << offset); + + iowrite16be(*shadow, port); + spin_unlock(&mod->lock); +} + +static void __devinit ttl_write_reg(struct ttl_module *mod, u8 reg, u16 val) +{ + iowrite16be(reg, &mod->regs->control); + iowrite16be(val, &mod->regs->control); +} + +static void __devinit ttl_setup_device(struct ttl_module *mod) +{ + /* reset the device to a known state */ + iowrite16be(0x0000, &mod->regs->control); + iowrite16be(0x0001, &mod->regs->control); + iowrite16be(0x0000, &mod->regs->control); + + /* put all ports in open-drain mode */ + ttl_write_reg(mod, PORTA_IOCTL, 0x00ff); + ttl_write_reg(mod, PORTB_IOCTL, 0x00ff); + ttl_write_reg(mod, PORTC_IOCTL, 0x000f); + + /* set all ports as outputs */ + ttl_write_reg(mod, PORTA_DIRECTION, 0x0000); + ttl_write_reg(mod, PORTB_DIRECTION, 0x0000); + ttl_write_reg(mod, PORTC_DIRECTION, 0x0000); + + /* set all ports to drive zeroes */ + iowrite16be(0x0000, &mod->regs->porta); + iowrite16be(0x0000, &mod->regs->portb); + iowrite16be(0x0000, &mod->regs->portc); + + /* enable all ports */ + ttl_write_reg(mod, MASTER_CONF_CTL, CONF_PAE | CONF_PBE | CONF_PCE); +} + +static int __devinit ttl_probe(struct platform_device *pdev) +{ + struct janz_platform_data *pdata; + struct device *dev = &pdev->dev; + struct ttl_module *mod; + struct gpio_chip *gpio; + struct resource *res; + int ret; + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(dev, "no platform data\n"); + ret = -ENXIO; + goto out_return; + } + + mod = kzalloc(sizeof(*mod), GFP_KERNEL); + if (!mod) { + dev_err(dev, "unable to allocate private data\n"); + ret = -ENOMEM; + goto out_return; + } + + platform_set_drvdata(pdev, mod); + spin_lock_init(&mod->lock); + + /* get access to the MODULbus registers for this module */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "MODULbus registers not found\n"); + ret = -ENODEV; + goto out_free_mod; + } + + mod->regs = ioremap(res->start, resource_size(res)); + if (!mod->regs) { + dev_err(dev, "MODULbus registers not ioremap\n"); + ret = -ENOMEM; + goto out_free_mod; + } + + ttl_setup_device(mod); + + /* Initialize the GPIO data structures */ + gpio = &mod->gpio; + gpio->dev = &pdev->dev; + gpio->label = pdev->name; + gpio->get = ttl_get_value; + gpio->set = ttl_set_value; + gpio->owner = THIS_MODULE; + + /* request dynamic allocation */ + gpio->base = -1; + gpio->ngpio = 20; + + ret = gpiochip_add(gpio); + if (ret) { + dev_err(dev, "unable to add GPIO chip\n"); + goto out_iounmap_regs; + } + + return 0; + +out_iounmap_regs: + iounmap(mod->regs); +out_free_mod: + kfree(mod); +out_return: + return ret; +} + +static int __devexit ttl_remove(struct platform_device *pdev) +{ + struct ttl_module *mod = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + int ret; + + ret = gpiochip_remove(&mod->gpio); + if (ret) { + dev_err(dev, "unable to remove GPIO chip\n"); + return ret; + } + + iounmap(mod->regs); + kfree(mod); + return 0; +} + +static struct platform_driver ttl_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + .probe = ttl_probe, + .remove = __devexit_p(ttl_remove), +}; + +module_platform_driver(ttl_driver); + +MODULE_AUTHOR("Ira W. Snyder <iws@ovro.caltech.edu>"); +MODULE_DESCRIPTION("Janz MODULbus VMOD-TTL Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:janz-ttl"); diff --git a/drivers/gpio/gpio-ks8695.c b/drivers/gpio/gpio-ks8695.c new file mode 100644 index 00000000..a3ac66ea --- /dev/null +++ b/drivers/gpio/gpio-ks8695.c @@ -0,0 +1,319 @@ +/* + * arch/arm/mach-ks8695/gpio.c + * + * Copyright (C) 2006 Andrew Victor + * Updated to GPIOLIB, Copyright 2008 Simtec Electronics + * Daniel Silverstone <dsilvers@simtec.co.uk> + * + * 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. + * + * 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/gpio.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/init.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/module.h> +#include <linux/io.h> + +#include <mach/hardware.h> +#include <asm/mach/irq.h> + +#include <mach/regs-gpio.h> +#include <mach/gpio-ks8695.h> + +/* + * Configure a GPIO line for either GPIO function, or its internal + * function (Interrupt, Timer, etc). + */ +static void ks8695_gpio_mode(unsigned int pin, short gpio) +{ + unsigned int enable[] = { IOPC_IOEINT0EN, IOPC_IOEINT1EN, IOPC_IOEINT2EN, IOPC_IOEINT3EN, IOPC_IOTIM0EN, IOPC_IOTIM1EN }; + unsigned long x, flags; + + if (pin > KS8695_GPIO_5) /* only GPIO 0..5 have internal functions */ + return; + + local_irq_save(flags); + + x = __raw_readl(KS8695_GPIO_VA + KS8695_IOPC); + if (gpio) /* GPIO: set bit to 0 */ + x &= ~enable[pin]; + else /* Internal function: set bit to 1 */ + x |= enable[pin]; + __raw_writel(x, KS8695_GPIO_VA + KS8695_IOPC); + + local_irq_restore(flags); +} + + +static unsigned short gpio_irq[] = { KS8695_IRQ_EXTERN0, KS8695_IRQ_EXTERN1, KS8695_IRQ_EXTERN2, KS8695_IRQ_EXTERN3 }; + +/* + * Configure GPIO pin as external interrupt source. + */ +int ks8695_gpio_interrupt(unsigned int pin, unsigned int type) +{ + unsigned long x, flags; + + if (pin > KS8695_GPIO_3) /* only GPIO 0..3 can generate IRQ */ + return -EINVAL; + + local_irq_save(flags); + + /* set pin as input */ + x = __raw_readl(KS8695_GPIO_VA + KS8695_IOPM); + x &= ~IOPM(pin); + __raw_writel(x, KS8695_GPIO_VA + KS8695_IOPM); + + local_irq_restore(flags); + + /* Set IRQ triggering type */ + irq_set_irq_type(gpio_irq[pin], type); + + /* enable interrupt mode */ + ks8695_gpio_mode(pin, 0); + + return 0; +} +EXPORT_SYMBOL(ks8695_gpio_interrupt); + + + +/* .... Generic GPIO interface .............................................. */ + +/* + * Configure the GPIO line as an input. + */ +static int ks8695_gpio_direction_input(struct gpio_chip *gc, unsigned int pin) +{ + unsigned long x, flags; + + if (pin > KS8695_GPIO_15) + return -EINVAL; + + /* set pin to GPIO mode */ + ks8695_gpio_mode(pin, 1); + + local_irq_save(flags); + + /* set pin as input */ + x = __raw_readl(KS8695_GPIO_VA + KS8695_IOPM); + x &= ~IOPM(pin); + __raw_writel(x, KS8695_GPIO_VA + KS8695_IOPM); + + local_irq_restore(flags); + + return 0; +} + + +/* + * Configure the GPIO line as an output, with default state. + */ +static int ks8695_gpio_direction_output(struct gpio_chip *gc, + unsigned int pin, int state) +{ + unsigned long x, flags; + + if (pin > KS8695_GPIO_15) + return -EINVAL; + + /* set pin to GPIO mode */ + ks8695_gpio_mode(pin, 1); + + local_irq_save(flags); + + /* set line state */ + x = __raw_readl(KS8695_GPIO_VA + KS8695_IOPD); + if (state) + x |= IOPD(pin); + else + x &= ~IOPD(pin); + __raw_writel(x, KS8695_GPIO_VA + KS8695_IOPD); + + /* set pin as output */ + x = __raw_readl(KS8695_GPIO_VA + KS8695_IOPM); + x |= IOPM(pin); + __raw_writel(x, KS8695_GPIO_VA + KS8695_IOPM); + + local_irq_restore(flags); + + return 0; +} + + +/* + * Set the state of an output GPIO line. + */ +static void ks8695_gpio_set_value(struct gpio_chip *gc, + unsigned int pin, int state) +{ + unsigned long x, flags; + + if (pin > KS8695_GPIO_15) + return; + + local_irq_save(flags); + + /* set output line state */ + x = __raw_readl(KS8695_GPIO_VA + KS8695_IOPD); + if (state) + x |= IOPD(pin); + else + x &= ~IOPD(pin); + __raw_writel(x, KS8695_GPIO_VA + KS8695_IOPD); + + local_irq_restore(flags); +} + + +/* + * Read the state of a GPIO line. + */ +static int ks8695_gpio_get_value(struct gpio_chip *gc, unsigned int pin) +{ + unsigned long x; + + if (pin > KS8695_GPIO_15) + return -EINVAL; + + x = __raw_readl(KS8695_GPIO_VA + KS8695_IOPD); + return (x & IOPD(pin)) != 0; +} + + +/* + * Map GPIO line to IRQ number. + */ +static int ks8695_gpio_to_irq(struct gpio_chip *gc, unsigned int pin) +{ + if (pin > KS8695_GPIO_3) /* only GPIO 0..3 can generate IRQ */ + return -EINVAL; + + return gpio_irq[pin]; +} + +/* + * Map IRQ number to GPIO line. + */ +int irq_to_gpio(unsigned int irq) +{ + if ((irq < KS8695_IRQ_EXTERN0) || (irq > KS8695_IRQ_EXTERN3)) + return -EINVAL; + + return (irq - KS8695_IRQ_EXTERN0); +} +EXPORT_SYMBOL(irq_to_gpio); + +/* GPIOLIB interface */ + +static struct gpio_chip ks8695_gpio_chip = { + .label = "KS8695", + .direction_input = ks8695_gpio_direction_input, + .direction_output = ks8695_gpio_direction_output, + .get = ks8695_gpio_get_value, + .set = ks8695_gpio_set_value, + .to_irq = ks8695_gpio_to_irq, + .base = 0, + .ngpio = 16, + .can_sleep = 0, +}; + +/* Register the GPIOs */ +void ks8695_register_gpios(void) +{ + if (gpiochip_add(&ks8695_gpio_chip)) + printk(KERN_ERR "Unable to register core GPIOs\n"); +} + +/* .... Debug interface ..................................................... */ + +#ifdef CONFIG_DEBUG_FS + +static int ks8695_gpio_show(struct seq_file *s, void *unused) +{ + unsigned int enable[] = { IOPC_IOEINT0EN, IOPC_IOEINT1EN, IOPC_IOEINT2EN, IOPC_IOEINT3EN, IOPC_IOTIM0EN, IOPC_IOTIM1EN }; + unsigned int intmask[] = { IOPC_IOEINT0TM, IOPC_IOEINT1TM, IOPC_IOEINT2TM, IOPC_IOEINT3TM }; + unsigned long mode, ctrl, data; + int i; + + mode = __raw_readl(KS8695_GPIO_VA + KS8695_IOPM); + ctrl = __raw_readl(KS8695_GPIO_VA + KS8695_IOPC); + data = __raw_readl(KS8695_GPIO_VA + KS8695_IOPD); + + seq_printf(s, "Pin\tI/O\tFunction\tState\n\n"); + + for (i = KS8695_GPIO_0; i <= KS8695_GPIO_15 ; i++) { + seq_printf(s, "%i:\t", i); + + seq_printf(s, "%s\t", (mode & IOPM(i)) ? "Output" : "Input"); + + if (i <= KS8695_GPIO_3) { + if (ctrl & enable[i]) { + seq_printf(s, "EXT%i ", i); + + switch ((ctrl & intmask[i]) >> (4 * i)) { + case IOPC_TM_LOW: + seq_printf(s, "(Low)"); break; + case IOPC_TM_HIGH: + seq_printf(s, "(High)"); break; + case IOPC_TM_RISING: + seq_printf(s, "(Rising)"); break; + case IOPC_TM_FALLING: + seq_printf(s, "(Falling)"); break; + case IOPC_TM_EDGE: + seq_printf(s, "(Edges)"); break; + } + } + else + seq_printf(s, "GPIO\t"); + } + else if (i <= KS8695_GPIO_5) { + if (ctrl & enable[i]) + seq_printf(s, "TOUT%i\t", i - KS8695_GPIO_4); + else + seq_printf(s, "GPIO\t"); + } + else + seq_printf(s, "GPIO\t"); + + seq_printf(s, "\t"); + + seq_printf(s, "%i\n", (data & IOPD(i)) ? 1 : 0); + } + return 0; +} + +static int ks8695_gpio_open(struct inode *inode, struct file *file) +{ + return single_open(file, ks8695_gpio_show, NULL); +} + +static const struct file_operations ks8695_gpio_operations = { + .open = ks8695_gpio_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int __init ks8695_gpio_debugfs_init(void) +{ + /* /sys/kernel/debug/ks8695_gpio */ + (void) debugfs_create_file("ks8695_gpio", S_IFREG | S_IRUGO, NULL, NULL, &ks8695_gpio_operations); + return 0; +} +postcore_initcall(ks8695_gpio_debugfs_init); + +#endif diff --git a/drivers/gpio/gpio-langwell.c b/drivers/gpio/gpio-langwell.c new file mode 100644 index 00000000..00692e89 --- /dev/null +++ b/drivers/gpio/gpio-langwell.c @@ -0,0 +1,485 @@ +/* + * Moorestown platform Langwell chip GPIO driver + * + * Copyright (c) 2008 - 2009, Intel Corporation. + * + * 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. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* Supports: + * Moorestown platform Langwell chip. + * Medfield platform Penwell chip. + * Whitney point. + */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/stddef.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/irq.h> +#include <linux/io.h> +#include <linux/gpio.h> +#include <linux/slab.h> +#include <linux/pm_runtime.h> + +/* + * Langwell chip has 64 pins and thus there are 2 32bit registers to control + * each feature, while Penwell chip has 96 pins for each block, and need 3 32bit + * registers to control them, so we only define the order here instead of a + * structure, to get a bit offset for a pin (use GPDR as an example): + * + * nreg = ngpio / 32; + * reg = offset / 32; + * bit = offset % 32; + * reg_addr = reg_base + GPDR * nreg * 4 + reg * 4; + * + * so the bit of reg_addr is to control pin offset's GPDR feature +*/ + +enum GPIO_REG { + GPLR = 0, /* pin level read-only */ + GPDR, /* pin direction */ + GPSR, /* pin set */ + GPCR, /* pin clear */ + GRER, /* rising edge detect */ + GFER, /* falling edge detect */ + GEDR, /* edge detect result */ + GAFR, /* alt function */ +}; + +struct lnw_gpio { + struct gpio_chip chip; + void *reg_base; + spinlock_t lock; + unsigned irq_base; + struct pci_dev *pdev; +}; + +static void __iomem *gpio_reg(struct gpio_chip *chip, unsigned offset, + enum GPIO_REG reg_type) +{ + struct lnw_gpio *lnw = container_of(chip, struct lnw_gpio, chip); + unsigned nreg = chip->ngpio / 32; + u8 reg = offset / 32; + void __iomem *ptr; + + ptr = (void __iomem *)(lnw->reg_base + reg_type * nreg * 4 + reg * 4); + return ptr; +} + +static void __iomem *gpio_reg_2bit(struct gpio_chip *chip, unsigned offset, + enum GPIO_REG reg_type) +{ + struct lnw_gpio *lnw = container_of(chip, struct lnw_gpio, chip); + unsigned nreg = chip->ngpio / 32; + u8 reg = offset / 16; + void __iomem *ptr; + + ptr = (void __iomem *)(lnw->reg_base + reg_type * nreg * 4 + reg * 4); + return ptr; +} + +static int lnw_gpio_request(struct gpio_chip *chip, unsigned offset) +{ + void __iomem *gafr = gpio_reg_2bit(chip, offset, GAFR); + u32 value = readl(gafr); + int shift = (offset % 16) << 1, af = (value >> shift) & 3; + + if (af) { + value &= ~(3 << shift); + writel(value, gafr); + } + return 0; +} + +static int lnw_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + void __iomem *gplr = gpio_reg(chip, offset, GPLR); + + return readl(gplr) & BIT(offset % 32); +} + +static void lnw_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + void __iomem *gpsr, *gpcr; + + if (value) { + gpsr = gpio_reg(chip, offset, GPSR); + writel(BIT(offset % 32), gpsr); + } else { + gpcr = gpio_reg(chip, offset, GPCR); + writel(BIT(offset % 32), gpcr); + } +} + +static int lnw_gpio_direction_input(struct gpio_chip *chip, unsigned offset) +{ + struct lnw_gpio *lnw = container_of(chip, struct lnw_gpio, chip); + void __iomem *gpdr = gpio_reg(chip, offset, GPDR); + u32 value; + unsigned long flags; + + if (lnw->pdev) + pm_runtime_get(&lnw->pdev->dev); + + spin_lock_irqsave(&lnw->lock, flags); + value = readl(gpdr); + value &= ~BIT(offset % 32); + writel(value, gpdr); + spin_unlock_irqrestore(&lnw->lock, flags); + + if (lnw->pdev) + pm_runtime_put(&lnw->pdev->dev); + + return 0; +} + +static int lnw_gpio_direction_output(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct lnw_gpio *lnw = container_of(chip, struct lnw_gpio, chip); + void __iomem *gpdr = gpio_reg(chip, offset, GPDR); + unsigned long flags; + + lnw_gpio_set(chip, offset, value); + + if (lnw->pdev) + pm_runtime_get(&lnw->pdev->dev); + + spin_lock_irqsave(&lnw->lock, flags); + value = readl(gpdr); + value |= BIT(offset % 32); + writel(value, gpdr); + spin_unlock_irqrestore(&lnw->lock, flags); + + if (lnw->pdev) + pm_runtime_put(&lnw->pdev->dev); + + return 0; +} + +static int lnw_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct lnw_gpio *lnw = container_of(chip, struct lnw_gpio, chip); + return lnw->irq_base + offset; +} + +static int lnw_irq_type(struct irq_data *d, unsigned type) +{ + struct lnw_gpio *lnw = irq_data_get_irq_chip_data(d); + u32 gpio = d->irq - lnw->irq_base; + unsigned long flags; + u32 value; + void __iomem *grer = gpio_reg(&lnw->chip, gpio, GRER); + void __iomem *gfer = gpio_reg(&lnw->chip, gpio, GFER); + + if (gpio >= lnw->chip.ngpio) + return -EINVAL; + + if (lnw->pdev) + pm_runtime_get(&lnw->pdev->dev); + + spin_lock_irqsave(&lnw->lock, flags); + if (type & IRQ_TYPE_EDGE_RISING) + value = readl(grer) | BIT(gpio % 32); + else + value = readl(grer) & (~BIT(gpio % 32)); + writel(value, grer); + + if (type & IRQ_TYPE_EDGE_FALLING) + value = readl(gfer) | BIT(gpio % 32); + else + value = readl(gfer) & (~BIT(gpio % 32)); + writel(value, gfer); + spin_unlock_irqrestore(&lnw->lock, flags); + + if (lnw->pdev) + pm_runtime_put(&lnw->pdev->dev); + + return 0; +} + +static void lnw_irq_unmask(struct irq_data *d) +{ +} + +static void lnw_irq_mask(struct irq_data *d) +{ +} + +static struct irq_chip lnw_irqchip = { + .name = "LNW-GPIO", + .irq_mask = lnw_irq_mask, + .irq_unmask = lnw_irq_unmask, + .irq_set_type = lnw_irq_type, +}; + +static DEFINE_PCI_DEVICE_TABLE(lnw_gpio_ids) = { /* pin number */ + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x080f), .driver_data = 64 }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x081f), .driver_data = 96 }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x081a), .driver_data = 96 }, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, lnw_gpio_ids); + +static void lnw_irq_handler(unsigned irq, struct irq_desc *desc) +{ + struct irq_data *data = irq_desc_get_irq_data(desc); + struct lnw_gpio *lnw = irq_data_get_irq_handler_data(data); + struct irq_chip *chip = irq_data_get_irq_chip(data); + u32 base, gpio, mask; + unsigned long pending; + void __iomem *gedr; + + /* check GPIO controller to check which pin triggered the interrupt */ + for (base = 0; base < lnw->chip.ngpio; base += 32) { + gedr = gpio_reg(&lnw->chip, base, GEDR); + pending = readl(gedr); + while (pending) { + gpio = __ffs(pending); + mask = BIT(gpio); + pending &= ~mask; + /* Clear before handling so we can't lose an edge */ + writel(mask, gedr); + generic_handle_irq(lnw->irq_base + base + gpio); + } + } + + chip->irq_eoi(data); +} + +#ifdef CONFIG_PM +static int lnw_gpio_runtime_resume(struct device *dev) +{ + return 0; +} + +static int lnw_gpio_runtime_suspend(struct device *dev) +{ + return 0; +} + +static int lnw_gpio_runtime_idle(struct device *dev) +{ + int err = pm_schedule_suspend(dev, 500); + + if (!err) + return 0; + + return -EBUSY; +} + +#else +#define lnw_gpio_runtime_suspend NULL +#define lnw_gpio_runtime_resume NULL +#define lnw_gpio_runtime_idle NULL +#endif + +static const struct dev_pm_ops lnw_gpio_pm_ops = { + .runtime_suspend = lnw_gpio_runtime_suspend, + .runtime_resume = lnw_gpio_runtime_resume, + .runtime_idle = lnw_gpio_runtime_idle, +}; + +static int __devinit lnw_gpio_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + void *base; + int i; + resource_size_t start, len; + struct lnw_gpio *lnw; + u32 irq_base; + u32 gpio_base; + int retval = 0; + + retval = pci_enable_device(pdev); + if (retval) + goto done; + + retval = pci_request_regions(pdev, "langwell_gpio"); + if (retval) { + dev_err(&pdev->dev, "error requesting resources\n"); + goto err2; + } + /* get the irq_base from bar1 */ + start = pci_resource_start(pdev, 1); + len = pci_resource_len(pdev, 1); + base = ioremap_nocache(start, len); + if (!base) { + dev_err(&pdev->dev, "error mapping bar1\n"); + goto err3; + } + irq_base = *(u32 *)base; + gpio_base = *((u32 *)base + 1); + /* release the IO mapping, since we already get the info from bar1 */ + iounmap(base); + /* get the register base from bar0 */ + start = pci_resource_start(pdev, 0); + len = pci_resource_len(pdev, 0); + base = ioremap_nocache(start, len); + if (!base) { + dev_err(&pdev->dev, "error mapping bar0\n"); + retval = -EFAULT; + goto err3; + } + + lnw = kzalloc(sizeof(struct lnw_gpio), GFP_KERNEL); + if (!lnw) { + dev_err(&pdev->dev, "can't allocate langwell_gpio chip data\n"); + retval = -ENOMEM; + goto err4; + } + lnw->reg_base = base; + lnw->irq_base = irq_base; + lnw->chip.label = dev_name(&pdev->dev); + lnw->chip.request = lnw_gpio_request; + lnw->chip.direction_input = lnw_gpio_direction_input; + lnw->chip.direction_output = lnw_gpio_direction_output; + lnw->chip.get = lnw_gpio_get; + lnw->chip.set = lnw_gpio_set; + lnw->chip.to_irq = lnw_gpio_to_irq; + lnw->chip.base = gpio_base; + lnw->chip.ngpio = id->driver_data; + lnw->chip.can_sleep = 0; + lnw->pdev = pdev; + pci_set_drvdata(pdev, lnw); + retval = gpiochip_add(&lnw->chip); + if (retval) { + dev_err(&pdev->dev, "langwell gpiochip_add error %d\n", retval); + goto err5; + } + irq_set_handler_data(pdev->irq, lnw); + irq_set_chained_handler(pdev->irq, lnw_irq_handler); + for (i = 0; i < lnw->chip.ngpio; i++) { + irq_set_chip_and_handler_name(i + lnw->irq_base, &lnw_irqchip, + handle_simple_irq, "demux"); + irq_set_chip_data(i + lnw->irq_base, lnw); + } + + spin_lock_init(&lnw->lock); + + pm_runtime_put_noidle(&pdev->dev); + pm_runtime_allow(&pdev->dev); + + goto done; +err5: + kfree(lnw); +err4: + iounmap(base); +err3: + pci_release_regions(pdev); +err2: + pci_disable_device(pdev); +done: + return retval; +} + +static struct pci_driver lnw_gpio_driver = { + .name = "langwell_gpio", + .id_table = lnw_gpio_ids, + .probe = lnw_gpio_probe, + .driver = { + .pm = &lnw_gpio_pm_ops, + }, +}; + + +static int __devinit wp_gpio_probe(struct platform_device *pdev) +{ + struct lnw_gpio *lnw; + struct gpio_chip *gc; + struct resource *rc; + int retval = 0; + + rc = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!rc) + return -EINVAL; + + lnw = kzalloc(sizeof(struct lnw_gpio), GFP_KERNEL); + if (!lnw) { + dev_err(&pdev->dev, + "can't allocate whitneypoint_gpio chip data\n"); + return -ENOMEM; + } + lnw->reg_base = ioremap_nocache(rc->start, resource_size(rc)); + if (lnw->reg_base == NULL) { + retval = -EINVAL; + goto err_kmalloc; + } + spin_lock_init(&lnw->lock); + gc = &lnw->chip; + gc->label = dev_name(&pdev->dev); + gc->owner = THIS_MODULE; + gc->direction_input = lnw_gpio_direction_input; + gc->direction_output = lnw_gpio_direction_output; + gc->get = lnw_gpio_get; + gc->set = lnw_gpio_set; + gc->to_irq = NULL; + gc->base = 0; + gc->ngpio = 64; + gc->can_sleep = 0; + retval = gpiochip_add(gc); + if (retval) { + dev_err(&pdev->dev, "whitneypoint gpiochip_add error %d\n", + retval); + goto err_ioremap; + } + platform_set_drvdata(pdev, lnw); + return 0; +err_ioremap: + iounmap(lnw->reg_base); +err_kmalloc: + kfree(lnw); + return retval; +} + +static int __devexit wp_gpio_remove(struct platform_device *pdev) +{ + struct lnw_gpio *lnw = platform_get_drvdata(pdev); + int err; + err = gpiochip_remove(&lnw->chip); + if (err) + dev_err(&pdev->dev, "failed to remove gpio_chip.\n"); + iounmap(lnw->reg_base); + kfree(lnw); + platform_set_drvdata(pdev, NULL); + return 0; +} + +static struct platform_driver wp_gpio_driver = { + .probe = wp_gpio_probe, + .remove = __devexit_p(wp_gpio_remove), + .driver = { + .name = "wp_gpio", + .owner = THIS_MODULE, + }, +}; + +static int __init lnw_gpio_init(void) +{ + int ret; + ret = pci_register_driver(&lnw_gpio_driver); + if (ret < 0) + return ret; + ret = platform_driver_register(&wp_gpio_driver); + if (ret < 0) + pci_unregister_driver(&lnw_gpio_driver); + return ret; +} + +device_initcall(lnw_gpio_init); diff --git a/drivers/gpio/gpio-lpc32xx.c b/drivers/gpio/gpio-lpc32xx.c new file mode 100644 index 00000000..61c2d08d --- /dev/null +++ b/drivers/gpio/gpio-lpc32xx.c @@ -0,0 +1,463 @@ +/* + * arch/arm/mach-lpc32xx/gpiolib.c + * + * Author: Kevin Wells <kevin.wells@nxp.com> + * + * Copyright (C) 2010 NXP Semiconductors + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/errno.h> +#include <linux/gpio.h> + +#include <mach/hardware.h> +#include <mach/platform.h> +#include <mach/gpio-lpc32xx.h> + +#define LPC32XX_GPIO_P3_INP_STATE _GPREG(0x000) +#define LPC32XX_GPIO_P3_OUTP_SET _GPREG(0x004) +#define LPC32XX_GPIO_P3_OUTP_CLR _GPREG(0x008) +#define LPC32XX_GPIO_P3_OUTP_STATE _GPREG(0x00C) +#define LPC32XX_GPIO_P2_DIR_SET _GPREG(0x010) +#define LPC32XX_GPIO_P2_DIR_CLR _GPREG(0x014) +#define LPC32XX_GPIO_P2_DIR_STATE _GPREG(0x018) +#define LPC32XX_GPIO_P2_INP_STATE _GPREG(0x01C) +#define LPC32XX_GPIO_P2_OUTP_SET _GPREG(0x020) +#define LPC32XX_GPIO_P2_OUTP_CLR _GPREG(0x024) +#define LPC32XX_GPIO_P2_MUX_SET _GPREG(0x028) +#define LPC32XX_GPIO_P2_MUX_CLR _GPREG(0x02C) +#define LPC32XX_GPIO_P2_MUX_STATE _GPREG(0x030) +#define LPC32XX_GPIO_P0_INP_STATE _GPREG(0x040) +#define LPC32XX_GPIO_P0_OUTP_SET _GPREG(0x044) +#define LPC32XX_GPIO_P0_OUTP_CLR _GPREG(0x048) +#define LPC32XX_GPIO_P0_OUTP_STATE _GPREG(0x04C) +#define LPC32XX_GPIO_P0_DIR_SET _GPREG(0x050) +#define LPC32XX_GPIO_P0_DIR_CLR _GPREG(0x054) +#define LPC32XX_GPIO_P0_DIR_STATE _GPREG(0x058) +#define LPC32XX_GPIO_P1_INP_STATE _GPREG(0x060) +#define LPC32XX_GPIO_P1_OUTP_SET _GPREG(0x064) +#define LPC32XX_GPIO_P1_OUTP_CLR _GPREG(0x068) +#define LPC32XX_GPIO_P1_OUTP_STATE _GPREG(0x06C) +#define LPC32XX_GPIO_P1_DIR_SET _GPREG(0x070) +#define LPC32XX_GPIO_P1_DIR_CLR _GPREG(0x074) +#define LPC32XX_GPIO_P1_DIR_STATE _GPREG(0x078) + +#define GPIO012_PIN_TO_BIT(x) (1 << (x)) +#define GPIO3_PIN_TO_BIT(x) (1 << ((x) + 25)) +#define GPO3_PIN_TO_BIT(x) (1 << (x)) +#define GPIO012_PIN_IN_SEL(x, y) (((x) >> (y)) & 1) +#define GPIO3_PIN_IN_SHIFT(x) ((x) == 5 ? 24 : 10 + (x)) +#define GPIO3_PIN_IN_SEL(x, y) (((x) >> GPIO3_PIN_IN_SHIFT(y)) & 1) +#define GPIO3_PIN5_IN_SEL(x) (((x) >> 24) & 1) +#define GPI3_PIN_IN_SEL(x, y) (((x) >> (y)) & 1) +#define GPO3_PIN_IN_SEL(x, y) (((x) >> (y)) & 1) + +struct gpio_regs { + void __iomem *inp_state; + void __iomem *outp_state; + void __iomem *outp_set; + void __iomem *outp_clr; + void __iomem *dir_set; + void __iomem *dir_clr; +}; + +/* + * GPIO names + */ +static const char *gpio_p0_names[LPC32XX_GPIO_P0_MAX] = { + "p0.0", "p0.1", "p0.2", "p0.3", + "p0.4", "p0.5", "p0.6", "p0.7" +}; + +static const char *gpio_p1_names[LPC32XX_GPIO_P1_MAX] = { + "p1.0", "p1.1", "p1.2", "p1.3", + "p1.4", "p1.5", "p1.6", "p1.7", + "p1.8", "p1.9", "p1.10", "p1.11", + "p1.12", "p1.13", "p1.14", "p1.15", + "p1.16", "p1.17", "p1.18", "p1.19", + "p1.20", "p1.21", "p1.22", "p1.23", +}; + +static const char *gpio_p2_names[LPC32XX_GPIO_P2_MAX] = { + "p2.0", "p2.1", "p2.2", "p2.3", + "p2.4", "p2.5", "p2.6", "p2.7", + "p2.8", "p2.9", "p2.10", "p2.11", + "p2.12" +}; + +static const char *gpio_p3_names[LPC32XX_GPIO_P3_MAX] = { + "gpio00", "gpio01", "gpio02", "gpio03", + "gpio04", "gpio05" +}; + +static const char *gpi_p3_names[LPC32XX_GPI_P3_MAX] = { + "gpi00", "gpi01", "gpi02", "gpi03", + "gpi04", "gpi05", "gpi06", "gpi07", + "gpi08", "gpi09", NULL, NULL, + NULL, NULL, NULL, "gpi15", + "gpi16", "gpi17", "gpi18", "gpi19", + "gpi20", "gpi21", "gpi22", "gpi23", + "gpi24", "gpi25", "gpi26", "gpi27" +}; + +static const char *gpo_p3_names[LPC32XX_GPO_P3_MAX] = { + "gpo00", "gpo01", "gpo02", "gpo03", + "gpo04", "gpo05", "gpo06", "gpo07", + "gpo08", "gpo09", "gpo10", "gpo11", + "gpo12", "gpo13", "gpo14", "gpo15", + "gpo16", "gpo17", "gpo18", "gpo19", + "gpo20", "gpo21", "gpo22", "gpo23" +}; + +static struct gpio_regs gpio_grp_regs_p0 = { + .inp_state = LPC32XX_GPIO_P0_INP_STATE, + .outp_set = LPC32XX_GPIO_P0_OUTP_SET, + .outp_clr = LPC32XX_GPIO_P0_OUTP_CLR, + .dir_set = LPC32XX_GPIO_P0_DIR_SET, + .dir_clr = LPC32XX_GPIO_P0_DIR_CLR, +}; + +static struct gpio_regs gpio_grp_regs_p1 = { + .inp_state = LPC32XX_GPIO_P1_INP_STATE, + .outp_set = LPC32XX_GPIO_P1_OUTP_SET, + .outp_clr = LPC32XX_GPIO_P1_OUTP_CLR, + .dir_set = LPC32XX_GPIO_P1_DIR_SET, + .dir_clr = LPC32XX_GPIO_P1_DIR_CLR, +}; + +static struct gpio_regs gpio_grp_regs_p2 = { + .inp_state = LPC32XX_GPIO_P2_INP_STATE, + .outp_set = LPC32XX_GPIO_P2_OUTP_SET, + .outp_clr = LPC32XX_GPIO_P2_OUTP_CLR, + .dir_set = LPC32XX_GPIO_P2_DIR_SET, + .dir_clr = LPC32XX_GPIO_P2_DIR_CLR, +}; + +static struct gpio_regs gpio_grp_regs_p3 = { + .inp_state = LPC32XX_GPIO_P3_INP_STATE, + .outp_state = LPC32XX_GPIO_P3_OUTP_STATE, + .outp_set = LPC32XX_GPIO_P3_OUTP_SET, + .outp_clr = LPC32XX_GPIO_P3_OUTP_CLR, + .dir_set = LPC32XX_GPIO_P2_DIR_SET, + .dir_clr = LPC32XX_GPIO_P2_DIR_CLR, +}; + +struct lpc32xx_gpio_chip { + struct gpio_chip chip; + struct gpio_regs *gpio_grp; +}; + +static inline struct lpc32xx_gpio_chip *to_lpc32xx_gpio( + struct gpio_chip *gpc) +{ + return container_of(gpc, struct lpc32xx_gpio_chip, chip); +} + +static void __set_gpio_dir_p012(struct lpc32xx_gpio_chip *group, + unsigned pin, int input) +{ + if (input) + __raw_writel(GPIO012_PIN_TO_BIT(pin), + group->gpio_grp->dir_clr); + else + __raw_writel(GPIO012_PIN_TO_BIT(pin), + group->gpio_grp->dir_set); +} + +static void __set_gpio_dir_p3(struct lpc32xx_gpio_chip *group, + unsigned pin, int input) +{ + u32 u = GPIO3_PIN_TO_BIT(pin); + + if (input) + __raw_writel(u, group->gpio_grp->dir_clr); + else + __raw_writel(u, group->gpio_grp->dir_set); +} + +static void __set_gpio_level_p012(struct lpc32xx_gpio_chip *group, + unsigned pin, int high) +{ + if (high) + __raw_writel(GPIO012_PIN_TO_BIT(pin), + group->gpio_grp->outp_set); + else + __raw_writel(GPIO012_PIN_TO_BIT(pin), + group->gpio_grp->outp_clr); +} + +static void __set_gpio_level_p3(struct lpc32xx_gpio_chip *group, + unsigned pin, int high) +{ + u32 u = GPIO3_PIN_TO_BIT(pin); + + if (high) + __raw_writel(u, group->gpio_grp->outp_set); + else + __raw_writel(u, group->gpio_grp->outp_clr); +} + +static void __set_gpo_level_p3(struct lpc32xx_gpio_chip *group, + unsigned pin, int high) +{ + if (high) + __raw_writel(GPO3_PIN_TO_BIT(pin), group->gpio_grp->outp_set); + else + __raw_writel(GPO3_PIN_TO_BIT(pin), group->gpio_grp->outp_clr); +} + +static int __get_gpio_state_p012(struct lpc32xx_gpio_chip *group, + unsigned pin) +{ + return GPIO012_PIN_IN_SEL(__raw_readl(group->gpio_grp->inp_state), + pin); +} + +static int __get_gpio_state_p3(struct lpc32xx_gpio_chip *group, + unsigned pin) +{ + int state = __raw_readl(group->gpio_grp->inp_state); + + /* + * P3 GPIO pin input mapping is not contiguous, GPIOP3-0..4 is mapped + * to bits 10..14, while GPIOP3-5 is mapped to bit 24. + */ + return GPIO3_PIN_IN_SEL(state, pin); +} + +static int __get_gpi_state_p3(struct lpc32xx_gpio_chip *group, + unsigned pin) +{ + return GPI3_PIN_IN_SEL(__raw_readl(group->gpio_grp->inp_state), pin); +} + +static int __get_gpo_state_p3(struct lpc32xx_gpio_chip *group, + unsigned pin) +{ + return GPO3_PIN_IN_SEL(__raw_readl(group->gpio_grp->outp_state), pin); +} + +/* + * GENERIC_GPIO primitives. + */ +static int lpc32xx_gpio_dir_input_p012(struct gpio_chip *chip, + unsigned pin) +{ + struct lpc32xx_gpio_chip *group = to_lpc32xx_gpio(chip); + + __set_gpio_dir_p012(group, pin, 1); + + return 0; +} + +static int lpc32xx_gpio_dir_input_p3(struct gpio_chip *chip, + unsigned pin) +{ + struct lpc32xx_gpio_chip *group = to_lpc32xx_gpio(chip); + + __set_gpio_dir_p3(group, pin, 1); + + return 0; +} + +static int lpc32xx_gpio_dir_in_always(struct gpio_chip *chip, + unsigned pin) +{ + return 0; +} + +static int lpc32xx_gpio_get_value_p012(struct gpio_chip *chip, unsigned pin) +{ + struct lpc32xx_gpio_chip *group = to_lpc32xx_gpio(chip); + + return __get_gpio_state_p012(group, pin); +} + +static int lpc32xx_gpio_get_value_p3(struct gpio_chip *chip, unsigned pin) +{ + struct lpc32xx_gpio_chip *group = to_lpc32xx_gpio(chip); + + return __get_gpio_state_p3(group, pin); +} + +static int lpc32xx_gpi_get_value(struct gpio_chip *chip, unsigned pin) +{ + struct lpc32xx_gpio_chip *group = to_lpc32xx_gpio(chip); + + return __get_gpi_state_p3(group, pin); +} + +static int lpc32xx_gpio_dir_output_p012(struct gpio_chip *chip, unsigned pin, + int value) +{ + struct lpc32xx_gpio_chip *group = to_lpc32xx_gpio(chip); + + __set_gpio_dir_p012(group, pin, 0); + + return 0; +} + +static int lpc32xx_gpio_dir_output_p3(struct gpio_chip *chip, unsigned pin, + int value) +{ + struct lpc32xx_gpio_chip *group = to_lpc32xx_gpio(chip); + + __set_gpio_dir_p3(group, pin, 0); + + return 0; +} + +static int lpc32xx_gpio_dir_out_always(struct gpio_chip *chip, unsigned pin, + int value) +{ + return 0; +} + +static void lpc32xx_gpio_set_value_p012(struct gpio_chip *chip, unsigned pin, + int value) +{ + struct lpc32xx_gpio_chip *group = to_lpc32xx_gpio(chip); + + __set_gpio_level_p012(group, pin, value); +} + +static void lpc32xx_gpio_set_value_p3(struct gpio_chip *chip, unsigned pin, + int value) +{ + struct lpc32xx_gpio_chip *group = to_lpc32xx_gpio(chip); + + __set_gpio_level_p3(group, pin, value); +} + +static void lpc32xx_gpo_set_value(struct gpio_chip *chip, unsigned pin, + int value) +{ + struct lpc32xx_gpio_chip *group = to_lpc32xx_gpio(chip); + + __set_gpo_level_p3(group, pin, value); +} + +static int lpc32xx_gpo_get_value(struct gpio_chip *chip, unsigned pin) +{ + struct lpc32xx_gpio_chip *group = to_lpc32xx_gpio(chip); + + return __get_gpo_state_p3(group, pin); +} + +static int lpc32xx_gpio_request(struct gpio_chip *chip, unsigned pin) +{ + if (pin < chip->ngpio) + return 0; + + return -EINVAL; +} + +static struct lpc32xx_gpio_chip lpc32xx_gpiochip[] = { + { + .chip = { + .label = "gpio_p0", + .direction_input = lpc32xx_gpio_dir_input_p012, + .get = lpc32xx_gpio_get_value_p012, + .direction_output = lpc32xx_gpio_dir_output_p012, + .set = lpc32xx_gpio_set_value_p012, + .request = lpc32xx_gpio_request, + .base = LPC32XX_GPIO_P0_GRP, + .ngpio = LPC32XX_GPIO_P0_MAX, + .names = gpio_p0_names, + .can_sleep = 0, + }, + .gpio_grp = &gpio_grp_regs_p0, + }, + { + .chip = { + .label = "gpio_p1", + .direction_input = lpc32xx_gpio_dir_input_p012, + .get = lpc32xx_gpio_get_value_p012, + .direction_output = lpc32xx_gpio_dir_output_p012, + .set = lpc32xx_gpio_set_value_p012, + .request = lpc32xx_gpio_request, + .base = LPC32XX_GPIO_P1_GRP, + .ngpio = LPC32XX_GPIO_P1_MAX, + .names = gpio_p1_names, + .can_sleep = 0, + }, + .gpio_grp = &gpio_grp_regs_p1, + }, + { + .chip = { + .label = "gpio_p2", + .direction_input = lpc32xx_gpio_dir_input_p012, + .get = lpc32xx_gpio_get_value_p012, + .direction_output = lpc32xx_gpio_dir_output_p012, + .set = lpc32xx_gpio_set_value_p012, + .request = lpc32xx_gpio_request, + .base = LPC32XX_GPIO_P2_GRP, + .ngpio = LPC32XX_GPIO_P2_MAX, + .names = gpio_p2_names, + .can_sleep = 0, + }, + .gpio_grp = &gpio_grp_regs_p2, + }, + { + .chip = { + .label = "gpio_p3", + .direction_input = lpc32xx_gpio_dir_input_p3, + .get = lpc32xx_gpio_get_value_p3, + .direction_output = lpc32xx_gpio_dir_output_p3, + .set = lpc32xx_gpio_set_value_p3, + .request = lpc32xx_gpio_request, + .base = LPC32XX_GPIO_P3_GRP, + .ngpio = LPC32XX_GPIO_P3_MAX, + .names = gpio_p3_names, + .can_sleep = 0, + }, + .gpio_grp = &gpio_grp_regs_p3, + }, + { + .chip = { + .label = "gpi_p3", + .direction_input = lpc32xx_gpio_dir_in_always, + .get = lpc32xx_gpi_get_value, + .request = lpc32xx_gpio_request, + .base = LPC32XX_GPI_P3_GRP, + .ngpio = LPC32XX_GPI_P3_MAX, + .names = gpi_p3_names, + .can_sleep = 0, + }, + .gpio_grp = &gpio_grp_regs_p3, + }, + { + .chip = { + .label = "gpo_p3", + .direction_output = lpc32xx_gpio_dir_out_always, + .set = lpc32xx_gpo_set_value, + .get = lpc32xx_gpo_get_value, + .request = lpc32xx_gpio_request, + .base = LPC32XX_GPO_P3_GRP, + .ngpio = LPC32XX_GPO_P3_MAX, + .names = gpo_p3_names, + .can_sleep = 0, + }, + .gpio_grp = &gpio_grp_regs_p3, + }, +}; + +void __init lpc32xx_gpio_init(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(lpc32xx_gpiochip); i++) + gpiochip_add(&lpc32xx_gpiochip[i].chip); +} diff --git a/drivers/gpio/gpio-max7300.c b/drivers/gpio/gpio-max7300.c new file mode 100644 index 00000000..a5ca0ab1 --- /dev/null +++ b/drivers/gpio/gpio-max7300.c @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2009 Wolfram Sang, Pengutronix + * + * 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. + * + * Check max730x.c for further details. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/mutex.h> +#include <linux/i2c.h> +#include <linux/spi/max7301.h> +#include <linux/slab.h> + +static int max7300_i2c_write(struct device *dev, unsigned int reg, + unsigned int val) +{ + struct i2c_client *client = to_i2c_client(dev); + + return i2c_smbus_write_byte_data(client, reg, val); +} + +static int max7300_i2c_read(struct device *dev, unsigned int reg) +{ + struct i2c_client *client = to_i2c_client(dev); + + return i2c_smbus_read_byte_data(client, reg); +} + +static int __devinit max7300_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct max7301 *ts; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + ts = kzalloc(sizeof(struct max7301), GFP_KERNEL); + if (!ts) + return -ENOMEM; + + ts->read = max7300_i2c_read; + ts->write = max7300_i2c_write; + ts->dev = &client->dev; + + ret = __max730x_probe(ts); + if (ret) + kfree(ts); + return ret; +} + +static int __devexit max7300_remove(struct i2c_client *client) +{ + return __max730x_remove(&client->dev); +} + +static const struct i2c_device_id max7300_id[] = { + { "max7300", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max7300_id); + +static struct i2c_driver max7300_driver = { + .driver = { + .name = "max7300", + .owner = THIS_MODULE, + }, + .probe = max7300_probe, + .remove = __devexit_p(max7300_remove), + .id_table = max7300_id, +}; + +static int __init max7300_init(void) +{ + return i2c_add_driver(&max7300_driver); +} +subsys_initcall(max7300_init); + +static void __exit max7300_exit(void) +{ + i2c_del_driver(&max7300_driver); +} +module_exit(max7300_exit); + +MODULE_AUTHOR("Wolfram Sang"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MAX7300 GPIO-Expander"); diff --git a/drivers/gpio/gpio-max7301.c b/drivers/gpio/gpio-max7301.c new file mode 100644 index 00000000..741acfcb --- /dev/null +++ b/drivers/gpio/gpio-max7301.c @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2006 Juergen Beisert, Pengutronix + * Copyright (C) 2008 Guennadi Liakhovetski, Pengutronix + * Copyright (C) 2009 Wolfram Sang, Pengutronix + * + * 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. + * + * Check max730x.c for further details. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> +#include <linux/spi/max7301.h> + +/* A write to the MAX7301 means one message with one transfer */ +static int max7301_spi_write(struct device *dev, unsigned int reg, + unsigned int val) +{ + struct spi_device *spi = to_spi_device(dev); + u16 word = ((reg & 0x7F) << 8) | (val & 0xFF); + + return spi_write(spi, (const u8 *)&word, sizeof(word)); +} + +/* A read from the MAX7301 means two transfers; here, one message each */ + +static int max7301_spi_read(struct device *dev, unsigned int reg) +{ + int ret; + u16 word; + struct spi_device *spi = to_spi_device(dev); + + word = 0x8000 | (reg << 8); + ret = spi_write(spi, (const u8 *)&word, sizeof(word)); + if (ret) + return ret; + /* + * This relies on the fact, that a transfer with NULL tx_buf shifts out + * zero bytes (=NOOP for MAX7301) + */ + ret = spi_read(spi, (u8 *)&word, sizeof(word)); + if (ret) + return ret; + return word & 0xff; +} + +static int __devinit max7301_probe(struct spi_device *spi) +{ + struct max7301 *ts; + int ret; + + /* bits_per_word cannot be configured in platform data */ + spi->bits_per_word = 16; + ret = spi_setup(spi); + if (ret < 0) + return ret; + + ts = kzalloc(sizeof(struct max7301), GFP_KERNEL); + if (!ts) + return -ENOMEM; + + ts->read = max7301_spi_read; + ts->write = max7301_spi_write; + ts->dev = &spi->dev; + + ret = __max730x_probe(ts); + if (ret) + kfree(ts); + return ret; +} + +static int __devexit max7301_remove(struct spi_device *spi) +{ + return __max730x_remove(&spi->dev); +} + +static const struct spi_device_id max7301_id[] = { + { "max7301", 0 }, + { } +}; +MODULE_DEVICE_TABLE(spi, max7301_id); + +static struct spi_driver max7301_driver = { + .driver = { + .name = "max7301", + .owner = THIS_MODULE, + }, + .probe = max7301_probe, + .remove = __devexit_p(max7301_remove), + .id_table = max7301_id, +}; + +static int __init max7301_init(void) +{ + return spi_register_driver(&max7301_driver); +} +/* register after spi postcore initcall and before + * subsys initcalls that may rely on these GPIOs + */ +subsys_initcall(max7301_init); + +static void __exit max7301_exit(void) +{ + spi_unregister_driver(&max7301_driver); +} +module_exit(max7301_exit); + +MODULE_AUTHOR("Juergen Beisert, Wolfram Sang"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MAX7301 GPIO-Expander"); diff --git a/drivers/gpio/gpio-max730x.c b/drivers/gpio/gpio-max730x.c new file mode 100644 index 00000000..05e2dac6 --- /dev/null +++ b/drivers/gpio/gpio-max730x.c @@ -0,0 +1,255 @@ +/** + * Copyright (C) 2006 Juergen Beisert, Pengutronix + * Copyright (C) 2008 Guennadi Liakhovetski, Pengutronix + * Copyright (C) 2009 Wolfram Sang, Pengutronix + * + * 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. + * + * The Maxim MAX7300/1 device is an I2C/SPI driven GPIO expander. There are + * 28 GPIOs. 8 of them can trigger an interrupt. See datasheet for more + * details + * Note: + * - DIN must be stable at the rising edge of clock. + * - when writing: + * - always clock in 16 clocks at once + * - at DIN: D15 first, D0 last + * - D0..D7 = databyte, D8..D14 = commandbyte + * - D15 = low -> write command + * - when reading + * - always clock in 16 clocks at once + * - at DIN: D15 first, D0 last + * - D0..D7 = dummy, D8..D14 = register address + * - D15 = high -> read command + * - raise CS and assert it again + * - always clock in 16 clocks at once + * - at DOUT: D15 first, D0 last + * - D0..D7 contains the data from the first cycle + * + * The driver exports a standard gpiochip interface + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/mutex.h> +#include <linux/spi/max7301.h> +#include <linux/gpio.h> +#include <linux/slab.h> + +/* + * Pin configurations, see MAX7301 datasheet page 6 + */ +#define PIN_CONFIG_MASK 0x03 +#define PIN_CONFIG_IN_PULLUP 0x03 +#define PIN_CONFIG_IN_WO_PULLUP 0x02 +#define PIN_CONFIG_OUT 0x01 + +#define PIN_NUMBER 28 + +static int max7301_direction_input(struct gpio_chip *chip, unsigned offset) +{ + struct max7301 *ts = container_of(chip, struct max7301, chip); + u8 *config; + u8 offset_bits, pin_config; + int ret; + + /* First 4 pins are unused in the controller */ + offset += 4; + offset_bits = (offset & 3) << 1; + + config = &ts->port_config[offset >> 2]; + + if (ts->input_pullup_active & BIT(offset)) + pin_config = PIN_CONFIG_IN_PULLUP; + else + pin_config = PIN_CONFIG_IN_WO_PULLUP; + + mutex_lock(&ts->lock); + + *config = (*config & ~(PIN_CONFIG_MASK << offset_bits)) + | (pin_config << offset_bits); + + ret = ts->write(ts->dev, 0x08 + (offset >> 2), *config); + + mutex_unlock(&ts->lock); + + return ret; +} + +static int __max7301_set(struct max7301 *ts, unsigned offset, int value) +{ + if (value) { + ts->out_level |= 1 << offset; + return ts->write(ts->dev, 0x20 + offset, 0x01); + } else { + ts->out_level &= ~(1 << offset); + return ts->write(ts->dev, 0x20 + offset, 0x00); + } +} + +static int max7301_direction_output(struct gpio_chip *chip, unsigned offset, + int value) +{ + struct max7301 *ts = container_of(chip, struct max7301, chip); + u8 *config; + u8 offset_bits; + int ret; + + /* First 4 pins are unused in the controller */ + offset += 4; + offset_bits = (offset & 3) << 1; + + config = &ts->port_config[offset >> 2]; + + mutex_lock(&ts->lock); + + *config = (*config & ~(PIN_CONFIG_MASK << offset_bits)) + | (PIN_CONFIG_OUT << offset_bits); + + ret = __max7301_set(ts, offset, value); + + if (!ret) + ret = ts->write(ts->dev, 0x08 + (offset >> 2), *config); + + mutex_unlock(&ts->lock); + + return ret; +} + +static int max7301_get(struct gpio_chip *chip, unsigned offset) +{ + struct max7301 *ts = container_of(chip, struct max7301, chip); + int config, level = -EINVAL; + + /* First 4 pins are unused in the controller */ + offset += 4; + + mutex_lock(&ts->lock); + + config = (ts->port_config[offset >> 2] >> ((offset & 3) << 1)) + & PIN_CONFIG_MASK; + + switch (config) { + case PIN_CONFIG_OUT: + /* Output: return cached level */ + level = !!(ts->out_level & (1 << offset)); + break; + case PIN_CONFIG_IN_WO_PULLUP: + case PIN_CONFIG_IN_PULLUP: + /* Input: read out */ + level = ts->read(ts->dev, 0x20 + offset) & 0x01; + } + mutex_unlock(&ts->lock); + + return level; +} + +static void max7301_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct max7301 *ts = container_of(chip, struct max7301, chip); + + /* First 4 pins are unused in the controller */ + offset += 4; + + mutex_lock(&ts->lock); + + __max7301_set(ts, offset, value); + + mutex_unlock(&ts->lock); +} + +int __devinit __max730x_probe(struct max7301 *ts) +{ + struct device *dev = ts->dev; + struct max7301_platform_data *pdata; + int i, ret; + + pdata = dev->platform_data; + if (!pdata || !pdata->base) { + dev_err(dev, "incorrect or missing platform data\n"); + return -EINVAL; + } + + mutex_init(&ts->lock); + dev_set_drvdata(dev, ts); + + /* Power up the chip and disable IRQ output */ + ts->write(dev, 0x04, 0x01); + + ts->input_pullup_active = pdata->input_pullup_active; + ts->chip.label = dev->driver->name; + + ts->chip.direction_input = max7301_direction_input; + ts->chip.get = max7301_get; + ts->chip.direction_output = max7301_direction_output; + ts->chip.set = max7301_set; + + ts->chip.base = pdata->base; + ts->chip.ngpio = PIN_NUMBER; + ts->chip.can_sleep = 1; + ts->chip.dev = dev; + ts->chip.owner = THIS_MODULE; + + /* + * initialize pullups according to platform data and cache the + * register values for later use. + */ + for (i = 1; i < 8; i++) { + int j; + /* + * initialize port_config with "0xAA", which means + * input with internal pullup disabled. This is needed + * to avoid writing zeros (in the inner for loop), + * which is not allowed according to the datasheet. + */ + ts->port_config[i] = 0xAA; + for (j = 0; j < 4; j++) { + int offset = (i - 1) * 4 + j; + ret = max7301_direction_input(&ts->chip, offset); + if (ret) + goto exit_destroy; + } + } + + ret = gpiochip_add(&ts->chip); + if (ret) + goto exit_destroy; + + return ret; + +exit_destroy: + dev_set_drvdata(dev, NULL); + mutex_destroy(&ts->lock); + return ret; +} +EXPORT_SYMBOL_GPL(__max730x_probe); + +int __devexit __max730x_remove(struct device *dev) +{ + struct max7301 *ts = dev_get_drvdata(dev); + int ret; + + if (ts == NULL) + return -ENODEV; + + dev_set_drvdata(dev, NULL); + + /* Power down the chip and disable IRQ output */ + ts->write(dev, 0x04, 0x00); + + ret = gpiochip_remove(&ts->chip); + if (!ret) { + mutex_destroy(&ts->lock); + kfree(ts); + } else + dev_err(dev, "Failed to remove GPIO controller: %d\n", ret); + + return ret; +} +EXPORT_SYMBOL_GPL(__max730x_remove); + +MODULE_AUTHOR("Juergen Beisert, Wolfram Sang"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MAX730x GPIO-Expanders, generic parts"); diff --git a/drivers/gpio/gpio-max732x.c b/drivers/gpio/gpio-max732x.c new file mode 100644 index 00000000..95041208 --- /dev/null +++ b/drivers/gpio/gpio-max732x.c @@ -0,0 +1,714 @@ +/* + * MAX732x I2C Port Expander with 8/16 I/O + * + * Copyright (C) 2007 Marvell International Ltd. + * Copyright (C) 2008 Jack Ren <jack.ren@marvell.com> + * Copyright (C) 2008 Eric Miao <eric.miao@marvell.com> + * + * Derived from drivers/gpio/pca953x.c + * + * 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. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/i2c.h> +#include <linux/i2c/max732x.h> + + +/* + * Each port of MAX732x (including MAX7319) falls into one of the + * following three types: + * + * - Push Pull Output + * - Input + * - Open Drain I/O + * + * designated by 'O', 'I' and 'P' individually according to MAXIM's + * datasheets. 'I' and 'P' ports are interrupt capables, some with + * a dedicated interrupt mask. + * + * There are two groups of I/O ports, each group usually includes + * up to 8 I/O ports, and is accessed by a specific I2C address: + * + * - Group A : by I2C address 0b'110xxxx + * - Group B : by I2C address 0b'101xxxx + * + * where 'xxxx' is decided by the connections of pin AD2/AD0. The + * address used also affects the initial state of output signals. + * + * Within each group of ports, there are five known combinations of + * I/O ports: 4I4O, 4P4O, 8I, 8P, 8O, see the definitions below for + * the detailed organization of these ports. Only Goup A is interrupt + * capable. + * + * GPIO numbers start from 'gpio_base + 0' to 'gpio_base + 8/16', + * and GPIOs from GROUP_A are numbered before those from GROUP_B + * (if there are two groups). + * + * NOTE: MAX7328/MAX7329 are drop-in replacements for PCF8574/a, so + * they are not supported by this driver. + */ + +#define PORT_NONE 0x0 /* '/' No Port */ +#define PORT_OUTPUT 0x1 /* 'O' Push-Pull, Output Only */ +#define PORT_INPUT 0x2 /* 'I' Input Only */ +#define PORT_OPENDRAIN 0x3 /* 'P' Open-Drain, I/O */ + +#define IO_4I4O 0x5AA5 /* O7 O6 I5 I4 I3 I2 O1 O0 */ +#define IO_4P4O 0x5FF5 /* O7 O6 P5 P4 P3 P2 O1 O0 */ +#define IO_8I 0xAAAA /* I7 I6 I5 I4 I3 I2 I1 I0 */ +#define IO_8P 0xFFFF /* P7 P6 P5 P4 P3 P2 P1 P0 */ +#define IO_8O 0x5555 /* O7 O6 O5 O4 O3 O2 O1 O0 */ + +#define GROUP_A(x) ((x) & 0xffff) /* I2C Addr: 0b'110xxxx */ +#define GROUP_B(x) ((x) << 16) /* I2C Addr: 0b'101xxxx */ + +#define INT_NONE 0x0 /* No interrupt capability */ +#define INT_NO_MASK 0x1 /* Has interrupts, no mask */ +#define INT_INDEP_MASK 0x2 /* Has interrupts, independent mask */ +#define INT_MERGED_MASK 0x3 /* Has interrupts, merged mask */ + +#define INT_CAPS(x) (((uint64_t)(x)) << 32) + +enum { + MAX7319, + MAX7320, + MAX7321, + MAX7322, + MAX7323, + MAX7324, + MAX7325, + MAX7326, + MAX7327, +}; + +static uint64_t max732x_features[] = { + [MAX7319] = GROUP_A(IO_8I) | INT_CAPS(INT_MERGED_MASK), + [MAX7320] = GROUP_B(IO_8O), + [MAX7321] = GROUP_A(IO_8P) | INT_CAPS(INT_NO_MASK), + [MAX7322] = GROUP_A(IO_4I4O) | INT_CAPS(INT_MERGED_MASK), + [MAX7323] = GROUP_A(IO_4P4O) | INT_CAPS(INT_INDEP_MASK), + [MAX7324] = GROUP_A(IO_8I) | GROUP_B(IO_8O) | INT_CAPS(INT_MERGED_MASK), + [MAX7325] = GROUP_A(IO_8P) | GROUP_B(IO_8O) | INT_CAPS(INT_NO_MASK), + [MAX7326] = GROUP_A(IO_4I4O) | GROUP_B(IO_8O) | INT_CAPS(INT_MERGED_MASK), + [MAX7327] = GROUP_A(IO_4P4O) | GROUP_B(IO_8O) | INT_CAPS(INT_NO_MASK), +}; + +static const struct i2c_device_id max732x_id[] = { + { "max7319", MAX7319 }, + { "max7320", MAX7320 }, + { "max7321", MAX7321 }, + { "max7322", MAX7322 }, + { "max7323", MAX7323 }, + { "max7324", MAX7324 }, + { "max7325", MAX7325 }, + { "max7326", MAX7326 }, + { "max7327", MAX7327 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, max732x_id); + +struct max732x_chip { + struct gpio_chip gpio_chip; + + struct i2c_client *client; /* "main" client */ + struct i2c_client *client_dummy; + struct i2c_client *client_group_a; + struct i2c_client *client_group_b; + + unsigned int mask_group_a; + unsigned int dir_input; + unsigned int dir_output; + + struct mutex lock; + uint8_t reg_out[2]; + +#ifdef CONFIG_GPIO_MAX732X_IRQ + struct mutex irq_lock; + int irq_base; + uint8_t irq_mask; + uint8_t irq_mask_cur; + uint8_t irq_trig_raise; + uint8_t irq_trig_fall; + uint8_t irq_features; +#endif +}; + +static int max732x_writeb(struct max732x_chip *chip, int group_a, uint8_t val) +{ + struct i2c_client *client; + int ret; + + client = group_a ? chip->client_group_a : chip->client_group_b; + ret = i2c_smbus_write_byte(client, val); + if (ret < 0) { + dev_err(&client->dev, "failed writing\n"); + return ret; + } + + return 0; +} + +static int max732x_readb(struct max732x_chip *chip, int group_a, uint8_t *val) +{ + struct i2c_client *client; + int ret; + + client = group_a ? chip->client_group_a : chip->client_group_b; + ret = i2c_smbus_read_byte(client); + if (ret < 0) { + dev_err(&client->dev, "failed reading\n"); + return ret; + } + + *val = (uint8_t)ret; + return 0; +} + +static inline int is_group_a(struct max732x_chip *chip, unsigned off) +{ + return (1u << off) & chip->mask_group_a; +} + +static int max732x_gpio_get_value(struct gpio_chip *gc, unsigned off) +{ + struct max732x_chip *chip; + uint8_t reg_val; + int ret; + + chip = container_of(gc, struct max732x_chip, gpio_chip); + + ret = max732x_readb(chip, is_group_a(chip, off), ®_val); + if (ret < 0) + return 0; + + return reg_val & (1u << (off & 0x7)); +} + +static void max732x_gpio_set_value(struct gpio_chip *gc, unsigned off, int val) +{ + struct max732x_chip *chip; + uint8_t reg_out, mask = 1u << (off & 0x7); + int ret; + + chip = container_of(gc, struct max732x_chip, gpio_chip); + + mutex_lock(&chip->lock); + + reg_out = (off > 7) ? chip->reg_out[1] : chip->reg_out[0]; + reg_out = (val) ? reg_out | mask : reg_out & ~mask; + + ret = max732x_writeb(chip, is_group_a(chip, off), reg_out); + if (ret < 0) + goto out; + + /* update the shadow register then */ + if (off > 7) + chip->reg_out[1] = reg_out; + else + chip->reg_out[0] = reg_out; +out: + mutex_unlock(&chip->lock); +} + +static int max732x_gpio_direction_input(struct gpio_chip *gc, unsigned off) +{ + struct max732x_chip *chip; + unsigned int mask = 1u << off; + + chip = container_of(gc, struct max732x_chip, gpio_chip); + + if ((mask & chip->dir_input) == 0) { + dev_dbg(&chip->client->dev, "%s port %d is output only\n", + chip->client->name, off); + return -EACCES; + } + + /* + * Open-drain pins must be set to high impedance (which is + * equivalent to output-high) to be turned into an input. + */ + if ((mask & chip->dir_output)) + max732x_gpio_set_value(gc, off, 1); + + return 0; +} + +static int max732x_gpio_direction_output(struct gpio_chip *gc, + unsigned off, int val) +{ + struct max732x_chip *chip; + unsigned int mask = 1u << off; + + chip = container_of(gc, struct max732x_chip, gpio_chip); + + if ((mask & chip->dir_output) == 0) { + dev_dbg(&chip->client->dev, "%s port %d is input only\n", + chip->client->name, off); + return -EACCES; + } + + max732x_gpio_set_value(gc, off, val); + return 0; +} + +#ifdef CONFIG_GPIO_MAX732X_IRQ +static int max732x_writew(struct max732x_chip *chip, uint16_t val) +{ + int ret; + + val = cpu_to_le16(val); + + ret = i2c_master_send(chip->client_group_a, (char *)&val, 2); + if (ret < 0) { + dev_err(&chip->client_group_a->dev, "failed writing\n"); + return ret; + } + + return 0; +} + +static int max732x_readw(struct max732x_chip *chip, uint16_t *val) +{ + int ret; + + ret = i2c_master_recv(chip->client_group_a, (char *)val, 2); + if (ret < 0) { + dev_err(&chip->client_group_a->dev, "failed reading\n"); + return ret; + } + + *val = le16_to_cpu(*val); + return 0; +} + +static void max732x_irq_update_mask(struct max732x_chip *chip) +{ + uint16_t msg; + + if (chip->irq_mask == chip->irq_mask_cur) + return; + + chip->irq_mask = chip->irq_mask_cur; + + if (chip->irq_features == INT_NO_MASK) + return; + + mutex_lock(&chip->lock); + + switch (chip->irq_features) { + case INT_INDEP_MASK: + msg = (chip->irq_mask << 8) | chip->reg_out[0]; + max732x_writew(chip, msg); + break; + + case INT_MERGED_MASK: + msg = chip->irq_mask | chip->reg_out[0]; + max732x_writeb(chip, 1, (uint8_t)msg); + break; + } + + mutex_unlock(&chip->lock); +} + +static int max732x_gpio_to_irq(struct gpio_chip *gc, unsigned off) +{ + struct max732x_chip *chip; + + chip = container_of(gc, struct max732x_chip, gpio_chip); + return chip->irq_base + off; +} + +static void max732x_irq_mask(struct irq_data *d) +{ + struct max732x_chip *chip = irq_data_get_irq_chip_data(d); + + chip->irq_mask_cur &= ~(1 << (d->irq - chip->irq_base)); +} + +static void max732x_irq_unmask(struct irq_data *d) +{ + struct max732x_chip *chip = irq_data_get_irq_chip_data(d); + + chip->irq_mask_cur |= 1 << (d->irq - chip->irq_base); +} + +static void max732x_irq_bus_lock(struct irq_data *d) +{ + struct max732x_chip *chip = irq_data_get_irq_chip_data(d); + + mutex_lock(&chip->irq_lock); + chip->irq_mask_cur = chip->irq_mask; +} + +static void max732x_irq_bus_sync_unlock(struct irq_data *d) +{ + struct max732x_chip *chip = irq_data_get_irq_chip_data(d); + + max732x_irq_update_mask(chip); + mutex_unlock(&chip->irq_lock); +} + +static int max732x_irq_set_type(struct irq_data *d, unsigned int type) +{ + struct max732x_chip *chip = irq_data_get_irq_chip_data(d); + uint16_t off = d->irq - chip->irq_base; + uint16_t mask = 1 << off; + + if (!(mask & chip->dir_input)) { + dev_dbg(&chip->client->dev, "%s port %d is output only\n", + chip->client->name, off); + return -EACCES; + } + + if (!(type & IRQ_TYPE_EDGE_BOTH)) { + dev_err(&chip->client->dev, "irq %d: unsupported type %d\n", + d->irq, type); + return -EINVAL; + } + + if (type & IRQ_TYPE_EDGE_FALLING) + chip->irq_trig_fall |= mask; + else + chip->irq_trig_fall &= ~mask; + + if (type & IRQ_TYPE_EDGE_RISING) + chip->irq_trig_raise |= mask; + else + chip->irq_trig_raise &= ~mask; + + return max732x_gpio_direction_input(&chip->gpio_chip, off); +} + +static struct irq_chip max732x_irq_chip = { + .name = "max732x", + .irq_mask = max732x_irq_mask, + .irq_unmask = max732x_irq_unmask, + .irq_bus_lock = max732x_irq_bus_lock, + .irq_bus_sync_unlock = max732x_irq_bus_sync_unlock, + .irq_set_type = max732x_irq_set_type, +}; + +static uint8_t max732x_irq_pending(struct max732x_chip *chip) +{ + uint8_t cur_stat; + uint8_t old_stat; + uint8_t trigger; + uint8_t pending; + uint16_t status; + int ret; + + ret = max732x_readw(chip, &status); + if (ret) + return 0; + + trigger = status >> 8; + trigger &= chip->irq_mask; + + if (!trigger) + return 0; + + cur_stat = status & 0xFF; + cur_stat &= chip->irq_mask; + + old_stat = cur_stat ^ trigger; + + pending = (old_stat & chip->irq_trig_fall) | + (cur_stat & chip->irq_trig_raise); + pending &= trigger; + + return pending; +} + +static irqreturn_t max732x_irq_handler(int irq, void *devid) +{ + struct max732x_chip *chip = devid; + uint8_t pending; + uint8_t level; + + pending = max732x_irq_pending(chip); + + if (!pending) + return IRQ_HANDLED; + + do { + level = __ffs(pending); + handle_nested_irq(level + chip->irq_base); + + pending &= ~(1 << level); + } while (pending); + + return IRQ_HANDLED; +} + +static int max732x_irq_setup(struct max732x_chip *chip, + const struct i2c_device_id *id) +{ + struct i2c_client *client = chip->client; + struct max732x_platform_data *pdata = client->dev.platform_data; + int has_irq = max732x_features[id->driver_data] >> 32; + int ret; + + if (pdata->irq_base && has_irq != INT_NONE) { + int lvl; + + chip->irq_base = pdata->irq_base; + chip->irq_features = has_irq; + mutex_init(&chip->irq_lock); + + for (lvl = 0; lvl < chip->gpio_chip.ngpio; lvl++) { + int irq = lvl + chip->irq_base; + + if (!(chip->dir_input & (1 << lvl))) + continue; + + irq_set_chip_data(irq, chip); + irq_set_chip_and_handler(irq, &max732x_irq_chip, + handle_edge_irq); + irq_set_nested_thread(irq, 1); +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID); +#else + irq_set_noprobe(irq); +#endif + } + + ret = request_threaded_irq(client->irq, + NULL, + max732x_irq_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + dev_name(&client->dev), chip); + if (ret) { + dev_err(&client->dev, "failed to request irq %d\n", + client->irq); + goto out_failed; + } + + chip->gpio_chip.to_irq = max732x_gpio_to_irq; + } + + return 0; + +out_failed: + chip->irq_base = 0; + return ret; +} + +static void max732x_irq_teardown(struct max732x_chip *chip) +{ + if (chip->irq_base) + free_irq(chip->client->irq, chip); +} +#else /* CONFIG_GPIO_MAX732X_IRQ */ +static int max732x_irq_setup(struct max732x_chip *chip, + const struct i2c_device_id *id) +{ + struct i2c_client *client = chip->client; + struct max732x_platform_data *pdata = client->dev.platform_data; + int has_irq = max732x_features[id->driver_data] >> 32; + + if (pdata->irq_base && has_irq != INT_NONE) + dev_warn(&client->dev, "interrupt support not compiled in\n"); + + return 0; +} + +static void max732x_irq_teardown(struct max732x_chip *chip) +{ +} +#endif + +static int __devinit max732x_setup_gpio(struct max732x_chip *chip, + const struct i2c_device_id *id, + unsigned gpio_start) +{ + struct gpio_chip *gc = &chip->gpio_chip; + uint32_t id_data = (uint32_t)max732x_features[id->driver_data]; + int i, port = 0; + + for (i = 0; i < 16; i++, id_data >>= 2) { + unsigned int mask = 1 << port; + + switch (id_data & 0x3) { + case PORT_OUTPUT: + chip->dir_output |= mask; + break; + case PORT_INPUT: + chip->dir_input |= mask; + break; + case PORT_OPENDRAIN: + chip->dir_output |= mask; + chip->dir_input |= mask; + break; + default: + continue; + } + + if (i < 8) + chip->mask_group_a |= mask; + port++; + } + + if (chip->dir_input) + gc->direction_input = max732x_gpio_direction_input; + if (chip->dir_output) { + gc->direction_output = max732x_gpio_direction_output; + gc->set = max732x_gpio_set_value; + } + gc->get = max732x_gpio_get_value; + gc->can_sleep = 1; + + gc->base = gpio_start; + gc->ngpio = port; + gc->label = chip->client->name; + gc->owner = THIS_MODULE; + + return port; +} + +static int __devinit max732x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct max732x_platform_data *pdata; + struct max732x_chip *chip; + struct i2c_client *c; + uint16_t addr_a, addr_b; + int ret, nr_port; + + pdata = client->dev.platform_data; + if (pdata == NULL) { + dev_dbg(&client->dev, "no platform data\n"); + return -EINVAL; + } + + chip = kzalloc(sizeof(struct max732x_chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + chip->client = client; + + nr_port = max732x_setup_gpio(chip, id, pdata->gpio_base); + + addr_a = (client->addr & 0x0f) | 0x60; + addr_b = (client->addr & 0x0f) | 0x50; + + switch (client->addr & 0x70) { + case 0x60: + chip->client_group_a = client; + if (nr_port > 8) { + c = i2c_new_dummy(client->adapter, addr_b); + chip->client_group_b = chip->client_dummy = c; + } + break; + case 0x50: + chip->client_group_b = client; + if (nr_port > 8) { + c = i2c_new_dummy(client->adapter, addr_a); + chip->client_group_a = chip->client_dummy = c; + } + break; + default: + dev_err(&client->dev, "invalid I2C address specified %02x\n", + client->addr); + ret = -EINVAL; + goto out_failed; + } + + mutex_init(&chip->lock); + + max732x_readb(chip, is_group_a(chip, 0), &chip->reg_out[0]); + if (nr_port > 8) + max732x_readb(chip, is_group_a(chip, 8), &chip->reg_out[1]); + + ret = max732x_irq_setup(chip, id); + if (ret) + goto out_failed; + + ret = gpiochip_add(&chip->gpio_chip); + if (ret) + goto out_failed; + + if (pdata->setup) { + ret = pdata->setup(client, chip->gpio_chip.base, + chip->gpio_chip.ngpio, pdata->context); + if (ret < 0) + dev_warn(&client->dev, "setup failed, %d\n", ret); + } + + i2c_set_clientdata(client, chip); + return 0; + +out_failed: + max732x_irq_teardown(chip); + kfree(chip); + return ret; +} + +static int __devexit max732x_remove(struct i2c_client *client) +{ + struct max732x_platform_data *pdata = client->dev.platform_data; + struct max732x_chip *chip = i2c_get_clientdata(client); + int ret; + + if (pdata->teardown) { + ret = pdata->teardown(client, chip->gpio_chip.base, + chip->gpio_chip.ngpio, pdata->context); + if (ret < 0) { + dev_err(&client->dev, "%s failed, %d\n", + "teardown", ret); + return ret; + } + } + + ret = gpiochip_remove(&chip->gpio_chip); + if (ret) { + dev_err(&client->dev, "%s failed, %d\n", + "gpiochip_remove()", ret); + return ret; + } + + max732x_irq_teardown(chip); + + /* unregister any dummy i2c_client */ + if (chip->client_dummy) + i2c_unregister_device(chip->client_dummy); + + kfree(chip); + return 0; +} + +static struct i2c_driver max732x_driver = { + .driver = { + .name = "max732x", + .owner = THIS_MODULE, + }, + .probe = max732x_probe, + .remove = __devexit_p(max732x_remove), + .id_table = max732x_id, +}; + +static int __init max732x_init(void) +{ + return i2c_add_driver(&max732x_driver); +} +/* register after i2c postcore initcall and before + * subsys initcalls that may rely on these GPIOs + */ +subsys_initcall(max732x_init); + +static void __exit max732x_exit(void) +{ + i2c_del_driver(&max732x_driver); +} +module_exit(max732x_exit); + +MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>"); +MODULE_DESCRIPTION("GPIO expander driver for MAX732X"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpio/gpio-mc33880.c b/drivers/gpio/gpio-mc33880.c new file mode 100644 index 00000000..2de57ce5 --- /dev/null +++ b/drivers/gpio/gpio-mc33880.c @@ -0,0 +1,198 @@ +/* + * MC33880 high-side/low-side switch GPIO driver + * Copyright (c) 2009 Intel Corporation + * + * 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. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* Supports: + * Freescale MC33880 high-side/low-side switch + */ + +#include <linux/init.h> +#include <linux/mutex.h> +#include <linux/spi/spi.h> +#include <linux/spi/mc33880.h> +#include <linux/gpio.h> +#include <linux/slab.h> +#include <linux/module.h> + +#define DRIVER_NAME "mc33880" + +/* + * Pin configurations, see MAX7301 datasheet page 6 + */ +#define PIN_CONFIG_MASK 0x03 +#define PIN_CONFIG_IN_PULLUP 0x03 +#define PIN_CONFIG_IN_WO_PULLUP 0x02 +#define PIN_CONFIG_OUT 0x01 + +#define PIN_NUMBER 8 + + +/* + * Some registers must be read back to modify. + * To save time we cache them here in memory + */ +struct mc33880 { + struct mutex lock; /* protect from simultaneous accesses */ + u8 port_config; + struct gpio_chip chip; + struct spi_device *spi; +}; + +static int mc33880_write_config(struct mc33880 *mc) +{ + return spi_write(mc->spi, &mc->port_config, sizeof(mc->port_config)); +} + + +static int __mc33880_set(struct mc33880 *mc, unsigned offset, int value) +{ + if (value) + mc->port_config |= 1 << offset; + else + mc->port_config &= ~(1 << offset); + + return mc33880_write_config(mc); +} + + +static void mc33880_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct mc33880 *mc = container_of(chip, struct mc33880, chip); + + mutex_lock(&mc->lock); + + __mc33880_set(mc, offset, value); + + mutex_unlock(&mc->lock); +} + +static int __devinit mc33880_probe(struct spi_device *spi) +{ + struct mc33880 *mc; + struct mc33880_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; + + mc = kzalloc(sizeof(struct mc33880), GFP_KERNEL); + if (!mc) + return -ENOMEM; + + mutex_init(&mc->lock); + + dev_set_drvdata(&spi->dev, mc); + + mc->spi = spi; + + mc->chip.label = DRIVER_NAME, + mc->chip.set = mc33880_set; + mc->chip.base = pdata->base; + mc->chip.ngpio = PIN_NUMBER; + mc->chip.can_sleep = 1; + mc->chip.dev = &spi->dev; + mc->chip.owner = THIS_MODULE; + + mc->port_config = 0x00; + /* write twice, because during initialisation the first setting + * is just for testing SPI communication, and the second is the + * "real" configuration + */ + ret = mc33880_write_config(mc); + mc->port_config = 0x00; + if (!ret) + ret = mc33880_write_config(mc); + + if (ret) { + printk(KERN_ERR "Failed writing to " DRIVER_NAME ": %d\n", ret); + goto exit_destroy; + } + + ret = gpiochip_add(&mc->chip); + if (ret) + goto exit_destroy; + + return ret; + +exit_destroy: + dev_set_drvdata(&spi->dev, NULL); + mutex_destroy(&mc->lock); + kfree(mc); + return ret; +} + +static int __devexit mc33880_remove(struct spi_device *spi) +{ + struct mc33880 *mc; + int ret; + + mc = dev_get_drvdata(&spi->dev); + if (mc == NULL) + return -ENODEV; + + dev_set_drvdata(&spi->dev, NULL); + + ret = gpiochip_remove(&mc->chip); + if (!ret) { + mutex_destroy(&mc->lock); + kfree(mc); + } else + dev_err(&spi->dev, "Failed to remove the GPIO controller: %d\n", + ret); + + return ret; +} + +static struct spi_driver mc33880_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = mc33880_probe, + .remove = __devexit_p(mc33880_remove), +}; + +static int __init mc33880_init(void) +{ + return spi_register_driver(&mc33880_driver); +} +/* register after spi postcore initcall and before + * subsys initcalls that may rely on these GPIOs + */ +subsys_initcall(mc33880_init); + +static void __exit mc33880_exit(void) +{ + spi_unregister_driver(&mc33880_driver); +} +module_exit(mc33880_exit); + +MODULE_AUTHOR("Mocean Laboratories <info@mocean-labs.com>"); +MODULE_LICENSE("GPL v2"); + diff --git a/drivers/gpio/gpio-mc9s08dz60.c b/drivers/gpio/gpio-mc9s08dz60.c new file mode 100644 index 00000000..2738cc44 --- /dev/null +++ b/drivers/gpio/gpio-mc9s08dz60.c @@ -0,0 +1,161 @@ +/* + * Copyright 2009-2012 Freescale Semiconductor, Inc. All Rights Reserved. + * + * Author: Wu Guoxing <b39297@freescale.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. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/gpio.h> + +#define GPIO_GROUP_NUM 2 +#define GPIO_NUM_PER_GROUP 8 +#define GPIO_NUM (GPIO_GROUP_NUM*GPIO_NUM_PER_GROUP) + +struct mc9s08dz60 { + struct i2c_client *client; + struct gpio_chip chip; +}; + +static inline struct mc9s08dz60 *to_mc9s08dz60(struct gpio_chip *gc) +{ + return container_of(gc, struct mc9s08dz60, chip); +} + + +static void mc9s_gpio_to_reg_and_bit(int offset, u8 *reg, u8 *bit) +{ + *reg = 0x20 + offset / GPIO_NUM_PER_GROUP; + *bit = offset % GPIO_NUM_PER_GROUP; +} + +static int mc9s08dz60_get_value(struct gpio_chip *gc, unsigned offset) +{ + u8 reg, bit; + s32 value; + struct mc9s08dz60 *mc9s = to_mc9s08dz60(gc); + + mc9s_gpio_to_reg_and_bit(offset, ®, &bit); + value = i2c_smbus_read_byte_data(mc9s->client, reg); + + return (value >= 0) ? (value >> bit) & 0x1 : 0; +} + +static int mc9s08dz60_set(struct mc9s08dz60 *mc9s, unsigned offset, int val) +{ + u8 reg, bit; + s32 value; + + mc9s_gpio_to_reg_and_bit(offset, ®, &bit); + value = i2c_smbus_read_byte_data(mc9s->client, reg); + if (value >= 0) { + if (val) + value |= 1 << bit; + else + value &= ~(1 << bit); + + return i2c_smbus_write_byte_data(mc9s->client, reg, value); + } else + return value; + +} + + +static void mc9s08dz60_set_value(struct gpio_chip *gc, unsigned offset, int val) +{ + struct mc9s08dz60 *mc9s = to_mc9s08dz60(gc); + + mc9s08dz60_set(mc9s, offset, val); +} + +static int mc9s08dz60_direction_output(struct gpio_chip *gc, + unsigned offset, int val) +{ + struct mc9s08dz60 *mc9s = to_mc9s08dz60(gc); + + return mc9s08dz60_set(mc9s, offset, val); +} + +static int mc9s08dz60_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0; + struct mc9s08dz60 *mc9s; + + mc9s = kzalloc(sizeof(*mc9s), GFP_KERNEL); + if (!mc9s) + return -ENOMEM; + + mc9s->chip.label = client->name; + mc9s->chip.base = -1; + mc9s->chip.dev = &client->dev; + mc9s->chip.owner = THIS_MODULE; + mc9s->chip.ngpio = GPIO_NUM; + mc9s->chip.can_sleep = 1; + mc9s->chip.get = mc9s08dz60_get_value; + mc9s->chip.set = mc9s08dz60_set_value; + mc9s->chip.direction_output = mc9s08dz60_direction_output; + mc9s->client = client; + i2c_set_clientdata(client, mc9s); + + ret = gpiochip_add(&mc9s->chip); + if (ret) + goto error; + + return 0; + + error: + kfree(mc9s); + return ret; +} + +static int mc9s08dz60_remove(struct i2c_client *client) +{ + struct mc9s08dz60 *mc9s; + int ret; + + mc9s = i2c_get_clientdata(client); + + ret = gpiochip_remove(&mc9s->chip); + if (!ret) + kfree(mc9s); + + return ret; + +} + +static const struct i2c_device_id mc9s08dz60_id[] = { + {"mc9s08dz60", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, mc9s08dz60_id); + +static struct i2c_driver mc9s08dz60_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "mc9s08dz60", + }, + .probe = mc9s08dz60_probe, + .remove = mc9s08dz60_remove, + .id_table = mc9s08dz60_id, +}; + +module_i2c_driver(mc9s08dz60_i2c_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc. " + "Wu Guoxing <b39297@freescale.com>"); +MODULE_DESCRIPTION("mc9s08dz60 gpio function on mx35 3ds board"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpio/gpio-mcp23s08.c b/drivers/gpio/gpio-mcp23s08.c new file mode 100644 index 00000000..c5d83a8a --- /dev/null +++ b/drivers/gpio/gpio-mcp23s08.c @@ -0,0 +1,724 @@ +/* + * MCP23S08 SPI/GPIO gpio expander driver + */ + +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/spi/spi.h> +#include <linux/spi/mcp23s08.h> +#include <linux/slab.h> +#include <asm/byteorder.h> + +/** + * MCP types supported by driver + */ +#define MCP_TYPE_S08 0 +#define MCP_TYPE_S17 1 +#define MCP_TYPE_008 2 +#define MCP_TYPE_017 3 + +/* Registers are all 8 bits wide. + * + * The mcp23s17 has twice as many bits, and can be configured to work + * with either 16 bit registers or with two adjacent 8 bit banks. + */ +#define MCP_IODIR 0x00 /* init/reset: all ones */ +#define MCP_IPOL 0x01 +#define MCP_GPINTEN 0x02 +#define MCP_DEFVAL 0x03 +#define MCP_INTCON 0x04 +#define MCP_IOCON 0x05 +# define IOCON_SEQOP (1 << 5) +# define IOCON_HAEN (1 << 3) +# define IOCON_ODR (1 << 2) +# define IOCON_INTPOL (1 << 1) +#define MCP_GPPU 0x06 +#define MCP_INTF 0x07 +#define MCP_INTCAP 0x08 +#define MCP_GPIO 0x09 +#define MCP_OLAT 0x0a + +struct mcp23s08; + +struct mcp23s08_ops { + int (*read)(struct mcp23s08 *mcp, unsigned reg); + int (*write)(struct mcp23s08 *mcp, unsigned reg, unsigned val); + int (*read_regs)(struct mcp23s08 *mcp, unsigned reg, + u16 *vals, unsigned n); +}; + +struct mcp23s08 { + u8 addr; + + u16 cache[11]; + /* lock protects the cached values */ + struct mutex lock; + + struct gpio_chip chip; + + const struct mcp23s08_ops *ops; + void *data; /* ops specific data */ +}; + +/* A given spi_device can represent up to eight mcp23sxx chips + * sharing the same chipselect but using different addresses + * (e.g. chips #0 and #3 might be populated, but not #1 or $2). + * Driver data holds all the per-chip data. + */ +struct mcp23s08_driver_data { + unsigned ngpio; + struct mcp23s08 *mcp[8]; + struct mcp23s08 chip[]; +}; + +/*----------------------------------------------------------------------*/ + +#ifdef CONFIG_I2C + +static int mcp23008_read(struct mcp23s08 *mcp, unsigned reg) +{ + return i2c_smbus_read_byte_data(mcp->data, reg); +} + +static int mcp23008_write(struct mcp23s08 *mcp, unsigned reg, unsigned val) +{ + return i2c_smbus_write_byte_data(mcp->data, reg, val); +} + +static int +mcp23008_read_regs(struct mcp23s08 *mcp, unsigned reg, u16 *vals, unsigned n) +{ + while (n--) { + int ret = mcp23008_read(mcp, reg++); + if (ret < 0) + return ret; + *vals++ = ret; + } + + return 0; +} + +static int mcp23017_read(struct mcp23s08 *mcp, unsigned reg) +{ + return i2c_smbus_read_word_data(mcp->data, reg << 1); +} + +static int mcp23017_write(struct mcp23s08 *mcp, unsigned reg, unsigned val) +{ + return i2c_smbus_write_word_data(mcp->data, reg << 1, val); +} + +static int +mcp23017_read_regs(struct mcp23s08 *mcp, unsigned reg, u16 *vals, unsigned n) +{ + while (n--) { + int ret = mcp23017_read(mcp, reg++); + if (ret < 0) + return ret; + *vals++ = ret; + } + + return 0; +} + +static const struct mcp23s08_ops mcp23008_ops = { + .read = mcp23008_read, + .write = mcp23008_write, + .read_regs = mcp23008_read_regs, +}; + +static const struct mcp23s08_ops mcp23017_ops = { + .read = mcp23017_read, + .write = mcp23017_write, + .read_regs = mcp23017_read_regs, +}; + +#endif /* CONFIG_I2C */ + +/*----------------------------------------------------------------------*/ + +#ifdef CONFIG_SPI_MASTER + +static int mcp23s08_read(struct mcp23s08 *mcp, unsigned reg) +{ + u8 tx[2], rx[1]; + int status; + + tx[0] = mcp->addr | 0x01; + tx[1] = reg; + status = spi_write_then_read(mcp->data, tx, sizeof tx, rx, sizeof rx); + return (status < 0) ? status : rx[0]; +} + +static int mcp23s08_write(struct mcp23s08 *mcp, unsigned reg, unsigned val) +{ + u8 tx[3]; + + tx[0] = mcp->addr; + tx[1] = reg; + tx[2] = val; + return spi_write_then_read(mcp->data, tx, sizeof tx, NULL, 0); +} + +static int +mcp23s08_read_regs(struct mcp23s08 *mcp, unsigned reg, u16 *vals, unsigned n) +{ + u8 tx[2], *tmp; + int status; + + if ((n + reg) > sizeof mcp->cache) + return -EINVAL; + tx[0] = mcp->addr | 0x01; + tx[1] = reg; + + tmp = (u8 *)vals; + status = spi_write_then_read(mcp->data, tx, sizeof tx, tmp, n); + if (status >= 0) { + while (n--) + vals[n] = tmp[n]; /* expand to 16bit */ + } + return status; +} + +static int mcp23s17_read(struct mcp23s08 *mcp, unsigned reg) +{ + u8 tx[2], rx[2]; + int status; + + tx[0] = mcp->addr | 0x01; + tx[1] = reg << 1; + status = spi_write_then_read(mcp->data, tx, sizeof tx, rx, sizeof rx); + return (status < 0) ? status : (rx[0] | (rx[1] << 8)); +} + +static int mcp23s17_write(struct mcp23s08 *mcp, unsigned reg, unsigned val) +{ + u8 tx[4]; + + tx[0] = mcp->addr; + tx[1] = reg << 1; + tx[2] = val; + tx[3] = val >> 8; + return spi_write_then_read(mcp->data, tx, sizeof tx, NULL, 0); +} + +static int +mcp23s17_read_regs(struct mcp23s08 *mcp, unsigned reg, u16 *vals, unsigned n) +{ + u8 tx[2]; + int status; + + if ((n + reg) > sizeof mcp->cache) + return -EINVAL; + tx[0] = mcp->addr | 0x01; + tx[1] = reg << 1; + + status = spi_write_then_read(mcp->data, tx, sizeof tx, + (u8 *)vals, n * 2); + if (status >= 0) { + while (n--) + vals[n] = __le16_to_cpu((__le16)vals[n]); + } + + return status; +} + +static const struct mcp23s08_ops mcp23s08_ops = { + .read = mcp23s08_read, + .write = mcp23s08_write, + .read_regs = mcp23s08_read_regs, +}; + +static const struct mcp23s08_ops mcp23s17_ops = { + .read = mcp23s17_read, + .write = mcp23s17_write, + .read_regs = mcp23s17_read_regs, +}; + +#endif /* CONFIG_SPI_MASTER */ + +/*----------------------------------------------------------------------*/ + +static int mcp23s08_direction_input(struct gpio_chip *chip, unsigned offset) +{ + struct mcp23s08 *mcp = container_of(chip, struct mcp23s08, chip); + int status; + + mutex_lock(&mcp->lock); + mcp->cache[MCP_IODIR] |= (1 << offset); + status = mcp->ops->write(mcp, MCP_IODIR, mcp->cache[MCP_IODIR]); + mutex_unlock(&mcp->lock); + return status; +} + +static int mcp23s08_get(struct gpio_chip *chip, unsigned offset) +{ + struct mcp23s08 *mcp = container_of(chip, struct mcp23s08, chip); + int status; + + mutex_lock(&mcp->lock); + + /* REVISIT reading this clears any IRQ ... */ + status = mcp->ops->read(mcp, MCP_GPIO); + if (status < 0) + status = 0; + else { + mcp->cache[MCP_GPIO] = status; + status = !!(status & (1 << offset)); + } + mutex_unlock(&mcp->lock); + return status; +} + +static int __mcp23s08_set(struct mcp23s08 *mcp, unsigned mask, int value) +{ + unsigned olat = mcp->cache[MCP_OLAT]; + + if (value) + olat |= mask; + else + olat &= ~mask; + mcp->cache[MCP_OLAT] = olat; + return mcp->ops->write(mcp, MCP_OLAT, olat); +} + +static void mcp23s08_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct mcp23s08 *mcp = container_of(chip, struct mcp23s08, chip); + unsigned mask = 1 << offset; + + mutex_lock(&mcp->lock); + __mcp23s08_set(mcp, mask, value); + mutex_unlock(&mcp->lock); +} + +static int +mcp23s08_direction_output(struct gpio_chip *chip, unsigned offset, int value) +{ + struct mcp23s08 *mcp = container_of(chip, struct mcp23s08, chip); + unsigned mask = 1 << offset; + int status; + + mutex_lock(&mcp->lock); + status = __mcp23s08_set(mcp, mask, value); + if (status == 0) { + mcp->cache[MCP_IODIR] &= ~mask; + status = mcp->ops->write(mcp, MCP_IODIR, mcp->cache[MCP_IODIR]); + } + mutex_unlock(&mcp->lock); + return status; +} + +/*----------------------------------------------------------------------*/ + +#ifdef CONFIG_DEBUG_FS + +#include <linux/seq_file.h> + +/* + * This shows more info than the generic gpio dump code: + * pullups, deglitching, open drain drive. + */ +static void mcp23s08_dbg_show(struct seq_file *s, struct gpio_chip *chip) +{ + struct mcp23s08 *mcp; + char bank; + int t; + unsigned mask; + + mcp = container_of(chip, struct mcp23s08, chip); + + /* NOTE: we only handle one bank for now ... */ + bank = '0' + ((mcp->addr >> 1) & 0x7); + + mutex_lock(&mcp->lock); + t = mcp->ops->read_regs(mcp, 0, mcp->cache, ARRAY_SIZE(mcp->cache)); + if (t < 0) { + seq_printf(s, " I/O ERROR %d\n", t); + goto done; + } + + for (t = 0, mask = 1; t < chip->ngpio; t++, mask <<= 1) { + const char *label; + + label = gpiochip_is_requested(chip, t); + if (!label) + continue; + + seq_printf(s, " gpio-%-3d P%c.%d (%-12s) %s %s %s", + chip->base + t, bank, t, label, + (mcp->cache[MCP_IODIR] & mask) ? "in " : "out", + (mcp->cache[MCP_GPIO] & mask) ? "hi" : "lo", + (mcp->cache[MCP_GPPU] & mask) ? " " : "up"); + /* NOTE: ignoring the irq-related registers */ + seq_printf(s, "\n"); + } +done: + mutex_unlock(&mcp->lock); +} + +#else +#define mcp23s08_dbg_show NULL +#endif + +/*----------------------------------------------------------------------*/ + +static int mcp23s08_probe_one(struct mcp23s08 *mcp, struct device *dev, + void *data, unsigned addr, + unsigned type, unsigned base, unsigned pullups) +{ + int status; + + mutex_init(&mcp->lock); + + mcp->data = data; + mcp->addr = addr; + + mcp->chip.direction_input = mcp23s08_direction_input; + mcp->chip.get = mcp23s08_get; + mcp->chip.direction_output = mcp23s08_direction_output; + mcp->chip.set = mcp23s08_set; + mcp->chip.dbg_show = mcp23s08_dbg_show; + + switch (type) { +#ifdef CONFIG_SPI_MASTER + case MCP_TYPE_S08: + mcp->ops = &mcp23s08_ops; + mcp->chip.ngpio = 8; + mcp->chip.label = "mcp23s08"; + break; + + case MCP_TYPE_S17: + mcp->ops = &mcp23s17_ops; + mcp->chip.ngpio = 16; + mcp->chip.label = "mcp23s17"; + break; +#endif /* CONFIG_SPI_MASTER */ + +#ifdef CONFIG_I2C + case MCP_TYPE_008: + mcp->ops = &mcp23008_ops; + mcp->chip.ngpio = 8; + mcp->chip.label = "mcp23008"; + break; + + case MCP_TYPE_017: + mcp->ops = &mcp23017_ops; + mcp->chip.ngpio = 16; + mcp->chip.label = "mcp23017"; + break; +#endif /* CONFIG_I2C */ + + default: + dev_err(dev, "invalid device type (%d)\n", type); + return -EINVAL; + } + + mcp->chip.base = base; + mcp->chip.can_sleep = 1; + mcp->chip.dev = dev; + mcp->chip.owner = THIS_MODULE; + + /* verify MCP_IOCON.SEQOP = 0, so sequential reads work, + * and MCP_IOCON.HAEN = 1, so we work with all chips. + */ + status = mcp->ops->read(mcp, MCP_IOCON); + if (status < 0) + goto fail; + if ((status & IOCON_SEQOP) || !(status & IOCON_HAEN)) { + /* mcp23s17 has IOCON twice, make sure they are in sync */ + status &= ~(IOCON_SEQOP | (IOCON_SEQOP << 8)); + status |= IOCON_HAEN | (IOCON_HAEN << 8); + status = mcp->ops->write(mcp, MCP_IOCON, status); + if (status < 0) + goto fail; + } + + /* configure ~100K pullups */ + status = mcp->ops->write(mcp, MCP_GPPU, pullups); + if (status < 0) + goto fail; + + status = mcp->ops->read_regs(mcp, 0, mcp->cache, ARRAY_SIZE(mcp->cache)); + if (status < 0) + goto fail; + + /* disable inverter on input */ + if (mcp->cache[MCP_IPOL] != 0) { + mcp->cache[MCP_IPOL] = 0; + status = mcp->ops->write(mcp, MCP_IPOL, 0); + if (status < 0) + goto fail; + } + + /* disable irqs */ + if (mcp->cache[MCP_GPINTEN] != 0) { + mcp->cache[MCP_GPINTEN] = 0; + status = mcp->ops->write(mcp, MCP_GPINTEN, 0); + if (status < 0) + goto fail; + } + + status = gpiochip_add(&mcp->chip); +fail: + if (status < 0) + dev_dbg(dev, "can't setup chip %d, --> %d\n", + addr, status); + return status; +} + +/*----------------------------------------------------------------------*/ + +#ifdef CONFIG_I2C + +static int __devinit mcp230xx_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct mcp23s08_platform_data *pdata; + struct mcp23s08 *mcp; + int status; + + pdata = client->dev.platform_data; + if (!pdata || !gpio_is_valid(pdata->base)) { + dev_dbg(&client->dev, "invalid or missing platform data\n"); + return -EINVAL; + } + + mcp = kzalloc(sizeof *mcp, GFP_KERNEL); + if (!mcp) + return -ENOMEM; + + status = mcp23s08_probe_one(mcp, &client->dev, client, client->addr, + id->driver_data, pdata->base, + pdata->chip[0].pullups); + if (status) + goto fail; + + i2c_set_clientdata(client, mcp); + + return 0; + +fail: + kfree(mcp); + + return status; +} + +static int __devexit mcp230xx_remove(struct i2c_client *client) +{ + struct mcp23s08 *mcp = i2c_get_clientdata(client); + int status; + + status = gpiochip_remove(&mcp->chip); + if (status == 0) + kfree(mcp); + + return status; +} + +static const struct i2c_device_id mcp230xx_id[] = { + { "mcp23008", MCP_TYPE_008 }, + { "mcp23017", MCP_TYPE_017 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, mcp230xx_id); + +static struct i2c_driver mcp230xx_driver = { + .driver = { + .name = "mcp230xx", + .owner = THIS_MODULE, + }, + .probe = mcp230xx_probe, + .remove = __devexit_p(mcp230xx_remove), + .id_table = mcp230xx_id, +}; + +static int __init mcp23s08_i2c_init(void) +{ + return i2c_add_driver(&mcp230xx_driver); +} + +static void mcp23s08_i2c_exit(void) +{ + i2c_del_driver(&mcp230xx_driver); +} + +#else + +static int __init mcp23s08_i2c_init(void) { return 0; } +static void mcp23s08_i2c_exit(void) { } + +#endif /* CONFIG_I2C */ + +/*----------------------------------------------------------------------*/ + +#ifdef CONFIG_SPI_MASTER + +static int mcp23s08_probe(struct spi_device *spi) +{ + struct mcp23s08_platform_data *pdata; + unsigned addr; + unsigned chips = 0; + struct mcp23s08_driver_data *data; + int status, type; + unsigned base; + + type = spi_get_device_id(spi)->driver_data; + + pdata = spi->dev.platform_data; + if (!pdata || !gpio_is_valid(pdata->base)) { + dev_dbg(&spi->dev, "invalid or missing platform data\n"); + return -EINVAL; + } + + for (addr = 0; addr < ARRAY_SIZE(pdata->chip); addr++) { + if (!pdata->chip[addr].is_present) + continue; + chips++; + if ((type == MCP_TYPE_S08) && (addr > 3)) { + dev_err(&spi->dev, + "mcp23s08 only supports address 0..3\n"); + return -EINVAL; + } + } + if (!chips) + return -ENODEV; + + data = kzalloc(sizeof *data + chips * sizeof(struct mcp23s08), + GFP_KERNEL); + if (!data) + return -ENOMEM; + spi_set_drvdata(spi, data); + + base = pdata->base; + for (addr = 0; addr < ARRAY_SIZE(pdata->chip); addr++) { + if (!pdata->chip[addr].is_present) + continue; + chips--; + data->mcp[addr] = &data->chip[chips]; + status = mcp23s08_probe_one(data->mcp[addr], &spi->dev, spi, + 0x40 | (addr << 1), type, base, + pdata->chip[addr].pullups); + if (status < 0) + goto fail; + + base += (type == MCP_TYPE_S17) ? 16 : 8; + } + data->ngpio = base - pdata->base; + + /* NOTE: these chips have a relatively sane IRQ framework, with + * per-signal masking and level/edge triggering. It's not yet + * handled here... + */ + + return 0; + +fail: + for (addr = 0; addr < ARRAY_SIZE(data->mcp); addr++) { + int tmp; + + if (!data->mcp[addr]) + continue; + tmp = gpiochip_remove(&data->mcp[addr]->chip); + if (tmp < 0) + dev_err(&spi->dev, "%s --> %d\n", "remove", tmp); + } + kfree(data); + return status; +} + +static int mcp23s08_remove(struct spi_device *spi) +{ + struct mcp23s08_driver_data *data = spi_get_drvdata(spi); + unsigned addr; + int status = 0; + + for (addr = 0; addr < ARRAY_SIZE(data->mcp); addr++) { + int tmp; + + if (!data->mcp[addr]) + continue; + + tmp = gpiochip_remove(&data->mcp[addr]->chip); + if (tmp < 0) { + dev_err(&spi->dev, "%s --> %d\n", "remove", tmp); + status = tmp; + } + } + if (status == 0) + kfree(data); + return status; +} + +static const struct spi_device_id mcp23s08_ids[] = { + { "mcp23s08", MCP_TYPE_S08 }, + { "mcp23s17", MCP_TYPE_S17 }, + { }, +}; +MODULE_DEVICE_TABLE(spi, mcp23s08_ids); + +static struct spi_driver mcp23s08_driver = { + .probe = mcp23s08_probe, + .remove = mcp23s08_remove, + .id_table = mcp23s08_ids, + .driver = { + .name = "mcp23s08", + .owner = THIS_MODULE, + }, +}; + +static int __init mcp23s08_spi_init(void) +{ + return spi_register_driver(&mcp23s08_driver); +} + +static void mcp23s08_spi_exit(void) +{ + spi_unregister_driver(&mcp23s08_driver); +} + +#else + +static int __init mcp23s08_spi_init(void) { return 0; } +static void mcp23s08_spi_exit(void) { } + +#endif /* CONFIG_SPI_MASTER */ + +/*----------------------------------------------------------------------*/ + +static int __init mcp23s08_init(void) +{ + int ret; + + ret = mcp23s08_spi_init(); + if (ret) + goto spi_fail; + + ret = mcp23s08_i2c_init(); + if (ret) + goto i2c_fail; + + return 0; + + i2c_fail: + mcp23s08_spi_exit(); + spi_fail: + return ret; +} +/* register after spi/i2c postcore initcall and before + * subsys initcalls that may rely on these GPIOs + */ +subsys_initcall(mcp23s08_init); + +static void __exit mcp23s08_exit(void) +{ + mcp23s08_spi_exit(); + mcp23s08_i2c_exit(); +} +module_exit(mcp23s08_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/gpio/gpio-ml-ioh.c b/drivers/gpio/gpio-ml-ioh.c new file mode 100644 index 00000000..f0febe5b --- /dev/null +++ b/drivers/gpio/gpio-ml-ioh.c @@ -0,0 +1,627 @@ +/* + * 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 <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/irq.h> + +#define IOH_EDGE_FALLING 0 +#define IOH_EDGE_RISING BIT(0) +#define IOH_LEVEL_L BIT(1) +#define IOH_LEVEL_H (BIT(0) | BIT(1)) +#define IOH_EDGE_BOTH BIT(2) +#define IOH_IM_MASK (BIT(0) | BIT(1) | BIT(2)) + +#define IOH_IRQ_BASE 0 + +#define PCI_VENDOR_ID_ROHM 0x10DB + +struct ioh_reg_comn { + u32 ien; + u32 istatus; + u32 idisp; + u32 iclr; + u32 imask; + u32 imaskclr; + u32 po; + u32 pi; + u32 pm; + u32 im_0; + u32 im_1; + u32 reserved; +}; + +struct ioh_regs { + struct ioh_reg_comn regs[8]; + u32 reserve1[16]; + u32 ioh_sel_reg[4]; + u32 reserve2[11]; + u32 srst; +}; + +/** + * struct ioh_gpio_reg_data - The register store data. + * @ien_reg To store contents of interrupt enable register. + * @imask_reg: To store contents of interrupt mask regist + * @po_reg: To store contents of PO register. + * @pm_reg: To store contents of PM register. + * @im0_reg: To store contents of interrupt mode regist0 + * @im1_reg: To store contents of interrupt mode regist1 + * @use_sel_reg: To store contents of GPIO_USE_SEL0~3 + */ +struct ioh_gpio_reg_data { + u32 ien_reg; + u32 imask_reg; + u32 po_reg; + u32 pm_reg; + u32 im0_reg; + u32 im1_reg; + u32 use_sel_reg; +}; + +/** + * struct ioh_gpio - GPIO private data structure. + * @base: PCI base address of Memory mapped I/O register. + * @reg: Memory mapped IOH GPIO register list. + * @dev: Pointer to device structure. + * @gpio: Data for GPIO infrastructure. + * @ioh_gpio_reg: Memory mapped Register data is saved here + * when suspend. + * @gpio_use_sel: Save GPIO_USE_SEL1~4 register for PM + * @ch: Indicate GPIO channel + * @irq_base: Save base of IRQ number for interrupt + * @spinlock: Used for register access protection in + * interrupt context ioh_irq_type and PM; + */ +struct ioh_gpio { + void __iomem *base; + struct ioh_regs __iomem *reg; + struct device *dev; + struct gpio_chip gpio; + struct ioh_gpio_reg_data ioh_gpio_reg; + u32 gpio_use_sel; + struct mutex lock; + int ch; + int irq_base; + spinlock_t spinlock; +}; + +static const int num_ports[] = {6, 12, 16, 16, 15, 16, 16, 12}; + +static void ioh_gpio_set(struct gpio_chip *gpio, unsigned nr, int val) +{ + u32 reg_val; + struct ioh_gpio *chip = container_of(gpio, struct ioh_gpio, gpio); + + mutex_lock(&chip->lock); + reg_val = ioread32(&chip->reg->regs[chip->ch].po); + if (val) + reg_val |= (1 << nr); + else + reg_val &= ~(1 << nr); + + iowrite32(reg_val, &chip->reg->regs[chip->ch].po); + mutex_unlock(&chip->lock); +} + +static int ioh_gpio_get(struct gpio_chip *gpio, unsigned nr) +{ + struct ioh_gpio *chip = container_of(gpio, struct ioh_gpio, gpio); + + return ioread32(&chip->reg->regs[chip->ch].pi) & (1 << nr); +} + +static int ioh_gpio_direction_output(struct gpio_chip *gpio, unsigned nr, + int val) +{ + struct ioh_gpio *chip = container_of(gpio, struct ioh_gpio, gpio); + u32 pm; + u32 reg_val; + + mutex_lock(&chip->lock); + pm = ioread32(&chip->reg->regs[chip->ch].pm) & + ((1 << num_ports[chip->ch]) - 1); + pm |= (1 << nr); + iowrite32(pm, &chip->reg->regs[chip->ch].pm); + + reg_val = ioread32(&chip->reg->regs[chip->ch].po); + if (val) + reg_val |= (1 << nr); + else + reg_val &= ~(1 << nr); + iowrite32(reg_val, &chip->reg->regs[chip->ch].po); + + mutex_unlock(&chip->lock); + + return 0; +} + +static int ioh_gpio_direction_input(struct gpio_chip *gpio, unsigned nr) +{ + struct ioh_gpio *chip = container_of(gpio, struct ioh_gpio, gpio); + u32 pm; + + mutex_lock(&chip->lock); + pm = ioread32(&chip->reg->regs[chip->ch].pm) & + ((1 << num_ports[chip->ch]) - 1); + pm &= ~(1 << nr); + iowrite32(pm, &chip->reg->regs[chip->ch].pm); + mutex_unlock(&chip->lock); + + return 0; +} + +#ifdef CONFIG_PM +/* + * Save register configuration and disable interrupts. + */ +static void ioh_gpio_save_reg_conf(struct ioh_gpio *chip) +{ + int i; + + for (i = 0; i < 8; i ++, chip++) { + chip->ioh_gpio_reg.po_reg = + ioread32(&chip->reg->regs[chip->ch].po); + chip->ioh_gpio_reg.pm_reg = + ioread32(&chip->reg->regs[chip->ch].pm); + chip->ioh_gpio_reg.ien_reg = + ioread32(&chip->reg->regs[chip->ch].ien); + chip->ioh_gpio_reg.imask_reg = + ioread32(&chip->reg->regs[chip->ch].imask); + chip->ioh_gpio_reg.im0_reg = + ioread32(&chip->reg->regs[chip->ch].im_0); + chip->ioh_gpio_reg.im1_reg = + ioread32(&chip->reg->regs[chip->ch].im_1); + if (i < 4) + chip->ioh_gpio_reg.use_sel_reg = + ioread32(&chip->reg->ioh_sel_reg[i]); + } +} + +/* + * This function restores the register configuration of the GPIO device. + */ +static void ioh_gpio_restore_reg_conf(struct ioh_gpio *chip) +{ + int i; + + for (i = 0; i < 8; i ++, chip++) { + iowrite32(chip->ioh_gpio_reg.po_reg, + &chip->reg->regs[chip->ch].po); + iowrite32(chip->ioh_gpio_reg.pm_reg, + &chip->reg->regs[chip->ch].pm); + iowrite32(chip->ioh_gpio_reg.ien_reg, + &chip->reg->regs[chip->ch].ien); + iowrite32(chip->ioh_gpio_reg.imask_reg, + &chip->reg->regs[chip->ch].imask); + iowrite32(chip->ioh_gpio_reg.im0_reg, + &chip->reg->regs[chip->ch].im_0); + iowrite32(chip->ioh_gpio_reg.im1_reg, + &chip->reg->regs[chip->ch].im_1); + if (i < 4) + iowrite32(chip->ioh_gpio_reg.use_sel_reg, + &chip->reg->ioh_sel_reg[i]); + } +} +#endif + +static int ioh_gpio_to_irq(struct gpio_chip *gpio, unsigned offset) +{ + struct ioh_gpio *chip = container_of(gpio, struct ioh_gpio, gpio); + return chip->irq_base + offset; +} + +static void ioh_gpio_setup(struct ioh_gpio *chip, int num_port) +{ + struct gpio_chip *gpio = &chip->gpio; + + gpio->label = dev_name(chip->dev); + gpio->owner = THIS_MODULE; + gpio->direction_input = ioh_gpio_direction_input; + gpio->get = ioh_gpio_get; + gpio->direction_output = ioh_gpio_direction_output; + gpio->set = ioh_gpio_set; + gpio->dbg_show = NULL; + gpio->base = -1; + gpio->ngpio = num_port; + gpio->can_sleep = 0; + gpio->to_irq = ioh_gpio_to_irq; +} + +static int ioh_irq_type(struct irq_data *d, unsigned int type) +{ + u32 im; + void __iomem *im_reg; + u32 ien; + u32 im_pos; + int ch; + unsigned long flags; + u32 val; + int irq = d->irq; + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct ioh_gpio *chip = gc->private; + + ch = irq - chip->irq_base; + if (irq <= chip->irq_base + 7) { + im_reg = &chip->reg->regs[chip->ch].im_0; + im_pos = ch; + } else { + im_reg = &chip->reg->regs[chip->ch].im_1; + im_pos = ch - 8; + } + dev_dbg(chip->dev, "%s:irq=%d type=%d ch=%d pos=%d type=%d\n", + __func__, irq, type, ch, im_pos, type); + + spin_lock_irqsave(&chip->spinlock, flags); + + switch (type) { + case IRQ_TYPE_EDGE_RISING: + val = IOH_EDGE_RISING; + break; + case IRQ_TYPE_EDGE_FALLING: + val = IOH_EDGE_FALLING; + break; + case IRQ_TYPE_EDGE_BOTH: + val = IOH_EDGE_BOTH; + break; + case IRQ_TYPE_LEVEL_HIGH: + val = IOH_LEVEL_H; + break; + case IRQ_TYPE_LEVEL_LOW: + val = IOH_LEVEL_L; + break; + case IRQ_TYPE_PROBE: + goto end; + default: + dev_warn(chip->dev, "%s: unknown type(%dd)", + __func__, type); + goto end; + } + + /* Set interrupt mode */ + im = ioread32(im_reg) & ~(IOH_IM_MASK << (im_pos * 4)); + iowrite32(im | (val << (im_pos * 4)), im_reg); + + /* iclr */ + iowrite32(BIT(ch), &chip->reg->regs[chip->ch].iclr); + + /* IMASKCLR */ + iowrite32(BIT(ch), &chip->reg->regs[chip->ch].imaskclr); + + /* Enable interrupt */ + ien = ioread32(&chip->reg->regs[chip->ch].ien); + iowrite32(ien | BIT(ch), &chip->reg->regs[chip->ch].ien); +end: + spin_unlock_irqrestore(&chip->spinlock, flags); + + return 0; +} + +static void ioh_irq_unmask(struct irq_data *d) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct ioh_gpio *chip = gc->private; + + iowrite32(1 << (d->irq - chip->irq_base), + &chip->reg->regs[chip->ch].imaskclr); +} + +static void ioh_irq_mask(struct irq_data *d) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct ioh_gpio *chip = gc->private; + + iowrite32(1 << (d->irq - chip->irq_base), + &chip->reg->regs[chip->ch].imask); +} + +static void ioh_irq_disable(struct irq_data *d) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct ioh_gpio *chip = gc->private; + unsigned long flags; + u32 ien; + + spin_lock_irqsave(&chip->spinlock, flags); + ien = ioread32(&chip->reg->regs[chip->ch].ien); + ien &= ~(1 << (d->irq - chip->irq_base)); + iowrite32(ien, &chip->reg->regs[chip->ch].ien); + spin_unlock_irqrestore(&chip->spinlock, flags); +} + +static void ioh_irq_enable(struct irq_data *d) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct ioh_gpio *chip = gc->private; + unsigned long flags; + u32 ien; + + spin_lock_irqsave(&chip->spinlock, flags); + ien = ioread32(&chip->reg->regs[chip->ch].ien); + ien |= 1 << (d->irq - chip->irq_base); + iowrite32(ien, &chip->reg->regs[chip->ch].ien); + spin_unlock_irqrestore(&chip->spinlock, flags); +} + +static irqreturn_t ioh_gpio_handler(int irq, void *dev_id) +{ + struct ioh_gpio *chip = dev_id; + u32 reg_val; + int i, j; + int ret = IRQ_NONE; + + for (i = 0; i < 8; i++, chip++) { + reg_val = ioread32(&chip->reg->regs[i].istatus); + for (j = 0; j < num_ports[i]; j++) { + if (reg_val & BIT(j)) { + dev_dbg(chip->dev, + "%s:[%d]:irq=%d status=0x%x\n", + __func__, j, irq, reg_val); + iowrite32(BIT(j), + &chip->reg->regs[chip->ch].iclr); + generic_handle_irq(chip->irq_base + j); + ret = IRQ_HANDLED; + } + } + } + return ret; +} + +static __devinit void ioh_gpio_alloc_generic_chip(struct ioh_gpio *chip, + unsigned int irq_start, unsigned int num) +{ + struct irq_chip_generic *gc; + struct irq_chip_type *ct; + + gc = irq_alloc_generic_chip("ioh_gpio", 1, irq_start, chip->base, + handle_simple_irq); + gc->private = chip; + ct = gc->chip_types; + + ct->chip.irq_mask = ioh_irq_mask; + ct->chip.irq_unmask = ioh_irq_unmask; + ct->chip.irq_set_type = ioh_irq_type; + ct->chip.irq_disable = ioh_irq_disable; + ct->chip.irq_enable = ioh_irq_enable; + + irq_setup_generic_chip(gc, IRQ_MSK(num), IRQ_GC_INIT_MASK_CACHE, + IRQ_NOREQUEST | IRQ_NOPROBE, 0); +} + +static int __devinit ioh_gpio_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + int ret; + int i, j; + struct ioh_gpio *chip; + void __iomem *base; + void *chip_save; + int irq_base; + + 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; + } + + base = pci_iomap(pdev, 1, 0); + if (!base) { + dev_err(&pdev->dev, "%s : pci_iomap failed", __func__); + ret = -ENOMEM; + goto err_iomap; + } + + chip_save = kzalloc(sizeof(*chip) * 8, GFP_KERNEL); + if (chip_save == NULL) { + dev_err(&pdev->dev, "%s : kzalloc failed", __func__); + ret = -ENOMEM; + goto err_kzalloc; + } + + chip = chip_save; + for (i = 0; i < 8; i++, chip++) { + chip->dev = &pdev->dev; + chip->base = base; + chip->reg = chip->base; + chip->ch = i; + mutex_init(&chip->lock); + spin_lock_init(&chip->spinlock); + ioh_gpio_setup(chip, num_ports[i]); + ret = gpiochip_add(&chip->gpio); + if (ret) { + dev_err(&pdev->dev, "IOH gpio: Failed to register GPIO\n"); + goto err_gpiochip_add; + } + } + + chip = chip_save; + for (j = 0; j < 8; j++, chip++) { + irq_base = irq_alloc_descs(-1, IOH_IRQ_BASE, num_ports[j], + NUMA_NO_NODE); + if (irq_base < 0) { + dev_warn(&pdev->dev, + "ml_ioh_gpio: Failed to get IRQ base num\n"); + chip->irq_base = -1; + goto err_irq_alloc_descs; + } + chip->irq_base = irq_base; + ioh_gpio_alloc_generic_chip(chip, irq_base, num_ports[j]); + } + + chip = chip_save; + ret = request_irq(pdev->irq, ioh_gpio_handler, + IRQF_SHARED, KBUILD_MODNAME, chip); + if (ret != 0) { + dev_err(&pdev->dev, + "%s request_irq failed\n", __func__); + goto err_request_irq; + } + + pci_set_drvdata(pdev, chip); + + return 0; + +err_request_irq: + chip = chip_save; +err_irq_alloc_descs: + while (--j >= 0) { + chip--; + irq_free_descs(chip->irq_base, num_ports[j]); + } + + chip = chip_save; +err_gpiochip_add: + while (--i >= 0) { + chip--; + ret = gpiochip_remove(&chip->gpio); + if (ret) + dev_err(&pdev->dev, "Failed gpiochip_remove(%d)\n", i); + } + kfree(chip_save); + +err_kzalloc: + pci_iounmap(pdev, base); + +err_iomap: + pci_release_regions(pdev); + +err_request_regions: + pci_disable_device(pdev); + +err_pci_enable: + + dev_err(&pdev->dev, "%s Failed returns %d\n", __func__, ret); + return ret; +} + +static void __devexit ioh_gpio_remove(struct pci_dev *pdev) +{ + int err; + int i; + struct ioh_gpio *chip = pci_get_drvdata(pdev); + void *chip_save; + + chip_save = chip; + + free_irq(pdev->irq, chip); + + for (i = 0; i < 8; i++, chip++) { + irq_free_descs(chip->irq_base, num_ports[i]); + err = gpiochip_remove(&chip->gpio); + if (err) + dev_err(&pdev->dev, "Failed gpiochip_remove\n"); + } + + chip = chip_save; + pci_iounmap(pdev, chip->base); + pci_release_regions(pdev); + pci_disable_device(pdev); + kfree(chip); +} + +#ifdef CONFIG_PM +static int ioh_gpio_suspend(struct pci_dev *pdev, pm_message_t state) +{ + s32 ret; + struct ioh_gpio *chip = pci_get_drvdata(pdev); + unsigned long flags; + + spin_lock_irqsave(&chip->spinlock, flags); + ioh_gpio_save_reg_conf(chip); + spin_unlock_irqrestore(&chip->spinlock, flags); + + 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 ioh_gpio_resume(struct pci_dev *pdev) +{ + s32 ret; + struct ioh_gpio *chip = pci_get_drvdata(pdev); + unsigned long flags; + + 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); + + spin_lock_irqsave(&chip->spinlock, flags); + iowrite32(0x01, &chip->reg->srst); + iowrite32(0x00, &chip->reg->srst); + ioh_gpio_restore_reg_conf(chip); + spin_unlock_irqrestore(&chip->spinlock, flags); + + return 0; +} +#else +#define ioh_gpio_suspend NULL +#define ioh_gpio_resume NULL +#endif + +static DEFINE_PCI_DEVICE_TABLE(ioh_gpio_pcidev_id) = { + { PCI_DEVICE(PCI_VENDOR_ID_ROHM, 0x802E) }, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, ioh_gpio_pcidev_id); + +static struct pci_driver ioh_gpio_driver = { + .name = "ml_ioh_gpio", + .id_table = ioh_gpio_pcidev_id, + .probe = ioh_gpio_probe, + .remove = __devexit_p(ioh_gpio_remove), + .suspend = ioh_gpio_suspend, + .resume = ioh_gpio_resume +}; + +static int __init ioh_gpio_pci_init(void) +{ + return pci_register_driver(&ioh_gpio_driver); +} +module_init(ioh_gpio_pci_init); + +static void __exit ioh_gpio_pci_exit(void) +{ + pci_unregister_driver(&ioh_gpio_driver); +} +module_exit(ioh_gpio_pci_exit); + +MODULE_DESCRIPTION("OKI SEMICONDUCTOR ML-IOH series GPIO Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpio/gpio-mpc5200.c b/drivers/gpio/gpio-mpc5200.c new file mode 100644 index 00000000..2c7cef36 --- /dev/null +++ b/drivers/gpio/gpio-mpc5200.c @@ -0,0 +1,377 @@ +/* + * MPC52xx gpio driver + * + * Copyright (c) 2008 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix + * + * 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. + * + * 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/of.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/of_gpio.h> +#include <linux/io.h> +#include <linux/of_platform.h> +#include <linux/module.h> + +#include <asm/gpio.h> +#include <asm/mpc52xx.h> +#include <sysdev/fsl_soc.h> + +static DEFINE_SPINLOCK(gpio_lock); + +struct mpc52xx_gpiochip { + struct of_mm_gpio_chip mmchip; + unsigned int shadow_dvo; + unsigned int shadow_gpioe; + unsigned int shadow_ddr; +}; + +/* + * GPIO LIB API implementation for wakeup GPIOs. + * + * There's a maximum of 8 wakeup GPIOs. Which of these are available + * for use depends on your board setup. + * + * 0 -> GPIO_WKUP_7 + * 1 -> GPIO_WKUP_6 + * 2 -> PSC6_1 + * 3 -> PSC6_0 + * 4 -> ETH_17 + * 5 -> PSC3_9 + * 6 -> PSC2_4 + * 7 -> PSC1_4 + * + */ +static int mpc52xx_wkup_gpio_get(struct gpio_chip *gc, unsigned int gpio) +{ + struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc); + struct mpc52xx_gpio_wkup __iomem *regs = mm_gc->regs; + unsigned int ret; + + ret = (in_8(®s->wkup_ival) >> (7 - gpio)) & 1; + + pr_debug("%s: gpio: %d ret: %d\n", __func__, gpio, ret); + + return ret; +} + +static inline void +__mpc52xx_wkup_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val) +{ + struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc); + struct mpc52xx_gpiochip *chip = container_of(mm_gc, + struct mpc52xx_gpiochip, mmchip); + struct mpc52xx_gpio_wkup __iomem *regs = mm_gc->regs; + + if (val) + chip->shadow_dvo |= 1 << (7 - gpio); + else + chip->shadow_dvo &= ~(1 << (7 - gpio)); + + out_8(®s->wkup_dvo, chip->shadow_dvo); +} + +static void +mpc52xx_wkup_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val) +{ + unsigned long flags; + + spin_lock_irqsave(&gpio_lock, flags); + + __mpc52xx_wkup_gpio_set(gc, gpio, val); + + spin_unlock_irqrestore(&gpio_lock, flags); + + pr_debug("%s: gpio: %d val: %d\n", __func__, gpio, val); +} + +static int mpc52xx_wkup_gpio_dir_in(struct gpio_chip *gc, unsigned int gpio) +{ + struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc); + struct mpc52xx_gpiochip *chip = container_of(mm_gc, + struct mpc52xx_gpiochip, mmchip); + struct mpc52xx_gpio_wkup __iomem *regs = mm_gc->regs; + unsigned long flags; + + spin_lock_irqsave(&gpio_lock, flags); + + /* set the direction */ + chip->shadow_ddr &= ~(1 << (7 - gpio)); + out_8(®s->wkup_ddr, chip->shadow_ddr); + + /* and enable the pin */ + chip->shadow_gpioe |= 1 << (7 - gpio); + out_8(®s->wkup_gpioe, chip->shadow_gpioe); + + spin_unlock_irqrestore(&gpio_lock, flags); + + return 0; +} + +static int +mpc52xx_wkup_gpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val) +{ + struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc); + struct mpc52xx_gpio_wkup __iomem *regs = mm_gc->regs; + struct mpc52xx_gpiochip *chip = container_of(mm_gc, + struct mpc52xx_gpiochip, mmchip); + unsigned long flags; + + spin_lock_irqsave(&gpio_lock, flags); + + __mpc52xx_wkup_gpio_set(gc, gpio, val); + + /* Then set direction */ + chip->shadow_ddr |= 1 << (7 - gpio); + out_8(®s->wkup_ddr, chip->shadow_ddr); + + /* Finally enable the pin */ + chip->shadow_gpioe |= 1 << (7 - gpio); + out_8(®s->wkup_gpioe, chip->shadow_gpioe); + + spin_unlock_irqrestore(&gpio_lock, flags); + + pr_debug("%s: gpio: %d val: %d\n", __func__, gpio, val); + + return 0; +} + +static int __devinit mpc52xx_wkup_gpiochip_probe(struct platform_device *ofdev) +{ + struct mpc52xx_gpiochip *chip; + struct mpc52xx_gpio_wkup __iomem *regs; + struct gpio_chip *gc; + int ret; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + gc = &chip->mmchip.gc; + + gc->ngpio = 8; + gc->direction_input = mpc52xx_wkup_gpio_dir_in; + gc->direction_output = mpc52xx_wkup_gpio_dir_out; + gc->get = mpc52xx_wkup_gpio_get; + gc->set = mpc52xx_wkup_gpio_set; + + ret = of_mm_gpiochip_add(ofdev->dev.of_node, &chip->mmchip); + if (ret) + return ret; + + regs = chip->mmchip.regs; + chip->shadow_gpioe = in_8(®s->wkup_gpioe); + chip->shadow_ddr = in_8(®s->wkup_ddr); + chip->shadow_dvo = in_8(®s->wkup_dvo); + + return 0; +} + +static int mpc52xx_gpiochip_remove(struct platform_device *ofdev) +{ + return -EBUSY; +} + +static const struct of_device_id mpc52xx_wkup_gpiochip_match[] = { + { .compatible = "fsl,mpc5200-gpio-wkup", }, + {} +}; + +static struct platform_driver mpc52xx_wkup_gpiochip_driver = { + .driver = { + .name = "mpc5200-gpio-wkup", + .owner = THIS_MODULE, + .of_match_table = mpc52xx_wkup_gpiochip_match, + }, + .probe = mpc52xx_wkup_gpiochip_probe, + .remove = mpc52xx_gpiochip_remove, +}; + +/* + * GPIO LIB API implementation for simple GPIOs + * + * There's a maximum of 32 simple GPIOs. Which of these are available + * for use depends on your board setup. + * The numbering reflects the bit numbering in the port registers: + * + * 0..1 > reserved + * 2..3 > IRDA + * 4..7 > ETHR + * 8..11 > reserved + * 12..15 > USB + * 16..17 > reserved + * 18..23 > PSC3 + * 24..27 > PSC2 + * 28..31 > PSC1 + */ +static int mpc52xx_simple_gpio_get(struct gpio_chip *gc, unsigned int gpio) +{ + struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc); + struct mpc52xx_gpio __iomem *regs = mm_gc->regs; + unsigned int ret; + + ret = (in_be32(®s->simple_ival) >> (31 - gpio)) & 1; + + return ret; +} + +static inline void +__mpc52xx_simple_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val) +{ + struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc); + struct mpc52xx_gpiochip *chip = container_of(mm_gc, + struct mpc52xx_gpiochip, mmchip); + struct mpc52xx_gpio __iomem *regs = mm_gc->regs; + + if (val) + chip->shadow_dvo |= 1 << (31 - gpio); + else + chip->shadow_dvo &= ~(1 << (31 - gpio)); + out_be32(®s->simple_dvo, chip->shadow_dvo); +} + +static void +mpc52xx_simple_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val) +{ + unsigned long flags; + + spin_lock_irqsave(&gpio_lock, flags); + + __mpc52xx_simple_gpio_set(gc, gpio, val); + + spin_unlock_irqrestore(&gpio_lock, flags); + + pr_debug("%s: gpio: %d val: %d\n", __func__, gpio, val); +} + +static int mpc52xx_simple_gpio_dir_in(struct gpio_chip *gc, unsigned int gpio) +{ + struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc); + struct mpc52xx_gpiochip *chip = container_of(mm_gc, + struct mpc52xx_gpiochip, mmchip); + struct mpc52xx_gpio __iomem *regs = mm_gc->regs; + unsigned long flags; + + spin_lock_irqsave(&gpio_lock, flags); + + /* set the direction */ + chip->shadow_ddr &= ~(1 << (31 - gpio)); + out_be32(®s->simple_ddr, chip->shadow_ddr); + + /* and enable the pin */ + chip->shadow_gpioe |= 1 << (31 - gpio); + out_be32(®s->simple_gpioe, chip->shadow_gpioe); + + spin_unlock_irqrestore(&gpio_lock, flags); + + return 0; +} + +static int +mpc52xx_simple_gpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val) +{ + struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc); + struct mpc52xx_gpiochip *chip = container_of(mm_gc, + struct mpc52xx_gpiochip, mmchip); + struct mpc52xx_gpio __iomem *regs = mm_gc->regs; + unsigned long flags; + + spin_lock_irqsave(&gpio_lock, flags); + + /* First set initial value */ + __mpc52xx_simple_gpio_set(gc, gpio, val); + + /* Then set direction */ + chip->shadow_ddr |= 1 << (31 - gpio); + out_be32(®s->simple_ddr, chip->shadow_ddr); + + /* Finally enable the pin */ + chip->shadow_gpioe |= 1 << (31 - gpio); + out_be32(®s->simple_gpioe, chip->shadow_gpioe); + + spin_unlock_irqrestore(&gpio_lock, flags); + + pr_debug("%s: gpio: %d val: %d\n", __func__, gpio, val); + + return 0; +} + +static int __devinit mpc52xx_simple_gpiochip_probe(struct platform_device *ofdev) +{ + struct mpc52xx_gpiochip *chip; + struct gpio_chip *gc; + struct mpc52xx_gpio __iomem *regs; + int ret; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + gc = &chip->mmchip.gc; + + gc->ngpio = 32; + gc->direction_input = mpc52xx_simple_gpio_dir_in; + gc->direction_output = mpc52xx_simple_gpio_dir_out; + gc->get = mpc52xx_simple_gpio_get; + gc->set = mpc52xx_simple_gpio_set; + + ret = of_mm_gpiochip_add(ofdev->dev.of_node, &chip->mmchip); + if (ret) + return ret; + + regs = chip->mmchip.regs; + chip->shadow_gpioe = in_be32(®s->simple_gpioe); + chip->shadow_ddr = in_be32(®s->simple_ddr); + chip->shadow_dvo = in_be32(®s->simple_dvo); + + return 0; +} + +static const struct of_device_id mpc52xx_simple_gpiochip_match[] = { + { .compatible = "fsl,mpc5200-gpio", }, + {} +}; + +static struct platform_driver mpc52xx_simple_gpiochip_driver = { + .driver = { + .name = "mpc5200-gpio", + .owner = THIS_MODULE, + .of_match_table = mpc52xx_simple_gpiochip_match, + }, + .probe = mpc52xx_simple_gpiochip_probe, + .remove = mpc52xx_gpiochip_remove, +}; + +static int __init mpc52xx_gpio_init(void) +{ + if (platform_driver_register(&mpc52xx_wkup_gpiochip_driver)) + printk(KERN_ERR "Unable to register wakeup GPIO driver\n"); + + if (platform_driver_register(&mpc52xx_simple_gpiochip_driver)) + printk(KERN_ERR "Unable to register simple GPIO driver\n"); + + return 0; +} + + +/* Make sure we get initialised before anyone else tries to use us */ +subsys_initcall(mpc52xx_gpio_init); + +/* No exit call at the moment as we cannot unregister of gpio chips */ + +MODULE_DESCRIPTION("Freescale MPC52xx gpio driver"); +MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de"); +MODULE_LICENSE("GPL v2"); + diff --git a/drivers/gpio/gpio-mpc8xxx.c b/drivers/gpio/gpio-mpc8xxx.c new file mode 100644 index 00000000..5a1817ee --- /dev/null +++ b/drivers/gpio/gpio-mpc8xxx.c @@ -0,0 +1,389 @@ +/* + * GPIOs on MPC512x/8349/8572/8610 and compatible + * + * Copyright (C) 2008 Peter Korsgaard <jacmet@sunsite.dk> + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/gpio.h> +#include <linux/slab.h> +#include <linux/irq.h> + +#define MPC8XXX_GPIO_PINS 32 + +#define GPIO_DIR 0x00 +#define GPIO_ODR 0x04 +#define GPIO_DAT 0x08 +#define GPIO_IER 0x0c +#define GPIO_IMR 0x10 +#define GPIO_ICR 0x14 +#define GPIO_ICR2 0x18 + +struct mpc8xxx_gpio_chip { + struct of_mm_gpio_chip mm_gc; + spinlock_t lock; + + /* + * shadowed data register to be able to clear/set output pins in + * open drain mode safely + */ + u32 data; + struct irq_domain *irq; + void *of_dev_id_data; +}; + +static inline u32 mpc8xxx_gpio2mask(unsigned int gpio) +{ + return 1u << (MPC8XXX_GPIO_PINS - 1 - gpio); +} + +static inline struct mpc8xxx_gpio_chip * +to_mpc8xxx_gpio_chip(struct of_mm_gpio_chip *mm) +{ + return container_of(mm, struct mpc8xxx_gpio_chip, mm_gc); +} + +static void mpc8xxx_gpio_save_regs(struct of_mm_gpio_chip *mm) +{ + struct mpc8xxx_gpio_chip *mpc8xxx_gc = to_mpc8xxx_gpio_chip(mm); + + mpc8xxx_gc->data = in_be32(mm->regs + GPIO_DAT); +} + +/* Workaround GPIO 1 errata on MPC8572/MPC8536. The status of GPIOs + * defined as output cannot be determined by reading GPDAT register, + * so we use shadow data register instead. The status of input pins + * is determined by reading GPDAT register. + */ +static int mpc8572_gpio_get(struct gpio_chip *gc, unsigned int gpio) +{ + u32 val; + struct of_mm_gpio_chip *mm = to_of_mm_gpio_chip(gc); + struct mpc8xxx_gpio_chip *mpc8xxx_gc = to_mpc8xxx_gpio_chip(mm); + + val = in_be32(mm->regs + GPIO_DAT) & ~in_be32(mm->regs + GPIO_DIR); + + return (val | mpc8xxx_gc->data) & mpc8xxx_gpio2mask(gpio); +} + +static int mpc8xxx_gpio_get(struct gpio_chip *gc, unsigned int gpio) +{ + struct of_mm_gpio_chip *mm = to_of_mm_gpio_chip(gc); + + return in_be32(mm->regs + GPIO_DAT) & mpc8xxx_gpio2mask(gpio); +} + +static void mpc8xxx_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val) +{ + struct of_mm_gpio_chip *mm = to_of_mm_gpio_chip(gc); + struct mpc8xxx_gpio_chip *mpc8xxx_gc = to_mpc8xxx_gpio_chip(mm); + unsigned long flags; + + spin_lock_irqsave(&mpc8xxx_gc->lock, flags); + + if (val) + mpc8xxx_gc->data |= mpc8xxx_gpio2mask(gpio); + else + mpc8xxx_gc->data &= ~mpc8xxx_gpio2mask(gpio); + + out_be32(mm->regs + GPIO_DAT, mpc8xxx_gc->data); + + spin_unlock_irqrestore(&mpc8xxx_gc->lock, flags); +} + +static int mpc8xxx_gpio_dir_in(struct gpio_chip *gc, unsigned int gpio) +{ + struct of_mm_gpio_chip *mm = to_of_mm_gpio_chip(gc); + struct mpc8xxx_gpio_chip *mpc8xxx_gc = to_mpc8xxx_gpio_chip(mm); + unsigned long flags; + + spin_lock_irqsave(&mpc8xxx_gc->lock, flags); + + clrbits32(mm->regs + GPIO_DIR, mpc8xxx_gpio2mask(gpio)); + + spin_unlock_irqrestore(&mpc8xxx_gc->lock, flags); + + return 0; +} + +static int mpc8xxx_gpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val) +{ + struct of_mm_gpio_chip *mm = to_of_mm_gpio_chip(gc); + struct mpc8xxx_gpio_chip *mpc8xxx_gc = to_mpc8xxx_gpio_chip(mm); + unsigned long flags; + + mpc8xxx_gpio_set(gc, gpio, val); + + spin_lock_irqsave(&mpc8xxx_gc->lock, flags); + + setbits32(mm->regs + GPIO_DIR, mpc8xxx_gpio2mask(gpio)); + + spin_unlock_irqrestore(&mpc8xxx_gc->lock, flags); + + return 0; +} + +static int mpc5121_gpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val) +{ + /* GPIO 28..31 are input only on MPC5121 */ + if (gpio >= 28) + return -EINVAL; + + return mpc8xxx_gpio_dir_out(gc, gpio, val); +} + +static int mpc8xxx_gpio_to_irq(struct gpio_chip *gc, unsigned offset) +{ + struct of_mm_gpio_chip *mm = to_of_mm_gpio_chip(gc); + struct mpc8xxx_gpio_chip *mpc8xxx_gc = to_mpc8xxx_gpio_chip(mm); + + if (mpc8xxx_gc->irq && offset < MPC8XXX_GPIO_PINS) + return irq_create_mapping(mpc8xxx_gc->irq, offset); + else + return -ENXIO; +} + +static void mpc8xxx_gpio_irq_cascade(unsigned int irq, struct irq_desc *desc) +{ + struct mpc8xxx_gpio_chip *mpc8xxx_gc = irq_desc_get_handler_data(desc); + struct irq_chip *chip = irq_desc_get_chip(desc); + struct of_mm_gpio_chip *mm = &mpc8xxx_gc->mm_gc; + unsigned int mask; + + mask = in_be32(mm->regs + GPIO_IER) & in_be32(mm->regs + GPIO_IMR); + if (mask) + generic_handle_irq(irq_linear_revmap(mpc8xxx_gc->irq, + 32 - ffs(mask))); + if (chip->irq_eoi) + chip->irq_eoi(&desc->irq_data); +} + +static void mpc8xxx_irq_unmask(struct irq_data *d) +{ + struct mpc8xxx_gpio_chip *mpc8xxx_gc = irq_data_get_irq_chip_data(d); + struct of_mm_gpio_chip *mm = &mpc8xxx_gc->mm_gc; + unsigned long flags; + + spin_lock_irqsave(&mpc8xxx_gc->lock, flags); + + setbits32(mm->regs + GPIO_IMR, mpc8xxx_gpio2mask(irqd_to_hwirq(d))); + + spin_unlock_irqrestore(&mpc8xxx_gc->lock, flags); +} + +static void mpc8xxx_irq_mask(struct irq_data *d) +{ + struct mpc8xxx_gpio_chip *mpc8xxx_gc = irq_data_get_irq_chip_data(d); + struct of_mm_gpio_chip *mm = &mpc8xxx_gc->mm_gc; + unsigned long flags; + + spin_lock_irqsave(&mpc8xxx_gc->lock, flags); + + clrbits32(mm->regs + GPIO_IMR, mpc8xxx_gpio2mask(irqd_to_hwirq(d))); + + spin_unlock_irqrestore(&mpc8xxx_gc->lock, flags); +} + +static void mpc8xxx_irq_ack(struct irq_data *d) +{ + struct mpc8xxx_gpio_chip *mpc8xxx_gc = irq_data_get_irq_chip_data(d); + struct of_mm_gpio_chip *mm = &mpc8xxx_gc->mm_gc; + + out_be32(mm->regs + GPIO_IER, mpc8xxx_gpio2mask(irqd_to_hwirq(d))); +} + +static int mpc8xxx_irq_set_type(struct irq_data *d, unsigned int flow_type) +{ + struct mpc8xxx_gpio_chip *mpc8xxx_gc = irq_data_get_irq_chip_data(d); + struct of_mm_gpio_chip *mm = &mpc8xxx_gc->mm_gc; + unsigned long flags; + + switch (flow_type) { + case IRQ_TYPE_EDGE_FALLING: + spin_lock_irqsave(&mpc8xxx_gc->lock, flags); + setbits32(mm->regs + GPIO_ICR, + mpc8xxx_gpio2mask(irqd_to_hwirq(d))); + spin_unlock_irqrestore(&mpc8xxx_gc->lock, flags); + break; + + case IRQ_TYPE_EDGE_BOTH: + spin_lock_irqsave(&mpc8xxx_gc->lock, flags); + clrbits32(mm->regs + GPIO_ICR, + mpc8xxx_gpio2mask(irqd_to_hwirq(d))); + spin_unlock_irqrestore(&mpc8xxx_gc->lock, flags); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int mpc512x_irq_set_type(struct irq_data *d, unsigned int flow_type) +{ + struct mpc8xxx_gpio_chip *mpc8xxx_gc = irq_data_get_irq_chip_data(d); + struct of_mm_gpio_chip *mm = &mpc8xxx_gc->mm_gc; + unsigned long gpio = irqd_to_hwirq(d); + void __iomem *reg; + unsigned int shift; + unsigned long flags; + + if (gpio < 16) { + reg = mm->regs + GPIO_ICR; + shift = (15 - gpio) * 2; + } else { + reg = mm->regs + GPIO_ICR2; + shift = (15 - (gpio % 16)) * 2; + } + + switch (flow_type) { + case IRQ_TYPE_EDGE_FALLING: + case IRQ_TYPE_LEVEL_LOW: + spin_lock_irqsave(&mpc8xxx_gc->lock, flags); + clrsetbits_be32(reg, 3 << shift, 2 << shift); + spin_unlock_irqrestore(&mpc8xxx_gc->lock, flags); + break; + + case IRQ_TYPE_EDGE_RISING: + case IRQ_TYPE_LEVEL_HIGH: + spin_lock_irqsave(&mpc8xxx_gc->lock, flags); + clrsetbits_be32(reg, 3 << shift, 1 << shift); + spin_unlock_irqrestore(&mpc8xxx_gc->lock, flags); + break; + + case IRQ_TYPE_EDGE_BOTH: + spin_lock_irqsave(&mpc8xxx_gc->lock, flags); + clrbits32(reg, 3 << shift); + spin_unlock_irqrestore(&mpc8xxx_gc->lock, flags); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static struct irq_chip mpc8xxx_irq_chip = { + .name = "mpc8xxx-gpio", + .irq_unmask = mpc8xxx_irq_unmask, + .irq_mask = mpc8xxx_irq_mask, + .irq_ack = mpc8xxx_irq_ack, + .irq_set_type = mpc8xxx_irq_set_type, +}; + +static int mpc8xxx_gpio_irq_map(struct irq_domain *h, unsigned int virq, + irq_hw_number_t hw) +{ + struct mpc8xxx_gpio_chip *mpc8xxx_gc = h->host_data; + + if (mpc8xxx_gc->of_dev_id_data) + mpc8xxx_irq_chip.irq_set_type = mpc8xxx_gc->of_dev_id_data; + + irq_set_chip_data(virq, h->host_data); + irq_set_chip_and_handler(virq, &mpc8xxx_irq_chip, handle_level_irq); + irq_set_irq_type(virq, IRQ_TYPE_NONE); + + return 0; +} + +static struct irq_domain_ops mpc8xxx_gpio_irq_ops = { + .map = mpc8xxx_gpio_irq_map, + .xlate = irq_domain_xlate_twocell, +}; + +static struct of_device_id mpc8xxx_gpio_ids[] __initdata = { + { .compatible = "fsl,mpc8349-gpio", }, + { .compatible = "fsl,mpc8572-gpio", }, + { .compatible = "fsl,mpc8610-gpio", }, + { .compatible = "fsl,mpc5121-gpio", .data = mpc512x_irq_set_type, }, + { .compatible = "fsl,pq3-gpio", }, + { .compatible = "fsl,qoriq-gpio", }, + {} +}; + +static void __init mpc8xxx_add_controller(struct device_node *np) +{ + struct mpc8xxx_gpio_chip *mpc8xxx_gc; + struct of_mm_gpio_chip *mm_gc; + struct gpio_chip *gc; + const struct of_device_id *id; + unsigned hwirq; + int ret; + + mpc8xxx_gc = kzalloc(sizeof(*mpc8xxx_gc), GFP_KERNEL); + if (!mpc8xxx_gc) { + ret = -ENOMEM; + goto err; + } + + spin_lock_init(&mpc8xxx_gc->lock); + + mm_gc = &mpc8xxx_gc->mm_gc; + gc = &mm_gc->gc; + + mm_gc->save_regs = mpc8xxx_gpio_save_regs; + gc->ngpio = MPC8XXX_GPIO_PINS; + gc->direction_input = mpc8xxx_gpio_dir_in; + gc->direction_output = of_device_is_compatible(np, "fsl,mpc5121-gpio") ? + mpc5121_gpio_dir_out : mpc8xxx_gpio_dir_out; + gc->get = of_device_is_compatible(np, "fsl,mpc8572-gpio") ? + mpc8572_gpio_get : mpc8xxx_gpio_get; + gc->set = mpc8xxx_gpio_set; + gc->to_irq = mpc8xxx_gpio_to_irq; + + ret = of_mm_gpiochip_add(np, mm_gc); + if (ret) + goto err; + + hwirq = irq_of_parse_and_map(np, 0); + if (hwirq == NO_IRQ) + goto skip_irq; + + mpc8xxx_gc->irq = irq_domain_add_linear(np, MPC8XXX_GPIO_PINS, + &mpc8xxx_gpio_irq_ops, mpc8xxx_gc); + if (!mpc8xxx_gc->irq) + goto skip_irq; + + id = of_match_node(mpc8xxx_gpio_ids, np); + if (id) + mpc8xxx_gc->of_dev_id_data = id->data; + + /* ack and mask all irqs */ + out_be32(mm_gc->regs + GPIO_IER, 0xffffffff); + out_be32(mm_gc->regs + GPIO_IMR, 0); + + irq_set_handler_data(hwirq, mpc8xxx_gc); + irq_set_chained_handler(hwirq, mpc8xxx_gpio_irq_cascade); + +skip_irq: + return; + +err: + pr_err("%s: registration failed with status %d\n", + np->full_name, ret); + kfree(mpc8xxx_gc); + + return; +} + +static int __init mpc8xxx_add_gpiochips(void) +{ + struct device_node *np; + + for_each_matching_node(np, mpc8xxx_gpio_ids) + mpc8xxx_add_controller(np); + + return 0; +} +arch_initcall(mpc8xxx_add_gpiochips); diff --git a/drivers/gpio/gpio-msm-v1.c b/drivers/gpio/gpio-msm-v1.c new file mode 100644 index 00000000..52a4d428 --- /dev/null +++ b/drivers/gpio/gpio-msm-v1.c @@ -0,0 +1,636 @@ +/* + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/bitops.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <mach/cpu.h> +#include <mach/msm_gpiomux.h> +#include <mach/msm_iomap.h> + +/* see 80-VA736-2 Rev C pp 695-751 +** +** These are actually the *shadow* gpio registers, since the +** real ones (which allow full access) are only available to the +** ARM9 side of the world. +** +** Since the _BASE need to be page-aligned when we're mapping them +** to virtual addresses, adjust for the additional offset in these +** macros. +*/ + +#define MSM_GPIO1_REG(off) (MSM_GPIO1_BASE + (off)) +#define MSM_GPIO2_REG(off) (MSM_GPIO2_BASE + 0x400 + (off)) +#define MSM_GPIO1_SHADOW_REG(off) (MSM_GPIO1_BASE + 0x800 + (off)) +#define MSM_GPIO2_SHADOW_REG(off) (MSM_GPIO2_BASE + 0xC00 + (off)) + +/* + * MSM7X00 registers + */ +/* output value */ +#define MSM7X00_GPIO_OUT_0 MSM_GPIO1_SHADOW_REG(0x00) /* gpio 15-0 */ +#define MSM7X00_GPIO_OUT_1 MSM_GPIO2_SHADOW_REG(0x00) /* gpio 42-16 */ +#define MSM7X00_GPIO_OUT_2 MSM_GPIO1_SHADOW_REG(0x04) /* gpio 67-43 */ +#define MSM7X00_GPIO_OUT_3 MSM_GPIO1_SHADOW_REG(0x08) /* gpio 94-68 */ +#define MSM7X00_GPIO_OUT_4 MSM_GPIO1_SHADOW_REG(0x0C) /* gpio 106-95 */ +#define MSM7X00_GPIO_OUT_5 MSM_GPIO1_SHADOW_REG(0x50) /* gpio 107-121 */ + +/* same pin map as above, output enable */ +#define MSM7X00_GPIO_OE_0 MSM_GPIO1_SHADOW_REG(0x10) +#define MSM7X00_GPIO_OE_1 MSM_GPIO2_SHADOW_REG(0x08) +#define MSM7X00_GPIO_OE_2 MSM_GPIO1_SHADOW_REG(0x14) +#define MSM7X00_GPIO_OE_3 MSM_GPIO1_SHADOW_REG(0x18) +#define MSM7X00_GPIO_OE_4 MSM_GPIO1_SHADOW_REG(0x1C) +#define MSM7X00_GPIO_OE_5 MSM_GPIO1_SHADOW_REG(0x54) + +/* same pin map as above, input read */ +#define MSM7X00_GPIO_IN_0 MSM_GPIO1_SHADOW_REG(0x34) +#define MSM7X00_GPIO_IN_1 MSM_GPIO2_SHADOW_REG(0x20) +#define MSM7X00_GPIO_IN_2 MSM_GPIO1_SHADOW_REG(0x38) +#define MSM7X00_GPIO_IN_3 MSM_GPIO1_SHADOW_REG(0x3C) +#define MSM7X00_GPIO_IN_4 MSM_GPIO1_SHADOW_REG(0x40) +#define MSM7X00_GPIO_IN_5 MSM_GPIO1_SHADOW_REG(0x44) + +/* same pin map as above, 1=edge 0=level interrup */ +#define MSM7X00_GPIO_INT_EDGE_0 MSM_GPIO1_SHADOW_REG(0x60) +#define MSM7X00_GPIO_INT_EDGE_1 MSM_GPIO2_SHADOW_REG(0x50) +#define MSM7X00_GPIO_INT_EDGE_2 MSM_GPIO1_SHADOW_REG(0x64) +#define MSM7X00_GPIO_INT_EDGE_3 MSM_GPIO1_SHADOW_REG(0x68) +#define MSM7X00_GPIO_INT_EDGE_4 MSM_GPIO1_SHADOW_REG(0x6C) +#define MSM7X00_GPIO_INT_EDGE_5 MSM_GPIO1_SHADOW_REG(0xC0) + +/* same pin map as above, 1=positive 0=negative */ +#define MSM7X00_GPIO_INT_POS_0 MSM_GPIO1_SHADOW_REG(0x70) +#define MSM7X00_GPIO_INT_POS_1 MSM_GPIO2_SHADOW_REG(0x58) +#define MSM7X00_GPIO_INT_POS_2 MSM_GPIO1_SHADOW_REG(0x74) +#define MSM7X00_GPIO_INT_POS_3 MSM_GPIO1_SHADOW_REG(0x78) +#define MSM7X00_GPIO_INT_POS_4 MSM_GPIO1_SHADOW_REG(0x7C) +#define MSM7X00_GPIO_INT_POS_5 MSM_GPIO1_SHADOW_REG(0xBC) + +/* same pin map as above, interrupt enable */ +#define MSM7X00_GPIO_INT_EN_0 MSM_GPIO1_SHADOW_REG(0x80) +#define MSM7X00_GPIO_INT_EN_1 MSM_GPIO2_SHADOW_REG(0x60) +#define MSM7X00_GPIO_INT_EN_2 MSM_GPIO1_SHADOW_REG(0x84) +#define MSM7X00_GPIO_INT_EN_3 MSM_GPIO1_SHADOW_REG(0x88) +#define MSM7X00_GPIO_INT_EN_4 MSM_GPIO1_SHADOW_REG(0x8C) +#define MSM7X00_GPIO_INT_EN_5 MSM_GPIO1_SHADOW_REG(0xB8) + +/* same pin map as above, write 1 to clear interrupt */ +#define MSM7X00_GPIO_INT_CLEAR_0 MSM_GPIO1_SHADOW_REG(0x90) +#define MSM7X00_GPIO_INT_CLEAR_1 MSM_GPIO2_SHADOW_REG(0x68) +#define MSM7X00_GPIO_INT_CLEAR_2 MSM_GPIO1_SHADOW_REG(0x94) +#define MSM7X00_GPIO_INT_CLEAR_3 MSM_GPIO1_SHADOW_REG(0x98) +#define MSM7X00_GPIO_INT_CLEAR_4 MSM_GPIO1_SHADOW_REG(0x9C) +#define MSM7X00_GPIO_INT_CLEAR_5 MSM_GPIO1_SHADOW_REG(0xB4) + +/* same pin map as above, 1=interrupt pending */ +#define MSM7X00_GPIO_INT_STATUS_0 MSM_GPIO1_SHADOW_REG(0xA0) +#define MSM7X00_GPIO_INT_STATUS_1 MSM_GPIO2_SHADOW_REG(0x70) +#define MSM7X00_GPIO_INT_STATUS_2 MSM_GPIO1_SHADOW_REG(0xA4) +#define MSM7X00_GPIO_INT_STATUS_3 MSM_GPIO1_SHADOW_REG(0xA8) +#define MSM7X00_GPIO_INT_STATUS_4 MSM_GPIO1_SHADOW_REG(0xAC) +#define MSM7X00_GPIO_INT_STATUS_5 MSM_GPIO1_SHADOW_REG(0xB0) + +/* + * QSD8X50 registers + */ +/* output value */ +#define QSD8X50_GPIO_OUT_0 MSM_GPIO1_SHADOW_REG(0x00) /* gpio 15-0 */ +#define QSD8X50_GPIO_OUT_1 MSM_GPIO2_SHADOW_REG(0x00) /* gpio 42-16 */ +#define QSD8X50_GPIO_OUT_2 MSM_GPIO1_SHADOW_REG(0x04) /* gpio 67-43 */ +#define QSD8X50_GPIO_OUT_3 MSM_GPIO1_SHADOW_REG(0x08) /* gpio 94-68 */ +#define QSD8X50_GPIO_OUT_4 MSM_GPIO1_SHADOW_REG(0x0C) /* gpio 103-95 */ +#define QSD8X50_GPIO_OUT_5 MSM_GPIO1_SHADOW_REG(0x10) /* gpio 121-104 */ +#define QSD8X50_GPIO_OUT_6 MSM_GPIO1_SHADOW_REG(0x14) /* gpio 152-122 */ +#define QSD8X50_GPIO_OUT_7 MSM_GPIO1_SHADOW_REG(0x18) /* gpio 164-153 */ + +/* same pin map as above, output enable */ +#define QSD8X50_GPIO_OE_0 MSM_GPIO1_SHADOW_REG(0x20) +#define QSD8X50_GPIO_OE_1 MSM_GPIO2_SHADOW_REG(0x08) +#define QSD8X50_GPIO_OE_2 MSM_GPIO1_SHADOW_REG(0x24) +#define QSD8X50_GPIO_OE_3 MSM_GPIO1_SHADOW_REG(0x28) +#define QSD8X50_GPIO_OE_4 MSM_GPIO1_SHADOW_REG(0x2C) +#define QSD8X50_GPIO_OE_5 MSM_GPIO1_SHADOW_REG(0x30) +#define QSD8X50_GPIO_OE_6 MSM_GPIO1_SHADOW_REG(0x34) +#define QSD8X50_GPIO_OE_7 MSM_GPIO1_SHADOW_REG(0x38) + +/* same pin map as above, input read */ +#define QSD8X50_GPIO_IN_0 MSM_GPIO1_SHADOW_REG(0x50) +#define QSD8X50_GPIO_IN_1 MSM_GPIO2_SHADOW_REG(0x20) +#define QSD8X50_GPIO_IN_2 MSM_GPIO1_SHADOW_REG(0x54) +#define QSD8X50_GPIO_IN_3 MSM_GPIO1_SHADOW_REG(0x58) +#define QSD8X50_GPIO_IN_4 MSM_GPIO1_SHADOW_REG(0x5C) +#define QSD8X50_GPIO_IN_5 MSM_GPIO1_SHADOW_REG(0x60) +#define QSD8X50_GPIO_IN_6 MSM_GPIO1_SHADOW_REG(0x64) +#define QSD8X50_GPIO_IN_7 MSM_GPIO1_SHADOW_REG(0x68) + +/* same pin map as above, 1=edge 0=level interrup */ +#define QSD8X50_GPIO_INT_EDGE_0 MSM_GPIO1_SHADOW_REG(0x70) +#define QSD8X50_GPIO_INT_EDGE_1 MSM_GPIO2_SHADOW_REG(0x50) +#define QSD8X50_GPIO_INT_EDGE_2 MSM_GPIO1_SHADOW_REG(0x74) +#define QSD8X50_GPIO_INT_EDGE_3 MSM_GPIO1_SHADOW_REG(0x78) +#define QSD8X50_GPIO_INT_EDGE_4 MSM_GPIO1_SHADOW_REG(0x7C) +#define QSD8X50_GPIO_INT_EDGE_5 MSM_GPIO1_SHADOW_REG(0x80) +#define QSD8X50_GPIO_INT_EDGE_6 MSM_GPIO1_SHADOW_REG(0x84) +#define QSD8X50_GPIO_INT_EDGE_7 MSM_GPIO1_SHADOW_REG(0x88) + +/* same pin map as above, 1=positive 0=negative */ +#define QSD8X50_GPIO_INT_POS_0 MSM_GPIO1_SHADOW_REG(0x90) +#define QSD8X50_GPIO_INT_POS_1 MSM_GPIO2_SHADOW_REG(0x58) +#define QSD8X50_GPIO_INT_POS_2 MSM_GPIO1_SHADOW_REG(0x94) +#define QSD8X50_GPIO_INT_POS_3 MSM_GPIO1_SHADOW_REG(0x98) +#define QSD8X50_GPIO_INT_POS_4 MSM_GPIO1_SHADOW_REG(0x9C) +#define QSD8X50_GPIO_INT_POS_5 MSM_GPIO1_SHADOW_REG(0xA0) +#define QSD8X50_GPIO_INT_POS_6 MSM_GPIO1_SHADOW_REG(0xA4) +#define QSD8X50_GPIO_INT_POS_7 MSM_GPIO1_SHADOW_REG(0xA8) + +/* same pin map as above, interrupt enable */ +#define QSD8X50_GPIO_INT_EN_0 MSM_GPIO1_SHADOW_REG(0xB0) +#define QSD8X50_GPIO_INT_EN_1 MSM_GPIO2_SHADOW_REG(0x60) +#define QSD8X50_GPIO_INT_EN_2 MSM_GPIO1_SHADOW_REG(0xB4) +#define QSD8X50_GPIO_INT_EN_3 MSM_GPIO1_SHADOW_REG(0xB8) +#define QSD8X50_GPIO_INT_EN_4 MSM_GPIO1_SHADOW_REG(0xBC) +#define QSD8X50_GPIO_INT_EN_5 MSM_GPIO1_SHADOW_REG(0xC0) +#define QSD8X50_GPIO_INT_EN_6 MSM_GPIO1_SHADOW_REG(0xC4) +#define QSD8X50_GPIO_INT_EN_7 MSM_GPIO1_SHADOW_REG(0xC8) + +/* same pin map as above, write 1 to clear interrupt */ +#define QSD8X50_GPIO_INT_CLEAR_0 MSM_GPIO1_SHADOW_REG(0xD0) +#define QSD8X50_GPIO_INT_CLEAR_1 MSM_GPIO2_SHADOW_REG(0x68) +#define QSD8X50_GPIO_INT_CLEAR_2 MSM_GPIO1_SHADOW_REG(0xD4) +#define QSD8X50_GPIO_INT_CLEAR_3 MSM_GPIO1_SHADOW_REG(0xD8) +#define QSD8X50_GPIO_INT_CLEAR_4 MSM_GPIO1_SHADOW_REG(0xDC) +#define QSD8X50_GPIO_INT_CLEAR_5 MSM_GPIO1_SHADOW_REG(0xE0) +#define QSD8X50_GPIO_INT_CLEAR_6 MSM_GPIO1_SHADOW_REG(0xE4) +#define QSD8X50_GPIO_INT_CLEAR_7 MSM_GPIO1_SHADOW_REG(0xE8) + +/* same pin map as above, 1=interrupt pending */ +#define QSD8X50_GPIO_INT_STATUS_0 MSM_GPIO1_SHADOW_REG(0xF0) +#define QSD8X50_GPIO_INT_STATUS_1 MSM_GPIO2_SHADOW_REG(0x70) +#define QSD8X50_GPIO_INT_STATUS_2 MSM_GPIO1_SHADOW_REG(0xF4) +#define QSD8X50_GPIO_INT_STATUS_3 MSM_GPIO1_SHADOW_REG(0xF8) +#define QSD8X50_GPIO_INT_STATUS_4 MSM_GPIO1_SHADOW_REG(0xFC) +#define QSD8X50_GPIO_INT_STATUS_5 MSM_GPIO1_SHADOW_REG(0x100) +#define QSD8X50_GPIO_INT_STATUS_6 MSM_GPIO1_SHADOW_REG(0x104) +#define QSD8X50_GPIO_INT_STATUS_7 MSM_GPIO1_SHADOW_REG(0x108) + +/* + * MSM7X30 registers + */ +/* output value */ +#define MSM7X30_GPIO_OUT_0 MSM_GPIO1_REG(0x00) /* gpio 15-0 */ +#define MSM7X30_GPIO_OUT_1 MSM_GPIO2_REG(0x00) /* gpio 43-16 */ +#define MSM7X30_GPIO_OUT_2 MSM_GPIO1_REG(0x04) /* gpio 67-44 */ +#define MSM7X30_GPIO_OUT_3 MSM_GPIO1_REG(0x08) /* gpio 94-68 */ +#define MSM7X30_GPIO_OUT_4 MSM_GPIO1_REG(0x0C) /* gpio 106-95 */ +#define MSM7X30_GPIO_OUT_5 MSM_GPIO1_REG(0x50) /* gpio 133-107 */ +#define MSM7X30_GPIO_OUT_6 MSM_GPIO1_REG(0xC4) /* gpio 150-134 */ +#define MSM7X30_GPIO_OUT_7 MSM_GPIO1_REG(0x214) /* gpio 181-151 */ + +/* same pin map as above, output enable */ +#define MSM7X30_GPIO_OE_0 MSM_GPIO1_REG(0x10) +#define MSM7X30_GPIO_OE_1 MSM_GPIO2_REG(0x08) +#define MSM7X30_GPIO_OE_2 MSM_GPIO1_REG(0x14) +#define MSM7X30_GPIO_OE_3 MSM_GPIO1_REG(0x18) +#define MSM7X30_GPIO_OE_4 MSM_GPIO1_REG(0x1C) +#define MSM7X30_GPIO_OE_5 MSM_GPIO1_REG(0x54) +#define MSM7X30_GPIO_OE_6 MSM_GPIO1_REG(0xC8) +#define MSM7X30_GPIO_OE_7 MSM_GPIO1_REG(0x218) + +/* same pin map as above, input read */ +#define MSM7X30_GPIO_IN_0 MSM_GPIO1_REG(0x34) +#define MSM7X30_GPIO_IN_1 MSM_GPIO2_REG(0x20) +#define MSM7X30_GPIO_IN_2 MSM_GPIO1_REG(0x38) +#define MSM7X30_GPIO_IN_3 MSM_GPIO1_REG(0x3C) +#define MSM7X30_GPIO_IN_4 MSM_GPIO1_REG(0x40) +#define MSM7X30_GPIO_IN_5 MSM_GPIO1_REG(0x44) +#define MSM7X30_GPIO_IN_6 MSM_GPIO1_REG(0xCC) +#define MSM7X30_GPIO_IN_7 MSM_GPIO1_REG(0x21C) + +/* same pin map as above, 1=edge 0=level interrup */ +#define MSM7X30_GPIO_INT_EDGE_0 MSM_GPIO1_REG(0x60) +#define MSM7X30_GPIO_INT_EDGE_1 MSM_GPIO2_REG(0x50) +#define MSM7X30_GPIO_INT_EDGE_2 MSM_GPIO1_REG(0x64) +#define MSM7X30_GPIO_INT_EDGE_3 MSM_GPIO1_REG(0x68) +#define MSM7X30_GPIO_INT_EDGE_4 MSM_GPIO1_REG(0x6C) +#define MSM7X30_GPIO_INT_EDGE_5 MSM_GPIO1_REG(0xC0) +#define MSM7X30_GPIO_INT_EDGE_6 MSM_GPIO1_REG(0xD0) +#define MSM7X30_GPIO_INT_EDGE_7 MSM_GPIO1_REG(0x240) + +/* same pin map as above, 1=positive 0=negative */ +#define MSM7X30_GPIO_INT_POS_0 MSM_GPIO1_REG(0x70) +#define MSM7X30_GPIO_INT_POS_1 MSM_GPIO2_REG(0x58) +#define MSM7X30_GPIO_INT_POS_2 MSM_GPIO1_REG(0x74) +#define MSM7X30_GPIO_INT_POS_3 MSM_GPIO1_REG(0x78) +#define MSM7X30_GPIO_INT_POS_4 MSM_GPIO1_REG(0x7C) +#define MSM7X30_GPIO_INT_POS_5 MSM_GPIO1_REG(0xBC) +#define MSM7X30_GPIO_INT_POS_6 MSM_GPIO1_REG(0xD4) +#define MSM7X30_GPIO_INT_POS_7 MSM_GPIO1_REG(0x228) + +/* same pin map as above, interrupt enable */ +#define MSM7X30_GPIO_INT_EN_0 MSM_GPIO1_REG(0x80) +#define MSM7X30_GPIO_INT_EN_1 MSM_GPIO2_REG(0x60) +#define MSM7X30_GPIO_INT_EN_2 MSM_GPIO1_REG(0x84) +#define MSM7X30_GPIO_INT_EN_3 MSM_GPIO1_REG(0x88) +#define MSM7X30_GPIO_INT_EN_4 MSM_GPIO1_REG(0x8C) +#define MSM7X30_GPIO_INT_EN_5 MSM_GPIO1_REG(0xB8) +#define MSM7X30_GPIO_INT_EN_6 MSM_GPIO1_REG(0xD8) +#define MSM7X30_GPIO_INT_EN_7 MSM_GPIO1_REG(0x22C) + +/* same pin map as above, write 1 to clear interrupt */ +#define MSM7X30_GPIO_INT_CLEAR_0 MSM_GPIO1_REG(0x90) +#define MSM7X30_GPIO_INT_CLEAR_1 MSM_GPIO2_REG(0x68) +#define MSM7X30_GPIO_INT_CLEAR_2 MSM_GPIO1_REG(0x94) +#define MSM7X30_GPIO_INT_CLEAR_3 MSM_GPIO1_REG(0x98) +#define MSM7X30_GPIO_INT_CLEAR_4 MSM_GPIO1_REG(0x9C) +#define MSM7X30_GPIO_INT_CLEAR_5 MSM_GPIO1_REG(0xB4) +#define MSM7X30_GPIO_INT_CLEAR_6 MSM_GPIO1_REG(0xDC) +#define MSM7X30_GPIO_INT_CLEAR_7 MSM_GPIO1_REG(0x230) + +/* same pin map as above, 1=interrupt pending */ +#define MSM7X30_GPIO_INT_STATUS_0 MSM_GPIO1_REG(0xA0) +#define MSM7X30_GPIO_INT_STATUS_1 MSM_GPIO2_REG(0x70) +#define MSM7X30_GPIO_INT_STATUS_2 MSM_GPIO1_REG(0xA4) +#define MSM7X30_GPIO_INT_STATUS_3 MSM_GPIO1_REG(0xA8) +#define MSM7X30_GPIO_INT_STATUS_4 MSM_GPIO1_REG(0xAC) +#define MSM7X30_GPIO_INT_STATUS_5 MSM_GPIO1_REG(0xB0) +#define MSM7X30_GPIO_INT_STATUS_6 MSM_GPIO1_REG(0xE0) +#define MSM7X30_GPIO_INT_STATUS_7 MSM_GPIO1_REG(0x234) + +#define FIRST_GPIO_IRQ MSM_GPIO_TO_INT(0) + +#define MSM_GPIO_BANK(soc, bank, first, last) \ + { \ + .regs = { \ + .out = soc##_GPIO_OUT_##bank, \ + .in = soc##_GPIO_IN_##bank, \ + .int_status = soc##_GPIO_INT_STATUS_##bank, \ + .int_clear = soc##_GPIO_INT_CLEAR_##bank, \ + .int_en = soc##_GPIO_INT_EN_##bank, \ + .int_edge = soc##_GPIO_INT_EDGE_##bank, \ + .int_pos = soc##_GPIO_INT_POS_##bank, \ + .oe = soc##_GPIO_OE_##bank, \ + }, \ + .chip = { \ + .base = (first), \ + .ngpio = (last) - (first) + 1, \ + .get = msm_gpio_get, \ + .set = msm_gpio_set, \ + .direction_input = msm_gpio_direction_input, \ + .direction_output = msm_gpio_direction_output, \ + .to_irq = msm_gpio_to_irq, \ + .request = msm_gpio_request, \ + .free = msm_gpio_free, \ + } \ + } + +#define MSM_GPIO_BROKEN_INT_CLEAR 1 + +struct msm_gpio_regs { + void __iomem *out; + void __iomem *in; + void __iomem *int_status; + void __iomem *int_clear; + void __iomem *int_en; + void __iomem *int_edge; + void __iomem *int_pos; + void __iomem *oe; +}; + +struct msm_gpio_chip { + spinlock_t lock; + struct gpio_chip chip; + struct msm_gpio_regs regs; +#if MSM_GPIO_BROKEN_INT_CLEAR + unsigned int_status_copy; +#endif + unsigned int both_edge_detect; + unsigned int int_enable[2]; /* 0: awake, 1: sleep */ +}; + +static int msm_gpio_write(struct msm_gpio_chip *msm_chip, + unsigned offset, unsigned on) +{ + unsigned mask = BIT(offset); + unsigned val; + + val = readl(msm_chip->regs.out); + if (on) + writel(val | mask, msm_chip->regs.out); + else + writel(val & ~mask, msm_chip->regs.out); + return 0; +} + +static void msm_gpio_update_both_edge_detect(struct msm_gpio_chip *msm_chip) +{ + int loop_limit = 100; + unsigned pol, val, val2, intstat; + do { + val = readl(msm_chip->regs.in); + pol = readl(msm_chip->regs.int_pos); + pol = (pol & ~msm_chip->both_edge_detect) | + (~val & msm_chip->both_edge_detect); + writel(pol, msm_chip->regs.int_pos); + intstat = readl(msm_chip->regs.int_status); + val2 = readl(msm_chip->regs.in); + if (((val ^ val2) & msm_chip->both_edge_detect & ~intstat) == 0) + return; + } while (loop_limit-- > 0); + printk(KERN_ERR "msm_gpio_update_both_edge_detect, " + "failed to reach stable state %x != %x\n", val, val2); +} + +static int msm_gpio_clear_detect_status(struct msm_gpio_chip *msm_chip, + unsigned offset) +{ + unsigned bit = BIT(offset); + +#if MSM_GPIO_BROKEN_INT_CLEAR + /* Save interrupts that already triggered before we loose them. */ + /* Any interrupt that triggers between the read of int_status */ + /* and the write to int_clear will still be lost though. */ + msm_chip->int_status_copy |= readl(msm_chip->regs.int_status); + msm_chip->int_status_copy &= ~bit; +#endif + writel(bit, msm_chip->regs.int_clear); + msm_gpio_update_both_edge_detect(msm_chip); + return 0; +} + +static int msm_gpio_direction_input(struct gpio_chip *chip, unsigned offset) +{ + struct msm_gpio_chip *msm_chip; + unsigned long irq_flags; + + msm_chip = container_of(chip, struct msm_gpio_chip, chip); + spin_lock_irqsave(&msm_chip->lock, irq_flags); + writel(readl(msm_chip->regs.oe) & ~BIT(offset), msm_chip->regs.oe); + spin_unlock_irqrestore(&msm_chip->lock, irq_flags); + return 0; +} + +static int +msm_gpio_direction_output(struct gpio_chip *chip, unsigned offset, int value) +{ + struct msm_gpio_chip *msm_chip; + unsigned long irq_flags; + + msm_chip = container_of(chip, struct msm_gpio_chip, chip); + spin_lock_irqsave(&msm_chip->lock, irq_flags); + msm_gpio_write(msm_chip, offset, value); + writel(readl(msm_chip->regs.oe) | BIT(offset), msm_chip->regs.oe); + spin_unlock_irqrestore(&msm_chip->lock, irq_flags); + return 0; +} + +static int msm_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct msm_gpio_chip *msm_chip; + + msm_chip = container_of(chip, struct msm_gpio_chip, chip); + return (readl(msm_chip->regs.in) & (1U << offset)) ? 1 : 0; +} + +static void msm_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct msm_gpio_chip *msm_chip; + unsigned long irq_flags; + + msm_chip = container_of(chip, struct msm_gpio_chip, chip); + spin_lock_irqsave(&msm_chip->lock, irq_flags); + msm_gpio_write(msm_chip, offset, value); + spin_unlock_irqrestore(&msm_chip->lock, irq_flags); +} + +static int msm_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + return MSM_GPIO_TO_INT(chip->base + offset); +} + +#ifdef CONFIG_MSM_GPIOMUX +static int msm_gpio_request(struct gpio_chip *chip, unsigned offset) +{ + return msm_gpiomux_get(chip->base + offset); +} + +static void msm_gpio_free(struct gpio_chip *chip, unsigned offset) +{ + msm_gpiomux_put(chip->base + offset); +} +#else +#define msm_gpio_request NULL +#define msm_gpio_free NULL +#endif + +static struct msm_gpio_chip *msm_gpio_chips; +static int msm_gpio_count; + +static struct msm_gpio_chip msm_gpio_chips_msm7x01[] = { + MSM_GPIO_BANK(MSM7X00, 0, 0, 15), + MSM_GPIO_BANK(MSM7X00, 1, 16, 42), + MSM_GPIO_BANK(MSM7X00, 2, 43, 67), + MSM_GPIO_BANK(MSM7X00, 3, 68, 94), + MSM_GPIO_BANK(MSM7X00, 4, 95, 106), + MSM_GPIO_BANK(MSM7X00, 5, 107, 121), +}; + +static struct msm_gpio_chip msm_gpio_chips_msm7x30[] = { + MSM_GPIO_BANK(MSM7X30, 0, 0, 15), + MSM_GPIO_BANK(MSM7X30, 1, 16, 43), + MSM_GPIO_BANK(MSM7X30, 2, 44, 67), + MSM_GPIO_BANK(MSM7X30, 3, 68, 94), + MSM_GPIO_BANK(MSM7X30, 4, 95, 106), + MSM_GPIO_BANK(MSM7X30, 5, 107, 133), + MSM_GPIO_BANK(MSM7X30, 6, 134, 150), + MSM_GPIO_BANK(MSM7X30, 7, 151, 181), +}; + +static struct msm_gpio_chip msm_gpio_chips_qsd8x50[] = { + MSM_GPIO_BANK(QSD8X50, 0, 0, 15), + MSM_GPIO_BANK(QSD8X50, 1, 16, 42), + MSM_GPIO_BANK(QSD8X50, 2, 43, 67), + MSM_GPIO_BANK(QSD8X50, 3, 68, 94), + MSM_GPIO_BANK(QSD8X50, 4, 95, 103), + MSM_GPIO_BANK(QSD8X50, 5, 104, 121), + MSM_GPIO_BANK(QSD8X50, 6, 122, 152), + MSM_GPIO_BANK(QSD8X50, 7, 153, 164), +}; + +static void msm_gpio_irq_ack(struct irq_data *d) +{ + unsigned long irq_flags; + struct msm_gpio_chip *msm_chip = irq_data_get_irq_chip_data(d); + spin_lock_irqsave(&msm_chip->lock, irq_flags); + msm_gpio_clear_detect_status(msm_chip, + d->irq - gpio_to_irq(msm_chip->chip.base)); + spin_unlock_irqrestore(&msm_chip->lock, irq_flags); +} + +static void msm_gpio_irq_mask(struct irq_data *d) +{ + unsigned long irq_flags; + struct msm_gpio_chip *msm_chip = irq_data_get_irq_chip_data(d); + unsigned offset = d->irq - gpio_to_irq(msm_chip->chip.base); + + spin_lock_irqsave(&msm_chip->lock, irq_flags); + /* level triggered interrupts are also latched */ + if (!(readl(msm_chip->regs.int_edge) & BIT(offset))) + msm_gpio_clear_detect_status(msm_chip, offset); + msm_chip->int_enable[0] &= ~BIT(offset); + writel(msm_chip->int_enable[0], msm_chip->regs.int_en); + spin_unlock_irqrestore(&msm_chip->lock, irq_flags); +} + +static void msm_gpio_irq_unmask(struct irq_data *d) +{ + unsigned long irq_flags; + struct msm_gpio_chip *msm_chip = irq_data_get_irq_chip_data(d); + unsigned offset = d->irq - gpio_to_irq(msm_chip->chip.base); + + spin_lock_irqsave(&msm_chip->lock, irq_flags); + /* level triggered interrupts are also latched */ + if (!(readl(msm_chip->regs.int_edge) & BIT(offset))) + msm_gpio_clear_detect_status(msm_chip, offset); + msm_chip->int_enable[0] |= BIT(offset); + writel(msm_chip->int_enable[0], msm_chip->regs.int_en); + spin_unlock_irqrestore(&msm_chip->lock, irq_flags); +} + +static int msm_gpio_irq_set_wake(struct irq_data *d, unsigned int on) +{ + unsigned long irq_flags; + struct msm_gpio_chip *msm_chip = irq_data_get_irq_chip_data(d); + unsigned offset = d->irq - gpio_to_irq(msm_chip->chip.base); + + spin_lock_irqsave(&msm_chip->lock, irq_flags); + + if (on) + msm_chip->int_enable[1] |= BIT(offset); + else + msm_chip->int_enable[1] &= ~BIT(offset); + + spin_unlock_irqrestore(&msm_chip->lock, irq_flags); + return 0; +} + +static int msm_gpio_irq_set_type(struct irq_data *d, unsigned int flow_type) +{ + unsigned long irq_flags; + struct msm_gpio_chip *msm_chip = irq_data_get_irq_chip_data(d); + unsigned offset = d->irq - gpio_to_irq(msm_chip->chip.base); + unsigned val, mask = BIT(offset); + + spin_lock_irqsave(&msm_chip->lock, irq_flags); + val = readl(msm_chip->regs.int_edge); + if (flow_type & IRQ_TYPE_EDGE_BOTH) { + writel(val | mask, msm_chip->regs.int_edge); + __irq_set_handler_locked(d->irq, handle_edge_irq); + } else { + writel(val & ~mask, msm_chip->regs.int_edge); + __irq_set_handler_locked(d->irq, handle_level_irq); + } + if ((flow_type & IRQ_TYPE_EDGE_BOTH) == IRQ_TYPE_EDGE_BOTH) { + msm_chip->both_edge_detect |= mask; + msm_gpio_update_both_edge_detect(msm_chip); + } else { + msm_chip->both_edge_detect &= ~mask; + val = readl(msm_chip->regs.int_pos); + if (flow_type & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_HIGH)) + writel(val | mask, msm_chip->regs.int_pos); + else + writel(val & ~mask, msm_chip->regs.int_pos); + } + spin_unlock_irqrestore(&msm_chip->lock, irq_flags); + return 0; +} + +static void msm_gpio_irq_handler(unsigned int irq, struct irq_desc *desc) +{ + int i, j, mask; + unsigned val; + + for (i = 0; i < msm_gpio_count; i++) { + struct msm_gpio_chip *msm_chip = &msm_gpio_chips[i]; + val = readl(msm_chip->regs.int_status); + val &= msm_chip->int_enable[0]; + while (val) { + mask = val & -val; + j = fls(mask) - 1; + /* printk("%s %08x %08x bit %d gpio %d irq %d\n", + __func__, v, m, j, msm_chip->chip.start + j, + FIRST_GPIO_IRQ + msm_chip->chip.start + j); */ + val &= ~mask; + generic_handle_irq(FIRST_GPIO_IRQ + + msm_chip->chip.base + j); + } + } + desc->irq_data.chip->irq_ack(&desc->irq_data); +} + +static struct irq_chip msm_gpio_irq_chip = { + .name = "msmgpio", + .irq_ack = msm_gpio_irq_ack, + .irq_mask = msm_gpio_irq_mask, + .irq_unmask = msm_gpio_irq_unmask, + .irq_set_wake = msm_gpio_irq_set_wake, + .irq_set_type = msm_gpio_irq_set_type, +}; + +static int __init msm_init_gpio(void) +{ + int i, j = 0; + + if (cpu_is_msm7x01()) { + msm_gpio_chips = msm_gpio_chips_msm7x01; + msm_gpio_count = ARRAY_SIZE(msm_gpio_chips_msm7x01); + } else if (cpu_is_msm7x30()) { + msm_gpio_chips = msm_gpio_chips_msm7x30; + msm_gpio_count = ARRAY_SIZE(msm_gpio_chips_msm7x30); + } else if (cpu_is_qsd8x50()) { + msm_gpio_chips = msm_gpio_chips_qsd8x50; + msm_gpio_count = ARRAY_SIZE(msm_gpio_chips_qsd8x50); + } else { + return 0; + } + + for (i = FIRST_GPIO_IRQ; i < FIRST_GPIO_IRQ + NR_GPIO_IRQS; i++) { + if (i - FIRST_GPIO_IRQ >= + msm_gpio_chips[j].chip.base + + msm_gpio_chips[j].chip.ngpio) + j++; + irq_set_chip_data(i, &msm_gpio_chips[j]); + irq_set_chip_and_handler(i, &msm_gpio_irq_chip, + handle_edge_irq); + set_irq_flags(i, IRQF_VALID); + } + + for (i = 0; i < msm_gpio_count; i++) { + spin_lock_init(&msm_gpio_chips[i].lock); + writel(0, msm_gpio_chips[i].regs.int_en); + gpiochip_add(&msm_gpio_chips[i].chip); + } + + irq_set_chained_handler(INT_GPIO_GROUP1, msm_gpio_irq_handler); + irq_set_chained_handler(INT_GPIO_GROUP2, msm_gpio_irq_handler); + irq_set_irq_wake(INT_GPIO_GROUP1, 1); + irq_set_irq_wake(INT_GPIO_GROUP2, 2); + return 0; +} + +postcore_initcall(msm_init_gpio); diff --git a/drivers/gpio/gpio-msm-v2.c b/drivers/gpio/gpio-msm-v2.c new file mode 100644 index 00000000..5cb1227d --- /dev/null +++ b/drivers/gpio/gpio-msm-v2.c @@ -0,0 +1,433 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + */ +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include <linux/bitmap.h> +#include <linux/bitops.h> +#include <linux/gpio.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> + +#include <asm/mach/irq.h> + +#include <mach/msm_gpiomux.h> +#include <mach/msm_iomap.h> + +/* Bits of interest in the GPIO_IN_OUT register. + */ +enum { + GPIO_IN = 0, + GPIO_OUT = 1 +}; + +/* Bits of interest in the GPIO_INTR_STATUS register. + */ +enum { + INTR_STATUS = 0, +}; + +/* Bits of interest in the GPIO_CFG register. + */ +enum { + GPIO_OE = 9, +}; + +/* Bits of interest in the GPIO_INTR_CFG register. + * When a GPIO triggers, two separate decisions are made, controlled + * by two separate flags. + * + * - First, INTR_RAW_STATUS_EN controls whether or not the GPIO_INTR_STATUS + * register for that GPIO will be updated to reflect the triggering of that + * gpio. If this bit is 0, this register will not be updated. + * - Second, INTR_ENABLE controls whether an interrupt is triggered. + * + * If INTR_ENABLE is set and INTR_RAW_STATUS_EN is NOT set, an interrupt + * can be triggered but the status register will not reflect it. + */ +enum { + INTR_ENABLE = 0, + INTR_POL_CTL = 1, + INTR_DECT_CTL = 2, + INTR_RAW_STATUS_EN = 3, +}; + +/* Codes of interest in GPIO_INTR_CFG_SU. + */ +enum { + TARGET_PROC_SCORPION = 4, + TARGET_PROC_NONE = 7, +}; + + +#define GPIO_INTR_CFG_SU(gpio) (MSM_TLMM_BASE + 0x0400 + (0x04 * (gpio))) +#define GPIO_CONFIG(gpio) (MSM_TLMM_BASE + 0x1000 + (0x10 * (gpio))) +#define GPIO_IN_OUT(gpio) (MSM_TLMM_BASE + 0x1004 + (0x10 * (gpio))) +#define GPIO_INTR_CFG(gpio) (MSM_TLMM_BASE + 0x1008 + (0x10 * (gpio))) +#define GPIO_INTR_STATUS(gpio) (MSM_TLMM_BASE + 0x100c + (0x10 * (gpio))) + +/** + * struct msm_gpio_dev: the MSM8660 SoC GPIO device structure + * + * @enabled_irqs: a bitmap used to optimize the summary-irq handler. By + * keeping track of which gpios are unmasked as irq sources, we avoid + * having to do readl calls on hundreds of iomapped registers each time + * the summary interrupt fires in order to locate the active interrupts. + * + * @wake_irqs: a bitmap for tracking which interrupt lines are enabled + * as wakeup sources. When the device is suspended, interrupts which are + * not wakeup sources are disabled. + * + * @dual_edge_irqs: a bitmap used to track which irqs are configured + * as dual-edge, as this is not supported by the hardware and requires + * some special handling in the driver. + */ +struct msm_gpio_dev { + struct gpio_chip gpio_chip; + DECLARE_BITMAP(enabled_irqs, NR_GPIO_IRQS); + DECLARE_BITMAP(wake_irqs, NR_GPIO_IRQS); + DECLARE_BITMAP(dual_edge_irqs, NR_GPIO_IRQS); +}; + +static DEFINE_SPINLOCK(tlmm_lock); + +static inline struct msm_gpio_dev *to_msm_gpio_dev(struct gpio_chip *chip) +{ + return container_of(chip, struct msm_gpio_dev, gpio_chip); +} + +static inline void set_gpio_bits(unsigned n, void __iomem *reg) +{ + writel(readl(reg) | n, reg); +} + +static inline void clear_gpio_bits(unsigned n, void __iomem *reg) +{ + writel(readl(reg) & ~n, reg); +} + +static int msm_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + return readl(GPIO_IN_OUT(offset)) & BIT(GPIO_IN); +} + +static void msm_gpio_set(struct gpio_chip *chip, unsigned offset, int val) +{ + writel(val ? BIT(GPIO_OUT) : 0, GPIO_IN_OUT(offset)); +} + +static int msm_gpio_direction_input(struct gpio_chip *chip, unsigned offset) +{ + unsigned long irq_flags; + + spin_lock_irqsave(&tlmm_lock, irq_flags); + clear_gpio_bits(BIT(GPIO_OE), GPIO_CONFIG(offset)); + spin_unlock_irqrestore(&tlmm_lock, irq_flags); + return 0; +} + +static int msm_gpio_direction_output(struct gpio_chip *chip, + unsigned offset, + int val) +{ + unsigned long irq_flags; + + spin_lock_irqsave(&tlmm_lock, irq_flags); + msm_gpio_set(chip, offset, val); + set_gpio_bits(BIT(GPIO_OE), GPIO_CONFIG(offset)); + spin_unlock_irqrestore(&tlmm_lock, irq_flags); + return 0; +} + +static int msm_gpio_request(struct gpio_chip *chip, unsigned offset) +{ + return msm_gpiomux_get(chip->base + offset); +} + +static void msm_gpio_free(struct gpio_chip *chip, unsigned offset) +{ + msm_gpiomux_put(chip->base + offset); +} + +static int msm_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + return MSM_GPIO_TO_INT(chip->base + offset); +} + +static inline int msm_irq_to_gpio(struct gpio_chip *chip, unsigned irq) +{ + return irq - MSM_GPIO_TO_INT(chip->base); +} + +static struct msm_gpio_dev msm_gpio = { + .gpio_chip = { + .base = 0, + .ngpio = NR_GPIO_IRQS, + .direction_input = msm_gpio_direction_input, + .direction_output = msm_gpio_direction_output, + .get = msm_gpio_get, + .set = msm_gpio_set, + .to_irq = msm_gpio_to_irq, + .request = msm_gpio_request, + .free = msm_gpio_free, + }, +}; + +/* For dual-edge interrupts in software, since the hardware has no + * such support: + * + * At appropriate moments, this function may be called to flip the polarity + * settings of both-edge irq lines to try and catch the next edge. + * + * The attempt is considered successful if: + * - the status bit goes high, indicating that an edge was caught, or + * - the input value of the gpio doesn't change during the attempt. + * If the value changes twice during the process, that would cause the first + * test to fail but would force the second, as two opposite + * transitions would cause a detection no matter the polarity setting. + * + * The do-loop tries to sledge-hammer closed the timing hole between + * the initial value-read and the polarity-write - if the line value changes + * during that window, an interrupt is lost, the new polarity setting is + * incorrect, and the first success test will fail, causing a retry. + * + * Algorithm comes from Google's msmgpio driver, see mach-msm/gpio.c. + */ +static void msm_gpio_update_dual_edge_pos(unsigned gpio) +{ + int loop_limit = 100; + unsigned val, val2, intstat; + + do { + val = readl(GPIO_IN_OUT(gpio)) & BIT(GPIO_IN); + if (val) + clear_gpio_bits(BIT(INTR_POL_CTL), GPIO_INTR_CFG(gpio)); + else + set_gpio_bits(BIT(INTR_POL_CTL), GPIO_INTR_CFG(gpio)); + val2 = readl(GPIO_IN_OUT(gpio)) & BIT(GPIO_IN); + intstat = readl(GPIO_INTR_STATUS(gpio)) & BIT(INTR_STATUS); + if (intstat || val == val2) + return; + } while (loop_limit-- > 0); + pr_err("dual-edge irq failed to stabilize, " + "interrupts dropped. %#08x != %#08x\n", + val, val2); +} + +static void msm_gpio_irq_ack(struct irq_data *d) +{ + int gpio = msm_irq_to_gpio(&msm_gpio.gpio_chip, d->irq); + + writel(BIT(INTR_STATUS), GPIO_INTR_STATUS(gpio)); + if (test_bit(gpio, msm_gpio.dual_edge_irqs)) + msm_gpio_update_dual_edge_pos(gpio); +} + +static void msm_gpio_irq_mask(struct irq_data *d) +{ + int gpio = msm_irq_to_gpio(&msm_gpio.gpio_chip, d->irq); + unsigned long irq_flags; + + spin_lock_irqsave(&tlmm_lock, irq_flags); + writel(TARGET_PROC_NONE, GPIO_INTR_CFG_SU(gpio)); + clear_gpio_bits(INTR_RAW_STATUS_EN | INTR_ENABLE, GPIO_INTR_CFG(gpio)); + __clear_bit(gpio, msm_gpio.enabled_irqs); + spin_unlock_irqrestore(&tlmm_lock, irq_flags); +} + +static void msm_gpio_irq_unmask(struct irq_data *d) +{ + int gpio = msm_irq_to_gpio(&msm_gpio.gpio_chip, d->irq); + unsigned long irq_flags; + + spin_lock_irqsave(&tlmm_lock, irq_flags); + __set_bit(gpio, msm_gpio.enabled_irqs); + set_gpio_bits(INTR_RAW_STATUS_EN | INTR_ENABLE, GPIO_INTR_CFG(gpio)); + writel(TARGET_PROC_SCORPION, GPIO_INTR_CFG_SU(gpio)); + spin_unlock_irqrestore(&tlmm_lock, irq_flags); +} + +static int msm_gpio_irq_set_type(struct irq_data *d, unsigned int flow_type) +{ + int gpio = msm_irq_to_gpio(&msm_gpio.gpio_chip, d->irq); + unsigned long irq_flags; + uint32_t bits; + + spin_lock_irqsave(&tlmm_lock, irq_flags); + + bits = readl(GPIO_INTR_CFG(gpio)); + + if (flow_type & IRQ_TYPE_EDGE_BOTH) { + bits |= BIT(INTR_DECT_CTL); + __irq_set_handler_locked(d->irq, handle_edge_irq); + if ((flow_type & IRQ_TYPE_EDGE_BOTH) == IRQ_TYPE_EDGE_BOTH) + __set_bit(gpio, msm_gpio.dual_edge_irqs); + else + __clear_bit(gpio, msm_gpio.dual_edge_irqs); + } else { + bits &= ~BIT(INTR_DECT_CTL); + __irq_set_handler_locked(d->irq, handle_level_irq); + __clear_bit(gpio, msm_gpio.dual_edge_irqs); + } + + if (flow_type & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_LEVEL_HIGH)) + bits |= BIT(INTR_POL_CTL); + else + bits &= ~BIT(INTR_POL_CTL); + + writel(bits, GPIO_INTR_CFG(gpio)); + + if ((flow_type & IRQ_TYPE_EDGE_BOTH) == IRQ_TYPE_EDGE_BOTH) + msm_gpio_update_dual_edge_pos(gpio); + + spin_unlock_irqrestore(&tlmm_lock, irq_flags); + + return 0; +} + +/* + * When the summary IRQ is raised, any number of GPIO lines may be high. + * It is the job of the summary handler to find all those GPIO lines + * which have been set as summary IRQ lines and which are triggered, + * and to call their interrupt handlers. + */ +static void msm_summary_irq_handler(unsigned int irq, struct irq_desc *desc) +{ + unsigned long i; + struct irq_chip *chip = irq_desc_get_chip(desc); + + chained_irq_enter(chip, desc); + + for (i = find_first_bit(msm_gpio.enabled_irqs, NR_GPIO_IRQS); + i < NR_GPIO_IRQS; + i = find_next_bit(msm_gpio.enabled_irqs, NR_GPIO_IRQS, i + 1)) { + if (readl(GPIO_INTR_STATUS(i)) & BIT(INTR_STATUS)) + generic_handle_irq(msm_gpio_to_irq(&msm_gpio.gpio_chip, + i)); + } + + chained_irq_exit(chip, desc); +} + +static int msm_gpio_irq_set_wake(struct irq_data *d, unsigned int on) +{ + int gpio = msm_irq_to_gpio(&msm_gpio.gpio_chip, d->irq); + + if (on) { + if (bitmap_empty(msm_gpio.wake_irqs, NR_GPIO_IRQS)) + irq_set_irq_wake(TLMM_SCSS_SUMMARY_IRQ, 1); + set_bit(gpio, msm_gpio.wake_irqs); + } else { + clear_bit(gpio, msm_gpio.wake_irqs); + if (bitmap_empty(msm_gpio.wake_irqs, NR_GPIO_IRQS)) + irq_set_irq_wake(TLMM_SCSS_SUMMARY_IRQ, 0); + } + + return 0; +} + +static struct irq_chip msm_gpio_irq_chip = { + .name = "msmgpio", + .irq_mask = msm_gpio_irq_mask, + .irq_unmask = msm_gpio_irq_unmask, + .irq_ack = msm_gpio_irq_ack, + .irq_set_type = msm_gpio_irq_set_type, + .irq_set_wake = msm_gpio_irq_set_wake, +}; + +static int __devinit msm_gpio_probe(struct platform_device *dev) +{ + int i, irq, ret; + + bitmap_zero(msm_gpio.enabled_irqs, NR_GPIO_IRQS); + bitmap_zero(msm_gpio.wake_irqs, NR_GPIO_IRQS); + bitmap_zero(msm_gpio.dual_edge_irqs, NR_GPIO_IRQS); + msm_gpio.gpio_chip.label = dev->name; + ret = gpiochip_add(&msm_gpio.gpio_chip); + if (ret < 0) + return ret; + + for (i = 0; i < msm_gpio.gpio_chip.ngpio; ++i) { + irq = msm_gpio_to_irq(&msm_gpio.gpio_chip, i); + irq_set_chip_and_handler(irq, &msm_gpio_irq_chip, + handle_level_irq); + set_irq_flags(irq, IRQF_VALID); + } + + irq_set_chained_handler(TLMM_SCSS_SUMMARY_IRQ, + msm_summary_irq_handler); + return 0; +} + +static int __devexit msm_gpio_remove(struct platform_device *dev) +{ + int ret = gpiochip_remove(&msm_gpio.gpio_chip); + + if (ret < 0) + return ret; + + irq_set_handler(TLMM_SCSS_SUMMARY_IRQ, NULL); + + return 0; +} + +static struct platform_driver msm_gpio_driver = { + .probe = msm_gpio_probe, + .remove = __devexit_p(msm_gpio_remove), + .driver = { + .name = "msmgpio", + .owner = THIS_MODULE, + }, +}; + +static struct platform_device msm_device_gpio = { + .name = "msmgpio", + .id = -1, +}; + +static int __init msm_gpio_init(void) +{ + int rc; + + rc = platform_driver_register(&msm_gpio_driver); + if (!rc) { + rc = platform_device_register(&msm_device_gpio); + if (rc) + platform_driver_unregister(&msm_gpio_driver); + } + + return rc; +} + +static void __exit msm_gpio_exit(void) +{ + platform_device_unregister(&msm_device_gpio); + platform_driver_unregister(&msm_gpio_driver); +} + +postcore_initcall(msm_gpio_init); +module_exit(msm_gpio_exit); + +MODULE_AUTHOR("Gregory Bean <gbean@codeaurora.org>"); +MODULE_DESCRIPTION("Driver for Qualcomm MSM TLMMv2 SoC GPIOs"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:msmgpio"); diff --git a/drivers/gpio/gpio-mxc.c b/drivers/gpio/gpio-mxc.c new file mode 100644 index 00000000..e7914763 --- /dev/null +++ b/drivers/gpio/gpio-mxc.c @@ -0,0 +1,479 @@ +/* + * MXC GPIO support. (c) 2008 Daniel Mack <daniel@caiaq.de> + * Copyright 2008 Juergen Beisert, kernel@pengutronix.de + * + * Based on code from Freescale, + * Copyright (C) 2004-2010 Freescale Semiconductor, Inc. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/basic_mmio_gpio.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/module.h> +#include <asm-generic/bug.h> +#include <asm/mach/irq.h> + +#define irq_to_gpio(irq) ((irq) - MXC_GPIO_IRQ_START) + +enum mxc_gpio_hwtype { + IMX1_GPIO, /* runs on i.mx1 */ + IMX21_GPIO, /* runs on i.mx21 and i.mx27 */ + IMX31_GPIO, /* runs on all other i.mx */ +}; + +/* device type dependent stuff */ +struct mxc_gpio_hwdata { + unsigned dr_reg; + unsigned gdir_reg; + unsigned psr_reg; + unsigned icr1_reg; + unsigned icr2_reg; + unsigned imr_reg; + unsigned isr_reg; + unsigned low_level; + unsigned high_level; + unsigned rise_edge; + unsigned fall_edge; +}; + +struct mxc_gpio_port { + struct list_head node; + void __iomem *base; + int irq; + int irq_high; + int virtual_irq_start; + struct bgpio_chip bgc; + u32 both_edges; +}; + +static struct mxc_gpio_hwdata imx1_imx21_gpio_hwdata = { + .dr_reg = 0x1c, + .gdir_reg = 0x00, + .psr_reg = 0x24, + .icr1_reg = 0x28, + .icr2_reg = 0x2c, + .imr_reg = 0x30, + .isr_reg = 0x34, + .low_level = 0x03, + .high_level = 0x02, + .rise_edge = 0x00, + .fall_edge = 0x01, +}; + +static struct mxc_gpio_hwdata imx31_gpio_hwdata = { + .dr_reg = 0x00, + .gdir_reg = 0x04, + .psr_reg = 0x08, + .icr1_reg = 0x0c, + .icr2_reg = 0x10, + .imr_reg = 0x14, + .isr_reg = 0x18, + .low_level = 0x00, + .high_level = 0x01, + .rise_edge = 0x02, + .fall_edge = 0x03, +}; + +static enum mxc_gpio_hwtype mxc_gpio_hwtype; +static struct mxc_gpio_hwdata *mxc_gpio_hwdata; + +#define GPIO_DR (mxc_gpio_hwdata->dr_reg) +#define GPIO_GDIR (mxc_gpio_hwdata->gdir_reg) +#define GPIO_PSR (mxc_gpio_hwdata->psr_reg) +#define GPIO_ICR1 (mxc_gpio_hwdata->icr1_reg) +#define GPIO_ICR2 (mxc_gpio_hwdata->icr2_reg) +#define GPIO_IMR (mxc_gpio_hwdata->imr_reg) +#define GPIO_ISR (mxc_gpio_hwdata->isr_reg) + +#define GPIO_INT_LOW_LEV (mxc_gpio_hwdata->low_level) +#define GPIO_INT_HIGH_LEV (mxc_gpio_hwdata->high_level) +#define GPIO_INT_RISE_EDGE (mxc_gpio_hwdata->rise_edge) +#define GPIO_INT_FALL_EDGE (mxc_gpio_hwdata->fall_edge) +#define GPIO_INT_NONE 0x4 + +static struct platform_device_id mxc_gpio_devtype[] = { + { + .name = "imx1-gpio", + .driver_data = IMX1_GPIO, + }, { + .name = "imx21-gpio", + .driver_data = IMX21_GPIO, + }, { + .name = "imx31-gpio", + .driver_data = IMX31_GPIO, + }, { + /* sentinel */ + } +}; + +static const struct of_device_id mxc_gpio_dt_ids[] = { + { .compatible = "fsl,imx1-gpio", .data = &mxc_gpio_devtype[IMX1_GPIO], }, + { .compatible = "fsl,imx21-gpio", .data = &mxc_gpio_devtype[IMX21_GPIO], }, + { .compatible = "fsl,imx31-gpio", .data = &mxc_gpio_devtype[IMX31_GPIO], }, + { /* sentinel */ } +}; + +/* + * MX2 has one interrupt *for all* gpio ports. The list is used + * to save the references to all ports, so that mx2_gpio_irq_handler + * can walk through all interrupt status registers. + */ +static LIST_HEAD(mxc_gpio_ports); + +/* Note: This driver assumes 32 GPIOs are handled in one register */ + +static int gpio_set_irq_type(struct irq_data *d, u32 type) +{ + u32 gpio = irq_to_gpio(d->irq); + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct mxc_gpio_port *port = gc->private; + u32 bit, val; + int edge; + void __iomem *reg = port->base; + + port->both_edges &= ~(1 << (gpio & 31)); + switch (type) { + case IRQ_TYPE_EDGE_RISING: + edge = GPIO_INT_RISE_EDGE; + break; + case IRQ_TYPE_EDGE_FALLING: + edge = GPIO_INT_FALL_EDGE; + break; + case IRQ_TYPE_EDGE_BOTH: + val = gpio_get_value(gpio); + if (val) { + edge = GPIO_INT_LOW_LEV; + pr_debug("mxc: set GPIO %d to low trigger\n", gpio); + } else { + edge = GPIO_INT_HIGH_LEV; + pr_debug("mxc: set GPIO %d to high trigger\n", gpio); + } + port->both_edges |= 1 << (gpio & 31); + break; + case IRQ_TYPE_LEVEL_LOW: + edge = GPIO_INT_LOW_LEV; + break; + case IRQ_TYPE_LEVEL_HIGH: + edge = GPIO_INT_HIGH_LEV; + break; + default: + return -EINVAL; + } + + reg += GPIO_ICR1 + ((gpio & 0x10) >> 2); /* lower or upper register */ + bit = gpio & 0xf; + val = readl(reg) & ~(0x3 << (bit << 1)); + writel(val | (edge << (bit << 1)), reg); + writel(1 << (gpio & 0x1f), port->base + GPIO_ISR); + + return 0; +} + +static void mxc_flip_edge(struct mxc_gpio_port *port, u32 gpio) +{ + void __iomem *reg = port->base; + u32 bit, val; + int edge; + + reg += GPIO_ICR1 + ((gpio & 0x10) >> 2); /* lower or upper register */ + bit = gpio & 0xf; + val = readl(reg); + edge = (val >> (bit << 1)) & 3; + val &= ~(0x3 << (bit << 1)); + if (edge == GPIO_INT_HIGH_LEV) { + edge = GPIO_INT_LOW_LEV; + pr_debug("mxc: switch GPIO %d to low trigger\n", gpio); + } else if (edge == GPIO_INT_LOW_LEV) { + edge = GPIO_INT_HIGH_LEV; + pr_debug("mxc: switch GPIO %d to high trigger\n", gpio); + } else { + pr_err("mxc: invalid configuration for GPIO %d: %x\n", + gpio, edge); + return; + } + writel(val | (edge << (bit << 1)), reg); +} + +/* handle 32 interrupts in one status register */ +static void mxc_gpio_irq_handler(struct mxc_gpio_port *port, u32 irq_stat) +{ + u32 gpio_irq_no_base = port->virtual_irq_start; + + while (irq_stat != 0) { + int irqoffset = fls(irq_stat) - 1; + + if (port->both_edges & (1 << irqoffset)) + mxc_flip_edge(port, irqoffset); + + generic_handle_irq(gpio_irq_no_base + irqoffset); + + irq_stat &= ~(1 << irqoffset); + } +} + +/* MX1 and MX3 has one interrupt *per* gpio port */ +static void mx3_gpio_irq_handler(u32 irq, struct irq_desc *desc) +{ + u32 irq_stat; + struct mxc_gpio_port *port = irq_get_handler_data(irq); + struct irq_chip *chip = irq_get_chip(irq); + + chained_irq_enter(chip, desc); + + irq_stat = readl(port->base + GPIO_ISR) & readl(port->base + GPIO_IMR); + + mxc_gpio_irq_handler(port, irq_stat); + + chained_irq_exit(chip, desc); +} + +/* MX2 has one interrupt *for all* gpio ports */ +static void mx2_gpio_irq_handler(u32 irq, struct irq_desc *desc) +{ + u32 irq_msk, irq_stat; + struct mxc_gpio_port *port; + + /* walk through all interrupt status registers */ + list_for_each_entry(port, &mxc_gpio_ports, node) { + irq_msk = readl(port->base + GPIO_IMR); + if (!irq_msk) + continue; + + irq_stat = readl(port->base + GPIO_ISR) & irq_msk; + if (irq_stat) + mxc_gpio_irq_handler(port, irq_stat); + } +} + +/* + * Set interrupt number "irq" in the GPIO as a wake-up source. + * While system is running, all registered GPIO interrupts need to have + * wake-up enabled. When system is suspended, only selected GPIO interrupts + * need to have wake-up enabled. + * @param irq interrupt source number + * @param enable enable as wake-up if equal to non-zero + * @return This function returns 0 on success. + */ +static int gpio_set_wake_irq(struct irq_data *d, u32 enable) +{ + u32 gpio = irq_to_gpio(d->irq); + u32 gpio_idx = gpio & 0x1F; + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct mxc_gpio_port *port = gc->private; + + if (enable) { + if (port->irq_high && (gpio_idx >= 16)) + enable_irq_wake(port->irq_high); + else + enable_irq_wake(port->irq); + } else { + if (port->irq_high && (gpio_idx >= 16)) + disable_irq_wake(port->irq_high); + else + disable_irq_wake(port->irq); + } + + return 0; +} + +static void __init mxc_gpio_init_gc(struct mxc_gpio_port *port) +{ + struct irq_chip_generic *gc; + struct irq_chip_type *ct; + + gc = irq_alloc_generic_chip("gpio-mxc", 1, port->virtual_irq_start, + port->base, handle_level_irq); + gc->private = port; + + ct = gc->chip_types; + ct->chip.irq_ack = irq_gc_ack_set_bit; + ct->chip.irq_mask = irq_gc_mask_clr_bit; + ct->chip.irq_unmask = irq_gc_mask_set_bit; + ct->chip.irq_set_type = gpio_set_irq_type; + ct->chip.irq_set_wake = gpio_set_wake_irq; + ct->regs.ack = GPIO_ISR; + ct->regs.mask = GPIO_IMR; + + irq_setup_generic_chip(gc, IRQ_MSK(32), IRQ_GC_INIT_NESTED_LOCK, + IRQ_NOREQUEST, 0); +} + +static void __devinit mxc_gpio_get_hw(struct platform_device *pdev) +{ + const struct of_device_id *of_id = + of_match_device(mxc_gpio_dt_ids, &pdev->dev); + enum mxc_gpio_hwtype hwtype; + + if (of_id) + pdev->id_entry = of_id->data; + hwtype = pdev->id_entry->driver_data; + + if (mxc_gpio_hwtype) { + /* + * The driver works with a reasonable presupposition, + * that is all gpio ports must be the same type when + * running on one soc. + */ + BUG_ON(mxc_gpio_hwtype != hwtype); + return; + } + + if (hwtype == IMX31_GPIO) + mxc_gpio_hwdata = &imx31_gpio_hwdata; + else + mxc_gpio_hwdata = &imx1_imx21_gpio_hwdata; + + mxc_gpio_hwtype = hwtype; +} + +static int mxc_gpio_to_irq(struct gpio_chip *gc, unsigned offset) +{ + struct bgpio_chip *bgc = to_bgpio_chip(gc); + struct mxc_gpio_port *port = + container_of(bgc, struct mxc_gpio_port, bgc); + + return port->virtual_irq_start + offset; +} + +static int __devinit mxc_gpio_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct mxc_gpio_port *port; + struct resource *iores; + int err; + + mxc_gpio_get_hw(pdev); + + port = kzalloc(sizeof(struct mxc_gpio_port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!iores) { + err = -ENODEV; + goto out_kfree; + } + + if (!request_mem_region(iores->start, resource_size(iores), + pdev->name)) { + err = -EBUSY; + goto out_kfree; + } + + port->base = ioremap(iores->start, resource_size(iores)); + if (!port->base) { + err = -ENOMEM; + goto out_release_mem; + } + + port->irq_high = platform_get_irq(pdev, 1); + port->irq = platform_get_irq(pdev, 0); + if (port->irq < 0) { + err = -EINVAL; + goto out_iounmap; + } + + /* disable the interrupt and clear the status */ + writel(0, port->base + GPIO_IMR); + writel(~0, port->base + GPIO_ISR); + + if (mxc_gpio_hwtype == IMX21_GPIO) { + /* setup one handler for all GPIO interrupts */ + if (pdev->id == 0) + irq_set_chained_handler(port->irq, + mx2_gpio_irq_handler); + } else { + /* setup one handler for each entry */ + irq_set_chained_handler(port->irq, mx3_gpio_irq_handler); + irq_set_handler_data(port->irq, port); + if (port->irq_high > 0) { + /* setup handler for GPIO 16 to 31 */ + irq_set_chained_handler(port->irq_high, + mx3_gpio_irq_handler); + irq_set_handler_data(port->irq_high, port); + } + } + + err = bgpio_init(&port->bgc, &pdev->dev, 4, + port->base + GPIO_PSR, + port->base + GPIO_DR, NULL, + port->base + GPIO_GDIR, NULL, false); + if (err) + goto out_iounmap; + + port->bgc.gc.to_irq = mxc_gpio_to_irq; + port->bgc.gc.base = pdev->id * 32; + port->bgc.dir = port->bgc.read_reg(port->bgc.reg_dir); + port->bgc.data = port->bgc.read_reg(port->bgc.reg_set); + + err = gpiochip_add(&port->bgc.gc); + if (err) + goto out_bgpio_remove; + + /* + * In dt case, we use gpio number range dynamically + * allocated by gpio core. + */ + port->virtual_irq_start = MXC_GPIO_IRQ_START + (np ? port->bgc.gc.base : + pdev->id * 32); + + /* gpio-mxc can be a generic irq chip */ + mxc_gpio_init_gc(port); + + list_add_tail(&port->node, &mxc_gpio_ports); + + return 0; + +out_bgpio_remove: + bgpio_remove(&port->bgc); +out_iounmap: + iounmap(port->base); +out_release_mem: + release_mem_region(iores->start, resource_size(iores)); +out_kfree: + kfree(port); + dev_info(&pdev->dev, "%s failed with errno %d\n", __func__, err); + return err; +} + +static struct platform_driver mxc_gpio_driver = { + .driver = { + .name = "gpio-mxc", + .owner = THIS_MODULE, + .of_match_table = mxc_gpio_dt_ids, + }, + .probe = mxc_gpio_probe, + .id_table = mxc_gpio_devtype, +}; + +static int __init gpio_mxc_init(void) +{ + return platform_driver_register(&mxc_gpio_driver); +} +postcore_initcall(gpio_mxc_init); + +MODULE_AUTHOR("Freescale Semiconductor, " + "Daniel Mack <danielncaiaq.de>, " + "Juergen Beisert <kernel@pengutronix.de>"); +MODULE_DESCRIPTION("Freescale MXC GPIO"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpio/gpio-mxs.c b/drivers/gpio/gpio-mxs.c new file mode 100644 index 00000000..385c58e8 --- /dev/null +++ b/drivers/gpio/gpio-mxs.c @@ -0,0 +1,292 @@ +/* + * MXC GPIO support. (c) 2008 Daniel Mack <daniel@caiaq.de> + * Copyright 2008 Juergen Beisert, kernel@pengutronix.de + * + * Based on code from Freescale, + * Copyright (C) 2004-2010 Freescale Semiconductor, Inc. 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., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/basic_mmio_gpio.h> +#include <linux/module.h> +#include <mach/mxs.h> + +#define MXS_SET 0x4 +#define MXS_CLR 0x8 + +#define PINCTRL_DOUT(n) ((cpu_is_mx23() ? 0x0500 : 0x0700) + (n) * 0x10) +#define PINCTRL_DIN(n) ((cpu_is_mx23() ? 0x0600 : 0x0900) + (n) * 0x10) +#define PINCTRL_DOE(n) ((cpu_is_mx23() ? 0x0700 : 0x0b00) + (n) * 0x10) +#define PINCTRL_PIN2IRQ(n) ((cpu_is_mx23() ? 0x0800 : 0x1000) + (n) * 0x10) +#define PINCTRL_IRQEN(n) ((cpu_is_mx23() ? 0x0900 : 0x1100) + (n) * 0x10) +#define PINCTRL_IRQLEV(n) ((cpu_is_mx23() ? 0x0a00 : 0x1200) + (n) * 0x10) +#define PINCTRL_IRQPOL(n) ((cpu_is_mx23() ? 0x0b00 : 0x1300) + (n) * 0x10) +#define PINCTRL_IRQSTAT(n) ((cpu_is_mx23() ? 0x0c00 : 0x1400) + (n) * 0x10) + +#define GPIO_INT_FALL_EDGE 0x0 +#define GPIO_INT_LOW_LEV 0x1 +#define GPIO_INT_RISE_EDGE 0x2 +#define GPIO_INT_HIGH_LEV 0x3 +#define GPIO_INT_LEV_MASK (1 << 0) +#define GPIO_INT_POL_MASK (1 << 1) + +#define irq_to_gpio(irq) ((irq) - MXS_GPIO_IRQ_START) + +struct mxs_gpio_port { + void __iomem *base; + int id; + int irq; + int virtual_irq_start; + struct bgpio_chip bgc; +}; + +/* Note: This driver assumes 32 GPIOs are handled in one register */ + +static int mxs_gpio_set_irq_type(struct irq_data *d, unsigned int type) +{ + u32 gpio = irq_to_gpio(d->irq); + u32 pin_mask = 1 << (gpio & 31); + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct mxs_gpio_port *port = gc->private; + void __iomem *pin_addr; + int edge; + + switch (type) { + case IRQ_TYPE_EDGE_RISING: + edge = GPIO_INT_RISE_EDGE; + break; + case IRQ_TYPE_EDGE_FALLING: + edge = GPIO_INT_FALL_EDGE; + break; + case IRQ_TYPE_LEVEL_LOW: + edge = GPIO_INT_LOW_LEV; + break; + case IRQ_TYPE_LEVEL_HIGH: + edge = GPIO_INT_HIGH_LEV; + break; + default: + return -EINVAL; + } + + /* set level or edge */ + pin_addr = port->base + PINCTRL_IRQLEV(port->id); + if (edge & GPIO_INT_LEV_MASK) + writel(pin_mask, pin_addr + MXS_SET); + else + writel(pin_mask, pin_addr + MXS_CLR); + + /* set polarity */ + pin_addr = port->base + PINCTRL_IRQPOL(port->id); + if (edge & GPIO_INT_POL_MASK) + writel(pin_mask, pin_addr + MXS_SET); + else + writel(pin_mask, pin_addr + MXS_CLR); + + writel(1 << (gpio & 0x1f), + port->base + PINCTRL_IRQSTAT(port->id) + MXS_CLR); + + return 0; +} + +/* MXS has one interrupt *per* gpio port */ +static void mxs_gpio_irq_handler(u32 irq, struct irq_desc *desc) +{ + u32 irq_stat; + struct mxs_gpio_port *port = irq_get_handler_data(irq); + u32 gpio_irq_no_base = port->virtual_irq_start; + + desc->irq_data.chip->irq_ack(&desc->irq_data); + + irq_stat = readl(port->base + PINCTRL_IRQSTAT(port->id)) & + readl(port->base + PINCTRL_IRQEN(port->id)); + + while (irq_stat != 0) { + int irqoffset = fls(irq_stat) - 1; + generic_handle_irq(gpio_irq_no_base + irqoffset); + irq_stat &= ~(1 << irqoffset); + } +} + +/* + * Set interrupt number "irq" in the GPIO as a wake-up source. + * While system is running, all registered GPIO interrupts need to have + * wake-up enabled. When system is suspended, only selected GPIO interrupts + * need to have wake-up enabled. + * @param irq interrupt source number + * @param enable enable as wake-up if equal to non-zero + * @return This function returns 0 on success. + */ +static int mxs_gpio_set_wake_irq(struct irq_data *d, unsigned int enable) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct mxs_gpio_port *port = gc->private; + + if (enable) + enable_irq_wake(port->irq); + else + disable_irq_wake(port->irq); + + return 0; +} + +static void __init mxs_gpio_init_gc(struct mxs_gpio_port *port) +{ + struct irq_chip_generic *gc; + struct irq_chip_type *ct; + + gc = irq_alloc_generic_chip("gpio-mxs", 1, port->virtual_irq_start, + port->base, handle_level_irq); + gc->private = port; + + ct = gc->chip_types; + ct->chip.irq_ack = irq_gc_ack_set_bit; + ct->chip.irq_mask = irq_gc_mask_clr_bit; + ct->chip.irq_unmask = irq_gc_mask_set_bit; + ct->chip.irq_set_type = mxs_gpio_set_irq_type; + ct->chip.irq_set_wake = mxs_gpio_set_wake_irq; + ct->regs.ack = PINCTRL_IRQSTAT(port->id) + MXS_CLR; + ct->regs.mask = PINCTRL_IRQEN(port->id); + + irq_setup_generic_chip(gc, IRQ_MSK(32), 0, IRQ_NOREQUEST, 0); +} + +static int mxs_gpio_to_irq(struct gpio_chip *gc, unsigned offset) +{ + struct bgpio_chip *bgc = to_bgpio_chip(gc); + struct mxs_gpio_port *port = + container_of(bgc, struct mxs_gpio_port, bgc); + + return port->virtual_irq_start + offset; +} + +static int __devinit mxs_gpio_probe(struct platform_device *pdev) +{ + static void __iomem *base; + struct mxs_gpio_port *port; + struct resource *iores = NULL; + int err; + + port = kzalloc(sizeof(struct mxs_gpio_port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + port->id = pdev->id; + port->virtual_irq_start = MXS_GPIO_IRQ_START + port->id * 32; + + /* + * map memory region only once, as all the gpio ports + * share the same one + */ + if (!base) { + iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!iores) { + err = -ENODEV; + goto out_kfree; + } + + if (!request_mem_region(iores->start, resource_size(iores), + pdev->name)) { + err = -EBUSY; + goto out_kfree; + } + + base = ioremap(iores->start, resource_size(iores)); + if (!base) { + err = -ENOMEM; + goto out_release_mem; + } + } + port->base = base; + + port->irq = platform_get_irq(pdev, 0); + if (port->irq < 0) { + err = -EINVAL; + goto out_iounmap; + } + + /* + * select the pin interrupt functionality but initially + * disable the interrupts + */ + writel(~0U, port->base + PINCTRL_PIN2IRQ(port->id)); + writel(0, port->base + PINCTRL_IRQEN(port->id)); + + /* clear address has to be used to clear IRQSTAT bits */ + writel(~0U, port->base + PINCTRL_IRQSTAT(port->id) + MXS_CLR); + + /* gpio-mxs can be a generic irq chip */ + mxs_gpio_init_gc(port); + + /* setup one handler for each entry */ + irq_set_chained_handler(port->irq, mxs_gpio_irq_handler); + irq_set_handler_data(port->irq, port); + + err = bgpio_init(&port->bgc, &pdev->dev, 4, + port->base + PINCTRL_DIN(port->id), + port->base + PINCTRL_DOUT(port->id), NULL, + port->base + PINCTRL_DOE(port->id), NULL, false); + if (err) + goto out_iounmap; + + port->bgc.gc.to_irq = mxs_gpio_to_irq; + port->bgc.gc.base = port->id * 32; + + err = gpiochip_add(&port->bgc.gc); + if (err) + goto out_bgpio_remove; + + return 0; + +out_bgpio_remove: + bgpio_remove(&port->bgc); +out_iounmap: + if (iores) + iounmap(port->base); +out_release_mem: + if (iores) + release_mem_region(iores->start, resource_size(iores)); +out_kfree: + kfree(port); + dev_info(&pdev->dev, "%s failed with errno %d\n", __func__, err); + return err; +} + +static struct platform_driver mxs_gpio_driver = { + .driver = { + .name = "gpio-mxs", + .owner = THIS_MODULE, + }, + .probe = mxs_gpio_probe, +}; + +static int __init mxs_gpio_init(void) +{ + return platform_driver_register(&mxs_gpio_driver); +} +postcore_initcall(mxs_gpio_init); + +MODULE_AUTHOR("Freescale Semiconductor, " + "Daniel Mack <danielncaiaq.de>, " + "Juergen Beisert <kernel@pengutronix.de>"); +MODULE_DESCRIPTION("Freescale MXS GPIO"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpio/gpio-nomadik.c b/drivers/gpio/gpio-nomadik.c new file mode 100644 index 00000000..839624f9 --- /dev/null +++ b/drivers/gpio/gpio-nomadik.c @@ -0,0 +1,1187 @@ +/* + * Generic GPIO driver for logic cells found in the Nomadik SoC + * + * Copyright (C) 2008,2009 STMicroelectronics + * Copyright (C) 2009 Alessandro Rubini <rubini@unipv.it> + * Rewritten based on work by Prafulla WADASKAR <prafulla.wadaskar@st.com> + * Copyright (C) 2011 Linus Walleij <linus.walleij@linaro.org> + * + * 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/slab.h> + +#include <asm/mach/irq.h> + +#include <plat/pincfg.h> +#include <plat/gpio-nomadik.h> +#include <mach/hardware.h> +#include <asm/gpio.h> + +/* + * The GPIO module in the Nomadik family of Systems-on-Chip is an + * AMBA device, managing 32 pins and alternate functions. The logic block + * is currently used in the Nomadik and ux500. + * + * Symbols in this file are called "nmk_gpio" for "nomadik gpio" + */ + +#define NMK_GPIO_PER_CHIP 32 + +struct nmk_gpio_chip { + struct gpio_chip chip; + void __iomem *addr; + struct clk *clk; + unsigned int bank; + unsigned int parent_irq; + int secondary_parent_irq; + u32 (*get_secondary_status)(unsigned int bank); + void (*set_ioforce)(bool enable); + spinlock_t lock; + bool sleepmode; + /* Keep track of configured edges */ + u32 edge_rising; + u32 edge_falling; + u32 real_wake; + u32 rwimsc; + u32 fwimsc; + u32 slpm; + u32 pull_up; +}; + +static struct nmk_gpio_chip * +nmk_gpio_chips[DIV_ROUND_UP(ARCH_NR_GPIOS, NMK_GPIO_PER_CHIP)]; + +static DEFINE_SPINLOCK(nmk_gpio_slpm_lock); + +#define NUM_BANKS ARRAY_SIZE(nmk_gpio_chips) + +static void __nmk_gpio_set_mode(struct nmk_gpio_chip *nmk_chip, + unsigned offset, int gpio_mode) +{ + u32 bit = 1 << offset; + u32 afunc, bfunc; + + afunc = readl(nmk_chip->addr + NMK_GPIO_AFSLA) & ~bit; + bfunc = readl(nmk_chip->addr + NMK_GPIO_AFSLB) & ~bit; + if (gpio_mode & NMK_GPIO_ALT_A) + afunc |= bit; + if (gpio_mode & NMK_GPIO_ALT_B) + bfunc |= bit; + writel(afunc, nmk_chip->addr + NMK_GPIO_AFSLA); + writel(bfunc, nmk_chip->addr + NMK_GPIO_AFSLB); +} + +static void __nmk_gpio_set_slpm(struct nmk_gpio_chip *nmk_chip, + unsigned offset, enum nmk_gpio_slpm mode) +{ + u32 bit = 1 << offset; + u32 slpm; + + slpm = readl(nmk_chip->addr + NMK_GPIO_SLPC); + if (mode == NMK_GPIO_SLPM_NOCHANGE) + slpm |= bit; + else + slpm &= ~bit; + writel(slpm, nmk_chip->addr + NMK_GPIO_SLPC); +} + +static void __nmk_gpio_set_pull(struct nmk_gpio_chip *nmk_chip, + unsigned offset, enum nmk_gpio_pull pull) +{ + u32 bit = 1 << offset; + u32 pdis; + + pdis = readl(nmk_chip->addr + NMK_GPIO_PDIS); + if (pull == NMK_GPIO_PULL_NONE) { + pdis |= bit; + nmk_chip->pull_up &= ~bit; + } else { + pdis &= ~bit; + } + + writel(pdis, nmk_chip->addr + NMK_GPIO_PDIS); + + if (pull == NMK_GPIO_PULL_UP) { + nmk_chip->pull_up |= bit; + writel(bit, nmk_chip->addr + NMK_GPIO_DATS); + } else if (pull == NMK_GPIO_PULL_DOWN) { + nmk_chip->pull_up &= ~bit; + writel(bit, nmk_chip->addr + NMK_GPIO_DATC); + } +} + +static void __nmk_gpio_make_input(struct nmk_gpio_chip *nmk_chip, + unsigned offset) +{ + writel(1 << offset, nmk_chip->addr + NMK_GPIO_DIRC); +} + +static void __nmk_gpio_set_output(struct nmk_gpio_chip *nmk_chip, + unsigned offset, int val) +{ + if (val) + writel(1 << offset, nmk_chip->addr + NMK_GPIO_DATS); + else + writel(1 << offset, nmk_chip->addr + NMK_GPIO_DATC); +} + +static void __nmk_gpio_make_output(struct nmk_gpio_chip *nmk_chip, + unsigned offset, int val) +{ + writel(1 << offset, nmk_chip->addr + NMK_GPIO_DIRS); + __nmk_gpio_set_output(nmk_chip, offset, val); +} + +static void __nmk_gpio_set_mode_safe(struct nmk_gpio_chip *nmk_chip, + unsigned offset, int gpio_mode, + bool glitch) +{ + u32 rwimsc = readl(nmk_chip->addr + NMK_GPIO_RWIMSC); + u32 fwimsc = readl(nmk_chip->addr + NMK_GPIO_FWIMSC); + + if (glitch && nmk_chip->set_ioforce) { + u32 bit = BIT(offset); + + /* Prevent spurious wakeups */ + writel(rwimsc & ~bit, nmk_chip->addr + NMK_GPIO_RWIMSC); + writel(fwimsc & ~bit, nmk_chip->addr + NMK_GPIO_FWIMSC); + + nmk_chip->set_ioforce(true); + } + + __nmk_gpio_set_mode(nmk_chip, offset, gpio_mode); + + if (glitch && nmk_chip->set_ioforce) { + nmk_chip->set_ioforce(false); + + writel(rwimsc, nmk_chip->addr + NMK_GPIO_RWIMSC); + writel(fwimsc, nmk_chip->addr + NMK_GPIO_FWIMSC); + } +} + +static void __nmk_config_pin(struct nmk_gpio_chip *nmk_chip, unsigned offset, + pin_cfg_t cfg, bool sleep, unsigned int *slpmregs) +{ + static const char *afnames[] = { + [NMK_GPIO_ALT_GPIO] = "GPIO", + [NMK_GPIO_ALT_A] = "A", + [NMK_GPIO_ALT_B] = "B", + [NMK_GPIO_ALT_C] = "C" + }; + static const char *pullnames[] = { + [NMK_GPIO_PULL_NONE] = "none", + [NMK_GPIO_PULL_UP] = "up", + [NMK_GPIO_PULL_DOWN] = "down", + [3] /* illegal */ = "??" + }; + static const char *slpmnames[] = { + [NMK_GPIO_SLPM_INPUT] = "input/wakeup", + [NMK_GPIO_SLPM_NOCHANGE] = "no-change/no-wakeup", + }; + + int pin = PIN_NUM(cfg); + int pull = PIN_PULL(cfg); + int af = PIN_ALT(cfg); + int slpm = PIN_SLPM(cfg); + int output = PIN_DIR(cfg); + int val = PIN_VAL(cfg); + bool glitch = af == NMK_GPIO_ALT_C; + + dev_dbg(nmk_chip->chip.dev, "pin %d [%#lx]: af %s, pull %s, slpm %s (%s%s)\n", + pin, cfg, afnames[af], pullnames[pull], slpmnames[slpm], + output ? "output " : "input", + output ? (val ? "high" : "low") : ""); + + if (sleep) { + int slpm_pull = PIN_SLPM_PULL(cfg); + int slpm_output = PIN_SLPM_DIR(cfg); + int slpm_val = PIN_SLPM_VAL(cfg); + + af = NMK_GPIO_ALT_GPIO; + + /* + * The SLPM_* values are normal values + 1 to allow zero to + * mean "same as normal". + */ + if (slpm_pull) + pull = slpm_pull - 1; + if (slpm_output) + output = slpm_output - 1; + if (slpm_val) + val = slpm_val - 1; + + dev_dbg(nmk_chip->chip.dev, "pin %d: sleep pull %s, dir %s, val %s\n", + pin, + slpm_pull ? pullnames[pull] : "same", + slpm_output ? (output ? "output" : "input") : "same", + slpm_val ? (val ? "high" : "low") : "same"); + } + + if (output) + __nmk_gpio_make_output(nmk_chip, offset, val); + else { + __nmk_gpio_make_input(nmk_chip, offset); + __nmk_gpio_set_pull(nmk_chip, offset, pull); + } + + /* + * If we've backed up the SLPM registers (glitch workaround), modify + * the backups since they will be restored. + */ + if (slpmregs) { + if (slpm == NMK_GPIO_SLPM_NOCHANGE) + slpmregs[nmk_chip->bank] |= BIT(offset); + else + slpmregs[nmk_chip->bank] &= ~BIT(offset); + } else + __nmk_gpio_set_slpm(nmk_chip, offset, slpm); + + __nmk_gpio_set_mode_safe(nmk_chip, offset, af, glitch); +} + +/* + * Safe sequence used to switch IOs between GPIO and Alternate-C mode: + * - Save SLPM registers + * - Set SLPM=0 for the IOs you want to switch and others to 1 + * - Configure the GPIO registers for the IOs that are being switched + * - Set IOFORCE=1 + * - Modify the AFLSA/B registers for the IOs that are being switched + * - Set IOFORCE=0 + * - Restore SLPM registers + * - Any spurious wake up event during switch sequence to be ignored and + * cleared + */ +static void nmk_gpio_glitch_slpm_init(unsigned int *slpm) +{ + int i; + + for (i = 0; i < NUM_BANKS; i++) { + struct nmk_gpio_chip *chip = nmk_gpio_chips[i]; + unsigned int temp = slpm[i]; + + if (!chip) + break; + + clk_enable(chip->clk); + + slpm[i] = readl(chip->addr + NMK_GPIO_SLPC); + writel(temp, chip->addr + NMK_GPIO_SLPC); + } +} + +static void nmk_gpio_glitch_slpm_restore(unsigned int *slpm) +{ + int i; + + for (i = 0; i < NUM_BANKS; i++) { + struct nmk_gpio_chip *chip = nmk_gpio_chips[i]; + + if (!chip) + break; + + writel(slpm[i], chip->addr + NMK_GPIO_SLPC); + + clk_disable(chip->clk); + } +} + +static int __nmk_config_pins(pin_cfg_t *cfgs, int num, bool sleep) +{ + static unsigned int slpm[NUM_BANKS]; + unsigned long flags; + bool glitch = false; + int ret = 0; + int i; + + for (i = 0; i < num; i++) { + if (PIN_ALT(cfgs[i]) == NMK_GPIO_ALT_C) { + glitch = true; + break; + } + } + + spin_lock_irqsave(&nmk_gpio_slpm_lock, flags); + + if (glitch) { + memset(slpm, 0xff, sizeof(slpm)); + + for (i = 0; i < num; i++) { + int pin = PIN_NUM(cfgs[i]); + int offset = pin % NMK_GPIO_PER_CHIP; + + if (PIN_ALT(cfgs[i]) == NMK_GPIO_ALT_C) + slpm[pin / NMK_GPIO_PER_CHIP] &= ~BIT(offset); + } + + nmk_gpio_glitch_slpm_init(slpm); + } + + for (i = 0; i < num; i++) { + struct nmk_gpio_chip *nmk_chip; + int pin = PIN_NUM(cfgs[i]); + + nmk_chip = irq_get_chip_data(NOMADIK_GPIO_TO_IRQ(pin)); + if (!nmk_chip) { + ret = -EINVAL; + break; + } + + clk_enable(nmk_chip->clk); + spin_lock(&nmk_chip->lock); + __nmk_config_pin(nmk_chip, pin - nmk_chip->chip.base, + cfgs[i], sleep, glitch ? slpm : NULL); + spin_unlock(&nmk_chip->lock); + clk_disable(nmk_chip->clk); + } + + if (glitch) + nmk_gpio_glitch_slpm_restore(slpm); + + spin_unlock_irqrestore(&nmk_gpio_slpm_lock, flags); + + return ret; +} + +/** + * nmk_config_pin - configure a pin's mux attributes + * @cfg: pin confguration + * + * Configures a pin's mode (alternate function or GPIO), its pull up status, + * and its sleep mode based on the specified configuration. The @cfg is + * usually one of the SoC specific macros defined in mach/<soc>-pins.h. These + * are constructed using, and can be further enhanced with, the macros in + * plat/pincfg.h. + * + * If a pin's mode is set to GPIO, it is configured as an input to avoid + * side-effects. The gpio can be manipulated later using standard GPIO API + * calls. + */ +int nmk_config_pin(pin_cfg_t cfg, bool sleep) +{ + return __nmk_config_pins(&cfg, 1, sleep); +} +EXPORT_SYMBOL(nmk_config_pin); + +/** + * nmk_config_pins - configure several pins at once + * @cfgs: array of pin configurations + * @num: number of elments in the array + * + * Configures several pins using nmk_config_pin(). Refer to that function for + * further information. + */ +int nmk_config_pins(pin_cfg_t *cfgs, int num) +{ + return __nmk_config_pins(cfgs, num, false); +} +EXPORT_SYMBOL(nmk_config_pins); + +int nmk_config_pins_sleep(pin_cfg_t *cfgs, int num) +{ + return __nmk_config_pins(cfgs, num, true); +} +EXPORT_SYMBOL(nmk_config_pins_sleep); + +/** + * nmk_gpio_set_slpm() - configure the sleep mode of a pin + * @gpio: pin number + * @mode: NMK_GPIO_SLPM_INPUT or NMK_GPIO_SLPM_NOCHANGE, + * + * This register is actually in the pinmux layer, not the GPIO block itself. + * The GPIO1B_SLPM register defines the GPIO mode when SLEEP/DEEP-SLEEP + * mode is entered (i.e. when signal IOFORCE is HIGH by the platform code). + * Each GPIO can be configured to be forced into GPIO mode when IOFORCE is + * HIGH, overriding the normal setting defined by GPIO_AFSELx registers. + * When IOFORCE returns LOW (by software, after SLEEP/DEEP-SLEEP exit), + * the GPIOs return to the normal setting defined by GPIO_AFSELx registers. + * + * If @mode is NMK_GPIO_SLPM_INPUT, the corresponding GPIO is switched to GPIO + * mode when signal IOFORCE is HIGH (i.e. when SLEEP/DEEP-SLEEP mode is + * entered) regardless of the altfunction selected. Also wake-up detection is + * ENABLED. + * + * If @mode is NMK_GPIO_SLPM_NOCHANGE, the corresponding GPIO remains + * controlled by NMK_GPIO_DATC, NMK_GPIO_DATS, NMK_GPIO_DIR, NMK_GPIO_PDIS + * (for altfunction GPIO) or respective on-chip peripherals (for other + * altfuncs) when IOFORCE is HIGH. Also wake-up detection DISABLED. + * + * Note that enable_irq_wake() will automatically enable wakeup detection. + */ +int nmk_gpio_set_slpm(int gpio, enum nmk_gpio_slpm mode) +{ + struct nmk_gpio_chip *nmk_chip; + unsigned long flags; + + nmk_chip = irq_get_chip_data(NOMADIK_GPIO_TO_IRQ(gpio)); + if (!nmk_chip) + return -EINVAL; + + clk_enable(nmk_chip->clk); + spin_lock_irqsave(&nmk_gpio_slpm_lock, flags); + spin_lock(&nmk_chip->lock); + + __nmk_gpio_set_slpm(nmk_chip, gpio - nmk_chip->chip.base, mode); + + spin_unlock(&nmk_chip->lock); + spin_unlock_irqrestore(&nmk_gpio_slpm_lock, flags); + clk_disable(nmk_chip->clk); + + return 0; +} + +/** + * nmk_gpio_set_pull() - enable/disable pull up/down on a gpio + * @gpio: pin number + * @pull: one of NMK_GPIO_PULL_DOWN, NMK_GPIO_PULL_UP, and NMK_GPIO_PULL_NONE + * + * Enables/disables pull up/down on a specified pin. This only takes effect if + * the pin is configured as an input (either explicitly or by the alternate + * function). + * + * NOTE: If enabling the pull up/down, the caller must ensure that the GPIO is + * configured as an input. Otherwise, due to the way the controller registers + * work, this function will change the value output on the pin. + */ +int nmk_gpio_set_pull(int gpio, enum nmk_gpio_pull pull) +{ + struct nmk_gpio_chip *nmk_chip; + unsigned long flags; + + nmk_chip = irq_get_chip_data(NOMADIK_GPIO_TO_IRQ(gpio)); + if (!nmk_chip) + return -EINVAL; + + clk_enable(nmk_chip->clk); + spin_lock_irqsave(&nmk_chip->lock, flags); + __nmk_gpio_set_pull(nmk_chip, gpio - nmk_chip->chip.base, pull); + spin_unlock_irqrestore(&nmk_chip->lock, flags); + clk_disable(nmk_chip->clk); + + return 0; +} + +/* Mode functions */ +/** + * nmk_gpio_set_mode() - set the mux mode of a gpio pin + * @gpio: pin number + * @gpio_mode: one of NMK_GPIO_ALT_GPIO, NMK_GPIO_ALT_A, + * NMK_GPIO_ALT_B, and NMK_GPIO_ALT_C + * + * Sets the mode of the specified pin to one of the alternate functions or + * plain GPIO. + */ +int nmk_gpio_set_mode(int gpio, int gpio_mode) +{ + struct nmk_gpio_chip *nmk_chip; + unsigned long flags; + + nmk_chip = irq_get_chip_data(NOMADIK_GPIO_TO_IRQ(gpio)); + if (!nmk_chip) + return -EINVAL; + + clk_enable(nmk_chip->clk); + spin_lock_irqsave(&nmk_chip->lock, flags); + __nmk_gpio_set_mode(nmk_chip, gpio - nmk_chip->chip.base, gpio_mode); + spin_unlock_irqrestore(&nmk_chip->lock, flags); + clk_disable(nmk_chip->clk); + + return 0; +} +EXPORT_SYMBOL(nmk_gpio_set_mode); + +int nmk_gpio_get_mode(int gpio) +{ + struct nmk_gpio_chip *nmk_chip; + u32 afunc, bfunc, bit; + + nmk_chip = irq_get_chip_data(NOMADIK_GPIO_TO_IRQ(gpio)); + if (!nmk_chip) + return -EINVAL; + + bit = 1 << (gpio - nmk_chip->chip.base); + + clk_enable(nmk_chip->clk); + + afunc = readl(nmk_chip->addr + NMK_GPIO_AFSLA) & bit; + bfunc = readl(nmk_chip->addr + NMK_GPIO_AFSLB) & bit; + + clk_disable(nmk_chip->clk); + + return (afunc ? NMK_GPIO_ALT_A : 0) | (bfunc ? NMK_GPIO_ALT_B : 0); +} +EXPORT_SYMBOL(nmk_gpio_get_mode); + + +/* IRQ functions */ +static inline int nmk_gpio_get_bitmask(int gpio) +{ + return 1 << (gpio % 32); +} + +static void nmk_gpio_irq_ack(struct irq_data *d) +{ + int gpio; + struct nmk_gpio_chip *nmk_chip; + + gpio = NOMADIK_IRQ_TO_GPIO(d->irq); + nmk_chip = irq_data_get_irq_chip_data(d); + if (!nmk_chip) + return; + + clk_enable(nmk_chip->clk); + writel(nmk_gpio_get_bitmask(gpio), nmk_chip->addr + NMK_GPIO_IC); + clk_disable(nmk_chip->clk); +} + +enum nmk_gpio_irq_type { + NORMAL, + WAKE, +}; + +static void __nmk_gpio_irq_modify(struct nmk_gpio_chip *nmk_chip, + int gpio, enum nmk_gpio_irq_type which, + bool enable) +{ + u32 rimsc = which == WAKE ? NMK_GPIO_RWIMSC : NMK_GPIO_RIMSC; + u32 fimsc = which == WAKE ? NMK_GPIO_FWIMSC : NMK_GPIO_FIMSC; + u32 bitmask = nmk_gpio_get_bitmask(gpio); + u32 reg; + + /* we must individually set/clear the two edges */ + if (nmk_chip->edge_rising & bitmask) { + reg = readl(nmk_chip->addr + rimsc); + if (enable) + reg |= bitmask; + else + reg &= ~bitmask; + writel(reg, nmk_chip->addr + rimsc); + } + if (nmk_chip->edge_falling & bitmask) { + reg = readl(nmk_chip->addr + fimsc); + if (enable) + reg |= bitmask; + else + reg &= ~bitmask; + writel(reg, nmk_chip->addr + fimsc); + } +} + +static void __nmk_gpio_set_wake(struct nmk_gpio_chip *nmk_chip, + int gpio, bool on) +{ + if (nmk_chip->sleepmode) { + __nmk_gpio_set_slpm(nmk_chip, gpio - nmk_chip->chip.base, + on ? NMK_GPIO_SLPM_WAKEUP_ENABLE + : NMK_GPIO_SLPM_WAKEUP_DISABLE); + } + + __nmk_gpio_irq_modify(nmk_chip, gpio, WAKE, on); +} + +static int nmk_gpio_irq_maskunmask(struct irq_data *d, bool enable) +{ + int gpio; + struct nmk_gpio_chip *nmk_chip; + unsigned long flags; + u32 bitmask; + + gpio = NOMADIK_IRQ_TO_GPIO(d->irq); + nmk_chip = irq_data_get_irq_chip_data(d); + bitmask = nmk_gpio_get_bitmask(gpio); + if (!nmk_chip) + return -EINVAL; + + clk_enable(nmk_chip->clk); + spin_lock_irqsave(&nmk_gpio_slpm_lock, flags); + spin_lock(&nmk_chip->lock); + + __nmk_gpio_irq_modify(nmk_chip, gpio, NORMAL, enable); + + if (!(nmk_chip->real_wake & bitmask)) + __nmk_gpio_set_wake(nmk_chip, gpio, enable); + + spin_unlock(&nmk_chip->lock); + spin_unlock_irqrestore(&nmk_gpio_slpm_lock, flags); + clk_disable(nmk_chip->clk); + + return 0; +} + +static void nmk_gpio_irq_mask(struct irq_data *d) +{ + nmk_gpio_irq_maskunmask(d, false); +} + +static void nmk_gpio_irq_unmask(struct irq_data *d) +{ + nmk_gpio_irq_maskunmask(d, true); +} + +static int nmk_gpio_irq_set_wake(struct irq_data *d, unsigned int on) +{ + struct nmk_gpio_chip *nmk_chip; + unsigned long flags; + u32 bitmask; + int gpio; + + gpio = NOMADIK_IRQ_TO_GPIO(d->irq); + nmk_chip = irq_data_get_irq_chip_data(d); + if (!nmk_chip) + return -EINVAL; + bitmask = nmk_gpio_get_bitmask(gpio); + + clk_enable(nmk_chip->clk); + spin_lock_irqsave(&nmk_gpio_slpm_lock, flags); + spin_lock(&nmk_chip->lock); + + if (irqd_irq_disabled(d)) + __nmk_gpio_set_wake(nmk_chip, gpio, on); + + if (on) + nmk_chip->real_wake |= bitmask; + else + nmk_chip->real_wake &= ~bitmask; + + spin_unlock(&nmk_chip->lock); + spin_unlock_irqrestore(&nmk_gpio_slpm_lock, flags); + clk_disable(nmk_chip->clk); + + return 0; +} + +static int nmk_gpio_irq_set_type(struct irq_data *d, unsigned int type) +{ + bool enabled = !irqd_irq_disabled(d); + bool wake = irqd_is_wakeup_set(d); + int gpio; + struct nmk_gpio_chip *nmk_chip; + unsigned long flags; + u32 bitmask; + + gpio = NOMADIK_IRQ_TO_GPIO(d->irq); + nmk_chip = irq_data_get_irq_chip_data(d); + bitmask = nmk_gpio_get_bitmask(gpio); + if (!nmk_chip) + return -EINVAL; + + if (type & IRQ_TYPE_LEVEL_HIGH) + return -EINVAL; + if (type & IRQ_TYPE_LEVEL_LOW) + return -EINVAL; + + clk_enable(nmk_chip->clk); + spin_lock_irqsave(&nmk_chip->lock, flags); + + if (enabled) + __nmk_gpio_irq_modify(nmk_chip, gpio, NORMAL, false); + + if (enabled || wake) + __nmk_gpio_irq_modify(nmk_chip, gpio, WAKE, false); + + nmk_chip->edge_rising &= ~bitmask; + if (type & IRQ_TYPE_EDGE_RISING) + nmk_chip->edge_rising |= bitmask; + + nmk_chip->edge_falling &= ~bitmask; + if (type & IRQ_TYPE_EDGE_FALLING) + nmk_chip->edge_falling |= bitmask; + + if (enabled) + __nmk_gpio_irq_modify(nmk_chip, gpio, NORMAL, true); + + if (enabled || wake) + __nmk_gpio_irq_modify(nmk_chip, gpio, WAKE, true); + + spin_unlock_irqrestore(&nmk_chip->lock, flags); + clk_disable(nmk_chip->clk); + + return 0; +} + +static unsigned int nmk_gpio_irq_startup(struct irq_data *d) +{ + struct nmk_gpio_chip *nmk_chip = irq_data_get_irq_chip_data(d); + + clk_enable(nmk_chip->clk); + nmk_gpio_irq_unmask(d); + return 0; +} + +static void nmk_gpio_irq_shutdown(struct irq_data *d) +{ + struct nmk_gpio_chip *nmk_chip = irq_data_get_irq_chip_data(d); + + nmk_gpio_irq_mask(d); + clk_disable(nmk_chip->clk); +} + +static struct irq_chip nmk_gpio_irq_chip = { + .name = "Nomadik-GPIO", + .irq_ack = nmk_gpio_irq_ack, + .irq_mask = nmk_gpio_irq_mask, + .irq_unmask = nmk_gpio_irq_unmask, + .irq_set_type = nmk_gpio_irq_set_type, + .irq_set_wake = nmk_gpio_irq_set_wake, + .irq_startup = nmk_gpio_irq_startup, + .irq_shutdown = nmk_gpio_irq_shutdown, +}; + +static void __nmk_gpio_irq_handler(unsigned int irq, struct irq_desc *desc, + u32 status) +{ + struct nmk_gpio_chip *nmk_chip; + struct irq_chip *host_chip = irq_get_chip(irq); + unsigned int first_irq; + + chained_irq_enter(host_chip, desc); + + nmk_chip = irq_get_handler_data(irq); + first_irq = NOMADIK_GPIO_TO_IRQ(nmk_chip->chip.base); + while (status) { + int bit = __ffs(status); + + generic_handle_irq(first_irq + bit); + status &= ~BIT(bit); + } + + chained_irq_exit(host_chip, desc); +} + +static void nmk_gpio_irq_handler(unsigned int irq, struct irq_desc *desc) +{ + struct nmk_gpio_chip *nmk_chip = irq_get_handler_data(irq); + u32 status; + + clk_enable(nmk_chip->clk); + status = readl(nmk_chip->addr + NMK_GPIO_IS); + clk_disable(nmk_chip->clk); + + __nmk_gpio_irq_handler(irq, desc, status); +} + +static void nmk_gpio_secondary_irq_handler(unsigned int irq, + struct irq_desc *desc) +{ + struct nmk_gpio_chip *nmk_chip = irq_get_handler_data(irq); + u32 status = nmk_chip->get_secondary_status(nmk_chip->bank); + + __nmk_gpio_irq_handler(irq, desc, status); +} + +static int nmk_gpio_init_irq(struct nmk_gpio_chip *nmk_chip) +{ + unsigned int first_irq; + int i; + + first_irq = NOMADIK_GPIO_TO_IRQ(nmk_chip->chip.base); + for (i = first_irq; i < first_irq + nmk_chip->chip.ngpio; i++) { + irq_set_chip_and_handler(i, &nmk_gpio_irq_chip, + handle_edge_irq); + set_irq_flags(i, IRQF_VALID); + irq_set_chip_data(i, nmk_chip); + irq_set_irq_type(i, IRQ_TYPE_EDGE_FALLING); + } + + irq_set_chained_handler(nmk_chip->parent_irq, nmk_gpio_irq_handler); + irq_set_handler_data(nmk_chip->parent_irq, nmk_chip); + + if (nmk_chip->secondary_parent_irq >= 0) { + irq_set_chained_handler(nmk_chip->secondary_parent_irq, + nmk_gpio_secondary_irq_handler); + irq_set_handler_data(nmk_chip->secondary_parent_irq, nmk_chip); + } + + return 0; +} + +/* I/O Functions */ +static int nmk_gpio_make_input(struct gpio_chip *chip, unsigned offset) +{ + struct nmk_gpio_chip *nmk_chip = + container_of(chip, struct nmk_gpio_chip, chip); + + clk_enable(nmk_chip->clk); + + writel(1 << offset, nmk_chip->addr + NMK_GPIO_DIRC); + + clk_disable(nmk_chip->clk); + + return 0; +} + +static int nmk_gpio_get_input(struct gpio_chip *chip, unsigned offset) +{ + struct nmk_gpio_chip *nmk_chip = + container_of(chip, struct nmk_gpio_chip, chip); + u32 bit = 1 << offset; + int value; + + clk_enable(nmk_chip->clk); + + value = (readl(nmk_chip->addr + NMK_GPIO_DAT) & bit) != 0; + + clk_disable(nmk_chip->clk); + + return value; +} + +static void nmk_gpio_set_output(struct gpio_chip *chip, unsigned offset, + int val) +{ + struct nmk_gpio_chip *nmk_chip = + container_of(chip, struct nmk_gpio_chip, chip); + + clk_enable(nmk_chip->clk); + + __nmk_gpio_set_output(nmk_chip, offset, val); + + clk_disable(nmk_chip->clk); +} + +static int nmk_gpio_make_output(struct gpio_chip *chip, unsigned offset, + int val) +{ + struct nmk_gpio_chip *nmk_chip = + container_of(chip, struct nmk_gpio_chip, chip); + + clk_enable(nmk_chip->clk); + + __nmk_gpio_make_output(nmk_chip, offset, val); + + clk_disable(nmk_chip->clk); + + return 0; +} + +static int nmk_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct nmk_gpio_chip *nmk_chip = + container_of(chip, struct nmk_gpio_chip, chip); + + return NOMADIK_GPIO_TO_IRQ(nmk_chip->chip.base) + offset; +} + +#ifdef CONFIG_DEBUG_FS + +#include <linux/seq_file.h> + +static void nmk_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) +{ + int mode; + unsigned i; + unsigned gpio = chip->base; + int is_out; + struct nmk_gpio_chip *nmk_chip = + container_of(chip, struct nmk_gpio_chip, chip); + const char *modes[] = { + [NMK_GPIO_ALT_GPIO] = "gpio", + [NMK_GPIO_ALT_A] = "altA", + [NMK_GPIO_ALT_B] = "altB", + [NMK_GPIO_ALT_C] = "altC", + }; + + clk_enable(nmk_chip->clk); + + for (i = 0; i < chip->ngpio; i++, gpio++) { + const char *label = gpiochip_is_requested(chip, i); + bool pull; + u32 bit = 1 << i; + + is_out = readl(nmk_chip->addr + NMK_GPIO_DIR) & bit; + pull = !(readl(nmk_chip->addr + NMK_GPIO_PDIS) & bit); + mode = nmk_gpio_get_mode(gpio); + seq_printf(s, " gpio-%-3d (%-20.20s) %s %s %s %s", + gpio, label ?: "(none)", + is_out ? "out" : "in ", + chip->get + ? (chip->get(chip, i) ? "hi" : "lo") + : "? ", + (mode < 0) ? "unknown" : modes[mode], + pull ? "pull" : "none"); + + if (label && !is_out) { + int irq = gpio_to_irq(gpio); + struct irq_desc *desc = irq_to_desc(irq); + + /* This races with request_irq(), set_irq_type(), + * and set_irq_wake() ... but those are "rare". + */ + if (irq >= 0 && desc->action) { + char *trigger; + u32 bitmask = nmk_gpio_get_bitmask(gpio); + + if (nmk_chip->edge_rising & bitmask) + trigger = "edge-rising"; + else if (nmk_chip->edge_falling & bitmask) + trigger = "edge-falling"; + else + trigger = "edge-undefined"; + + seq_printf(s, " irq-%d %s%s", + irq, trigger, + irqd_is_wakeup_set(&desc->irq_data) + ? " wakeup" : ""); + } + } + + seq_printf(s, "\n"); + } + + clk_disable(nmk_chip->clk); +} + +#else +#define nmk_gpio_dbg_show NULL +#endif + +/* This structure is replicated for each GPIO block allocated at probe time */ +static struct gpio_chip nmk_gpio_template = { + .direction_input = nmk_gpio_make_input, + .get = nmk_gpio_get_input, + .direction_output = nmk_gpio_make_output, + .set = nmk_gpio_set_output, + .to_irq = nmk_gpio_to_irq, + .dbg_show = nmk_gpio_dbg_show, + .can_sleep = 0, +}; + +void nmk_gpio_clocks_enable(void) +{ + int i; + + for (i = 0; i < NUM_BANKS; i++) { + struct nmk_gpio_chip *chip = nmk_gpio_chips[i]; + + if (!chip) + continue; + + clk_enable(chip->clk); + } +} + +void nmk_gpio_clocks_disable(void) +{ + int i; + + for (i = 0; i < NUM_BANKS; i++) { + struct nmk_gpio_chip *chip = nmk_gpio_chips[i]; + + if (!chip) + continue; + + clk_disable(chip->clk); + } +} + +/* + * Called from the suspend/resume path to only keep the real wakeup interrupts + * (those that have had set_irq_wake() called on them) as wakeup interrupts, + * and not the rest of the interrupts which we needed to have as wakeups for + * cpuidle. + * + * PM ops are not used since this needs to be done at the end, after all the + * other drivers are done with their suspend callbacks. + */ +void nmk_gpio_wakeups_suspend(void) +{ + int i; + + for (i = 0; i < NUM_BANKS; i++) { + struct nmk_gpio_chip *chip = nmk_gpio_chips[i]; + + if (!chip) + break; + + clk_enable(chip->clk); + + chip->rwimsc = readl(chip->addr + NMK_GPIO_RWIMSC); + chip->fwimsc = readl(chip->addr + NMK_GPIO_FWIMSC); + + writel(chip->rwimsc & chip->real_wake, + chip->addr + NMK_GPIO_RWIMSC); + writel(chip->fwimsc & chip->real_wake, + chip->addr + NMK_GPIO_FWIMSC); + + if (chip->sleepmode) { + chip->slpm = readl(chip->addr + NMK_GPIO_SLPC); + + /* 0 -> wakeup enable */ + writel(~chip->real_wake, chip->addr + NMK_GPIO_SLPC); + } + + clk_disable(chip->clk); + } +} + +void nmk_gpio_wakeups_resume(void) +{ + int i; + + for (i = 0; i < NUM_BANKS; i++) { + struct nmk_gpio_chip *chip = nmk_gpio_chips[i]; + + if (!chip) + break; + + clk_enable(chip->clk); + + writel(chip->rwimsc, chip->addr + NMK_GPIO_RWIMSC); + writel(chip->fwimsc, chip->addr + NMK_GPIO_FWIMSC); + + if (chip->sleepmode) + writel(chip->slpm, chip->addr + NMK_GPIO_SLPC); + + clk_disable(chip->clk); + } +} + +/* + * Read the pull up/pull down status. + * A bit set in 'pull_up' means that pull up + * is selected if pull is enabled in PDIS register. + * Note: only pull up/down set via this driver can + * be detected due to HW limitations. + */ +void nmk_gpio_read_pull(int gpio_bank, u32 *pull_up) +{ + if (gpio_bank < NUM_BANKS) { + struct nmk_gpio_chip *chip = nmk_gpio_chips[gpio_bank]; + + if (!chip) + return; + + *pull_up = chip->pull_up; + } +} + +static int __devinit nmk_gpio_probe(struct platform_device *dev) +{ + struct nmk_gpio_platform_data *pdata = dev->dev.platform_data; + struct nmk_gpio_chip *nmk_chip; + struct gpio_chip *chip; + struct resource *res; + struct clk *clk; + int secondary_irq; + int irq; + int ret; + + if (!pdata) + return -ENODEV; + + res = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENOENT; + goto out; + } + + irq = platform_get_irq(dev, 0); + if (irq < 0) { + ret = irq; + goto out; + } + + secondary_irq = platform_get_irq(dev, 1); + if (secondary_irq >= 0 && !pdata->get_secondary_status) { + ret = -EINVAL; + goto out; + } + + if (request_mem_region(res->start, resource_size(res), + dev_name(&dev->dev)) == NULL) { + ret = -EBUSY; + goto out; + } + + clk = clk_get(&dev->dev, NULL); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + goto out_release; + } + + nmk_chip = kzalloc(sizeof(*nmk_chip), GFP_KERNEL); + if (!nmk_chip) { + ret = -ENOMEM; + goto out_clk; + } + /* + * The virt address in nmk_chip->addr is in the nomadik register space, + * so we can simply convert the resource address, without remapping + */ + nmk_chip->bank = dev->id; + nmk_chip->clk = clk; + nmk_chip->addr = io_p2v(res->start); + nmk_chip->chip = nmk_gpio_template; + nmk_chip->parent_irq = irq; + nmk_chip->secondary_parent_irq = secondary_irq; + nmk_chip->get_secondary_status = pdata->get_secondary_status; + nmk_chip->set_ioforce = pdata->set_ioforce; + nmk_chip->sleepmode = pdata->supports_sleepmode; + spin_lock_init(&nmk_chip->lock); + + chip = &nmk_chip->chip; + chip->base = pdata->first_gpio; + chip->ngpio = pdata->num_gpio; + chip->label = pdata->name ?: dev_name(&dev->dev); + chip->dev = &dev->dev; + chip->owner = THIS_MODULE; + + ret = gpiochip_add(&nmk_chip->chip); + if (ret) + goto out_free; + + BUG_ON(nmk_chip->bank >= ARRAY_SIZE(nmk_gpio_chips)); + + nmk_gpio_chips[nmk_chip->bank] = nmk_chip; + platform_set_drvdata(dev, nmk_chip); + + nmk_gpio_init_irq(nmk_chip); + + dev_info(&dev->dev, "at address %p\n", + nmk_chip->addr); + return 0; + +out_free: + kfree(nmk_chip); +out_clk: + clk_disable(clk); + clk_put(clk); +out_release: + release_mem_region(res->start, resource_size(res)); +out: + dev_err(&dev->dev, "Failure %i for GPIO %i-%i\n", ret, + pdata->first_gpio, pdata->first_gpio+31); + return ret; +} + +static struct platform_driver nmk_gpio_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "gpio", + }, + .probe = nmk_gpio_probe, +}; + +static int __init nmk_gpio_init(void) +{ + return platform_driver_register(&nmk_gpio_driver); +} + +core_initcall(nmk_gpio_init); + +MODULE_AUTHOR("Prafulla WADASKAR and Alessandro Rubini"); +MODULE_DESCRIPTION("Nomadik GPIO Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpio/gpio-omap.c b/drivers/gpio/gpio-omap.c new file mode 100644 index 00000000..44615406 --- /dev/null +++ b/drivers/gpio/gpio-omap.c @@ -0,0 +1,1545 @@ +/* + * Support functions for OMAP GPIO + * + * Copyright (C) 2003-2005 Nokia Corporation + * Written by Juha Yrjölä <juha.yrjola@nokia.com> + * + * Copyright (C) 2009 Texas Instruments + * Added OMAP4 support - Santosh Shilimkar <santosh.shilimkar@ti.com> + * + * 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 <linux/init.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/syscore_ops.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/device.h> +#include <linux/pm_runtime.h> +#include <linux/pm.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/irqdomain.h> + +#include <mach/hardware.h> +#include <asm/irq.h> +#include <mach/irqs.h> +#include <asm/gpio.h> +#include <asm/mach/irq.h> + +#define OFF_MODE 1 + +static LIST_HEAD(omap_gpio_list); + +struct gpio_regs { + u32 irqenable1; + u32 irqenable2; + u32 wake_en; + u32 ctrl; + u32 oe; + u32 leveldetect0; + u32 leveldetect1; + u32 risingdetect; + u32 fallingdetect; + u32 dataout; + u32 debounce; + u32 debounce_en; +}; + +struct gpio_bank { + struct list_head node; + void __iomem *base; + u16 irq; + int irq_base; + struct irq_domain *domain; + u32 suspend_wakeup; + u32 saved_wakeup; + u32 non_wakeup_gpios; + u32 enabled_non_wakeup_gpios; + struct gpio_regs context; + u32 saved_datain; + u32 saved_fallingdetect; + u32 saved_risingdetect; + u32 level_mask; + u32 toggle_mask; + spinlock_t lock; + struct gpio_chip chip; + struct clk *dbck; + u32 mod_usage; + u32 dbck_enable_mask; + bool dbck_enabled; + struct device *dev; + bool is_mpuio; + bool dbck_flag; + bool loses_context; + int stride; + u32 width; + int context_loss_count; + int power_mode; + bool workaround_enabled; + + void (*set_dataout)(struct gpio_bank *bank, int gpio, int enable); + int (*get_context_loss_count)(struct device *dev); + + struct omap_gpio_reg_offs *regs; +}; + +#define GPIO_INDEX(bank, gpio) (gpio % bank->width) +#define GPIO_BIT(bank, gpio) (1 << GPIO_INDEX(bank, gpio)) +#define GPIO_MOD_CTRL_BIT BIT(0) + +static int irq_to_gpio(struct gpio_bank *bank, unsigned int gpio_irq) +{ + return gpio_irq - bank->irq_base + bank->chip.base; +} + +static void _set_gpio_direction(struct gpio_bank *bank, int gpio, int is_input) +{ + void __iomem *reg = bank->base; + u32 l; + + reg += bank->regs->direction; + l = __raw_readl(reg); + if (is_input) + l |= 1 << gpio; + else + l &= ~(1 << gpio); + __raw_writel(l, reg); + bank->context.oe = l; +} + + +/* set data out value using dedicate set/clear register */ +static void _set_gpio_dataout_reg(struct gpio_bank *bank, int gpio, int enable) +{ + void __iomem *reg = bank->base; + u32 l = GPIO_BIT(bank, gpio); + + if (enable) { + reg += bank->regs->set_dataout; + bank->context.dataout |= l; + } else { + reg += bank->regs->clr_dataout; + bank->context.dataout &= ~l; + } + + __raw_writel(l, reg); +} + +/* set data out value using mask register */ +static void _set_gpio_dataout_mask(struct gpio_bank *bank, int gpio, int enable) +{ + void __iomem *reg = bank->base + bank->regs->dataout; + u32 gpio_bit = GPIO_BIT(bank, gpio); + u32 l; + + l = __raw_readl(reg); + if (enable) + l |= gpio_bit; + else + l &= ~gpio_bit; + __raw_writel(l, reg); + bank->context.dataout = l; +} + +static int _get_gpio_datain(struct gpio_bank *bank, int offset) +{ + void __iomem *reg = bank->base + bank->regs->datain; + + return (__raw_readl(reg) & (1 << offset)) != 0; +} + +static int _get_gpio_dataout(struct gpio_bank *bank, int offset) +{ + void __iomem *reg = bank->base + bank->regs->dataout; + + return (__raw_readl(reg) & (1 << offset)) != 0; +} + +static inline void _gpio_rmw(void __iomem *base, u32 reg, u32 mask, bool set) +{ + int l = __raw_readl(base + reg); + + if (set) + l |= mask; + else + l &= ~mask; + + __raw_writel(l, base + reg); +} + +static inline void _gpio_dbck_enable(struct gpio_bank *bank) +{ + if (bank->dbck_enable_mask && !bank->dbck_enabled) { + clk_enable(bank->dbck); + bank->dbck_enabled = true; + } +} + +static inline void _gpio_dbck_disable(struct gpio_bank *bank) +{ + if (bank->dbck_enable_mask && bank->dbck_enabled) { + clk_disable(bank->dbck); + bank->dbck_enabled = false; + } +} + +/** + * _set_gpio_debounce - low level gpio debounce time + * @bank: the gpio bank we're acting upon + * @gpio: the gpio number on this @gpio + * @debounce: debounce time to use + * + * OMAP's debounce time is in 31us steps so we need + * to convert and round up to the closest unit. + */ +static void _set_gpio_debounce(struct gpio_bank *bank, unsigned gpio, + unsigned debounce) +{ + void __iomem *reg; + u32 val; + u32 l; + + if (!bank->dbck_flag) + return; + + if (debounce < 32) + debounce = 0x01; + else if (debounce > 7936) + debounce = 0xff; + else + debounce = (debounce / 0x1f) - 1; + + l = GPIO_BIT(bank, gpio); + + clk_enable(bank->dbck); + reg = bank->base + bank->regs->debounce; + __raw_writel(debounce, reg); + + reg = bank->base + bank->regs->debounce_en; + val = __raw_readl(reg); + + if (debounce) + val |= l; + else + val &= ~l; + bank->dbck_enable_mask = val; + + __raw_writel(val, reg); + clk_disable(bank->dbck); + /* + * Enable debounce clock per module. + * This call is mandatory because in omap_gpio_request() when + * *_runtime_get_sync() is called, _gpio_dbck_enable() within + * runtime callbck fails to turn on dbck because dbck_enable_mask + * used within _gpio_dbck_enable() is still not initialized at + * that point. Therefore we have to enable dbck here. + */ + _gpio_dbck_enable(bank); + if (bank->dbck_enable_mask) { + bank->context.debounce = debounce; + bank->context.debounce_en = val; + } +} + +static inline void set_gpio_trigger(struct gpio_bank *bank, int gpio, + unsigned trigger) +{ + void __iomem *base = bank->base; + u32 gpio_bit = 1 << gpio; + + _gpio_rmw(base, bank->regs->leveldetect0, gpio_bit, + trigger & IRQ_TYPE_LEVEL_LOW); + _gpio_rmw(base, bank->regs->leveldetect1, gpio_bit, + trigger & IRQ_TYPE_LEVEL_HIGH); + _gpio_rmw(base, bank->regs->risingdetect, gpio_bit, + trigger & IRQ_TYPE_EDGE_RISING); + _gpio_rmw(base, bank->regs->fallingdetect, gpio_bit, + trigger & IRQ_TYPE_EDGE_FALLING); + + bank->context.leveldetect0 = + __raw_readl(bank->base + bank->regs->leveldetect0); + bank->context.leveldetect1 = + __raw_readl(bank->base + bank->regs->leveldetect1); + bank->context.risingdetect = + __raw_readl(bank->base + bank->regs->risingdetect); + bank->context.fallingdetect = + __raw_readl(bank->base + bank->regs->fallingdetect); + + if (likely(!(bank->non_wakeup_gpios & gpio_bit))) { + _gpio_rmw(base, bank->regs->wkup_en, gpio_bit, trigger != 0); + bank->context.wake_en = + __raw_readl(bank->base + bank->regs->wkup_en); + } + + /* This part needs to be executed always for OMAP{34xx, 44xx} */ + if (!bank->regs->irqctrl) { + /* On omap24xx proceed only when valid GPIO bit is set */ + if (bank->non_wakeup_gpios) { + if (!(bank->non_wakeup_gpios & gpio_bit)) + goto exit; + } + + /* + * Log the edge gpio and manually trigger the IRQ + * after resume if the input level changes + * to avoid irq lost during PER RET/OFF mode + * Applies for omap2 non-wakeup gpio and all omap3 gpios + */ + if (trigger & IRQ_TYPE_EDGE_BOTH) + bank->enabled_non_wakeup_gpios |= gpio_bit; + else + bank->enabled_non_wakeup_gpios &= ~gpio_bit; + } + +exit: + bank->level_mask = + __raw_readl(bank->base + bank->regs->leveldetect0) | + __raw_readl(bank->base + bank->regs->leveldetect1); +} + +#ifdef CONFIG_ARCH_OMAP1 +/* + * This only applies to chips that can't do both rising and falling edge + * detection at once. For all other chips, this function is a noop. + */ +static void _toggle_gpio_edge_triggering(struct gpio_bank *bank, int gpio) +{ + void __iomem *reg = bank->base; + u32 l = 0; + + if (!bank->regs->irqctrl) + return; + + reg += bank->regs->irqctrl; + + l = __raw_readl(reg); + if ((l >> gpio) & 1) + l &= ~(1 << gpio); + else + l |= 1 << gpio; + + __raw_writel(l, reg); +} +#else +static void _toggle_gpio_edge_triggering(struct gpio_bank *bank, int gpio) {} +#endif + +static int _set_gpio_triggering(struct gpio_bank *bank, int gpio, + unsigned trigger) +{ + void __iomem *reg = bank->base; + void __iomem *base = bank->base; + u32 l = 0; + + if (bank->regs->leveldetect0 && bank->regs->wkup_en) { + set_gpio_trigger(bank, gpio, trigger); + } else if (bank->regs->irqctrl) { + reg += bank->regs->irqctrl; + + l = __raw_readl(reg); + if ((trigger & IRQ_TYPE_SENSE_MASK) == IRQ_TYPE_EDGE_BOTH) + bank->toggle_mask |= 1 << gpio; + if (trigger & IRQ_TYPE_EDGE_RISING) + l |= 1 << gpio; + else if (trigger & IRQ_TYPE_EDGE_FALLING) + l &= ~(1 << gpio); + else + return -EINVAL; + + __raw_writel(l, reg); + } else if (bank->regs->edgectrl1) { + if (gpio & 0x08) + reg += bank->regs->edgectrl2; + else + reg += bank->regs->edgectrl1; + + gpio &= 0x07; + l = __raw_readl(reg); + l &= ~(3 << (gpio << 1)); + if (trigger & IRQ_TYPE_EDGE_RISING) + l |= 2 << (gpio << 1); + if (trigger & IRQ_TYPE_EDGE_FALLING) + l |= 1 << (gpio << 1); + + /* Enable wake-up during idle for dynamic tick */ + _gpio_rmw(base, bank->regs->wkup_en, 1 << gpio, trigger); + bank->context.wake_en = + __raw_readl(bank->base + bank->regs->wkup_en); + __raw_writel(l, reg); + } + return 0; +} + +static int gpio_irq_type(struct irq_data *d, unsigned type) +{ + struct gpio_bank *bank = irq_data_get_irq_chip_data(d); + unsigned gpio; + int retval; + unsigned long flags; + + if (!cpu_class_is_omap2() && d->irq > IH_MPUIO_BASE) + gpio = OMAP_MPUIO(d->irq - IH_MPUIO_BASE); + else + gpio = irq_to_gpio(bank, d->irq); + + if (type & ~IRQ_TYPE_SENSE_MASK) + return -EINVAL; + + if (!bank->regs->leveldetect0 && + (type & (IRQ_TYPE_LEVEL_LOW|IRQ_TYPE_LEVEL_HIGH))) + return -EINVAL; + + spin_lock_irqsave(&bank->lock, flags); + retval = _set_gpio_triggering(bank, GPIO_INDEX(bank, gpio), type); + spin_unlock_irqrestore(&bank->lock, flags); + + if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH)) + __irq_set_handler_locked(d->irq, handle_level_irq); + else if (type & (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)) + __irq_set_handler_locked(d->irq, handle_edge_irq); + + return retval; +} + +static void _clear_gpio_irqbank(struct gpio_bank *bank, int gpio_mask) +{ + void __iomem *reg = bank->base; + + reg += bank->regs->irqstatus; + __raw_writel(gpio_mask, reg); + + /* Workaround for clearing DSP GPIO interrupts to allow retention */ + if (bank->regs->irqstatus2) { + reg = bank->base + bank->regs->irqstatus2; + __raw_writel(gpio_mask, reg); + } + + /* Flush posted write for the irq status to avoid spurious interrupts */ + __raw_readl(reg); +} + +static inline void _clear_gpio_irqstatus(struct gpio_bank *bank, int gpio) +{ + _clear_gpio_irqbank(bank, GPIO_BIT(bank, gpio)); +} + +static u32 _get_gpio_irqbank_mask(struct gpio_bank *bank) +{ + void __iomem *reg = bank->base; + u32 l; + u32 mask = (1 << bank->width) - 1; + + reg += bank->regs->irqenable; + l = __raw_readl(reg); + if (bank->regs->irqenable_inv) + l = ~l; + l &= mask; + return l; +} + +static void _enable_gpio_irqbank(struct gpio_bank *bank, int gpio_mask) +{ + void __iomem *reg = bank->base; + u32 l; + + if (bank->regs->set_irqenable) { + reg += bank->regs->set_irqenable; + l = gpio_mask; + bank->context.irqenable1 |= gpio_mask; + } else { + reg += bank->regs->irqenable; + l = __raw_readl(reg); + if (bank->regs->irqenable_inv) + l &= ~gpio_mask; + else + l |= gpio_mask; + bank->context.irqenable1 = l; + } + + __raw_writel(l, reg); +} + +static void _disable_gpio_irqbank(struct gpio_bank *bank, int gpio_mask) +{ + void __iomem *reg = bank->base; + u32 l; + + if (bank->regs->clr_irqenable) { + reg += bank->regs->clr_irqenable; + l = gpio_mask; + bank->context.irqenable1 &= ~gpio_mask; + } else { + reg += bank->regs->irqenable; + l = __raw_readl(reg); + if (bank->regs->irqenable_inv) + l |= gpio_mask; + else + l &= ~gpio_mask; + bank->context.irqenable1 = l; + } + + __raw_writel(l, reg); +} + +static inline void _set_gpio_irqenable(struct gpio_bank *bank, int gpio, int enable) +{ + if (enable) + _enable_gpio_irqbank(bank, GPIO_BIT(bank, gpio)); + else + _disable_gpio_irqbank(bank, GPIO_BIT(bank, gpio)); +} + +/* + * Note that ENAWAKEUP needs to be enabled in GPIO_SYSCONFIG register. + * 1510 does not seem to have a wake-up register. If JTAG is connected + * to the target, system will wake up always on GPIO events. While + * system is running all registered GPIO interrupts need to have wake-up + * enabled. When system is suspended, only selected GPIO interrupts need + * to have wake-up enabled. + */ +static int _set_gpio_wakeup(struct gpio_bank *bank, int gpio, int enable) +{ + u32 gpio_bit = GPIO_BIT(bank, gpio); + unsigned long flags; + + if (bank->non_wakeup_gpios & gpio_bit) { + dev_err(bank->dev, + "Unable to modify wakeup on non-wakeup GPIO%d\n", gpio); + return -EINVAL; + } + + spin_lock_irqsave(&bank->lock, flags); + if (enable) + bank->suspend_wakeup |= gpio_bit; + else + bank->suspend_wakeup &= ~gpio_bit; + + __raw_writel(bank->suspend_wakeup, bank->base + bank->regs->wkup_en); + spin_unlock_irqrestore(&bank->lock, flags); + + return 0; +} + +static void _reset_gpio(struct gpio_bank *bank, int gpio) +{ + _set_gpio_direction(bank, GPIO_INDEX(bank, gpio), 1); + _set_gpio_irqenable(bank, gpio, 0); + _clear_gpio_irqstatus(bank, gpio); + _set_gpio_triggering(bank, GPIO_INDEX(bank, gpio), IRQ_TYPE_NONE); +} + +/* Use disable_irq_wake() and enable_irq_wake() functions from drivers */ +static int gpio_wake_enable(struct irq_data *d, unsigned int enable) +{ + struct gpio_bank *bank = irq_data_get_irq_chip_data(d); + unsigned int gpio = irq_to_gpio(bank, d->irq); + + return _set_gpio_wakeup(bank, gpio, enable); +} + +static int omap_gpio_request(struct gpio_chip *chip, unsigned offset) +{ + struct gpio_bank *bank = container_of(chip, struct gpio_bank, chip); + unsigned long flags; + + /* + * If this is the first gpio_request for the bank, + * enable the bank module. + */ + if (!bank->mod_usage) + pm_runtime_get_sync(bank->dev); + + spin_lock_irqsave(&bank->lock, flags); + /* Set trigger to none. You need to enable the desired trigger with + * request_irq() or set_irq_type(). + */ + _set_gpio_triggering(bank, offset, IRQ_TYPE_NONE); + + if (bank->regs->pinctrl) { + void __iomem *reg = bank->base + bank->regs->pinctrl; + + /* Claim the pin for MPU */ + __raw_writel(__raw_readl(reg) | (1 << offset), reg); + } + + if (bank->regs->ctrl && !bank->mod_usage) { + void __iomem *reg = bank->base + bank->regs->ctrl; + u32 ctrl; + + ctrl = __raw_readl(reg); + /* Module is enabled, clocks are not gated */ + ctrl &= ~GPIO_MOD_CTRL_BIT; + __raw_writel(ctrl, reg); + bank->context.ctrl = ctrl; + } + + bank->mod_usage |= 1 << offset; + + spin_unlock_irqrestore(&bank->lock, flags); + + return 0; +} + +static void omap_gpio_free(struct gpio_chip *chip, unsigned offset) +{ + struct gpio_bank *bank = container_of(chip, struct gpio_bank, chip); + void __iomem *base = bank->base; + unsigned long flags; + + spin_lock_irqsave(&bank->lock, flags); + + if (bank->regs->wkup_en) { + /* Disable wake-up during idle for dynamic tick */ + _gpio_rmw(base, bank->regs->wkup_en, 1 << offset, 0); + bank->context.wake_en = + __raw_readl(bank->base + bank->regs->wkup_en); + } + + bank->mod_usage &= ~(1 << offset); + + if (bank->regs->ctrl && !bank->mod_usage) { + void __iomem *reg = bank->base + bank->regs->ctrl; + u32 ctrl; + + ctrl = __raw_readl(reg); + /* Module is disabled, clocks are gated */ + ctrl |= GPIO_MOD_CTRL_BIT; + __raw_writel(ctrl, reg); + bank->context.ctrl = ctrl; + } + + _reset_gpio(bank, bank->chip.base + offset); + spin_unlock_irqrestore(&bank->lock, flags); + + /* + * If this is the last gpio to be freed in the bank, + * disable the bank module. + */ + if (!bank->mod_usage) + pm_runtime_put(bank->dev); +} + +/* + * We need to unmask the GPIO bank interrupt as soon as possible to + * avoid missing GPIO interrupts for other lines in the bank. + * Then we need to mask-read-clear-unmask the triggered GPIO lines + * in the bank to avoid missing nested interrupts for a GPIO line. + * If we wait to unmask individual GPIO lines in the bank after the + * line's interrupt handler has been run, we may miss some nested + * interrupts. + */ +static void gpio_irq_handler(unsigned int irq, struct irq_desc *desc) +{ + void __iomem *isr_reg = NULL; + u32 isr; + unsigned int gpio_irq, gpio_index; + struct gpio_bank *bank; + u32 retrigger = 0; + int unmasked = 0; + struct irq_chip *chip = irq_desc_get_chip(desc); + + chained_irq_enter(chip, desc); + + bank = irq_get_handler_data(irq); + isr_reg = bank->base + bank->regs->irqstatus; + pm_runtime_get_sync(bank->dev); + + if (WARN_ON(!isr_reg)) + goto exit; + + while(1) { + u32 isr_saved, level_mask = 0; + u32 enabled; + + enabled = _get_gpio_irqbank_mask(bank); + isr_saved = isr = __raw_readl(isr_reg) & enabled; + + if (bank->level_mask) + level_mask = bank->level_mask & enabled; + + /* clear edge sensitive interrupts before handler(s) are + called so that we don't miss any interrupt occurred while + executing them */ + _disable_gpio_irqbank(bank, isr_saved & ~level_mask); + _clear_gpio_irqbank(bank, isr_saved & ~level_mask); + _enable_gpio_irqbank(bank, isr_saved & ~level_mask); + + /* if there is only edge sensitive GPIO pin interrupts + configured, we could unmask GPIO bank interrupt immediately */ + if (!level_mask && !unmasked) { + unmasked = 1; + chained_irq_exit(chip, desc); + } + + isr |= retrigger; + retrigger = 0; + if (!isr) + break; + + gpio_irq = bank->irq_base; + for (; isr != 0; isr >>= 1, gpio_irq++) { + int gpio = irq_to_gpio(bank, gpio_irq); + + if (!(isr & 1)) + continue; + + gpio_index = GPIO_INDEX(bank, gpio); + + /* + * Some chips can't respond to both rising and falling + * at the same time. If this irq was requested with + * both flags, we need to flip the ICR data for the IRQ + * to respond to the IRQ for the opposite direction. + * This will be indicated in the bank toggle_mask. + */ + if (bank->toggle_mask & (1 << gpio_index)) + _toggle_gpio_edge_triggering(bank, gpio_index); + + generic_handle_irq(gpio_irq); + } + } + /* if bank has any level sensitive GPIO pin interrupt + configured, we must unmask the bank interrupt only after + handler(s) are executed in order to avoid spurious bank + interrupt */ +exit: + if (!unmasked) + chained_irq_exit(chip, desc); + pm_runtime_put(bank->dev); +} + +static void gpio_irq_shutdown(struct irq_data *d) +{ + struct gpio_bank *bank = irq_data_get_irq_chip_data(d); + unsigned int gpio = irq_to_gpio(bank, d->irq); + unsigned long flags; + + spin_lock_irqsave(&bank->lock, flags); + _reset_gpio(bank, gpio); + spin_unlock_irqrestore(&bank->lock, flags); +} + +static void gpio_ack_irq(struct irq_data *d) +{ + struct gpio_bank *bank = irq_data_get_irq_chip_data(d); + unsigned int gpio = irq_to_gpio(bank, d->irq); + + _clear_gpio_irqstatus(bank, gpio); +} + +static void gpio_mask_irq(struct irq_data *d) +{ + struct gpio_bank *bank = irq_data_get_irq_chip_data(d); + unsigned int gpio = irq_to_gpio(bank, d->irq); + unsigned long flags; + + spin_lock_irqsave(&bank->lock, flags); + _set_gpio_irqenable(bank, gpio, 0); + _set_gpio_triggering(bank, GPIO_INDEX(bank, gpio), IRQ_TYPE_NONE); + spin_unlock_irqrestore(&bank->lock, flags); +} + +static void gpio_unmask_irq(struct irq_data *d) +{ + struct gpio_bank *bank = irq_data_get_irq_chip_data(d); + unsigned int gpio = irq_to_gpio(bank, d->irq); + unsigned int irq_mask = GPIO_BIT(bank, gpio); + u32 trigger = irqd_get_trigger_type(d); + unsigned long flags; + + spin_lock_irqsave(&bank->lock, flags); + if (trigger) + _set_gpio_triggering(bank, GPIO_INDEX(bank, gpio), trigger); + + /* For level-triggered GPIOs, the clearing must be done after + * the HW source is cleared, thus after the handler has run */ + if (bank->level_mask & irq_mask) { + _set_gpio_irqenable(bank, gpio, 0); + _clear_gpio_irqstatus(bank, gpio); + } + + _set_gpio_irqenable(bank, gpio, 1); + spin_unlock_irqrestore(&bank->lock, flags); +} + +static struct irq_chip gpio_irq_chip = { + .name = "GPIO", + .irq_shutdown = gpio_irq_shutdown, + .irq_ack = gpio_ack_irq, + .irq_mask = gpio_mask_irq, + .irq_unmask = gpio_unmask_irq, + .irq_set_type = gpio_irq_type, + .irq_set_wake = gpio_wake_enable, +}; + +/*---------------------------------------------------------------------*/ + +static int omap_mpuio_suspend_noirq(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct gpio_bank *bank = platform_get_drvdata(pdev); + void __iomem *mask_reg = bank->base + + OMAP_MPUIO_GPIO_MASKIT / bank->stride; + unsigned long flags; + + spin_lock_irqsave(&bank->lock, flags); + bank->saved_wakeup = __raw_readl(mask_reg); + __raw_writel(0xffff & ~bank->suspend_wakeup, mask_reg); + spin_unlock_irqrestore(&bank->lock, flags); + + return 0; +} + +static int omap_mpuio_resume_noirq(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct gpio_bank *bank = platform_get_drvdata(pdev); + void __iomem *mask_reg = bank->base + + OMAP_MPUIO_GPIO_MASKIT / bank->stride; + unsigned long flags; + + spin_lock_irqsave(&bank->lock, flags); + __raw_writel(bank->saved_wakeup, mask_reg); + spin_unlock_irqrestore(&bank->lock, flags); + + return 0; +} + +static const struct dev_pm_ops omap_mpuio_dev_pm_ops = { + .suspend_noirq = omap_mpuio_suspend_noirq, + .resume_noirq = omap_mpuio_resume_noirq, +}; + +/* use platform_driver for this. */ +static struct platform_driver omap_mpuio_driver = { + .driver = { + .name = "mpuio", + .pm = &omap_mpuio_dev_pm_ops, + }, +}; + +static struct platform_device omap_mpuio_device = { + .name = "mpuio", + .id = -1, + .dev = { + .driver = &omap_mpuio_driver.driver, + } + /* could list the /proc/iomem resources */ +}; + +static inline void mpuio_init(struct gpio_bank *bank) +{ + platform_set_drvdata(&omap_mpuio_device, bank); + + if (platform_driver_register(&omap_mpuio_driver) == 0) + (void) platform_device_register(&omap_mpuio_device); +} + +/*---------------------------------------------------------------------*/ + +static int gpio_input(struct gpio_chip *chip, unsigned offset) +{ + struct gpio_bank *bank; + unsigned long flags; + + bank = container_of(chip, struct gpio_bank, chip); + spin_lock_irqsave(&bank->lock, flags); + _set_gpio_direction(bank, offset, 1); + spin_unlock_irqrestore(&bank->lock, flags); + return 0; +} + +static int gpio_is_input(struct gpio_bank *bank, int mask) +{ + void __iomem *reg = bank->base + bank->regs->direction; + + return __raw_readl(reg) & mask; +} + +static int gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct gpio_bank *bank; + u32 mask; + + bank = container_of(chip, struct gpio_bank, chip); + mask = (1 << offset); + + if (gpio_is_input(bank, mask)) + return _get_gpio_datain(bank, offset); + else + return _get_gpio_dataout(bank, offset); +} + +static int gpio_output(struct gpio_chip *chip, unsigned offset, int value) +{ + struct gpio_bank *bank; + unsigned long flags; + + bank = container_of(chip, struct gpio_bank, chip); + spin_lock_irqsave(&bank->lock, flags); + bank->set_dataout(bank, offset, value); + _set_gpio_direction(bank, offset, 0); + spin_unlock_irqrestore(&bank->lock, flags); + return 0; +} + +static int gpio_debounce(struct gpio_chip *chip, unsigned offset, + unsigned debounce) +{ + struct gpio_bank *bank; + unsigned long flags; + + bank = container_of(chip, struct gpio_bank, chip); + + if (!bank->dbck) { + bank->dbck = clk_get(bank->dev, "dbclk"); + if (IS_ERR(bank->dbck)) + dev_err(bank->dev, "Could not get gpio dbck\n"); + } + + spin_lock_irqsave(&bank->lock, flags); + _set_gpio_debounce(bank, offset, debounce); + spin_unlock_irqrestore(&bank->lock, flags); + + return 0; +} + +static void gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct gpio_bank *bank; + unsigned long flags; + + bank = container_of(chip, struct gpio_bank, chip); + spin_lock_irqsave(&bank->lock, flags); + bank->set_dataout(bank, offset, value); + spin_unlock_irqrestore(&bank->lock, flags); +} + +static int gpio_2irq(struct gpio_chip *chip, unsigned offset) +{ + struct gpio_bank *bank; + + bank = container_of(chip, struct gpio_bank, chip); + return bank->irq_base + offset; +} + +/*---------------------------------------------------------------------*/ + +static void __init omap_gpio_show_rev(struct gpio_bank *bank) +{ + static bool called; + u32 rev; + + if (called || bank->regs->revision == USHRT_MAX) + return; + + rev = __raw_readw(bank->base + bank->regs->revision); + pr_info("OMAP GPIO hardware version %d.%d\n", + (rev >> 4) & 0x0f, rev & 0x0f); + + called = true; +} + +/* This lock class tells lockdep that GPIO irqs are in a different + * category than their parents, so it won't report false recursion. + */ +static struct lock_class_key gpio_lock_class; + +static void omap_gpio_mod_init(struct gpio_bank *bank) +{ + void __iomem *base = bank->base; + u32 l = 0xffffffff; + + if (bank->width == 16) + l = 0xffff; + + if (bank->is_mpuio) { + __raw_writel(l, bank->base + bank->regs->irqenable); + return; + } + + _gpio_rmw(base, bank->regs->irqenable, l, bank->regs->irqenable_inv); + _gpio_rmw(base, bank->regs->irqstatus, l, !bank->regs->irqenable_inv); + if (bank->regs->debounce_en) + __raw_writel(0, base + bank->regs->debounce_en); + + /* Save OE default value (0xffffffff) in the context */ + bank->context.oe = __raw_readl(bank->base + bank->regs->direction); + /* Initialize interface clk ungated, module enabled */ + if (bank->regs->ctrl) + __raw_writel(0, base + bank->regs->ctrl); +} + +static __devinit void +omap_mpuio_alloc_gc(struct gpio_bank *bank, unsigned int irq_start, + unsigned int num) +{ + struct irq_chip_generic *gc; + struct irq_chip_type *ct; + + gc = irq_alloc_generic_chip("MPUIO", 1, irq_start, bank->base, + handle_simple_irq); + if (!gc) { + dev_err(bank->dev, "Memory alloc failed for gc\n"); + return; + } + + ct = gc->chip_types; + + /* NOTE: No ack required, reading IRQ status clears it. */ + ct->chip.irq_mask = irq_gc_mask_set_bit; + ct->chip.irq_unmask = irq_gc_mask_clr_bit; + ct->chip.irq_set_type = gpio_irq_type; + + if (bank->regs->wkup_en) + ct->chip.irq_set_wake = gpio_wake_enable, + + ct->regs.mask = OMAP_MPUIO_GPIO_INT / bank->stride; + irq_setup_generic_chip(gc, IRQ_MSK(num), IRQ_GC_INIT_MASK_CACHE, + IRQ_NOREQUEST | IRQ_NOPROBE, 0); +} + +static void __devinit omap_gpio_chip_init(struct gpio_bank *bank) +{ + int j; + static int gpio; + + /* + * REVISIT eventually switch from OMAP-specific gpio structs + * over to the generic ones + */ + bank->chip.request = omap_gpio_request; + bank->chip.free = omap_gpio_free; + bank->chip.direction_input = gpio_input; + bank->chip.get = gpio_get; + bank->chip.direction_output = gpio_output; + bank->chip.set_debounce = gpio_debounce; + bank->chip.set = gpio_set; + bank->chip.to_irq = gpio_2irq; + if (bank->is_mpuio) { + bank->chip.label = "mpuio"; + if (bank->regs->wkup_en) + bank->chip.dev = &omap_mpuio_device.dev; + bank->chip.base = OMAP_MPUIO(0); + } else { + bank->chip.label = "gpio"; + bank->chip.base = gpio; + gpio += bank->width; + } + bank->chip.ngpio = bank->width; + + gpiochip_add(&bank->chip); + + for (j = bank->irq_base; j < bank->irq_base + bank->width; j++) { + irq_set_lockdep_class(j, &gpio_lock_class); + irq_set_chip_data(j, bank); + if (bank->is_mpuio) { + omap_mpuio_alloc_gc(bank, j, bank->width); + } else { + irq_set_chip(j, &gpio_irq_chip); + irq_set_handler(j, handle_simple_irq); + set_irq_flags(j, IRQF_VALID); + } + } + irq_set_chained_handler(bank->irq, gpio_irq_handler); + irq_set_handler_data(bank->irq, bank); +} + +static const struct of_device_id omap_gpio_match[]; + +static int __devinit omap_gpio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + const struct of_device_id *match; + struct omap_gpio_platform_data *pdata; + struct resource *res; + struct gpio_bank *bank; + int ret = 0; + + match = of_match_device(of_match_ptr(omap_gpio_match), dev); + + pdata = match ? match->data : dev->platform_data; + if (!pdata) + return -EINVAL; + + bank = devm_kzalloc(&pdev->dev, sizeof(struct gpio_bank), GFP_KERNEL); + if (!bank) { + dev_err(dev, "Memory alloc failed\n"); + return -ENOMEM; + } + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (unlikely(!res)) { + dev_err(dev, "Invalid IRQ resource\n"); + return -ENODEV; + } + + bank->irq = res->start; + bank->dev = dev; + bank->dbck_flag = pdata->dbck_flag; + bank->stride = pdata->bank_stride; + bank->width = pdata->bank_width; + bank->is_mpuio = pdata->is_mpuio; + bank->non_wakeup_gpios = pdata->non_wakeup_gpios; + bank->loses_context = pdata->loses_context; + bank->get_context_loss_count = pdata->get_context_loss_count; + bank->regs = pdata->regs; +#ifdef CONFIG_OF_GPIO + bank->chip.of_node = of_node_get(node); +#endif + + bank->irq_base = irq_alloc_descs(-1, 0, bank->width, 0); + if (bank->irq_base < 0) { + dev_err(dev, "Couldn't allocate IRQ numbers\n"); + return -ENODEV; + } + + bank->domain = irq_domain_add_legacy(node, bank->width, bank->irq_base, + 0, &irq_domain_simple_ops, NULL); + + if (bank->regs->set_dataout && bank->regs->clr_dataout) + bank->set_dataout = _set_gpio_dataout_reg; + else + bank->set_dataout = _set_gpio_dataout_mask; + + spin_lock_init(&bank->lock); + + /* Static mapping, never released */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (unlikely(!res)) { + dev_err(dev, "Invalid mem resource\n"); + return -ENODEV; + } + + if (!devm_request_mem_region(dev, res->start, resource_size(res), + pdev->name)) { + dev_err(dev, "Region already claimed\n"); + return -EBUSY; + } + + bank->base = devm_ioremap(dev, res->start, resource_size(res)); + if (!bank->base) { + dev_err(dev, "Could not ioremap\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, bank); + + pm_runtime_enable(bank->dev); + pm_runtime_irq_safe(bank->dev); + pm_runtime_get_sync(bank->dev); + + if (bank->is_mpuio) + mpuio_init(bank); + + omap_gpio_mod_init(bank); + omap_gpio_chip_init(bank); + omap_gpio_show_rev(bank); + + pm_runtime_put(bank->dev); + + list_add_tail(&bank->node, &omap_gpio_list); + + return ret; +} + +#ifdef CONFIG_ARCH_OMAP2PLUS + +#if defined(CONFIG_PM_SLEEP) +static int omap_gpio_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct gpio_bank *bank = platform_get_drvdata(pdev); + void __iomem *base = bank->base; + void __iomem *wakeup_enable; + unsigned long flags; + + if (!bank->mod_usage || !bank->loses_context) + return 0; + + if (!bank->regs->wkup_en || !bank->suspend_wakeup) + return 0; + + wakeup_enable = bank->base + bank->regs->wkup_en; + + spin_lock_irqsave(&bank->lock, flags); + bank->saved_wakeup = __raw_readl(wakeup_enable); + _gpio_rmw(base, bank->regs->wkup_en, 0xffffffff, 0); + _gpio_rmw(base, bank->regs->wkup_en, bank->suspend_wakeup, 1); + spin_unlock_irqrestore(&bank->lock, flags); + + return 0; +} + +static int omap_gpio_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct gpio_bank *bank = platform_get_drvdata(pdev); + void __iomem *base = bank->base; + unsigned long flags; + + if (!bank->mod_usage || !bank->loses_context) + return 0; + + if (!bank->regs->wkup_en || !bank->saved_wakeup) + return 0; + + spin_lock_irqsave(&bank->lock, flags); + _gpio_rmw(base, bank->regs->wkup_en, 0xffffffff, 0); + _gpio_rmw(base, bank->regs->wkup_en, bank->saved_wakeup, 1); + spin_unlock_irqrestore(&bank->lock, flags); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +#if defined(CONFIG_PM_RUNTIME) +static void omap_gpio_restore_context(struct gpio_bank *bank); + +static int omap_gpio_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct gpio_bank *bank = platform_get_drvdata(pdev); + u32 l1 = 0, l2 = 0; + unsigned long flags; + u32 wake_low, wake_hi; + + spin_lock_irqsave(&bank->lock, flags); + + /* + * Only edges can generate a wakeup event to the PRCM. + * + * Therefore, ensure any wake-up capable GPIOs have + * edge-detection enabled before going idle to ensure a wakeup + * to the PRCM is generated on a GPIO transition. (c.f. 34xx + * NDA TRM 25.5.3.1) + * + * The normal values will be restored upon ->runtime_resume() + * by writing back the values saved in bank->context. + */ + wake_low = bank->context.leveldetect0 & bank->context.wake_en; + if (wake_low) + __raw_writel(wake_low | bank->context.fallingdetect, + bank->base + bank->regs->fallingdetect); + wake_hi = bank->context.leveldetect1 & bank->context.wake_en; + if (wake_hi) + __raw_writel(wake_hi | bank->context.risingdetect, + bank->base + bank->regs->risingdetect); + + if (bank->power_mode != OFF_MODE) { + bank->power_mode = 0; + goto update_gpio_context_count; + } + /* + * If going to OFF, remove triggering for all + * non-wakeup GPIOs. Otherwise spurious IRQs will be + * generated. See OMAP2420 Errata item 1.101. + */ + bank->saved_datain = __raw_readl(bank->base + + bank->regs->datain); + l1 = __raw_readl(bank->base + bank->regs->fallingdetect); + l2 = __raw_readl(bank->base + bank->regs->risingdetect); + + bank->saved_fallingdetect = l1; + bank->saved_risingdetect = l2; + l1 &= ~bank->enabled_non_wakeup_gpios; + l2 &= ~bank->enabled_non_wakeup_gpios; + + __raw_writel(l1, bank->base + bank->regs->fallingdetect); + __raw_writel(l2, bank->base + bank->regs->risingdetect); + + bank->workaround_enabled = true; + +update_gpio_context_count: + if (bank->get_context_loss_count) + bank->context_loss_count = + bank->get_context_loss_count(bank->dev); + + _gpio_dbck_disable(bank); + spin_unlock_irqrestore(&bank->lock, flags); + + return 0; +} + +static int omap_gpio_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct gpio_bank *bank = platform_get_drvdata(pdev); + int context_lost_cnt_after; + u32 l = 0, gen, gen0, gen1; + unsigned long flags; + + spin_lock_irqsave(&bank->lock, flags); + _gpio_dbck_enable(bank); + + /* + * In ->runtime_suspend(), level-triggered, wakeup-enabled + * GPIOs were set to edge trigger also in order to be able to + * generate a PRCM wakeup. Here we restore the + * pre-runtime_suspend() values for edge triggering. + */ + __raw_writel(bank->context.fallingdetect, + bank->base + bank->regs->fallingdetect); + __raw_writel(bank->context.risingdetect, + bank->base + bank->regs->risingdetect); + + if (!bank->workaround_enabled) { + spin_unlock_irqrestore(&bank->lock, flags); + return 0; + } + + if (bank->get_context_loss_count) { + context_lost_cnt_after = + bank->get_context_loss_count(bank->dev); + if (context_lost_cnt_after != bank->context_loss_count || + !context_lost_cnt_after) { + omap_gpio_restore_context(bank); + } else { + spin_unlock_irqrestore(&bank->lock, flags); + return 0; + } + } + + __raw_writel(bank->saved_fallingdetect, + bank->base + bank->regs->fallingdetect); + __raw_writel(bank->saved_risingdetect, + bank->base + bank->regs->risingdetect); + l = __raw_readl(bank->base + bank->regs->datain); + + /* + * Check if any of the non-wakeup interrupt GPIOs have changed + * state. If so, generate an IRQ by software. This is + * horribly racy, but it's the best we can do to work around + * this silicon bug. + */ + l ^= bank->saved_datain; + l &= bank->enabled_non_wakeup_gpios; + + /* + * No need to generate IRQs for the rising edge for gpio IRQs + * configured with falling edge only; and vice versa. + */ + gen0 = l & bank->saved_fallingdetect; + gen0 &= bank->saved_datain; + + gen1 = l & bank->saved_risingdetect; + gen1 &= ~(bank->saved_datain); + + /* FIXME: Consider GPIO IRQs with level detections properly! */ + gen = l & (~(bank->saved_fallingdetect) & ~(bank->saved_risingdetect)); + /* Consider all GPIO IRQs needed to be updated */ + gen |= gen0 | gen1; + + if (gen) { + u32 old0, old1; + + old0 = __raw_readl(bank->base + bank->regs->leveldetect0); + old1 = __raw_readl(bank->base + bank->regs->leveldetect1); + + if (cpu_is_omap24xx() || cpu_is_omap34xx()) { + __raw_writel(old0 | gen, bank->base + + bank->regs->leveldetect0); + __raw_writel(old1 | gen, bank->base + + bank->regs->leveldetect1); + } + + if (cpu_is_omap44xx()) { + __raw_writel(old0 | l, bank->base + + bank->regs->leveldetect0); + __raw_writel(old1 | l, bank->base + + bank->regs->leveldetect1); + } + __raw_writel(old0, bank->base + bank->regs->leveldetect0); + __raw_writel(old1, bank->base + bank->regs->leveldetect1); + } + + bank->workaround_enabled = false; + spin_unlock_irqrestore(&bank->lock, flags); + + return 0; +} +#endif /* CONFIG_PM_RUNTIME */ + +void omap2_gpio_prepare_for_idle(int pwr_mode) +{ + struct gpio_bank *bank; + + list_for_each_entry(bank, &omap_gpio_list, node) { + if (!bank->mod_usage || !bank->loses_context) + continue; + + bank->power_mode = pwr_mode; + + pm_runtime_put_sync_suspend(bank->dev); + } +} + +void omap2_gpio_resume_after_idle(void) +{ + struct gpio_bank *bank; + + list_for_each_entry(bank, &omap_gpio_list, node) { + if (!bank->mod_usage || !bank->loses_context) + continue; + + pm_runtime_get_sync(bank->dev); + } +} + +#if defined(CONFIG_PM_RUNTIME) +static void omap_gpio_restore_context(struct gpio_bank *bank) +{ + __raw_writel(bank->context.wake_en, + bank->base + bank->regs->wkup_en); + __raw_writel(bank->context.ctrl, bank->base + bank->regs->ctrl); + __raw_writel(bank->context.leveldetect0, + bank->base + bank->regs->leveldetect0); + __raw_writel(bank->context.leveldetect1, + bank->base + bank->regs->leveldetect1); + __raw_writel(bank->context.risingdetect, + bank->base + bank->regs->risingdetect); + __raw_writel(bank->context.fallingdetect, + bank->base + bank->regs->fallingdetect); + if (bank->regs->set_dataout && bank->regs->clr_dataout) + __raw_writel(bank->context.dataout, + bank->base + bank->regs->set_dataout); + else + __raw_writel(bank->context.dataout, + bank->base + bank->regs->dataout); + __raw_writel(bank->context.oe, bank->base + bank->regs->direction); + + if (bank->dbck_enable_mask) { + __raw_writel(bank->context.debounce, bank->base + + bank->regs->debounce); + __raw_writel(bank->context.debounce_en, + bank->base + bank->regs->debounce_en); + } + + __raw_writel(bank->context.irqenable1, + bank->base + bank->regs->irqenable); + __raw_writel(bank->context.irqenable2, + bank->base + bank->regs->irqenable2); +} +#endif /* CONFIG_PM_RUNTIME */ +#else +#define omap_gpio_suspend NULL +#define omap_gpio_resume NULL +#define omap_gpio_runtime_suspend NULL +#define omap_gpio_runtime_resume NULL +#endif + +static const struct dev_pm_ops gpio_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(omap_gpio_suspend, omap_gpio_resume) + SET_RUNTIME_PM_OPS(omap_gpio_runtime_suspend, omap_gpio_runtime_resume, + NULL) +}; + +#if defined(CONFIG_OF) +static struct omap_gpio_reg_offs omap2_gpio_regs = { + .revision = OMAP24XX_GPIO_REVISION, + .direction = OMAP24XX_GPIO_OE, + .datain = OMAP24XX_GPIO_DATAIN, + .dataout = OMAP24XX_GPIO_DATAOUT, + .set_dataout = OMAP24XX_GPIO_SETDATAOUT, + .clr_dataout = OMAP24XX_GPIO_CLEARDATAOUT, + .irqstatus = OMAP24XX_GPIO_IRQSTATUS1, + .irqstatus2 = OMAP24XX_GPIO_IRQSTATUS2, + .irqenable = OMAP24XX_GPIO_IRQENABLE1, + .irqenable2 = OMAP24XX_GPIO_IRQENABLE2, + .set_irqenable = OMAP24XX_GPIO_SETIRQENABLE1, + .clr_irqenable = OMAP24XX_GPIO_CLEARIRQENABLE1, + .debounce = OMAP24XX_GPIO_DEBOUNCE_VAL, + .debounce_en = OMAP24XX_GPIO_DEBOUNCE_EN, + .ctrl = OMAP24XX_GPIO_CTRL, + .wkup_en = OMAP24XX_GPIO_WAKE_EN, + .leveldetect0 = OMAP24XX_GPIO_LEVELDETECT0, + .leveldetect1 = OMAP24XX_GPIO_LEVELDETECT1, + .risingdetect = OMAP24XX_GPIO_RISINGDETECT, + .fallingdetect = OMAP24XX_GPIO_FALLINGDETECT, +}; + +static struct omap_gpio_reg_offs omap4_gpio_regs = { + .revision = OMAP4_GPIO_REVISION, + .direction = OMAP4_GPIO_OE, + .datain = OMAP4_GPIO_DATAIN, + .dataout = OMAP4_GPIO_DATAOUT, + .set_dataout = OMAP4_GPIO_SETDATAOUT, + .clr_dataout = OMAP4_GPIO_CLEARDATAOUT, + .irqstatus = OMAP4_GPIO_IRQSTATUS0, + .irqstatus2 = OMAP4_GPIO_IRQSTATUS1, + .irqenable = OMAP4_GPIO_IRQSTATUSSET0, + .irqenable2 = OMAP4_GPIO_IRQSTATUSSET1, + .set_irqenable = OMAP4_GPIO_IRQSTATUSSET0, + .clr_irqenable = OMAP4_GPIO_IRQSTATUSCLR0, + .debounce = OMAP4_GPIO_DEBOUNCINGTIME, + .debounce_en = OMAP4_GPIO_DEBOUNCENABLE, + .ctrl = OMAP4_GPIO_CTRL, + .wkup_en = OMAP4_GPIO_IRQWAKEN0, + .leveldetect0 = OMAP4_GPIO_LEVELDETECT0, + .leveldetect1 = OMAP4_GPIO_LEVELDETECT1, + .risingdetect = OMAP4_GPIO_RISINGDETECT, + .fallingdetect = OMAP4_GPIO_FALLINGDETECT, +}; + +static struct omap_gpio_platform_data omap2_pdata = { + .regs = &omap2_gpio_regs, + .bank_width = 32, + .dbck_flag = false, +}; + +static struct omap_gpio_platform_data omap3_pdata = { + .regs = &omap2_gpio_regs, + .bank_width = 32, + .dbck_flag = true, +}; + +static struct omap_gpio_platform_data omap4_pdata = { + .regs = &omap4_gpio_regs, + .bank_width = 32, + .dbck_flag = true, +}; + +static const struct of_device_id omap_gpio_match[] = { + { + .compatible = "ti,omap4-gpio", + .data = &omap4_pdata, + }, + { + .compatible = "ti,omap3-gpio", + .data = &omap3_pdata, + }, + { + .compatible = "ti,omap2-gpio", + .data = &omap2_pdata, + }, + { }, +}; +MODULE_DEVICE_TABLE(of, omap_gpio_match); +#endif + +static struct platform_driver omap_gpio_driver = { + .probe = omap_gpio_probe, + .driver = { + .name = "omap_gpio", + .pm = &gpio_pm_ops, + .of_match_table = of_match_ptr(omap_gpio_match), + }, +}; + +/* + * gpio driver register needs to be done before + * machine_init functions access gpio APIs. + * Hence omap_gpio_drv_reg() is a postcore_initcall. + */ +static int __init omap_gpio_drv_reg(void) +{ + return platform_driver_register(&omap_gpio_driver); +} +postcore_initcall(omap_gpio_drv_reg); diff --git a/drivers/gpio/gpio-pca953x.c b/drivers/gpio/gpio-pca953x.c new file mode 100644 index 00000000..d3f3e8f5 --- /dev/null +++ b/drivers/gpio/gpio-pca953x.c @@ -0,0 +1,758 @@ +/* + * PCA953x 4/8/16 bit I/O ports + * + * Copyright (C) 2005 Ben Gardner <bgardner@wabtec.com> + * Copyright (C) 2007 Marvell International Ltd. + * + * Derived from drivers/i2c/chips/pca9539.c + * + * 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. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/i2c.h> +#include <linux/i2c/pca953x.h> +#include <linux/slab.h> +#ifdef CONFIG_OF_GPIO +#include <linux/of_platform.h> +#endif + +#define PCA953X_INPUT 0 +#define PCA953X_OUTPUT 1 +#define PCA953X_INVERT 2 +#define PCA953X_DIRECTION 3 + +#define PCA957X_IN 0 +#define PCA957X_INVRT 1 +#define PCA957X_BKEN 2 +#define PCA957X_PUPD 3 +#define PCA957X_CFG 4 +#define PCA957X_OUT 5 +#define PCA957X_MSK 6 +#define PCA957X_INTS 7 + +#define PCA_GPIO_MASK 0x00FF +#define PCA_INT 0x0100 +#define PCA953X_TYPE 0x1000 +#define PCA957X_TYPE 0x2000 + +static const struct i2c_device_id pca953x_id[] = { + { "pca9534", 8 | PCA953X_TYPE | PCA_INT, }, + { "pca9535", 16 | PCA953X_TYPE | PCA_INT, }, + { "pca9536", 4 | PCA953X_TYPE, }, + { "pca9537", 4 | PCA953X_TYPE | PCA_INT, }, + { "pca9538", 8 | PCA953X_TYPE | PCA_INT, }, + { "pca9539", 16 | PCA953X_TYPE | PCA_INT, }, + { "pca9554", 8 | PCA953X_TYPE | PCA_INT, }, + { "pca9555", 16 | PCA953X_TYPE | PCA_INT, }, + { "pca9556", 8 | PCA953X_TYPE, }, + { "pca9557", 8 | PCA953X_TYPE, }, + { "pca9574", 8 | PCA957X_TYPE | PCA_INT, }, + { "pca9575", 16 | PCA957X_TYPE | PCA_INT, }, + + { "max7310", 8 | PCA953X_TYPE, }, + { "max7312", 16 | PCA953X_TYPE | PCA_INT, }, + { "max7313", 16 | PCA953X_TYPE | PCA_INT, }, + { "max7315", 8 | PCA953X_TYPE | PCA_INT, }, + { "pca6107", 8 | PCA953X_TYPE | PCA_INT, }, + { "tca6408", 8 | PCA953X_TYPE | PCA_INT, }, + { "tca6416", 16 | PCA953X_TYPE | PCA_INT, }, + /* NYET: { "tca6424", 24, }, */ + { } +}; +MODULE_DEVICE_TABLE(i2c, pca953x_id); + +struct pca953x_chip { + unsigned gpio_start; + uint16_t reg_output; + uint16_t reg_direction; + struct mutex i2c_lock; + +#ifdef CONFIG_GPIO_PCA953X_IRQ + struct mutex irq_lock; + uint16_t irq_mask; + uint16_t irq_stat; + uint16_t irq_trig_raise; + uint16_t irq_trig_fall; + int irq_base; +#endif + + struct i2c_client *client; + struct gpio_chip gpio_chip; + const char *const *names; + int chip_type; +}; + +static int pca953x_write_reg(struct pca953x_chip *chip, int reg, uint16_t val) +{ + int ret = 0; + + if (chip->gpio_chip.ngpio <= 8) + ret = i2c_smbus_write_byte_data(chip->client, reg, val); + else { + switch (chip->chip_type) { + case PCA953X_TYPE: + ret = i2c_smbus_write_word_data(chip->client, + reg << 1, val); + break; + case PCA957X_TYPE: + ret = i2c_smbus_write_byte_data(chip->client, reg << 1, + val & 0xff); + if (ret < 0) + break; + ret = i2c_smbus_write_byte_data(chip->client, + (reg << 1) + 1, + (val & 0xff00) >> 8); + break; + } + } + + if (ret < 0) { + dev_err(&chip->client->dev, "failed writing register\n"); + return ret; + } + + return 0; +} + +static int pca953x_read_reg(struct pca953x_chip *chip, int reg, uint16_t *val) +{ + int ret; + + if (chip->gpio_chip.ngpio <= 8) + ret = i2c_smbus_read_byte_data(chip->client, reg); + else + ret = i2c_smbus_read_word_data(chip->client, reg << 1); + + if (ret < 0) { + dev_err(&chip->client->dev, "failed reading register\n"); + return ret; + } + + *val = (uint16_t)ret; + return 0; +} + +static int pca953x_gpio_direction_input(struct gpio_chip *gc, unsigned off) +{ + struct pca953x_chip *chip; + uint16_t reg_val; + int ret, offset = 0; + + chip = container_of(gc, struct pca953x_chip, gpio_chip); + + mutex_lock(&chip->i2c_lock); + reg_val = chip->reg_direction | (1u << off); + + switch (chip->chip_type) { + case PCA953X_TYPE: + offset = PCA953X_DIRECTION; + break; + case PCA957X_TYPE: + offset = PCA957X_CFG; + break; + } + ret = pca953x_write_reg(chip, offset, reg_val); + if (ret) + goto exit; + + chip->reg_direction = reg_val; + ret = 0; +exit: + mutex_unlock(&chip->i2c_lock); + return ret; +} + +static int pca953x_gpio_direction_output(struct gpio_chip *gc, + unsigned off, int val) +{ + struct pca953x_chip *chip; + uint16_t reg_val; + int ret, offset = 0; + + chip = container_of(gc, struct pca953x_chip, gpio_chip); + + mutex_lock(&chip->i2c_lock); + /* set output level */ + if (val) + reg_val = chip->reg_output | (1u << off); + else + reg_val = chip->reg_output & ~(1u << off); + + switch (chip->chip_type) { + case PCA953X_TYPE: + offset = PCA953X_OUTPUT; + break; + case PCA957X_TYPE: + offset = PCA957X_OUT; + break; + } + ret = pca953x_write_reg(chip, offset, reg_val); + if (ret) + goto exit; + + chip->reg_output = reg_val; + + /* then direction */ + reg_val = chip->reg_direction & ~(1u << off); + switch (chip->chip_type) { + case PCA953X_TYPE: + offset = PCA953X_DIRECTION; + break; + case PCA957X_TYPE: + offset = PCA957X_CFG; + break; + } + ret = pca953x_write_reg(chip, offset, reg_val); + if (ret) + goto exit; + + chip->reg_direction = reg_val; + ret = 0; +exit: + mutex_unlock(&chip->i2c_lock); + return ret; +} + +static int pca953x_gpio_get_value(struct gpio_chip *gc, unsigned off) +{ + struct pca953x_chip *chip; + uint16_t reg_val; + int ret, offset = 0; + + chip = container_of(gc, struct pca953x_chip, gpio_chip); + + mutex_lock(&chip->i2c_lock); + switch (chip->chip_type) { + case PCA953X_TYPE: + offset = PCA953X_INPUT; + break; + case PCA957X_TYPE: + offset = PCA957X_IN; + break; + } + ret = pca953x_read_reg(chip, offset, ®_val); + mutex_unlock(&chip->i2c_lock); + if (ret < 0) { + /* NOTE: diagnostic already emitted; that's all we should + * do unless gpio_*_value_cansleep() calls become different + * from their nonsleeping siblings (and report faults). + */ + return 0; + } + + return (reg_val & (1u << off)) ? 1 : 0; +} + +static void pca953x_gpio_set_value(struct gpio_chip *gc, unsigned off, int val) +{ + struct pca953x_chip *chip; + uint16_t reg_val; + int ret, offset = 0; + + chip = container_of(gc, struct pca953x_chip, gpio_chip); + + mutex_lock(&chip->i2c_lock); + if (val) + reg_val = chip->reg_output | (1u << off); + else + reg_val = chip->reg_output & ~(1u << off); + + switch (chip->chip_type) { + case PCA953X_TYPE: + offset = PCA953X_OUTPUT; + break; + case PCA957X_TYPE: + offset = PCA957X_OUT; + break; + } + ret = pca953x_write_reg(chip, offset, reg_val); + if (ret) + goto exit; + + chip->reg_output = reg_val; +exit: + mutex_unlock(&chip->i2c_lock); +} + +static void pca953x_setup_gpio(struct pca953x_chip *chip, int gpios) +{ + struct gpio_chip *gc; + + gc = &chip->gpio_chip; + + gc->direction_input = pca953x_gpio_direction_input; + gc->direction_output = pca953x_gpio_direction_output; + gc->get = pca953x_gpio_get_value; + gc->set = pca953x_gpio_set_value; + gc->can_sleep = 1; + + gc->base = chip->gpio_start; + gc->ngpio = gpios; + gc->label = chip->client->name; + gc->dev = &chip->client->dev; + gc->owner = THIS_MODULE; + gc->names = chip->names; +} + +#ifdef CONFIG_GPIO_PCA953X_IRQ +static int pca953x_gpio_to_irq(struct gpio_chip *gc, unsigned off) +{ + struct pca953x_chip *chip; + + chip = container_of(gc, struct pca953x_chip, gpio_chip); + return chip->irq_base + off; +} + +static void pca953x_irq_mask(struct irq_data *d) +{ + struct pca953x_chip *chip = irq_data_get_irq_chip_data(d); + + chip->irq_mask &= ~(1 << (d->irq - chip->irq_base)); +} + +static void pca953x_irq_unmask(struct irq_data *d) +{ + struct pca953x_chip *chip = irq_data_get_irq_chip_data(d); + + chip->irq_mask |= 1 << (d->irq - chip->irq_base); +} + +static void pca953x_irq_bus_lock(struct irq_data *d) +{ + struct pca953x_chip *chip = irq_data_get_irq_chip_data(d); + + mutex_lock(&chip->irq_lock); +} + +static void pca953x_irq_bus_sync_unlock(struct irq_data *d) +{ + struct pca953x_chip *chip = irq_data_get_irq_chip_data(d); + uint16_t new_irqs; + uint16_t level; + + /* Look for any newly setup interrupt */ + new_irqs = chip->irq_trig_fall | chip->irq_trig_raise; + new_irqs &= ~chip->reg_direction; + + while (new_irqs) { + level = __ffs(new_irqs); + pca953x_gpio_direction_input(&chip->gpio_chip, level); + new_irqs &= ~(1 << level); + } + + mutex_unlock(&chip->irq_lock); +} + +static int pca953x_irq_set_type(struct irq_data *d, unsigned int type) +{ + struct pca953x_chip *chip = irq_data_get_irq_chip_data(d); + uint16_t level = d->irq - chip->irq_base; + uint16_t mask = 1 << level; + + if (!(type & IRQ_TYPE_EDGE_BOTH)) { + dev_err(&chip->client->dev, "irq %d: unsupported type %d\n", + d->irq, type); + return -EINVAL; + } + + if (type & IRQ_TYPE_EDGE_FALLING) + chip->irq_trig_fall |= mask; + else + chip->irq_trig_fall &= ~mask; + + if (type & IRQ_TYPE_EDGE_RISING) + chip->irq_trig_raise |= mask; + else + chip->irq_trig_raise &= ~mask; + + return 0; +} + +static struct irq_chip pca953x_irq_chip = { + .name = "pca953x", + .irq_mask = pca953x_irq_mask, + .irq_unmask = pca953x_irq_unmask, + .irq_bus_lock = pca953x_irq_bus_lock, + .irq_bus_sync_unlock = pca953x_irq_bus_sync_unlock, + .irq_set_type = pca953x_irq_set_type, +}; + +static uint16_t pca953x_irq_pending(struct pca953x_chip *chip) +{ + uint16_t cur_stat; + uint16_t old_stat; + uint16_t pending; + uint16_t trigger; + int ret, offset = 0; + + switch (chip->chip_type) { + case PCA953X_TYPE: + offset = PCA953X_INPUT; + break; + case PCA957X_TYPE: + offset = PCA957X_IN; + break; + } + ret = pca953x_read_reg(chip, offset, &cur_stat); + if (ret) + return 0; + + /* Remove output pins from the equation */ + cur_stat &= chip->reg_direction; + + old_stat = chip->irq_stat; + trigger = (cur_stat ^ old_stat) & chip->irq_mask; + + if (!trigger) + return 0; + + chip->irq_stat = cur_stat; + + pending = (old_stat & chip->irq_trig_fall) | + (cur_stat & chip->irq_trig_raise); + pending &= trigger; + + return pending; +} + +static irqreturn_t pca953x_irq_handler(int irq, void *devid) +{ + struct pca953x_chip *chip = devid; + uint16_t pending; + uint16_t level; + + pending = pca953x_irq_pending(chip); + + if (!pending) + return IRQ_HANDLED; + + do { + level = __ffs(pending); + handle_nested_irq(level + chip->irq_base); + + pending &= ~(1 << level); + } while (pending); + + return IRQ_HANDLED; +} + +static int pca953x_irq_setup(struct pca953x_chip *chip, + const struct i2c_device_id *id, + int irq_base) +{ + struct i2c_client *client = chip->client; + int ret, offset = 0; + + if (irq_base != -1 + && (id->driver_data & PCA_INT)) { + int lvl; + + switch (chip->chip_type) { + case PCA953X_TYPE: + offset = PCA953X_INPUT; + break; + case PCA957X_TYPE: + offset = PCA957X_IN; + break; + } + ret = pca953x_read_reg(chip, offset, &chip->irq_stat); + if (ret) + goto out_failed; + + /* + * There is no way to know which GPIO line generated the + * interrupt. We have to rely on the previous read for + * this purpose. + */ + chip->irq_stat &= chip->reg_direction; + mutex_init(&chip->irq_lock); + + chip->irq_base = irq_alloc_descs(-1, irq_base, chip->gpio_chip.ngpio, -1); + if (chip->irq_base < 0) + goto out_failed; + + for (lvl = 0; lvl < chip->gpio_chip.ngpio; lvl++) { + int irq = lvl + chip->irq_base; + + irq_clear_status_flags(irq, IRQ_NOREQUEST); + irq_set_chip_data(irq, chip); + irq_set_chip(irq, &pca953x_irq_chip); + irq_set_nested_thread(irq, true); +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID); +#else + irq_set_noprobe(irq); +#endif + } + + ret = request_threaded_irq(client->irq, + NULL, + pca953x_irq_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + dev_name(&client->dev), chip); + if (ret) { + dev_err(&client->dev, "failed to request irq %d\n", + client->irq); + goto out_failed; + } + + chip->gpio_chip.to_irq = pca953x_gpio_to_irq; + } + + return 0; + +out_failed: + chip->irq_base = -1; + return ret; +} + +static void pca953x_irq_teardown(struct pca953x_chip *chip) +{ + if (chip->irq_base != -1) { + irq_free_descs(chip->irq_base, chip->gpio_chip.ngpio); + free_irq(chip->client->irq, chip); + } +} +#else /* CONFIG_GPIO_PCA953X_IRQ */ +static int pca953x_irq_setup(struct pca953x_chip *chip, + const struct i2c_device_id *id, + int irq_base) +{ + struct i2c_client *client = chip->client; + + if (irq_base != -1 && (id->driver_data & PCA_INT)) + dev_warn(&client->dev, "interrupt support not compiled in\n"); + + return 0; +} + +static void pca953x_irq_teardown(struct pca953x_chip *chip) +{ +} +#endif + +/* + * Handlers for alternative sources of platform_data + */ +#ifdef CONFIG_OF_GPIO +/* + * Translate OpenFirmware node properties into platform_data + * WARNING: This is DEPRECATED and will be removed eventually! + */ +static void +pca953x_get_alt_pdata(struct i2c_client *client, int *gpio_base, int *invert) +{ + struct device_node *node; + const __be32 *val; + int size; + + node = client->dev.of_node; + if (node == NULL) + return; + + *gpio_base = -1; + val = of_get_property(node, "linux,gpio-base", &size); + WARN(val, "%s: device-tree property 'linux,gpio-base' is deprecated!", __func__); + if (val) { + if (size != sizeof(*val)) + dev_warn(&client->dev, "%s: wrong linux,gpio-base\n", + node->full_name); + else + *gpio_base = be32_to_cpup(val); + } + + val = of_get_property(node, "polarity", NULL); + WARN(val, "%s: device-tree property 'polarity' is deprecated!", __func__); + if (val) + *invert = *val; +} +#else +static void +pca953x_get_alt_pdata(struct i2c_client *client, int *gpio_base, int *invert) +{ + *gpio_base = -1; +} +#endif + +static int __devinit device_pca953x_init(struct pca953x_chip *chip, int invert) +{ + int ret; + + ret = pca953x_read_reg(chip, PCA953X_OUTPUT, &chip->reg_output); + if (ret) + goto out; + + ret = pca953x_read_reg(chip, PCA953X_DIRECTION, + &chip->reg_direction); + if (ret) + goto out; + + /* set platform specific polarity inversion */ + ret = pca953x_write_reg(chip, PCA953X_INVERT, invert); +out: + return ret; +} + +static int __devinit device_pca957x_init(struct pca953x_chip *chip, int invert) +{ + int ret; + uint16_t val = 0; + + /* Let every port in proper state, that could save power */ + pca953x_write_reg(chip, PCA957X_PUPD, 0x0); + pca953x_write_reg(chip, PCA957X_CFG, 0xffff); + pca953x_write_reg(chip, PCA957X_OUT, 0x0); + + ret = pca953x_read_reg(chip, PCA957X_IN, &val); + if (ret) + goto out; + ret = pca953x_read_reg(chip, PCA957X_OUT, &chip->reg_output); + if (ret) + goto out; + ret = pca953x_read_reg(chip, PCA957X_CFG, &chip->reg_direction); + if (ret) + goto out; + + /* set platform specific polarity inversion */ + pca953x_write_reg(chip, PCA957X_INVRT, invert); + + /* To enable register 6, 7 to controll pull up and pull down */ + pca953x_write_reg(chip, PCA957X_BKEN, 0x202); + + return 0; +out: + return ret; +} + +static int __devinit pca953x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct pca953x_platform_data *pdata; + struct pca953x_chip *chip; + int irq_base=0, invert=0; + int ret; + + chip = kzalloc(sizeof(struct pca953x_chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + + pdata = client->dev.platform_data; + if (pdata) { + irq_base = pdata->irq_base; + chip->gpio_start = pdata->gpio_base; + invert = pdata->invert; + chip->names = pdata->names; + } else { + pca953x_get_alt_pdata(client, &chip->gpio_start, &invert); +#ifdef CONFIG_OF_GPIO + /* If I2C node has no interrupts property, disable GPIO interrupts */ + if (of_find_property(client->dev.of_node, "interrupts", NULL) == NULL) + irq_base = -1; +#endif + } + + chip->client = client; + + chip->chip_type = id->driver_data & (PCA953X_TYPE | PCA957X_TYPE); + + mutex_init(&chip->i2c_lock); + + /* initialize cached registers from their original values. + * we can't share this chip with another i2c master. + */ + pca953x_setup_gpio(chip, id->driver_data & PCA_GPIO_MASK); + + if (chip->chip_type == PCA953X_TYPE) + ret = device_pca953x_init(chip, invert); + else + ret = device_pca957x_init(chip, invert); + if (ret) + goto out_failed; + + ret = pca953x_irq_setup(chip, id, irq_base); + if (ret) + goto out_failed; + + ret = gpiochip_add(&chip->gpio_chip); + if (ret) + goto out_failed_irq; + + if (pdata && pdata->setup) { + ret = pdata->setup(client, chip->gpio_chip.base, + chip->gpio_chip.ngpio, pdata->context); + if (ret < 0) + dev_warn(&client->dev, "setup failed, %d\n", ret); + } + + i2c_set_clientdata(client, chip); + return 0; + +out_failed_irq: + pca953x_irq_teardown(chip); +out_failed: + kfree(chip); + return ret; +} + +static int pca953x_remove(struct i2c_client *client) +{ + struct pca953x_platform_data *pdata = client->dev.platform_data; + struct pca953x_chip *chip = i2c_get_clientdata(client); + int ret = 0; + + if (pdata && pdata->teardown) { + ret = pdata->teardown(client, chip->gpio_chip.base, + chip->gpio_chip.ngpio, pdata->context); + if (ret < 0) { + dev_err(&client->dev, "%s failed, %d\n", + "teardown", ret); + return ret; + } + } + + ret = gpiochip_remove(&chip->gpio_chip); + if (ret) { + dev_err(&client->dev, "%s failed, %d\n", + "gpiochip_remove()", ret); + return ret; + } + + pca953x_irq_teardown(chip); + kfree(chip); + return 0; +} + +static struct i2c_driver pca953x_driver = { + .driver = { + .name = "pca953x", + }, + .probe = pca953x_probe, + .remove = pca953x_remove, + .id_table = pca953x_id, +}; + +static int __init pca953x_init(void) +{ + return i2c_add_driver(&pca953x_driver); +} +/* register after i2c postcore initcall and before + * subsys initcalls that may rely on these GPIOs + */ +subsys_initcall(pca953x_init); + +static void __exit pca953x_exit(void) +{ + i2c_del_driver(&pca953x_driver); +} +module_exit(pca953x_exit); + +MODULE_AUTHOR("eric miao <eric.miao@marvell.com>"); +MODULE_DESCRIPTION("GPIO expander driver for PCA953x"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpio/gpio-pcf857x.c b/drivers/gpio/gpio-pcf857x.c new file mode 100644 index 00000000..2d1de9e7 --- /dev/null +++ b/drivers/gpio/gpio-pcf857x.c @@ -0,0 +1,367 @@ +/* + * Driver for pcf857x, pca857x, and pca967x I2C GPIO expanders + * + * Copyright (C) 2007 David Brownell + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/i2c/pcf857x.h> +#include <linux/module.h> + + +static const struct i2c_device_id pcf857x_id[] = { + { "pcf8574", 8 }, + { "pcf8574a", 8 }, + { "pca8574", 8 }, + { "pca9670", 8 }, + { "pca9672", 8 }, + { "pca9674", 8 }, + { "pcf8575", 16 }, + { "pca8575", 16 }, + { "pca9671", 16 }, + { "pca9673", 16 }, + { "pca9675", 16 }, + { "max7328", 8 }, + { "max7329", 8 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pcf857x_id); + +/* + * The pcf857x, pca857x, and pca967x chips only expose one read and one + * write register. Writing a "one" bit (to match the reset state) lets + * that pin be used as an input; it's not an open-drain model, but acts + * a bit like one. This is described as "quasi-bidirectional"; read the + * chip documentation for details. + * + * Many other I2C GPIO expander chips (like the pca953x models) have + * more complex register models and more conventional circuitry using + * push/pull drivers. They often use the same 0x20..0x27 addresses as + * pcf857x parts, making the "legacy" I2C driver model problematic. + */ +struct pcf857x { + struct gpio_chip chip; + struct i2c_client *client; + struct mutex lock; /* protect 'out' */ + unsigned out; /* software latch */ +}; + +/*-------------------------------------------------------------------------*/ + +/* Talk to 8-bit I/O expander */ + +static int pcf857x_input8(struct gpio_chip *chip, unsigned offset) +{ + struct pcf857x *gpio = container_of(chip, struct pcf857x, chip); + int status; + + mutex_lock(&gpio->lock); + gpio->out |= (1 << offset); + status = i2c_smbus_write_byte(gpio->client, gpio->out); + mutex_unlock(&gpio->lock); + + return status; +} + +static int pcf857x_get8(struct gpio_chip *chip, unsigned offset) +{ + struct pcf857x *gpio = container_of(chip, struct pcf857x, chip); + s32 value; + + value = i2c_smbus_read_byte(gpio->client); + return (value < 0) ? 0 : (value & (1 << offset)); +} + +static int pcf857x_output8(struct gpio_chip *chip, unsigned offset, int value) +{ + struct pcf857x *gpio = container_of(chip, struct pcf857x, chip); + unsigned bit = 1 << offset; + int status; + + mutex_lock(&gpio->lock); + if (value) + gpio->out |= bit; + else + gpio->out &= ~bit; + status = i2c_smbus_write_byte(gpio->client, gpio->out); + mutex_unlock(&gpio->lock); + + return status; +} + +static void pcf857x_set8(struct gpio_chip *chip, unsigned offset, int value) +{ + pcf857x_output8(chip, offset, value); +} + +/*-------------------------------------------------------------------------*/ + +/* Talk to 16-bit I/O expander */ + +static int i2c_write_le16(struct i2c_client *client, u16 word) +{ + u8 buf[2] = { word & 0xff, word >> 8, }; + int status; + + status = i2c_master_send(client, buf, 2); + return (status < 0) ? status : 0; +} + +static int i2c_read_le16(struct i2c_client *client) +{ + u8 buf[2]; + int status; + + status = i2c_master_recv(client, buf, 2); + if (status < 0) + return status; + return (buf[1] << 8) | buf[0]; +} + +static int pcf857x_input16(struct gpio_chip *chip, unsigned offset) +{ + struct pcf857x *gpio = container_of(chip, struct pcf857x, chip); + int status; + + mutex_lock(&gpio->lock); + gpio->out |= (1 << offset); + status = i2c_write_le16(gpio->client, gpio->out); + mutex_unlock(&gpio->lock); + + return status; +} + +static int pcf857x_get16(struct gpio_chip *chip, unsigned offset) +{ + struct pcf857x *gpio = container_of(chip, struct pcf857x, chip); + int value; + + value = i2c_read_le16(gpio->client); + return (value < 0) ? 0 : (value & (1 << offset)); +} + +static int pcf857x_output16(struct gpio_chip *chip, unsigned offset, int value) +{ + struct pcf857x *gpio = container_of(chip, struct pcf857x, chip); + unsigned bit = 1 << offset; + int status; + + mutex_lock(&gpio->lock); + if (value) + gpio->out |= bit; + else + gpio->out &= ~bit; + status = i2c_write_le16(gpio->client, gpio->out); + mutex_unlock(&gpio->lock); + + return status; +} + +static void pcf857x_set16(struct gpio_chip *chip, unsigned offset, int value) +{ + pcf857x_output16(chip, offset, value); +} + +/*-------------------------------------------------------------------------*/ + +static int pcf857x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct pcf857x_platform_data *pdata; + struct pcf857x *gpio; + int status; + + pdata = client->dev.platform_data; + if (!pdata) { + dev_dbg(&client->dev, "no platform data\n"); + } + + /* Allocate, initialize, and register this gpio_chip. */ + gpio = kzalloc(sizeof *gpio, GFP_KERNEL); + if (!gpio) + return -ENOMEM; + + mutex_init(&gpio->lock); + + gpio->chip.base = pdata ? pdata->gpio_base : -1; + gpio->chip.can_sleep = 1; + gpio->chip.dev = &client->dev; + gpio->chip.owner = THIS_MODULE; + + /* NOTE: the OnSemi jlc1562b is also largely compatible with + * these parts, notably for output. It has a low-resolution + * DAC instead of pin change IRQs; and its inputs can be the + * result of comparators. + */ + + /* 8574 addresses are 0x20..0x27; 8574a uses 0x38..0x3f; + * 9670, 9672, 9764, and 9764a use quite a variety. + * + * NOTE: we don't distinguish here between *4 and *4a parts. + */ + gpio->chip.ngpio = id->driver_data; + if (gpio->chip.ngpio == 8) { + gpio->chip.direction_input = pcf857x_input8; + gpio->chip.get = pcf857x_get8; + gpio->chip.direction_output = pcf857x_output8; + gpio->chip.set = pcf857x_set8; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE)) + status = -EIO; + + /* fail if there's no chip present */ + else + status = i2c_smbus_read_byte(client); + + /* '75/'75c addresses are 0x20..0x27, just like the '74; + * the '75c doesn't have a current source pulling high. + * 9671, 9673, and 9765 use quite a variety of addresses. + * + * NOTE: we don't distinguish here between '75 and '75c parts. + */ + } else if (gpio->chip.ngpio == 16) { + gpio->chip.direction_input = pcf857x_input16; + gpio->chip.get = pcf857x_get16; + gpio->chip.direction_output = pcf857x_output16; + gpio->chip.set = pcf857x_set16; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + status = -EIO; + + /* fail if there's no chip present */ + else + status = i2c_read_le16(client); + + } else { + dev_dbg(&client->dev, "unsupported number of gpios\n"); + status = -EINVAL; + } + + if (status < 0) + goto fail; + + gpio->chip.label = client->name; + + gpio->client = client; + i2c_set_clientdata(client, gpio); + + /* NOTE: these chips have strange "quasi-bidirectional" I/O pins. + * We can't actually know whether a pin is configured (a) as output + * and driving the signal low, or (b) as input and reporting a low + * value ... without knowing the last value written since the chip + * came out of reset (if any). We can't read the latched output. + * + * In short, the only reliable solution for setting up pin direction + * is to do it explicitly. The setup() method can do that, but it + * may cause transient glitching since it can't know the last value + * written (some pins may need to be driven low). + * + * Using pdata->n_latch avoids that trouble. When left initialized + * to zero, our software copy of the "latch" then matches the chip's + * all-ones reset state. Otherwise it flags pins to be driven low. + */ + gpio->out = pdata ? ~pdata->n_latch : ~0; + + status = gpiochip_add(&gpio->chip); + if (status < 0) + goto fail; + + /* NOTE: these chips can issue "some pin-changed" IRQs, which we + * don't yet even try to use. Among other issues, the relevant + * genirq state isn't available to modular drivers; and most irq + * methods can't be called from sleeping contexts. + */ + + dev_info(&client->dev, "%s\n", + client->irq ? " (irq ignored)" : ""); + + /* Let platform code set up the GPIOs and their users. + * Now is the first time anyone could use them. + */ + if (pdata && pdata->setup) { + status = pdata->setup(client, + gpio->chip.base, gpio->chip.ngpio, + pdata->context); + if (status < 0) + dev_warn(&client->dev, "setup --> %d\n", status); + } + + return 0; + +fail: + dev_dbg(&client->dev, "probe error %d for '%s'\n", + status, client->name); + kfree(gpio); + return status; +} + +static int pcf857x_remove(struct i2c_client *client) +{ + struct pcf857x_platform_data *pdata = client->dev.platform_data; + struct pcf857x *gpio = i2c_get_clientdata(client); + int status = 0; + + if (pdata && pdata->teardown) { + status = pdata->teardown(client, + gpio->chip.base, gpio->chip.ngpio, + pdata->context); + if (status < 0) { + dev_err(&client->dev, "%s --> %d\n", + "teardown", status); + return status; + } + } + + status = gpiochip_remove(&gpio->chip); + if (status == 0) + kfree(gpio); + else + dev_err(&client->dev, "%s --> %d\n", "remove", status); + return status; +} + +static struct i2c_driver pcf857x_driver = { + .driver = { + .name = "pcf857x", + .owner = THIS_MODULE, + }, + .probe = pcf857x_probe, + .remove = pcf857x_remove, + .id_table = pcf857x_id, +}; + +static int __init pcf857x_init(void) +{ + return i2c_add_driver(&pcf857x_driver); +} +/* register after i2c postcore initcall and before + * subsys initcalls that may rely on these GPIOs + */ +subsys_initcall(pcf857x_init); + +static void __exit pcf857x_exit(void) +{ + i2c_del_driver(&pcf857x_driver); +} +module_exit(pcf857x_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Brownell"); diff --git a/drivers/gpio/gpio-pch.c b/drivers/gpio/gpio-pch.c new file mode 100644 index 00000000..2cd958e0 --- /dev/null +++ b/drivers/gpio/gpio-pch.c @@ -0,0 +1,554 @@ +/* + * Copyright (C) 2011 LAPIS 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 <linux/module.h> +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/irq.h> + +#define PCH_EDGE_FALLING 0 +#define PCH_EDGE_RISING BIT(0) +#define PCH_LEVEL_L BIT(1) +#define PCH_LEVEL_H (BIT(0) | BIT(1)) +#define PCH_EDGE_BOTH BIT(2) +#define PCH_IM_MASK (BIT(0) | BIT(1) | BIT(2)) + +#define PCH_IRQ_BASE 24 + +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[3]; + u32 gpio_use_sel; + u32 reset; +}; + +enum pch_type_t { + INTEL_EG20T_PCH, + OKISEMI_ML7223m_IOH, /* LAPIS Semiconductor ML7223 IOH PCIe Bus-m */ + OKISEMI_ML7223n_IOH /* LAPIS Semiconductor ML7223 IOH PCIe Bus-n */ +}; + +/* Specifies number of GPIO PINS */ +static int gpio_pins[] = { + [INTEL_EG20T_PCH] = 12, + [OKISEMI_ML7223m_IOH] = 8, + [OKISEMI_ML7223n_IOH] = 8, +}; + +/** + * struct pch_gpio_reg_data - The register store data. + * @ien_reg: To store contents of IEN register. + * @imask_reg: To store contents of IMASK register. + * @po_reg: To store contents of PO register. + * @pm_reg: To store contents of PM register. + * @im0_reg: To store contents of IM0 register. + * @im1_reg: To store contents of IM1 register. + * @gpio_use_sel_reg : To store contents of GPIO_USE_SEL register. + * (Only ML7223 Bus-n) + */ +struct pch_gpio_reg_data { + u32 ien_reg; + u32 imask_reg; + u32 po_reg; + u32 pm_reg; + u32 im0_reg; + u32 im1_reg; + u32 gpio_use_sel_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. + * @lock: Used for register access protection + * @irq_base: Save base of IRQ number for interrupt + * @ioh: IOH ID + * @spinlock: Used for register access protection in + * interrupt context pch_irq_mask, + * pch_irq_unmask and pch_irq_type; + */ +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; + int irq_base; + enum pch_type_t ioh; + spinlock_t spinlock; +}; + +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) & ((1 << gpio_pins[chip->ioh]) - 1); + 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); + iowrite32(reg_val, &chip->reg->po); + + 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) & ((1 << gpio_pins[chip->ioh]) - 1); + 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.ien_reg = ioread32(&chip->reg->ien); + chip->pch_gpio_reg.imask_reg = ioread32(&chip->reg->imask); + chip->pch_gpio_reg.po_reg = ioread32(&chip->reg->po); + chip->pch_gpio_reg.pm_reg = ioread32(&chip->reg->pm); + chip->pch_gpio_reg.im0_reg = ioread32(&chip->reg->im0); + if (chip->ioh == INTEL_EG20T_PCH) + chip->pch_gpio_reg.im1_reg = ioread32(&chip->reg->im1); + if (chip->ioh == OKISEMI_ML7223n_IOH) + chip->pch_gpio_reg.gpio_use_sel_reg =\ + ioread32(&chip->reg->gpio_use_sel); +} + +/* + * This function restores the register configuration of the GPIO device. + */ +static void pch_gpio_restore_reg_conf(struct pch_gpio *chip) +{ + iowrite32(chip->pch_gpio_reg.ien_reg, &chip->reg->ien); + iowrite32(chip->pch_gpio_reg.imask_reg, &chip->reg->imask); + /* 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); + iowrite32(chip->pch_gpio_reg.im0_reg, &chip->reg->im0); + if (chip->ioh == INTEL_EG20T_PCH) + iowrite32(chip->pch_gpio_reg.im1_reg, &chip->reg->im1); + if (chip->ioh == OKISEMI_ML7223n_IOH) + iowrite32(chip->pch_gpio_reg.gpio_use_sel_reg, + &chip->reg->gpio_use_sel); +} + +static int pch_gpio_to_irq(struct gpio_chip *gpio, unsigned offset) +{ + struct pch_gpio *chip = container_of(gpio, struct pch_gpio, gpio); + return chip->irq_base + offset; +} + +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_pins[chip->ioh]; + gpio->can_sleep = 0; + gpio->to_irq = pch_gpio_to_irq; +} + +static int pch_irq_type(struct irq_data *d, unsigned int type) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct pch_gpio *chip = gc->private; + u32 im, im_pos, val; + u32 __iomem *im_reg; + unsigned long flags; + int ch, irq = d->irq; + + ch = irq - chip->irq_base; + if (irq <= chip->irq_base + 7) { + im_reg = &chip->reg->im0; + im_pos = ch; + } else { + im_reg = &chip->reg->im1; + im_pos = ch - 8; + } + dev_dbg(chip->dev, "%s:irq=%d type=%d ch=%d pos=%d\n", + __func__, irq, type, ch, im_pos); + + spin_lock_irqsave(&chip->spinlock, flags); + + switch (type) { + case IRQ_TYPE_EDGE_RISING: + val = PCH_EDGE_RISING; + break; + case IRQ_TYPE_EDGE_FALLING: + val = PCH_EDGE_FALLING; + break; + case IRQ_TYPE_EDGE_BOTH: + val = PCH_EDGE_BOTH; + break; + case IRQ_TYPE_LEVEL_HIGH: + val = PCH_LEVEL_H; + break; + case IRQ_TYPE_LEVEL_LOW: + val = PCH_LEVEL_L; + break; + default: + goto unlock; + } + + /* Set interrupt mode */ + im = ioread32(im_reg) & ~(PCH_IM_MASK << (im_pos * 4)); + iowrite32(im | (val << (im_pos * 4)), im_reg); + + /* And the handler */ + if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH)) + __irq_set_handler_locked(d->irq, handle_level_irq); + else if (type & (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)) + __irq_set_handler_locked(d->irq, handle_edge_irq); + +unlock: + spin_unlock_irqrestore(&chip->spinlock, flags); + return 0; +} + +static void pch_irq_unmask(struct irq_data *d) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct pch_gpio *chip = gc->private; + + iowrite32(1 << (d->irq - chip->irq_base), &chip->reg->imaskclr); +} + +static void pch_irq_mask(struct irq_data *d) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct pch_gpio *chip = gc->private; + + iowrite32(1 << (d->irq - chip->irq_base), &chip->reg->imask); +} + +static void pch_irq_ack(struct irq_data *d) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct pch_gpio *chip = gc->private; + + iowrite32(1 << (d->irq - chip->irq_base), &chip->reg->iclr); +} + +static irqreturn_t pch_gpio_handler(int irq, void *dev_id) +{ + struct pch_gpio *chip = dev_id; + u32 reg_val = ioread32(&chip->reg->istatus); + int i, ret = IRQ_NONE; + + for (i = 0; i < gpio_pins[chip->ioh]; i++) { + if (reg_val & BIT(i)) { + dev_dbg(chip->dev, "%s:[%d]:irq=%d status=0x%x\n", + __func__, i, irq, reg_val); + generic_handle_irq(chip->irq_base + i); + ret = IRQ_HANDLED; + } + } + return ret; +} + +static __devinit void pch_gpio_alloc_generic_chip(struct pch_gpio *chip, + unsigned int irq_start, unsigned int num) +{ + struct irq_chip_generic *gc; + struct irq_chip_type *ct; + + gc = irq_alloc_generic_chip("pch_gpio", 1, irq_start, chip->base, + handle_simple_irq); + gc->private = chip; + ct = gc->chip_types; + + ct->chip.irq_ack = pch_irq_ack; + ct->chip.irq_mask = pch_irq_mask; + ct->chip.irq_unmask = pch_irq_unmask; + ct->chip.irq_set_type = pch_irq_type; + + irq_setup_generic_chip(gc, IRQ_MSK(num), IRQ_GC_INIT_MASK_CACHE, + IRQ_NOREQUEST | IRQ_NOPROBE, 0); +} + +static int __devinit pch_gpio_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + s32 ret; + struct pch_gpio *chip; + int irq_base; + u32 msk; + + 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) { + dev_err(&pdev->dev, "%s : pci_iomap FAILED", __func__); + ret = -ENOMEM; + goto err_iomap; + } + + if (pdev->device == 0x8803) + chip->ioh = INTEL_EG20T_PCH; + else if (pdev->device == 0x8014) + chip->ioh = OKISEMI_ML7223m_IOH; + else if (pdev->device == 0x8043) + chip->ioh = OKISEMI_ML7223n_IOH; + + chip->reg = chip->base; + pci_set_drvdata(pdev, chip); + mutex_init(&chip->lock); + spin_lock_init(&chip->spinlock); + 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; + } + + irq_base = irq_alloc_descs(-1, 0, gpio_pins[chip->ioh], NUMA_NO_NODE); + if (irq_base < 0) { + dev_warn(&pdev->dev, "PCH gpio: Failed to get IRQ base num\n"); + chip->irq_base = -1; + goto end; + } + chip->irq_base = irq_base; + + /* Mask all interrupts, but enable them */ + msk = (1 << gpio_pins[chip->ioh]) - 1; + iowrite32(msk, &chip->reg->imask); + iowrite32(msk, &chip->reg->ien); + + ret = request_irq(pdev->irq, pch_gpio_handler, + IRQF_SHARED, KBUILD_MODNAME, chip); + if (ret != 0) { + dev_err(&pdev->dev, + "%s request_irq failed\n", __func__); + goto err_request_irq; + } + + pch_gpio_alloc_generic_chip(chip, irq_base, gpio_pins[chip->ioh]); + +end: + return 0; + +err_request_irq: + irq_free_descs(irq_base, gpio_pins[chip->ioh]); + + ret = gpiochip_remove(&chip->gpio); + if (ret) + dev_err(&pdev->dev, "%s gpiochip_remove failed\n", __func__); + +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); + + if (chip->irq_base != -1) { + free_irq(pdev->irq, chip); + + irq_free_descs(chip->irq_base, gpio_pins[chip->ioh]); + } + + 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); + unsigned long flags; + + spin_lock_irqsave(&chip->spinlock, flags); + pch_gpio_save_reg_conf(chip); + spin_unlock_irqrestore(&chip->spinlock, flags); + + 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); + unsigned long flags; + + 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); + + spin_lock_irqsave(&chip->spinlock, flags); + iowrite32(0x01, &chip->reg->reset); + iowrite32(0x00, &chip->reg->reset); + pch_gpio_restore_reg_conf(chip); + spin_unlock_irqrestore(&chip->spinlock, flags); + + return 0; +} +#else +#define pch_gpio_suspend NULL +#define pch_gpio_resume NULL +#endif + +#define PCI_VENDOR_ID_ROHM 0x10DB +static DEFINE_PCI_DEVICE_TABLE(pch_gpio_pcidev_id) = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x8803) }, + { PCI_DEVICE(PCI_VENDOR_ID_ROHM, 0x8014) }, + { PCI_DEVICE(PCI_VENDOR_ID_ROHM, 0x8043) }, + { PCI_DEVICE(PCI_VENDOR_ID_ROHM, 0x8803) }, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, pch_gpio_pcidev_id); + +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"); diff --git a/drivers/gpio/gpio-pl061.c b/drivers/gpio/gpio-pl061.c new file mode 100644 index 00000000..b4b5da4f --- /dev/null +++ b/drivers/gpio/gpio-pl061.c @@ -0,0 +1,392 @@ +/* + * Copyright (C) 2008, 2009 Provigent Ltd. + * + * 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. + * + * Driver for the ARM PrimeCell(tm) General Purpose Input/Output (PL061) + * + * Data sheet: ARM DDI 0190B, September 2000 + */ +#include <linux/spinlock.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/irq.h> +#include <linux/bitops.h> +#include <linux/workqueue.h> +#include <linux/gpio.h> +#include <linux/device.h> +#include <linux/amba/bus.h> +#include <linux/amba/pl061.h> +#include <linux/slab.h> +#include <linux/pm.h> +#include <asm/mach/irq.h> + +#define GPIODIR 0x400 +#define GPIOIS 0x404 +#define GPIOIBE 0x408 +#define GPIOIEV 0x40C +#define GPIOIE 0x410 +#define GPIORIS 0x414 +#define GPIOMIS 0x418 +#define GPIOIC 0x41C + +#define PL061_GPIO_NR 8 + +#ifdef CONFIG_PM +struct pl061_context_save_regs { + u8 gpio_data; + u8 gpio_dir; + u8 gpio_is; + u8 gpio_ibe; + u8 gpio_iev; + u8 gpio_ie; +}; +#endif + +struct pl061_gpio { + /* Each of the two spinlocks protects a different set of hardware + * regiters and data structurs. This decouples the code of the IRQ from + * the GPIO code. This also makes the case of a GPIO routine call from + * the IRQ code simpler. + */ + spinlock_t lock; /* GPIO registers */ + + void __iomem *base; + int irq_base; + struct irq_chip_generic *irq_gc; + struct gpio_chip gc; + +#ifdef CONFIG_PM + struct pl061_context_save_regs csave_regs; +#endif +}; + +static int pl061_direction_input(struct gpio_chip *gc, unsigned offset) +{ + struct pl061_gpio *chip = container_of(gc, struct pl061_gpio, gc); + unsigned long flags; + unsigned char gpiodir; + + if (offset >= gc->ngpio) + return -EINVAL; + + spin_lock_irqsave(&chip->lock, flags); + gpiodir = readb(chip->base + GPIODIR); + gpiodir &= ~(1 << offset); + writeb(gpiodir, chip->base + GPIODIR); + spin_unlock_irqrestore(&chip->lock, flags); + + return 0; +} + +static int pl061_direction_output(struct gpio_chip *gc, unsigned offset, + int value) +{ + struct pl061_gpio *chip = container_of(gc, struct pl061_gpio, gc); + unsigned long flags; + unsigned char gpiodir; + + if (offset >= gc->ngpio) + return -EINVAL; + + spin_lock_irqsave(&chip->lock, flags); + writeb(!!value << offset, chip->base + (1 << (offset + 2))); + gpiodir = readb(chip->base + GPIODIR); + gpiodir |= 1 << offset; + writeb(gpiodir, chip->base + GPIODIR); + + /* + * gpio value is set again, because pl061 doesn't allow to set value of + * a gpio pin before configuring it in OUT mode. + */ + writeb(!!value << offset, chip->base + (1 << (offset + 2))); + spin_unlock_irqrestore(&chip->lock, flags); + + return 0; +} + +static int pl061_get_value(struct gpio_chip *gc, unsigned offset) +{ + struct pl061_gpio *chip = container_of(gc, struct pl061_gpio, gc); + + return !!readb(chip->base + (1 << (offset + 2))); +} + +static void pl061_set_value(struct gpio_chip *gc, unsigned offset, int value) +{ + struct pl061_gpio *chip = container_of(gc, struct pl061_gpio, gc); + + writeb(!!value << offset, chip->base + (1 << (offset + 2))); +} + +static int pl061_to_irq(struct gpio_chip *gc, unsigned offset) +{ + struct pl061_gpio *chip = container_of(gc, struct pl061_gpio, gc); + + if (chip->irq_base <= 0) + return -EINVAL; + + return chip->irq_base + offset; +} + +static int pl061_irq_type(struct irq_data *d, unsigned trigger) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct pl061_gpio *chip = gc->private; + int offset = d->irq - chip->irq_base; + unsigned long flags; + u8 gpiois, gpioibe, gpioiev; + + if (offset < 0 || offset >= PL061_GPIO_NR) + return -EINVAL; + + raw_spin_lock_irqsave(&gc->lock, flags); + + gpioiev = readb(chip->base + GPIOIEV); + + gpiois = readb(chip->base + GPIOIS); + if (trigger & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW)) { + gpiois |= 1 << offset; + if (trigger & IRQ_TYPE_LEVEL_HIGH) + gpioiev |= 1 << offset; + else + gpioiev &= ~(1 << offset); + } else + gpiois &= ~(1 << offset); + writeb(gpiois, chip->base + GPIOIS); + + gpioibe = readb(chip->base + GPIOIBE); + if ((trigger & IRQ_TYPE_EDGE_BOTH) == IRQ_TYPE_EDGE_BOTH) + gpioibe |= 1 << offset; + else { + gpioibe &= ~(1 << offset); + if (trigger & IRQ_TYPE_EDGE_RISING) + gpioiev |= 1 << offset; + else if (trigger & IRQ_TYPE_EDGE_FALLING) + gpioiev &= ~(1 << offset); + } + writeb(gpioibe, chip->base + GPIOIBE); + + writeb(gpioiev, chip->base + GPIOIEV); + + raw_spin_unlock_irqrestore(&gc->lock, flags); + + return 0; +} + +static void pl061_irq_handler(unsigned irq, struct irq_desc *desc) +{ + unsigned long pending; + int offset; + struct pl061_gpio *chip = irq_desc_get_handler_data(desc); + struct irq_chip *irqchip = irq_desc_get_chip(desc); + + chained_irq_enter(irqchip, desc); + + pending = readb(chip->base + GPIOMIS); + writeb(pending, chip->base + GPIOIC); + if (pending) { + for_each_set_bit(offset, &pending, PL061_GPIO_NR) + generic_handle_irq(pl061_to_irq(&chip->gc, offset)); + } + + chained_irq_exit(irqchip, desc); +} + +static void __init pl061_init_gc(struct pl061_gpio *chip, int irq_base) +{ + struct irq_chip_type *ct; + + chip->irq_gc = irq_alloc_generic_chip("gpio-pl061", 1, irq_base, + chip->base, handle_simple_irq); + chip->irq_gc->private = chip; + + ct = chip->irq_gc->chip_types; + ct->chip.irq_mask = irq_gc_mask_clr_bit; + ct->chip.irq_unmask = irq_gc_mask_set_bit; + ct->chip.irq_set_type = pl061_irq_type; + ct->chip.irq_set_wake = irq_gc_set_wake; + ct->regs.mask = GPIOIE; + + irq_setup_generic_chip(chip->irq_gc, IRQ_MSK(PL061_GPIO_NR), + IRQ_GC_INIT_NESTED_LOCK, IRQ_NOREQUEST, 0); +} + +static int pl061_probe(struct amba_device *dev, const struct amba_id *id) +{ + struct pl061_platform_data *pdata; + struct pl061_gpio *chip; + int ret, irq, i; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + + pdata = dev->dev.platform_data; + if (pdata) { + chip->gc.base = pdata->gpio_base; + chip->irq_base = pdata->irq_base; + } else if (dev->dev.of_node) { + chip->gc.base = -1; + chip->irq_base = 0; + } else { + ret = -ENODEV; + goto free_mem; + } + + if (!request_mem_region(dev->res.start, + resource_size(&dev->res), "pl061")) { + ret = -EBUSY; + goto free_mem; + } + + chip->base = ioremap(dev->res.start, resource_size(&dev->res)); + if (chip->base == NULL) { + ret = -ENOMEM; + goto release_region; + } + + spin_lock_init(&chip->lock); + + chip->gc.direction_input = pl061_direction_input; + chip->gc.direction_output = pl061_direction_output; + chip->gc.get = pl061_get_value; + chip->gc.set = pl061_set_value; + chip->gc.to_irq = pl061_to_irq; + chip->gc.ngpio = PL061_GPIO_NR; + chip->gc.label = dev_name(&dev->dev); + chip->gc.dev = &dev->dev; + chip->gc.owner = THIS_MODULE; + + ret = gpiochip_add(&chip->gc); + if (ret) + goto iounmap; + + /* + * irq_chip support + */ + + if (chip->irq_base <= 0) + return 0; + + pl061_init_gc(chip, chip->irq_base); + + writeb(0, chip->base + GPIOIE); /* disable irqs */ + irq = dev->irq[0]; + if (irq < 0) { + ret = -ENODEV; + goto iounmap; + } + irq_set_chained_handler(irq, pl061_irq_handler); + irq_set_handler_data(irq, chip); + + for (i = 0; i < PL061_GPIO_NR; i++) { + if (pdata) { + if (pdata->directions & (1 << i)) + pl061_direction_output(&chip->gc, i, + pdata->values & (1 << i)); + else + pl061_direction_input(&chip->gc, i); + } + } + + amba_set_drvdata(dev, chip); + + return 0; + +iounmap: + iounmap(chip->base); +release_region: + release_mem_region(dev->res.start, resource_size(&dev->res)); +free_mem: + kfree(chip); + + return ret; +} + +#ifdef CONFIG_PM +static int pl061_suspend(struct device *dev) +{ + struct pl061_gpio *chip = dev_get_drvdata(dev); + int offset; + + chip->csave_regs.gpio_data = 0; + chip->csave_regs.gpio_dir = readb(chip->base + GPIODIR); + chip->csave_regs.gpio_is = readb(chip->base + GPIOIS); + chip->csave_regs.gpio_ibe = readb(chip->base + GPIOIBE); + chip->csave_regs.gpio_iev = readb(chip->base + GPIOIEV); + chip->csave_regs.gpio_ie = readb(chip->base + GPIOIE); + + for (offset = 0; offset < PL061_GPIO_NR; offset++) { + if (chip->csave_regs.gpio_dir & (1 << offset)) + chip->csave_regs.gpio_data |= + pl061_get_value(&chip->gc, offset) << offset; + } + + return 0; +} + +static int pl061_resume(struct device *dev) +{ + struct pl061_gpio *chip = dev_get_drvdata(dev); + int offset; + + for (offset = 0; offset < PL061_GPIO_NR; offset++) { + if (chip->csave_regs.gpio_dir & (1 << offset)) + pl061_direction_output(&chip->gc, offset, + chip->csave_regs.gpio_data & + (1 << offset)); + else + pl061_direction_input(&chip->gc, offset); + } + + writeb(chip->csave_regs.gpio_is, chip->base + GPIOIS); + writeb(chip->csave_regs.gpio_ibe, chip->base + GPIOIBE); + writeb(chip->csave_regs.gpio_iev, chip->base + GPIOIEV); + writeb(chip->csave_regs.gpio_ie, chip->base + GPIOIE); + + return 0; +} + +static const struct dev_pm_ops pl061_dev_pm_ops = { + .suspend = pl061_suspend, + .resume = pl061_resume, + .freeze = pl061_suspend, + .restore = pl061_resume, +}; +#endif + +static struct amba_id pl061_ids[] = { + { + .id = 0x00041061, + .mask = 0x000fffff, + }, + { 0, 0 }, +}; + +MODULE_DEVICE_TABLE(amba, pl061_ids); + +static struct amba_driver pl061_gpio_driver = { + .drv = { + .name = "pl061_gpio", +#ifdef CONFIG_PM + .pm = &pl061_dev_pm_ops, +#endif + }, + .id_table = pl061_ids, + .probe = pl061_probe, +}; + +static int __init pl061_gpio_init(void) +{ + return amba_driver_register(&pl061_gpio_driver); +} +subsys_initcall(pl061_gpio_init); + +MODULE_AUTHOR("Baruch Siach <baruch@tkos.co.il>"); +MODULE_DESCRIPTION("PL061 GPIO driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpio/gpio-pxa.c b/drivers/gpio/gpio-pxa.c new file mode 100644 index 00000000..fc3ace3f --- /dev/null +++ b/drivers/gpio/gpio-pxa.c @@ -0,0 +1,634 @@ +/* + * linux/arch/arm/plat-pxa/gpio.c + * + * Generic PXA GPIO handling + * + * Author: Nicolas Pitre + * Created: Jun 15, 2001 + * Copyright: MontaVista Software Inc. + * + * 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 <linux/clk.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/gpio-pxa.h> +#include <linux/init.h> +#include <linux/irq.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/syscore_ops.h> +#include <linux/slab.h> + +#include <mach/irqs.h> + +/* + * We handle the GPIOs by banks, each bank covers up to 32 GPIOs with + * one set of registers. The register offsets are organized below: + * + * GPLR GPDR GPSR GPCR GRER GFER GEDR + * BANK 0 - 0x0000 0x000C 0x0018 0x0024 0x0030 0x003C 0x0048 + * BANK 1 - 0x0004 0x0010 0x001C 0x0028 0x0034 0x0040 0x004C + * BANK 2 - 0x0008 0x0014 0x0020 0x002C 0x0038 0x0044 0x0050 + * + * BANK 3 - 0x0100 0x010C 0x0118 0x0124 0x0130 0x013C 0x0148 + * BANK 4 - 0x0104 0x0110 0x011C 0x0128 0x0134 0x0140 0x014C + * BANK 5 - 0x0108 0x0114 0x0120 0x012C 0x0138 0x0144 0x0150 + * + * NOTE: + * BANK 3 is only available on PXA27x and later processors. + * BANK 4 and 5 are only available on PXA935 + */ + +#define GPLR_OFFSET 0x00 +#define GPDR_OFFSET 0x0C +#define GPSR_OFFSET 0x18 +#define GPCR_OFFSET 0x24 +#define GRER_OFFSET 0x30 +#define GFER_OFFSET 0x3C +#define GEDR_OFFSET 0x48 +#define GAFR_OFFSET 0x54 +#define ED_MASK_OFFSET 0x9C /* GPIO edge detection for AP side */ + +#define BANK_OFF(n) (((n) < 3) ? (n) << 2 : 0x100 + (((n) - 3) << 2)) + +int pxa_last_gpio; + +struct pxa_gpio_chip { + struct gpio_chip chip; + void __iomem *regbase; + char label[10]; + + unsigned long irq_mask; + unsigned long irq_edge_rise; + unsigned long irq_edge_fall; + int (*set_wake)(unsigned int gpio, unsigned int on); + +#ifdef CONFIG_PM + unsigned long saved_gplr; + unsigned long saved_gpdr; + unsigned long saved_grer; + unsigned long saved_gfer; +#endif +}; + +enum { + PXA25X_GPIO = 0, + PXA26X_GPIO, + PXA27X_GPIO, + PXA3XX_GPIO, + PXA93X_GPIO, + MMP_GPIO = 0x10, + MMP2_GPIO, +}; + +static DEFINE_SPINLOCK(gpio_lock); +static struct pxa_gpio_chip *pxa_gpio_chips; +static int gpio_type; +static void __iomem *gpio_reg_base; + +#define for_each_gpio_chip(i, c) \ + for (i = 0, c = &pxa_gpio_chips[0]; i <= pxa_last_gpio; i += 32, c++) + +static inline void __iomem *gpio_chip_base(struct gpio_chip *c) +{ + return container_of(c, struct pxa_gpio_chip, chip)->regbase; +} + +static inline struct pxa_gpio_chip *gpio_to_pxachip(unsigned gpio) +{ + return &pxa_gpio_chips[gpio_to_bank(gpio)]; +} + +static inline int gpio_is_pxa_type(int type) +{ + return (type & MMP_GPIO) == 0; +} + +static inline int gpio_is_mmp_type(int type) +{ + return (type & MMP_GPIO) != 0; +} + +/* GPIO86/87/88/89 on PXA26x have their direction bits in PXA_GPDR(2 inverted, + * as well as their Alternate Function value being '1' for GPIO in GAFRx. + */ +static inline int __gpio_is_inverted(int gpio) +{ + if ((gpio_type == PXA26X_GPIO) && (gpio > 85)) + return 1; + return 0; +} + +/* + * On PXA25x and PXA27x, GAFRx and GPDRx together decide the alternate + * function of a GPIO, and GPDRx cannot be altered once configured. It + * is attributed as "occupied" here (I know this terminology isn't + * accurate, you are welcome to propose a better one :-) + */ +static inline int __gpio_is_occupied(unsigned gpio) +{ + struct pxa_gpio_chip *pxachip; + void __iomem *base; + unsigned long gafr = 0, gpdr = 0; + int ret, af = 0, dir = 0; + + pxachip = gpio_to_pxachip(gpio); + base = gpio_chip_base(&pxachip->chip); + gpdr = readl_relaxed(base + GPDR_OFFSET); + + switch (gpio_type) { + case PXA25X_GPIO: + case PXA26X_GPIO: + case PXA27X_GPIO: + gafr = readl_relaxed(base + GAFR_OFFSET); + af = (gafr >> ((gpio & 0xf) * 2)) & 0x3; + dir = gpdr & GPIO_bit(gpio); + + if (__gpio_is_inverted(gpio)) + ret = (af != 1) || (dir == 0); + else + ret = (af != 0) || (dir != 0); + break; + default: + ret = gpdr & GPIO_bit(gpio); + break; + } + return ret; +} + +#ifdef CONFIG_ARCH_PXA +static inline int __pxa_gpio_to_irq(int gpio) +{ + if (gpio_is_pxa_type(gpio_type)) + return PXA_GPIO_TO_IRQ(gpio); + return -1; +} + +static inline int __pxa_irq_to_gpio(int irq) +{ + if (gpio_is_pxa_type(gpio_type)) + return irq - PXA_GPIO_TO_IRQ(0); + return -1; +} +#else +static inline int __pxa_gpio_to_irq(int gpio) { return -1; } +static inline int __pxa_irq_to_gpio(int irq) { return -1; } +#endif + +#ifdef CONFIG_ARCH_MMP +static inline int __mmp_gpio_to_irq(int gpio) +{ + if (gpio_is_mmp_type(gpio_type)) + return MMP_GPIO_TO_IRQ(gpio); + return -1; +} + +static inline int __mmp_irq_to_gpio(int irq) +{ + if (gpio_is_mmp_type(gpio_type)) + return irq - MMP_GPIO_TO_IRQ(0); + return -1; +} +#else +static inline int __mmp_gpio_to_irq(int gpio) { return -1; } +static inline int __mmp_irq_to_gpio(int irq) { return -1; } +#endif + +static int pxa_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + int gpio, ret; + + gpio = chip->base + offset; + ret = __pxa_gpio_to_irq(gpio); + if (ret >= 0) + return ret; + return __mmp_gpio_to_irq(gpio); +} + +int pxa_irq_to_gpio(int irq) +{ + int ret; + + ret = __pxa_irq_to_gpio(irq); + if (ret >= 0) + return ret; + return __mmp_irq_to_gpio(irq); +} + +static int pxa_gpio_direction_input(struct gpio_chip *chip, unsigned offset) +{ + void __iomem *base = gpio_chip_base(chip); + uint32_t value, mask = 1 << offset; + unsigned long flags; + + spin_lock_irqsave(&gpio_lock, flags); + + value = readl_relaxed(base + GPDR_OFFSET); + if (__gpio_is_inverted(chip->base + offset)) + value |= mask; + else + value &= ~mask; + writel_relaxed(value, base + GPDR_OFFSET); + + spin_unlock_irqrestore(&gpio_lock, flags); + return 0; +} + +static int pxa_gpio_direction_output(struct gpio_chip *chip, + unsigned offset, int value) +{ + void __iomem *base = gpio_chip_base(chip); + uint32_t tmp, mask = 1 << offset; + unsigned long flags; + + writel_relaxed(mask, base + (value ? GPSR_OFFSET : GPCR_OFFSET)); + + spin_lock_irqsave(&gpio_lock, flags); + + tmp = readl_relaxed(base + GPDR_OFFSET); + if (__gpio_is_inverted(chip->base + offset)) + tmp &= ~mask; + else + tmp |= mask; + writel_relaxed(tmp, base + GPDR_OFFSET); + + spin_unlock_irqrestore(&gpio_lock, flags); + return 0; +} + +static int pxa_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + return readl_relaxed(gpio_chip_base(chip) + GPLR_OFFSET) & (1 << offset); +} + +static void pxa_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + writel_relaxed(1 << offset, gpio_chip_base(chip) + + (value ? GPSR_OFFSET : GPCR_OFFSET)); +} + +static int __devinit pxa_init_gpio_chip(int gpio_end, + int (*set_wake)(unsigned int, unsigned int)) +{ + int i, gpio, nbanks = gpio_to_bank(gpio_end) + 1; + struct pxa_gpio_chip *chips; + + chips = kzalloc(nbanks * sizeof(struct pxa_gpio_chip), GFP_KERNEL); + if (chips == NULL) { + pr_err("%s: failed to allocate GPIO chips\n", __func__); + return -ENOMEM; + } + + for (i = 0, gpio = 0; i < nbanks; i++, gpio += 32) { + struct gpio_chip *c = &chips[i].chip; + + sprintf(chips[i].label, "gpio-%d", i); + chips[i].regbase = gpio_reg_base + BANK_OFF(i); + chips[i].set_wake = set_wake; + + c->base = gpio; + c->label = chips[i].label; + + c->direction_input = pxa_gpio_direction_input; + c->direction_output = pxa_gpio_direction_output; + c->get = pxa_gpio_get; + c->set = pxa_gpio_set; + c->to_irq = pxa_gpio_to_irq; + + /* number of GPIOs on last bank may be less than 32 */ + c->ngpio = (gpio + 31 > gpio_end) ? (gpio_end - gpio + 1) : 32; + gpiochip_add(c); + } + pxa_gpio_chips = chips; + return 0; +} + +/* Update only those GRERx and GFERx edge detection register bits if those + * bits are set in c->irq_mask + */ +static inline void update_edge_detect(struct pxa_gpio_chip *c) +{ + uint32_t grer, gfer; + + grer = readl_relaxed(c->regbase + GRER_OFFSET) & ~c->irq_mask; + gfer = readl_relaxed(c->regbase + GFER_OFFSET) & ~c->irq_mask; + grer |= c->irq_edge_rise & c->irq_mask; + gfer |= c->irq_edge_fall & c->irq_mask; + writel_relaxed(grer, c->regbase + GRER_OFFSET); + writel_relaxed(gfer, c->regbase + GFER_OFFSET); +} + +static int pxa_gpio_irq_type(struct irq_data *d, unsigned int type) +{ + struct pxa_gpio_chip *c; + int gpio = pxa_irq_to_gpio(d->irq); + unsigned long gpdr, mask = GPIO_bit(gpio); + + c = gpio_to_pxachip(gpio); + + if (type == IRQ_TYPE_PROBE) { + /* Don't mess with enabled GPIOs using preconfigured edges or + * GPIOs set to alternate function or to output during probe + */ + if ((c->irq_edge_rise | c->irq_edge_fall) & GPIO_bit(gpio)) + return 0; + + if (__gpio_is_occupied(gpio)) + return 0; + + type = IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING; + } + + gpdr = readl_relaxed(c->regbase + GPDR_OFFSET); + + if (__gpio_is_inverted(gpio)) + writel_relaxed(gpdr | mask, c->regbase + GPDR_OFFSET); + else + writel_relaxed(gpdr & ~mask, c->regbase + GPDR_OFFSET); + + if (type & IRQ_TYPE_EDGE_RISING) + c->irq_edge_rise |= mask; + else + c->irq_edge_rise &= ~mask; + + if (type & IRQ_TYPE_EDGE_FALLING) + c->irq_edge_fall |= mask; + else + c->irq_edge_fall &= ~mask; + + update_edge_detect(c); + + pr_debug("%s: IRQ%d (GPIO%d) - edge%s%s\n", __func__, d->irq, gpio, + ((type & IRQ_TYPE_EDGE_RISING) ? " rising" : ""), + ((type & IRQ_TYPE_EDGE_FALLING) ? " falling" : "")); + return 0; +} + +static void pxa_gpio_demux_handler(unsigned int irq, struct irq_desc *desc) +{ + struct pxa_gpio_chip *c; + int loop, gpio, gpio_base, n; + unsigned long gedr; + + do { + loop = 0; + for_each_gpio_chip(gpio, c) { + gpio_base = c->chip.base; + + gedr = readl_relaxed(c->regbase + GEDR_OFFSET); + gedr = gedr & c->irq_mask; + writel_relaxed(gedr, c->regbase + GEDR_OFFSET); + + n = find_first_bit(&gedr, BITS_PER_LONG); + while (n < BITS_PER_LONG) { + loop = 1; + + generic_handle_irq(gpio_to_irq(gpio_base + n)); + n = find_next_bit(&gedr, BITS_PER_LONG, n + 1); + } + } + } while (loop); +} + +static void pxa_ack_muxed_gpio(struct irq_data *d) +{ + int gpio = pxa_irq_to_gpio(d->irq); + struct pxa_gpio_chip *c = gpio_to_pxachip(gpio); + + writel_relaxed(GPIO_bit(gpio), c->regbase + GEDR_OFFSET); +} + +static void pxa_mask_muxed_gpio(struct irq_data *d) +{ + int gpio = pxa_irq_to_gpio(d->irq); + struct pxa_gpio_chip *c = gpio_to_pxachip(gpio); + uint32_t grer, gfer; + + c->irq_mask &= ~GPIO_bit(gpio); + + grer = readl_relaxed(c->regbase + GRER_OFFSET) & ~GPIO_bit(gpio); + gfer = readl_relaxed(c->regbase + GFER_OFFSET) & ~GPIO_bit(gpio); + writel_relaxed(grer, c->regbase + GRER_OFFSET); + writel_relaxed(gfer, c->regbase + GFER_OFFSET); +} + +static int pxa_gpio_set_wake(struct irq_data *d, unsigned int on) +{ + int gpio = pxa_irq_to_gpio(d->irq); + struct pxa_gpio_chip *c = gpio_to_pxachip(gpio); + + if (c->set_wake) + return c->set_wake(gpio, on); + else + return 0; +} + +static void pxa_unmask_muxed_gpio(struct irq_data *d) +{ + int gpio = pxa_irq_to_gpio(d->irq); + struct pxa_gpio_chip *c = gpio_to_pxachip(gpio); + + c->irq_mask |= GPIO_bit(gpio); + update_edge_detect(c); +} + +static struct irq_chip pxa_muxed_gpio_chip = { + .name = "GPIO", + .irq_ack = pxa_ack_muxed_gpio, + .irq_mask = pxa_mask_muxed_gpio, + .irq_unmask = pxa_unmask_muxed_gpio, + .irq_set_type = pxa_gpio_irq_type, + .irq_set_wake = pxa_gpio_set_wake, +}; + +static int pxa_gpio_nums(void) +{ + int count = 0; + +#ifdef CONFIG_ARCH_PXA + if (cpu_is_pxa25x()) { +#ifdef CONFIG_CPU_PXA26x + count = 89; + gpio_type = PXA26X_GPIO; +#elif defined(CONFIG_PXA25x) + count = 84; + gpio_type = PXA26X_GPIO; +#endif /* CONFIG_CPU_PXA26x */ + } else if (cpu_is_pxa27x()) { + count = 120; + gpio_type = PXA27X_GPIO; + } else if (cpu_is_pxa93x() || cpu_is_pxa95x()) { + count = 191; + gpio_type = PXA93X_GPIO; + } else if (cpu_is_pxa3xx()) { + count = 127; + gpio_type = PXA3XX_GPIO; + } +#endif /* CONFIG_ARCH_PXA */ + +#ifdef CONFIG_ARCH_MMP + if (cpu_is_pxa168() || cpu_is_pxa910()) { + count = 127; + gpio_type = MMP_GPIO; + } else if (cpu_is_mmp2()) { + count = 191; + gpio_type = MMP2_GPIO; + } +#endif /* CONFIG_ARCH_MMP */ + return count; +} + +static int __devinit pxa_gpio_probe(struct platform_device *pdev) +{ + struct pxa_gpio_chip *c; + struct resource *res; + struct clk *clk; + struct pxa_gpio_platform_data *info; + int gpio, irq, ret; + int irq0 = 0, irq1 = 0, irq_mux, gpio_offset = 0; + + pxa_last_gpio = pxa_gpio_nums(); + if (!pxa_last_gpio) + return -EINVAL; + + irq0 = platform_get_irq_byname(pdev, "gpio0"); + irq1 = platform_get_irq_byname(pdev, "gpio1"); + irq_mux = platform_get_irq_byname(pdev, "gpio_mux"); + if ((irq0 > 0 && irq1 <= 0) || (irq0 <= 0 && irq1 > 0) + || (irq_mux <= 0)) + return -EINVAL; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + gpio_reg_base = ioremap(res->start, resource_size(res)); + if (!gpio_reg_base) + return -EINVAL; + + if (irq0 > 0) + gpio_offset = 2; + + clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "Error %ld to get gpio clock\n", + PTR_ERR(clk)); + iounmap(gpio_reg_base); + return PTR_ERR(clk); + } + ret = clk_prepare(clk); + if (ret) { + clk_put(clk); + iounmap(gpio_reg_base); + return ret; + } + ret = clk_enable(clk); + if (ret) { + clk_unprepare(clk); + clk_put(clk); + iounmap(gpio_reg_base); + return ret; + } + + /* Initialize GPIO chips */ + info = dev_get_platdata(&pdev->dev); + pxa_init_gpio_chip(pxa_last_gpio, info ? info->gpio_set_wake : NULL); + + /* clear all GPIO edge detects */ + for_each_gpio_chip(gpio, c) { + writel_relaxed(0, c->regbase + GFER_OFFSET); + writel_relaxed(0, c->regbase + GRER_OFFSET); + writel_relaxed(~0,c->regbase + GEDR_OFFSET); + /* unmask GPIO edge detect for AP side */ + if (gpio_is_mmp_type(gpio_type)) + writel_relaxed(~0, c->regbase + ED_MASK_OFFSET); + } + +#ifdef CONFIG_ARCH_PXA + irq = gpio_to_irq(0); + irq_set_chip_and_handler(irq, &pxa_muxed_gpio_chip, + handle_edge_irq); + set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); + irq_set_chained_handler(IRQ_GPIO0, pxa_gpio_demux_handler); + + irq = gpio_to_irq(1); + irq_set_chip_and_handler(irq, &pxa_muxed_gpio_chip, + handle_edge_irq); + set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); + irq_set_chained_handler(IRQ_GPIO1, pxa_gpio_demux_handler); +#endif + + for (irq = gpio_to_irq(gpio_offset); + irq <= gpio_to_irq(pxa_last_gpio); irq++) { + irq_set_chip_and_handler(irq, &pxa_muxed_gpio_chip, + handle_edge_irq); + set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); + } + + irq_set_chained_handler(irq_mux, pxa_gpio_demux_handler); + return 0; +} + +static struct platform_driver pxa_gpio_driver = { + .probe = pxa_gpio_probe, + .driver = { + .name = "pxa-gpio", + }, +}; + +static int __init pxa_gpio_init(void) +{ + return platform_driver_register(&pxa_gpio_driver); +} +postcore_initcall(pxa_gpio_init); + +#ifdef CONFIG_PM +static int pxa_gpio_suspend(void) +{ + struct pxa_gpio_chip *c; + int gpio; + + for_each_gpio_chip(gpio, c) { + c->saved_gplr = readl_relaxed(c->regbase + GPLR_OFFSET); + c->saved_gpdr = readl_relaxed(c->regbase + GPDR_OFFSET); + c->saved_grer = readl_relaxed(c->regbase + GRER_OFFSET); + c->saved_gfer = readl_relaxed(c->regbase + GFER_OFFSET); + + /* Clear GPIO transition detect bits */ + writel_relaxed(0xffffffff, c->regbase + GEDR_OFFSET); + } + return 0; +} + +static void pxa_gpio_resume(void) +{ + struct pxa_gpio_chip *c; + int gpio; + + for_each_gpio_chip(gpio, c) { + /* restore level with set/clear */ + writel_relaxed( c->saved_gplr, c->regbase + GPSR_OFFSET); + writel_relaxed(~c->saved_gplr, c->regbase + GPCR_OFFSET); + + writel_relaxed(c->saved_grer, c->regbase + GRER_OFFSET); + writel_relaxed(c->saved_gfer, c->regbase + GFER_OFFSET); + writel_relaxed(c->saved_gpdr, c->regbase + GPDR_OFFSET); + } +} +#else +#define pxa_gpio_suspend NULL +#define pxa_gpio_resume NULL +#endif + +struct syscore_ops pxa_gpio_syscore_ops = { + .suspend = pxa_gpio_suspend, + .resume = pxa_gpio_resume, +}; + +static int __init pxa_gpio_sysinit(void) +{ + register_syscore_ops(&pxa_gpio_syscore_ops); + return 0; +} +postcore_initcall(pxa_gpio_sysinit); diff --git a/drivers/gpio/gpio-rdc321x.c b/drivers/gpio/gpio-rdc321x.c new file mode 100644 index 00000000..e97016af --- /dev/null +++ b/drivers/gpio/gpio-rdc321x.c @@ -0,0 +1,235 @@ +/* + * RDC321x GPIO driver + * + * Copyright (C) 2008, Volker Weiss <dev@tintuc.de> + * Copyright (C) 2007-2010 Florian Fainelli <florian@openwrt.org> + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/platform_device.h> +#include <linux/pci.h> +#include <linux/gpio.h> +#include <linux/mfd/rdc321x.h> +#include <linux/slab.h> + +struct rdc321x_gpio { + spinlock_t lock; + struct pci_dev *sb_pdev; + u32 data_reg[2]; + int reg1_ctrl_base; + int reg1_data_base; + int reg2_ctrl_base; + int reg2_data_base; + struct gpio_chip chip; +}; + +/* read GPIO pin */ +static int rdc_gpio_get_value(struct gpio_chip *chip, unsigned gpio) +{ + struct rdc321x_gpio *gpch; + u32 value = 0; + int reg; + + gpch = container_of(chip, struct rdc321x_gpio, chip); + reg = gpio < 32 ? gpch->reg1_data_base : gpch->reg2_data_base; + + spin_lock(&gpch->lock); + pci_write_config_dword(gpch->sb_pdev, reg, + gpch->data_reg[gpio < 32 ? 0 : 1]); + pci_read_config_dword(gpch->sb_pdev, reg, &value); + spin_unlock(&gpch->lock); + + return (1 << (gpio & 0x1f)) & value ? 1 : 0; +} + +static void rdc_gpio_set_value_impl(struct gpio_chip *chip, + unsigned gpio, int value) +{ + struct rdc321x_gpio *gpch; + int reg = (gpio < 32) ? 0 : 1; + + gpch = container_of(chip, struct rdc321x_gpio, chip); + + if (value) + gpch->data_reg[reg] |= 1 << (gpio & 0x1f); + else + gpch->data_reg[reg] &= ~(1 << (gpio & 0x1f)); + + pci_write_config_dword(gpch->sb_pdev, + reg ? gpch->reg2_data_base : gpch->reg1_data_base, + gpch->data_reg[reg]); +} + +/* set GPIO pin to value */ +static void rdc_gpio_set_value(struct gpio_chip *chip, + unsigned gpio, int value) +{ + struct rdc321x_gpio *gpch; + + gpch = container_of(chip, struct rdc321x_gpio, chip); + spin_lock(&gpch->lock); + rdc_gpio_set_value_impl(chip, gpio, value); + spin_unlock(&gpch->lock); +} + +static int rdc_gpio_config(struct gpio_chip *chip, + unsigned gpio, int value) +{ + struct rdc321x_gpio *gpch; + int err; + u32 reg; + + gpch = container_of(chip, struct rdc321x_gpio, chip); + + spin_lock(&gpch->lock); + err = pci_read_config_dword(gpch->sb_pdev, gpio < 32 ? + gpch->reg1_ctrl_base : gpch->reg2_ctrl_base, ®); + if (err) + goto unlock; + + reg |= 1 << (gpio & 0x1f); + + err = pci_write_config_dword(gpch->sb_pdev, gpio < 32 ? + gpch->reg1_ctrl_base : gpch->reg2_ctrl_base, reg); + if (err) + goto unlock; + + rdc_gpio_set_value_impl(chip, gpio, value); + +unlock: + spin_unlock(&gpch->lock); + + return err; +} + +/* configure GPIO pin as input */ +static int rdc_gpio_direction_input(struct gpio_chip *chip, unsigned gpio) +{ + return rdc_gpio_config(chip, gpio, 1); +} + +/* + * Cache the initial value of both GPIO data registers + */ +static int __devinit rdc321x_gpio_probe(struct platform_device *pdev) +{ + int err; + struct resource *r; + struct rdc321x_gpio *rdc321x_gpio_dev; + struct rdc321x_gpio_pdata *pdata; + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "no platform data supplied\n"); + return -ENODEV; + } + + rdc321x_gpio_dev = kzalloc(sizeof(struct rdc321x_gpio), GFP_KERNEL); + if (!rdc321x_gpio_dev) { + dev_err(&pdev->dev, "failed to allocate private data\n"); + return -ENOMEM; + } + + r = platform_get_resource_byname(pdev, IORESOURCE_IO, "gpio-reg1"); + if (!r) { + dev_err(&pdev->dev, "failed to get gpio-reg1 resource\n"); + err = -ENODEV; + goto out_free; + } + + spin_lock_init(&rdc321x_gpio_dev->lock); + rdc321x_gpio_dev->sb_pdev = pdata->sb_pdev; + rdc321x_gpio_dev->reg1_ctrl_base = r->start; + rdc321x_gpio_dev->reg1_data_base = r->start + 0x4; + + r = platform_get_resource_byname(pdev, IORESOURCE_IO, "gpio-reg2"); + if (!r) { + dev_err(&pdev->dev, "failed to get gpio-reg2 resource\n"); + err = -ENODEV; + goto out_free; + } + + rdc321x_gpio_dev->reg2_ctrl_base = r->start; + rdc321x_gpio_dev->reg2_data_base = r->start + 0x4; + + rdc321x_gpio_dev->chip.label = "rdc321x-gpio"; + rdc321x_gpio_dev->chip.direction_input = rdc_gpio_direction_input; + rdc321x_gpio_dev->chip.direction_output = rdc_gpio_config; + rdc321x_gpio_dev->chip.get = rdc_gpio_get_value; + rdc321x_gpio_dev->chip.set = rdc_gpio_set_value; + rdc321x_gpio_dev->chip.base = 0; + rdc321x_gpio_dev->chip.ngpio = pdata->max_gpios; + + platform_set_drvdata(pdev, rdc321x_gpio_dev); + + /* This might not be, what others (BIOS, bootloader, etc.) + wrote to these registers before, but it's a good guess. Still + better than just using 0xffffffff. */ + err = pci_read_config_dword(rdc321x_gpio_dev->sb_pdev, + rdc321x_gpio_dev->reg1_data_base, + &rdc321x_gpio_dev->data_reg[0]); + if (err) + goto out_drvdata; + + err = pci_read_config_dword(rdc321x_gpio_dev->sb_pdev, + rdc321x_gpio_dev->reg2_data_base, + &rdc321x_gpio_dev->data_reg[1]); + if (err) + goto out_drvdata; + + dev_info(&pdev->dev, "registering %d GPIOs\n", + rdc321x_gpio_dev->chip.ngpio); + return gpiochip_add(&rdc321x_gpio_dev->chip); + +out_drvdata: + platform_set_drvdata(pdev, NULL); +out_free: + kfree(rdc321x_gpio_dev); + return err; +} + +static int __devexit rdc321x_gpio_remove(struct platform_device *pdev) +{ + int ret; + struct rdc321x_gpio *rdc321x_gpio_dev = platform_get_drvdata(pdev); + + ret = gpiochip_remove(&rdc321x_gpio_dev->chip); + if (ret) + dev_err(&pdev->dev, "failed to unregister chip\n"); + + kfree(rdc321x_gpio_dev); + platform_set_drvdata(pdev, NULL); + + return ret; +} + +static struct platform_driver rdc321x_gpio_driver = { + .driver.name = "rdc321x-gpio", + .driver.owner = THIS_MODULE, + .probe = rdc321x_gpio_probe, + .remove = __devexit_p(rdc321x_gpio_remove), +}; + +module_platform_driver(rdc321x_gpio_driver); + +MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>"); +MODULE_DESCRIPTION("RDC321x GPIO driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:rdc321x-gpio"); diff --git a/drivers/gpio/gpio-sa1100.c b/drivers/gpio/gpio-sa1100.c new file mode 100644 index 00000000..8ea3b33d --- /dev/null +++ b/drivers/gpio/gpio-sa1100.c @@ -0,0 +1,70 @@ +/* + * linux/arch/arm/mach-sa1100/gpio.c + * + * Generic SA-1100 GPIO handling + * + * 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 <linux/gpio.h> +#include <linux/init.h> +#include <linux/module.h> + +#include <mach/hardware.h> +#include <mach/irqs.h> + +static int sa1100_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + return GPLR & GPIO_GPIO(offset); +} + +static void sa1100_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + if (value) + GPSR = GPIO_GPIO(offset); + else + GPCR = GPIO_GPIO(offset); +} + +static int sa1100_direction_input(struct gpio_chip *chip, unsigned offset) +{ + unsigned long flags; + + local_irq_save(flags); + GPDR &= ~GPIO_GPIO(offset); + local_irq_restore(flags); + return 0; +} + +static int sa1100_direction_output(struct gpio_chip *chip, unsigned offset, int value) +{ + unsigned long flags; + + local_irq_save(flags); + sa1100_gpio_set(chip, offset, value); + GPDR |= GPIO_GPIO(offset); + local_irq_restore(flags); + return 0; +} + +static int sa1100_to_irq(struct gpio_chip *chip, unsigned offset) +{ + return offset < 11 ? (IRQ_GPIO0 + offset) : (IRQ_GPIO11 - 11 + offset); +} + +static struct gpio_chip sa1100_gpio_chip = { + .label = "gpio", + .direction_input = sa1100_direction_input, + .direction_output = sa1100_direction_output, + .set = sa1100_gpio_set, + .get = sa1100_gpio_get, + .to_irq = sa1100_to_irq, + .base = 0, + .ngpio = GPIO_MAX + 1, +}; + +void __init sa1100_init_gpio(void) +{ + gpiochip_add(&sa1100_gpio_chip); +} diff --git a/drivers/gpio/gpio-samsung.c b/drivers/gpio/gpio-samsung.c new file mode 100644 index 00000000..e991d917 --- /dev/null +++ b/drivers/gpio/gpio-samsung.c @@ -0,0 +1,3205 @@ +/* + * Copyright (c) 2009-2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * Copyright 2008 Openmoko, Inc. + * Copyright 2008 Simtec Electronics + * Ben Dooks <ben@simtec.co.uk> + * http://armlinux.simtec.co.uk/ + * + * SAMSUNG - GPIOlib support + * + * 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 <linux/kernel.h> +#include <linux/irq.h> +#include <linux/io.h> +#include <linux/gpio.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/ioport.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <linux/of_address.h> + +#include <asm/irq.h> + +#include <mach/hardware.h> +#include <mach/map.h> +#include <mach/regs-clock.h> +#include <mach/regs-gpio.h> + +#include <plat/cpu.h> +#include <plat/gpio-core.h> +#include <plat/gpio-cfg.h> +#include <plat/gpio-cfg-helpers.h> +#include <plat/gpio-fns.h> +#include <plat/pm.h> + +#ifndef DEBUG_GPIO +#define gpio_dbg(x...) do { } while (0) +#else +#define gpio_dbg(x...) printk(KERN_DEBUG x) +#endif + +int samsung_gpio_setpull_updown(struct samsung_gpio_chip *chip, + unsigned int off, samsung_gpio_pull_t pull) +{ + void __iomem *reg = chip->base + 0x08; + int shift = off * 2; + u32 pup; + + pup = __raw_readl(reg); + pup &= ~(3 << shift); + pup |= pull << shift; + __raw_writel(pup, reg); + + return 0; +} + +samsung_gpio_pull_t samsung_gpio_getpull_updown(struct samsung_gpio_chip *chip, + unsigned int off) +{ + void __iomem *reg = chip->base + 0x08; + int shift = off * 2; + u32 pup = __raw_readl(reg); + + pup >>= shift; + pup &= 0x3; + + return (__force samsung_gpio_pull_t)pup; +} + +int s3c2443_gpio_setpull(struct samsung_gpio_chip *chip, + unsigned int off, samsung_gpio_pull_t pull) +{ + switch (pull) { + case S3C_GPIO_PULL_NONE: + pull = 0x01; + break; + case S3C_GPIO_PULL_UP: + pull = 0x00; + break; + case S3C_GPIO_PULL_DOWN: + pull = 0x02; + break; + } + return samsung_gpio_setpull_updown(chip, off, pull); +} + +samsung_gpio_pull_t s3c2443_gpio_getpull(struct samsung_gpio_chip *chip, + unsigned int off) +{ + samsung_gpio_pull_t pull; + + pull = samsung_gpio_getpull_updown(chip, off); + + switch (pull) { + case 0x00: + pull = S3C_GPIO_PULL_UP; + break; + case 0x01: + case 0x03: + pull = S3C_GPIO_PULL_NONE; + break; + case 0x02: + pull = S3C_GPIO_PULL_DOWN; + break; + } + + return pull; +} + +static int s3c24xx_gpio_setpull_1(struct samsung_gpio_chip *chip, + unsigned int off, samsung_gpio_pull_t pull, + samsung_gpio_pull_t updown) +{ + void __iomem *reg = chip->base + 0x08; + u32 pup = __raw_readl(reg); + + if (pull == updown) + pup &= ~(1 << off); + else if (pull == S3C_GPIO_PULL_NONE) + pup |= (1 << off); + else + return -EINVAL; + + __raw_writel(pup, reg); + return 0; +} + +static samsung_gpio_pull_t s3c24xx_gpio_getpull_1(struct samsung_gpio_chip *chip, + unsigned int off, + samsung_gpio_pull_t updown) +{ + void __iomem *reg = chip->base + 0x08; + u32 pup = __raw_readl(reg); + + pup &= (1 << off); + return pup ? S3C_GPIO_PULL_NONE : updown; +} + +samsung_gpio_pull_t s3c24xx_gpio_getpull_1up(struct samsung_gpio_chip *chip, + unsigned int off) +{ + return s3c24xx_gpio_getpull_1(chip, off, S3C_GPIO_PULL_UP); +} + +int s3c24xx_gpio_setpull_1up(struct samsung_gpio_chip *chip, + unsigned int off, samsung_gpio_pull_t pull) +{ + return s3c24xx_gpio_setpull_1(chip, off, pull, S3C_GPIO_PULL_UP); +} + +samsung_gpio_pull_t s3c24xx_gpio_getpull_1down(struct samsung_gpio_chip *chip, + unsigned int off) +{ + return s3c24xx_gpio_getpull_1(chip, off, S3C_GPIO_PULL_DOWN); +} + +int s3c24xx_gpio_setpull_1down(struct samsung_gpio_chip *chip, + unsigned int off, samsung_gpio_pull_t pull) +{ + return s3c24xx_gpio_setpull_1(chip, off, pull, S3C_GPIO_PULL_DOWN); +} + +static int exynos_gpio_setpull(struct samsung_gpio_chip *chip, + unsigned int off, samsung_gpio_pull_t pull) +{ + if (pull == S3C_GPIO_PULL_UP) + pull = 3; + + return samsung_gpio_setpull_updown(chip, off, pull); +} + +static samsung_gpio_pull_t exynos_gpio_getpull(struct samsung_gpio_chip *chip, + unsigned int off) +{ + samsung_gpio_pull_t pull; + + pull = samsung_gpio_getpull_updown(chip, off); + + if (pull == 3) + pull = S3C_GPIO_PULL_UP; + + return pull; +} + +/* + * samsung_gpio_setcfg_2bit - Samsung 2bit style GPIO configuration. + * @chip: The gpio chip that is being configured. + * @off: The offset for the GPIO being configured. + * @cfg: The configuration value to set. + * + * This helper deal with the GPIO cases where the control register + * has two bits of configuration per gpio, which have the following + * functions: + * 00 = input + * 01 = output + * 1x = special function + */ + +static int samsung_gpio_setcfg_2bit(struct samsung_gpio_chip *chip, + unsigned int off, unsigned int cfg) +{ + void __iomem *reg = chip->base; + unsigned int shift = off * 2; + u32 con; + + if (samsung_gpio_is_cfg_special(cfg)) { + cfg &= 0xf; + if (cfg > 3) + return -EINVAL; + + cfg <<= shift; + } + + con = __raw_readl(reg); + con &= ~(0x3 << shift); + con |= cfg; + __raw_writel(con, reg); + + return 0; +} + +/* + * samsung_gpio_getcfg_2bit - Samsung 2bit style GPIO configuration read. + * @chip: The gpio chip that is being configured. + * @off: The offset for the GPIO being configured. + * + * The reverse of samsung_gpio_setcfg_2bit(). Will return a value which + * could be directly passed back to samsung_gpio_setcfg_2bit(), from the + * S3C_GPIO_SPECIAL() macro. + */ + +static unsigned int samsung_gpio_getcfg_2bit(struct samsung_gpio_chip *chip, + unsigned int off) +{ + u32 con; + + con = __raw_readl(chip->base); + con >>= off * 2; + con &= 3; + + /* this conversion works for IN and OUT as well as special mode */ + return S3C_GPIO_SPECIAL(con); +} + +/* + * samsung_gpio_setcfg_4bit - Samsung 4bit single register GPIO config. + * @chip: The gpio chip that is being configured. + * @off: The offset for the GPIO being configured. + * @cfg: The configuration value to set. + * + * This helper deal with the GPIO cases where the control register has 4 bits + * of control per GPIO, generally in the form of: + * 0000 = Input + * 0001 = Output + * others = Special functions (dependent on bank) + * + * Note, since the code to deal with the case where there are two control + * registers instead of one, we do not have a separate set of functions for + * each case. + */ + +static int samsung_gpio_setcfg_4bit(struct samsung_gpio_chip *chip, + unsigned int off, unsigned int cfg) +{ + void __iomem *reg = chip->base; + unsigned int shift = (off & 7) * 4; + u32 con; + + if (off < 8 && chip->chip.ngpio > 8) + reg -= 4; + + if (samsung_gpio_is_cfg_special(cfg)) { + cfg &= 0xf; + cfg <<= shift; + } + + con = __raw_readl(reg); + con &= ~(0xf << shift); + con |= cfg; + __raw_writel(con, reg); + + return 0; +} + +/* + * samsung_gpio_getcfg_4bit - Samsung 4bit single register GPIO config read. + * @chip: The gpio chip that is being configured. + * @off: The offset for the GPIO being configured. + * + * The reverse of samsung_gpio_setcfg_4bit(), turning a gpio configuration + * register setting into a value the software can use, such as could be passed + * to samsung_gpio_setcfg_4bit(). + * + * @sa samsung_gpio_getcfg_2bit + */ + +static unsigned samsung_gpio_getcfg_4bit(struct samsung_gpio_chip *chip, + unsigned int off) +{ + void __iomem *reg = chip->base; + unsigned int shift = (off & 7) * 4; + u32 con; + + if (off < 8 && chip->chip.ngpio > 8) + reg -= 4; + + con = __raw_readl(reg); + con >>= shift; + con &= 0xf; + + /* this conversion works for IN and OUT as well as special mode */ + return S3C_GPIO_SPECIAL(con); +} + +#ifdef CONFIG_PLAT_S3C24XX +/* + * s3c24xx_gpio_setcfg_abank - S3C24XX style GPIO configuration (Bank A) + * @chip: The gpio chip that is being configured. + * @off: The offset for the GPIO being configured. + * @cfg: The configuration value to set. + * + * This helper deal with the GPIO cases where the control register + * has one bit of configuration for the gpio, where setting the bit + * means the pin is in special function mode and unset means output. + */ + +static int s3c24xx_gpio_setcfg_abank(struct samsung_gpio_chip *chip, + unsigned int off, unsigned int cfg) +{ + void __iomem *reg = chip->base; + unsigned int shift = off; + u32 con; + + if (samsung_gpio_is_cfg_special(cfg)) { + cfg &= 0xf; + + /* Map output to 0, and SFN2 to 1 */ + cfg -= 1; + if (cfg > 1) + return -EINVAL; + + cfg <<= shift; + } + + con = __raw_readl(reg); + con &= ~(0x1 << shift); + con |= cfg; + __raw_writel(con, reg); + + return 0; +} + +/* + * s3c24xx_gpio_getcfg_abank - S3C24XX style GPIO configuration read (Bank A) + * @chip: The gpio chip that is being configured. + * @off: The offset for the GPIO being configured. + * + * The reverse of s3c24xx_gpio_setcfg_abank() turning an GPIO into a usable + * GPIO configuration value. + * + * @sa samsung_gpio_getcfg_2bit + * @sa samsung_gpio_getcfg_4bit + */ + +static unsigned s3c24xx_gpio_getcfg_abank(struct samsung_gpio_chip *chip, + unsigned int off) +{ + u32 con; + + con = __raw_readl(chip->base); + con >>= off; + con &= 1; + con++; + + return S3C_GPIO_SFN(con); +} +#endif + +#if defined(CONFIG_CPU_S5P6440) || defined(CONFIG_CPU_S5P6450) +static int s5p64x0_gpio_setcfg_rbank(struct samsung_gpio_chip *chip, + unsigned int off, unsigned int cfg) +{ + void __iomem *reg = chip->base; + unsigned int shift; + u32 con; + + switch (off) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + shift = (off & 7) * 4; + reg -= 4; + break; + case 6: + shift = ((off + 1) & 7) * 4; + reg -= 4; + default: + shift = ((off + 1) & 7) * 4; + break; + } + + if (samsung_gpio_is_cfg_special(cfg)) { + cfg &= 0xf; + cfg <<= shift; + } + + con = __raw_readl(reg); + con &= ~(0xf << shift); + con |= cfg; + __raw_writel(con, reg); + + return 0; +} +#endif + +static void __init samsung_gpiolib_set_cfg(struct samsung_gpio_cfg *chipcfg, + int nr_chips) +{ + for (; nr_chips > 0; nr_chips--, chipcfg++) { + if (!chipcfg->set_config) + chipcfg->set_config = samsung_gpio_setcfg_4bit; + if (!chipcfg->get_config) + chipcfg->get_config = samsung_gpio_getcfg_4bit; + if (!chipcfg->set_pull) + chipcfg->set_pull = samsung_gpio_setpull_updown; + if (!chipcfg->get_pull) + chipcfg->get_pull = samsung_gpio_getpull_updown; + } +} + +struct samsung_gpio_cfg s3c24xx_gpiocfg_default = { + .set_config = samsung_gpio_setcfg_2bit, + .get_config = samsung_gpio_getcfg_2bit, +}; + +#ifdef CONFIG_PLAT_S3C24XX +static struct samsung_gpio_cfg s3c24xx_gpiocfg_banka = { + .set_config = s3c24xx_gpio_setcfg_abank, + .get_config = s3c24xx_gpio_getcfg_abank, +}; +#endif + +#if defined(CONFIG_ARCH_EXYNOS4) || defined(CONFIG_ARCH_EXYNOS5) +static struct samsung_gpio_cfg exynos_gpio_cfg = { + .set_pull = exynos_gpio_setpull, + .get_pull = exynos_gpio_getpull, + .set_config = samsung_gpio_setcfg_4bit, + .get_config = samsung_gpio_getcfg_4bit, +}; +#endif + +#if defined(CONFIG_CPU_S5P6440) || defined(CONFIG_CPU_S5P6450) +static struct samsung_gpio_cfg s5p64x0_gpio_cfg_rbank = { + .cfg_eint = 0x3, + .set_config = s5p64x0_gpio_setcfg_rbank, + .get_config = samsung_gpio_getcfg_4bit, + .set_pull = samsung_gpio_setpull_updown, + .get_pull = samsung_gpio_getpull_updown, +}; +#endif + +static struct samsung_gpio_cfg samsung_gpio_cfgs[] = { + [0] = { + .cfg_eint = 0x0, + }, + [1] = { + .cfg_eint = 0x3, + }, + [2] = { + .cfg_eint = 0x7, + }, + [3] = { + .cfg_eint = 0xF, + }, + [4] = { + .cfg_eint = 0x0, + .set_config = samsung_gpio_setcfg_2bit, + .get_config = samsung_gpio_getcfg_2bit, + }, + [5] = { + .cfg_eint = 0x2, + .set_config = samsung_gpio_setcfg_2bit, + .get_config = samsung_gpio_getcfg_2bit, + }, + [6] = { + .cfg_eint = 0x3, + .set_config = samsung_gpio_setcfg_2bit, + .get_config = samsung_gpio_getcfg_2bit, + }, + [7] = { + .set_config = samsung_gpio_setcfg_2bit, + .get_config = samsung_gpio_getcfg_2bit, + }, + [8] = { + .set_pull = exynos_gpio_setpull, + .get_pull = exynos_gpio_getpull, + }, + [9] = { + .cfg_eint = 0x3, + .set_pull = exynos_gpio_setpull, + .get_pull = exynos_gpio_getpull, + } +}; + +/* + * Default routines for controlling GPIO, based on the original S3C24XX + * GPIO functions which deal with the case where each gpio bank of the + * chip is as following: + * + * base + 0x00: Control register, 2 bits per gpio + * gpio n: 2 bits starting at (2*n) + * 00 = input, 01 = output, others mean special-function + * base + 0x04: Data register, 1 bit per gpio + * bit n: data bit n +*/ + +static int samsung_gpiolib_2bit_input(struct gpio_chip *chip, unsigned offset) +{ + struct samsung_gpio_chip *ourchip = to_samsung_gpio(chip); + void __iomem *base = ourchip->base; + unsigned long flags; + unsigned long con; + + samsung_gpio_lock(ourchip, flags); + + con = __raw_readl(base + 0x00); + con &= ~(3 << (offset * 2)); + + __raw_writel(con, base + 0x00); + + samsung_gpio_unlock(ourchip, flags); + return 0; +} + +static int samsung_gpiolib_2bit_output(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct samsung_gpio_chip *ourchip = to_samsung_gpio(chip); + void __iomem *base = ourchip->base; + unsigned long flags; + unsigned long dat; + unsigned long con; + + samsung_gpio_lock(ourchip, flags); + + dat = __raw_readl(base + 0x04); + dat &= ~(1 << offset); + if (value) + dat |= 1 << offset; + __raw_writel(dat, base + 0x04); + + con = __raw_readl(base + 0x00); + con &= ~(3 << (offset * 2)); + con |= 1 << (offset * 2); + + __raw_writel(con, base + 0x00); + __raw_writel(dat, base + 0x04); + + samsung_gpio_unlock(ourchip, flags); + return 0; +} + +/* + * The samsung_gpiolib_4bit routines are to control the gpio banks where + * the gpio configuration register (GPxCON) has 4 bits per GPIO, as the + * following example: + * + * base + 0x00: Control register, 4 bits per gpio + * gpio n: 4 bits starting at (4*n) + * 0000 = input, 0001 = output, others mean special-function + * base + 0x04: Data register, 1 bit per gpio + * bit n: data bit n + * + * Note, since the data register is one bit per gpio and is at base + 0x4 + * we can use samsung_gpiolib_get and samsung_gpiolib_set to change the + * state of the output. + */ + +static int samsung_gpiolib_4bit_input(struct gpio_chip *chip, + unsigned int offset) +{ + struct samsung_gpio_chip *ourchip = to_samsung_gpio(chip); + void __iomem *base = ourchip->base; + unsigned long con; + + con = __raw_readl(base + GPIOCON_OFF); + con &= ~(0xf << con_4bit_shift(offset)); + __raw_writel(con, base + GPIOCON_OFF); + + gpio_dbg("%s: %p: CON now %08lx\n", __func__, base, con); + + return 0; +} + +static int samsung_gpiolib_4bit_output(struct gpio_chip *chip, + unsigned int offset, int value) +{ + struct samsung_gpio_chip *ourchip = to_samsung_gpio(chip); + void __iomem *base = ourchip->base; + unsigned long con; + unsigned long dat; + + con = __raw_readl(base + GPIOCON_OFF); + con &= ~(0xf << con_4bit_shift(offset)); + con |= 0x1 << con_4bit_shift(offset); + + dat = __raw_readl(base + GPIODAT_OFF); + + if (value) + dat |= 1 << offset; + else + dat &= ~(1 << offset); + + __raw_writel(dat, base + GPIODAT_OFF); + __raw_writel(con, base + GPIOCON_OFF); + __raw_writel(dat, base + GPIODAT_OFF); + + gpio_dbg("%s: %p: CON %08lx, DAT %08lx\n", __func__, base, con, dat); + + return 0; +} + +/* + * The next set of routines are for the case where the GPIO configuration + * registers are 4 bits per GPIO but there is more than one register (the + * bank has more than 8 GPIOs. + * + * This case is the similar to the 4 bit case, but the registers are as + * follows: + * + * base + 0x00: Control register, 4 bits per gpio (lower 8 GPIOs) + * gpio n: 4 bits starting at (4*n) + * 0000 = input, 0001 = output, others mean special-function + * base + 0x04: Control register, 4 bits per gpio (up to 8 additions GPIOs) + * gpio n: 4 bits starting at (4*n) + * 0000 = input, 0001 = output, others mean special-function + * base + 0x08: Data register, 1 bit per gpio + * bit n: data bit n + * + * To allow us to use the samsung_gpiolib_get and samsung_gpiolib_set + * routines we store the 'base + 0x4' address so that these routines see + * the data register at ourchip->base + 0x04. + */ + +static int samsung_gpiolib_4bit2_input(struct gpio_chip *chip, + unsigned int offset) +{ + struct samsung_gpio_chip *ourchip = to_samsung_gpio(chip); + void __iomem *base = ourchip->base; + void __iomem *regcon = base; + unsigned long con; + + if (offset > 7) + offset -= 8; + else + regcon -= 4; + + con = __raw_readl(regcon); + con &= ~(0xf << con_4bit_shift(offset)); + __raw_writel(con, regcon); + + gpio_dbg("%s: %p: CON %08lx\n", __func__, base, con); + + return 0; +} + +static int samsung_gpiolib_4bit2_output(struct gpio_chip *chip, + unsigned int offset, int value) +{ + struct samsung_gpio_chip *ourchip = to_samsung_gpio(chip); + void __iomem *base = ourchip->base; + void __iomem *regcon = base; + unsigned long con; + unsigned long dat; + unsigned con_offset = offset; + + if (con_offset > 7) + con_offset -= 8; + else + regcon -= 4; + + con = __raw_readl(regcon); + con &= ~(0xf << con_4bit_shift(con_offset)); + con |= 0x1 << con_4bit_shift(con_offset); + + dat = __raw_readl(base + GPIODAT_OFF); + + if (value) + dat |= 1 << offset; + else + dat &= ~(1 << offset); + + __raw_writel(dat, base + GPIODAT_OFF); + __raw_writel(con, regcon); + __raw_writel(dat, base + GPIODAT_OFF); + + gpio_dbg("%s: %p: CON %08lx, DAT %08lx\n", __func__, base, con, dat); + + return 0; +} + +#ifdef CONFIG_PLAT_S3C24XX +/* The next set of routines are for the case of s3c24xx bank a */ + +static int s3c24xx_gpiolib_banka_input(struct gpio_chip *chip, unsigned offset) +{ + return -EINVAL; +} + +static int s3c24xx_gpiolib_banka_output(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct samsung_gpio_chip *ourchip = to_samsung_gpio(chip); + void __iomem *base = ourchip->base; + unsigned long flags; + unsigned long dat; + unsigned long con; + + local_irq_save(flags); + + con = __raw_readl(base + 0x00); + dat = __raw_readl(base + 0x04); + + dat &= ~(1 << offset); + if (value) + dat |= 1 << offset; + + __raw_writel(dat, base + 0x04); + + con &= ~(1 << offset); + + __raw_writel(con, base + 0x00); + __raw_writel(dat, base + 0x04); + + local_irq_restore(flags); + return 0; +} +#endif + +/* The next set of routines are for the case of s5p64x0 bank r */ + +static int s5p64x0_gpiolib_rbank_input(struct gpio_chip *chip, + unsigned int offset) +{ + struct samsung_gpio_chip *ourchip = to_samsung_gpio(chip); + void __iomem *base = ourchip->base; + void __iomem *regcon = base; + unsigned long con; + unsigned long flags; + + switch (offset) { + case 6: + offset += 1; + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + regcon -= 4; + break; + default: + offset -= 7; + break; + } + + samsung_gpio_lock(ourchip, flags); + + con = __raw_readl(regcon); + con &= ~(0xf << con_4bit_shift(offset)); + __raw_writel(con, regcon); + + samsung_gpio_unlock(ourchip, flags); + + return 0; +} + +static int s5p64x0_gpiolib_rbank_output(struct gpio_chip *chip, + unsigned int offset, int value) +{ + struct samsung_gpio_chip *ourchip = to_samsung_gpio(chip); + void __iomem *base = ourchip->base; + void __iomem *regcon = base; + unsigned long con; + unsigned long dat; + unsigned long flags; + unsigned con_offset = offset; + + switch (con_offset) { + case 6: + con_offset += 1; + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + regcon -= 4; + break; + default: + con_offset -= 7; + break; + } + + samsung_gpio_lock(ourchip, flags); + + con = __raw_readl(regcon); + con &= ~(0xf << con_4bit_shift(con_offset)); + con |= 0x1 << con_4bit_shift(con_offset); + + dat = __raw_readl(base + GPIODAT_OFF); + if (value) + dat |= 1 << offset; + else + dat &= ~(1 << offset); + + __raw_writel(con, regcon); + __raw_writel(dat, base + GPIODAT_OFF); + + samsung_gpio_unlock(ourchip, flags); + + return 0; +} + +static void samsung_gpiolib_set(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct samsung_gpio_chip *ourchip = to_samsung_gpio(chip); + void __iomem *base = ourchip->base; + unsigned long flags; + unsigned long dat; + + samsung_gpio_lock(ourchip, flags); + + dat = __raw_readl(base + 0x04); + dat &= ~(1 << offset); + if (value) + dat |= 1 << offset; + __raw_writel(dat, base + 0x04); + + samsung_gpio_unlock(ourchip, flags); +} + +static int samsung_gpiolib_get(struct gpio_chip *chip, unsigned offset) +{ + struct samsung_gpio_chip *ourchip = to_samsung_gpio(chip); + unsigned long val; + + val = __raw_readl(ourchip->base + 0x04); + val >>= offset; + val &= 1; + + return val; +} + +/* + * CONFIG_S3C_GPIO_TRACK enables the tracking of the s3c specific gpios + * for use with the configuration calls, and other parts of the s3c gpiolib + * support code. + * + * Not all s3c support code will need this, as some configurations of cpu + * may only support one or two different configuration options and have an + * easy gpio to samsung_gpio_chip mapping function. If this is the case, then + * the machine support file should provide its own samsung_gpiolib_getchip() + * and any other necessary functions. + */ + +#ifdef CONFIG_S3C_GPIO_TRACK +struct samsung_gpio_chip *s3c_gpios[S3C_GPIO_END]; + +static __init void s3c_gpiolib_track(struct samsung_gpio_chip *chip) +{ + unsigned int gpn; + int i; + + gpn = chip->chip.base; + for (i = 0; i < chip->chip.ngpio; i++, gpn++) { + BUG_ON(gpn >= ARRAY_SIZE(s3c_gpios)); + s3c_gpios[gpn] = chip; + } +} +#endif /* CONFIG_S3C_GPIO_TRACK */ + +/* + * samsung_gpiolib_add() - add the Samsung gpio_chip. + * @chip: The chip to register + * + * This is a wrapper to gpiochip_add() that takes our specific gpio chip + * information and makes the necessary alterations for the platform and + * notes the information for use with the configuration systems and any + * other parts of the system. + */ + +static void __init samsung_gpiolib_add(struct samsung_gpio_chip *chip) +{ + struct gpio_chip *gc = &chip->chip; + int ret; + + BUG_ON(!chip->base); + BUG_ON(!gc->label); + BUG_ON(!gc->ngpio); + + spin_lock_init(&chip->lock); + + if (!gc->direction_input) + gc->direction_input = samsung_gpiolib_2bit_input; + if (!gc->direction_output) + gc->direction_output = samsung_gpiolib_2bit_output; + if (!gc->set) + gc->set = samsung_gpiolib_set; + if (!gc->get) + gc->get = samsung_gpiolib_get; + +#ifdef CONFIG_PM + if (chip->pm != NULL) { + if (!chip->pm->save || !chip->pm->resume) + printk(KERN_ERR "gpio: %s has missing PM functions\n", + gc->label); + } else + printk(KERN_ERR "gpio: %s has no PM function\n", gc->label); +#endif + + /* gpiochip_add() prints own failure message on error. */ + ret = gpiochip_add(gc); + if (ret >= 0) + s3c_gpiolib_track(chip); +} + +static void __init s3c24xx_gpiolib_add_chips(struct samsung_gpio_chip *chip, + int nr_chips, void __iomem *base) +{ + int i; + struct gpio_chip *gc = &chip->chip; + + for (i = 0 ; i < nr_chips; i++, chip++) { + /* skip banks not present on SoC */ + if (chip->chip.base >= S3C_GPIO_END) + continue; + + if (!chip->config) + chip->config = &s3c24xx_gpiocfg_default; + if (!chip->pm) + chip->pm = __gpio_pm(&samsung_gpio_pm_2bit); + if ((base != NULL) && (chip->base == NULL)) + chip->base = base + ((i) * 0x10); + + if (!gc->direction_input) + gc->direction_input = samsung_gpiolib_2bit_input; + if (!gc->direction_output) + gc->direction_output = samsung_gpiolib_2bit_output; + + samsung_gpiolib_add(chip); + } +} + +static void __init samsung_gpiolib_add_2bit_chips(struct samsung_gpio_chip *chip, + int nr_chips, void __iomem *base, + unsigned int offset) +{ + int i; + + for (i = 0 ; i < nr_chips; i++, chip++) { + chip->chip.direction_input = samsung_gpiolib_2bit_input; + chip->chip.direction_output = samsung_gpiolib_2bit_output; + + if (!chip->config) + chip->config = &samsung_gpio_cfgs[7]; + if (!chip->pm) + chip->pm = __gpio_pm(&samsung_gpio_pm_2bit); + if ((base != NULL) && (chip->base == NULL)) + chip->base = base + ((i) * offset); + + samsung_gpiolib_add(chip); + } +} + +/* + * samsung_gpiolib_add_4bit_chips - 4bit single register GPIO config. + * @chip: The gpio chip that is being configured. + * @nr_chips: The no of chips (gpio ports) for the GPIO being configured. + * + * This helper deal with the GPIO cases where the control register has 4 bits + * of control per GPIO, generally in the form of: + * 0000 = Input + * 0001 = Output + * others = Special functions (dependent on bank) + * + * Note, since the code to deal with the case where there are two control + * registers instead of one, we do not have a separate set of function + * (samsung_gpiolib_add_4bit2_chips)for each case. + */ + +static void __init samsung_gpiolib_add_4bit_chips(struct samsung_gpio_chip *chip, + int nr_chips, void __iomem *base) +{ + int i; + + for (i = 0 ; i < nr_chips; i++, chip++) { + chip->chip.direction_input = samsung_gpiolib_4bit_input; + chip->chip.direction_output = samsung_gpiolib_4bit_output; + + if (!chip->config) + chip->config = &samsung_gpio_cfgs[2]; + if (!chip->pm) + chip->pm = __gpio_pm(&samsung_gpio_pm_4bit); + if ((base != NULL) && (chip->base == NULL)) + chip->base = base + ((i) * 0x20); + + samsung_gpiolib_add(chip); + } +} + +static void __init samsung_gpiolib_add_4bit2_chips(struct samsung_gpio_chip *chip, + int nr_chips) +{ + for (; nr_chips > 0; nr_chips--, chip++) { + chip->chip.direction_input = samsung_gpiolib_4bit2_input; + chip->chip.direction_output = samsung_gpiolib_4bit2_output; + + if (!chip->config) + chip->config = &samsung_gpio_cfgs[2]; + if (!chip->pm) + chip->pm = __gpio_pm(&samsung_gpio_pm_4bit); + + samsung_gpiolib_add(chip); + } +} + +static void __init s5p64x0_gpiolib_add_rbank(struct samsung_gpio_chip *chip, + int nr_chips) +{ + for (; nr_chips > 0; nr_chips--, chip++) { + chip->chip.direction_input = s5p64x0_gpiolib_rbank_input; + chip->chip.direction_output = s5p64x0_gpiolib_rbank_output; + + if (!chip->pm) + chip->pm = __gpio_pm(&samsung_gpio_pm_4bit); + + samsung_gpiolib_add(chip); + } +} + +int samsung_gpiolib_to_irq(struct gpio_chip *chip, unsigned int offset) +{ + struct samsung_gpio_chip *samsung_chip = container_of(chip, struct samsung_gpio_chip, chip); + + return samsung_chip->irq_base + offset; +} + +#ifdef CONFIG_PLAT_S3C24XX +static int s3c24xx_gpiolib_fbank_to_irq(struct gpio_chip *chip, unsigned offset) +{ + if (offset < 4) + return IRQ_EINT0 + offset; + + if (offset < 8) + return IRQ_EINT4 + offset - 4; + + return -EINVAL; +} +#endif + +#ifdef CONFIG_PLAT_S3C64XX +static int s3c64xx_gpiolib_mbank_to_irq(struct gpio_chip *chip, unsigned pin) +{ + return pin < 5 ? IRQ_EINT(23) + pin : -ENXIO; +} + +static int s3c64xx_gpiolib_lbank_to_irq(struct gpio_chip *chip, unsigned pin) +{ + return pin >= 8 ? IRQ_EINT(16) + pin - 8 : -ENXIO; +} +#endif + +struct samsung_gpio_chip s3c24xx_gpios[] = { +#ifdef CONFIG_PLAT_S3C24XX + { + .config = &s3c24xx_gpiocfg_banka, + .chip = { + .base = S3C2410_GPA(0), + .owner = THIS_MODULE, + .label = "GPIOA", + .ngpio = 24, + .direction_input = s3c24xx_gpiolib_banka_input, + .direction_output = s3c24xx_gpiolib_banka_output, + }, + }, { + .chip = { + .base = S3C2410_GPB(0), + .owner = THIS_MODULE, + .label = "GPIOB", + .ngpio = 16, + }, + }, { + .chip = { + .base = S3C2410_GPC(0), + .owner = THIS_MODULE, + .label = "GPIOC", + .ngpio = 16, + }, + }, { + .chip = { + .base = S3C2410_GPD(0), + .owner = THIS_MODULE, + .label = "GPIOD", + .ngpio = 16, + }, + }, { + .chip = { + .base = S3C2410_GPE(0), + .label = "GPIOE", + .owner = THIS_MODULE, + .ngpio = 16, + }, + }, { + .chip = { + .base = S3C2410_GPF(0), + .owner = THIS_MODULE, + .label = "GPIOF", + .ngpio = 8, + .to_irq = s3c24xx_gpiolib_fbank_to_irq, + }, + }, { + .irq_base = IRQ_EINT8, + .chip = { + .base = S3C2410_GPG(0), + .owner = THIS_MODULE, + .label = "GPIOG", + .ngpio = 16, + .to_irq = samsung_gpiolib_to_irq, + }, + }, { + .chip = { + .base = S3C2410_GPH(0), + .owner = THIS_MODULE, + .label = "GPIOH", + .ngpio = 11, + }, + }, + /* GPIOS for the S3C2443 and later devices. */ + { + .base = S3C2440_GPJCON, + .chip = { + .base = S3C2410_GPJ(0), + .owner = THIS_MODULE, + .label = "GPIOJ", + .ngpio = 16, + }, + }, { + .base = S3C2443_GPKCON, + .chip = { + .base = S3C2410_GPK(0), + .owner = THIS_MODULE, + .label = "GPIOK", + .ngpio = 16, + }, + }, { + .base = S3C2443_GPLCON, + .chip = { + .base = S3C2410_GPL(0), + .owner = THIS_MODULE, + .label = "GPIOL", + .ngpio = 15, + }, + }, { + .base = S3C2443_GPMCON, + .chip = { + .base = S3C2410_GPM(0), + .owner = THIS_MODULE, + .label = "GPIOM", + .ngpio = 2, + }, + }, +#endif +}; + +/* + * GPIO bank summary: + * + * Bank GPIOs Style SlpCon ExtInt Group + * A 8 4Bit Yes 1 + * B 7 4Bit Yes 1 + * C 8 4Bit Yes 2 + * D 5 4Bit Yes 3 + * E 5 4Bit Yes None + * F 16 2Bit Yes 4 [1] + * G 7 4Bit Yes 5 + * H 10 4Bit[2] Yes 6 + * I 16 2Bit Yes None + * J 12 2Bit Yes None + * K 16 4Bit[2] No None + * L 15 4Bit[2] No None + * M 6 4Bit No IRQ_EINT + * N 16 2Bit No IRQ_EINT + * O 16 2Bit Yes 7 + * P 15 2Bit Yes 8 + * Q 9 2Bit Yes 9 + * + * [1] BANKF pins 14,15 do not form part of the external interrupt sources + * [2] BANK has two control registers, GPxCON0 and GPxCON1 + */ + +static struct samsung_gpio_chip s3c64xx_gpios_4bit[] = { +#ifdef CONFIG_PLAT_S3C64XX + { + .chip = { + .base = S3C64XX_GPA(0), + .ngpio = S3C64XX_GPIO_A_NR, + .label = "GPA", + }, + }, { + .chip = { + .base = S3C64XX_GPB(0), + .ngpio = S3C64XX_GPIO_B_NR, + .label = "GPB", + }, + }, { + .chip = { + .base = S3C64XX_GPC(0), + .ngpio = S3C64XX_GPIO_C_NR, + .label = "GPC", + }, + }, { + .chip = { + .base = S3C64XX_GPD(0), + .ngpio = S3C64XX_GPIO_D_NR, + .label = "GPD", + }, + }, { + .config = &samsung_gpio_cfgs[0], + .chip = { + .base = S3C64XX_GPE(0), + .ngpio = S3C64XX_GPIO_E_NR, + .label = "GPE", + }, + }, { + .base = S3C64XX_GPG_BASE, + .chip = { + .base = S3C64XX_GPG(0), + .ngpio = S3C64XX_GPIO_G_NR, + .label = "GPG", + }, + }, { + .base = S3C64XX_GPM_BASE, + .config = &samsung_gpio_cfgs[1], + .chip = { + .base = S3C64XX_GPM(0), + .ngpio = S3C64XX_GPIO_M_NR, + .label = "GPM", + .to_irq = s3c64xx_gpiolib_mbank_to_irq, + }, + }, +#endif +}; + +static struct samsung_gpio_chip s3c64xx_gpios_4bit2[] = { +#ifdef CONFIG_PLAT_S3C64XX + { + .base = S3C64XX_GPH_BASE + 0x4, + .chip = { + .base = S3C64XX_GPH(0), + .ngpio = S3C64XX_GPIO_H_NR, + .label = "GPH", + }, + }, { + .base = S3C64XX_GPK_BASE + 0x4, + .config = &samsung_gpio_cfgs[0], + .chip = { + .base = S3C64XX_GPK(0), + .ngpio = S3C64XX_GPIO_K_NR, + .label = "GPK", + }, + }, { + .base = S3C64XX_GPL_BASE + 0x4, + .config = &samsung_gpio_cfgs[1], + .chip = { + .base = S3C64XX_GPL(0), + .ngpio = S3C64XX_GPIO_L_NR, + .label = "GPL", + .to_irq = s3c64xx_gpiolib_lbank_to_irq, + }, + }, +#endif +}; + +static struct samsung_gpio_chip s3c64xx_gpios_2bit[] = { +#ifdef CONFIG_PLAT_S3C64XX + { + .base = S3C64XX_GPF_BASE, + .config = &samsung_gpio_cfgs[6], + .chip = { + .base = S3C64XX_GPF(0), + .ngpio = S3C64XX_GPIO_F_NR, + .label = "GPF", + }, + }, { + .config = &samsung_gpio_cfgs[7], + .chip = { + .base = S3C64XX_GPI(0), + .ngpio = S3C64XX_GPIO_I_NR, + .label = "GPI", + }, + }, { + .config = &samsung_gpio_cfgs[7], + .chip = { + .base = S3C64XX_GPJ(0), + .ngpio = S3C64XX_GPIO_J_NR, + .label = "GPJ", + }, + }, { + .config = &samsung_gpio_cfgs[6], + .chip = { + .base = S3C64XX_GPO(0), + .ngpio = S3C64XX_GPIO_O_NR, + .label = "GPO", + }, + }, { + .config = &samsung_gpio_cfgs[6], + .chip = { + .base = S3C64XX_GPP(0), + .ngpio = S3C64XX_GPIO_P_NR, + .label = "GPP", + }, + }, { + .config = &samsung_gpio_cfgs[6], + .chip = { + .base = S3C64XX_GPQ(0), + .ngpio = S3C64XX_GPIO_Q_NR, + .label = "GPQ", + }, + }, { + .base = S3C64XX_GPN_BASE, + .irq_base = IRQ_EINT(0), + .config = &samsung_gpio_cfgs[5], + .chip = { + .base = S3C64XX_GPN(0), + .ngpio = S3C64XX_GPIO_N_NR, + .label = "GPN", + .to_irq = samsung_gpiolib_to_irq, + }, + }, +#endif +}; + +/* + * S5P6440 GPIO bank summary: + * + * Bank GPIOs Style SlpCon ExtInt Group + * A 6 4Bit Yes 1 + * B 7 4Bit Yes 1 + * C 8 4Bit Yes 2 + * F 2 2Bit Yes 4 [1] + * G 7 4Bit Yes 5 + * H 10 4Bit[2] Yes 6 + * I 16 2Bit Yes None + * J 12 2Bit Yes None + * N 16 2Bit No IRQ_EINT + * P 8 2Bit Yes 8 + * R 15 4Bit[2] Yes 8 + */ + +static struct samsung_gpio_chip s5p6440_gpios_4bit[] = { +#ifdef CONFIG_CPU_S5P6440 + { + .chip = { + .base = S5P6440_GPA(0), + .ngpio = S5P6440_GPIO_A_NR, + .label = "GPA", + }, + }, { + .chip = { + .base = S5P6440_GPB(0), + .ngpio = S5P6440_GPIO_B_NR, + .label = "GPB", + }, + }, { + .chip = { + .base = S5P6440_GPC(0), + .ngpio = S5P6440_GPIO_C_NR, + .label = "GPC", + }, + }, { + .base = S5P64X0_GPG_BASE, + .chip = { + .base = S5P6440_GPG(0), + .ngpio = S5P6440_GPIO_G_NR, + .label = "GPG", + }, + }, +#endif +}; + +static struct samsung_gpio_chip s5p6440_gpios_4bit2[] = { +#ifdef CONFIG_CPU_S5P6440 + { + .base = S5P64X0_GPH_BASE + 0x4, + .chip = { + .base = S5P6440_GPH(0), + .ngpio = S5P6440_GPIO_H_NR, + .label = "GPH", + }, + }, +#endif +}; + +static struct samsung_gpio_chip s5p6440_gpios_rbank[] = { +#ifdef CONFIG_CPU_S5P6440 + { + .base = S5P64X0_GPR_BASE + 0x4, + .config = &s5p64x0_gpio_cfg_rbank, + .chip = { + .base = S5P6440_GPR(0), + .ngpio = S5P6440_GPIO_R_NR, + .label = "GPR", + }, + }, +#endif +}; + +static struct samsung_gpio_chip s5p6440_gpios_2bit[] = { +#ifdef CONFIG_CPU_S5P6440 + { + .base = S5P64X0_GPF_BASE, + .config = &samsung_gpio_cfgs[6], + .chip = { + .base = S5P6440_GPF(0), + .ngpio = S5P6440_GPIO_F_NR, + .label = "GPF", + }, + }, { + .base = S5P64X0_GPI_BASE, + .config = &samsung_gpio_cfgs[4], + .chip = { + .base = S5P6440_GPI(0), + .ngpio = S5P6440_GPIO_I_NR, + .label = "GPI", + }, + }, { + .base = S5P64X0_GPJ_BASE, + .config = &samsung_gpio_cfgs[4], + .chip = { + .base = S5P6440_GPJ(0), + .ngpio = S5P6440_GPIO_J_NR, + .label = "GPJ", + }, + }, { + .base = S5P64X0_GPN_BASE, + .config = &samsung_gpio_cfgs[5], + .chip = { + .base = S5P6440_GPN(0), + .ngpio = S5P6440_GPIO_N_NR, + .label = "GPN", + }, + }, { + .base = S5P64X0_GPP_BASE, + .config = &samsung_gpio_cfgs[6], + .chip = { + .base = S5P6440_GPP(0), + .ngpio = S5P6440_GPIO_P_NR, + .label = "GPP", + }, + }, +#endif +}; + +/* + * S5P6450 GPIO bank summary: + * + * Bank GPIOs Style SlpCon ExtInt Group + * A 6 4Bit Yes 1 + * B 7 4Bit Yes 1 + * C 8 4Bit Yes 2 + * D 8 4Bit Yes None + * F 2 2Bit Yes None + * G 14 4Bit[2] Yes 5 + * H 10 4Bit[2] Yes 6 + * I 16 2Bit Yes None + * J 12 2Bit Yes None + * K 5 4Bit Yes None + * N 16 2Bit No IRQ_EINT + * P 11 2Bit Yes 8 + * Q 14 2Bit Yes None + * R 15 4Bit[2] Yes None + * S 8 2Bit Yes None + * + * [1] BANKF pins 14,15 do not form part of the external interrupt sources + * [2] BANK has two control registers, GPxCON0 and GPxCON1 + */ + +static struct samsung_gpio_chip s5p6450_gpios_4bit[] = { +#ifdef CONFIG_CPU_S5P6450 + { + .chip = { + .base = S5P6450_GPA(0), + .ngpio = S5P6450_GPIO_A_NR, + .label = "GPA", + }, + }, { + .chip = { + .base = S5P6450_GPB(0), + .ngpio = S5P6450_GPIO_B_NR, + .label = "GPB", + }, + }, { + .chip = { + .base = S5P6450_GPC(0), + .ngpio = S5P6450_GPIO_C_NR, + .label = "GPC", + }, + }, { + .chip = { + .base = S5P6450_GPD(0), + .ngpio = S5P6450_GPIO_D_NR, + .label = "GPD", + }, + }, { + .base = S5P6450_GPK_BASE, + .chip = { + .base = S5P6450_GPK(0), + .ngpio = S5P6450_GPIO_K_NR, + .label = "GPK", + }, + }, +#endif +}; + +static struct samsung_gpio_chip s5p6450_gpios_4bit2[] = { +#ifdef CONFIG_CPU_S5P6450 + { + .base = S5P64X0_GPG_BASE + 0x4, + .chip = { + .base = S5P6450_GPG(0), + .ngpio = S5P6450_GPIO_G_NR, + .label = "GPG", + }, + }, { + .base = S5P64X0_GPH_BASE + 0x4, + .chip = { + .base = S5P6450_GPH(0), + .ngpio = S5P6450_GPIO_H_NR, + .label = "GPH", + }, + }, +#endif +}; + +static struct samsung_gpio_chip s5p6450_gpios_rbank[] = { +#ifdef CONFIG_CPU_S5P6450 + { + .base = S5P64X0_GPR_BASE + 0x4, + .config = &s5p64x0_gpio_cfg_rbank, + .chip = { + .base = S5P6450_GPR(0), + .ngpio = S5P6450_GPIO_R_NR, + .label = "GPR", + }, + }, +#endif +}; + +static struct samsung_gpio_chip s5p6450_gpios_2bit[] = { +#ifdef CONFIG_CPU_S5P6450 + { + .base = S5P64X0_GPF_BASE, + .config = &samsung_gpio_cfgs[6], + .chip = { + .base = S5P6450_GPF(0), + .ngpio = S5P6450_GPIO_F_NR, + .label = "GPF", + }, + }, { + .base = S5P64X0_GPI_BASE, + .config = &samsung_gpio_cfgs[4], + .chip = { + .base = S5P6450_GPI(0), + .ngpio = S5P6450_GPIO_I_NR, + .label = "GPI", + }, + }, { + .base = S5P64X0_GPJ_BASE, + .config = &samsung_gpio_cfgs[4], + .chip = { + .base = S5P6450_GPJ(0), + .ngpio = S5P6450_GPIO_J_NR, + .label = "GPJ", + }, + }, { + .base = S5P64X0_GPN_BASE, + .config = &samsung_gpio_cfgs[5], + .chip = { + .base = S5P6450_GPN(0), + .ngpio = S5P6450_GPIO_N_NR, + .label = "GPN", + }, + }, { + .base = S5P64X0_GPP_BASE, + .config = &samsung_gpio_cfgs[6], + .chip = { + .base = S5P6450_GPP(0), + .ngpio = S5P6450_GPIO_P_NR, + .label = "GPP", + }, + }, { + .base = S5P6450_GPQ_BASE, + .config = &samsung_gpio_cfgs[5], + .chip = { + .base = S5P6450_GPQ(0), + .ngpio = S5P6450_GPIO_Q_NR, + .label = "GPQ", + }, + }, { + .base = S5P6450_GPS_BASE, + .config = &samsung_gpio_cfgs[6], + .chip = { + .base = S5P6450_GPS(0), + .ngpio = S5P6450_GPIO_S_NR, + .label = "GPS", + }, + }, +#endif +}; + +/* + * S5PC100 GPIO bank summary: + * + * Bank GPIOs Style INT Type + * A0 8 4Bit GPIO_INT0 + * A1 5 4Bit GPIO_INT1 + * B 8 4Bit GPIO_INT2 + * C 5 4Bit GPIO_INT3 + * D 7 4Bit GPIO_INT4 + * E0 8 4Bit GPIO_INT5 + * E1 6 4Bit GPIO_INT6 + * F0 8 4Bit GPIO_INT7 + * F1 8 4Bit GPIO_INT8 + * F2 8 4Bit GPIO_INT9 + * F3 4 4Bit GPIO_INT10 + * G0 8 4Bit GPIO_INT11 + * G1 3 4Bit GPIO_INT12 + * G2 7 4Bit GPIO_INT13 + * G3 7 4Bit GPIO_INT14 + * H0 8 4Bit WKUP_INT + * H1 8 4Bit WKUP_INT + * H2 8 4Bit WKUP_INT + * H3 8 4Bit WKUP_INT + * I 8 4Bit GPIO_INT15 + * J0 8 4Bit GPIO_INT16 + * J1 5 4Bit GPIO_INT17 + * J2 8 4Bit GPIO_INT18 + * J3 8 4Bit GPIO_INT19 + * J4 4 4Bit GPIO_INT20 + * K0 8 4Bit None + * K1 6 4Bit None + * K2 8 4Bit None + * K3 8 4Bit None + * L0 8 4Bit None + * L1 8 4Bit None + * L2 8 4Bit None + * L3 8 4Bit None + */ + +static struct samsung_gpio_chip s5pc100_gpios_4bit[] = { +#ifdef CONFIG_CPU_S5PC100 + { + .chip = { + .base = S5PC100_GPA0(0), + .ngpio = S5PC100_GPIO_A0_NR, + .label = "GPA0", + }, + }, { + .chip = { + .base = S5PC100_GPA1(0), + .ngpio = S5PC100_GPIO_A1_NR, + .label = "GPA1", + }, + }, { + .chip = { + .base = S5PC100_GPB(0), + .ngpio = S5PC100_GPIO_B_NR, + .label = "GPB", + }, + }, { + .chip = { + .base = S5PC100_GPC(0), + .ngpio = S5PC100_GPIO_C_NR, + .label = "GPC", + }, + }, { + .chip = { + .base = S5PC100_GPD(0), + .ngpio = S5PC100_GPIO_D_NR, + .label = "GPD", + }, + }, { + .chip = { + .base = S5PC100_GPE0(0), + .ngpio = S5PC100_GPIO_E0_NR, + .label = "GPE0", + }, + }, { + .chip = { + .base = S5PC100_GPE1(0), + .ngpio = S5PC100_GPIO_E1_NR, + .label = "GPE1", + }, + }, { + .chip = { + .base = S5PC100_GPF0(0), + .ngpio = S5PC100_GPIO_F0_NR, + .label = "GPF0", + }, + }, { + .chip = { + .base = S5PC100_GPF1(0), + .ngpio = S5PC100_GPIO_F1_NR, + .label = "GPF1", + }, + }, { + .chip = { + .base = S5PC100_GPF2(0), + .ngpio = S5PC100_GPIO_F2_NR, + .label = "GPF2", + }, + }, { + .chip = { + .base = S5PC100_GPF3(0), + .ngpio = S5PC100_GPIO_F3_NR, + .label = "GPF3", + }, + }, { + .chip = { + .base = S5PC100_GPG0(0), + .ngpio = S5PC100_GPIO_G0_NR, + .label = "GPG0", + }, + }, { + .chip = { + .base = S5PC100_GPG1(0), + .ngpio = S5PC100_GPIO_G1_NR, + .label = "GPG1", + }, + }, { + .chip = { + .base = S5PC100_GPG2(0), + .ngpio = S5PC100_GPIO_G2_NR, + .label = "GPG2", + }, + }, { + .chip = { + .base = S5PC100_GPG3(0), + .ngpio = S5PC100_GPIO_G3_NR, + .label = "GPG3", + }, + }, { + .chip = { + .base = S5PC100_GPI(0), + .ngpio = S5PC100_GPIO_I_NR, + .label = "GPI", + }, + }, { + .chip = { + .base = S5PC100_GPJ0(0), + .ngpio = S5PC100_GPIO_J0_NR, + .label = "GPJ0", + }, + }, { + .chip = { + .base = S5PC100_GPJ1(0), + .ngpio = S5PC100_GPIO_J1_NR, + .label = "GPJ1", + }, + }, { + .chip = { + .base = S5PC100_GPJ2(0), + .ngpio = S5PC100_GPIO_J2_NR, + .label = "GPJ2", + }, + }, { + .chip = { + .base = S5PC100_GPJ3(0), + .ngpio = S5PC100_GPIO_J3_NR, + .label = "GPJ3", + }, + }, { + .chip = { + .base = S5PC100_GPJ4(0), + .ngpio = S5PC100_GPIO_J4_NR, + .label = "GPJ4", + }, + }, { + .chip = { + .base = S5PC100_GPK0(0), + .ngpio = S5PC100_GPIO_K0_NR, + .label = "GPK0", + }, + }, { + .chip = { + .base = S5PC100_GPK1(0), + .ngpio = S5PC100_GPIO_K1_NR, + .label = "GPK1", + }, + }, { + .chip = { + .base = S5PC100_GPK2(0), + .ngpio = S5PC100_GPIO_K2_NR, + .label = "GPK2", + }, + }, { + .chip = { + .base = S5PC100_GPK3(0), + .ngpio = S5PC100_GPIO_K3_NR, + .label = "GPK3", + }, + }, { + .chip = { + .base = S5PC100_GPL0(0), + .ngpio = S5PC100_GPIO_L0_NR, + .label = "GPL0", + }, + }, { + .chip = { + .base = S5PC100_GPL1(0), + .ngpio = S5PC100_GPIO_L1_NR, + .label = "GPL1", + }, + }, { + .chip = { + .base = S5PC100_GPL2(0), + .ngpio = S5PC100_GPIO_L2_NR, + .label = "GPL2", + }, + }, { + .chip = { + .base = S5PC100_GPL3(0), + .ngpio = S5PC100_GPIO_L3_NR, + .label = "GPL3", + }, + }, { + .chip = { + .base = S5PC100_GPL4(0), + .ngpio = S5PC100_GPIO_L4_NR, + .label = "GPL4", + }, + }, { + .base = (S5P_VA_GPIO + 0xC00), + .irq_base = IRQ_EINT(0), + .chip = { + .base = S5PC100_GPH0(0), + .ngpio = S5PC100_GPIO_H0_NR, + .label = "GPH0", + .to_irq = samsung_gpiolib_to_irq, + }, + }, { + .base = (S5P_VA_GPIO + 0xC20), + .irq_base = IRQ_EINT(8), + .chip = { + .base = S5PC100_GPH1(0), + .ngpio = S5PC100_GPIO_H1_NR, + .label = "GPH1", + .to_irq = samsung_gpiolib_to_irq, + }, + }, { + .base = (S5P_VA_GPIO + 0xC40), + .irq_base = IRQ_EINT(16), + .chip = { + .base = S5PC100_GPH2(0), + .ngpio = S5PC100_GPIO_H2_NR, + .label = "GPH2", + .to_irq = samsung_gpiolib_to_irq, + }, + }, { + .base = (S5P_VA_GPIO + 0xC60), + .irq_base = IRQ_EINT(24), + .chip = { + .base = S5PC100_GPH3(0), + .ngpio = S5PC100_GPIO_H3_NR, + .label = "GPH3", + .to_irq = samsung_gpiolib_to_irq, + }, + }, +#endif +}; + +/* + * Followings are the gpio banks in S5PV210/S5PC110 + * + * The 'config' member when left to NULL, is initialized to the default + * structure samsung_gpio_cfgs[3] in the init function below. + * + * The 'base' member is also initialized in the init function below. + * Note: The initialization of 'base' member of samsung_gpio_chip structure + * uses the above macro and depends on the banks being listed in order here. + */ + +static struct samsung_gpio_chip s5pv210_gpios_4bit[] = { +#ifdef CONFIG_CPU_S5PV210 + { + .chip = { + .base = S5PV210_GPA0(0), + .ngpio = S5PV210_GPIO_A0_NR, + .label = "GPA0", + }, + }, { + .chip = { + .base = S5PV210_GPA1(0), + .ngpio = S5PV210_GPIO_A1_NR, + .label = "GPA1", + }, + }, { + .chip = { + .base = S5PV210_GPB(0), + .ngpio = S5PV210_GPIO_B_NR, + .label = "GPB", + }, + }, { + .chip = { + .base = S5PV210_GPC0(0), + .ngpio = S5PV210_GPIO_C0_NR, + .label = "GPC0", + }, + }, { + .chip = { + .base = S5PV210_GPC1(0), + .ngpio = S5PV210_GPIO_C1_NR, + .label = "GPC1", + }, + }, { + .chip = { + .base = S5PV210_GPD0(0), + .ngpio = S5PV210_GPIO_D0_NR, + .label = "GPD0", + }, + }, { + .chip = { + .base = S5PV210_GPD1(0), + .ngpio = S5PV210_GPIO_D1_NR, + .label = "GPD1", + }, + }, { + .chip = { + .base = S5PV210_GPE0(0), + .ngpio = S5PV210_GPIO_E0_NR, + .label = "GPE0", + }, + }, { + .chip = { + .base = S5PV210_GPE1(0), + .ngpio = S5PV210_GPIO_E1_NR, + .label = "GPE1", + }, + }, { + .chip = { + .base = S5PV210_GPF0(0), + .ngpio = S5PV210_GPIO_F0_NR, + .label = "GPF0", + }, + }, { + .chip = { + .base = S5PV210_GPF1(0), + .ngpio = S5PV210_GPIO_F1_NR, + .label = "GPF1", + }, + }, { + .chip = { + .base = S5PV210_GPF2(0), + .ngpio = S5PV210_GPIO_F2_NR, + .label = "GPF2", + }, + }, { + .chip = { + .base = S5PV210_GPF3(0), + .ngpio = S5PV210_GPIO_F3_NR, + .label = "GPF3", + }, + }, { + .chip = { + .base = S5PV210_GPG0(0), + .ngpio = S5PV210_GPIO_G0_NR, + .label = "GPG0", + }, + }, { + .chip = { + .base = S5PV210_GPG1(0), + .ngpio = S5PV210_GPIO_G1_NR, + .label = "GPG1", + }, + }, { + .chip = { + .base = S5PV210_GPG2(0), + .ngpio = S5PV210_GPIO_G2_NR, + .label = "GPG2", + }, + }, { + .chip = { + .base = S5PV210_GPG3(0), + .ngpio = S5PV210_GPIO_G3_NR, + .label = "GPG3", + }, + }, { + .chip = { + .base = S5PV210_GPI(0), + .ngpio = S5PV210_GPIO_I_NR, + .label = "GPI", + }, + }, { + .chip = { + .base = S5PV210_GPJ0(0), + .ngpio = S5PV210_GPIO_J0_NR, + .label = "GPJ0", + }, + }, { + .chip = { + .base = S5PV210_GPJ1(0), + .ngpio = S5PV210_GPIO_J1_NR, + .label = "GPJ1", + }, + }, { + .chip = { + .base = S5PV210_GPJ2(0), + .ngpio = S5PV210_GPIO_J2_NR, + .label = "GPJ2", + }, + }, { + .chip = { + .base = S5PV210_GPJ3(0), + .ngpio = S5PV210_GPIO_J3_NR, + .label = "GPJ3", + }, + }, { + .chip = { + .base = S5PV210_GPJ4(0), + .ngpio = S5PV210_GPIO_J4_NR, + .label = "GPJ4", + }, + }, { + .chip = { + .base = S5PV210_MP01(0), + .ngpio = S5PV210_GPIO_MP01_NR, + .label = "MP01", + }, + }, { + .chip = { + .base = S5PV210_MP02(0), + .ngpio = S5PV210_GPIO_MP02_NR, + .label = "MP02", + }, + }, { + .chip = { + .base = S5PV210_MP03(0), + .ngpio = S5PV210_GPIO_MP03_NR, + .label = "MP03", + }, + }, { + .chip = { + .base = S5PV210_MP04(0), + .ngpio = S5PV210_GPIO_MP04_NR, + .label = "MP04", + }, + }, { + .chip = { + .base = S5PV210_MP05(0), + .ngpio = S5PV210_GPIO_MP05_NR, + .label = "MP05", + }, + }, { + .base = (S5P_VA_GPIO + 0xC00), + .irq_base = IRQ_EINT(0), + .chip = { + .base = S5PV210_GPH0(0), + .ngpio = S5PV210_GPIO_H0_NR, + .label = "GPH0", + .to_irq = samsung_gpiolib_to_irq, + }, + }, { + .base = (S5P_VA_GPIO + 0xC20), + .irq_base = IRQ_EINT(8), + .chip = { + .base = S5PV210_GPH1(0), + .ngpio = S5PV210_GPIO_H1_NR, + .label = "GPH1", + .to_irq = samsung_gpiolib_to_irq, + }, + }, { + .base = (S5P_VA_GPIO + 0xC40), + .irq_base = IRQ_EINT(16), + .chip = { + .base = S5PV210_GPH2(0), + .ngpio = S5PV210_GPIO_H2_NR, + .label = "GPH2", + .to_irq = samsung_gpiolib_to_irq, + }, + }, { + .base = (S5P_VA_GPIO + 0xC60), + .irq_base = IRQ_EINT(24), + .chip = { + .base = S5PV210_GPH3(0), + .ngpio = S5PV210_GPIO_H3_NR, + .label = "GPH3", + .to_irq = samsung_gpiolib_to_irq, + }, + }, +#endif +}; + +/* + * Followings are the gpio banks in EXYNOS SoCs + * + * The 'config' member when left to NULL, is initialized to the default + * structure exynos_gpio_cfg in the init function below. + * + * The 'base' member is also initialized in the init function below. + * Note: The initialization of 'base' member of samsung_gpio_chip structure + * uses the above macro and depends on the banks being listed in order here. + */ + +#ifdef CONFIG_ARCH_EXYNOS4 +static struct samsung_gpio_chip exynos4_gpios_1[] = { + { + .chip = { + .base = EXYNOS4_GPA0(0), + .ngpio = EXYNOS4_GPIO_A0_NR, + .label = "GPA0", + }, + }, { + .chip = { + .base = EXYNOS4_GPA1(0), + .ngpio = EXYNOS4_GPIO_A1_NR, + .label = "GPA1", + }, + }, { + .chip = { + .base = EXYNOS4_GPB(0), + .ngpio = EXYNOS4_GPIO_B_NR, + .label = "GPB", + }, + }, { + .chip = { + .base = EXYNOS4_GPC0(0), + .ngpio = EXYNOS4_GPIO_C0_NR, + .label = "GPC0", + }, + }, { + .chip = { + .base = EXYNOS4_GPC1(0), + .ngpio = EXYNOS4_GPIO_C1_NR, + .label = "GPC1", + }, + }, { + .chip = { + .base = EXYNOS4_GPD0(0), + .ngpio = EXYNOS4_GPIO_D0_NR, + .label = "GPD0", + }, + }, { + .chip = { + .base = EXYNOS4_GPD1(0), + .ngpio = EXYNOS4_GPIO_D1_NR, + .label = "GPD1", + }, + }, { + .chip = { + .base = EXYNOS4_GPE0(0), + .ngpio = EXYNOS4_GPIO_E0_NR, + .label = "GPE0", + }, + }, { + .chip = { + .base = EXYNOS4_GPE1(0), + .ngpio = EXYNOS4_GPIO_E1_NR, + .label = "GPE1", + }, + }, { + .chip = { + .base = EXYNOS4_GPE2(0), + .ngpio = EXYNOS4_GPIO_E2_NR, + .label = "GPE2", + }, + }, { + .chip = { + .base = EXYNOS4_GPE3(0), + .ngpio = EXYNOS4_GPIO_E3_NR, + .label = "GPE3", + }, + }, { + .chip = { + .base = EXYNOS4_GPE4(0), + .ngpio = EXYNOS4_GPIO_E4_NR, + .label = "GPE4", + }, + }, { + .chip = { + .base = EXYNOS4_GPF0(0), + .ngpio = EXYNOS4_GPIO_F0_NR, + .label = "GPF0", + }, + }, { + .chip = { + .base = EXYNOS4_GPF1(0), + .ngpio = EXYNOS4_GPIO_F1_NR, + .label = "GPF1", + }, + }, { + .chip = { + .base = EXYNOS4_GPF2(0), + .ngpio = EXYNOS4_GPIO_F2_NR, + .label = "GPF2", + }, + }, { + .chip = { + .base = EXYNOS4_GPF3(0), + .ngpio = EXYNOS4_GPIO_F3_NR, + .label = "GPF3", + }, + }, +}; +#endif + +#ifdef CONFIG_ARCH_EXYNOS4 +static struct samsung_gpio_chip exynos4_gpios_2[] = { + { + .chip = { + .base = EXYNOS4_GPJ0(0), + .ngpio = EXYNOS4_GPIO_J0_NR, + .label = "GPJ0", + }, + }, { + .chip = { + .base = EXYNOS4_GPJ1(0), + .ngpio = EXYNOS4_GPIO_J1_NR, + .label = "GPJ1", + }, + }, { + .chip = { + .base = EXYNOS4_GPK0(0), + .ngpio = EXYNOS4_GPIO_K0_NR, + .label = "GPK0", + }, + }, { + .chip = { + .base = EXYNOS4_GPK1(0), + .ngpio = EXYNOS4_GPIO_K1_NR, + .label = "GPK1", + }, + }, { + .chip = { + .base = EXYNOS4_GPK2(0), + .ngpio = EXYNOS4_GPIO_K2_NR, + .label = "GPK2", + }, + }, { + .chip = { + .base = EXYNOS4_GPK3(0), + .ngpio = EXYNOS4_GPIO_K3_NR, + .label = "GPK3", + }, + }, { + .chip = { + .base = EXYNOS4_GPL0(0), + .ngpio = EXYNOS4_GPIO_L0_NR, + .label = "GPL0", + }, + }, { + .chip = { + .base = EXYNOS4_GPL1(0), + .ngpio = EXYNOS4_GPIO_L1_NR, + .label = "GPL1", + }, + }, { + .chip = { + .base = EXYNOS4_GPL2(0), + .ngpio = EXYNOS4_GPIO_L2_NR, + .label = "GPL2", + }, + }, { + .config = &samsung_gpio_cfgs[8], + .chip = { + .base = EXYNOS4_GPY0(0), + .ngpio = EXYNOS4_GPIO_Y0_NR, + .label = "GPY0", + }, + }, { + .config = &samsung_gpio_cfgs[8], + .chip = { + .base = EXYNOS4_GPY1(0), + .ngpio = EXYNOS4_GPIO_Y1_NR, + .label = "GPY1", + }, + }, { + .config = &samsung_gpio_cfgs[8], + .chip = { + .base = EXYNOS4_GPY2(0), + .ngpio = EXYNOS4_GPIO_Y2_NR, + .label = "GPY2", + }, + }, { + .config = &samsung_gpio_cfgs[8], + .chip = { + .base = EXYNOS4_GPY3(0), + .ngpio = EXYNOS4_GPIO_Y3_NR, + .label = "GPY3", + }, + }, { + .config = &samsung_gpio_cfgs[8], + .chip = { + .base = EXYNOS4_GPY4(0), + .ngpio = EXYNOS4_GPIO_Y4_NR, + .label = "GPY4", + }, + }, { + .config = &samsung_gpio_cfgs[8], + .chip = { + .base = EXYNOS4_GPY5(0), + .ngpio = EXYNOS4_GPIO_Y5_NR, + .label = "GPY5", + }, + }, { + .config = &samsung_gpio_cfgs[8], + .chip = { + .base = EXYNOS4_GPY6(0), + .ngpio = EXYNOS4_GPIO_Y6_NR, + .label = "GPY6", + }, + }, { + .config = &samsung_gpio_cfgs[9], + .irq_base = IRQ_EINT(0), + .chip = { + .base = EXYNOS4_GPX0(0), + .ngpio = EXYNOS4_GPIO_X0_NR, + .label = "GPX0", + .to_irq = samsung_gpiolib_to_irq, + }, + }, { + .config = &samsung_gpio_cfgs[9], + .irq_base = IRQ_EINT(8), + .chip = { + .base = EXYNOS4_GPX1(0), + .ngpio = EXYNOS4_GPIO_X1_NR, + .label = "GPX1", + .to_irq = samsung_gpiolib_to_irq, + }, + }, { + .config = &samsung_gpio_cfgs[9], + .irq_base = IRQ_EINT(16), + .chip = { + .base = EXYNOS4_GPX2(0), + .ngpio = EXYNOS4_GPIO_X2_NR, + .label = "GPX2", + .to_irq = samsung_gpiolib_to_irq, + }, + }, { + .config = &samsung_gpio_cfgs[9], + .irq_base = IRQ_EINT(24), + .chip = { + .base = EXYNOS4_GPX3(0), + .ngpio = EXYNOS4_GPIO_X3_NR, + .label = "GPX3", + .to_irq = samsung_gpiolib_to_irq, + }, + }, +}; +#endif + +#ifdef CONFIG_ARCH_EXYNOS4 +static struct samsung_gpio_chip exynos4_gpios_3[] = { + { + .chip = { + .base = EXYNOS4_GPZ(0), + .ngpio = EXYNOS4_GPIO_Z_NR, + .label = "GPZ", + }, + }, +}; +#endif + +#ifdef CONFIG_ARCH_EXYNOS5 +static struct samsung_gpio_chip exynos5_gpios_1[] = { + { + .chip = { + .base = EXYNOS5_GPA0(0), + .ngpio = EXYNOS5_GPIO_A0_NR, + .label = "GPA0", + }, + }, { + .chip = { + .base = EXYNOS5_GPA1(0), + .ngpio = EXYNOS5_GPIO_A1_NR, + .label = "GPA1", + }, + }, { + .chip = { + .base = EXYNOS5_GPA2(0), + .ngpio = EXYNOS5_GPIO_A2_NR, + .label = "GPA2", + }, + }, { + .chip = { + .base = EXYNOS5_GPB0(0), + .ngpio = EXYNOS5_GPIO_B0_NR, + .label = "GPB0", + }, + }, { + .chip = { + .base = EXYNOS5_GPB1(0), + .ngpio = EXYNOS5_GPIO_B1_NR, + .label = "GPB1", + }, + }, { + .chip = { + .base = EXYNOS5_GPB2(0), + .ngpio = EXYNOS5_GPIO_B2_NR, + .label = "GPB2", + }, + }, { + .chip = { + .base = EXYNOS5_GPB3(0), + .ngpio = EXYNOS5_GPIO_B3_NR, + .label = "GPB3", + }, + }, { + .chip = { + .base = EXYNOS5_GPC0(0), + .ngpio = EXYNOS5_GPIO_C0_NR, + .label = "GPC0", + }, + }, { + .chip = { + .base = EXYNOS5_GPC1(0), + .ngpio = EXYNOS5_GPIO_C1_NR, + .label = "GPC1", + }, + }, { + .chip = { + .base = EXYNOS5_GPC2(0), + .ngpio = EXYNOS5_GPIO_C2_NR, + .label = "GPC2", + }, + }, { + .chip = { + .base = EXYNOS5_GPC3(0), + .ngpio = EXYNOS5_GPIO_C3_NR, + .label = "GPC3", + }, + }, { + .chip = { + .base = EXYNOS5_GPD0(0), + .ngpio = EXYNOS5_GPIO_D0_NR, + .label = "GPD0", + }, + }, { + .chip = { + .base = EXYNOS5_GPD1(0), + .ngpio = EXYNOS5_GPIO_D1_NR, + .label = "GPD1", + }, + }, { + .chip = { + .base = EXYNOS5_GPY0(0), + .ngpio = EXYNOS5_GPIO_Y0_NR, + .label = "GPY0", + }, + }, { + .chip = { + .base = EXYNOS5_GPY1(0), + .ngpio = EXYNOS5_GPIO_Y1_NR, + .label = "GPY1", + }, + }, { + .chip = { + .base = EXYNOS5_GPY2(0), + .ngpio = EXYNOS5_GPIO_Y2_NR, + .label = "GPY2", + }, + }, { + .chip = { + .base = EXYNOS5_GPY3(0), + .ngpio = EXYNOS5_GPIO_Y3_NR, + .label = "GPY3", + }, + }, { + .chip = { + .base = EXYNOS5_GPY4(0), + .ngpio = EXYNOS5_GPIO_Y4_NR, + .label = "GPY4", + }, + }, { + .chip = { + .base = EXYNOS5_GPY5(0), + .ngpio = EXYNOS5_GPIO_Y5_NR, + .label = "GPY5", + }, + }, { + .chip = { + .base = EXYNOS5_GPY6(0), + .ngpio = EXYNOS5_GPIO_Y6_NR, + .label = "GPY6", + }, + }, { + .config = &samsung_gpio_cfgs[9], + .irq_base = IRQ_EINT(0), + .chip = { + .base = EXYNOS5_GPX0(0), + .ngpio = EXYNOS5_GPIO_X0_NR, + .label = "GPX0", + .to_irq = samsung_gpiolib_to_irq, + }, + }, { + .config = &samsung_gpio_cfgs[9], + .irq_base = IRQ_EINT(8), + .chip = { + .base = EXYNOS5_GPX1(0), + .ngpio = EXYNOS5_GPIO_X1_NR, + .label = "GPX1", + .to_irq = samsung_gpiolib_to_irq, + }, + }, { + .config = &samsung_gpio_cfgs[9], + .irq_base = IRQ_EINT(16), + .chip = { + .base = EXYNOS5_GPX2(0), + .ngpio = EXYNOS5_GPIO_X2_NR, + .label = "GPX2", + .to_irq = samsung_gpiolib_to_irq, + }, + }, { + .config = &samsung_gpio_cfgs[9], + .irq_base = IRQ_EINT(24), + .chip = { + .base = EXYNOS5_GPX3(0), + .ngpio = EXYNOS5_GPIO_X3_NR, + .label = "GPX3", + .to_irq = samsung_gpiolib_to_irq, + }, + }, +}; +#endif + +#ifdef CONFIG_ARCH_EXYNOS5 +static struct samsung_gpio_chip exynos5_gpios_2[] = { + { + .chip = { + .base = EXYNOS5_GPE0(0), + .ngpio = EXYNOS5_GPIO_E0_NR, + .label = "GPE0", + }, + }, { + .chip = { + .base = EXYNOS5_GPE1(0), + .ngpio = EXYNOS5_GPIO_E1_NR, + .label = "GPE1", + }, + }, { + .chip = { + .base = EXYNOS5_GPF0(0), + .ngpio = EXYNOS5_GPIO_F0_NR, + .label = "GPF0", + }, + }, { + .chip = { + .base = EXYNOS5_GPF1(0), + .ngpio = EXYNOS5_GPIO_F1_NR, + .label = "GPF1", + }, + }, { + .chip = { + .base = EXYNOS5_GPG0(0), + .ngpio = EXYNOS5_GPIO_G0_NR, + .label = "GPG0", + }, + }, { + .chip = { + .base = EXYNOS5_GPG1(0), + .ngpio = EXYNOS5_GPIO_G1_NR, + .label = "GPG1", + }, + }, { + .chip = { + .base = EXYNOS5_GPG2(0), + .ngpio = EXYNOS5_GPIO_G2_NR, + .label = "GPG2", + }, + }, { + .chip = { + .base = EXYNOS5_GPH0(0), + .ngpio = EXYNOS5_GPIO_H0_NR, + .label = "GPH0", + }, + }, { + .chip = { + .base = EXYNOS5_GPH1(0), + .ngpio = EXYNOS5_GPIO_H1_NR, + .label = "GPH1", + + }, + }, +}; +#endif + +#ifdef CONFIG_ARCH_EXYNOS5 +static struct samsung_gpio_chip exynos5_gpios_3[] = { + { + .chip = { + .base = EXYNOS5_GPV0(0), + .ngpio = EXYNOS5_GPIO_V0_NR, + .label = "GPV0", + }, + }, { + .chip = { + .base = EXYNOS5_GPV1(0), + .ngpio = EXYNOS5_GPIO_V1_NR, + .label = "GPV1", + }, + }, { + .chip = { + .base = EXYNOS5_GPV2(0), + .ngpio = EXYNOS5_GPIO_V2_NR, + .label = "GPV2", + }, + }, { + .chip = { + .base = EXYNOS5_GPV3(0), + .ngpio = EXYNOS5_GPIO_V3_NR, + .label = "GPV3", + }, + }, { + .chip = { + .base = EXYNOS5_GPV4(0), + .ngpio = EXYNOS5_GPIO_V4_NR, + .label = "GPV4", + }, + }, +}; +#endif + +#ifdef CONFIG_ARCH_EXYNOS5 +static struct samsung_gpio_chip exynos5_gpios_4[] = { + { + .chip = { + .base = EXYNOS5_GPZ(0), + .ngpio = EXYNOS5_GPIO_Z_NR, + .label = "GPZ", + }, + }, +}; +#endif + + +#if defined(CONFIG_ARCH_EXYNOS) && defined(CONFIG_OF) +static int exynos_gpio_xlate(struct gpio_chip *gc, + const struct of_phandle_args *gpiospec, u32 *flags) +{ + unsigned int pin; + + if (WARN_ON(gc->of_gpio_n_cells < 4)) + return -EINVAL; + + if (WARN_ON(gpiospec->args_count < gc->of_gpio_n_cells)) + return -EINVAL; + + if (gpiospec->args[0] > gc->ngpio) + return -EINVAL; + + pin = gc->base + gpiospec->args[0]; + + if (s3c_gpio_cfgpin(pin, S3C_GPIO_SFN(gpiospec->args[1]))) + pr_warn("gpio_xlate: failed to set pin function\n"); + if (s3c_gpio_setpull(pin, gpiospec->args[2])) + pr_warn("gpio_xlate: failed to set pin pull up/down\n"); + if (s5p_gpio_set_drvstr(pin, gpiospec->args[3])) + pr_warn("gpio_xlate: failed to set pin drive strength\n"); + + return gpiospec->args[0]; +} + +static const struct of_device_id exynos_gpio_dt_match[] __initdata = { + { .compatible = "samsung,exynos4-gpio", }, + {} +}; + +static __init void exynos_gpiolib_attach_ofnode(struct samsung_gpio_chip *chip, + u64 base, u64 offset) +{ + struct gpio_chip *gc = &chip->chip; + u64 address; + + if (!of_have_populated_dt()) + return; + + address = chip->base ? base + ((u32)chip->base & 0xfff) : base + offset; + gc->of_node = of_find_matching_node_by_address(NULL, + exynos_gpio_dt_match, address); + if (!gc->of_node) { + pr_info("gpio: device tree node not found for gpio controller" + " with base address %08llx\n", address); + return; + } + gc->of_gpio_n_cells = 4; + gc->of_xlate = exynos_gpio_xlate; +} +#elif defined(CONFIG_ARCH_EXYNOS) +static __init void exynos_gpiolib_attach_ofnode(struct samsung_gpio_chip *chip, + u64 base, u64 offset) +{ + return; +} +#endif /* defined(CONFIG_ARCH_EXYNOS) && defined(CONFIG_OF) */ + +/* TODO: cleanup soc_is_* */ +static __init int samsung_gpiolib_init(void) +{ + struct samsung_gpio_chip *chip; + int i, nr_chips; +#if defined(CONFIG_CPU_EXYNOS4210) || defined(CONFIG_SOC_EXYNOS5250) + void __iomem *gpio_base1, *gpio_base2, *gpio_base3, *gpio_base4; +#endif + int group = 0; + + samsung_gpiolib_set_cfg(samsung_gpio_cfgs, ARRAY_SIZE(samsung_gpio_cfgs)); + + if (soc_is_s3c24xx()) { + s3c24xx_gpiolib_add_chips(s3c24xx_gpios, + ARRAY_SIZE(s3c24xx_gpios), S3C24XX_VA_GPIO); + } else if (soc_is_s3c64xx()) { + samsung_gpiolib_add_2bit_chips(s3c64xx_gpios_2bit, + ARRAY_SIZE(s3c64xx_gpios_2bit), + S3C64XX_VA_GPIO + 0xE0, 0x20); + samsung_gpiolib_add_4bit_chips(s3c64xx_gpios_4bit, + ARRAY_SIZE(s3c64xx_gpios_4bit), + S3C64XX_VA_GPIO); + samsung_gpiolib_add_4bit2_chips(s3c64xx_gpios_4bit2, + ARRAY_SIZE(s3c64xx_gpios_4bit2)); + } else if (soc_is_s5p6440()) { + samsung_gpiolib_add_2bit_chips(s5p6440_gpios_2bit, + ARRAY_SIZE(s5p6440_gpios_2bit), NULL, 0x0); + samsung_gpiolib_add_4bit_chips(s5p6440_gpios_4bit, + ARRAY_SIZE(s5p6440_gpios_4bit), S5P_VA_GPIO); + samsung_gpiolib_add_4bit2_chips(s5p6440_gpios_4bit2, + ARRAY_SIZE(s5p6440_gpios_4bit2)); + s5p64x0_gpiolib_add_rbank(s5p6440_gpios_rbank, + ARRAY_SIZE(s5p6440_gpios_rbank)); + } else if (soc_is_s5p6450()) { + samsung_gpiolib_add_2bit_chips(s5p6450_gpios_2bit, + ARRAY_SIZE(s5p6450_gpios_2bit), NULL, 0x0); + samsung_gpiolib_add_4bit_chips(s5p6450_gpios_4bit, + ARRAY_SIZE(s5p6450_gpios_4bit), S5P_VA_GPIO); + samsung_gpiolib_add_4bit2_chips(s5p6450_gpios_4bit2, + ARRAY_SIZE(s5p6450_gpios_4bit2)); + s5p64x0_gpiolib_add_rbank(s5p6450_gpios_rbank, + ARRAY_SIZE(s5p6450_gpios_rbank)); + } else if (soc_is_s5pc100()) { + group = 0; + chip = s5pc100_gpios_4bit; + nr_chips = ARRAY_SIZE(s5pc100_gpios_4bit); + + for (i = 0; i < nr_chips; i++, chip++) { + if (!chip->config) { + chip->config = &samsung_gpio_cfgs[3]; + chip->group = group++; + } + } + samsung_gpiolib_add_4bit_chips(s5pc100_gpios_4bit, nr_chips, S5P_VA_GPIO); +#if defined(CONFIG_CPU_S5PC100) && defined(CONFIG_S5P_GPIO_INT) + s5p_register_gpioint_bank(IRQ_GPIOINT, 0, S5P_GPIOINT_GROUP_MAXNR); +#endif + } else if (soc_is_s5pv210()) { + group = 0; + chip = s5pv210_gpios_4bit; + nr_chips = ARRAY_SIZE(s5pv210_gpios_4bit); + + for (i = 0; i < nr_chips; i++, chip++) { + if (!chip->config) { + chip->config = &samsung_gpio_cfgs[3]; + chip->group = group++; + } + } + samsung_gpiolib_add_4bit_chips(s5pv210_gpios_4bit, nr_chips, S5P_VA_GPIO); +#if defined(CONFIG_CPU_S5PV210) && defined(CONFIG_S5P_GPIO_INT) + s5p_register_gpioint_bank(IRQ_GPIOINT, 0, S5P_GPIOINT_GROUP_MAXNR); +#endif + } else if (soc_is_exynos4210()) { +#ifdef CONFIG_CPU_EXYNOS4210 + void __iomem *gpx_base; + + /* gpio part1 */ + gpio_base1 = ioremap(EXYNOS4_PA_GPIO1, SZ_4K); + if (gpio_base1 == NULL) { + pr_err("unable to ioremap for gpio_base1\n"); + goto err_ioremap1; + } + + chip = exynos4_gpios_1; + nr_chips = ARRAY_SIZE(exynos4_gpios_1); + + for (i = 0; i < nr_chips; i++, chip++) { + if (!chip->config) { + chip->config = &exynos_gpio_cfg; + chip->group = group++; + } + exynos_gpiolib_attach_ofnode(chip, + EXYNOS4_PA_GPIO1, i * 0x20); + } + samsung_gpiolib_add_4bit_chips(exynos4_gpios_1, + nr_chips, gpio_base1); + + /* gpio part2 */ + gpio_base2 = ioremap(EXYNOS4_PA_GPIO2, SZ_4K); + if (gpio_base2 == NULL) { + pr_err("unable to ioremap for gpio_base2\n"); + goto err_ioremap2; + } + + /* need to set base address for gpx */ + chip = &exynos4_gpios_2[16]; + gpx_base = gpio_base2 + 0xC00; + for (i = 0; i < 4; i++, chip++, gpx_base += 0x20) + chip->base = gpx_base; + + chip = exynos4_gpios_2; + nr_chips = ARRAY_SIZE(exynos4_gpios_2); + + for (i = 0; i < nr_chips; i++, chip++) { + if (!chip->config) { + chip->config = &exynos_gpio_cfg; + chip->group = group++; + } + exynos_gpiolib_attach_ofnode(chip, + EXYNOS4_PA_GPIO2, i * 0x20); + } + samsung_gpiolib_add_4bit_chips(exynos4_gpios_2, + nr_chips, gpio_base2); + + /* gpio part3 */ + gpio_base3 = ioremap(EXYNOS4_PA_GPIO3, SZ_256); + if (gpio_base3 == NULL) { + pr_err("unable to ioremap for gpio_base3\n"); + goto err_ioremap3; + } + + chip = exynos4_gpios_3; + nr_chips = ARRAY_SIZE(exynos4_gpios_3); + + for (i = 0; i < nr_chips; i++, chip++) { + if (!chip->config) { + chip->config = &exynos_gpio_cfg; + chip->group = group++; + } + exynos_gpiolib_attach_ofnode(chip, + EXYNOS4_PA_GPIO3, i * 0x20); + } + samsung_gpiolib_add_4bit_chips(exynos4_gpios_3, + nr_chips, gpio_base3); + +#if defined(CONFIG_CPU_EXYNOS4210) && defined(CONFIG_S5P_GPIO_INT) + s5p_register_gpioint_bank(IRQ_GPIO_XA, 0, IRQ_GPIO1_NR_GROUPS); + s5p_register_gpioint_bank(IRQ_GPIO_XB, IRQ_GPIO1_NR_GROUPS, IRQ_GPIO2_NR_GROUPS); +#endif + +#endif /* CONFIG_CPU_EXYNOS4210 */ + } else if (soc_is_exynos5250()) { +#ifdef CONFIG_SOC_EXYNOS5250 + void __iomem *gpx_base; + + /* gpio part1 */ + gpio_base1 = ioremap(EXYNOS5_PA_GPIO1, SZ_4K); + if (gpio_base1 == NULL) { + pr_err("unable to ioremap for gpio_base1\n"); + goto err_ioremap1; + } + + /* need to set base address for gpx */ + chip = &exynos5_gpios_1[20]; + gpx_base = gpio_base1 + 0xC00; + for (i = 0; i < 4; i++, chip++, gpx_base += 0x20) + chip->base = gpx_base; + + chip = exynos5_gpios_1; + nr_chips = ARRAY_SIZE(exynos5_gpios_1); + + for (i = 0; i < nr_chips; i++, chip++) { + if (!chip->config) { + chip->config = &exynos_gpio_cfg; + chip->group = group++; + } + exynos_gpiolib_attach_ofnode(chip, + EXYNOS5_PA_GPIO1, i * 0x20); + } + samsung_gpiolib_add_4bit_chips(exynos5_gpios_1, + nr_chips, gpio_base1); + + /* gpio part2 */ + gpio_base2 = ioremap(EXYNOS5_PA_GPIO2, SZ_4K); + if (gpio_base2 == NULL) { + pr_err("unable to ioremap for gpio_base2\n"); + goto err_ioremap2; + } + + chip = exynos5_gpios_2; + nr_chips = ARRAY_SIZE(exynos5_gpios_2); + + for (i = 0; i < nr_chips; i++, chip++) { + if (!chip->config) { + chip->config = &exynos_gpio_cfg; + chip->group = group++; + } + exynos_gpiolib_attach_ofnode(chip, + EXYNOS5_PA_GPIO2, i * 0x20); + } + samsung_gpiolib_add_4bit_chips(exynos5_gpios_2, + nr_chips, gpio_base2); + + /* gpio part3 */ + gpio_base3 = ioremap(EXYNOS5_PA_GPIO3, SZ_4K); + if (gpio_base3 == NULL) { + pr_err("unable to ioremap for gpio_base3\n"); + goto err_ioremap3; + } + + /* need to set base address for gpv */ + exynos5_gpios_3[0].base = gpio_base3; + exynos5_gpios_3[1].base = gpio_base3 + 0x20; + exynos5_gpios_3[2].base = gpio_base3 + 0x60; + exynos5_gpios_3[3].base = gpio_base3 + 0x80; + exynos5_gpios_3[4].base = gpio_base3 + 0xC0; + + chip = exynos5_gpios_3; + nr_chips = ARRAY_SIZE(exynos5_gpios_3); + + for (i = 0; i < nr_chips; i++, chip++) { + if (!chip->config) { + chip->config = &exynos_gpio_cfg; + chip->group = group++; + } + exynos_gpiolib_attach_ofnode(chip, + EXYNOS5_PA_GPIO3, i * 0x20); + } + samsung_gpiolib_add_4bit_chips(exynos5_gpios_3, + nr_chips, gpio_base3); + + /* gpio part4 */ + gpio_base4 = ioremap(EXYNOS5_PA_GPIO4, SZ_4K); + if (gpio_base4 == NULL) { + pr_err("unable to ioremap for gpio_base4\n"); + goto err_ioremap4; + } + + chip = exynos5_gpios_4; + nr_chips = ARRAY_SIZE(exynos5_gpios_4); + + for (i = 0; i < nr_chips; i++, chip++) { + if (!chip->config) { + chip->config = &exynos_gpio_cfg; + chip->group = group++; + } + exynos_gpiolib_attach_ofnode(chip, + EXYNOS5_PA_GPIO4, i * 0x20); + } + samsung_gpiolib_add_4bit_chips(exynos5_gpios_4, + nr_chips, gpio_base4); +#endif /* CONFIG_SOC_EXYNOS5250 */ + } else { + WARN(1, "Unknown SoC in gpio-samsung, no GPIOs added\n"); + return -ENODEV; + } + + return 0; + +#if defined(CONFIG_CPU_EXYNOS4210) || defined(CONFIG_SOC_EXYNOS5250) +err_ioremap4: + iounmap(gpio_base3); +err_ioremap3: + iounmap(gpio_base2); +err_ioremap2: + iounmap(gpio_base1); +err_ioremap1: + return -ENOMEM; +#endif +} +core_initcall(samsung_gpiolib_init); + +int s3c_gpio_cfgpin(unsigned int pin, unsigned int config) +{ + struct samsung_gpio_chip *chip = samsung_gpiolib_getchip(pin); + unsigned long flags; + int offset; + int ret; + + if (!chip) + return -EINVAL; + + offset = pin - chip->chip.base; + + samsung_gpio_lock(chip, flags); + ret = samsung_gpio_do_setcfg(chip, offset, config); + samsung_gpio_unlock(chip, flags); + + return ret; +} +EXPORT_SYMBOL(s3c_gpio_cfgpin); + +int s3c_gpio_cfgpin_range(unsigned int start, unsigned int nr, + unsigned int cfg) +{ + int ret; + + for (; nr > 0; nr--, start++) { + ret = s3c_gpio_cfgpin(start, cfg); + if (ret != 0) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(s3c_gpio_cfgpin_range); + +int s3c_gpio_cfgall_range(unsigned int start, unsigned int nr, + unsigned int cfg, samsung_gpio_pull_t pull) +{ + int ret; + + for (; nr > 0; nr--, start++) { + s3c_gpio_setpull(start, pull); + ret = s3c_gpio_cfgpin(start, cfg); + if (ret != 0) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(s3c_gpio_cfgall_range); + +unsigned s3c_gpio_getcfg(unsigned int pin) +{ + struct samsung_gpio_chip *chip = samsung_gpiolib_getchip(pin); + unsigned long flags; + unsigned ret = 0; + int offset; + + if (chip) { + offset = pin - chip->chip.base; + + samsung_gpio_lock(chip, flags); + ret = samsung_gpio_do_getcfg(chip, offset); + samsung_gpio_unlock(chip, flags); + } + + return ret; +} +EXPORT_SYMBOL(s3c_gpio_getcfg); + +int s3c_gpio_setpull(unsigned int pin, samsung_gpio_pull_t pull) +{ + struct samsung_gpio_chip *chip = samsung_gpiolib_getchip(pin); + unsigned long flags; + int offset, ret; + + if (!chip) + return -EINVAL; + + offset = pin - chip->chip.base; + + samsung_gpio_lock(chip, flags); + ret = samsung_gpio_do_setpull(chip, offset, pull); + samsung_gpio_unlock(chip, flags); + + return ret; +} +EXPORT_SYMBOL(s3c_gpio_setpull); + +samsung_gpio_pull_t s3c_gpio_getpull(unsigned int pin) +{ + struct samsung_gpio_chip *chip = samsung_gpiolib_getchip(pin); + unsigned long flags; + int offset; + u32 pup = 0; + + if (chip) { + offset = pin - chip->chip.base; + + samsung_gpio_lock(chip, flags); + pup = samsung_gpio_do_getpull(chip, offset); + samsung_gpio_unlock(chip, flags); + } + + return (__force samsung_gpio_pull_t)pup; +} +EXPORT_SYMBOL(s3c_gpio_getpull); + +/* gpiolib wrappers until these are totally eliminated */ + +void s3c2410_gpio_pullup(unsigned int pin, unsigned int to) +{ + int ret; + + WARN_ON(to); /* should be none of these left */ + + if (!to) { + /* if pull is enabled, try first with up, and if that + * fails, try using down */ + + ret = s3c_gpio_setpull(pin, S3C_GPIO_PULL_UP); + if (ret) + s3c_gpio_setpull(pin, S3C_GPIO_PULL_DOWN); + } else { + s3c_gpio_setpull(pin, S3C_GPIO_PULL_NONE); + } +} +EXPORT_SYMBOL(s3c2410_gpio_pullup); + +void s3c2410_gpio_setpin(unsigned int pin, unsigned int to) +{ + /* do this via gpiolib until all users removed */ + + gpio_request(pin, "temporary"); + gpio_set_value(pin, to); + gpio_free(pin); +} +EXPORT_SYMBOL(s3c2410_gpio_setpin); + +unsigned int s3c2410_gpio_getpin(unsigned int pin) +{ + struct samsung_gpio_chip *chip = samsung_gpiolib_getchip(pin); + unsigned long offs = pin - chip->chip.base; + + return __raw_readl(chip->base + 0x04) & (1 << offs); +} +EXPORT_SYMBOL(s3c2410_gpio_getpin); + +#ifdef CONFIG_S5P_GPIO_DRVSTR +s5p_gpio_drvstr_t s5p_gpio_get_drvstr(unsigned int pin) +{ + struct samsung_gpio_chip *chip = samsung_gpiolib_getchip(pin); + unsigned int off; + void __iomem *reg; + int shift; + u32 drvstr; + + if (!chip) + return -EINVAL; + + off = pin - chip->chip.base; + shift = off * 2; + reg = chip->base + 0x0C; + + drvstr = __raw_readl(reg); + drvstr = drvstr >> shift; + drvstr &= 0x3; + + return (__force s5p_gpio_drvstr_t)drvstr; +} +EXPORT_SYMBOL(s5p_gpio_get_drvstr); + +int s5p_gpio_set_drvstr(unsigned int pin, s5p_gpio_drvstr_t drvstr) +{ + struct samsung_gpio_chip *chip = samsung_gpiolib_getchip(pin); + unsigned int off; + void __iomem *reg; + int shift; + u32 tmp; + + if (!chip) + return -EINVAL; + + off = pin - chip->chip.base; + shift = off * 2; + reg = chip->base + 0x0C; + + tmp = __raw_readl(reg); + tmp &= ~(0x3 << shift); + tmp |= drvstr << shift; + + __raw_writel(tmp, reg); + + return 0; +} +EXPORT_SYMBOL(s5p_gpio_set_drvstr); +#endif /* CONFIG_S5P_GPIO_DRVSTR */ + +#ifdef CONFIG_PLAT_S3C24XX +unsigned int s3c2410_modify_misccr(unsigned int clear, unsigned int change) +{ + unsigned long flags; + unsigned long misccr; + + local_irq_save(flags); + misccr = __raw_readl(S3C24XX_MISCCR); + misccr &= ~clear; + misccr ^= change; + __raw_writel(misccr, S3C24XX_MISCCR); + local_irq_restore(flags); + + return misccr; +} +EXPORT_SYMBOL(s3c2410_modify_misccr); +#endif diff --git a/drivers/gpio/gpio-sch.c b/drivers/gpio/gpio-sch.c new file mode 100644 index 00000000..8cadf4d6 --- /dev/null +++ b/drivers/gpio/gpio-sch.c @@ -0,0 +1,305 @@ +/* + * GPIO interface for Intel Poulsbo SCH + * + * Copyright (c) 2010 CompuLab Ltd + * Author: Denis Turischev <denis@compulab.co.il> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 2 as published + * by the Free Software Foundation. + * + * 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; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/errno.h> +#include <linux/acpi.h> +#include <linux/platform_device.h> +#include <linux/pci_ids.h> + +#include <linux/gpio.h> + +static DEFINE_SPINLOCK(gpio_lock); + +#define CGEN (0x00) +#define CGIO (0x04) +#define CGLV (0x08) + +#define RGEN (0x20) +#define RGIO (0x24) +#define RGLV (0x28) + +static unsigned short gpio_ba; + +static int sch_gpio_core_direction_in(struct gpio_chip *gc, unsigned gpio_num) +{ + u8 curr_dirs; + unsigned short offset, bit; + + spin_lock(&gpio_lock); + + offset = CGIO + gpio_num / 8; + bit = gpio_num % 8; + + curr_dirs = inb(gpio_ba + offset); + + if (!(curr_dirs & (1 << bit))) + outb(curr_dirs | (1 << bit), gpio_ba + offset); + + spin_unlock(&gpio_lock); + return 0; +} + +static int sch_gpio_core_get(struct gpio_chip *gc, unsigned gpio_num) +{ + int res; + unsigned short offset, bit; + + offset = CGLV + gpio_num / 8; + bit = gpio_num % 8; + + res = !!(inb(gpio_ba + offset) & (1 << bit)); + return res; +} + +static void sch_gpio_core_set(struct gpio_chip *gc, unsigned gpio_num, int val) +{ + u8 curr_vals; + unsigned short offset, bit; + + spin_lock(&gpio_lock); + + offset = CGLV + gpio_num / 8; + bit = gpio_num % 8; + + curr_vals = inb(gpio_ba + offset); + + if (val) + outb(curr_vals | (1 << bit), gpio_ba + offset); + else + outb((curr_vals & ~(1 << bit)), gpio_ba + offset); + spin_unlock(&gpio_lock); +} + +static int sch_gpio_core_direction_out(struct gpio_chip *gc, + unsigned gpio_num, int val) +{ + u8 curr_dirs; + unsigned short offset, bit; + + sch_gpio_core_set(gc, gpio_num, val); + + spin_lock(&gpio_lock); + + offset = CGIO + gpio_num / 8; + bit = gpio_num % 8; + + curr_dirs = inb(gpio_ba + offset); + if (curr_dirs & (1 << bit)) + outb(curr_dirs & ~(1 << bit), gpio_ba + offset); + + spin_unlock(&gpio_lock); + return 0; +} + +static struct gpio_chip sch_gpio_core = { + .label = "sch_gpio_core", + .owner = THIS_MODULE, + .direction_input = sch_gpio_core_direction_in, + .get = sch_gpio_core_get, + .direction_output = sch_gpio_core_direction_out, + .set = sch_gpio_core_set, +}; + +static int sch_gpio_resume_direction_in(struct gpio_chip *gc, + unsigned gpio_num) +{ + u8 curr_dirs; + + spin_lock(&gpio_lock); + + curr_dirs = inb(gpio_ba + RGIO); + + if (!(curr_dirs & (1 << gpio_num))) + outb(curr_dirs | (1 << gpio_num) , gpio_ba + RGIO); + + spin_unlock(&gpio_lock); + return 0; +} + +static int sch_gpio_resume_get(struct gpio_chip *gc, unsigned gpio_num) +{ + return !!(inb(gpio_ba + RGLV) & (1 << gpio_num)); +} + +static void sch_gpio_resume_set(struct gpio_chip *gc, + unsigned gpio_num, int val) +{ + u8 curr_vals; + + spin_lock(&gpio_lock); + + curr_vals = inb(gpio_ba + RGLV); + + if (val) + outb(curr_vals | (1 << gpio_num), gpio_ba + RGLV); + else + outb((curr_vals & ~(1 << gpio_num)), gpio_ba + RGLV); + + spin_unlock(&gpio_lock); +} + +static int sch_gpio_resume_direction_out(struct gpio_chip *gc, + unsigned gpio_num, int val) +{ + u8 curr_dirs; + + sch_gpio_resume_set(gc, gpio_num, val); + + spin_lock(&gpio_lock); + + curr_dirs = inb(gpio_ba + RGIO); + if (curr_dirs & (1 << gpio_num)) + outb(curr_dirs & ~(1 << gpio_num), gpio_ba + RGIO); + + spin_unlock(&gpio_lock); + return 0; +} + +static struct gpio_chip sch_gpio_resume = { + .label = "sch_gpio_resume", + .owner = THIS_MODULE, + .direction_input = sch_gpio_resume_direction_in, + .get = sch_gpio_resume_get, + .direction_output = sch_gpio_resume_direction_out, + .set = sch_gpio_resume_set, +}; + +static int __devinit sch_gpio_probe(struct platform_device *pdev) +{ + struct resource *res; + int err, id; + + id = pdev->id; + if (!id) + return -ENODEV; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!res) + return -EBUSY; + + if (!request_region(res->start, resource_size(res), pdev->name)) + return -EBUSY; + + gpio_ba = res->start; + + switch (id) { + case PCI_DEVICE_ID_INTEL_SCH_LPC: + sch_gpio_core.base = 0; + sch_gpio_core.ngpio = 10; + + sch_gpio_resume.base = 10; + sch_gpio_resume.ngpio = 4; + + /* + * GPIO[6:0] enabled by default + * GPIO7 is configured by the CMC as SLPIOVR + * Enable GPIO[9:8] core powered gpios explicitly + */ + outb(0x3, gpio_ba + CGEN + 1); + /* + * SUS_GPIO[2:0] enabled by default + * Enable SUS_GPIO3 resume powered gpio explicitly + */ + outb(0x8, gpio_ba + RGEN); + break; + + case PCI_DEVICE_ID_INTEL_ITC_LPC: + sch_gpio_core.base = 0; + sch_gpio_core.ngpio = 5; + + sch_gpio_resume.base = 5; + sch_gpio_resume.ngpio = 9; + break; + + default: + return -ENODEV; + } + + sch_gpio_core.dev = &pdev->dev; + sch_gpio_resume.dev = &pdev->dev; + + err = gpiochip_add(&sch_gpio_core); + if (err < 0) + goto err_sch_gpio_core; + + err = gpiochip_add(&sch_gpio_resume); + if (err < 0) + goto err_sch_gpio_resume; + + return 0; + +err_sch_gpio_resume: + err = gpiochip_remove(&sch_gpio_core); + if (err) + dev_err(&pdev->dev, "%s failed, %d\n", + "gpiochip_remove()", err); + +err_sch_gpio_core: + release_region(res->start, resource_size(res)); + gpio_ba = 0; + + return err; +} + +static int __devexit sch_gpio_remove(struct platform_device *pdev) +{ + struct resource *res; + if (gpio_ba) { + int err; + + err = gpiochip_remove(&sch_gpio_core); + if (err) + dev_err(&pdev->dev, "%s failed, %d\n", + "gpiochip_remove()", err); + err = gpiochip_remove(&sch_gpio_resume); + if (err) + dev_err(&pdev->dev, "%s failed, %d\n", + "gpiochip_remove()", err); + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + + release_region(res->start, resource_size(res)); + gpio_ba = 0; + + return err; + } + + return 0; +} + +static struct platform_driver sch_gpio_driver = { + .driver = { + .name = "sch_gpio", + .owner = THIS_MODULE, + }, + .probe = sch_gpio_probe, + .remove = __devexit_p(sch_gpio_remove), +}; + +module_platform_driver(sch_gpio_driver); + +MODULE_AUTHOR("Denis Turischev <denis@compulab.co.il>"); +MODULE_DESCRIPTION("GPIO interface for Intel Poulsbo SCH"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:sch_gpio"); diff --git a/drivers/gpio/gpio-sodaville.c b/drivers/gpio/gpio-sodaville.c new file mode 100644 index 00000000..031e5d24 --- /dev/null +++ b/drivers/gpio/gpio-sodaville.c @@ -0,0 +1,299 @@ +/* + * GPIO interface for Intel Sodaville SoCs. + * + * Copyright (c) 2010, 2011 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 2 as published + * by the Free Software Foundation. + * + */ + +#include <linux/errno.h> +#include <linux/gpio.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/of_irq.h> +#include <linux/basic_mmio_gpio.h> + +#define DRV_NAME "sdv_gpio" +#define SDV_NUM_PUB_GPIOS 12 +#define PCI_DEVICE_ID_SDV_GPIO 0x2e67 +#define GPIO_BAR 0 + +#define GPOUTR 0x00 +#define GPOER 0x04 +#define GPINR 0x08 + +#define GPSTR 0x0c +#define GPIT1R0 0x10 +#define GPIO_INT 0x14 +#define GPIT1R1 0x18 + +#define GPMUXCTL 0x1c + +struct sdv_gpio_chip_data { + int irq_base; + void __iomem *gpio_pub_base; + struct irq_domain *id; + struct irq_chip_generic *gc; + struct bgpio_chip bgpio; +}; + +static int sdv_gpio_pub_set_type(struct irq_data *d, unsigned int type) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct sdv_gpio_chip_data *sd = gc->private; + void __iomem *type_reg; + u32 reg; + + if (d->hwirq < 8) + type_reg = sd->gpio_pub_base + GPIT1R0; + else + type_reg = sd->gpio_pub_base + GPIT1R1; + + reg = readl(type_reg); + + switch (type) { + case IRQ_TYPE_LEVEL_HIGH: + reg &= ~BIT(4 * (d->hwirq % 8)); + break; + + case IRQ_TYPE_LEVEL_LOW: + reg |= BIT(4 * (d->hwirq % 8)); + break; + + default: + return -EINVAL; + } + + writel(reg, type_reg); + return 0; +} + +static irqreturn_t sdv_gpio_pub_irq_handler(int irq, void *data) +{ + struct sdv_gpio_chip_data *sd = data; + u32 irq_stat = readl(sd->gpio_pub_base + GPSTR); + + irq_stat &= readl(sd->gpio_pub_base + GPIO_INT); + if (!irq_stat) + return IRQ_NONE; + + while (irq_stat) { + u32 irq_bit = __fls(irq_stat); + + irq_stat &= ~BIT(irq_bit); + generic_handle_irq(irq_find_mapping(sd->id, irq_bit)); + } + + return IRQ_HANDLED; +} + +static int sdv_xlate(struct irq_domain *h, struct device_node *node, + const u32 *intspec, u32 intsize, irq_hw_number_t *out_hwirq, + u32 *out_type) +{ + u32 line, type; + + if (node != h->of_node) + return -EINVAL; + + if (intsize < 2) + return -EINVAL; + + line = *intspec; + *out_hwirq = line; + + intspec++; + type = *intspec; + + switch (type) { + case IRQ_TYPE_LEVEL_LOW: + case IRQ_TYPE_LEVEL_HIGH: + *out_type = type; + break; + default: + return -EINVAL; + } + return 0; +} + +static struct irq_domain_ops irq_domain_sdv_ops = { + .xlate = sdv_xlate, +}; + +static __devinit int sdv_register_irqsupport(struct sdv_gpio_chip_data *sd, + struct pci_dev *pdev) +{ + struct irq_chip_type *ct; + int ret; + + sd->irq_base = irq_alloc_descs(-1, 0, SDV_NUM_PUB_GPIOS, -1); + if (sd->irq_base < 0) + return sd->irq_base; + + /* mask + ACK all interrupt sources */ + writel(0, sd->gpio_pub_base + GPIO_INT); + writel((1 << 11) - 1, sd->gpio_pub_base + GPSTR); + + ret = request_irq(pdev->irq, sdv_gpio_pub_irq_handler, IRQF_SHARED, + "sdv_gpio", sd); + if (ret) + goto out_free_desc; + + /* + * This gpio irq controller latches level irqs. Testing shows that if + * we unmask & ACK the IRQ before the source of the interrupt is gone + * then the interrupt is active again. + */ + sd->gc = irq_alloc_generic_chip("sdv-gpio", 1, sd->irq_base, + sd->gpio_pub_base, handle_fasteoi_irq); + if (!sd->gc) { + ret = -ENOMEM; + goto out_free_irq; + } + + sd->gc->private = sd; + ct = sd->gc->chip_types; + ct->type = IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW; + ct->regs.eoi = GPSTR; + ct->regs.mask = GPIO_INT; + ct->chip.irq_mask = irq_gc_mask_clr_bit; + ct->chip.irq_unmask = irq_gc_mask_set_bit; + ct->chip.irq_eoi = irq_gc_eoi; + ct->chip.irq_set_type = sdv_gpio_pub_set_type; + + irq_setup_generic_chip(sd->gc, IRQ_MSK(SDV_NUM_PUB_GPIOS), + IRQ_GC_INIT_MASK_CACHE, IRQ_NOREQUEST, + IRQ_LEVEL | IRQ_NOPROBE); + + sd->id = irq_domain_add_legacy(pdev->dev.of_node, SDV_NUM_PUB_GPIOS, + sd->irq_base, 0, &irq_domain_sdv_ops, sd); + if (!sd->id) + goto out_free_irq; + return 0; +out_free_irq: + free_irq(pdev->irq, sd); +out_free_desc: + irq_free_descs(sd->irq_base, SDV_NUM_PUB_GPIOS); + return ret; +} + +static int __devinit sdv_gpio_probe(struct pci_dev *pdev, + const struct pci_device_id *pci_id) +{ + struct sdv_gpio_chip_data *sd; + unsigned long addr; + const void *prop; + int len; + int ret; + u32 mux_val; + + sd = kzalloc(sizeof(struct sdv_gpio_chip_data), GFP_KERNEL); + if (!sd) + return -ENOMEM; + ret = pci_enable_device(pdev); + if (ret) { + dev_err(&pdev->dev, "can't enable device.\n"); + goto done; + } + + ret = pci_request_region(pdev, GPIO_BAR, DRV_NAME); + if (ret) { + dev_err(&pdev->dev, "can't alloc PCI BAR #%d\n", GPIO_BAR); + goto disable_pci; + } + + addr = pci_resource_start(pdev, GPIO_BAR); + if (!addr) + goto release_reg; + sd->gpio_pub_base = ioremap(addr, pci_resource_len(pdev, GPIO_BAR)); + + prop = of_get_property(pdev->dev.of_node, "intel,muxctl", &len); + if (prop && len == 4) { + mux_val = of_read_number(prop, 1); + writel(mux_val, sd->gpio_pub_base + GPMUXCTL); + } + + ret = bgpio_init(&sd->bgpio, &pdev->dev, 4, + sd->gpio_pub_base + GPINR, sd->gpio_pub_base + GPOUTR, + NULL, sd->gpio_pub_base + GPOER, NULL, false); + if (ret) + goto unmap; + sd->bgpio.gc.ngpio = SDV_NUM_PUB_GPIOS; + + ret = gpiochip_add(&sd->bgpio.gc); + if (ret < 0) { + dev_err(&pdev->dev, "gpiochip_add() failed.\n"); + goto unmap; + } + + ret = sdv_register_irqsupport(sd, pdev); + if (ret) + goto unmap; + + pci_set_drvdata(pdev, sd); + dev_info(&pdev->dev, "Sodaville GPIO driver registered.\n"); + return 0; + +unmap: + iounmap(sd->gpio_pub_base); +release_reg: + pci_release_region(pdev, GPIO_BAR); +disable_pci: + pci_disable_device(pdev); +done: + kfree(sd); + return ret; +} + +static void sdv_gpio_remove(struct pci_dev *pdev) +{ + struct sdv_gpio_chip_data *sd = pci_get_drvdata(pdev); + + free_irq(pdev->irq, sd); + irq_free_descs(sd->irq_base, SDV_NUM_PUB_GPIOS); + + if (gpiochip_remove(&sd->bgpio.gc)) + dev_err(&pdev->dev, "gpiochip_remove() failed.\n"); + + pci_release_region(pdev, GPIO_BAR); + iounmap(sd->gpio_pub_base); + pci_disable_device(pdev); + kfree(sd); +} + +static struct pci_device_id sdv_gpio_pci_ids[] __devinitdata = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_SDV_GPIO) }, + { 0, }, +}; + +static struct pci_driver sdv_gpio_driver = { + .name = DRV_NAME, + .id_table = sdv_gpio_pci_ids, + .probe = sdv_gpio_probe, + .remove = sdv_gpio_remove, +}; + +static int __init sdv_gpio_init(void) +{ + return pci_register_driver(&sdv_gpio_driver); +} +module_init(sdv_gpio_init); + +static void __exit sdv_gpio_exit(void) +{ + pci_unregister_driver(&sdv_gpio_driver); +} +module_exit(sdv_gpio_exit); + +MODULE_AUTHOR("Hans J. Koch <hjk@linutronix.de>"); +MODULE_DESCRIPTION("GPIO interface for Intel Sodaville SoCs"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpio/gpio-stmpe.c b/drivers/gpio/gpio-stmpe.c new file mode 100644 index 00000000..dce34727 --- /dev/null +++ b/drivers/gpio/gpio-stmpe.c @@ -0,0 +1,432 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License, version 2 + * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/mfd/stmpe.h> + +/* + * 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; + unsigned norequest_mask; + + /* 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); + + /* + * Some variants have single register for gpio set/clear functionality. + * For them we need to write 0 to clear and 1 to set. + */ + if (stmpe->regs[STMPE_IDX_GPSR_LSB] == stmpe->regs[STMPE_IDX_GPCR_LSB]) + stmpe_set_bits(stmpe, reg, mask, val ? mask : 0); + else + 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; + + if (stmpe_gpio->norequest_mask & (1 << offset)) + return -EINVAL; + + 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(struct irq_data *d, unsigned int type) +{ + struct stmpe_gpio *stmpe_gpio = irq_data_get_irq_chip_data(d); + int offset = d->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; + + /* STMPE801 doesn't have RE and FE registers */ + if (stmpe_gpio->stmpe->partnum == STMPE801) + return 0; + + 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(struct irq_data *d) +{ + struct stmpe_gpio *stmpe_gpio = irq_data_get_irq_chip_data(d); + + mutex_lock(&stmpe_gpio->irq_lock); +} + +static void stmpe_gpio_irq_sync_unlock(struct irq_data *d) +{ + struct stmpe_gpio *stmpe_gpio = irq_data_get_irq_chip_data(d); + 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++) { + /* STMPE801 doesn't have RE and FE registers */ + if ((stmpe->partnum == STMPE801) && + (i != REG_IE)) + continue; + + 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(struct irq_data *d) +{ + struct stmpe_gpio *stmpe_gpio = irq_data_get_irq_chip_data(d); + int offset = d->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(struct irq_data *d) +{ + struct stmpe_gpio *stmpe_gpio = irq_data_get_irq_chip_data(d); + int offset = d->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", + .irq_bus_lock = stmpe_gpio_irq_lock, + .irq_bus_sync_unlock = stmpe_gpio_irq_sync_unlock, + .irq_mask = stmpe_gpio_irq_mask, + .irq_unmask = stmpe_gpio_irq_unmask, + .irq_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]); + + /* Edge detect register is not present on 801 */ + if (stmpe->partnum != STMPE801) + 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++) { + irq_set_chip_data(irq, stmpe_gpio); + irq_set_chip_and_handler(irq, &stmpe_gpio_irq_chip, + handle_simple_irq); + irq_set_nested_thread(irq, 1); +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID); +#else + irq_set_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 + irq_set_chip_and_handler(irq, NULL, NULL); + irq_set_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 = 0; + + pdata = stmpe->pdata->gpio; + + irq = platform_get_irq(pdev, 0); + + 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->norequest_mask = pdata ? pdata->norequest_mask : 0; + + 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; + + if (irq >= 0) + stmpe_gpio->irq_base = stmpe->irq_base + STMPE_INT_GPIO(0); + else + dev_info(&pdev->dev, + "device configured in no-irq mode; " + "irqs are not available\n"); + + ret = stmpe_enable(stmpe, STMPE_BLOCK_GPIO); + if (ret) + goto out_free; + + if (irq >= 0) { + ret = stmpe_gpio_irq_init(stmpe_gpio); + if (ret) + goto out_disable; + + 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: + if (irq >= 0) + free_irq(irq, stmpe_gpio); +out_removeirq: + if (irq >= 0) + stmpe_gpio_irq_remove(stmpe_gpio); +out_disable: + stmpe_disable(stmpe, STMPE_BLOCK_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); + + if (irq >= 0) { + 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 <rabin.vincent@stericsson.com>"); diff --git a/drivers/gpio/gpio-sx150x.c b/drivers/gpio/gpio-sx150x.c new file mode 100644 index 00000000..a4f73534 --- /dev/null +++ b/drivers/gpio/gpio-sx150x.c @@ -0,0 +1,680 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/i2c/sx150x.h> + +#define NO_UPDATE_PENDING -1 + +struct sx150x_device_data { + u8 reg_pullup; + u8 reg_pulldn; + u8 reg_drain; + u8 reg_polarity; + u8 reg_dir; + u8 reg_data; + u8 reg_irq_mask; + u8 reg_irq_src; + u8 reg_sense; + u8 reg_clock; + u8 reg_misc; + u8 reg_reset; + u8 ngpios; +}; + +struct sx150x_chip { + struct gpio_chip gpio_chip; + struct i2c_client *client; + const struct sx150x_device_data *dev_cfg; + int irq_summary; + int irq_base; + int irq_update; + u32 irq_sense; + u32 irq_masked; + u32 dev_sense; + u32 dev_masked; + struct irq_chip irq_chip; + struct mutex lock; +}; + +static const struct sx150x_device_data sx150x_devices[] = { + [0] = { /* sx1508q */ + .reg_pullup = 0x03, + .reg_pulldn = 0x04, + .reg_drain = 0x05, + .reg_polarity = 0x06, + .reg_dir = 0x07, + .reg_data = 0x08, + .reg_irq_mask = 0x09, + .reg_irq_src = 0x0c, + .reg_sense = 0x0b, + .reg_clock = 0x0f, + .reg_misc = 0x10, + .reg_reset = 0x7d, + .ngpios = 8 + }, + [1] = { /* sx1509q */ + .reg_pullup = 0x07, + .reg_pulldn = 0x09, + .reg_drain = 0x0b, + .reg_polarity = 0x0d, + .reg_dir = 0x0f, + .reg_data = 0x11, + .reg_irq_mask = 0x13, + .reg_irq_src = 0x19, + .reg_sense = 0x17, + .reg_clock = 0x1e, + .reg_misc = 0x1f, + .reg_reset = 0x7d, + .ngpios = 16 + }, +}; + +static const struct i2c_device_id sx150x_id[] = { + {"sx1508q", 0}, + {"sx1509q", 1}, + {} +}; +MODULE_DEVICE_TABLE(i2c, sx150x_id); + +static s32 sx150x_i2c_write(struct i2c_client *client, u8 reg, u8 val) +{ + s32 err = i2c_smbus_write_byte_data(client, reg, val); + + if (err < 0) + dev_warn(&client->dev, + "i2c write fail: can't write %02x to %02x: %d\n", + val, reg, err); + return err; +} + +static s32 sx150x_i2c_read(struct i2c_client *client, u8 reg, u8 *val) +{ + s32 err = i2c_smbus_read_byte_data(client, reg); + + if (err >= 0) + *val = err; + else + dev_warn(&client->dev, + "i2c read fail: can't read from %02x: %d\n", + reg, err); + return err; +} + +static inline bool offset_is_oscio(struct sx150x_chip *chip, unsigned offset) +{ + return (chip->dev_cfg->ngpios == offset); +} + +/* + * These utility functions solve the common problem of locating and setting + * configuration bits. Configuration bits are grouped into registers + * whose indexes increase downwards. For example, with eight-bit registers, + * sixteen gpios would have their config bits grouped in the following order: + * REGISTER N-1 [ f e d c b a 9 8 ] + * N [ 7 6 5 4 3 2 1 0 ] + * + * For multi-bit configurations, the pattern gets wider: + * REGISTER N-3 [ f f e e d d c c ] + * N-2 [ b b a a 9 9 8 8 ] + * N-1 [ 7 7 6 6 5 5 4 4 ] + * N [ 3 3 2 2 1 1 0 0 ] + * + * Given the address of the starting register 'N', the index of the gpio + * whose configuration we seek to change, and the width in bits of that + * configuration, these functions allow us to locate the correct + * register and mask the correct bits. + */ +static inline void sx150x_find_cfg(u8 offset, u8 width, + u8 *reg, u8 *mask, u8 *shift) +{ + *reg -= offset * width / 8; + *mask = (1 << width) - 1; + *shift = (offset * width) % 8; + *mask <<= *shift; +} + +static s32 sx150x_write_cfg(struct sx150x_chip *chip, + u8 offset, u8 width, u8 reg, u8 val) +{ + u8 mask; + u8 data; + u8 shift; + s32 err; + + sx150x_find_cfg(offset, width, ®, &mask, &shift); + err = sx150x_i2c_read(chip->client, reg, &data); + if (err < 0) + return err; + + data &= ~mask; + data |= (val << shift) & mask; + return sx150x_i2c_write(chip->client, reg, data); +} + +static int sx150x_get_io(struct sx150x_chip *chip, unsigned offset) +{ + u8 reg = chip->dev_cfg->reg_data; + u8 mask; + u8 data; + u8 shift; + s32 err; + + sx150x_find_cfg(offset, 1, ®, &mask, &shift); + err = sx150x_i2c_read(chip->client, reg, &data); + if (err >= 0) + err = (data & mask) != 0 ? 1 : 0; + + return err; +} + +static void sx150x_set_oscio(struct sx150x_chip *chip, int val) +{ + sx150x_i2c_write(chip->client, + chip->dev_cfg->reg_clock, + (val ? 0x1f : 0x10)); +} + +static void sx150x_set_io(struct sx150x_chip *chip, unsigned offset, int val) +{ + sx150x_write_cfg(chip, + offset, + 1, + chip->dev_cfg->reg_data, + (val ? 1 : 0)); +} + +static int sx150x_io_input(struct sx150x_chip *chip, unsigned offset) +{ + return sx150x_write_cfg(chip, + offset, + 1, + chip->dev_cfg->reg_dir, + 1); +} + +static int sx150x_io_output(struct sx150x_chip *chip, unsigned offset, int val) +{ + int err; + + err = sx150x_write_cfg(chip, + offset, + 1, + chip->dev_cfg->reg_data, + (val ? 1 : 0)); + if (err >= 0) + err = sx150x_write_cfg(chip, + offset, + 1, + chip->dev_cfg->reg_dir, + 0); + return err; +} + +static int sx150x_gpio_get(struct gpio_chip *gc, unsigned offset) +{ + struct sx150x_chip *chip; + int status = -EINVAL; + + chip = container_of(gc, struct sx150x_chip, gpio_chip); + + if (!offset_is_oscio(chip, offset)) { + mutex_lock(&chip->lock); + status = sx150x_get_io(chip, offset); + mutex_unlock(&chip->lock); + } + + return status; +} + +static void sx150x_gpio_set(struct gpio_chip *gc, unsigned offset, int val) +{ + struct sx150x_chip *chip; + + chip = container_of(gc, struct sx150x_chip, gpio_chip); + + mutex_lock(&chip->lock); + if (offset_is_oscio(chip, offset)) + sx150x_set_oscio(chip, val); + else + sx150x_set_io(chip, offset, val); + mutex_unlock(&chip->lock); +} + +static int sx150x_gpio_direction_input(struct gpio_chip *gc, unsigned offset) +{ + struct sx150x_chip *chip; + int status = -EINVAL; + + chip = container_of(gc, struct sx150x_chip, gpio_chip); + + if (!offset_is_oscio(chip, offset)) { + mutex_lock(&chip->lock); + status = sx150x_io_input(chip, offset); + mutex_unlock(&chip->lock); + } + return status; +} + +static int sx150x_gpio_direction_output(struct gpio_chip *gc, + unsigned offset, + int val) +{ + struct sx150x_chip *chip; + int status = 0; + + chip = container_of(gc, struct sx150x_chip, gpio_chip); + + if (!offset_is_oscio(chip, offset)) { + mutex_lock(&chip->lock); + status = sx150x_io_output(chip, offset, val); + mutex_unlock(&chip->lock); + } + return status; +} + +static int sx150x_gpio_to_irq(struct gpio_chip *gc, unsigned offset) +{ + struct sx150x_chip *chip; + + chip = container_of(gc, struct sx150x_chip, gpio_chip); + + if (offset >= chip->dev_cfg->ngpios) + return -EINVAL; + + if (chip->irq_base < 0) + return -EINVAL; + + return chip->irq_base + offset; +} + +static void sx150x_irq_mask(struct irq_data *d) +{ + struct irq_chip *ic = irq_data_get_irq_chip(d); + struct sx150x_chip *chip; + unsigned n; + + chip = container_of(ic, struct sx150x_chip, irq_chip); + n = d->irq - chip->irq_base; + chip->irq_masked |= (1 << n); + chip->irq_update = n; +} + +static void sx150x_irq_unmask(struct irq_data *d) +{ + struct irq_chip *ic = irq_data_get_irq_chip(d); + struct sx150x_chip *chip; + unsigned n; + + chip = container_of(ic, struct sx150x_chip, irq_chip); + n = d->irq - chip->irq_base; + + chip->irq_masked &= ~(1 << n); + chip->irq_update = n; +} + +static int sx150x_irq_set_type(struct irq_data *d, unsigned int flow_type) +{ + struct irq_chip *ic = irq_data_get_irq_chip(d); + struct sx150x_chip *chip; + unsigned n, val = 0; + + if (flow_type & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW)) + return -EINVAL; + + chip = container_of(ic, struct sx150x_chip, irq_chip); + n = d->irq - chip->irq_base; + + if (flow_type & IRQ_TYPE_EDGE_RISING) + val |= 0x1; + if (flow_type & IRQ_TYPE_EDGE_FALLING) + val |= 0x2; + + chip->irq_sense &= ~(3UL << (n * 2)); + chip->irq_sense |= val << (n * 2); + chip->irq_update = n; + return 0; +} + +static irqreturn_t sx150x_irq_thread_fn(int irq, void *dev_id) +{ + struct sx150x_chip *chip = (struct sx150x_chip *)dev_id; + unsigned nhandled = 0; + unsigned sub_irq; + unsigned n; + s32 err; + u8 val; + int i; + + for (i = (chip->dev_cfg->ngpios / 8) - 1; i >= 0; --i) { + err = sx150x_i2c_read(chip->client, + chip->dev_cfg->reg_irq_src - i, + &val); + if (err < 0) + continue; + + sx150x_i2c_write(chip->client, + chip->dev_cfg->reg_irq_src - i, + val); + for (n = 0; n < 8; ++n) { + if (val & (1 << n)) { + sub_irq = chip->irq_base + (i * 8) + n; + handle_nested_irq(sub_irq); + ++nhandled; + } + } + } + + return (nhandled > 0 ? IRQ_HANDLED : IRQ_NONE); +} + +static void sx150x_irq_bus_lock(struct irq_data *d) +{ + struct irq_chip *ic = irq_data_get_irq_chip(d); + struct sx150x_chip *chip; + + chip = container_of(ic, struct sx150x_chip, irq_chip); + + mutex_lock(&chip->lock); +} + +static void sx150x_irq_bus_sync_unlock(struct irq_data *d) +{ + struct irq_chip *ic = irq_data_get_irq_chip(d); + struct sx150x_chip *chip; + unsigned n; + + chip = container_of(ic, struct sx150x_chip, irq_chip); + + if (chip->irq_update == NO_UPDATE_PENDING) + goto out; + + n = chip->irq_update; + chip->irq_update = NO_UPDATE_PENDING; + + /* Avoid updates if nothing changed */ + if (chip->dev_sense == chip->irq_sense && + chip->dev_sense == chip->irq_masked) + goto out; + + chip->dev_sense = chip->irq_sense; + chip->dev_masked = chip->irq_masked; + + if (chip->irq_masked & (1 << n)) { + sx150x_write_cfg(chip, n, 1, chip->dev_cfg->reg_irq_mask, 1); + sx150x_write_cfg(chip, n, 2, chip->dev_cfg->reg_sense, 0); + } else { + sx150x_write_cfg(chip, n, 1, chip->dev_cfg->reg_irq_mask, 0); + sx150x_write_cfg(chip, n, 2, chip->dev_cfg->reg_sense, + chip->irq_sense >> (n * 2)); + } +out: + mutex_unlock(&chip->lock); +} + +static void sx150x_init_chip(struct sx150x_chip *chip, + struct i2c_client *client, + kernel_ulong_t driver_data, + struct sx150x_platform_data *pdata) +{ + mutex_init(&chip->lock); + + chip->client = client; + chip->dev_cfg = &sx150x_devices[driver_data]; + chip->gpio_chip.label = client->name; + chip->gpio_chip.direction_input = sx150x_gpio_direction_input; + chip->gpio_chip.direction_output = sx150x_gpio_direction_output; + chip->gpio_chip.get = sx150x_gpio_get; + chip->gpio_chip.set = sx150x_gpio_set; + chip->gpio_chip.to_irq = sx150x_gpio_to_irq; + chip->gpio_chip.base = pdata->gpio_base; + chip->gpio_chip.can_sleep = 1; + chip->gpio_chip.ngpio = chip->dev_cfg->ngpios; + if (pdata->oscio_is_gpo) + ++chip->gpio_chip.ngpio; + + chip->irq_chip.name = client->name; + chip->irq_chip.irq_mask = sx150x_irq_mask; + chip->irq_chip.irq_unmask = sx150x_irq_unmask; + chip->irq_chip.irq_set_type = sx150x_irq_set_type; + chip->irq_chip.irq_bus_lock = sx150x_irq_bus_lock; + chip->irq_chip.irq_bus_sync_unlock = sx150x_irq_bus_sync_unlock; + chip->irq_summary = -1; + chip->irq_base = -1; + chip->irq_masked = ~0; + chip->irq_sense = 0; + chip->dev_masked = ~0; + chip->dev_sense = 0; + chip->irq_update = NO_UPDATE_PENDING; +} + +static int sx150x_init_io(struct sx150x_chip *chip, u8 base, u16 cfg) +{ + int err = 0; + unsigned n; + + for (n = 0; err >= 0 && n < (chip->dev_cfg->ngpios / 8); ++n) + err = sx150x_i2c_write(chip->client, base - n, cfg >> (n * 8)); + return err; +} + +static int sx150x_reset(struct sx150x_chip *chip) +{ + int err; + + err = i2c_smbus_write_byte_data(chip->client, + chip->dev_cfg->reg_reset, + 0x12); + if (err < 0) + return err; + + err = i2c_smbus_write_byte_data(chip->client, + chip->dev_cfg->reg_reset, + 0x34); + return err; +} + +static int sx150x_init_hw(struct sx150x_chip *chip, + struct sx150x_platform_data *pdata) +{ + int err = 0; + + if (pdata->reset_during_probe) { + err = sx150x_reset(chip); + if (err < 0) + return err; + } + + err = sx150x_i2c_write(chip->client, + chip->dev_cfg->reg_misc, + 0x01); + if (err < 0) + return err; + + err = sx150x_init_io(chip, chip->dev_cfg->reg_pullup, + pdata->io_pullup_ena); + if (err < 0) + return err; + + err = sx150x_init_io(chip, chip->dev_cfg->reg_pulldn, + pdata->io_pulldn_ena); + if (err < 0) + return err; + + err = sx150x_init_io(chip, chip->dev_cfg->reg_drain, + pdata->io_open_drain_ena); + if (err < 0) + return err; + + err = sx150x_init_io(chip, chip->dev_cfg->reg_polarity, + pdata->io_polarity); + if (err < 0) + return err; + + if (pdata->oscio_is_gpo) + sx150x_set_oscio(chip, 0); + + return err; +} + +static int sx150x_install_irq_chip(struct sx150x_chip *chip, + int irq_summary, + int irq_base) +{ + int err; + unsigned n; + unsigned irq; + + chip->irq_summary = irq_summary; + chip->irq_base = irq_base; + + for (n = 0; n < chip->dev_cfg->ngpios; ++n) { + irq = irq_base + n; + irq_set_chip_and_handler(irq, &chip->irq_chip, handle_edge_irq); + irq_set_nested_thread(irq, 1); +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID); +#else + irq_set_noprobe(irq); +#endif + } + + err = request_threaded_irq(irq_summary, + NULL, + sx150x_irq_thread_fn, + IRQF_SHARED | IRQF_TRIGGER_FALLING, + chip->irq_chip.name, + chip); + if (err < 0) { + chip->irq_summary = -1; + chip->irq_base = -1; + } + + return err; +} + +static void sx150x_remove_irq_chip(struct sx150x_chip *chip) +{ + unsigned n; + unsigned irq; + + free_irq(chip->irq_summary, chip); + + for (n = 0; n < chip->dev_cfg->ngpios; ++n) { + irq = chip->irq_base + n; + irq_set_chip_and_handler(irq, NULL, NULL); + } +} + +static int __devinit sx150x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + static const u32 i2c_funcs = I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WRITE_WORD_DATA; + struct sx150x_platform_data *pdata; + struct sx150x_chip *chip; + int rc; + + pdata = client->dev.platform_data; + if (!pdata) + return -EINVAL; + + if (!i2c_check_functionality(client->adapter, i2c_funcs)) + return -ENOSYS; + + chip = kzalloc(sizeof(struct sx150x_chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + sx150x_init_chip(chip, client, id->driver_data, pdata); + rc = sx150x_init_hw(chip, pdata); + if (rc < 0) + goto probe_fail_pre_gpiochip_add; + + rc = gpiochip_add(&chip->gpio_chip); + if (rc < 0) + goto probe_fail_pre_gpiochip_add; + + if (pdata->irq_summary >= 0) { + rc = sx150x_install_irq_chip(chip, + pdata->irq_summary, + pdata->irq_base); + if (rc < 0) + goto probe_fail_post_gpiochip_add; + } + + i2c_set_clientdata(client, chip); + + return 0; +probe_fail_post_gpiochip_add: + WARN_ON(gpiochip_remove(&chip->gpio_chip) < 0); +probe_fail_pre_gpiochip_add: + kfree(chip); + return rc; +} + +static int __devexit sx150x_remove(struct i2c_client *client) +{ + struct sx150x_chip *chip; + int rc; + + chip = i2c_get_clientdata(client); + rc = gpiochip_remove(&chip->gpio_chip); + if (rc < 0) + return rc; + + if (chip->irq_summary >= 0) + sx150x_remove_irq_chip(chip); + + kfree(chip); + + return 0; +} + +static struct i2c_driver sx150x_driver = { + .driver = { + .name = "sx150x", + .owner = THIS_MODULE + }, + .probe = sx150x_probe, + .remove = __devexit_p(sx150x_remove), + .id_table = sx150x_id, +}; + +static int __init sx150x_init(void) +{ + return i2c_add_driver(&sx150x_driver); +} +subsys_initcall(sx150x_init); + +static void __exit sx150x_exit(void) +{ + return i2c_del_driver(&sx150x_driver); +} +module_exit(sx150x_exit); + +MODULE_AUTHOR("Gregory Bean <gbean@codeaurora.org>"); +MODULE_DESCRIPTION("Driver for Semtech SX150X I2C GPIO Expanders"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("i2c:sx150x"); diff --git a/drivers/gpio/gpio-tc3589x.c b/drivers/gpio/gpio-tc3589x.c new file mode 100644 index 00000000..2a82e899 --- /dev/null +++ b/drivers/gpio/gpio-tc3589x.c @@ -0,0 +1,389 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License, version 2 + * Author: Hanumath Prasad <hanumath.prasad@stericsson.com> for ST-Ericsson + * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/mfd/tc3589x.h> + +/* + * These registers are modified under the irq bus lock and cached to avoid + * unnecessary writes in bus_sync_unlock. + */ +enum { REG_IBE, REG_IEV, REG_IS, REG_IE }; + +#define CACHE_NR_REGS 4 +#define CACHE_NR_BANKS 3 + +struct tc3589x_gpio { + struct gpio_chip chip; + struct tc3589x *tc3589x; + 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 tc3589x_gpio *to_tc3589x_gpio(struct gpio_chip *chip) +{ + return container_of(chip, struct tc3589x_gpio, chip); +} + +static int tc3589x_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct tc3589x_gpio *tc3589x_gpio = to_tc3589x_gpio(chip); + struct tc3589x *tc3589x = tc3589x_gpio->tc3589x; + u8 reg = TC3589x_GPIODATA0 + (offset / 8) * 2; + u8 mask = 1 << (offset % 8); + int ret; + + ret = tc3589x_reg_read(tc3589x, reg); + if (ret < 0) + return ret; + + return ret & mask; +} + +static void tc3589x_gpio_set(struct gpio_chip *chip, unsigned offset, int val) +{ + struct tc3589x_gpio *tc3589x_gpio = to_tc3589x_gpio(chip); + struct tc3589x *tc3589x = tc3589x_gpio->tc3589x; + u8 reg = TC3589x_GPIODATA0 + (offset / 8) * 2; + unsigned pos = offset % 8; + u8 data[] = {!!val << pos, 1 << pos}; + + tc3589x_block_write(tc3589x, reg, ARRAY_SIZE(data), data); +} + +static int tc3589x_gpio_direction_output(struct gpio_chip *chip, + unsigned offset, int val) +{ + struct tc3589x_gpio *tc3589x_gpio = to_tc3589x_gpio(chip); + struct tc3589x *tc3589x = tc3589x_gpio->tc3589x; + u8 reg = TC3589x_GPIODIR0 + offset / 8; + unsigned pos = offset % 8; + + tc3589x_gpio_set(chip, offset, val); + + return tc3589x_set_bits(tc3589x, reg, 1 << pos, 1 << pos); +} + +static int tc3589x_gpio_direction_input(struct gpio_chip *chip, + unsigned offset) +{ + struct tc3589x_gpio *tc3589x_gpio = to_tc3589x_gpio(chip); + struct tc3589x *tc3589x = tc3589x_gpio->tc3589x; + u8 reg = TC3589x_GPIODIR0 + offset / 8; + unsigned pos = offset % 8; + + return tc3589x_set_bits(tc3589x, reg, 1 << pos, 0); +} + +static int tc3589x_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct tc3589x_gpio *tc3589x_gpio = to_tc3589x_gpio(chip); + + return tc3589x_gpio->irq_base + offset; +} + +static struct gpio_chip template_chip = { + .label = "tc3589x", + .owner = THIS_MODULE, + .direction_input = tc3589x_gpio_direction_input, + .get = tc3589x_gpio_get, + .direction_output = tc3589x_gpio_direction_output, + .set = tc3589x_gpio_set, + .to_irq = tc3589x_gpio_to_irq, + .can_sleep = 1, +}; + +static int tc3589x_gpio_irq_set_type(struct irq_data *d, unsigned int type) +{ + struct tc3589x_gpio *tc3589x_gpio = irq_data_get_irq_chip_data(d); + int offset = d->irq - tc3589x_gpio->irq_base; + int regoffset = offset / 8; + int mask = 1 << (offset % 8); + + if (type == IRQ_TYPE_EDGE_BOTH) { + tc3589x_gpio->regs[REG_IBE][regoffset] |= mask; + return 0; + } + + tc3589x_gpio->regs[REG_IBE][regoffset] &= ~mask; + + if (type == IRQ_TYPE_LEVEL_LOW || type == IRQ_TYPE_LEVEL_HIGH) + tc3589x_gpio->regs[REG_IS][regoffset] |= mask; + else + tc3589x_gpio->regs[REG_IS][regoffset] &= ~mask; + + if (type == IRQ_TYPE_EDGE_RISING || type == IRQ_TYPE_LEVEL_HIGH) + tc3589x_gpio->regs[REG_IEV][regoffset] |= mask; + else + tc3589x_gpio->regs[REG_IEV][regoffset] &= ~mask; + + return 0; +} + +static void tc3589x_gpio_irq_lock(struct irq_data *d) +{ + struct tc3589x_gpio *tc3589x_gpio = irq_data_get_irq_chip_data(d); + + mutex_lock(&tc3589x_gpio->irq_lock); +} + +static void tc3589x_gpio_irq_sync_unlock(struct irq_data *d) +{ + struct tc3589x_gpio *tc3589x_gpio = irq_data_get_irq_chip_data(d); + struct tc3589x *tc3589x = tc3589x_gpio->tc3589x; + static const u8 regmap[] = { + [REG_IBE] = TC3589x_GPIOIBE0, + [REG_IEV] = TC3589x_GPIOIEV0, + [REG_IS] = TC3589x_GPIOIS0, + [REG_IE] = TC3589x_GPIOIE0, + }; + int i, j; + + for (i = 0; i < CACHE_NR_REGS; i++) { + for (j = 0; j < CACHE_NR_BANKS; j++) { + u8 old = tc3589x_gpio->oldregs[i][j]; + u8 new = tc3589x_gpio->regs[i][j]; + + if (new == old) + continue; + + tc3589x_gpio->oldregs[i][j] = new; + tc3589x_reg_write(tc3589x, regmap[i] + j * 8, new); + } + } + + mutex_unlock(&tc3589x_gpio->irq_lock); +} + +static void tc3589x_gpio_irq_mask(struct irq_data *d) +{ + struct tc3589x_gpio *tc3589x_gpio = irq_data_get_irq_chip_data(d); + int offset = d->irq - tc3589x_gpio->irq_base; + int regoffset = offset / 8; + int mask = 1 << (offset % 8); + + tc3589x_gpio->regs[REG_IE][regoffset] &= ~mask; +} + +static void tc3589x_gpio_irq_unmask(struct irq_data *d) +{ + struct tc3589x_gpio *tc3589x_gpio = irq_data_get_irq_chip_data(d); + int offset = d->irq - tc3589x_gpio->irq_base; + int regoffset = offset / 8; + int mask = 1 << (offset % 8); + + tc3589x_gpio->regs[REG_IE][regoffset] |= mask; +} + +static struct irq_chip tc3589x_gpio_irq_chip = { + .name = "tc3589x-gpio", + .irq_bus_lock = tc3589x_gpio_irq_lock, + .irq_bus_sync_unlock = tc3589x_gpio_irq_sync_unlock, + .irq_mask = tc3589x_gpio_irq_mask, + .irq_unmask = tc3589x_gpio_irq_unmask, + .irq_set_type = tc3589x_gpio_irq_set_type, +}; + +static irqreturn_t tc3589x_gpio_irq(int irq, void *dev) +{ + struct tc3589x_gpio *tc3589x_gpio = dev; + struct tc3589x *tc3589x = tc3589x_gpio->tc3589x; + u8 status[CACHE_NR_BANKS]; + int ret; + int i; + + ret = tc3589x_block_read(tc3589x, TC3589x_GPIOMIS0, + ARRAY_SIZE(status), status); + if (ret < 0) + return IRQ_NONE; + + for (i = 0; i < ARRAY_SIZE(status); i++) { + unsigned int stat = status[i]; + if (!stat) + continue; + + while (stat) { + int bit = __ffs(stat); + int line = i * 8 + bit; + + handle_nested_irq(tc3589x_gpio->irq_base + line); + stat &= ~(1 << bit); + } + + tc3589x_reg_write(tc3589x, TC3589x_GPIOIC0 + i, status[i]); + } + + return IRQ_HANDLED; +} + +static int tc3589x_gpio_irq_init(struct tc3589x_gpio *tc3589x_gpio) +{ + int base = tc3589x_gpio->irq_base; + int irq; + + for (irq = base; irq < base + tc3589x_gpio->chip.ngpio; irq++) { + irq_set_chip_data(irq, tc3589x_gpio); + irq_set_chip_and_handler(irq, &tc3589x_gpio_irq_chip, + handle_simple_irq); + irq_set_nested_thread(irq, 1); +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID); +#else + irq_set_noprobe(irq); +#endif + } + + return 0; +} + +static void tc3589x_gpio_irq_remove(struct tc3589x_gpio *tc3589x_gpio) +{ + int base = tc3589x_gpio->irq_base; + int irq; + + for (irq = base; irq < base + tc3589x_gpio->chip.ngpio; irq++) { +#ifdef CONFIG_ARM + set_irq_flags(irq, 0); +#endif + irq_set_chip_and_handler(irq, NULL, NULL); + irq_set_chip_data(irq, NULL); + } +} + +static int __devinit tc3589x_gpio_probe(struct platform_device *pdev) +{ + struct tc3589x *tc3589x = dev_get_drvdata(pdev->dev.parent); + struct tc3589x_gpio_platform_data *pdata; + struct tc3589x_gpio *tc3589x_gpio; + int ret; + int irq; + + pdata = tc3589x->pdata->gpio; + if (!pdata) + return -ENODEV; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + tc3589x_gpio = kzalloc(sizeof(struct tc3589x_gpio), GFP_KERNEL); + if (!tc3589x_gpio) + return -ENOMEM; + + mutex_init(&tc3589x_gpio->irq_lock); + + tc3589x_gpio->dev = &pdev->dev; + tc3589x_gpio->tc3589x = tc3589x; + + tc3589x_gpio->chip = template_chip; + tc3589x_gpio->chip.ngpio = tc3589x->num_gpio; + tc3589x_gpio->chip.dev = &pdev->dev; + tc3589x_gpio->chip.base = pdata->gpio_base; + + tc3589x_gpio->irq_base = tc3589x->irq_base + TC3589x_INT_GPIO(0); + + /* Bring the GPIO module out of reset */ + ret = tc3589x_set_bits(tc3589x, TC3589x_RSTCTRL, + TC3589x_RSTCTRL_GPIRST, 0); + if (ret < 0) + goto out_free; + + ret = tc3589x_gpio_irq_init(tc3589x_gpio); + if (ret) + goto out_free; + + ret = request_threaded_irq(irq, NULL, tc3589x_gpio_irq, IRQF_ONESHOT, + "tc3589x-gpio", tc3589x_gpio); + if (ret) { + dev_err(&pdev->dev, "unable to get irq: %d\n", ret); + goto out_removeirq; + } + + ret = gpiochip_add(&tc3589x_gpio->chip); + if (ret) { + dev_err(&pdev->dev, "unable to add gpiochip: %d\n", ret); + goto out_freeirq; + } + + if (pdata->setup) + pdata->setup(tc3589x, tc3589x_gpio->chip.base); + + platform_set_drvdata(pdev, tc3589x_gpio); + + return 0; + +out_freeirq: + free_irq(irq, tc3589x_gpio); +out_removeirq: + tc3589x_gpio_irq_remove(tc3589x_gpio); +out_free: + kfree(tc3589x_gpio); + return ret; +} + +static int __devexit tc3589x_gpio_remove(struct platform_device *pdev) +{ + struct tc3589x_gpio *tc3589x_gpio = platform_get_drvdata(pdev); + struct tc3589x *tc3589x = tc3589x_gpio->tc3589x; + struct tc3589x_gpio_platform_data *pdata = tc3589x->pdata->gpio; + int irq = platform_get_irq(pdev, 0); + int ret; + + if (pdata->remove) + pdata->remove(tc3589x, tc3589x_gpio->chip.base); + + ret = gpiochip_remove(&tc3589x_gpio->chip); + if (ret < 0) { + dev_err(tc3589x_gpio->dev, + "unable to remove gpiochip: %d\n", ret); + return ret; + } + + free_irq(irq, tc3589x_gpio); + tc3589x_gpio_irq_remove(tc3589x_gpio); + + platform_set_drvdata(pdev, NULL); + kfree(tc3589x_gpio); + + return 0; +} + +static struct platform_driver tc3589x_gpio_driver = { + .driver.name = "tc3589x-gpio", + .driver.owner = THIS_MODULE, + .probe = tc3589x_gpio_probe, + .remove = __devexit_p(tc3589x_gpio_remove), +}; + +static int __init tc3589x_gpio_init(void) +{ + return platform_driver_register(&tc3589x_gpio_driver); +} +subsys_initcall(tc3589x_gpio_init); + +static void __exit tc3589x_gpio_exit(void) +{ + platform_driver_unregister(&tc3589x_gpio_driver); +} +module_exit(tc3589x_gpio_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("TC3589x GPIO driver"); +MODULE_AUTHOR("Hanumath Prasad, Rabin Vincent"); diff --git a/drivers/gpio/gpio-tegra.c b/drivers/gpio/gpio-tegra.c new file mode 100644 index 00000000..12f349b3 --- /dev/null +++ b/drivers/gpio/gpio-tegra.c @@ -0,0 +1,554 @@ +/* + * arch/arm/mach-tegra/gpio.c + * + * Copyright (c) 2010 Google, Inc + * + * Author: + * Erik Gilling <konkers@google.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/init.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/gpio.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/irqdomain.h> + +#include <asm/mach/irq.h> + +#include <mach/gpio-tegra.h> +#include <mach/iomap.h> +#include <mach/suspend.h> + +#define GPIO_BANK(x) ((x) >> 5) +#define GPIO_PORT(x) (((x) >> 3) & 0x3) +#define GPIO_BIT(x) ((x) & 0x7) + +#define GPIO_REG(x) (GPIO_BANK(x) * tegra_gpio_bank_stride + \ + GPIO_PORT(x) * 4) + +#define GPIO_CNF(x) (GPIO_REG(x) + 0x00) +#define GPIO_OE(x) (GPIO_REG(x) + 0x10) +#define GPIO_OUT(x) (GPIO_REG(x) + 0X20) +#define GPIO_IN(x) (GPIO_REG(x) + 0x30) +#define GPIO_INT_STA(x) (GPIO_REG(x) + 0x40) +#define GPIO_INT_ENB(x) (GPIO_REG(x) + 0x50) +#define GPIO_INT_LVL(x) (GPIO_REG(x) + 0x60) +#define GPIO_INT_CLR(x) (GPIO_REG(x) + 0x70) + +#define GPIO_MSK_CNF(x) (GPIO_REG(x) + tegra_gpio_upper_offset + 0x00) +#define GPIO_MSK_OE(x) (GPIO_REG(x) + tegra_gpio_upper_offset + 0x10) +#define GPIO_MSK_OUT(x) (GPIO_REG(x) + tegra_gpio_upper_offset + 0X20) +#define GPIO_MSK_INT_STA(x) (GPIO_REG(x) + tegra_gpio_upper_offset + 0x40) +#define GPIO_MSK_INT_ENB(x) (GPIO_REG(x) + tegra_gpio_upper_offset + 0x50) +#define GPIO_MSK_INT_LVL(x) (GPIO_REG(x) + tegra_gpio_upper_offset + 0x60) + +#define GPIO_INT_LVL_MASK 0x010101 +#define GPIO_INT_LVL_EDGE_RISING 0x000101 +#define GPIO_INT_LVL_EDGE_FALLING 0x000100 +#define GPIO_INT_LVL_EDGE_BOTH 0x010100 +#define GPIO_INT_LVL_LEVEL_HIGH 0x000001 +#define GPIO_INT_LVL_LEVEL_LOW 0x000000 + +struct tegra_gpio_bank { + int bank; + int irq; + spinlock_t lvl_lock[4]; +#ifdef CONFIG_PM + u32 cnf[4]; + u32 out[4]; + u32 oe[4]; + u32 int_enb[4]; + u32 int_lvl[4]; +#endif +}; + +static struct irq_domain *irq_domain; +static void __iomem *regs; +static u32 tegra_gpio_bank_count; +static u32 tegra_gpio_bank_stride; +static u32 tegra_gpio_upper_offset; +static struct tegra_gpio_bank *tegra_gpio_banks; + +static inline void tegra_gpio_writel(u32 val, u32 reg) +{ + __raw_writel(val, regs + reg); +} + +static inline u32 tegra_gpio_readl(u32 reg) +{ + return __raw_readl(regs + reg); +} + +static int tegra_gpio_compose(int bank, int port, int bit) +{ + return (bank << 5) | ((port & 0x3) << 3) | (bit & 0x7); +} + +static void tegra_gpio_mask_write(u32 reg, int gpio, int value) +{ + u32 val; + + val = 0x100 << GPIO_BIT(gpio); + if (value) + val |= 1 << GPIO_BIT(gpio); + tegra_gpio_writel(val, reg); +} + +void tegra_gpio_enable(int gpio) +{ + tegra_gpio_mask_write(GPIO_MSK_CNF(gpio), gpio, 1); +} +EXPORT_SYMBOL_GPL(tegra_gpio_enable); + +void tegra_gpio_disable(int gpio) +{ + tegra_gpio_mask_write(GPIO_MSK_CNF(gpio), gpio, 0); +} +EXPORT_SYMBOL_GPL(tegra_gpio_disable); + +static void tegra_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + tegra_gpio_mask_write(GPIO_MSK_OUT(offset), offset, value); +} + +static int tegra_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + return (tegra_gpio_readl(GPIO_IN(offset)) >> GPIO_BIT(offset)) & 0x1; +} + +static int tegra_gpio_direction_input(struct gpio_chip *chip, unsigned offset) +{ + tegra_gpio_mask_write(GPIO_MSK_OE(offset), offset, 0); + return 0; +} + +static int tegra_gpio_direction_output(struct gpio_chip *chip, unsigned offset, + int value) +{ + tegra_gpio_set(chip, offset, value); + tegra_gpio_mask_write(GPIO_MSK_OE(offset), offset, 1); + return 0; +} + +static int tegra_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + return irq_find_mapping(irq_domain, offset); +} + +static struct gpio_chip tegra_gpio_chip = { + .label = "tegra-gpio", + .direction_input = tegra_gpio_direction_input, + .get = tegra_gpio_get, + .direction_output = tegra_gpio_direction_output, + .set = tegra_gpio_set, + .to_irq = tegra_gpio_to_irq, + .base = 0, + .ngpio = TEGRA_NR_GPIOS, +}; + +static void tegra_gpio_irq_ack(struct irq_data *d) +{ + int gpio = d->hwirq; + + tegra_gpio_writel(1 << GPIO_BIT(gpio), GPIO_INT_CLR(gpio)); +} + +static void tegra_gpio_irq_mask(struct irq_data *d) +{ + int gpio = d->hwirq; + + tegra_gpio_mask_write(GPIO_MSK_INT_ENB(gpio), gpio, 0); +} + +static void tegra_gpio_irq_unmask(struct irq_data *d) +{ + int gpio = d->hwirq; + + tegra_gpio_mask_write(GPIO_MSK_INT_ENB(gpio), gpio, 1); +} + +static int tegra_gpio_irq_set_type(struct irq_data *d, unsigned int type) +{ + int gpio = d->hwirq; + struct tegra_gpio_bank *bank = irq_data_get_irq_chip_data(d); + int port = GPIO_PORT(gpio); + int lvl_type; + int val; + unsigned long flags; + + switch (type & IRQ_TYPE_SENSE_MASK) { + case IRQ_TYPE_EDGE_RISING: + lvl_type = GPIO_INT_LVL_EDGE_RISING; + break; + + case IRQ_TYPE_EDGE_FALLING: + lvl_type = GPIO_INT_LVL_EDGE_FALLING; + break; + + case IRQ_TYPE_EDGE_BOTH: + lvl_type = GPIO_INT_LVL_EDGE_BOTH; + break; + + case IRQ_TYPE_LEVEL_HIGH: + lvl_type = GPIO_INT_LVL_LEVEL_HIGH; + break; + + case IRQ_TYPE_LEVEL_LOW: + lvl_type = GPIO_INT_LVL_LEVEL_LOW; + break; + + default: + return -EINVAL; + } + + spin_lock_irqsave(&bank->lvl_lock[port], flags); + + val = tegra_gpio_readl(GPIO_INT_LVL(gpio)); + val &= ~(GPIO_INT_LVL_MASK << GPIO_BIT(gpio)); + val |= lvl_type << GPIO_BIT(gpio); + tegra_gpio_writel(val, GPIO_INT_LVL(gpio)); + + spin_unlock_irqrestore(&bank->lvl_lock[port], flags); + + if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH)) + __irq_set_handler_locked(d->irq, handle_level_irq); + else if (type & (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)) + __irq_set_handler_locked(d->irq, handle_edge_irq); + + return 0; +} + +static void tegra_gpio_irq_handler(unsigned int irq, struct irq_desc *desc) +{ + struct tegra_gpio_bank *bank; + int port; + int pin; + int unmasked = 0; + struct irq_chip *chip = irq_desc_get_chip(desc); + + chained_irq_enter(chip, desc); + + bank = irq_get_handler_data(irq); + + for (port = 0; port < 4; port++) { + int gpio = tegra_gpio_compose(bank->bank, port, 0); + unsigned long sta = tegra_gpio_readl(GPIO_INT_STA(gpio)) & + tegra_gpio_readl(GPIO_INT_ENB(gpio)); + u32 lvl = tegra_gpio_readl(GPIO_INT_LVL(gpio)); + + for_each_set_bit(pin, &sta, 8) { + tegra_gpio_writel(1 << pin, GPIO_INT_CLR(gpio)); + + /* if gpio is edge triggered, clear condition + * before executing the hander so that we don't + * miss edges + */ + if (lvl & (0x100 << pin)) { + unmasked = 1; + chained_irq_exit(chip, desc); + } + + generic_handle_irq(gpio_to_irq(gpio + pin)); + } + } + + if (!unmasked) + chained_irq_exit(chip, desc); + +} + +#ifdef CONFIG_PM +void tegra_gpio_resume(void) +{ + unsigned long flags; + int b; + int p; + + local_irq_save(flags); + + for (b = 0; b < tegra_gpio_bank_count; b++) { + struct tegra_gpio_bank *bank = &tegra_gpio_banks[b]; + + for (p = 0; p < ARRAY_SIZE(bank->oe); p++) { + unsigned int gpio = (b<<5) | (p<<3); + tegra_gpio_writel(bank->cnf[p], GPIO_CNF(gpio)); + tegra_gpio_writel(bank->out[p], GPIO_OUT(gpio)); + tegra_gpio_writel(bank->oe[p], GPIO_OE(gpio)); + tegra_gpio_writel(bank->int_lvl[p], GPIO_INT_LVL(gpio)); + tegra_gpio_writel(bank->int_enb[p], GPIO_INT_ENB(gpio)); + } + } + + local_irq_restore(flags); +} + +void tegra_gpio_suspend(void) +{ + unsigned long flags; + int b; + int p; + + local_irq_save(flags); + for (b = 0; b < tegra_gpio_bank_count; b++) { + struct tegra_gpio_bank *bank = &tegra_gpio_banks[b]; + + for (p = 0; p < ARRAY_SIZE(bank->oe); p++) { + unsigned int gpio = (b<<5) | (p<<3); + bank->cnf[p] = tegra_gpio_readl(GPIO_CNF(gpio)); + bank->out[p] = tegra_gpio_readl(GPIO_OUT(gpio)); + bank->oe[p] = tegra_gpio_readl(GPIO_OE(gpio)); + bank->int_enb[p] = tegra_gpio_readl(GPIO_INT_ENB(gpio)); + bank->int_lvl[p] = tegra_gpio_readl(GPIO_INT_LVL(gpio)); + } + } + local_irq_restore(flags); +} + +static int tegra_gpio_wake_enable(struct irq_data *d, unsigned int enable) +{ + struct tegra_gpio_bank *bank = irq_data_get_irq_chip_data(d); + return irq_set_irq_wake(bank->irq, enable); +} +#endif + +static struct irq_chip tegra_gpio_irq_chip = { + .name = "GPIO", + .irq_ack = tegra_gpio_irq_ack, + .irq_mask = tegra_gpio_irq_mask, + .irq_unmask = tegra_gpio_irq_unmask, + .irq_set_type = tegra_gpio_irq_set_type, +#ifdef CONFIG_PM + .irq_set_wake = tegra_gpio_wake_enable, +#endif +}; + +struct tegra_gpio_soc_config { + u32 bank_stride; + u32 upper_offset; +}; + +static struct tegra_gpio_soc_config tegra20_gpio_config = { + .bank_stride = 0x80, + .upper_offset = 0x800, +}; + +static struct tegra_gpio_soc_config tegra30_gpio_config = { + .bank_stride = 0x100, + .upper_offset = 0x80, +}; + +static struct of_device_id tegra_gpio_of_match[] __devinitdata = { + { .compatible = "nvidia,tegra30-gpio", .data = &tegra30_gpio_config }, + { .compatible = "nvidia,tegra20-gpio", .data = &tegra20_gpio_config }, + { }, +}; + +/* This lock class tells lockdep that GPIO irqs are in a different + * category than their parents, so it won't report false recursion. + */ +static struct lock_class_key gpio_lock_class; + +static int __devinit tegra_gpio_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + struct tegra_gpio_soc_config *config; + int irq_base; + struct resource *res; + struct tegra_gpio_bank *bank; + int gpio; + int i; + int j; + + match = of_match_device(tegra_gpio_of_match, &pdev->dev); + if (match) + config = (struct tegra_gpio_soc_config *)match->data; + else + config = &tegra20_gpio_config; + + tegra_gpio_bank_stride = config->bank_stride; + tegra_gpio_upper_offset = config->upper_offset; + + for (;;) { + res = platform_get_resource(pdev, IORESOURCE_IRQ, tegra_gpio_bank_count); + if (!res) + break; + tegra_gpio_bank_count++; + } + if (!tegra_gpio_bank_count) { + dev_err(&pdev->dev, "Missing IRQ resource\n"); + return -ENODEV; + } + + tegra_gpio_chip.ngpio = tegra_gpio_bank_count * 32; + + tegra_gpio_banks = devm_kzalloc(&pdev->dev, + tegra_gpio_bank_count * sizeof(*tegra_gpio_banks), + GFP_KERNEL); + if (!tegra_gpio_banks) { + dev_err(&pdev->dev, "Couldn't allocate bank structure\n"); + return -ENODEV; + } + + irq_base = irq_alloc_descs(-1, 0, tegra_gpio_chip.ngpio, 0); + if (irq_base < 0) { + dev_err(&pdev->dev, "Couldn't allocate IRQ numbers\n"); + return -ENODEV; + } + irq_domain = irq_domain_add_legacy(pdev->dev.of_node, + tegra_gpio_chip.ngpio, irq_base, 0, + &irq_domain_simple_ops, NULL); + + for (i = 0; i < tegra_gpio_bank_count; i++) { + res = platform_get_resource(pdev, IORESOURCE_IRQ, i); + if (!res) { + dev_err(&pdev->dev, "Missing IRQ resource\n"); + return -ENODEV; + } + + bank = &tegra_gpio_banks[i]; + bank->bank = i; + bank->irq = res->start; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Missing MEM resource\n"); + return -ENODEV; + } + + regs = devm_request_and_ioremap(&pdev->dev, res); + if (!regs) { + dev_err(&pdev->dev, "Couldn't ioremap regs\n"); + return -ENODEV; + } + + for (i = 0; i < tegra_gpio_bank_count; i++) { + for (j = 0; j < 4; j++) { + int gpio = tegra_gpio_compose(i, j, 0); + tegra_gpio_writel(0x00, GPIO_INT_ENB(gpio)); + } + } + +#ifdef CONFIG_OF_GPIO + tegra_gpio_chip.of_node = pdev->dev.of_node; +#endif + + gpiochip_add(&tegra_gpio_chip); + + for (gpio = 0; gpio < tegra_gpio_chip.ngpio; gpio++) { + int irq = irq_find_mapping(irq_domain, gpio); + /* No validity check; all Tegra GPIOs are valid IRQs */ + + bank = &tegra_gpio_banks[GPIO_BANK(gpio)]; + + irq_set_lockdep_class(irq, &gpio_lock_class); + irq_set_chip_data(irq, bank); + irq_set_chip_and_handler(irq, &tegra_gpio_irq_chip, + handle_simple_irq); + set_irq_flags(irq, IRQF_VALID); + } + + for (i = 0; i < tegra_gpio_bank_count; i++) { + bank = &tegra_gpio_banks[i]; + + irq_set_chained_handler(bank->irq, tegra_gpio_irq_handler); + irq_set_handler_data(bank->irq, bank); + + for (j = 0; j < 4; j++) + spin_lock_init(&bank->lvl_lock[j]); + } + + return 0; +} + +static struct platform_driver tegra_gpio_driver = { + .driver = { + .name = "tegra-gpio", + .owner = THIS_MODULE, + .of_match_table = tegra_gpio_of_match, + }, + .probe = tegra_gpio_probe, +}; + +static int __init tegra_gpio_init(void) +{ + return platform_driver_register(&tegra_gpio_driver); +} +postcore_initcall(tegra_gpio_init); + +void tegra_gpio_config(struct tegra_gpio_table *table, int num) +{ + int i; + + for (i = 0; i < num; i++) { + int gpio = table[i].gpio; + + if (table[i].enable) + tegra_gpio_enable(gpio); + else + tegra_gpio_disable(gpio); + } +} + +#ifdef CONFIG_DEBUG_FS + +#include <linux/debugfs.h> +#include <linux/seq_file.h> + +static int dbg_gpio_show(struct seq_file *s, void *unused) +{ + int i; + int j; + + for (i = 0; i < tegra_gpio_bank_count; i++) { + for (j = 0; j < 4; j++) { + int gpio = tegra_gpio_compose(i, j, 0); + seq_printf(s, + "%d:%d %02x %02x %02x %02x %02x %02x %06x\n", + i, j, + tegra_gpio_readl(GPIO_CNF(gpio)), + tegra_gpio_readl(GPIO_OE(gpio)), + tegra_gpio_readl(GPIO_OUT(gpio)), + tegra_gpio_readl(GPIO_IN(gpio)), + tegra_gpio_readl(GPIO_INT_STA(gpio)), + tegra_gpio_readl(GPIO_INT_ENB(gpio)), + tegra_gpio_readl(GPIO_INT_LVL(gpio))); + } + } + return 0; +} + +static int dbg_gpio_open(struct inode *inode, struct file *file) +{ + return single_open(file, dbg_gpio_show, &inode->i_private); +} + +static const struct file_operations debug_fops = { + .open = dbg_gpio_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int __init tegra_gpio_debuginit(void) +{ + (void) debugfs_create_file("tegra_gpio", S_IRUGO, + NULL, NULL, &debug_fops); + return 0; +} +late_initcall(tegra_gpio_debuginit); +#endif diff --git a/drivers/gpio/gpio-timberdale.c b/drivers/gpio/gpio-timberdale.c new file mode 100644 index 00000000..031c6adf --- /dev/null +++ b/drivers/gpio/gpio-timberdale.c @@ -0,0 +1,368 @@ +/* + * Timberdale FPGA GPIO driver + * Copyright (c) 2009 Intel Corporation + * + * 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. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* Supports: + * Timberdale FPGA GPIO + */ + +#include <linux/module.h> +#include <linux/gpio.h> +#include <linux/platform_device.h> +#include <linux/irq.h> +#include <linux/io.h> +#include <linux/timb_gpio.h> +#include <linux/interrupt.h> +#include <linux/slab.h> + +#define DRIVER_NAME "timb-gpio" + +#define TGPIOVAL 0x00 +#define TGPIODIR 0x04 +#define TGPIO_IER 0x08 +#define TGPIO_ISR 0x0c +#define TGPIO_IPR 0x10 +#define TGPIO_ICR 0x14 +#define TGPIO_FLR 0x18 +#define TGPIO_LVR 0x1c +#define TGPIO_VER 0x20 +#define TGPIO_BFLR 0x24 + +struct timbgpio { + void __iomem *membase; + spinlock_t lock; /* mutual exclusion */ + struct gpio_chip gpio; + int irq_base; + unsigned long last_ier; +}; + +static int timbgpio_update_bit(struct gpio_chip *gpio, unsigned index, + unsigned offset, bool enabled) +{ + struct timbgpio *tgpio = container_of(gpio, struct timbgpio, gpio); + u32 reg; + + spin_lock(&tgpio->lock); + reg = ioread32(tgpio->membase + offset); + + if (enabled) + reg |= (1 << index); + else + reg &= ~(1 << index); + + iowrite32(reg, tgpio->membase + offset); + spin_unlock(&tgpio->lock); + + return 0; +} + +static int timbgpio_gpio_direction_input(struct gpio_chip *gpio, unsigned nr) +{ + return timbgpio_update_bit(gpio, nr, TGPIODIR, true); +} + +static int timbgpio_gpio_get(struct gpio_chip *gpio, unsigned nr) +{ + struct timbgpio *tgpio = container_of(gpio, struct timbgpio, gpio); + u32 value; + + value = ioread32(tgpio->membase + TGPIOVAL); + return (value & (1 << nr)) ? 1 : 0; +} + +static int timbgpio_gpio_direction_output(struct gpio_chip *gpio, + unsigned nr, int val) +{ + return timbgpio_update_bit(gpio, nr, TGPIODIR, false); +} + +static void timbgpio_gpio_set(struct gpio_chip *gpio, + unsigned nr, int val) +{ + timbgpio_update_bit(gpio, nr, TGPIOVAL, val != 0); +} + +static int timbgpio_to_irq(struct gpio_chip *gpio, unsigned offset) +{ + struct timbgpio *tgpio = container_of(gpio, struct timbgpio, gpio); + + if (tgpio->irq_base <= 0) + return -EINVAL; + + return tgpio->irq_base + offset; +} + +/* + * GPIO IRQ + */ +static void timbgpio_irq_disable(struct irq_data *d) +{ + struct timbgpio *tgpio = irq_data_get_irq_chip_data(d); + int offset = d->irq - tgpio->irq_base; + unsigned long flags; + + spin_lock_irqsave(&tgpio->lock, flags); + tgpio->last_ier &= ~(1 << offset); + iowrite32(tgpio->last_ier, tgpio->membase + TGPIO_IER); + spin_unlock_irqrestore(&tgpio->lock, flags); +} + +static void timbgpio_irq_enable(struct irq_data *d) +{ + struct timbgpio *tgpio = irq_data_get_irq_chip_data(d); + int offset = d->irq - tgpio->irq_base; + unsigned long flags; + + spin_lock_irqsave(&tgpio->lock, flags); + tgpio->last_ier |= 1 << offset; + iowrite32(tgpio->last_ier, tgpio->membase + TGPIO_IER); + spin_unlock_irqrestore(&tgpio->lock, flags); +} + +static int timbgpio_irq_type(struct irq_data *d, unsigned trigger) +{ + struct timbgpio *tgpio = irq_data_get_irq_chip_data(d); + int offset = d->irq - tgpio->irq_base; + unsigned long flags; + u32 lvr, flr, bflr = 0; + u32 ver; + int ret = 0; + + if (offset < 0 || offset > tgpio->gpio.ngpio) + return -EINVAL; + + ver = ioread32(tgpio->membase + TGPIO_VER); + + spin_lock_irqsave(&tgpio->lock, flags); + + lvr = ioread32(tgpio->membase + TGPIO_LVR); + flr = ioread32(tgpio->membase + TGPIO_FLR); + if (ver > 2) + bflr = ioread32(tgpio->membase + TGPIO_BFLR); + + if (trigger & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW)) { + bflr &= ~(1 << offset); + flr &= ~(1 << offset); + if (trigger & IRQ_TYPE_LEVEL_HIGH) + lvr |= 1 << offset; + else + lvr &= ~(1 << offset); + } + + if ((trigger & IRQ_TYPE_EDGE_BOTH) == IRQ_TYPE_EDGE_BOTH) { + if (ver < 3) { + ret = -EINVAL; + goto out; + } + else { + flr |= 1 << offset; + bflr |= 1 << offset; + } + } else { + bflr &= ~(1 << offset); + flr |= 1 << offset; + if (trigger & IRQ_TYPE_EDGE_FALLING) + lvr &= ~(1 << offset); + else + lvr |= 1 << offset; + } + + iowrite32(lvr, tgpio->membase + TGPIO_LVR); + iowrite32(flr, tgpio->membase + TGPIO_FLR); + if (ver > 2) + iowrite32(bflr, tgpio->membase + TGPIO_BFLR); + + iowrite32(1 << offset, tgpio->membase + TGPIO_ICR); + +out: + spin_unlock_irqrestore(&tgpio->lock, flags); + return ret; +} + +static void timbgpio_irq(unsigned int irq, struct irq_desc *desc) +{ + struct timbgpio *tgpio = irq_get_handler_data(irq); + unsigned long ipr; + int offset; + + desc->irq_data.chip->irq_ack(irq_get_irq_data(irq)); + ipr = ioread32(tgpio->membase + TGPIO_IPR); + iowrite32(ipr, tgpio->membase + TGPIO_ICR); + + /* + * Some versions of the hardware trash the IER register if more than + * one interrupt is received simultaneously. + */ + iowrite32(0, tgpio->membase + TGPIO_IER); + + for_each_set_bit(offset, &ipr, tgpio->gpio.ngpio) + generic_handle_irq(timbgpio_to_irq(&tgpio->gpio, offset)); + + iowrite32(tgpio->last_ier, tgpio->membase + TGPIO_IER); +} + +static struct irq_chip timbgpio_irqchip = { + .name = "GPIO", + .irq_enable = timbgpio_irq_enable, + .irq_disable = timbgpio_irq_disable, + .irq_set_type = timbgpio_irq_type, +}; + +static int __devinit timbgpio_probe(struct platform_device *pdev) +{ + int err, i; + struct gpio_chip *gc; + struct timbgpio *tgpio; + struct resource *iomem; + struct timbgpio_platform_data *pdata = pdev->dev.platform_data; + int irq = platform_get_irq(pdev, 0); + + if (!pdata || pdata->nr_pins > 32) { + err = -EINVAL; + goto err_mem; + } + + iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!iomem) { + err = -EINVAL; + goto err_mem; + } + + tgpio = kzalloc(sizeof(*tgpio), GFP_KERNEL); + if (!tgpio) { + err = -EINVAL; + goto err_mem; + } + tgpio->irq_base = pdata->irq_base; + + spin_lock_init(&tgpio->lock); + + if (!request_mem_region(iomem->start, resource_size(iomem), + DRIVER_NAME)) { + err = -EBUSY; + goto err_request; + } + + tgpio->membase = ioremap(iomem->start, resource_size(iomem)); + if (!tgpio->membase) { + err = -ENOMEM; + goto err_ioremap; + } + + gc = &tgpio->gpio; + + gc->label = dev_name(&pdev->dev); + gc->owner = THIS_MODULE; + gc->dev = &pdev->dev; + gc->direction_input = timbgpio_gpio_direction_input; + gc->get = timbgpio_gpio_get; + gc->direction_output = timbgpio_gpio_direction_output; + gc->set = timbgpio_gpio_set; + gc->to_irq = (irq >= 0 && tgpio->irq_base > 0) ? timbgpio_to_irq : NULL; + gc->dbg_show = NULL; + gc->base = pdata->gpio_base; + gc->ngpio = pdata->nr_pins; + gc->can_sleep = 0; + + err = gpiochip_add(gc); + if (err) + goto err_chipadd; + + platform_set_drvdata(pdev, tgpio); + + /* make sure to disable interrupts */ + iowrite32(0x0, tgpio->membase + TGPIO_IER); + + if (irq < 0 || tgpio->irq_base <= 0) + return 0; + + for (i = 0; i < pdata->nr_pins; i++) { + irq_set_chip_and_handler_name(tgpio->irq_base + i, + &timbgpio_irqchip, handle_simple_irq, "mux"); + irq_set_chip_data(tgpio->irq_base + i, tgpio); +#ifdef CONFIG_ARM + set_irq_flags(tgpio->irq_base + i, IRQF_VALID | IRQF_PROBE); +#endif + } + + irq_set_handler_data(irq, tgpio); + irq_set_chained_handler(irq, timbgpio_irq); + + return 0; + +err_chipadd: + iounmap(tgpio->membase); +err_ioremap: + release_mem_region(iomem->start, resource_size(iomem)); +err_request: + kfree(tgpio); +err_mem: + printk(KERN_ERR DRIVER_NAME": Failed to register GPIOs: %d\n", err); + + return err; +} + +static int __devexit timbgpio_remove(struct platform_device *pdev) +{ + int err; + struct timbgpio_platform_data *pdata = pdev->dev.platform_data; + struct timbgpio *tgpio = platform_get_drvdata(pdev); + struct resource *iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + int irq = platform_get_irq(pdev, 0); + + if (irq >= 0 && tgpio->irq_base > 0) { + int i; + for (i = 0; i < pdata->nr_pins; i++) { + irq_set_chip(tgpio->irq_base + i, NULL); + irq_set_chip_data(tgpio->irq_base + i, NULL); + } + + irq_set_handler(irq, NULL); + irq_set_handler_data(irq, NULL); + } + + err = gpiochip_remove(&tgpio->gpio); + if (err) + printk(KERN_ERR DRIVER_NAME": failed to remove gpio_chip\n"); + + iounmap(tgpio->membase); + release_mem_region(iomem->start, resource_size(iomem)); + kfree(tgpio); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver timbgpio_platform_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = timbgpio_probe, + .remove = timbgpio_remove, +}; + +/*--------------------------------------------------------------------------*/ + +module_platform_driver(timbgpio_platform_driver); + +MODULE_DESCRIPTION("Timberdale GPIO driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Mocean Laboratories"); +MODULE_ALIAS("platform:"DRIVER_NAME); + diff --git a/drivers/gpio/gpio-tnetv107x.c b/drivers/gpio/gpio-tnetv107x.c new file mode 100644 index 00000000..3fa3e286 --- /dev/null +++ b/drivers/gpio/gpio-tnetv107x.c @@ -0,0 +1,205 @@ +/* + * Texas Instruments TNETV107X GPIO Controller + * + * Copyright (C) 2010 Texas Instruments + * + * 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. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/gpio.h> + +#include <mach/common.h> +#include <mach/tnetv107x.h> + +struct tnetv107x_gpio_regs { + u32 idver; + u32 data_in[3]; + u32 data_out[3]; + u32 direction[3]; + u32 enable[3]; +}; + +#define gpio_reg_index(gpio) ((gpio) >> 5) +#define gpio_reg_bit(gpio) BIT((gpio) & 0x1f) + +#define gpio_reg_rmw(reg, mask, val) \ + __raw_writel((__raw_readl(reg) & ~(mask)) | (val), (reg)) + +#define gpio_reg_set_bit(reg, gpio) \ + gpio_reg_rmw((reg) + gpio_reg_index(gpio), 0, gpio_reg_bit(gpio)) + +#define gpio_reg_clear_bit(reg, gpio) \ + gpio_reg_rmw((reg) + gpio_reg_index(gpio), gpio_reg_bit(gpio), 0) + +#define gpio_reg_get_bit(reg, gpio) \ + (__raw_readl((reg) + gpio_reg_index(gpio)) & gpio_reg_bit(gpio)) + +#define chip2controller(chip) \ + container_of(chip, struct davinci_gpio_controller, chip) + +#define TNETV107X_GPIO_CTLRS DIV_ROUND_UP(TNETV107X_N_GPIO, 32) + +static struct davinci_gpio_controller chips[TNETV107X_GPIO_CTLRS]; + +static int tnetv107x_gpio_request(struct gpio_chip *chip, unsigned offset) +{ + struct davinci_gpio_controller *ctlr = chip2controller(chip); + struct tnetv107x_gpio_regs __iomem *regs = ctlr->regs; + unsigned gpio = chip->base + offset; + unsigned long flags; + + spin_lock_irqsave(&ctlr->lock, flags); + + gpio_reg_set_bit(regs->enable, gpio); + + spin_unlock_irqrestore(&ctlr->lock, flags); + + return 0; +} + +static void tnetv107x_gpio_free(struct gpio_chip *chip, unsigned offset) +{ + struct davinci_gpio_controller *ctlr = chip2controller(chip); + struct tnetv107x_gpio_regs __iomem *regs = ctlr->regs; + unsigned gpio = chip->base + offset; + unsigned long flags; + + spin_lock_irqsave(&ctlr->lock, flags); + + gpio_reg_clear_bit(regs->enable, gpio); + + spin_unlock_irqrestore(&ctlr->lock, flags); +} + +static int tnetv107x_gpio_dir_in(struct gpio_chip *chip, unsigned offset) +{ + struct davinci_gpio_controller *ctlr = chip2controller(chip); + struct tnetv107x_gpio_regs __iomem *regs = ctlr->regs; + unsigned gpio = chip->base + offset; + unsigned long flags; + + spin_lock_irqsave(&ctlr->lock, flags); + + gpio_reg_set_bit(regs->direction, gpio); + + spin_unlock_irqrestore(&ctlr->lock, flags); + + return 0; +} + +static int tnetv107x_gpio_dir_out(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct davinci_gpio_controller *ctlr = chip2controller(chip); + struct tnetv107x_gpio_regs __iomem *regs = ctlr->regs; + unsigned gpio = chip->base + offset; + unsigned long flags; + + spin_lock_irqsave(&ctlr->lock, flags); + + if (value) + gpio_reg_set_bit(regs->data_out, gpio); + else + gpio_reg_clear_bit(regs->data_out, gpio); + + gpio_reg_clear_bit(regs->direction, gpio); + + spin_unlock_irqrestore(&ctlr->lock, flags); + + return 0; +} + +static int tnetv107x_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct davinci_gpio_controller *ctlr = chip2controller(chip); + struct tnetv107x_gpio_regs __iomem *regs = ctlr->regs; + unsigned gpio = chip->base + offset; + int ret; + + ret = gpio_reg_get_bit(regs->data_in, gpio); + + return ret ? 1 : 0; +} + +static void tnetv107x_gpio_set(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct davinci_gpio_controller *ctlr = chip2controller(chip); + struct tnetv107x_gpio_regs __iomem *regs = ctlr->regs; + unsigned gpio = chip->base + offset; + unsigned long flags; + + spin_lock_irqsave(&ctlr->lock, flags); + + if (value) + gpio_reg_set_bit(regs->data_out, gpio); + else + gpio_reg_clear_bit(regs->data_out, gpio); + + spin_unlock_irqrestore(&ctlr->lock, flags); +} + +static int __init tnetv107x_gpio_setup(void) +{ + int i, base; + unsigned ngpio; + struct davinci_soc_info *soc_info = &davinci_soc_info; + struct tnetv107x_gpio_regs *regs; + struct davinci_gpio_controller *ctlr; + + if (soc_info->gpio_type != GPIO_TYPE_TNETV107X) + return 0; + + ngpio = soc_info->gpio_num; + if (ngpio == 0) { + pr_err("GPIO setup: how many GPIOs?\n"); + return -EINVAL; + } + + if (WARN_ON(TNETV107X_N_GPIO < ngpio)) + ngpio = TNETV107X_N_GPIO; + + regs = ioremap(soc_info->gpio_base, SZ_4K); + if (WARN_ON(!regs)) + return -EINVAL; + + for (i = 0, base = 0; base < ngpio; i++, base += 32) { + ctlr = &chips[i]; + + ctlr->chip.label = "tnetv107x"; + ctlr->chip.can_sleep = 0; + ctlr->chip.base = base; + ctlr->chip.ngpio = ngpio - base; + if (ctlr->chip.ngpio > 32) + ctlr->chip.ngpio = 32; + + ctlr->chip.request = tnetv107x_gpio_request; + ctlr->chip.free = tnetv107x_gpio_free; + ctlr->chip.direction_input = tnetv107x_gpio_dir_in; + ctlr->chip.get = tnetv107x_gpio_get; + ctlr->chip.direction_output = tnetv107x_gpio_dir_out; + ctlr->chip.set = tnetv107x_gpio_set; + + spin_lock_init(&ctlr->lock); + + ctlr->regs = regs; + ctlr->set_data = ®s->data_out[i]; + ctlr->clr_data = ®s->data_out[i]; + ctlr->in_data = ®s->data_in[i]; + + gpiochip_add(&ctlr->chip); + } + + soc_info->gpio_ctlrs = chips; + soc_info->gpio_ctlrs_num = DIV_ROUND_UP(ngpio, 32); + return 0; +} +pure_initcall(tnetv107x_gpio_setup); diff --git a/drivers/gpio/gpio-tps65910.c b/drivers/gpio/gpio-tps65910.c new file mode 100644 index 00000000..7eef648a --- /dev/null +++ b/drivers/gpio/gpio-tps65910.c @@ -0,0 +1,118 @@ +/* + * TI TPS6591x GPIO driver + * + * Copyright 2010 Texas Instruments Inc. + * + * Author: Graeme Gregory <gg@slimlogic.co.uk> + * Author: Jorge Eduardo Candelaria jedu@slimlogic.co.uk> + * + * 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. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/mfd/tps65910.h> + +static int tps65910_gpio_get(struct gpio_chip *gc, unsigned offset) +{ + struct tps65910 *tps65910 = container_of(gc, struct tps65910, gpio); + uint8_t val; + + tps65910->read(tps65910, TPS65910_GPIO0 + offset, 1, &val); + + if (val & GPIO_STS_MASK) + return 1; + + return 0; +} + +static void tps65910_gpio_set(struct gpio_chip *gc, unsigned offset, + int value) +{ + struct tps65910 *tps65910 = container_of(gc, struct tps65910, gpio); + + if (value) + tps65910_set_bits(tps65910, TPS65910_GPIO0 + offset, + GPIO_SET_MASK); + else + tps65910_clear_bits(tps65910, TPS65910_GPIO0 + offset, + GPIO_SET_MASK); +} + +static int tps65910_gpio_output(struct gpio_chip *gc, unsigned offset, + int value) +{ + struct tps65910 *tps65910 = container_of(gc, struct tps65910, gpio); + + /* Set the initial value */ + tps65910_gpio_set(gc, offset, value); + + return tps65910_set_bits(tps65910, TPS65910_GPIO0 + offset, + GPIO_CFG_MASK); +} + +static int tps65910_gpio_input(struct gpio_chip *gc, unsigned offset) +{ + struct tps65910 *tps65910 = container_of(gc, struct tps65910, gpio); + + return tps65910_clear_bits(tps65910, TPS65910_GPIO0 + offset, + GPIO_CFG_MASK); +} + +void tps65910_gpio_init(struct tps65910 *tps65910, int gpio_base) +{ + int ret; + struct tps65910_board *board_data; + + if (!gpio_base) + return; + + tps65910->gpio.owner = THIS_MODULE; + tps65910->gpio.label = tps65910->i2c_client->name; + tps65910->gpio.dev = tps65910->dev; + tps65910->gpio.base = gpio_base; + + switch(tps65910_chip_id(tps65910)) { + case TPS65910: + tps65910->gpio.ngpio = TPS65910_NUM_GPIO; + break; + case TPS65911: + tps65910->gpio.ngpio = TPS65911_NUM_GPIO; + break; + default: + return; + } + tps65910->gpio.can_sleep = 1; + + tps65910->gpio.direction_input = tps65910_gpio_input; + tps65910->gpio.direction_output = tps65910_gpio_output; + tps65910->gpio.set = tps65910_gpio_set; + tps65910->gpio.get = tps65910_gpio_get; + + /* Configure sleep control for gpios */ + board_data = dev_get_platdata(tps65910->dev); + if (board_data) { + int i; + for (i = 0; i < tps65910->gpio.ngpio; ++i) { + if (board_data->en_gpio_sleep[i]) { + ret = tps65910_set_bits(tps65910, + TPS65910_GPIO0 + i, GPIO_SLEEP_MASK); + if (ret < 0) + dev_warn(tps65910->dev, + "GPIO Sleep setting failed\n"); + } + } + } + + ret = gpiochip_add(&tps65910->gpio); + + if (ret) + dev_warn(tps65910->dev, "GPIO registration failed: %d\n", ret); +} diff --git a/drivers/gpio/gpio-tps65912.c b/drivers/gpio/gpio-tps65912.c new file mode 100644 index 00000000..79e66c00 --- /dev/null +++ b/drivers/gpio/gpio-tps65912.c @@ -0,0 +1,156 @@ +/* + * Copyright 2011 Texas Instruments Inc. + * + * Author: Margarita Olaya <magi@slimlogic.co.uk> + * + * 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 driver is based on wm8350 implementation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/gpio.h> +#include <linux/mfd/core.h> +#include <linux/platform_device.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/mfd/tps65912.h> + +struct tps65912_gpio_data { + struct tps65912 *tps65912; + struct gpio_chip gpio_chip; +}; + +static int tps65912_gpio_get(struct gpio_chip *gc, unsigned offset) +{ + struct tps65912 *tps65912 = container_of(gc, struct tps65912, gpio); + int val; + + val = tps65912_reg_read(tps65912, TPS65912_GPIO1 + offset); + + if (val & GPIO_STS_MASK) + return 1; + + return 0; +} + +static void tps65912_gpio_set(struct gpio_chip *gc, unsigned offset, + int value) +{ + struct tps65912 *tps65912 = container_of(gc, struct tps65912, gpio); + + if (value) + tps65912_set_bits(tps65912, TPS65912_GPIO1 + offset, + GPIO_SET_MASK); + else + tps65912_clear_bits(tps65912, TPS65912_GPIO1 + offset, + GPIO_SET_MASK); +} + +static int tps65912_gpio_output(struct gpio_chip *gc, unsigned offset, + int value) +{ + struct tps65912 *tps65912 = container_of(gc, struct tps65912, gpio); + + /* Set the initial value */ + tps65912_gpio_set(gc, offset, value); + + return tps65912_set_bits(tps65912, TPS65912_GPIO1 + offset, + GPIO_CFG_MASK); +} + +static int tps65912_gpio_input(struct gpio_chip *gc, unsigned offset) +{ + struct tps65912 *tps65912 = container_of(gc, struct tps65912, gpio); + + return tps65912_clear_bits(tps65912, TPS65912_GPIO1 + offset, + GPIO_CFG_MASK); + +} + +static struct gpio_chip template_chip = { + .label = "tps65912", + .owner = THIS_MODULE, + .direction_input = tps65912_gpio_input, + .direction_output = tps65912_gpio_output, + .get = tps65912_gpio_get, + .set = tps65912_gpio_set, + .can_sleep = 1, + .ngpio = 5, + .base = -1, +}; + +static int __devinit tps65912_gpio_probe(struct platform_device *pdev) +{ + struct tps65912 *tps65912 = dev_get_drvdata(pdev->dev.parent); + struct tps65912_board *pdata = tps65912->dev->platform_data; + struct tps65912_gpio_data *tps65912_gpio; + int ret; + + tps65912_gpio = kzalloc(sizeof(*tps65912_gpio), GFP_KERNEL); + if (tps65912_gpio == NULL) + return -ENOMEM; + + tps65912_gpio->tps65912 = tps65912; + tps65912_gpio->gpio_chip = template_chip; + tps65912_gpio->gpio_chip.dev = &pdev->dev; + if (pdata && pdata->gpio_base) + tps65912_gpio->gpio_chip.base = pdata->gpio_base; + + ret = gpiochip_add(&tps65912_gpio->gpio_chip); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to register gpiochip, %d\n", ret); + goto err; + } + + platform_set_drvdata(pdev, tps65912_gpio); + + return ret; + +err: + kfree(tps65912_gpio); + return ret; +} + +static int __devexit tps65912_gpio_remove(struct platform_device *pdev) +{ + struct tps65912_gpio_data *tps65912_gpio = platform_get_drvdata(pdev); + int ret; + + ret = gpiochip_remove(&tps65912_gpio->gpio_chip); + if (ret == 0) + kfree(tps65912_gpio); + + return ret; +} + +static struct platform_driver tps65912_gpio_driver = { + .driver = { + .name = "tps65912-gpio", + .owner = THIS_MODULE, + }, + .probe = tps65912_gpio_probe, + .remove = __devexit_p(tps65912_gpio_remove), +}; + +static int __init tps65912_gpio_init(void) +{ + return platform_driver_register(&tps65912_gpio_driver); +} +subsys_initcall(tps65912_gpio_init); + +static void __exit tps65912_gpio_exit(void) +{ + platform_driver_unregister(&tps65912_gpio_driver); +} +module_exit(tps65912_gpio_exit); + +MODULE_AUTHOR("Margarita Olaya Cabrera <magi@slimlogic.co.uk>"); +MODULE_DESCRIPTION("GPIO interface for TPS65912 PMICs"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:tps65912-gpio"); diff --git a/drivers/gpio/gpio-twl4030.c b/drivers/gpio/gpio-twl4030.c new file mode 100644 index 00000000..94256fe7 --- /dev/null +++ b/drivers/gpio/gpio-twl4030.c @@ -0,0 +1,536 @@ +/* + * Access to GPIOs on TWL4030/TPS659x0 chips + * + * Copyright (C) 2006-2007 Texas Instruments, Inc. + * Copyright (C) 2006 MontaVista Software, Inc. + * + * Code re-arranged and cleaned up by: + * Syed Mohammed Khasim <x0khasim@ti.com> + * + * Initial Code: + * Andy Lowe / Nishanth Menon + * + * 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/interrupt.h> +#include <linux/kthread.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/irqdomain.h> + +#include <linux/i2c/twl.h> + + +/* + * The GPIO "subchip" supports 18 GPIOs which can be configured as + * inputs or outputs, with pullups or pulldowns on each pin. Each + * GPIO can trigger interrupts on either or both edges. + * + * GPIO interrupts can be fed to either of two IRQ lines; this is + * intended to support multiple hosts. + * + * There are also two LED pins used sometimes as output-only GPIOs. + */ + + +static struct gpio_chip twl_gpiochip; +static int twl4030_gpio_irq_base; + +/* genirq interfaces are not available to modules */ +#ifdef MODULE +#define is_module() true +#else +#define is_module() false +#endif + +/* GPIO_CTRL Fields */ +#define MASK_GPIO_CTRL_GPIO0CD1 BIT(0) +#define MASK_GPIO_CTRL_GPIO1CD2 BIT(1) +#define MASK_GPIO_CTRL_GPIO_ON BIT(2) + +/* Mask for GPIO registers when aggregated into a 32-bit integer */ +#define GPIO_32_MASK 0x0003ffff + +/* Data structures */ +static DEFINE_MUTEX(gpio_lock); + +/* store usage of each GPIO. - each bit represents one GPIO */ +static unsigned int gpio_usage_count; + +/*----------------------------------------------------------------------*/ + +/* + * To configure TWL4030 GPIO module registers + */ +static inline int gpio_twl4030_write(u8 address, u8 data) +{ + return twl_i2c_write_u8(TWL4030_MODULE_GPIO, data, address); +} + +/*----------------------------------------------------------------------*/ + +/* + * LED register offsets (use TWL4030_MODULE_{LED,PWMA,PWMB})) + * PWMs A and B are dedicated to LEDs A and B, respectively. + */ + +#define TWL4030_LED_LEDEN 0x0 + +/* LEDEN bits */ +#define LEDEN_LEDAON BIT(0) +#define LEDEN_LEDBON BIT(1) +#define LEDEN_LEDAEXT BIT(2) +#define LEDEN_LEDBEXT BIT(3) +#define LEDEN_LEDAPWM BIT(4) +#define LEDEN_LEDBPWM BIT(5) +#define LEDEN_PWM_LENGTHA BIT(6) +#define LEDEN_PWM_LENGTHB BIT(7) + +#define TWL4030_PWMx_PWMxON 0x0 +#define TWL4030_PWMx_PWMxOFF 0x1 + +#define PWMxON_LENGTH BIT(7) + +/*----------------------------------------------------------------------*/ + +/* + * To read a TWL4030 GPIO module register + */ +static inline int gpio_twl4030_read(u8 address) +{ + u8 data; + int ret = 0; + + ret = twl_i2c_read_u8(TWL4030_MODULE_GPIO, &data, address); + return (ret < 0) ? ret : data; +} + +/*----------------------------------------------------------------------*/ + +static u8 cached_leden; /* protected by gpio_lock */ + +/* The LED lines are open drain outputs ... a FET pulls to GND, so an + * external pullup is needed. We could also expose the integrated PWM + * as a LED brightness control; we initialize it as "always on". + */ +static void twl4030_led_set_value(int led, int value) +{ + u8 mask = LEDEN_LEDAON | LEDEN_LEDAPWM; + int status; + + if (led) + mask <<= 1; + + mutex_lock(&gpio_lock); + if (value) + cached_leden &= ~mask; + else + cached_leden |= mask; + status = twl_i2c_write_u8(TWL4030_MODULE_LED, cached_leden, + TWL4030_LED_LEDEN); + mutex_unlock(&gpio_lock); +} + +static int twl4030_set_gpio_direction(int gpio, int is_input) +{ + u8 d_bnk = gpio >> 3; + u8 d_msk = BIT(gpio & 0x7); + u8 reg = 0; + u8 base = REG_GPIODATADIR1 + d_bnk; + int ret = 0; + + mutex_lock(&gpio_lock); + ret = gpio_twl4030_read(base); + if (ret >= 0) { + if (is_input) + reg = ret & ~d_msk; + else + reg = ret | d_msk; + + ret = gpio_twl4030_write(base, reg); + } + mutex_unlock(&gpio_lock); + return ret; +} + +static int twl4030_set_gpio_dataout(int gpio, int enable) +{ + u8 d_bnk = gpio >> 3; + u8 d_msk = BIT(gpio & 0x7); + u8 base = 0; + + if (enable) + base = REG_SETGPIODATAOUT1 + d_bnk; + else + base = REG_CLEARGPIODATAOUT1 + d_bnk; + + return gpio_twl4030_write(base, d_msk); +} + +static int twl4030_get_gpio_datain(int gpio) +{ + u8 d_bnk = gpio >> 3; + u8 d_off = gpio & 0x7; + u8 base = 0; + int ret = 0; + + if (unlikely((gpio >= TWL4030_GPIO_MAX) + || !(gpio_usage_count & BIT(gpio)))) + return -EPERM; + + base = REG_GPIODATAIN1 + d_bnk; + ret = gpio_twl4030_read(base); + if (ret > 0) + ret = (ret >> d_off) & 0x1; + + return ret; +} + +/*----------------------------------------------------------------------*/ + +static int twl_request(struct gpio_chip *chip, unsigned offset) +{ + int status = 0; + + mutex_lock(&gpio_lock); + + /* Support the two LED outputs as output-only GPIOs. */ + if (offset >= TWL4030_GPIO_MAX) { + u8 ledclr_mask = LEDEN_LEDAON | LEDEN_LEDAEXT + | LEDEN_LEDAPWM | LEDEN_PWM_LENGTHA; + u8 module = TWL4030_MODULE_PWMA; + + offset -= TWL4030_GPIO_MAX; + if (offset) { + ledclr_mask <<= 1; + module = TWL4030_MODULE_PWMB; + } + + /* initialize PWM to always-drive */ + status = twl_i2c_write_u8(module, 0x7f, + TWL4030_PWMx_PWMxOFF); + if (status < 0) + goto done; + status = twl_i2c_write_u8(module, 0x7f, + TWL4030_PWMx_PWMxON); + if (status < 0) + goto done; + + /* init LED to not-driven (high) */ + module = TWL4030_MODULE_LED; + status = twl_i2c_read_u8(module, &cached_leden, + TWL4030_LED_LEDEN); + if (status < 0) + goto done; + cached_leden &= ~ledclr_mask; + status = twl_i2c_write_u8(module, cached_leden, + TWL4030_LED_LEDEN); + if (status < 0) + goto done; + + status = 0; + goto done; + } + + /* on first use, turn GPIO module "on" */ + if (!gpio_usage_count) { + struct twl4030_gpio_platform_data *pdata; + u8 value = MASK_GPIO_CTRL_GPIO_ON; + + /* optionally have the first two GPIOs switch vMMC1 + * and vMMC2 power supplies based on card presence. + */ + pdata = chip->dev->platform_data; + if (pdata) + value |= pdata->mmc_cd & 0x03; + + status = gpio_twl4030_write(REG_GPIO_CTRL, value); + } + + if (!status) + gpio_usage_count |= (0x1 << offset); + +done: + mutex_unlock(&gpio_lock); + return status; +} + +static void twl_free(struct gpio_chip *chip, unsigned offset) +{ + if (offset >= TWL4030_GPIO_MAX) { + twl4030_led_set_value(offset - TWL4030_GPIO_MAX, 1); + return; + } + + mutex_lock(&gpio_lock); + + gpio_usage_count &= ~BIT(offset); + + /* on last use, switch off GPIO module */ + if (!gpio_usage_count) + gpio_twl4030_write(REG_GPIO_CTRL, 0x0); + + mutex_unlock(&gpio_lock); +} + +static int twl_direction_in(struct gpio_chip *chip, unsigned offset) +{ + return (offset < TWL4030_GPIO_MAX) + ? twl4030_set_gpio_direction(offset, 1) + : -EINVAL; +} + +static int twl_get(struct gpio_chip *chip, unsigned offset) +{ + int status = 0; + + if (offset < TWL4030_GPIO_MAX) + status = twl4030_get_gpio_datain(offset); + else if (offset == TWL4030_GPIO_MAX) + status = cached_leden & LEDEN_LEDAON; + else + status = cached_leden & LEDEN_LEDBON; + return (status < 0) ? 0 : status; +} + +static int twl_direction_out(struct gpio_chip *chip, unsigned offset, int value) +{ + if (offset < TWL4030_GPIO_MAX) { + twl4030_set_gpio_dataout(offset, value); + return twl4030_set_gpio_direction(offset, 0); + } else { + twl4030_led_set_value(offset - TWL4030_GPIO_MAX, value); + return 0; + } +} + +static void twl_set(struct gpio_chip *chip, unsigned offset, int value) +{ + if (offset < TWL4030_GPIO_MAX) + twl4030_set_gpio_dataout(offset, value); + else + twl4030_led_set_value(offset - TWL4030_GPIO_MAX, value); +} + +static int twl_to_irq(struct gpio_chip *chip, unsigned offset) +{ + return (twl4030_gpio_irq_base && (offset < TWL4030_GPIO_MAX)) + ? (twl4030_gpio_irq_base + offset) + : -EINVAL; +} + +static struct gpio_chip twl_gpiochip = { + .label = "twl4030", + .owner = THIS_MODULE, + .request = twl_request, + .free = twl_free, + .direction_input = twl_direction_in, + .get = twl_get, + .direction_output = twl_direction_out, + .set = twl_set, + .to_irq = twl_to_irq, + .can_sleep = 1, +}; + +/*----------------------------------------------------------------------*/ + +static int __devinit gpio_twl4030_pulls(u32 ups, u32 downs) +{ + u8 message[6]; + unsigned i, gpio_bit; + + /* For most pins, a pulldown was enabled by default. + * We should have data that's specific to this board. + */ + for (gpio_bit = 1, i = 1; i < 6; i++) { + u8 bit_mask; + unsigned j; + + for (bit_mask = 0, j = 0; j < 8; j += 2, gpio_bit <<= 1) { + if (ups & gpio_bit) + bit_mask |= 1 << (j + 1); + else if (downs & gpio_bit) + bit_mask |= 1 << (j + 0); + } + message[i] = bit_mask; + } + + return twl_i2c_write(TWL4030_MODULE_GPIO, message, + REG_GPIOPUPDCTR1, 5); +} + +static int __devinit gpio_twl4030_debounce(u32 debounce, u8 mmc_cd) +{ + u8 message[4]; + + /* 30 msec of debouncing is always used for MMC card detect, + * and is optional for everything else. + */ + message[1] = (debounce & 0xff) | (mmc_cd & 0x03); + debounce >>= 8; + message[2] = (debounce & 0xff); + debounce >>= 8; + message[3] = (debounce & 0x03); + + return twl_i2c_write(TWL4030_MODULE_GPIO, message, + REG_GPIO_DEBEN1, 3); +} + +static int gpio_twl4030_remove(struct platform_device *pdev); + +static int __devinit gpio_twl4030_probe(struct platform_device *pdev) +{ + struct twl4030_gpio_platform_data *pdata = pdev->dev.platform_data; + struct device_node *node = pdev->dev.of_node; + int ret, irq_base; + + /* maybe setup IRQs */ + if (is_module()) { + dev_err(&pdev->dev, "can't dispatch IRQs from modules\n"); + goto no_irqs; + } + + irq_base = irq_alloc_descs(-1, 0, TWL4030_GPIO_MAX, 0); + if (irq_base < 0) { + dev_err(&pdev->dev, "Failed to alloc irq_descs\n"); + return irq_base; + } + + irq_domain_add_legacy(node, TWL4030_GPIO_MAX, irq_base, 0, + &irq_domain_simple_ops, NULL); + + ret = twl4030_sih_setup(&pdev->dev, TWL4030_MODULE_GPIO, irq_base); + if (ret < 0) + return ret; + + twl4030_gpio_irq_base = irq_base; + +no_irqs: + twl_gpiochip.base = -1; + twl_gpiochip.ngpio = TWL4030_GPIO_MAX; + twl_gpiochip.dev = &pdev->dev; + + if (pdata) { + twl_gpiochip.base = pdata->gpio_base; + + /* + * NOTE: boards may waste power if they don't set pullups + * and pulldowns correctly ... default for non-ULPI pins is + * pulldown, and some other pins may have external pullups + * or pulldowns. Careful! + */ + ret = gpio_twl4030_pulls(pdata->pullups, pdata->pulldowns); + if (ret) + dev_dbg(&pdev->dev, "pullups %.05x %.05x --> %d\n", + pdata->pullups, pdata->pulldowns, + ret); + + ret = gpio_twl4030_debounce(pdata->debounce, pdata->mmc_cd); + if (ret) + dev_dbg(&pdev->dev, "debounce %.03x %.01x --> %d\n", + pdata->debounce, pdata->mmc_cd, + ret); + + /* + * NOTE: we assume VIBRA_CTL.VIBRA_EN, in MODULE_AUDIO_VOICE, + * is (still) clear if use_leds is set. + */ + if (pdata->use_leds) + twl_gpiochip.ngpio += 2; + } + + ret = gpiochip_add(&twl_gpiochip); + if (ret < 0) { + dev_err(&pdev->dev, "could not register gpiochip, %d\n", ret); + twl_gpiochip.ngpio = 0; + gpio_twl4030_remove(pdev); + } else if (pdata && pdata->setup) { + int status; + + status = pdata->setup(&pdev->dev, + pdata->gpio_base, TWL4030_GPIO_MAX); + if (status) + dev_dbg(&pdev->dev, "setup --> %d\n", status); + } + + return ret; +} + +/* Cannot use __devexit as gpio_twl4030_probe() calls us */ +static int gpio_twl4030_remove(struct platform_device *pdev) +{ + struct twl4030_gpio_platform_data *pdata = pdev->dev.platform_data; + int status; + + if (pdata && pdata->teardown) { + status = pdata->teardown(&pdev->dev, + pdata->gpio_base, TWL4030_GPIO_MAX); + if (status) { + dev_dbg(&pdev->dev, "teardown --> %d\n", status); + return status; + } + } + + status = gpiochip_remove(&twl_gpiochip); + if (status < 0) + return status; + + if (is_module()) + return 0; + + /* REVISIT no support yet for deregistering all the IRQs */ + WARN_ON(1); + return -EIO; +} + +static const struct of_device_id twl_gpio_match[] = { + { .compatible = "ti,twl4030-gpio", }, + { }, +}; +MODULE_DEVICE_TABLE(of, twl_gpio_match); + +/* Note: this hardware lives inside an I2C-based multi-function device. */ +MODULE_ALIAS("platform:twl4030_gpio"); + +static struct platform_driver gpio_twl4030_driver = { + .driver = { + .name = "twl4030_gpio", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(twl_gpio_match), + }, + .probe = gpio_twl4030_probe, + .remove = gpio_twl4030_remove, +}; + +static int __init gpio_twl4030_init(void) +{ + return platform_driver_register(&gpio_twl4030_driver); +} +subsys_initcall(gpio_twl4030_init); + +static void __exit gpio_twl4030_exit(void) +{ + platform_driver_unregister(&gpio_twl4030_driver); +} +module_exit(gpio_twl4030_exit); + +MODULE_AUTHOR("Texas Instruments, Inc."); +MODULE_DESCRIPTION("GPIO interface for TWL4030"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpio/gpio-ucb1400.c b/drivers/gpio/gpio-ucb1400.c new file mode 100644 index 00000000..26405efe --- /dev/null +++ b/drivers/gpio/gpio-ucb1400.c @@ -0,0 +1,114 @@ +/* + * Philips UCB1400 GPIO driver + * + * Author: Marek Vasut <marek.vasut@gmail.com> + * + * 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 <linux/module.h> +#include <linux/ucb1400.h> + +struct ucb1400_gpio_data *ucbdata; + +static int ucb1400_gpio_dir_in(struct gpio_chip *gc, unsigned off) +{ + struct ucb1400_gpio *gpio; + gpio = container_of(gc, struct ucb1400_gpio, gc); + ucb1400_gpio_set_direction(gpio->ac97, off, 0); + return 0; +} + +static int ucb1400_gpio_dir_out(struct gpio_chip *gc, unsigned off, int val) +{ + struct ucb1400_gpio *gpio; + gpio = container_of(gc, struct ucb1400_gpio, gc); + ucb1400_gpio_set_direction(gpio->ac97, off, 1); + ucb1400_gpio_set_value(gpio->ac97, off, val); + return 0; +} + +static int ucb1400_gpio_get(struct gpio_chip *gc, unsigned off) +{ + struct ucb1400_gpio *gpio; + gpio = container_of(gc, struct ucb1400_gpio, gc); + return ucb1400_gpio_get_value(gpio->ac97, off); +} + +static void ucb1400_gpio_set(struct gpio_chip *gc, unsigned off, int val) +{ + struct ucb1400_gpio *gpio; + gpio = container_of(gc, struct ucb1400_gpio, gc); + ucb1400_gpio_set_value(gpio->ac97, off, val); +} + +static int ucb1400_gpio_probe(struct platform_device *dev) +{ + struct ucb1400_gpio *ucb = dev->dev.platform_data; + int err = 0; + + if (!(ucbdata && ucbdata->gpio_offset)) { + err = -EINVAL; + goto err; + } + + platform_set_drvdata(dev, ucb); + + ucb->gc.label = "ucb1400_gpio"; + ucb->gc.base = ucbdata->gpio_offset; + ucb->gc.ngpio = 10; + ucb->gc.owner = THIS_MODULE; + + ucb->gc.direction_input = ucb1400_gpio_dir_in; + ucb->gc.direction_output = ucb1400_gpio_dir_out; + ucb->gc.get = ucb1400_gpio_get; + ucb->gc.set = ucb1400_gpio_set; + ucb->gc.can_sleep = 1; + + err = gpiochip_add(&ucb->gc); + if (err) + goto err; + + if (ucbdata && ucbdata->gpio_setup) + err = ucbdata->gpio_setup(&dev->dev, ucb->gc.ngpio); + +err: + return err; + +} + +static int ucb1400_gpio_remove(struct platform_device *dev) +{ + int err = 0; + struct ucb1400_gpio *ucb = platform_get_drvdata(dev); + + if (ucbdata && ucbdata->gpio_teardown) { + err = ucbdata->gpio_teardown(&dev->dev, ucb->gc.ngpio); + if (err) + return err; + } + + err = gpiochip_remove(&ucb->gc); + return err; +} + +static struct platform_driver ucb1400_gpio_driver = { + .probe = ucb1400_gpio_probe, + .remove = ucb1400_gpio_remove, + .driver = { + .name = "ucb1400_gpio" + }, +}; + +void __init ucb1400_gpio_set_data(struct ucb1400_gpio_data *data) +{ + ucbdata = data; +} + +module_platform_driver(ucb1400_gpio_driver); + +MODULE_DESCRIPTION("Philips UCB1400 GPIO driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpio/gpio-vr41xx.c b/drivers/gpio/gpio-vr41xx.c new file mode 100644 index 00000000..82d5c20a --- /dev/null +++ b/drivers/gpio/gpio-vr41xx.c @@ -0,0 +1,574 @@ +/* + * Driver for NEC VR4100 series General-purpose I/O Unit. + * + * Copyright (C) 2002 MontaVista Software Inc. + * Author: Yoichi Yuasa <source@mvista.com> + * Copyright (C) 2003-2009 Yoichi Yuasa <yuasa@linux-mips.org> + * + * 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/errno.h> +#include <linux/fs.h> +#include <linux/gpio.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/types.h> + +#include <asm/vr41xx/giu.h> +#include <asm/vr41xx/irq.h> +#include <asm/vr41xx/vr41xx.h> + +MODULE_AUTHOR("Yoichi Yuasa <yuasa@linux-mips.org>"); +MODULE_DESCRIPTION("NEC VR4100 series General-purpose I/O Unit driver"); +MODULE_LICENSE("GPL"); + +#define GIUIOSELL 0x00 +#define GIUIOSELH 0x02 +#define GIUPIODL 0x04 +#define GIUPIODH 0x06 +#define GIUINTSTATL 0x08 +#define GIUINTSTATH 0x0a +#define GIUINTENL 0x0c +#define GIUINTENH 0x0e +#define GIUINTTYPL 0x10 +#define GIUINTTYPH 0x12 +#define GIUINTALSELL 0x14 +#define GIUINTALSELH 0x16 +#define GIUINTHTSELL 0x18 +#define GIUINTHTSELH 0x1a +#define GIUPODATL 0x1c +#define GIUPODATEN 0x1c +#define GIUPODATH 0x1e + #define PIOEN0 0x0100 + #define PIOEN1 0x0200 +#define GIUPODAT 0x1e +#define GIUFEDGEINHL 0x20 +#define GIUFEDGEINHH 0x22 +#define GIUREDGEINHL 0x24 +#define GIUREDGEINHH 0x26 + +#define GIUUSEUPDN 0x1e0 +#define GIUTERMUPDN 0x1e2 + +#define GPIO_HAS_PULLUPDOWN_IO 0x0001 +#define GPIO_HAS_OUTPUT_ENABLE 0x0002 +#define GPIO_HAS_INTERRUPT_EDGE_SELECT 0x0100 + +enum { + GPIO_INPUT, + GPIO_OUTPUT, +}; + +static DEFINE_SPINLOCK(giu_lock); +static unsigned long giu_flags; + +static void __iomem *giu_base; + +#define giu_read(offset) readw(giu_base + (offset)) +#define giu_write(offset, value) writew((value), giu_base + (offset)) + +#define GPIO_PIN_OF_IRQ(irq) ((irq) - GIU_IRQ_BASE) +#define GIUINT_HIGH_OFFSET 16 +#define GIUINT_HIGH_MAX 32 + +static inline u16 giu_set(u16 offset, u16 set) +{ + u16 data; + + data = giu_read(offset); + data |= set; + giu_write(offset, data); + + return data; +} + +static inline u16 giu_clear(u16 offset, u16 clear) +{ + u16 data; + + data = giu_read(offset); + data &= ~clear; + giu_write(offset, data); + + return data; +} + +static void ack_giuint_low(struct irq_data *d) +{ + giu_write(GIUINTSTATL, 1 << GPIO_PIN_OF_IRQ(d->irq)); +} + +static void mask_giuint_low(struct irq_data *d) +{ + giu_clear(GIUINTENL, 1 << GPIO_PIN_OF_IRQ(d->irq)); +} + +static void mask_ack_giuint_low(struct irq_data *d) +{ + unsigned int pin; + + pin = GPIO_PIN_OF_IRQ(d->irq); + giu_clear(GIUINTENL, 1 << pin); + giu_write(GIUINTSTATL, 1 << pin); +} + +static void unmask_giuint_low(struct irq_data *d) +{ + giu_set(GIUINTENL, 1 << GPIO_PIN_OF_IRQ(d->irq)); +} + +static struct irq_chip giuint_low_irq_chip = { + .name = "GIUINTL", + .irq_ack = ack_giuint_low, + .irq_mask = mask_giuint_low, + .irq_mask_ack = mask_ack_giuint_low, + .irq_unmask = unmask_giuint_low, +}; + +static void ack_giuint_high(struct irq_data *d) +{ + giu_write(GIUINTSTATH, + 1 << (GPIO_PIN_OF_IRQ(d->irq) - GIUINT_HIGH_OFFSET)); +} + +static void mask_giuint_high(struct irq_data *d) +{ + giu_clear(GIUINTENH, 1 << (GPIO_PIN_OF_IRQ(d->irq) - GIUINT_HIGH_OFFSET)); +} + +static void mask_ack_giuint_high(struct irq_data *d) +{ + unsigned int pin; + + pin = GPIO_PIN_OF_IRQ(d->irq) - GIUINT_HIGH_OFFSET; + giu_clear(GIUINTENH, 1 << pin); + giu_write(GIUINTSTATH, 1 << pin); +} + +static void unmask_giuint_high(struct irq_data *d) +{ + giu_set(GIUINTENH, 1 << (GPIO_PIN_OF_IRQ(d->irq) - GIUINT_HIGH_OFFSET)); +} + +static struct irq_chip giuint_high_irq_chip = { + .name = "GIUINTH", + .irq_ack = ack_giuint_high, + .irq_mask = mask_giuint_high, + .irq_mask_ack = mask_ack_giuint_high, + .irq_unmask = unmask_giuint_high, +}; + +static int giu_get_irq(unsigned int irq) +{ + u16 pendl, pendh, maskl, maskh; + int i; + + pendl = giu_read(GIUINTSTATL); + pendh = giu_read(GIUINTSTATH); + maskl = giu_read(GIUINTENL); + maskh = giu_read(GIUINTENH); + + maskl &= pendl; + maskh &= pendh; + + if (maskl) { + for (i = 0; i < 16; i++) { + if (maskl & (1 << i)) + return GIU_IRQ(i); + } + } else if (maskh) { + for (i = 0; i < 16; i++) { + if (maskh & (1 << i)) + return GIU_IRQ(i + GIUINT_HIGH_OFFSET); + } + } + + printk(KERN_ERR "spurious GIU interrupt: %04x(%04x),%04x(%04x)\n", + maskl, pendl, maskh, pendh); + + atomic_inc(&irq_err_count); + + return -EINVAL; +} + +void vr41xx_set_irq_trigger(unsigned int pin, irq_trigger_t trigger, + irq_signal_t signal) +{ + u16 mask; + + if (pin < GIUINT_HIGH_OFFSET) { + mask = 1 << pin; + if (trigger != IRQ_TRIGGER_LEVEL) { + giu_set(GIUINTTYPL, mask); + if (signal == IRQ_SIGNAL_HOLD) + giu_set(GIUINTHTSELL, mask); + else + giu_clear(GIUINTHTSELL, mask); + if (giu_flags & GPIO_HAS_INTERRUPT_EDGE_SELECT) { + switch (trigger) { + case IRQ_TRIGGER_EDGE_FALLING: + giu_set(GIUFEDGEINHL, mask); + giu_clear(GIUREDGEINHL, mask); + break; + case IRQ_TRIGGER_EDGE_RISING: + giu_clear(GIUFEDGEINHL, mask); + giu_set(GIUREDGEINHL, mask); + break; + default: + giu_set(GIUFEDGEINHL, mask); + giu_set(GIUREDGEINHL, mask); + break; + } + } + irq_set_chip_and_handler(GIU_IRQ(pin), + &giuint_low_irq_chip, + handle_edge_irq); + } else { + giu_clear(GIUINTTYPL, mask); + giu_clear(GIUINTHTSELL, mask); + irq_set_chip_and_handler(GIU_IRQ(pin), + &giuint_low_irq_chip, + handle_level_irq); + } + giu_write(GIUINTSTATL, mask); + } else if (pin < GIUINT_HIGH_MAX) { + mask = 1 << (pin - GIUINT_HIGH_OFFSET); + if (trigger != IRQ_TRIGGER_LEVEL) { + giu_set(GIUINTTYPH, mask); + if (signal == IRQ_SIGNAL_HOLD) + giu_set(GIUINTHTSELH, mask); + else + giu_clear(GIUINTHTSELH, mask); + if (giu_flags & GPIO_HAS_INTERRUPT_EDGE_SELECT) { + switch (trigger) { + case IRQ_TRIGGER_EDGE_FALLING: + giu_set(GIUFEDGEINHH, mask); + giu_clear(GIUREDGEINHH, mask); + break; + case IRQ_TRIGGER_EDGE_RISING: + giu_clear(GIUFEDGEINHH, mask); + giu_set(GIUREDGEINHH, mask); + break; + default: + giu_set(GIUFEDGEINHH, mask); + giu_set(GIUREDGEINHH, mask); + break; + } + } + irq_set_chip_and_handler(GIU_IRQ(pin), + &giuint_high_irq_chip, + handle_edge_irq); + } else { + giu_clear(GIUINTTYPH, mask); + giu_clear(GIUINTHTSELH, mask); + irq_set_chip_and_handler(GIU_IRQ(pin), + &giuint_high_irq_chip, + handle_level_irq); + } + giu_write(GIUINTSTATH, mask); + } +} +EXPORT_SYMBOL_GPL(vr41xx_set_irq_trigger); + +void vr41xx_set_irq_level(unsigned int pin, irq_level_t level) +{ + u16 mask; + + if (pin < GIUINT_HIGH_OFFSET) { + mask = 1 << pin; + if (level == IRQ_LEVEL_HIGH) + giu_set(GIUINTALSELL, mask); + else + giu_clear(GIUINTALSELL, mask); + giu_write(GIUINTSTATL, mask); + } else if (pin < GIUINT_HIGH_MAX) { + mask = 1 << (pin - GIUINT_HIGH_OFFSET); + if (level == IRQ_LEVEL_HIGH) + giu_set(GIUINTALSELH, mask); + else + giu_clear(GIUINTALSELH, mask); + giu_write(GIUINTSTATH, mask); + } +} +EXPORT_SYMBOL_GPL(vr41xx_set_irq_level); + +static int giu_set_direction(struct gpio_chip *chip, unsigned pin, int dir) +{ + u16 offset, mask, reg; + unsigned long flags; + + if (pin >= chip->ngpio) + return -EINVAL; + + if (pin < 16) { + offset = GIUIOSELL; + mask = 1 << pin; + } else if (pin < 32) { + offset = GIUIOSELH; + mask = 1 << (pin - 16); + } else { + if (giu_flags & GPIO_HAS_OUTPUT_ENABLE) { + offset = GIUPODATEN; + mask = 1 << (pin - 32); + } else { + switch (pin) { + case 48: + offset = GIUPODATH; + mask = PIOEN0; + break; + case 49: + offset = GIUPODATH; + mask = PIOEN1; + break; + default: + return -EINVAL; + } + } + } + + spin_lock_irqsave(&giu_lock, flags); + + reg = giu_read(offset); + if (dir == GPIO_OUTPUT) + reg |= mask; + else + reg &= ~mask; + giu_write(offset, reg); + + spin_unlock_irqrestore(&giu_lock, flags); + + return 0; +} + +int vr41xx_gpio_pullupdown(unsigned int pin, gpio_pull_t pull) +{ + u16 reg, mask; + unsigned long flags; + + if ((giu_flags & GPIO_HAS_PULLUPDOWN_IO) != GPIO_HAS_PULLUPDOWN_IO) + return -EPERM; + + if (pin >= 15) + return -EINVAL; + + mask = 1 << pin; + + spin_lock_irqsave(&giu_lock, flags); + + if (pull == GPIO_PULL_UP || pull == GPIO_PULL_DOWN) { + reg = giu_read(GIUTERMUPDN); + if (pull == GPIO_PULL_UP) + reg |= mask; + else + reg &= ~mask; + giu_write(GIUTERMUPDN, reg); + + reg = giu_read(GIUUSEUPDN); + reg |= mask; + giu_write(GIUUSEUPDN, reg); + } else { + reg = giu_read(GIUUSEUPDN); + reg &= ~mask; + giu_write(GIUUSEUPDN, reg); + } + + spin_unlock_irqrestore(&giu_lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(vr41xx_gpio_pullupdown); + +static int vr41xx_gpio_get(struct gpio_chip *chip, unsigned pin) +{ + u16 reg, mask; + + if (pin >= chip->ngpio) + return -EINVAL; + + if (pin < 16) { + reg = giu_read(GIUPIODL); + mask = 1 << pin; + } else if (pin < 32) { + reg = giu_read(GIUPIODH); + mask = 1 << (pin - 16); + } else if (pin < 48) { + reg = giu_read(GIUPODATL); + mask = 1 << (pin - 32); + } else { + reg = giu_read(GIUPODATH); + mask = 1 << (pin - 48); + } + + if (reg & mask) + return 1; + + return 0; +} + +static void vr41xx_gpio_set(struct gpio_chip *chip, unsigned pin, + int value) +{ + u16 offset, mask, reg; + unsigned long flags; + + if (pin >= chip->ngpio) + return; + + if (pin < 16) { + offset = GIUPIODL; + mask = 1 << pin; + } else if (pin < 32) { + offset = GIUPIODH; + mask = 1 << (pin - 16); + } else if (pin < 48) { + offset = GIUPODATL; + mask = 1 << (pin - 32); + } else { + offset = GIUPODATH; + mask = 1 << (pin - 48); + } + + spin_lock_irqsave(&giu_lock, flags); + + reg = giu_read(offset); + if (value) + reg |= mask; + else + reg &= ~mask; + giu_write(offset, reg); + + spin_unlock_irqrestore(&giu_lock, flags); +} + + +static int vr41xx_gpio_direction_input(struct gpio_chip *chip, unsigned offset) +{ + return giu_set_direction(chip, offset, GPIO_INPUT); +} + +static int vr41xx_gpio_direction_output(struct gpio_chip *chip, unsigned offset, + int value) +{ + vr41xx_gpio_set(chip, offset, value); + + return giu_set_direction(chip, offset, GPIO_OUTPUT); +} + +static int vr41xx_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + if (offset >= chip->ngpio) + return -EINVAL; + + return GIU_IRQ_BASE + offset; +} + +static struct gpio_chip vr41xx_gpio_chip = { + .label = "vr41xx", + .owner = THIS_MODULE, + .direction_input = vr41xx_gpio_direction_input, + .get = vr41xx_gpio_get, + .direction_output = vr41xx_gpio_direction_output, + .set = vr41xx_gpio_set, + .to_irq = vr41xx_gpio_to_irq, +}; + +static int __devinit giu_probe(struct platform_device *pdev) +{ + struct resource *res; + unsigned int trigger, i, pin; + struct irq_chip *chip; + int irq, retval; + + switch (pdev->id) { + case GPIO_50PINS_PULLUPDOWN: + giu_flags = GPIO_HAS_PULLUPDOWN_IO; + vr41xx_gpio_chip.ngpio = 50; + break; + case GPIO_36PINS: + vr41xx_gpio_chip.ngpio = 36; + break; + case GPIO_48PINS_EDGE_SELECT: + giu_flags = GPIO_HAS_INTERRUPT_EDGE_SELECT; + vr41xx_gpio_chip.ngpio = 48; + break; + default: + dev_err(&pdev->dev, "GIU: unknown ID %d\n", pdev->id); + return -ENODEV; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EBUSY; + + giu_base = ioremap(res->start, resource_size(res)); + if (!giu_base) + return -ENOMEM; + + vr41xx_gpio_chip.dev = &pdev->dev; + + retval = gpiochip_add(&vr41xx_gpio_chip); + + giu_write(GIUINTENL, 0); + giu_write(GIUINTENH, 0); + + trigger = giu_read(GIUINTTYPH) << 16; + trigger |= giu_read(GIUINTTYPL); + for (i = GIU_IRQ_BASE; i <= GIU_IRQ_LAST; i++) { + pin = GPIO_PIN_OF_IRQ(i); + if (pin < GIUINT_HIGH_OFFSET) + chip = &giuint_low_irq_chip; + else + chip = &giuint_high_irq_chip; + + if (trigger & (1 << pin)) + irq_set_chip_and_handler(i, chip, handle_edge_irq); + else + irq_set_chip_and_handler(i, chip, handle_level_irq); + + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0 || irq >= nr_irqs) + return -EBUSY; + + return cascade_irq(irq, giu_get_irq); +} + +static int __devexit giu_remove(struct platform_device *pdev) +{ + if (giu_base) { + iounmap(giu_base); + giu_base = NULL; + } + + return 0; +} + +static struct platform_driver giu_device_driver = { + .probe = giu_probe, + .remove = __devexit_p(giu_remove), + .driver = { + .name = "GIU", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(giu_device_driver); diff --git a/drivers/gpio/gpio-vx855.c b/drivers/gpio/gpio-vx855.c new file mode 100644 index 00000000..76ebfe5f --- /dev/null +++ b/drivers/gpio/gpio-vx855.c @@ -0,0 +1,323 @@ +/* + * 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 <HaraldWelte@viatech.com> + * 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/gpio.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/pci.h> +#include <linux/io.h> + +#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), +}; + +module_platform_driver(vx855gpio_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Harald Welte <HaraldWelte@viatech.com>"); +MODULE_DESCRIPTION("GPIO driver for the VIA VX855 chipset"); +MODULE_ALIAS("platform:vx855_gpio"); diff --git a/drivers/gpio/gpio-wm831x.c b/drivers/gpio/gpio-wm831x.c new file mode 100644 index 00000000..deb949e7 --- /dev/null +++ b/drivers/gpio/gpio-wm831x.c @@ -0,0 +1,318 @@ +/* + * gpiolib support for Wolfson WM831x PMICs + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.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. + * + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/gpio.h> +#include <linux/mfd/core.h> +#include <linux/platform_device.h> +#include <linux/seq_file.h> + +#include <linux/mfd/wm831x/core.h> +#include <linux/mfd/wm831x/pdata.h> +#include <linux/mfd/wm831x/gpio.h> +#include <linux/mfd/wm831x/irq.h> + +struct wm831x_gpio { + struct wm831x *wm831x; + struct gpio_chip gpio_chip; +}; + +static inline struct wm831x_gpio *to_wm831x_gpio(struct gpio_chip *chip) +{ + return container_of(chip, struct wm831x_gpio, gpio_chip); +} + +static int wm831x_gpio_direction_in(struct gpio_chip *chip, unsigned offset) +{ + struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip); + struct wm831x *wm831x = wm831x_gpio->wm831x; + int val = WM831X_GPN_DIR; + + if (wm831x->has_gpio_ena) + val |= WM831X_GPN_TRI; + + return wm831x_set_bits(wm831x, WM831X_GPIO1_CONTROL + offset, + WM831X_GPN_DIR | WM831X_GPN_TRI | + WM831X_GPN_FN_MASK, val); +} + +static int wm831x_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip); + struct wm831x *wm831x = wm831x_gpio->wm831x; + int ret; + + ret = wm831x_reg_read(wm831x, WM831X_GPIO_LEVEL); + if (ret < 0) + return ret; + + if (ret & 1 << offset) + return 1; + else + return 0; +} + +static void wm831x_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip); + struct wm831x *wm831x = wm831x_gpio->wm831x; + + wm831x_set_bits(wm831x, WM831X_GPIO_LEVEL, 1 << offset, + value << offset); +} + +static int wm831x_gpio_direction_out(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip); + struct wm831x *wm831x = wm831x_gpio->wm831x; + int val = 0; + int ret; + + if (wm831x->has_gpio_ena) + val |= WM831X_GPN_TRI; + + ret = wm831x_set_bits(wm831x, WM831X_GPIO1_CONTROL + offset, + WM831X_GPN_DIR | WM831X_GPN_TRI | + WM831X_GPN_FN_MASK, val); + if (ret < 0) + return ret; + + /* Can only set GPIO state once it's in output mode */ + wm831x_gpio_set(chip, offset, value); + + return 0; +} + +static int wm831x_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip); + struct wm831x *wm831x = wm831x_gpio->wm831x; + + if (!wm831x->irq_base) + return -EINVAL; + + return wm831x->irq_base + WM831X_IRQ_GPIO_1 + offset; +} + +static int wm831x_gpio_set_debounce(struct gpio_chip *chip, unsigned offset, + unsigned debounce) +{ + struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip); + struct wm831x *wm831x = wm831x_gpio->wm831x; + int reg = WM831X_GPIO1_CONTROL + offset; + int ret, fn; + + ret = wm831x_reg_read(wm831x, reg); + if (ret < 0) + return ret; + + switch (ret & WM831X_GPN_FN_MASK) { + case 0: + case 1: + break; + default: + /* Not in GPIO mode */ + return -EBUSY; + } + + if (debounce >= 32 && debounce <= 64) + fn = 0; + else if (debounce >= 4000 && debounce <= 8000) + fn = 1; + else + return -EINVAL; + + return wm831x_set_bits(wm831x, reg, WM831X_GPN_FN_MASK, fn); +} + +#ifdef CONFIG_DEBUG_FS +static void wm831x_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) +{ + struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip); + struct wm831x *wm831x = wm831x_gpio->wm831x; + int i, tristated; + + for (i = 0; i < chip->ngpio; i++) { + int gpio = i + chip->base; + int reg; + const char *label, *pull, *powerdomain; + + /* We report the GPIO even if it's not requested since + * we're also reporting things like alternate + * functions which apply even when the GPIO is not in + * use as a GPIO. + */ + label = gpiochip_is_requested(chip, i); + if (!label) + label = "Unrequested"; + + seq_printf(s, " gpio-%-3d (%-20.20s) ", gpio, label); + + reg = wm831x_reg_read(wm831x, WM831X_GPIO1_CONTROL + i); + if (reg < 0) { + dev_err(wm831x->dev, + "GPIO control %d read failed: %d\n", + gpio, reg); + seq_printf(s, "\n"); + continue; + } + + switch (reg & WM831X_GPN_PULL_MASK) { + case WM831X_GPIO_PULL_NONE: + pull = "nopull"; + break; + case WM831X_GPIO_PULL_DOWN: + pull = "pulldown"; + break; + case WM831X_GPIO_PULL_UP: + pull = "pullup"; + break; + default: + pull = "INVALID PULL"; + break; + } + + switch (i + 1) { + case 1 ... 3: + case 7 ... 9: + if (reg & WM831X_GPN_PWR_DOM) + powerdomain = "VPMIC"; + else + powerdomain = "DBVDD"; + break; + + case 4 ... 6: + case 10 ... 12: + if (reg & WM831X_GPN_PWR_DOM) + powerdomain = "SYSVDD"; + else + powerdomain = "DBVDD"; + break; + + case 13 ... 16: + powerdomain = "TPVDD"; + break; + + default: + BUG(); + break; + } + + tristated = reg & WM831X_GPN_TRI; + if (wm831x->has_gpio_ena) + tristated = !tristated; + + seq_printf(s, " %s %s %s %s%s\n" + " %s%s (0x%4x)\n", + reg & WM831X_GPN_DIR ? "in" : "out", + wm831x_gpio_get(chip, i) ? "high" : "low", + pull, + powerdomain, + reg & WM831X_GPN_POL ? "" : " inverted", + reg & WM831X_GPN_OD ? "open-drain" : "CMOS", + tristated ? " tristated" : "", + reg); + } +} +#else +#define wm831x_gpio_dbg_show NULL +#endif + +static struct gpio_chip template_chip = { + .label = "wm831x", + .owner = THIS_MODULE, + .direction_input = wm831x_gpio_direction_in, + .get = wm831x_gpio_get, + .direction_output = wm831x_gpio_direction_out, + .set = wm831x_gpio_set, + .to_irq = wm831x_gpio_to_irq, + .set_debounce = wm831x_gpio_set_debounce, + .dbg_show = wm831x_gpio_dbg_show, + .can_sleep = 1, +}; + +static int __devinit wm831x_gpio_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_pdata *pdata = wm831x->dev->platform_data; + struct wm831x_gpio *wm831x_gpio; + int ret; + + wm831x_gpio = kzalloc(sizeof(*wm831x_gpio), GFP_KERNEL); + if (wm831x_gpio == NULL) + return -ENOMEM; + + wm831x_gpio->wm831x = wm831x; + wm831x_gpio->gpio_chip = template_chip; + wm831x_gpio->gpio_chip.ngpio = wm831x->num_gpio; + wm831x_gpio->gpio_chip.dev = &pdev->dev; + if (pdata && pdata->gpio_base) + wm831x_gpio->gpio_chip.base = pdata->gpio_base; + else + wm831x_gpio->gpio_chip.base = -1; + + ret = gpiochip_add(&wm831x_gpio->gpio_chip); + if (ret < 0) { + dev_err(&pdev->dev, "Could not register gpiochip, %d\n", + ret); + goto err; + } + + platform_set_drvdata(pdev, wm831x_gpio); + + return ret; + +err: + kfree(wm831x_gpio); + return ret; +} + +static int __devexit wm831x_gpio_remove(struct platform_device *pdev) +{ + struct wm831x_gpio *wm831x_gpio = platform_get_drvdata(pdev); + int ret; + + ret = gpiochip_remove(&wm831x_gpio->gpio_chip); + if (ret == 0) + kfree(wm831x_gpio); + + return ret; +} + +static struct platform_driver wm831x_gpio_driver = { + .driver.name = "wm831x-gpio", + .driver.owner = THIS_MODULE, + .probe = wm831x_gpio_probe, + .remove = __devexit_p(wm831x_gpio_remove), +}; + +static int __init wm831x_gpio_init(void) +{ + return platform_driver_register(&wm831x_gpio_driver); +} +subsys_initcall(wm831x_gpio_init); + +static void __exit wm831x_gpio_exit(void) +{ + platform_driver_unregister(&wm831x_gpio_driver); +} +module_exit(wm831x_gpio_exit); + +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); +MODULE_DESCRIPTION("GPIO interface for WM831x PMICs"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm831x-gpio"); diff --git a/drivers/gpio/gpio-wm8350.c b/drivers/gpio/gpio-wm8350.c new file mode 100644 index 00000000..a06af515 --- /dev/null +++ b/drivers/gpio/gpio-wm8350.c @@ -0,0 +1,182 @@ +/* + * gpiolib support for Wolfson WM835x PMICs + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.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. + * + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/gpio.h> +#include <linux/mfd/core.h> +#include <linux/platform_device.h> +#include <linux/seq_file.h> + +#include <linux/mfd/wm8350/core.h> +#include <linux/mfd/wm8350/gpio.h> + +struct wm8350_gpio_data { + struct wm8350 *wm8350; + struct gpio_chip gpio_chip; +}; + +static inline struct wm8350_gpio_data *to_wm8350_gpio(struct gpio_chip *chip) +{ + return container_of(chip, struct wm8350_gpio_data, gpio_chip); +} + +static int wm8350_gpio_direction_in(struct gpio_chip *chip, unsigned offset) +{ + struct wm8350_gpio_data *wm8350_gpio = to_wm8350_gpio(chip); + struct wm8350 *wm8350 = wm8350_gpio->wm8350; + + return wm8350_set_bits(wm8350, WM8350_GPIO_CONFIGURATION_I_O, + 1 << offset); +} + +static int wm8350_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct wm8350_gpio_data *wm8350_gpio = to_wm8350_gpio(chip); + struct wm8350 *wm8350 = wm8350_gpio->wm8350; + int ret; + + ret = wm8350_reg_read(wm8350, WM8350_GPIO_LEVEL); + if (ret < 0) + return ret; + + if (ret & (1 << offset)) + return 1; + else + return 0; +} + +static void wm8350_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct wm8350_gpio_data *wm8350_gpio = to_wm8350_gpio(chip); + struct wm8350 *wm8350 = wm8350_gpio->wm8350; + + if (value) + wm8350_set_bits(wm8350, WM8350_GPIO_LEVEL, 1 << offset); + else + wm8350_clear_bits(wm8350, WM8350_GPIO_LEVEL, 1 << offset); +} + +static int wm8350_gpio_direction_out(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct wm8350_gpio_data *wm8350_gpio = to_wm8350_gpio(chip); + struct wm8350 *wm8350 = wm8350_gpio->wm8350; + int ret; + + ret = wm8350_clear_bits(wm8350, WM8350_GPIO_CONFIGURATION_I_O, + 1 << offset); + if (ret < 0) + return ret; + + /* Don't have an atomic direction/value setup */ + wm8350_gpio_set(chip, offset, value); + + return 0; +} + +static int wm8350_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct wm8350_gpio_data *wm8350_gpio = to_wm8350_gpio(chip); + struct wm8350 *wm8350 = wm8350_gpio->wm8350; + + if (!wm8350->irq_base) + return -EINVAL; + + return wm8350->irq_base + WM8350_IRQ_GPIO(offset); +} + +static struct gpio_chip template_chip = { + .label = "wm8350", + .owner = THIS_MODULE, + .direction_input = wm8350_gpio_direction_in, + .get = wm8350_gpio_get, + .direction_output = wm8350_gpio_direction_out, + .set = wm8350_gpio_set, + .to_irq = wm8350_gpio_to_irq, + .can_sleep = 1, +}; + +static int __devinit wm8350_gpio_probe(struct platform_device *pdev) +{ + struct wm8350 *wm8350 = dev_get_drvdata(pdev->dev.parent); + struct wm8350_platform_data *pdata = wm8350->dev->platform_data; + struct wm8350_gpio_data *wm8350_gpio; + int ret; + + wm8350_gpio = kzalloc(sizeof(*wm8350_gpio), GFP_KERNEL); + if (wm8350_gpio == NULL) + return -ENOMEM; + + wm8350_gpio->wm8350 = wm8350; + wm8350_gpio->gpio_chip = template_chip; + wm8350_gpio->gpio_chip.ngpio = 13; + wm8350_gpio->gpio_chip.dev = &pdev->dev; + if (pdata && pdata->gpio_base) + wm8350_gpio->gpio_chip.base = pdata->gpio_base; + else + wm8350_gpio->gpio_chip.base = -1; + + ret = gpiochip_add(&wm8350_gpio->gpio_chip); + if (ret < 0) { + dev_err(&pdev->dev, "Could not register gpiochip, %d\n", + ret); + goto err; + } + + platform_set_drvdata(pdev, wm8350_gpio); + + return ret; + +err: + kfree(wm8350_gpio); + return ret; +} + +static int __devexit wm8350_gpio_remove(struct platform_device *pdev) +{ + struct wm8350_gpio_data *wm8350_gpio = platform_get_drvdata(pdev); + int ret; + + ret = gpiochip_remove(&wm8350_gpio->gpio_chip); + if (ret == 0) + kfree(wm8350_gpio); + + return ret; +} + +static struct platform_driver wm8350_gpio_driver = { + .driver.name = "wm8350-gpio", + .driver.owner = THIS_MODULE, + .probe = wm8350_gpio_probe, + .remove = __devexit_p(wm8350_gpio_remove), +}; + +static int __init wm8350_gpio_init(void) +{ + return platform_driver_register(&wm8350_gpio_driver); +} +subsys_initcall(wm8350_gpio_init); + +static void __exit wm8350_gpio_exit(void) +{ + platform_driver_unregister(&wm8350_gpio_driver); +} +module_exit(wm8350_gpio_exit); + +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); +MODULE_DESCRIPTION("GPIO interface for WM8350 PMICs"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm8350-gpio"); diff --git a/drivers/gpio/gpio-wm8994.c b/drivers/gpio/gpio-wm8994.c new file mode 100644 index 00000000..aa61ad2f --- /dev/null +++ b/drivers/gpio/gpio-wm8994.c @@ -0,0 +1,320 @@ +/* + * gpiolib support for Wolfson WM8994 + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.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. + * + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/gpio.h> +#include <linux/mfd/core.h> +#include <linux/platform_device.h> +#include <linux/seq_file.h> + +#include <linux/mfd/wm8994/core.h> +#include <linux/mfd/wm8994/pdata.h> +#include <linux/mfd/wm8994/gpio.h> +#include <linux/mfd/wm8994/registers.h> + +struct wm8994_gpio { + struct wm8994 *wm8994; + struct gpio_chip gpio_chip; +}; + +static inline struct wm8994_gpio *to_wm8994_gpio(struct gpio_chip *chip) +{ + return container_of(chip, struct wm8994_gpio, gpio_chip); +} + +static int wm8994_gpio_request(struct gpio_chip *chip, unsigned offset) +{ + struct wm8994_gpio *wm8994_gpio = to_wm8994_gpio(chip); + struct wm8994 *wm8994 = wm8994_gpio->wm8994; + + switch (wm8994->type) { + case WM8958: + switch (offset) { + case 1: + case 2: + case 3: + case 4: + case 6: + return -EINVAL; + } + break; + default: + break; + } + + return 0; +} + +static int wm8994_gpio_direction_in(struct gpio_chip *chip, unsigned offset) +{ + struct wm8994_gpio *wm8994_gpio = to_wm8994_gpio(chip); + struct wm8994 *wm8994 = wm8994_gpio->wm8994; + + return wm8994_set_bits(wm8994, WM8994_GPIO_1 + offset, + WM8994_GPN_DIR, WM8994_GPN_DIR); +} + +static int wm8994_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct wm8994_gpio *wm8994_gpio = to_wm8994_gpio(chip); + struct wm8994 *wm8994 = wm8994_gpio->wm8994; + int ret; + + ret = wm8994_reg_read(wm8994, WM8994_GPIO_1 + offset); + if (ret < 0) + return ret; + + if (ret & WM8994_GPN_LVL) + return 1; + else + return 0; +} + +static int wm8994_gpio_direction_out(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct wm8994_gpio *wm8994_gpio = to_wm8994_gpio(chip); + struct wm8994 *wm8994 = wm8994_gpio->wm8994; + + if (value) + value = WM8994_GPN_LVL; + + return wm8994_set_bits(wm8994, WM8994_GPIO_1 + offset, + WM8994_GPN_DIR | WM8994_GPN_LVL, value); +} + +static void wm8994_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct wm8994_gpio *wm8994_gpio = to_wm8994_gpio(chip); + struct wm8994 *wm8994 = wm8994_gpio->wm8994; + + if (value) + value = WM8994_GPN_LVL; + + wm8994_set_bits(wm8994, WM8994_GPIO_1 + offset, WM8994_GPN_LVL, value); +} + +static int wm8994_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct wm8994_gpio *wm8994_gpio = to_wm8994_gpio(chip); + struct wm8994 *wm8994 = wm8994_gpio->wm8994; + + if (!wm8994->irq_base) + return -EINVAL; + + return wm8994->irq_base + offset; +} + + +#ifdef CONFIG_DEBUG_FS +static const char *wm8994_gpio_fn(u16 fn) +{ + switch (fn) { + case WM8994_GP_FN_PIN_SPECIFIC: + return "pin-specific"; + case WM8994_GP_FN_GPIO: + return "GPIO"; + case WM8994_GP_FN_SDOUT: + return "SDOUT"; + case WM8994_GP_FN_IRQ: + return "IRQ"; + case WM8994_GP_FN_TEMPERATURE: + return "Temperature"; + case WM8994_GP_FN_MICBIAS1_DET: + return "MICBIAS1 detect"; + case WM8994_GP_FN_MICBIAS1_SHORT: + return "MICBIAS1 short"; + case WM8994_GP_FN_MICBIAS2_DET: + return "MICBIAS2 detect"; + case WM8994_GP_FN_MICBIAS2_SHORT: + return "MICBIAS2 short"; + case WM8994_GP_FN_FLL1_LOCK: + return "FLL1 lock"; + case WM8994_GP_FN_FLL2_LOCK: + return "FLL2 lock"; + case WM8994_GP_FN_SRC1_LOCK: + return "SRC1 lock"; + case WM8994_GP_FN_SRC2_LOCK: + return "SRC2 lock"; + case WM8994_GP_FN_DRC1_ACT: + return "DRC1 activity"; + case WM8994_GP_FN_DRC2_ACT: + return "DRC2 activity"; + case WM8994_GP_FN_DRC3_ACT: + return "DRC3 activity"; + case WM8994_GP_FN_WSEQ_STATUS: + return "Write sequencer"; + case WM8994_GP_FN_FIFO_ERROR: + return "FIFO error"; + case WM8994_GP_FN_OPCLK: + return "OPCLK"; + case WM8994_GP_FN_THW: + return "Thermal warning"; + case WM8994_GP_FN_DCS_DONE: + return "DC servo"; + case WM8994_GP_FN_FLL1_OUT: + return "FLL1 output"; + case WM8994_GP_FN_FLL2_OUT: + return "FLL1 output"; + default: + return "Unknown"; + } +} + +static void wm8994_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) +{ + struct wm8994_gpio *wm8994_gpio = to_wm8994_gpio(chip); + struct wm8994 *wm8994 = wm8994_gpio->wm8994; + int i; + + for (i = 0; i < chip->ngpio; i++) { + int gpio = i + chip->base; + int reg; + const char *label; + + /* We report the GPIO even if it's not requested since + * we're also reporting things like alternate + * functions which apply even when the GPIO is not in + * use as a GPIO. + */ + label = gpiochip_is_requested(chip, i); + if (!label) + label = "Unrequested"; + + seq_printf(s, " gpio-%-3d (%-20.20s) ", gpio, label); + + reg = wm8994_reg_read(wm8994, WM8994_GPIO_1 + i); + if (reg < 0) { + dev_err(wm8994->dev, + "GPIO control %d read failed: %d\n", + gpio, reg); + seq_printf(s, "\n"); + continue; + } + + if (reg & WM8994_GPN_DIR) + seq_printf(s, "in "); + else + seq_printf(s, "out "); + + if (reg & WM8994_GPN_PU) + seq_printf(s, "pull up "); + + if (reg & WM8994_GPN_PD) + seq_printf(s, "pull down "); + + if (reg & WM8994_GPN_POL) + seq_printf(s, "inverted "); + else + seq_printf(s, "noninverted "); + + if (reg & WM8994_GPN_OP_CFG) + seq_printf(s, "open drain "); + else + seq_printf(s, "CMOS "); + + seq_printf(s, "%s (%x)\n", + wm8994_gpio_fn(reg & WM8994_GPN_FN_MASK), reg); + } +} +#else +#define wm8994_gpio_dbg_show NULL +#endif + +static struct gpio_chip template_chip = { + .label = "wm8994", + .owner = THIS_MODULE, + .request = wm8994_gpio_request, + .direction_input = wm8994_gpio_direction_in, + .get = wm8994_gpio_get, + .direction_output = wm8994_gpio_direction_out, + .set = wm8994_gpio_set, + .to_irq = wm8994_gpio_to_irq, + .dbg_show = wm8994_gpio_dbg_show, + .can_sleep = 1, +}; + +static int __devinit wm8994_gpio_probe(struct platform_device *pdev) +{ + struct wm8994 *wm8994 = dev_get_drvdata(pdev->dev.parent); + struct wm8994_pdata *pdata = wm8994->dev->platform_data; + struct wm8994_gpio *wm8994_gpio; + int ret; + + wm8994_gpio = kzalloc(sizeof(*wm8994_gpio), GFP_KERNEL); + if (wm8994_gpio == NULL) + return -ENOMEM; + + wm8994_gpio->wm8994 = wm8994; + wm8994_gpio->gpio_chip = template_chip; + wm8994_gpio->gpio_chip.ngpio = WM8994_GPIO_MAX; + wm8994_gpio->gpio_chip.dev = &pdev->dev; + if (pdata && pdata->gpio_base) + wm8994_gpio->gpio_chip.base = pdata->gpio_base; + else + wm8994_gpio->gpio_chip.base = -1; + + ret = gpiochip_add(&wm8994_gpio->gpio_chip); + if (ret < 0) { + dev_err(&pdev->dev, "Could not register gpiochip, %d\n", + ret); + goto err; + } + + platform_set_drvdata(pdev, wm8994_gpio); + + return ret; + +err: + kfree(wm8994_gpio); + return ret; +} + +static int __devexit wm8994_gpio_remove(struct platform_device *pdev) +{ + struct wm8994_gpio *wm8994_gpio = platform_get_drvdata(pdev); + int ret; + + ret = gpiochip_remove(&wm8994_gpio->gpio_chip); + if (ret == 0) + kfree(wm8994_gpio); + + return ret; +} + +static struct platform_driver wm8994_gpio_driver = { + .driver.name = "wm8994-gpio", + .driver.owner = THIS_MODULE, + .probe = wm8994_gpio_probe, + .remove = __devexit_p(wm8994_gpio_remove), +}; + +static int __init wm8994_gpio_init(void) +{ + return platform_driver_register(&wm8994_gpio_driver); +} +subsys_initcall(wm8994_gpio_init); + +static void __exit wm8994_gpio_exit(void) +{ + platform_driver_unregister(&wm8994_gpio_driver); +} +module_exit(wm8994_gpio_exit); + +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); +MODULE_DESCRIPTION("GPIO interface for WM8994"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm8994-gpio"); diff --git a/drivers/gpio/gpio-xilinx.c b/drivers/gpio/gpio-xilinx.c new file mode 100644 index 00000000..79b0fe8a --- /dev/null +++ b/drivers/gpio/gpio-xilinx.c @@ -0,0 +1,233 @@ +/* + * Xilinx gpio driver + * + * Copyright 2008 Xilinx, Inc. + * + * 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. + * + * 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/init.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> +#include <linux/of_gpio.h> +#include <linux/io.h> +#include <linux/gpio.h> +#include <linux/slab.h> + +/* Register Offset Definitions */ +#define XGPIO_DATA_OFFSET (0x0) /* Data register */ +#define XGPIO_TRI_OFFSET (0x4) /* I/O direction register */ + +struct xgpio_instance { + struct of_mm_gpio_chip mmchip; + u32 gpio_state; /* GPIO state shadow register */ + u32 gpio_dir; /* GPIO direction shadow register */ + spinlock_t gpio_lock; /* Lock used for synchronization */ +}; + +/** + * xgpio_get - Read the specified signal of the GPIO device. + * @gc: Pointer to gpio_chip device structure. + * @gpio: GPIO signal number. + * + * This function reads the specified signal of the GPIO device. It returns 0 if + * the signal clear, 1 if signal is set or negative value on error. + */ +static int xgpio_get(struct gpio_chip *gc, unsigned int gpio) +{ + struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc); + + return (in_be32(mm_gc->regs + XGPIO_DATA_OFFSET) >> gpio) & 1; +} + +/** + * xgpio_set - Write the specified signal of the GPIO device. + * @gc: Pointer to gpio_chip device structure. + * @gpio: GPIO signal number. + * @val: Value to be written to specified signal. + * + * This function writes the specified value in to the specified signal of the + * GPIO device. + */ +static void xgpio_set(struct gpio_chip *gc, unsigned int gpio, int val) +{ + unsigned long flags; + struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc); + struct xgpio_instance *chip = + container_of(mm_gc, struct xgpio_instance, mmchip); + + spin_lock_irqsave(&chip->gpio_lock, flags); + + /* Write to GPIO signal and set its direction to output */ + if (val) + chip->gpio_state |= 1 << gpio; + else + chip->gpio_state &= ~(1 << gpio); + out_be32(mm_gc->regs + XGPIO_DATA_OFFSET, chip->gpio_state); + + spin_unlock_irqrestore(&chip->gpio_lock, flags); +} + +/** + * xgpio_dir_in - Set the direction of the specified GPIO signal as input. + * @gc: Pointer to gpio_chip device structure. + * @gpio: GPIO signal number. + * + * This function sets the direction of specified GPIO signal as input. + * It returns 0 if direction of GPIO signals is set as input otherwise it + * returns negative error value. + */ +static int xgpio_dir_in(struct gpio_chip *gc, unsigned int gpio) +{ + unsigned long flags; + struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc); + struct xgpio_instance *chip = + container_of(mm_gc, struct xgpio_instance, mmchip); + + spin_lock_irqsave(&chip->gpio_lock, flags); + + /* Set the GPIO bit in shadow register and set direction as input */ + chip->gpio_dir |= (1 << gpio); + out_be32(mm_gc->regs + XGPIO_TRI_OFFSET, chip->gpio_dir); + + spin_unlock_irqrestore(&chip->gpio_lock, flags); + + return 0; +} + +/** + * xgpio_dir_out - Set the direction of the specified GPIO signal as output. + * @gc: Pointer to gpio_chip device structure. + * @gpio: GPIO signal number. + * @val: Value to be written to specified signal. + * + * This function sets the direction of specified GPIO signal as output. If all + * GPIO signals of GPIO chip is configured as input then it returns + * error otherwise it returns 0. + */ +static int xgpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val) +{ + unsigned long flags; + struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc); + struct xgpio_instance *chip = + container_of(mm_gc, struct xgpio_instance, mmchip); + + spin_lock_irqsave(&chip->gpio_lock, flags); + + /* Write state of GPIO signal */ + if (val) + chip->gpio_state |= 1 << gpio; + else + chip->gpio_state &= ~(1 << gpio); + out_be32(mm_gc->regs + XGPIO_DATA_OFFSET, chip->gpio_state); + + /* Clear the GPIO bit in shadow register and set direction as output */ + chip->gpio_dir &= (~(1 << gpio)); + out_be32(mm_gc->regs + XGPIO_TRI_OFFSET, chip->gpio_dir); + + spin_unlock_irqrestore(&chip->gpio_lock, flags); + + return 0; +} + +/** + * xgpio_save_regs - Set initial values of GPIO pins + * @mm_gc: pointer to memory mapped GPIO chip structure + */ +static void xgpio_save_regs(struct of_mm_gpio_chip *mm_gc) +{ + struct xgpio_instance *chip = + container_of(mm_gc, struct xgpio_instance, mmchip); + + out_be32(mm_gc->regs + XGPIO_DATA_OFFSET, chip->gpio_state); + out_be32(mm_gc->regs + XGPIO_TRI_OFFSET, chip->gpio_dir); +} + +/** + * xgpio_of_probe - Probe method for the GPIO device. + * @np: pointer to device tree node + * + * This function probes the GPIO device in the device tree. It initializes the + * driver data structure. It returns 0, if the driver is bound to the GPIO + * device, or a negative value if there is an error. + */ +static int __devinit xgpio_of_probe(struct device_node *np) +{ + struct xgpio_instance *chip; + int status = 0; + const u32 *tree_info; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + /* Update GPIO state shadow register with default value */ + tree_info = of_get_property(np, "xlnx,dout-default", NULL); + if (tree_info) + chip->gpio_state = be32_to_cpup(tree_info); + + /* Update GPIO direction shadow register with default value */ + chip->gpio_dir = 0xFFFFFFFF; /* By default, all pins are inputs */ + tree_info = of_get_property(np, "xlnx,tri-default", NULL); + if (tree_info) + chip->gpio_dir = be32_to_cpup(tree_info); + + /* Check device node and parent device node for device width */ + chip->mmchip.gc.ngpio = 32; /* By default assume full GPIO controller */ + tree_info = of_get_property(np, "xlnx,gpio-width", NULL); + if (!tree_info) + tree_info = of_get_property(np->parent, + "xlnx,gpio-width", NULL); + if (tree_info) + chip->mmchip.gc.ngpio = be32_to_cpup(tree_info); + + spin_lock_init(&chip->gpio_lock); + + chip->mmchip.gc.direction_input = xgpio_dir_in; + chip->mmchip.gc.direction_output = xgpio_dir_out; + chip->mmchip.gc.get = xgpio_get; + chip->mmchip.gc.set = xgpio_set; + + chip->mmchip.save_regs = xgpio_save_regs; + + /* Call the OF gpio helper to setup and register the GPIO device */ + status = of_mm_gpiochip_add(np, &chip->mmchip); + if (status) { + kfree(chip); + pr_err("%s: error in probe function with status %d\n", + np->full_name, status); + return status; + } + return 0; +} + +static struct of_device_id xgpio_of_match[] __devinitdata = { + { .compatible = "xlnx,xps-gpio-1.00.a", }, + { /* end of list */ }, +}; + +static int __init xgpio_init(void) +{ + struct device_node *np; + + for_each_matching_node(np, xgpio_of_match) + xgpio_of_probe(np); + + return 0; +} + +/* Make sure we get initialized before anyone else tries to use us */ +subsys_initcall(xgpio_init); +/* No exit call at the moment as we cannot unregister of GPIO chips */ + +MODULE_AUTHOR("Xilinx, Inc."); +MODULE_DESCRIPTION("Xilinx GPIO driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c new file mode 100644 index 00000000..c4c68359 --- /dev/null +++ b/drivers/gpio/gpiolib.c @@ -0,0 +1,1866 @@ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/spinlock.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <linux/idr.h> +#include <linux/slab.h> + +#define CREATE_TRACE_POINTS +#include <trace/events/gpio.h> + +/* Optional implementation infrastructure for GPIO interfaces. + * + * Platforms may want to use this if they tend to use very many GPIOs + * that aren't part of a System-On-Chip core; or across I2C/SPI/etc. + * + * When kernel footprint or instruction count is an issue, simpler + * implementations may be preferred. The GPIO programming interface + * allows for inlining speed-critical get/set operations for common + * cases, so that access to SOC-integrated GPIOs can sometimes cost + * only an instruction or two per bit. + */ + + +/* When debugging, extend minimal trust to callers and platform code. + * Also emit diagnostic messages that may help initial bringup, when + * board setup or driver bugs are most common. + * + * Otherwise, minimize overhead in what may be bitbanging codepaths. + */ +#ifdef DEBUG +#define extra_checks 1 +#else +#define extra_checks 0 +#endif + +/* gpio_lock prevents conflicts during gpio_desc[] table updates. + * While any GPIO is requested, its gpio_chip is not removable; + * each GPIO's "requested" flag serves as a lock and refcount. + */ +static DEFINE_SPINLOCK(gpio_lock); + +struct gpio_desc { + struct gpio_chip *chip; + unsigned long flags; +/* flag symbols are bit numbers */ +#define FLAG_REQUESTED 0 +#define FLAG_IS_OUT 1 +#define FLAG_RESERVED 2 +#define FLAG_EXPORT 3 /* protected by sysfs_lock */ +#define FLAG_SYSFS 4 /* exported via /sys/class/gpio/control */ +#define FLAG_TRIG_FALL 5 /* trigger on falling edge */ +#define FLAG_TRIG_RISE 6 /* trigger on rising edge */ +#define FLAG_ACTIVE_LOW 7 /* sysfs value has active low */ +#define FLAG_OPEN_DRAIN 8 /* Gpio is open drain type */ +#define FLAG_OPEN_SOURCE 9 /* Gpio is open source type */ + +#define ID_SHIFT 16 /* add new flags before this one */ + +#define GPIO_FLAGS_MASK ((1 << ID_SHIFT) - 1) +#define GPIO_TRIGGER_MASK (BIT(FLAG_TRIG_FALL) | BIT(FLAG_TRIG_RISE)) + +#ifdef CONFIG_DEBUG_FS + const char *label; +#endif +}; +static struct gpio_desc gpio_desc[ARCH_NR_GPIOS]; + +#ifdef CONFIG_GPIO_SYSFS +static DEFINE_IDR(dirent_idr); +#endif + +static inline void desc_set_label(struct gpio_desc *d, const char *label) +{ +#ifdef CONFIG_DEBUG_FS + d->label = label; +#endif +} + +/* Warn when drivers omit gpio_request() calls -- legal but ill-advised + * when setting direction, and otherwise illegal. Until board setup code + * and drivers use explicit requests everywhere (which won't happen when + * those calls have no teeth) we can't avoid autorequesting. This nag + * message should motivate switching to explicit requests... so should + * the weaker cleanup after faults, compared to gpio_request(). + * + * NOTE: the autorequest mechanism is going away; at this point it's + * only "legal" in the sense that (old) code using it won't break yet, + * but instead only triggers a WARN() stack dump. + */ +static int gpio_ensure_requested(struct gpio_desc *desc, unsigned offset) +{ + const struct gpio_chip *chip = desc->chip; + const int gpio = chip->base + offset; + + if (WARN(test_and_set_bit(FLAG_REQUESTED, &desc->flags) == 0, + "autorequest GPIO-%d\n", gpio)) { + if (!try_module_get(chip->owner)) { + pr_err("GPIO-%d: module can't be gotten \n", gpio); + clear_bit(FLAG_REQUESTED, &desc->flags); + /* lose */ + return -EIO; + } + desc_set_label(desc, "[auto]"); + /* caller must chip->request() w/o spinlock */ + if (chip->request) + return 1; + } + return 0; +} + +/* caller holds gpio_lock *OR* gpio is marked as requested */ +struct gpio_chip *gpio_to_chip(unsigned gpio) +{ + return gpio_desc[gpio].chip; +} + +/* dynamic allocation of GPIOs, e.g. on a hotplugged device */ +static int gpiochip_find_base(int ngpio) +{ + int i; + int spare = 0; + int base = -ENOSPC; + + for (i = ARCH_NR_GPIOS - 1; i >= 0 ; i--) { + struct gpio_desc *desc = &gpio_desc[i]; + struct gpio_chip *chip = desc->chip; + + if (!chip && !test_bit(FLAG_RESERVED, &desc->flags)) { + spare++; + if (spare == ngpio) { + base = i; + break; + } + } else { + spare = 0; + if (chip) + i -= chip->ngpio - 1; + } + } + + if (gpio_is_valid(base)) + pr_debug("%s: found new base at %d\n", __func__, base); + return base; +} + +/** + * gpiochip_reserve() - reserve range of gpios to use with platform code only + * @start: starting gpio number + * @ngpio: number of gpios to reserve + * Context: platform init, potentially before irqs or kmalloc will work + * + * Returns a negative errno if any gpio within the range is already reserved + * or registered, else returns zero as a success code. Use this function + * to mark a range of gpios as unavailable for dynamic gpio number allocation, + * for example because its driver support is not yet loaded. + */ +int __init gpiochip_reserve(int start, int ngpio) +{ + int ret = 0; + unsigned long flags; + int i; + + if (!gpio_is_valid(start) || !gpio_is_valid(start + ngpio - 1)) + return -EINVAL; + + spin_lock_irqsave(&gpio_lock, flags); + + for (i = start; i < start + ngpio; i++) { + struct gpio_desc *desc = &gpio_desc[i]; + + if (desc->chip || test_bit(FLAG_RESERVED, &desc->flags)) { + ret = -EBUSY; + goto err; + } + + set_bit(FLAG_RESERVED, &desc->flags); + } + + pr_debug("%s: reserved gpios from %d to %d\n", + __func__, start, start + ngpio - 1); +err: + spin_unlock_irqrestore(&gpio_lock, flags); + + return ret; +} + +#ifdef CONFIG_GPIO_SYSFS + +/* lock protects against unexport_gpio() being called while + * sysfs files are active. + */ +static DEFINE_MUTEX(sysfs_lock); + +/* + * /sys/class/gpio/gpioN... only for GPIOs that are exported + * /direction + * * MAY BE OMITTED if kernel won't allow direction changes + * * is read/write as "in" or "out" + * * may also be written as "high" or "low", initializing + * output value as specified ("out" implies "low") + * /value + * * always readable, subject to hardware behavior + * * may be writable, as zero/nonzero + * /edge + * * configures behavior of poll(2) on /value + * * available only if pin can generate IRQs on input + * * is read/write as "none", "falling", "rising", or "both" + * /active_low + * * configures polarity of /value + * * is read/write as zero/nonzero + * * also affects existing and subsequent "falling" and "rising" + * /edge configuration + */ + +static ssize_t gpio_direction_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const struct gpio_desc *desc = dev_get_drvdata(dev); + ssize_t status; + + mutex_lock(&sysfs_lock); + + if (!test_bit(FLAG_EXPORT, &desc->flags)) + status = -EIO; + else + status = sprintf(buf, "%s\n", + test_bit(FLAG_IS_OUT, &desc->flags) + ? "out" : "in"); + + mutex_unlock(&sysfs_lock); + return status; +} + +static ssize_t gpio_direction_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + const struct gpio_desc *desc = dev_get_drvdata(dev); + unsigned gpio = desc - gpio_desc; + ssize_t status; + + mutex_lock(&sysfs_lock); + + if (!test_bit(FLAG_EXPORT, &desc->flags)) + status = -EIO; + else if (sysfs_streq(buf, "high")) + status = gpio_direction_output(gpio, 1); + else if (sysfs_streq(buf, "out") || sysfs_streq(buf, "low")) + status = gpio_direction_output(gpio, 0); + else if (sysfs_streq(buf, "in")) + status = gpio_direction_input(gpio); + else + status = -EINVAL; + + mutex_unlock(&sysfs_lock); + return status ? : size; +} + +static /* const */ DEVICE_ATTR(direction, 0644, + gpio_direction_show, gpio_direction_store); + +static ssize_t gpio_value_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const struct gpio_desc *desc = dev_get_drvdata(dev); + unsigned gpio = desc - gpio_desc; + ssize_t status; + + mutex_lock(&sysfs_lock); + + if (!test_bit(FLAG_EXPORT, &desc->flags)) { + status = -EIO; + } else { + int value; + + value = !!gpio_get_value_cansleep(gpio); + if (test_bit(FLAG_ACTIVE_LOW, &desc->flags)) + value = !value; + + status = sprintf(buf, "%d\n", value); + } + + mutex_unlock(&sysfs_lock); + return status; +} + +static ssize_t gpio_value_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + const struct gpio_desc *desc = dev_get_drvdata(dev); + unsigned gpio = desc - gpio_desc; + ssize_t status; + + mutex_lock(&sysfs_lock); + + if (!test_bit(FLAG_EXPORT, &desc->flags)) + status = -EIO; + else if (!test_bit(FLAG_IS_OUT, &desc->flags)) + status = -EPERM; + else { + long value; + + status = strict_strtol(buf, 0, &value); + if (status == 0) { + if (test_bit(FLAG_ACTIVE_LOW, &desc->flags)) + value = !value; + printk("[GPIO] %d, %ld\n", gpio, value); + gpio_set_value_cansleep(gpio, value != 0); + status = size; + } + } + + mutex_unlock(&sysfs_lock); + return status; +} + +static const DEVICE_ATTR(value, 0644, + gpio_value_show, gpio_value_store); + +static irqreturn_t gpio_sysfs_irq(int irq, void *priv) +{ + struct sysfs_dirent *value_sd = priv; + + sysfs_notify_dirent(value_sd); + return IRQ_HANDLED; +} + +static int gpio_setup_irq(struct gpio_desc *desc, struct device *dev, + unsigned long gpio_flags) +{ + struct sysfs_dirent *value_sd; + unsigned long irq_flags; + int ret, irq, id; + + if ((desc->flags & GPIO_TRIGGER_MASK) == gpio_flags) + return 0; + + irq = gpio_to_irq(desc - gpio_desc); + if (irq < 0) + return -EIO; + + id = desc->flags >> ID_SHIFT; + value_sd = idr_find(&dirent_idr, id); + if (value_sd) + free_irq(irq, value_sd); + + desc->flags &= ~GPIO_TRIGGER_MASK; + + if (!gpio_flags) { + ret = 0; + goto free_id; + } + + irq_flags = IRQF_SHARED; + if (test_bit(FLAG_TRIG_FALL, &gpio_flags)) + irq_flags |= test_bit(FLAG_ACTIVE_LOW, &desc->flags) ? + IRQF_TRIGGER_RISING : IRQF_TRIGGER_FALLING; + if (test_bit(FLAG_TRIG_RISE, &gpio_flags)) + irq_flags |= test_bit(FLAG_ACTIVE_LOW, &desc->flags) ? + IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING; + + if (!value_sd) { + value_sd = sysfs_get_dirent(dev->kobj.sd, NULL, "value"); + if (!value_sd) { + ret = -ENODEV; + goto err_out; + } + + do { + ret = -ENOMEM; + if (idr_pre_get(&dirent_idr, GFP_KERNEL)) + ret = idr_get_new_above(&dirent_idr, value_sd, + 1, &id); + } while (ret == -EAGAIN); + + if (ret) + goto free_sd; + + desc->flags &= GPIO_FLAGS_MASK; + desc->flags |= (unsigned long)id << ID_SHIFT; + + if (desc->flags >> ID_SHIFT != id) { + ret = -ERANGE; + goto free_id; + } + } + + ret = request_any_context_irq(irq, gpio_sysfs_irq, irq_flags, + "gpiolib", value_sd); + if (ret < 0) + goto free_id; + + desc->flags |= gpio_flags; + return 0; + +free_id: + idr_remove(&dirent_idr, id); + desc->flags &= GPIO_FLAGS_MASK; +free_sd: + if (value_sd) + sysfs_put(value_sd); +err_out: + return ret; +} + +static const struct { + const char *name; + unsigned long flags; +} trigger_types[] = { + { "none", 0 }, + { "falling", BIT(FLAG_TRIG_FALL) }, + { "rising", BIT(FLAG_TRIG_RISE) }, + { "both", BIT(FLAG_TRIG_FALL) | BIT(FLAG_TRIG_RISE) }, +}; + +static ssize_t gpio_edge_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const struct gpio_desc *desc = dev_get_drvdata(dev); + ssize_t status; + + mutex_lock(&sysfs_lock); + + if (!test_bit(FLAG_EXPORT, &desc->flags)) + status = -EIO; + else { + int i; + + status = 0; + for (i = 0; i < ARRAY_SIZE(trigger_types); i++) + if ((desc->flags & GPIO_TRIGGER_MASK) + == trigger_types[i].flags) { + status = sprintf(buf, "%s\n", + trigger_types[i].name); + break; + } + } + + mutex_unlock(&sysfs_lock); + return status; +} + +static ssize_t gpio_edge_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct gpio_desc *desc = dev_get_drvdata(dev); + ssize_t status; + int i; + + for (i = 0; i < ARRAY_SIZE(trigger_types); i++) + if (sysfs_streq(trigger_types[i].name, buf)) + goto found; + return -EINVAL; + +found: + mutex_lock(&sysfs_lock); + + if (!test_bit(FLAG_EXPORT, &desc->flags)) + status = -EIO; + else { + status = gpio_setup_irq(desc, dev, trigger_types[i].flags); + if (!status) + status = size; + } + + mutex_unlock(&sysfs_lock); + + return status; +} + +static DEVICE_ATTR(edge, 0644, gpio_edge_show, gpio_edge_store); + +static int sysfs_set_active_low(struct gpio_desc *desc, struct device *dev, + int value) +{ + int status = 0; + + if (!!test_bit(FLAG_ACTIVE_LOW, &desc->flags) == !!value) + return 0; + + if (value) + set_bit(FLAG_ACTIVE_LOW, &desc->flags); + else + clear_bit(FLAG_ACTIVE_LOW, &desc->flags); + + /* reconfigure poll(2) support if enabled on one edge only */ + if (dev != NULL && (!!test_bit(FLAG_TRIG_RISE, &desc->flags) ^ + !!test_bit(FLAG_TRIG_FALL, &desc->flags))) { + unsigned long trigger_flags = desc->flags & GPIO_TRIGGER_MASK; + + gpio_setup_irq(desc, dev, 0); + status = gpio_setup_irq(desc, dev, trigger_flags); + } + + return status; +} + +static ssize_t gpio_active_low_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const struct gpio_desc *desc = dev_get_drvdata(dev); + ssize_t status; + + mutex_lock(&sysfs_lock); + + if (!test_bit(FLAG_EXPORT, &desc->flags)) + status = -EIO; + else + status = sprintf(buf, "%d\n", + !!test_bit(FLAG_ACTIVE_LOW, &desc->flags)); + + mutex_unlock(&sysfs_lock); + + return status; +} + +static ssize_t gpio_active_low_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct gpio_desc *desc = dev_get_drvdata(dev); + ssize_t status; + + mutex_lock(&sysfs_lock); + + if (!test_bit(FLAG_EXPORT, &desc->flags)) { + status = -EIO; + } else { + long value; + + status = strict_strtol(buf, 0, &value); + if (status == 0) + status = sysfs_set_active_low(desc, dev, value != 0); + } + + mutex_unlock(&sysfs_lock); + + return status ? : size; +} + +static const DEVICE_ATTR(active_low, 0644, + gpio_active_low_show, gpio_active_low_store); + +static const struct attribute *gpio_attrs[] = { + &dev_attr_value.attr, + &dev_attr_active_low.attr, + NULL, +}; + +static const struct attribute_group gpio_attr_group = { + .attrs = (struct attribute **) gpio_attrs, +}; + +/* + * /sys/class/gpio/gpiochipN/ + * /base ... matching gpio_chip.base (N) + * /label ... matching gpio_chip.label + * /ngpio ... matching gpio_chip.ngpio + */ + +static ssize_t chip_base_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const struct gpio_chip *chip = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", chip->base); +} +static DEVICE_ATTR(base, 0444, chip_base_show, NULL); + +static ssize_t chip_label_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const struct gpio_chip *chip = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", chip->label ? : ""); +} +static DEVICE_ATTR(label, 0444, chip_label_show, NULL); + +static ssize_t chip_ngpio_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const struct gpio_chip *chip = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", chip->ngpio); +} +static DEVICE_ATTR(ngpio, 0444, chip_ngpio_show, NULL); + +static const struct attribute *gpiochip_attrs[] = { + &dev_attr_base.attr, + &dev_attr_label.attr, + &dev_attr_ngpio.attr, + NULL, +}; + +static const struct attribute_group gpiochip_attr_group = { + .attrs = (struct attribute **) gpiochip_attrs, +}; + +/* + * /sys/class/gpio/export ... write-only + * integer N ... number of GPIO to export (full access) + * /sys/class/gpio/unexport ... write-only + * integer N ... number of GPIO to unexport + */ +static ssize_t export_store(struct class *class, + struct class_attribute *attr, + const char *buf, size_t len) +{ + long gpio; + int status; + + status = strict_strtol(buf, 0, &gpio); + if (status < 0) + goto done; + + /* No extra locking here; FLAG_SYSFS just signifies that the + * request and export were done by on behalf of userspace, so + * they may be undone on its behalf too. + */ + + status = gpio_request(gpio, "sysfs"); + if (status < 0) + goto done; + + status = gpio_export(gpio, true); + if (status < 0) + gpio_free(gpio); + else + set_bit(FLAG_SYSFS, &gpio_desc[gpio].flags); + +done: + if (status) + pr_debug("%s: status %d\n", __func__, status); + return status ? : len; +} + +static ssize_t unexport_store(struct class *class, + struct class_attribute *attr, + const char *buf, size_t len) +{ + long gpio; + int status; + + status = strict_strtol(buf, 0, &gpio); + if (status < 0) + goto done; + + status = -EINVAL; + + /* reject bogus commands (gpio_unexport ignores them) */ + if (!gpio_is_valid(gpio)) + goto done; + + /* No extra locking here; FLAG_SYSFS just signifies that the + * request and export were done by on behalf of userspace, so + * they may be undone on its behalf too. + */ + if (test_and_clear_bit(FLAG_SYSFS, &gpio_desc[gpio].flags)) { + status = 0; + gpio_free(gpio); + } +done: + if (status) + pr_debug("%s: status %d\n", __func__, status); + return status ? : len; +} + +static struct class_attribute gpio_class_attrs[] = { + __ATTR(export, 0200, NULL, export_store), + __ATTR(unexport, 0200, NULL, unexport_store), + __ATTR_NULL, +}; + +static struct class gpio_class = { + .name = "gpio", + .owner = THIS_MODULE, + + .class_attrs = gpio_class_attrs, +}; + + +/** + * gpio_export - export a GPIO through sysfs + * @gpio: gpio to make available, already requested + * @direction_may_change: true if userspace may change gpio direction + * Context: arch_initcall or later + * + * When drivers want to make a GPIO accessible to userspace after they + * have requested it -- perhaps while debugging, or as part of their + * public interface -- they may use this routine. If the GPIO can + * change direction (some can't) and the caller allows it, userspace + * will see "direction" sysfs attribute which may be used to change + * the gpio's direction. A "value" attribute will always be provided. + * + * Returns zero on success, else an error. + */ +int gpio_export(unsigned gpio, bool direction_may_change) +{ + unsigned long flags; + struct gpio_desc *desc; + int status = -EINVAL; + const char *ioname = NULL; + + /* can't export until sysfs is available ... */ + if (!gpio_class.p) { + pr_debug("%s: called too early!\n", __func__); + return -ENOENT; + } + + if (!gpio_is_valid(gpio)) + goto done; + + mutex_lock(&sysfs_lock); + + spin_lock_irqsave(&gpio_lock, flags); + desc = &gpio_desc[gpio]; + if (test_bit(FLAG_REQUESTED, &desc->flags) + && !test_bit(FLAG_EXPORT, &desc->flags)) { + status = 0; + if (!desc->chip->direction_input + || !desc->chip->direction_output) + direction_may_change = false; + } + spin_unlock_irqrestore(&gpio_lock, flags); + + if (desc->chip->names && desc->chip->names[gpio - desc->chip->base]) + ioname = desc->chip->names[gpio - desc->chip->base]; + + if (status == 0) { + struct device *dev; + + dev = device_create(&gpio_class, desc->chip->dev, MKDEV(0, 0), + desc, ioname ? ioname : "gpio%u", gpio); + if (!IS_ERR(dev)) { + status = sysfs_create_group(&dev->kobj, + &gpio_attr_group); + + if (!status && direction_may_change) + status = device_create_file(dev, + &dev_attr_direction); + + if (!status && gpio_to_irq(gpio) >= 0 + && (direction_may_change + || !test_bit(FLAG_IS_OUT, + &desc->flags))) + status = device_create_file(dev, + &dev_attr_edge); + + if (status != 0) + device_unregister(dev); + } else + status = PTR_ERR(dev); + if (status == 0) + set_bit(FLAG_EXPORT, &desc->flags); + } + + mutex_unlock(&sysfs_lock); + +done: + if (status) + pr_debug("%s: gpio%d status %d\n", __func__, gpio, status); + + return status; +} +EXPORT_SYMBOL_GPL(gpio_export); + +static int match_export(struct device *dev, void *data) +{ + return dev_get_drvdata(dev) == data; +} + +/** + * gpio_export_link - create a sysfs link to an exported GPIO node + * @dev: device under which to create symlink + * @name: name of the symlink + * @gpio: gpio to create symlink to, already exported + * + * Set up a symlink from /sys/.../dev/name to /sys/class/gpio/gpioN + * node. Caller is responsible for unlinking. + * + * Returns zero on success, else an error. + */ +int gpio_export_link(struct device *dev, const char *name, unsigned gpio) +{ + struct gpio_desc *desc; + int status = -EINVAL; + + if (!gpio_is_valid(gpio)) + goto done; + + mutex_lock(&sysfs_lock); + + desc = &gpio_desc[gpio]; + + if (test_bit(FLAG_EXPORT, &desc->flags)) { + struct device *tdev; + + tdev = class_find_device(&gpio_class, NULL, desc, match_export); + if (tdev != NULL) { + status = sysfs_create_link(&dev->kobj, &tdev->kobj, + name); + } else { + status = -ENODEV; + } + } + + mutex_unlock(&sysfs_lock); + +done: + if (status) + pr_debug("%s: gpio%d status %d\n", __func__, gpio, status); + + return status; +} +EXPORT_SYMBOL_GPL(gpio_export_link); + + +/** + * gpio_sysfs_set_active_low - set the polarity of gpio sysfs value + * @gpio: gpio to change + * @value: non-zero to use active low, i.e. inverted values + * + * Set the polarity of /sys/class/gpio/gpioN/value sysfs attribute. + * The GPIO does not have to be exported yet. If poll(2) support has + * been enabled for either rising or falling edge, it will be + * reconfigured to follow the new polarity. + * + * Returns zero on success, else an error. + */ +int gpio_sysfs_set_active_low(unsigned gpio, int value) +{ + struct gpio_desc *desc; + struct device *dev = NULL; + int status = -EINVAL; + + if (!gpio_is_valid(gpio)) + goto done; + + mutex_lock(&sysfs_lock); + + desc = &gpio_desc[gpio]; + + if (test_bit(FLAG_EXPORT, &desc->flags)) { + dev = class_find_device(&gpio_class, NULL, desc, match_export); + if (dev == NULL) { + status = -ENODEV; + goto unlock; + } + } + + status = sysfs_set_active_low(desc, dev, value); + +unlock: + mutex_unlock(&sysfs_lock); + +done: + if (status) + pr_debug("%s: gpio%d status %d\n", __func__, gpio, status); + + return status; +} +EXPORT_SYMBOL_GPL(gpio_sysfs_set_active_low); + +/** + * gpio_unexport - reverse effect of gpio_export() + * @gpio: gpio to make unavailable + * + * This is implicit on gpio_free(). + */ +void gpio_unexport(unsigned gpio) +{ + struct gpio_desc *desc; + int status = 0; + struct device *dev = NULL; + + if (!gpio_is_valid(gpio)) { + status = -EINVAL; + goto done; + } + + mutex_lock(&sysfs_lock); + + desc = &gpio_desc[gpio]; + + if (test_bit(FLAG_EXPORT, &desc->flags)) { + + dev = class_find_device(&gpio_class, NULL, desc, match_export); + if (dev) { + gpio_setup_irq(desc, dev, 0); + clear_bit(FLAG_EXPORT, &desc->flags); + } else + status = -ENODEV; + } + + mutex_unlock(&sysfs_lock); + if (dev) { + device_unregister(dev); + put_device(dev); + } +done: + if (status) + pr_debug("%s: gpio%d status %d\n", __func__, gpio, status); +} +EXPORT_SYMBOL_GPL(gpio_unexport); + +static int gpiochip_export(struct gpio_chip *chip) +{ + int status; + struct device *dev; + + /* Many systems register gpio chips for SOC support very early, + * before driver model support is available. In those cases we + * export this later, in gpiolib_sysfs_init() ... here we just + * verify that _some_ field of gpio_class got initialized. + */ + if (!gpio_class.p) + return 0; + + /* use chip->base for the ID; it's already known to be unique */ + mutex_lock(&sysfs_lock); + dev = device_create(&gpio_class, chip->dev, MKDEV(0, 0), chip, + "gpiochip%d", chip->base); + if (!IS_ERR(dev)) { + status = sysfs_create_group(&dev->kobj, + &gpiochip_attr_group); + } else + status = PTR_ERR(dev); + chip->exported = (status == 0); + mutex_unlock(&sysfs_lock); + + if (status) { + unsigned long flags; + unsigned gpio; + + spin_lock_irqsave(&gpio_lock, flags); + gpio = chip->base; + while (gpio_desc[gpio].chip == chip) + gpio_desc[gpio++].chip = NULL; + spin_unlock_irqrestore(&gpio_lock, flags); + + pr_debug("%s: chip %s status %d\n", __func__, + chip->label, status); + } + + return status; +} + +static void gpiochip_unexport(struct gpio_chip *chip) +{ + int status; + struct device *dev; + + mutex_lock(&sysfs_lock); + dev = class_find_device(&gpio_class, NULL, chip, match_export); + if (dev) { + put_device(dev); + device_unregister(dev); + chip->exported = 0; + status = 0; + } else + status = -ENODEV; + mutex_unlock(&sysfs_lock); + + if (status) + pr_debug("%s: chip %s status %d\n", __func__, + chip->label, status); +} + +static int __init gpiolib_sysfs_init(void) +{ + int status; + unsigned long flags; + unsigned gpio; + + status = class_register(&gpio_class); + if (status < 0) + return status; + + /* Scan and register the gpio_chips which registered very + * early (e.g. before the class_register above was called). + * + * We run before arch_initcall() so chip->dev nodes can have + * registered, and so arch_initcall() can always gpio_export(). + */ + spin_lock_irqsave(&gpio_lock, flags); + for (gpio = 0; gpio < ARCH_NR_GPIOS; gpio++) { + struct gpio_chip *chip; + + chip = gpio_desc[gpio].chip; + if (!chip || chip->exported) + continue; + + spin_unlock_irqrestore(&gpio_lock, flags); + status = gpiochip_export(chip); + spin_lock_irqsave(&gpio_lock, flags); + } + spin_unlock_irqrestore(&gpio_lock, flags); + + + return status; +} +postcore_initcall(gpiolib_sysfs_init); + +#else +static inline int gpiochip_export(struct gpio_chip *chip) +{ + return 0; +} + +static inline void gpiochip_unexport(struct gpio_chip *chip) +{ +} + +#endif /* CONFIG_GPIO_SYSFS */ + +/** + * gpiochip_add() - register a gpio_chip + * @chip: the chip to register, with chip->base initialized + * Context: potentially before irqs or kmalloc will work + * + * Returns a negative errno if the chip can't be registered, such as + * because the chip->base is invalid or already associated with a + * different chip. Otherwise it returns zero as a success code. + * + * When gpiochip_add() is called very early during boot, so that GPIOs + * can be freely used, the chip->dev device must be registered before + * the gpio framework's arch_initcall(). Otherwise sysfs initialization + * for GPIOs will fail rudely. + * + * If chip->base is negative, this requests dynamic assignment of + * a range of valid GPIOs. + */ +int gpiochip_add(struct gpio_chip *chip) +{ + unsigned long flags; + int status = 0; + unsigned id; + int base = chip->base; + + if ((!gpio_is_valid(base) || !gpio_is_valid(base + chip->ngpio - 1)) + && base >= 0) { + status = -EINVAL; + goto fail; + } + + spin_lock_irqsave(&gpio_lock, flags); + + if (base < 0) { + base = gpiochip_find_base(chip->ngpio); + if (base < 0) { + status = base; + goto unlock; + } + chip->base = base; + } + + /* these GPIO numbers must not be managed by another gpio_chip */ + for (id = base; id < base + chip->ngpio; id++) { + if (gpio_desc[id].chip != NULL) { + status = -EBUSY; + break; + } + } + if (status == 0) { + for (id = base; id < base + chip->ngpio; id++) { + gpio_desc[id].chip = chip; + + /* REVISIT: most hardware initializes GPIOs as + * inputs (often with pullups enabled) so power + * usage is minimized. Linux code should set the + * gpio direction first thing; but until it does, + * we may expose the wrong direction in sysfs. + */ + gpio_desc[id].flags = !chip->direction_input + ? (1 << FLAG_IS_OUT) + : 0; + } + } + + of_gpiochip_add(chip); + +unlock: + spin_unlock_irqrestore(&gpio_lock, flags); + + if (status) + goto fail; + + status = gpiochip_export(chip); + if (status) + goto fail; + + pr_info("gpiochip_add: registered GPIOs %d to %d on device: %s\n", + chip->base, chip->base + chip->ngpio - 1, + chip->label ? : "generic"); + + return 0; +fail: + /* failures here can mean systems won't boot... */ + pr_err("gpiochip_add: gpios %d..%d (%s) failed to register\n", + chip->base, chip->base + chip->ngpio - 1, + chip->label ? : "generic"); + return status; +} +EXPORT_SYMBOL_GPL(gpiochip_add); + +/** + * gpiochip_remove() - unregister a gpio_chip + * @chip: the chip to unregister + * + * A gpio_chip with any GPIOs still requested may not be removed. + */ +int gpiochip_remove(struct gpio_chip *chip) +{ + unsigned long flags; + int status = 0; + unsigned id; + + spin_lock_irqsave(&gpio_lock, flags); + + of_gpiochip_remove(chip); + + for (id = chip->base; id < chip->base + chip->ngpio; id++) { + if (test_bit(FLAG_REQUESTED, &gpio_desc[id].flags)) { + status = -EBUSY; + break; + } + } + if (status == 0) { + for (id = chip->base; id < chip->base + chip->ngpio; id++) + gpio_desc[id].chip = NULL; + } + + spin_unlock_irqrestore(&gpio_lock, flags); + + if (status == 0) + gpiochip_unexport(chip); + + return status; +} +EXPORT_SYMBOL_GPL(gpiochip_remove); + +/** + * gpiochip_find() - iterator for locating a specific gpio_chip + * @data: data to pass to match function + * @callback: Callback function to check gpio_chip + * + * Similar to bus_find_device. It returns a reference to a gpio_chip as + * determined by a user supplied @match callback. The callback should return + * 0 if the device doesn't match and non-zero if it does. If the callback is + * non-zero, this function will return to the caller and not iterate over any + * more gpio_chips. + */ +struct gpio_chip *gpiochip_find(const void *data, + int (*match)(struct gpio_chip *chip, + const void *data)) +{ + struct gpio_chip *chip = NULL; + unsigned long flags; + int i; + + spin_lock_irqsave(&gpio_lock, flags); + for (i = 0; i < ARCH_NR_GPIOS; i++) { + if (!gpio_desc[i].chip) + continue; + + if (match(gpio_desc[i].chip, data)) { + chip = gpio_desc[i].chip; + break; + } + } + spin_unlock_irqrestore(&gpio_lock, flags); + + return chip; +} +EXPORT_SYMBOL_GPL(gpiochip_find); + +/* These "optional" allocation calls help prevent drivers from stomping + * on each other, and help provide better diagnostics in debugfs. + * They're called even less than the "set direction" calls. + */ +int gpio_request(unsigned gpio, const char *label) +{ + struct gpio_desc *desc; + struct gpio_chip *chip; + int status = -EINVAL; + unsigned long flags; + + spin_lock_irqsave(&gpio_lock, flags); + + if (!gpio_is_valid(gpio)) + goto done; + desc = &gpio_desc[gpio]; + chip = desc->chip; + if (chip == NULL) + goto done; + + if (!try_module_get(chip->owner)) + goto done; + + /* NOTE: gpio_request() can be called in early boot, + * before IRQs are enabled, for non-sleeping (SOC) GPIOs. + */ + + if (test_and_set_bit(FLAG_REQUESTED, &desc->flags) == 0) { + desc_set_label(desc, label ? : "?"); + status = 0; + } else { + status = -EBUSY; + module_put(chip->owner); + goto done; + } + + if (chip->request) { + /* chip->request may sleep */ + spin_unlock_irqrestore(&gpio_lock, flags); + status = chip->request(chip, gpio - chip->base); + spin_lock_irqsave(&gpio_lock, flags); + + if (status < 0) { + desc_set_label(desc, NULL); + module_put(chip->owner); + clear_bit(FLAG_REQUESTED, &desc->flags); + } + } + +done: + if (status) + pr_debug("gpio_request: gpio-%d (%s) status %d\n", + gpio, label ? : "?", status); + spin_unlock_irqrestore(&gpio_lock, flags); + return status; +} +EXPORT_SYMBOL_GPL(gpio_request); + +void gpio_free(unsigned gpio) +{ + unsigned long flags; + struct gpio_desc *desc; + struct gpio_chip *chip; + + might_sleep(); + + if (!gpio_is_valid(gpio)) { + WARN_ON(extra_checks); + return; + } + + gpio_unexport(gpio); + + spin_lock_irqsave(&gpio_lock, flags); + + desc = &gpio_desc[gpio]; + chip = desc->chip; + if (chip && test_bit(FLAG_REQUESTED, &desc->flags)) { + if (chip->free) { + spin_unlock_irqrestore(&gpio_lock, flags); + might_sleep_if(chip->can_sleep); + chip->free(chip, gpio - chip->base); + spin_lock_irqsave(&gpio_lock, flags); + } + desc_set_label(desc, NULL); + module_put(desc->chip->owner); + clear_bit(FLAG_ACTIVE_LOW, &desc->flags); + clear_bit(FLAG_REQUESTED, &desc->flags); + clear_bit(FLAG_OPEN_DRAIN, &desc->flags); + clear_bit(FLAG_OPEN_SOURCE, &desc->flags); + } else + WARN_ON(extra_checks); + + spin_unlock_irqrestore(&gpio_lock, flags); +} +EXPORT_SYMBOL_GPL(gpio_free); + +/** + * gpio_request_one - request a single GPIO with initial configuration + * @gpio: the GPIO number + * @flags: GPIO configuration as specified by GPIOF_* + * @label: a literal description string of this GPIO + */ +int gpio_request_one(unsigned gpio, unsigned long flags, const char *label) +{ + int err; + + err = gpio_request(gpio, label); + if (err) + return err; + + if (flags & GPIOF_OPEN_DRAIN) + set_bit(FLAG_OPEN_DRAIN, &gpio_desc[gpio].flags); + + if (flags & GPIOF_OPEN_SOURCE) + set_bit(FLAG_OPEN_SOURCE, &gpio_desc[gpio].flags); + + if (flags & GPIOF_DIR_IN) + err = gpio_direction_input(gpio); + else + err = gpio_direction_output(gpio, + (flags & GPIOF_INIT_HIGH) ? 1 : 0); + + if (err) + gpio_free(gpio); + + return err; +} +EXPORT_SYMBOL_GPL(gpio_request_one); + +/** + * gpio_request_array - request multiple GPIOs in a single call + * @array: array of the 'struct gpio' + * @num: how many GPIOs in the array + */ +int gpio_request_array(const struct gpio *array, size_t num) +{ + int i, err; + + for (i = 0; i < num; i++, array++) { + err = gpio_request_one(array->gpio, array->flags, array->label); + if (err) + goto err_free; + } + return 0; + +err_free: + while (i--) + gpio_free((--array)->gpio); + return err; +} +EXPORT_SYMBOL_GPL(gpio_request_array); + +/** + * gpio_free_array - release multiple GPIOs in a single call + * @array: array of the 'struct gpio' + * @num: how many GPIOs in the array + */ +void gpio_free_array(const struct gpio *array, size_t num) +{ + while (num--) + gpio_free((array++)->gpio); +} +EXPORT_SYMBOL_GPL(gpio_free_array); + +/** + * gpiochip_is_requested - return string iff signal was requested + * @chip: controller managing the signal + * @offset: of signal within controller's 0..(ngpio - 1) range + * + * Returns NULL if the GPIO is not currently requested, else a string. + * If debugfs support is enabled, the string returned is the label passed + * to gpio_request(); otherwise it is a meaningless constant. + * + * This function is for use by GPIO controller drivers. The label can + * help with diagnostics, and knowing that the signal is used as a GPIO + * can help avoid accidentally multiplexing it to another controller. + */ +const char *gpiochip_is_requested(struct gpio_chip *chip, unsigned offset) +{ + unsigned gpio = chip->base + offset; + + if (!gpio_is_valid(gpio) || gpio_desc[gpio].chip != chip) + return NULL; + if (test_bit(FLAG_REQUESTED, &gpio_desc[gpio].flags) == 0) + return NULL; +#ifdef CONFIG_DEBUG_FS + return gpio_desc[gpio].label; +#else + return "?"; +#endif +} +EXPORT_SYMBOL_GPL(gpiochip_is_requested); + + +/* Drivers MUST set GPIO direction before making get/set calls. In + * some cases this is done in early boot, before IRQs are enabled. + * + * As a rule these aren't called more than once (except for drivers + * using the open-drain emulation idiom) so these are natural places + * to accumulate extra debugging checks. Note that we can't (yet) + * rely on gpio_request() having been called beforehand. + */ + +int gpio_direction_input(unsigned gpio) +{ + unsigned long flags; + struct gpio_chip *chip; + struct gpio_desc *desc = &gpio_desc[gpio]; + int status = -EINVAL; + + spin_lock_irqsave(&gpio_lock, flags); + + if (!gpio_is_valid(gpio)) + goto fail; + chip = desc->chip; + if (!chip || !chip->get || !chip->direction_input) + goto fail; + gpio -= chip->base; + if (gpio >= chip->ngpio) + goto fail; + status = gpio_ensure_requested(desc, gpio); + if (status < 0) + goto fail; + + /* now we know the gpio is valid and chip won't vanish */ + + spin_unlock_irqrestore(&gpio_lock, flags); + + might_sleep_if(chip->can_sleep); + + if (status) { + status = chip->request(chip, gpio); + if (status < 0) { + pr_debug("GPIO-%d: chip request fail, %d\n", + chip->base + gpio, status); + /* and it's not available to anyone else ... + * gpio_request() is the fully clean solution. + */ + goto lose; + } + } + + status = chip->direction_input(chip, gpio); + if (status == 0) + clear_bit(FLAG_IS_OUT, &desc->flags); + + trace_gpio_direction(chip->base + gpio, 1, status); +lose: + return status; +fail: + spin_unlock_irqrestore(&gpio_lock, flags); + if (status) + pr_debug("%s: gpio-%d status %d\n", + __func__, gpio, status); + return status; +} +EXPORT_SYMBOL_GPL(gpio_direction_input); + +int gpio_direction_output(unsigned gpio, int value) +{ + unsigned long flags; + struct gpio_chip *chip; + struct gpio_desc *desc = &gpio_desc[gpio]; + int status = -EINVAL; + + /* Open drain pin should not be driven to 1 */ + if (value && test_bit(FLAG_OPEN_DRAIN, &desc->flags)) + return gpio_direction_input(gpio); + + /* Open source pin should not be driven to 0 */ + if (!value && test_bit(FLAG_OPEN_SOURCE, &desc->flags)) + return gpio_direction_input(gpio); + + spin_lock_irqsave(&gpio_lock, flags); + + if (!gpio_is_valid(gpio)) + goto fail; + chip = desc->chip; + if (!chip || !chip->set || !chip->direction_output) + goto fail; + gpio -= chip->base; + if (gpio >= chip->ngpio) + goto fail; + status = gpio_ensure_requested(desc, gpio); + if (status < 0) + goto fail; + + /* now we know the gpio is valid and chip won't vanish */ + + spin_unlock_irqrestore(&gpio_lock, flags); + + might_sleep_if(chip->can_sleep); + + if (status) { + status = chip->request(chip, gpio); + if (status < 0) { + pr_debug("GPIO-%d: chip request fail, %d\n", + chip->base + gpio, status); + /* and it's not available to anyone else ... + * gpio_request() is the fully clean solution. + */ + goto lose; + } + } + + status = chip->direction_output(chip, gpio, value); + if (status == 0) + set_bit(FLAG_IS_OUT, &desc->flags); + trace_gpio_value(chip->base + gpio, 0, value); + trace_gpio_direction(chip->base + gpio, 0, status); +lose: + return status; +fail: + spin_unlock_irqrestore(&gpio_lock, flags); + if (status) + pr_debug("%s: gpio-%d status %d\n", + __func__, gpio, status); + return status; +} +EXPORT_SYMBOL_GPL(gpio_direction_output); + +int gpio_re_enabled(unsigned gpio) +{ + unsigned long flags; + struct gpio_chip *chip; + struct gpio_desc *desc = &gpio_desc[gpio]; + int status = -EINVAL; + + spin_lock_irqsave(&gpio_lock, flags); + if (!gpio_is_valid(gpio)) + goto fail; + chip = desc->chip; + if (!chip || !chip->set || !chip->direction_output) + goto fail; + gpio -= chip->base; + if (gpio >= chip->ngpio) + goto fail; + status = gpio_ensure_requested(desc, gpio); + if (status < 0) + goto fail; + + spin_unlock_irqrestore(&gpio_lock, flags); + + status = chip->request(chip, gpio); + if (status < 0) { + pr_debug("GPIO-%d: chip request fail, %d\n", + chip->base + gpio, status); + } + return status; + +fail: + spin_unlock_irqrestore(&gpio_lock, flags); + if (status) + pr_debug("%s: gpio-%d status %d\n", + __func__, gpio, status); + return status; +} +EXPORT_SYMBOL_GPL(gpio_re_enabled); + +/** + * gpio_set_debounce - sets @debounce time for a @gpio + * @gpio: the gpio to set debounce time + * @debounce: debounce time is microseconds + */ +int gpio_set_debounce(unsigned gpio, unsigned debounce) +{ + unsigned long flags; + struct gpio_chip *chip; + struct gpio_desc *desc = &gpio_desc[gpio]; + int status = -EINVAL; + + spin_lock_irqsave(&gpio_lock, flags); + + if (!gpio_is_valid(gpio)) + goto fail; + chip = desc->chip; + if (!chip || !chip->set || !chip->set_debounce) + goto fail; + gpio -= chip->base; + if (gpio >= chip->ngpio) + goto fail; + status = gpio_ensure_requested(desc, gpio); + if (status < 0) + goto fail; + + /* now we know the gpio is valid and chip won't vanish */ + + spin_unlock_irqrestore(&gpio_lock, flags); + + might_sleep_if(chip->can_sleep); + + return chip->set_debounce(chip, gpio, debounce); + +fail: + spin_unlock_irqrestore(&gpio_lock, flags); + if (status) + pr_debug("%s: gpio-%d status %d\n", + __func__, gpio, status); + + return status; +} +EXPORT_SYMBOL_GPL(gpio_set_debounce); + +/* I/O calls are only valid after configuration completed; the relevant + * "is this a valid GPIO" error checks should already have been done. + * + * "Get" operations are often inlinable as reading a pin value register, + * and masking the relevant bit in that register. + * + * When "set" operations are inlinable, they involve writing that mask to + * one register to set a low value, or a different register to set it high. + * Otherwise locking is needed, so there may be little value to inlining. + * + *------------------------------------------------------------------------ + * + * IMPORTANT!!! The hot paths -- get/set value -- assume that callers + * have requested the GPIO. That can include implicit requesting by + * a direction setting call. Marking a gpio as requested locks its chip + * in memory, guaranteeing that these table lookups need no more locking + * and that gpiochip_remove() will fail. + * + * REVISIT when debugging, consider adding some instrumentation to ensure + * that the GPIO was actually requested. + */ + +/** + * __gpio_get_value() - return a gpio's value + * @gpio: gpio whose value will be returned + * Context: any + * + * This is used directly or indirectly to implement gpio_get_value(). + * It returns the zero or nonzero value provided by the associated + * gpio_chip.get() method; or zero if no such method is provided. + */ +int __gpio_get_value(unsigned gpio) +{ + struct gpio_chip *chip; + int value; + + chip = gpio_to_chip(gpio); + /* Should be using gpio_get_value_cansleep() */ + WARN_ON(chip->can_sleep); + value = chip->get ? chip->get(chip, gpio - chip->base) : 0; + trace_gpio_value(gpio, 1, value); + return value; +} +EXPORT_SYMBOL_GPL(__gpio_get_value); + +/* + * _gpio_set_open_drain_value() - Set the open drain gpio's value. + * @gpio: Gpio whose state need to be set. + * @chip: Gpio chip. + * @value: Non-zero for setting it HIGH otherise it will set to LOW. + */ +static void _gpio_set_open_drain_value(unsigned gpio, + struct gpio_chip *chip, int value) +{ + int err = 0; + if (value) { + err = chip->direction_input(chip, gpio - chip->base); + if (!err) + clear_bit(FLAG_IS_OUT, &gpio_desc[gpio].flags); + } else { + err = chip->direction_output(chip, gpio - chip->base, 0); + if (!err) + set_bit(FLAG_IS_OUT, &gpio_desc[gpio].flags); + } + trace_gpio_direction(gpio, value, err); + if (err < 0) + pr_err("%s: Error in set_value for open drain gpio%d err %d\n", + __func__, gpio, err); +} + +/* + * _gpio_set_open_source() - Set the open source gpio's value. + * @gpio: Gpio whose state need to be set. + * @chip: Gpio chip. + * @value: Non-zero for setting it HIGH otherise it will set to LOW. + */ +static void _gpio_set_open_source_value(unsigned gpio, + struct gpio_chip *chip, int value) +{ + int err = 0; + if (value) { + err = chip->direction_output(chip, gpio - chip->base, 1); + if (!err) + set_bit(FLAG_IS_OUT, &gpio_desc[gpio].flags); + } else { + err = chip->direction_input(chip, gpio - chip->base); + if (!err) + clear_bit(FLAG_IS_OUT, &gpio_desc[gpio].flags); + } + trace_gpio_direction(gpio, !value, err); + if (err < 0) + pr_err("%s: Error in set_value for open source gpio%d err %d\n", + __func__, gpio, err); +} + + +/** + * __gpio_set_value() - assign a gpio's value + * @gpio: gpio whose value will be assigned + * @value: value to assign + * Context: any + * + * This is used directly or indirectly to implement gpio_set_value(). + * It invokes the associated gpio_chip.set() method. + */ +void __gpio_set_value(unsigned gpio, int value) +{ + struct gpio_chip *chip; + + chip = gpio_to_chip(gpio); + /* Should be using gpio_set_value_cansleep() */ + WARN_ON(chip->can_sleep); + trace_gpio_value(gpio, 0, value); + if (test_bit(FLAG_OPEN_DRAIN, &gpio_desc[gpio].flags)) + _gpio_set_open_drain_value(gpio, chip, value); + else if (test_bit(FLAG_OPEN_SOURCE, &gpio_desc[gpio].flags)) + _gpio_set_open_source_value(gpio, chip, value); + else + chip->set(chip, gpio - chip->base, value); +} +EXPORT_SYMBOL_GPL(__gpio_set_value); + +/** + * __gpio_cansleep() - report whether gpio value access will sleep + * @gpio: gpio in question + * Context: any + * + * This is used directly or indirectly to implement gpio_cansleep(). It + * returns nonzero if access reading or writing the GPIO value can sleep. + */ +int __gpio_cansleep(unsigned gpio) +{ + struct gpio_chip *chip; + + /* only call this on GPIOs that are valid! */ + chip = gpio_to_chip(gpio); + + return chip->can_sleep; +} +EXPORT_SYMBOL_GPL(__gpio_cansleep); + +/** + * __gpio_to_irq() - return the IRQ corresponding to a GPIO + * @gpio: gpio whose IRQ will be returned (already requested) + * Context: any + * + * This is used directly or indirectly to implement gpio_to_irq(). + * It returns the number of the IRQ signaled by this (input) GPIO, + * or a negative errno. + */ +int __gpio_to_irq(unsigned gpio) +{ + struct gpio_chip *chip; + + chip = gpio_to_chip(gpio); + return chip->to_irq ? chip->to_irq(chip, gpio - chip->base) : -ENXIO; +} +EXPORT_SYMBOL_GPL(__gpio_to_irq); + + + +/* There's no value in making it easy to inline GPIO calls that may sleep. + * Common examples include ones connected to I2C or SPI chips. + */ + +int gpio_get_value_cansleep(unsigned gpio) +{ + struct gpio_chip *chip; + int value; + + might_sleep_if(extra_checks); + chip = gpio_to_chip(gpio); + value = chip->get ? chip->get(chip, gpio - chip->base) : 0; + trace_gpio_value(gpio, 1, value); + return value; +} +EXPORT_SYMBOL_GPL(gpio_get_value_cansleep); + +void gpio_set_value_cansleep(unsigned gpio, int value) +{ + struct gpio_chip *chip; + + might_sleep_if(extra_checks); + chip = gpio_to_chip(gpio); + trace_gpio_value(gpio, 0, value); + if (test_bit(FLAG_OPEN_DRAIN, &gpio_desc[gpio].flags)) + _gpio_set_open_drain_value(gpio, chip, value); + else if (test_bit(FLAG_OPEN_SOURCE, &gpio_desc[gpio].flags)) + _gpio_set_open_source_value(gpio, chip, value); + else + chip->set(chip, gpio - chip->base, value); +} +EXPORT_SYMBOL_GPL(gpio_set_value_cansleep); + + +#ifdef CONFIG_DEBUG_FS + +static void gpiolib_dbg_show(struct seq_file *s, struct gpio_chip *chip) +{ + unsigned i; + unsigned gpio = chip->base; + struct gpio_desc *gdesc = &gpio_desc[gpio]; + int is_out; + + for (i = 0; i < chip->ngpio; i++, gpio++, gdesc++) { + if (!test_bit(FLAG_REQUESTED, &gdesc->flags)) + continue; + + is_out = test_bit(FLAG_IS_OUT, &gdesc->flags); + seq_printf(s, " gpio-%-3d (%-20.20s) %s %s", + gpio, gdesc->label, + is_out ? "out" : "in ", + chip->get + ? (chip->get(chip, i) ? "hi" : "lo") + : "? "); + seq_printf(s, "\n"); + } +} + +static int gpiolib_show(struct seq_file *s, void *unused) +{ + struct gpio_chip *chip = NULL; + unsigned gpio; + int started = 0; + + /* REVISIT this isn't locked against gpio_chip removal ... */ + + for (gpio = 0; gpio_is_valid(gpio); gpio++) { + struct device *dev; + + if (chip == gpio_desc[gpio].chip) + continue; + chip = gpio_desc[gpio].chip; + if (!chip) + continue; + + seq_printf(s, "%sGPIOs %d-%d", + started ? "\n" : "", + chip->base, chip->base + chip->ngpio - 1); + dev = chip->dev; + if (dev) + seq_printf(s, ", %s/%s", + dev->bus ? dev->bus->name : "no-bus", + dev_name(dev)); + if (chip->label) + seq_printf(s, ", %s", chip->label); + if (chip->can_sleep) + seq_printf(s, ", can sleep"); + seq_printf(s, ":\n"); + + started = 1; + if (chip->dbg_show) + chip->dbg_show(s, chip); + else + gpiolib_dbg_show(s, chip); + } + return 0; +} + +static int gpiolib_open(struct inode *inode, struct file *file) +{ + return single_open(file, gpiolib_show, NULL); +} + +static const struct file_operations gpiolib_operations = { + .open = gpiolib_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int __init gpiolib_debugfs_init(void) +{ + /* /sys/kernel/debug/gpio */ + (void) debugfs_create_file("gpio", S_IFREG | S_IRUGO, + NULL, NULL, &gpiolib_operations); + return 0; +} +subsys_initcall(gpiolib_debugfs_init); + +#endif /* DEBUG_FS */ |