Blackfin: bf538: add support for extended GPIO banks

The GPIOs on ports C/D/E on the BF538/BF539 do not behave the same way as
the other ports on the part and the same way as all other Blackfin parts.
The MMRs are programmed slightly different and they cannot be used to
generate interrupts or wakeup a sleeping system.  Since these guys don't
fit into the existing code, create a simple gpiolib driver for them.

Signed-off-by: Michael Hennerich <michael.hennerich@analog.com>
Signed-off-by: Mike Frysinger <vapier@gentoo.org>
diff --git a/arch/blackfin/include/asm/gpio.h b/arch/blackfin/include/asm/gpio.h
index 5b44d05..539468a 100644
--- a/arch/blackfin/include/asm/gpio.h
+++ b/arch/blackfin/include/asm/gpio.h
@@ -159,6 +159,11 @@
 };
 #endif
 
+#ifdef BFIN_SPECIAL_GPIO_BANKS
+void bfin_special_gpio_free(unsigned gpio);
+int bfin_special_gpio_request(unsigned gpio, const char *label);
+#endif
+
 #ifdef CONFIG_PM
 
 unsigned int bfin_pm_standby_setup(void);
diff --git a/arch/blackfin/kernel/bfin_gpio.c b/arch/blackfin/kernel/bfin_gpio.c
index c4161e0..a174596 100644
--- a/arch/blackfin/kernel/bfin_gpio.c
+++ b/arch/blackfin/kernel/bfin_gpio.c
@@ -100,6 +100,12 @@
 };
 # endif
 
+#elif defined(BF538_FAMILY)
+static unsigned short * const port_fer[] = {
+	(unsigned short *) PORTCIO_FER,
+	(unsigned short *) PORTDIO_FER,
+	(unsigned short *) PORTEIO_FER,
+};
 #endif
 
 static unsigned short reserved_gpio_map[GPIO_BANK_NUM];
@@ -163,6 +169,27 @@
 
 static void port_setup(unsigned gpio, unsigned short usage)
 {
+#if defined(BF538_FAMILY)
+	/*
+	 * BF538/9 Port C,D and E are special.
+	 * Inverted PORT_FER polarity on CDE and no PORF_FER on F
+	 * Regular PORT F GPIOs are handled here, CDE are exclusively
+	 * managed by GPIOLIB
+	 */
+
+	if (gpio < MAX_BLACKFIN_GPIOS || gpio >= MAX_RESOURCES)
+		return;
+
+	gpio -= MAX_BLACKFIN_GPIOS;
+
+	if (usage == GPIO_USAGE)
+		*port_fer[gpio_bank(gpio)] |= gpio_bit(gpio);
+	else
+		*port_fer[gpio_bank(gpio)] &= ~gpio_bit(gpio);
+	SSYNC();
+	return;
+#endif
+
 	if (check_gpio(gpio))
 		return;
 
@@ -981,6 +1008,76 @@
 }
 EXPORT_SYMBOL(bfin_gpio_free);
 
