[ARM] pxa: better MFP low power state support for pxa25x/pxa27x

When configured as a specific low power state: MFP_LPM_DRIVE_LOW,
MFP_LPM_DRIVE_HIGH, the corresponding GPDR register bit during
low power mode shall be re-configured as output (if they are not
configured so), thus the PGSRx bits can output.

Create an additional low power values GPDR registers, and properly
save/restore the GAFR + GPDR registers when doing suspend/resume.

Signed-off-by: Eric Miao <eric.miao@marvell.com>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
diff --git a/arch/arm/mach-pxa/mfp-pxa2xx.c b/arch/arm/mach-pxa/mfp-pxa2xx.c
index 925575f..3ee1f39 100644
--- a/arch/arm/mach-pxa/mfp-pxa2xx.c
+++ b/arch/arm/mach-pxa/mfp-pxa2xx.c
@@ -25,7 +25,12 @@
 
 #include "generic.h"
 
-#define PGSR(x)		__REG2(0x40F00020, ((x) & 0x60) >> 3)
+#define gpio_to_bank(gpio)	((gpio) >> 5)
+
+#define PGSR(x)		__REG2(0x40F00020, (x) << 2)
+#define __GAFR(u, x)	__REG2((u) ? 0x40E00058 : 0x40E00054, (x) << 3)
+#define GAFR_L(x)	__GAFR(0, x)
+#define GAFR_U(x)	__GAFR(1, x)
 
 #define PWER_WE35	(1 << 24)
 
@@ -38,49 +43,59 @@
 };
 
 static struct gpio_desc gpio_desc[MFP_PIN_GPIO127 + 1];
+static int gpio_nr;
 
