From 4c0d495d04d72907690fe4f4dddaa59582e044a3 Mon Sep 17 00:00:00 2001
From: Oliver Ford <oliford@NervousEnergy.(none)>
Date: Thu, 19 Jun 2008 23:12:38 +0100
Subject: [PATCH] Support for the HP iPAQ 21x - version 3. Including:
	Capable of working from cold boot.
	Audio.
	Suspend/Resume support to D3 (and maybe D2).
	NAND Flash support (forced to iPAQ File Store area only).
	Kernel space Bluetooth power driver (activated through sysfs).
	More accurate Multi-function pin configuration (better power consumption?)

	Known Issues:
		Audio often doesn't work after resume.
---
 arch/arm/mach-pxa/Kconfig                |    7 +
 arch/arm/mach-pxa/Makefile               |    1 +
 arch/arm/mach-pxa/devices.c              |   77 ++++
 arch/arm/mach-pxa/devices.h              |    2 +
 arch/arm/mach-pxa/hpipaq214-bt.c         |  196 ++++++++++
 arch/arm/mach-pxa/hpipaq214-mfp.c        |  193 ++++++++++
 arch/arm/mach-pxa/hpipaq214.c            |  573 ++++++++++++++++++++++++++++++
 arch/arm/mach-pxa/pxa3xx.c               |   49 +++-
 drivers/input/touchscreen/Kconfig        |   13 +
 drivers/input/touchscreen/Makefile       |    1 +
 drivers/input/touchscreen/hpipaq214-ts.c |  403 +++++++++++++++++++++
 drivers/mtd/nand/pxa3xx_nand.c           |   45 +++-
 drivers/serial/pxa.c                     |    5 +-
 include/asm-arm/arch-pxa/pxa3xx_nand.h   |    2 +
 sound/soc/codecs/wm9713.c                |    7 +
 sound/soc/pxa/Kconfig                    |    8 +
 sound/soc/pxa/Makefile                   |    2 +
 sound/soc/pxa/hpipaq214-audio.c          |  332 +++++++++++++++++
 18 files changed, 1908 insertions(+), 8 deletions(-)
 create mode 100644 arch/arm/mach-pxa/hpipaq214-bt.c
 create mode 100644 arch/arm/mach-pxa/hpipaq214-mfp.c
 create mode 100644 arch/arm/mach-pxa/hpipaq214.c
 create mode 100644 drivers/input/touchscreen/hpipaq214-ts.c
 create mode 100644 sound/soc/pxa/hpipaq214-audio.c

diff --git a/arch/arm/mach-pxa/Kconfig b/arch/arm/mach-pxa/Kconfig
index cd33f3f..29942fa 100644
--- a/arch/arm/mach-pxa/Kconfig
+++ b/arch/arm/mach-pxa/Kconfig
@@ -119,6 +119,13 @@ config MACH_ZYLONITE
 	select PXA3xx
 	select HAVE_PWM
 
+config MACH_HPIPAQ214
+	bool "HP iPAQ 214"
+	select PXA3xx
+	select PXA310
+	select PXA_SSP
+	select HAVE_PWM
+
 config MACH_LITTLETON
 	bool "PXA3xx Form Factor Platform (aka Littleton)"
 	select PXA3xx
diff --git a/arch/arm/mach-pxa/Makefile b/arch/arm/mach-pxa/Makefile
index 2721c32..70074e7 100644
--- a/arch/arm/mach-pxa/Makefile
+++ b/arch/arm/mach-pxa/Makefile
@@ -43,6 +43,7 @@ ifeq ($(CONFIG_MACH_ZYLONITE),y)
   obj-$(CONFIG_CPU_PXA300)	+= zylonite_pxa300.o
   obj-$(CONFIG_CPU_PXA320)	+= zylonite_pxa320.o
 endif
+obj-$(CONFIG_MACH_HPIPAQ214)	+= hpipaq214.o hpipaq214-bt.o hpipaq214-mfp.o
 obj-$(CONFIG_MACH_LITTLETON)	+= littleton.o
 
 obj-$(CONFIG_MACH_ARMCORE)      += cm-x270.o
diff --git a/arch/arm/mach-pxa/devices.c b/arch/arm/mach-pxa/devices.c
index 4a1eebb..e15e5a3 100644
--- a/arch/arm/mach-pxa/devices.c
+++ b/arch/arm/mach-pxa/devices.c
@@ -14,9 +14,79 @@
 #include <asm/arch/ohci.h>
 #include <asm/arch/pxa27x_keypad.h>
 #include <asm/arch/camera.h>
+#include <asm/arch/pxa3xx_nand.h>
 
 #include "devices.h"
 #include "generic.h"