+#ifdef BFIN_SPECIAL_GPIO_BANKS
+static unsigned short reserved_special_gpio_map[gpio_bank(MAX_RESOURCES)];
+
+int bfin_special_gpio_request(unsigned gpio, const char *label)
+{
+	unsigned long flags;
+
+	local_irq_save_hw(flags);
+
+	/*
+	 * Allow that the identical GPIO can
+	 * be requested from the same driver twice
+	 * Do nothing and return -
+	 */
+
+	if (cmp_label(gpio, label) == 0) {
+		local_irq_restore_hw(flags);
+		return 0;
+	}
+
+	if (unlikely(reserved_special_gpio_map[gpio_bank(gpio)] & gpio_bit(gpio))) {
+		local_irq_restore_hw(flags);
+		printk(KERN_ERR "bfin-gpio: GPIO %d is already reserved by %s !\n",
+		       gpio, get_label(gpio));
+
+		return -EBUSY;
+	}
+	if (unlikely(reserved_peri_map[gpio_bank(gpio)] & gpio_bit(gpio))) {
+		local_irq_restore_hw(flags);
+		printk(KERN_ERR
+		       "bfin-gpio: GPIO %d is already reserved as Peripheral by %s !\n",
+		       gpio, get_label(gpio));
+
+		return -EBUSY;
+	}
+
+	reserved_special_gpio_map[gpio_bank(gpio)] |= gpio_bit(gpio);
+	reserved_peri_map[gpio_bank(gpio)] |= gpio_bit(gpio);
+
+	set_label(gpio, label);
+	local_irq_restore_hw(flags);
+	port_setup(gpio, GPIO_USAGE);
+
+	return 0;
+}
+EXPORT_SYMBOL(bfin_special_gpio_request);
+
+void bfin_special_gpio_free(unsigned gpio)
+{
+	unsigned long flags;
+
+	might_sleep();
+
+	local_irq_save_hw(flags);
+
+	if (unlikely(!(reserved_special_gpio_map[gpio_bank(gpio)] & gpio_bit(gpio)))) {
+		gpio_error(gpio);
+		local_irq_restore_hw(flags);
+		return;
+	}
+
+	reserved_special_gpio_map[gpio_bank(gpio)] &= ~gpio_bit(gpio);
+	reserved_peri_map[gpio_bank(gpio)] &= ~gpio_bit(gpio);
+	set_label(gpio, "free");
+	local_irq_restore_hw(flags);
+}
+EXPORT_SYMBOL(bfin_special_gpio_free);
+#endif
+
+
 int bfin_gpio_irq_request(unsigned gpio, const char *label)
 {
 	unsigned long flags;
diff --git a/arch/blackfin/mach-bf538/Makefile b/arch/blackfin/mach-bf538/Makefile
index 8cd2719..c0be54f 100644
--- a/arch/blackfin/mach-bf538/Makefile
+++ b/arch/blackfin/mach-bf538/Makefile
@@ -3,3 +3,4 @@
 #
 
 obj-y := ints-priority.o dma.o
+obj-$(CONFIG_GPIOLIB)	+= ext-gpio.o
diff --git a/arch/blackfin/mach-bf538/ext-gpio.c b/arch/blackfin/mach-bf538/ext-gpio.c
new file mode 100644
index 0000000..180b125
--- /dev/null
+++ b/arch/blackfin/mach-bf538/ext-gpio.c
@@ -0,0 +1,123 @@
+/*
+ * GPIOLIB interface for BF538/9 PORT C, D, and E GPIOs
+ *
+ * Copyright 2009 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/module.h>
+#include <linux/err.h>
+#include <asm/blackfin.h>
+#include <asm/gpio.h>
+#include <asm/portmux.h>
+
+#define DEFINE_REG(reg, off) \
+static inline u16 read_##reg(void __iomem *port) \
+	{ return bfin_read16(port + off); } \
+static inline void write_##reg(void __iomem *port, u16 v) \
+	{ bfin_write16(port + off, v); }
+
+DEFINE_REG(PORTIO, 0x00)
+DEFINE_REG(PORTIO_CLEAR, 0x10)
+DEFINE_REG(PORTIO_SET, 0x20)
+DEFINE_REG(PORTIO_DIR, 0x40)
+DEFINE_REG(PORTIO_INEN, 0x50)
+
+static void __iomem *gpio_chip_to_mmr(struct gpio_chip *chip)
+{
+	switch (chip->base) {
+	default: /* not really needed, but keeps gcc happy */
+	case GPIO_PC0: return (void __iomem *)PORTCIO;
+	case GPIO_PD0: return (void __iomem *)PORTDIO;
+	case GPIO_PE0: return (void __iomem *)PORTEIO;
+	}
+}
+
+static int bf538_gpio_get_value(struct gpio_chip *chip, unsigned gpio)
+{
+	void __iomem *port = gpio_chip_to_mmr(chip);
+	return !!(read_PORTIO(port) & (1u << gpio));
+}
+
+static void bf538_gpio_set_value(struct gpio_chip *chip, unsigned gpio, int value)
+{
+	void __iomem *port = gpio_chip_to_mmr(chip);
+	if (value)
+		write_PORTIO_SET(port, (1u << gpio));
+	else
+		write_PORTIO_CLEAR(port, (1u << gpio));
+}
+
+static int bf538_gpio_direction_input(struct gpio_chip *chip, unsigned gpio)
+{
+	void __iomem *port = gpio_chip_to_mmr(chip);
+	write_PORTIO_DIR(port, read_PORTIO_DIR(port) & ~(1u << gpio));
+	write_PORTIO_INEN(port, read_PORTIO_INEN(port) | (1u << gpio));
+	return 0;
+}
+
+static int bf538_gpio_direction_output(struct gpio_chip *chip, unsigned gpio, int value)
+{
+	void __iomem *port = gpio_chip_to_mmr(chip);
+	write_PORTIO_INEN(port, read_PORTIO_INEN(port) & ~(1u << gpio));
+	bf538_gpio_set_value(port, gpio, value);
+	write_PORTIO_DIR(port, read_PORTIO_DIR(port) | (1u << gpio));
+	return 0;
+}
+
+static int bf538_gpio_request(struct gpio_chip *chip, unsigned gpio)
+{
+	return bfin_special_gpio_request(chip->base + gpio, chip->label);
+}
+
+static void bf538_gpio_free(struct gpio_chip *chip, unsigned gpio)
+{
+	return bfin_special_gpio_free(chip->base + gpio);
+}
+
+/* We don't set the irq fields as these banks cannot generate interrupts */
+
+static struct gpio_chip bf538_portc_chip = {
+	.label = "GPIO-PC",
+	.direction_input = bf538_gpio_direction_input,
+	.get = bf538_gpio_get_value,
+	.direction_output = bf538_gpio_direction_output,
+	.set = bf538_gpio_set_value,
+	.request = bf538_gpio_request,
+	.free = bf538_gpio_free,
+	.base = GPIO_PC0,
+	.ngpio = GPIO_PC9 - GPIO_PC0 + 1,
+};
+
+static struct gpio_chip bf538_portd_chip = {
+	.label = "GPIO-PD",
+	.direction_input = bf538_gpio_direction_input,
+	.get = bf538_gpio_get_value,
+	.direction_output = bf538_gpio_direction_output,
+	.set = bf538_gpio_set_value,
+	.request = bf538_gpio_request,
+	.free = bf538_gpio_free,
+	.base = GPIO_PD0,
+	.ngpio = GPIO_PD13 - GPIO_PD0 + 1,
+};
+
+static struct gpio_chip bf538_porte_chip = {
+	.label = "GPIO-PE",
+	.direction_input = bf538_gpio_direction_input,
+	.get = bf538_gpio_get_value,
+	.direction_output = bf538_gpio_direction_output,
+	.set = bf538_gpio_set_value,
+	.request = bf538_gpio_request,
+	.free = bf538_gpio_free,
+	.base = GPIO_PE0,
+	.ngpio = GPIO_PE15 - GPIO_PE0 + 1,
+};
+
+static int __init bf538_extgpio_setup(void)
+{
+	return gpiochip_add(&bf538_portc_chip) |
+		gpiochip_add(&bf538_portd_chip) |
+		gpiochip_add(&bf538_porte_chip);
+}
+arch_initcall(bf538_extgpio_setup);
diff --git a/arch/blackfin/mach-bf538/include/mach/defBF539.h b/arch/blackfin/mach-bf538/include/mach/defBF539.h
index 5f6c34d..1f1aeab 100644
--- a/arch/blackfin/mach-bf538/include/mach/defBF539.h
+++ b/arch/blackfin/mach-bf538/include/mach/defBF539.h
@@ -468,31 +468,31 @@
 /* General-Purpose Ports  (0xFFC01500 -	0xFFC015FF)	 */
 
 /* GPIO	Port C Register	Names */
