From: Ning Li <ning...@intel.com>

This driver supports the GPIO controllers found in newer Intel SoCs like
Cherryview and Braswell.

Signed-off-by: Ning Li <ning...@intel.com>
Signed-off-by: Alan Cox <a...@linux.intel.com>
Signed-off-by: Mika Westerberg <mika.westerb...@linux.intel.com>
---
 drivers/gpio/Kconfig           |   8 +
 drivers/gpio/Makefile          |   1 +
 drivers/gpio/gpio-cherryview.c | 947 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 956 insertions(+)
 create mode 100644 drivers/gpio/gpio-cherryview.c

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 9de1515e5808..73a317d7b7bf 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -736,6 +736,14 @@ config GPIO_INTEL_MID
        help
          Say Y here to support Intel Mid GPIO.
 
+config GPIO_CHERRYVIEW
+       tristate "Intel CherryView/Braswell GPIO support"
+       depends on ACPI
+       select GPIOLIB_IRQCHIP
+       help
+         Enable support for GPIO host controllers found on Intel SoCs
+         such as CherryView and Braswell.
+
 config GPIO_PCH
        tristate "Intel EG20T PCH/LAPIS Semiconductor IOH(ML7223/ML7831) GPIO"
        depends on PCI && (X86_32 || COMPILE_TEST)
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 5d024e396622..1162b08564a7 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_GPIO_AMD8111)    += gpio-amd8111.o
 obj-$(CONFIG_GPIO_ARIZONA)     += gpio-arizona.o
 obj-$(CONFIG_GPIO_BCM_KONA)    += gpio-bcm-kona.o
 obj-$(CONFIG_GPIO_BT8XX)       += gpio-bt8xx.o
+obj-$(CONFIG_GPIO_CHERRYVIEW)  += gpio-cherryview.o
 obj-$(CONFIG_GPIO_CLPS711X)    += gpio-clps711x.o
 obj-$(CONFIG_GPIO_CS5535)      += gpio-cs5535.o
 obj-$(CONFIG_GPIO_CRYSTAL_COVE)        += gpio-crystalcove.o
