[PATCH 1/2] gpio: Add CNS3xxx mmio gpio support

Tommy Lin tommy.lin.1101 at gmail.com
Wed Aug 10 16:12:47 EDT 2011


Add CNS3XXX generic memory mapped GPIO controller support. Also
implement cascaded GPIO interrupts for all 64 GPIOs.

Signed-off-by: Tommy Lin <tommy.lin.1101 at gmail.com>
---
 arch/arm/Kconfig                             |    3 +
 arch/arm/configs/cns3420vb_defconfig         |    1 +
 arch/arm/mach-cns3xxx/cns3420vb.c            |  120 ++++++
 arch/arm/mach-cns3xxx/core.c                 |   10 -
 arch/arm/mach-cns3xxx/include/mach/cns3xxx.h |    8 +-
 arch/arm/mach-cns3xxx/include/mach/gpio.h    |   64 ++++
 drivers/gpio/Kconfig                         |    6 +
 drivers/gpio/Makefile                        |    1 +
 drivers/gpio/gpio-cns3xxx.c                  |  516 ++++++++++++++++++++++++++
 9 files changed, 717 insertions(+), 12 deletions(-)
 create mode 100644 arch/arm/mach-cns3xxx/include/mach/gpio.h
 create mode 100644 drivers/gpio/gpio-cns3xxx.c

diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index 2c71a8f..32705e5 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -333,6 +333,9 @@ config ARCH_CNS3XXX
 	select ARM_GIC
 	select MIGHT_HAVE_PCI
 	select PCI_DOMAINS if PCI
+	select ARCH_REQUIRE_GPIOLIB
+	select GPIO_GENERIC_PLATFORM
+	select GENERIC_IRQ_CHIP
 	help
 	  Support for Cavium Networks CNS3XXX platform.
 
diff --git a/arch/arm/configs/cns3420vb_defconfig b/arch/arm/configs/cns3420vb_defconfig
index 313627a..c83d14c 100644
--- a/arch/arm/configs/cns3420vb_defconfig
+++ b/arch/arm/configs/cns3420vb_defconfig
@@ -52,6 +52,7 @@ CONFIG_SERIAL_8250_CONSOLE=y
 CONFIG_LEGACY_PTY_COUNT=16
 # CONFIG_HW_RANDOM is not set
 # CONFIG_HWMON is not set
+CONFIG_GPIO_SYSFS=y
 # CONFIG_VGA_CONSOLE is not set
 # CONFIG_HID_SUPPORT is not set
 # CONFIG_USB_SUPPORT is not set
diff --git a/arch/arm/mach-cns3xxx/cns3420vb.c b/arch/arm/mach-cns3xxx/cns3420vb.c
index 3e7d149..b414564 100644
--- a/arch/arm/mach-cns3xxx/cns3420vb.c
+++ b/arch/arm/mach-cns3xxx/cns3420vb.c
@@ -24,6 +24,7 @@
 #include <linux/mtd/mtd.h>
 #include <linux/mtd/physmap.h>
 #include <linux/mtd/partitions.h>
+#include <linux/basic_mmio_gpio.h>
 #include <asm/setup.h>
 #include <asm/mach-types.h>
 #include <asm/mach/arch.h>
@@ -158,10 +159,129 @@ static struct platform_device cns3xxx_usb_ohci_device = {
 	},
 };
 