+#define DEFINE_RESMEM(_pre, _dev, _mem, _len)				\
+static struct resource _pre##_resource_##_dev[] = {			\
+	[0] = {								\
+		.start	= (_mem),					\
+		.end	= (_mem) + (_len) - 1,				\
+		.flags	= IORESOURCE_MEM,				\
+	},								\
+}
+
+#define DEFINE_RESIRQ(_pre, _dev, _mem, _len, _irq)			\
+static struct resource _pre##_resource_##_dev[] = {			\
+	[0] = {								\
+		.start	= (_mem),					\
+		.end	= (_mem) + (_len) - 1,				\
+		.flags	= IORESOURCE_MEM,				\
+	},								\
+	[1] = {								\
+		.start	= (_irq),					\
+		.end	= (_irq),					\
+		.flags	= IORESOURCE_IRQ,				\
+	},								\
+}
+
+#define DEFINE_RESDMA(_pre, _dev, _mem, _len, _irq, _dma_rx, _dma_tx)	\
+static struct resource _pre##_resource_##_dev[] = {			\
+	[0] = {								\
+		.start	= (_mem),					\
+		.end	= (_mem) + (_len) - 1,				\
+		.flags	= IORESOURCE_MEM,				\
+	},								\
+	[1] = {								\
+		.start	= (_irq),					\
+		.end	= (_irq),					\
+		.flags	= IORESOURCE_IRQ,				\
+	},								\
+	[2] = {								\
+		.start	= (_dma_rx),					\
+		.end	= (_dma_rx),					\
+		.flags	= IORESOURCE_DMA,				\
+	},								\
+	[3] = {								\
+		.start	= (_dma_tx),					\
+		.end	= (_dma_tx),					\
+		.flags	= IORESOURCE_DMA,				\
+	},								\
+}
+
+#define DEFINE_DEVICE(_pre, _dev, _idx, _name)				\
+struct platform_device _pre##_device_##_dev = {				\
+	.name		= _name,					\
+	.id		= _idx,						\
+	.resource	= _pre##_resource_##_dev,			\
+	.num_resources	= ARRAY_SIZE(_pre##_resource_##_dev),		\
+}
+
+#define DEFINE_DEVDMA(_pre, _dev, _idx, _name)				\
+static u64 _pre##_dev##_dma_mask = DMA_BIT_MASK(32);			\
+struct platform_device _pre##_device_##_dev = {				\
+	.name		= _name,					\
+	.id		= _idx,						\
+	.dev		= {						\
+		.dma_mask = &(_pre##_dev##_dma_mask),			\
+		.coherent_dma_mask = DMA_BIT_MASK(32),			\
+	},								\
+	.resource	= _pre##_resource_##_dev,			\
+	.num_resources	= ARRAY_SIZE(_pre##_resource_##_dev),		\
+}
+
+
 
 void __init pxa_register_device(struct platform_device *dev, void *data)
 {
@@ -787,4 +857,11 @@ void __init pxa3xx_set_mci3_info(struct pxamci_platform_data *info)
 	pxa_register_device(&pxa3xx_device_mci3, info);
 }
 
+DEFINE_RESDMA(pxa3xx, nand, 0x43100000, 0x54, IRQ_NAND, 97, 98);
+DEFINE_DEVDMA(pxa3xx, nand, -1, "pxa3xx-nand");
+
+void __init pxa3xx_set_nand_info(struct pxa3xx_nand_platform_data *info)
+{
+	pxa_register_device(&pxa3xx_device_nand, info);
+}
 #endif /* CONFIG_PXA3xx */
diff --git a/arch/arm/mach-pxa/devices.h b/arch/arm/mach-pxa/devices.h
index e620a33..870b19f 100644
--- a/arch/arm/mach-pxa/devices.h
+++ b/arch/arm/mach-pxa/devices.h
@@ -29,4 +29,6 @@ extern struct platform_device pxa25x_device_pwm1;
 extern struct platform_device pxa27x_device_pwm0;
 extern struct platform_device pxa27x_device_pwm1;
 
+extern struct platform_device pxa3xx_device_nand;
+
 void __init pxa_register_device(struct platform_device *dev, void *data);
diff --git a/arch/arm/mach-pxa/hpipaq214-bt.c b/arch/arm/mach-pxa/hpipaq214-bt.c
new file mode 100644
index 0000000..2589169
--- /dev/null
+++ b/arch/arm/mach-pxa/hpipaq214-bt.c
@@ -0,0 +1,196 @@
+/*
+ * Driver for bluetooth power for HP iPAQ 214
+ *
+ * Oliver Ford <ipaqcode-oliford-co-uk>
+ *
+ * Mostly copied from drivers/video/backlight/backlight.c
+ * 
+ * FIXME: Seems to only work from cold boot with debugging on at 
+ *         the moment. Maybe it needs some more delays.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+
+#include <asm/hardware.h>
+#include <asm/arch/gpio.h>
+#include <asm/arch/mfp-pxa300.h>
+
+#define ARRAY_AND_SIZE(x)	(x), ARRAY_SIZE(x)
+
+static int state; //should be part in platform device data, not global to driver, but we can only have 1 anyway
+
+static void dumpAll(void){ //debugging
+	int i;
+	int gpio[] = { 53, 71, 79, 81, 84, 114, 124 };	
+	printk("BT GPIOS: ");
+	for(i=0;i<7;i++){		
+		printk("%i=%s ",gpio[i], gpio_get_value(gpio[i]) ? "on" : "off");
+	}
+	printk("\n");
+}
+
+static mfp_cfg_t rts_cts_as_uart[] = {
+	GPIO79_UART1_CTS, 
+	GPIO84_UART1_RTS,
+};
+
+static mfp_cfg_t rts_cts_as_gpio[] = {
+	GPIO79_GPIO,
+	GPIO84_GPIO,
+};
+
+
+static void hpipaq214_bt_power_off(void){	
+	gpio_set_value(81,0);
+	gpio_set_value(3,0);
+	gpio_set_value(53,0);
+	gpio_set_value(71,0);
+	gpio_set_value(124,0);
+	gpio_set_value(114,0);
+	state = 0;
+	dumpAll();
+}
+
+static void hpipaq214_bt_power_on(struct device *dev){
+	if(gpio_request(79, "FFUART CTS (BlueTooth)") ||
+	   gpio_request(84, "FFUART RTS (BlueTooth)")){
+		dev_err(dev, "Unable to get FFUART CTS/RTS control gpios");
+		state = -2;
+		return;
+	}
+	dumpAll();
+	gpio_direction_input(79);
+	gpio_direction_output(84, 1); 
+	
+	//reconfig RTS and CTS as GPIOs for this bit
+	pxa3xx_mfp_config(ARRAY_AND_SIZE(rts_cts_as_gpio));
+	dumpAll();
+	
+	hpipaq214_bt_power_off();	//everything off
+	mdelay(10); dumpAll();
+	
+	gpio_set_value(53, 1);
+	gpio_set_value(71, 1);
+	gpio_set_value(124, 1);
+	mdelay(1); dumpAll();
+		
+	gpio_set_value(81, 0);
+	gpio_set_value(84, 1);		// RTS	off
+
+	gpio_set_value(114, 1);
+	mdelay(2); dumpAll();
+
+	gpio_set_value(114, 0);
+	mdelay(2); dumpAll();
+
+	gpio_set_value(114, 1);
+	gpio_set_value(81, 1);
+	mdelay(500); dumpAll();
+
+	gpio_set_value(84, 0);		// RTS on
+	mdelay(1); dumpAll();
+
+	if( !gpio_get_value(79) ){	//check CTS status (active low)
+		dev_info(dev,"Bluetooth on and ready\n");	
+		state = 1;
+	}else{
+		dev_err(dev, "ERROR: Bluetooth not stating clear to send\n");	
+		hpipaq214_bt_power_off();
+		state = -2;
+	}
+
+	dumpAll();
+	gpio_free(79);
+	gpio_free(84);
+	
+	pxa3xx_mfp_config(ARRAY_AND_SIZE(rts_cts_as_uart));
+	dumpAll();
+}
+
+static ssize_t hpipaq214_bt_show_btstate(struct platform_device *pdev,
+				struct device_attribute *attr, char *buf){	
+	strcpy(buf, (state < 0) ? "error" : (state > 0 ? "on" : "off"));
+	return strlen(buf) + 1;	
+}
+
+static ssize_t hpipaq214_bt_store_btstate(struct platform_device *pdev,
+				struct device_attribute *attr, const char *buf, size_t count){
+	
+	if(buf[0] == 'o'){
+		if(buf[1] == 'n')
+			hpipaq214_bt_power_on(&pdev->dev);		
+		else if(buf[1] == 'f' && buf[2] == 'f'){
+			hpipaq214_bt_power_off();
+			dev_info(&pdev->dev,"Bluetooth off\n");
+		}
+	}
+	
+	return count;
+}
+
+DEVICE_ATTR(btstate, (S_IRUGO | S_IWUGO), hpipaq214_bt_show_btstate, hpipaq214_bt_store_btstate);
+
+static int hpipaq214_bt_probe(struct platform_device *pdev)
+{	
+	int bt_gpios[] = { 53, 71, 81, 114, 124 };
+	int i;
+
+	device_create_file(&pdev->dev, &dev_attr_btstate);
+	state = 0;
+
+	for(i=0;i<5;i++)
+		if(!gpio_request(bt_gpios[i], "Bluetooth Control GPIO"))
+			gpio_direction_output(bt_gpios[i], 0);
+
+	return 0;
+}
+
+static int hpipaq214_bt_remove(struct platform_device *pdev)
+{
+	hpipaq214_bt_power_off();	
+	return 0;
+}
+
+static int hpipaq214_bt_suspend(struct platform_device *pdev, pm_message_t pmstate)
+{
+	if(state > 0)
+		dev_info(&pdev->dev, "Forcing BT off for suspend");
+
+	hpipaq214_bt_power_off();
+	
+	return 0;
+}
+
+static struct platform_driver bluetooth_driver = {
+	.driver		= {	
+		.name	= "hpipaq214-bluetooth",
+	},
+	.probe		= hpipaq214_bt_probe,
+	.suspend	= hpipaq214_bt_suspend,
+	.remove		= hpipaq214_bt_remove,
+	
+};
+
+static int __init hpipaq214_bt_init(void)
+{
+	printk(KERN_INFO "HP iPAQ 214 Bluetooth Driver\n");
+	platform_driver_register(&bluetooth_driver);
+
+	return 0;
+}
+
+static void __exit hpipaq214_bt_exit(void)
+{
+        platform_driver_unregister(&bluetooth_driver);
+}
+
+module_init(hpipaq214_bt_init);
+module_exit(hpipaq214_bt_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Oliver Ford  <ipaqcode-at-oliford-co-uk>");
+MODULE_DESCRIPTION("HP iPAQ 214 BlueTooth Chip Driver");
diff --git a/arch/arm/mach-pxa/hpipaq214-mfp.c b/arch/arm/mach-pxa/hpipaq214-mfp.c
new file mode 100644
index 0000000..4cdaaed
--- /dev/null
+++ b/arch/arm/mach-pxa/hpipaq214-mfp.c
@@ -0,0 +1,193 @@
+/*
+ * linux/arch/arm/mach-pxa/hpipaq214-mfp.c
+ *
+ * Multi-Function Pin Support for the Hewlett-Packard iPAQ 21x.
+ *
+ * 2008-06-19: Oliver Ford <ipaqcode-oliford-co-uk>
+ *
+ * These are all configured as I've found them elsewhere to ensure the suspend/resume works properly.
+ *
+ * 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/module.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+
+#include <asm/mach-types.h>
+#include <asm/mach/arch.h>
+#include <asm/hardware.h>
+#include <asm/arch/mfp-pxa300.h>
+
+#include "generic.h"
+#include "devices.h"
+
+#define MAX_SLOTS	3
+#define ARRAY_AND_SIZE(x)	(x), ARRAY_SIZE(x)
+
+
+/********************************************************
+* Multi Function Pin                                    *
+********************************************************/
+
+static mfp_cfg_t hpipaq214_mfp_cfg[] __initdata = {
+	/* FLASH */
+	MFP_CFG_X(DF_INT_RnB, AF0, DS10X, FLOAT),
+	MFP_CFG_X(DF_nWE, AF1, DS10X, PULL_HIGH),
+	MFP_CFG_X(DF_nRE_nOE, AF1, DS10X, PULL_HIGH),
+	MFP_CFG_DRV(nBE0, AF0, DS01X),
+	MFP_CFG_DRV(nBE1, AF0, DS01X),
+	MFP_CFG_X(DF_ALE_nWE, AF1, DS10X, PULL_LOW),
+	MFP_CFG_DRV(DF_ADDR0, AF0, DS01X),
+	MFP_CFG_DRV(DF_ADDR1, AF0, DS01X),
+	MFP_CFG_DRV(DF_ADDR2, AF0, DS01X),
+	MFP_CFG_DRV(DF_ADDR3, AF0, DS01X),
+	MFP_CFG_X(DF_IO0, AF1, DS08X, PULL_HIGH),
+	MFP_CFG_X(DF_IO8, AF1, DS08X, PULL_HIGH),
+	MFP_CFG_X(DF_IO1, AF1, DS08X, PULL_HIGH),
+	MFP_CFG_X(DF_IO9, AF1, DS08X, PULL_HIGH),
+	MFP_CFG_X(DF_IO2, AF1, DS08X, PULL_HIGH),
+	MFP_CFG_X(DF_IO10, AF1, DS08X, PULL_HIGH),
+	MFP_CFG_X(DF_IO3, AF1, DS08X, PULL_HIGH),
+	MFP_CFG_X(DF_IO11, AF1, DS08X, PULL_HIGH),
+	MFP_CFG_X(DF_CLE_nOE, AF0, DS10X, PULL_LOW),
+	MFP_CFG_DRV(nLUA, AF0, DS01X),
+	MFP_CFG_X(DF_nCS0, AF1, DS10X, PULL_HIGH),
+	MFP_CFG_DRV(nLLA, AF0, DS01X),
+	MFP_CFG_X(DF_IO4, AF1, DS08X, PULL_HIGH),
+	MFP_CFG_X(DF_IO12, AF1, DS08X, PULL_HIGH),
+	MFP_CFG_X(DF_IO5, AF1, DS08X, PULL_HIGH),
+	MFP_CFG_X(DF_IO13, AF1, DS08X, PULL_HIGH),
+	MFP_CFG_X(DF_IO6, AF1, DS08X, PULL_HIGH),
+	MFP_CFG_X(DF_IO14, AF1, DS08X, PULL_HIGH),
+	MFP_CFG_X(DF_IO7, AF1, DS08X, PULL_HIGH),
+	MFP_CFG_X(DF_IO15, AF1, DS08X, PULL_HIGH),
+	MFP_CFG_X(DF_nCS1, AF1, DS06X, PULL_HIGH),
+
+	/* LCD */	
+	MFP_CFG_DRV(GPIO54, AF1, DS03X),	//GPIO54_LCD_LDD_0,
+	MFP_CFG_DRV(GPIO55, AF1, DS03X),	//GPIO55_LCD_LDD_1,
+	MFP_CFG_DRV(GPIO56, AF1, DS03X),	//GPIO56_LCD_LDD_2,
+	MFP_CFG_DRV(GPIO57, AF1, DS03X),	//GPIO57_LCD_LDD_3,
+	MFP_CFG_DRV(GPIO58, AF1, DS03X),	//GPIO58_LCD_LDD_4,
+	MFP_CFG_DRV(GPIO59, AF1, DS03X),	//GPIO59_LCD_LDD_5,
+	MFP_CFG_DRV(GPIO60, AF1, DS03X),	//GPIO60_LCD_LDD_6,
+	MFP_CFG_DRV(GPIO61, AF1, DS03X),	//GPIO61_LCD_LDD_7,
+	MFP_CFG_DRV(GPIO62, AF1, DS03X),	//GPIO62_LCD_LDD_8,
+	MFP_CFG_DRV(GPIO63, AF1, DS03X),	//GPIO63_LCD_LDD_9,
+	MFP_CFG_DRV(GPIO64, AF1, DS03X),	//GPIO64_LCD_LDD_10,
+	MFP_CFG_DRV(GPIO65, AF1, DS03X),	//GPIO65_LCD_LDD_11,
+	MFP_CFG_DRV(GPIO66, AF1, DS03X),	//GPIO66_LCD_LDD_12,
+	MFP_CFG_DRV(GPIO67, AF1, DS03X),	//GPIO67_LCD_LDD_13,
+	MFP_CFG_DRV(GPIO68, AF1, DS03X),	//GPIO68_LCD_LDD_14,
+	MFP_CFG_DRV(GPIO69, AF1, DS03X),	//GPIO69_LCD_LDD_15,
+//	GPIO70_LCD_LDD_16, // 16-bit LCD and these are used for something else
+//	GPIO71_LCD_LDD_17, 
+	MFP_CFG_DRV(GPIO72, AF1, DS03X),	//GPIO72_LCD_FCLK,
+	GPIO73_LCD_LCLK,
+	GPIO74_LCD_PCLK,
+	GPIO75_LCD_BIAS,
+	GPIO76_LCD_VSYNC,
+	GPIO127_LCD_CS_N,
+
+	// KEYPAD
+	// right config for wake up source?
+ 	GPIO121_KP_MKOUT_0,	//MFP_CFG_LPM(GPIO121, AF1, DRIVE_HIGH)	Y
+	GPIO122_KP_MKOUT_1,	//MFP_CFG_LPM(GPIO122, AF1, DRIVE_HIGH)	Y
+	GPIO123_KP_MKOUT_2,	//MFP_CFG_LPM(GPIO123, AF1, DRIVE_HIGH)	Y
+	GPIO115_KP_MKIN_0 | MFP_LPM_EDGE_BOTH,	//MFP_CFG_LPM(GPIO115, AF1, FLOAT)	Y
+	GPIO116_KP_MKIN_1 | MFP_LPM_EDGE_BOTH,	//MFP_CFG_LPM(GPIO116, AF1, FLOAT)	Y
+	GPIO117_KP_MKIN_2 | MFP_LPM_EDGE_BOTH,	//MFP_CFG_LPM(GPIO117, AF1, FLOAT)	Y
+	GPIO118_KP_MKIN_3 | MFP_LPM_EDGE_BOTH,	//MFP_CFG_LPM(GPIO118, AF1, FLOAT)	Y 
+
+
+	//. AC97
+	GPIO23_AC97_nACRESET,
+//	GPIO24_AC97_SYSCLK,	//doesn't appear to be a sysclk
+	GPIO29_AC97_BITCLK,
+	GPIO25_AC97_SDATA_IN_0,
+	GPIO27_AC97_SDATA_OUT,
+	GPIO28_AC97_SYNC,
+
+	// MMC1 - config DRIVE_LOW for D3
+	MFP_CFG_X(GPIO3, AF4, DS03X, DRIVE_LOW),	// GPIO3_MMC1_DAT0 
+	MFP_CFG_X(GPIO4, AF4, DS03X, DRIVE_LOW),	// GPIO3_MMC1_DAT1
+	MFP_CFG_X(GPIO5, AF4, DS03X, DRIVE_LOW),	// GPIO3_MMC1_DAT2
+	MFP_CFG_X(GPIO6, AF4, DS03X, DRIVE_LOW),	// GPIO3_MMC1_DAT3
+	MFP_CFG_X(GPIO7, AF4, DS03X, DRIVE_LOW),	// GPIO7_MMC1_CLK
+	MFP_CFG_X(GPIO8, AF4, DS03X, DRIVE_LOW),	// GPIO8_MMC1_CMD
+ 	
+	// MMC2: SDIO Marvell 8686 Wifi (libertas) - force pull high in D0, drive low in D3
+	MFP_CFG_X(GPIO9, AF4, DS13X, DRIVE_LOW) | MFP_PULL_HIGH,		// GPIO9_MMC2_DAT0
+	MFP_CFG_X(GPIO10, AF4, DS13X, DRIVE_LOW) | MFP_PULL_HIGH,		// GPIO10_MMC2_DAT1
+	MFP_CFG_X(GPIO11, AF4, DS13X, DRIVE_LOW) | MFP_PULL_HIGH,		// GPIO11_MMC2_DAT2
+	MFP_CFG_X(GPIO12, AF4, DS13X, DRIVE_LOW) | MFP_PULL_HIGH,		// GPIO12_MMC2_DAT3
+	MFP_CFG_X(GPIO13, AF4, DS13X, DRIVE_LOW) | MFP_PULL_HIGH,		// GPIO13_MMC2_CLK
+	MFP_CFG_X(GPIO14, AF4, DS13X, DRIVE_LOW) | MFP_PULL_HIGH,		// GPIO14_MMC2_CMD
+
+	//PWM
+	GPIO19_PWM2_OUT,	// Backlight drive - PWM2 	
+
+	//Unknown GPIOs used to turn the BCM2045 BlueTooth chip on
+	MFP_CFG_DRV(GPIO53, AF0, DS03X),
+	MFP_CFG_DRV(GPIO71, AF0, DS03X),
+	MFP_CFG_DRV(GPIO81, AF0, DS03X),
+	MFP_CFG_DRV(GPIO114, AF0, DS01X),
+	MFP_CFG_DRV(GPIO124, AF0, DS03X),
+
+	// Misc GPIOs
+	MFP_CFG_DRV(GPIO3_2, AF0, DS04X),	// The BLUE LED (Bluetooth/wifi on light) 
+	MFP_CFG_DRV(GPIO5_2, AF0, DS04X),	// The green LED in same place as blue one
+	MFP_CFG_DRV(GPIO89, AF0, DS03X),	// Pen-down IRQ for the touchscreen device
+	GPIO15_GPIO,				// MMC1 Card Detect	
+	GPIO102_GPIO,				// MMC1 Write Protect
+	MFP_CFG_DRV(GPIO104, AF0, DS13X),	// WM9713 Power?
+	MFP_CFG_DRV(GPIO96, AF0, DS13X),	// Audio on/off
+
+
+	//Unused but wired, need to get LPM config right (set for D3)
+	GPIO30_USB_P2_2 | MFP_LPM_PULL_LOW,
+	GPIO31_USB_P2_6  | MFP_LPM_PULL_LOW,
+	GPIO32_USB_P2_4 | MFP_LPM_PULL_LOW,
+	GPIO33_ULPI_OTG_INTR | MFP_LPM_PULL_LOW,
+	GPIO34_USB_P2_5 | MFP_LPM_PULL_LOW,
+	GPIO35_USB_P2_3 | MFP_LPM_PULL_LOW,
+	GPIO36_UTM_PHYDATA_6 | MFP_LPM_PULL_LOW,
+	GPIO37_UTM_PHYDATA_7 | MFP_LPM_PULL_LOW,
+	GPIO38_UTM_CLK  | MFP_LPM_DRIVE_LOW,
+
+/*	//Quick capture, unused but should set for LPM config
+	GPIO46_CI_DD_7,	
+	GPIO49_CI_MCLK,
+	GPIO50_CI_PCLK,
+	GPIO51_CI_VSYNC,
+	GPIO52_CI_HSYNC,
+*/
+	GPIO126_OW_DQ | MFP_LPM_FLOAT,
+	MFP_CFG_X(GPIO109, AF1, DS01X, DRIVE_LOW), // GPIO109_UART3_TXD but with drive low in D3
+	MFP_CFG_X(GPIO110, AF1, DS01X, DRIVE_LOW), // GPIO110_UART3_RXD but with drive low in D3
+
+	//SSP and GPIO112 used for LCD up/down code
+	GPIO85_SSP1_SCLK,
+	GPIO86_SSP1_FRM,
+	GPIO87_SSP1_TXD,
+	GPIO88_SSP1_RXD,
+	GPIO112_GPIO,	//use in LCD power up/down
+
+	// FFUART used for bluetooth
+	GPIO77_UART1_RXD,
+	GPIO78_UART1_TXD,
+	GPIO79_UART1_CTS, 
+	GPIO84_UART1_RTS, 
+//*/
+
+};
+
+
+void hpipaq214_init_mfp(void){
+
+	pxa3xx_mfp_config(ARRAY_AND_SIZE(hpipaq214_mfp_cfg));
+}
diff --git a/arch/arm/mach-pxa/hpipaq214.c b/arch/arm/mach-pxa/hpipaq214.c
new file mode 100644
index 0000000..a4ffe90
--- /dev/null
+++ b/arch/arm/mach-pxa/hpipaq214.c
@@ -0,0 +1,573 @@
+/*
+ * linux/arch/arm/mach-pxa/hpipaq214.c
+ *
+ * Support for the Hewlett-Packard iPAQ214. (Also 210, probably all 21x)
+ *
+ * 2008-08-03: Alex Meakins/Oliver Ford <ipaqcode-oliford-co-uk>
+ *		Derived from zylonite.c and zylonite_pxa300.c	
+ *
+ * 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/module.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/pwm_backlight.h>
+#include <linux/clk.h>
+
+#include <asm/gpio.h>
+#include <asm/mach-types.h>
+#include <asm/mach/arch.h>
+#include <asm/hardware.h>
+#include <asm/arch/gpio.h>
+#include <asm/arch/pxafb.h>
+#include <asm/arch/mmc.h>
+#include <asm/arch/ssp.h>
+#include <asm/arch/pxa3xx_nand.h>
+#include <asm/arch/pxa27x_keypad.h>
+#include <linux/delay.h>
+
+#include "generic.h"
+#include "devices.h"
+
+#define MAX_SLOTS	3
+#define ARRAY_AND_SIZE(x)	(x), ARRAY_SIZE(x)
+
+extern void hpipaq214_init_mfp(void);		// defined in hpipaq214-mfp.c
+
+
+/********************************************************
+* Platform devices                                      *
+********************************************************/
+
+//The AC97 Controller in the PXA 
+static struct platform_device hpipaq214_ac97_device = {
+	.name		= "pxa2xx-ac97",
+	.id		= -1,
+};
+
+//PCM Audio - not sure about this yet
+static struct platform_device hpipaq214_pxa2xx_pcm = {
+	.name		= "pxa2xx-pcm",
+	.id		= -1,
+};
+// (pxa2xx-ac97 and pxa2xx-pcm may appear in mach-pxa/devices.c at some point)
+
+//Audio device for the WM9713 chip on the AC97 bus
+static struct platform_device hpipaq214_wm9713_codec = {
+	.name		= "wm9713-codec",
+	.id		= -1,
+	.dev		= {
+		.parent		= &hpipaq214_ac97_device.dev,
+	},
+};
+
+static struct platform_device hpipaq214_bluetooth = {
+	.name		= "hpipaq214-bluetooth",
+	.id		= -1,
+};
+
+//Platform device for audio 
+static struct platform_device hpipaq214_sound_fabric_device = {
+	.name		= "hpipaq214-audio",
+	.id		= -1,
+};
+
+// The touchscreen platform device is added by wm97xx-core.c and driven by 
+//	drivers/input/touchscreen/hpipaq214-wm97xx.c
+
+//Other devices are added by arch/arm/mach-pxa/devices.c because we have a PXA3xx
+static struct platform_device *hpipaq214_devices[] __initdata = {
+	&hpipaq214_pxa2xx_pcm,
+	&hpipaq214_ac97_device,
+	&hpipaq214_wm9713_codec,
+	&hpipaq214_sound_fabric_device,
+	&hpipaq214_bluetooth,
+};
+
+
+
+/****************************
+* Keypad                    *
+****************************/
+#if defined(CONFIG_KEYBOARD_PXA27x) || defined(CONFIG_KEYBOARD_PXA27x_MODULES)
+static unsigned int hpipaq214_matrix_key_map[] = {
+
+	/* KEY(row, col, key_code) */
+	KEY(2, 0, KEY_0),	//Func key A - left most
+	KEY(3, 0, KEY_1),	//Func key B
+	KEY(1, 0, KEY_2),	//Func key C
+	KEY(1, 1, KEY_3),	//Func key D - right most
+	KEY(0, 2, KEY_4),	//The record button
+	
+	KEY(0, 0, KEY_5),	//Unknown or N/C 1
+	KEY(0, 1, KEY_6),	//Unknown or N/C 2
+		
+	KEY(1, 2, KEY_LEFT),
+	KEY(2, 1, KEY_UP),
+	KEY(2, 2, KEY_RIGHT),
+	KEY(3, 1, KEY_DOWN),
+	KEY(3, 2, KEY_ENTER),	//Nav centre key
+	
+};
+
+static struct pxa27x_keypad_platform_data hpipaq214_keypad_info = {
+	.matrix_key_rows	= 4,
+	.matrix_key_cols	= 3,
+	.matrix_key_map		= hpipaq214_matrix_key_map,
+	.matrix_key_map_size	= ARRAY_SIZE(hpipaq214_matrix_key_map),
+
+	.enable_rotary0		= 0,
+
+	.debounce_interval	= 30,
+};
+
+static void __init hpipaq214_init_keypad(void)
+{
+	pxa_set_keypad_info(&hpipaq214_keypad_info);
+}
+#else
+static inline void hpipaq214_init_keypad(void) {}
+#endif
+
+
+/********************************************************
+* Liquid Crystal Display                                *
+********************************************************/
+#if defined(CONFIG_FB_PXA) || defined(CONFIG_FB_PXA_MODULES)
+int gpio_lcd_setup = 112;
+
+// This must be sent down SSP1 before the LCD is enabled.
+static uint32_t ssp_data_on[] = {
+	0x00007003, 0x00007200, 0x00007000, 0x00007210, 
+	0x00007010, 0x000072af, 0x00007011, 0x00007200, 
+	0x00007012, 0x00007200, 0x00007013, 0x00007206, 
+	0x00007014, 0x00007206, 0x00007015, 0x00007270, 
+	0x00007016, 0x0000721f, 0x00007017, 0x00007207, 
+	0x00007018, 0x00007207, 0x00007019, 0x00007233, 
+	0x00007002, 0x00007200, 0x00007001, 0x00007210, 
+	0x00007005, 0x00007224, 0x00007006, 0x00007245, 
+	0x0000701a, 0x00007202, 0x0000701b, 0x00007251, 
+	0x00007020, 0x00007224, 0x00007021, 0x0000720b, 
+	0x00007022, 0x00007219, 0x00007023, 0x0000720c, 
+	0x00007024, 0x00007207, 0x00007025, 0x00007219, 
+	0x00007026, 0x00007213, 0x00007027, 0x00007210, 
+	0x00007028, 0x00007202, 0x00007029, 0x00007202, 
+	0x0000702a, 0x00007202, 0x0000702b, 0x00007204, 
+	0x0000702c, 0x0000720b, 0x0000702d, 0x00007219, 
+	0x0000702e, 0x0000720c, 0x0000702f, 0x00007207, 
+	0x00007030, 0x00007219, 0x00007031, 0x00007213, 
+	0x00007032, 0x00007210, 0x00007033, 0x00007202, 
+	0x00007034, 0x00007202, 0x00007035, 0x00007202, 
+	0x00007050, 0x00007202, 0x00007053, 0x00007200, 
+	0x00007054, 0x00007244, 0x00007055, 0x00007244, 
+	0x00007056, 0x0000720a, 0x00007059, 0x0000720b, 
+	0x0000705a, 0x00007204, 0x0000705b, 0x000072ca, 
+	0x0000705c, 0x00007204, 0x0000705d, 0x0000720a, 
+	0x0000705e, 0x00007210, 0x0000705f, 0x0000721d, 
+	0x00007062, 0x00007230, 0x00007063, 0x0000721d, 
+	0x00007066, 0x00007250, 0x00007067, 0x0000721d, 
+	0x00007070, 0x0000726d, 0x00007071, 0x00007214, 
+	0x00007076, 0x000072ff, 0x00007079, 0x00007202, 
+	0x00007000, 0x00007200
+};
+
+static uint32_t ssp_data_off[] = {
+	0x00007002, 0x00007201
+};
+
+static void ssp_send_sequence(uint32_t *data, int len){
+	int i,ret;
+	int lcd_ssp_port = 1; //1 based
+	static struct ssp_dev ssp;
+
+	ret = ssp_init(&ssp, lcd_ssp_port, SSP_NO_IRQ);	
+	if(ret){
+		printk("Error initialising the SSP port.\n");
+		return;
+	}
+		
+	//setup: 
+	//  CTRL 0: disabled, no interrupts, data size = 16bit, 
+	//  CTRL 1: Recv Threshold = 4, Trans Threshold = 8, SPH=1, SPO=1
+	//  SSPSP = ? = 0
+	//  speed = 0x081,  ( or'ed with CTRL1)
+	ssp_config(&ssp, 0x00c0000f, 0x00001218, 0x00000000, 0x00008100); 
+	ssp_enable(&ssp);
+
+	for(i=0;i<len;i++){
+		ret = ssp_write_word(&ssp, data[i]);
+		if(ret){
+			printk("ssp_write_word: Error (timeout) writing to SSP.\n");
+			return;
+		}
+		udelay(50); //delay 50us -probly not neccesary with the slightly better SSP comms
+		ssp_flush(&ssp);
+		if(ret){
+			printk("ssp_flush: Error (timeout) writing to SSP.\n");
+			return;
+		}
+	}
+	
+	ssp_exit(&ssp);	
+}
+
+static void hpipaq214_lcd_onoff(int on, struct fb_var_screeninfo *var)
+{	//presumably power, but who knows
+
+	if(gpio_lcd_setup < 0) //didn't get the GPIO, can't do anything
+		return;
+	
+	if (on) {
+		//gpio twiddle
+		gpio_set_value(gpio_lcd_setup, 1);
+		mdelay(2);
+		gpio_set_value(gpio_lcd_setup, 0);
+		mdelay(20);
+		gpio_set_value(gpio_lcd_setup, 1);
+		mdelay(20);
+
+		ssp_send_sequence(ssp_data_on, sizeof(ssp_data_on) / 4);
+		mdelay(250);
+
+	}else{
+ 		ssp_send_sequence(ssp_data_off, sizeof(ssp_data_off) / 4);
+		mdelay(50);
+	}	
+}
+
+static struct platform_pwm_backlight_data hpipaq214_backlight_data = {
+	.pwm_id		= 1,
+	.max_brightness	= 1023,
+	.dft_brightness	= 384,
+	.pwm_period_ns	= 10000,
+};
+
+static struct platform_device hpipaq214_backlight_device = {
+	.name		= "pwm-backlight",
+	.dev		= {
+		.parent = &pxa27x_device_pwm0.dev,
+		.platform_data	= &hpipaq214_backlight_data,
+	},
+};
+
+// values extracted violently from wince 5 
+static struct pxafb_mode_info hpipaq214_mode = {
+	//.pixclock		= 96153,
+	.pixclock		= 110000,
+	.xres			= 480,
+	.yres			= 640,
+	.bpp			= 16,
+	.hsync_len		= 7,
+	.left_margin		= 2,
+	.right_margin		= 6,
+	.vsync_len		= 7,
+	.upper_margin		= 4,
+	.lower_margin		= 3,
+	.sync			= 0,
+};
+
+static struct pxafb_mach_info hpipaq214_lcd_info = {
+	.modes			= &hpipaq214_mode,
+	.num_modes      	= 1,
+//	.lccr0			= LCCR0_Act,
+//	.lccr3			= LCCR3_PixFlEdg | LCCR3_OutEnH,
+	.lcd_conn		= LCD_COLOR_TFT_16BPP | LCD_PCLK_EDGE_FALL,
+
+	.pxafb_lcd_power	= hpipaq214_lcd_onoff,
+	//.pxafb_backlight_power	= hpipaq214_backlight_power,
+};
+
+static void hpipaq214_init_lcd(void)
+{	
+	int err;
+
+	platform_device_register(&hpipaq214_backlight_device);
+
+	err = gpio_request(gpio_lcd_setup, "LCD setup gpio");
+	if(err){
+		printk("Unable to register LCD setup gpio (%i)\n",gpio_lcd_setup);
+		gpio_lcd_setup = -1;
+	}else{		
+		gpio_direction_output(gpio_lcd_setup, 1); //keep it on for now!
+	}
+
+	set_pxa_fb_info(&hpipaq214_lcd_info);
+}
+#else
+static inline void hpipaq214_init_lcd(void) {};
+#endif /* CONFIG_FB_PXA || CONFIG_FB_PXA_MODULES */
+
+
+
+/********************************************************
+* Multi Media Card Reader                               *
+********************************************************/
+int gpio_mmc1_wp = 102; //write protect
+int gpio_mmc1_cd = 15; //card detect
+
+#if defined(CONFIG_MMC)
+static int hpipaq214_mci_ro(struct device *dev)
+{
+	//struct platform_device *pdev = to_platform_device(dev);
+
+	return gpio_get_value(gpio_mmc1_wp);
+}
+
+static int hpipaq214_mci_init(struct device *dev,
+			     irq_handler_t hpipaq214_detect_int,
+			     void *data)
+{
+	//struct platform_device *pdev = to_platform_device(dev);
+	int err, cd_irq;
+
+	cd_irq = gpio_to_irq(gpio_mmc1_cd);	
+
+	/*
+	 * setup GPIO for MMC controller
+	 */
+	err = gpio_request(gpio_mmc1_cd, "MMC card detect");
+	if (err)
+		goto err_request_cd;
+	gpio_direction_input(gpio_mmc1_cd);
+
+	err = gpio_request(gpio_mmc1_wp, "MMC write protect");
+	if (err)
+		goto err_request_wp;
+	gpio_direction_input(gpio_mmc1_wp);
+
+	err = request_irq(cd_irq, hpipaq214_detect_int,
+			  IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+			  "MMC card detect", data);
+	if (err) {
+		printk(KERN_ERR "%s: MMC/SD/SDIO: "
+				"can't request card detect IRQ\n", __func__);
+		goto err_request_irq;
+	}
+
+	return 0;
+
+err_request_irq:
+	gpio_free(gpio_mmc1_wp);
+err_request_wp:
+	gpio_free(gpio_mmc1_cd);
+err_request_cd:
+	return err;
+}
+
+static void hpipaq214_mci_exit(struct device *dev, void *data)
+{
+	//struct platform_device *pdev = to_platform_device(dev);
+	int cd_irq;
+
+	cd_irq = gpio_to_irq(gpio_mmc1_cd);
+	free_irq(cd_irq, data);
+	gpio_free(gpio_mmc1_cd);
+	gpio_free(gpio_mmc1_wp);
+} 
+
+//This stuff is almost certainly the power I2C. It's requried for powering up/down the MMC1 card.
+#define PWC4	__REG(0x40f500c4)
+#define PWC8	__REG(0x40f500c8)	
+#define PWCC	__REG(0x40f500cc)
+
+static int hpipaq214_pw_c0_wait_on_cc(void){
+	int i;
+	unsigned long l;
+	for(i=0;i<2500;i++){
+		l = PWCC;
+		
+		if( (l & 0x40) == 0x40 ){
+			PWCC = l | 0x40;
+			return 0; //done
+		}
+		mdelay(1);
+	}
+	return 1; //timeout
+}
+
+static int hpipaq214_pw_c0_send_byte(unsigned char byte1, unsigned char byte2){
+	unsigned long l;
+
+	printk("pwr_c0 sending %02x %02x... ",byte1,byte2);
+	PWC4 = 0x68;	
+	PWC8 = (PWC8 & ~0x0f) | 0x09;
+	if(hpipaq214_pw_c0_wait_on_cc())
+		goto timeout;
+
+	PWC4 = byte1;	
+	PWC8 = (PWC8 & ~0x03) | 0x08;
+	if(hpipaq214_pw_c0_wait_on_cc())
+		goto timeout;
+
+
+	PWC4 = byte2;	
+	l = (PWC8 & ~0x01) | 0x08 | 0x02;
+	/*if( Zero flag?? )
+		l |= 0x02;
+	else
+		l &= ~0x02;*/
+	PWC8 = l;
+	if(hpipaq214_pw_c0_wait_on_cc())
+		goto timeout;
+	
+	PWC8 = PWC8 & ~0x02;	
+
+	printk("OK\n");
+	return 0; //ok
+timeout:
+	printk("Timed out\n");
+	return 1;	
+}
+
+static void hpipaq214_mci_setpower(struct device *dev, unsigned int vdd){
+	int ret;
+	struct pxamci_platform_data* p_d = dev->platform_data;
+
+	printk("%s: requested voltage %i\n", __func__,vdd);
+	if (( 1 << vdd) & p_d->ocr_mask) {
+		printk("%s: on\n", __func__);
+		ret =  hpipaq214_pw_c0_send_byte(0x12,0x07);
+	} else {
+		printk("%s: off\n", __func__);
+		ret =  hpipaq214_pw_c0_send_byte(0x12,0x05);
+	}
+	if(ret)
+		printk("%s: power byte send failed\n", __func__);
+}
+
+static struct pxamci_platform_data hpipaq214_mci_platform_data = {
+	.detect_delay	= 20,
+	.ocr_mask	= MMC_VDD_32_33|MMC_VDD_33_34,
+	.init 		= hpipaq214_mci_init,
+	.exit		= hpipaq214_mci_exit,
+	.get_ro		= hpipaq214_mci_ro,
+	.setpower 	= hpipaq214_mci_setpower,
+};
+
+/********************************************************
+* MMC2: SDIO Wireless Connection                        *
+********************************************************/
+
+static int hpipaq214_mci2_init(struct device *dev,
+			     irq_handler_t hpipaq214_detect_int,
+			     void *data)
+{
+	/*gpio_request(4,"Wifi"); gpio_direction_output(4, 0);
+	gpio_request(97,"Wifi"); gpio_direction_output(97, 0);
+	gpio_request(99,"Wifi"); gpio_direction_output(99, 0);
+	gpio_request(100,"Wifi"); gpio_direction_output(100, 0);
+	gpio_request(103,"Wifi"); gpio_direction_output(103, 0);
+	*/
+	printk("mci2(wifi) init\n");
+	return 0;
+}
+
+static void hpipaq214_mci2_exit(struct device *dev, void *data)
+{
+	printk("mci2(wifi) exit\n");
+}
+
+static void hpipaq214_mci2_setpower(struct device *dev, unsigned int vdd){
+	printk("mci2(wifi) setpower %i\n",vdd);
+	
+	/*if( vdd <= 0){	//wifi off
+		gpio_set_value(100, 0);
+		gpio_set_value(97, 0);
+		gpio_set_value(4, 0);
+		gpio_set_value(99, 0);
+	}else{	//wifi on
+		gpio_set_value(103, 0);
+		gpio_set_value(103, 1);
+		gpio_set_value(99, 1);
+		mdelay(30);
+		gpio_set_value(103, 0);
+		gpio_set_value(100, 1);
+		gpio_set_value(97, 1);
+		mdelay(10);
+		gpio_set_value(103, 1);		
+	}	*/
+}
+
+static struct pxamci_platform_data hpipaq214_mci2_platform_data = {
+	.detect_delay	= 20,
+	.ocr_mask	= MMC_VDD_32_33|MMC_VDD_33_34,
+	.init 		= hpipaq214_mci2_init,
+	.exit		= hpipaq214_mci2_exit,	
+	.setpower 	= hpipaq214_mci2_setpower,
+};
+
+static void __init hpipaq214_init_mmc(void)
+{
+	pxa_set_mci_info(&hpipaq214_mci_platform_data);
+	pxa3xx_set_mci2_info(&hpipaq214_mci2_platform_data); //
+	//pxa3xx_set_mci3_info(&hpipaq214_mci2_platform_data); //don't have this
+	
+}
+#else
+static inline void hpipaq214_init_mmc(void) {}
+#endif
+
+//initlisation of stuff I don't actually know where to put yet
+static void __init hpipaq214_init_misc(void){
+	if(!gpio_request(3, "Blue LED (left)"))
+		gpio_direction_output(3, 0);
+	if(!gpio_request(5, "Green LED (left)"))
+		gpio_direction_output(5, 1); //turn it on to say we're alive!		
+}
+
+#if defined(CONFIG_MTD_NAND_PXA3xx) || defined(CONFIG_MTD_NAND_PXA3xx_MODULE)
+//This includes support for the FLASH. To avoid overwriting windows or the boot 
+//  ROM I currently only let is use the iPAQ File Store region (set here and forced
+//  in pxa3xx_nand.c). Windows will kill anything you put there when it boots 
+// but it serves to prove linux will successfully read from/write to/boot from it.
+static struct mtd_partition hpipaq214_nand_partitions[] = {
+	[0] = {
+		.name        = "FileStore",
+		.offset      = 0x0e380000,	//iPAQ File Store region at the moment
+		.size        = 0x1900000,
+		//.mask_flags  = MTD_WRITEABLE, // force read-only
+	},
+};
+
+static struct pxa3xx_nand_platform_data hpipaq214_nand_info = {
+	.enable_arbiter	= 1,
+	.parts		= hpipaq214_nand_partitions,
+	.nr_parts	= ARRAY_SIZE(hpipaq214_nand_partitions),
+};
+
+static void __init hpipaq214_init_nand(void)
+{
+	pxa3xx_set_nand_info(&hpipaq214_nand_info);
+}
+#else
+static inline void hpipaq214_init_nand(void) {}
+#endif /* CONFIG_MTD_NAND_PXA3xx || CONFIG_MTD_NAND_PXA3xx_MODULE */
+
+
+static void __init hpipaq214_init(void)
+{	
+	hpipaq214_init_mfp();
+	hpipaq214_init_misc();
+	hpipaq214_init_lcd();	
+        hpipaq214_init_mmc();
+	hpipaq214_init_keypad();
+	hpipaq214_init_nand();
+	
+	//add the various platform devices
+	platform_add_devices(hpipaq214_devices, ARRAY_SIZE(hpipaq214_devices));		
+}
+
+MACHINE_START(HPIPAQ214, "Hewlett-Packard iPAQ 214")
+	.phys_io	= 0x40000000,
+	.boot_params	= 0xa0000100,
+	.io_pg_offst	= (io_p2v(0x40000000) >> 18) & 0xfffc,
+	.map_io		= pxa_map_io,
+	.init_irq	= pxa3xx_init_irq,
+	.timer		= &pxa_timer,
+	.init_machine	= hpipaq214_init,
+MACHINE_END
diff --git a/arch/arm/mach-pxa/pxa3xx.c b/arch/arm/mach-pxa/pxa3xx.c
index d26a9b0..59786a5 100644
--- a/arch/arm/mach-pxa/pxa3xx.c
+++ b/arch/arm/mach-pxa/pxa3xx.c
@@ -245,6 +245,8 @@ static struct clk pxa3xx_clks[] = {
 	PXA3xx_CKEN("MMCCLK", MMC1, 19500000, 0, &pxa_device_mci.dev),
 	PXA3xx_CKEN("MMCCLK", MMC2, 19500000, 0, &pxa3xx_device_mci2.dev),
 	PXA3xx_CKEN("MMCCLK", MMC3, 19500000, 0, &pxa3xx_device_mci3.dev),
+
+	PXA3xx_CKEN("NANDCLK", NAND, 156000000, 0, &pxa3xx_device_nand.dev),
 };
 
 #ifdef CONFIG_PM
@@ -292,12 +294,16 @@ static void pxa3xx_cpu_standby(unsigned int pwrmode)
 	extern const char pm_enter_standby_start[], pm_enter_standby_end[];
 	void (*fn)(unsigned int) = (void __force *)(sram + 0x8000);
 
+#ifdef CONFIG_MACH_HPIPAQ214
+	CKENA |= (1 << CKEN_ISC);	//make sure SRAM clock is on, winCE turns it off
+#endif
+
 	memcpy_toio(sram + 0x8000, pm_enter_standby_start,
 		    pm_enter_standby_end - pm_enter_standby_start);
 
 	AD2D0SR = ~0;
 	AD2D1SR = ~0;
-	AD2D0ER = wakeup_src;
+	AD2D0ER = wakeup_src | ADXER_WEXTWAKE0 | ADXER_WEXTWAKE1;
 	AD2D1ER = 0;
 	ASCR = ASCR;
 	ARSR = ARSR;
@@ -319,19 +325,35 @@ static void pxa3xx_cpu_standby(unsigned int pwrmode)
  */
 static void pxa3xx_cpu_pm_suspend(void)
 {
-	volatile unsigned long *p = (volatile void *)0xc0000000;
+
+#ifdef CONFIG_MACH_HPIPAQ214
+	volatile unsigned long *resumePtr =  phys_to_virt(0xa0000800);
+	volatile unsigned long *resumeDataPtr = phys_to_virt(0xa0000804);
+	unsigned long saved1 = *resumePtr;
+	unsigned long saved2 = *resumeDataPtr;
+
+	unsigned long resumeData[512];	
+	int i;
+#else
+	volatile unsigned long *p = (volatile void *)0xc0000000;	
 	unsigned long saved_data = *p;
+#endif
 
 	extern void pxa3xx_cpu_suspend(void);
 	extern void pxa3xx_cpu_resume(void);
 
 	/* resuming from D2 requires the HSIO2/BOOT/TPM clocks enabled */
+#ifdef CONFIG_MACH_HPIPAQ214	
+	//ipaq boot rom deploys into SRAM, and windows switches it off
+	CKENA |= (1 << CKEN_BOOT) | (1 << CKEN_TPM) | (1 << CKEN_ISC);
+#else
 	CKENA |= (1 << CKEN_BOOT) | (1 << CKEN_TPM);
+#endif	
 	CKENB |= 1 << (CKEN_HSIO2 & 0x1f);
 
 	/* clear and setup wakeup source */
 	AD3SR = ~0;
-	AD3ER = wakeup_src;
+	AD3ER = wakeup_src | ADXER_WEXTWAKE0 | ADXER_WEXTWAKE1;
 	ASCR = ASCR;
 	ARSR = ARSR;
 
@@ -340,12 +362,27 @@ static void pxa3xx_cpu_pm_suspend(void)
 
 	PSPR = 0x5c014000;
 
+#ifdef CONFIG_MACH_HPIPAQ214
+	//there is a bunch of stuff that is needed by the boot ROM on the ipaq214
+	*resumePtr = virt_to_phys(pxa3xx_cpu_resume);
+	*resumeDataPtr = virt_to_phys(resumeData);
+	//It checks
+	resumeData[204] = 3; //specifically, but...
+	for(i=0;i<512;i++) //just for luck
+		resumeData[i] = 3;
+#else
 	/* overwrite with the resume address */
 	*p = virt_to_phys(pxa3xx_cpu_resume);
-
+#endif 
+	
 	pxa3xx_cpu_suspend();
-
+	
+#ifdef CONFIG_MACH_HPIPAQ214
+	*resumePtr = saved1;
+	*resumeDataPtr = saved2;
+#else
 	*p = saved_data;
+#endif
 
 	AD3ER = 0;
 }
@@ -357,7 +394,7 @@ static void pxa3xx_cpu_pm_enter(suspend_state_t state)
 	 */
 	if (wakeup_src == 0) {
 		printk(KERN_ERR "Not suspending: no wakeup sources\n");
-		return;
+		//return;
 	}
 
 	switch (state) {
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 565ec71..ab2c437 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -226,6 +226,19 @@ config TOUCHSCREEN_WM9713
 
 	  If unsure, say N.
 
+config TOUCHSCREEN_WM97XX_HPIPAQ214
+	tristate "WM9713 touchscreen for HP iPAQ 214"
+	depends on TOUCHSCREEN_WM97XX && MACH_HPIPAQ214
+	help
+	  Say Y here for support for the WM9713 touchscreen on the HP iPAQ 214.
+
+	  If unsure, say N.
+
+#
+#	It may well work as a module but not tested yet.
+#	  To compile this driver as a module, choose M here: the module will be
+#	  called ???.
+
 config TOUCHSCREEN_WM97XX_MAINSTONE
 	tristate "WM97xx Mainstone accelerated touch"
 	depends on TOUCHSCREEN_WM97XX && ARCH_PXA
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 3c096d7..7386266 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -25,4 +25,5 @@ obj-$(CONFIG_TOUCHSCREEN_WM97XX)	+= wm97xx-ts.o
 wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9705)	+= wm9705.o
 wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9712)	+= wm9712.o
 wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9713)	+= wm9713.o