-static int __mfp_config_lpm(unsigned gpio, unsigned long lpm)
-{
-	unsigned mask = GPIO_bit(gpio);
-
-	/* low power state */
-	switch (lpm) {
-	case MFP_LPM_DRIVE_HIGH:
-		PGSR(gpio) |= mask;
-		break;
-	case MFP_LPM_DRIVE_LOW:
-		PGSR(gpio) &= ~mask;
-		break;
-	case MFP_LPM_INPUT:
-		break;
-	default:
-		pr_warning("%s: invalid low power state for GPIO%d\n",
-				__func__, gpio);
-		return -EINVAL;
-	}
-	return 0;
-}
+static unsigned long gpdr_lpm[4];
 
 static int __mfp_config_gpio(unsigned gpio, unsigned long c)
 {
 	unsigned long gafr, mask = GPIO_bit(gpio);
-	int fn;
+	int bank = gpio_to_bank(gpio);
+	int uorl = !!(gpio & 0x10); /* GAFRx_U or GAFRx_L ? */
+	int shft = (gpio & 0xf) << 1;
+	int fn = MFP_AF(c);
+	int dir = c & MFP_DIR_OUT;
 
-	fn = MFP_AF(c);
 	if (fn > 3)
 		return -EINVAL;
 
-	/* alternate function and direction */
-	gafr = GAFR(gpio) & ~(0x3 << ((gpio & 0xf) * 2));
-	GAFR(gpio) = gafr |  (fn  << ((gpio & 0xf) * 2));
+	/* alternate function and direction at run-time */
+	gafr = (uorl == 0) ? GAFR_L(bank) : GAFR_U(bank);
+	gafr = (gafr & ~(0x3 << shft)) | (fn << shft);
 
-	if (c & MFP_DIR_OUT)
+	if (uorl == 0)
+		GAFR_L(bank) = gafr;
+	else
+		GAFR_U(bank) = gafr;
+
+	if (dir == MFP_DIR_OUT)
 		GPDR(gpio) |= mask;
 	else
 		GPDR(gpio) &= ~mask;
 
-	if (__mfp_config_lpm(gpio, c & MFP_LPM_STATE_MASK))
-		return -EINVAL;
+	/* alternate function and direction at low power mode */
+	switch (c & MFP_LPM_STATE_MASK) {
+	case MFP_LPM_DRIVE_HIGH:
+		PGSR(bank) |= mask;
+		dir = MFP_DIR_OUT;
+		break;
+	case MFP_LPM_DRIVE_LOW:
+		PGSR(bank) &= ~mask;
+		dir = MFP_DIR_OUT;
+		break;
+	case MFP_LPM_DEFAULT:
+		break;
+	default:
+		/* warning and fall through, treat as MFP_LPM_DEFAULT */
+		pr_warning("%s: GPIO%d: unsupported low power mode\n",
+				__func__, gpio);
+		break;
+	}
+
+	if (dir == MFP_DIR_OUT)
+		gpdr_lpm[bank] |= mask;
+	else
+		gpdr_lpm[bank] &= ~mask;
 
 	/* give early warning if MFP_LPM_CAN_WAKEUP is set on the
 	 * configurations of those pins not able to wakeup
@@ -91,7 +106,7 @@
 		return -EINVAL;
 	}
 
-	if ((c & MFP_LPM_CAN_WAKEUP) && (c & MFP_DIR_OUT)) {
+	if ((c & MFP_LPM_CAN_WAKEUP) && (dir == MFP_DIR_OUT)) {
 		pr_warning("%s: output GPIO%d unable to wakeup\n",
 				__func__, gpio);
 		return -EINVAL;
@@ -135,7 +150,7 @@
 
 void pxa2xx_mfp_set_lpm(int mfp, unsigned long lpm)
 {
-	unsigned long flags;
+	unsigned long flags, c;
 	int gpio;
 
 	gpio = __mfp_validate(mfp);
@@ -143,7 +158,11 @@
 		return;
 
 	local_irq_save(flags);
-	__mfp_config_lpm(gpio, lpm);
+
+	c = gpio_desc[gpio].config;
+	c = (c & ~MFP_LPM_STATE_MASK) | lpm;
+	__mfp_config_gpio(gpio, c);
+
 	local_irq_restore(flags);
 }
 
@@ -187,23 +206,22 @@
 }
 
 #ifdef CONFIG_PXA25x
-static int __init pxa25x_mfp_init(void)
+static void __init pxa25x_mfp_init(void)
 {
 	int i;
 
-	if (cpu_is_pxa25x()) {
-		for (i = 0; i <= 84; i++)
-			gpio_desc[i].valid = 1;
+	for (i = 0; i <= 84; i++)
+		gpio_desc[i].valid = 1;
 
-		for (i = 0; i <= 15; i++) {
-			gpio_desc[i].can_wakeup = 1;
-			gpio_desc[i].mask = GPIO_bit(i);
-		}
+	for (i = 0; i <= 15; i++) {
+		gpio_desc[i].can_wakeup = 1;
+		gpio_desc[i].mask = GPIO_bit(i);
 	}
 
-	return 0;
+	gpio_nr = 85;
 }
-postcore_initcall(pxa25x_mfp_init);
+#else
+static inline void pxa25x_mfp_init(void) {}
 #endif /* CONFIG_PXA25x */
 
 #ifdef CONFIG_PXA27x
@@ -233,45 +251,103 @@
 	return 0;
 }
 