+/* GPIO */
+static struct resource cns3xxx_gpio_resources[] = {
+	[0] = {
+		.start = IRQ_CNS3XXX_GPIOA,
+		.flags = IORESOURCE_IRQ,
+		.name  = "GPIOA"
+	},
+	[1] = {
+		.start = CNS3XXX_GPIOA_BASE + GPIO_INTR_ENABLE_OFFSET,
+		.end   = CNS3XXX_GPIOA_BASE + GPIO_BOUNCE_PRESCALE_OFFSET + 3,
+		.flags = IORESOURCE_MEM,
+		.name  = "GPIOA",
+	},
+	[2] = {
+		.start = IRQ_CNS3XXX_GPIOB,
+		.flags = IORESOURCE_IRQ,
+		.name  = "GPIOB"
+	},
+	[3] = {
+		.start = CNS3XXX_GPIOB_BASE + GPIO_INTR_ENABLE_OFFSET,
+		.end   = CNS3XXX_GPIOB_BASE + GPIO_BOUNCE_PRESCALE_OFFSET + 3,
+		.flags = IORESOURCE_MEM,
+		.name  = "GPIOB",
+	},
+	[4] = {
+		.start = CNS3XXX_MISC_BASE,
+		.end   = CNS3XXX_MISC_BASE + 0x800,
+		.flags = IORESOURCE_MEM,
+		.name  = "MISC"
+	},
+};
+
+static struct platform_device cns3xxx_gpio_device = {
+	.name		= "cns3xxx-gpio",
+	.id		= -1,
+	.num_resources	= ARRAY_SIZE(cns3xxx_gpio_resources),
+	.resource	= cns3xxx_gpio_resources,
+};
+
+/* Basic memory-mapped GPIO */
+static struct resource cns3xxx_mmgpio_resources_a[] = {
+	[0] = {
+		.start = CNS3XXX_GPIOA_BASE + GPIO_INPUT_OFFSET,
+		.end   = CNS3XXX_GPIOA_BASE + GPIO_INPUT_OFFSET + 3,
+		.flags = IORESOURCE_MEM,
+		.name  = "dat",
+	},
+	[1] = {
+		.start = CNS3XXX_GPIOA_BASE + GPIO_BIT_SET_OFFSET,
+		.end   = CNS3XXX_GPIOA_BASE + GPIO_BIT_SET_OFFSET + 3,
+		.flags = IORESOURCE_MEM,
+		.name  = "set",
+	},
+	[2] = {
+		.start = CNS3XXX_GPIOA_BASE + GPIO_BIT_CLEAR_OFFSET,
+		.end   = CNS3XXX_GPIOA_BASE + GPIO_BIT_CLEAR_OFFSET + 3,
+		.flags = IORESOURCE_MEM,
+		.name  = "clr",
+	},
+	[3] = {
+		.start = CNS3XXX_GPIOA_BASE + GPIO_DIR_OFFSET,
+		.end   = CNS3XXX_GPIOA_BASE + GPIO_DIR_OFFSET + 3,
+		.flags = IORESOURCE_MEM,
+		.name  = "dirout",
+	},
+};
+
+static struct platform_device cns3xxx_mmgpio_device_a = {
+	.name		= "basic-mmio-gpio",
+	.id		= 0,
+	.num_resources	= ARRAY_SIZE(cns3xxx_mmgpio_resources_a),
+	.resource	= cns3xxx_mmgpio_resources_a,
+	.dev.platform_data = &(struct bgpio_pdata) {
+		.base	= 0,
+		.ngpio	= MAX_GPIOA_NO,
+	},
+};
+
+static struct resource cns3xxx_mmgpio_resources_b[] = {
+	[0] = {
+		.start = CNS3XXX_GPIOB_BASE + GPIO_INPUT_OFFSET,
+		.end   = CNS3XXX_GPIOB_BASE + GPIO_INPUT_OFFSET + 3,
+		.flags = IORESOURCE_MEM,
+		.name  = "dat",
+	},
+	[1] = {
+		.start = CNS3XXX_GPIOB_BASE + GPIO_BIT_SET_OFFSET,
+		.end   = CNS3XXX_GPIOB_BASE + GPIO_BIT_SET_OFFSET + 3,
+		.flags = IORESOURCE_MEM,
+		.name  = "set",
+	},
+	[2] = {
+		.start = CNS3XXX_GPIOB_BASE + GPIO_BIT_CLEAR_OFFSET,
+		.end   = CNS3XXX_GPIOB_BASE + GPIO_BIT_CLEAR_OFFSET + 3,
+		.flags = IORESOURCE_MEM,
+		.name  = "clr",
+	},
+	[3] = {
+		.start = CNS3XXX_GPIOB_BASE + GPIO_DIR_OFFSET,
+		.end   = CNS3XXX_GPIOB_BASE + GPIO_DIR_OFFSET + 3,
+		.flags = IORESOURCE_MEM,
+		.name  = "dirout",
+	},
+};
+
+static struct platform_device cns3xxx_mmgpio_device_b = {
+	.name		= "basic-mmio-gpio",
+	.id		= 1,
+	.num_resources	= ARRAY_SIZE(cns3xxx_mmgpio_resources_b),
+	.resource	= cns3xxx_mmgpio_resources_b,
+	.dev.platform_data = &(struct bgpio_pdata) {
+		.base	= MAX_GPIOA_NO,
+		.ngpio	= MAX_GPIOB_NO,
+	},
+};
+
 /*
  * Initialization
  */
 static struct platform_device *cns3420_pdevs[] __initdata = {
+	&cns3xxx_gpio_device,
+	&cns3xxx_mmgpio_device_a,
+	&cns3xxx_mmgpio_device_b,
 	&cns3420_nor_pdev,
 	&cns3xxx_usb_ehci_device,
 	&cns3xxx_usb_ohci_device,
diff --git a/arch/arm/mach-cns3xxx/core.c b/arch/arm/mach-cns3xxx/core.c
index 941a308..59ed13f 100644
--- a/arch/arm/mach-cns3xxx/core.c
+++ b/arch/arm/mach-cns3xxx/core.c
@@ -42,16 +42,6 @@ static struct map_desc cns3xxx_io_desc[] __initdata = {
 		.length		= SZ_4K,
 		.type		= MT_DEVICE,
 	}, {
-		.virtual	= CNS3XXX_GPIOA_BASE_VIRT,
-		.pfn		= __phys_to_pfn(CNS3XXX_GPIOA_BASE),
-		.length		= SZ_4K,
-		.type		= MT_DEVICE,
-	}, {
-		.virtual	= CNS3XXX_GPIOB_BASE_VIRT,
-		.pfn		= __phys_to_pfn(CNS3XXX_GPIOB_BASE),
-		.length		= SZ_4K,
-		.type		= MT_DEVICE,
-	}, {
 		.virtual	= CNS3XXX_MISC_BASE_VIRT,
 		.pfn		= __phys_to_pfn(CNS3XXX_MISC_BASE),
 		.length		= SZ_4K,
diff --git a/arch/arm/mach-cns3xxx/include/mach/cns3xxx.h b/arch/arm/mach-cns3xxx/include/mach/cns3xxx.h
index 191c8e5..6559d24 100644
--- a/arch/arm/mach-cns3xxx/include/mach/cns3xxx.h
+++ b/arch/arm/mach-cns3xxx/include/mach/cns3xxx.h
@@ -624,9 +624,13 @@ int cns3xxx_cpu_clock(void);
 
 #define NR_IRQS_CNS3XXX			(IRQ_TC11MP_GIC_START + 64)
 
-#if !defined(NR_IRQS) || (NR_IRQS < NR_IRQS_CNS3XXX)
+#define MAX_GPIOA_NO			32
+#define MAX_GPIOB_NO			32
+#define MAX_GPIO_NO			(MAX_GPIOA_NO + MAX_GPIOB_NO)
+
+#if !defined(NR_IRQS) || (NR_IRQS < (NR_IRQS_CNS3XXX + MAX_GPIO_NO))
 #undef NR_IRQS
-#define NR_IRQS				NR_IRQS_CNS3XXX
+#define NR_IRQS				(NR_IRQS_CNS3XXX + MAX_GPIO_NO)
 #endif
 
 #endif	/* __MACH_BOARD_CNS3XXX_H */
diff --git a/arch/arm/mach-cns3xxx/include/mach/gpio.h b/arch/arm/mach-cns3xxx/include/mach/gpio.h
new file mode 100644
index 0000000..7b68acf
--- /dev/null
+++ b/arch/arm/mach-cns3xxx/include/mach/gpio.h
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ *
+ *  Copyright (c) 2011 Cavium
+ *
+ *  This file 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 file is distributed in the hope that it will be useful,
+ *  but AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or
+ *  NONINFRINGEMENT.  See the GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this file; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA or
+ *  visit http://www.gnu.org/licenses/.
+ *
+ *  This file may also be available under a different license from Cavium.
+ *  Contact Cavium for more information
+ *
+ ******************************************************************************/
+
+#ifndef	_CNS3XXX_GPIO_H_
+#define	_CNS3XXX_GPIO_H_
+
+#include <mach/cns3xxx.h>
+
+#define ARCH_NR_GPIOS				MAX_GPIO_NO
+
+#include <asm-generic/gpio.h>
+
+#define GPIO_OUTPUT_OFFSET			0x00
+#define GPIO_INPUT_OFFSET			0x04
+#define GPIO_DIR_OFFSET				0x08
+#define GPIO_BIT_SET_OFFSET			0x10
+#define GPIO_BIT_CLEAR_OFFSET			0x14
+#define GPIO_INTR_ENABLE_OFFSET			0x20
+#define GPIO_INTR_RAW_STATUS_OFFSET		0x24
+#define GPIO_INTR_MASKED_STATUS_OFFSET		0x28
+#define GPIO_INTR_MASK_OFFSET			0x2C
+#define GPIO_INTR_CLEAR_OFFSET			0x30
+#define GPIO_INTR_TRIGGER_METHOD_OFFSET		0x34
+#define GPIO_INTR_TRIGGER_BOTH_EDGES_OFFSET	0x38
+#define GPIO_INTR_TRIGGER_POL_OFFSET		0x3C
+#define GPIO_BOUNCE_ENABLE_OFFSET		0x40
+#define GPIO_BOUNCE_PRESCALE_OFFSET		0x44
+
+
+#define gpio_get_value			__gpio_get_value
+#define gpio_set_value			__gpio_set_value
+#define gpio_cansleep			__gpio_cansleep
+#define gpio_to_irq			cns3xxx_gpio_to_irq
+
+#define GPIOA(n)			n
+#define GPIOB(n)			(MAX_GPIOA_NO + n)
+
+/* Function prototype */
+int  cns3xxx_sharepin_request(unsigned gpio, const char *label);
+void cns3xxx_sharepin_free(unsigned gpio);
+inline int cns3xxx_gpio_to_irq(unsigned gpio);
+
+#endif
+
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index d539efd..368cd67 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -90,6 +90,12 @@ config GPIO_IT8761E
 	help
 	  Say yes here to support GPIO functionality of IT8761E super I/O chip.
 
+config GPIO_CNS3XXX
+	bool "CNS3XXX GPIO"
+	depends on ARCH_CNS3XXX
+	help
+	  Say yes here to support the Cavium CNS3XXX GPIO interrupts.
+
 config GPIO_EP93XX
 	def_bool y
 	depends on ARCH_EP93XX
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 9588948..7f472ec 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -12,6 +12,7 @@ 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_CNS3XXX)	+= gpio-cns3xxx.o
 obj-$(CONFIG_GPIO_CS5535)	+= gpio-cs5535.o
 obj-$(CONFIG_GPIO_DA9052)	+= gpio-da9052.o
 obj-$(CONFIG_GPIO_EP93XX)	+= gpio-ep93xx.o
diff --git a/drivers/gpio/gpio-cns3xxx.c b/drivers/gpio/gpio-cns3xxx.c
new file mode 100644
index 0000000..f60d8dd
--- /dev/null
+++ b/drivers/gpio/gpio-cns3xxx.c
@@ -0,0 +1,516 @@
+/*******************************************************************************
+ *
+ *  drivers/gpio/gpio-cns3xxx.c
+ *
+ *  GPIO driver for the CNS3XXX SOCs
+ *
+ *  Copyright (c) 2011 Cavium
+ *
+ *  This file 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 file is distributed in the hope that it will be useful,
+ *  but AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or
+ *  NONINFRINGEMENT.  See the GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this file; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA or
+ *  visit http://www.gnu.org/licenses/.
+ *
+ *  This file may also be available under a different license from Cavium.
+ *  Contact Cavium for more information
+ *
+ ******************************************************************************/
+
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+
+#include <asm/mach/irq.h>
+
+
+struct chog_gpio_chip {
+	char				*name;
+	int				base;
+	u16				ngpio;
+	int				irq;
+	struct irq_chip_generic		*gc;
+	void __iomem			*reg_base;
+	void __iomem			*reg_gpio_dis;
+};
+
+/* GPIO information that matches basic-mmio-gpio declaration in
+ * arch/arm/mach-cns3xxx/cns3420vb.c
+ */
+static struct chog_gpio_chip gc_info[] = {
+	/*name		base		ngpio*/
+	{"GPIOA",	0x00,		MAX_GPIOA_NO},
+	{"GPIOB",	MAX_GPIOA_NO,	MAX_GPIOB_NO},
+};
+#define nr_banks			ARRAY_SIZE(gc_info)
+
+/* The CNS3XXX GPIO pins are shard with special functions which is described in
+ * the following table. "none" in this table represent the corresponding pins
+ * are dedicate GPIO.
+ */
+static char *sharepin_desc[] = {
+	/* GPIOA group */
+/*  0 */ "none",	"none",		"SD_PWR_ON",	"OTG_DRV_VBUS",
+/*  4 */ "Don't use",	"none",		"none",		"none",
+/*  8 */ "CIM_nOE",	"LCD_Power",	"SMI_nCS3",	"SMI_nCS2",
+/* 12 */ "SMI_Clk",	"SMI_nADV",	"SMI_CRE",	"SMI_Addr[26]",
+/* 16 */ "SD_nCD",	"SD_nWP",	"SD_CLK",	"SD_CMD",
+/* 20 */ "SD_DT[7]",	"SD_DT[6]",	"SD_DT[5]",	"SD_DT[4]",
+/* 24 */ "SD_DT[3]",	"SD_DT[2]",	"SD_DT[1]",	"SD_DT[0]",
+/* 28 */ "SD_LED",	"UR_RXD1",	"UR_TXD1",	"UR_RTS2",
+	/* GPIOB group */
+/*  0 */ "UR_CTS2",	"UR_RXD2",	"UR_TXD2",	"PCMCLK",
+/*  4 */ "PCMFS",	"PCMDT",	"PCMDR",	"SPInCS1",
+/*  8 */ "SPInCS2",	"SPICLK",	"SPIDT",	"SPIDR",
+/* 12 */ "SCL",		"SDA",		"GMII2_CRS",	"GMII2_COL",
+/* 16 */ "RGMII1_CRS",	"RGMII1_COL",	"RGMII0_CRS",	"RGMII0_COL",
+/* 20 */ "MDC",		"MDIO",		"I2SCLK",	"I2SFS",
+/* 24 */ "I2SDT",	"I2SDR",	"ClkOut",	"Ext_Intr2",
+/* 28 */ "Ext_Intr1",	"Ext_Intr0",	"SATA_LED1",	"SATA_LED0",
+};
+
+struct cns3xxx_regs {
+	char		*name;
+	void __iomem	*addr;
+	u32		offset;
+};
+
+/* gc_info[x].gc->regs offsets are count from GPIO_INTR_ENABLE_OFFSET. */
+#define get_offset(n)			(n - GPIO_INTR_ENABLE_OFFSET)
+static struct cns3xxx_regs gpio_regs[] =  {
+	{"Interrupt Enable",		0, GPIO_INTR_ENABLE_OFFSET},
+	{"Interrupt Raw Status",	0, GPIO_INTR_RAW_STATUS_OFFSET},
+	{"Interrupt Masked Status",	0, GPIO_INTR_MASKED_STATUS_OFFSET},
+	{"Interrupt Level Trigger",	0, GPIO_INTR_TRIGGER_METHOD_OFFSET},
+	{"Interrupt Both Edge",		0, GPIO_INTR_TRIGGER_BOTH_EDGES_OFFSET},
+	{"Interrupt Falling Edge",	0, GPIO_INTR_TRIGGER_POL_OFFSET},
+	{"Interrupt MASKED",		0, GPIO_INTR_MASK_OFFSET},
+	{"GPIO Bounce Enable",		0, GPIO_BOUNCE_ENABLE_OFFSET},
+	{"GPIO Bounce Prescale",	0, GPIO_BOUNCE_PRESCALE_OFFSET},
+};
+
+static struct cns3xxx_regs misc_regs[] =  {
+	{"Drive Strength Register A",	MISC_IO_PAD_DRIVE_STRENGTH_CTRL_A},
+	{"Drive Strength Register B",	MISC_IO_PAD_DRIVE_STRENGTH_CTRL_B},
+	{"Pull Up/Down Ctrl A[15:0]",	MISC_GPIOA_15_0_PULL_CTRL_REG},
+	{"Pull Up/Down Ctrl A[31:16]",	MISC_GPIOA_16_31_PULL_CTRL_REG},
+	{"Pull Up/Down Ctrl B[15:0]",	MISC_GPIOB_15_0_PULL_CTRL_REG},
+	{"Pull Up/Down Ctrl B[31:16]",	MISC_GPIOB_16_31_PULL_CTRL_REG},
+};
+
+static DEFINE_SPINLOCK(gpio_lock);
+
+/*
+ * Turn on corresponding shared pin function.
+ * Turn on shared pin function will also disable GPIO function. Related GPIO
+ * control registers are still accessable but not reflect to pin.
+ */
+int cns3xxx_sharepin_request(unsigned gpio, const char *label)
+{
+	int i, val, ret, offset = gpio;
+
+	if (!label)
+		label = sharepin_desc[gpio];
+
+	ret = gpio_request(gpio, label);
+	if (ret) {
+		printk(KERN_INFO "gpio-%d already in use! Err=%d\n", gpio, ret);
+		return ret;
+	}
+
+	for (i = 0; i < nr_banks; i++) {
+		if (offset >= gc_info[i].ngpio) {
+			offset -= gc_info[i].ngpio;
+			continue;
+		}
+		spin_lock(&gpio_lock);
+		val = readl(gc_info[i].reg_gpio_dis);
+		val |= (1 << offset);
+		writel(val, gc_info[i].reg_gpio_dis);
+		spin_unlock(&gpio_lock);
+		printk(KERN_INFO "%s[%d] is used by %s function!\n",
+				gc_info[i].name, offset, sharepin_desc[gpio]);
+		break;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(cns3xxx_sharepin_request);
+
+/*
+ * Turn off corresponding share pin function.
+ */
+void cns3xxx_sharepin_free(unsigned gpio)
+{
+	int i, val, offset = gpio;
+
+	gpio_free(gpio);
+
+	for (i = 0; i < nr_banks; i++) {
+		if (offset >= gc_info[i].ngpio) {
+			offset -= gc_info[i].ngpio;
+			continue;
+		}
+		spin_lock(&gpio_lock);
+		val = readl(gc_info[i].reg_gpio_dis);
+		val &= ~(1 << offset);
+		writel(val, gc_info[i].reg_gpio_dis);
+		spin_unlock(&gpio_lock);
+		printk(KERN_INFO "%s[%d] share pin function (%s) disabled!\n",
+				gc_info[i].name, offset, sharepin_desc[gpio]);
+		break;
+	}
+}
+EXPORT_SYMBOL(cns3xxx_sharepin_free);
+
+inline int cns3xxx_gpio_to_irq(unsigned gpio)
+{
+	return NR_IRQS_CNS3XXX + gpio;
+}
+EXPORT_SYMBOL(gpio_to_irq);
+
+#ifdef CONFIG_DEBUG_FS
+
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+
+static int cns3xxx_dbg_gpio_show_reg(struct seq_file *s, void *unused)
+{
+	int i, offset;
+	seq_printf(s,	"Register Description        GPIOA     GPIOB\n"
+			"====================        =====     =====\n");
+	seq_printf(s,	"%-26.26s: %08x  %08x\n", "GPIO Disable",
+			readl(gc_info[0].reg_gpio_dis),
+			readl(gc_info[1].reg_gpio_dis));
+	for (i = 0; i < ARRAY_SIZE(gpio_regs); i++) {
+		offset = get_offset(gpio_regs[i].offset);
+		seq_printf(s, "%-26.26s: %08x  %08x\n",
+			gpio_regs[i].name,
+			readl(gc_info[0].reg_base + offset),
+			readl(gc_info[1].reg_base + offset));
+	}
+
+	seq_printf(s,	"\n"
+			"Register Description        Value\n"
+			"====================        =====\n");
+	for (i = 0; i < ARRAY_SIZE(misc_regs); i++) {
+		seq_printf(s, "%-26.26s: %08x\n",
+			misc_regs[i].name, readl(misc_regs[i].addr));
+	}
+	return 0;
+}
+
+static int dbg_gpio_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, cns3xxx_dbg_gpio_show_reg, &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 cns3xxx_gpio_debuginit(void)
+{
+	debugfs_create_file("gpio-regs", S_IRUGO, NULL, NULL, &debug_fops);
+	return 0;
+}
+late_initcall(cns3xxx_gpio_debuginit);
+
+#endif /* CONFIG_DEBUG_FS */
+
+/*
+ * GPIO interrtups are remapped to unused irq number.
+ * The remapped GPIO IRQ number start from NR_IRQS_CNS3XXX (96). Here is the
+ * table of GPIO to irq mapping table.
+ *
+ *	GPIOA	GPIOB	|	GPIOA	GPIOB
+ * No.	IRQ	IRQ	|  No.	IRQ	IRQ
+ *  0	 96	128	|  16	112	144
+ *  1	 97	129	|  17	113	145
+ *  2	 98	130	|  18	114	146
+ *  3	 99	131	|  19	115	147
+ *  4	100	132	|  20	116	148
+ *  5	101	133	|  21	117	149
+ *  6	102	134	|  22	118	150
+ *  7	103	135	|  23	119	151
+ *  8	104	136	|  24	120	152
+ *  9	105	137	|  25	121	153
+ * 10	106	138	|  26	122	154
+ * 11	107	139	|  27	123	155
+ * 12	108	140	|  28	124	156
+ * 13	109	141	|  29	125	157
+ * 14	110	142	|  30	126	158
+ * 15	111	143	|  31	127	159
+ */
+
+static inline struct irq_chip_regs *cur_regs(struct irq_data *d)
+{
+	return &container_of(d->chip, struct irq_chip_type, chip)->regs;
+}
+
+/*
+ * Set trigger type
+ *
+ * Trigger setting for corresponding bits.
+ *
+ *				Level	Both	Poln
+ * IRQ_TYPE_EDGE_RISING		0	0	0
+ * IRQ_TYPE_EDGE_FALLING	0	0	1
+ * IRQ_TYPE_EDGE_BOTH		0	1	X
+ * IRQ_TYPE_LEVEL_HIGH		1	X	0
+ * IRQ_TYPE_LEVEL_LOW		1	X	1
+ *
+ * The both edge register offset is not defined in the irq_chip_regs. Here take
+ * the advantage of "type" register offset, which is 4 byte prior to both edge
+ * register.
+ */
+int cns3xxx_gpio_set_irq_type(struct irq_data *d, unsigned int type)
+{
+	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+	u32 mask = 1 << (d->irq - gc->irq_base);
+	u32 reg_lvl, reg_both, reg_poln;
+
+	irq_gc_lock(gc);
+
+	reg_lvl  = irq_reg_readl(gc->reg_base + cur_regs(d)->type);
+	reg_both = irq_reg_readl(gc->reg_base + cur_regs(d)->type + 4);
+	reg_poln = irq_reg_readl(gc->reg_base + cur_regs(d)->polarity);
+
+	switch (type) {
+	case IRQ_TYPE_EDGE_RISING:
+		reg_lvl  &= ~mask;
+		reg_both &= ~mask;
+		reg_poln &= ~mask;
+		break;
+	case IRQ_TYPE_EDGE_FALLING:
+		reg_lvl  &= ~mask;
+		reg_both &= ~mask;
+		reg_poln |= mask;
+		break;
+	case IRQ_TYPE_EDGE_BOTH:
+		reg_lvl  &= ~mask;
+		reg_both |= mask;
+		break;
+	case IRQ_TYPE_LEVEL_HIGH:
+		reg_lvl  |= mask;
+		reg_poln &= ~mask;
+		break;
+	case IRQ_TYPE_LEVEL_LOW:
+		reg_lvl  |= mask;
+		reg_poln |= mask;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	irq_reg_writel(reg_lvl,  gc->reg_base + cur_regs(d)->type);
+	irq_reg_writel(reg_both, gc->reg_base + cur_regs(d)->type + 4);
+	irq_reg_writel(reg_poln, gc->reg_base + cur_regs(d)->polarity);
+
+	irq_gc_unlock(gc);
+	return 0;
+}
+
+static void gpio_irq_handler(unsigned irq, struct irq_desc *desc)
+{
+	struct chog_gpio_chip *cgc = irq_get_handler_data(irq);
+	struct irq_chip *ic = irq_desc_get_chip(desc);
+	unsigned i;
+	int target_irq;
+	u32 status;
+
+	chained_irq_enter(ic, desc);
+
+	status = readl(cgc->reg_base +
+			get_offset(GPIO_INTR_MASKED_STATUS_OFFSET));
+	pr_debug("%s interrupt status=0x%08x\n", __func__, status);
+
+	for (i = 0; i < cgc->ngpio; i++) {
+		if (status & (1 << i)) {
+			target_irq = i + NR_IRQS_CNS3XXX + cgc->base;
+			pr_debug("Invoke cascaded irq %d from irq %d\n",
+					target_irq, cgc->irq);
+			generic_handle_irq(target_irq);
+		}
+	}
+
+	chained_irq_exit(ic, desc);
+}
+
+static void __iomem *gpio_map(struct platform_device *pdev,
+		const char *name, 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);
+	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 gpio_probe(struct platform_device *pdev)
+{
+	int i, nr_gpios = 0, err = 0;
+	struct device *dev = &pdev->dev;
+	struct resource *res;
+	void __iomem *misc_reg;
+	struct irq_chip_type *ct;
+
+	/* TODO Once the clk framework (clk_get() + clk_enable()) is
+	 * implemented. Use clok framework APIs instead of these APIs.
+	 */
+	cns3xxx_pwr_clk_en(0x1 << PM_CLK_GATE_REG_OFFSET_GPIO);
+	cns3xxx_pwr_soft_rst(0x1 << PM_CLK_GATE_REG_OFFSET_GPIO);
+
+
+	misc_reg = gpio_map(pdev, "MISC", &err);
+	if (!misc_reg) {
+		dev_dbg(dev, "%s gpio_map \"MISC\" failure! err=%d\n",
+				__func__, err);
+		return err;
+	}
+
+	/* Scan and match GPIO resources */
+	for (i = 0; i < nr_banks; i++) {
+		/* Fetech GPIO interrupt control register base address */
+		gc_info[i].reg_base = gpio_map(pdev, gc_info[i].name, &err);
+		if (!gc_info[i].reg_base) {
+			dev_dbg(dev, "%s gpio_map %s failure! err=%d\n",
+					__func__, gc_info[i].name, err);
+			goto err1;
+		}
+
+		/* Fetech GPIO interrupt ID number */
+		res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
+				gc_info[i].name);
+		if (!res) {
+			dev_dbg(dev, "%s platform_get_resource_byname (%s)"
+					"failed!\n", gc_info[i].name, __func__);
+			err = -ENODEV;
+			goto err2;
+		}
+		gc_info[i].irq = res->start;
+		gc_info[i].reg_gpio_dis	= misc_reg + 0x14 + i * 4;
+
+		gc_info[i].gc = irq_alloc_generic_chip("GPIO", 1,
+				NR_IRQS_CNS3XXX + gc_info[i].base,
+				gc_info[i].reg_base, handle_level_irq);
+		if (!gc_info[i].gc) {
+			dev_dbg(dev, "%s irq_alloc_generic_chip failed!\n",
+					__func__);
+			err = -ENOMEM;
+			goto err2;
+		}
+		gc_info[i].gc->private = &gc_info[i];
+
+		ct = gc_info[i].gc->chip_types;
+		ct->regs.mask = get_offset(GPIO_INTR_ENABLE_OFFSET);
+		ct->regs.ack = get_offset(GPIO_INTR_CLEAR_OFFSET);
+		ct->regs.type = get_offset(GPIO_INTR_TRIGGER_METHOD_OFFSET);
+		ct->regs.polarity = get_offset(GPIO_INTR_TRIGGER_POL_OFFSET);
+		ct->type = IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW;
+		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 = cns3xxx_gpio_set_irq_type;
+
+		irq_setup_generic_chip(gc_info[i].gc, IRQ_MSK(gc_info[i].ngpio),
+			IRQ_GC_INIT_MASK_CACHE | IRQ_GC_INIT_NESTED_LOCK,
+			IRQ_NOREQUEST, IRQ_LEVEL | IRQ_NOPROBE);
+
+		irq_set_chained_handler(gc_info[i].irq, gpio_irq_handler);
+		err = irq_set_handler_data(gc_info[i].irq, &gc_info[i]);
+		if (err) {
+			dev_dbg(dev, "%s irq_set_handler_data fail!\n",
+					__func__);
+			goto err2;
+		}
+
+		nr_gpios += gc_info[i].ngpio;
+		if (nr_gpios >= MAX_GPIO_NO)
+			break;
+	}
+
+	return 0;
+
+err2:
+	if (gc_info[1].reg_base)
+		devm_iounmap(dev, gc_info[0].reg_base);
+
+err1:
+	if (gc_info[0].reg_base)
+		devm_iounmap(dev, gc_info[0].reg_base);
+
+	devm_iounmap(dev, misc_reg);
+
+	return err;
+}
+
+static int gpio_remove(struct platform_device *pdev)
+{
+	int i;
+
+	for (i = 0; i < nr_banks; i++)
+		irq_remove_generic_chip(gc_info[i].gc,
+				IRQ_MSK(gc_info[i].ngpio),
+				IRQ_NOREQUEST, IRQ_LEVEL | IRQ_NOPROBE);
+
+	return 0;
+}
+
+static struct platform_driver cns3xxx_gpio_driver = {
+	.probe		= gpio_probe,
+	.driver		= {
+		.owner	= THIS_MODULE,
+		.name	= "cns3xxx-gpio",
+	},
+	.remove		= gpio_remove,
+};
+
+int __init cns3xxx_gpio_init(void)
+{
+	return platform_driver_register(&cns3xxx_gpio_driver);
+}
+
+void __exit cns3xxx_gpio_exit(void)
+{
+	platform_driver_unregister(&cns3xxx_gpio_driver);
+}
+
+module_init(cns3xxx_gpio_init);
+module_exit(cns3xxx_gpio_exit);
+
+MODULE_LICENSE("GPL");
+
-- 
1.7.6




More information about the linux-arm-kernel mailing list