[PATCH 2/2] gpio: Add support for Intel Cherryview/Braswell GPIO controller

From: Mika Westerberg
Date: Mon Sep 08 2014 - 07:48:12 EST


From: Ning Li <ning.li@xxxxxxxxx>

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

Signed-off-by: Ning Li <ning.li@xxxxxxxxx>
Signed-off-by: Alan Cox <alan@xxxxxxxxxxxxxxx>
Signed-off-by: Mika Westerberg <mika.westerberg@xxxxxxxxxxxxxxx>
---
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.li@xxxxxxxxx>");
+MODULE_AUTHOR("Alan Cox <alan@xxxxxxxxxxxxxxx>");
+MODULE_AUTHOR("Mika Westerberg <mika.westerberg@xxxxxxxxxxxxxxx>");
+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 majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/