-#define	GPIO_C_CNFG			0xFFC01500	/* GPIO	Pin Port C Configuration Register */
-#define	GPIO_C_D			0xFFC01510	/* GPIO	Pin Port C Data	Register */
-#define	GPIO_C_C			0xFFC01520	/* Clear GPIO Pin Port C Register */
-#define	GPIO_C_S			0xFFC01530	/* Set GPIO Pin	Port C Register */
-#define	GPIO_C_T			0xFFC01540	/* Toggle GPIO Pin Port	C Register */
-#define	GPIO_C_DIR			0xFFC01550	/* GPIO	Pin Port C Direction Register */
-#define	GPIO_C_INEN			0xFFC01560	/* GPIO	Pin Port C Input Enable	Register */
+#define PORTCIO_FER			0xFFC01500	/* GPIO	Pin Port C Configuration Register */
+#define PORTCIO				0xFFC01510	/* GPIO	Pin Port C Data	Register */
+#define PORTCIO_CLEAR			0xFFC01520	/* Clear GPIO Pin Port C Register */
+#define PORTCIO_SET			0xFFC01530	/* Set GPIO Pin	Port C Register */
+#define PORTCIO_TOGGLE			0xFFC01540	/* Toggle GPIO Pin Port	C Register */
+#define PORTCIO_DIR			0xFFC01550	/* GPIO	Pin Port C Direction Register */
+#define PORTCIO_INEN			0xFFC01560	/* GPIO	Pin Port C Input Enable	Register */
 
 /* GPIO	Port D Register	Names */