+obj-$(CONFIG_TOUCHSCREEN_WM97XX_HPIPAQ214)	+= hpipaq214-ts.o
 obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE)	+= mainstone-wm97xx.o
diff --git a/drivers/input/touchscreen/hpipaq214-ts.c b/drivers/input/touchscreen/hpipaq214-ts.c
new file mode 100644
index 0000000..93cc87d
--- /dev/null
+++ b/drivers/input/touchscreen/hpipaq214-ts.c
@@ -0,0 +1,403 @@
+/*
+ * Machine specific driver for the HP iPAQ 214's Wolfson WM9713 AC97 codecs.
+ * Oliver Ford - <ipaqcode-oliford-co-uk>
+ *
+ * Mostly copied from the Mainstone maintone-wm97xx.c
+ *
+ * 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.
+ *
+ * Notes:
+ *     This is a wm97xx extended touch driver to capture touch
+ *     data in a continuous manner on the Intel XScale archictecture
+ *
+  *      - codec: WM9713
+ *       - processor: Intel XScale3 PXA310
+ *
+ */
+
+#define DEBUG
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/wm97xx.h>
+#include <linux/io.h>
+#include <asm/arch/gpio.h>
+#include <asm/arch/pxa-regs.h>
+
+#define VERSION		"0.13"
+
+
+#define IPAQ214_PENDOWN_IRQ_GPIO	89
+// define USE_IRQ
+
+
+struct continuous {
+	u16 id;    /* codec id */
+	u8 code;   /* continuous code */
+	u8 reads;  /* number of coord reads per read cycle */
+	u32 speed; /* number of coords per second */
+};
+
+#define WM_READS(sp) ((sp / HZ) + 1)
+
+static const struct continuous cinfo[] = {	//we only support the WM9713 for the iPAQ 214
+	{WM9713_ID2, 0, WM_READS(94), 94},	
+	{WM9713_ID2, 1, WM_READS(120), 120},
+	{WM9713_ID2, 2, WM_READS(154), 154},
+	{WM9713_ID2, 3, WM_READS(188), 188},
+};
+
+/* continuous speed index */
+static int sp_idx;
+static u16 last, tries;
+
+/*
+ * Pen sampling frequency (Hz) in continuous mode.
+ */
+static int cont_rate = 200;
+module_param(cont_rate, int, 0);
+MODULE_PARM_DESC(cont_rate, "Sampling rate in continuous mode (Hz)");
+
+/*
+ * Pen down detection.
+ *
+ * This driver can either poll or use an interrupt to indicate a pen down
+ * event. If the irq request fails then it will fall back to polling mode.
+ */
+static int pen_int = 1;
+module_param(pen_int, int, 0);
+MODULE_PARM_DESC(pen_int, "Pen down detection (1 = interrupt, 0 = polling)");
+
+/*
+ * Pressure readback.
+ *
+ * Set to 1 to read back pen down pressure
+ */
+static int pressure;
+module_param(pressure, int, 0);
+MODULE_PARM_DESC(pressure, "Pressure readback (1 = pressure, 0 = no pressure)");
+
+/*
+ * AC97 touch data slot.
+ *
+ * Touch screen readback data ac97 slot
+ */
+static int ac97_touch_slot = 5;
+module_param(ac97_touch_slot, int, 0);
+MODULE_PARM_DESC(ac97_touch_slot, "Touch screen data slot AC97 number");
+
+
+/* flush AC97 slot 5 FIFO on pxa machines */
+#ifdef CONFIG_PXA27x
+static void wm97xx_acc_pen_up(struct wm97xx *wm)
+{
+	dev_dbg(wm->dev,"wm97xx_acc_pen_up (PXA).\n");
+	schedule_timeout_uninterruptible(1);
+
+	while (MISR & (1 << 2))
+		MODR;
+}
+#else
+static void wm97xx_acc_pen_up(struct wm97xx *wm)
+{
+	int count = 16;
+	schedule_timeout_uninterruptible(1);
+	dev_dbg(wm->dev,"wm97xx_acc_pen_up (nonPXA).\n");
+
+	while (count < 16) {
+		MODR;
+		count--;
+	}
+}
+#endif
+
+/* pinched from Atmel at32-wm97xx.c */
+/*static irqreturn_t hpipaq214_wm97xx_interrupt(int irq, void *dev_id)
+{
+	//struct hpipaq214_wm97xx *hpipaq214_wm97xx = dev_id;
+	//struct wm97xx *wm = hpipaq214_wm97xx->wm;
+	//struct wm97xx *wm = dev_id;
+	int retval = IRQ_NONE;
+
+	retval = IRQ_HANDLED;
+	//dev_dbg(wm->dev, "IPAQ214_TOUCH_IRQ\n");
+	printk("I");
+		
+	int status = ac97c_readl(hpipaq214_wm97xx, CBSR);
+	int retval = IRQ_NONE;
+
+	if (status & AC97C_OVRUN) {
+		dev_dbg(&wm->touch_dev->dev, "AC97C overrun\n");
+		ac97c_readl(hpipaq214_wm97xx, CBRHR);
+		retval = IRQ_HANDLED;
+	} else if (status & AC97C_RXRDY) {
+		u16 data;
+		u16 value;
+		u16 source;
+		u16 pen_down;
+
+		data = ac97c_readl(hpipaq214_wm97xx, CBRHR);
+		value = data & 0x0fff;
+		source = data & WM97XX_ADCSRC_MASK;
+		pen_down = (data & WM97XX_PEN_DOWN) >> 8;
+
+		if (source == WM97XX_ADCSEL_X)
+			hpipaq214_wm97xx->x = value;
+		if (source == WM97XX_ADCSEL_Y)
+			hpipaq214_wm97xx->y = value;
+
+		if (!pressure && source == WM97XX_ADCSEL_Y) {
+			input_report_abs(wm->input_dev, ABS_X, hpipaq214_wm97xx->x);
+			input_report_abs(wm->input_dev, ABS_Y, hpipaq214_wm97xx->y);
+			input_report_abs(wm->input_dev, ABS_PRESSURE, pen_down);
+			input_sync(wm->input_dev);
+		} else if (pressure && source == WM97XX_ADCSEL_PRES) {
+			input_report_abs(wm->input_dev, ABS_X, hpipaq214_wm97xx->x);
+			input_report_abs(wm->input_dev, ABS_Y, hpipaq214_wm97xx->y);
+			input_report_abs(wm->input_dev, ABS_PRESSURE, value);
+			input_sync(wm->input_dev);
+		}
+
+		retval = IRQ_HANDLED;
+	}
+
+	return retval;
+}*/
+
+static int wm97xx_acc_pen_down(struct wm97xx *wm)
+{
+	u16 x, y, p = 0x100 | WM97XX_ADCSEL_PRES;
+	int reads = 0;
+
+//	dev_dbg(wm->dev,"wm97xx_acc_pen_down.\n");	
+//	return RC_PENUP;
+
+	//pretend we did something
+	
+	/* When the AC97 queue has been drained we need to allow time
+	 * to buffer up samples otherwise we end up spinning polling
+	 * for samples.  The controller can't have a suitably low
+	 * threashold set to use the notifications it gives.
+	 */
+	schedule_timeout_uninterruptible(1);
+
+	if (tries > 5) {
+		tries = 0;
+		return RC_PENUP;
+	}
+
+	x = MODR;
+	if (x == last) {
+		tries++;
+		return RC_AGAIN;
+	}
+	last = x;
+	do {
+		if (reads)
+			x = MODR;
+		y = MODR;
+		if (pressure)
+			p = MODR;
+
+		/* are samples valid */
+		if ((x & WM97XX_ADCSRC_MASK) != WM97XX_ADCSEL_X ||
+		    (y & WM97XX_ADCSRC_MASK) != WM97XX_ADCSEL_Y ||
+		    (p & WM97XX_ADCSRC_MASK) != WM97XX_ADCSEL_PRES)
+			goto up;
+
+		/* coordinate is good */
+		tries = 0;
+		input_report_abs(wm->input_dev, ABS_X, x & 0xfff);
+		input_report_abs(wm->input_dev, ABS_Y, y & 0xfff);
+		input_report_abs(wm->input_dev, ABS_PRESSURE, p & 0xfff);
+		input_sync(wm->input_dev);
+		reads++;
+	} while (reads < cinfo[sp_idx].reads);
+up:
+	return RC_PENDOWN | RC_AGAIN;
+}
+
+static int wm97xx_acc_startup(struct wm97xx *wm)
+{
+	int idx = 0;
+#ifdef USE_IRQ
+	int err;
+#endif
+
+	dev_dbg(wm->dev,"wm97xx_acc_startup.\n");
+	//return 0;
+
+	/* check we have a codec */
+	if (wm->ac97 == NULL){
+		dev_err(wm->dev,"No AC97 codec");
+		return -ENODEV;
+	}
+
+	/* Go you big red fire engine */
+	for (idx = 0; idx < ARRAY_SIZE(cinfo); idx++) {
+		if (wm->id != cinfo[idx].id)
+			continue;
+		sp_idx = idx;
+		if (cont_rate <= cinfo[idx].speed)
+			break;
+	}
+
+	wm->acc_rate = cinfo[sp_idx].code;
+	wm->acc_slot = ac97_touch_slot;
+	dev_info(wm->dev,
+		 "mainstone accelerated touchscreen driver, %d samples/sec\n",
+		 cinfo[sp_idx].speed);
+
+	/* codec specific irq config */
+	if (pen_int) {
+		if(wm->id != WM9713_ID2){
+			dev_err(wm->dev,"Expecting a WM9713 touchscreen codec.\n");
+			pen_int = 0;
+			return 1;
+		}
+
+		/* enable pen down interrupt */
+		/* use PEN_DOWN WM97_GPIO 13 to assert IRQ (output) on WM97_GPIO line 2 */
+
+#ifdef USE_IRQ
+		wm->pen_irq = gpio_to_irq(IPAQ214_PENDOWN_IRQ_GPIO);
+		
+
+		/* copied this from arch/arm/mach-pxa/zylonite.c MMC card detect code 
+		   The PXA's GPIO89 appears to be wired to IRQ output of WM9713 for use as interrupt. 
+			... I think.
+		*/
+		err = gpio_request(IPAQ214_PENDOWN_IRQ_GPIO, "WM9713 touch pendown.\n");
+		if (err){
+			dev_err(wm->dev,"Failed request for GPIO89 for pendown interrupt: %i\n",err);
+			return 1;
+		}
+
+		gpio_direction_input(IPAQ214_PENDOWN_IRQ_GPIO);
+		/* wm79xx-core.c does this
+			err = request_irq(wm->pen_irq, hpipaq214_wm97xx_interrupt,
+				IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+				"WM9713-touch-pendown", wm);
+
+		if (err) {
+			dev_err(wm->dev,"Failed request for pendown IRQ: %i",err);
+			return 1;
+		}*/
+		wm97xx_config_gpio(wm, WM97XX_GPIO_13, WM97XX_GPIO_IN,
+				   WM97XX_GPIO_POL_HIGH,
+				   WM97XX_GPIO_STICKY,
+				   WM97XX_GPIO_WAKE);
+		wm97xx_config_gpio(wm, WM97XX_GPIO_2, WM97XX_GPIO_OUT,
+				   WM97XX_GPIO_POL_HIGH,
+				   WM97XX_GPIO_NOTSTICKY,
+				   WM97XX_GPIO_NOWAKE);
+
+		dev_dbg(wm->dev,"IRQ Setup complete.\n");
+#else 
+	wm->pen_irq = 0; //make sure wm97xx-core.c doesn't try anything
+#endif
+	}
+	return 0;
+}
+
+static void wm97xx_acc_shutdown(struct wm97xx *wm)
+{
+	dev_dbg(wm->dev,"wm97xx_acc_shutdown.\n");
+	/* codec specific deconfig */
+	if (pen_int) {
+		switch (wm->id & 0xffff) {
+		case WM9705_ID2:
+			wm->pen_irq = 0;
+			break;
+		case WM9712_ID2:
+		case WM9713_ID2:
+			/* disable interrupt */
+			wm->pen_irq = 0;
+			break;
+		}
+	}
+}
+
+
+#ifdef USE_IRQ
+static void wm97xx_irq_enable(struct wm97xx *wm, int enable)
+{
+	dev_dbg(wm->dev,"wm97xx_irq_enable(%i).\n",enable);
+	if (enable)
+		enable_irq(wm->pen_irq);
+	else
+		disable_irq(wm->pen_irq);
+}
+#endif
+
+static struct wm97xx_mach_ops hpipaq214_mach_ops = {
+	.acc_enabled = 1,			//for now
+	.acc_pen_up = wm97xx_acc_pen_up,
+	.acc_pen_down = wm97xx_acc_pen_down, 	//I think we don't want this notification, since we get an IRQ
+	.acc_startup = wm97xx_acc_startup,
+	.acc_shutdown = wm97xx_acc_shutdown,
+
+#ifdef USE_IRQ
+	.irq_enable = wm97xx_irq_enable,	//yes we want the wm9713 to kick us on pendown
+	.irq_gpio = WM97XX_GPIO_2,	//that is the irq OUT of the WM9713
+#endif
+};
+
+static int hpipaq214_wm97xx_probe(struct platform_device *pdev)
+{
+	struct wm97xx *wm = platform_get_drvdata(pdev);
+	int ret;
+	printk("input/ts:hpipaq214_wm97xx.c:hpipaq214_wm97xx_probe.\n");
+	ret = wm97xx_register_mach_ops(wm, &hpipaq214_mach_ops);
+	
+	//attempt to turn it on. Used to need this, now don't for some reason
+	//wm->input_dev->open(wm->input_dev);
+
+	return ret;
+}
+
+static int hpipaq214_wm97xx_remove(struct platform_device *pdev)
+{
+	struct wm97xx *wm = platform_get_drvdata(pdev);	
+	printk("input/ts:hpipaq214_wm97xx.c:hpipaq214_wm97xx_remove.\n");
+	//TODO: should free the irq, something like this:
+	//free_irq(at32_wm97xx->ac97c_irq, at32_wm97xx);	
+	wm97xx_unregister_mach_ops(wm);
+	return 0;
+}
+
+static struct platform_driver hpipaq214_wm97xx_driver = {
+	.probe = hpipaq214_wm97xx_probe,
+	.remove = hpipaq214_wm97xx_remove,
+	.driver = {
+		.name = "wm97xx-touch",
+	},
+};
+
+static int __init hpipaq214_wm97xx_init(void)
+{
+	printk("input/ts:hpipaq214_wm97xx.c:hpipaq214_wm97xx_init\n");
+	return platform_driver_register(&hpipaq214_wm97xx_driver);
+}
+
+static void __exit hpipaq214_wm97xx_exit(void)
+{
+	printk("input/ts:hpipaq214_wm97xx.c:hpipaq214_wm97xx_exit\n");
+	platform_driver_unregister(&hpipaq214_wm97xx_driver);
+}
+
+module_init(hpipaq214_wm97xx_init);
+module_exit(hpipaq214_wm97xx_exit);
+
+/* Module information */
+MODULE_AUTHOR("Oliver Ford, edited from mainstone module");
+MODULE_DESCRIPTION("wm97xx continuous touch driver for HP iPAQ 214");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mtd/nand/pxa3xx_nand.c b/drivers/mtd/nand/pxa3xx_nand.c
index fceb468..2881c3c 100644
--- a/drivers/mtd/nand/pxa3xx_nand.c
+++ b/drivers/mtd/nand/pxa3xx_nand.c
@@ -291,10 +291,38 @@ static struct pxa3xx_nand_flash micron1GbX16 = {
 	.chip_id	= 0xb12c,
 };
 