-static int __init pxa27x_mfp_init(void)
+static void __init pxa27x_mfp_init(void)
 {
 	int i, gpio;
 
-	if (cpu_is_pxa27x()) {
-		for (i = 0; i <= 120; i++) {
-			/* skip GPIO2, 5, 6, 7, 8, they are not
-			 * valid pins allow configuration
-			 */
-			if (i == 2 || i == 5 || i == 6 ||
-			    i == 7 || i == 8)
-				continue;
+	for (i = 0; i <= 120; i++) {
+		/* skip GPIO2, 5, 6, 7, 8, they are not
+		 * valid pins allow configuration
+		 */
+		if (i == 2 || i == 5 || i == 6 || i == 7 || i == 8)
+			continue;
 
-			gpio_desc[i].valid = 1;
-		}
-
-		/* Keypad GPIOs */
-		for (i = 0; i < ARRAY_SIZE(pxa27x_pkwr_gpio); i++) {
-			gpio = pxa27x_pkwr_gpio[i];
-			gpio_desc[gpio].can_wakeup = 1;
-			gpio_desc[gpio].keypad_gpio = 1;
-			gpio_desc[gpio].mask = 1 << i;
-		}
-
-		/* Overwrite GPIO13 as a PWER wakeup source */
-		for (i = 0; i <= 15; i++) {
-			/* skip GPIO2, 5, 6, 7, 8 */
-			if (GPIO_bit(i) & 0x1e4)
-				continue;
-
-			gpio_desc[i].can_wakeup = 1;
-			gpio_desc[i].mask = GPIO_bit(i);
-		}
-
-		gpio_desc[35].can_wakeup = 1;
-		gpio_desc[35].mask = PWER_WE35;
+		gpio_desc[i].valid = 1;
 	}
 
+	/* Keypad GPIOs */
+	for (i = 0; i < ARRAY_SIZE(pxa27x_pkwr_gpio); i++) {
+		gpio = pxa27x_pkwr_gpio[i];
+		gpio_desc[gpio].can_wakeup = 1;
+		gpio_desc[gpio].keypad_gpio = 1;
+		gpio_desc[gpio].mask = 1 << i;
+	}
+
+	/* Overwrite GPIO13 as a PWER wakeup source */
+	for (i = 0; i <= 15; i++) {
+		/* skip GPIO2, 5, 6, 7, 8 */
+		if (GPIO_bit(i) & 0x1e4)
+			continue;
+
+		gpio_desc[i].can_wakeup = 1;
+		gpio_desc[i].mask = GPIO_bit(i);
+	}
+
+	gpio_desc[35].can_wakeup = 1;
+	gpio_desc[35].mask = PWER_WE35;
+
+	gpio_nr = 121;
+}
+#else
+static inline void pxa27x_mfp_init(void) {}
+#endif /* CONFIG_PXA27x */
+
+#ifdef CONFIG_PM
+static unsigned long saved_gafr[2][4];
+static unsigned long saved_gpdr[4];
+
+static int pxa2xx_mfp_suspend(struct sys_device *d, pm_message_t state)
+{
+	int i;
+
+	for (i = 0; i <= gpio_to_bank(gpio_nr); i++) {
+
+		saved_gafr[0][i] = GAFR_L(i);
+		saved_gafr[1][i] = GAFR_U(i);
+		saved_gpdr[i] = GPDR(i * 32);
+
+		GPDR(i * 32) = gpdr_lpm[i];
+	}
 	return 0;
 }
-postcore_initcall(pxa27x_mfp_init);
-#endif /* CONFIG_PXA27x */
+
+static int pxa2xx_mfp_resume(struct sys_device *d)
+{
+	int i;
+
+	for (i = 0; i <= gpio_to_bank(gpio_nr); i++) {
+		GAFR_L(i) = saved_gafr[0][i];
+		GAFR_U(i) = saved_gafr[1][i];
+		GPDR(i * 32) = saved_gpdr[i];
+	}
+	PSSR = PSSR_RDH | PSSR_PH;
+	return 0;
+}
+#else
+#define pxa2xx_mfp_suspend	NULL
+#define pxa2xx_mfp_resume	NULL
+#endif
+
+struct sysdev_class pxa2xx_mfp_sysclass = {
+	.name		= "mfp",
+	.suspend	= pxa2xx_mfp_suspend,
+	.resume		= pxa2xx_mfp_resume,
+};
+
+static int __init pxa2xx_mfp_init(void)
+{
+	int i;
+
+	if (cpu_is_pxa25x())
+		pxa25x_mfp_init();
+
+	if (cpu_is_pxa27x())
+		pxa27x_mfp_init();
+
+	/* initialize gafr_run[], pgsr_lpm[] from existing values */
+	for (i = 0; i <= gpio_to_bank(gpio_nr); i++)
+		gpdr_lpm[i] = GPDR(i * 32);
+
+	return sysdev_class_register(&pxa2xx_mfp_sysclass);
+}
+postcore_initcall(pxa2xx_mfp_init);