-#define	GPIO_D_CNFG			0xFFC01504	/* GPIO	Pin Port D Configuration Register */
-#define	GPIO_D_D			0xFFC01514	/* GPIO	Pin Port D Data	Register */
-#define	GPIO_D_C			0xFFC01524	/* Clear GPIO Pin Port D Register */
-#define	GPIO_D_S			0xFFC01534	/* Set GPIO Pin	Port D Register */
-#define	GPIO_D_T			0xFFC01544	/* Toggle GPIO Pin Port	D Register */
-#define	GPIO_D_DIR			0xFFC01554	/* GPIO	Pin Port D Direction Register */
-#define	GPIO_D_INEN			0xFFC01564	/* GPIO	Pin Port D Input Enable	Register */
+#define PORTDIO_FER			0xFFC01504	/* GPIO	Pin Port D Configuration Register */
+#define PORTDIO				0xFFC01514	/* GPIO	Pin Port D Data	Register */
+#define PORTDIO_CLEAR			0xFFC01524	/* Clear GPIO Pin Port D Register */
+#define PORTDIO_SET			0xFFC01534	/* Set GPIO Pin	Port D Register */
+#define PORTDIO_TOGGLE			0xFFC01544	/* Toggle GPIO Pin Port	D Register */
+#define PORTDIO_DIR			0xFFC01554	/* GPIO	Pin Port D Direction Register */
+#define PORTDIO_INEN			0xFFC01564	/* GPIO	Pin Port D Input Enable	Register */
 
 /* GPIO	Port E Register	Names */
-#define	GPIO_E_CNFG			0xFFC01508	/* GPIO	Pin Port E Configuration Register */
-#define	GPIO_E_D			0xFFC01518	/* GPIO	Pin Port E Data	Register */
-#define	GPIO_E_C			0xFFC01528	/* Clear GPIO Pin Port E Register */
-#define	GPIO_E_S			0xFFC01538	/* Set GPIO Pin	Port E Register */
-#define	GPIO_E_T			0xFFC01548	/* Toggle GPIO Pin Port	E Register */
-#define	GPIO_E_DIR			0xFFC01558	/* GPIO	Pin Port E Direction Register */
-#define	GPIO_E_INEN			0xFFC01568	/* GPIO	Pin Port E Input Enable	Register */
+#define PORTEIO_FER			0xFFC01508	/* GPIO	Pin Port E Configuration Register */
+#define PORTEIO				0xFFC01518	/* GPIO	Pin Port E Data	Register */
+#define PORTEIO_CLEAR			0xFFC01528	/* Clear GPIO Pin Port E Register */
+#define PORTEIO_SET			0xFFC01538	/* Set GPIO Pin	Port E Register */
+#define PORTEIO_TOGGLE			0xFFC01548	/* Toggle GPIO Pin Port	E Register */
+#define PORTEIO_DIR			0xFFC01558	/* GPIO	Pin Port E Direction Register */
+#define PORTEIO_INEN			0xFFC01568	/* GPIO	Pin Port E Input Enable	Register */
 
 /* DMA Controller 1 Traffic Control Registers (0xFFC01B00 - 0xFFC01BFF) */
 