+#ifdef CONFIG_MACH_HPIPAQ214
+static struct pxa3xx_nand_timing samsung_k9f2g08UXA_timing = { //using 1.8V settings
+	.tCH	= 10, //min 5
+	.tCS	= 25,
+	.tWH	= 15,
+	.tWP	= 21,
+	.tRH	= 15, //min 10
+	.tRP	= 25, //min 21
+	.tR	= 25000,
+	.tWHR	= 60,
+	.tAR	= 10,
+};
+
+static struct pxa3xx_nand_flash samsung_k9f2g08UXA = { //for HP iPAQ 214
+	.timing		= &samsung_k9f2g08UXA_timing,
+	.cmdset		= &largepage_cmdset,	//read2, lock, unlock and  lock_status not verified correct
+	.page_per_block	= 64,
+	.page_size	= 2048,
+	.flash_width	= 8,
+	.dfc_width	= 8,
+	.num_blocks	= 2048,
+	.chip_id	= 0xaaec,
+};
+#endif
+
 static struct pxa3xx_nand_flash *builtin_flash_types[] = {
 	&samsung512MbX16,
 	&micron1GbX8,
 	&micron1GbX16,
+#ifdef CONFIG_MACH_HPIPAQ214
+	&samsung_k9f2g08UXA,
+#endif
 };
 
 #define NDTR0_tCH(c)	(min((c), 7) << 19)