diff --git a/drivers/gpio/gpio-cherryview.c b/drivers/gpio/gpio-cherryview.c
new file mode 100644
index 000000000000..ba45741a345c
--- /dev/null
+++ b/drivers/gpio/gpio-cherryview.c
@@ -0,0 +1,947 @@
+/*
+ * GPIO controller driver for Intel Cherryview/Braswell.
+ *
+ * Copyright (C) 2014, 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/bitops.h>
+#include <linux/interrupt.h>
+#include <linux/gpio/driver.h>
+#include <linux/seq_file.h>
+#include <linux/ioport.h>
+#include <linux/acpi.h>
+#include <linux/platform_device.h>
+
+#define FAMILY0_PAD_REGS_OFF   0x4400
+#define FAMILY_PAD_REGS_SIZE   0x400
+#define MAX_FAMILY_PAD_GPIO_NO 15
+#define GPIO_REGS_SIZE         8
+
+#define CV_PADCTRL0_REG                0x000
+#define CV_PADCTRL1_REG                0x004
+#define CV_INT_STAT_REG                0x300
+#define CV_INT_MASK_REG                0x380
+
+#define CV_GPIO_RX_STAT                BIT(0)
+#define CV_GPIO_TX_STAT                BIT(1)
+#define CV_GPIO_EN             BIT(15)
+
+#define CV_CFG_LOCK_MASK       BIT(31)
+#define CV_INT_CFG_MASK                (BIT(0) | BIT(1) | BIT(2))
+#define CV_PAD_MODE_MASK       (0xf << 16)
+
+#define CV_GPIO_CFG_MASK       (BIT(8) | BIT(9) | BIT(10))
+#define CV_GPIO_TX_EN          (1 << 8)
+#define CV_GPIO_RX_EN          (2 << 8)
+
+#define CV_INV_RX_DATA         BIT(6)
+
+#define CV_INT_SEL_MASK                (0xf << 28)
+
+enum {
+       CV_INTR_DISABLE,
+       CV_TRIG_EDGE_FALLING,
+       CV_TRIG_EDGE_RISING,
+       CV_TRIG_EDGE_BOTH,
+       CV_TRIG_LEVEL,
+};
+
+struct chv_gpio_bank {
+       const char *name;
+       const char *uid;
+       const char * const *pads;
+       size_t npads;
+};
+
+struct chv_gpio {
+       struct gpio_chip chip;
+       spinlock_t lock;
+       void __iomem *reg_base;
+       int intr_lines[16];
+       const struct chv_gpio_bank *bank;
+};
+
+static const char * const north_pads[] = {
+       "GPIO_DFX_0",
+       "GPIO_DFX_3",
+       "GPIO_DFX_7",
+       "GPIO_DFX_1",
+       "GPIO_DFX_5",
+       "GPIO_DFX_4",
+       "GPIO_DFX_8",
+       "GPIO_DFX_2",
+       "GPIO_DFX_6",
+
+       [9 ... 14] = 0,
+
+       "GPIO_SUS0",
+       "SEC_GPIO_SUS10",
+       "GPIO_SUS3",
+       "GPIO_SUS7",
+       "GPIO_SUS1",
+       "GPIO_SUS5",
+       "SEC_GPIO_SUS11",
+       "GPIO_SUS4",
+       "SEC_GPIO_SUS8",
+       "GPIO_SUS2",
+       "GPIO_SUS6",
+       "CX_PREQ_B",
+       "SEC_GPIO_SUS9",
+
+       [28 ... 29] = 0,
+
+       "TRST_B",
+       "TCK",
+       "PROCHOT_B",
+       "SVIDO_DATA",
+       "TMS",
+       "CX_PRDY_B_2",
+       "TDO_2",
+       "CX_PRDY_B",
+       "SVIDO_ALERT_B",
+       "TDO",
+       "SVIDO_CLK",
+       "TDI",
+
+       [42 ... 44] = 0,
+
+       "GP_CAMERASB_05",
+       "GP_CAMERASB_02",
+       "GP_CAMERASB_08",
+       "GP_CAMERASB_00",
+       "GP_CAMERASB_06",
+       "GP_CAMERASB_10",
+       "GP_CAMERASB_03",
+       "GP_CAMERASB_09",
+       "GP_CAMERASB_01",
+       "GP_CAMERASB_07",
+       "GP_CAMERASB_11",
+       "GP_CAMERASB_04",
+
+       [57 ... 59] = 0,
+
+       "PANEL0_BKLTEN",
+       "HV_DDI0_HPD",
+       "HV_DDI2_DDC_SDA",
+       "PANEL1_BKLTCTL",
+       "HV_DDI1_HPD",
+       "PANEL0_BKLTCTL",
+       "HV_DDI0_DDC_SDA",
+       "HV_DDI2_DDC_SCL",
+       "HV_DDI2_HPD",
+       "PANEL1_VDDEN",
+       "PANEL1_BKLTEN",
+       "HV_DDI0_DDC_SCL",
+       "PANEL0_VDDEN",
+};
+
+static const char * const southeast_pads[] = {
+       "MF_PLT_CLK0",
+       "PWM1",
+       "MF_PLT_CLK1",
+       "MF_PLT_CLK4",
+       "MF_PLT_CLK3",
+       "PWM0",
+       "MF_PLT_CLK5",
+       "MF_PLT_CLK2",
+
+       [9 ... 14] = 0,
+
+       "SDMMC2_D3_CD_B",
+       "SDMMC1_CLK",
+       "SDMMC1_D0",
+       "SDMMC2_D1",
+       "SDMMC2_CLK",
+       "SDMMC1_D2",
+       "SDMMC2_D2",
+       "SDMMC2_CMD",
+       "SDMMC1_CMD",
+       "SDMMC1_D1",
+       "SDMMC2_D0",
+       "SDMMC1_D3_CD_B",
+
+       [27 ... 29] = 0,
+
+       "SDMMC3_D1",
+       "SDMMC3_CLK",
+       "SDMMC3_D3",
+       "SDMMC3_D2",
+       "SDMMC3_CMD",
+       "SDMMC3_D0",
+
+       [36 ... 44] = 0,
+
+       "MF_LPC_AD2",
+       "LPC_CLKRUNB",
+       "MF_LPC_AD0",
+       "LPC_FRAMEB",
+       "MF_LPC_CLKOUT1",
+       "MF_LPC_AD3",
+       "MF_LPC_CLKOUT0",
+       "MF_LPC_AD1",
+
+       [53 ... 59] = 0,
+
+       "SPI1_MISO",
+       "SPI1_CSO_B",
+       "SPI1_CLK",
+       "MMC1_D6",
+       "SPI1_MOSI",
+       "MMC1_D5",
+       "SPI1_CS1_B",
+       "MMC1_D4_SD_WE",
+       "MMC1_D7",
+       "MMC1_RCLK",
+
+       [70 ... 74] = 0,
+
+       "USB_OC1_B",
+       "PMU_RESETBUTTON_B",
+       "GPIO_ALERT",
+       "SDMMC3_PWR_EN_B",
+       "ILB_SERIRQ",
+       "USB_OC0_B",
+       "SDMMC3_CD_B",
+       "SPKR",
+       "SUSPWRDNACK",
+       "SPARE_PIN",
+       "SDMMC3_1P8_EN",
+};
+
+static const char * const east_pads[] = {
+       "PMU_SLP_S3_B",
+       "PMU_BATLOW_B",
+       "SUS_STAT_B",
+       "PMU_SLP_S0IX_B",
+       "PMU_AC_PRESENT",
+       "PMU_PLTRST_B",
+       "PMU_SUSCLK",
+       "PMU_SLP_LAN_B",
+       "PMU_PWRBTN_B",
+       "PMU_SLP_S4_B",
+       "PMU_WAKE_B",
+       "PMU_WAKE_LAN_B",
+
+       [12 ... 14] = 0,
+
+       "MF_ISH_GPIO_3",
+       "MF_ISH_GPIO_7",
+       "MF_ISH_I2C1_SCL",
+       "MF_ISH_GPIO_1",
+       "MF_ISH_GPIO_5",
+       "MF_ISH_GPIO_9",
+       "MF_ISH_GPIO_0",
+       "MF_ISH_GPIO_4",
+       "MF_ISH_GPIO_8",
+       "MF_ISH_GPIO_2",
+       "MF_ISH_GPIO_6",
+       "MF_ISH_I2C1_SDA",
+};
+
+static const char * const southwest_pads[] = {
+       "FST_SPI_D2",
+       "FST_SPI_D0",
+       "FST_SPI_CLK",
+       "FST_SPI_D3",
+       "FST_SPI_CS1_B",
+       "FST_SPI_D1",
+       "FST_SPI_CS0_B",
+       "FST_SPI_CS2_B",
+
+       [8 ... 14] = 0,
+
+       "UART1_RTS_B",
+       "UART1_RXD",
+       "UART2_RXD",
+       "UART1_CTS_B",
+       "UART2_RTS_B",
+       "UART1_TXD",
+       "UART2_TXD",
+       "UART2_CTS_B",
+
+       [23 ... 29] = 0,
+
+       "MF_HDA_CLK",
+       "MF_HDA_RSTB",
+       "MF_HDA_SDIO",
+       "MF_HDA_SDO",
+       "MF_HDA_DOCKRSTB",
+       "MF_HDA_SYNC",
+       "MF_HDA_SDI1",
+       "MF_HDA_DOCKENB",
+
+       [38 ... 44] = 0,
+
+       "I2C5_SDA",
+       "I2C4_SDA",
+       "I2C6_SDA",
+       "I2C5_SCL",
+       "I2C_NFC_SDA",
+       "I2C4_SCL",
+       "I2C6_SCL",
+       "I2C_NFC_SCL",
+
+       [53 ... 59] = 0,
+
+       "I2C1_SDA",
+       "I2C0_SDA",
+       "I2C2_SDA",
+       "I2C1_SCL",
+       "I2C3_SDA",
+       "I2C0_SCL",
+       "I2C2_SCL",
+       "I2C3_SCL",
+
+       [68 ... 74] = 0,
+
+       "SATA_GP0",
+       "SATA_GP1",
+       "SATA_LEDN",
+       "SATA_GP2",
+       "MF_SMB_ALERTB",
+       "SATA_GP3",
+       "MF_SMB_CLK",
+       "MF_SMB_DATA",
+
+       [83 ... 89] = 0,
+
+       "PCIE_CLKREQ0B",
+       "PCIE_CLKREQ1B",
+       "GP_SSP_2_CLK",
+       "PCIE_CLKREQ2B",
+       "GP_SSP_2_RXD",
+       "PCIE_CLKREQ3B",
+       "GP_SSP_2_FS",
+       "GP_SSP_2_TXD",
+};
+
+static const struct chv_gpio_bank chv_banks[] = {
+       {
+               .name = "SW",
+               .uid = "1",
+               .pads = southwest_pads,
+               .npads = ARRAY_SIZE(southwest_pads),
+       },
+       {
+               .name = "N",
+               .uid = "2",
+               .pads = north_pads,
+               .npads = ARRAY_SIZE(north_pads),
+       },
+       {
+               .name = "E",
+               .uid = "3",
+               .pads = east_pads,
+               .npads = ARRAY_SIZE(east_pads),
+       },
+       {
+               .name = "SE",
+               .uid = "4",
+               .pads = southeast_pads,
+               .npads = ARRAY_SIZE(southeast_pads),
+       },
+};
+
+#define to_chv_gpio(c) container_of(c, struct chv_gpio, chip)
+
+static void __iomem *chv_gpio_reg(struct chv_gpio *cg, unsigned offset, int 
reg)
+{
+       u32 reg_offset;
+
+       if (reg == CV_INT_STAT_REG || reg == CV_INT_MASK_REG) {
+               reg_offset = 0;
+       } else {
+               reg_offset = FAMILY0_PAD_REGS_OFF +
+                     FAMILY_PAD_REGS_SIZE * (offset / MAX_FAMILY_PAD_GPIO_NO) +
+                     GPIO_REGS_SIZE * (offset % MAX_FAMILY_PAD_GPIO_NO);
+       }
+
+       return cg->reg_base + reg_offset + reg;
+}
+
+static inline void chv_writel(u32 value, void __iomem *reg)
+{
+       writel(value, reg);
+       /* simple readback to confirm the bus transferring done */
+       readl(reg);
+}
+
+/* When Pad Cfg is locked, driver can only change GPIOTXState or GPIORXState */
+static inline bool chv_gpio_pad_locked(struct chv_gpio *cg, unsigned offset)
+{
+       void __iomem *reg;
+
+       reg = chv_gpio_reg(cg, offset, CV_PADCTRL1_REG);
+       return readl(reg) & CV_CFG_LOCK_MASK;
+}
+
+static int chv_gpio_request(struct gpio_chip *chip, unsigned offset)
+{
+       struct chv_gpio *cg = to_chv_gpio(chip);
+       void __iomem *reg;
+       u32 value;
+
+       if (!cg->bank->pads[offset])
+               return -EINVAL;
+
+       if (chv_gpio_pad_locked(cg, offset))
+               return 0;
+
+       /* Disable interrupt generation */
+       reg = chv_gpio_reg(cg, offset, CV_PADCTRL1_REG);
+       value = readl(reg);
+       value &= ~(CV_INT_CFG_MASK | CV_INV_RX_DATA);
+       chv_writel(value, reg);
+
+       /* Switch to a GPIO mode */
+       reg = chv_gpio_reg(cg, offset, CV_PADCTRL0_REG);
+       value = readl(reg) | CV_GPIO_EN;
+       chv_writel(value, reg);
+
+       return 0;
+}
+
+static void chv_gpio_free(struct gpio_chip *chip, unsigned offset)
+{
+       struct chv_gpio *cg = to_chv_gpio(chip);
+       void __iomem *reg;
+       u32 value;
+
+       if (chv_gpio_pad_locked(cg, offset))
+               return;
+
+       reg = chv_gpio_reg(cg, offset, CV_PADCTRL0_REG);
+       value = readl(reg) & ~CV_GPIO_EN;
+       chv_writel(value, reg);
+}
+
+static void chv_update_irq_type(struct chv_gpio *cg, unsigned type,
+                               void __iomem *reg)
+{
+       u32 value;
+
+       value = readl(reg);
+       value &= ~CV_INT_CFG_MASK;
+       value &= ~CV_INV_RX_DATA;
+
+       if (type & IRQ_TYPE_EDGE_BOTH) {
+               if ((type & IRQ_TYPE_EDGE_BOTH) == IRQ_TYPE_EDGE_BOTH)
+                       value |= CV_TRIG_EDGE_BOTH;
+               else if (type & IRQ_TYPE_EDGE_RISING)
+                       value |= CV_TRIG_EDGE_RISING;
+               else if (type & IRQ_TYPE_EDGE_FALLING)
+                       value |= CV_TRIG_EDGE_FALLING;
+       } else if (type & IRQ_TYPE_LEVEL_MASK) {
+                       value |= CV_TRIG_LEVEL;
+               if (type & IRQ_TYPE_LEVEL_LOW)
+                       value |= CV_INV_RX_DATA;
+       }
+
+       chv_writel(value, reg);
+}
+
+/* BIOS programs IntSel bits for shared interrupt. GPIO driver follows it. */
+static void pad_intr_line_save(struct chv_gpio *cg, unsigned offset)
+{
+       void __iomem *reg = chv_gpio_reg(cg, offset, CV_PADCTRL0_REG);
+       u32 value, intr_line;
+
+       value = readl(reg);
+       intr_line = (value & CV_INT_SEL_MASK) >> 28;
+       cg->intr_lines[intr_line] = offset;
+}
+
+static int chv_irq_type(struct irq_data *d, unsigned type)
+{
+       struct chv_gpio *cg = irq_data_get_irq_chip_data(d);
+       u32 offset = irqd_to_hwirq(d);
+       void __iomem *reg;
+       unsigned long flags;
+
+       spin_lock_irqsave(&cg->lock, flags);
+
+       /*
+        * Pins which can be used as shared interrupt are configured in
+        * BIOS. Driver trusts BIOS configurations and assigns different
+        * handler according to the irq type.
+        *
+        * Driver needs to save the mapping between each pin and
+        * its interrupt line.
+        * 1. If the pin cfg is locked in BIOS:
+        *      Trust BIOS has programmed IntWakeCfg bits correctly,
+        *      driver just needs to save the mapping.
+        * 2. If the pin cfg is not locked in BIOS:
+        *      Driver programs the IntWakeCfg bits and save the mapping.
+        */
+       if (!chv_gpio_pad_locked(cg, offset)) {
+               reg = chv_gpio_reg(cg, offset, CV_PADCTRL1_REG);
+               chv_update_irq_type(cg, type, reg);
+       }
+
+       pad_intr_line_save(cg, offset);
+
+       if (type & IRQ_TYPE_EDGE_BOTH)
+               __irq_set_handler_locked(d->irq, handle_edge_irq);
+       else if (type & IRQ_TYPE_LEVEL_MASK)
+               __irq_set_handler_locked(d->irq, handle_level_irq);
+
+       spin_unlock_irqrestore(&cg->lock, flags);
+
+       return 0;
+}
+
+static int chv_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+       struct chv_gpio *cg = to_chv_gpio(chip);
+       unsigned long flags;
+       void __iomem *reg;
+       u32 value;
+       int ret;
+
+       reg = chv_gpio_reg(cg, offset, CV_PADCTRL0_REG);
+
+       spin_lock_irqsave(&cg->lock, flags);
+
+       value = readl(reg);
+       if (value & CV_GPIO_TX_EN)
+               ret = !!(value & CV_GPIO_TX_STAT);
+       else
+               ret = !!(value & CV_GPIO_RX_STAT);
+
+       spin_unlock_irqrestore(&cg->lock, flags);
+
+       return ret;
+}
+
+static void chv_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+       struct chv_gpio *cg = to_chv_gpio(chip);
+       void __iomem *reg;
+       unsigned long flags;
+       u32 old_val;
+
+       reg = chv_gpio_reg(cg, offset, CV_PADCTRL0_REG);
+
+       spin_lock_irqsave(&cg->lock, flags);
+
+       old_val = readl(reg);
+
+       if (value)
+               chv_writel(old_val | CV_GPIO_TX_STAT, reg);
+       else
+               chv_writel(old_val & ~CV_GPIO_TX_STAT, reg);
+
+       spin_unlock_irqrestore(&cg->lock, flags);
+}
+
+static int chv_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
+{
+       struct chv_gpio *cg = to_chv_gpio(chip);
+       unsigned long flags;
+       void __iomem *reg;
+       u32 value;
+
+       if (chv_gpio_pad_locked(cg, offset))
+               return 0;
+
+       reg = chv_gpio_reg(cg, offset, CV_PADCTRL0_REG);
+
+       spin_lock_irqsave(&cg->lock, flags);
+
+       value = readl(reg) & ~CV_GPIO_CFG_MASK;
+       /* Disable TX and Enable RX */
+       value |= CV_GPIO_RX_EN;
+       chv_writel(value, reg);
+
+       spin_unlock_irqrestore(&cg->lock, flags);
+
+       return 0;
+}
+
+static int chv_gpio_direction_output(struct gpio_chip *chip, unsigned offset,
+                                    int value)
+{
+       struct chv_gpio *cg = to_chv_gpio(chip);
+       unsigned long flags;
+       void __iomem *reg;
+       u32 reg_val;
+
+       if (chv_gpio_pad_locked(cg, offset))
+               return 0;
+
+       reg = chv_gpio_reg(cg, offset, CV_PADCTRL0_REG);
+
+       spin_lock_irqsave(&cg->lock, flags);
+       reg_val = readl(reg) & ~CV_GPIO_CFG_MASK;
+       reg_val |= CV_GPIO_TX_EN;
+
+       /* Control TX State */
+       if (value)
+               reg_val |= CV_GPIO_TX_STAT;
+       else
+               reg_val &= ~CV_GPIO_TX_STAT;
+
+       chv_writel(reg_val, reg);
+
+       spin_unlock_irqrestore(&cg->lock, flags);
+       return 0;
+}
+
+static void chv_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip)
+{
+       struct chv_gpio *cg = to_chv_gpio(chip);
+       u32 ctrl0, ctrl1, offs;
+       unsigned long flags;
+       void __iomem *reg;
+       int i;
+
+       spin_lock_irqsave(&cg->lock, flags);
+
+       for (i = 0; i < cg->chip.ngpio; i++) {
+               const char *intcfg;
+               const char *label;
+               const char *value;
+               const char *dir;
+               char pin[8];
+
+               if (!cg->bank->pads[i])
+                       continue;
+
+               offs = FAMILY0_PAD_REGS_OFF +
+                     FAMILY_PAD_REGS_SIZE * (i / MAX_FAMILY_PAD_GPIO_NO) +
+                     GPIO_REGS_SIZE * (i % MAX_FAMILY_PAD_GPIO_NO);
+
+               ctrl0 = readl(chv_gpio_reg(cg, i, CV_PADCTRL0_REG));
+               ctrl1 = readl(chv_gpio_reg(cg, i, CV_PADCTRL1_REG));
+
+               snprintf(pin, sizeof(pin), "%s%02d", cg->bank->name, i);
+
+               switch (ctrl1 & CV_INT_CFG_MASK) {
+               case CV_INTR_DISABLE:
+                       intcfg = "disabled";
+                       break;
+
+               case CV_TRIG_EDGE_FALLING:
+                       intcfg = "falling";
+                       break;
+
+               case CV_TRIG_EDGE_RISING:
+                       intcfg = "rising";
+                       break;
+
+               case CV_TRIG_EDGE_BOTH:
+                       intcfg = "both";
+                       break;
+
+               case CV_TRIG_LEVEL:
+                       if (ctrl1 & CV_INV_RX_DATA)
+                               intcfg = "low";
+                       else
+                               intcfg = "high";
+                       break;
+
+               default:
+                       intcfg = "unknown";
+                       break;
+               }
+
+               switch ((ctrl0 & CV_GPIO_CFG_MASK) >> 8) {
+               case 0:
+                       dir = "in out";
+                       break;
+               case 1:
+                       dir = "   out";
+                       break;
+               case 2:
+                       dir = "in";
+                       break;
+               case 3:
+                       dir = "HiZ";
+                       break;
+               default:
+                       dir = "unknown";
+                       break;
+               }
+
+               if (ctrl0 & CV_GPIO_TX_EN)
+                       value = ctrl0 & CV_GPIO_TX_STAT ? "high" : "low";
+               else
+                       value = ctrl0 & CV_GPIO_RX_STAT ? "high" : "low";
+
+               seq_printf(s,
+                          "%c%-4s %-17s %-8s %-4s 0x%03x %d %-8s %02d 0x%08x 
0x%08x",
+                          chv_gpio_pad_locked(cg, i) ? '*' : ' ',
+                          pin, cg->bank->pads[i], dir, value, offs,
+                          (ctrl0 & CV_PAD_MODE_MASK) >> 16, intcfg,
+                          (ctrl0 & CV_INT_SEL_MASK) >> 28, ctrl0, ctrl1);
+
+               label = gpiochip_is_requested(&cg->chip, i);
+               if (label)
+                       seq_printf(s, " %s\n", label);
+               else
+                       seq_puts(s, "\n");
+       }
+
+       reg = chv_gpio_reg(cg, 0, CV_INT_STAT_REG);
+       seq_printf(s, "CV_INT_STAT_REG: 0x%08x\n", readl(reg));
+
+       reg = chv_gpio_reg(cg, 0, CV_INT_MASK_REG);
+       seq_printf(s, "CV_INT_MASK_REG: 0x%08x\n", readl(reg));
+
+       for (i = 0; i < ARRAY_SIZE(cg->intr_lines); i++) {
+               if (cg->intr_lines[i] >= 0)
+                       seq_printf(s, "intline: %d, offset: %d\n", i,
+                                  cg->intr_lines[i]);
+       }
+
+       seq_puts(s, "\n");
+
+       spin_unlock_irqrestore(&cg->lock, flags);
+}
+
+static void chv_irq_unmask(struct irq_data *d)
+{
+       struct chv_gpio *cg = irq_data_get_irq_chip_data(d);
+       u32 offset = irqd_to_hwirq(d);
+       u32 value, intr_line;
+       unsigned long flags;
+       void __iomem *reg;
+
+       spin_lock_irqsave(&cg->lock, flags);
+
+       reg = chv_gpio_reg(cg, offset, CV_PADCTRL0_REG);
+       intr_line = (readl(reg) & CV_INT_SEL_MASK) >> 28;
+
+       reg = chv_gpio_reg(cg, 0, CV_INT_MASK_REG);
+       value = readl(reg);
+       value |= (1 << intr_line);
+       chv_writel(value, reg);
+
+       spin_unlock_irqrestore(&cg->lock, flags);
+}
+
+static void chv_irq_mask(struct irq_data *d)
+{
+       struct chv_gpio *cg = irq_data_get_irq_chip_data(d);
+       u32 offset = irqd_to_hwirq(d);
+       u32 value, intr_line;
+       unsigned long flags;
+       void __iomem *reg;
+
+       spin_lock_irqsave(&cg->lock, flags);
+
+       reg = chv_gpio_reg(cg, offset, CV_PADCTRL0_REG);
+       intr_line = (readl(reg) & CV_INT_SEL_MASK) >> 28;
+
+       value = readl(reg);
+       value &= ~(1 << intr_line);
+       chv_writel(value, reg);
+
+       spin_unlock_irqrestore(&cg->lock, flags);
+}
+
+static void chv_irq_ack(struct irq_data *d)
+{
+}
+
+static void chv_irq_shutdown(struct irq_data *d)
+{
+       struct chv_gpio *cg = irq_data_get_irq_chip_data(d);
+       u32 offset = irqd_to_hwirq(d);
+       void __iomem *reg;
+       unsigned long flags;
+
+       reg = chv_gpio_reg(cg, offset, CV_PADCTRL1_REG);
+
+       chv_irq_mask(d);
+
+       if (!chv_gpio_pad_locked(cg, offset)) {
+               spin_lock_irqsave(&cg->lock, flags);
+               chv_update_irq_type(cg, IRQ_TYPE_NONE, reg);
+               spin_unlock_irqrestore(&cg->lock, flags);
+       }
+}
+
+static struct irq_chip chv_irqchip = {
+       .name = "CHV-GPIO",
+       .irq_mask = chv_irq_mask,
+       .irq_unmask = chv_irq_unmask,
+       .irq_set_type = chv_irq_type,
+       .irq_ack = chv_irq_ack,
+       .irq_shutdown = chv_irq_shutdown,
+       .flags = IRQCHIP_SKIP_SET_WAKE,
+};
+
+static void chv_gpio_irq_handler(unsigned irq, struct irq_desc *desc)
+{
+       struct irq_data *data = irq_desc_get_irq_data(desc);
+       struct chv_gpio *cg = to_chv_gpio(irq_desc_get_handler_data(desc));
+       struct irq_chip *chip = irq_data_get_irq_chip(data);
+       u32 intr_line, mask, offset;
+       void __iomem *reg, *mask_reg;
+       u32 pending;
+
+       /* each GPIO controller has one INT_STAT reg */
+       reg = chv_gpio_reg(cg, 0, CV_INT_STAT_REG);
+       mask_reg = chv_gpio_reg(cg, 0, CV_INT_MASK_REG);
+       while ((pending = (readl(reg) & readl(mask_reg) & 0xffff))) {
+               unsigned irq;
+
+               intr_line = __ffs(pending);
+               mask = BIT(intr_line);
+               chv_writel(mask, reg);
+               offset = cg->intr_lines[intr_line];
+               if (unlikely(offset < 0)) {
+                       dev_warn(cg->chip.dev, "unregistered shared irq\n");
+                       continue;
+               }
+
+               irq = irq_find_mapping(cg->chip.irqdomain, offset);
+               generic_handle_irq(irq);
+       }
+
+       chip->irq_eoi(data);
+}
+
+static void chv_gpio_irq_init_hw(struct chv_gpio *cg)
+{
+       void __iomem *reg;
+
+       /* Mask all interrupts */
+       reg = chv_gpio_reg(cg, 0, CV_INT_MASK_REG);
+       chv_writel(0, reg);
+
+       reg = chv_gpio_reg(cg, 0, CV_INT_STAT_REG);
+       chv_writel(0xffff, reg);
+}
+
+static const struct gpio_chip chv_gpio_chip = {
+       .owner = THIS_MODULE,
+       .request = chv_gpio_request,
+       .free = chv_gpio_free,
+       .direction_input = chv_gpio_direction_input,
+       .direction_output = chv_gpio_direction_output,
+       .get = chv_gpio_get,
+       .set = chv_gpio_set,
+       .dbg_show = chv_gpio_dbg_show,
+       .base = -1,
+};
+
+static int chv_gpio_probe(struct platform_device *pdev)
+{
+       struct resource *mem_rc, *irq_rc;
+       const struct chv_gpio_bank *bank;
+       struct acpi_device *adev;
+       struct chv_gpio *cg;
+       int i, ret = 0;
+
+       adev = ACPI_COMPANION(&pdev->dev);
+       if (!adev)
+               return -ENODEV;
+
+       cg = devm_kzalloc(&pdev->dev, sizeof(*cg), GFP_KERNEL);
+       if (!cg)
+               return -ENOMEM;
+
+       for (i = 0; i < ARRAY_SIZE(chv_banks); i++) {
+               bank = &chv_banks[i];
+               if (!strcmp(adev->pnp.unique_id, bank->uid)) {
+                       cg->bank = bank;
+                       break;
+               }
+       }
+       if (i == ARRAY_SIZE(chv_banks))
+               return -ENODEV;
+
+       mem_rc = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+       cg->reg_base = devm_ioremap_resource(&pdev->dev, mem_rc);
+       if (IS_ERR(cg->reg_base))
+               return PTR_ERR(cg->reg_base);
+
+       spin_lock_init(&cg->lock);
+       cg->chip = chv_gpio_chip;
+       cg->chip.ngpio = cg->bank->npads;
+       cg->chip.label = dev_name(&pdev->dev);
+       cg->chip.dev = &pdev->dev;
+
+       /* Initialize interrupt lines array with negative value */
+       for (i = 0; i < ARRAY_SIZE(cg->intr_lines); i++)
+               cg->intr_lines[i] = -1;
+
+       ret = gpiochip_add(&cg->chip);
+       if (ret) {
+               dev_err(&pdev->dev, "Failed adding GPIO chip\n");
+               return ret;
+       }
+
+       irq_rc = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+       if (irq_rc && irq_rc->start) {
+               chv_gpio_irq_init_hw(cg);
+
+               ret = gpiochip_irqchip_add(&cg->chip, &chv_irqchip, 0,
+                                          handle_simple_irq, IRQ_TYPE_NONE);
+               if (ret) {
+                       dev_err(&pdev->dev, "Failed to add irqchip\n");
+                       gpiochip_remove(&cg->chip);
+                       return ret;
+               }
+
+               gpiochip_set_chained_irqchip(&cg->chip, &chv_irqchip,
+                                            (unsigned)irq_rc->start,
+                                            chv_gpio_irq_handler);
+       }
+
+       return 0;
+}
+
+static int chv_gpio_remove(struct platform_device *pdev)
+{
+       struct chv_gpio *cg = platform_get_drvdata(pdev);
+
+       gpiochip_remove(&cg->chip);
+       return 0;
+}
+
+static const struct acpi_device_id chv_gpio_acpi_match[] = {
+       { "INT33FF" },
+       { }
+};
+MODULE_DEVICE_TABLE(acpi, chv_gpio_acpi_match);
+
+static struct platform_driver chv_gpio_driver = {
+       .probe = chv_gpio_probe,
+       .remove = chv_gpio_remove,
+       .driver = {
+               .name = "chv_gpio",
+               .owner = THIS_MODULE,
+               .acpi_match_table = ACPI_PTR(chv_gpio_acpi_match),
+       },
+};
+
+static int __init chv_gpio_init(void)
+{
+       return platform_driver_register(&chv_gpio_driver);
+}
+subsys_initcall(chv_gpio_init);
+
+static void __exit chv_gpio_exit(void)
+{
+       platform_driver_unregister(&chv_gpio_driver);
+}
+module_exit(chv_gpio_exit);
+
+MODULE_DESCRIPTION("GPIO driver for Intel Cherryview/Braswell");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Ning Li <ning...@intel.com>");
+MODULE_AUTHOR("Alan Cox <a...@linux.intel.com>");
+MODULE_AUTHOR("Mika Westerberg <mika.westerb...@linux.intel.com>");
+MODULE_ALIAS("platform:chv_gpio");
-- 
2.1.0

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to