diff --git a/arch/blackfin/mach-bf538/include/mach/gpio.h b/arch/blackfin/mach-bf538/include/mach/gpio.h
index 295c78a..0c346fb 100644
--- a/arch/blackfin/mach-bf538/include/mach/gpio.h
+++ b/arch/blackfin/mach-bf538/include/mach/gpio.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 Analog Devices Inc.
+ * Copyright (C) 2008-2009 Analog Devices Inc.
  * Licensed under the GPL-2 or later.
  */
 
@@ -7,11 +7,8 @@
 #ifndef _MACH_GPIO_H_
 #define _MACH_GPIO_H_
 
-	/* FIXME:
-	 * For now only support PORTF GPIOs.
-	 * PORT C,D and E are for peripheral usage only
-	 */
 #define MAX_BLACKFIN_GPIOS 16
+#define BFIN_SPECIAL_GPIO_BANKS 3
 
 #define	GPIO_PF0	0	/* PF */
 #define	GPIO_PF1	1
diff --git a/arch/blackfin/mach-bf538/include/mach/portmux.h b/arch/blackfin/mach-bf538/include/mach/portmux.h
index 6121cf8..0083ba1 100644
--- a/arch/blackfin/mach-bf538/include/mach/portmux.h
+++ b/arch/blackfin/mach-bf538/include/mach/portmux.h
@@ -7,7 +7,7 @@
 #ifndef _MACH_PORTMUX_H_
 #define _MACH_PORTMUX_H_
 
-#define MAX_RESOURCES	MAX_BLACKFIN_GPIOS
+#define MAX_RESOURCES	64
 
 #define P_TMR2		(P_DONTCARE)
 #define P_TMR1		(P_DONTCARE)
diff --git a/arch/blackfin/mach-common/dpmc_modes.S b/arch/blackfin/mach-common/dpmc_modes.S
index 8009a51..b037168 100644
--- a/arch/blackfin/mach-common/dpmc_modes.S
+++ b/arch/blackfin/mach-common/dpmc_modes.S
@@ -404,6 +404,21 @@
 	PM_SYS_PUSH(EBIU_FCTL)
 #endif
 
+#ifdef PORTCIO_FER
+	PM_SYS_PUSH16(PORTCIO_DIR)
+	PM_SYS_PUSH16(PORTCIO_INEN)
+	PM_SYS_PUSH16(PORTCIO)
+	PM_SYS_PUSH16(PORTCIO_FER)
+	PM_SYS_PUSH16(PORTDIO_DIR)
+	PM_SYS_PUSH16(PORTDIO_INEN)
+	PM_SYS_PUSH16(PORTDIO)
+	PM_SYS_PUSH16(PORTDIO_FER)
+	PM_SYS_PUSH16(PORTEIO_DIR)
+	PM_SYS_PUSH16(PORTEIO_INEN)
+	PM_SYS_PUSH16(PORTEIO)
+	PM_SYS_PUSH16(PORTEIO_FER)
+#endif
+
 	PM_SYS_PUSH16(SYSCR)
 
 	/* Save Core MMRs */
@@ -716,6 +731,21 @@
 	P0.L = lo(PLL_CTL);
 	PM_SYS_POP16(SYSCR)
 
+#ifdef PORTCIO_FER
+	PM_SYS_POP16(PORTEIO_FER)
+	PM_SYS_POP16(PORTEIO)
+	PM_SYS_POP16(PORTEIO_INEN)
+	PM_SYS_POP16(PORTEIO_DIR)
+	PM_SYS_POP16(PORTDIO_FER)
+	PM_SYS_POP16(PORTDIO)
+	PM_SYS_POP16(PORTDIO_INEN)
+	PM_SYS_POP16(PORTDIO_DIR)
+	PM_SYS_POP16(PORTCIO_FER)
+	PM_SYS_POP16(PORTCIO)
+	PM_SYS_POP16(PORTCIO_INEN)
+	PM_SYS_POP16(PORTCIO_DIR)
+#endif
+
 #ifdef EBIU_FCTL
 	PM_SYS_POP(EBIU_FCTL)
 	PM_SYS_POP(EBIU_MODE)