@@ -701,6 +729,14 @@ static void pxa3xx_nand_cmdfunc(struct mtd_info *mtd, unsigned command,
 	case NAND_CMD_PAGEPROG:
 		info->use_ecc = (info->seqin_column >= mtd->writesize) ? 0 : 1;
 
+#ifdef CONFIG_MACH_HPIPAQ214
+		//printk("prepare_read_prog_cmd, col=%i, pageaddr=%i\n",info->seqin_column, info->seqin_page_addr);
+		if(info->seqin_page_addr < 116480 || info->seqin_page_addr >= 129280){
+			printk("Flash program outside IPQ hard protection\n");
+			break;
+		}
+#endif
+
 		if (prepare_read_prog_cmd(info, cmdset->program,
 				info->seqin_column, info->seqin_page_addr))
 			break;
@@ -708,6 +744,13 @@ static void pxa3xx_nand_cmdfunc(struct mtd_info *mtd, unsigned command,
 		pxa3xx_nand_do_cmd(info, NDSR_WRDREQ);
 		break;
 	case NAND_CMD_ERASE1:
+#ifdef CONFIG_MACH_HPIPAQ214
+		//printk("prepare_erase_cmd, pageaddr=%i\n",page_addr);
+		if(page_addr < 116480 || page_addr >= 129280){
+			printk("Flash erase outside IPQ hard protection\n");
+			break;
+		}
+#endif
 		if (prepare_erase_cmd(info, cmdset->erase, page_addr))
 			break;
 
@@ -1216,7 +1259,7 @@ static int pxa3xx_nand_resume(struct platform_device *pdev)
 
 	clk_enable(info->clk);
 
-	return pxa3xx_nand_config_flash(info);
+	return pxa3xx_nand_config_flash(info, info->flash_info);
 }
 #else
 #define pxa3xx_nand_suspend	NULL
diff --git a/drivers/serial/pxa.c b/drivers/serial/pxa.c
index b4f7ffb..089603d 100644
--- a/drivers/serial/pxa.c
+++ b/drivers/serial/pxa.c
@@ -346,11 +346,14 @@ static int serial_pxa_startup(struct uart_port *port)
 	struct uart_pxa_port *up = (struct uart_pxa_port *)port;
 	unsigned long flags;
 	int retval;
-
+#ifdef CONFIG_MACH_HPIPAQ214
+	up->mcr |= UART_MCR_AFE;	// all UARTS have Auto-flow control on the PXA3xx
+#else
 	if (port->line == 3) /* HWUART */
 		up->mcr |= UART_MCR_AFE;
 	else
 		up->mcr = 0;
+#endif
 
 	up->port.uartclk = clk_get_rate(up->clk);
 
diff --git a/include/asm-arm/arch-pxa/pxa3xx_nand.h b/include/asm-arm/arch-pxa/pxa3xx_nand.h
index 81a8937..427fa8a 100644
--- a/include/asm-arm/arch-pxa/pxa3xx_nand.h
+++ b/include/asm-arm/arch-pxa/pxa3xx_nand.h
@@ -15,4 +15,6 @@ struct pxa3xx_nand_platform_data {
 	struct mtd_partition *parts;
 	unsigned int	nr_parts;
 };
+
+void pxa3xx_set_nand_info(struct pxa3xx_nand_platform_data *info);
 #endif /* __ASM_ARCH_PXA3XX_NAND_H */
diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c
index 2cb4fb8..6304b76 100644
--- a/sound/soc/codecs/wm9713.c
+++ b/sound/soc/codecs/wm9713.c
@@ -29,6 +29,7 @@
 #include <sound/pcm_params.h>
 #include <sound/soc.h>
 #include <sound/soc-dapm.h>
+#include <linux/delay.h>
 
 #include "wm9713.h"
 
@@ -975,6 +976,12 @@ static int wm9713_resume(struct platform_device *pdev)
 	u16 id;
 
 	/* give the codec an AC97 warm reset to start the link */
+#ifdef CONFIG_MACH_HPIPAQ214
+	/* hpipaq214-audio.c will have switched WM97s power off and on for.
+	    suspend/resume. It needs cold reset to wake it up again */
+	codec->ac97->bus->ops->reset(codec->ac97);
+	msleep(1);
+#endif
 	codec->ac97->bus->ops->warm_reset(codec->ac97);
 	id = codec->soc_phys_read(codec->ac97, AC97_VENDOR_ID2); 
 	if (id != 0x4c13) {
diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig
index 7530227..aff1810 100644
--- a/sound/soc/pxa/Kconfig
+++ b/sound/soc/pxa/Kconfig
@@ -131,6 +131,14 @@ config SND_PXA3XX_SOC_ZYLONITE
 	help
 	  Say Y if you want to add support for SoC voice audio on Zylonite
 
+config SND_PXA3XX_SOC_HPIPAQ214
+	tristate "SoC audio support for HP iPAQ 214"
+	depends on SND_PXA2XX_SOC && MACH_HPIPAQ214
+	select SND_PXA2XX_SOC_AC97
+	select SND_SOC_WM9713
+	help
+	  Say Y if you want to add support for SoC audio on HP iPAQ 214
+
 config SND_PXA2XX_SOC_E800
 	tristate "SoC AC97 Audio support for e800"
 	depends on SND_PXA2XX_SOC && MACH_E800
diff --git a/sound/soc/pxa/Makefile b/sound/soc/pxa/Makefile
index fd45a09..cd3c74f 100644
--- a/sound/soc/pxa/Makefile
+++ b/sound/soc/pxa/Makefile
@@ -25,6 +25,7 @@ snd-soc-mainstone-wm8753-objs := mainstone_wm8753.o
 snd-soc-mainstone-wm9713-objs := mainstone_wm9713.o
 snd-soc-mainstone-wm9712-objs := mainstone_wm9712.o
 snd-soc-zylonite-objs := zylonite.o
+snd-soc-hpipaq214-audio-objs := hpipaq214-audio.o
 
 obj-$(CONFIG_SND_PXA2XX_SOC_CORGI) += snd-soc-corgi.o
 obj-$(CONFIG_SND_PXA2XX_SOC_POODLE) += snd-soc-poodle.o
@@ -38,3 +39,4 @@ obj-$(CONFIG_SND_PXA2XX_SOC_MAINSTONE_WM8753) += snd-soc-mainstone-wm8753.o
 obj-$(CONFIG_SND_PXA2XX_SOC_MAINSTONE_WM9713) += snd-soc-mainstone-wm9713.o
 obj-$(CONFIG_SND_PXA2XX_SOC_MAINSTONE_WM9712) += snd-soc-mainstone-wm9712.o
 obj-$(CONFIG_SND_PXA3XX_SOC_ZYLONITE) += snd-soc-zylonite.o
+obj-$(CONFIG_SND_PXA3XX_SOC_HPIPAQ214) += snd-soc-hpipaq214-audio.o
diff --git a/sound/soc/pxa/hpipaq214-audio.c b/sound/soc/pxa/hpipaq214-audio.c
new file mode 100644
index 0000000..82c24b0
--- /dev/null
+++ b/sound/soc/pxa/hpipaq214-audio.c
@@ -0,0 +1,332 @@
+/*
+ * hpipaq214.c  --  SoC audio for HP IPAQ 214.
+ *
+ * Mostly based on zylonite.c in same directory.
+ *
+ * Authors: Oliver Ford <ipaqcode-oliford-co-uk>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/ac97_codec.h>
+
+#include <asm/mach-types.h>
+#include <asm/arch/gpio.h>
+#include <asm/arch/hardware.h>
+#include <asm/arch/mfp-pxa320.h>
+#include <asm/arch/pxa-regs.h>
+
+#include "pxa2xx-pcm.h"
+#include "../codecs/wm9713.h"
+
+int wm9713_power_gpio = 104;
+int audio_gpio = 96;
+
+//static struct clk *mclk;   - pout clk not used
+
+static const struct snd_soc_dapm_widget hpipaq214_dapm_widgets[] = {
+SND_SOC_DAPM_HP("Headphones", NULL),
+SND_SOC_DAPM_MIC("Audio Jack Microphone", NULL),
+SND_SOC_DAPM_MIC("Handset Microphone", NULL),
+SND_SOC_DAPM_SPK("Speaker", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+
+         /* Audio jack has both microphone and headphones */
+	{"Headphones", NULL, "HPL"},
+	{"Headphones", NULL, "HPR"},
+	{"Audio Jack Microphone", NULL, "MIC2A"}, /* ??? */
+	{"Mic Bias", NULL, "Audio Jack Microphone"},
+
+	/* Handset microphone */
+	{"Handset Microphone", NULL, "MIC1"},
+	{"Mic Bias", NULL, "Handset Microphone"},
+
+	{"Speaker", NULL, "SPKL"},
+	{"Speaker", NULL, "OUT4"},
+};
+
+//these may be useful for switching things on and off
+static int hpipaq214_hifi_startup(struct snd_pcm_substream *substream){	
+	if(audio_gpio < 0) //didn't get the GPIO, can't do anything
+		return -EIO;
+	
+	printk("hpipaq214_hifi_startup, gpio%i -> on",audio_gpio);
+	gpio_set_value(audio_gpio, 1);	
+
+	return 0;
+}
+
+static void hpipaq214_hifi_shutdown(struct snd_pcm_substream *substream){
+	if(audio_gpio < 0) //didn't get the GPIO, can't do anything
+		return;
+	
+	printk("hpipaq214_hifi_shutdown, gpio%i -> off",audio_gpio);
+	gpio_set_value(audio_gpio, 0);	
+
+}
+
+static int hpipaq214_hifi_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params){
+	printk("hpipaq214_hifi_hw_params()\n");
+	return 0;
+}
+
+static struct snd_soc_ops hpipaq214_hifi_ops = {
+	.startup = hpipaq214_hifi_startup,
+	.shutdown = hpipaq214_hifi_shutdown,
+	.hw_params = hpipaq214_hifi_hw_params,
+};
+
+static struct snd_soc_ops hpipaq214_aux_ops = {
+};
+
+static struct snd_soc_pcm_config pcm_configs[] = {
+	{
+		.name		= "Aux",
+		.codec		= wm9713_codec_id,
+		.codec_dai	= wm9713_codec_aux_dai_id,
+		.platform	= pxa_platform_id,
+		.cpu_dai	= pxa_ac97_aux_dai_id,
+		.playback	= 1,
+		.ops		= &hpipaq214_aux_ops,
+	},
+	{
+		.name		= "HiFi",
+		.codec		= wm9713_codec_id,
+		.codec_dai	= wm9713_codec_hifi_dai_id,
+		.platform	= pxa_platform_id,
+		.cpu_dai	= pxa_ac97_hifi_dai_id,
+		.playback	= 1,
+		.capture	= 1,
+		.ops		= &hpipaq214_hifi_ops,
+	},
+};
+
+static int hpipaq214_init(struct snd_soc_card *card)
+{
+	struct snd_soc_codec *codec;
+	struct snd_soc_dai *dai;
+	struct snd_ac97_bus_ops *ac97_ops;
+	int ret;
+
+	codec = snd_soc_card_get_codec(card, wm9713_codec_id, 0);
+	if (codec == NULL) {
+		printk(KERN_ERR "Unable to obtain WM9713 codec\n");
+		return -ENODEV;
+	}
+
+	dai = snd_soc_card_get_dai(card, pxa_ac97_hifi_dai_id);
+	if (dai == NULL) {
+		printk(KERN_ERR "Unable to obtain WM9713 HiFi DAI\n");
+		return -ENODEV;
+	}
+
+	ac97_ops = snd_soc_card_get_ac97_ops(card, pxa_ac97_hifi_dai_id);
+	if (!ac97_ops) {
+		printk(KERN_ERR "Unable to obtain AC97 operations\n");
+		return -ENODEV;
+	}
+
+	/* register with AC97 bus for ad-hoc driver access */
+	ret = snd_soc_new_ac97_codec(codec, ac97_ops, card->card, 0, 0);
+	if (ret < 0) {
+		printk(KERN_ERR "Unable to instantiate AC97 codec\n");
+		return ret;
+	}
+
+	//clk_enable(mclk); //apparently not used  (see hpipaq214_probe())
+
+	/* do a cold reset for the controller and then try
+	 * a warm reset followed by an optional cold reset for codec */
+	ac97_ops->reset(codec->ac97);
+	ac97_ops->warm_reset(codec->ac97);
+
+	if (ac97_ops->read(codec->ac97, AC97_VENDOR_ID1) != 0x574d) {
+		printk(KERN_ERR "Unable to read codec vendor ID\n");
+		return -ENODEV;
+	}
+
+	snd_soc_card_init_codec(codec, card);
+
+/*	PLL doesn't appear to be used on the iPAQ214
+	ret = snd_soc_dai_set_pll(dai, 0, clk_get_rate(mclk), 1);
+	if (ret != 0) {
+		dev_err(codec->dev, "Unable to configure PLL: %d\n", ret);
+		return ret;
+	}*/
+	
+	//try to turn off PLL (it should be already)
+	ret = snd_soc_dai_set_pll(dai, 0, 0, 0);
+	printk("snd_soc_dai_set_pll returned %i\n",ret);
+
+	ret = snd_soc_dapm_new_controls(card, codec,
+			hpipaq214_dapm_widgets,
+			ARRAY_SIZE(hpipaq214_dapm_widgets));
+	if (ret < 0)
+		printk("snd_soc_dapm_new_controls failed: %i\n",ret);
+		//return ret;
+
+	ret = snd_soc_dapm_add_routes(card, audio_map,
+				     ARRAY_SIZE(audio_map));
+	if (ret < 0)
+		printk("snd_soc_dapm_add_routes failed: %i\n",ret);
+		//return ret;
+
+	//not sure where things are connected yet
+	snd_soc_dapm_enable_pin(card, "Speaker");	
+	snd_soc_dapm_enable_pin(card, "Headphones");
+
+	snd_soc_dapm_sync(card);
+
+	return 0;
+}
+
+
+static int hpipaq214_probe(struct platform_device *pdev)
+{
+	struct snd_soc_card *card;
+	int ret;
+
+	ret = gpio_request(wm9713_power_gpio, "WM9713 power?");	
+	if(ret){
+		printk(KERN_ERR "Unable to register WM9713/audio gpio (%i)\n",wm9713_power_gpio);
+		wm9713_power_gpio = -1;
+	}else
+		gpio_direction_output(wm9713_power_gpio, 1); //keep it on for now!
+	
+	ret = gpio_request(audio_gpio, "WM9713 power?");	
+	if(ret){
+		printk(KERN_ERR "Unable to register audio gpio (%i)\n",audio_gpio);
+		audio_gpio = -1;
+	}else
+		gpio_direction_output(audio_gpio, 0);
+	
+
+	card = snd_soc_card_create("hpipaq214", &pdev->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+	if (card == NULL)
+		return -ENOMEM;
+
+	card->longname = "hpipaq214";
+	card->init = hpipaq214_init,
+	card->private_data = pdev;
+	platform_set_drvdata(pdev, card);
+
+	ret = snd_soc_card_create_pcms(card, pcm_configs,
+				  ARRAY_SIZE(pcm_configs));
+	if (ret < 0)
+		dev_err(&pdev->dev," snd_soc_card_create_pcms failed: %d.\n",ret);
+		//goto err;
+
+	ret = snd_soc_card_register(card);
+	if (ret < 0)
+		dev_err(&pdev->dev," snd_soc_card_register failed: %d.\n",ret);
+		//goto err;
+
+	return ret;
+
+/*err:  not used atm, commented to supress warnings
+	dev_err(&pdev->dev, "probe() failed: %d\n", ret);
+	snd_soc_card_free(card);
+	return ret;*/
+}
+
+static int __exit hpipaq214_remove(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = platform_get_drvdata(pdev);
+
+	snd_soc_card_free(card);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+
+static int hpipaq214_suspend(struct platform_device *pdev,
+	pm_message_t state)
+{
+	//struct snd_soc_card *card = platform_get_drvdata(pdev);
+	return 0;
+	//return snd_soc_card_suspend_pcms(card, state);
+}
+
+static int hpipaq214_resume(struct platform_device *pdev)
+{
+	//struct snd_soc_card *card = platform_get_drvdata(pdev);
+	return 0;
+	//return snd_soc_card_resume_pcms(card);
+}
+
+static int hpipaq214_suspend_late(struct platform_device *pdev, pm_message_t state){
+	if(wm9713_power_gpio < 0) //didn't get the GPIO, can't do anything
+		return -EIO;
+	
+	dev_dbg(&pdev->dev, "hpipaq214_suspend_late, gpio%i -> off",wm9713_power_gpio);
+	gpio_set_value(wm9713_power_gpio, 0);	
+
+	return 0;
+}
+static int hpipaq214_resume_early(struct platform_device *pdev){
+	if(wm9713_power_gpio < 0) //didn't get the GPIO, can't do anything
+		return -EIO;
+
+	dev_dbg(&pdev->dev, "hpipaq214_resume_early, gpio%i -> on",wm9713_power_gpio);
+	gpio_set_value(wm9713_power_gpio, 1);
+
+	return 0;
+}
+
+#else
+#define hpipaq214_suspend NULL
+#define hpipaq214_resume  NULL
+#define hpipaq214_suspend_late NULL
+#define hpipaq214_resume_early  NULL
+#endif
+
+static struct platform_driver hpipaq214_driver = {
+	.probe		= hpipaq214_probe,
+//	.remove		= __devexit_p(hpipaq214_remove),  // this makes a build error, no idea why yet
+	.suspend	= hpipaq214_suspend,
+	.suspend_late	= hpipaq214_suspend_late,
+	.resume		= hpipaq214_resume,
+	.resume_early	= hpipaq214_resume_early,
+	
+	.driver		= {
+		.name		= "hpipaq214-audio",
+		.owner		= THIS_MODULE,
+	},
+};
+
+static int __init hpipaq214_asoc_init(void)
+{
+	printk("hpipaq214_asoc_init: Registering driver for '%s'.\n",hpipaq214_driver.driver.name);
+	return platform_driver_register(&hpipaq214_driver);
+}
+
+static void __exit hpipaq214_asoc_exit(void)
+{
+	printk("hpipaq214_asoc_exit.\n");
+	platform_driver_unregister(&hpipaq214_driver);
+}
+
+module_init(hpipaq214_asoc_init);
+module_exit(hpipaq214_asoc_exit);
+
+/* Module information */
+MODULE_AUTHOR("Oliver Ford <ipaqcode-at-oliford.co.uk>");
+MODULE_DESCRIPTION("ALSA SoC hpipaq214");
+MODULE_LICENSE("GPL");
-- 
1.5.2.5

