diff --git a/Documentation/exi.txt b/Documentation/exi.txt
new file mode 100644
index 0000000..a330c56
--- /dev/null
+++ b/Documentation/exi.txt
@@ -0,0 +1,81 @@
+The Expansion Interface (EXI)
+-----------------------------
+
+[Introductory information goes here...]
+
+
+Device drivers
+--------------
+
+Outlined below is the recommended practice when writing EXI device drivers.
+
+The bus driver already handles quite an amount of stuff, although some of
+it might have to be implemented by individual device drivers. If in doubt,
+see include/linux/exi.h.
+
+
+Registration
+------------
+
+Declare a struct exi_driver. Initialize at least the name, id_table,
+probe and remove fields:
+
+
+	static struct exi_device_id frob_id_tbl[] __devinitdata = {
+		{ .dev_id = EXI_ID_FROB0, },
+		{ .dev_id = EXI_ID_FROB1, },
+		{ .dev_id = EXI_ID_FROB2, },
+	};
+
+	static struct exi_driver frob_driver = {
+		.name		= "frob",
+		.id_table	= frob_id_tbl,
+		.probe		= frob_probe,
+		.remove		= __devexit_p(frob_remove),
+	};
+
+
+`name' distinguishes the driver from others registered with the bus.
+It should be short, unique, yet remain informative.
+
+`id_table' is a pointer to a table of device IDs the driver claims to
+support. These should be taken directly from include/linux/exi_ids.h.
+This table should be marked __devinitdata.
+
+`probe' is a pointer to a function that's called once the driver is bound
+to a device it claims to support. This should be marked __devinit.
+
+`remove' is a pointer to a function that's called when either the driver
+unregisters with the bus, or a device bound to that specific driver is
+physically unplugged from the bus. This should be marked __devexit and
+created with __devexit_p().
+
+From within the driver's initialization function, register the driver with
+the bus by calling exi_driver_register() with the driver structure declared
+previously:
+
+	static int __init frob_init(void)
+	{
+		return exi_driver_register(&frob_driver);
+	}
+
+
+Deregistration
+--------------
+
+If the driver may be compiled as a loadable kernel module, then all you
+have to do is call exi_driver_unregister() in the driver's exit function:
+
+	static void __exit frob_exit(void)
+	{
+		exi_driver_unregister(&frob_driver);
+	}
+
+Device Private Data
+-------------------
+
+The functions exi_set_drvdata()/exi_get_drvdata() are available for the
+sole purpose of setting and getting driver private data for an EXI device.
+These are simply helpers around the driver-model functions that do the
+actual work. Use them. That way, you don't have to worry (or worry less)
+about changes to the driver-model API.
diff --git a/Documentation/powerpc/dts-bindings/gpio/i2c.txt b/Documentation/powerpc/dts-bindings/gpio/i2c.txt
new file mode 100644
index 0000000..8a8cd98
--- /dev/null
+++ b/Documentation/powerpc/dts-bindings/gpio/i2c.txt
@@ -0,0 +1,42 @@
+GPIO-based I2C
+
+Required properties:
+- compatible : should be "virtual,i2c-gpio".
+- gpios : should specify GPIOs used for SDA and SCL lines, in that order.
+- sda-is-open-drain : should be non-zero if SDA gpio is open-drain.
+- sda-enforce-dir : should be non-zero if SDA gpio must be configured for
+		    input before reading and for output before writing.
+- scl-is-open-drain : should be non-zero if SCL gpio is open-drain.
+- scl-is-output-only : should be non-zero if SCL is an output gpio only.
+- udelay : signal toggle delay. SCL frequency is (500 / udelay) kHz
+- timeout : clock stretching timeout in milliseconds.
+
+Example:
+
+gpio0: starlet-gpio@0d8000c0 {
+	compatible = "nintendo,starlet-gpio";
+	reg = <0d8000c0 4>;
+	gpio-controller;
+	#gpio-cells = <2>;
+};
+
+i2c-video {
+	#address-cells = <1>;
+	#size-cells = <0>;
+	compatible = "virtual,i2c-gpio";
+
+	gpios = <&gpio0 10 0	/* SDA line */
+		 &gpio0 11 0	/* SCL line */
+		>;
+	sda-is-open-drain = <1>;
+	sda-enforce-dir = <1>;
+	scl-is-open-drain = <1>;
+	scl-is-output-only = <1>;
+	udelay = <2>;
+
+	audio-video-encoder {
+		compatible = "nintendo,wii-ave-rvl";
+		reg = <70>;
+	};
+};
+
diff --git a/arch/powerpc/Kconfig b/arch/powerpc/Kconfig
index 2ba14e7..84b2566 100644
--- a/arch/powerpc/Kconfig
+++ b/arch/powerpc/Kconfig
@@ -679,7 +679,7 @@ config PPC_PCI_CHOICE
 config PCI
 	bool "PCI support" if PPC_PCI_CHOICE
 	default y if !40x && !CPM2 && !8xx && !PPC_83xx \
-		&& !PPC_85xx && !PPC_86xx
+		&& !PPC_85xx && !PPC_86xx && !GAMECUBE_COMMON
 	default PCI_PERMEDIA if !4xx && !CPM2 && !8xx
 	default PCI_QSPAN if !4xx && !CPM2 && 8xx
 	select ARCH_SUPPORTS_MSI
diff --git a/arch/powerpc/Kconfig.debug b/arch/powerpc/Kconfig.debug
index 3b10051..11e385b 100644
--- a/arch/powerpc/Kconfig.debug
+++ b/arch/powerpc/Kconfig.debug
@@ -254,6 +254,14 @@ config PPC_EARLY_DEBUG_CPM
 	  using a CPM-based serial port.  This assumes that the bootwrapper
 	  has run, and set up the CPM in a particular way.
 
+config PPC_EARLY_DEBUG_USBGECKO
+	bool "Early debugging through the USB Gecko adapter"
+	depends on GAMECUBE_COMMON
+	select USBGECKO_UDBG
+	help
+	  Select this to enable early debugging for Nintendo GameCube/Wii
+	  consoles via an external USB Gecko adapter.
+
 endchoice
 
 config PPC_EARLY_DEBUG_44x_PHYSLOW
diff --git a/arch/powerpc/boot/Makefile b/arch/powerpc/boot/Makefile
index 7bfc8ad..fbaf101 100644
--- a/arch/powerpc/boot/Makefile
+++ b/arch/powerpc/boot/Makefile
@@ -66,7 +66,7 @@ src-wlib := string.S crt0.S crtsavres.S stdio.c main.c \
 		gunzip_util.c elf_util.c $(zlib) devtree.c oflib.c ofconsole.c \
 		4xx.c ebony.c mv64x60.c mpsc.c mv64x60_i2c.c cuboot.c bamboo.c \
 		cpm-serial.c stdlib.c mpc52xx-psc.c planetcore.c uartlite.c \
-		fsl-soc.c mpc8xx.c pq2.c
+		fsl-soc.c mpc8xx.c pq2.c ugecon.c
 src-plat := of.c cuboot-52xx.c cuboot-824x.c cuboot-83xx.c cuboot-85xx.c holly.c \
 		cuboot-ebony.c cuboot-hotfoot.c treeboot-ebony.c prpmc2800.c \
 		ps3-head.S ps3-hvcall.S ps3.c treeboot-bamboo.c cuboot-8xx.c \
@@ -76,7 +76,8 @@ src-plat := of.c cuboot-52xx.c cuboot-824x.c cuboot-83xx.c cuboot-85xx.c holly.c
 		cuboot-katmai.c cuboot-rainier.c redboot-8xx.c ep8248e.c \
 		cuboot-warp.c cuboot-85xx-cpm2.c cuboot-yosemite.c simpleboot.c \
 		virtex405-head.S virtex.c redboot-83xx.c cuboot-sam440ep.c \
-		cuboot-acadia.c cuboot-amigaone.c cuboot-kilauea.c
+		cuboot-acadia.c cuboot-amigaone.c cuboot-kilauea.c \
+		gamecube.c wii.c
 src-boot := $(src-wlib) $(src-plat) empty.c
 
 src-boot := $(addprefix $(obj)/, $(src-boot))
@@ -254,6 +255,8 @@ image-$(CONFIG_KSI8560)			+= cuImage.ksi8560
 image-$(CONFIG_STORCENTER)		+= cuImage.storcenter
 image-$(CONFIG_MPC7448HPC2)		+= cuImage.mpc7448hpc2
 image-$(CONFIG_PPC_C2K)			+= cuImage.c2k
+image-$(CONFIG_GAMECUBE)		+= dtbImage.gamecube
+image-$(CONFIG_WII)			+= dtbImage.wii
 
 # Board port in arch/powerpc/platform/amigaone/Kconfig
 image-$(CONFIG_AMIGAONE)		+= cuImage.amigaone
diff --git a/arch/powerpc/boot/dts/gamecube.dts b/arch/powerpc/boot/dts/gamecube.dts
new file mode 100644
index 0000000..aa94173
--- /dev/null
+++ b/arch/powerpc/boot/dts/gamecube.dts
@@ -0,0 +1,135 @@
+/*
+ * arch/powerpc/boot/dts/gamecube.dts
+ *
+ * Nintendo GameCube platform device tree source
+ * Copyright (C) 2007-2009 The GameCube Linux Team
+ * Copyright (C) 2007,2008,2009 Albert Herranz
+ *
+ * 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.
+ *
+ */
+
+/dts-v1/;
+
+/ {
+	model = "NintendoGameCube";
+	compatible = "nintendo,gamecube";
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	chosen {
+		bootargs = "root=/dev/nfs nfsroot=192.168.001.253:/nfsroot/cube,nfsvers=3,udp ip=on video=gcn-vifb:tv=auto force_keyboard_port=4";
+		linux,stdout-path = &USBGECKO0;
+	};
+
+	aliases {
+		ugecon = &USBGECKO0;
+	};
+
+	memory {
+		device_type = "memory";
+		/* 24M minus framebuffer memory area (640*576*2*2) */
+		reg = <0x00000000 0x01698000>;
+	};
+
+	cpus {
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		PowerPC,gekko@0 {
+			device_type = "cpu";
+			reg = <0>;
+			clock-frequency = <486000000>; /* 486MHz */
+			bus-frequency = <162000000>; /* 162MHz core-to-bus 3x */
+			timebase-frequency = <40500000>; /* 162MHz / 4 */
+			/* Following required by dtc but not used */
+			i-cache-line-size = <32>;
+			d-cache-line-size = <32>;
+			i-cache-size = <32768>;
+			d-cache-size = <32768>;
+		};
+	};
+
+	/* devices contained int the flipper chipset */
+	soc {
+		#address-cells = <1>;
+		#size-cells = <1>;
+		#interrupt-cells = <1>;
+		model = "flipper";
+		compatible = "nintendo,flipper";
+		clock-frequency = <162000000>; /* 162MHz */
+		ranges = <0x0c000000 0x0c000000 0x00010000>;
+
+		video@0c002000 {
+			compatible = "nintendo,flipper-video";
+			reg = <0x0c002000 0x100>;
+			interrupts = <8>;
+			interrupt-parent = <&pic>;
+			xfb-start = <0x01698000>; /* end-of-ram - xfb-size */
+			xfb-size = <0x168000>;
+		};
+
+		pic: pic@0c003000 {
+			#interrupt-cells = <1>;
+			compatible = "nintendo,flipper-pic";
+			reg = <0x0c003000 0x8>;
+			interrupt-controller;
+		};
+
+		resetswitch@0c003000 {
+			compatible = "nintendo,flipper-resetswitch";
+			reg = <0x0c003000 0x4>;
+			interrupts = <1>;
+			interrupt-parent = <&pic>;
+		};
+
+		auxram@0c005000 {
+			compatible = "nintendo,flipper-auxram";
+			reg = <0x0c005000 0x200>;	/* DSP */
+			interrupts = <6>;
+			interrupt-parent = <&pic>;
+		};
+
+		audio@0c005000 {
+			compatible = "nintendo,flipper-audio";
+			reg = <0x0c005000 0x200		/* DSP */
+			       0x0c006c00 0x20>;	/* AI */
+			interrupts = <6>;
+			interrupt-parent = <&pic>;
+		};
+
+		disk@0c006000 {
+			compatible = "nintendo,flipper-disk";
+			reg = <0x0c006000 0x40>;
+			interrupts = <2>;
+			interrupt-parent = <&pic>;
+		};
+
+		serial@0c006400 {
+			compatible = "nintendo,flipper-serial";
+			reg = <0x0c006400 0x100>;
+			interrupts = <3>;
+			interrupt-parent = <&pic>;
+		};
+
+		/* External Interface bus */
+		exi@0c006800 {
+			#address-cells = <1>;
+			#size-cells = <1>;
+			compatible = "nintendo,flipper-exi";
+			reg = <0x0c006800 0x40>;
+			interrupts = <4>;
+			interrupt-parent = <&pic>;
+
+			USBGECKO0: usbgecko@0c006814 {
+				compatible = "usbgecko,usbgecko";
+				reg = <0x0c006814 0x14>;
+				virtual-reg = <0xcc006814>;
+			};
+		};
+        };
+};
+
diff --git a/arch/powerpc/boot/dts/wii.dts b/arch/powerpc/boot/dts/wii.dts
new file mode 100644
index 0000000..2095f2b
--- /dev/null
+++ b/arch/powerpc/boot/dts/wii.dts
@@ -0,0 +1,262 @@
+/*
+ * arch/powerpc/boot/dts/wii.dts
+ *
+ * Nintendo Wii platform device tree source
+ * Copyright (C) 2008-2009 The GameCube Linux Team
+ * Copyright (C) 2008,2009 Albert Herranz
+ *
+ * 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.
+ *
+ */
+
+/dts-v1/;
+
+/memreserve/ 0x01694000 0x0004000;	/* lowmem stub save area */
+/memreserve/ 0x01698000 0x0168000;	/* framebuffer, see video@0c002000 */
+/memreserve/ 0x01800000 0xe800000;	/* memory hole */
+/memreserve/ 0x10000000 0x0004000;	/* DSP */
+
+/ {
+	model = "NintendoWii";
+	compatible = "nintendo,wii";
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	chosen {
+		/* ramdisk */
+		/* bootargs = "nobats root=/dev/ram0 video=gcnfb ip=on force_keyboard_port=4"; */
+
+		/* nfsroot */
+		/* bootargs = "nobats root=/dev/nfs nfsroot=192.168.001.253:/nfsroot/cube ip=on video=gcnfb:tv=NTSC force_keyboard_port=4"; */
+
+		/* root filesystem on 2nd partition of SD card, whiite style  */
+		bootargs = "nobats root=/dev/mmcblk0p2 rootwait force_keyboard_port=4 video=gcnfb:60Hz,tv=NTSC placeholder_for_additional_kernel_options_targetted_at_hexedit_lovers";
+		linux,stdout-path = &USBGECKO0;
+	};
+
+	aliases {
+		ugecon = &USBGECKO0;
+	};
+
+	lowmem-stub {
+		reg = <0x00000000 0x4000>;
+		save-area = <0x01694000>;	/* must be memreserve'd */
+	};
+
+	memory {
+		device_type = "memory";
+		/* mem1 + hole + mem2 - ioh */
+		reg = <0x00000000 0x133e0000>;
+	};
+
+	cpus {
+		#cpus = <1>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		PowerPC,broadway@0 {
+			device_type = "cpu";
+			reg = <0>;
+			clock-frequency = <729000000>; /* 729MHz */
+			bus-frequency = <243000000>; /* 243MHz core-to-bus 3x */
+			timebase-frequency = <60750000>; /* 243MHz / 4 */
+			/* Following required by dtc but not used */
+			i-cache-line-size = <32>;
+			d-cache-line-size = <32>;
+			i-cache-size = <32768>;
+			d-cache-size = <32768>;
+		};
+	};
+
+	/* devices contained in the hollywood chipset */
+	soc {
+		#address-cells = <1>;
+		#size-cells = <1>;
+		#interrupt-cells = <1>;
+		model = "hollywood";
+		compatible = "nintendo,hollywood";
+		clock-frequency = <243000000>; /* 243MHz */
+		ranges = <0x0c000000 0x0c000000 0x00010000
+			  0x0d000000 0x0d000000 0x00010000
+			  0x0d040000 0x0d040000 0x00050000
+			  0x0d800000 0x0d800000 0x00007000
+			  0x133e0000 0x133e0000 0x00c20000>;
+
+		video@0c002000 {
+			compatible = "nintendo,hollywood-video";
+			reg = <0x0c002000 0x100>;
+			interrupts = <8>;
+			interrupt-parent = <&PIC0>;
+			xfb-start = <0x01698000>; /* end-of-mem1 - xfb-size */
+			xfb-size = <0x168000>;    /* 640x576x2 x 2 bytes */
+		};
+
+		PIC0: pic0@0c003000 {
+			#interrupt-cells = <1>;
+			compatible = "nintendo,flipper-pic";
+			reg = <0x0c003000 0x8>;
+			interrupt-controller;
+		};
+
+		resetswitch@0c003000 {
+			compatible = "nintendo,hollywood-resetswitch";
+			reg = <0x0c003000 0x4>;
+			interrupts = <1>;
+			interrupt-parent = <&PIC0>;
+		};
+
+		audio@0c005000 {
+			compatible = "nintendo,hollywood-audio";
+			reg = <0x0c005000 0x200	/* DSP */
+			       0x0d006c00 0x20>;	/* AI */
+			interrupts = <6>;
+			interrupt-parent = <&PIC0>;
+		};
+
+		serial@0d006400 {
+			compatible = "nintendo,hollywood-serial";
+			reg = <0x0d006400 0x100>;
+			interrupts = <3>;
+			interrupt-parent = <&PIC0>;
+		};
+
+		/* External Interface bus */
+		exi@0d006800 {
+			#address-cells = <1>;
+			#size-cells = <1>;
+			compatible = "nintendo,hollywood-exi";
+			reg = <0x0d006800 0x40>;
+			interrupts = <4>;
+			interrupt-parent = <&PIC0>;
+
+			USBGECKO0: usbgecko@0d006814 {
+				compatible = "usbgecko,usbgecko";
+				reg = <0x0d006814 0x14>;
+				virtual-reg = <0xcd006814>;
+			};
+		};
+
+		/*
+		 * Firmware interfaces accessible through Nintendo IOS IPC.
+		 */
+		ios@0d000000 {
+			compatible = "nintendo,starlet-ios-ipc";
+			reg = <0x0d000000 0x40		/* IPC */
+			       0x133e0000 0x20000>;	/* MEM2 ioh 128K */
+			interrupts = <14>;
+			interrupt-parent = <&PIC0>;
+
+			starlet-ios-es {
+				compatible = "nintendo,starlet-ios-es";
+			};
+
+			starlet-ios-sd {
+				compatible = "nintendo,starlet-ios-sd";
+			};
+
+			starlet-ios-hcd {
+				compatible = "nintendo,starlet-ios-hcd";
+			};
+
+			starlet-ios-keyboard {
+				compatible = "nintendo,starlet-ios-keyboard";
+			};
+		};
+
+		/*
+		 * Hardware accessible through Team Twiizers 'mini' firmware
+		 * replacement for Starlet.
+		 */
+		mini@0d000000 {
+			#address-cells = <1>;
+			#size-cells = <1>;
+			#interrupt-cells = <1>;
+			compatible = "twiizers,starlet-mini-ipc";
+			reg = <0x0d000000 0x40	/* IPC */
+			       0x13fffffc 0x4>;	/* mini header pointer */
+
+			ranges = <0x0d040000 0x0d040000 0x00050000
+				  0x0d800000 0x0d800000 0x00007000
+				  0x133e0000 0x133e0000 0x00c20000>;
+
+			PIC1: pic1@0d800030 {
+				#interrupt-cells = <1>;
+				compatible = "nintendo,hollywood-pic";
+				reg = <0x0d800030 0x8>;
+				interrupt-controller;
+				interrupts = <14>;
+				interrupt-parent = <&PIC0>;
+			};
+			disk@0d806000 {
+				compatible = "nintendo,hollywood-disk";
+				reg = <0x0d806000 0x40>;
+				interrupts = <2>;
+				interrupt-parent = <&PIC0>;
+			};
+			ehci@0d040000 {
+				compatible = "nintendo,hollywood-ehci";
+				reg = <0x0d040000 0x100
+				       0x133e0000 0x80000>; /* 512 KB */
+				interrupts = <4>;
+				interrupt-parent = <&PIC1>;
+			};
+			ohci0@0d050000 {
+				compatible = "nintendo,hollywood-ohci";
+				reg = <0x0d050000 0x100
+				       0x13460000 0x80000>; /* 512 KB */
+				interrupts = <5>;
+				interrupt-parent = <&PIC1>;
+			};
+			ohci1@0d060000 {
+				compatible = "nintendo,hollywood-ohci";
+				reg = <0x0d060000 0x100
+				       0x134e0000 0x80000>; /* 512 KB */
+				interrupts = <6>;
+				interrupt-parent = <&PIC1>;
+			};
+			sdhc0@0d070000 {
+				compatible = "nintendo,hollywood-sdhci";
+				reg = <0x0d070000 0x200>;
+				interrupts = <7>;
+				interrupt-parent = <&PIC1>;
+			};
+			sdhc1@0d080000 {
+				compatible = "nintendo,hollywood-sdhci";
+				reg = <0x0d080000 0x200>;
+				interrupts = <8>;
+				interrupt-parent = <&PIC1>;
+			};
+		};
+
+		gpio0: hollywood-gpio@0d8000c0 {
+			compatible = "nintendo,hollywood-gpio";
+			reg = <0x0d8000c0 0x4>;
+			gpio-controller;
+			#gpio-cells = <2>;
+		};
+
+		i2c-video {
+			#address-cells = <1>;
+			#size-cells = <0>;
+		        compatible = "virtual,i2c-gpio";
+
+		        gpios = <&gpio0  16 0 /* 31-15 */
+		                 &gpio0  17 0 /* 31-14 */
+				>;
+		        sda-is-open-drain = <1>;
+		        sda-enforce-dir = <1>;
+		        scl-is-open-drain = <1>;
+		        scl-is-output-only = <1>;
+		        udelay = <2>;
+
+		        audio-video-encoder {
+		                compatible = "nintendo,wii-ave-rvl";
+		                reg = <0x70>;
+		        };
+		};
+	};
+};
+
diff --git a/arch/powerpc/boot/gamecube.c b/arch/powerpc/boot/gamecube.c
new file mode 100644
index 0000000..f3e3c0d
--- /dev/null
+++ b/arch/powerpc/boot/gamecube.c
@@ -0,0 +1,77 @@
+/*
+ * arch/powerpc/boot/gamecube.c
+ *
+ * Nintendo GameCube/Wii platforms
+ * Copyright (C) 2004-2009 The GameCube Linux Team
+ * Copyright (C) 2008,2009 Albert Herranz
+ *
+ * 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 <stddef.h>
+#include "stdio.h"
+#include "types.h"
+#include "io.h"
+#include "ops.h"
+
+#include "ugecon.h"
+
+BSS_STACK(8192);
+
+/*
+ * We enter with the MMU enabled and some legacy memory mappings active.
+ *
+ * We leave the MMU enabled, but we switch to an identity mapped memory
+ * scheme as expected by the start code.
+ *
+ */
+asm ("\n\
+.text\n\
+.globl _zimage_start\n\
+_zimage_start:\n\
+\n\
+	isync\n\
+	/* IBAT3,DBAT3 for first 16Mbytes */\n\
+	li	8, 0x01ff	/* 16MB */\n\
+	li      9, 0x0002	/* rw */\n\
+	mtspr   0x216, 8	/* IBAT3U */\n\
+	mtspr   0x217, 9	/* IBAT3L */\n\
+	mtspr   0x21e, 8	/* DBAT3U */\n\
+	mtspr   0x21f, 9	/* DBAT3L */\n\
+\n\
+	sync\n\
+	isync\n\
+\n\
+	li	3, 0\n\
+	li	4, 0\n\
+	li	5, 0\n\
+\n\
+	bcl-    20,4*cr7+so,1f\n\
+1:\n\
+	mflr    8\n\
+	clrlwi  8, 8, 3\n\
+	addi    8, 8, 2f - 1b\n\
+	mtlr    8\n\
+	blr\n\
+2:\n\
+	b _zimage_start_lib\n\
+");
+
+/*
+ *
+ */
+void platform_init(unsigned long r3, unsigned long r4, unsigned long r5)
+{
+	u32 heapsize = 16*1024*1024 - (u32)_end;
+
+	simple_alloc_init(_end, heapsize, 32, 64);
+	fdt_init(_dtb_start);
+
+	if (!ug_grab_io_base() && ug_is_adapter_present())
+		console_ops.write = ug_console_write;
+}
+
diff --git a/arch/powerpc/boot/ugecon.c b/arch/powerpc/boot/ugecon.c
new file mode 100644
index 0000000..704f374
--- /dev/null
+++ b/arch/powerpc/boot/ugecon.c
@@ -0,0 +1,128 @@
+/*
+ * arch/powerpc/boot/ugecon.c
+ *
+ * USB Gecko bootwrapper console.
+ * Copyright (C) 2008-2009 The GameCube Linux Team
+ * Copyright (C) 2008,2009 Albert Herranz
+ *
+ * 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 <stddef.h>
+#include "stdio.h"
+#include "types.h"
+#include "io.h"
+#include "ops.h"
+
+
+#define EXI_CLK_32MHZ           5
+
+#define EXI_CSR                 0x00
+#define   EXI_CSR_CLKMASK       (0x7<<4)
+#define     EXI_CSR_CLK_32MHZ   (EXI_CLK_32MHZ<<4)
+#define   EXI_CSR_CSMASK        (0x7<<7)
+#define     EXI_CSR_CS_0        (0x1<<7)  /* Chip Select 001 */
+
+#define EXI_CR                  0x0c
+#define   EXI_CR_TSTART         (1<<0)
+#define   EXI_CR_WRITE		(1<<2)
+#define   EXI_CR_READ_WRITE     (2<<2)
+#define   EXI_CR_TLEN(len)      (((len)-1)<<4)
+
+#define EXI_DATA                0x10
+
+
+/* virtual address base for input/output, retrieved from device tree */
+static void *ug_io_base;
+
+
+static u32 ug_io_transaction(u32 in)
+{
+	u32 *csr_reg = ug_io_base + EXI_CSR;
+	u32 *data_reg = ug_io_base + EXI_DATA;
+	u32 *cr_reg = ug_io_base + EXI_CR;
+	u32 csr, data, cr;
+
+	/* select */
+	csr = EXI_CSR_CLK_32MHZ | EXI_CSR_CS_0;
+	out_be32(csr_reg, csr);
+
+	/* read/write */
+	data = in;
+	out_be32(data_reg, data);
+	cr = EXI_CR_TLEN(2) | EXI_CR_READ_WRITE | EXI_CR_TSTART;
+	out_be32(cr_reg, cr);
+
+	while (in_be32(cr_reg) & EXI_CR_TSTART)
+		barrier();
+
+	/* deselect */
+	out_be32(csr_reg, 0);
+
+	data = in_be32(data_reg);
+	return data;
+}
+
+static int ug_is_txfifo_ready(void)
+{
+	return ug_io_transaction(0xc0000000) & 0x04000000;
+}
+
+static void ug_raw_putc(char ch)
+{
+	ug_io_transaction(0xb0000000 | (ch << 20));
+}
+
+static void ug_putc(char ch)
+{
+	int count = 16;
+
+	if (!ug_io_base)
+		return;
+
+	while (!ug_is_txfifo_ready() && count--)
+		barrier();
+	if (count)
+		ug_raw_putc(ch);
+}
+
+void ug_console_write(const char *buf, int len)
+{
+	char *b = (char *)buf;
+
+	while (len--) {
+		if (*b == '\n')
+			ug_putc('\r');
+		ug_putc(*b++);
+	}
+}
+
+int ug_is_adapter_present(void)
+{
+	if (!ug_io_base)
+		return 0;
+
+	return ug_io_transaction(0x90000000) == 0x04700000;
+}
+
+int ug_grab_io_base(void)
+{
+	u32 v;
+	void *devp;
+
+	devp = find_node_by_alias("ugecon");
+	if (devp == NULL)
+		goto err_out;
+	if (getprop(devp, "virtual-reg", &v, sizeof(v)) != sizeof(v))
+		goto err_out;
+
+	ug_io_base = (u8 *)v;
+	return 0;
+
+err_out:
+	return -1;
+}
diff --git a/arch/powerpc/boot/ugecon.h b/arch/powerpc/boot/ugecon.h
new file mode 100644
index 0000000..1fdb590
--- /dev/null
+++ b/arch/powerpc/boot/ugecon.h
@@ -0,0 +1,25 @@
+/*
+ * arch/powerpc/boot/ugecon.h
+ *
+ * USB Gecko early bootwrapper console.
+ * Copyright (C) 2008-2009 The GameCube Linux Team
+ * Copyright (C) 2008,2009 Albert Herranz
+ *
+ * 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.
+ *
+ */
+
+#ifndef __UGECON_H
+#define __UGECON_H
+
+extern int ug_grab_io_base(void);
+extern int ug_is_adapter_present(void);
+
+extern void ug_putc(char ch);
+extern void ug_console_write(const char *buf, int len);
+
+#endif /* __UGECON_H */
+
diff --git a/arch/powerpc/boot/wii.c b/arch/powerpc/boot/wii.c
new file mode 100644
index 0000000..e1c53de
--- /dev/null
+++ b/arch/powerpc/boot/wii.c
@@ -0,0 +1,313 @@
+/*
+ * arch/powerpc/boot/wii.c
+ *
+ * Nintendo Wii platform
+ * Copyright (C) 2004-2009 The GameCube Linux Team
+ * Copyright (C) 2008,2009 Albert Herranz
+ *
+ * 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 <stddef.h>
+#include "stdio.h"
+#include "types.h"
+#include "io.h"
+#include "ops.h"
+
+#include "ugecon.h"
+
+
+BSS_STACK(8192);
+
+/*
+ * We enter with the MMU enabled and some legacy memory mappings active.
+ *
+ * We leave the MMU enabled, but we switch to an identity mapped memory
+ * scheme as expected by the start code.
+ *
+ */
+asm ("\n\
+.text\n\
+.globl _zimage_start\n\
+_zimage_start:\n\
+\n\
+	mfmsr	9\n\
+	andi.	0, 9, (1<<4)|(1<<5) /* MSR_DR|MSR_IR */\n\
+	bcl	20, 31, 1f\n\
+1: \n\
+	mflr	8\n\
+	clrlwi	8, 8, 3		/* convert to a real address */\n\
+	addi	8, 8, _mmu_off - 1b\n\
+	andc	9, 9, 0\n\
+	mtspr	0x01a, 8	/* SRR0 */\n\
+	mtspr	0x01b, 9	/* SRR1 */\n\
+	sync\n\
+	rfi\n\
+_mmu_off: \n\
+	/* MMU disabled */\n\
+\n\
+	/* Setup BATs */\n\
+	isync\n\
+	li      8, 0\n\
+	mtspr	0x210, 8	/* IBAT0U */\n\
+	mtspr	0x211, 8	/* IBAT0L */\n\
+	mtspr	0x212, 8	/* IBAT1U */\n\
+	mtspr	0x213, 8	/* IBAT1L */\n\
+	mtspr	0x214, 8	/* IBAT2U */\n\
+	mtspr	0x215, 8	/* IBAT2L */\n\
+	mtspr	0x216, 8	/* IBAT3U */\n\
+	mtspr	0x217, 8	/* IBAT3L */\n\
+	mtspr	0x218, 8	/* DBAT0U */\n\
+	mtspr	0x219, 8	/* DBAT0L */\n\
+	mtspr	0x21a, 8	/* DBAT1U */\n\
+	mtspr	0x21b, 8	/* DBAT1L */\n\
+	mtspr	0x21c, 8	/* DBAT2U */\n\
+	mtspr	0x21d, 8	/* DBAT2L */\n\
+	mtspr	0x21e, 8	/* DBAT3U */\n\
+	mtspr	0x21f, 8	/* DBAT3L */\n\
+\n\
+	mtspr	0x230, 8	/* IBAT4U */\n\
+	mtspr	0x231, 8	/* IBAT4L */\n\
+	mtspr	0x232, 8	/* IBAT5U */\n\
+	mtspr	0x233, 8	/* IBAT5L */\n\
+	mtspr	0x234, 8	/* IBAT6U */\n\
+	mtspr	0x235, 8	/* IBAT6L */\n\
+	mtspr	0x236, 8	/* IBAT7U */\n\
+	mtspr	0x237, 8	/* IBAT7L */\n\
+	mtspr	0x238, 8	/* DBAT4U */\n\
+	mtspr	0x239, 8	/* DBAT4L */\n\
+	mtspr	0x23a, 8	/* DBAT5U */\n\
+	mtspr	0x23b, 8	/* DBAT5L */\n\
+	mtspr	0x23c, 8	/* DBAT6U */\n\
+	mtspr	0x23d, 8	/* DBAT6L */\n\
+	mtspr	0x23e, 8	/* DBAT7U */\n\
+	mtspr	0x23f, 8	/* DBAT7L */\n\
+\n\
+	isync\n\
+	li	8, 0x01ff	/* first 16MiB */\n\
+	li	9, 0x0002	/* rw */\n\
+	mtspr	0x210, 8	/* IBAT0U */\n\
+	mtspr	0x211, 9	/* IBAT0L */\n\
+	mtspr	0x218, 8	/* DBAT0U */\n\
+	mtspr	0x219, 9	/* DBAT0L */\n\
+\n\
+	lis	8, 0xcc00	/* I/O mem */\n\
+	ori	8, 8, 0x3ff	/* 32MiB */\n\
+	lis	9, 0x0c00\n\
+	ori	9, 9, 0x002a	/* uncached, guarded, rw */\n\
+	mtspr	0x21a, 8	/* DBAT1U */\n\
+	mtspr	0x21b, 9	/* DBAT1L */\n\
+\n\
+	lis	8, 0x0100	/* next 8MiB */\n\
+	ori	8, 8, 0x00ff	/* 8MiB */\n\
+	lis	9, 0x0100	/* next 8MiB */\n\
+	ori	9, 9, 0x0002	/* rw */\n\
+	mtspr	0x214, 8	/* IBAT2U */\n\
+	mtspr	0x215, 9	/* IBAT2L */\n\
+	mtspr	0x21c, 8	/* DBAT2U */\n\
+	mtspr	0x21d, 9	/* DBAT2L */\n\
+\n\
+	lis	8, 0x1000	/* MEM2 */\n\
+	ori	8, 8, 0x07ff	/* 64MiB */\n\
+	lis	9, 0x1000\n\
+	ori	9, 9, 0x0002	/* rw */\n\
+	mtspr	0x216, 8	/* IBAT3U */\n\
+	mtspr	0x217, 9	/* IBAT3L */\n\
+	mtspr	0x21e, 8	/* DBAT3U */\n\
+	mtspr	0x21f, 9	/* DBAT3L */\n\
+\n\
+	sync\n\
+	isync\n\
+\n\
+	/* enable high BATs */\n\
+	lis	8, 0x8200\n\
+	mtspr	0x3f3, 8	/* HID4 */\n\
+\n\
+	/* enable caches */\n\
+	mfspr	8, 0x3f0\n\
+	ori	8, 8, 0xc000\n\
+	mtspr	0x3f0, 8	/* HID0 */\n\
+	isync\n\
+\n\
+	li	3, 0\n\
+	li	4, 0\n\
+	li	5, 0\n\
+\n\
+	bcl	20, 31, 1f\n\
+1: \n\
+	mflr    8\n\
+	addi    8, 8, _mmu_on - 1b\n\
+	mfmsr	9\n\
+	ori	9, 9, (1<<4)|(1<<5) /* MSR_DR|MSR_IR */\n\
+	mtspr	0x01a, 8	/* SRR0 */\n\
+	mtspr	0x01b, 9	/* SRR1 */\n\
+	sync\n\
+	rfi\n\
+_mmu_on: \n\
+	/* turn on the front blue led (aka: yay! we got here!) */\n\
+	lis	8, 0xcd00\n\
+	ori	8, 8, 0x00c0\n\
+	lwz	9, 0(8)\n\
+	ori	9, 9, 0x20\n\
+	stw	9, 0(8)\n\
+	b _zimage_start_lib\n\
+");
+
+static int save_lowmem_stub(void)
+{
+	void *src, *dst;
+	size_t size;
+	u32 reg[2];
+	u32 v;
+	void *devp;
+
+	devp = finddevice("/lowmem-stub");
+	if (devp == NULL) {
+		printf("lowmem-stub: none\n");
+		goto err_out;
+	}
+
+	if (getprop(devp, "reg", reg, sizeof(reg)) != sizeof(reg)) {
+		printf("unable to find %s property\n", "reg");
+		goto err_out;
+	}
+	src = (void *)reg[0];
+	size = reg[1];
+	if (getprop(devp, "save-area", &v, sizeof(v)) != sizeof(v)) {
+		printf("unable to find %s property\n", "save-area");
+		goto err_out;
+	}
+	dst = (void *)v;
+
+	printf("lowmem-stub: relocating from %08lX to %08lX (%u bytes)\n",
+		(unsigned long)src, (unsigned long)dst, size);
+	memcpy(dst, src, size);
+	flush_cache(dst, size);
+
+	return 0;
+err_out:
+	return -1;
+}
+
+/*
+ *
+ *
+ */
+
+struct mipc_infohdr {
+	char magic[3];
+	u8 version;
+	u32 mem2_boundary;
+	u32 ipc_in;
+	size_t ipc_in_size;
+	u32 ipc_out;
+	size_t ipc_out_size;
+};
+
+static int mipc_check_address(u32 pa)
+{
+	if (pa < 0x10000000 || pa > 0x14000000)
+		return -EINVAL;
+	return 0;
+}
+
+static void platform_fixups(void)
+{
+	struct mipc_infohdr **hdrp, *hdr;
+	u32 reg[4];
+	u32 mem2_boundary, top;
+	void *devp;
+
+	/*
+	 * The mini header pointer is specified in the second "reg" entry
+	 * of the starlet-mini-ipc node.
+	 */
+	devp = find_node_by_compatible(NULL, "twiizers,starlet-mini-ipc");
+	if (!devp) {
+		printf("unable to find %s node\n", "twiizers,starlet-mini-ipc");
+		goto err_out;
+	}
+	if (getprop(devp, "reg", &reg, sizeof(reg)) != sizeof(reg)) {
+		printf("unable to find %s property\n", "reg");
+		goto err_out;
+	}
+	hdrp = (struct mipc_infohdr **)reg[2];
+	if (mipc_check_address((u32)hdrp)) {
+		printf("mini: invalid hdrp %08X\n", (u32)hdrp);
+		goto err_out;
+	}
+
+	hdr = *hdrp;
+	if (mipc_check_address((u32)hdr)) {
+		printf("mini: invalid hdr %08X\n", (u32)hdr);
+		goto err_out;
+	}
+	if (memcmp(hdr->magic, "IPC", 3)) {
+		printf("mini: invalid magic, asuming ios\n");
+		goto err_out;
+	}
+	mem2_boundary = hdr->mem2_boundary;
+	if (mipc_check_address(mem2_boundary)) {
+		printf("mini: invalid mem2_boundary %08X\n", mem2_boundary);
+		goto err_out;
+	}
+
+	top = mem2_boundary;
+	printf("top of mem @ %08X (%s)\n", top, "current");
+
+	/* fixup local memory for EHCI controller */
+	devp = NULL;
+	while ((devp = find_node_by_compatible(devp,
+					       "nintendo,hollywood-ehci"))) {
+		if (getprop(devp, "reg", &reg, sizeof(reg)) == sizeof(reg)) {
+			top -= reg[3];
+			printf("ehci %08X -> %08X\n", reg[2], top);
+			reg[2] = top;
+			setprop(devp, "reg", &reg, sizeof(reg));
+		}
+	}
+
+	/* fixup local memory for OHCI controllers */
+	devp = NULL;
+	while ((devp = find_node_by_compatible(devp,
+					       "nintendo,hollywood-ohci"))) {
+		if (getprop(devp, "reg", &reg, sizeof(reg)) == sizeof(reg)) {
+			top -= reg[3];
+			printf("ohci %08X -> %08X\n", reg[2], top);
+			reg[2] = top;
+			setprop(devp, "reg", &reg, sizeof(reg));
+		}
+	}
+
+	/* fixup available memory */
+	dt_fixup_memory(0, top);
+
+	printf("top of mem @ %08X (%s)\n", top, "final");
+
+	return;
+
+err_out:
+	return;
+}
+
+/*
+ *
+ */
+void platform_init(unsigned long r3, unsigned long r4, unsigned long r5)
+{
+	u32 heapsize = 24*1024*1024 - (u32)_end;
+
+	simple_alloc_init(_end, heapsize, 32, 64);
+	fdt_init(_dtb_start);
+
+	if (!ug_grab_io_base() && ug_is_adapter_present())
+		console_ops.write = ug_console_write;
+
+	platform_ops.fixups = platform_fixups;
+	save_lowmem_stub();
+}
+
diff --git a/arch/powerpc/boot/wrapper b/arch/powerpc/boot/wrapper
index ac9e9a5..0e9ce10 100755
--- a/arch/powerpc/boot/wrapper
+++ b/arch/powerpc/boot/wrapper
@@ -230,6 +230,9 @@ xpedite52*)
     link_address='0x1400000'
     platformo=$object/cuboot-85xx.o
     ;;
+wii|gamecube)
+    link_address='0x600000'
+    ;;
 esac
 
 vmz="$tmpdir/`basename \"$kernel\"`.$ext"
diff --git a/arch/powerpc/configs/gamecube_defconfig b/arch/powerpc/configs/gamecube_defconfig
new file mode 100644
index 0000000..25d4446
--- /dev/null
+++ b/arch/powerpc/configs/gamecube_defconfig
@@ -0,0 +1,1083 @@
+#
+# Automatically generated make config: don't edit
+# Linux kernel version: 2.6.32
+# Sun Dec  6 20:15:08 2009
+#
+# CONFIG_PPC64 is not set
+
+#
+# Processor support
+#
+CONFIG_PPC_BOOK3S_32=y
+# CONFIG_PPC_85xx is not set
+# CONFIG_PPC_8xx is not set
+# CONFIG_40x is not set
+# CONFIG_44x is not set
+# CONFIG_E200 is not set
+CONFIG_PPC_BOOK3S=y
+CONFIG_6xx=y
+CONFIG_PPC_FPU=y
+# CONFIG_ALTIVEC is not set
+CONFIG_PPC_STD_MMU=y
+CONFIG_PPC_STD_MMU_32=y
+# CONFIG_PPC_MM_SLICES is not set
+CONFIG_PPC_HAVE_PMU_SUPPORT=y
+CONFIG_PPC_PERF_CTRS=y
+# CONFIG_SMP is not set
+CONFIG_NOT_COHERENT_CACHE=y
+CONFIG_PPC32=y
+CONFIG_WORD_SIZE=32
+# CONFIG_ARCH_PHYS_ADDR_T_64BIT is not set
+CONFIG_MMU=y
+CONFIG_GENERIC_CMOS_UPDATE=y
+CONFIG_GENERIC_TIME=y
+CONFIG_GENERIC_TIME_VSYSCALL=y
+CONFIG_GENERIC_CLOCKEVENTS=y
+CONFIG_GENERIC_HARDIRQS=y
+CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ=y
+# CONFIG_HAVE_SETUP_PER_CPU_AREA is not set
+# CONFIG_NEED_PER_CPU_EMBED_FIRST_CHUNK is not set
+CONFIG_IRQ_PER_CPU=y
+CONFIG_STACKTRACE_SUPPORT=y
+CONFIG_HAVE_LATENCYTOP_SUPPORT=y
+CONFIG_TRACE_IRQFLAGS_SUPPORT=y
+CONFIG_LOCKDEP_SUPPORT=y
+CONFIG_RWSEM_XCHGADD_ALGORITHM=y
+CONFIG_ARCH_HAS_ILOG2_U32=y
+CONFIG_GENERIC_HWEIGHT=y
+CONFIG_GENERIC_FIND_NEXT_BIT=y
+# CONFIG_ARCH_NO_VIRT_TO_BUS is not set
+CONFIG_PPC=y
+CONFIG_EARLY_PRINTK=y
+CONFIG_GENERIC_NVRAM=y
+CONFIG_SCHED_OMIT_FRAME_POINTER=y
+CONFIG_ARCH_MAY_HAVE_PC_FDC=y
+CONFIG_PPC_OF=y
+CONFIG_OF=y
+# CONFIG_PPC_UDBG_16550 is not set
+# CONFIG_GENERIC_TBSYNC is not set
+CONFIG_AUDIT_ARCH=y
+CONFIG_GENERIC_BUG=y
+CONFIG_DTC=y
+# CONFIG_DEFAULT_UIMAGE is not set
+# CONFIG_PPC_DCR_NATIVE is not set
+# CONFIG_PPC_DCR_MMIO is not set
+CONFIG_ARCH_SUPPORTS_DEBUG_PAGEALLOC=y
+CONFIG_DEFCONFIG_LIST="/lib/modules/$UNAME_RELEASE/.config"
+CONFIG_CONSTRUCTORS=y
+
+#
+# General setup
+#
+CONFIG_EXPERIMENTAL=y
+CONFIG_BROKEN_ON_SMP=y
+CONFIG_LOCK_KERNEL=y
+CONFIG_INIT_ENV_ARG_LIMIT=32
+CONFIG_LOCALVERSION="-isobel-gcn"
+CONFIG_LOCALVERSION_AUTO=y
+CONFIG_SWAP=y
+CONFIG_SYSVIPC=y
+CONFIG_SYSVIPC_SYSCTL=y
+# CONFIG_POSIX_MQUEUE is not set
+# CONFIG_BSD_PROCESS_ACCT is not set
+# CONFIG_TASKSTATS is not set
+# CONFIG_AUDIT is not set
+
+#
+# RCU Subsystem
+#
+CONFIG_TREE_RCU=y
+# CONFIG_TREE_PREEMPT_RCU is not set
+# CONFIG_RCU_TRACE is not set
+CONFIG_RCU_FANOUT=32
+# CONFIG_RCU_FANOUT_EXACT is not set
+# CONFIG_TREE_RCU_TRACE is not set
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_LOG_BUF_SHIFT=14
+CONFIG_GROUP_SCHED=y
+CONFIG_FAIR_GROUP_SCHED=y
+# CONFIG_RT_GROUP_SCHED is not set
+CONFIG_USER_SCHED=y
+# CONFIG_CGROUP_SCHED is not set
+# CONFIG_CGROUPS is not set
+CONFIG_SYSFS_DEPRECATED=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+# CONFIG_RELAY is not set
+# CONFIG_NAMESPACES is not set
+CONFIG_BLK_DEV_INITRD=y
+CONFIG_INITRAMFS_SOURCE=""
+CONFIG_RD_GZIP=y
+# CONFIG_RD_BZIP2 is not set
+# CONFIG_RD_LZMA is not set
+# CONFIG_CC_OPTIMIZE_FOR_SIZE is not set
+CONFIG_SYSCTL=y
+CONFIG_ANON_INODES=y
+CONFIG_EMBEDDED=y
+CONFIG_SYSCTL_SYSCALL=y
+CONFIG_KALLSYMS=y
+CONFIG_KALLSYMS_ALL=y
+# CONFIG_KALLSYMS_EXTRA_PASS is not set
+CONFIG_HOTPLUG=y
+CONFIG_PRINTK=y
+CONFIG_BUG=y
+# CONFIG_ELF_CORE is not set
+CONFIG_BASE_FULL=y
+CONFIG_FUTEX=y
+CONFIG_EPOLL=y
+CONFIG_SIGNALFD=y
+CONFIG_TIMERFD=y
+CONFIG_EVENTFD=y
+CONFIG_SHMEM=y
+CONFIG_AIO=y
+CONFIG_HAVE_PERF_EVENTS=y
+
+#
+# Kernel Performance Events And Counters
+#
+CONFIG_PERF_EVENTS=y
+CONFIG_EVENT_PROFILE=y
+CONFIG_PERF_COUNTERS=y
+# CONFIG_DEBUG_PERF_USE_VMALLOC is not set
+# CONFIG_VM_EVENT_COUNTERS is not set
+CONFIG_COMPAT_BRK=y
+CONFIG_SLAB=y
+# CONFIG_SLUB is not set
+# CONFIG_SLOB is not set
+# CONFIG_PROFILING is not set
+CONFIG_TRACEPOINTS=y
+CONFIG_HAVE_OPROFILE=y
+# CONFIG_KPROBES is not set
+CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y
+CONFIG_HAVE_IOREMAP_PROT=y
+CONFIG_HAVE_KPROBES=y
+CONFIG_HAVE_KRETPROBES=y
+CONFIG_HAVE_ARCH_TRACEHOOK=y
+CONFIG_HAVE_DMA_ATTRS=y
+CONFIG_HAVE_DMA_API_DEBUG=y
+
+#
+# GCOV-based kernel profiling
+#
+# CONFIG_GCOV_KERNEL is not set
+CONFIG_SLOW_WORK=y
+# CONFIG_SLOW_WORK_DEBUG is not set
+# CONFIG_HAVE_GENERIC_DMA_COHERENT is not set
+CONFIG_SLABINFO=y
+CONFIG_RT_MUTEXES=y
+CONFIG_BASE_SMALL=0
+CONFIG_MODULES=y
+# CONFIG_MODULE_FORCE_LOAD is not set
+CONFIG_MODULE_UNLOAD=y
+# CONFIG_MODULE_FORCE_UNLOAD is not set
+# CONFIG_MODVERSIONS is not set
+# CONFIG_MODULE_SRCVERSION_ALL is not set
+CONFIG_BLOCK=y
+CONFIG_LBDAF=y
+# CONFIG_BLK_DEV_BSG is not set
+# CONFIG_BLK_DEV_INTEGRITY is not set
+
+#
+# IO Schedulers
+#
+CONFIG_IOSCHED_NOOP=y
+CONFIG_IOSCHED_AS=y
+CONFIG_IOSCHED_DEADLINE=y
+CONFIG_IOSCHED_CFQ=y
+CONFIG_DEFAULT_AS=y
+# CONFIG_DEFAULT_DEADLINE is not set
+# CONFIG_DEFAULT_CFQ is not set
+# CONFIG_DEFAULT_NOOP is not set
+CONFIG_DEFAULT_IOSCHED="anticipatory"
+# CONFIG_FREEZER is not set
+
+#
+# Platform support
+#
+# CONFIG_PPC_CHRP is not set
+# CONFIG_MPC5121_ADS is not set
+# CONFIG_MPC5121_GENERIC is not set
+# CONFIG_PPC_MPC52xx is not set
+# CONFIG_PPC_PMAC is not set
+# CONFIG_PPC_CELL is not set
+# CONFIG_PPC_CELL_NATIVE is not set
+# CONFIG_PPC_82xx is not set
+# CONFIG_PQ2ADS is not set
+# CONFIG_PPC_83xx is not set
+# CONFIG_PPC_86xx is not set
+CONFIG_EMBEDDED6xx=y
+# CONFIG_LINKSTATION is not set
+# CONFIG_STORCENTER is not set
+# CONFIG_MPC7448HPC2 is not set
+# CONFIG_PPC_HOLLY is not set
+# CONFIG_PPC_PRPMC2800 is not set
+# CONFIG_PPC_C2K is not set
+CONFIG_GAMECUBE=y
+# CONFIG_WII is not set
+CONFIG_FLIPPER_PIC=y
+CONFIG_GAMECUBE_COMMON=y
+CONFIG_GAMECUBE_UDBG=y
+CONFIG_USBGECKO_UDBG=y
+# CONFIG_GAMECUBE_VIDEO_UDBG is not set
+CONFIG_GAMECUBE_RSW=y
+# CONFIG_AMIGAONE is not set
+# CONFIG_PPC_OF_BOOT_TRAMPOLINE is not set
+# CONFIG_IPIC is not set
+# CONFIG_MPIC is not set
+# CONFIG_MPIC_WEIRD is not set
+# CONFIG_PPC_I8259 is not set
+# CONFIG_PPC_RTAS is not set
+# CONFIG_MMIO_NVRAM is not set
+# CONFIG_PPC_MPC106 is not set
+# CONFIG_PPC_970_NAP is not set
+# CONFIG_PPC_INDIRECT_IO is not set
+# CONFIG_GENERIC_IOMAP is not set
+# CONFIG_CPU_FREQ is not set
+# CONFIG_TAU is not set
+# CONFIG_FSL_ULI1575 is not set
+# CONFIG_SIMPLE_GPIO is not set
+
+#
+# Kernel options
+#
+# CONFIG_HIGHMEM is not set
+# CONFIG_NO_HZ is not set
+# CONFIG_HIGH_RES_TIMERS is not set
+CONFIG_GENERIC_CLOCKEVENTS_BUILD=y
+# CONFIG_HZ_100 is not set
+CONFIG_HZ_250=y
+# CONFIG_HZ_300 is not set
+# CONFIG_HZ_1000 is not set
+CONFIG_HZ=250
+# CONFIG_SCHED_HRTICK is not set
+# CONFIG_PREEMPT_NONE is not set
+# CONFIG_PREEMPT_VOLUNTARY is not set
+CONFIG_PREEMPT=y
+CONFIG_BINFMT_ELF=y
+# CONFIG_HAVE_AOUT is not set
+CONFIG_BINFMT_MISC=m
+# CONFIG_IOMMU_HELPER is not set
+# CONFIG_SWIOTLB is not set
+CONFIG_ARCH_ENABLE_MEMORY_HOTPLUG=y
+CONFIG_ARCH_HAS_WALK_MEMORY=y
+CONFIG_ARCH_ENABLE_MEMORY_HOTREMOVE=y
+CONFIG_KEXEC=y
+# CONFIG_CRASH_DUMP is not set
+CONFIG_MAX_ACTIVE_REGIONS=32
+CONFIG_ARCH_FLATMEM_ENABLE=y
+CONFIG_ARCH_POPULATES_NODE_MAP=y
+CONFIG_SELECT_MEMORY_MODEL=y
+CONFIG_FLATMEM_MANUAL=y
+# CONFIG_DISCONTIGMEM_MANUAL is not set
+# CONFIG_SPARSEMEM_MANUAL is not set
+CONFIG_FLATMEM=y
+CONFIG_FLAT_NODE_MEM_MAP=y
+CONFIG_PAGEFLAGS_EXTENDED=y
+CONFIG_SPLIT_PTLOCK_CPUS=4
+# CONFIG_MIGRATION is not set
+# CONFIG_PHYS_ADDR_T_64BIT is not set
+CONFIG_ZONE_DMA_FLAG=1
+CONFIG_BOUNCE=y
+CONFIG_VIRT_TO_BUS=y
+CONFIG_HAVE_MLOCK=y
+CONFIG_HAVE_MLOCKED_PAGE_BIT=y
+# CONFIG_KSM is not set
+CONFIG_DEFAULT_MMAP_MIN_ADDR=4096
+CONFIG_PPC_4K_PAGES=y
+# CONFIG_PPC_16K_PAGES is not set
+# CONFIG_PPC_64K_PAGES is not set
+# CONFIG_PPC_256K_PAGES is not set
+CONFIG_FORCE_MAX_ZONEORDER=11
+CONFIG_PROC_DEVICETREE=y
+# CONFIG_CMDLINE_BOOL is not set
+CONFIG_EXTRA_TARGETS=""
+# CONFIG_PM is not set
+# CONFIG_SECCOMP is not set
+CONFIG_ISA_DMA_API=y
+
+#
+# Bus options
+#
+CONFIG_ZONE_DMA=y
+CONFIG_GENERIC_ISA_DMA=y
+# CONFIG_PCI is not set
+# CONFIG_PCI_DOMAINS is not set
+# CONFIG_PCI_SYSCALL is not set
+# CONFIG_ARCH_SUPPORTS_MSI is not set
+# CONFIG_PCCARD is not set
+# CONFIG_HAS_RAPIDIO is not set
+
+#
+# Advanced setup
+#
+CONFIG_ADVANCED_OPTIONS=y
+# CONFIG_LOWMEM_SIZE_BOOL is not set
+CONFIG_LOWMEM_SIZE=0x30000000
+# CONFIG_PAGE_OFFSET_BOOL is not set
+CONFIG_PAGE_OFFSET=0xc0000000
+# CONFIG_KERNEL_START_BOOL is not set
+CONFIG_KERNEL_START=0xc0000000
+CONFIG_PHYSICAL_START=0x00000000
+# CONFIG_TASK_SIZE_BOOL is not set
+CONFIG_TASK_SIZE=0xc0000000
+# CONFIG_CONSISTENT_SIZE_BOOL is not set
+CONFIG_CONSISTENT_SIZE=0x00200000
+CONFIG_NET=y
+
+#
+# Networking options
+#
+CONFIG_PACKET=y
+# CONFIG_PACKET_MMAP is not set
+CONFIG_UNIX=y
+# CONFIG_NET_KEY is not set
+CONFIG_INET=y
+# CONFIG_IP_MULTICAST is not set
+# CONFIG_IP_ADVANCED_ROUTER is not set
+CONFIG_IP_FIB_HASH=y
+CONFIG_IP_PNP=y
+CONFIG_IP_PNP_DHCP=y
+# CONFIG_IP_PNP_BOOTP is not set
+CONFIG_IP_PNP_RARP=y
+# CONFIG_NET_IPIP is not set
+# CONFIG_NET_IPGRE is not set
+# CONFIG_ARPD is not set
+# CONFIG_SYN_COOKIES is not set
+# CONFIG_INET_AH is not set
+# CONFIG_INET_ESP is not set
+# CONFIG_INET_IPCOMP is not set
+# CONFIG_INET_XFRM_TUNNEL is not set
+# CONFIG_INET_TUNNEL is not set
+# CONFIG_INET_XFRM_MODE_TRANSPORT is not set
+# CONFIG_INET_XFRM_MODE_TUNNEL is not set
+# CONFIG_INET_XFRM_MODE_BEET is not set
+# CONFIG_INET_LRO is not set
+# CONFIG_INET_DIAG is not set
+# CONFIG_TCP_CONG_ADVANCED is not set
+CONFIG_TCP_CONG_CUBIC=y
+CONFIG_DEFAULT_TCP_CONG="cubic"
+# CONFIG_TCP_MD5SIG is not set
+# CONFIG_IPV6 is not set
+# CONFIG_NETWORK_SECMARK is not set
+# CONFIG_NETFILTER is not set
+# CONFIG_IP_DCCP is not set
+# CONFIG_IP_SCTP is not set
+# CONFIG_RDS is not set
+# CONFIG_TIPC is not set
+# CONFIG_ATM is not set
+# CONFIG_BRIDGE is not set
+# CONFIG_NET_DSA is not set
+# CONFIG_VLAN_8021Q is not set
+# CONFIG_DECNET is not set
+# CONFIG_LLC2 is not set
+# CONFIG_IPX is not set
+# CONFIG_ATALK is not set
+# CONFIG_X25 is not set
+# CONFIG_LAPB is not set
+# CONFIG_ECONET is not set
+# CONFIG_WAN_ROUTER is not set
+# CONFIG_PHONET is not set
+# CONFIG_IEEE802154 is not set
+# CONFIG_NET_SCHED is not set
+# CONFIG_DCB is not set
+
+#
+# Network testing
+#
+# CONFIG_NET_PKTGEN is not set
+# CONFIG_NET_DROP_MONITOR is not set
+# CONFIG_HAMRADIO is not set
+# CONFIG_CAN is not set
+# CONFIG_IRDA is not set
+# CONFIG_BT is not set
+# CONFIG_AF_RXRPC is not set
+# CONFIG_WIRELESS is not set
+# CONFIG_WIMAX is not set
+# CONFIG_RFKILL is not set
+# CONFIG_NET_9P is not set
+
+#
+# Device Drivers
+#
+
+#
+# Generic Driver Options
+#
+CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug"
+# CONFIG_DEVTMPFS is not set
+# CONFIG_STANDALONE is not set
+CONFIG_PREVENT_FIRMWARE_BUILD=y
+# CONFIG_FW_LOADER is not set
+# CONFIG_DEBUG_DRIVER is not set
+# CONFIG_DEBUG_DEVRES is not set
+# CONFIG_SYS_HYPERVISOR is not set
+# CONFIG_CONNECTOR is not set
+# CONFIG_MTD is not set
+CONFIG_OF_DEVICE=y
+# CONFIG_PARPORT is not set
+CONFIG_BLK_DEV=y
+# CONFIG_BLK_DEV_FD is not set
+CONFIG_GAMECUBE_SD=y
+CONFIG_GAMECUBE_ARAM=y
+CONFIG_GAMECUBE_DI=y
+# CONFIG_BLK_DEV_COW_COMMON is not set
+CONFIG_BLK_DEV_LOOP=y
+# CONFIG_BLK_DEV_CRYPTOLOOP is not set
+CONFIG_BLK_DEV_NBD=m
+CONFIG_BLK_DEV_RAM=y
+CONFIG_BLK_DEV_RAM_COUNT=2
+CONFIG_BLK_DEV_RAM_SIZE=4096
+# CONFIG_BLK_DEV_XIP is not set
+# CONFIG_CDROM_PKTCDVD is not set
+# CONFIG_ATA_OVER_ETH is not set
+# CONFIG_BLK_DEV_HD is not set
+CONFIG_MISC_DEVICES=y
+CONFIG_GAMECUBE_GQR=m
+CONFIG_GAMECUBE_MI=m
+# CONFIG_ENCLOSURE_SERVICES is not set
+# CONFIG_C2PORT is not set
+
+#
+# EEPROM support
+#
+# CONFIG_EEPROM_93CX6 is not set
+CONFIG_HAVE_IDE=y
+# CONFIG_IDE is not set
+
+#
+# SCSI device support
+#
+# CONFIG_RAID_ATTRS is not set
+# CONFIG_SCSI is not set
+# CONFIG_SCSI_DMA is not set
+# CONFIG_SCSI_NETLINK is not set
+# CONFIG_ATA is not set
+# CONFIG_MD is not set
+# CONFIG_MACINTOSH_DRIVERS is not set
+CONFIG_NETDEVICES=y
+# CONFIG_DUMMY is not set
+# CONFIG_BONDING is not set
+# CONFIG_MACVLAN is not set
+# CONFIG_EQUALIZER is not set
+# CONFIG_TUN is not set
+# CONFIG_VETH is not set
+# CONFIG_PHYLIB is not set
+CONFIG_NET_ETHERNET=y
+# CONFIG_MII is not set
+CONFIG_GAMECUBE_BBA=y
+# CONFIG_ETHOC is not set
+# CONFIG_DNET is not set
+# CONFIG_IBM_NEW_EMAC_ZMII is not set
+# CONFIG_IBM_NEW_EMAC_RGMII is not set
+# CONFIG_IBM_NEW_EMAC_TAH is not set
+# CONFIG_IBM_NEW_EMAC_EMAC4 is not set
+# CONFIG_IBM_NEW_EMAC_NO_FLOW_CTRL is not set
+# CONFIG_IBM_NEW_EMAC_MAL_CLR_ICINTSTAT is not set
+# CONFIG_IBM_NEW_EMAC_MAL_COMMON_ERR is not set
+# CONFIG_B44 is not set
+# CONFIG_KS8842 is not set
+# CONFIG_KS8851_MLL is not set
+# CONFIG_XILINX_EMACLITE is not set
+# CONFIG_NETDEV_1000 is not set
+# CONFIG_NETDEV_10000 is not set
+# CONFIG_WLAN is not set
+
+#
+# Enable WiMAX (Networking options) to see the WiMAX drivers
+#
+# CONFIG_WAN is not set
+# CONFIG_PPP is not set
+# CONFIG_SLIP is not set
+# CONFIG_NETCONSOLE is not set
+# CONFIG_NETPOLL is not set
+# CONFIG_NET_POLL_CONTROLLER is not set
+# CONFIG_ISDN is not set
+# CONFIG_PHONE is not set
+
+#
+# Input device support
+#
+CONFIG_INPUT=y
+CONFIG_INPUT_FF_MEMLESS=m
+# CONFIG_INPUT_POLLDEV is not set
+
+#
+# Userland interfaces
+#
+# CONFIG_INPUT_MOUSEDEV is not set
+CONFIG_INPUT_JOYDEV=y
+CONFIG_INPUT_EVDEV=y
+# CONFIG_INPUT_EVBUG is not set
+
+#
+# Input Device Drivers
+#
+CONFIG_INPUT_KEYBOARD=y
+# CONFIG_KEYBOARD_ATKBD is not set
+# CONFIG_KEYBOARD_LKKBD is not set
+# CONFIG_KEYBOARD_NEWTON is not set
+# CONFIG_KEYBOARD_OPENCORES is not set
+# CONFIG_KEYBOARD_STOWAWAY is not set
+# CONFIG_KEYBOARD_SUNKBD is not set
+# CONFIG_KEYBOARD_XTKBD is not set
+# CONFIG_INPUT_MOUSE is not set
+CONFIG_INPUT_JOYSTICK=y
+# CONFIG_JOYSTICK_ANALOG is not set
+# CONFIG_JOYSTICK_A3D is not set
+# CONFIG_JOYSTICK_ADI is not set
+# CONFIG_JOYSTICK_COBRA is not set
+# CONFIG_JOYSTICK_GF2K is not set
+# CONFIG_JOYSTICK_GRIP is not set
+# CONFIG_JOYSTICK_GRIP_MP is not set
+# CONFIG_JOYSTICK_GUILLEMOT is not set
+# CONFIG_JOYSTICK_INTERACT is not set
+# CONFIG_JOYSTICK_SIDEWINDER is not set
+# CONFIG_JOYSTICK_TMDC is not set
+# CONFIG_JOYSTICK_IFORCE is not set
+# CONFIG_JOYSTICK_WARRIOR is not set
+# CONFIG_JOYSTICK_MAGELLAN is not set
+# CONFIG_JOYSTICK_SPACEORB is not set
+# CONFIG_JOYSTICK_SPACEBALL is not set
+# CONFIG_JOYSTICK_STINGER is not set
+# CONFIG_JOYSTICK_TWIDJOY is not set
+# CONFIG_JOYSTICK_ZHENHUA is not set
+# CONFIG_JOYSTICK_JOYDUMP is not set
+# CONFIG_INPUT_TABLET is not set
+# CONFIG_INPUT_TOUCHSCREEN is not set
+# CONFIG_INPUT_MISC is not set
+
+#
+# Hardware I/O ports
+#
+CONFIG_SERIO=y
+# CONFIG_SERIO_I8042 is not set
+# CONFIG_SERIO_SERPORT is not set
+# CONFIG_SERIO_LIBPS2 is not set
+# CONFIG_SERIO_RAW is not set
+# CONFIG_SERIO_XILINX_XPS_PS2 is not set
+# CONFIG_GAMEPORT is not set
+CONFIG_GAMECUBE_SI=y
+
+#
+# Character devices
+#
+CONFIG_VT=y
+CONFIG_CONSOLE_TRANSLATIONS=y
+CONFIG_VT_CONSOLE=y
+CONFIG_HW_CONSOLE=y
+# CONFIG_VT_HW_CONSOLE_BINDING is not set
+# CONFIG_DEVKMEM is not set
+# CONFIG_SERIAL_NONSTANDARD is not set
+
+#
+# Serial drivers
+#
+# CONFIG_SERIAL_8250 is not set
+
+#
+# Non-8250 serial port support
+#
+# CONFIG_SERIAL_UARTLITE is not set
+# CONFIG_SERIAL_USBGECKO is not set
+CONFIG_UNIX98_PTYS=y
+# CONFIG_DEVPTS_MULTIPLE_INSTANCES is not set
+CONFIG_LEGACY_PTYS=y
+CONFIG_LEGACY_PTY_COUNT=64
+# CONFIG_HVC_UDBG is not set
+# CONFIG_IPMI_HANDLER is not set
+# CONFIG_HW_RANDOM is not set
+# CONFIG_NVRAM is not set
+# CONFIG_R3964 is not set
+# CONFIG_RAW_DRIVER is not set
+# CONFIG_TCG_TPM is not set
+# CONFIG_I2C is not set
+
+#
+# EXI support
+#
+CONFIG_GAMECUBE_EXI=y
+# CONFIG_SPI is not set
+
+#
+# PPS support
+#
+# CONFIG_PPS is not set
+CONFIG_ARCH_WANT_OPTIONAL_GPIOLIB=y
+# CONFIG_GPIOLIB is not set
+# CONFIG_W1 is not set
+# CONFIG_POWER_SUPPLY is not set
+# CONFIG_HWMON is not set
+# CONFIG_THERMAL is not set
+# CONFIG_WATCHDOG is not set
+CONFIG_SSB_POSSIBLE=y
+
+#
+# Sonics Silicon Backplane
+#
+# CONFIG_SSB is not set
+
+#
+# Multifunction device drivers
+#
+# CONFIG_MFD_CORE is not set
+# CONFIG_MFD_SM501 is not set
+# CONFIG_HTC_PASIC3 is not set
+# CONFIG_MFD_TMIO is not set
+# CONFIG_REGULATOR is not set
+# CONFIG_MEDIA_SUPPORT is not set
+
+#
+# Graphics support
+#
+# CONFIG_VGASTATE is not set
+# CONFIG_VIDEO_OUTPUT_CONTROL is not set
+CONFIG_FB=y
+# CONFIG_FIRMWARE_EDID is not set
+# CONFIG_FB_DDC is not set
+# CONFIG_FB_BOOT_VESA_SUPPORT is not set
+CONFIG_FB_CFB_FILLRECT=y
+CONFIG_FB_CFB_COPYAREA=y
+CONFIG_FB_CFB_IMAGEBLIT=y
+# CONFIG_FB_CFB_REV_PIXELS_IN_BYTE is not set
+# CONFIG_FB_SYS_FILLRECT is not set
+# CONFIG_FB_SYS_COPYAREA is not set
+# CONFIG_FB_SYS_IMAGEBLIT is not set
+# CONFIG_FB_FOREIGN_ENDIAN is not set
+# CONFIG_FB_SYS_FOPS is not set
+# CONFIG_FB_SVGALIB is not set
+# CONFIG_FB_MACMODES is not set
+# CONFIG_FB_BACKLIGHT is not set
+# CONFIG_FB_MODE_HELPERS is not set
+# CONFIG_FB_TILEBLITTING is not set
+
+#
+# Frame buffer hardware drivers
+#
+# CONFIG_FB_OF is not set
+# CONFIG_FB_VGA16 is not set
+# CONFIG_FB_S1D13XXX is not set
+CONFIG_FB_GAMECUBE=y
+# CONFIG_FB_IBM_GXT4500 is not set
+# CONFIG_FB_VIRTUAL is not set
+# CONFIG_FB_METRONOME is not set
+# CONFIG_FB_MB862XX is not set
+# CONFIG_FB_BROADSHEET is not set
+# CONFIG_BACKLIGHT_LCD_SUPPORT is not set
+
+#
+# Display device support
+#
+# CONFIG_DISPLAY_SUPPORT is not set
+
+#
+# Console display driver support
+#
+# CONFIG_VGA_CONSOLE is not set
+CONFIG_DUMMY_CONSOLE=y
+CONFIG_FRAMEBUFFER_CONSOLE=y
+# CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY is not set
+# CONFIG_FRAMEBUFFER_CONSOLE_ROTATION is not set
+# CONFIG_FONTS is not set
+CONFIG_FONT_8x8=y
+CONFIG_FONT_8x16=y
+CONFIG_LOGO=y
+# CONFIG_LOGO_LINUX_MONO is not set
+# CONFIG_LOGO_LINUX_VGA16 is not set
+# CONFIG_LOGO_LINUX_CLUT224 is not set
+CONFIG_LOGO_GAMECUBE_CLUT224=y
+CONFIG_SOUND=y
+CONFIG_SOUND_OSS_CORE=y
+CONFIG_SOUND_OSS_CORE_PRECLAIM=y
+CONFIG_SND=y
+CONFIG_SND_TIMER=y
+CONFIG_SND_PCM=y
+CONFIG_SND_SEQUENCER=y
+# CONFIG_SND_SEQ_DUMMY is not set
+CONFIG_SND_OSSEMUL=y
+CONFIG_SND_MIXER_OSS=y
+CONFIG_SND_PCM_OSS=y
+CONFIG_SND_PCM_OSS_PLUGINS=y
+CONFIG_SND_SEQUENCER_OSS=y
+# CONFIG_SND_DYNAMIC_MINORS is not set
+CONFIG_SND_SUPPORT_OLD_API=y
+# CONFIG_SND_VERBOSE_PROCFS is not set
+# CONFIG_SND_VERBOSE_PRINTK is not set
+# CONFIG_SND_DEBUG is not set
+# CONFIG_SND_RAWMIDI_SEQ is not set
+# CONFIG_SND_OPL3_LIB_SEQ is not set
+# CONFIG_SND_OPL4_LIB_SEQ is not set
+# CONFIG_SND_SBAWE_SEQ is not set
+# CONFIG_SND_EMU10K1_SEQ is not set
+CONFIG_SND_DRIVERS=y
+# CONFIG_SND_DUMMY is not set
+# CONFIG_SND_VIRMIDI is not set
+# CONFIG_SND_MTPAV is not set
+# CONFIG_SND_SERIAL_U16550 is not set
+# CONFIG_SND_MPU401 is not set
+CONFIG_SND_PPC=y
+CONFIG_SND_GAMECUBE=y
+CONFIG_SND_GAMECUBE_MIC=m
+# CONFIG_SND_SOC is not set
+# CONFIG_SOUND_PRIME is not set
+CONFIG_HID_SUPPORT=y
+CONFIG_HID=y
+# CONFIG_HIDRAW is not set
+# CONFIG_HID_PID is not set
+
+#
+# Special HID drivers
+#
+# CONFIG_USB_SUPPORT is not set
+# CONFIG_MMC is not set
+# CONFIG_MEMSTICK is not set
+# CONFIG_NEW_LEDS is not set
+# CONFIG_ACCESSIBILITY is not set
+# CONFIG_EDAC is not set
+CONFIG_RTC_LIB=y
+CONFIG_RTC_CLASS=y
+CONFIG_RTC_HCTOSYS=y
+CONFIG_RTC_HCTOSYS_DEVICE="rtc0"
+# CONFIG_RTC_DEBUG is not set
+
+#
+# RTC interfaces
+#
+CONFIG_RTC_INTF_SYSFS=y
+CONFIG_RTC_INTF_PROC=y
+CONFIG_RTC_INTF_DEV=y
+# CONFIG_RTC_INTF_DEV_UIE_EMUL is not set
+# CONFIG_RTC_DRV_TEST is not set
+
+#
+# SPI RTC drivers
+#
+
+#
+# Platform RTC drivers
+#
+# CONFIG_RTC_DRV_CMOS is not set
+# CONFIG_RTC_DRV_DS1286 is not set
+# CONFIG_RTC_DRV_DS1511 is not set
+# CONFIG_RTC_DRV_DS1553 is not set
+# CONFIG_RTC_DRV_DS1742 is not set
+# CONFIG_RTC_DRV_STK17TA8 is not set
+# CONFIG_RTC_DRV_M48T86 is not set
+# CONFIG_RTC_DRV_M48T35 is not set
+# CONFIG_RTC_DRV_M48T59 is not set
+# CONFIG_RTC_DRV_BQ4802 is not set
+# CONFIG_RTC_DRV_V3020 is not set
+CONFIG_RTC_DRV_GCN=y
+
+#
+# on-CPU RTC drivers
+#
+CONFIG_RTC_DRV_GENERIC=y
+# CONFIG_DMADEVICES is not set
+# CONFIG_AUXDISPLAY is not set
+# CONFIG_UIO is not set
+
+#
+# TI VLYNQ
+#
+# CONFIG_STAGING is not set
+
+#
+# File systems
+#
+CONFIG_EXT2_FS=y
+# CONFIG_EXT2_FS_XATTR is not set
+# CONFIG_EXT2_FS_XIP is not set
+CONFIG_EXT3_FS=y
+# CONFIG_EXT3_DEFAULTS_TO_ORDERED is not set
+# CONFIG_EXT3_FS_XATTR is not set
+# CONFIG_EXT4_FS is not set
+CONFIG_JBD=y
+# CONFIG_JBD_DEBUG is not set
+# CONFIG_REISERFS_FS is not set
+# CONFIG_JFS_FS is not set
+# CONFIG_FS_POSIX_ACL is not set
+# CONFIG_XFS_FS is not set
+# CONFIG_GFS2_FS is not set
+# CONFIG_OCFS2_FS is not set
+# CONFIG_BTRFS_FS is not set
+# CONFIG_NILFS2_FS is not set
+CONFIG_FILE_LOCKING=y
+CONFIG_FSNOTIFY=y
+CONFIG_DNOTIFY=y
+CONFIG_INOTIFY=y
+CONFIG_INOTIFY_USER=y
+# CONFIG_QUOTA is not set
+# CONFIG_AUTOFS_FS is not set
+# CONFIG_AUTOFS4_FS is not set
+# CONFIG_FUSE_FS is not set
+
+#
+# Caches
+#
+# CONFIG_FSCACHE is not set
+
+#
+# CD-ROM/DVD Filesystems
+#
+CONFIG_ISO9660_FS=y
+CONFIG_JOLIET=y
+# CONFIG_ZISOFS is not set
+# CONFIG_UDF_FS is not set
+
+#
+# DOS/FAT/NT Filesystems
+#
+CONFIG_FAT_FS=y
+CONFIG_MSDOS_FS=y
+CONFIG_VFAT_FS=y
+CONFIG_FAT_DEFAULT_CODEPAGE=437
+CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1"
+# CONFIG_NTFS_FS is not set
+
+#
+# Pseudo filesystems
+#
+CONFIG_PROC_FS=y
+CONFIG_PROC_KCORE=y
+CONFIG_PROC_SYSCTL=y
+# CONFIG_PROC_PAGE_MONITOR is not set
+CONFIG_SYSFS=y
+CONFIG_TMPFS=y
+# CONFIG_TMPFS_POSIX_ACL is not set
+# CONFIG_HUGETLB_PAGE is not set
+# CONFIG_CONFIGFS_FS is not set
+CONFIG_MISC_FILESYSTEMS=y
+# CONFIG_ADFS_FS is not set
+# CONFIG_AFFS_FS is not set
+# CONFIG_HFS_FS is not set
+# CONFIG_HFSPLUS_FS is not set
+# CONFIG_BEFS_FS is not set
+# CONFIG_BFS_FS is not set
+# CONFIG_EFS_FS is not set
+# CONFIG_CRAMFS is not set
+# CONFIG_SQUASHFS is not set
+# CONFIG_VXFS_FS is not set
+# CONFIG_MINIX_FS is not set
+# CONFIG_OMFS_FS is not set
+# CONFIG_HPFS_FS is not set
+# CONFIG_QNX4FS_FS is not set
+# CONFIG_ROMFS_FS is not set
+# CONFIG_SYSV_FS is not set
+# CONFIG_UFS_FS is not set
+CONFIG_NETWORK_FILESYSTEMS=y
+CONFIG_NFS_FS=y
+CONFIG_NFS_V3=y
+# CONFIG_NFS_V3_ACL is not set
+# CONFIG_NFS_V4 is not set
+CONFIG_ROOT_NFS=y
+# CONFIG_NFSD is not set
+CONFIG_LOCKD=y
+CONFIG_LOCKD_V4=y
+CONFIG_NFS_COMMON=y
+CONFIG_SUNRPC=y
+# CONFIG_RPCSEC_GSS_KRB5 is not set
+# CONFIG_RPCSEC_GSS_SPKM3 is not set
+# CONFIG_SMB_FS is not set
+CONFIG_CIFS=y
+# CONFIG_CIFS_STATS is not set
+# CONFIG_CIFS_WEAK_PW_HASH is not set
+# CONFIG_CIFS_XATTR is not set
+# CONFIG_CIFS_DEBUG2 is not set
+# CONFIG_CIFS_EXPERIMENTAL is not set
+# CONFIG_NCP_FS is not set
+# CONFIG_CODA_FS is not set
+# CONFIG_AFS_FS is not set
+
+#
+# Partition Types
+#
+# CONFIG_PARTITION_ADVANCED is not set
+CONFIG_MSDOS_PARTITION=y
+CONFIG_NLS=y
+CONFIG_NLS_DEFAULT="iso8859-1"
+CONFIG_NLS_CODEPAGE_437=y
+# CONFIG_NLS_CODEPAGE_737 is not set
+# CONFIG_NLS_CODEPAGE_775 is not set
+# CONFIG_NLS_CODEPAGE_850 is not set
+# CONFIG_NLS_CODEPAGE_852 is not set
+# CONFIG_NLS_CODEPAGE_855 is not set
+# CONFIG_NLS_CODEPAGE_857 is not set
+# CONFIG_NLS_CODEPAGE_860 is not set
+# CONFIG_NLS_CODEPAGE_861 is not set
+# CONFIG_NLS_CODEPAGE_862 is not set
+# CONFIG_NLS_CODEPAGE_863 is not set
+# CONFIG_NLS_CODEPAGE_864 is not set
+# CONFIG_NLS_CODEPAGE_865 is not set
+# CONFIG_NLS_CODEPAGE_866 is not set
+# CONFIG_NLS_CODEPAGE_869 is not set
+# CONFIG_NLS_CODEPAGE_936 is not set
+# CONFIG_NLS_CODEPAGE_950 is not set
+# CONFIG_NLS_CODEPAGE_932 is not set
+# CONFIG_NLS_CODEPAGE_949 is not set
+# CONFIG_NLS_CODEPAGE_874 is not set
+# CONFIG_NLS_ISO8859_8 is not set
+# CONFIG_NLS_CODEPAGE_1250 is not set
+# CONFIG_NLS_CODEPAGE_1251 is not set
+# CONFIG_NLS_ASCII is not set
+CONFIG_NLS_ISO8859_1=y
+# CONFIG_NLS_ISO8859_2 is not set
+# CONFIG_NLS_ISO8859_3 is not set
+# CONFIG_NLS_ISO8859_4 is not set
+# CONFIG_NLS_ISO8859_5 is not set
+# CONFIG_NLS_ISO8859_6 is not set
+# CONFIG_NLS_ISO8859_7 is not set
+# CONFIG_NLS_ISO8859_9 is not set
+# CONFIG_NLS_ISO8859_13 is not set
+# CONFIG_NLS_ISO8859_14 is not set
+# CONFIG_NLS_ISO8859_15 is not set
+# CONFIG_NLS_KOI8_R is not set
+# CONFIG_NLS_KOI8_U is not set
+# CONFIG_NLS_UTF8 is not set
+# CONFIG_DLM is not set
+CONFIG_BINARY_PRINTF=y
+
+#
+# Library routines
+#
+CONFIG_BITREVERSE=y
+CONFIG_GENERIC_FIND_LAST_BIT=y
+CONFIG_CRC_CCITT=y
+# CONFIG_CRC16 is not set
+# CONFIG_CRC_T10DIF is not set
+# CONFIG_CRC_ITU_T is not set
+CONFIG_CRC32=y
+# CONFIG_CRC7 is not set
+# CONFIG_LIBCRC32C is not set
+CONFIG_ZLIB_INFLATE=y
+CONFIG_DECOMPRESS_GZIP=y
+CONFIG_HAS_IOMEM=y
+CONFIG_HAS_IOPORT=y
+CONFIG_HAS_DMA=y
+CONFIG_HAVE_LMB=y
+CONFIG_NLATTR=y
+CONFIG_GENERIC_ATOMIC64=y
+
+#
+# Kernel hacking
+#
+CONFIG_PRINTK_TIME=y
+CONFIG_ENABLE_WARN_DEPRECATED=y
+CONFIG_ENABLE_MUST_CHECK=y
+CONFIG_FRAME_WARN=1024
+# CONFIG_MAGIC_SYSRQ is not set
+# CONFIG_STRIP_ASM_SYMS is not set
+# CONFIG_UNUSED_SYMBOLS is not set
+CONFIG_DEBUG_FS=y
+# CONFIG_HEADERS_CHECK is not set
+CONFIG_DEBUG_KERNEL=y
+# CONFIG_DEBUG_SHIRQ is not set
+CONFIG_DETECT_SOFTLOCKUP=y
+# CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC is not set
+CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC_VALUE=0
+# CONFIG_DETECT_HUNG_TASK is not set
+CONFIG_SCHED_DEBUG=y
+CONFIG_SCHEDSTATS=y
+# CONFIG_TIMER_STATS is not set
+# CONFIG_DEBUG_OBJECTS is not set
+# CONFIG_DEBUG_SLAB is not set
+# CONFIG_DEBUG_KMEMLEAK is not set
+CONFIG_DEBUG_PREEMPT=y
+# CONFIG_DEBUG_RT_MUTEXES is not set
+# CONFIG_RT_MUTEX_TESTER is not set
+CONFIG_DEBUG_SPINLOCK=y
+CONFIG_DEBUG_MUTEXES=y
+# CONFIG_DEBUG_LOCK_ALLOC is not set
+# CONFIG_PROVE_LOCKING is not set
+# CONFIG_LOCK_STAT is not set
+CONFIG_DEBUG_SPINLOCK_SLEEP=y
+# CONFIG_DEBUG_LOCKING_API_SELFTESTS is not set
+CONFIG_STACKTRACE=y
+# CONFIG_DEBUG_KOBJECT is not set
+CONFIG_DEBUG_BUGVERBOSE=y
+# CONFIG_DEBUG_INFO is not set
+# CONFIG_DEBUG_VM is not set
+# CONFIG_DEBUG_WRITECOUNT is not set
+# CONFIG_DEBUG_MEMORY_INIT is not set
+# CONFIG_DEBUG_LIST is not set
+# CONFIG_DEBUG_SG is not set
+# CONFIG_DEBUG_NOTIFIERS is not set
+# CONFIG_DEBUG_CREDENTIALS is not set
+# CONFIG_RCU_TORTURE_TEST is not set
+# CONFIG_RCU_CPU_STALL_DETECTOR is not set
+# CONFIG_BACKTRACE_SELF_TEST is not set
+# CONFIG_DEBUG_BLOCK_EXT_DEVT is not set
+# CONFIG_DEBUG_FORCE_WEAK_PER_CPU is not set
+# CONFIG_FAULT_INJECTION is not set
+CONFIG_LATENCYTOP=y
+CONFIG_SYSCTL_SYSCALL_CHECK=y
+# CONFIG_DEBUG_PAGEALLOC is not set
+CONFIG_NOP_TRACER=y
+CONFIG_HAVE_FUNCTION_TRACER=y
+CONFIG_HAVE_FUNCTION_GRAPH_TRACER=y
+CONFIG_HAVE_DYNAMIC_FTRACE=y
+CONFIG_HAVE_FTRACE_MCOUNT_RECORD=y
+CONFIG_TRACER_MAX_TRACE=y
+CONFIG_RING_BUFFER=y
+CONFIG_EVENT_TRACING=y
+CONFIG_CONTEXT_SWITCH_TRACER=y
+CONFIG_TRACING=y
+CONFIG_GENERIC_TRACER=y
+CONFIG_TRACING_SUPPORT=y
+CONFIG_FTRACE=y
+# CONFIG_FUNCTION_TRACER is not set
+# CONFIG_IRQSOFF_TRACER is not set
+# CONFIG_PREEMPT_TRACER is not set
+CONFIG_SCHED_TRACER=y
+CONFIG_BOOT_TRACER=y
+CONFIG_BRANCH_PROFILE_NONE=y
+# CONFIG_PROFILE_ANNOTATED_BRANCHES is not set
+# CONFIG_PROFILE_ALL_BRANCHES is not set
+# CONFIG_STACK_TRACER is not set
+# CONFIG_KMEMTRACE is not set
+# CONFIG_WORKQUEUE_TRACER is not set
+# CONFIG_BLK_DEV_IO_TRACE is not set
+# CONFIG_FTRACE_STARTUP_TEST is not set
+# CONFIG_RING_BUFFER_BENCHMARK is not set
+# CONFIG_DYNAMIC_DEBUG is not set
+# CONFIG_DMA_API_DEBUG is not set
+# CONFIG_SAMPLES is not set
+CONFIG_HAVE_ARCH_KGDB=y
+# CONFIG_KGDB is not set
+# CONFIG_PPC_DISABLE_WERROR is not set
+CONFIG_PPC_WERROR=y
+CONFIG_PRINT_STACK_DEPTH=64
+# CONFIG_DEBUG_STACKOVERFLOW is not set
+# CONFIG_DEBUG_STACK_USAGE is not set
+# CONFIG_PPC_EMULATED_STATS is not set
+# CONFIG_CODE_PATCHING_SELFTEST is not set
+# CONFIG_FTR_FIXUP_SELFTEST is not set
+# CONFIG_MSI_BITMAP_SELFTEST is not set
+# CONFIG_XMON is not set
+# CONFIG_IRQSTACKS is not set
+# CONFIG_VIRQ_DEBUG is not set
+# CONFIG_BDI_SWITCH is not set
+# CONFIG_BOOTX_TEXT is not set
+CONFIG_PPC_EARLY_DEBUG=y
+# CONFIG_PPC_EARLY_DEBUG_LPAR is not set
+# CONFIG_PPC_EARLY_DEBUG_G5 is not set
+# CONFIG_PPC_EARLY_DEBUG_RTAS_PANEL is not set
+# CONFIG_PPC_EARLY_DEBUG_RTAS_CONSOLE is not set
+# CONFIG_PPC_EARLY_DEBUG_MAPLE is not set
+# CONFIG_PPC_EARLY_DEBUG_ISERIES is not set
+# CONFIG_PPC_EARLY_DEBUG_PAS_REALMODE is not set
+# CONFIG_PPC_EARLY_DEBUG_BEAT is not set
+# CONFIG_PPC_EARLY_DEBUG_44x is not set
+# CONFIG_PPC_EARLY_DEBUG_40x is not set
+# CONFIG_PPC_EARLY_DEBUG_CPM is not set
+CONFIG_PPC_EARLY_DEBUG_USBGECKO=y
+
+#
+# Security options
+#
+# CONFIG_KEYS is not set
+# CONFIG_SECURITY is not set
+# CONFIG_SECURITYFS is not set
+# CONFIG_SECURITY_FILE_CAPABILITIES is not set
+# CONFIG_CRYPTO is not set
+# CONFIG_PPC_CLOCK is not set
+# CONFIG_VIRTUALIZATION is not set
diff --git a/arch/powerpc/configs/wii_defconfig b/arch/powerpc/configs/wii_defconfig
new file mode 100644
index 0000000..7a5ab2b
--- /dev/null
+++ b/arch/powerpc/configs/wii_defconfig
@@ -0,0 +1,1622 @@
+#
+# Automatically generated make config: don't edit
+# Linux kernel version: 2.6.32
+# Sun Dec  6 20:39:03 2009
+#
+# CONFIG_PPC64 is not set
+
+#
+# Processor support
+#
+CONFIG_PPC_BOOK3S_32=y
+# CONFIG_PPC_85xx is not set
+# CONFIG_PPC_8xx is not set
+# CONFIG_40x is not set
+# CONFIG_44x is not set
+# CONFIG_E200 is not set
+CONFIG_PPC_BOOK3S=y
+CONFIG_6xx=y
+CONFIG_PPC_FPU=y
+# CONFIG_ALTIVEC is not set
+CONFIG_PPC_STD_MMU=y
+CONFIG_PPC_STD_MMU_32=y
+# CONFIG_PPC_MM_SLICES is not set
+CONFIG_PPC_HAVE_PMU_SUPPORT=y
+CONFIG_PPC_PERF_CTRS=y
+# CONFIG_SMP is not set
+CONFIG_NOT_COHERENT_CACHE=y
+CONFIG_PPC32=y
+CONFIG_WORD_SIZE=32
+# CONFIG_ARCH_PHYS_ADDR_T_64BIT is not set
+CONFIG_MMU=y
+CONFIG_GENERIC_CMOS_UPDATE=y
+CONFIG_GENERIC_TIME=y
+CONFIG_GENERIC_TIME_VSYSCALL=y
+CONFIG_GENERIC_CLOCKEVENTS=y
+CONFIG_GENERIC_HARDIRQS=y
+CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ=y
+# CONFIG_HAVE_SETUP_PER_CPU_AREA is not set
+# CONFIG_NEED_PER_CPU_EMBED_FIRST_CHUNK is not set
+CONFIG_IRQ_PER_CPU=y
+CONFIG_STACKTRACE_SUPPORT=y
+CONFIG_HAVE_LATENCYTOP_SUPPORT=y
+CONFIG_TRACE_IRQFLAGS_SUPPORT=y
+CONFIG_LOCKDEP_SUPPORT=y
+CONFIG_RWSEM_XCHGADD_ALGORITHM=y
+CONFIG_ARCH_HAS_ILOG2_U32=y
+CONFIG_GENERIC_HWEIGHT=y
+CONFIG_GENERIC_FIND_NEXT_BIT=y
+CONFIG_GENERIC_GPIO=y
+# CONFIG_ARCH_NO_VIRT_TO_BUS is not set
+CONFIG_PPC=y
+CONFIG_EARLY_PRINTK=y
+CONFIG_GENERIC_NVRAM=y
+CONFIG_SCHED_OMIT_FRAME_POINTER=y
+CONFIG_ARCH_MAY_HAVE_PC_FDC=y
+CONFIG_PPC_OF=y
+CONFIG_OF=y
+# CONFIG_PPC_UDBG_16550 is not set
+# CONFIG_GENERIC_TBSYNC is not set
+CONFIG_AUDIT_ARCH=y
+CONFIG_GENERIC_BUG=y
+CONFIG_DTC=y
+# CONFIG_DEFAULT_UIMAGE is not set
+# CONFIG_PPC_DCR_NATIVE is not set
+# CONFIG_PPC_DCR_MMIO is not set
+CONFIG_ARCH_SUPPORTS_DEBUG_PAGEALLOC=y
+CONFIG_DEFCONFIG_LIST="/lib/modules/$UNAME_RELEASE/.config"
+CONFIG_CONSTRUCTORS=y
+
+#
+# General setup
+#
+CONFIG_EXPERIMENTAL=y
+CONFIG_BROKEN_ON_SMP=y
+CONFIG_LOCK_KERNEL=y
+CONFIG_INIT_ENV_ARG_LIMIT=32
+CONFIG_LOCALVERSION="-isobel-wii"
+# CONFIG_LOCALVERSION_AUTO is not set
+CONFIG_SWAP=y
+CONFIG_SYSVIPC=y
+CONFIG_SYSVIPC_SYSCTL=y
+# CONFIG_POSIX_MQUEUE is not set
+# CONFIG_BSD_PROCESS_ACCT is not set
+# CONFIG_TASKSTATS is not set
+# CONFIG_AUDIT is not set
+
+#
+# RCU Subsystem
+#
+CONFIG_TREE_RCU=y
+# CONFIG_TREE_PREEMPT_RCU is not set
+# CONFIG_RCU_TRACE is not set
+CONFIG_RCU_FANOUT=32
+# CONFIG_RCU_FANOUT_EXACT is not set
+# CONFIG_TREE_RCU_TRACE is not set
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_LOG_BUF_SHIFT=14
+CONFIG_GROUP_SCHED=y
+CONFIG_FAIR_GROUP_SCHED=y
+# CONFIG_RT_GROUP_SCHED is not set
+CONFIG_USER_SCHED=y
+# CONFIG_CGROUP_SCHED is not set
+# CONFIG_CGROUPS is not set
+CONFIG_SYSFS_DEPRECATED=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+# CONFIG_RELAY is not set
+# CONFIG_NAMESPACES is not set
+CONFIG_BLK_DEV_INITRD=y
+CONFIG_INITRAMFS_SOURCE=""
+CONFIG_RD_GZIP=y
+# CONFIG_RD_BZIP2 is not set
+# CONFIG_RD_LZMA is not set
+# CONFIG_CC_OPTIMIZE_FOR_SIZE is not set
+CONFIG_SYSCTL=y
+CONFIG_ANON_INODES=y
+CONFIG_EMBEDDED=y
+CONFIG_SYSCTL_SYSCALL=y
+CONFIG_KALLSYMS=y
+CONFIG_KALLSYMS_ALL=y
+# CONFIG_KALLSYMS_EXTRA_PASS is not set
+CONFIG_HOTPLUG=y
+CONFIG_PRINTK=y
+CONFIG_BUG=y
+# CONFIG_ELF_CORE is not set
+CONFIG_BASE_FULL=y
+CONFIG_FUTEX=y
+CONFIG_EPOLL=y
+CONFIG_SIGNALFD=y
+CONFIG_TIMERFD=y
+CONFIG_EVENTFD=y
+CONFIG_SHMEM=y
+CONFIG_AIO=y
+CONFIG_HAVE_PERF_EVENTS=y
+
+#
+# Kernel Performance Events And Counters
+#
+CONFIG_PERF_EVENTS=y
+CONFIG_EVENT_PROFILE=y
+CONFIG_PERF_COUNTERS=y
+# CONFIG_DEBUG_PERF_USE_VMALLOC is not set
+# CONFIG_VM_EVENT_COUNTERS is not set
+CONFIG_COMPAT_BRK=y
+CONFIG_SLAB=y
+# CONFIG_SLUB is not set
+# CONFIG_SLOB is not set
+# CONFIG_PROFILING is not set
+CONFIG_TRACEPOINTS=y
+CONFIG_HAVE_OPROFILE=y
+# CONFIG_KPROBES is not set
+CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y
+CONFIG_HAVE_IOREMAP_PROT=y
+CONFIG_HAVE_KPROBES=y
+CONFIG_HAVE_KRETPROBES=y
+CONFIG_HAVE_ARCH_TRACEHOOK=y
+CONFIG_HAVE_DMA_ATTRS=y
+CONFIG_HAVE_DMA_API_DEBUG=y
+
+#
+# GCOV-based kernel profiling
+#
+# CONFIG_GCOV_KERNEL is not set
+CONFIG_SLOW_WORK=y
+# CONFIG_SLOW_WORK_DEBUG is not set
+CONFIG_HAVE_GENERIC_DMA_COHERENT=y
+CONFIG_SLABINFO=y
+CONFIG_RT_MUTEXES=y
+CONFIG_BASE_SMALL=0
+CONFIG_MODULES=y
+# CONFIG_MODULE_FORCE_LOAD is not set
+CONFIG_MODULE_UNLOAD=y
+# CONFIG_MODULE_FORCE_UNLOAD is not set
+# CONFIG_MODVERSIONS is not set
+# CONFIG_MODULE_SRCVERSION_ALL is not set
+CONFIG_BLOCK=y
+CONFIG_LBDAF=y
+# CONFIG_BLK_DEV_BSG is not set
+# CONFIG_BLK_DEV_INTEGRITY is not set
+
+#
+# IO Schedulers
+#
+CONFIG_IOSCHED_NOOP=y
+CONFIG_IOSCHED_AS=y
+CONFIG_IOSCHED_DEADLINE=y
+CONFIG_IOSCHED_CFQ=y
+CONFIG_DEFAULT_AS=y
+# CONFIG_DEFAULT_DEADLINE is not set
+# CONFIG_DEFAULT_CFQ is not set
+# CONFIG_DEFAULT_NOOP is not set
+CONFIG_DEFAULT_IOSCHED="anticipatory"
+# CONFIG_FREEZER is not set
+
+#
+# Platform support
+#
+# CONFIG_PPC_CHRP is not set
+# CONFIG_MPC5121_ADS is not set
+# CONFIG_MPC5121_GENERIC is not set
+# CONFIG_PPC_MPC52xx is not set
+# CONFIG_PPC_PMAC is not set
+# CONFIG_PPC_CELL is not set
+# CONFIG_PPC_CELL_NATIVE is not set
+# CONFIG_PPC_82xx is not set
+# CONFIG_PQ2ADS is not set
+# CONFIG_PPC_83xx is not set
+# CONFIG_PPC_86xx is not set
+CONFIG_EMBEDDED6xx=y
+# CONFIG_LINKSTATION is not set
+# CONFIG_STORCENTER is not set
+# CONFIG_MPC7448HPC2 is not set
+# CONFIG_PPC_HOLLY is not set
+# CONFIG_PPC_PRPMC2800 is not set
+# CONFIG_PPC_C2K is not set
+# CONFIG_GAMECUBE is not set
+CONFIG_WII=y
+CONFIG_STARLET_IOS=y
+CONFIG_STARLET_MINI=y
+CONFIG_FLIPPER_PIC=y
+CONFIG_HLWD_PIC=y
+CONFIG_GAMECUBE_COMMON=y
+CONFIG_GAMECUBE_UDBG=y
+CONFIG_USBGECKO_UDBG=y
+# CONFIG_GAMECUBE_VIDEO_UDBG is not set
+CONFIG_GAMECUBE_RSW=y
+CONFIG_HLWD_GPIO=y
+# CONFIG_AMIGAONE is not set
+# CONFIG_PPC_OF_BOOT_TRAMPOLINE is not set
+# CONFIG_IPIC is not set
+# CONFIG_MPIC is not set
+# CONFIG_MPIC_WEIRD is not set
+# CONFIG_PPC_I8259 is not set
+# CONFIG_PPC_RTAS is not set
+# CONFIG_MMIO_NVRAM is not set
+# CONFIG_PPC_MPC106 is not set
+# CONFIG_PPC_970_NAP is not set
+# CONFIG_PPC_INDIRECT_IO is not set
+# CONFIG_GENERIC_IOMAP is not set
+# CONFIG_CPU_FREQ is not set
+# CONFIG_TAU is not set
+# CONFIG_FSL_ULI1575 is not set
+# CONFIG_SIMPLE_GPIO is not set
+
+#
+# Kernel options
+#
+# CONFIG_HIGHMEM is not set
+# CONFIG_NO_HZ is not set
+# CONFIG_HIGH_RES_TIMERS is not set
+CONFIG_GENERIC_CLOCKEVENTS_BUILD=y
+# CONFIG_HZ_100 is not set
+CONFIG_HZ_250=y
+# CONFIG_HZ_300 is not set
+# CONFIG_HZ_1000 is not set
+CONFIG_HZ=250
+# CONFIG_SCHED_HRTICK is not set
+# CONFIG_PREEMPT_NONE is not set
+# CONFIG_PREEMPT_VOLUNTARY is not set
+CONFIG_PREEMPT=y
+CONFIG_BINFMT_ELF=y
+# CONFIG_HAVE_AOUT is not set
+CONFIG_BINFMT_MISC=m
+# CONFIG_IOMMU_HELPER is not set
+# CONFIG_SWIOTLB is not set
+CONFIG_ARCH_ENABLE_MEMORY_HOTPLUG=y
+CONFIG_ARCH_HAS_WALK_MEMORY=y
+CONFIG_ARCH_ENABLE_MEMORY_HOTREMOVE=y
+CONFIG_KEXEC=y
+# CONFIG_CRASH_DUMP is not set
+CONFIG_MAX_ACTIVE_REGIONS=32
+CONFIG_ARCH_FLATMEM_ENABLE=y
+CONFIG_ARCH_POPULATES_NODE_MAP=y
+CONFIG_SELECT_MEMORY_MODEL=y
+CONFIG_FLATMEM_MANUAL=y
+# CONFIG_DISCONTIGMEM_MANUAL is not set
+# CONFIG_SPARSEMEM_MANUAL is not set
+CONFIG_FLATMEM=y
+CONFIG_FLAT_NODE_MEM_MAP=y
+CONFIG_PAGEFLAGS_EXTENDED=y
+CONFIG_SPLIT_PTLOCK_CPUS=4
+# CONFIG_MIGRATION is not set
+# CONFIG_PHYS_ADDR_T_64BIT is not set
+CONFIG_ZONE_DMA_FLAG=1
+CONFIG_BOUNCE=y
+CONFIG_VIRT_TO_BUS=y
+CONFIG_HAVE_MLOCK=y
+CONFIG_HAVE_MLOCKED_PAGE_BIT=y
+# CONFIG_KSM is not set
+CONFIG_DEFAULT_MMAP_MIN_ADDR=4096
+CONFIG_PPC_4K_PAGES=y
+# CONFIG_PPC_16K_PAGES is not set
+# CONFIG_PPC_64K_PAGES is not set
+# CONFIG_PPC_256K_PAGES is not set
+CONFIG_FORCE_MAX_ZONEORDER=11
+CONFIG_PROC_DEVICETREE=y
+# CONFIG_CMDLINE_BOOL is not set
+CONFIG_EXTRA_TARGETS=""
+# CONFIG_PM is not set
+# CONFIG_SECCOMP is not set
+CONFIG_ISA_DMA_API=y
+
+#
+# Bus options
+#
+CONFIG_ZONE_DMA=y
+CONFIG_GENERIC_ISA_DMA=y
+# CONFIG_PCI is not set
+# CONFIG_PCI_DOMAINS is not set
+# CONFIG_PCI_SYSCALL is not set
+# CONFIG_ARCH_SUPPORTS_MSI is not set
+# CONFIG_PCCARD is not set
+# CONFIG_HAS_RAPIDIO is not set
+
+#
+# Advanced setup
+#
+CONFIG_ADVANCED_OPTIONS=y
+# CONFIG_LOWMEM_SIZE_BOOL is not set
+CONFIG_LOWMEM_SIZE=0x30000000
+# CONFIG_PAGE_OFFSET_BOOL is not set
+CONFIG_PAGE_OFFSET=0xc0000000
+# CONFIG_KERNEL_START_BOOL is not set
+CONFIG_KERNEL_START=0xc0000000
+CONFIG_PHYSICAL_START=0x00000000
+# CONFIG_TASK_SIZE_BOOL is not set
+CONFIG_TASK_SIZE=0xc0000000
+# CONFIG_CONSISTENT_SIZE_BOOL is not set
+CONFIG_CONSISTENT_SIZE=0x00200000
+CONFIG_NET=y
+
+#
+# Networking options
+#
+CONFIG_PACKET=y
+# CONFIG_PACKET_MMAP is not set
+CONFIG_UNIX=y
+# CONFIG_NET_KEY is not set
+CONFIG_INET=y
+# CONFIG_IP_MULTICAST is not set
+# CONFIG_IP_ADVANCED_ROUTER is not set
+CONFIG_IP_FIB_HASH=y
+CONFIG_IP_PNP=y
+CONFIG_IP_PNP_DHCP=y
+# CONFIG_IP_PNP_BOOTP is not set
+CONFIG_IP_PNP_RARP=y
+# CONFIG_NET_IPIP is not set
+# CONFIG_NET_IPGRE is not set
+# CONFIG_ARPD is not set
+# CONFIG_SYN_COOKIES is not set
+# CONFIG_INET_AH is not set
+# CONFIG_INET_ESP is not set
+# CONFIG_INET_IPCOMP is not set
+# CONFIG_INET_XFRM_TUNNEL is not set
+# CONFIG_INET_TUNNEL is not set
+# CONFIG_INET_XFRM_MODE_TRANSPORT is not set
+# CONFIG_INET_XFRM_MODE_TUNNEL is not set
+# CONFIG_INET_XFRM_MODE_BEET is not set
+# CONFIG_INET_LRO is not set
+# CONFIG_INET_DIAG is not set
+# CONFIG_TCP_CONG_ADVANCED is not set
+CONFIG_TCP_CONG_CUBIC=y
+CONFIG_DEFAULT_TCP_CONG="cubic"
+# CONFIG_TCP_MD5SIG is not set
+# CONFIG_IPV6 is not set
+# CONFIG_NETWORK_SECMARK is not set
+# CONFIG_NETFILTER is not set
+# CONFIG_IP_DCCP is not set
+# CONFIG_IP_SCTP is not set
+# CONFIG_RDS is not set
+# CONFIG_TIPC is not set
+# CONFIG_ATM is not set
+# CONFIG_BRIDGE is not set
+# CONFIG_NET_DSA is not set
+# CONFIG_VLAN_8021Q is not set
+# CONFIG_DECNET is not set
+# CONFIG_LLC2 is not set
+# CONFIG_IPX is not set
+# CONFIG_ATALK is not set
+# CONFIG_X25 is not set
+# CONFIG_LAPB is not set
+# CONFIG_ECONET is not set
+# CONFIG_WAN_ROUTER is not set
+# CONFIG_PHONET is not set
+# CONFIG_IEEE802154 is not set
+# CONFIG_NET_SCHED is not set
+# CONFIG_DCB is not set
+
+#
+# Network testing
+#
+# CONFIG_NET_PKTGEN is not set
+# CONFIG_NET_DROP_MONITOR is not set
+# CONFIG_HAMRADIO is not set
+# CONFIG_CAN is not set
+# CONFIG_IRDA is not set
+CONFIG_BT=y
+CONFIG_BT_L2CAP=y
+# CONFIG_BT_SCO is not set
+CONFIG_BT_RFCOMM=y
+CONFIG_BT_RFCOMM_TTY=y
+CONFIG_BT_BNEP=y
+CONFIG_BT_BNEP_MC_FILTER=y
+# CONFIG_BT_BNEP_PROTO_FILTER is not set
+CONFIG_BT_HIDP=y
+
+#
+# Bluetooth device drivers
+#
+CONFIG_BT_HCIBTUSB=y
+# CONFIG_BT_HCIBTSDIO is not set
+# CONFIG_BT_HCIUART is not set
+# CONFIG_BT_HCIBCM203X is not set
+# CONFIG_BT_HCIBPA10X is not set
+# CONFIG_BT_HCIBFUSB is not set
+# CONFIG_BT_HCIVHCI is not set
+# CONFIG_BT_MRVL is not set
+# CONFIG_AF_RXRPC is not set
+CONFIG_WIRELESS=y
+CONFIG_CFG80211=y
+# CONFIG_NL80211_TESTMODE is not set
+# CONFIG_CFG80211_DEVELOPER_WARNINGS is not set
+# CONFIG_CFG80211_REG_DEBUG is not set
+CONFIG_CFG80211_DEFAULT_PS=y
+CONFIG_CFG80211_DEFAULT_PS_VALUE=1
+# CONFIG_CFG80211_DEBUGFS is not set
+CONFIG_WIRELESS_OLD_REGULATORY=y
+CONFIG_WIRELESS_EXT=y
+CONFIG_WIRELESS_EXT_SYSFS=y
+# CONFIG_LIB80211 is not set
+CONFIG_MAC80211=y
+# CONFIG_MAC80211_RC_PID is not set
+CONFIG_MAC80211_RC_MINSTREL=y
+# CONFIG_MAC80211_RC_DEFAULT_PID is not set
+CONFIG_MAC80211_RC_DEFAULT_MINSTREL=y
+CONFIG_MAC80211_RC_DEFAULT="minstrel"
+# CONFIG_MAC80211_MESH is not set
+# CONFIG_MAC80211_LEDS is not set
+# CONFIG_MAC80211_DEBUGFS is not set
+# CONFIG_MAC80211_DEBUG_MENU is not set
+# CONFIG_WIMAX is not set
+# CONFIG_RFKILL is not set
+# CONFIG_NET_9P is not set
+
+#
+# Device Drivers
+#
+
+#
+# Generic Driver Options
+#
+CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug"
+# CONFIG_DEVTMPFS is not set
+# CONFIG_STANDALONE is not set
+CONFIG_PREVENT_FIRMWARE_BUILD=y
+CONFIG_FW_LOADER=y
+# CONFIG_FIRMWARE_IN_KERNEL is not set
+CONFIG_EXTRA_FIRMWARE=""
+# CONFIG_DEBUG_DRIVER is not set
+# CONFIG_DEBUG_DEVRES is not set
+# CONFIG_SYS_HYPERVISOR is not set
+# CONFIG_CONNECTOR is not set
+# CONFIG_MTD is not set
+CONFIG_OF_DEVICE=y
+CONFIG_OF_GPIO=y
+CONFIG_OF_I2C=y
+# CONFIG_PARPORT is not set
+CONFIG_BLK_DEV=y
+# CONFIG_BLK_DEV_FD is not set
+CONFIG_GAMECUBE_SD=m
+CONFIG_WII_MEM2=m
+CONFIG_WII_SD=y
+CONFIG_WII_DI=y
+# CONFIG_BLK_DEV_COW_COMMON is not set
+CONFIG_BLK_DEV_LOOP=y
+# CONFIG_BLK_DEV_CRYPTOLOOP is not set
+# CONFIG_BLK_DEV_NBD is not set
+# CONFIG_BLK_DEV_UB is not set
+CONFIG_BLK_DEV_RAM=y
+CONFIG_BLK_DEV_RAM_COUNT=2
+CONFIG_BLK_DEV_RAM_SIZE=4096
+# CONFIG_BLK_DEV_XIP is not set
+# CONFIG_CDROM_PKTCDVD is not set
+# CONFIG_ATA_OVER_ETH is not set
+# CONFIG_BLK_DEV_HD is not set
+CONFIG_MISC_DEVICES=y
+CONFIG_GAMECUBE_GQR=m
+# CONFIG_ICS932S401 is not set
+# CONFIG_ENCLOSURE_SERVICES is not set
+# CONFIG_ISL29003 is not set
+# CONFIG_C2PORT is not set
+
+#
+# EEPROM support
+#
+# CONFIG_EEPROM_AT24 is not set
+# CONFIG_EEPROM_LEGACY is not set
+# CONFIG_EEPROM_MAX6875 is not set
+# CONFIG_EEPROM_93CX6 is not set
+CONFIG_HAVE_IDE=y
+# CONFIG_IDE is not set
+
+#
+# SCSI device support
+#
+# CONFIG_RAID_ATTRS is not set
+CONFIG_SCSI=y
+CONFIG_SCSI_DMA=y
+# CONFIG_SCSI_TGT is not set
+# CONFIG_SCSI_NETLINK is not set
+CONFIG_SCSI_PROC_FS=y
+
+#
+# SCSI support type (disk, tape, CD-ROM)
+#
+CONFIG_BLK_DEV_SD=y
+# CONFIG_CHR_DEV_ST is not set
+# CONFIG_CHR_DEV_OSST is not set
+# CONFIG_BLK_DEV_SR is not set
+# CONFIG_CHR_DEV_SG is not set
+# CONFIG_CHR_DEV_SCH is not set
+CONFIG_SCSI_MULTI_LUN=y
+# CONFIG_SCSI_CONSTANTS is not set
+# CONFIG_SCSI_LOGGING is not set
+# CONFIG_SCSI_SCAN_ASYNC is not set
+CONFIG_SCSI_WAIT_SCAN=m
+
+#
+# SCSI Transports
+#
+# CONFIG_SCSI_SPI_ATTRS is not set
+# CONFIG_SCSI_FC_ATTRS is not set
+# CONFIG_SCSI_ISCSI_ATTRS is not set
+# CONFIG_SCSI_SAS_LIBSAS is not set
+# CONFIG_SCSI_SRP_ATTRS is not set
+CONFIG_SCSI_LOWLEVEL=y
+# CONFIG_ISCSI_TCP is not set
+# CONFIG_LIBFC is not set
+# CONFIG_LIBFCOE is not set
+# CONFIG_SCSI_DEBUG is not set
+# CONFIG_SCSI_DH is not set
+# CONFIG_SCSI_OSD_INITIATOR is not set
+# CONFIG_ATA is not set
+# CONFIG_MD is not set
+# CONFIG_MACINTOSH_DRIVERS is not set
+CONFIG_NETDEVICES=y
+# CONFIG_DUMMY is not set
+# CONFIG_BONDING is not set
+# CONFIG_MACVLAN is not set
+# CONFIG_EQUALIZER is not set
+# CONFIG_TUN is not set
+# CONFIG_VETH is not set
+# CONFIG_PHYLIB is not set
+CONFIG_NET_ETHERNET=y
+CONFIG_MII=y
+# CONFIG_ETHOC is not set
+# CONFIG_DNET is not set
+# CONFIG_IBM_NEW_EMAC_ZMII is not set
+# CONFIG_IBM_NEW_EMAC_RGMII is not set
+# CONFIG_IBM_NEW_EMAC_TAH is not set
+# CONFIG_IBM_NEW_EMAC_EMAC4 is not set
+# CONFIG_IBM_NEW_EMAC_NO_FLOW_CTRL is not set
+# CONFIG_IBM_NEW_EMAC_MAL_CLR_ICINTSTAT is not set
+# CONFIG_IBM_NEW_EMAC_MAL_COMMON_ERR is not set
+# CONFIG_B44 is not set
+# CONFIG_KS8842 is not set
+# CONFIG_KS8851_MLL is not set
+# CONFIG_XILINX_EMACLITE is not set
+# CONFIG_NETDEV_1000 is not set
+# CONFIG_NETDEV_10000 is not set
+CONFIG_WLAN=y
+# CONFIG_WLAN_PRE80211 is not set
+CONFIG_WLAN_80211=y
+# CONFIG_LIBERTAS is not set
+# CONFIG_LIBERTAS_THINFIRM is not set
+# CONFIG_AT76C50X_USB is not set
+# CONFIG_USB_ZD1201 is not set
+# CONFIG_USB_NET_RNDIS_WLAN is not set
+# CONFIG_RTL8187 is not set
+# CONFIG_MAC80211_HWSIM is not set
+# CONFIG_P54_COMMON is not set
+# CONFIG_ATH_COMMON is not set
+# CONFIG_HOSTAP is not set
+CONFIG_B43=y
+CONFIG_B43_SDIO=y
+CONFIG_B43_PIO=y
+# CONFIG_B43_PHY_LP is not set
+# CONFIG_B43_DEBUG is not set
+CONFIG_B43_DEFAULT_QOS_OFF=y
+# CONFIG_B43LEGACY is not set
+# CONFIG_ZD1211RW is not set
+# CONFIG_RT2X00 is not set
+# CONFIG_WL12XX is not set
+# CONFIG_IWM is not set
+
+#
+# Enable WiMAX (Networking options) to see the WiMAX drivers
+#
+
+#
+# USB Network Adapters
+#
+# CONFIG_USB_CATC is not set
+# CONFIG_USB_KAWETH is not set
+# CONFIG_USB_PEGASUS is not set
+# CONFIG_USB_RTL8150 is not set
+CONFIG_USB_USBNET=y
+CONFIG_USB_NET_AX8817X=y
+# CONFIG_USB_NET_CDCETHER is not set
+# CONFIG_USB_NET_CDC_EEM is not set
+# CONFIG_USB_NET_DM9601 is not set
+# CONFIG_USB_NET_SMSC95XX is not set
+# CONFIG_USB_NET_GL620A is not set
+# CONFIG_USB_NET_NET1080 is not set
+# CONFIG_USB_NET_PLUSB is not set
+# CONFIG_USB_NET_MCS7830 is not set
+# CONFIG_USB_NET_RNDIS_HOST is not set
+# CONFIG_USB_NET_CDC_SUBSET is not set
+# CONFIG_USB_NET_ZAURUS is not set
+# CONFIG_USB_NET_INT51X1 is not set
+# CONFIG_WAN is not set
+# CONFIG_PPP is not set
+# CONFIG_SLIP is not set
+# CONFIG_NETCONSOLE is not set
+# CONFIG_NETPOLL is not set
+# CONFIG_NET_POLL_CONTROLLER is not set
+# CONFIG_ISDN is not set
+# CONFIG_PHONE is not set
+
+#
+# Input device support
+#
+CONFIG_INPUT=y
+CONFIG_INPUT_FF_MEMLESS=m
+# CONFIG_INPUT_POLLDEV is not set
+
+#
+# Userland interfaces
+#
+CONFIG_INPUT_MOUSEDEV=y
+# CONFIG_INPUT_MOUSEDEV_PSAUX is not set
+CONFIG_INPUT_MOUSEDEV_SCREEN_X=640
+CONFIG_INPUT_MOUSEDEV_SCREEN_Y=480
+CONFIG_INPUT_JOYDEV=y
+CONFIG_INPUT_EVDEV=y
+# CONFIG_INPUT_EVBUG is not set
+
+#
+# Input Device Drivers
+#
+CONFIG_INPUT_KEYBOARD=y
+# CONFIG_KEYBOARD_ADP5588 is not set
+# CONFIG_KEYBOARD_ATKBD is not set
+# CONFIG_QT2160 is not set
+# CONFIG_KEYBOARD_LKKBD is not set
+# CONFIG_KEYBOARD_GPIO is not set
+# CONFIG_KEYBOARD_MATRIX is not set
+# CONFIG_KEYBOARD_MAX7359 is not set
+# CONFIG_KEYBOARD_NEWTON is not set
+# CONFIG_KEYBOARD_OPENCORES is not set
+# CONFIG_KEYBOARD_STOWAWAY is not set
+# CONFIG_KEYBOARD_SUNKBD is not set
+# CONFIG_KEYBOARD_XTKBD is not set
+CONFIG_INPUT_MOUSE=y
+# CONFIG_MOUSE_PS2 is not set
+# CONFIG_MOUSE_SERIAL is not set
+# CONFIG_MOUSE_APPLETOUCH is not set
+# CONFIG_MOUSE_BCM5974 is not set
+# CONFIG_MOUSE_VSXXXAA is not set
+# CONFIG_MOUSE_GPIO is not set
+# CONFIG_MOUSE_SYNAPTICS_I2C is not set
+CONFIG_INPUT_JOYSTICK=y
+# CONFIG_JOYSTICK_ANALOG is not set
+# CONFIG_JOYSTICK_A3D is not set
+# CONFIG_JOYSTICK_ADI is not set
+# CONFIG_JOYSTICK_COBRA is not set
+# CONFIG_JOYSTICK_GF2K is not set
+# CONFIG_JOYSTICK_GRIP is not set
+# CONFIG_JOYSTICK_GRIP_MP is not set
+# CONFIG_JOYSTICK_GUILLEMOT is not set
+# CONFIG_JOYSTICK_INTERACT is not set
+# CONFIG_JOYSTICK_SIDEWINDER is not set
+# CONFIG_JOYSTICK_TMDC is not set
+# CONFIG_JOYSTICK_IFORCE is not set
+# CONFIG_JOYSTICK_WARRIOR is not set
+# CONFIG_JOYSTICK_MAGELLAN is not set
+# CONFIG_JOYSTICK_SPACEORB is not set
+# CONFIG_JOYSTICK_SPACEBALL is not set
+# CONFIG_JOYSTICK_STINGER is not set
+# CONFIG_JOYSTICK_TWIDJOY is not set
+# CONFIG_JOYSTICK_ZHENHUA is not set
+# CONFIG_JOYSTICK_JOYDUMP is not set
+# CONFIG_JOYSTICK_XPAD is not set
+# CONFIG_INPUT_TABLET is not set
+# CONFIG_INPUT_TOUCHSCREEN is not set
+CONFIG_INPUT_MISC=y
+# CONFIG_INPUT_ATI_REMOTE is not set
+# CONFIG_INPUT_ATI_REMOTE2 is not set
+# CONFIG_INPUT_KEYSPAN_REMOTE is not set
+# CONFIG_INPUT_POWERMATE is not set
+# CONFIG_INPUT_YEALINK is not set
+# CONFIG_INPUT_CM109 is not set
+CONFIG_INPUT_UINPUT=y
+# CONFIG_INPUT_GPIO_ROTARY_ENCODER is not set
+
+#
+# Hardware I/O ports
+#
+CONFIG_SERIO=y
+# CONFIG_SERIO_I8042 is not set
+# CONFIG_SERIO_SERPORT is not set
+# CONFIG_SERIO_LIBPS2 is not set
+# CONFIG_SERIO_RAW is not set
+# CONFIG_SERIO_XILINX_XPS_PS2 is not set
+# CONFIG_GAMEPORT is not set
+CONFIG_GAMECUBE_SI=y
+
+#
+# Character devices
+#
+CONFIG_VT=y
+CONFIG_CONSOLE_TRANSLATIONS=y
+CONFIG_VT_CONSOLE=y
+CONFIG_HW_CONSOLE=y
+# CONFIG_VT_HW_CONSOLE_BINDING is not set
+# CONFIG_DEVKMEM is not set
+# CONFIG_SERIAL_NONSTANDARD is not set
+
+#
+# Serial drivers
+#
+# CONFIG_SERIAL_8250 is not set
+
+#
+# Non-8250 serial port support
+#
+# CONFIG_SERIAL_UARTLITE is not set
+# CONFIG_SERIAL_USBGECKO is not set
+CONFIG_UNIX98_PTYS=y
+# CONFIG_DEVPTS_MULTIPLE_INSTANCES is not set
+CONFIG_LEGACY_PTYS=y
+CONFIG_LEGACY_PTY_COUNT=64
+# CONFIG_HVC_UDBG is not set
+# CONFIG_IPMI_HANDLER is not set
+# CONFIG_HW_RANDOM is not set
+CONFIG_NVRAM=y
+# CONFIG_R3964 is not set
+# CONFIG_RAW_DRIVER is not set
+# CONFIG_TCG_TPM is not set
+CONFIG_I2C=y
+CONFIG_I2C_BOARDINFO=y
+CONFIG_I2C_COMPAT=y
+CONFIG_I2C_CHARDEV=y
+CONFIG_I2C_HELPER_AUTO=y
+CONFIG_I2C_ALGOBIT=y
+
+#
+# I2C Hardware Bus support
+#
+
+#
+# I2C system bus drivers (mostly embedded / system-on-chip)
+#
+CONFIG_I2C_GPIO_COMMON=y
+CONFIG_I2C_GPIO=y
+CONFIG_I2C_GPIO_OF=y
+# CONFIG_I2C_MPC is not set
+# CONFIG_I2C_OCORES is not set
+# CONFIG_I2C_SIMTEC is not set
+
+#
+# External I2C/SMBus adapter drivers
+#
+# CONFIG_I2C_PARPORT_LIGHT is not set
+# CONFIG_I2C_TAOS_EVM is not set
+# CONFIG_I2C_TINY_USB is not set
+
+#
+# Other I2C/SMBus bus drivers
+#
+# CONFIG_I2C_PCA_PLATFORM is not set
+# CONFIG_I2C_STUB is not set
+
+#
+# Miscellaneous I2C Chip support
+#
+# CONFIG_DS1682 is not set
+# CONFIG_SENSORS_TSL2550 is not set
+# CONFIG_I2C_DEBUG_CORE is not set
+# CONFIG_I2C_DEBUG_ALGO is not set
+# CONFIG_I2C_DEBUG_BUS is not set
+# CONFIG_I2C_DEBUG_CHIP is not set
+
+#
+# EXI support
+#
+CONFIG_GAMECUBE_EXI=y
+# CONFIG_SPI is not set
+
+#
+# PPS support
+#
+# CONFIG_PPS is not set
+CONFIG_ARCH_WANT_OPTIONAL_GPIOLIB=y
+CONFIG_GPIOLIB=y
+# CONFIG_DEBUG_GPIO is not set
+CONFIG_GPIO_SYSFS=y
+
+#
+# Memory mapped GPIO expanders:
+#
+# CONFIG_GPIO_XILINX is not set
+
+#
+# I2C GPIO expanders:
+#
+# CONFIG_GPIO_MAX732X is not set
+# CONFIG_GPIO_PCA953X is not set
+# CONFIG_GPIO_PCF857X is not set
+
+#
+# PCI GPIO expanders:
+#
+
+#
+# SPI GPIO expanders:
+#
+
+#
+# AC97 GPIO expanders:
+#
+# CONFIG_W1 is not set
+# CONFIG_POWER_SUPPLY is not set
+# CONFIG_HWMON is not set
+# CONFIG_THERMAL is not set
+# CONFIG_WATCHDOG is not set
+CONFIG_SSB_POSSIBLE=y
+
+#
+# Sonics Silicon Backplane
+#
+CONFIG_SSB=y
+CONFIG_SSB_BLOCKIO=y
+CONFIG_SSB_SDIOHOST_POSSIBLE=y
+CONFIG_SSB_SDIOHOST=y
+# CONFIG_SSB_SILENT is not set
+CONFIG_SSB_DEBUG=y
+
+#
+# Multifunction device drivers
+#
+# CONFIG_MFD_CORE is not set
+# CONFIG_MFD_SM501 is not set
+# CONFIG_HTC_PASIC3 is not set
+# CONFIG_TPS65010 is not set
+# CONFIG_TWL4030_CORE is not set
+# CONFIG_MFD_TMIO is not set
+# CONFIG_PMIC_DA903X is not set
+# CONFIG_MFD_WM8400 is not set
+# CONFIG_MFD_WM831X is not set
+# CONFIG_MFD_WM8350_I2C is not set
+# CONFIG_MFD_PCF50633 is not set
+# CONFIG_AB3100_CORE is not set
+# CONFIG_REGULATOR is not set
+# CONFIG_MEDIA_SUPPORT is not set
+
+#
+# Graphics support
+#
+# CONFIG_VGASTATE is not set
+# CONFIG_VIDEO_OUTPUT_CONTROL is not set
+CONFIG_FB=y
+# CONFIG_FIRMWARE_EDID is not set
+# CONFIG_FB_DDC is not set
+# CONFIG_FB_BOOT_VESA_SUPPORT is not set
+CONFIG_FB_CFB_FILLRECT=y
+CONFIG_FB_CFB_COPYAREA=y
+CONFIG_FB_CFB_IMAGEBLIT=y
+# CONFIG_FB_CFB_REV_PIXELS_IN_BYTE is not set
+# CONFIG_FB_SYS_FILLRECT is not set
+# CONFIG_FB_SYS_COPYAREA is not set
+# CONFIG_FB_SYS_IMAGEBLIT is not set
+# CONFIG_FB_FOREIGN_ENDIAN is not set
+# CONFIG_FB_SYS_FOPS is not set
+# CONFIG_FB_SVGALIB is not set
+# CONFIG_FB_MACMODES is not set
+# CONFIG_FB_BACKLIGHT is not set
+# CONFIG_FB_MODE_HELPERS is not set
+# CONFIG_FB_TILEBLITTING is not set
+
+#
+# Frame buffer hardware drivers
+#
+# CONFIG_FB_OF is not set
+# CONFIG_FB_VGA16 is not set
+# CONFIG_FB_S1D13XXX is not set
+CONFIG_FB_GAMECUBE=y
+CONFIG_WII_AVE_RVL=y
+# CONFIG_FB_IBM_GXT4500 is not set
+# CONFIG_FB_VIRTUAL is not set
+# CONFIG_FB_METRONOME is not set
+# CONFIG_FB_MB862XX is not set
+# CONFIG_FB_BROADSHEET is not set
+# CONFIG_BACKLIGHT_LCD_SUPPORT is not set
+
+#
+# Display device support
+#
+# CONFIG_DISPLAY_SUPPORT is not set
+
+#
+# Console display driver support
+#
+# CONFIG_VGA_CONSOLE is not set
+CONFIG_DUMMY_CONSOLE=y
+CONFIG_FRAMEBUFFER_CONSOLE=y
+# CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY is not set
+# CONFIG_FRAMEBUFFER_CONSOLE_ROTATION is not set
+# CONFIG_FONTS is not set
+CONFIG_FONT_8x8=y
+CONFIG_FONT_8x16=y
+# CONFIG_LOGO is not set
+CONFIG_SOUND=y
+CONFIG_SOUND_OSS_CORE=y
+CONFIG_SOUND_OSS_CORE_PRECLAIM=y
+CONFIG_SND=y
+CONFIG_SND_TIMER=y
+CONFIG_SND_PCM=y
+CONFIG_SND_HWDEP=y
+CONFIG_SND_RAWMIDI=y
+CONFIG_SND_SEQUENCER=y
+# CONFIG_SND_SEQ_DUMMY is not set
+CONFIG_SND_OSSEMUL=y
+CONFIG_SND_MIXER_OSS=y
+CONFIG_SND_PCM_OSS=y
+CONFIG_SND_PCM_OSS_PLUGINS=y
+CONFIG_SND_SEQUENCER_OSS=y
+# CONFIG_SND_DYNAMIC_MINORS is not set
+CONFIG_SND_SUPPORT_OLD_API=y
+# CONFIG_SND_VERBOSE_PROCFS is not set
+# CONFIG_SND_VERBOSE_PRINTK is not set
+# CONFIG_SND_DEBUG is not set
+CONFIG_SND_RAWMIDI_SEQ=y
+# CONFIG_SND_OPL3_LIB_SEQ is not set
+# CONFIG_SND_OPL4_LIB_SEQ is not set
+# CONFIG_SND_SBAWE_SEQ is not set
+# CONFIG_SND_EMU10K1_SEQ is not set
+CONFIG_SND_DRIVERS=y
+# CONFIG_SND_DUMMY is not set
+# CONFIG_SND_VIRMIDI is not set
+# CONFIG_SND_MTPAV is not set
+# CONFIG_SND_SERIAL_U16550 is not set
+# CONFIG_SND_MPU401 is not set
+CONFIG_SND_PPC=y
+CONFIG_SND_GAMECUBE=y
+CONFIG_SND_GAMECUBE_MIC=m
+CONFIG_SND_USB=y
+CONFIG_SND_USB_AUDIO=y
+CONFIG_SND_USB_USX2Y=m
+CONFIG_SND_USB_CAIAQ=m
+# CONFIG_SND_USB_CAIAQ_INPUT is not set
+# CONFIG_SND_SOC is not set
+# CONFIG_SOUND_PRIME is not set
+CONFIG_HID_SUPPORT=y
+CONFIG_HID=y
+# CONFIG_HIDRAW is not set
+
+#
+# USB Input Devices
+#
+CONFIG_USB_HID=y
+# CONFIG_HID_PID is not set
+# CONFIG_USB_HIDDEV is not set
+
+#
+# Special HID drivers
+#
+CONFIG_HID_A4TECH=m
+CONFIG_HID_APPLE=m
+CONFIG_HID_BELKIN=m
+CONFIG_HID_CHERRY=m
+CONFIG_HID_CHICONY=m
+CONFIG_HID_CYPRESS=m
+CONFIG_HID_DRAGONRISE=m
+CONFIG_DRAGONRISE_FF=y
+CONFIG_HID_EZKEY=m
+CONFIG_HID_KYE=m
+CONFIG_HID_GYRATION=m
+CONFIG_HID_TWINHAN=m
+CONFIG_HID_KENSINGTON=m
+CONFIG_HID_LOGITECH=m
+CONFIG_LOGITECH_FF=y
+CONFIG_LOGIRUMBLEPAD2_FF=y
+CONFIG_HID_MICROSOFT=m
+CONFIG_HID_MONTEREY=m
+CONFIG_HID_NTRIG=m
+CONFIG_HID_PANTHERLORD=m
+CONFIG_PANTHERLORD_FF=y
+CONFIG_HID_PETALYNX=m
+CONFIG_HID_SAMSUNG=m
+CONFIG_HID_SONY=m
+CONFIG_HID_SUNPLUS=m
+CONFIG_HID_GREENASIA=m
+CONFIG_GREENASIA_FF=y
+CONFIG_HID_SMARTJOYPLUS=m
+CONFIG_SMARTJOYPLUS_FF=y
+CONFIG_HID_TOPSEED=m
+CONFIG_HID_THRUSTMASTER=m
+CONFIG_THRUSTMASTER_FF=y
+CONFIG_HID_WACOM=m
+CONFIG_HID_ZEROPLUS=m
+CONFIG_ZEROPLUS_FF=y
+CONFIG_USB_SUPPORT=y
+CONFIG_USB_ARCH_HAS_HCD=y
+CONFIG_USB_ARCH_HAS_OHCI=y
+CONFIG_USB_ARCH_HAS_EHCI=y
+CONFIG_USB=y
+# CONFIG_USB_DEBUG is not set
+CONFIG_USB_ANNOUNCE_NEW_DEVICES=y
+
+#
+# Miscellaneous USB options
+#
+CONFIG_USB_DEVICEFS=y
+CONFIG_USB_DEVICE_CLASS=y
+# CONFIG_USB_DYNAMIC_MINORS is not set
+# CONFIG_USB_OTG is not set
+# CONFIG_USB_OTG_WHITELIST is not set
+# CONFIG_USB_OTG_BLACKLIST_HUB is not set
+# CONFIG_USB_MON is not set
+# CONFIG_USB_WUSB is not set
+# CONFIG_USB_WUSB_CBAF is not set
+
+#
+# USB Host Controller Drivers
+#
+# CONFIG_USB_C67X00_HCD is not set
+CONFIG_USB_EHCI_HCD=y
+# CONFIG_USB_EHCI_ROOT_HUB_TT is not set
+# CONFIG_USB_EHCI_TT_NEWSCHED is not set
+# CONFIG_USB_EHCI_HCD_PPC_OF is not set
+CONFIG_USB_EHCI_HCD_HLWD=y
+# CONFIG_USB_OXU210HP_HCD is not set
+# CONFIG_USB_ISP116X_HCD is not set
+# CONFIG_USB_ISP1760_HCD is not set
+# CONFIG_USB_ISP1362_HCD is not set
+CONFIG_USB_OHCI_HCD=y
+# CONFIG_USB_OHCI_HCD_PPC_OF_BE is not set
+# CONFIG_USB_OHCI_HCD_PPC_OF_LE is not set
+# CONFIG_USB_OHCI_HCD_PPC_OF is not set
+CONFIG_USB_OHCI_HCD_HLWD=y
+# CONFIG_USB_OHCI_HCD_SSB is not set
+# CONFIG_USB_OHCI_BIG_ENDIAN_DESC is not set
+# CONFIG_USB_OHCI_BIG_ENDIAN_MMIO is not set
+CONFIG_USB_OHCI_LITTLE_ENDIAN=y
+# CONFIG_USB_SL811_HCD is not set
+# CONFIG_USB_R8A66597_HCD is not set
+CONFIG_USB_WII_HCD=y
+# CONFIG_USB_HWA_HCD is not set
+
+#
+# USB Device Class drivers
+#
+# CONFIG_USB_ACM is not set
+# CONFIG_USB_PRINTER is not set
+# CONFIG_USB_WDM is not set
+# CONFIG_USB_TMC is not set
+
+#
+# NOTE: USB_STORAGE depends on SCSI but BLK_DEV_SD may
+#
+
+#
+# also be needed; see USB_STORAGE Help for more info
+#
+CONFIG_USB_STORAGE=y
+# CONFIG_USB_STORAGE_DEBUG is not set
+# CONFIG_USB_STORAGE_DATAFAB is not set
+# CONFIG_USB_STORAGE_FREECOM is not set
+# CONFIG_USB_STORAGE_ISD200 is not set
+# CONFIG_USB_STORAGE_USBAT is not set
+# CONFIG_USB_STORAGE_SDDR09 is not set
+# CONFIG_USB_STORAGE_SDDR55 is not set
+# CONFIG_USB_STORAGE_JUMPSHOT is not set
+# CONFIG_USB_STORAGE_ALAUDA is not set
+# CONFIG_USB_STORAGE_ONETOUCH is not set
+# CONFIG_USB_STORAGE_KARMA is not set
+# CONFIG_USB_STORAGE_CYPRESS_ATACB is not set
+CONFIG_USB_LIBUSUAL=y
+
+#
+# USB Imaging devices
+#
+# CONFIG_USB_MDC800 is not set
+# CONFIG_USB_MICROTEK is not set
+
+#
+# USB port drivers
+#
+# CONFIG_USB_SERIAL is not set
+
+#
+# USB Miscellaneous drivers
+#
+# CONFIG_USB_EMI62 is not set
+# CONFIG_USB_EMI26 is not set
+# CONFIG_USB_ADUTUX is not set
+# CONFIG_USB_SEVSEG is not set
+# CONFIG_USB_RIO500 is not set
+# CONFIG_USB_LEGOTOWER is not set
+# CONFIG_USB_LCD is not set
+# CONFIG_USB_BERRY_CHARGE is not set
+# CONFIG_USB_LED is not set
+# CONFIG_USB_CYPRESS_CY7C63 is not set
+# CONFIG_USB_CYTHERM is not set
+# CONFIG_USB_IDMOUSE is not set
+# CONFIG_USB_FTDI_ELAN is not set
+# CONFIG_USB_APPLEDISPLAY is not set
+# CONFIG_USB_SISUSBVGA is not set
+# CONFIG_USB_LD is not set
+# CONFIG_USB_TRANCEVIBRATOR is not set
+# CONFIG_USB_IOWARRIOR is not set
+# CONFIG_USB_TEST is not set
+# CONFIG_USB_ISIGHTFW is not set
+# CONFIG_USB_VST is not set
+# CONFIG_USB_GADGET is not set
+
+#
+# OTG and related infrastructure
+#
+# CONFIG_USB_GPIO_VBUS is not set
+# CONFIG_NOP_USB_XCEIV is not set
+CONFIG_MMC=y
+# CONFIG_MMC_DEBUG is not set
+# CONFIG_MMC_UNSAFE_RESUME is not set
+
+#
+# MMC/SD/SDIO Card Drivers
+#
+CONFIG_MMC_BLOCK=y
+CONFIG_MMC_BLOCK_BOUNCE=y
+# CONFIG_SDIO_UART is not set
+# CONFIG_MMC_TEST is not set
+
+#
+# MMC/SD/SDIO Host Controller Drivers
+#
+CONFIG_MMC_SDHCI=y
+CONFIG_MMC_SDHCI_IO_ACCESSORS=y
+# CONFIG_MMC_SDHCI_OF is not set
+# CONFIG_MMC_SDHCI_PLTFM is not set
+CONFIG_MMC_SDHCI_HLWD=y
+# CONFIG_MMC_WBSD is not set
+# CONFIG_MMC_AT91 is not set
+# CONFIG_MMC_ATMELMCI is not set
+# CONFIG_MEMSTICK is not set
+# CONFIG_NEW_LEDS is not set
+# CONFIG_ACCESSIBILITY is not set
+# CONFIG_EDAC is not set
+CONFIG_RTC_LIB=y
+CONFIG_RTC_CLASS=y
+CONFIG_RTC_HCTOSYS=y
+CONFIG_RTC_HCTOSYS_DEVICE="rtc0"
+# CONFIG_RTC_DEBUG is not set
+
+#
+# RTC interfaces
+#
+CONFIG_RTC_INTF_SYSFS=y
+CONFIG_RTC_INTF_PROC=y
+CONFIG_RTC_INTF_DEV=y
+# CONFIG_RTC_INTF_DEV_UIE_EMUL is not set
+# CONFIG_RTC_DRV_TEST is not set
+
+#
+# I2C RTC drivers
+#
+# CONFIG_RTC_DRV_DS1307 is not set
+# CONFIG_RTC_DRV_DS1374 is not set
+# CONFIG_RTC_DRV_DS1672 is not set
+# CONFIG_RTC_DRV_MAX6900 is not set
+# CONFIG_RTC_DRV_RS5C372 is not set
+# CONFIG_RTC_DRV_ISL1208 is not set
+# CONFIG_RTC_DRV_X1205 is not set
+# CONFIG_RTC_DRV_PCF8563 is not set
+# CONFIG_RTC_DRV_PCF8583 is not set
+# CONFIG_RTC_DRV_M41T80 is not set
+# CONFIG_RTC_DRV_S35390A is not set
+# CONFIG_RTC_DRV_FM3130 is not set
+# CONFIG_RTC_DRV_RX8581 is not set
+# CONFIG_RTC_DRV_RX8025 is not set
+
+#
+# SPI RTC drivers
+#
+
+#
+# Platform RTC drivers
+#
+# CONFIG_RTC_DRV_CMOS is not set
+# CONFIG_RTC_DRV_DS1286 is not set
+# CONFIG_RTC_DRV_DS1511 is not set
+# CONFIG_RTC_DRV_DS1553 is not set
+# CONFIG_RTC_DRV_DS1742 is not set
+# CONFIG_RTC_DRV_STK17TA8 is not set
+# CONFIG_RTC_DRV_M48T86 is not set
+# CONFIG_RTC_DRV_M48T35 is not set
+# CONFIG_RTC_DRV_M48T59 is not set
+# CONFIG_RTC_DRV_BQ4802 is not set
+# CONFIG_RTC_DRV_V3020 is not set
+CONFIG_RTC_DRV_GCN=y
+
+#
+# on-CPU RTC drivers
+#
+CONFIG_RTC_DRV_GENERIC=y
+# CONFIG_DMADEVICES is not set
+# CONFIG_AUXDISPLAY is not set
+# CONFIG_UIO is not set
+
+#
+# TI VLYNQ
+#
+# CONFIG_STAGING is not set
+
+#
+# File systems
+#
+CONFIG_EXT2_FS=y
+# CONFIG_EXT2_FS_XATTR is not set
+# CONFIG_EXT2_FS_XIP is not set
+CONFIG_EXT3_FS=y
+# CONFIG_EXT3_DEFAULTS_TO_ORDERED is not set
+# CONFIG_EXT3_FS_XATTR is not set
+# CONFIG_EXT4_FS is not set
+CONFIG_JBD=y
+# CONFIG_JBD_DEBUG is not set
+# CONFIG_REISERFS_FS is not set
+# CONFIG_JFS_FS is not set
+# CONFIG_FS_POSIX_ACL is not set
+# CONFIG_XFS_FS is not set
+# CONFIG_GFS2_FS is not set
+# CONFIG_OCFS2_FS is not set
+# CONFIG_BTRFS_FS is not set
+# CONFIG_NILFS2_FS is not set
+CONFIG_FILE_LOCKING=y
+CONFIG_FSNOTIFY=y
+CONFIG_DNOTIFY=y
+CONFIG_INOTIFY=y
+CONFIG_INOTIFY_USER=y
+# CONFIG_QUOTA is not set
+# CONFIG_AUTOFS_FS is not set
+# CONFIG_AUTOFS4_FS is not set
+CONFIG_FUSE_FS=m
+CONFIG_CUSE=m
+
+#
+# Caches
+#
+# CONFIG_FSCACHE is not set
+
+#
+# CD-ROM/DVD Filesystems
+#
+CONFIG_ISO9660_FS=y
+CONFIG_JOLIET=y
+# CONFIG_ZISOFS is not set
+# CONFIG_UDF_FS is not set
+
+#
+# DOS/FAT/NT Filesystems
+#
+CONFIG_FAT_FS=y
+CONFIG_MSDOS_FS=y
+CONFIG_VFAT_FS=y
+CONFIG_FAT_DEFAULT_CODEPAGE=437
+CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1"
+# CONFIG_NTFS_FS is not set
+
+#
+# Pseudo filesystems
+#
+CONFIG_PROC_FS=y
+CONFIG_PROC_KCORE=y
+CONFIG_PROC_SYSCTL=y
+# CONFIG_PROC_PAGE_MONITOR is not set
+CONFIG_SYSFS=y
+CONFIG_TMPFS=y
+# CONFIG_TMPFS_POSIX_ACL is not set
+# CONFIG_HUGETLB_PAGE is not set
+# CONFIG_CONFIGFS_FS is not set
+CONFIG_MISC_FILESYSTEMS=y
+# CONFIG_ADFS_FS is not set
+# CONFIG_AFFS_FS is not set
+# CONFIG_HFS_FS is not set
+# CONFIG_HFSPLUS_FS is not set
+# CONFIG_BEFS_FS is not set
+# CONFIG_BFS_FS is not set
+# CONFIG_EFS_FS is not set
+# CONFIG_CRAMFS is not set
+# CONFIG_SQUASHFS is not set
+# CONFIG_VXFS_FS is not set
+# CONFIG_MINIX_FS is not set
+# CONFIG_OMFS_FS is not set
+# CONFIG_HPFS_FS is not set
+# CONFIG_QNX4FS_FS is not set
+# CONFIG_ROMFS_FS is not set
+# CONFIG_SYSV_FS is not set
+# CONFIG_UFS_FS is not set
+CONFIG_NETWORK_FILESYSTEMS=y
+CONFIG_NFS_FS=y
+CONFIG_NFS_V3=y
+# CONFIG_NFS_V3_ACL is not set
+# CONFIG_NFS_V4 is not set
+CONFIG_ROOT_NFS=y
+# CONFIG_NFSD is not set
+CONFIG_LOCKD=y
+CONFIG_LOCKD_V4=y
+CONFIG_NFS_COMMON=y
+CONFIG_SUNRPC=y
+# CONFIG_RPCSEC_GSS_KRB5 is not set
+# CONFIG_RPCSEC_GSS_SPKM3 is not set
+# CONFIG_SMB_FS is not set
+CONFIG_CIFS=m
+# CONFIG_CIFS_STATS is not set
+# CONFIG_CIFS_WEAK_PW_HASH is not set
+# CONFIG_CIFS_XATTR is not set
+# CONFIG_CIFS_DEBUG2 is not set
+# CONFIG_CIFS_EXPERIMENTAL is not set
+# CONFIG_NCP_FS is not set
+# CONFIG_CODA_FS is not set
+# CONFIG_AFS_FS is not set
+
+#
+# Partition Types
+#
+# CONFIG_PARTITION_ADVANCED is not set
+CONFIG_MSDOS_PARTITION=y
+CONFIG_NLS=y
+CONFIG_NLS_DEFAULT="iso8859-1"
+CONFIG_NLS_CODEPAGE_437=y
+# CONFIG_NLS_CODEPAGE_737 is not set
+# CONFIG_NLS_CODEPAGE_775 is not set
+# CONFIG_NLS_CODEPAGE_850 is not set
+# CONFIG_NLS_CODEPAGE_852 is not set
+# CONFIG_NLS_CODEPAGE_855 is not set
+# CONFIG_NLS_CODEPAGE_857 is not set
+# CONFIG_NLS_CODEPAGE_860 is not set
+# CONFIG_NLS_CODEPAGE_861 is not set
+# CONFIG_NLS_CODEPAGE_862 is not set
+# CONFIG_NLS_CODEPAGE_863 is not set
+# CONFIG_NLS_CODEPAGE_864 is not set
+# CONFIG_NLS_CODEPAGE_865 is not set
+# CONFIG_NLS_CODEPAGE_866 is not set
+# CONFIG_NLS_CODEPAGE_869 is not set
+# CONFIG_NLS_CODEPAGE_936 is not set
+# CONFIG_NLS_CODEPAGE_950 is not set
+# CONFIG_NLS_CODEPAGE_932 is not set
+# CONFIG_NLS_CODEPAGE_949 is not set
+# CONFIG_NLS_CODEPAGE_874 is not set
+# CONFIG_NLS_ISO8859_8 is not set
+# CONFIG_NLS_CODEPAGE_1250 is not set
+# CONFIG_NLS_CODEPAGE_1251 is not set
+# CONFIG_NLS_ASCII is not set
+CONFIG_NLS_ISO8859_1=y
+# CONFIG_NLS_ISO8859_2 is not set
+# CONFIG_NLS_ISO8859_3 is not set
+# CONFIG_NLS_ISO8859_4 is not set
+# CONFIG_NLS_ISO8859_5 is not set
+# CONFIG_NLS_ISO8859_6 is not set
+# CONFIG_NLS_ISO8859_7 is not set
+# CONFIG_NLS_ISO8859_9 is not set
+# CONFIG_NLS_ISO8859_13 is not set
+# CONFIG_NLS_ISO8859_14 is not set
+# CONFIG_NLS_ISO8859_15 is not set
+# CONFIG_NLS_KOI8_R is not set
+# CONFIG_NLS_KOI8_U is not set
+# CONFIG_NLS_UTF8 is not set
+# CONFIG_DLM is not set
+CONFIG_BINARY_PRINTF=y
+
+#
+# Library routines
+#
+CONFIG_BITREVERSE=y
+CONFIG_GENERIC_FIND_LAST_BIT=y
+CONFIG_CRC_CCITT=y
+CONFIG_CRC16=y
+# CONFIG_CRC_T10DIF is not set
+# CONFIG_CRC_ITU_T is not set
+CONFIG_CRC32=y
+# CONFIG_CRC7 is not set
+# CONFIG_LIBCRC32C is not set
+CONFIG_ZLIB_INFLATE=y
+CONFIG_DECOMPRESS_GZIP=y
+CONFIG_HAS_IOMEM=y
+CONFIG_HAS_IOPORT=y
+CONFIG_HAS_DMA=y
+CONFIG_HAVE_LMB=y
+CONFIG_NLATTR=y
+CONFIG_GENERIC_ATOMIC64=y
+
+#
+# Kernel hacking
+#
+CONFIG_PRINTK_TIME=y
+CONFIG_ENABLE_WARN_DEPRECATED=y
+CONFIG_ENABLE_MUST_CHECK=y
+CONFIG_FRAME_WARN=1024
+CONFIG_MAGIC_SYSRQ=y
+# CONFIG_STRIP_ASM_SYMS is not set
+# CONFIG_UNUSED_SYMBOLS is not set
+CONFIG_DEBUG_FS=y
+# CONFIG_HEADERS_CHECK is not set
+CONFIG_DEBUG_KERNEL=y
+# CONFIG_DEBUG_SHIRQ is not set
+CONFIG_DETECT_SOFTLOCKUP=y
+# CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC is not set
+CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC_VALUE=0
+# CONFIG_DETECT_HUNG_TASK is not set
+CONFIG_SCHED_DEBUG=y
+CONFIG_SCHEDSTATS=y
+# CONFIG_TIMER_STATS is not set
+# CONFIG_DEBUG_OBJECTS is not set
+# CONFIG_DEBUG_SLAB is not set
+# CONFIG_DEBUG_KMEMLEAK is not set
+CONFIG_DEBUG_PREEMPT=y
+# CONFIG_DEBUG_RT_MUTEXES is not set
+# CONFIG_RT_MUTEX_TESTER is not set
+CONFIG_DEBUG_SPINLOCK=y
+CONFIG_DEBUG_MUTEXES=y
+# CONFIG_DEBUG_LOCK_ALLOC is not set
+# CONFIG_PROVE_LOCKING is not set
+# CONFIG_LOCK_STAT is not set
+CONFIG_DEBUG_SPINLOCK_SLEEP=y
+# CONFIG_DEBUG_LOCKING_API_SELFTESTS is not set
+CONFIG_STACKTRACE=y
+# CONFIG_DEBUG_KOBJECT is not set
+CONFIG_DEBUG_BUGVERBOSE=y
+# CONFIG_DEBUG_INFO is not set
+# CONFIG_DEBUG_VM is not set
+# CONFIG_DEBUG_WRITECOUNT is not set
+# CONFIG_DEBUG_MEMORY_INIT is not set
+# CONFIG_DEBUG_LIST is not set
+# CONFIG_DEBUG_SG is not set
+# CONFIG_DEBUG_NOTIFIERS is not set
+# CONFIG_DEBUG_CREDENTIALS is not set
+# CONFIG_RCU_TORTURE_TEST is not set
+# CONFIG_RCU_CPU_STALL_DETECTOR is not set
+# CONFIG_BACKTRACE_SELF_TEST is not set
+# CONFIG_DEBUG_BLOCK_EXT_DEVT is not set
+# CONFIG_DEBUG_FORCE_WEAK_PER_CPU is not set
+# CONFIG_FAULT_INJECTION is not set
+CONFIG_LATENCYTOP=y
+CONFIG_SYSCTL_SYSCALL_CHECK=y
+# CONFIG_DEBUG_PAGEALLOC is not set
+CONFIG_NOP_TRACER=y
+CONFIG_HAVE_FUNCTION_TRACER=y
+CONFIG_HAVE_FUNCTION_GRAPH_TRACER=y
+CONFIG_HAVE_DYNAMIC_FTRACE=y
+CONFIG_HAVE_FTRACE_MCOUNT_RECORD=y
+CONFIG_TRACER_MAX_TRACE=y
+CONFIG_RING_BUFFER=y
+CONFIG_EVENT_TRACING=y
+CONFIG_CONTEXT_SWITCH_TRACER=y
+CONFIG_TRACING=y
+CONFIG_GENERIC_TRACER=y
+CONFIG_TRACING_SUPPORT=y
+CONFIG_FTRACE=y
+# CONFIG_FUNCTION_TRACER is not set
+# CONFIG_IRQSOFF_TRACER is not set
+# CONFIG_PREEMPT_TRACER is not set
+CONFIG_SCHED_TRACER=y
+CONFIG_BOOT_TRACER=y
+CONFIG_BRANCH_PROFILE_NONE=y
+# CONFIG_PROFILE_ANNOTATED_BRANCHES is not set
+# CONFIG_PROFILE_ALL_BRANCHES is not set
+# CONFIG_STACK_TRACER is not set
+# CONFIG_KMEMTRACE is not set
+# CONFIG_WORKQUEUE_TRACER is not set
+# CONFIG_BLK_DEV_IO_TRACE is not set
+# CONFIG_FTRACE_STARTUP_TEST is not set
+# CONFIG_RING_BUFFER_BENCHMARK is not set
+# CONFIG_DYNAMIC_DEBUG is not set
+CONFIG_DMA_API_DEBUG=y
+# CONFIG_SAMPLES is not set
+CONFIG_HAVE_ARCH_KGDB=y
+# CONFIG_KGDB is not set
+# CONFIG_PPC_DISABLE_WERROR is not set
+CONFIG_PPC_WERROR=y
+CONFIG_PRINT_STACK_DEPTH=64
+# CONFIG_DEBUG_STACKOVERFLOW is not set
+# CONFIG_DEBUG_STACK_USAGE is not set
+# CONFIG_PPC_EMULATED_STATS is not set
+# CONFIG_CODE_PATCHING_SELFTEST is not set
+# CONFIG_FTR_FIXUP_SELFTEST is not set
+# CONFIG_MSI_BITMAP_SELFTEST is not set
+# CONFIG_XMON is not set
+# CONFIG_IRQSTACKS is not set
+# CONFIG_VIRQ_DEBUG is not set
+# CONFIG_BDI_SWITCH is not set
+# CONFIG_BOOTX_TEXT is not set
+CONFIG_PPC_EARLY_DEBUG=y
+# CONFIG_PPC_EARLY_DEBUG_LPAR is not set
+# CONFIG_PPC_EARLY_DEBUG_G5 is not set
+# CONFIG_PPC_EARLY_DEBUG_RTAS_PANEL is not set
+# CONFIG_PPC_EARLY_DEBUG_RTAS_CONSOLE is not set
+# CONFIG_PPC_EARLY_DEBUG_MAPLE is not set
+# CONFIG_PPC_EARLY_DEBUG_ISERIES is not set
+# CONFIG_PPC_EARLY_DEBUG_PAS_REALMODE is not set
+# CONFIG_PPC_EARLY_DEBUG_BEAT is not set
+# CONFIG_PPC_EARLY_DEBUG_44x is not set
+# CONFIG_PPC_EARLY_DEBUG_40x is not set
+# CONFIG_PPC_EARLY_DEBUG_CPM is not set
+CONFIG_PPC_EARLY_DEBUG_USBGECKO=y
+
+#
+# Security options
+#
+# CONFIG_KEYS is not set
+# CONFIG_SECURITY is not set
+# CONFIG_SECURITYFS is not set
+# CONFIG_SECURITY_FILE_CAPABILITIES is not set
+CONFIG_CRYPTO=y
+
+#
+# Crypto core or helper
+#
+CONFIG_CRYPTO_ALGAPI=y
+CONFIG_CRYPTO_ALGAPI2=y
+CONFIG_CRYPTO_AEAD2=y
+CONFIG_CRYPTO_BLKCIPHER=y
+CONFIG_CRYPTO_BLKCIPHER2=y
+CONFIG_CRYPTO_HASH2=y
+CONFIG_CRYPTO_RNG2=y
+CONFIG_CRYPTO_PCOMP=y
+CONFIG_CRYPTO_MANAGER=y
+CONFIG_CRYPTO_MANAGER2=y
+# CONFIG_CRYPTO_GF128MUL is not set
+# CONFIG_CRYPTO_NULL is not set
+CONFIG_CRYPTO_WORKQUEUE=y
+# CONFIG_CRYPTO_CRYPTD is not set
+# CONFIG_CRYPTO_AUTHENC is not set
+# CONFIG_CRYPTO_TEST is not set
+
+#
+# Authenticated Encryption with Associated Data
+#
+# CONFIG_CRYPTO_CCM is not set
+# CONFIG_CRYPTO_GCM is not set
+# CONFIG_CRYPTO_SEQIV is not set
+
+#
+# Block modes
+#
+# CONFIG_CRYPTO_CBC is not set
+# CONFIG_CRYPTO_CTR is not set
+# CONFIG_CRYPTO_CTS is not set
+CONFIG_CRYPTO_ECB=y
+# CONFIG_CRYPTO_LRW is not set
+# CONFIG_CRYPTO_PCBC is not set
+# CONFIG_CRYPTO_XTS is not set
+
+#
+# Hash modes
+#
+# CONFIG_CRYPTO_HMAC is not set
+# CONFIG_CRYPTO_XCBC is not set
+# CONFIG_CRYPTO_VMAC is not set
+
+#
+# Digest
+#
+# CONFIG_CRYPTO_CRC32C is not set
+# CONFIG_CRYPTO_GHASH is not set
+# CONFIG_CRYPTO_MD4 is not set
+# CONFIG_CRYPTO_MD5 is not set
+# CONFIG_CRYPTO_MICHAEL_MIC is not set
+# CONFIG_CRYPTO_RMD128 is not set
+# CONFIG_CRYPTO_RMD160 is not set
+# CONFIG_CRYPTO_RMD256 is not set
+# CONFIG_CRYPTO_RMD320 is not set
+# CONFIG_CRYPTO_SHA1 is not set
+# CONFIG_CRYPTO_SHA256 is not set
+# CONFIG_CRYPTO_SHA512 is not set
+# CONFIG_CRYPTO_TGR192 is not set
+# CONFIG_CRYPTO_WP512 is not set
+
+#
+# Ciphers
+#
+CONFIG_CRYPTO_AES=y
+# CONFIG_CRYPTO_ANUBIS is not set
+CONFIG_CRYPTO_ARC4=y
+# CONFIG_CRYPTO_BLOWFISH is not set
+# CONFIG_CRYPTO_CAMELLIA is not set
+# CONFIG_CRYPTO_CAST5 is not set
+# CONFIG_CRYPTO_CAST6 is not set
+# CONFIG_CRYPTO_DES is not set
+# CONFIG_CRYPTO_FCRYPT is not set
+# CONFIG_CRYPTO_KHAZAD is not set
+# CONFIG_CRYPTO_SALSA20 is not set
+# CONFIG_CRYPTO_SEED is not set
+# CONFIG_CRYPTO_SERPENT is not set
+# CONFIG_CRYPTO_TEA is not set
+# CONFIG_CRYPTO_TWOFISH is not set
+
+#
+# Compression
+#
+# CONFIG_CRYPTO_DEFLATE is not set
+# CONFIG_CRYPTO_ZLIB is not set
+# CONFIG_CRYPTO_LZO is not set
+
+#
+# Random Number Generation
+#
+# CONFIG_CRYPTO_ANSI_CPRNG is not set
+# CONFIG_CRYPTO_HW is not set
+# CONFIG_PPC_CLOCK is not set
+CONFIG_PPC_LIB_RHEAP=y
+# CONFIG_VIRTUALIZATION is not set
diff --git a/arch/powerpc/include/asm/dma-mapping.h b/arch/powerpc/include/asm/dma-mapping.h
index e281dae..e35e0f0 100644
--- a/arch/powerpc/include/asm/dma-mapping.h
+++ b/arch/powerpc/include/asm/dma-mapping.h
@@ -17,6 +17,7 @@
 #include <linux/dma-debug.h>
 #include <asm/io.h>
 #include <asm/swiotlb.h>
+#include <asm-generic/dma-coherent.h>
 
 #define DMA_ERROR_CODE		(~(dma_addr_t)0x0)
 
diff --git a/arch/powerpc/include/asm/starlet-ios.h b/arch/powerpc/include/asm/starlet-ios.h
new file mode 100644
index 0000000..926ee2d
--- /dev/null
+++ b/arch/powerpc/include/asm/starlet-ios.h
@@ -0,0 +1,253 @@
+/*
+ * arch/powerpc/include/asm/starlet-ios.h
+ *
+ * Nintendo Wii Starlet IOS definitions
+ * Copyright (C) 2008-2009 The GameCube Linux Team
+ * Copyright (C) 2008,2009 Albert Herranz
+ *
+ * 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.
+ *
+ */
+
+#ifndef __STARLET_IOS_H
+#define __STARLET_IOS_H
+
+#include <linux/types.h>
+#include <linux/spinlock_types.h>
+#include <linux/platform_device.h>
+#include <linux/dmapool.h>
+#include <linux/dma-mapping.h>
+#include <linux/list.h>
+#include <linux/scatterlist.h>
+#include <linux/timer.h>
+#include <asm/rheap.h>
+
+#define STARLET_TITLE_HBC_HAXX	0x0001000148415858ULL
+#define STARLET_TITLE_HBC_JODI	0x000100014A4F4449ULL
+
+#define STARLET_EINVAL	-4
+
+#define STARLET_IPC_DMA_ALIGN   0x1f /* 32 bytes */
+
+struct starlet_ipc_request;
+
+/* input/output heap */
+struct starlet_ioh {
+	spinlock_t lock;
+	rh_info_t *rheap;
+	unsigned long base_phys;
+	void *base;
+	size_t size;
+};
+
+/* pseudo-scatterlist support for the input/output heap */
+struct starlet_ioh_sg {
+	void *buf;
+	size_t len;
+	dma_addr_t dma_addr;
+};
+
+/* inter-process communication device abstraction */
+struct starlet_ipc_device {
+	unsigned long flags;
+
+	void __iomem *io_base;
+	int irq;
+
+	struct dma_pool *dma_pool;	/* to allocate requests */
+	struct starlet_ioh *ioh;	/* to allocate special io buffers */
+
+	unsigned int random_id;
+
+	spinlock_t list_lock;
+	struct list_head outstanding_list;
+	unsigned long nr_outstanding;
+	struct list_head pending_list;
+	unsigned long nr_pending;
+
+	struct timer_list timer;
+
+	struct starlet_ipc_request *req; /* for requests causing a ios reboot */
+
+	struct device *dev;
+};
+
+/* iovec entry suitable for ioctlv */
+struct starlet_iovec {
+	dma_addr_t dma_addr;
+	u32 dma_len;
+};
+
+typedef int (*starlet_ipc_callback_t)(struct starlet_ipc_request *req);
+
+struct starlet_ipc_request {
+	/* begin starlet firmware request format */
+	u32 cmd;				/* 0x00 */
+	s32 result;				/* 0x04 */
+	union {					/* 0x08 */
+		s32 fd;
+		u32 req_cmd;
+	};
+	union {
+		struct {
+			dma_addr_t pathname;	/* 0x0c */
+			u32 mode;		/* 0x10 */
+		} open;
+		struct {
+			u32 request;		/* 0x0c */
+			dma_addr_t ibuf;	/* 0x10 */
+			u32 ilen;		/* 0x14 */
+			dma_addr_t obuf;	/* 0x18 */
+			u32 olen;		/* 0x1c */
+		} ioctl;
+		struct {
+			u32 request;		/* 0x0c */
+			u32 argc_in;		/* 0x10 */
+			u32 argc_io;		/* 0x14 */
+			dma_addr_t iovec_da;	/* 0x18 */
+		} ioctlv;
+		u32 argv[5];			/* 0x0c,0x10,0x14,0x18,0x1c */
+	};
+	/* end starlet firmware request format */
+
+	/*
+	 * A signature is used to discard bogus requests from earlier
+	 * IPC instances.
+	 */
+	unsigned int sig;
+
+	dma_addr_t dma_addr;	/* request dma address */
+
+	/* ioctlv related data */
+	struct starlet_iovec *iovec;
+	size_t iovec_size;
+
+	unsigned sgl_nents_in;
+	unsigned sgl_nents_io;
+	union {
+		struct scatterlist *sgl_in;
+		struct starlet_ioh_sg *ioh_sgl_in;
+	};
+	union {
+		struct scatterlist *sgl_io;
+		struct starlet_ioh_sg *ioh_sgl_io;
+	};
+
+	void *done_data;
+	starlet_ipc_callback_t done;
+
+	starlet_ipc_callback_t complete;
+
+	unsigned long jiffies;
+
+	struct list_head node; /* for queueing */
+
+	struct starlet_ipc_device *ipc_dev;
+};
+
+
+
+/* from starlet-malloc.c */
+
+extern int starlet_malloc_lib_bootstrap(struct resource *mem);
+
+extern void *starlet_kzalloc(size_t size, gfp_t flags);
+extern void starlet_kfree(void *ptr);
+
+extern void *starlet_ioh_kzalloc(size_t size);
+extern void starlet_ioh_kfree(void *ptr);
+
+extern unsigned long starlet_ioh_virt_to_phys(void *ptr);
+
+extern void starlet_ioh_sg_init_table(struct starlet_ioh_sg *sgl,
+				      unsigned int nents);
+extern void starlet_ioh_sg_set_buf(struct starlet_ioh_sg *sg,
+				   void *buf, size_t len);
+
+#define starlet_ioh_for_each_sg(sgl, sg, nr, __i) \
+	for (__i = 0, sg = (sgl); __i < nr; __i++, sg++)
+
+extern int starlet_ioh_dma_map_sg(struct device *dev,
+				  struct starlet_ioh_sg *sgl, int nents,
+				  enum dma_data_direction direction);
+extern void starlet_ioh_dma_unmap_sg(struct device *dev,
+				     struct starlet_ioh_sg *sgl, int nents,
+				     enum dma_data_direction direction);
+/* from starlet-ipc.c */
+
+extern struct starlet_ipc_device *starlet_ipc_get_device(void);
+
+extern struct starlet_ipc_request *
+starlet_ipc_alloc_request(struct starlet_ipc_device *ipc_dev, gfp_t flags);
+extern void starlet_ipc_free_request(struct starlet_ipc_request *req);
+
+
+extern int starlet_open(const char *pathname, int flags);
+extern int starlet_open_polled(const char *pathname, int flags,
+			       unsigned long usecs);
+extern int starlet_close(int fd);
+extern int starlet_close_polled(int fd, unsigned long usecs);
+
+extern int starlet_ioctl(int fd, int request,
+			 void *ibuf, size_t ilen,
+			 void *obuf, size_t olen);
+extern int starlet_ioctl_nowait(int fd, int request,
+				void *ibuf, size_t ilen,
+				void *obuf, size_t olen,
+				starlet_ipc_callback_t callback,
+				void *arg);
+extern int starlet_ioctl_polled(int fd, int request,
+				void *ibuf, size_t ilen,
+				void *obuf, size_t olen, unsigned long usecs);
+
+extern int starlet_ioctlv(int fd, int request,
+			      unsigned int nents_in,
+			      struct scatterlist *sgl_in,
+			      unsigned int nents_out,
+			      struct scatterlist *sgl_out);
+extern int starlet_ioctlv_nowait(int fd, int request,
+				 unsigned int nents_in,
+				 struct scatterlist *sgl_in,
+				 unsigned int nents_out,
+				 struct scatterlist *sgl_out,
+				 starlet_ipc_callback_t callback,
+				 void *arg);
+extern int starlet_ioctlv_polled(int fd, int request,
+				 unsigned int nents_in,
+				 struct scatterlist *sgl_in,
+				 unsigned int nents_out,
+				 struct scatterlist *sgl_out,
+				 unsigned long usecs);
+extern int starlet_ioctlv_and_reboot(int fd, int request,
+					 unsigned int nents_in,
+					 struct scatterlist *sgl_in,
+					 unsigned int nents_out,
+					 struct scatterlist *sgl_out);
+
+extern int starlet_ioh_ioctlv(int fd, int request,
+		       unsigned int nents_in,
+		       struct starlet_ioh_sg *ioh_sgl_in,
+		       unsigned int nents_io,
+		       struct starlet_ioh_sg *ioh_sgl_io);
+extern int starlet_ioh_ioctlv_nowait(int fd, int request,
+				     unsigned int nents_in,
+				     struct starlet_ioh_sg *ioh_sgl_in,
+				     unsigned int nents_io,
+				     struct starlet_ioh_sg *ioh_sgl_io,
+				     starlet_ipc_callback_t callback,
+				     void *arg);
+
+/* from starlet-es.c */
+
+extern int starlet_es_reload_ios_and_discard(void);
+extern int starlet_es_reload_ios_and_launch(u64 title);
+
+/* from starlet-stm.c */
+
+extern void starlet_stm_restart(void);
+extern void starlet_stm_power_off(void);
+
+#endif /* __STARLET_IOS_H */
diff --git a/arch/powerpc/include/asm/starlet-mini.h b/arch/powerpc/include/asm/starlet-mini.h
new file mode 100644
index 0000000..603038e
--- /dev/null
+++ b/arch/powerpc/include/asm/starlet-mini.h
@@ -0,0 +1,173 @@
+/*
+ * arch/powerpc/include/asm/starlet-mini.h
+ *
+ * Definitions for the 'mini' firmware replacement for Starlet
+ * Copyright (C) 2009 The GameCube Linux Team
+ * Copyright (C) 2009 Albert Herranz
+ *
+ * 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.
+ *
+ */
+
+#ifndef __STARLET_MINI_H
+#define __STARLET_MINI_H
+
+#ifdef CONFIG_STARLET_MINI
+
+/*
+ * mini ipc call numbering scheme
+ */
+
+#define _MIPC_FAST        0x01
+#define _MIPC_SLOW        0x00
+
+#define _MIPC_DEV_SYS     0x00
+#define _MIPC_DEV_NAND    0x01
+#define _MIPC_DEV_SDHC    0x02
+#define _MIPC_DEV_KEYS    0x03
+#define _MIPC_DEV_AES     0x04
+#define _MIPC_DEV_BOOT2   0x05
+#define _MIPC_DEV_PPC     0x06
+#define _MIPC_DEV_SDMMC   0x07
+
+#define _MIPC_SYS_PING    0x0000
+#define _MIPC_SYS_JUMP    0x0001
+#define _MIPC_SYS_GETVERS 0x0002
+#define _MIPC_SYS_GETGITS 0x0003
+#define _MIPC_SYS_WRITE32 0x0100
+#define _MIPC_SYS_WRITE16 0x0101
+#define _MIPC_SYS_WRITE8  0x0102
+#define _MIPC_SYS_READ32  0x0103
+#define _MIPC_SYS_READ16  0x0104
+#define _MIPC_SYS_READ8   0x0105
+#define _MIPC_SYS_SET32   0x0106
+#define _MIPC_SYS_SET16   0x0107
+#define _MIPC_SYS_SET8    0x0108
+#define _MIPC_SYS_CLEAR32 0x0109
+#define _MIPC_SYS_CLEAR16 0x010a
+#define _MIPC_SYS_CLEAR8  0x010b
+#define _MIPC_SYS_MASK32  0x010c
+#define _MIPC_SYS_MASK16  0x010d
+#define _MIPC_SYS_MASK8   0x010e
+
+#define _MIPC_NAND_RESET  0x0000
+#define _MIPC_NAND_GETID  0x0001
+#define _MIPC_NAND_READ   0x0002
+#define _MIPC_NAND_WRITE  0x0003
+#define _MIPC_NAND_ERASE  0x0004
+#define _MIPC_NAND_STATUS 0x0005
+
+#define _MIPC_SDHC_DISCOVER 0x0000
+#define _MIPC_SDHC_EXIT	    0x0001
+
+#define _MIPC_SDMMC_ACK   0x0000
+#define _MIPC_SDMMC_READ  0x0001
+#define _MIPC_SDMMC_WRITE 0x0002
+#define _MIPC_SDMMC_STATE 0x0003
+#define _MIPC_SDMMC_SIZE  0x0004
+
+#define _MIPC_KEYS_GETOTP 0x0000
+#define _MIPC_KEYS_GETEEP 0x0001
+
+#define _MIPC_AES_RESET   0x0000
+#define _MIPC_AES_SETIV   0x0001
+#define _MIPC_AES_SETKEY  0x0002
+#define _MIPC_AES_DECRYPT 0x0003
+
+#define _MIPC_BOOT2_RUN   0x0000
+#define _MIPC_BOOT2_TMD   0x0001
+
+#define _MIPC_PPC_BOOT    0x0000
+
+
+/*
+ *
+ */
+
+#define _MIPC_MODEBITS	8
+#define _MIPC_DEVBITS	8
+#define _MIPC_NRBITS	16
+
+#define _MIPC_MODEMASK	((1 << _MIPC_MODEBITS)-1)
+#define _MIPC_DEVMASK	((1 << _MIPC_DEVBITS)-1)
+#define _MIPC_NRMASK	((1 << _MIPC_NRBITS)-1)
+
+#define _MIPC_MODESHIFT	(_MIPC_DEVSHIFT + _MIPC_DEVBITS)
+#define _MIPC_DEVSHIFT	(_MIPC_NRSHIFT + _MIPC_NRBITS)
+#define _MIPC_NRSHIFT	0
+
+#define _MIPC(mode, dev, nr) \
+	(((mode) << _MIPC_MODESHIFT) | \
+	 ((dev) << _MIPC_DEVSHIFT) | \
+	 ((nr) << _MIPC_NRSHIFT))
+
+#define _MIPC_FAST_SYS(nr)	_MIPC(_MIPC_FAST, _MIPC_DEV_SYS, nr)
+
+#define MIPC_SYS_PING		_MIPC_FAST_SYS(_MIPC_SYS_PING)
+#define MIPC_SYS_WRITE32	_MIPC_FAST_SYS(_MIPC_SYS_WRITE32)
+#define MIPC_SYS_WRITE16	_MIPC_FAST_SYS(_MIPC_SYS_WRITE16)
+#define MIPC_SYS_WRITE8		_MIPC_FAST_SYS(_MIPC_SYS_WRITE8)
+#define MIPC_SYS_READ32		_MIPC_FAST_SYS(_MIPC_SYS_READ32)
+#define MIPC_SYS_READ16		_MIPC_FAST_SYS(_MIPC_SYS_READ16)
+#define MIPC_SYS_READ8		_MIPC_FAST_SYS(_MIPC_SYS_READ8)
+#define MIPC_SYS_SET32		_MIPC_FAST_SYS(_MIPC_SYS_SET32)
+#define MIPC_SYS_SET16		_MIPC_FAST_SYS(_MIPC_SYS_SET16)
+#define MIPC_SYS_SET8		_MIPC_FAST_SYS(_MIPC_SYS_SET8)
+#define MIPC_SYS_CLEAR32	_MIPC_FAST_SYS(_MIPC_SYS_CLEAR32)
+#define MIPC_SYS_CLEAR16	_MIPC_FAST_SYS(_MIPC_SYS_CLEAR16)
+#define MIPC_SYS_CLEAR8		_MIPC_FAST_SYS(_MIPC_SYS_CLEAR8)
+#define MIPC_SYS_MASK32		_MIPC_FAST_SYS(_MIPC_SYS_MASK32)
+#define MIPC_SYS_MASK16		_MIPC_FAST_SYS(_MIPC_SYS_MASK16)
+#define MIPC_SYS_MASK8		_MIPC_FAST_SYS(_MIPC_SYS_MASK8)
+
+#define MIPC_REQ_MAX_ARGS	6
+
+struct mipc_infohdr {
+	char magic[3];
+	u8 version;
+	phys_addr_t mem2_boundary;
+	phys_addr_t ipc_in;
+	size_t ipc_in_size;
+	phys_addr_t ipc_out;
+	size_t ipc_out_size;
+};
+
+struct mipc_device;
+struct mipc_req;
+
+extern int mipc_discover(struct mipc_infohdr **hdrp);
+
+extern u32 mipc_in_be32(const volatile u32 __iomem *addr);
+extern u16 mipc_in_be16(const volatile u16 __iomem *addr);
+extern u8 mipc_in_8(const volatile u8 __iomem *addr);
+
+extern void mipc_out_be32(const volatile u32 __iomem *addr, u32 val);
+extern void mipc_out_be16(const volatile u16 __iomem *addr, u16 val);
+extern void mipc_out_8(const volatile u8 __iomem *addr, u8 val);
+
+extern void mipc_clear_bit(int nr, volatile unsigned long *addr);
+extern void mipc_set_bit(int nr, volatile unsigned long *addr);
+extern void mipc_clrsetbits_be32(const volatile u32 __iomem *addr,
+				 u32 clear, u32 set);
+
+extern void mipc_wmb(void);
+
+extern void __iomem *mipc_ioremap(phys_addr_t addr, unsigned long size);
+extern void mipc_iounmap(volatile void __iomem *addr);
+
+#else
+
+struct mipc_infohdr;
+
+static inline int mipc_discover(struct mipc_infohdr **hdrp)
+{
+	return -ENODEV;
+}
+
+#endif /* CONFIG_STARLET_MINI */
+
+#endif /* __STARLET_MINI_H */
+
diff --git a/arch/powerpc/include/asm/starlet.h b/arch/powerpc/include/asm/starlet.h
new file mode 100644
index 0000000..1e7e9f6
--- /dev/null
+++ b/arch/powerpc/include/asm/starlet.h
@@ -0,0 +1,27 @@
+/*
+ * arch/powerpc/include/asm/starlet.h
+ *
+ * Definitions for the Starlet co-processor
+ * Copyright (C) 2009 The GameCube Linux Team
+ * Copyright (C) 2009 Albert Herranz
+ *
+ * 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.
+ *
+ */
+
+#ifndef __STARLET_H
+#define __STARLET_H
+
+
+enum starlet_ipc_flavour {
+	STARLET_IPC_IOS,
+	STARLET_IPC_MINI,
+};
+
+int starlet_discover_ipc_flavour(void);
+enum starlet_ipc_flavour starlet_get_ipc_flavour(void);
+
+#endif /* __STARLET_H */
diff --git a/arch/powerpc/include/asm/udbg.h b/arch/powerpc/include/asm/udbg.h
index cd21e5e..11ae699 100644
--- a/arch/powerpc/include/asm/udbg.h
+++ b/arch/powerpc/include/asm/udbg.h
@@ -51,6 +51,7 @@ extern void __init udbg_init_btext(void);
 extern void __init udbg_init_44x_as1(void);
 extern void __init udbg_init_40x_realmode(void);
 extern void __init udbg_init_cpm(void);
+extern void __init udbg_init_usbgecko(void);
 
 #endif /* __KERNEL__ */
 #endif /* _ASM_POWERPC_UDBG_H */
diff --git a/arch/powerpc/kernel/cputable.c b/arch/powerpc/kernel/cputable.c
index 03c862b..85841e8 100644
--- a/arch/powerpc/kernel/cputable.c
+++ b/arch/powerpc/kernel/cputable.c
@@ -697,13 +697,13 @@ static struct cpu_spec __initdata cpu_specs[] = {
 		.machine_check		= machine_check_generic,
 		.platform		= "ppc750",
 	},
-	{	/* 750CL */
-		.pvr_mask		= 0xfffff0f0,
-		.pvr_value		= 0x00087010,
-		.cpu_name		= "750CL",
-		.cpu_features		= CPU_FTRS_750CL,
+	{	/* 745/755 */
+		.pvr_mask		= 0xfffff000,
+		.pvr_value		= 0x00083000,
+		.cpu_name		= "745/755",
+		.cpu_features		= CPU_FTRS_750,
 		.cpu_user_features	= COMMON_USER | PPC_FEATURE_PPC_LE,
-		.mmu_features		= MMU_FTR_HPTE_TABLE | MMU_FTR_USE_HIGH_BATS,
+		.mmu_features		= MMU_FTR_HPTE_TABLE,
 		.icache_bsize		= 32,
 		.dcache_bsize		= 32,
 		.num_pmcs		= 4,
@@ -711,16 +711,14 @@ static struct cpu_spec __initdata cpu_specs[] = {
 		.cpu_setup		= __setup_cpu_750,
 		.machine_check		= machine_check_generic,
 		.platform		= "ppc750",
-		.oprofile_cpu_type      = "ppc/750",
-		.oprofile_type		= PPC_OPROFILE_G4,
 	},
-	{	/* 745/755 */
-		.pvr_mask		= 0xfffff000,
-		.pvr_value		= 0x00083000,
-		.cpu_name		= "745/755",
-		.cpu_features		= CPU_FTRS_750,
+	{	/* 750CL (and "Broadway") */
+		.pvr_mask		= 0xfffff0e0,
+		.pvr_value		= 0x00087000,
+		.cpu_name		= "750CL",
+		.cpu_features		= CPU_FTRS_750CL,
 		.cpu_user_features	= COMMON_USER | PPC_FEATURE_PPC_LE,
-		.mmu_features		= MMU_FTR_HPTE_TABLE,
+		.mmu_features		= MMU_FTR_HPTE_TABLE | MMU_FTR_USE_HIGH_BATS,
 		.icache_bsize		= 32,
 		.dcache_bsize		= 32,
 		.num_pmcs		= 4,
@@ -728,6 +726,8 @@ static struct cpu_spec __initdata cpu_specs[] = {
 		.cpu_setup		= __setup_cpu_750,
 		.machine_check		= machine_check_generic,
 		.platform		= "ppc750",
+		.oprofile_cpu_type      = "ppc/750",
+		.oprofile_type		= PPC_OPROFILE_G4,
 	},
 	{	/* 750FX rev 1.x */
 		.pvr_mask		= 0xffffff00,
diff --git a/arch/powerpc/kernel/dma.c b/arch/powerpc/kernel/dma.c
index 6215062..83d5232 100644
--- a/arch/powerpc/kernel/dma.c
+++ b/arch/powerpc/kernel/dma.c
@@ -27,6 +27,9 @@ void *dma_direct_alloc_coherent(struct device *dev, size_t size,
 {
 	void *ret;
 #ifdef CONFIG_NOT_COHERENT_CACHE
+	if (dma_alloc_from_coherent(dev, size, dma_handle, &ret))
+		return ret;
+
 	ret = __dma_alloc_coherent(dev, size, dma_handle, flag);
 	if (ret == NULL)
 		return NULL;
@@ -54,6 +57,8 @@ void dma_direct_free_coherent(struct device *dev, size_t size,
 			      void *vaddr, dma_addr_t dma_handle)
 {
 #ifdef CONFIG_NOT_COHERENT_CACHE
+	if (dma_release_from_coherent(dev, get_order(size), vaddr))
+		return;
 	__dma_free_coherent(size, vaddr);
 #else
 	free_pages((unsigned long)vaddr, get_order(size));
diff --git a/arch/powerpc/kernel/head_32.S b/arch/powerpc/kernel/head_32.S
index 829c3fe..83a0b79 100644
--- a/arch/powerpc/kernel/head_32.S
+++ b/arch/powerpc/kernel/head_32.S
@@ -164,6 +164,9 @@ __after_mmu_off:
 #ifdef CONFIG_PPC_EARLY_DEBUG_CPM
 	bl	setup_cpm_bat
 #endif
+#ifdef CONFIG_PPC_EARLY_DEBUG_USBGECKO
+	bl	setup_usbgecko_bat
+#endif
 
 /*
  * Call setup_cpu for CPU 0 and initialize 6xx Idle
@@ -1203,6 +1206,24 @@ setup_cpm_bat:
 	blr
 #endif
 
+#ifdef CONFIG_PPC_EARLY_DEBUG_USBGECKO
+setup_usbgecko_bat:
+	/* prepare a BAT for early io */
+	lis	r8, 0x0c00
+	ori	r8, r8, 0x002a	/* uncached, guarded ,rw */
+	lis	r11, 0xcc00
+	ori	r11, r11, 0x3	/* 128K */
+#ifdef CONFIG_WII
+	oris	r8, r8, 0x0100
+	oris	r11, r11, 0x0100
+#endif
+	mtspr	SPRN_DBAT1L, r8
+	mtspr	SPRN_DBAT1U, r11
+	sync
+	isync
+	blr
+#endif
+
 #ifdef CONFIG_8260
 /* Jump into the system reset for the rom.
  * We first disable the MMU, and then jump to the ROM reset address.
diff --git a/arch/powerpc/kernel/udbg.c b/arch/powerpc/kernel/udbg.c
index fc9af47..e39cad8 100644
--- a/arch/powerpc/kernel/udbg.c
+++ b/arch/powerpc/kernel/udbg.c
@@ -60,6 +60,8 @@ void __init udbg_early_init(void)
 	udbg_init_40x_realmode();
 #elif defined(CONFIG_PPC_EARLY_DEBUG_CPM)
 	udbg_init_cpm();
+#elif defined(CONFIG_PPC_EARLY_DEBUG_USBGECKO)
+	udbg_init_usbgecko();
 #endif
 
 #ifdef CONFIG_PPC_EARLY_DEBUG
diff --git a/arch/powerpc/mm/pgtable_32.c b/arch/powerpc/mm/pgtable_32.c
index cb96cb2..dea5930 100644
--- a/arch/powerpc/mm/pgtable_32.c
+++ b/arch/powerpc/mm/pgtable_32.c
@@ -192,9 +192,19 @@ __ioremap_caller(phys_addr_t addr, unsigned long size, unsigned long flags,
 	 * mem_init() sets high_memory so only do the check after that.
 	 */
 	if (mem_init_done && (p < virt_to_phys(high_memory))) {
-		printk("__ioremap(): phys addr 0x%llx is RAM lr %p\n",
-		       (unsigned long long)p, __builtin_return_address(0));
-		return NULL;
+		/*
+		 * On some systems, though, we may want to remap normal RAM
+		 * that we have memreserve'd at the device tree.
+		 * But we can't do that safely if we are using BATs.
+		 *
+		 */
+		if (!__map_without_bats) {
+			printk(KERN_WARNING
+			       "__ioremap(): phys addr 0x%llx is RAM lr %p\n",
+			       (unsigned long long)p,
+				 __builtin_return_address(0));
+			return NULL;
+		}
 	}
 #endif
 
diff --git a/arch/powerpc/platforms/Kconfig.cputype b/arch/powerpc/platforms/Kconfig.cputype
index e382cae..80d934b 100644
--- a/arch/powerpc/platforms/Kconfig.cputype
+++ b/arch/powerpc/platforms/Kconfig.cputype
@@ -312,7 +312,7 @@ config NR_CPUS
 
 config NOT_COHERENT_CACHE
 	bool
-	depends on 4xx || 8xx || E200 || PPC_MPC512x
+	depends on 4xx || 8xx || E200 || PPC_MPC512x || GAMECUBE_COMMON
 	default y
 
 config CHECK_CACHE_COHERENCY
diff --git a/arch/powerpc/platforms/embedded6xx/Kconfig b/arch/powerpc/platforms/embedded6xx/Kconfig
index 291ac9d..d9fce8f 100644
--- a/arch/powerpc/platforms/embedded6xx/Kconfig
+++ b/arch/powerpc/platforms/embedded6xx/Kconfig
@@ -90,3 +90,111 @@ config MPC10X_OPENPIC
 config MPC10X_STORE_GATHERING
 	bool "Enable MPC10x store gathering"
 	depends on MPC10X_BRIDGE
+
+config GAMECUBE
+	bool "Nintendo-GameCube"
+	depends on EMBEDDED6xx
+	select GAMECUBE_COMMON
+	help
+	  Select GAMECUBE if configuring for the Nintendo GameCube.
+	  More information at: <http://gc-linux.sourceforge.net/>
+
+config WII
+	bool "Nintendo-Wii"
+	depends on EMBEDDED6xx
+	select GAMECUBE_COMMON
+	select PPC_LIB_RHEAP if STARLET_IOS
+	select USB_ARCH_HAS_EHCI if STARLET_MINI
+	select USB_ARCH_HAS_OHCI if STARLET_MINI
+	select HAVE_GENERIC_DMA_COHERENT
+	help
+	  Select WII if configuring for the Nintendo Wii.
+	  More information at: <http://gc-linux.sourceforge.net/>
+
+config STARLET_IOS
+	bool "Nintendo Wii Starlet IOS support"
+	depends on WII
+	default y
+
+config STARLET_MINI
+	bool "Team Twiizers 'mini' firmware support"
+	depends on WII && EXPERIMENTAL
+	default y
+
+config FLIPPER_PIC
+	bool
+	depends on GAMECUBE_COMMON
+	default y
+
+config HLWD_PIC
+	bool
+	depends on STARLET_MINI
+	default y
+
+config GAMECUBE_COMMON
+	bool
+	select NOT_COHERENT_CACHE
+	select FLIPPER_PIC
+	default n
+
+config GAMECUBE_UDBG
+	bool "Nintendo GameCube/Wii udbg support"
+	depends on GAMECUBE_COMMON
+	default n
+	help
+	  If you say yes to this option, you will be able to choose between
+	  several udbg drivers available for the Nintendo GameCube/Wii.
+
+	  If in doubt, say N here.
+
+choice
+	prompt "Nintendo GameCube/Wii udbg drivers"
+	depends on GAMECUBE_UDBG
+
+config USBGECKO_UDBG
+	bool "USB Gecko udbg console for the Nintendo GameCube/Wii"
+	help
+	  If you say yes to this option, support will be included for the
+	  USB Gecko adapter as an udbg console.
+	  The USB Gecko is a EXI to USB Serial converter that can be plugged
+	  into a memcard slot in the Nintendo GameCube/Wii.
+
+	  This driver bypasses the EXI layer completely.
+
+	  If in doubt, say N here.
+
+config GAMECUBE_VIDEO_UDBG
+	bool "Nintendo GameCube/Wii framebuffer udbg console"
+	select FONTS
+	select FONT_8x16
+	help
+	  If you say yes to this option, support will be included for a
+	  framebuffer based udbg console for the Nintendo GameCube/Wii.
+
+	  If in doubt, say N here.
+
+endchoice
+
+config GAMECUBE_RSW
+	bool "Nintendo GameCube/Wii reset switch/button"
+	depends on GAMECUBE_COMMON
+	default y
+	help
+	  If you say yes to this option, support will be included for the
+	  reset switch/button of the Nintendo GameCube/Wii.
+
+	  If in doubt, say Y here.
+
+config HLWD_GPIO
+	bool "Nintendo Wii (Hollywood) GPIO support"
+	depends on GPIOLIB && WII
+	default y
+	help
+	  If you say yes to this option, support will be included for the
+	  GPIOs provided by the Hollywood chipset of the Nintendo Wii
+	  video game console.
+	  Those GPIO lines control, for example, the sensor bar IR leds,
+	  the front led, or the eject switch of the disk unit.
+
+	  If in doubt, say Y here.
+
diff --git a/arch/powerpc/platforms/embedded6xx/Makefile b/arch/powerpc/platforms/embedded6xx/Makefile
index 0773c08..19c14fa 100644
--- a/arch/powerpc/platforms/embedded6xx/Makefile
+++ b/arch/powerpc/platforms/embedded6xx/Makefile
@@ -7,3 +7,14 @@ obj-$(CONFIG_STORCENTER)	+= storcenter.o
 obj-$(CONFIG_PPC_HOLLY)		+= holly.o
 obj-$(CONFIG_PPC_PRPMC2800)	+= prpmc2800.o
 obj-$(CONFIG_PPC_C2K)		+= c2k.o
+obj-$(CONFIG_GAMECUBE)		+= gamecube.o gamecube_dev.o
+obj-$(CONFIG_WII)		+= wii.o wii_dev.o
+obj-$(CONFIG_HLWD_GPIO)		+= hlwd-gpio.o
+obj-$(CONFIG_STARLET_MINI)	+= starlet-mipc.o
+obj-$(CONFIG_STARLET_IOS)	+= starlet-ipc.o starlet-malloc.o \
+					starlet-stm.o starlet-es.o
+obj-$(CONFIG_FLIPPER_PIC)	+= flipper-pic.o
+obj-$(CONFIG_HLWD_PIC)		+= hlwd-pic.o
+obj-$(CONFIG_USBGECKO_UDBG)	+= usbgecko_udbg.o
+obj-$(CONFIG_GAMECUBE_VIDEO_UDBG)	+= gcnvi_udbg.o
+obj-$(CONFIG_GAMECUBE_RSW)	+= gcn-rsw.o
diff --git a/arch/powerpc/platforms/embedded6xx/flipper-pic.c b/arch/powerpc/platforms/embedded6xx/flipper-pic.c
new file mode 100644
index 0000000..8239d5e
--- /dev/null
+++ b/arch/powerpc/platforms/embedded6xx/flipper-pic.c
@@ -0,0 +1,229 @@
+/*
+ * arch/powerpc/platforms/embedded6xx/flipper-pic.c
+ *
+ * Nintendo GameCube/Wii interrupt controller support.
+ * Copyright (C) 2004-2009 The GameCube Linux Team
+ * Copyright (C) 2007,2008,2009 Albert Herranz
+ *
+ * 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.
+ *
+ */
+#define DRV_MODULE_NAME "flipper-pic"
+#define pr_fmt(fmt) DRV_MODULE_NAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/irq.h>
+#include <linux/of.h>
+#include <asm/io.h>
+
+#include "flipper-pic.h"
+
+/*
+ * IRQ chip hooks.
+ *
+ */
+
+static void flipper_pic_mask_and_ack(unsigned int virq)
+{
+	int irq = virq_to_hw(virq);
+	void __iomem *io_base = get_irq_chip_data(virq);
+
+	clear_bit(irq, io_base + FLIPPER_IMR);
+	set_bit(irq, io_base + FLIPPER_ICR);
+}
+
+static void flipper_pic_ack(unsigned int virq)
+{
+	int irq = virq_to_hw(virq);
+	void __iomem *io_base = get_irq_chip_data(virq);
+
+	set_bit(irq, io_base + FLIPPER_ICR);
+}
+
+static void flipper_pic_mask(unsigned int virq)
+{
+	int irq = virq_to_hw(virq);
+	void __iomem *io_base = get_irq_chip_data(virq);
+
+	clear_bit(irq, io_base + FLIPPER_IMR);
+}
+
+static void flipper_pic_unmask(unsigned int virq)
+{
+	int irq = virq_to_hw(virq);
+	void __iomem *io_base = get_irq_chip_data(virq);
+
+	set_bit(irq, io_base + FLIPPER_IMR);
+}
+
+
+static struct irq_chip flipper_pic = {
+	.typename	= "flipper-pic",
+	.ack		= flipper_pic_ack,
+	.mask_ack	= flipper_pic_mask_and_ack,
+	.mask		= flipper_pic_mask,
+	.unmask		= flipper_pic_unmask,
+};
+
+/*
+ * IRQ host hooks.
+ *
+ */
+
+static struct irq_host *flipper_irq_host;
+
+static int flipper_pic_map(struct irq_host *h, unsigned int virq,
+			   irq_hw_number_t hwirq)
+{
+	set_irq_chip_data(virq, h->host_data);
+	get_irq_desc(virq)->status |= IRQ_LEVEL;
+	set_irq_chip_and_handler(virq, &flipper_pic, handle_level_irq);
+	return 0;
+}
+
+static void flipper_pic_unmap(struct irq_host *h, unsigned int irq)
+{
+	set_irq_chip_data(irq, NULL);
+	set_irq_chip(irq, NULL);
+}
+
+static int flipper_pic_match(struct irq_host *h, struct device_node *np)
+{
+	return 1;
+}
+
+
+static struct irq_host_ops flipper_irq_host_ops = {
+	.map = flipper_pic_map,
+	.unmap = flipper_pic_unmap,
+	.match = flipper_pic_match,
+};
+
+/*
+ * Platform hooks.
+ *
+ */
+
+static void __flipper_quiesce(void __iomem *io_base)
+{
+	/* mask and ack all IRQs */
+	out_be32(io_base + FLIPPER_IMR, 0x00000000);
+	out_be32(io_base + FLIPPER_ICR, 0xffffffff);
+}
+
+struct irq_host * __init flipper_pic_init(struct device_node *np)
+{
+	struct irq_host *irq_host;
+	struct resource res;
+	void __iomem *io_base;
+	int retval;
+
+	retval = of_address_to_resource(np, 0, &res);
+	if (retval) {
+		pr_err("no io memory range found\n");
+		return NULL;
+	}
+	io_base = ioremap(res.start, res.end - res.start + 1);
+
+	pr_info("controller at 0x%08x mapped to 0x%p\n", res.start, io_base);
+
+	__flipper_quiesce(io_base);
+
+	irq_host = irq_alloc_host(np, IRQ_HOST_MAP_LINEAR, FLIPPER_NR_IRQS,
+				  &flipper_irq_host_ops, -1);
+	if (!irq_host) {
+		pr_err("failed to allocate irq_host\n");
+		return NULL;
+	}
+
+	irq_host->host_data = io_base;
+
+	return irq_host;
+}
+
+unsigned int flipper_pic_get_irq(void)
+{
+	void __iomem *io_base = flipper_irq_host->host_data;
+	int irq;
+	u32 irq_status;
+
+	irq_status = in_be32(io_base + FLIPPER_ICR) &
+		     in_be32(io_base + FLIPPER_IMR);
+	if (irq_status == 0)
+		return -1;	/* no more IRQs pending */
+
+	__asm__ __volatile__("cntlzw %0,%1" : "=r"(irq) : "r"(irq_status));
+	return irq_linear_revmap(flipper_irq_host, 31 - irq);
+}
+
+/*
+ * Probe function.
+ *
+ */
+
+void __init flipper_pic_probe(void)
+{
+	struct device_node *np;
+
+	np = of_find_compatible_node(NULL, NULL, "nintendo,flipper-pic");
+	BUG_ON(!np);
+
+	flipper_irq_host = flipper_pic_init(np);
+	BUG_ON(!flipper_irq_host);
+
+	irq_set_default_host(flipper_irq_host);
+
+	of_node_put(np);
+}
+
+/*
+ * Misc functions related to the flipper chipset.
+ *
+ */
+
+/**
+ * flipper_quiesce() - quiesce flipper irq controller
+ *
+ * Mask and ack all interrupt sources.
+ *
+ */
+void flipper_quiesce(void)
+{
+	void __iomem *io_base = flipper_irq_host->host_data;
+
+	__flipper_quiesce(io_base);
+}
+
+/*
+ * Resets the platform.
+ */
+void flipper_platform_reset(void)
+{
+	void __iomem *io_base;
+
+	if (flipper_irq_host && flipper_irq_host->host_data) {
+		io_base = flipper_irq_host->host_data;
+		out_8(io_base + FLIPPER_RESET, 0x00);
+	}
+}
+
+/*
+ * Returns non-zero if the reset button is pressed.
+ */
+int flipper_is_reset_button_pressed(void)
+{
+	void __iomem *io_base;
+	u32 icr;
+
+	if (flipper_irq_host && flipper_irq_host->host_data) {
+		io_base = flipper_irq_host->host_data;
+		icr = in_be32(io_base + FLIPPER_ICR);
+		return !(icr & FLIPPER_ICR_RSS);
+	}
+	return 0;
+}
+
diff --git a/arch/powerpc/platforms/embedded6xx/flipper-pic.h b/arch/powerpc/platforms/embedded6xx/flipper-pic.h
new file mode 100644
index 0000000..81212ce
--- /dev/null
+++ b/arch/powerpc/platforms/embedded6xx/flipper-pic.h
@@ -0,0 +1,42 @@
+/*
+ * arch/powerpc/platforms/embedded6xx/flipper-pic.h
+ *
+ * Nintendo GameCube/Wii interrupt controller support.
+ * Copyright (C) 2004-2009 The GameCube Linux Team
+ * Copyright (C) 2007,2008,2009 Albert Herranz
+ *
+ * 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.
+ *
+ */
+
+#ifndef __FLIPPER_PIC_H
+#define __FLIPPER_PIC_H
+
+#define FLIPPER_NR_IRQS		32
+
+/*
+ * Each interrupt has a corresponding bit in both
+ * the Interrupt Cause (ICR) and Interrupt Mask (IMR) registers.
+ *
+ * Enabling/disabling an interrupt line involves asserting/clearing
+ * the corresponding bit in IMR. ACK'ing a request simply involves
+ * asserting the corresponding bit in ICR.
+ */
+#define FLIPPER_ICR		0x00
+#define FLIPPER_ICR_RSS		(1<<16) /* reset switch state */
+
+#define FLIPPER_IMR		0x04
+
+#define FLIPPER_RESET		0x24
+
+unsigned int flipper_pic_get_irq(void);
+void __init flipper_pic_probe(void);
+
+void flipper_quiesce(void);
+void flipper_platform_reset(void);
+int flipper_is_reset_button_pressed(void);
+
+#endif
diff --git a/arch/powerpc/platforms/embedded6xx/gamecube.c b/arch/powerpc/platforms/embedded6xx/gamecube.c
new file mode 100644
index 0000000..b3b4db9
--- /dev/null
+++ b/arch/powerpc/platforms/embedded6xx/gamecube.c
@@ -0,0 +1,111 @@
+/*
+ * arch/powerpc/platforms/embedded6xx/gamecube.c
+ *
+ * Nintendo GameCube board-specific support
+ * Copyright (C) 2004-2009 The GameCube Linux Team
+ * Copyright (C) 2007,2008,2009 Albert Herranz
+ *
+ * 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/kernel.h>
+#include <linux/init.h>
+#include <linux/irq.h>
+#include <linux/kexec.h>
+#include <linux/seq_file.h>
+
+#include <asm/io.h>
+#include <asm/machdep.h>
+#include <asm/prom.h>
+#include <asm/time.h>
+#include <asm/udbg.h>
+
+#include "flipper-pic.h"
+#include "usbgecko_udbg.h"
+
+
+static void gamecube_restart(char *cmd)
+{
+	local_irq_disable();
+	flipper_platform_reset();
+	/* spin until power button pressed */
+	for (;;)
+		cpu_relax();
+}
+
+static void gamecube_power_off(void)
+{
+	local_irq_disable();
+	/* spin until power button pressed */
+	for (;;)
+		cpu_relax();
+}
+
+static void gamecube_halt(void)
+{
+	gamecube_restart(NULL);
+}
+
+static void gamecube_show_cpuinfo(struct seq_file *m)
+{
+	seq_printf(m, "vendor\t\t: IBM\n");
+	seq_printf(m, "machine\t\t: Nintendo GameCube\n");
+}
+
+static void gamecube_setup_arch(void)
+{
+}
+
+static void __init gamecube_init_early(void)
+{
+	ug_udbg_init();
+}
+
+static int __init gamecube_probe(void)
+{
+	unsigned long dt_root;
+
+	dt_root = of_get_flat_dt_root();
+	if (!of_flat_dt_is_compatible(dt_root, "nintendo,gamecube"))
+		return 0;
+
+	return 1;
+}
+
+static void gamecube_shutdown(void)
+{
+	/* currently not used */
+}
+
+#ifdef CONFIG_KEXEC
+static int gamecube_kexec_prepare(struct kimage *image)
+{
+	return 0;
+}
+#endif /* CONFIG_KEXEC */
+
+
+define_machine(gamecube) {
+	.name			= "gamecube",
+	.probe			= gamecube_probe,
+	.setup_arch		= gamecube_setup_arch,
+	.init_early		= gamecube_init_early,
+	.show_cpuinfo		= gamecube_show_cpuinfo,
+	.restart		= gamecube_restart,
+	.power_off		= gamecube_power_off,
+	.halt			= gamecube_halt,
+	.init_IRQ		= flipper_pic_probe,
+	.get_irq		= flipper_pic_get_irq,
+	.calibrate_decr		= generic_calibrate_decr,
+	.progress		= udbg_progress,
+	.machine_shutdown	= gamecube_shutdown,
+#ifdef CONFIG_KEXEC
+	.machine_kexec_prepare	= gamecube_kexec_prepare,
+	.machine_kexec		= default_machine_kexec,
+#endif
+};
+
diff --git a/arch/powerpc/platforms/embedded6xx/gamecube_dev.c b/arch/powerpc/platforms/embedded6xx/gamecube_dev.c
new file mode 100644
index 0000000..13e1f73
--- /dev/null
+++ b/arch/powerpc/platforms/embedded6xx/gamecube_dev.c
@@ -0,0 +1,34 @@
+/*
+ * arch/powerpc/platforms/embedded6xx/gamecube_dev.c
+ *
+ * Nintendo GameCube platform device setup.
+ * Copyright (C) 2008-2009 The GameCube Linux Team
+ * Copyright (C) 2008,2009 Albert Herranz
+ *
+ * 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/kernel.h>
+#include <linux/init.h>
+#include <linux/of_platform.h>
+
+#include <asm/machdep.h>
+
+static struct of_device_id gamecube_of_bus[] = {
+	{ .compatible = "nintendo,flipper", },
+	{ },
+};
+
+static int __init gamecube_device_probe(void)
+{
+	if (!machine_is(gamecube))
+		return 0;
+
+	of_platform_bus_probe(NULL, gamecube_of_bus, NULL);
+	return 0;
+}
+device_initcall(gamecube_device_probe);
diff --git a/arch/powerpc/platforms/embedded6xx/gcn-rsw.c b/arch/powerpc/platforms/embedded6xx/gcn-rsw.c
new file mode 100644
index 0000000..0bb20f0
--- /dev/null
+++ b/arch/powerpc/platforms/embedded6xx/gcn-rsw.c
@@ -0,0 +1,308 @@
+/*
+ * arch/powerpc/platforms/embedded6xx/gcn-rsw.c
+ *
+ * Nintendo GameCube/Wii reset switch (RSW) driver.
+ * Copyright (C) 2004-2009 The GameCube Linux Team
+ * Copyright (C) 2004 Stefan Esser
+ * Copyright (C) 2004,2005,2008,2009 Albert Herranz
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/of_platform.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include <linux/reboot.h>
+#include <linux/kexec.h>
+
+/* for flipper hardware registers */
+#include "flipper-pic.h"
+
+#define DRV_MODULE_NAME      "gcn-rsw"
+#define DRV_DESCRIPTION      "Nintendo GameCube/Wii Reset SWitch (RSW) driver"
+#define DRV_AUTHOR           "Stefan Esser <se@nopiracy.de>, " \
+			     "Albert Herranz"
+
+static char rsw_driver_version[] = "1.0i";
+
+#define drv_printk(level, format, arg...) \
+	printk(level DRV_MODULE_NAME ": " format , ## arg)
+
+
+#define RSW_NORMAL_TIMEOUT      3	/* seconds */
+#define RSW_EMERGENCY_PUSHES   10
+
+enum rsw_state {
+	IDLE = 0,		/* nothing to do */
+	NORMAL_RESET,		/* reboot requested */
+	EMERGENCY_RESET,	/* try emergency reboot */
+};
+
+struct rsw_drvdata {
+	enum rsw_state state;
+	struct timer_list timer;
+	unsigned long jiffies;
+	int pushes;
+	int timeout;
+	spinlock_t lock;
+
+	void __iomem *io_base;
+	unsigned int irq;
+
+	struct device *dev;
+};
+
+
+/*
+ * Tells if the reset button is pressed.
+ */
+static int rsw_is_button_pressed(void __iomem *io_base)
+{
+	u32 icr = in_be32(io_base + FLIPPER_ICR);
+
+	drv_printk(KERN_INFO, "%x\n", icr);
+	return !(icr & FLIPPER_ICR_RSS);
+}
+
+/*
+ * Invokes a normal system restart.
+ */
+static void rsw_normal_restart(unsigned long dummy)
+{
+	ctrl_alt_del();
+}
+
+/*
+ * Performs a low level system restart.
+ */
+static void rsw_emergency_restart(void)
+{
+#ifdef CONFIG_KEXEC
+	struct kimage *image;
+	image = xchg(&kexec_image, 0);
+	if (image)
+		machine_kexec(image);
+#endif
+	machine_restart(NULL);
+}
+
+/*
+ * Handles the interrupt associated to the reset button.
+ */
+static irqreturn_t rsw_handler(int irq, void *data)
+{
+	struct rsw_drvdata *drvdata = (struct rsw_drvdata *)data;
+	unsigned long flags;
+
+	if (!rsw_is_button_pressed(drvdata->io_base)) {
+		/* nothing to do */
+		return IRQ_HANDLED;
+	}
+
+	spin_lock_irqsave(&drvdata->lock, flags);
+
+	/* someone pushed the reset button */
+	switch (drvdata->state) {
+	case IDLE:
+		drvdata->state = NORMAL_RESET;
+		printk(KERN_EMERG "Rebooting in %d seconds...\n",
+		       drvdata->timeout);
+		printk(KERN_WARNING
+		       "Push the Reset button again to cancel reboot!\n");
+
+		/* schedule a reboot in a few seconds */
+		init_timer(&drvdata->timer);
+		drvdata->timer.expires = jiffies + drvdata->timeout * HZ;
+		drvdata->timer.function =
+		    (void (*)(unsigned long))rsw_normal_restart;
+		add_timer(&drvdata->timer);
+		drvdata->jiffies = jiffies;
+		break;
+	case NORMAL_RESET:
+		if (time_before(jiffies,
+				drvdata->jiffies + drvdata->timeout * HZ)) {
+			/* the reset button was hit again before deadline */
+			del_timer(&drvdata->timer);
+			drvdata->state = IDLE;
+			printk(KERN_EMERG "Reboot cancelled!\n");
+		} else {
+			/*
+			 * Time expired. System should be now restarting.
+			 * Go to emergency mode in case something goes bad.
+			 */
+			drvdata->state = EMERGENCY_RESET;
+			drvdata->pushes = 0;
+			printk(KERN_WARNING
+			       "SWITCHED TO EMERGENCY RESET MODE!\n"
+			       "Push %d times the Reset button to force"
+			       " a hard reset!\n"
+			       "NOTE THAT THIS COULD CAUSE DATA LOSS!\n",
+			       RSW_EMERGENCY_PUSHES);
+		}
+		break;
+	case EMERGENCY_RESET:
+		/* force a hard reset if the user insists ... */
+		if (++drvdata->pushes >= RSW_EMERGENCY_PUSHES) {
+			spin_unlock_irqrestore(&drvdata->lock, flags);
+			rsw_emergency_restart();
+			return IRQ_HANDLED;
+		} else {
+			printk(KERN_INFO "%d/%d\n", drvdata->pushes,
+			       RSW_EMERGENCY_PUSHES);
+		}
+		break;
+	}
+
+	spin_unlock_irqrestore(&drvdata->lock, flags);
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * Setup routines.
+ *
+ */
+
+static int rsw_init(struct rsw_drvdata *drvdata, struct resource *mem, int irq)
+{
+	int retval;
+
+	drvdata->io_base = ioremap(mem->start, mem->end - mem->start + 1);
+	drvdata->irq = irq;
+
+	spin_lock_init(&drvdata->lock);
+	drvdata->state = IDLE;
+	drvdata->timeout = RSW_NORMAL_TIMEOUT;
+
+	retval = request_irq(drvdata->irq, rsw_handler, 0,
+			     DRV_MODULE_NAME, drvdata);
+	if (retval) {
+		drv_printk(KERN_ERR, "request of IRQ %d failed\n",
+			   drvdata->irq);
+	}
+	return retval;
+}
+
+static void rsw_exit(struct rsw_drvdata *drvdata)
+{
+	free_irq(drvdata->irq, drvdata);
+	if (drvdata->io_base) {
+		iounmap(drvdata->io_base);
+		drvdata->io_base = NULL;
+	}
+}
+
+/*
+ * Driver model helper routines.
+ *
+ */
+
+static int rsw_do_probe(struct device *dev, struct resource *mem, int irq)
+{
+	struct rsw_drvdata *drvdata;
+	int retval;
+
+	drvdata = kzalloc(sizeof(*drvdata), GFP_KERNEL);
+	if (!drvdata) {
+		drv_printk(KERN_ERR, "failed to allocate rsw_drvdata\n");
+		return -ENOMEM;
+	}
+	dev_set_drvdata(dev, drvdata);
+	drvdata->dev = dev;
+
+	retval = rsw_init(drvdata, mem, irq);
+	if (retval) {
+		dev_set_drvdata(dev, NULL);
+		kfree(drvdata);
+	}
+	return retval;
+}
+
+static int rsw_do_remove(struct device *dev)
+{
+	struct rsw_drvdata *drvdata = dev_get_drvdata(dev);
+
+	if (drvdata) {
+		rsw_exit(drvdata);
+		dev_set_drvdata(dev, NULL);
+		kfree(drvdata);
+		return 0;
+	}
+	return -ENODEV;
+}
+
+/*
+ * OF platform driver hooks.
+ *
+ */
+
+static int __init rsw_of_probe(struct of_device *odev,
+			       const struct of_device_id *match)
+{
+	struct resource mem;
+	int retval;
+
+	retval = of_address_to_resource(odev->node, 0, &mem);
+	if (retval) {
+		drv_printk(KERN_ERR, "no io memory range found\n");
+		return -ENODEV;
+	}
+
+	return rsw_do_probe(&odev->dev,
+			    &mem, irq_of_parse_and_map(odev->node, 0));
+}
+
+static int __exit rsw_of_remove(struct of_device *odev)
+{
+	return rsw_do_remove(&odev->dev);
+}
+
+static struct of_device_id rsw_of_match[] = {
+	{.compatible = "nintendo,flipper-resetswitch"},
+	{.compatible = "nintendo,hollywood-resetswitch"},
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, rsw_of_match);
+
+static struct of_platform_driver rsw_of_driver = {
+	.owner = THIS_MODULE,
+	.name = DRV_MODULE_NAME,
+	.match_table = rsw_of_match,
+	.probe = rsw_of_probe,
+	.remove = rsw_of_remove,
+};
+
+/*
+ * Kernel module hooks.
+ *
+ */
+
+static int __init rsw_init_module(void)
+{
+	drv_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION,
+		   rsw_driver_version);
+
+	return of_register_platform_driver(&rsw_of_driver);
+}
+
+static void __exit rsw_exit_module(void)
+{
+	of_unregister_platform_driver(&rsw_of_driver);
+}
+
+module_init(rsw_init_module);
+module_exit(rsw_exit_module);
+
+MODULE_DESCRIPTION(DRV_DESCRIPTION);
+MODULE_AUTHOR(DRV_AUTHOR);
+MODULE_LICENSE("GPL");
diff --git a/arch/powerpc/platforms/embedded6xx/gcnvi_udbg.c b/arch/powerpc/platforms/embedded6xx/gcnvi_udbg.c
new file mode 100644
index 0000000..6b63e48
--- /dev/null
+++ b/arch/powerpc/platforms/embedded6xx/gcnvi_udbg.c
@@ -0,0 +1,315 @@
+/*
+ * arch/powerpc/platforms/embedded6xx/gcnvi_udbg.c
+ *
+ * Nintendo GameCube/Wii framebuffer udbg output support.
+ * Copyright (C) 2008-2009 The GameCube Linux Team
+ * Copyright (C) 2008,2009 Albert Herranz
+ *
+ * Based on arch/ppc/platforms/gcn-con.c
+ *
+ * Nintendo GameCube early debug console
+ * Copyright (C) 2004-2005 The GameCube Linux Team
+ *
+ * Based on console.c by tmbinc.
+ *
+ * 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/io.h>
+#include <linux/string.h>
+#include <linux/console.h>
+#include <linux/font.h>
+#include <asm/prom.h>
+#include <asm/udbg.h>
+#include <mm/mmu_decl.h>
+
+#include "gcnvi_udbg.h"
+
+/*
+ * Console settings.
+ *
+ */
+#define SCREEN_WIDTH	640
+#define SCREEN_HEIGHT	480
+
+#define FONT_XSIZE  8
+#define FONT_YSIZE  16
+#define FONT_XFACTOR 1
+#define FONT_YFACTOR 1
+#define FONT_XGAP   2
+#define FONT_YGAP   0
+
+#define COLOR_WHITE 0xFF80FF80
+#define COLOR_BLACK 0x00800080
+
+struct console_data {
+	unsigned char *framebuffer;
+	int xres, yres, stride;
+
+	const unsigned char *font;
+
+	int cursor_x, cursor_y;
+	int foreground, background;
+
+	int border_left, border_right, border_top, border_bottom;
+
+	int scrolled_lines;
+};
+
+static struct console_data *default_console;
+
+#if 0
+static int console_set_color(int background, int foreground)
+{
+	default_console->foreground = foreground;
+	default_console->background = background;
+	return 0;
+}
+#endif
+
+static void console_drawc(struct console_data *con, int x, int y,
+			  unsigned char c)
+{
+	int ax, ay;
+	unsigned long *ptr;
+	unsigned long color, color2x[2];
+	int bits;
+
+	x >>= 1;
+	ptr = (unsigned long *)(con->framebuffer + con->stride * y + x * 4);
+
+	for (ay = 0; ay < FONT_YSIZE; ay++) {
+#if FONT_XFACTOR == 2
+		for (ax = 0; ax < 8; ax++) {
+			if ((con->font[c * FONT_YSIZE + ay] << ax) & 0x80)
+				color = con->foreground;
+			else
+				color = con->background;
+#if FONT_YFACTOR == 2
+			/* pixel doubling: we write u32 */
+			ptr[ay * 2 * con->stride / 4 + ax] = color;
+			/* line doubling */
+			ptr[(ay * 2 + 1) * con->stride / 4 + ax] = color;
+#else
+			ptr[ay * con->stride / 4 + ax] = color;
+#endif
+		}
+#else
+		for (ax = 0; ax < 4; ax++) {
+			bits = (con->font[c * FONT_YSIZE + ay] << (ax * 2));
+			if (bits & 0x80)
+				color2x[0] = con->foreground;
+			else
+				color2x[0] = con->background;
+			if (bits & 0x40)
+				color2x[1] = con->foreground;
+			else
+				color2x[1] = con->background;
+			ptr[ay * con->stride / 4 + ax] =
+			    (color2x[0] & 0xFFFF00FF) |
+			    (color2x[1] & 0x0000FF00);
+		}
+#endif
+	}
+}
+
+static void console_putc(struct console_data *con, char c)
+{
+	int cnt;
+	unsigned long *ptr;
+
+	switch (c) {
+	case '\n':
+		con->cursor_y += FONT_YSIZE * FONT_YFACTOR + FONT_YGAP;
+		con->cursor_x = con->border_left;
+		break;
+	default:
+		console_drawc(con, con->cursor_x, con->cursor_y, c);
+		con->cursor_x += FONT_XSIZE * FONT_XFACTOR + FONT_XGAP;
+		if ((con->cursor_x + (FONT_XSIZE * FONT_XFACTOR)) >
+		    con->border_right) {
+			con->cursor_y += FONT_YSIZE * FONT_YFACTOR + FONT_YGAP;
+			con->cursor_x = con->border_left;
+		}
+	}
+	if ((con->cursor_y + FONT_YSIZE * FONT_YFACTOR) >= con->border_bottom) {
+		memcpy(con->framebuffer,
+		       con->framebuffer +
+		       con->stride * (FONT_YSIZE * FONT_YFACTOR + FONT_YGAP),
+		       con->stride * con->yres - FONT_YSIZE);
+		cnt = (con->stride * (FONT_YSIZE*FONT_YFACTOR + FONT_YGAP)) / 4;
+		ptr = (unsigned long *)(con->framebuffer +
+				      con->stride * (con->yres - FONT_YSIZE));
+		while (cnt--)
+			*ptr++ = con->background;
+		con->cursor_y -= FONT_YSIZE * FONT_YFACTOR + FONT_YGAP;
+	}
+}
+
+static void console_init(struct console_data *con, void *framebuffer,
+			 int xres, int yres, int stride)
+{
+	int c;
+	unsigned long *p;
+
+	con->framebuffer = framebuffer;
+	con->xres = xres;
+	con->yres = yres;
+	con->border_left = 0;
+	con->border_top = 0;
+	con->border_right = con->xres;
+	con->border_bottom = con->yres;
+	con->stride = stride;
+	con->cursor_x = con->cursor_y = 0;
+
+	con->font = font_vga_8x16.data;
+
+	con->foreground = COLOR_WHITE;
+	con->background = COLOR_BLACK;
+
+	con->scrolled_lines = 0;
+
+	/* clear screen */
+	c = con->xres * con->yres / 2;
+	p = (unsigned long *)con->framebuffer;
+	while (c--)
+		*p++ = con->background;
+
+	default_console = con;
+}
+
+/*
+ * Video hardware setup.
+ *
+ */
+
+/* Hardware registers */
+#define VI_TFBL                 0x1c
+#define VI_TFBR                 0x20
+#define VI_BFBL                 0x24
+#define VI_BFBR                 0x28
+#define VI_DPV                  0x2c
+
+/* NTSC settings (640x480) */
+static const u32 vi_Mode640X480NtscYUV16[32] = {
+	0x0F060001, 0x476901AD, 0x02EA5140, 0x00030018,
+	0x00020019, 0x410C410C, 0x40ED40ED, 0x00435A4E,
+	0x00000000, 0x00435A4E, 0x00000000, 0x00000000,
+	0x110701AE, 0x10010001, 0x00010001, 0x00010001,
+	0x00000000, 0x00000000, 0x28500100, 0x1AE771F0,
+	0x0DB4A574, 0x00C1188E, 0xC4C0CBE2, 0xFCECDECF,
+	0x13130F08, 0x00080C0F, 0x00FF0000, 0x00000000,
+	0x02800000, 0x000000FF, 0x00FF00FF, 0x00FF00FF
+};
+
+static void vi_setup_video(void __iomem *io_base, unsigned long xfb_start)
+{
+	const u32 *regs = vi_Mode640X480NtscYUV16;
+	int i;
+
+	/* initialize video registers */
+	for (i = 0; i < 7; i++)
+		out_be32(io_base + i * sizeof(__u32), regs[i]);
+
+	out_be32(io_base + VI_TFBR, regs[VI_TFBR / sizeof(__u32)]);
+	out_be32(io_base + VI_BFBR, regs[VI_BFBR / sizeof(__u32)]);
+	out_be32(io_base + VI_DPV, regs[VI_DPV / sizeof(__u32)]);
+	for (i = 16; i < 32; i++)
+		out_be32(io_base + i * sizeof(__u32), regs[i]);
+
+	/* set framebuffer address, interlaced mode */
+	out_be32(io_base + VI_TFBL, 0x10000000 | (xfb_start >> 5));
+	xfb_start += 2 * SCREEN_WIDTH;	/* line length */
+	out_be32(io_base + VI_BFBL, 0x10000000 | (xfb_start >> 5));
+}
+
+/*
+ * Retrieves and prepares the virtual address needed to access the hardware.
+ */
+static void __iomem *vi_setup_io_base(struct device_node *np)
+{
+	phys_addr_t paddr;
+	const unsigned int *reg;
+	void *io_base = NULL;
+
+	reg = of_get_property(np, "reg", NULL);
+	if (reg) {
+		paddr = of_translate_address(np, reg);
+		if (paddr)
+			io_base = ioremap(paddr, reg[1]);
+	}
+	return io_base;
+}
+
+/*
+ * udbg functions.
+ *
+ */
+
+/* OF bindings */
+static struct of_device_id gcnvi_udbg_ids[] __initdata = {
+	{ .compatible = "nintendo,hollywood-video", },
+	{ .compatible = "nintendo,gamecube-video", },
+};
+
+static struct console_data gcnvi_udbg_console;
+
+/*
+ * Transmits a character.
+ */
+void gcnvi_udbg_putc(char ch)
+{
+	if (default_console)
+		console_putc(default_console, ch);
+}
+
+/*
+ * Initializes udbg support.
+ */
+void __init gcnvi_udbg_init(void)
+{
+	unsigned long xfb_start = 0, xfb_size = 0;
+	struct device_node *np = NULL;
+	const unsigned long *prop;
+	void *screen_base;
+	void *io_base;
+
+	for_each_matching_node(np, gcnvi_udbg_ids) {
+		if (np)
+			break;
+	}
+	if (!np)
+		return;
+
+	prop = of_get_property(np, "xfb-start", NULL);
+	if (prop) {
+		xfb_start = *prop;
+		prop = of_get_property(np, "xfb-size", NULL);
+		if (prop)
+			xfb_size = *prop;
+	}
+	io_base = vi_setup_io_base(np);
+
+	of_node_put(np);
+
+	if (!prop || !io_base)
+		return;
+
+	if (xfb_size < 2 * SCREEN_WIDTH * SCREEN_HEIGHT)
+		return;
+
+	screen_base = ioremap_nocache(xfb_start, xfb_size);
+	if (!screen_base)
+		return;
+
+	vi_setup_video(io_base, xfb_start);
+	console_init(&gcnvi_udbg_console, screen_base,
+		     SCREEN_WIDTH, SCREEN_HEIGHT, 2 * SCREEN_WIDTH);
+
+	udbg_putc = gcnvi_udbg_putc;
+	printk(KERN_INFO "gcnvi_udbg: ready\n");
+}
diff --git a/arch/powerpc/platforms/embedded6xx/gcnvi_udbg.h b/arch/powerpc/platforms/embedded6xx/gcnvi_udbg.h
new file mode 100644
index 0000000..daba549
--- /dev/null
+++ b/arch/powerpc/platforms/embedded6xx/gcnvi_udbg.h
@@ -0,0 +1,30 @@
+/*
+ * arch/powerpc/platforms/embedded6xx/gcnvi_udbg.h
+ *
+ * Nintendo GameCube/Wii framebuffer udbg output support.
+ * Copyright (C) 2008-2009 The GameCube Linux Team
+ * Copyright (C) 2008,2009 Albert Herranz
+ *
+ * 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.
+ *
+ */
+
+#ifndef __GCNVI_UDBG_H
+#define __GCNVI_UDBG_H
+
+#ifdef CONFIG_GAMECUBE_VIDEO_UDBG
+
+extern void __init gcnvi_udbg_init(void);
+
+#else
+
+static inline void __init gcnvi_udbg_init(void)
+{
+}
+
+#endif /* CONFIG_GAMECUBE_VIDEO_UDBG */
+
+#endif /* __GCNVI_UDBG_H */
diff --git a/arch/powerpc/platforms/embedded6xx/hlwd-gpio.c b/arch/powerpc/platforms/embedded6xx/hlwd-gpio.c
new file mode 100644
index 0000000..023b71c
--- /dev/null
+++ b/arch/powerpc/platforms/embedded6xx/hlwd-gpio.c
@@ -0,0 +1,150 @@
+/*
+ * arch/powerpc/platforms/embedded6xx/hlwd-gpio.c
+ *
+ * Nintendo Wii (Hollywood) GPIO driver
+ * Copyright (C) 2008-2009 The GameCube Linux Team
+ * Copyright (C) 2008,2009 Albert Herranz
+ *
+ * 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/io.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/of_platform.h>
+
+#define DRV_MODULE_NAME "hlwd-gpio"
+
+#define drv_printk(level, format, arg...) \
+	printk(level DRV_MODULE_NAME ": " format , ## arg)
+
+
+struct hlwd_gpio_chip {
+	struct of_mm_gpio_chip mmchip;
+	spinlock_t lock;
+};
+
+struct hlwd_gpio_regs {
+	__be32 out, dir, in;
+};
+
+
+static inline struct hlwd_gpio_chip *
+to_hlwd_gpio_chip(struct of_mm_gpio_chip *mm_gc)
+{
+	return container_of(mm_gc, struct hlwd_gpio_chip, mmchip);
+}
+
+static int hlwd_gpio_get(struct gpio_chip *gc, unsigned int gpio)
+{
+	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+	struct hlwd_gpio_regs __iomem *regs = mm_gc->regs;
+	u32 pin_mask = 1 << (31 - gpio);
+	unsigned int val;
+
+	val = !!(in_be32(&regs->in) & pin_mask);
+
+	pr_debug("%s: gpio: %d val: %d\n", __func__, gpio, val);
+
+	return val;
+}
+
+static void hlwd_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+	struct hlwd_gpio_chip *st_gc = to_hlwd_gpio_chip(mm_gc);
+	struct hlwd_gpio_regs __iomem *regs = mm_gc->regs;
+	u32 pin_mask = 1 << (31 - gpio);
+	u32 data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&st_gc->lock, flags);
+	data = in_be32(&regs->in) & ~pin_mask;
+	if (val)
+		data |= pin_mask;
+	out_be32(&regs->out, data);
+	spin_unlock_irqrestore(&st_gc->lock, flags);
+
+	pr_debug("%s: gpio: %d val: %d\n", __func__, gpio, val);
+}
+
+static int hlwd_gpio_dir_in(struct gpio_chip *gc, unsigned int gpio)
+{
+	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+	struct hlwd_gpio_regs __iomem *regs = mm_gc->regs;
+	u32 pin_mask = 1 << (31 - gpio);
+
+	clrbits32(&regs->dir, pin_mask);
+
+	return 0;
+}
+
+static int hlwd_gpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+	struct hlwd_gpio_regs __iomem *regs = mm_gc->regs;
+	u32 pin_mask = 1 << (31 - gpio);
+
+	setbits32(&regs->dir, pin_mask);
+	hlwd_gpio_set(gc, gpio, val);
+
+	return 0;
+}
+
+int hlwd_gpio_add32(struct device_node *np)
+{
+	struct of_mm_gpio_chip *mm_gc;
+	struct of_gpio_chip *of_gc;
+	struct gpio_chip *gc;
+	struct hlwd_gpio_chip *st_gc;
+	const unsigned long *prop;
+	int error;
+
+	st_gc = kzalloc(sizeof(*st_gc), GFP_KERNEL);
+	if (!st_gc)
+		return -ENOMEM;
+
+	spin_lock_init(&st_gc->lock);
+	mm_gc = &st_gc->mmchip;
+	of_gc = &mm_gc->of_gc;
+	gc = &of_gc->gc;
+
+	prop = of_get_property(np, "#gpio-cells", NULL);
+	if (prop && *prop >= 2)
+		of_gc->gpio_cells = *prop;
+	else
+		of_gc->gpio_cells = 2; /* gpio pin number, flags */
+
+	gc->ngpio = 32;
+	gc->direction_input = hlwd_gpio_dir_in;
+	gc->direction_output = hlwd_gpio_dir_out;
+	gc->get = hlwd_gpio_get;
+	gc->set = hlwd_gpio_set;
+
+	error = of_mm_gpiochip_add(np, mm_gc);
+	if (!error)
+		drv_printk(KERN_INFO, "%s: added %u gpios at %p\n",
+			   np->name, gc->ngpio, mm_gc->regs);
+	return error;
+}
+
+static int hlwd_gpio_init(void)
+{
+	struct device_node *np;
+	int error;
+
+	for_each_compatible_node(np, NULL, "nintendo,hollywood-gpio") {
+		error = hlwd_gpio_add32(np);
+		if (error < 0)
+			drv_printk(KERN_ERR, "error %d adding gpios"
+				   " for %s\n", error, np->full_name);
+	}
+	return 0; /* whatever */
+}
+arch_initcall(hlwd_gpio_init);
+
diff --git a/arch/powerpc/platforms/embedded6xx/hlwd-pic.c b/arch/powerpc/platforms/embedded6xx/hlwd-pic.c
new file mode 100644
index 0000000..b024800
--- /dev/null
+++ b/arch/powerpc/platforms/embedded6xx/hlwd-pic.c
@@ -0,0 +1,238 @@
+/*
+ * arch/powerpc/platforms/embedded6xx/hlwd-pic.c
+ *
+ * Nintendo Wii "Hollywood" interrupt controller support.
+ * Copyright (C) 2009 The GameCube Linux Team
+ * Copyright (C) 2009 Albert Herranz
+ *
+ * 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.
+ *
+ */
+#define DRV_MODULE_NAME "hlwd-pic"
+#define pr_fmt(fmt) DRV_MODULE_NAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/irq.h>
+#include <linux/of.h>
+#include <asm/io.h>
+
+#include "hlwd-pic.h"
+
+#define HLWD_NR_IRQS	32
+
+/*
+ * Each interrupt has a corresponding bit in both
+ * the Interrupt Cause (ICR) and Interrupt Mask (IMR) registers.
+ *
+ * Enabling/disabling an interrupt line involves asserting/clearing
+ * the corresponding bit in IMR. ACK'ing a request simply involves
+ * asserting the corresponding bit in ICR.
+ */
+#define HW_BROADWAY_ICR		0x00
+#define HW_BROADWAY_IMR		0x04
+
+
+/*
+ * IRQ chip hooks.
+ *
+ */
+
+static void hlwd_pic_mask_and_ack(unsigned int virq)
+{
+	int irq = virq_to_hw(virq);
+	void __iomem *io_base = get_irq_chip_data(virq);
+
+	clear_bit(irq, io_base + HW_BROADWAY_IMR);
+	set_bit(irq, io_base + HW_BROADWAY_ICR);
+}
+
+static void hlwd_pic_ack(unsigned int virq)
+{
+	int irq = virq_to_hw(virq);
+	void __iomem *io_base = get_irq_chip_data(virq);
+
+	set_bit(irq, io_base + HW_BROADWAY_ICR);
+}
+
+static void hlwd_pic_mask(unsigned int virq)
+{
+	int irq = virq_to_hw(virq);
+	void __iomem *io_base = get_irq_chip_data(virq);
+
+	clear_bit(irq, io_base + HW_BROADWAY_IMR);
+}
+
+static void hlwd_pic_unmask(unsigned int virq)
+{
+	int irq = virq_to_hw(virq);
+	void __iomem *io_base = get_irq_chip_data(virq);
+
+	set_bit(irq, io_base + HW_BROADWAY_IMR);
+}
+
+
+static struct irq_chip hlwd_pic = {
+	.typename	= "hlwd-pic",
+	.ack		= hlwd_pic_ack,
+	.mask_ack	= hlwd_pic_mask_and_ack,
+	.mask		= hlwd_pic_mask,
+	.unmask		= hlwd_pic_unmask,
+};
+
+/*
+ * IRQ host hooks.
+ *
+ */
+
+static struct irq_host *hlwd_irq_host;
+
+static int hlwd_pic_map(struct irq_host *h, unsigned int virq,
+			   irq_hw_number_t hwirq)
+{
+	set_irq_chip_data(virq, h->host_data);
+	get_irq_desc(virq)->status |= IRQ_LEVEL;
+	set_irq_chip_and_handler(virq, &hlwd_pic, handle_level_irq);
+	return 0;
+}
+
+static void hlwd_pic_unmap(struct irq_host *h, unsigned int irq)
+{
+	set_irq_chip_data(irq, NULL);
+	set_irq_chip(irq, NULL);
+}
+
+static struct irq_host_ops hlwd_irq_host_ops = {
+	.map = hlwd_pic_map,
+	.unmap = hlwd_pic_unmap,
+};
+
+static unsigned int __hlwd_pic_get_irq(struct irq_host *h)
+{
+	void __iomem *io_base = h->host_data;
+	int irq;
+	u32 irq_status;
+
+	irq_status = in_be32(io_base + HW_BROADWAY_ICR) &
+		     in_be32(io_base + HW_BROADWAY_IMR);
+	if (irq_status == 0)
+		return NO_IRQ_IGNORE;	/* no more IRQs pending */
+
+	__asm__ __volatile__("cntlzw %0,%1" : "=r"(irq) : "r"(irq_status));
+	return irq_linear_revmap(h, 31 - irq);
+}
+
+static void hlwd_pic_irq_cascade(unsigned int cascade_virq,
+				      struct irq_desc *desc)
+{
+	struct irq_host *irq_host = get_irq_data(cascade_virq);
+	unsigned int virq;
+
+	spin_lock(&desc->lock);
+	desc->chip->mask(cascade_virq); /* IRQ_LEVEL */
+	spin_unlock(&desc->lock);
+
+	virq = __hlwd_pic_get_irq(irq_host);
+	if (virq != NO_IRQ_IGNORE)
+		generic_handle_irq(virq);
+	else
+		pr_err("spurious interrupt!\n");
+
+	spin_lock(&desc->lock);
+	desc->chip->ack(cascade_virq); /* IRQ_LEVEL */
+	if (!(desc->status & IRQ_DISABLED) && desc->chip->unmask)
+		desc->chip->unmask(cascade_virq);
+	spin_unlock(&desc->lock);
+}
+
+/*
+ * Platform hooks.
+ *
+ */
+
+static void __hlwd_quiesce(void __iomem *io_base)
+{
+	/* mask and ack all IRQs */
+	out_be32(io_base + HW_BROADWAY_IMR, 0);
+	out_be32(io_base + HW_BROADWAY_ICR, ~0);
+}
+
+struct irq_host *hlwd_pic_init(struct device_node *np)
+{
+	struct irq_host *irq_host;
+	struct resource res;
+	void __iomem *io_base;
+	int retval;
+
+	retval = of_address_to_resource(np, 0, &res);
+	if (retval) {
+		pr_err("no io memory range found\n");
+		return NULL;
+	}
+	io_base = ioremap(res.start, resource_size(&res));
+	if (!io_base) {
+		pr_err("ioremap failed\n");
+		return NULL;
+	}
+
+	pr_info("controller at 0x%08x mapped to 0x%p\n", res.start, io_base);
+
+	__hlwd_quiesce(io_base);
+
+	irq_host = irq_alloc_host(np, IRQ_HOST_MAP_LINEAR, HLWD_NR_IRQS,
+				  &hlwd_irq_host_ops, NO_IRQ_IGNORE);
+	if (!irq_host) {
+		pr_err("failed to allocate irq_host\n");
+		return NULL;
+	}
+	irq_host->host_data = io_base;
+
+	return irq_host;
+}
+
+unsigned int hlwd_pic_get_irq(void)
+{
+	return __hlwd_pic_get_irq(hlwd_irq_host);
+}
+
+/*
+ * Probe function.
+ *
+ */
+
+void hlwd_pic_probe(void)
+{
+	struct irq_host *host;
+	struct device_node *np;
+	const u32 *interrupts;
+	int cascade_virq;
+
+	for_each_compatible_node(np, NULL, "nintendo,hollywood-pic") {
+		interrupts = of_get_property(np, "interrupts", NULL);
+		if (interrupts) {
+			host = hlwd_pic_init(np);
+			BUG_ON(!host);
+			cascade_virq = irq_of_parse_and_map(np, 0);
+			set_irq_data(cascade_virq, host);
+			set_irq_chained_handler(cascade_virq,
+						hlwd_pic_irq_cascade);
+		}
+	}
+}
+
+/**
+ * hlwd_quiesce() - quiesce hollywood irq controller
+ *
+ * Mask and ack all interrupt sources.
+ *
+ */
+void hlwd_quiesce(void)
+{
+	void __iomem *io_base = hlwd_irq_host->host_data;
+
+	__hlwd_quiesce(io_base);
+}
+
diff --git a/arch/powerpc/platforms/embedded6xx/hlwd-pic.h b/arch/powerpc/platforms/embedded6xx/hlwd-pic.h
new file mode 100644
index 0000000..d2e5a09
--- /dev/null
+++ b/arch/powerpc/platforms/embedded6xx/hlwd-pic.h
@@ -0,0 +1,22 @@
+/*
+ * arch/powerpc/platforms/embedded6xx/hlwd-pic.h
+ *
+ * Nintendo Wii "Hollywood" interrupt controller support.
+ * Copyright (C) 2009 The GameCube Linux Team
+ * Copyright (C) 2009 Albert Herranz
+ *
+ * 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.
+ *
+ */
+
+#ifndef __HLWD_PIC_H
+#define __HLWD_PIC_H
+
+extern unsigned int hlwd_pic_get_irq(void);
+extern void hlwd_pic_probe(void);
+extern void hlwd_quiesce(void);
+
+#endif
diff --git a/arch/powerpc/platforms/embedded6xx/starlet-es.c b/arch/powerpc/platforms/embedded6xx/starlet-es.c
new file mode 100644
index 0000000..d9e4ff5
--- /dev/null
+++ b/arch/powerpc/platforms/embedded6xx/starlet-es.c
@@ -0,0 +1,680 @@
+/*
+ * arch/powerpc/platforms/embedded6xx/starlet-es.c
+ *
+ * Nintendo Wii starlet ES driver
+ * Copyright (C) 2008-2009 The GameCube Linux Team
+ * Copyright (C) 2008,2009 Albert Herranz
+ *
+ * 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.
+ *
+ */
+
+#define DEBUG
+
+#include <linux/kernel.h>
+#include <linux/of_platform.h>
+#include <linux/scatterlist.h>
+#include <asm/starlet.h>
+#include <asm/starlet-ios.h>
+
+#define DRV_MODULE_NAME		"starlet-es"
+#define DRV_DESCRIPTION		"Nintendo Wii starlet ES driver"
+#define DRV_AUTHOR		"Albert Herranz"
+
+static const char starlet_es_driver_version[] = "0.3i";
+
+#define DBG(fmt, arg...)	pr_debug(fmt, ##arg)
+
+#define drv_printk(level, format, arg...) \
+	 printk(level DRV_MODULE_NAME ": " format , ## arg)
+
+
+#define STARLET_ES_IOS_MIN 30
+#define STARLET_ES_IOS_MAX 36
+
+#define STARLET_ES_TIMEOUT 1000000 /* usecs */
+
+
+struct starlet_es_device {
+	int fd;
+	u64 ios_title;
+
+	struct device *dev;
+};
+
+struct starlet_es_ticket_limit {
+	u32 tag;
+	u32 value;
+} __attribute__((packed));
+
+struct starlet_es_ticket_view {
+	u32 view;
+	u64 ticketid;
+	u32 devicetype;
+	u64 title;
+	u16 access_mask;
+	u8 reserved[0x3c];
+	u8 cidx_mask[0x40];
+	u16 padding;
+	struct starlet_es_ticket_limit limits[8];
+} __attribute__((packed));
+
+/*
+ * /dev/es
+ *
+ */
+
+#define ES_IOCTLV_LAUNCHTITLE		0x08
+#define ES_IOCTLV_GETTITLECOUNT		0x0e
+#define ES_IOCTLV_GETTITLES		0x0f
+#define ES_IOCTLV_GETTICKETVIEWCOUNT	0x12
+#define ES_IOCTLV_GETTICKETVIEWS	0x13
+
+static const char dev_es[] = "/dev/es";
+
+/*
+ * Handy small buffer routines.
+ * We use a small static aligned buffer to avoid allocations for short-lived
+ * operations involving 1 to 4 byte data transfers to/from IOS.
+ *
+ */
+
+static u32 es_small_buf[L1_CACHE_BYTES / sizeof(u32)]
+		    __attribute__ ((aligned(STARLET_IPC_DMA_ALIGN + 1)));
+static const size_t es_small_buf_size = sizeof(es_small_buf_size);
+static DEFINE_MUTEX(es_small_buf_lock);
+
+static u32 *es_small_buf_get(void)
+{
+	u32 *buf;
+
+	if (!mutex_trylock(&es_small_buf_lock))
+		buf = starlet_kzalloc(es_small_buf_size, GFP_ATOMIC);
+	else {
+		memset(es_small_buf, 0, es_small_buf_size);
+		buf = es_small_buf;
+	}
+
+	return buf;
+}
+
+static void es_small_buf_put(u32 *buf)
+{
+	if (buf == es_small_buf)
+		mutex_unlock(&es_small_buf_lock);
+	else
+		starlet_kfree(buf);
+}
+
+/*
+ *
+ *
+ */
+
+static struct starlet_es_device *starlet_es_device_instance;
+
+/**
+ * starlet_es_get_device() - get ES device handle
+ *
+ * Returns the handle for the Encryption Services (ES) device instance.
+ */
+struct starlet_es_device *starlet_es_get_device(void)
+{
+	if (!starlet_es_device_instance)
+		drv_printk(KERN_ERR, "uninitialized device instance!\n");
+	return starlet_es_device_instance;
+}
+EXPORT_SYMBOL_GPL(starlet_es_get_device);
+
+/*
+ *
+ */
+static int starlet_es_get_title_count(unsigned long *count)
+{
+	struct starlet_es_device *es_dev = starlet_es_get_device();
+	struct scatterlist io[1];
+	u32 *count_buf;
+	int error;
+
+	if (!es_dev)
+		return -ENODEV;
+
+	count_buf = es_small_buf_get();
+	if (!count_buf)
+		return -ENOMEM;
+
+	*count_buf = 0;
+	sg_init_one(io, count_buf, sizeof(*count_buf));
+
+	error = starlet_ioctlv(es_dev->fd, ES_IOCTLV_GETTITLECOUNT,
+				   0, NULL, 1, io);
+	if (error)
+		DBG("%s: error=%d (%08x)\n", __func__, error, error);
+	else
+		*count = *count_buf;
+
+	es_small_buf_put(count_buf);
+
+	return error;
+}
+
+/*
+ *
+ */
+static int starlet_es_get_titles(u64 *titles, unsigned long count)
+{
+	struct starlet_es_device *es_dev = starlet_es_get_device();
+	struct scatterlist in[1], io[1];
+	u32 *count_buf;
+	int error;
+
+	if (!es_dev)
+		return -ENODEV;
+
+	count_buf = es_small_buf_get();
+	if (!count_buf)
+		return -ENOMEM;
+
+	*count_buf = count;
+	sg_init_one(in, count_buf, sizeof(*count_buf));
+	sg_init_one(io, titles, sizeof(*titles)*count);
+
+	error = starlet_ioctlv(es_dev->fd, ES_IOCTLV_GETTITLES,
+				   1, in, 1, io);
+	if (error)
+		DBG("%s: error=%d (%08x)\n", __func__, error, error);
+
+	es_small_buf_put(count_buf);
+
+	return error;
+}
+
+/*
+ * This call may be used in a non-sleeping context
+ */
+static int starlet_es_get_ticket_view_count(u64 title, unsigned long *count)
+{
+	struct starlet_es_device *es_dev = starlet_es_get_device();
+	struct scatterlist in[1], io[1];
+	u64 *title_buf;
+	u32 *count_buf;
+	int error;
+
+	if (!es_dev)
+		return -ENODEV;
+
+	title_buf = starlet_kzalloc(sizeof(*title_buf), GFP_ATOMIC);
+	if (!title_buf)
+		return -ENOMEM;
+
+	count_buf = es_small_buf_get();
+	if (!count_buf) {
+		starlet_kfree(title_buf);
+		return -ENOMEM;
+	}
+
+	*title_buf = title;
+	sg_init_one(in, title_buf, sizeof(*title_buf));
+	sg_init_one(io, count_buf, sizeof(*count_buf));
+
+	error = starlet_ioctlv_polled(es_dev->fd, ES_IOCTLV_GETTICKETVIEWCOUNT,
+				      1, in, 1, io, STARLET_ES_TIMEOUT);
+	if (error)
+		DBG("%s: error=%d (%08x)\n", __func__, error, error);
+	else
+		*count = *count_buf;
+
+	starlet_kfree(title_buf);
+	es_small_buf_put(count_buf);
+
+	return error;
+}
+
+/*
+ * This call may be used in a non-sleeping context
+ */
+static int starlet_es_get_ticket_views(u64 title,
+				       struct starlet_es_ticket_view *views,
+				       unsigned long count)
+{
+	struct starlet_es_device *es_dev = starlet_es_get_device();
+	struct scatterlist in[2], io[1];
+	u32 *count_buf;
+	u64 *title_buf;
+	int error;
+
+	if (!es_dev)
+		return -ENODEV;
+
+	title_buf = starlet_kzalloc(sizeof(*title_buf), GFP_ATOMIC);
+	if (!title_buf)
+		return -ENOMEM;
+
+	count_buf = es_small_buf_get();
+	if (!count_buf) {
+		starlet_kfree(title_buf);
+		return -ENOMEM;
+	}
+
+	*title_buf = title;
+	*count_buf = count;
+	sg_init_table(in, 2);
+	sg_set_buf(&in[0], title_buf, sizeof(*title_buf));
+	sg_set_buf(&in[1], count_buf, sizeof(*count_buf));
+
+	sg_init_one(io, views, sizeof(*views)*count);
+
+	error = starlet_ioctlv_polled(es_dev->fd, ES_IOCTLV_GETTICKETVIEWS,
+				      2, in, 1, io, STARLET_ES_TIMEOUT);
+	if (error)
+		DBG("%s: error=%d (%08x)\n", __func__, error, error);
+
+	es_small_buf_put(count_buf);
+	starlet_kfree(title_buf);
+
+	return error;
+}
+
+/*
+ * This call may be used in a non-sleeping context
+ */
+static int starlet_es_launch_title_view(u64 title,
+					struct starlet_es_ticket_view *view)
+{
+	struct starlet_es_device *es_dev = starlet_es_get_device();
+	struct scatterlist in[2];
+	u64 *title_buf;
+	int error;
+
+	if (!es_dev)
+		return -ENODEV;
+
+	title_buf = starlet_kzalloc(sizeof(*title_buf), GFP_ATOMIC);
+	if (!title_buf)
+		return -ENOMEM;
+
+	*title_buf = title;
+	sg_init_table(in, 2);
+	sg_set_buf(&in[0], title_buf, sizeof(*title_buf));
+	sg_set_buf(&in[1], view, sizeof(*view));
+
+	error = starlet_ioctlv_and_reboot(es_dev->fd,
+					  ES_IOCTLV_LAUNCHTITLE,
+					  2, in, 0, NULL);
+	if (error)
+		DBG("%s: error=%d (%08x)\n", __func__, error, error);
+
+	starlet_kfree(title_buf);
+
+	return error;
+}
+
+/*
+ * This call may be used in a non-sleeping context
+ */
+static int starlet_es_launch_title(struct starlet_es_device *es_dev, u64 title)
+{
+	struct starlet_es_ticket_view *views;
+	unsigned long count = 0;
+	int error;
+
+	error = starlet_es_get_ticket_view_count(title, &count);
+	if (error)
+		return error;
+	if (!count)
+		return -ENOENT;
+
+	views = starlet_kzalloc(sizeof(*views)*count, GFP_ATOMIC);
+	if (!views) {
+		DBG("%s: out of memory\n", __func__);
+		return -ENOMEM;
+	}
+
+	error =	starlet_es_get_ticket_views(title, views, count);
+	if (error) {
+		starlet_kfree(views);
+		return error;
+	}
+
+	drv_printk(KERN_INFO, "launching title %u-%u\n",
+		   (u32)(title >> 32), (u32)title);
+	error = starlet_es_launch_title_view(title, views); /* first view */
+
+	starlet_kfree(views);
+
+	return error;
+}
+
+/*
+ * This call may be used in a non-sleeping context
+ */
+static int starlet_es_reopen(struct starlet_es_device *es_dev)
+{
+	int error;
+
+	error = starlet_open_polled(dev_es, 0, STARLET_ES_TIMEOUT);
+	if (error < 0) {
+		drv_printk(KERN_ERR, "unable to reopen %s (%d)\n",
+			   dev_es, error);
+		goto out;
+	}
+	es_dev->fd = error;
+	error = 0;
+out:
+	return error;
+}
+
+/*
+ * This call may be used in a non-sleeping context
+ */
+static int __starlet_es_reload_ios(struct starlet_es_device *es_dev)
+{
+	int error = -EINVAL;
+
+	if (!es_dev->ios_title) {
+		drv_printk(KERN_ERR, "no IOS previously loaded\n");
+		goto out;
+	}
+
+	error = starlet_es_launch_title(es_dev, es_dev->ios_title);
+	if (!error)
+		es_dev->fd = -1;
+out:
+	return error;
+}
+
+/*
+ * This call may be used in a non-sleeping context
+ */
+static int starlet_es_reload_ios(struct starlet_es_device *es_dev)
+{
+	int error;
+
+	error = __starlet_es_reload_ios(es_dev);
+	if (!error)
+		error = starlet_es_reopen(es_dev);
+
+	if (error)
+		DBG("%s: error=%d (%08x)\n", __func__, error, error);
+	return error;
+}
+
+
+/**
+ * starlet_es_reload_ios_and_discard() - reload IOS and stop using it
+ *
+ * Reloads the version of IOS loaded at boot time.
+ * All IOS dependent devices will fail after this call unless they are
+ * reinitialized.
+ *
+ * This call may be used in a non-sleeping context
+ */
+int starlet_es_reload_ios_and_discard(void)
+{
+	struct starlet_es_device *es_dev = starlet_es_get_device();
+	int error = -EINVAL;
+
+	if (!es_dev)
+		goto err_out;
+
+	error = starlet_es_reload_ios(es_dev);
+err_out:
+	return error;
+}
+EXPORT_SYMBOL_GPL(starlet_es_reload_ios_and_discard);
+
+/**
+ * starlet_es_reload_ios_and_launch() - reload IOS and launch a title
+ *
+ * Reload the version of IOS loaded at boot time and launch a title.
+ * If the title loaded is a non-IOS title, this function will not return and
+ * is equivalent to a platform restart.
+ *
+ * This call may be used in a non-sleeping context
+ */
+int starlet_es_reload_ios_and_launch(u64 title)
+{
+	struct starlet_es_device *es_dev = starlet_es_get_device();
+	int error = -EINVAL;
+
+	if (!es_dev)
+		goto err_out;
+
+	error = starlet_es_reload_ios(es_dev);
+	if (!error)
+		error = starlet_es_launch_title(es_dev, title);
+err_out:
+	return error;
+}
+EXPORT_SYMBOL_GPL(starlet_es_reload_ios_and_launch);
+
+static int starlet_es_find_newest_title(struct starlet_es_device *es_dev,
+					u64 *title,
+					u64 title_min, u64 title_max)
+{
+	u64 *titles;
+	u64 candidate;
+	unsigned long count = 0;
+	int found, i;
+	int error;
+
+	error = starlet_es_get_title_count(&count);
+	if (error)
+		return error;
+	if (!count)
+		return -ENOENT;
+
+	titles = starlet_kzalloc(sizeof(*titles)*count, GFP_KERNEL);
+	if (!titles) {
+		DBG("%s: out of memory\n", __func__);
+		return -ENOMEM;
+	}
+
+	error =	starlet_es_get_titles(titles, count);
+	if (error) {
+		starlet_kfree(titles);
+		return error;
+	}
+
+	found = 0;
+	candidate = title_min;
+	for (i = 0; i < count; i++) {
+		if (titles[i] > candidate && titles[i] <= title_max) {
+			candidate = titles[i];
+			found = 1;
+		}
+	}
+
+	starlet_kfree(titles);
+
+	if (!found)
+		return 0;
+
+	*title = candidate;
+
+	return 1;
+}
+
+static int starlet_es_load_preferred_ios(struct starlet_es_device *es_dev,
+					 u64 ios_min, u64 ios_max)
+{
+	u64 title = 0;
+	int error;
+
+	error = starlet_es_find_newest_title(es_dev, &title, ios_min, ios_max);
+	if (!error)
+		return -ENOENT;
+	if (error > 0) {
+		es_dev->ios_title = title;
+		error = starlet_es_launch_title(es_dev, title);
+		if (!error)
+			error = starlet_es_reopen(es_dev);
+	}
+
+	return error;
+}
+
+static int starlet_nwc24_stop_scheduler(void)
+{
+	void *obuf;
+	const size_t osize = 0x20;
+	int fd;
+	int error = 0;
+
+	obuf = es_small_buf_get();
+	if (!obuf)
+		return -ENOMEM;
+
+	fd = starlet_open("/dev/net/kd/request", 0);
+	if (fd >= 0) {
+		error = starlet_ioctl(fd, 1, NULL, 0, obuf, osize);
+		starlet_close(fd);
+	}
+
+	es_small_buf_put(obuf);
+
+	if (error)
+		DBG("%s: error=%d (%08x)\n", __func__, error, error);
+	return error;
+}
+
+static int starlet_es_init(struct starlet_es_device *es_dev)
+{
+	u64 ios_min, ios_max;
+	int error;
+
+	error = starlet_open(dev_es, 0);
+	if (error >= 0) {
+		starlet_es_device_instance = es_dev;
+		es_dev->fd = error;
+
+		ios_min = 0x100000000ULL | STARLET_ES_IOS_MIN;
+		ios_max = 0x100000000ULL | STARLET_ES_IOS_MAX;
+
+		error = starlet_es_load_preferred_ios(es_dev, ios_min, ios_max);
+		if (error)
+			drv_printk(KERN_WARNING, "unable to load preferred"
+				   " IOS version (min %llx, max %llx)\n",
+				   ios_min, ios_max);
+	}
+
+	/*
+	 * Try to disable the Nintendo Wifi Connect 24 scheduler.
+	 * And do this even if we failed to load our preferred IOS.
+	 *
+	 * When the scheduler kicks in, starlet IPC calls from Broadway fail.
+	 */
+	starlet_nwc24_stop_scheduler();
+
+	return error;
+}
+
+static void starlet_es_exit(struct starlet_es_device *es_dev)
+{
+	starlet_es_device_instance = NULL;
+	starlet_close(es_dev->fd);
+	es_dev->fd = -1;
+}
+
+
+/*
+ * Driver model helper routines.
+ *
+ */
+
+static int starlet_es_do_probe(struct device *dev)
+{
+	struct starlet_es_device *es_dev;
+	int retval;
+
+	if (starlet_get_ipc_flavour() != STARLET_IPC_IOS)
+		return -ENODEV;
+
+	es_dev = kzalloc(sizeof(*es_dev), GFP_KERNEL);
+	if (!es_dev) {
+		drv_printk(KERN_ERR, "failed to allocate es_dev\n");
+		return -ENOMEM;
+	}
+	dev_set_drvdata(dev, es_dev);
+	es_dev->dev = dev;
+
+	retval = starlet_es_init(es_dev);
+	if (retval) {
+		dev_set_drvdata(dev, NULL);
+		kfree(es_dev);
+	}
+	return retval;
+}
+
+static int starlet_es_do_remove(struct device *dev)
+{
+	struct starlet_es_device *es_dev = dev_get_drvdata(dev);
+
+	if (es_dev) {
+		starlet_es_exit(es_dev);
+		dev_set_drvdata(dev, NULL);
+		kfree(es_dev);
+		return 0;
+	}
+	return -ENODEV;
+}
+
+/*
+ * OF platform driver hooks.
+ *
+ */
+
+static int starlet_es_of_probe(struct of_device *odev,
+			       const struct of_device_id *dev_id)
+{
+	return starlet_es_do_probe(&odev->dev);
+}
+
+static int starlet_es_of_remove(struct of_device *odev)
+{
+	return starlet_es_do_remove(&odev->dev);
+}
+
+static struct of_device_id starlet_es_of_match[] = {
+	{ .compatible = "nintendo,starlet-ios-es" },
+	{ },
+};
+
+MODULE_DEVICE_TABLE(of, starlet_es_of_match);
+
+static struct of_platform_driver starlet_es_of_driver = {
+	.owner = THIS_MODULE,
+	.name = DRV_MODULE_NAME,
+	.match_table = starlet_es_of_match,
+	.probe = starlet_es_of_probe,
+	.remove = starlet_es_of_remove,
+};
+
+/*
+ * Kernel module interface hooks.
+ *
+ */
+
+static int __init starlet_es_init_module(void)
+{
+	drv_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION,
+		   starlet_es_driver_version);
+
+	return of_register_platform_driver(&starlet_es_of_driver);
+}
+
+static void __exit starlet_es_exit_module(void)
+{
+	of_unregister_platform_driver(&starlet_es_of_driver);
+}
+
+module_init(starlet_es_init_module);
+module_exit(starlet_es_exit_module);
+
+MODULE_DESCRIPTION(DRV_DESCRIPTION);
+MODULE_AUTHOR(DRV_AUTHOR);
+MODULE_LICENSE("GPL");
+
diff --git a/arch/powerpc/platforms/embedded6xx/starlet-ipc.c b/arch/powerpc/platforms/embedded6xx/starlet-ipc.c
new file mode 100644
index 0000000..9dd50ad
--- /dev/null
+++ b/arch/powerpc/platforms/embedded6xx/starlet-ipc.c
@@ -0,0 +1,1605 @@
+/*
+ * arch/powerpc/platforms/embedded6xx/starlet-ipc.c
+ *
+ * Nintendo Wii starlet IPC driver
+ * Copyright (C) 2008-2009 The GameCube Linux Team
+ * Copyright (C) 2008,2009 Albert Herranz
+ *
+ * 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.
+ *
+ */
+
+#define DEBUG
+
+/*#define DBG(fmt, arg...)	pr_debug(fmt, ##arg)*/
+#define DBG(fmt, arg...)	drv_printk(KERN_INFO, fmt, ##arg)
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/of_platform.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/jiffies.h>
+#include <linux/dmapool.h>
+#include <linux/dma-mapping.h>
+#include <linux/random.h>
+#include <linux/scatterlist.h>
+#include <linux/string.h>
+#include <asm/io.h>
+#include <asm/bitops.h>
+#include <asm/time.h>
+#include <asm/starlet.h>
+#include <asm/starlet-ios.h>
+
+
+#define DRV_MODULE_NAME		"starlet-ipc"
+#define DRV_DESCRIPTION		"Nintendo Wii starlet IPC driver"
+#define DRV_AUTHOR		"Albert Herranz"
+
+static char starlet_ipc_driver_version[] = "0.3i";
+
+#define drv_printk(level, format, arg...) \
+	 printk(level DRV_MODULE_NAME ": " format , ## arg)
+
+/*
+ * Hardware registers
+ */
+#define STARLET_IPC_TXBUF	0x00	/* data from cpu to starlet */
+
+#define STARLET_IPC_CSR		0x04
+#define   STARLET_IPC_CSR_TXSTART	(1<<0)	/* start transmit */
+#define   STARLET_IPC_CSR_TBEI		(1<<1)	/* tx buf empty int */
+#define   STARLET_IPC_CSR_RBFI		(1<<2)	/* rx buf full int */
+#define   STARLET_IPC_CSR_RXRDY		(1<<3)	/* receiver ready */
+#define   STARLET_IPC_CSR_RBFIMASK	(1<<4)	/* rx buf full int mask */
+#define   STARLET_IPC_CSR_TBEIMASK	(1<<5)	/* tx buf empty int mask */
+
+#define STARLET_IPC_RXBUF	0x08	/* data from starlet to cpu */
+
+#define STARLET_IPC_ISR		0x30
+
+/* IOS calls */
+#define STARLET_IOS_OPEN	0x01
+#define STARLET_IOS_CLOSE	0x02
+#define STARLET_IOS_IOCTL	0x06
+#define STARLET_IOS_IOCTLV	0x07
+
+
+/* starlet_ipc_device flags */
+enum {
+	__TX_INUSE = 0,		/* tx buffer in use flag */
+	__REBOOT,		/* request causes IOS reboot */
+};
+
+/*
+ *
+ * Hardware.
+ */
+
+/*
+ * Update control and status register.
+ */
+static inline void starlet_ipc_update_csr(void __iomem *io_base, u32 val)
+{
+	u32 csr;
+
+	csr = in_be32(io_base + STARLET_IPC_CSR);
+	/* preserve interrupt masks */
+	csr &= STARLET_IPC_CSR_RBFIMASK | STARLET_IPC_CSR_TBEIMASK;
+	csr |= val;
+	out_be32(io_base + STARLET_IPC_CSR, csr);
+}
+
+/*
+ * Put data for starlet in the transmit fifo.
+ */
+static inline void starlet_ipc_sendto(void __iomem *io_base, u32 data)
+{
+	out_be32(io_base + STARLET_IPC_TXBUF, data);
+}
+
+/*
+ * Get data from starlet out the receive fifo.
+ */
+static inline u32 starlet_ipc_recvfrom(void __iomem *io_base)
+{
+	return in_be32(io_base + STARLET_IPC_RXBUF);
+}
+
+/*
+ * Issue an end-of-interrupt sequence.
+ */
+static void starlet_ipc_rx_ready(void __iomem *io_base)
+{
+	starlet_ipc_update_csr(io_base, STARLET_IPC_CSR_RXRDY);
+}
+
+/*
+ * Calm the hardware down.
+ */
+static void starlet_ipc_quiesce(struct starlet_ipc_device *ipc_dev)
+{
+	u32 csr;
+
+	/* ack and disable MBOX? and REPLY interrupts */
+	csr = in_be32(ipc_dev->io_base + STARLET_IPC_CSR);
+	csr &= ~(STARLET_IPC_CSR_TBEIMASK | STARLET_IPC_CSR_RBFIMASK);
+	csr |= STARLET_IPC_CSR_TBEI | STARLET_IPC_CSR_RBFI;
+	out_be32(ipc_dev->io_base + STARLET_IPC_CSR, csr);
+}
+
+/*
+ * Request routines.
+ *
+ */
+
+#if 0
+
+#define __case_string(_s)	\
+case _s:			\
+	str = #_s;		\
+	break;
+
+static char *stipc_cmd_string(u32 cmd)
+{
+	char *str = "unknown";
+
+	switch (cmd) {
+__case_string(STARLET_IOS_OPEN)
+__case_string(STARLET_IOS_CLOSE)
+__case_string(STARLET_IOS_IOCTL)
+__case_string(STARLET_IOS_IOCTLV)
+	}
+	return str;
+}
+
+static void starlet_ipc_pretty_print_request(struct starlet_ipc_request *req)
+{
+	drv_printk(KERN_INFO, "\n"
+		   " struct starlet_ipc_request = {\n"
+		   "     cmd = %s (0x%08x)\n"
+		   "     result = %d (0x%08x)%s\n"
+		   "     seconds_elapsed = %u\n"
+		   "     dma_addr = %p\n"
+		   " };\n"
+		   ,
+		   stipc_cmd_string(req->cmd), req->cmd,
+		   req->result, req->result,
+		   (req->result == 0xdeadbeef) ? " /* pending */" : "",
+		   jiffies_to_msecs(jiffies - req->jiffies) / 1000,
+		   (void *)req->dma_addr
+		   );
+}
+
+#endif
+
+static void starlet_ipc_debug_print_request(struct starlet_ipc_request *req)
+{
+#if 0
+	DBG("cmd=%x, result=%x, fd=%x, dma_addr=%p\n",
+	    req->cmd, req->result, req->fd, (void *)req->dma_addr);
+#endif
+}
+
+struct starlet_ipc_request *
+starlet_ipc_alloc_request(struct starlet_ipc_device *ipc_dev, gfp_t flags)
+{
+	struct starlet_ipc_request *req;
+	dma_addr_t dma_addr;
+
+	req = dma_pool_alloc(ipc_dev->dma_pool, flags, &dma_addr);
+	if (req) {
+		memset(req, 0, sizeof(*req));
+		req->ipc_dev = ipc_dev;
+		req->result = 0xdeadbeef;
+		req->sig = ipc_dev->random_id;
+		req->dma_addr = dma_addr;
+		INIT_LIST_HEAD(&req->node);
+	}
+	return req;
+}
+
+void starlet_ipc_free_request(struct starlet_ipc_request *req)
+{
+	dma_pool_free(req->ipc_dev->dma_pool, req, req->dma_addr);
+}
+
+static void starlet_ipc_start_request(struct starlet_ipc_request *req)
+{
+	struct starlet_ipc_device *ipc_dev = req->ipc_dev;
+	void __iomem *io_base = ipc_dev->io_base;
+	unsigned long flags;
+
+	starlet_ipc_debug_print_request(req);
+
+	spin_lock_irqsave(&ipc_dev->list_lock, flags);
+	list_add_tail(&req->node, &ipc_dev->outstanding_list);
+	ipc_dev->nr_outstanding++;
+	req->jiffies = jiffies;
+	spin_unlock_irqrestore(&ipc_dev->list_lock, flags);
+
+	starlet_ipc_sendto(io_base, (u32) req->dma_addr);
+	starlet_ipc_update_csr(io_base, STARLET_IPC_CSR_TXSTART);
+}
+
+static void starlet_ipc_complete_request(struct starlet_ipc_request *req)
+{
+	struct starlet_ipc_device *ipc_dev = req->ipc_dev;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ipc_dev->list_lock, flags);
+	list_del_init(&req->node);
+	ipc_dev->nr_outstanding--;
+	req->jiffies = 0;
+	spin_unlock_irqrestore(&ipc_dev->list_lock, flags);
+
+	starlet_ipc_debug_print_request(req);
+
+	/* per request completion callback */
+	if (req->complete)
+		req->complete(req);
+
+	/* async callback */
+	if (req->done)
+		req->done(req);
+}
+
+static void starlet_ipc_submit_request(struct starlet_ipc_request *req)
+{
+	struct starlet_ipc_device *ipc_dev = req->ipc_dev;
+	unsigned long flags;
+
+	if (test_and_set_bit(__TX_INUSE, &ipc_dev->flags)) {
+		spin_lock_irqsave(&ipc_dev->list_lock, flags);
+		list_add_tail(&req->node, &ipc_dev->pending_list);
+		ipc_dev->nr_pending++;
+		spin_unlock_irqrestore(&ipc_dev->list_lock, flags);
+	} else
+		starlet_ipc_start_request(req);
+}
+
+static struct starlet_ipc_request *
+starlet_ipc_find_request_by_bus_addr(struct starlet_ipc_device *ipc_dev,
+				     dma_addr_t	req_bus_addr)
+{
+	struct starlet_ipc_request *req;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ipc_dev->list_lock, flags);
+	list_for_each_entry(req, &ipc_dev->outstanding_list, node) {
+		if (req && req->sig != ipc_dev->random_id) {
+			drv_printk(KERN_ERR, "IPC trash detected\n");
+			/* leak memory, we can't safely use it */
+			ipc_dev->nr_outstanding = 0;
+			INIT_LIST_HEAD(&ipc_dev->outstanding_list);
+			INIT_LIST_HEAD(&req->node);
+			spin_unlock_irqrestore(&ipc_dev->list_lock, flags);
+			return NULL;
+		}
+		if (req && req_bus_addr == req->dma_addr) {
+			spin_unlock_irqrestore(&ipc_dev->list_lock, flags);
+			return req;
+		}
+	}
+	spin_unlock_irqrestore(&ipc_dev->list_lock, flags);
+	return NULL;
+}
+
+/*
+ * Interrupt handlers.
+ *
+ */
+
+/*
+ * Transmit Buffer Empty Interrupt dispatcher.
+ */
+static int starlet_ipc_dispatch_tbei(struct starlet_ipc_device *ipc_dev)
+{
+	void __iomem *io_base = ipc_dev->io_base;
+	struct starlet_ipc_request *req = NULL;
+	struct list_head *pending = &ipc_dev->pending_list;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ipc_dev->list_lock, flags);
+	if (!list_empty(pending)) {
+		req = list_entry(pending->next, struct starlet_ipc_request,
+				 node);
+		list_del_init(&req->node);
+		ipc_dev->nr_pending--;
+	}
+	spin_unlock_irqrestore(&ipc_dev->list_lock, flags);
+	if (req)
+		starlet_ipc_start_request(req);
+	else {
+		if (!test_and_clear_bit(__TX_INUSE, &ipc_dev->flags)) {
+			/* we get two consecutive TBEIs on reboot */
+			if (test_and_clear_bit(__REBOOT, &ipc_dev->flags)) {
+				req = ipc_dev->req;
+				ipc_dev->req = NULL;
+				if (req) {
+					req->result = 0;
+					starlet_ipc_complete_request(req);
+				}
+				starlet_ipc_rx_ready(io_base);
+			}
+		}
+	}
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * Receive Buffer Full Interrupt dispatcher.
+ */
+static int starlet_ipc_dispatch_rbfi(struct starlet_ipc_device *ipc_dev)
+{
+	void __iomem *io_base = ipc_dev->io_base;
+	struct starlet_ipc_request *req;
+	unsigned long req_bus_addr;
+
+	req_bus_addr = starlet_ipc_recvfrom(io_base);
+	if (!req_bus_addr)
+		return IRQ_NONE;
+
+	req = starlet_ipc_find_request_by_bus_addr(ipc_dev, req_bus_addr);
+	if (req)
+		starlet_ipc_complete_request(req);
+	else
+		drv_printk(KERN_WARNING, "unknown request, bus=%p\n",
+			   (void *)req_bus_addr);
+	starlet_ipc_rx_ready(io_base);
+	return IRQ_HANDLED;
+}
+
+typedef int (*ipc_handler_t) (struct starlet_ipc_device *);
+
+static int
+starlet_ipc_cond_dispatch_irq(struct starlet_ipc_device *ipc_dev,
+			      u32 irqmask, u32 irq, ipc_handler_t handler)
+{
+	void __iomem *io_base = ipc_dev->io_base;
+	u32 csr;
+	int retval = IRQ_NONE;
+
+	csr = in_be32(io_base + STARLET_IPC_CSR);
+	if ((csr & (irqmask | irq)) == (irqmask | irq)) {
+		/* early ack */
+		starlet_ipc_update_csr(io_base, irq);
+		out_be32(io_base + STARLET_IPC_ISR, 0x40000000); /* huh? */
+		retval = handler(ipc_dev);
+	}
+	return retval;
+}
+
+static irqreturn_t starlet_ipc_handler(int irq, void *data)
+{
+	struct starlet_ipc_device *ipc_dev = (struct starlet_ipc_device *)data;
+	int handled = 0;
+	int retval;
+
+	/* starlet acked a request */
+	retval = starlet_ipc_cond_dispatch_irq(ipc_dev,
+					       STARLET_IPC_CSR_TBEIMASK,
+					       STARLET_IPC_CSR_TBEI,
+					       starlet_ipc_dispatch_tbei);
+	if (retval == IRQ_HANDLED)
+		handled++;
+
+	/* starlet delivered a reply */
+	retval = starlet_ipc_cond_dispatch_irq(ipc_dev,
+					       STARLET_IPC_CSR_RBFIMASK,
+					       STARLET_IPC_CSR_RBFI,
+					       starlet_ipc_dispatch_rbfi);
+	if (retval == IRQ_HANDLED)
+		handled++;
+
+	if (!handled)
+		return IRQ_NONE;
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * IPC Calls.
+ *
+ */
+
+static int starlet_ipc_call_done(struct starlet_ipc_request *req)
+{
+	complete(req->done_data);
+	return 0;
+}
+
+static int starlet_ipc_call(struct starlet_ipc_request *req)
+{
+	DECLARE_COMPLETION(complete);
+
+	req->done_data = &complete;
+	req->done = starlet_ipc_call_done;
+	starlet_ipc_submit_request(req);
+	wait_for_completion(&complete);
+	return req->result;
+}
+
+static void starlet_ipc_call_nowait(struct starlet_ipc_request *req,
+				    starlet_ipc_callback_t callback, void *arg)
+{
+	req->done_data = arg;
+	req->done = callback;
+	starlet_ipc_submit_request(req);
+}
+
+static DEFINE_SPINLOCK(starlet_ipc_poll_lock);
+
+#define __spin_event_timeout(condition, usecs, result, __end_ts) \
+	for (__end_ts = get_tbl() + tb_ticks_per_usec * usecs;	\
+	     !(result = (condition)) && __end_ts - get_tbl() > 0;)
+
+static int __starlet_ipc_poll_req(struct starlet_ipc_request *req,
+				  unsigned long usecs)
+{
+	struct starlet_ipc_device *ipc_dev = req->ipc_dev;
+	unsigned long counter;
+	int result;
+
+	__spin_event_timeout(req->jiffies == 0 &&
+			     !test_bit(__REBOOT, &ipc_dev->flags),
+			     usecs, result, counter)
+		starlet_ipc_handler(0, ipc_dev);
+
+	if (!result)
+		req->result = -ETIME;
+
+	/* debug */
+	if (req->result < 0)
+		drv_printk(KERN_ERR, "%s: result %d\n", __func__,
+			   req->result);
+	return req->result;
+}
+
+static int starlet_ipc_call_polled(struct starlet_ipc_request *req,
+				   unsigned long usecs)
+{
+	unsigned long flags;
+	int error;
+
+	req->done = NULL;
+	spin_lock_irqsave(&starlet_ipc_poll_lock, flags);
+	starlet_ipc_submit_request(req);
+	error = __starlet_ipc_poll_req(req, usecs);
+	spin_unlock_irqrestore(&starlet_ipc_poll_lock, flags);
+	return error;
+}
+
+/*
+ *
+ * IOS High level interfaces.
+ */
+
+static struct starlet_ipc_device *starlet_ipc_device_instance;
+
+/**
+ *
+ */
+struct starlet_ipc_device *starlet_ipc_get_device(void)
+{
+	if (!starlet_ipc_device_instance)
+		drv_printk(KERN_ERR, "uninitialized device instance!\n");
+	return starlet_ipc_device_instance;
+}
+EXPORT_SYMBOL_GPL(starlet_ipc_get_device);
+
+/**
+ *
+ */
+static int _starlet_open(const char *pathname, int flags,
+			 gfp_t gfp_flags, int poll, unsigned long usecs)
+{
+#define STSD_OPEN_BUF_SIZE	64
+	static char open_buf[STSD_OPEN_BUF_SIZE]
+		    __attribute__ ((aligned(STARLET_IPC_DMA_ALIGN + 1)));
+	static DEFINE_MUTEX(open_buf_lock);
+
+	struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device();
+	struct starlet_ipc_request *req;
+	dma_addr_t dma_addr;
+	char *local_pathname = NULL;
+	size_t len;
+	int error = -ENOMEM;
+
+	if (!ipc_dev)
+		return -ENODEV;
+
+	req = starlet_ipc_alloc_request(ipc_dev, gfp_flags);
+	if (req) {
+		len = strlen(pathname) + 1;
+		if (len < sizeof(open_buf)) {
+			if (mutex_trylock(&open_buf_lock))
+				local_pathname = open_buf;
+		}
+		if (!local_pathname) {
+			local_pathname = starlet_kzalloc(len, gfp_flags);
+			if (!local_pathname) {
+				starlet_ipc_free_request(req);
+				return -ENOMEM;
+			}
+		}
+
+		strncpy(local_pathname, pathname, len-1);
+		local_pathname[len-1] = 0;
+		dma_addr = dma_map_single(ipc_dev->dev, local_pathname, len,
+					  DMA_TO_DEVICE);
+
+		req->cmd = STARLET_IOS_OPEN;
+		req->open.pathname = dma_addr;	/* bus address */
+		req->open.mode = flags;
+		error = (poll) ? starlet_ipc_call_polled(req, usecs) :
+				 starlet_ipc_call(req);
+
+		dma_unmap_single(ipc_dev->dev, dma_addr, len, DMA_TO_DEVICE);
+
+		if (local_pathname == open_buf)
+			mutex_unlock(&open_buf_lock);
+		else
+			starlet_kfree(local_pathname);
+
+		starlet_ipc_free_request(req);
+	}
+	if (error < 0)
+		DBG("%s: %s: error=%d (%x)\n", __func__, pathname,
+		    error, error);
+	return error;
+}
+
+int starlet_open(const char *pathname, int flags)
+{
+	return _starlet_open(pathname, flags, GFP_KERNEL, 0, 0);
+}
+EXPORT_SYMBOL_GPL(starlet_open);
+
+int starlet_open_polled(const char *pathname, int flags,
+			unsigned long usecs)
+{
+	return _starlet_open(pathname, flags, GFP_ATOMIC, 1, usecs);
+}
+EXPORT_SYMBOL_GPL(starlet_open_polled);
+
+/*
+ *
+ */
+static int _starlet_close(int fd, gfp_t gfp_flags, int poll,
+			  unsigned long usecs)
+{
+	struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device();
+	struct starlet_ipc_request *req;
+	int error = -ENOMEM;
+
+	if (!ipc_dev)
+		return -ENODEV;
+
+	req = starlet_ipc_alloc_request(ipc_dev, gfp_flags);
+	if (req) {
+		req->cmd = STARLET_IOS_CLOSE;
+		req->fd = fd;
+		error = (poll) ? starlet_ipc_call_polled(req, usecs) :
+				 starlet_ipc_call(req);
+		starlet_ipc_free_request(req);
+	}
+	return error;
+}
+
+int starlet_close(int fd)
+{
+	return _starlet_close(fd, GFP_KERNEL, 0, 0);
+}
+EXPORT_SYMBOL_GPL(starlet_close);
+
+int starlet_close_polled(int fd, unsigned long usecs)
+{
+	return _starlet_close(fd, GFP_ATOMIC, 1, usecs);
+}
+EXPORT_SYMBOL_GPL(starlet_close_polled);
+
+
+
+/*
+ * starlet_ioctl*
+ *
+ */
+
+static int starlet_ioctl_dma_complete(struct starlet_ipc_request *req)
+{
+	return 0;
+}
+
+/*
+ *
+ */
+int starlet_ioctl_dma_prepare(struct starlet_ipc_request *req,
+				  int fd, int request,
+				  dma_addr_t ibuf, size_t ilen,
+				  dma_addr_t obuf, size_t olen)
+{
+	struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device();
+
+	if (!ipc_dev)
+		return -ENODEV;
+
+	req->cmd = STARLET_IOS_IOCTL;
+	req->fd = fd;
+	req->ioctl.request = (u32) request;
+	req->ioctl.ibuf = ibuf;
+	req->ioctl.ilen = ilen;
+	req->ioctl.obuf = obuf;
+	req->ioctl.olen = olen;
+	req->complete = starlet_ioctl_dma_complete;
+
+	return 0;
+}
+
+/*
+ *
+ */
+int starlet_ioctl_dma(int fd, int request,
+			  dma_addr_t ibuf, size_t ilen,
+			  dma_addr_t obuf, size_t olen)
+{
+	struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device();
+	struct starlet_ipc_request *req;
+	int error;
+
+	req = starlet_ipc_alloc_request(ipc_dev, GFP_ATOMIC);
+	if (!req)
+		return -ENOMEM;
+
+	error = starlet_ioctl_dma_prepare(req, fd, request,
+					      ibuf, ilen,
+					      obuf, olen);
+	if (!error)
+		error = starlet_ipc_call(req);
+	starlet_ipc_free_request(req);
+
+	if (error)
+		DBG("%s: error=%d (%x)\n", __func__, error, error);
+	return error;
+}
+EXPORT_SYMBOL_GPL(starlet_ioctl_dma);
+
+/**
+ *
+ */
+int starlet_ioctl_dma_nowait(int fd, int request,
+				 dma_addr_t ibuf, size_t ilen,
+				 dma_addr_t obuf, size_t olen,
+				 starlet_ipc_callback_t callback, void *arg)
+{
+	struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device();
+	struct starlet_ipc_request *req;
+	int error;
+
+	req = starlet_ipc_alloc_request(ipc_dev, GFP_ATOMIC);
+	if (!req)
+		return -ENOMEM;
+
+	error = starlet_ioctl_dma_prepare(req,
+					      fd, request,
+					      ibuf, ilen,
+					      obuf, olen);
+	if (!error)
+		starlet_ipc_call_nowait(req, callback, arg);
+	else
+		starlet_ipc_free_request(req);
+
+	if (error)
+		DBG("%s: error=%d (%x)\n", __func__, error, error);
+	return error;
+}
+EXPORT_SYMBOL_GPL(starlet_ioctl_dma_nowait);
+
+static int starlet_ioctl_complete(struct starlet_ipc_request *req)
+{
+	struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device();
+	dma_addr_t ibuf_ba, obuf_ba;
+	size_t ilen, olen;
+
+	ibuf_ba = req->ioctl.ibuf;
+	ilen = req->ioctl.ilen;
+	obuf_ba = req->ioctl.obuf;
+	olen = req->ioctl.olen;
+
+	if (ibuf_ba)
+		dma_unmap_single(ipc_dev->dev, ibuf_ba, ilen, DMA_TO_DEVICE);
+	if (obuf_ba)
+		dma_unmap_single(ipc_dev->dev, obuf_ba, olen, DMA_FROM_DEVICE);
+
+	return 0;
+}
+
+/*
+ *
+ */
+int starlet_ioctl_prepare(struct starlet_ipc_request *req,
+			      int fd,  int request,
+			      void *ibuf, size_t ilen,
+			      void *obuf, size_t olen)
+{
+	struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device();
+	dma_addr_t ibuf_ba, obuf_ba;
+	int error;
+
+	if (!ipc_dev)
+		return -ENODEV;
+
+	BUG_ON(!IS_ALIGNED((unsigned long)ibuf, STARLET_IPC_DMA_ALIGN+1));
+	BUG_ON(!IS_ALIGNED((unsigned long)obuf, STARLET_IPC_DMA_ALIGN+1));
+
+	ibuf_ba = (ibuf) ? dma_map_single(ipc_dev->dev, ibuf, ilen,
+					  DMA_TO_DEVICE) : 0;
+	obuf_ba = (obuf) ? dma_map_single(ipc_dev->dev, obuf, olen,
+					  DMA_FROM_DEVICE) : 0;
+
+	error = starlet_ioctl_dma_prepare(req, fd, request,
+					      ibuf_ba, ilen, obuf_ba, olen);
+	if (!error) {
+		req->complete = starlet_ioctl_complete;
+		if (ibuf)
+			dma_unmap_single(ipc_dev->dev, ibuf_ba, ilen,
+					 DMA_TO_DEVICE);
+		if (obuf)
+			dma_unmap_single(ipc_dev->dev, obuf_ba, olen,
+					 DMA_FROM_DEVICE);
+	}
+	return error;
+}
+
+/**
+ *
+ */
+static int _starlet_ioctl(int fd, int request,
+			  void *ibuf, size_t ilen,
+			  void *obuf, size_t olen,
+			  int poll, unsigned long usecs)
+{
+	struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device();
+	struct starlet_ipc_request *req;
+	int error;
+
+	req = starlet_ipc_alloc_request(ipc_dev, GFP_ATOMIC);
+	if (!req)
+		return -ENOMEM;
+
+	error = starlet_ioctl_prepare(req, fd, request,
+					  ibuf, ilen, obuf, olen);
+	if (!error)
+		error = (poll) ? starlet_ipc_call_polled(req, usecs) :
+				 starlet_ipc_call(req);
+	starlet_ipc_free_request(req);
+
+	if (error < 0)
+		DBG("%s: error=%d (%x)\n", __func__, error, error);
+	return error;
+}
+
+int starlet_ioctl(int fd, int request,
+		  void *ibuf, size_t ilen,
+		  void *obuf, size_t olen)
+{
+	return _starlet_ioctl(fd, request, ibuf, ilen, obuf, olen, 0, 0);
+}
+EXPORT_SYMBOL_GPL(starlet_ioctl);
+
+int starlet_ioctl_polled(int fd, int request,
+			 void *ibuf, size_t ilen,
+			 void *obuf, size_t olen,
+			 unsigned long usecs)
+{
+	return _starlet_ioctl(fd, request, ibuf, ilen, obuf, olen, 1, usecs);
+}
+EXPORT_SYMBOL_GPL(starlet_ioctl_polled);
+
+/**
+ *
+ */
+int starlet_ioctl_nowait(int fd, int request,
+			     void *ibuf, size_t ilen,
+			     void *obuf, size_t olen,
+			     starlet_ipc_callback_t callback, void *arg)
+{
+	struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device();
+	struct starlet_ipc_request *req;
+	int error;
+
+	req = starlet_ipc_alloc_request(ipc_dev, GFP_ATOMIC);
+	if (!req)
+		return -ENOMEM;
+
+	error = starlet_ioctl_prepare(req, fd, request,
+					  ibuf, ilen, obuf, olen);
+	if (!error)
+		starlet_ipc_call_nowait(req, callback, arg);
+	else
+		starlet_ipc_free_request(req);
+
+	if (error)
+		DBG("%s: error=%d (%x)\n", __func__, error, error);
+	return error;
+}
+EXPORT_SYMBOL_GPL(starlet_ioctl_nowait);
+
+
+static int starlet_ioctlv_complete(struct starlet_ipc_request *req)
+{
+	struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device();
+	struct starlet_iovec *iovec = req->iovec;
+	dma_addr_t iovec_da = req->ioctlv.iovec_da;
+	size_t iovec_size = req->iovec_size;
+
+#if 0
+	unsigned int nents;
+	DBG("%s: nents_in=%u, nents_io=%u\n", __func__,
+	    req->sgl_nents_in, req->sgl_nents_io);
+#endif
+
+	if (req->sgl_nents_in > 0)
+		dma_unmap_sg(ipc_dev->dev, req->sgl_in, req->sgl_nents_in,
+			     DMA_TO_DEVICE);
+	if (req->sgl_nents_io > 0)
+		dma_unmap_sg(ipc_dev->dev, req->sgl_io, req->sgl_nents_io,
+			     DMA_BIDIRECTIONAL);
+	if (iovec) {
+		dma_unmap_single(ipc_dev->dev,
+				 iovec_da, iovec_size, DMA_TO_DEVICE);
+
+#if 0
+		struct starlet_iovec *p;
+		p = iovec;
+		nents = req->sgl_nents_in;
+		while (nents--) {
+			DBG("%s: in: dma_addr=%p, dma_len=%u\n", __func__,
+				(void *)p->dma_addr, p->dma_len);
+			p++;
+		}
+		nents = req->sgl_nents_io;
+		while (nents--) {
+			DBG("%s: io: dma_addr=%p, dma_len=%u\n", __func__,
+				(void *)p->dma_addr, p->dma_len);
+			p++;
+		}
+#endif
+
+		starlet_kfree(iovec);
+	}
+	return 0;
+}
+
+/*
+ *
+ */
+int starlet_ioctlv_prepare(struct starlet_ipc_request *req,
+			       int fd, int request,
+			       unsigned int nents_in,
+			       struct scatterlist *sgl_in,
+			       unsigned int nents_io,
+			       struct scatterlist *sgl_io)
+{
+	struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device();
+	struct starlet_iovec *iovec, *p;
+	dma_addr_t iovec_da = 0;
+	size_t iovec_size = 0;
+	struct scatterlist *sg;
+	unsigned int nents, i;
+
+	if (!ipc_dev)
+		return -ENODEV;
+
+	BUG_ON(nents_in > 0 && !sgl_in);
+	BUG_ON(nents_io > 0 && !sgl_io);
+
+	nents  = nents_in + nents_io;
+	if (nents > 0) {
+		iovec_size = nents * sizeof(*iovec);
+		iovec = starlet_kzalloc(iovec_size, GFP_ATOMIC);
+		if (!iovec)
+			return -ENOMEM;
+	} else {
+		iovec = NULL;
+	}
+
+	p = iovec;
+	if (nents_in > 0) {
+		nents_in = dma_map_sg(ipc_dev->dev, sgl_in, nents_in,
+				      DMA_TO_DEVICE);
+		for_each_sg(sgl_in, sg, nents_in, i) {
+#if 0
+			DBG("%s: in: dma_addr=%p, dma_len=%u\n", __func__,
+				(void *)sg_dma_address(sg), sg_dma_len(sg));
+#endif
+			p->dma_addr = sg_dma_address(sg);
+			p->dma_len = sg_dma_len(sg);
+			p++;
+		}
+	}
+	if (nents_io > 0) {
+		nents_io = dma_map_sg(ipc_dev->dev, sgl_io, nents_io,
+				      DMA_BIDIRECTIONAL);
+		for_each_sg(sgl_io, sg, nents_io, i) {
+#if 0
+			DBG("%s: io: dma_addr=%p, dma_len=%u\n", __func__,
+				(void *)sg_dma_address(sg), sg_dma_len(sg));
+#endif
+			p->dma_addr = sg_dma_address(sg);
+			p->dma_len = sg_dma_len(sg);
+			p++;
+		}
+	}
+
+	if (iovec)
+		iovec_da = dma_map_single(ipc_dev->dev,
+					  iovec, iovec_size,
+					  DMA_TO_DEVICE);
+
+	req->iovec = iovec;
+	req->iovec_size = iovec_size;
+	req->sgl_nents_in = nents_in;
+	req->sgl_in = sgl_in;
+	req->sgl_nents_io = nents_io;
+	req->sgl_io = sgl_io;
+
+	req->cmd = STARLET_IOS_IOCTLV;
+	req->fd = fd;
+	req->ioctlv.request = request;
+	req->ioctlv.argc_in = nents_in;
+	req->ioctlv.argc_io = nents_io;
+	req->ioctlv.iovec_da = iovec_da;
+	req->complete = starlet_ioctlv_complete;
+
+#if 0
+		DBG("%s: fd=%d, request=%d,"
+		    " argc_in=%u, argc_io=%u, iovec_da=%08x\n" ,  __func__,
+				req->fd, req->ioctlv.request,
+				req->ioctlv.argc_in, req->ioctlv.argc_io,
+				req->ioctlv.iovec_da);
+#endif
+	return 0;
+}
+
+/**
+ *
+ */
+static int _starlet_ioctlv(int fd, int request,
+			   unsigned int nents_in,
+			   struct scatterlist *sgl_in,
+			   unsigned int nents_io,
+			   struct scatterlist *sgl_io,
+			   int poll, unsigned long usecs)
+{
+	struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device();
+	struct starlet_ipc_request *req;
+	int error;
+
+	req = starlet_ipc_alloc_request(ipc_dev, GFP_ATOMIC);
+	if (!req)
+		return -ENOMEM;
+
+	error = starlet_ioctlv_prepare(req, fd, request,
+					   nents_in, sgl_in,
+					   nents_io, sgl_io);
+	if (!error)
+		error = (poll) ? starlet_ipc_call_polled(req, usecs) :
+				 starlet_ipc_call(req);
+	starlet_ipc_free_request(req);
+
+	if (error < 0)
+		DBG("%s: error=%d (%x)\n", __func__, error, error);
+	return error;
+}
+
+int starlet_ioctlv(int fd, int request,
+		       unsigned int nents_in,
+		       struct scatterlist *sgl_in,
+		       unsigned int nents_io,
+		       struct scatterlist *sgl_io)
+{
+	return _starlet_ioctlv(fd, request,
+			       nents_in, sgl_in, nents_io, sgl_io, 0, 0);
+}
+EXPORT_SYMBOL_GPL(starlet_ioctlv);
+
+int starlet_ioctlv_polled(int fd, int request,
+			  unsigned int nents_in,
+			  struct scatterlist *sgl_in,
+			  unsigned int nents_io,
+			  struct scatterlist *sgl_io,
+			  unsigned long usecs)
+{
+	return _starlet_ioctlv(fd, request,
+			       nents_in, sgl_in, nents_io, sgl_io, 1, usecs);
+}
+EXPORT_SYMBOL_GPL(starlet_ioctlv_polled);
+
+/**
+ *
+ */
+int starlet_ioctlv_nowait(int fd, int request,
+			      unsigned int nents_in,
+			      struct scatterlist *sgl_in,
+			      unsigned int nents_io,
+			      struct scatterlist *sgl_io,
+			      starlet_ipc_callback_t callback, void *arg)
+{
+	struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device();
+	struct starlet_ipc_request *req;
+	int error;
+
+	req = starlet_ipc_alloc_request(ipc_dev, GFP_ATOMIC);
+	if (!req)
+		return -ENOMEM;
+
+	error = starlet_ioctlv_prepare(req, fd, request,
+					   nents_in, sgl_in,
+					   nents_io, sgl_io);
+	if (!error)
+		starlet_ipc_call_nowait(req, callback, arg);
+	else
+		starlet_ipc_free_request(req);
+
+	if (error < 0)
+		DBG("%s: error=%d (%x)\n", __func__, error, error);
+	return error;
+}
+EXPORT_SYMBOL_GPL(starlet_ioctlv_nowait);
+
+/**
+ *
+ */
+int starlet_ioctlv_and_reboot(int fd, int request,
+			      unsigned int nents_in,
+			      struct scatterlist *sgl_in,
+			      unsigned int nents_io,
+			      struct scatterlist *sgl_io)
+{
+	struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device();
+	struct starlet_ipc_request *req;
+	int error;
+
+	req = starlet_ipc_alloc_request(ipc_dev, GFP_ATOMIC);
+	if (!req)
+		return -ENOMEM;
+
+	error = starlet_ioctlv_prepare(req, fd, request,
+				       nents_in, sgl_in,
+				       nents_io, sgl_io);
+	if (!error) {
+		ipc_dev->req = req;
+		set_bit(__REBOOT, &ipc_dev->flags);
+		error = starlet_ipc_call_polled(req, 10000000 /* usecs */);
+	}
+	starlet_ipc_free_request(req);
+
+	if (error < 0)
+		DBG("%s: error=%d (%x)\n", __func__, error, error);
+	return error;
+}
+EXPORT_SYMBOL_GPL(starlet_ioctlv_and_reboot);
+
+/*
+ * starlet_ioh_ioctlv*
+ *
+ */
+
+static int starlet_ioh_ioctlv_complete(struct starlet_ipc_request *req)
+{
+	struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device();
+	struct starlet_iovec *iovec = req->iovec;
+	dma_addr_t iovec_da = req->ioctlv.iovec_da;
+	size_t iovec_size = req->iovec_size;
+
+#if 0
+	unsigned int nents;
+	DBG("%s: nents_in=%u, nents_io=%u\n", __func__,
+	    req->sgl_nents_in, req->sgl_nents_io);
+#endif
+
+	if (req->sgl_nents_in > 0)
+		starlet_ioh_dma_unmap_sg(ipc_dev->dev,
+					 req->ioh_sgl_in, req->sgl_nents_in,
+					 DMA_TO_DEVICE);
+	if (req->sgl_nents_io > 0)
+		starlet_ioh_dma_unmap_sg(ipc_dev->dev,
+					 req->ioh_sgl_io, req->sgl_nents_io,
+					 DMA_BIDIRECTIONAL);
+	if (iovec) {
+		dma_unmap_single(ipc_dev->dev,
+				 iovec_da, iovec_size, DMA_TO_DEVICE);
+
+		{ /* begin debug */
+#if 0
+		struct starlet_iovec *p;
+		p = iovec;
+		nents = req->sgl_nents_in;
+		while (nents--) {
+			DBG("%s: in: dma_addr=%p, dma_len=%u\n", __func__,
+				(void *)p->dma_addr, p->dma_len);
+			p++;
+		}
+		nents = req->sgl_nents_io;
+		while (nents--) {
+			DBG("%s: io: dma_addr=%p, dma_len=%u\n", __func__,
+				(void *)p->dma_addr, p->dma_len);
+			p++;
+		}
+#endif
+		} /* end debug */
+
+		starlet_kfree(iovec);
+	}
+	return 0;
+}
+
+/*
+ *
+ */
+int starlet_ioh_ioctlv_prepare(struct starlet_ipc_request *req,
+			       int fd, int request,
+			       unsigned int nents_in,
+			       struct starlet_ioh_sg *ioh_sgl_in,
+			       unsigned int nents_io,
+			       struct starlet_ioh_sg *ioh_sgl_io)
+{
+	struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device();
+	struct starlet_iovec *iovec, *p;
+	dma_addr_t iovec_da = 0;
+	size_t iovec_size = 0;
+	struct starlet_ioh_sg *ioh_sg;
+	unsigned int nents, i;
+
+	if (!ipc_dev)
+		return -ENODEV;
+
+	BUG_ON(nents_in > 0 && !ioh_sgl_in);
+	BUG_ON(nents_io > 0 && !ioh_sgl_io);
+
+	nents  = nents_in + nents_io;
+	if (nents > 0) {
+		iovec_size = nents * sizeof(*iovec);
+		iovec = starlet_kzalloc(iovec_size, GFP_ATOMIC);
+		if (!iovec)
+			return -ENOMEM;
+	} else {
+		iovec = NULL;
+	}
+
+	p = iovec;
+	if (nents_in > 0) {
+		nents_in = starlet_ioh_dma_map_sg(ipc_dev->dev,
+						  ioh_sgl_in, nents_in,
+						  DMA_TO_DEVICE);
+		starlet_ioh_for_each_sg(ioh_sgl_in, ioh_sg, nents_in, i) {
+#if 0
+			DBG("%s: in: dma_addr=%p, dma_len=%u\n", __func__,
+				(void *)ioh_sg->dma_addr, ioh_sg->len);
+#endif
+			p->dma_addr = ioh_sg->dma_addr;
+			p->dma_len = ioh_sg->len;
+			p++;
+		}
+	}
+	if (nents_io > 0) {
+		nents_io = starlet_ioh_dma_map_sg(ipc_dev->dev,
+						  ioh_sgl_io, nents_io,
+						  DMA_BIDIRECTIONAL);
+		starlet_ioh_for_each_sg(ioh_sgl_io, ioh_sg, nents_io, i) {
+#if 0
+			DBG("%s: io: dma_addr=%p, dma_len=%u\n", __func__,
+				(void *)ioh_sg->dma_addr, ioh_sg->len);
+#endif
+			p->dma_addr = ioh_sg->dma_addr;
+			p->dma_len = ioh_sg->len;
+			p++;
+		}
+	}
+
+	if (iovec)
+		iovec_da = dma_map_single(ipc_dev->dev,
+					  iovec, iovec_size,
+					  DMA_TO_DEVICE);
+
+	req->iovec = iovec;
+	req->iovec_size = iovec_size;
+	req->sgl_nents_in = nents_in;
+	req->ioh_sgl_in = ioh_sgl_in;
+	req->sgl_nents_io = nents_io;
+	req->ioh_sgl_io = ioh_sgl_io;
+
+	req->cmd = STARLET_IOS_IOCTLV;
+	req->fd = fd;
+	req->ioctlv.request = request;
+	req->ioctlv.argc_in = nents_in;
+	req->ioctlv.argc_io = nents_io;
+	req->ioctlv.iovec_da = iovec_da;
+	req->complete = starlet_ioh_ioctlv_complete;
+
+	return 0;
+}
+
+/**
+ *
+ */
+int starlet_ioh_ioctlv(int fd, int request,
+		       unsigned int nents_in,
+		       struct starlet_ioh_sg *ioh_sgl_in,
+		       unsigned int nents_io,
+		       struct starlet_ioh_sg *ioh_sgl_io)
+{
+	struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device();
+	struct starlet_ipc_request *req;
+	int error;
+
+	req = starlet_ipc_alloc_request(ipc_dev, GFP_ATOMIC);
+	if (!req)
+		return -ENOMEM;
+
+	error = starlet_ioh_ioctlv_prepare(req, fd, request,
+					   nents_in, ioh_sgl_in,
+					   nents_io, ioh_sgl_io);
+	if (!error)
+		error = starlet_ipc_call(req);
+	starlet_ipc_free_request(req);
+
+	if (error < 0)
+		DBG("%s: error=%d (%x)\n", __func__, error, error);
+	return error;
+}
+EXPORT_SYMBOL_GPL(starlet_ioh_ioctlv);
+
+/**
+ *
+ */
+int starlet_ioh_ioctlv_nowait(int fd, int request,
+			      unsigned int nents_in,
+			       struct starlet_ioh_sg *ioh_sgl_in,
+			       unsigned int nents_io,
+			       struct starlet_ioh_sg *ioh_sgl_io,
+			      starlet_ipc_callback_t callback, void *arg)
+{
+	struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device();
+	struct starlet_ipc_request *req;
+	int error;
+
+	req = starlet_ipc_alloc_request(ipc_dev, GFP_ATOMIC);
+	if (!req)
+		return -ENOMEM;
+
+	error = starlet_ioh_ioctlv_prepare(req, fd, request,
+					   nents_in, ioh_sgl_in,
+					   nents_io, ioh_sgl_io);
+	if (!error)
+		starlet_ipc_call_nowait(req, callback, arg);
+	else
+		starlet_ipc_free_request(req);
+
+	if (error < 0)
+		DBG("%s: error=%d (%x)\n", __func__, error, error);
+	return error;
+}
+EXPORT_SYMBOL_GPL(starlet_ioh_ioctlv_nowait);
+
+
+/*
+ * This "watchdog" code may be used to detect misbehaving requests.
+ *
+ * Note that some requests can take a lot of time to complete.
+ * For example, a keyboard event, which is delivered every time a key is
+ * pressed or released (or a keyboard is connected/disconnected), may take an
+ * arbitrary amount of time to arrive.
+ *
+ */
+
+#define STARLET_IPC_WATCHDOG_TIME (60 * HZ)
+
+static void starlet_ipc_watchdog(unsigned long arg)
+{
+#if 0
+	struct starlet_ipc_device *ipc_dev = (struct starlet_ipc_device *)arg;
+	struct starlet_ipc_request *req;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ipc_dev->list_lock, flags);
+	list_for_each_entry(req, &ipc_dev->outstanding_list, node) {
+		if (req &&
+		    time_after(jiffies,
+			       req->jiffies + STARLET_IPC_WATCHDOG_TIME)) {
+			drv_printk(KERN_INFO, "request on the outstanding"
+				   " list for too long\n");
+			starlet_ipc_pretty_print_request(req);
+		}
+	}
+	spin_unlock_irqrestore(&ipc_dev->list_lock, flags);
+
+	mod_timer(&ipc_dev->timer, jiffies + STARLET_IPC_WATCHDOG_TIME);
+#endif
+}
+
+/*
+ * Setup routines.
+ *
+ */
+
+/*
+ * Place here any desired hardware cleanups while drivers get written.
+ */
+static void starlet_fixups(void)
+{
+	static u32 buf[8]
+	    __attribute__ ((aligned(STARLET_IPC_DMA_ALIGN + 1)));
+	struct scatterlist in[6], io[1];
+	int fd;
+	void *gpio;
+
+	/* close any open file descriptors, just in case */
+	for (fd = 0; fd < 24; fd++)
+		starlet_close(fd);
+
+	/*
+	 * Hey! We are super-green. And you?
+	 */
+
+	/* try to stop the dvd unit motor */
+	fd = starlet_open("/dev/di", 0);
+	if (fd >= 0) {
+		buf[0] = 0xe3000000; /* stop motor command */
+		buf[1] = 0;
+		buf[2] = 0;
+		starlet_ioctl(fd, buf[0],
+				  buf, sizeof(buf),
+				  buf, sizeof(buf));
+		starlet_close(fd);
+	}
+
+	/* try to disconnect the wiimote */
+	fd = starlet_open("/dev/usb/oh1/57e/305", 2);
+	if (fd >= 0) {
+		/*
+		 * This assumes big endianness and 4 byte dma alignment.
+		 */
+		buf[0] = 0x20000000; /* bmRequestType	0x20 */
+		buf[1] = 0x00000000; /* bRequest	0x00 */
+		buf[2] = 0x00000000; /* wValue		0x00, 0x00 */
+		buf[3] = 0x00000000; /* wIndex		0x00, 0x00 */
+		buf[4] = 0x03000000; /* wLength		0x03, 0x00 */
+		buf[5] = 0x00000000; /* timeout?	0x00 */
+		buf[6] = 0x030c0000; /* payload		0x03, 0x0c, 0x00 */
+		sg_init_table(in, 6);
+		sg_set_buf(&in[0], &buf[0], 1);
+		sg_set_buf(&in[1], &buf[1], 1);
+		sg_set_buf(&in[2], &buf[2], 2);
+		sg_set_buf(&in[3], &buf[3], 2);
+		sg_set_buf(&in[4], &buf[4], 2);
+		sg_set_buf(&in[5], &buf[5], 1);
+		sg_init_table(io, 1);
+		sg_set_buf(&io[0], &buf[6], 3);
+		starlet_ioctlv(fd, 0, 6, in, 1, io);
+		starlet_close(fd);
+	}
+
+	/*
+	 * Try to turn off the front led and sensor bar.
+	 * (not strictly starlet-only stuff but anyway...)
+	 */
+	gpio = ioremap(0x0d8000c0, 4);
+	if (gpio) {
+		out_be32(gpio, in_be32(gpio) & ~0x120);
+		iounmap(gpio);
+	}
+}
+
+static int starlet_ipc_init(struct starlet_ipc_device *ipc_dev,
+			    struct resource *mem, int irq)
+{
+	size_t size, io_size;
+	int error;
+
+	ipc_dev->random_id = get_random_int();
+
+	error = starlet_malloc_lib_bootstrap(&mem[1]);
+	if (error)
+		return error;
+
+	io_size = mem[0].end - mem[0].start + 1;
+	ipc_dev->io_base = ioremap(mem[0].start, io_size);
+	ipc_dev->irq = irq;
+
+	size = max((size_t)64, sizeof(struct starlet_ipc_request));
+	ipc_dev->dma_pool = dma_pool_create(DRV_MODULE_NAME,
+					    ipc_dev->dev,
+					    size, STARLET_IPC_DMA_ALIGN + 1, 0);
+	if (!ipc_dev->dma_pool) {
+		drv_printk(KERN_ERR, "dma_pool_create failed\n");
+		iounmap(ipc_dev->io_base);
+		return -ENOMEM;
+	}
+	spin_lock_init(&ipc_dev->list_lock);
+	INIT_LIST_HEAD(&ipc_dev->pending_list);
+	INIT_LIST_HEAD(&ipc_dev->outstanding_list);
+
+	starlet_ipc_device_instance = ipc_dev;
+
+	init_timer(&ipc_dev->timer);
+	ipc_dev->timer.function = starlet_ipc_watchdog;
+	ipc_dev->timer.data = (unsigned long)ipc_dev;
+	ipc_dev->timer.expires = jiffies + STARLET_IPC_WATCHDOG_TIME;
+	add_timer(&ipc_dev->timer);
+
+	error = request_irq(ipc_dev->irq, starlet_ipc_handler, 0,
+			    DRV_MODULE_NAME, ipc_dev);
+	if (error) {
+		drv_printk(KERN_ERR, "request of IRQ %d failed\n", irq);
+		starlet_ipc_device_instance = NULL;
+		dma_pool_destroy(ipc_dev->dma_pool);
+		iounmap(ipc_dev->io_base);
+		return error;
+	}
+
+	/* ack and enable RBFI and TBEI interrupts */
+	out_be32(ipc_dev->io_base + STARLET_IPC_CSR,
+		 STARLET_IPC_CSR_TBEIMASK | STARLET_IPC_CSR_RBFIMASK |
+		 STARLET_IPC_CSR_TBEI | STARLET_IPC_CSR_RBFI);
+
+	starlet_fixups();
+
+	return error;
+}
+
+static void starlet_ipc_exit(struct starlet_ipc_device *ipc_dev)
+{
+	starlet_ipc_device_instance = NULL;
+	starlet_ipc_quiesce(ipc_dev);
+
+	del_timer(&ipc_dev->timer);
+
+	free_irq(ipc_dev->irq, ipc_dev);
+	dma_pool_destroy(ipc_dev->dma_pool);
+	iounmap(ipc_dev->io_base);
+	ipc_dev->io_base = NULL;
+}
+
+
+/*
+ * Driver model helper routines.
+ *
+ */
+
+static int starlet_ipc_do_probe(struct device *dev, struct resource *mem,
+				int irq)
+{
+	struct starlet_ipc_device *ipc_dev;
+	int retval;
+
+	if (starlet_get_ipc_flavour() != STARLET_IPC_IOS)
+		return -ENODEV;
+
+	ipc_dev = kzalloc(sizeof(*ipc_dev), GFP_KERNEL);
+	if (!ipc_dev) {
+		drv_printk(KERN_ERR, "failed to allocate ipc_dev\n");
+		return -ENOMEM;
+	}
+	dev_set_drvdata(dev, ipc_dev);
+	ipc_dev->dev = dev;
+
+	retval = starlet_ipc_init(ipc_dev, mem, irq);
+	if (retval) {
+		dev_set_drvdata(dev, NULL);
+		kfree(ipc_dev);
+	}
+	return retval;
+}
+
+static int starlet_ipc_do_remove(struct device *dev)
+{
+	struct starlet_ipc_device *ipc_dev = dev_get_drvdata(dev);
+
+	if (ipc_dev) {
+		starlet_ipc_exit(ipc_dev);
+		dev_set_drvdata(dev, NULL);
+		kfree(ipc_dev);
+		return 0;
+	}
+	return -ENODEV;
+}
+
+static int starlet_ipc_do_shutdown(struct device *dev)
+{
+	struct starlet_ipc_device *ipc_dev = dev_get_drvdata(dev);
+
+	if (ipc_dev) {
+		/*
+		 * We can't shutdown IPC as we need it to reboot the
+		 * machine.
+		 * Thus, no starlet_ipc_quiesce(ipc_dev); here, sorry.
+		 */
+		return 0;
+	}
+	return -ENODEV;
+}
+
+/*
+ * OF platform driver hooks.
+ *
+ */
+
+static int starlet_ipc_of_probe(struct of_device *odev,
+				const struct of_device_id *dev_id)
+{
+	struct resource mem[2];
+	int error;
+
+	error = of_address_to_resource(odev->node, 0, &mem[0]);
+	if (error) {
+		drv_printk(KERN_ERR, "no io memory range found\n");
+		return -ENODEV;
+	}
+	error = of_address_to_resource(odev->node, 1, &mem[1]);
+	if (error) {
+		drv_printk(KERN_ERR, "missing ioh memory area (%d)\n", error);
+		return -ENODEV;
+	}
+
+	return starlet_ipc_do_probe(&odev->dev, mem,
+				    irq_of_parse_and_map(odev->node, 0));
+}
+
+static int starlet_ipc_of_remove(struct of_device *odev)
+{
+	return starlet_ipc_do_remove(&odev->dev);
+}
+
+static int starlet_ipc_of_shutdown(struct of_device *odev)
+{
+	return starlet_ipc_do_shutdown(&odev->dev);
+}
+
+static struct of_device_id starlet_ipc_of_match[] = {
+	{ .compatible = "nintendo,starlet-ios-ipc" },
+	{ },
+};
+
+MODULE_DEVICE_TABLE(of, starlet_ipc_of_match);
+
+static struct of_platform_driver starlet_ipc_of_driver = {
+	.owner = THIS_MODULE,
+	.name = DRV_MODULE_NAME,
+	.match_table = starlet_ipc_of_match,
+	.probe = starlet_ipc_of_probe,
+	.remove = starlet_ipc_of_remove,
+	.shutdown = starlet_ipc_of_shutdown,
+};
+
+/*
+ * Kernel module interface hooks.
+ *
+ */
+
+static int __init starlet_ipc_init_module(void)
+{
+	drv_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION,
+		   starlet_ipc_driver_version);
+
+	return of_register_platform_driver(&starlet_ipc_of_driver);
+}
+
+static void __exit starlet_ipc_exit_module(void)
+{
+	of_unregister_platform_driver(&starlet_ipc_of_driver);
+}
+
+module_init(starlet_ipc_init_module);
+module_exit(starlet_ipc_exit_module);
+
+MODULE_DESCRIPTION(DRV_DESCRIPTION);
+MODULE_AUTHOR(DRV_AUTHOR);
+MODULE_LICENSE("GPL");
+
diff --git a/arch/powerpc/platforms/embedded6xx/starlet-malloc.c b/arch/powerpc/platforms/embedded6xx/starlet-malloc.c
new file mode 100644
index 0000000..3985898
--- /dev/null
+++ b/arch/powerpc/platforms/embedded6xx/starlet-malloc.c
@@ -0,0 +1,365 @@
+/*
+ * arch/powerpc/platforms/embedded6xx/starlet-malloc.c
+ *
+ * Nintendo Wii starlet memory allocation library
+ * Copyright (C) 2008-2009 The GameCube Linux Team
+ * Copyright (C) 2008,2009 Albert Herranz
+ *
+ * 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.
+ *
+ */
+
+/*
+ * Notes from the trenches:
+ *
+ * writes from broadway to mem2
+ * - 8 or 16 bit writes to mem2 modify 64 bits
+ *   - writing 0xaa results in 0xaaffffffaaffffff being written
+ *   - writing 0xaabb results in 0xaabbffffaabbffff being written
+ * - 32 bit writes work fine
+ * writes from starlet to mem1
+ * - data must be 4 byte aligned, length must be 4 byte aligned
+ *
+ * write protected area (reads after writes do not return written info)
+ * 0x13620000 - 0x14000000
+ *
+ */
+
+#define DEBUG
+
+#define DBG(fmt, arg...)	pr_debug(fmt, ##arg)
+
+#include <linux/kernel.h>
+#include <linux/resource.h>
+#include <asm/starlet-ios.h>
+
+
+#define LIB_MODULE_NAME		"starlet-malloc"
+#define LIB_DESCRIPTION		"Nintendo Wii starlet malloc library"
+#define LIB_AUTHOR		"Albert Herranz"
+
+static char starlet_malloc_lib_version[] = "0.1i";
+
+#define drv_printk(level, format, arg...) \
+	 printk(level LIB_MODULE_NAME ": " format , ## arg)
+
+
+#define STARLET_IOH_ALIGN	31
+
+/*
+ * Simple aligned kzalloc and free.
+ *
+ * Based on the idea proposed by Satya Kiran Popuri
+ * http://www.cs.uic.edu/~spopuri/amalloc.html
+ */
+
+/**
+ *
+ */
+static void *kzalloc_aligned(size_t size, gfp_t flags, size_t align)
+{
+	void *ptr, *aligned_ptr;
+	size_t aligned_size;
+
+	/* not a power of two */
+	if (align & (align - 1))
+		return NULL;
+
+	/* worst case allocation size */
+	aligned_size = size + align - 1;
+
+	/* add extra space to store allocation delta */
+	aligned_size += sizeof(size_t);
+
+	/* allocate all space */
+	ptr = kzalloc(aligned_size, flags);
+	if (!ptr)
+		return NULL;
+
+	/* calculate the aligned address, making room for the delta value */
+	aligned_ptr = PTR_ALIGN(ptr + sizeof(size_t), align);
+
+	/* save the delta before the address returned to caller */
+	*((size_t *)aligned_ptr - 1) = aligned_ptr - ptr;
+
+	return aligned_ptr;
+}
+
+static void kfree_aligned(void *aligned_ptr)
+{
+	void *ptr;
+	size_t delta;
+
+	if (!aligned_ptr)
+		return;
+
+	/* retrieve extra allocation delta */
+	delta = *((size_t *)aligned_ptr - 1);
+
+	/* calculate original allocation area start */
+	ptr = aligned_ptr - delta;
+
+	kfree(ptr);
+}
+
+
+/**
+ *
+ */
+void *starlet_kzalloc(size_t size, gfp_t flags)
+{
+	return kzalloc_aligned(size, flags, STARLET_IPC_DMA_ALIGN+1);
+}
+
+/**
+ *
+ */
+void starlet_kfree(void *ptr)
+{
+	kfree_aligned(ptr);
+}
+
+
+
+/*
+ * Functions for special input/output buffer allocations.
+ *
+ * Starlet seems to have a limitation when doing non-32 bit writes to MEM1.
+ * This can cause up to a 3 byte data loss when starlet delivers
+ * data of an unaligned size.
+ * Writes to MEM2 don't have such a limitation.
+ *
+ * We use special buffers when we need to retrieve data of an unaligned size
+ * from starlet.
+ *
+ */
+
+static int starlet_ioh_init(struct starlet_ioh *ioh, struct resource *mem)
+{
+	size_t size = mem->end - mem->start + 1;
+	rh_info_t *rheap;
+	int error = -ENOMEM;
+
+	ioh->base = ioremap_flags(mem->start, size, _PAGE_GUARDED);
+	if (!ioh->base) {
+		drv_printk(KERN_ERR, "unable to ioremap ioh area\n");
+		goto err;
+	}
+	ioh->base_phys = mem->start;
+	ioh->size = size;
+
+	{
+		void *first = NULL, *last = NULL;
+		u32 *p;
+
+		p = ioh->base + size;
+		do {
+			p--;
+			*p = 0xdeadbabe;
+		} while (p != ioh->base);
+		__dma_sync(ioh->base, size, DMA_TO_DEVICE);
+
+		p = ioh->base + size;
+		do {
+			p--;
+			if (*p != 0xdeadbabe) {
+				if (!last)
+					last = p;
+				first = p;
+			}
+		} while (p != ioh->base);
+
+		if (first)
+			drv_printk(KERN_INFO, "unreliable writes from"
+				   " %p to %p\n", first, last);
+	}
+
+	rheap = rh_create(STARLET_IOH_ALIGN+1);
+	if (IS_ERR(rheap)) {
+		error = PTR_ERR(rheap);
+		goto err_rh_create;
+	}
+	ioh->rheap = rheap;
+
+	error = rh_attach_region(rheap, 0, size);
+	if (error)
+		goto err_rh_attach_region;
+
+	spin_lock_init(&ioh->lock);
+
+	drv_printk(KERN_INFO, "ioh at 0x%08lx, mapped to 0x%p, size %uk\n",
+		   ioh->base_phys, ioh->base, ioh->size / 1024);
+
+	return 0;
+
+err_rh_create:
+	iounmap(ioh->base);
+err_rh_attach_region:
+	rh_destroy(ioh->rheap);
+err:
+	return error;
+}
+
+static struct starlet_ioh *starlet_ioh;
+
+/**
+ *
+ */
+static struct starlet_ioh *starlet_ioh_get(void)
+{
+	if (unlikely(!starlet_ioh))
+		drv_printk(KERN_ERR, "uninitialized ioh instance!\n");
+	return starlet_ioh;
+}
+
+unsigned long starlet_ioh_virt_to_phys(void *ptr)
+{
+	struct starlet_ioh *ioh = starlet_ioh_get();
+	unsigned long offset;
+
+	if (!ioh || !ptr)
+		return 0;
+
+	offset = ptr - ioh->base;
+	return ioh->base_phys + offset;
+}
+
+/**
+ *
+ */
+void *starlet_ioh_kzalloc_aligned(size_t size, size_t align)
+{
+	struct starlet_ioh *ioh = starlet_ioh_get();
+	unsigned long offset;
+	void *ptr;
+	unsigned long flags;
+
+	if (!ioh)
+		return NULL;
+
+	spin_lock_irqsave(&ioh->lock, flags);
+	offset = rh_alloc_align(ioh->rheap, size, align, NULL);
+	spin_unlock_irqrestore(&ioh->lock, flags);
+
+	if (IS_ERR_VALUE(offset))
+		return NULL;
+
+	ptr = ioh->base + offset;
+	memset(ptr, 0, size);
+
+	return ptr;
+}
+
+/**
+ *
+ */
+void *starlet_ioh_kzalloc(size_t size)
+{
+	return starlet_ioh_kzalloc_aligned(size, STARLET_IOH_ALIGN+1);
+}
+
+/**
+ *
+ */
+void starlet_ioh_kfree(void *ptr)
+{
+	struct starlet_ioh *ioh = starlet_ioh_get();
+	unsigned long offset;
+	unsigned long flags;
+
+	if (!ioh || !ptr)
+		return;
+
+	offset = ptr - ioh->base;
+
+	spin_lock_irqsave(&ioh->lock, flags);
+	rh_free(ioh->rheap, offset);
+	spin_unlock_irqrestore(&ioh->lock, flags);
+}
+
+int starlet_ioh_dma_map_sg(struct device *dev, struct starlet_ioh_sg *sgl,
+			   int nents, enum dma_data_direction direction)
+{
+	struct starlet_ioh_sg *sg;
+	int i;
+
+	BUG_ON(direction == DMA_NONE);
+
+	starlet_ioh_for_each_sg(sgl, sg, nents, i) {
+		if (!sg->buf || sg->len == 0)
+			continue;
+		__dma_sync(sg->buf, sg->len, direction);
+	}
+	return nents;
+}
+
+void starlet_ioh_dma_unmap_sg(struct device *dev, struct starlet_ioh_sg *sgl,
+			      int nents, enum dma_data_direction direction)
+{
+	/* nothing to do */
+}
+
+void starlet_ioh_sg_init_table(struct starlet_ioh_sg *sgl, unsigned int nents)
+{
+	memset(sgl, 0, nents * sizeof(*sgl));
+}
+
+/**
+ *
+ * @buf: must have been allocated using one of the starlet_ioh_* alloc
+ *       functions.
+ */
+void starlet_ioh_sg_set_buf(struct starlet_ioh_sg *sg, void *buf, size_t len)
+{
+	struct starlet_ioh *ioh = starlet_ioh_get();
+	unsigned long offset;
+
+	if (buf && len) {
+		offset = buf - ioh->base;
+
+		sg->buf = buf;
+		sg->len = len;
+		sg->dma_addr = ioh->base_phys + offset;
+	} else {
+		sg->buf = NULL;
+		sg->len = 0;
+		sg->dma_addr = 0;
+	}
+}
+
+
+/**
+ *
+ */
+int starlet_malloc_lib_bootstrap(struct resource *mem)
+{
+	struct starlet_ioh *ioh;
+	int error;
+
+	if (starlet_ioh) {
+		drv_printk(KERN_WARNING, "already bootstrapped\n");
+		return 0;
+	}
+
+	drv_printk(KERN_INFO, "%s - version %s\n", LIB_DESCRIPTION,
+		   starlet_malloc_lib_version);
+
+	ioh = kzalloc(sizeof(*ioh), GFP_KERNEL);
+	if (!ioh) {
+		drv_printk(KERN_ERR, "failed to allocate ioh\n");
+		return -ENOMEM;
+	}
+
+	error = starlet_ioh_init(ioh, mem);
+	if (error)
+		kfree(ioh);
+	else
+		starlet_ioh = ioh;
+
+	return error;
+}
+
+
diff --git a/arch/powerpc/platforms/embedded6xx/starlet-mipc.c b/arch/powerpc/platforms/embedded6xx/starlet-mipc.c
new file mode 100644
index 0000000..3ff5a8d
--- /dev/null
+++ b/arch/powerpc/platforms/embedded6xx/starlet-mipc.c
@@ -0,0 +1,1013 @@
+/*
+ * arch/powerpc/platforms/embedded6xx/starlet-mipc.c
+ *
+ * IPC driver for the 'mini' firmware replacement for Starlet
+ * Copyright (C) 2009 The GameCube Linux Team
+ * Copyright (C) 2009 Albert Herranz
+ *
+ * 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.
+ *
+ */
+#define DRV_MODULE_NAME		"starlet-mipc"
+#define pr_fmt(fmt) DRV_MODULE_NAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/of_platform.h>
+#include <linux/io.h>
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>	/* for mdelay() */
+#include <asm/pgtable.h>	/* for _PAGE_KERNEL_NC */
+#include <asm/time.h>		/* for get_tbl() */
+#include <asm/starlet-mini.h>
+
+#include "hlwd-pic.h"
+
+
+#define DRV_DESCRIPTION		"IPC driver for 'mini'"
+#define DRV_AUTHOR		"Albert Herranz"
+
+static char mipc_driver_version[] = "0.4i";
+
+
+/*
+ * Hardware registers
+ */
+#define MIPC_TXBUF	0x00	/* data from cpu to starlet */
+
+#define MIPC_CSR		0x04
+#define   MIPC_CSR_TXSTART	(1<<0)	/* start transmit */
+#define   MIPC_CSR_TBEI		(1<<1)	/* tx buf empty int */
+#define   MIPC_CSR_RBFI		(1<<2)	/* rx buf full int */
+#define   MIPC_CSR_RXRDY	(1<<3)	/* receiver ready */
+#define   MIPC_CSR_RBFIMASK	(1<<4)	/* rx buf full int mask */
+#define   MIPC_CSR_TBEIMASK	(1<<5)	/* tx buf empty int mask */
+
+#define MIPC_RXBUF	0x08	/* data from starlet to cpu */
+
+
+#define MIPC_MIN_VER	1
+#define MIPC_MAX_VER	1
+
+#define MIPC_INITIAL_TAG	1
+
+#define MIPC_SYS_IO_TIMEOUT	(250*1000)	/* usecs */
+#define MIPC_DEV_TIMEOUT	(10*1000*1000)	/* usecs */
+
+
+/*
+ * Firmware request.
+ *
+ */
+struct mipc_req {
+	union {
+		struct {
+			u8 flags;
+			u8 device;
+			u16 req;
+		};
+		u32 code;
+	};
+	u32 tag;
+	u32 args[MIPC_REQ_MAX_ARGS];
+} __attribute__ ((packed));
+
+/*
+ *
+ */
+struct mipc_device {
+	void __iomem *io_base;
+	int irq;
+
+	struct device *dev;
+
+	spinlock_t call_lock;	/* serialize firmware calls */
+	spinlock_t io_lock;	/* serialize access to io registers */
+
+	struct mipc_infohdr *hdr;
+
+	struct mipc_req *in_ring;
+	size_t in_ring_size;
+	volatile u16 intail_idx;
+
+	struct mipc_req *out_ring;
+	size_t out_ring_size;
+	volatile u16 outhead_idx;
+
+	u32 tag;
+};
+
+#define __spin_event_timeout(condition, timeout_usecs, result, __end_tbl) \
+	for (__end_tbl = get_tbl() + tb_ticks_per_usec * timeout_usecs;	\
+	     !(result = (condition)) && (int)(__end_tbl - get_tbl()) > 0;)
+
+/*
+ * Update control and status register.
+ */
+static inline void mipc_update_csr(void __iomem *io_base, u32 val)
+{
+	u32 csr;
+
+	csr = in_be32(io_base + MIPC_CSR);
+	/* preserve interrupt masks */
+	csr &= MIPC_CSR_RBFIMASK | MIPC_CSR_TBEIMASK;
+	csr |= val;
+	out_be32(io_base + MIPC_CSR, csr);
+}
+
+static u16 mipc_peek_outtail(void __iomem *io_base)
+{
+	return in_be32(io_base + MIPC_RXBUF) & 0xffff;
+}
+
+static u16 mipc_peek_inhead(void __iomem *io_base)
+{
+	return in_be32(io_base + MIPC_RXBUF) >> 16;
+}
+
+static u16 mipc_peek_first_intail(void __iomem *io_base)
+{
+	return in_be32(io_base + MIPC_TXBUF) & 0xffff;
+}
+
+static u16 mipc_peek_first_outhead(void __iomem *io_base)
+{
+	return in_be32(io_base + MIPC_TXBUF) >> 16;
+}
+
+static void mipc_poke_intail(struct mipc_device *ipc_dev, u16 val)
+{
+	void __iomem *io_base = ipc_dev->io_base;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ipc_dev->io_lock, flags);
+	out_be32(io_base + MIPC_TXBUF,
+		 (in_be32(io_base + MIPC_TXBUF) & 0xffff0000) | val);
+	spin_unlock_irqrestore(&ipc_dev->io_lock, flags);
+}
+
+static void mipc_poke_outhead(struct mipc_device *ipc_dev, u16 val)
+{
+	void __iomem *io_base = ipc_dev->io_base;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ipc_dev->io_lock, flags);
+	out_be32(io_base + MIPC_TXBUF,
+		 (in_be32(io_base + MIPC_TXBUF) & 0x0000ffff) | val<<16);
+	spin_unlock_irqrestore(&ipc_dev->io_lock, flags);
+}
+
+
+
+static u16 mipc_get_next_intail(struct mipc_device *ipc_dev)
+{
+	return (ipc_dev->intail_idx + 1) & (ipc_dev->in_ring_size - 1);
+}
+
+static u16 mipc_get_next_outhead(struct mipc_device *ipc_dev)
+{
+	return (ipc_dev->outhead_idx + 1) & (ipc_dev->out_ring_size - 1);
+}
+
+static void mipc_print_req(struct mipc_req *req)
+{
+	int i;
+
+	pr_info("req %pP = {\n", req);
+	pr_cont("code = %08X, tag = %08X\n", req->code, req->tag);
+	for (i = 0; i < MIPC_REQ_MAX_ARGS; i++)
+		pr_cont("arg[%d] = %08X\n", i, req->args[i]);
+	pr_cont("}\n");
+}
+
+#ifdef DEBUG_RINGS
+static void mipc_dump_ring(struct mipc_req *req, size_t count)
+{
+	int i;
+
+	for (i = 0; i < count; i++)
+		pr_devel("%d: %X (%08X)\n", i, req[i].tag, req[i].code);
+}
+#endif
+
+static void mipc_print_status(struct mipc_device *ipc_dev)
+{
+	size_t in_size, out_size;
+
+	in_size = ipc_dev->in_ring_size * sizeof(*ipc_dev->in_ring);
+	out_size = ipc_dev->out_ring_size * sizeof(*ipc_dev->out_ring);
+
+	pr_info("ppc: intail_idx=%u, outhead_idx=%u\n",
+		ipc_dev->intail_idx, ipc_dev->outhead_idx);
+	pr_cont("arm: inhead_idx=%u, outtail_idx=%u\n",
+		mipc_peek_inhead(ipc_dev->io_base),
+		mipc_peek_outtail(ipc_dev->io_base));
+	pr_cont("in_ring=%uK@%p, out_ring=%uK@%p\n",
+		in_size / 1024, ipc_dev->in_ring,
+		out_size / 1024, ipc_dev->out_ring);
+}
+
+static int mipc_send_req(struct mipc_device *ipc_dev, unsigned long timeout,
+			 struct mipc_req *req)
+{
+	void __iomem *io_base = ipc_dev->io_base;
+	struct mipc_req *firm_req;
+	unsigned long ctx;
+	int result;
+	int error = 0;
+
+	if (mipc_peek_inhead(io_base) == mipc_get_next_intail(ipc_dev)) {
+		pr_err("%s queue full\n", "ppc->arm ipc");
+		__spin_event_timeout(mipc_peek_inhead(io_base) !=
+				     mipc_get_next_intail(ipc_dev),
+				     timeout, result, ctx) {
+			/* busy wait */
+			cpu_relax();
+		}
+		if (!result) {
+			pr_err("%s queue drain timed out\n", "ppc->arm ipc");
+			error = -EIO;
+			goto out;
+		}
+	}
+
+	firm_req = ipc_dev->in_ring + ipc_dev->intail_idx;
+	*firm_req = *req;
+	ipc_dev->intail_idx = mipc_get_next_intail(ipc_dev);
+	mipc_poke_intail(ipc_dev, ipc_dev->intail_idx);
+	mipc_update_csr(ipc_dev->io_base, MIPC_CSR_TXSTART);
+out:
+	if (error)
+		pr_devel("exit %d\n", error);
+	return error;
+}
+
+static int __mipc_recv_req(struct mipc_device *ipc_dev, unsigned long timeout,
+			   struct mipc_req *req)
+{
+	void __iomem *io_base = ipc_dev->io_base;
+	struct mipc_req *firm_req;
+	unsigned long ctx;
+	int result;
+	int error = 0;
+
+	__spin_event_timeout(mipc_peek_outtail(io_base) != ipc_dev->outhead_idx,
+			     timeout, result, ctx) {
+		/* busy wait */
+		cpu_relax();
+	}
+	if (mipc_peek_outtail(io_base) == ipc_dev->outhead_idx) {
+		error = -EIO;
+		goto out;
+	}
+	firm_req = ipc_dev->out_ring + ipc_dev->outhead_idx;
+	*req = *firm_req;
+	ipc_dev->outhead_idx = mipc_get_next_outhead(ipc_dev);
+	mipc_poke_outhead(ipc_dev, ipc_dev->outhead_idx);
+out:
+	return error;
+}
+
+static int mipc_recv_req(struct mipc_device *ipc_dev, unsigned long timeout,
+			 struct mipc_req *req)
+{
+	int error;
+
+	error = __mipc_recv_req(ipc_dev, timeout, req);
+	if (error)
+		pr_devel("arm->ppc ipc request timed out (%d)\n", error);
+	return error;
+}
+
+static int mipc_recv_tagged(struct mipc_device *ipc_dev,
+				unsigned long timeout,
+				u32 code, u32 tag,
+				struct mipc_req *req)
+{
+	unsigned long ctx;
+	int result;
+	int error;
+
+	error = mipc_recv_req(ipc_dev, timeout, req);
+	if (error)
+		goto out;
+
+	__spin_event_timeout(req->code == code && req->tag == tag,
+			     timeout, result, ctx) {
+		pr_devel("expected: code=%08X, tag=%08X\n", code, tag);
+		mipc_print_req(req);
+		pr_devel("+++ status\n");
+		mipc_print_status(ipc_dev);
+#ifdef DEBUG_RINGS
+		pr_devel("+++ in_ring\n");
+		mipc_dump_ring(ipc_dev->in_ring, ipc_dev->in_ring_size);
+		pr_devel("+++ out_ring\n");
+		mipc_dump_ring(ipc_dev->out_ring, ipc_dev->out_ring_size);
+#endif
+
+		error = mipc_recv_req(ipc_dev, timeout, req);
+		if (error)
+			goto out;
+	}
+	if (!result) {
+		pr_err("%s: recv timed out\n", __func__);
+		error = -EIO;
+		goto out;
+	} else
+		error = 0;
+
+out:
+	if (error)
+		pr_devel("exit %d\n", error);
+	return error;
+}
+
+static void __mipc_fill_req(struct mipc_req *req, u32 code)
+{
+	memset(req, 0, sizeof(*req));
+	req->code = code;
+}
+
+static int mipc_sendrecv_call(struct mipc_device *ipc_dev,
+			      unsigned long timeout,
+			      struct mipc_req *req, struct mipc_req *resp)
+{
+	unsigned long flags;
+	int error;
+
+	spin_lock_irqsave(&ipc_dev->call_lock, flags);
+	req->tag = ipc_dev->tag++;
+	error = mipc_send_req(ipc_dev, timeout, req);
+	if (error)
+		goto out;
+	error = mipc_recv_tagged(ipc_dev, timeout, req->code, req->tag, resp);
+out:
+	spin_unlock_irqrestore(&ipc_dev->call_lock, flags);
+
+	return error;
+}
+
+static int mipc_sendrecv1_call(struct mipc_device *ipc_dev,
+			       unsigned long timeout,
+			       struct mipc_req *resp, u32 code, u32 arg)
+{
+	struct mipc_req req;
+
+	__mipc_fill_req(&req, code);
+	req.args[0] = arg;
+	return mipc_sendrecv_call(ipc_dev, timeout, &req, resp);
+}
+
+
+static int mipc_send_call(struct mipc_device *ipc_dev, unsigned long timeout,
+			  struct mipc_req *req)
+{
+	unsigned long flags;
+	int error;
+
+	spin_lock_irqsave(&ipc_dev->call_lock, flags);
+	req->tag = ipc_dev->tag++;
+	error = mipc_send_req(ipc_dev, timeout, req);
+	spin_unlock_irqrestore(&ipc_dev->call_lock, flags);
+
+	return error;
+}
+
+static int mipc_send2_call(struct mipc_device *ipc_dev, unsigned long timeout,
+			   u32 code, u32 arg1, u32 arg2)
+{
+	struct mipc_req req;
+
+	__mipc_fill_req(&req, code);
+	req.args[0] = arg1;
+	req.args[1] = arg2;
+	return mipc_send_call(ipc_dev, timeout, &req);
+}
+
+static int mipc_send3_call(struct mipc_device *ipc_dev, unsigned long timeout,
+			   u32 code, u32 arg1, u32 arg2, u32 arg3)
+{
+	struct mipc_req req;
+
+	__mipc_fill_req(&req, code);
+	req.args[0] = arg1;
+	req.args[1] = arg2;
+	req.args[2] = arg3;
+	return mipc_send_call(ipc_dev, timeout, &req);
+}
+
+static int mipc_flush_send(struct mipc_device *ipc_dev, unsigned long timeout)
+{
+	void __iomem *io_base = ipc_dev->io_base;
+	unsigned long ctx;
+	int result;
+	int error = 0;
+
+	__spin_event_timeout(mipc_peek_inhead(io_base) == ipc_dev->intail_idx,
+			     timeout, result, ctx) {
+		/* busy wait */
+		cpu_relax();
+	}
+	if (!result) {
+		pr_err("%s: flush timed out\n", __func__);
+		error = -EIO;
+		goto out;
+	}
+out:
+	if (error)
+		pr_devel("exit %d\n", error);
+	return error;
+}
+
+static void mipc_flush_recv(struct mipc_device *ipc_dev,
+			    unsigned long timeout)
+{
+	struct mipc_req req;
+	int error;
+
+	do {
+		error = __mipc_recv_req(ipc_dev, timeout, &req);
+	} while (!error);
+}
+
+
+
+static struct mipc_device *mipc_device_instance;
+
+struct mipc_device *mipc_get_device(void)
+{
+	if (!mipc_device_instance)
+		pr_err("uninitialized device instance!\n");
+	return mipc_device_instance;
+}
+
+static int mipc_ping(struct mipc_device *ipc_dev, unsigned long timeout)
+{
+	struct mipc_req resp;
+	int error;
+
+	error = mipc_sendrecv1_call(ipc_dev, timeout, &resp, MIPC_SYS_PING, 0);
+	if (error)
+		pr_devel("exit %d\n", error);
+	return error;
+}
+
+#define __declare_ipc_send2_accessor(_name, _suffix, _size, _call) \
+void mipc_##_name##_suffix(_size a, void __iomem *addr) \
+{									\
+	struct mipc_device *ipc_dev = mipc_get_device();		\
+	int error;							\
+									\
+	error = mipc_send2_call(ipc_dev, MIPC_SYS_IO_TIMEOUT, _call,	\
+				(u32)addr, a);				\
+	if (!error)							\
+		return;							\
+									\
+	pr_devel(__stringify(_name, _suffix) "(%p,%x)\n", addr, a);	\
+	BUG();								\
+}
+
+#define __declare_ipc_send3_accessor(_name, _suffix, _size, _call) \
+void mipc_##_name##_suffix(_size a, _size b, void __iomem *addr) \
+{									\
+	struct mipc_device *ipc_dev = mipc_get_device();		\
+	int error;							\
+									\
+	error = mipc_send3_call(ipc_dev, MIPC_SYS_IO_TIMEOUT, _call,	\
+				(u32)addr, a, b);			\
+	if (!error)							\
+		return;							\
+									\
+	pr_devel(__stringify(_name, _suffix) "(%p,%x,%x)\n", addr, a, b);\
+	BUG();								\
+}
+
+#define __declare_ipc_sendrecv1_accessor(_name, _suffix, _size, _call) \
+_size mipc_##_name##_suffix(void __iomem *addr) \
+{									\
+	struct mipc_device *ipc_dev = mipc_get_device();		\
+	struct mipc_req resp;						\
+	int error;							\
+									\
+	error = mipc_sendrecv1_call(ipc_dev, MIPC_SYS_IO_TIMEOUT,	\
+				    &resp, _call, (u32)addr);		\
+	if (!error)							\
+		return resp.args[0];					\
+									\
+	pr_devel(__stringify(_name, _suffix) "(%p)\n", addr);		\
+	BUG();								\
+	return 0;							\
+}
+
+__declare_ipc_sendrecv1_accessor(read, l, unsigned int, MIPC_SYS_READ32)
+__declare_ipc_sendrecv1_accessor(read, w, unsigned short, MIPC_SYS_READ16)
+__declare_ipc_sendrecv1_accessor(read, b, unsigned char, MIPC_SYS_READ8)
+
+__declare_ipc_send2_accessor(write, l, unsigned int, MIPC_SYS_WRITE32)
+__declare_ipc_send2_accessor(write, w, unsigned short, MIPC_SYS_WRITE16)
+__declare_ipc_send2_accessor(write, b, unsigned char, MIPC_SYS_WRITE8)
+
+__declare_ipc_send2_accessor(setbit, l, unsigned int, MIPC_SYS_SET32)
+__declare_ipc_send2_accessor(clearbit, l, unsigned int, MIPC_SYS_CLEAR32)
+__declare_ipc_send3_accessor(clrsetbits, l, unsigned int, MIPC_SYS_MASK32)
+
+void mipc_wmb(void)
+{
+	struct mipc_device *ipc_dev = mipc_get_device();
+	int error;
+
+	error = mipc_ping(ipc_dev, MIPC_SYS_IO_TIMEOUT);
+	if (!error)
+		return;
+
+	pr_devel(__stringify(_name, _suffix) "()\n");
+	BUG();
+}
+
+void __iomem *mipc_ioremap(phys_addr_t addr, unsigned long size)
+{
+	return (void __iomem *)addr;
+}
+
+void mipc_iounmap(volatile void __iomem *addr)
+{
+	/* nothing to do */
+}
+
+/*
+ *
+ *
+ */
+
+#define BITOP_MASK(nr)          (1UL << ((nr) % BITS_PER_LONG))
+#define BITOP_WORD(nr)          ((nr) / BITS_PER_LONG)
+
+void mipc_clear_bit(int nr, volatile unsigned long *addr)
+{
+	unsigned long mask = BITOP_MASK(nr);
+	unsigned long *p = ((unsigned long *)addr) + BITOP_WORD(nr);
+
+	mipc_clearbitl(mask, p);
+}
+
+void mipc_set_bit(int nr, volatile unsigned long *addr)
+{
+	unsigned long mask = BITOP_MASK(nr);
+	unsigned long *p = ((unsigned long *)addr) + BITOP_WORD(nr);
+
+	mipc_setbitl(mask, p);
+}
+
+void mipc_clrsetbits_be32(const volatile u32 __iomem *addr, u32 clear, u32 set)
+{
+	mipc_clrsetbitsl(clear, set, (void __iomem *)addr);
+}
+
+u32 mipc_in_be32(const volatile u32 __iomem *addr)
+{
+	return mipc_readl((void __iomem *)addr);
+}
+
+void mipc_out_be32(const volatile u32 __iomem *addr, u32 val)
+{
+	mipc_writel(val, (void __iomem *)addr);
+}
+
+u16 mipc_in_be16(const volatile u16 __iomem *addr)
+{
+	return mipc_readw((void __iomem *)addr);
+}
+
+void mipc_out_be16(const volatile u16 __iomem *addr, u16 val)
+{
+	mipc_writew(val, (void __iomem *)addr);
+}
+
+u8 mipc_in_8(const volatile u8 __iomem *addr)
+{
+	return mipc_readb((void __iomem *)addr);
+}
+
+void mipc_out_8(const volatile u8 __iomem *addr, u8 val)
+{
+	mipc_writeb(val, (void __iomem *)addr);
+}
+
+
+
+
+static int mipc_check_address(phys_addr_t pa)
+{
+	if (pa < 0x10000000 || pa > 0x14000000)
+		return -EINVAL;
+	return 0;
+}
+
+int mipc_discover(struct mipc_infohdr **hdrp)
+{
+	struct mipc_infohdr *hdr;
+	char magic[4];
+	phys_addr_t *p;
+	int error;
+
+	/* REVISIT, infohdr pointer should come from dts */
+
+	/* grab mini information header location */
+	p = ioremap(0x13fffffc, 4);
+	if (!p) {
+		pr_err("unable to ioremap mini ipc header ptr\n");
+		error = -ENOMEM;
+		goto out;
+	}
+	/* check that the header pointer points to MEM2 */
+	if (mipc_check_address(*p)) {
+		pr_devel("wrong mini ipc header address %pP\n", (void *)*p);
+		error = -ENODEV;
+		goto out_unmap_p;
+	}
+
+	hdr = (struct mipc_infohdr *)ioremap_prot(*p, sizeof(*hdr),
+						      PAGE_KERNEL);
+	if (!hdr) {
+		pr_err("unable to ioremap mini ipc header\n");
+		error = -ENOMEM;
+		goto out_unmap_p;
+	}
+	__dma_sync(hdr, sizeof(*hdr), DMA_FROM_DEVICE);
+
+	memcpy(magic, hdr->magic, 3);
+	magic[3] = 0;
+	if (memcmp(magic, "IPC", 3)) {
+		pr_devel("wrong magic \"%s\"\n", magic);
+		error = -ENODEV;
+		goto out_unmap_hdr;
+	}
+	if (hdr->version < MIPC_MIN_VER && hdr->version > MIPC_MAX_VER) {
+		pr_err("unsupported mini ipc version %d"
+			   " (min %d, max %d)\n", hdr->version,
+			   MIPC_MIN_VER, MIPC_MAX_VER);
+		error = -ENODEV;
+		goto out_unmap_hdr;
+	}
+	if (mipc_check_address(hdr->mem2_boundary)) {
+		pr_err("invalid mem2_boundary %pP\n",
+		       (void *)hdr->mem2_boundary);
+		error = -EINVAL;
+		goto out_unmap_hdr;
+	}
+	if (mipc_check_address(hdr->ipc_in)) {
+		pr_err("invalid ipc_in %pP\n", (void *)hdr->ipc_in);
+		error = -EINVAL;
+		goto out_unmap_hdr;
+	}
+	if (mipc_check_address(hdr->ipc_out)) {
+		pr_err("invalid ipc_out %pP\n", (void *)hdr->ipc_out);
+		error = -EINVAL;
+		goto out_unmap_hdr;
+	}
+
+	*hdrp = hdr;
+	error = 0;
+	goto out_unmap_p;
+
+out_unmap_hdr:
+	iounmap(hdr);
+out_unmap_p:
+	iounmap(p);
+out:
+	return error;
+}
+
+static void mipc_print_infohdr(struct mipc_infohdr *hdr)
+{
+	pr_info("magic=%c%c%c, version=%d, mem2_boundary=%pP\n",
+		hdr->magic[0], hdr->magic[1], hdr->magic[2],
+		hdr->version,
+		(void *)hdr->mem2_boundary);
+	pr_cont("ipc_in[%u] @ %pP, ipc_out[%u] @ %pP\n",
+		hdr->ipc_in_size, (void *)hdr->ipc_in,
+		hdr->ipc_out_size, (void *)hdr->ipc_out);
+}
+
+static int mipc_do_simple_tests = 0;
+
+#ifndef MODULE
+static int __init mipc_simple_tests_setup(char *str)
+{
+	if (*str)
+		return 0;
+	mipc_do_simple_tests = 1;
+	return 1;
+}
+__setup("mipc_simple_tests", mipc_simple_tests_setup);
+#endif
+
+static unsigned long tbl_to_ns(unsigned long tbl)
+{
+	return (tbl * 1000) / tb_ticks_per_usec;
+}
+
+static void mipc_simple_tests(struct mipc_device *ipc_dev)
+{
+	void __iomem *io_base = ipc_dev->io_base;
+	void *gpio;
+	unsigned long t0;
+	unsigned long t_read, t_write;
+	unsigned long t_mipc_read, t_mipc_write, t_mipc_ping;
+	u32 val;
+	int i;
+
+	gpio = mipc_ioremap(0x0d8000c0, 4);
+	if (!gpio) {
+		pr_err("ioremap failed\n");
+		return;
+	}
+
+	for (i = 0; i < 64000; i++) {
+		t0 = get_tbl();
+		in_be32(io_base + MIPC_CSR);
+		t_read = get_tbl() - t0;
+
+		t0 = get_tbl();
+		out_be32(io_base + MIPC_CSR, 0);
+		t_write = get_tbl() - t0;
+
+		t0 = get_tbl();
+		val = mipc_readl(gpio);
+		t_mipc_read = get_tbl() - t0;
+
+		t0 = get_tbl();
+		mipc_writel(val & ~0x20, gpio);
+		t_mipc_write = get_tbl() - t0;
+
+		t0 = get_tbl();
+		mipc_ping(ipc_dev, MIPC_SYS_IO_TIMEOUT);
+		t_mipc_ping = get_tbl() - t0;
+	}
+
+	pr_info("io timings in timebase ticks"
+		" (1 usec = %lu ticks)\n", tb_ticks_per_usec);
+	pr_cont("mmio: read=%lu (%lu ns), write=%lu (%lu ns)\n",
+		t_read, tbl_to_ns(t_read), t_write, tbl_to_ns(t_write));
+	pr_cont("mipc: read=%lu (%lu ns), write=%lu (%lu ns)\n",
+		t_mipc_read, tbl_to_ns(t_mipc_read),
+		t_mipc_write, tbl_to_ns(t_mipc_write));
+	pr_cont("mipc: ping=%lu (%lu ns)\n",
+		t_mipc_ping, tbl_to_ns(t_mipc_ping));
+
+	mipc_iounmap(gpio);
+}
+
+static void mipc_shutdown_mini_devs(struct mipc_device *ipc_dev)
+{
+	struct mipc_req resp;
+	int error;
+
+	error = mipc_sendrecv1_call(ipc_dev, MIPC_DEV_TIMEOUT, &resp,
+				    _MIPC(_MIPC_SLOW, _MIPC_DEV_SDHC,
+					   _MIPC_SDHC_EXIT), 0);
+	if (error)
+		pr_err("unable to shutdown mini SDHC subsystem\n");
+}
+
+static void mipc_starlet_fixups(struct mipc_device *ipc_dev)
+{
+	void __iomem *gpio;
+
+	/*
+	 * Try to turn off the front led and sensor bar.
+	 * (not strictly starlet-only stuff but anyway...)
+	 */
+	gpio = mipc_ioremap(0x0d8000c0, 4);
+	if (gpio) {
+		mipc_clearbitl(0x120, gpio);
+		mipc_iounmap(gpio);
+	}
+
+	/* tell 'mini' to relinquish control of hardware */
+	mipc_shutdown_mini_devs(ipc_dev);
+}
+
+static void mipc_init_ahbprot(struct mipc_device *ipc_dev)
+{
+	void __iomem *hw_ahbprot = (void __iomem *)0x0d800064;
+	u32 initial_ahbprot, ahbprot;
+
+	initial_ahbprot = mipc_readl(hw_ahbprot);
+	if (initial_ahbprot != 0xffffffff) {
+		pr_debug("AHBPROT=%08X (before)\n", initial_ahbprot);
+		mipc_writel(0xffffffff, hw_ahbprot);
+	}
+
+	ahbprot = mipc_readl(hw_ahbprot);
+	if (initial_ahbprot != ahbprot)
+		pr_debug("AHBPROT=%08X (after)\n", ahbprot);
+	if (ahbprot != 0xffffffff)
+		pr_err("failed to set AHBPROT\n");
+}
+
+static int mipc_init(struct mipc_device *ipc_dev, struct resource *mem, int irq)
+{
+	struct mipc_infohdr *hdr;
+	void __iomem *io_base;
+	size_t io_size, in_size, out_size;
+	int error;
+
+	error = mipc_discover(&hdr);
+	if (error) {
+		pr_err("unable to find mini ipc instance\n");
+		goto out;
+	}
+
+	spin_lock_init(&ipc_dev->call_lock);
+	spin_lock_init(&ipc_dev->io_lock);
+
+	io_size = mem[0].end - mem[0].start + 1;
+	io_base = ipc_dev->io_base = ioremap(mem[0].start, io_size);
+	ipc_dev->irq = irq;
+
+	ipc_dev->hdr = hdr;
+
+	mipc_print_infohdr(hdr);
+
+	in_size = hdr->ipc_in_size * sizeof(*ipc_dev->in_ring);
+	ipc_dev->in_ring = ioremap(hdr->ipc_in, in_size);
+	ipc_dev->in_ring_size = hdr->ipc_in_size;
+	ipc_dev->intail_idx = mipc_peek_first_intail(io_base);
+
+	out_size = hdr->ipc_out_size * sizeof(*ipc_dev->out_ring);
+	ipc_dev->out_ring = ioremap(hdr->ipc_out, out_size);
+	ipc_dev->out_ring_size = hdr->ipc_out_size;
+	ipc_dev->outhead_idx = mipc_peek_first_outhead(io_base);
+
+	ipc_dev->tag = MIPC_INITIAL_TAG;
+	mipc_device_instance = ipc_dev;
+
+	mipc_print_status(ipc_dev);
+
+	mipc_flush_send(ipc_dev, 5*1000);
+	mipc_flush_recv(ipc_dev, 5*1000);
+	error = mipc_ping(ipc_dev, 1*1000*1000);
+	if (error)
+		goto out;
+
+	pr_info("ping OK\n");
+	if (mipc_do_simple_tests)
+		mipc_simple_tests(ipc_dev);
+
+	mipc_init_ahbprot(ipc_dev);
+	mipc_starlet_fixups(ipc_dev);
+
+out:
+	return error;
+}
+
+static void mipc_exit(struct mipc_device *ipc_dev)
+{
+	if (ipc_dev->in_ring)
+		iounmap(ipc_dev->in_ring);
+	if (ipc_dev->out_ring)
+		iounmap(ipc_dev->out_ring);
+}
+
+
+/*
+ * Driver model helper routines.
+ *
+ */
+
+static int mipc_do_probe(struct device *dev, struct resource *mem, int irq)
+{
+	struct mipc_device *ipc_dev;
+	int error;
+
+	ipc_dev = kzalloc(sizeof(*ipc_dev), GFP_KERNEL);
+	if (!ipc_dev) {
+		pr_err("failed to allocate ipc_dev\n");
+		error = -ENOMEM;
+		goto out;
+	}
+	dev_set_drvdata(dev, ipc_dev);
+	ipc_dev->dev = dev;
+
+	error = mipc_init(ipc_dev, mem, irq);
+	if (error) {
+		dev_set_drvdata(dev, NULL);
+		kfree(ipc_dev);
+		goto out;
+	}
+
+	pr_info("ready\n");
+	hlwd_pic_probe();
+
+out:
+	return error;
+}
+
+static int mipc_do_remove(struct device *dev)
+{
+	struct mipc_device *ipc_dev = dev_get_drvdata(dev);
+	int error = 0;
+
+	if (!ipc_dev) {
+		error = -ENODEV;
+		goto out;
+	}
+
+	mipc_exit(ipc_dev);
+	dev_set_drvdata(dev, NULL);
+	kfree(ipc_dev);
+out:
+	return error;
+}
+
+static int mipc_do_shutdown(struct device *dev)
+{
+	struct mipc_device *ipc_dev = dev_get_drvdata(dev);
+	int error = 0;
+
+	if (!ipc_dev) {
+		error = -ENODEV;
+		goto out;
+	}
+out:
+	return error;
+}
+
+/*
+ * OF platform driver hooks.
+ *
+ */
+
+static int mipc_of_probe(struct of_device *odev,
+			 const struct of_device_id *dev_id)
+{
+	struct resource mem[2];
+	int error;
+
+	error = of_address_to_resource(odev->node, 0, &mem[0]);
+	if (error) {
+		pr_err("no io memory range found (%d)\n", error);
+		goto out;
+	}
+
+	error = mipc_do_probe(&odev->dev, mem,
+			      irq_of_parse_and_map(odev->node, 0));
+out:
+	return error;
+}
+
+static int mipc_of_remove(struct of_device *odev)
+{
+	return mipc_do_remove(&odev->dev);
+}
+
+static int mipc_of_shutdown(struct of_device *odev)
+{
+	return mipc_do_shutdown(&odev->dev);
+}
+
+static struct of_device_id mipc_of_match[] = {
+	{ .compatible = "twiizers,starlet-mini-ipc" },
+	{ },
+};
+
+MODULE_DEVICE_TABLE(of, mipc_of_match);
+
+static struct of_platform_driver mipc_of_driver = {
+	.owner = THIS_MODULE,
+	.name = DRV_MODULE_NAME,
+	.match_table = mipc_of_match,
+	.probe = mipc_of_probe,
+	.remove = mipc_of_remove,
+	.shutdown = mipc_of_shutdown,
+};
+
+/*
+ * Kernel module interface hooks.
+ *
+ */
+
+static int __init mipc_init_module(void)
+{
+	pr_info("%s - version %s\n", DRV_DESCRIPTION, mipc_driver_version);
+
+	return of_register_platform_driver(&mipc_of_driver);
+}
+
+static void __exit mipc_exit_module(void)
+{
+	of_unregister_platform_driver(&mipc_of_driver);
+}
+
+module_init(mipc_init_module);
+module_exit(mipc_exit_module);
+
+MODULE_DESCRIPTION(DRV_DESCRIPTION);
+MODULE_AUTHOR(DRV_AUTHOR);
+MODULE_LICENSE("GPL");
+
diff --git a/arch/powerpc/platforms/embedded6xx/starlet-stm.c b/arch/powerpc/platforms/embedded6xx/starlet-stm.c
new file mode 100644
index 0000000..b79df58
--- /dev/null
+++ b/arch/powerpc/platforms/embedded6xx/starlet-stm.c
@@ -0,0 +1,106 @@
+/*
+ * arch/powerpc/platforms/embedded6xx/starlet-stm.c
+ *
+ * Nintendo Wii starlet STM routines
+ * Copyright (C) 2008-2009 The GameCube Linux Team
+ * Copyright (C) 2008,2009 Albert Herranz
+ *
+ * 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.
+ *
+ */
+
+#define DBG(fmt, arg...)        drv_printk(KERN_INFO, fmt, ##arg)
+
+#include <linux/kernel.h>
+#include <linux/dma-mapping.h>
+#include <asm/starlet-ios.h>
+
+
+/*
+ * /dev/stm/immediate
+ *
+ */
+
+#define STARLET_STM_HOTRESET	0x2001
+#define STARLET_STM_SHUTDOWN	0x2003
+
+#define STARLET_STM_TIMEOUT	1000000	/* usecs */
+
+#define STARLET_DEV_STM_IMMEDIATE	"/dev/stm/immediate"
+
+#define drv_printk(level, format, arg...) \
+	 printk(level "starlet-stm: " format , ## arg)
+
+
+static const char dev_stm_immediate[] = STARLET_DEV_STM_IMMEDIATE;
+
+/* private aligned buffer for restart/power_off operations */
+static u32 starlet_stm_buf[(STARLET_IPC_DMA_ALIGN+1)/sizeof(u32)]
+		 __attribute__ ((aligned(STARLET_IPC_DMA_ALIGN+1)));
+
+/*
+ *
+ */
+static void starlet_stm_common_restart(int request, u32 value)
+{
+	static int already_in_restart;
+	u32 *buf = starlet_stm_buf;
+	size_t len = sizeof(starlet_stm_buf);
+	int fd;
+	int error;
+
+	if (already_in_restart) {
+		drv_printk(KERN_ERR, "previous restart attempt failed,"
+			   " halting\n");
+		goto halt;
+	}
+
+	drv_printk(KERN_INFO, "trying IPC restart...\n");
+	already_in_restart = 1;
+
+	fd = starlet_open_polled(dev_stm_immediate, 0, STARLET_STM_TIMEOUT);
+	if (fd < 0) {
+		drv_printk(KERN_ERR, "failed to open %s\n", dev_stm_immediate);
+		error = fd;
+		goto done;
+	}
+
+	*buf = value;
+	error = starlet_ioctl_polled(fd, request, buf, len, buf, len,
+				     STARLET_STM_TIMEOUT);
+	if (error < 0) {
+		drv_printk(KERN_ERR, "ioctl %d failed\n", request);
+		starlet_close_polled(fd, STARLET_STM_TIMEOUT);
+	} else {
+halt:
+		for (;;)
+			cpu_relax();
+	}
+
+done:
+	if (error < 0)
+		DBG("%s: error=%d (%x)\n", __func__, error, error);
+}
+
+/*
+ *
+ */
+void starlet_stm_restart(void)
+{
+	starlet_stm_common_restart(STARLET_STM_HOTRESET, 0);
+}
+/*EXPORT_SYMBOL_GPL(starlet_stm_restart);*/
+
+/*
+ *
+ */
+void starlet_stm_power_off(void)
+{
+	starlet_stm_common_restart(STARLET_STM_SHUTDOWN, 0);
+}
+/*EXPORT_SYMBOL_GPL(starlet_stm_power_off);*/
+
+
diff --git a/arch/powerpc/platforms/embedded6xx/usbgecko_udbg.c b/arch/powerpc/platforms/embedded6xx/usbgecko_udbg.c
new file mode 100644
index 0000000..872ed5a
--- /dev/null
+++ b/arch/powerpc/platforms/embedded6xx/usbgecko_udbg.c
@@ -0,0 +1,318 @@
+/*
+ * arch/powerpc/platforms/embedded6xx/usbgecko_udbg.c
+ *
+ * udbg serial input/output routines for the USB Gecko adapter.
+ * Copyright (C) 2008-2009 The GameCube Linux Team
+ * Copyright (C) 2008,2009 Albert Herranz
+ *
+ * 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 <asm/io.h>
+#include <asm/prom.h>
+#include <asm/udbg.h>
+
+#include <mm/mmu_decl.h>
+
+#include "usbgecko_udbg.h"
+
+
+#define EXI_CLK_32MHZ           5
+
+#define EXI_CSR                 0x00
+#define   EXI_CSR_CLKMASK       (0x7<<4)
+#define     EXI_CSR_CLK_32MHZ   (EXI_CLK_32MHZ<<4)
+#define   EXI_CSR_CSMASK        (0x7<<7)
+#define     EXI_CSR_CS_0        (0x1<<7)  /* Chip Select 001 */
+
+#define EXI_CR                  0x0c
+#define   EXI_CR_TSTART         (1<<0)
+#define   EXI_CR_WRITE		(1<<2)
+#define   EXI_CR_READ_WRITE     (2<<2)
+#define   EXI_CR_TLEN(len)      (((len)-1)<<4)
+
+#define EXI_DATA                0x10
+
+#define UG_READ_ATTEMPTS	100
+#define UG_WRITE_ATTEMPTS	100
+
+
+static void __iomem *ug_io_base;
+
+/*
+ * Performs one input/output transaction between the spi host and the usbgecko.
+ */
+static u32 ug_io_transaction(u32 in)
+{
+	u32 __iomem *csr_reg = ug_io_base + EXI_CSR;
+	u32 __iomem *data_reg = ug_io_base + EXI_DATA;
+	u32 __iomem *cr_reg = ug_io_base + EXI_CR;
+	u32 csr, data, cr;
+
+	/* select */
+	csr = EXI_CSR_CLK_32MHZ | EXI_CSR_CS_0;
+	out_be32(csr_reg, csr);
+
+	/* read/write */
+	data = in;
+	out_be32(data_reg, data);
+	cr = EXI_CR_TLEN(2) | EXI_CR_READ_WRITE | EXI_CR_TSTART;
+	out_be32(cr_reg, cr);
+
+	while (in_be32(cr_reg) & EXI_CR_TSTART)
+		barrier();
+
+	/* deselect */
+	out_be32(csr_reg, 0);
+
+	/* result */
+	data = in_be32(data_reg);
+
+	return data;
+}
+
+/*
+ * Returns true if an usbgecko adapter is found.
+ */
+static int ug_is_adapter_present(void)
+{
+	if (!ug_io_base)
+		return 0;
+
+	return ug_io_transaction(0x90000000) == 0x04700000;
+}
+
+/*
+ * Returns true if the TX fifo is ready for transmission.
+ */
+static int ug_is_txfifo_ready(void)
+{
+	return ug_io_transaction(0xc0000000) & 0x04000000;
+}
+
+/*
+ * Tries to transmit a character.
+ * If the TX fifo is not ready the result is undefined.
+ */
+static void ug_raw_putc(char ch)
+{
+	ug_io_transaction(0xb0000000 | (ch << 20));
+}
+
+/*
+ * Transmits a character.
+ * It silently fails if the TX fifo is not ready after a number of retries.
+ */
+static void ug_putc(char ch)
+{
+	int count = UG_WRITE_ATTEMPTS;
+
+	if (!ug_io_base)
+		return;
+
+	if (ch == '\n')
+		ug_putc('\r');
+
+	while (!ug_is_txfifo_ready() && count--)
+		barrier();
+	if (count)
+		ug_raw_putc(ch);
+}
+
+#if 0
+/*
+ * Trasmits a null terminated character string.
+ */
+static void ug_puts(char *s)
+{
+	while (*s)
+		ug_putc(*s++);
+}
+#endif
+
+/*
+ * Returns true if the RX fifo is ready for transmission.
+ */
+static int ug_is_rxfifo_ready(void)
+{
+	return ug_io_transaction(0xd0000000) & 0x04000000;
+}
+
+/*
+ * Tries to receive a character.
+ * If a character is unavailable the function returns -1.
+ */
+static int ug_raw_getc(void)
+{
+	u32 data = ug_io_transaction(0xa0000000);
+	if (data & 0x08000000)
+		return (data >> 16) & 0xff;
+	else
+		return -1;
+}
+
+/*
+ * Receives a character.
+ * It fails if the RX fifo is not ready after a number of retries.
+ */
+static int ug_getc(void)
+{
+	int count = UG_READ_ATTEMPTS;
+
+	if (!ug_io_base)
+		return -1;
+
+	while (!ug_is_rxfifo_ready() && count--)
+		barrier();
+	return ug_raw_getc();
+}
+
+/*
+ * udbg functions.
+ *
+ */
+
+/*
+ * Transmits a character.
+ */
+void ug_udbg_putc(char ch)
+{
+	ug_putc(ch);
+}
+
+/*
+ * Receives a character. Waits until a character is available.
+ */
+static int ug_udbg_getc(void)
+{
+	int ch;
+
+	while ((ch = ug_getc()) == -1)
+		barrier();
+	return ch;
+}
+
+/*
+ * Receives a character. If a character is not available, returns -1.
+ */
+static int ug_udbg_getc_poll(void)
+{
+	if (!ug_is_rxfifo_ready())
+		return -1;
+	return ug_getc();
+}
+
+/*
+ * Retrieves and prepares the virtual address needed to access the hardware.
+ */
+static void __iomem *ug_udbg_setup_io_base(struct device_node *np)
+{
+	phys_addr_t paddr;
+	const unsigned int *reg;
+
+	reg = of_get_property(np, "reg", NULL);
+	if (reg) {
+		paddr = of_translate_address(np, reg);
+		if (paddr) {
+			ug_io_base = ioremap(paddr, reg[1]);
+			return ug_io_base;
+		}
+	}
+	return NULL;
+}
+
+/*
+ * USB Gecko udbg support initialization.
+ */
+void __init ug_udbg_init(void)
+{
+	struct device_node *np = NULL;
+	struct device_node *stdout;
+	const char *path;
+
+	if (ug_io_base)
+		udbg_printf("%s: early -> final\n", __func__);
+
+	if (!of_chosen) {
+		udbg_printf("%s: missing of_chosen\n", __func__);
+		goto done;
+	}
+
+	path = of_get_property(of_chosen, "linux,stdout-path", NULL);
+	if (!path) {
+		udbg_printf("%s: missing %s property", __func__,
+			    "linux,stdout-path");
+		goto done;
+	}
+
+	stdout = of_find_node_by_path(path);
+	if (!stdout) {
+		udbg_printf("%s: missing path %s", __func__, path);
+		goto done;
+	}
+
+	for (np = NULL;
+	    (np = of_find_compatible_node(np, NULL, "usbgecko,usbgecko"));)
+		if (np == stdout)
+			break;
+
+	of_node_put(stdout);
+	if (!np) {
+		udbg_printf("%s: stdout is not an usbgecko", __func__);
+		goto done;
+	}
+
+	if (!ug_udbg_setup_io_base(np)) {
+		udbg_printf("%s: failed to setup io base", __func__);
+		goto done;
+	}
+
+	if (!ug_is_adapter_present()) {
+		udbg_printf("usbgecko_udbg: not found\n");
+		ug_io_base = NULL;
+	} else {
+		udbg_putc = ug_udbg_putc;
+		udbg_getc = ug_udbg_getc;
+		udbg_getc_poll = ug_udbg_getc_poll;
+		udbg_printf("usbgecko_udbg: ready\n");
+	}
+
+done:
+	if (np)
+		of_node_put(np);
+	return;
+}
+
+#ifdef CONFIG_PPC_EARLY_DEBUG_USBGECKO
+
+/*
+ * USB Gecko early debug support initialization for udbg.
+ *
+ */
+void __init udbg_init_usbgecko(void)
+{
+	unsigned long vaddr, paddr;
+
+#if defined(CONFIG_GAMECUBE)
+	paddr = 0x0c000000;
+#elif defined(CONFIG_WII)
+	paddr = 0x0d000000;
+#else
+#error Invalid platform for USB Gecko based early debugging.
+#endif
+
+	vaddr = 0xc0000000 | paddr;
+	setbat(1, vaddr, paddr, 128*1024, PAGE_KERNEL_NCG);
+
+	ug_io_base = (void __iomem *)(vaddr | 0x6814);
+
+	udbg_putc = ug_udbg_putc;
+	udbg_getc = ug_udbg_getc;
+	udbg_getc_poll = ug_udbg_getc_poll;
+}
+
+#endif /* CONFIG_PPC_EARLY_DEBUG_USBGECKO */
diff --git a/arch/powerpc/platforms/embedded6xx/usbgecko_udbg.h b/arch/powerpc/platforms/embedded6xx/usbgecko_udbg.h
new file mode 100644
index 0000000..98034ee
--- /dev/null
+++ b/arch/powerpc/platforms/embedded6xx/usbgecko_udbg.h
@@ -0,0 +1,36 @@
+/*
+ * arch/powerpc/platforms/embedded6xx/usbgecko_udbg.h
+ *
+ * udbg serial input/output routines for the USB Gecko adapter.
+ * Copyright (C) 2008-2009 The GameCube Linux Team
+ * Copyright (C) 2008,2009 Albert Herranz
+ *
+ * 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.
+ *
+ */
+
+#ifndef __USBGECKO_UDBG_H
+#define __USBGECKO_UDBG_H
+
+#ifdef CONFIG_USBGECKO_UDBG
+
+extern void __init ug_udbg_init(void);
+
+#else
+
+static inline void __init ug_udbg_init(void)
+{
+}
+
+#endif /* CONFIG_USBGECKO_UDBG */
+
+#ifdef CONFIG_PPC_EARLY_DEBUG_USBGECKO
+
+void __init udbg_init_usbgecko(void);
+
+#endif /* CONFIG_PPC_EARLY_DEBUG_USBGECKO */
+
+#endif /* __USBGECKO_UDBG_H */
diff --git a/arch/powerpc/platforms/embedded6xx/wii.c b/arch/powerpc/platforms/embedded6xx/wii.c
new file mode 100644
index 0000000..45275bd
--- /dev/null
+++ b/arch/powerpc/platforms/embedded6xx/wii.c
@@ -0,0 +1,299 @@
+/*
+ * arch/powerpc/platforms/embedded6xx/wii.c
+ *
+ * Nintendo Wii board-specific support
+ * Copyright (C) 2008-2009 The GameCube Linux Team
+ * Copyright (C) 2008,2009 Albert Herranz
+ *
+ * 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/kernel.h>
+#include <linux/init.h>
+#include <linux/irq.h>
+#include <linux/seq_file.h>
+#include <linux/kexec.h>
+#include <linux/exi.h>
+#include <linux/gpio.h>
+
+#include <asm/io.h>
+#include <asm/machdep.h>
+#include <asm/prom.h>
+#include <asm/time.h>
+#include <asm/starlet.h>
+#include <asm/starlet-ios.h>
+#include <asm/starlet-mini.h>
+#include <asm/udbg.h>
+
+#include "flipper-pic.h"
+#include "gcnvi_udbg.h"
+#include "usbgecko_udbg.h"
+
+
+static enum starlet_ipc_flavour starlet_ipc_flavour;
+
+static void wii_death_loop(void)
+{
+	local_irq_disable();
+	for (;;)
+		cpu_relax();
+}
+
+#ifdef CONFIG_STARLET_IOS
+
+static void wii_ios_restart(char *cmd)
+{
+	local_irq_disable();
+
+	/* try first to launch The Homebrew Channel... */
+	starlet_es_reload_ios_and_launch(STARLET_TITLE_HBC_JODI);
+	starlet_es_reload_ios_and_launch(STARLET_TITLE_HBC_HAXX);
+	/* ..and if that fails, try an assisted restart */
+	starlet_stm_restart();
+
+	/* fallback to spinning until the power button pressed */
+	for (;;)
+		cpu_relax();
+}
+
+static void wii_ios_power_off(void)
+{
+	local_irq_disable();
+
+	/* try an assisted poweroff */
+	starlet_stm_power_off();
+
+	/* fallback to spinning until the power button pressed */
+	for (;;)
+		cpu_relax();
+}
+
+#else
+
+static void wii_ios_restart(char *cmd)
+{
+	wii_death_loop();
+}
+
+static void wii_ios_power_off(void)
+{
+	wii_death_loop();
+}
+
+#endif /* CONFIG_STARLET_IOS */
+
+#ifdef CONFIG_STARLET_MINI
+
+static void wii_restart(char *cmd)
+{
+	void __iomem *hw_resets;
+
+	local_irq_disable();
+
+	hw_resets = ioremap(0x0d800194, 4);
+	if (hw_resets) {
+		/* clear the system reset pin to cause a reset */
+		clear_bit(0, hw_resets);
+		iounmap(hw_resets);
+	}
+
+	for (;;)
+		cpu_relax();
+}
+
+static void wii_power_off(void)
+{
+	void __iomem *gpio;
+
+	local_irq_disable();
+
+	gpio = ioremap(0x0d8000e0, 3*4);
+	if (gpio) {
+		/* make sure that the poweroff GPIO is configured as output */
+		out_be32(gpio + 4, in_be32(gpio + 4) | 0x2);
+
+		/* drive the poweroff GPIO high */
+		out_be32(gpio + 0, in_be32(gpio + 0) | 0x2);
+		iounmap(gpio);
+	}
+
+	for (;;)
+		cpu_relax();
+}
+
+#else
+
+static void wii_restart(char *cmd)
+{
+	wii_death_loop();
+}
+
+static void wii_power_off(void)
+{
+	wii_death_loop();
+}
+
+#endif /* CONFIG_STARLET_MINI */
+
+static void wii_halt(void)
+{
+	if (ppc_md.restart)
+		ppc_md.restart(NULL);
+	wii_death_loop();
+}
+
+static void wii_show_cpuinfo(struct seq_file *m)
+{
+	seq_printf(m, "vendor\t\t: IBM\n");
+	seq_printf(m, "machine\t\t: Nintendo Wii\n");
+}
+
+int starlet_discover_ipc_flavour(void)
+{
+	struct mipc_infohdr *hdrp;
+	int error;
+
+	error = mipc_discover(&hdrp);
+	if (!error) {
+		starlet_ipc_flavour = STARLET_IPC_MINI;
+		ppc_md.restart = wii_restart;
+		ppc_md.power_off = wii_power_off;
+	} else {
+		starlet_ipc_flavour = STARLET_IPC_IOS;
+		ppc_md.restart = wii_ios_restart;
+		ppc_md.power_off = wii_ios_power_off;
+	}
+
+	return 0;
+}
+
+enum starlet_ipc_flavour starlet_get_ipc_flavour(void)
+{
+	return starlet_ipc_flavour;
+}
+
+static void __init wii_setup_arch(void)
+{
+	ug_udbg_init();
+	gcnvi_udbg_init();
+
+	starlet_discover_ipc_flavour();
+}
+
+static void __init wii_init_early(void)
+{
+}
+
+static int __init wii_probe(void)
+{
+	unsigned long dt_root;
+
+	dt_root = of_get_flat_dt_root();
+	if (!of_flat_dt_is_compatible(dt_root, "nintendo,wii"))
+		return 0;
+
+	return 1;
+}
+
+#ifdef CONFIG_KEXEC
+static void wii_shutdown(void)
+{
+	exi_quiesce();
+	flipper_quiesce();
+}
+
+static int restore_lowmem_stub(struct kimage *image)
+{
+	struct device_node *node;
+	struct resource res;
+	const unsigned long *prop;
+	unsigned long dst, src;
+	size_t size;
+	int error;
+
+	node = of_find_node_by_name(NULL, "lowmem-stub");
+	if (!node) {
+		printk(KERN_ERR "unable to find node %s\n", "lowmem-stub");
+		error = -ENODEV;
+		goto out;
+	}
+
+	error = of_address_to_resource(node, 0, &res);
+	if (error) {
+		printk(KERN_ERR "no lowmem-stub range found\n");
+		goto out_put;
+	}
+	dst = res.start;
+	size = res.end - res.start + 1;
+
+	prop = of_get_property(node, "save-area", NULL);
+	if (!prop) {
+		printk(KERN_ERR "unable to find %s property\n", "save-area");
+		error = -EINVAL;
+		goto out_put;
+	}
+	src = *prop;
+
+	printk(KERN_DEBUG "lowmem-stub: preparing restore from %08lX to %08lX"
+		" (%u bytes)\n", src, dst, size);
+
+	/* schedule a copy of the lowmem stub to its original location */
+	error = kimage_add_preserved_region(image, dst, src, PAGE_ALIGN(size));
+
+out_put:
+	of_node_put(node);
+out:
+	return error;
+}
+
+static int wii_machine_kexec_prepare(struct kimage *image)
+{
+	int error;
+
+	error = restore_lowmem_stub(image);
+	if (error)
+		printk(KERN_ERR "%s: error %d\n", __func__, error);
+	return error;
+}
+
+static void wii_machine_kexec(struct kimage *image)
+{
+	local_irq_disable();
+
+#ifdef CONFIG_STARLET_IOS
+	/*
+	 * Reload IOS to make sure that I/O resources are freed before
+	 * the final kexec phase.
+	 */
+	if (starlet_get_ipc_flavour() == STARLET_IPC_IOS)
+		starlet_es_reload_ios_and_discard();
+#endif
+
+	default_machine_kexec(image);
+}
+
+#endif /* CONFIG_KEXEC */
+
+
+define_machine(wii) {
+	.name			= "wii",
+	.probe			= wii_probe,
+	.setup_arch		= wii_setup_arch,
+	.init_early		= wii_init_early,
+	.show_cpuinfo		= wii_show_cpuinfo,
+	.halt			= wii_halt,
+	.init_IRQ		= flipper_pic_probe,
+	.get_irq		= flipper_pic_get_irq,
+	.calibrate_decr		= generic_calibrate_decr,
+	.progress		= udbg_progress,
+#ifdef CONFIG_KEXEC
+	.machine_shutdown	= wii_shutdown,
+	.machine_kexec_prepare	= wii_machine_kexec_prepare,
+	.machine_kexec		= wii_machine_kexec,
+#endif
+};
+
diff --git a/arch/powerpc/platforms/embedded6xx/wii_dev.c b/arch/powerpc/platforms/embedded6xx/wii_dev.c
new file mode 100644
index 0000000..c16f774
--- /dev/null
+++ b/arch/powerpc/platforms/embedded6xx/wii_dev.c
@@ -0,0 +1,50 @@
+/*
+ * arch/powerpc/platforms/embedded6xx/wii_dev.c
+ *
+ * Nintendo Wii platform device setup.
+ * Copyright (C) 2008-2009 The GameCube Linux Team
+ * Copyright (C) 2008,2009 Albert Herranz
+ *
+ * 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/kernel.h>
+#include <linux/init.h>
+#include <linux/of_platform.h>
+
+#include <asm/machdep.h>
+
+static struct of_device_id wii_of_bus[] = {
+	{ .compatible = "nintendo,hollywood", },
+#ifdef CONFIG_STARLET_IOS
+	{ .compatible = "nintendo,starlet-ios-ipc", },
+#endif
+#ifdef CONFIG_STARLET_MINI
+	{ .compatible = "twiizers,starlet-mini-ipc", },
+#endif
+	{ },
+};
+
+static int __init wii_device_probe(void)
+{
+	struct device_node *np;
+
+	if (!machine_is(wii))
+		return 0;
+
+	of_platform_bus_probe(NULL, wii_of_bus, NULL);
+
+	np = of_find_compatible_node(NULL, NULL, "nintendo,hollywood-mem2");
+	if (np) {
+		of_platform_device_create(np, NULL, NULL);
+		of_node_put(np);
+	}
+
+	return 0;
+}
+device_initcall(wii_device_probe);
+
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 48bbdbe..6087a67 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -50,6 +50,8 @@ source "drivers/char/Kconfig"
 
 source "drivers/i2c/Kconfig"
 
+source "drivers/exi/Kconfig"
+
 source "drivers/spi/Kconfig"
 
 source "drivers/pps/Kconfig"
diff --git a/drivers/Makefile b/drivers/Makefile
index 8b0b948..8b6073b 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -105,6 +105,8 @@ obj-$(CONFIG_DMA_ENGINE)	+= dma/
 obj-$(CONFIG_DCA)		+= dca/
 obj-$(CONFIG_HID)		+= hid/
 obj-$(CONFIG_PPC_PS3)		+= ps3/
+obj-$(CONFIG_GAMECUBE_EXI)	+= exi/
+obj-$(CONFIG_GAMECUBE_SI)	+= input/si/
 obj-$(CONFIG_OF)		+= of/
 obj-$(CONFIG_SSB)		+= ssb/
 obj-$(CONFIG_VLYNQ)		+= vlynq/
diff --git a/drivers/block/Kconfig b/drivers/block/Kconfig
index 1d886e0..d5150d5 100644
--- a/drivers/block/Kconfig
+++ b/drivers/block/Kconfig
@@ -63,6 +63,82 @@ config AMIGA_Z2RAM
 	  To compile this driver as a module, choose M here: the
 	  module will be called z2ram.
 
+config GAMECUBE_SD
+	tristate "Nintendo GameCube/Wii MMC/SD card"
+	depends on GAMECUBE_EXI
+	help
+	  This enables support for using SD and MMC cards through
+	  the Nintendo SD Card Adapter (DOL-019) or compatible hardware.
+
+	  You probably want to compile FAT support, and the required
+	  codepages, or mount will complain. See Filesystems -> DOS/FAT/NT
+	  filesystems and Filesystems -> Native Language Support
+
+	  Say Y if you want to include this driver in the kernel.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gcn-sd.
+
+config GAMECUBE_ARAM
+	tristate "Nintendo GameCube Auxiliary RAM (ARAM)"
+	depends on GAMECUBE
+	help
+	  This enables support for using the 16MB of ARAM found in the
+	  Nintendo GameCube as a block device.
+	  Say Y if you want to include this driver in the kernel.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gcn-aram.
+
+config GAMECUBE_DI
+	tristate "Nintendo GameCube Disk Interface (DI)"
+	depends on GAMECUBE
+	help
+	  This enables support for using the DVD drive unit found
+	  in the Nintendo GameCube.
+	  Say Y if you want to include this driver in the kernel.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gcn-di.
+
+config WII_MEM2
+	tristate "Nintendo Wii MEM2"
+	depends on WII
+	help
+	  This enables support for using the MEM2 found in the
+	  Nintendo Wii as a block device.
+	  Say Y if you want to include this driver in the kernel.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called rvl-mem2.
+
+config WII_SD
+	tristate "Nintendo Wii front slot MMC/SD"
+	depends on STARLET_IOS
+	help
+	  This enables support for MMC/SD cards using the front SD card
+	  slot of the Nintendo Wii.
+
+	  You probably want to compile FAT support, and the required
+	  codepages, or mount will complain. See Filesystems -> DOS/FAT/NT
+	  filesystems and Filesystems -> Native Language Support
+
+	  Say Y if you want to include this driver in the kernel.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called rvl-stsd.
+
+config WII_DI
+	tristate "Nintendo Wii Disk Interface (DI)"
+	depends on STARLET_MINI
+	help
+	  This enables support for using the DVD drive unit found
+	  in the Nintendo Wii.
+	  Say Y if you want to include this driver in the kernel.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called rvl-di.
+
 config BLK_DEV_XD
 	tristate "XT hard disk support"
 	depends on ISA && ISA_DMA_API
diff --git a/drivers/block/Makefile b/drivers/block/Makefile
index cdaa3f8..a2c1b07 100644
--- a/drivers/block/Makefile
+++ b/drivers/block/Makefile
@@ -13,6 +13,12 @@ obj-$(CONFIG_PS3_DISK)		+= ps3disk.o
 obj-$(CONFIG_PS3_VRAM)		+= ps3vram.o
 obj-$(CONFIG_ATARI_FLOPPY)	+= ataflop.o
 obj-$(CONFIG_AMIGA_Z2RAM)	+= z2ram.o
+obj-$(CONFIG_GAMECUBE_SD)	+= gcn-sd.o
+obj-$(CONFIG_GAMECUBE_ARAM)	+= gcn-aram.o
+obj-$(CONFIG_GAMECUBE_DI)	+= gcn-di/
+obj-$(CONFIG_WII_MEM2)		+= rvl-mem2.o
+obj-$(CONFIG_WII_SD)		+= rvl-stsd.o
+obj-$(CONFIG_WII_DI)		+= rvl-di.o
 obj-$(CONFIG_BLK_DEV_RAM)	+= brd.o
 obj-$(CONFIG_BLK_DEV_LOOP)	+= loop.o
 obj-$(CONFIG_BLK_DEV_XD)	+= xd.o
diff --git a/drivers/block/gcn-aram.c b/drivers/block/gcn-aram.c
new file mode 100644
index 0000000..8cbbf22
--- /dev/null
+++ b/drivers/block/gcn-aram.c
@@ -0,0 +1,597 @@
+/*
+ * drivers/block/gcn-aram.c
+ *
+ * Nintendo GameCube Auxiliary RAM (ARAM) block driver
+ * Copyright (C) 2004-2009 The GameCube Linux Team
+ * Copyright (C) 2005 Todd Jeffreys <todd@voidpointer.org>
+ * Copyright (C) 2005,2007,2008,2009 Albert Herranz
+ *
+ * Based on previous work by Franz Lehner.
+ *
+ * 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/blkdev.h>
+#include <linux/dma-mapping.h>
+#include <linux/fcntl.h>	/* O_ACCMODE */
+#include <linux/hdreg.h>	/* HDIO_GETGEO */
+#include <linux/interrupt.h>
+#include <linux/major.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/io.h>
+
+
+#define DRV_MODULE_NAME "gcn-aram"
+#define DRV_DESCRIPTION "Nintendo GameCube Auxiliary RAM (ARAM) block driver"
+#define DRV_AUTHOR      "Todd Jeffreys <todd@voidpointer.org>, " \
+			"Albert Herranz"
+
+static char aram_driver_version[] = "4.0i";
+
+#define drv_printk(level, format, arg...) \
+	printk(level DRV_MODULE_NAME ": " format , ## arg)
+
+
+/*
+ * Hardware.
+ */
+#define ARAM_DMA_ALIGN		0x1f	/* 32 bytes */
+
+#define DSP_CSR			0x00a
+#define  DSP_CSR_RES		(1<<0)
+#define  DSP_CSR_PIINT		(1<<1)
+#define  DSP_CSR_HALT		(1<<2)
+#define  DSP_CSR_AIDINT		(1<<3)
+#define  DSP_CSR_AIDINTMASK	(1<<4)
+#define  DSP_CSR_ARINT		(1<<5)
+#define  DSP_CSR_ARINTMASK	(1<<6)
+#define  DSP_CSR_DSPINT		(1<<7)
+#define  DSP_CSR_DSPINTMASK	(1<<8)
+#define  DSP_CSR_DSPDMA		(1<<9)
+#define  DSP_CSR_RESETXXX	(1<<11)
+
+#define AR_SIZE			0x012
+
+#define AR_MODE			0x016
+#define   AR_MODE_ACCELERATOR	(1 << 0)
+
+#define AR_REFRESH		0x01a
+
+#define AR_DMA_MMADDR		0x020
+
+#define AR_DMA_ARADDR		0x024
+
+#define AR_DMA_CNT_H		0x028
+#define   AR_READ		(1 << 31)
+#define   AR_WRITE		0
+
+#define AR_DMA_CNT_L		0x02a
+
+#define AR_DMA_CNT		AR_DMA_CNT_H
+
+/*
+ * Driver settings
+ */
+#define ARAM_NAME		DRV_MODULE_NAME
+#define ARAM_MAJOR		Z2RAM_MAJOR /* we share the major */
+
+#define ARAM_SECTOR_SIZE	PAGE_SIZE
+
+#define ARAM_BUFFERSIZE		(16*1024*1024)
+
+/*
+ * Driver data.
+ */
+struct aram_drvdata {
+	spinlock_t			queue_lock;
+
+	spinlock_t			io_lock;
+	void __iomem			*io_base;
+	int				irq;
+
+	struct block_device_operations	fops;
+	struct gendisk			*disk;
+	struct request_queue		*queue;
+
+	struct request			*req;	/* protected by ->io_lock */
+	dma_addr_t			dma_addr;
+	size_t				dma_len;
+
+	int				ref_count;
+
+	struct device			*dev;
+};
+
+
+static inline enum dma_data_direction rq_dir_to_dma_dir(struct request *req)
+{
+	if (rq_data_dir(req) == READ)
+		return DMA_FROM_DEVICE;
+	else
+		return DMA_TO_DEVICE;
+}
+
+static inline int rq_dir_to_aram_dir(struct request *req)
+{
+	if (rq_data_dir(req) == READ)
+		return AR_READ;
+	else
+		return AR_WRITE;
+}
+
+static void aram_start_dma_transfer(struct aram_drvdata *drvdata,
+				    unsigned long aram_addr)
+{
+	void __iomem *io_base = drvdata->io_base;
+	dma_addr_t dma_addr = drvdata->dma_addr;
+	size_t dma_len = drvdata->dma_len;
+
+	/* DMA transfers require proper alignment */
+	BUG_ON((dma_addr & ARAM_DMA_ALIGN) != 0 ||
+	       (dma_len & ARAM_DMA_ALIGN) != 0);
+
+	out_be32(io_base + AR_DMA_MMADDR, dma_addr);
+	out_be32(io_base + AR_DMA_ARADDR, aram_addr);
+
+	/* writing the low-word kicks off the DMA */
+	out_be32(io_base + AR_DMA_CNT,
+		 rq_dir_to_aram_dir(drvdata->req) | dma_len);
+}
+
+static irqreturn_t aram_irq_handler(int irq, void *dev0)
+{
+	struct aram_drvdata *drvdata = dev0;
+	struct request *req;
+	u16 __iomem *csr_reg = drvdata->io_base + DSP_CSR;
+	u16 csr;
+	unsigned long flags;
+
+	spin_lock_irqsave(&drvdata->io_lock, flags);
+
+	csr = in_be16(csr_reg);
+
+	/*
+	 * Do nothing if the interrupt is not targetted for us.
+	 * We share this interrupt with the sound driver.
+	 */
+	if (!(csr & DSP_CSR_ARINT)) {
+		spin_unlock_irqrestore(&drvdata->io_lock, flags);
+		return IRQ_NONE;
+	}
+
+	/* strictly ack the ARAM interrupt, and nothing more */
+	csr &= ~(DSP_CSR_AIDINT | DSP_CSR_DSPINT);
+	out_be16(csr_reg, csr);
+
+	/* pick up request in service */
+	req = drvdata->req;
+	drvdata->req = NULL;
+	if (drvdata->dma_len) {
+		dma_unmap_single(drvdata->dev,
+				 drvdata->dma_addr, drvdata->dma_len,
+				 rq_dir_to_dma_dir(req));
+		drvdata->dma_len = 0;
+	}
+
+	spin_unlock_irqrestore(&drvdata->io_lock, flags);
+
+	if (!req) {
+		drv_printk(KERN_ERR, "ignoring interrupt, no request\n");
+		goto out;
+	}
+
+	spin_lock(&drvdata->queue_lock);
+	__blk_end_request_cur(req, 0);
+	blk_start_queue(drvdata->queue);
+	spin_unlock(&drvdata->queue_lock);
+
+out:
+	return IRQ_HANDLED;
+}
+
+static void aram_do_request(struct request_queue *q)
+{
+	struct aram_drvdata *drvdata = q->queuedata;
+	struct request *req;
+	unsigned long aram_addr;
+	size_t len;
+	unsigned long flags;
+	int error;
+
+	req = blk_peek_request(q);
+	while (req) {
+		spin_lock_irqsave(&drvdata->io_lock, flags);
+		if (drvdata->req) {
+			blk_stop_queue(q);
+			spin_unlock_irqrestore(&drvdata->io_lock, flags);
+			break;
+		}
+
+		blk_start_request(req);
+		error = -EIO;
+
+		if (!blk_fs_request(req))
+			goto done;
+
+		/* calculate the ARAM address and length */
+		aram_addr = blk_rq_pos(req) << 9;
+		len = blk_rq_cur_bytes(req);
+
+		/* give up if the request goes out of bounds */
+		if (aram_addr + len > ARAM_BUFFERSIZE) {
+			drv_printk(KERN_ERR, "bad access: block=%lu,"
+				   " size=%u\n", (unsigned long)blk_rq_pos(req),
+				    len);
+			goto done;
+		}
+
+		drvdata->req = req;
+		spin_unlock_irqrestore(&drvdata->io_lock, flags);
+
+		/* perform DMA mappings and start the transfer */
+		drvdata->dma_len = len;
+		drvdata->dma_addr = dma_map_single(drvdata->dev,
+						   req->buffer, len,
+						   rq_dir_to_dma_dir(req));
+		aram_start_dma_transfer(drvdata, aram_addr);
+
+		/* one request at a time */
+		break;
+	done:
+		spin_unlock_irqrestore(&drvdata->io_lock, flags);
+		if (!__blk_end_request_cur(req, error))
+			req = blk_peek_request(q);
+	}
+}
+
+/*
+ * Block device hooks.
+ *
+ */
+
+static int aram_open(struct block_device *bdev, fmode_t mode)
+{
+	struct aram_drvdata *drvdata = bdev->bd_disk->private_data;
+	unsigned long flags;
+	int retval = 0;
+
+	spin_lock_irqsave(&drvdata->queue_lock, flags);
+
+	/* only allow a minor of 0 to be opened */
+	if (MINOR(bdev->bd_dev)) {
+		retval =  -ENODEV;
+		goto out;
+	}
+
+	/* honor exclusive open mode */
+	if (drvdata->ref_count == -1 ||
+	    (drvdata->ref_count && (mode & FMODE_EXCL))) {
+		retval = -EBUSY;
+		goto out;
+	}
+
+	if ((mode & FMODE_EXCL))
+		drvdata->ref_count = -1;
+	else
+		drvdata->ref_count++;
+
+out:
+	spin_unlock_irqrestore(&drvdata->queue_lock, flags);
+	return retval;
+}
+
+static int aram_release(struct gendisk *disk, fmode_t mode)
+{
+	struct aram_drvdata *drvdata = disk->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&drvdata->queue_lock, flags);
+	if (drvdata->ref_count > 0)
+		drvdata->ref_count--;
+	else
+		drvdata->ref_count = 0;
+	spin_unlock_irqrestore(&drvdata->queue_lock, flags);
+
+	return 0;
+}
+
+static int aram_getgeo(struct block_device *bdev, struct hd_geometry *geo)
+{
+	geo->cylinders = get_capacity(bdev->bd_disk) / (4 * 16);
+	geo->heads = 4;
+	geo->sectors = 16;
+	return 0;
+}
+
+static struct block_device_operations aram_fops = {
+	.owner = THIS_MODULE,
+	.open = aram_open,
+	.release = aram_release,
+	.getgeo = aram_getgeo,
+};
+
+
+/*
+ * Setup routines.
+ *
+ */
+
+static int aram_init_blk_dev(struct aram_drvdata *drvdata)
+{
+	struct gendisk *disk;
+	struct request_queue *queue;
+	int retval;
+
+	drvdata->ref_count = 0;
+
+	retval = register_blkdev(ARAM_MAJOR, ARAM_NAME);
+	if (retval)
+		goto err_register_blkdev;
+
+	retval = -ENOMEM;
+	spin_lock_init(&drvdata->queue_lock);
+	spin_lock_init(&drvdata->io_lock);
+	queue = blk_init_queue(aram_do_request, &drvdata->queue_lock);
+	if (!queue)
+		goto err_blk_init_queue;
+
+	blk_queue_logical_block_size(queue, ARAM_SECTOR_SIZE);
+	blk_queue_dma_alignment(queue, ARAM_DMA_ALIGN);
+	blk_queue_max_phys_segments(queue, 1);
+	blk_queue_max_hw_segments(queue, 1);
+	queue_flag_set_unlocked(QUEUE_FLAG_NONROT, queue);
+	queue->queuedata = drvdata;
+	drvdata->queue = queue;
+
+	disk = alloc_disk(1);
+	if (!disk)
+		goto err_alloc_disk;
+
+	disk->major = ARAM_MAJOR;
+	disk->first_minor = 0;
+	disk->fops = &aram_fops;
+	strcpy(disk->disk_name, ARAM_NAME);
+	disk->queue = drvdata->queue;
+	set_capacity(disk, ARAM_BUFFERSIZE >> 9);
+	disk->private_data = drvdata;
+	drvdata->disk = disk;
+
+	add_disk(drvdata->disk);
+
+	retval = 0;
+	goto out;
+
+err_alloc_disk:
+	blk_cleanup_queue(drvdata->queue);
+err_blk_init_queue:
+	unregister_blkdev(ARAM_MAJOR, ARAM_NAME);
+err_register_blkdev:
+out:
+	return retval;
+}
+
+static void aram_exit_blk_dev(struct aram_drvdata *drvdata)
+{
+	if (drvdata->disk) {
+		del_gendisk(drvdata->disk);
+		put_disk(drvdata->disk);
+	}
+	if (drvdata->queue)
+		blk_cleanup_queue(drvdata->queue);
+	unregister_blkdev(ARAM_MAJOR, ARAM_NAME);
+}
+
+static void aram_quiesce(struct aram_drvdata *drvdata)
+{
+	u16 __iomem *csr_reg = drvdata->io_base + DSP_CSR;
+	u16 csr;
+	unsigned long flags;
+
+	/*
+	 * Disable ARAM interrupts, but do not accidentally ack non-ARAM ones.
+	 */
+	spin_lock_irqsave(&drvdata->io_lock, flags);
+	csr = in_be16(csr_reg);
+	csr &= ~(DSP_CSR_AIDINT | DSP_CSR_DSPINT | DSP_CSR_ARINTMASK);
+	out_be16(csr_reg, csr);
+	spin_unlock_irqrestore(&drvdata->io_lock, flags);
+
+	/* wait until pending transfers are finished */
+	while (in_be16(csr_reg) & DSP_CSR_DSPDMA)
+		cpu_relax();
+}
+
+static int aram_init_irq(struct aram_drvdata *drvdata)
+{
+	u16 __iomem *csr_reg = drvdata->io_base + DSP_CSR;
+	u16 csr;
+	unsigned long flags;
+	int retval;
+
+	retval = request_irq(drvdata->irq, aram_irq_handler,
+			     IRQF_DISABLED | IRQF_SHARED,
+			     DRV_MODULE_NAME, drvdata);
+	if (retval) {
+		drv_printk(KERN_ERR, "request of IRQ %d failed\n",
+			   drvdata->irq);
+		goto out;
+	}
+
+	/*
+	 * Enable ARAM interrupts, and route them to the processor.
+	 * Make sure to preserve the AI and DSP interrupts.
+	 */
+	spin_lock_irqsave(&drvdata->io_lock, flags);
+	csr = in_be16(csr_reg);
+	csr |= (DSP_CSR_ARINT | DSP_CSR_ARINTMASK | DSP_CSR_PIINT);
+	csr &= ~(DSP_CSR_AIDINT | DSP_CSR_DSPINT);
+	out_be16(csr_reg, csr);
+	spin_unlock_irqrestore(&drvdata->io_lock, flags);
+
+out:
+	return retval;
+}
+
+static void aram_exit_irq(struct aram_drvdata *drvdata)
+{
+	aram_quiesce(drvdata);
+
+	free_irq(drvdata->irq, drvdata);
+}
+
+static int aram_init(struct aram_drvdata *drvdata,
+		     struct resource *mem, int irq)
+{
+	int retval;
+
+	drvdata->io_base = ioremap(mem->start, mem->end - mem->start + 1);
+	drvdata->irq = irq;
+
+	retval = aram_init_blk_dev(drvdata);
+	if (!retval) {
+		retval = aram_init_irq(drvdata);
+		if (retval)
+			aram_exit_blk_dev(drvdata);
+	}
+	return retval;
+}
+
+static void aram_exit(struct aram_drvdata *drvdata)
+{
+	aram_exit_blk_dev(drvdata);
+	aram_exit_irq(drvdata);
+	if (drvdata->io_base) {
+		iounmap(drvdata->io_base);
+		drvdata->io_base = NULL;
+	}
+}
+
+/*
+ * Driver model helper routines.
+ *
+ */
+
+static int aram_do_probe(struct device *dev, struct resource *mem,
+			 int irq)
+{
+	struct aram_drvdata *drvdata;
+	int retval;
+
+	drvdata = kzalloc(sizeof(*drvdata), GFP_KERNEL);
+	if (!drvdata) {
+		drv_printk(KERN_ERR, "failed to allocate aram_drvdata\n");
+		return -ENOMEM;
+	}
+	dev_set_drvdata(dev, drvdata);
+	drvdata->dev = dev;
+
+	retval = aram_init(drvdata, mem, irq);
+	if (retval) {
+		dev_set_drvdata(dev, NULL);
+		kfree(drvdata);
+	}
+	return retval;
+}
+
+static int aram_do_remove(struct device *dev)
+{
+	struct aram_drvdata *drvdata = dev_get_drvdata(dev);
+
+	if (drvdata) {
+		aram_exit(drvdata);
+		dev_set_drvdata(dev, NULL);
+		kfree(drvdata);
+		return 0;
+	}
+	return -ENODEV;
+}
+
+static int aram_do_shutdown(struct device *dev)
+{
+	struct aram_drvdata *drvdata = dev_get_drvdata(dev);
+
+	if (drvdata)
+		aram_quiesce(drvdata);
+	return 0;
+}
+
+
+/*
+ * OF platform device routines.
+ *
+ */
+
+static int __init aram_of_probe(struct of_device *odev,
+				const struct of_device_id *match)
+{
+	struct resource res;
+	int retval;
+
+	retval = of_address_to_resource(odev->node, 0, &res);
+	if (retval) {
+		drv_printk(KERN_ERR, "no io memory range found\n");
+		return -ENODEV;
+	}
+
+	return aram_do_probe(&odev->dev,
+			     &res, irq_of_parse_and_map(odev->node, 0));
+}
+
+static int __exit aram_of_remove(struct of_device *odev)
+{
+	return aram_do_remove(&odev->dev);
+}
+
+static int aram_of_shutdown(struct of_device *odev)
+{
+	return aram_do_shutdown(&odev->dev);
+}
+
+
+static struct of_device_id aram_of_match[] = {
+	{ .compatible = "nintendo,flipper-auxram" },
+	{ },
+};
+
+
+MODULE_DEVICE_TABLE(of, aram_of_match);
+
+static struct of_platform_driver aram_of_driver = {
+	.owner = THIS_MODULE,
+	.name = DRV_MODULE_NAME,
+	.match_table = aram_of_match,
+	.probe = aram_of_probe,
+	.remove = aram_of_remove,
+	.shutdown = aram_of_shutdown,
+};
+
+/*
+ * Module interfaces.
+ *
+ */
+
+static int __init aram_init_module(void)
+{
+	drv_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION,
+		   aram_driver_version);
+
+	return of_register_platform_driver(&aram_of_driver);
+}
+
+static void __exit aram_exit_module(void)
+{
+	of_unregister_platform_driver(&aram_of_driver);
+}
+
+module_init(aram_init_module);
+module_exit(aram_exit_module);
+
+MODULE_DESCRIPTION(DRV_DESCRIPTION);
+MODULE_AUTHOR(DRV_AUTHOR);
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/block/gcn-di/Makefile b/drivers/block/gcn-di/Makefile
new file mode 100644
index 0000000..09a410d
--- /dev/null
+++ b/drivers/block/gcn-di/Makefile
@@ -0,0 +1,50 @@
+obj-$(CONFIG_GAMECUBE_DI) += gcn-di.o
+
+$(obj)/gcn-di.o: $(obj)/drive_20010608.h \
+			$(obj)/drive_20010831.h \
+			$(obj)/drive_20020402.h \
+			$(obj)/drive_20020823.h
+
+#CONFIG_GAMECUBE_DI_BUILD_FIRMWARE=y
+
+ifeq ($(CONFIG_GAMECUBE_DI_BUILD_FIRMWARE),y)
+
+ASMN102 = mn10200-linux-as
+LDMN102 = mn10200-linux-ld
+OCMN102 = mn10200-linux-objcopy
+
+quiet_cmd_build_difw = BLD FW	$@
+cmd_build_difw = \
+	$(CPP) -DDRIVE_MODEL=$(DRIVE_MODEL) $< > $(obj)/$(@F).s; \
+	$(ASMN102) -o $(obj)/$(@F).o $(obj)/$(@F).s; \
+	$(LDMN102) --section-start absolute=0 -Ttext=0x40d000 \
+		-o $(obj)/$(@F).elf -e 0x40d000 $(obj)/$(@F).o; \
+	$(OCMN102) -I elf32-mn10200 -O binary $(obj)/$(@F).elf \
+		$(obj)/$(@F).bin; \
+	(echo -n "static "; cat $(obj)/$(@F).bin | scripts/bin2c "$(subst .h,,$(@F))_firmware") > $@; \
+	rm -f $(obj)/$(@F).o $(obj)/$(@F).elf $(obj)/$(@F).bin $(obj)/$(@F).s
+
+
+targets += drive_20010608.h
+$(obj)/drive_20010608.h: DRIVE_MODEL := 0x20010608
+$(obj)/drive_20010608.h: $(src)/drive_all.S FORCE
+	$(call if_changed,build_difw)
+
+targets += drive_20010831.h
+$(obj)/drive_20010831.h: DRIVE_MODEL := 0x20010831
+$(obj)/drive_20010831.h: $(src)/drive_all.S FORCE
+	$(call if_changed,build_difw)
+
+targets += drive_20020402.h
+$(obj)/drive_20020402.h: DRIVE_MODEL := 0x20020402
+$(obj)/drive_20020402.h: $(src)/drive_all.S FORCE
+	$(call if_changed,build_difw)
+
+targets += drive_20020823.h
+$(obj)/drive_20020823.h: DRIVE_MODEL := 0x20020823
+$(obj)/drive_20020823.h: $(src)/drive_all.S FORCE
+	$(call if_changed,build_difw)
+
+endif
+
+
diff --git a/drivers/block/gcn-di/drive_20010608.h b/drivers/block/gcn-di/drive_20010608.h
new file mode 100644
index 0000000..7c3e9d8
--- /dev/null
+++ b/drivers/block/gcn-di/drive_20010608.h
@@ -0,0 +1,25 @@
+static const char drive_20010608_firmware[] =
+	"\xf7\x10\xff\xf7\xf4\x74\x25\xd0\x40\xf7\x20\x4c\x80\xf4\x74\x42"
+	"\x9d\x08\xf7\x20\xd6\xfc\xf4\x74\x45\xb1\x08\xf7\x20\xd2\xfc\x80"
+	"\x0c\xc4\xda\xfc\xfe\xc8\xda\xfc\xf5\x00\x01\xe9\x70\xc8\xda\xfc"
+	"\xf5\x00\x02\xe9\x75\xf4\x74\x02\xed\x40\x80\x02\xf0\x20\xc8\x78"
+	"\x80\xc0\x90\x81\xdc\xa8\x80\xf5\x30\x00\xf4\x44\x41\xd1\x40\xf8"
+	"\xaa\x00\x10\xf4\xd0\x3c\xd1\x40\xf0\x01\xdc\xa8\x80\xf5\x30\x00"
+	"\xf7\x48\xaa\x00\xe9\x07\xf4\xc4\x41\xd1\x40\x10\xfe\xf7\x48\xee"
+	"\x00\xe8\x0c\xd8\x55\xe9\x25\xcc\xa9\x80\xfd\x79\x00\xea\x0c\xcc"
+	"\xa9\x80\xc4\xa4\x81\xcc\xaa\x80\xc4\x88\x81\xdc\xa8\x80\xf8\xe0"
+	"\x00\x10\xa0\xf5\x10\x01\xf5\x10\x02\xf5\x10\x03\xfe\xc8\xda\xfc"
+	"\xf7\x00\xfe\xff\xf7\x31\xd2\xfc\xea\x0b\xc8\xda\xfc\xf7\x00\xfd"
+	"\xff\xf7\x31\xd6\xfc\xc4\xda\xfc\xcc\x44\xfc\xf7\x00\xfe\xff\xc4"
+	"\x44\xfc\xf4\x7d\x45\xb1\x08\xe9\x07\xf4\x75\x0c\xd1\x40\xea\x0c"
+	"\xf4\x7d\x42\x9d\x08\xe9\x05\xf4\x75\x35\xd1\x40\xf2\x7c\xd0\x04"
+	"\xcc\x5b\x80\xd8\x01\xe9\x02\x7c\x04\x51\x20\x71\x34\xf4\x7d\xb9"
+	"\x85\x08\xe9\x13\x80\x00\x85\x00\xd8\x00\xe8\x02\x85\x0c\xc5\xda"
+	"\xfc\xf4\x75\x40\xd1\x40\x14\xfe\x80\x01\xea\xea\xf7\x10\xff\xf7"
+	"\xf4\xc9\x40\xd1\x40\xd9\x00\xe8\x17\x21\xf4\x79\x00\xf0\x00\xe9"
+	"\x05\x80\x00\xf5\x10\x09\xd9\x06\xe9\x06\x61\x06\xd5\x06\x41\x06"
+	"\xf4\xe0\x1b\xe0\xc7\xf4\xe0\x96\xcc\xc7\x00\x00\x74\x0a\x08\x00"
+	"\x01\x00\x00\x00"
+	;
+
+const int drive_20010608_firmware_size = 324;
diff --git a/drivers/block/gcn-di/drive_20010831.h b/drivers/block/gcn-di/drive_20010831.h
new file mode 100644
index 0000000..16df341
--- /dev/null
+++ b/drivers/block/gcn-di/drive_20010831.h
@@ -0,0 +1,25 @@
+static const char drive_20010831_firmware[] =
+	"\xf7\x10\xff\xf7\xf4\x74\x25\xd0\x40\xf7\x20\x4c\x80\xf4\x74\x39"
+	"\x9e\x08\xf7\x20\xd6\xfc\xf4\x74\x02\xb3\x08\xf7\x20\xd2\xfc\x80"
+	"\x0c\xc4\xda\xfc\xfe\xc8\xda\xfc\xf5\x00\x01\xe9\x70\xc8\xda\xfc"
+	"\xf5\x00\x02\xe9\x75\xf4\x74\x02\xed\x40\x80\x02\xf0\x20\xc8\x78"
+	"\x80\xc0\x92\x81\xdc\xaa\x80\xf5\x30\x00\xf4\x44\x41\xd1\x40\xf8"
+	"\xaa\x00\x10\xf4\xd0\x3c\xd1\x40\xf0\x01\xdc\xaa\x80\xf5\x30\x00"
+	"\xf7\x48\xaa\x00\xe9\x07\xf4\xc4\x41\xd1\x40\x10\xfe\xf7\x48\xee"
+	"\x00\xe8\x0c\xd8\x55\xe9\x25\xcc\xab\x80\xfd\x79\x00\xea\x0c\xcc"
+	"\xab\x80\xc4\xa6\x81\xcc\xac\x80\xc4\x8a\x81\xdc\xaa\x80\xf8\xe0"
+	"\x00\x10\xa0\xf5\x10\x01\xf5\x10\x02\xf5\x10\x03\xfe\xc8\xda\xfc"
+	"\xf7\x00\xfe\xff\xf7\x31\xd2\xfc\xea\x0b\xc8\xda\xfc\xf7\x00\xfd"
+	"\xff\xf7\x31\xd6\xfc\xc4\xda\xfc\xcc\x44\xfc\xf7\x00\xfe\xff\xc4"
+	"\x44\xfc\xf4\x7d\x02\xb3\x08\xe9\x07\xf4\x75\x0c\xd1\x40\xea\x0c"
+	"\xf4\x7d\x39\x9e\x08\xe9\x05\xf4\x75\x35\xd1\x40\xf2\x7c\xd0\x04"
+	"\xcc\x5b\x80\xd8\x01\xe9\x02\x7c\x04\x51\x20\x71\x34\xf4\x7d\x7f"
+	"\x86\x08\xe9\x13\x80\x00\x85\x00\xd8\x00\xe8\x02\x85\x0c\xc5\xda"
+	"\xfc\xf4\x75\x40\xd1\x40\x14\xfe\x80\x01\xea\xea\xf7\x10\xff\xf7"
+	"\xf4\xc9\x40\xd1\x40\xd9\x00\xe8\x17\x21\xf4\x79\x00\xf0\x00\xe9"
+	"\x05\x80\x00\xf5\x10\x09\xd9\x06\xe9\x06\x61\x06\xd5\x06\x41\x06"
+	"\xf4\xe0\xd8\xe1\xc7\xf4\xe0\x4a\xce\xc7\x00\x00\xa4\x0a\x08\x00"
+	"\x01\x00\x00\x00"
+	;
+
+const int drive_20010831_firmware_size = 324;
diff --git a/drivers/block/gcn-di/drive_20020402.h b/drivers/block/gcn-di/drive_20020402.h
new file mode 100644
index 0000000..dc80971
--- /dev/null
+++ b/drivers/block/gcn-di/drive_20020402.h
@@ -0,0 +1,25 @@
+static const char drive_20020402_firmware[] =
+	"\xf7\x10\xff\xf7\xf4\x74\x25\xd0\x40\xf7\x20\x4c\x80\xf4\x74\xd6"
+	"\x9c\x08\xf7\x20\xd6\xfc\xf4\x74\x28\xae\x08\xf7\x20\xd2\xfc\x80"
+	"\x0c\xc4\xda\xfc\xfe\xc8\xda\xfc\xf5\x00\x01\xe9\x70\xc8\xda\xfc"
+	"\xf5\x00\x02\xe9\x75\xf4\x74\xf9\xec\x40\x80\x02\xf0\x20\xc8\x84"
+	"\x80\xc0\x9c\x81\xdc\xb4\x80\xf5\x30\x00\xf4\x44\x41\xd1\x40\xf8"
+	"\xaa\x00\x10\xf4\xd0\x3c\xd1\x40\xf0\x01\xdc\xb4\x80\xf5\x30\x00"
+	"\xf7\x48\xaa\x00\xe9\x07\xf4\xc4\x41\xd1\x40\x10\xfe\xf7\x48\xee"
+	"\x00\xe8\x0c\xd8\x55\xe9\x25\xcc\xb5\x80\xfd\x79\x00\xea\x0c\xcc"
+	"\xb5\x80\xc4\xb0\x81\xcc\xb6\x80\xc4\x94\x81\xdc\xb4\x80\xf8\xe0"
+	"\x00\x10\xa0\xf5\x10\x01\xf5\x10\x02\xf5\x10\x03\xfe\xc8\xda\xfc"
+	"\xf7\x00\xfe\xff\xf7\x31\xd2\xfc\xea\x0b\xc8\xda\xfc\xf7\x00\xfd"
+	"\xff\xf7\x31\xd6\xfc\xc4\xda\xfc\xcc\x44\xfc\xf7\x00\xfe\xff\xc4"
+	"\x44\xfc\xf4\x7d\x28\xae\x08\xe9\x07\xf4\x75\x0c\xd1\x40\xea\x0c"
+	"\xf4\x7d\xd6\x9c\x08\xe9\x05\xf4\x75\x35\xd1\x40\xf2\x7c\xd0\x04"
+	"\xcc\x5b\x80\xd8\x01\xe9\x02\x7c\x04\x51\x20\x71\x34\xf4\x7d\xc1"
+	"\x85\x08\xe9\x13\x80\x00\x85\x00\xd8\x00\xe8\x02\x85\x0c\xc5\xda"
+	"\xfc\xf4\x75\x40\xd1\x40\x14\xfe\x80\x01\xea\xea\xf7\x10\xff\xf7"
+	"\xf4\xc9\x40\xd1\x40\xd9\x00\xe8\x17\x21\xf4\x79\x00\xf0\x00\xe9"
+	"\x05\x80\x00\xf5\x10\x09\xd9\x06\xe9\x06\x61\x06\xd5\x06\x41\x06"
+	"\xf4\xe0\xfe\xdc\xc7\xf4\xe0\x14\xcc\xc7\x00\x00\x74\x0a\x08\x00"
+	"\x01\x00\x00\x00"
+	;
+
+const int drive_20020402_firmware_size = 324;
diff --git a/drivers/block/gcn-di/drive_20020823.h b/drivers/block/gcn-di/drive_20020823.h
new file mode 100644
index 0000000..f20b5cc
--- /dev/null
+++ b/drivers/block/gcn-di/drive_20020823.h
@@ -0,0 +1,25 @@
+static const char drive_20020823_firmware[] =
+	"\xf7\x10\xff\xf7\xf4\x74\x25\xd0\x40\xf7\x20\x4c\x80\xf4\x74\x32"
+	"\x9d\x08\xf7\x20\xd6\xfc\xf4\x74\x75\xae\x08\xf7\x20\xd2\xfc\x80"
+	"\x0c\xc4\xda\xfc\xfe\xc8\xda\xfc\xf5\x00\x01\xe9\x70\xc8\xda\xfc"
+	"\xf5\x00\x02\xe9\x75\xf4\x74\xf5\xec\x40\x80\x02\xf0\x20\xc8\x80"
+	"\x80\xc0\x98\x81\xdc\xb0\x80\xf5\x30\x00\xf4\x44\x41\xd1\x40\xf8"
+	"\xaa\x00\x10\xf4\xd0\x3c\xd1\x40\xf0\x01\xdc\xb0\x80\xf5\x30\x00"
+	"\xf7\x48\xaa\x00\xe9\x07\xf4\xc4\x41\xd1\x40\x10\xfe\xf7\x48\xee"
+	"\x00\xe8\x0c\xd8\x55\xe9\x25\xcc\xb1\x80\xfd\x79\x00\xea\x0c\xcc"
+	"\xb1\x80\xc4\xac\x81\xcc\xb2\x80\xc4\x90\x81\xdc\xb0\x80\xf8\xe0"
+	"\x00\x10\xa0\xf5\x10\x01\xf5\x10\x02\xf5\x10\x03\xfe\xc8\xda\xfc"
+	"\xf7\x00\xfe\xff\xf7\x31\xd2\xfc\xea\x0b\xc8\xda\xfc\xf7\x00\xfd"
+	"\xff\xf7\x31\xd6\xfc\xc4\xda\xfc\xcc\x44\xfc\xf7\x00\xfe\xff\xc4"
+	"\x44\xfc\xf4\x7d\x75\xae\x08\xe9\x07\xf4\x75\x0c\xd1\x40\xea\x0c"
+	"\xf4\x7d\x32\x9d\x08\xe9\x05\xf4\x75\x35\xd1\x40\xf2\x7c\xd0\x04"
+	"\xcc\x5b\x80\xd8\x01\xe9\x02\x7c\x04\x51\x20\x71\x34\xf4\x7d\xc1"
+	"\x85\x08\xe9\x13\x80\x00\x85\x00\xd8\x00\xe8\x02\x85\x0c\xc5\xda"
+	"\xfc\xf4\x75\x40\xd1\x40\x14\xfe\x80\x01\xea\xea\xf7\x10\xff\xf7"
+	"\xf4\xc9\x40\xd1\x40\xd9\x00\xe8\x17\x21\xf4\x79\x00\xf0\x00\xe9"
+	"\x05\x80\x00\xf5\x10\x09\xd9\x06\xe9\x06\x61\x06\xd5\x06\x41\x06"
+	"\xf4\xe0\x4b\xdd\xc7\xf4\xe0\x6d\xcc\xc7\x00\x00\x74\x0a\x08\x00"
+	"\x01\x00\x00\x00"
+	;
+
+const int drive_20020823_firmware_size = 324;
diff --git a/drivers/block/gcn-di/drive_all.S b/drivers/block/gcn-di/drive_all.S
new file mode 100644
index 0000000..e9d91a0
--- /dev/null
+++ b/drivers/block/gcn-di/drive_all.S
@@ -0,0 +1,415 @@
+/*
+ * DVD+/-R compatible "cactus" firmware extensions
+ * Copyright (C) 2005-2009 The GameCube Linux Team
+ * Copyright (C) 2005,2006,2009 Albert Herranz
+ *
+ * Originally based on analysis of Cobra 1.0 drive code released by tmbinc
+ * on dextrose.com.
+ *
+ * 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.
+ *
+ */
+
+/*
+ * This code is compatible with binutils 2.15 limited mn10200 support.
+ * And it intentionally lacks the audio fix and DRE recovery features.
+ *
+ */
+
+
+.equ UNICR,			0xfc44
+.equ 	UNID,			(1<<0)
+.equ ADB0,			0xfcd2
+.equ ADB1,			0xfcd6
+.equ ADBCTL,			0xfcda
+.equ 	ADB0CK,			(1<<0)
+.equ 	ADB1CK,			(1<<1)
+.equ 	ADB0ON,			(1<<2)
+.equ 	ADB1ON,			(1<<3)
+
+
+.equ irq_handler_vector,	0x804c		/* 04, 06, 08, Panasonic Q */
+.equ irq_depth,			0x805b		/* 04, 06, 08, Panasonic Q */
+
+.equ fake_command,		0xaa
+.equ set_drive_status_command,	0xee		/* same as in gcn-di.c */
+.equ enable_extensions_command,	0x55		/* same as in gcn-di.c */
+
+.equ get_drive_status_command,	0xe0
+
+
+#if DRIVE_MODEL == 0x20020402 /* 04 */
+
+	.equ cmdbuf0,			0x80b4
+	.equ drive_status,		0x81b0
+	.equ drive_status2,		0x8194
+
+	.equ bert,			0x8084
+	.equ ernie,			0x819c
+	.equ cactus,			0x40ecf9
+
+	.section	absolute
+
+	.equ adb1_break_address,	0x089cd6
+	.org 0x089d4e
+	adb1_fixup_exit:
+
+	.equ adb0_break_address,	0x08ae28
+	.org 0x08ae33
+	adb0_fixup_exit:
+
+	.equ disable_extensions_when_called_from,	0x0885c1 /* 04, 08 */
+
+#elif DRIVE_MODEL == 0x20010608 /* 06 */
+
+	.equ cmdbuf0,			0x80a8
+	.equ drive_status,		0x81a4
+	.equ drive_status2,		0x8188
+
+	.equ bert,			0x8078
+	.equ ernie,			0x8190
+	.equ cactus,			0x40ed02
+
+	.section	absolute
+
+	.equ adb1_break_address,	0x089d42
+	.org 0x089dd0
+	adb1_fixup_exit:
+
+	.equ adb0_break_address,	0x08b145
+	.org 0x08b150
+	adb0_fixup_exit:
+
+	.equ disable_extensions_when_called_from,	0x0885b9
+
+#elif DRIVE_MODEL == 0x20020823 /* 08 */
+
+	.equ cmdbuf0,			0x80b0
+	.equ drive_status,		0x81ac
+	.equ drive_status2,		0x8190
+
+	.equ bert,			0x8080
+	.equ ernie,			0x8198
+	.equ cactus,			0x40ecf5
+
+	.section	absolute
+
+	.equ adb1_break_address,	0x089d32
+	.org 0x089da7
+	adb1_fixup_exit:
+
+	.equ adb0_break_address,	0x08ae75
+	.org 0x08ae80
+	adb0_fixup_exit:
+
+	.equ disable_extensions_when_called_from,	0x0885c1 /* 04, 08 */
+
+#elif DRIVE_MODEL == 0x20010831 /* Panasonic Q */
+
+	.equ cmdbuf0,			0x80aa
+	.equ drive_status,		0x81a6
+	.equ drive_status2,		0x818a
+
+	.equ bert,			0x8078
+	.equ ernie,			0x8192
+	.equ cactus,			0x40ed02
+
+	.section	absolute
+
+	.equ adb1_break_address,	0x089e39
+	.org 0x089f84
+	adb1_fixup_exit:
+
+	.equ adb0_break_address,	0x08b302
+	.org 0x08b30d
+	adb0_fixup_exit:
+
+	.equ disable_extensions_when_called_from,	0x08867f
+
+#else
+	#error Sorry, unsupported drive.
+#endif
+
+
+	.section	.text
+	.global		_start
+	.global		_exit
+
+/*
+ * We are launched now through the 'func' debug command.
+ */
+_start:
+_main:
+	/* disable interrupts, do not disturb */
+	and	0xf7ff, psw
+
+	/* replace the current irq handler with ours */
+	mov	our_irq_handler, a0
+	mov	a0, (irq_handler_vector)
+
+	/* setup our extending functions ... */
+	mov	adb1_break_address, a0
+	mov	a0, (ADB1)
+	mov	adb0_break_address, a0
+	mov	a0, (ADB0)
+
+	/* ... and enable them */
+	mov	ADB1ON|ADB0ON, d0
+	movb	d0, (ADBCTL)
+
+	rts
+
+
+our_irq_handler:
+	/* check for Address Break 0 */
+	mov	(ADBCTL), d0
+	and	ADB0CK, d0
+	bne	adb0_break_handler
+
+	/* check for Address Break 1 */
+	mov	(ADBCTL), d0
+	and	ADB1CK, d0
+	bne	adb1_break_handler
+
+	/* XXX not sure about this one... */
+//	mov	0x0c, d0
+//	movb	d0, (0x819a)
+
+	/* tell the drive to please accept the disk */
+	mov	cactus, a0
+	mov	2, d0
+	bset	d0, (a0)
+
+	/* this seems to avoid errors if the drive idles for too long */
+	mov	(bert), d0
+	mov	d0, (ernie)
+
+	/* save current command ... */
+	mov	cmdbuf0, a0
+	movbu	(0, a0), d0
+	movb	d0, (saved_cmdbuf0)
+
+	/* ... and place a temporary fake command, to detect new commands */
+	mov	fake_command, d0
+	movb	d0, (a0)
+
+	/* call the original handler */
+	mov	(saved_irq_handler), a0
+	jsr	(a0)
+
+	/* if our fake command changed, we assume a new command has arrived */
+	mov	cmdbuf0, a0
+	movbu	(0, a0), d0
+	cmp	fake_command, d0
+	bne	extra_command_parser
+
+	/* if there is no new command, restore the previously saved command */
+	movb	(saved_cmdbuf0), d0
+	movb	d0, (a0)
+
+	rts
+
+
+extra_command_parser:
+	/* "set drive status" command */
+	cmp	set_drive_status_command, d0
+	beq	set_drive_status
+
+	/* "enable extensions" command */
+	cmp	enable_extensions_command, d0
+	bne	done
+
+enable_or_disable_extensions:
+	/* 0x55, 0xZZ, 0x00, 0x00 */
+	/* ZZ=0 disable, otherwise enable */
+	movbu	(cmdbuf0+1), d0
+	jsr	di_enable_or_disable_extensions
+	jmp	get_drive_status
+
+set_drive_status:
+	/* 0xee, 0xZZ, 0xYY, 0x00 */
+	/* ZZ=drive_status, YY=drive_status2 */
+	movbu	(cmdbuf0+1), d0
+	movb	d0, (drive_status)
+	movbu	(cmdbuf0+2), d0
+	movb	d0, (drive_status2)
+
+
+get_drive_status:
+	/*
+	 * This saves us an invalid command error and updates the status
+	 * accordingly. In fact, our extended command becomes a "get status"
+	 * command.
+	 */
+	mov	cmdbuf0, a0
+	mov	get_drive_status_command, d0
+	movb	d0, (a0)
+	sub	d0, d0
+	movb	d0, (1,a0)
+	movb	d0, (2,a0)
+	movb	d0, (3,a0)
+
+done:
+	rts
+
+
+/*
+ * This is how the stacks look like when our interrupt handler is called.
+ *
+ * Our interrupt handler is in fact not the real interrupt handler, but
+ * just a subroutine called by the real interrupt handler.
+ * That's why we just RTS and not RTI from our interrupt handler.
+ *
+ *    |        |            |        |
+ *  00| d0    0| <- old a3  |        |
+ *  02|       8|            |        |
+ *  04| d1    6|            |        |
+ *  06|       4|            |        |
+ *  08| d2    2|            |        |
+ *  0a|       0|            |        |
+ *  0c| d3    8|            |        |
+ *  0e|       6|            |        |
+ *  10| a0    4|            |        |
+ *  12|       2|            |        |
+ *  14| a1    0|            |        |
+ *  16|       8|            |        |
+ *  18| a2    6|            |        |
+ *  1a|       4|            |        |
+ *  1c| MDR   2|            |        |
+ *  1e| PSW    |            |        |
+ *  20| PC lo  |            | PC lo  | <- a3
+ *  22| PC hi  |            | PC hi  |
+ *    :        :            | old a3 |
+ *    | ...    |            |        |
+ *    +--------+            +--------+ <- (0x8ea1c) for drive 04
+ *    normal context stack  interrupt context stack
+ *
+ */
+
+adb0_break_handler:
+	mov	(ADBCTL), d0
+	and	~ADB0CK, d0
+	mov	(ADB0), a1
+	jmp	address_break_handler
+
+adb1_break_handler:
+	mov	(ADBCTL), d0
+	and	~ADB1CK, d0
+	mov	(ADB1), a1
+
+address_break_handler:
+	/* ack the interrupt */
+	movb	d0, (ADBCTL)
+	movbu	(UNICR), d0
+	and	~UNID, d0
+	movb	d0, (UNICR)
+
+	cmp	adb0_break_address, a1
+	bne	1f
+	mov	adb0_fixup, a1
+	jmp	2f
+
+1:
+	cmp	adb1_break_address, a1
+	bne	2f
+	mov	adb1_fixup, a1
+
+2:
+	/* point to the previous stack pointer */
+	mov	a3, a0
+	add	4, a0
+
+	/*
+	 * Special case. When entering interrupt context the first time,
+	 * the old stack is pushed in the interrupt stack before calling us.
+	 */
+	movbu	(irq_depth), d0
+	cmp	1, d0
+	bne	1f
+	mov	(4, a3), a0	/* get the old stack pointer */
+1:
+	/* overwrite the original return address (look at the stack layout) */
+	mov	a1, (0x20, a0)
+
+	/*
+	 * We disable the extensions when an original disc is found.
+	 *
+	 * We do that by checking if we were called from a piece of
+	 * code reached only when original discs are inserted. Tricky.
+	 */
+
+	/* 0x20 + 0x10 + 0x04 = 0x34 */
+	mov	(0x34, a0), a1
+	cmp	disable_extensions_when_called_from, a1
+	bne	9f	/* else, do nothing */
+
+di_disable_extensions:
+	mov	0, d0
+
+di_enable_or_disable_extensions:
+	/* enable additional media if extensions are enabled */
+	mov	0, d1
+	cmp	0, d0
+	beq	1f
+	mov	ADB0ON|ADB1ON, d1
+1:
+	movb	d1, (ADBCTL)
+
+	mov	enable_extensions, a1
+	movb	d0, (a1)
+
+9:
+	rts
+
+di_enable_extensions:
+	mov	1, d0
+	jmp	di_enable_or_disable_extensions
+
+
+adb0_fixup:
+	/* disable interrupts, XXX really needed here...? */
+	and	0xf7ff, psw
+
+	/* check if we need to tweak things or not */
+	movbu	(enable_extensions), d1
+	cmp	0, d1
+	beq	1f
+
+	/* deal with the dvd seed */
+	mov	(a0), d1
+	cmp	0x00f000, d1		/* controller setup */
+	bne	2f
+	mov	0x00, d0		/* seed 0x00 for normal DVD */
+	movb	d0, (0x09, a0)
+2:
+	/* skip the extra field */
+	cmp	0x06, d1		/* transfer sector buffer */
+	bne	1f
+	mov	(0x06, a0), d1
+	add	6, d1			/* skip it */
+	mov	d1, (0x06, a0)
+1:
+	jmp	adb0_fixup_exit
+
+
+adb1_fixup:
+	jmp	adb1_fixup_exit
+
+
+.align 2
+saved_irq_handler:
+#if DRIVE_MODEL != 0x20010831
+	.long	0x00080A74	/* 04, 06, 08 */
+#else
+	.long	0x00080AA4	/* Panasonic Q */
+#endif
+enable_extensions:
+	.byte	0x01
+saved_cmdbuf0:
+	.byte	0x00
+
+_exit:
+
diff --git a/drivers/block/gcn-di/gcn-di.c b/drivers/block/gcn-di/gcn-di.c
new file mode 100644
index 0000000..2e3a023
--- /dev/null
+++ b/drivers/block/gcn-di/gcn-di.c
@@ -0,0 +1,2361 @@
+/*
+ * drivers/block/gcn-di/gcn-di.c
+ *
+ * Nintendo GameCube Disk Interface (DI) driver
+ * Copyright (C) 2005-2009 The GameCube Linux Team
+ * Copyright (C) 2005,2006,2007,2009 Albert Herranz
+ *
+ * Portions based on previous work by Scream|CT.
+ *
+ * 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/blkdev.h>
+#include <linux/cdrom.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/fcntl.h>
+#include <linux/hdreg.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/timer.h>
+#include <linux/io.h>
+
+#define DI_DEBUG
+
+#define DRV_MODULE_NAME	"gcn-di"
+#define DRV_DESCRIPTION	"Nintendo GameCube Disk Interface (DI) driver"
+#define DRV_AUTHOR	"Albert Herranz"
+
+static char di_driver_version[] = "1.0i";
+
+#define drv_printk(level, format, arg...) \
+	printk(level DRV_MODULE_NAME ": " format , ## arg)
+
+#ifdef DI_DEBUG
+#  define DBG(fmt, args...) \
+	   printk(KERN_ERR "%s: " fmt, __func__ , ## args)
+#else
+#  define DBG(fmt, args...)
+#endif
+
+
+/*
+ * Hardware.
+ */
+#define DI_DMA_ALIGN		0x1f /* 32 bytes */
+
+/* DI Status Register */
+#define DI_SR			0x00
+#define  DI_SR_BRK		(1<<0)
+#define  DI_SR_DEINTMASK	(1<<1)
+#define  DI_SR_DEINT		(1<<2)
+#define  DI_SR_TCINTMASK	(1<<3)
+#define  DI_SR_TCINT		(1<<4)
+#define  DI_SR_BRKINTMASK	(1<<5)
+#define  DI_SR_BRKINT		(1<<6)
+
+/* DI Cover Register */
+#define DI_CVR			0x04
+#define  DI_CVR_CVR		(1<<0)
+#define  DI_CVR_CVRINTMASK	(1<<1)
+#define  DI_CVR_CVRINT		(1<<2)
+
+/* DI Command Buffers */
+#define DI_CMDBUF0		0x08
+#define DI_CMDBUF1		0x0c
+#define DI_CMDBUF2		0x10
+
+/* DI DMA Memory Address Register */
+#define DI_MAR			0x14
+
+/* DI DMA Transfer Length Register */
+#define DI_LENGTH		0x18
+
+/* DI Control Register */
+#define DI_CR			0x1c
+#define  DI_CR_TSTART		(1<<0)
+#define  DI_CR_DMA		(1<<1)
+#define  DI_CR_RW		(1<<2)
+
+/* DI Immediate Data Buffer */
+#define DI_DATA			0x20
+
+/* DI Configuration Register */
+#define DI_CFG			0x24
+
+
+/* drive status, status */
+#define DI_STATUS(s)		((u8)((s)>>24))
+
+#define DI_STATUS_READY			0x00
+#define DI_STATUS_COVER_OPENED		0x01
+#define DI_STATUS_DISK_CHANGE		0x02
+#define DI_STATUS_NO_DISK		0x03
+#define DI_STATUS_MOTOR_STOP		0x04
+#define DI_STATUS_DISK_ID_NOT_READ	0x05
+
+/* drive status, error */
+#define DI_ERROR(s)		((u32)((s)&0x00ffffff))
+
+#define DI_ERROR_NO_ERROR		0x000000
+#define DI_ERROR_MOTOR_STOPPED		0x020400
+#define DI_ERROR_DISK_ID_NOT_READ	0x020401
+#define DI_ERROR_MEDIUM_NOT_PRESENT	0x023a00
+#define DI_ERROR_SEEK_INCOMPLETE	0x030200
+#define DI_ERROR_UNRECOVERABLE_READ	0x031100
+#define DI_ERROR_INVALID_COMMAND	0x052000
+#define DI_ERROR_BLOCK_OUT_OF_RANGE	0x052100
+#define DI_ERROR_INVALID_FIELD		0x052400
+#define DI_ERROR_MEDIUM_CHANGED		0x062800
+
+#define di_may_retry(s)		((DI_STATUS(s) == DI_STATUS_READY || \
+				  DI_STATUS(s) == DI_STATUS_DISK_ID_NOT_READ) \
+				 && \
+				 (DI_ERROR(s) != DI_ERROR_SEEK_INCOMPLETE))
+
+/* DI Sector Size */
+#define DI_SECTOR_SHIFT		11
+#define DI_SECTOR_SIZE		(1 << DI_SECTOR_SHIFT) /*2048*/
+#define DI_MAX_SECTORS		712880
+
+
+/* Driver Settings */
+#define DI_NAME			DRV_MODULE_NAME
+#define DI_MAJOR		60
+
+#define DI_COMMAND_TIMEOUT	20 /* seconds */
+#define DI_COMMAND_RETRIES	10 /* times */
+
+#define DI_MOTOR_OFF_TIMEOUT	10
+
+#define KERNEL_SECTOR_SHIFT	9
+#define KERNEL_SECTOR_SIZE	(1 << KERNEL_SECTOR_SHIFT) /*512*/
+
+
+/*
+ * Drive Information.
+ */
+struct di_drive_info {
+	u16				rev;
+	u16				code;
+	u32				date;
+	u8				pad[0x18];
+};
+
+/*
+ * Disk ID.
+ */
+struct di_disk_id {
+	u8				id[32];
+};
+
+/*
+ * An operation code.
+ */
+struct di_opcode {
+	u16				op;
+#define DI_OP(id, flags)		(((u8)(id)<<8)|((u8)(flags)))
+#define DI_OP_ID(op)		((u8)((op)>>8))
+#define DI_OP_FLAGS(op)		((u8)(op))
+
+#define   DI_DIR_READ		0x00
+#define   DI_DIR_WRITE		DI_CR_RW
+#define   DI_MODE_IMMED		0x00
+#define   DI_MODE_DMA		DI_CR_DMA
+#define   DI_IGNORE_ERRORS	(1<<7)
+
+	char				*name;
+
+	u32				cmdbuf0;
+};
+
+/*
+ * Drive code container.
+ */
+struct di_drive_code {
+	u32				address;
+	size_t				len;
+	void				*code;
+};
+
+struct di_device;
+
+/*
+ * A Disk Interface command.
+ */
+struct di_command {
+	u16				opidx;
+
+	u32				cmdbuf0;
+	u32				cmdbuf1;
+	u32				cmdbuf2;
+
+	void				*data;
+	size_t				len;
+
+	dma_addr_t			dma_addr;
+	size_t				dma_len;
+
+	void				*done_data;
+	void				(*done)(struct di_command *cmd);
+
+	u16				retries;
+	u16				max_retries;
+
+	u32				result;
+
+	struct di_device		*ddev;
+};
+
+#define di_result_ok(result)	((result) == DI_SR_TCINT)
+#define di_command_ok(cmd)	(di_result_ok((cmd)->result))
+
+enum {
+	__DI_INTEROPERABLE = 0,
+	__DI_MEDIA_CHANGED,
+	__DI_START_QUEUE,
+	__DI_RESETTING,
+	__DI_AVOID_DEBUG,
+};
+
+/*
+ * The Disk Interface device.
+ */
+struct di_device {
+	spinlock_t			lock;
+
+	int				irq;
+
+	spinlock_t			io_lock;
+	void __iomem			*io_base;
+
+	struct di_command		*cmd;
+	struct di_command		*failed_cmd;
+
+	struct di_command		status;
+	u32				drive_status;
+
+	struct gendisk                  *disk;
+	struct request_queue            *queue;
+	spinlock_t			queue_lock;
+
+	struct request                  *req;
+	struct di_command		req_cmd;
+
+	struct di_drive_code		*drive_code;
+
+	u32				model;
+	unsigned long			flags;
+#define DI_INTEROPERABLE	(1<<__DI_INTEROPERABLE)
+#define DI_MEDIA_CHANGED	(1<<__DI_MEDIA_CHANGED)
+#define DI_START_QUEUE		(1<<__DI_START_QUEUE)
+#define DI_RESETTING		(1<<__DI_RESETTING)
+#define DI_AVOID_DEBUG		(1<<__DI_AVOID_DEBUG)
+
+	unsigned long			nr_sectors;
+
+	struct timer_list		motor_off_timer;
+
+#ifdef CONFIG_PROC_FS
+	struct proc_dir_entry		*proc;
+#endif /* CONFIG_PROC_FS */
+
+	int				ref_count;
+
+	struct device			*dev;
+};
+
+
+static struct di_drive_info di_drive_info
+		 __attribute__ ((aligned(DI_DMA_ALIGN+1)));
+
+/*
+ * We do not accept original media with this driver, as there is currently no
+ * general need for that.
+ * If you ever develop an application (a media player for example) which works
+ * with original media, just change di_accept_gods and recompile.
+ */
+static const int di_accept_gods;
+
+/*
+ * Drive firmware extensions.
+ *
+ */
+
+#define DI_DRIVE_CODE_BASE	0x40d000
+#define DI_DRIVE_IRQ_VECTOR	0x00804c
+
+/*
+ * Drive 04 (20020402) firmware extensions.
+ */
+
+#include "drive_20020402.h"
+
+static struct di_drive_code drive_20020402 = {
+	.address = DI_DRIVE_CODE_BASE,
+	.len = sizeof(drive_20020402_firmware),
+	.code = (u8 *)drive_20020402_firmware,
+};
+
+/*
+ * Drive 06 (20010608) firmware extensions.
+ */
+
+#include "drive_20010608.h"
+
+static struct di_drive_code drive_20010608 = {
+	.address = DI_DRIVE_CODE_BASE,
+	.len = sizeof(drive_20010608_firmware),
+	.code = (u8 *)drive_20010608_firmware,
+};
+
+/*
+ * Drive 08 (20020823) firmware extensions.
+ */
+
+#include "drive_20020823.h"
+
+static struct di_drive_code drive_20020823 = {
+	.address = DI_DRIVE_CODE_BASE,
+	.len = sizeof(drive_20020823_firmware),
+	.code = (u8 *)drive_20020823_firmware,
+};
+
+/*
+ * Panasonic Q (20010831) firmware extensions.
+ */
+
+#include "drive_20010831.h"
+
+static struct di_drive_code drive_20010831 = {
+	.address = DI_DRIVE_CODE_BASE,
+	.len = sizeof(drive_20010831_firmware),
+	.code = (u8 *)drive_20010831_firmware,
+};
+
+
+/*
+ * Drive operations table, incomplete.
+ * We just include here some of the available functions, in no particular
+ * order.
+ */
+#define CMDBUF(a, b, c, d) (((a)<<24)|((b)<<16)|((c)<<8)|(d))
+
+static struct di_opcode di_opcodes[] = {
+
+#define DI_OP_NOP		0
+	[DI_OP_NOP] = {
+		.op = DI_OP(DI_OP_NOP, 0),
+		.name = "NOP",
+		.cmdbuf0 = 0,
+	},
+
+#define DI_OP_INQ		(DI_OP_NOP+1)
+	[DI_OP_INQ] = {
+		.op = DI_OP(DI_OP_INQ, DI_DIR_READ | DI_MODE_DMA),
+		.name = "INQ",
+		.cmdbuf0 = 0x12000000,
+	},
+
+#define DI_OP_STOPMOTOR		(DI_OP_INQ+1)
+	[DI_OP_STOPMOTOR] = {
+		.op = DI_OP(DI_OP_STOPMOTOR, DI_DIR_READ | DI_MODE_IMMED),
+		.name = "STOPMOTOR",
+		.cmdbuf0 = 0xe3000000,
+	},
+
+#define DI_OP_READDISKID	(DI_OP_STOPMOTOR+1)
+	[DI_OP_READDISKID] = {
+		.op = DI_OP(DI_OP_READDISKID, DI_DIR_READ | DI_MODE_DMA),
+		.name = "READDISKID",
+		.cmdbuf0 = 0xa8000040,
+	},
+
+#define DI_OP_READSECTOR	(DI_OP_READDISKID+1)
+	[DI_OP_READSECTOR] = {
+		.op = DI_OP(DI_OP_READSECTOR, DI_DIR_READ | DI_MODE_DMA),
+		.name = "READSECTOR",
+		.cmdbuf0 = 0xa8000000,
+	},
+
+#define DI_OP_ENABLE1		(DI_OP_READSECTOR+1)
+	[DI_OP_ENABLE1] = {
+		.op = DI_OP(DI_OP_ENABLE1, DI_DIR_READ | DI_MODE_IMMED),
+		.name = "MATSHITA",
+		.cmdbuf0 = 0,
+	},
+
+#define DI_OP_ENABLE2		(DI_OP_ENABLE1+1)
+	[DI_OP_ENABLE2] = {
+		.op = DI_OP(DI_OP_ENABLE2, DI_DIR_READ | DI_MODE_IMMED),
+		.name = "DVD-GAME",
+		.cmdbuf0 = 0,
+	},
+
+/*
+ * The following commands are available in debug mode only.
+ */
+
+#define DI_OP_READMEM		(DI_OP_ENABLE2+1)
+	[DI_OP_READMEM] = {
+		.op = DI_OP(DI_OP_READMEM, DI_DIR_READ | DI_MODE_IMMED),
+		.name = "READMEM",
+		.cmdbuf0 = 0xfe010000,
+	},
+
+#define DI_OP_WRITEMEM		(DI_OP_READMEM+1)
+	[DI_OP_WRITEMEM] = {
+		.op = DI_OP(DI_OP_WRITEMEM, DI_DIR_READ | DI_MODE_DMA),
+		.name = "WRITEMEM",
+		.cmdbuf0 = 0xfe010100,
+	},
+
+#define DI_OP_FUNC		(DI_OP_WRITEMEM+1)
+	[DI_OP_FUNC] = {
+		.op = DI_OP(DI_OP_FUNC, DI_DIR_READ | DI_MODE_IMMED),
+		.name = "FUNC",
+		.cmdbuf0 = 0xfe120000,
+	},
+
+#define DI_OP_GETSTATUS		(DI_OP_FUNC+1)
+	[DI_OP_GETSTATUS] = {
+		.op = DI_OP(DI_OP_GETSTATUS, DI_DIR_READ | DI_MODE_IMMED),
+		.name = "GETSTATUS",
+		.cmdbuf0 = 0xe0000000,
+	},
+
+/* thanks to blackcheck for pointing this one */
+#define DI_OP_SPINMOTOR		(DI_OP_GETSTATUS+1)
+	[DI_OP_SPINMOTOR] = {
+		.op = DI_OP(DI_OP_SPINMOTOR, DI_DIR_READ | DI_MODE_IMMED),
+		.name = "SPINMOTOR",
+		.cmdbuf0 = 0xfe110001,
+#define DI_SPINMOTOR_MASK	0x0000ff00
+#define DI_SPINMOTOR_DOWN	0x00000000
+#define DI_SPINMOTOR_UP		0x00000100
+#define DI_SPINMOTOR_CHECKDISK	0x00008000
+	},
+
+/*
+ * The following commands are part of the firmware extensions.
+ */
+
+#define DI_OP_SETSTATUS		(DI_OP_SPINMOTOR+1)
+	[DI_OP_SETSTATUS] = {
+		.op = DI_OP(DI_OP_SETSTATUS, DI_DIR_READ | DI_MODE_IMMED),
+		.name = "SETSTATUS",
+		.cmdbuf0 = 0xee000000,
+#define DI_SETSTATUS_MASK	0x00ff0000
+#define DI_SETSTATUS_SHIFT	16
+	},
+
+#define DI_OP_ENABLEEXTENSIONS	(DI_OP_SETSTATUS+1)
+	[DI_OP_ENABLEEXTENSIONS] = {
+		.op = DI_OP(DI_OP_ENABLEEXTENSIONS, DI_DIR_READ|DI_MODE_IMMED|
+							DI_IGNORE_ERRORS),
+		.name = "ENABLEEXTENSIONS",
+		.cmdbuf0 = 0x55000000,
+#define DI_ENABLEEXTENSIONS_MASK	0x00ff0000
+#define DI_ENABLEEXTENSIONS_SHIFT	16
+	},
+
+#define DI_OP_MAXOP		DI_OP_ENABLEEXTENSIONS
+};
+
+#define DI_OP_CUSTOM		((u16)~0)
+
+
+static void di_reset(struct di_device *ddev);
+static int di_run_command(struct di_command *cmd);
+
+/*
+ * Returns the operation code related data for a command.
+ */
+static inline struct di_opcode *di_get_opcode(struct di_command *cmd)
+{
+	BUG_ON(cmd->opidx > DI_OP_MAXOP && cmd->opidx != DI_OP_CUSTOM);
+
+	if (cmd->opidx == DI_OP_CUSTOM)
+		return cmd->data;
+	else
+		return &di_opcodes[cmd->opidx];
+}
+
+/*
+ * Returns the operation code for a command.
+ */
+static inline u16 di_op(struct di_command *cmd)
+{
+	return di_get_opcode(cmd)->op;
+}
+
+
+/*
+ * Basic initialization for all commands.
+ */
+static void di_op_basic(struct di_command *cmd,
+			struct di_device *ddev, u16 opidx)
+{
+	struct di_opcode *opcode;
+
+	memset(cmd, 0, sizeof(*cmd));
+	cmd->ddev = ddev;
+	cmd->opidx = opidx;
+	cmd->max_retries = cmd->retries = 0;
+	opcode = di_get_opcode(cmd);
+	if (opcode)
+		cmd->cmdbuf0 = opcode->cmdbuf0;
+}
+
+/*
+ * Builds an "Inquiry" command.
+ */
+static void di_op_inq(struct di_command *cmd,
+		      struct di_device *ddev,
+		      struct di_drive_info *drive_info)
+{
+	di_op_basic(cmd, ddev, DI_OP_INQ);
+	cmd->cmdbuf2 = sizeof(*drive_info);
+	cmd->data = drive_info;
+	cmd->len = sizeof(*drive_info);
+}
+
+/*
+ * Builds a "Stop Motor" command.
+ */
+static inline void di_op_stopmotor(struct di_command *cmd,
+				   struct di_device *ddev)
+{
+	di_op_basic(cmd, ddev, DI_OP_STOPMOTOR);
+}
+
+/*
+ * Builds a "Read Disc ID" command.
+ */
+static void di_op_readdiskid(struct di_command *cmd,
+			     struct di_device *ddev,
+			     struct di_disk_id *disk_id)
+{
+	di_op_basic(cmd, ddev, DI_OP_READDISKID);
+	cmd->cmdbuf2 = sizeof(*disk_id);
+	cmd->data = disk_id;
+	cmd->len = sizeof(*disk_id);
+	cmd->max_retries = cmd->retries = DI_COMMAND_RETRIES;
+}
+
+/*
+ * Builds a "Read Sector" command.
+ */
+static void di_op_readsector(struct di_command *cmd,
+			     struct di_device *ddev,
+			     u32 sector, void *data, size_t len)
+{
+	di_op_basic(cmd, ddev, DI_OP_READSECTOR);
+	cmd->cmdbuf1 = sector;
+	cmd->cmdbuf2 = len;
+	cmd->data = data;
+	cmd->len = len;
+	cmd->max_retries = cmd->retries = DI_COMMAND_RETRIES;
+}
+
+/*
+ * Builds the first enable command.
+ */
+static void di_op_enable1(struct di_command *cmd, struct di_device *ddev)
+{
+	di_op_basic(cmd, ddev, DI_OP_ENABLE1);
+	cmd->cmdbuf0 = CMDBUF(0xff, 0x01, 'M', 'A');
+	cmd->cmdbuf1 = CMDBUF('T', 'S', 'H', 'I');
+	cmd->cmdbuf2 = CMDBUF('T', 'A', 0x02, 0x00);
+}
+
+/*
+ * Builds the second enable command.
+ */
+static void di_op_enable2(struct di_command *cmd, struct di_device *ddev)
+{
+	di_op_basic(cmd, ddev, DI_OP_ENABLE2);
+	cmd->cmdbuf0 = CMDBUF(0xff, 0x00, 'D', 'V');
+	cmd->cmdbuf1 = CMDBUF('D', '-', 'G', 'A');
+	cmd->cmdbuf2 = CMDBUF('M', 'E', 0x03, 0x00);
+}
+
+/*
+ * Builds a "Read Memory" command.
+ * Requires debug mode enabled.
+ */
+static inline void di_op_readmem(struct di_command *cmd,
+				 struct di_device *ddev)
+{
+	di_op_basic(cmd, ddev, DI_OP_READMEM);
+	cmd->cmdbuf2 = 0x00010000;
+}
+
+/*
+ * Builds a "Invoke func" command.
+ * Requires debug mode enabled.
+ */
+static inline void di_op_func(struct di_command *cmd,
+			      struct di_device *ddev, u32 address)
+{
+	di_op_basic(cmd, ddev, DI_OP_FUNC);
+	cmd->cmdbuf1 = address;
+	cmd->cmdbuf2 = CMDBUF('f', 'u', 'n', 'c');
+}
+
+/*
+ * Builds a "Write Memory" command.
+ * Requires debug mode enabled.
+ */
+static inline void di_op_writemem(struct di_command *cmd,
+				 struct di_device *ddev)
+{
+	di_op_basic(cmd, ddev, DI_OP_WRITEMEM);
+}
+
+/*
+ * Builds a "get drive status" command.
+ */
+static inline void di_op_getstatus(struct di_command *cmd,
+				   struct di_device *ddev)
+{
+	di_op_basic(cmd, ddev, DI_OP_GETSTATUS);
+}
+
+/*
+ * Builds a "spin motor" command.
+ * Requires debug mode enabled.
+ */
+static void di_op_spinmotor(struct di_command *cmd,
+			    struct di_device *ddev, u32 flags)
+{
+	di_op_basic(cmd, ddev, DI_OP_SPINMOTOR);
+	cmd->cmdbuf0 |= (flags & DI_SPINMOTOR_MASK);
+}
+
+/*
+ * Builds a "set drive status" command.
+ * Requires debug mode enabled.
+ */
+static void di_op_setstatus(struct di_command *cmd,
+			    struct di_device *ddev, u8 status)
+{
+	di_op_basic(cmd, ddev, DI_OP_SETSTATUS);
+	cmd->cmdbuf0 |= ((status << DI_SETSTATUS_SHIFT) & DI_SETSTATUS_MASK);
+}
+
+/*
+ * Builds a "enable extensions" command.
+ * The extended firmware will transparently disable the extensions when
+ * original media is found.
+ * Requires debug mode enabled.
+ */
+static void di_op_enableextensions(struct di_command *cmd,
+				  struct di_device *ddev, u8 enable)
+{
+	di_op_basic(cmd, ddev, DI_OP_ENABLEEXTENSIONS);
+	cmd->cmdbuf0 |= ((enable << DI_ENABLEEXTENSIONS_SHIFT) &
+			DI_ENABLEEXTENSIONS_MASK);
+}
+
+/*
+ * Builds a customized command.
+ */
+static inline void di_op_custom(struct di_command *cmd,
+				struct di_device *ddev,
+				struct di_opcode *opcode)
+{
+	di_op_basic(cmd, ddev, DI_OP_NOP);
+	cmd->opidx = DI_OP_CUSTOM;
+	cmd->data = opcode;
+}
+
+
+/*
+ * Returns the printable form of the status part of a drive status.
+ */
+static char *di_printable_status(u32 drive_status)
+{
+	char *s = "unknown";
+
+	switch (DI_STATUS(drive_status)) {
+	case DI_STATUS_READY:
+		s = "ready";
+		break;
+	case DI_STATUS_COVER_OPENED:
+		s = "cover opened";
+		break;
+	case DI_STATUS_DISK_CHANGE:
+		s = "disk change";
+		break;
+	case DI_STATUS_NO_DISK:
+		s = "no disk";
+		break;
+	case DI_STATUS_MOTOR_STOP:
+		s = "motor stop";
+		break;
+	case DI_STATUS_DISK_ID_NOT_READ:
+		s = "disk id not read";
+		break;
+	}
+	return s;
+}
+
+/*
+ * Returns the printable form of the error part of a drive status.
+ */
+static char *di_printable_error(u32 drive_status)
+{
+	char *s = "unknown";
+
+	switch (DI_ERROR(drive_status)) {
+	case DI_ERROR_NO_ERROR:
+		s = "no error";
+		break;
+	case DI_ERROR_MOTOR_STOPPED:
+		s = "motor stopped";
+		break;
+	case DI_ERROR_DISK_ID_NOT_READ:
+		s = "disk id not read";
+		break;
+	case DI_ERROR_MEDIUM_NOT_PRESENT:
+		s = "medium not present";
+		break;
+	case DI_ERROR_SEEK_INCOMPLETE:
+		s = "seek incomplete";
+		break;
+	case DI_ERROR_UNRECOVERABLE_READ:
+		s = "unrecoverable read";
+		break;
+	case DI_ERROR_INVALID_COMMAND:
+		s = "invalid command";
+		break;
+	case DI_ERROR_BLOCK_OUT_OF_RANGE:
+		s = "block out of range";
+		break;
+	case DI_ERROR_INVALID_FIELD:
+		s = "invalid field";
+		break;
+	case DI_ERROR_MEDIUM_CHANGED:
+		s = "medium changed";
+		break;
+	}
+
+	return s;
+}
+
+/*
+ * Prints the given drive status, only if debug enabled.
+ */
+static inline void di_debug_print_drive_status(u32 drive_status)
+{
+	DBG("%08x, [%s, %s]\n", drive_status,
+	    di_printable_status(drive_status),
+	    di_printable_error(drive_status));
+}
+
+/*
+ * Prints the given drive status.
+ */
+static void di_print_drive_status(u32 drive_status)
+{
+	drv_printk(KERN_INFO, "drive_status=%08x, [%s, %s]\n", drive_status,
+		   di_printable_status(drive_status),
+		   di_printable_error(drive_status));
+}
+
+/*
+ * Prints the given disk identifier.
+ */
+static void di_print_disk_id(struct di_disk_id *disk_id)
+{
+	drv_printk(KERN_INFO, "disk_id = [%s]\n", disk_id->id);
+}
+
+/*
+ *
+ * I/O.
+ */
+
+/*
+ * Converts a request direction into a DMA data direction.
+ */
+static inline
+enum dma_data_direction di_opidx_to_dma_dir(struct di_command *cmd)
+{
+	u16 op = di_op(cmd);
+
+	if ((op & DI_DIR_WRITE))
+		return DMA_TO_DEVICE;
+	else
+		return DMA_FROM_DEVICE;
+}
+
+/*
+ * Starts a DMA transfer.
+ */
+static void di_start_dma_transfer_raw(struct di_device *ddev,
+				      dma_addr_t data, size_t len, int mode)
+{
+	void __iomem *io_base = ddev->io_base;
+	u32 __iomem *sr_reg = io_base + DI_SR;
+	unsigned long flags;
+
+	BUG_ON((data & DI_DMA_ALIGN) != 0 ||
+	       (len & DI_DMA_ALIGN) != 0);
+
+	/* setup address and length of transfer */
+	out_be32(io_base + DI_LENGTH, len);
+	out_be32(io_base + DI_MAR, data);
+
+	/* enable the Transfer Complete interrupt */
+	spin_lock_irqsave(&ddev->io_lock, flags);
+	out_be32(sr_reg, in_be32(sr_reg) | DI_SR_TCINTMASK);
+	spin_unlock_irqrestore(&ddev->io_lock, flags);
+
+	/* start the transfer */
+	out_be32(io_base + DI_CR, DI_CR_TSTART | DI_CR_DMA | (mode&0x4));
+}
+
+/*
+ * Internal. Busy-waits until a DMA transfer finishes or timeouts.
+ */
+static int __wait_for_dma_transfer_or_timeout(u32 __iomem *cr_reg,
+					      int secs)
+{
+	unsigned long timeout = jiffies + secs*HZ;
+
+	/* busy-wait for transfer complete */
+	while ((in_be32(cr_reg) & DI_CR_TSTART) &&
+			time_before(jiffies, timeout))
+		cpu_relax();
+
+	return (in_be32(cr_reg) & DI_CR_TSTART) ? -EBUSY : 0;
+}
+
+/*
+ * Busy-waits until DMA transfers are finished.
+ */
+static void di_wait_for_dma_transfer_raw(struct di_device *ddev)
+{
+	u32 __iomem *cr_reg = ddev->io_base + DI_CR;
+	u32 __iomem *sr_reg = ddev->io_base + DI_SR;
+	unsigned long flags;
+
+	/* we don't want TCINTs to disturb us while waiting */
+	spin_lock_irqsave(&ddev->io_lock, flags);
+	out_be32(sr_reg, in_be32(sr_reg) & ~DI_SR_TCINTMASK);
+	spin_unlock_irqrestore(&ddev->io_lock, flags);
+
+	/* if the drive got stuck, reset it */
+	if (__wait_for_dma_transfer_or_timeout(cr_reg, DI_COMMAND_TIMEOUT)) {
+		DBG("dvd stuck!\n");
+		di_reset(ddev);
+	}
+
+	/* ack and enable the Transfer Complete interrupt */
+	spin_lock_irqsave(&ddev->io_lock, flags);
+	out_be32(sr_reg, in_be32(sr_reg) | (DI_SR_TCINT|DI_SR_TCINTMASK));
+	spin_unlock_irqrestore(&ddev->io_lock, flags);
+
+	return;
+}
+
+/*
+ * Quiesces the hardware to a calm and known state.
+ */
+static void di_quiesce(struct di_device *ddev)
+{
+	void __iomem *io_base = ddev->io_base;
+	u32 __iomem *cr_reg = io_base + DI_CR;
+	u32 __iomem *sr_reg = io_base + DI_SR;
+	u32 __iomem *cvr_reg = io_base + DI_CVR;
+	u32 sr, cvr;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ddev->io_lock, flags);
+
+	/* ack and mask dvd io interrupts */
+	sr = in_be32(sr_reg);
+	sr |= DI_SR_BRKINT | DI_SR_TCINT | DI_SR_DEINT;
+	sr &= ~(DI_SR_BRKINTMASK | DI_SR_TCINTMASK | DI_SR_DEINTMASK);
+	out_be32(sr_reg, sr);
+
+	/* ack and mask dvd cover interrupts */
+	cvr = in_be32(cvr_reg);
+	out_be32(cvr_reg, (cvr | DI_CVR_CVRINT) & ~DI_CVR_CVRINTMASK);
+
+	spin_unlock_irqrestore(&ddev->io_lock, flags);
+
+	/* busy-wait for transfer complete */
+	__wait_for_dma_transfer_or_timeout(cr_reg, DI_COMMAND_TIMEOUT);
+}
+
+/*
+ * Command engine.
+ *
+ */
+
+/*
+ * Outputs the command buffers, and optionally starts a transfer.
+ */
+static void di_prepare_command(struct di_command *cmd, int tstart)
+{
+	struct di_opcode *opcode = di_get_opcode(cmd);
+	void __iomem *io_base = cmd->ddev->io_base;
+
+	/*DBG("buf0 = 0x%08x, buf1 = 0x%08x, buf2 = 0x%08x\n",
+	    cmd->cmdbuf0, cmd->cmdbuf1, cmd->cmdbuf2);*/
+
+	out_be32(io_base + DI_CMDBUF0, cmd->cmdbuf0);
+	out_be32(io_base + DI_CMDBUF1, cmd->cmdbuf1);
+	out_be32(io_base + DI_CMDBUF2, cmd->cmdbuf2);
+
+	cmd->ddev->drive_status = 0;
+
+	if (tstart)
+		out_be32(io_base + DI_CR, DI_CR_TSTART | (opcode->op & 0x6));
+}
+
+static void di_command_done(struct di_command *cmd);
+
+/*
+ * Starts a command by using the immediate mode.
+ */
+static int di_start_command(struct di_command *cmd)
+{
+	struct di_device *ddev = cmd->ddev;
+	unsigned long flags;
+	int retval = 1;
+
+	spin_lock_irqsave(&ddev->lock, flags);
+
+	BUG_ON(ddev->cmd);
+
+	ddev->cmd = cmd;
+	cmd->dma_len = 0; /* no dma here */
+	di_prepare_command(cmd, 1);
+
+	spin_unlock_irqrestore(&ddev->lock, flags);
+
+	return retval;
+}
+
+/*
+ * Starts a command by using the DMA mode.
+ */
+static int di_start_dma_command(struct di_command *cmd)
+{
+	struct di_device *ddev = cmd->ddev;
+	unsigned long flags;
+	int retval = 1;
+
+	spin_lock_irqsave(&ddev->lock, flags);
+
+	BUG_ON(ddev->cmd);
+
+	ddev->cmd = cmd;
+	cmd->dma_len = cmd->len;
+	cmd->dma_addr = dma_map_single(ddev->dev,
+				       cmd->data, cmd->len,
+				       di_opidx_to_dma_dir(cmd));
+
+	di_prepare_command(cmd, 0);
+	di_start_dma_transfer_raw(ddev, cmd->dma_addr, cmd->dma_len,
+				  di_op(cmd) & DI_DIR_WRITE);
+
+	spin_unlock_irqrestore(&ddev->lock, flags);
+
+	return retval;
+}
+
+/*
+ * Completes a "get drive status" command, after a failed command.
+ */
+static void di_complete_getstatus(struct di_command *cmd)
+{
+	struct di_device *ddev = cmd->ddev;
+	void __iomem *io_base = ddev->io_base;
+	u32 __iomem *data_reg = io_base + DI_DATA;
+
+	ddev->drive_status = in_be32(data_reg);
+}
+
+/*
+ * Called after a transfer is completed.
+ */
+static void di_complete_transfer(struct di_device *ddev, u32 result)
+{
+	struct di_command *cmd;
+	struct di_opcode *opcode;
+	u32 drive_status;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ddev->lock, flags);
+
+	/* do nothing if we have nothing to complete */
+	cmd = ddev->cmd;
+	if (!cmd) {
+		spin_unlock_irqrestore(&ddev->lock, flags);
+		goto out;
+	}
+
+	/* free the command slot */
+	ddev->cmd = NULL;
+	spin_unlock_irqrestore(&ddev->lock, flags);
+
+	/* deal with caches after a dma transfer */
+	if (cmd->dma_len) {
+		dma_unmap_single(ddev->dev,
+				 cmd->dma_addr, cmd->dma_len,
+				 di_opidx_to_dma_dir(cmd));
+	}
+
+	opcode = di_get_opcode(cmd);
+
+	/*
+	 * If a command fails we check the drive status. Depending on that
+	 * we may or not retry later the command.
+	 */
+	cmd->result = result;
+	if (!di_command_ok(cmd)) {
+		/* the MATSHITA command always reports failure, ignore it */
+		if (DI_OP_ID(opcode->op) != DI_OP_ENABLE1) {
+			BUG_ON(ddev->failed_cmd != NULL);
+
+			ddev->failed_cmd = cmd;
+
+			/*
+			 * Issue immediately a "get drive status"
+			 * after a failed command.
+			 */
+			cmd = &ddev->status;
+			di_op_getstatus(cmd, ddev);
+			cmd->done = di_complete_getstatus;
+			di_run_command(cmd);
+			goto out;
+		}
+	} else {
+		if (cmd->retries != cmd->max_retries) {
+			DBG("command %s succeeded after %d retries :-)\n",
+			    opcode->name, cmd->max_retries - cmd->retries);
+		}
+	}
+
+	/* complete a successful command, or the MATSHITA one */
+	di_command_done(cmd);
+
+	spin_lock_irqsave(&ddev->lock, flags);
+	if (ddev->failed_cmd) {
+		cmd = ddev->failed_cmd;
+		ddev->failed_cmd = NULL;
+		spin_unlock_irqrestore(&ddev->lock, flags);
+
+		drive_status = ddev->drive_status;
+		opcode = di_get_opcode(cmd);
+
+		/* retry a previously failed command if appropiate */
+		if (cmd->retries > 0) {
+			if (di_may_retry(drive_status)) {
+				DBG("command %s failed, %d retries left\n",
+				    opcode->name, cmd->retries);
+				di_debug_print_drive_status(drive_status);
+
+				cmd->retries--;
+				di_run_command(cmd);
+				goto out;
+			} else {
+				DBG("command %s failed,"
+				    " aborting due to drive status\n",
+				    opcode->name);
+			}
+		} else {
+			if (!(opcode->op & DI_IGNORE_ERRORS))
+				DBG("command %s failed\n", opcode->name);
+		}
+
+		if (!(opcode->op & DI_IGNORE_ERRORS))
+			di_print_drive_status(drive_status);
+
+		/* complete the failed command */
+		di_command_done(cmd);
+
+		/* update the driver status */
+		switch (DI_ERROR(drive_status)) {
+		case DI_ERROR_MOTOR_STOPPED:
+		case DI_ERROR_MEDIUM_NOT_PRESENT:
+		case DI_ERROR_MEDIUM_CHANGED:
+			set_bit(__DI_MEDIA_CHANGED, &ddev->flags);
+			break;
+		default:
+			break;
+		}
+
+	} else {
+		spin_unlock_irqrestore(&ddev->lock, flags);
+	}
+
+	/* start the block layer queue if someone requested it */
+	if (test_and_clear_bit(__DI_START_QUEUE, &ddev->flags)) {
+		spin_lock_irqsave(&ddev->queue_lock, flags);
+		blk_start_queue(ddev->queue);
+		spin_unlock_irqrestore(&ddev->queue_lock, flags);
+	}
+
+out:
+	return;
+}
+
+/*
+ * Calls any done hooks.
+ */
+static void di_command_done(struct di_command *cmd)
+{
+	/* if specified, call the completion routine */
+	if (cmd->done)
+		cmd->done(cmd);
+}
+
+/*
+ * Completion routine.
+ */
+static void di_wait_done(struct di_command *cmd)
+{
+	complete(cmd->done_data);
+}
+
+/*
+ * Runs a command.
+ */
+static int di_run_command(struct di_command *cmd)
+{
+	struct di_opcode *opcode = di_get_opcode(cmd);
+	int retval;
+
+	if (cmd->retries > cmd->max_retries)
+		cmd->retries = cmd->max_retries;
+
+	if (!(opcode->op & DI_MODE_DMA))
+		retval = di_start_command(cmd);
+	else
+		retval = di_start_dma_command(cmd);
+	return retval;
+}
+
+/*
+ * Runs a command and waits.
+ * Might sleep if called from user context.
+ */
+static int di_run_command_and_wait(struct di_command *cmd)
+{
+	DECLARE_COMPLETION(complete);
+
+	cmd->done_data = &complete;
+	cmd->done = di_wait_done;
+	if (di_run_command(cmd) > 0)
+		wait_for_completion(&complete);
+	return cmd->result;
+}
+
+/*
+ * Interrupt handler for DI interrupts.
+ */
+static irqreturn_t di_irq_handler(int irq, void *dev0)
+{
+	struct di_device *ddev = dev0;
+	void __iomem *io_base = ddev->io_base;
+	u32 __iomem *sr_reg = io_base + DI_SR;
+	u32 __iomem *cvr_reg = io_base + DI_CVR;
+	u32 sr, cvr, reason, mask;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ddev->io_lock, flags);
+
+	sr = in_be32(sr_reg);
+	mask = sr & (DI_SR_BRKINTMASK | DI_SR_TCINTMASK | DI_SR_DEINTMASK);
+	reason = sr; /* & (mask << 1); */
+	if (reason) {
+		out_be32(sr_reg, sr | reason);
+		spin_unlock_irqrestore(&ddev->io_lock, flags);
+
+		if (reason & DI_SR_TCINT)
+			di_complete_transfer(ddev, DI_SR_TCINT);
+		if (reason & DI_SR_BRKINT) {
+			DBG("BRKINT\n");
+			di_complete_transfer(ddev, DI_SR_BRKINT);
+		}
+		if (reason & DI_SR_DEINT)
+			di_complete_transfer(ddev, DI_SR_DEINT);
+
+		spin_lock_irqsave(&ddev->io_lock, flags);
+	}
+
+	cvr = in_be32(cvr_reg);
+	mask = cvr & DI_CVR_CVRINTMASK;
+	reason = cvr; /* & (mask << 1); */
+	if ((reason & DI_CVR_CVRINT)) {
+		out_be32(cvr_reg, cvr | DI_CVR_CVRINT);
+		set_bit(__DI_MEDIA_CHANGED, &ddev->flags);
+		if (test_and_clear_bit(__DI_RESETTING, &ddev->flags)) {
+			if (!test_bit(__DI_AVOID_DEBUG, &ddev->flags)) {
+				if (ddev->flags & DI_INTEROPERABLE) {
+					DBG("extensions loaded"
+					    " and hopefully working\n");
+				}
+			}
+		} else {
+			DBG("dvd cover interrupt\n");
+		}
+	}
+
+	spin_unlock_irqrestore(&ddev->io_lock, flags);
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * Hard-resets the drive.
+ */
+static void di_reset(struct di_device *ddev)
+{
+	u32 __iomem *reset_reg = (u32 __iomem *)0xcc003024;
+	u32 reset;
+
+#define FLIPPER_RESET_DVD 0x00000004
+
+	/* set flags, but preserve the alien firmware flag */
+	ddev->flags = (ddev->flags & DI_AVOID_DEBUG) |
+		      DI_RESETTING | DI_MEDIA_CHANGED;
+
+	reset = in_be32(reset_reg);
+	out_be32(reset_reg, (reset & ~FLIPPER_RESET_DVD) | 1);
+	mdelay(500);
+	out_be32(reset_reg, (reset | FLIPPER_RESET_DVD) | 1);
+	mdelay(500);
+
+	DBG("drive reset\n");
+}
+
+
+/*
+ * Misc routines.
+ *
+ */
+
+/*
+ * Retrieves (and prints out) the laser unit model.
+ */
+static u32 di_retrieve_drive_model(struct di_device *ddev)
+{
+	struct di_command cmd;
+
+	memset(&di_drive_info, 0, sizeof(di_drive_info));
+	di_op_inq(&cmd, ddev, &di_drive_info);
+	di_run_command_and_wait(&cmd);
+
+	drv_printk(KERN_INFO, "laser unit: rev=%x, code=%x, date=%x\n",
+		   di_drive_info.rev, di_drive_info.code,
+		   di_drive_info.date);
+
+	ddev->model = di_drive_info.date;
+	return ddev->model;
+}
+
+/*
+ * Gets the current drive status.
+ */
+static u32 di_get_drive_status(struct di_device *ddev)
+{
+	void __iomem *io_base = ddev->io_base;
+	u32 __iomem *data_reg = io_base + DI_DATA;
+	struct di_command cmd;
+	u32 drive_status;
+
+	di_op_getstatus(&cmd, ddev);
+	di_run_command_and_wait(&cmd);
+	drive_status = in_be32(data_reg);
+
+	return drive_status;
+}
+
+/*
+ * Checks if the drive is in a ready state.
+ */
+static int di_is_drive_ready(struct di_device *ddev)
+{
+	u32 drive_status;
+	int result = 0;
+
+	drive_status = di_get_drive_status(ddev);
+	if (DI_STATUS(drive_status) == DI_STATUS_DISK_ID_NOT_READ ||
+	    DI_STATUS(drive_status) == DI_STATUS_READY) {
+		result = 1;
+	}
+
+	return result;
+}
+
+/*
+ *
+ * Firmware handling.
+ */
+
+/*
+ * Reads a long word from drive addressable memory.
+ * Requires debug mode enabled.
+ */
+static int di_fw_read_meml(struct di_device *ddev,
+			   unsigned long *data, unsigned long address)
+{
+	void __iomem *io_base = ddev->io_base;
+	struct di_command cmd;
+	int result = -1;
+
+	di_op_readmem(&cmd, ddev);
+	cmd.cmdbuf1 = address;
+	di_run_command_and_wait(&cmd);
+	if (di_command_ok(&cmd)) {
+		*data = in_be32(io_base + DI_DATA);
+		result = 0;
+	}
+	return result;
+}
+
+/*
+ * Retrieves the current active interrupt handler for the drive.
+ * Requires debug mode enabled.
+ */
+static unsigned long di_fw_get_irq_handler(struct di_device *ddev)
+{
+	unsigned long data = 0;
+
+	di_fw_read_meml(ddev, &data, DI_DRIVE_IRQ_VECTOR);
+	return data;
+}
+
+/*
+ * Patches drive addressable memory.
+ * Requires debug mode enabled.
+ */
+static void di_fw_patch_mem(struct di_device *ddev, u32 address,
+			    void *data, size_t len)
+{
+	struct di_command cmd;
+	struct di_opcode opcode;
+	int chunk_size;
+	const int max_chunk_size = 3 * sizeof(cmd.cmdbuf0);
+
+	while (len > 0) {
+		/* we can write in groups of 12 bytes at max */
+		if (len > max_chunk_size)
+			chunk_size = max_chunk_size;
+		else
+			chunk_size = len;
+
+		/* prepare for writing to drive's memory ... */
+		di_op_writemem(&cmd, ddev);
+		cmd.cmdbuf1 = address;
+		cmd.cmdbuf2 = chunk_size << 16;
+		di_run_command_and_wait(&cmd);
+		if (!di_command_ok(&cmd))
+			break;
+
+		/* ... and actually write to it */
+		opcode.op = DI_OP(DI_OP_CUSTOM, DI_DIR_READ | DI_MODE_IMMED);
+		opcode.name = "custom write";
+		di_op_custom(&cmd, ddev, &opcode);
+		memcpy(&cmd.cmdbuf0, data, chunk_size);
+		di_run_command(&cmd);
+
+		/*
+		 * We can't rely on drive operating as expected here, so we
+		 * explicitly poll for end of transfer and timeout eventually.
+		 * Anyway, we assume everything was ok.
+		 */
+		di_wait_for_dma_transfer_raw(ddev);
+		di_complete_transfer(ddev, DI_SR_TCINT);
+
+		/* ok, next chunk */
+		address += chunk_size;
+		data += chunk_size;
+		len -= chunk_size;
+	}
+}
+
+/*
+ * Runs a series of patches.
+ * Requires debug mode enabled.
+ */
+static void di_fw_patch(struct di_device *ddev,
+			struct di_drive_code *section, int nr_sections)
+{
+	while (nr_sections > 0) {
+		di_fw_patch_mem(ddev, section->address,
+				section->code, section->len);
+		section++;
+		nr_sections--;
+	}
+}
+
+/*
+ * Selects the appropiate drive code for each drive.
+ */
+static int di_select_drive_code(struct di_device *ddev)
+{
+	ddev->drive_code = NULL;
+
+	switch (ddev->model) {
+	case 0x20020402:
+		ddev->drive_code = &drive_20020402;
+		break;
+	case 0x20010608:
+		ddev->drive_code = &drive_20010608;
+		break;
+	case 0x20020823:
+		ddev->drive_code = &drive_20020823;
+		break;
+	case 0x20010831:
+		ddev->drive_code = &drive_20010831;
+		break;
+	default:
+		drv_printk(KERN_ERR, "sorry, drive %x is not yet"
+			   " supported\n",
+			   di_drive_info.date);
+		break;
+	}
+
+	return (ddev->drive_code) ? 0 : -EINVAL;
+}
+
+/*
+ *
+ */
+static u8 parking_code[] = {
+	0xa0,				/* sub	d0, d0 */
+	0xc4, 0xda, 0xfc,		/* movb	d0, (ADBCTL) */
+	0xf4, 0x74, 0x74, 0x0a, 0x08,	/* mov	0x080a74, a0 */ /* fixup */
+	0xf7, 0x20, 0x4c, 0x80,		/* mov	a0, (0x804c) */
+	0xfe,				/* rts */
+};
+
+/*
+ * Parks (disables) any existing alien drive code.
+ * Requires debug mode enabled.
+ */
+static u32 di_park_firmware(struct di_device *ddev)
+{
+	struct di_command cmd;
+	u32 irq_handler, original_irq_handler;
+	u32 load_address;
+
+	/* calculate an appropiate load address for the parking code */
+	irq_handler = le32_to_cpu(di_fw_get_irq_handler(ddev));
+	load_address = (irq_handler >= 0x400000) ? 0x008502 : 0x40c600;
+
+	/* get the original interrupt handler */
+	irq_handler = (ddev->model != 0x20010831) ? 0x00080A74 : 0x00080AA4;
+	original_irq_handler = irq_handler;
+
+	/* fix the parking code to match our drive model */
+	cpu_to_le32s(&irq_handler);
+	memcpy(parking_code + 6, &irq_handler, 3);
+
+	/* load and call it */
+	di_fw_patch_mem(ddev, load_address, parking_code, sizeof(parking_code));
+	di_op_func(&cmd, ddev, load_address);
+	di_run_command_and_wait(&cmd);
+
+	/*
+	 * Check if the parking code did its job.
+	 * The drive should be running now under the original irq handler.
+	 */
+	irq_handler = le32_to_cpu(di_fw_get_irq_handler(ddev));
+	if (irq_handler != original_irq_handler) {
+		drv_printk(KERN_ERR, "parking failed!\n");
+		di_reset(ddev);
+	} else {
+		DBG("parking done, irq handler = %08x\n", irq_handler);
+	}
+
+	/* drive is not patched anymore here */
+	clear_bit(__DI_INTEROPERABLE, &ddev->flags);
+
+	return di_get_drive_status(ddev);
+}
+
+/*
+ * Spins down the drive, immediatelly.
+ */
+static void di_spin_down_drive(struct di_device *ddev)
+{
+	struct di_command cmd;
+
+	di_op_stopmotor(&cmd, ddev);
+	di_run_command_and_wait(&cmd);
+}
+
+/*
+ * Enables the "debug" command set.
+ */
+static int di_enable_debug_commands(struct di_device *ddev)
+{
+	struct di_command cmd;
+
+	/* send these two consecutive enable commands */
+	di_op_enable1(&cmd, ddev);
+	di_run_command_and_wait(&cmd);
+	di_op_enable2(&cmd, ddev);
+	return di_run_command_and_wait(&cmd);
+}
+
+/*
+ * Tries to determine if firmware extensions are currently installed.
+ * Requires debug mode enabled.
+ */
+static int di_has_alien_drive_code(struct di_device *ddev)
+{
+	unsigned long address;
+	int result = 1;
+
+	/*
+	 * We assume that alien drive code is in place if the interrupt handler
+	 * is not pointing to ROM address space.
+	 */
+	address = di_fw_get_irq_handler(ddev);
+	address = le32_to_cpu(address);
+	if (address) {
+		if ((address & 0xffff0000) == 0x00080000)
+			result = 0;
+	}
+	return result;
+}
+
+/*
+ * Enables some workarounds and/or special behaviours depending on
+ * the drive firmware found.
+ * Requires debug mode enabled.
+ */
+static void di_init_alien_drive_code_quirks(struct di_device *ddev)
+{
+	unsigned long fingerprint;
+
+	/*
+	 * Test if a xenogc/duoq is installed.
+	 */
+	if (!di_fw_read_meml(ddev, &fingerprint, 0x40c60a)) {
+		if (fingerprint == 0xf710fff7) {
+			drv_printk(KERN_INFO, "drivechip: xenogc/duoq\n");
+			set_bit(__DI_AVOID_DEBUG, &ddev->flags);
+		}
+	}
+}
+
+/*
+ * Configures the drive to accept DVD-R and DVD+R media.
+ */
+static void di_make_interoperable(struct di_device *ddev)
+{
+	struct di_command cmd;
+
+	if (!ddev->drive_code || test_bit(__DI_AVOID_DEBUG, &ddev->flags))
+		return;
+
+	/* calm things down */
+	di_spin_down_drive(ddev);
+
+	/* disable any alien drive code */
+	di_enable_debug_commands(ddev);
+	di_park_firmware(ddev);
+
+	/* re-enable debug commands */
+	di_enable_debug_commands(ddev);
+
+	/* load our own drive code extensions */
+	di_fw_patch(ddev, ddev->drive_code, 1);
+
+	/*
+	 * The drive will become interoperable now.
+	 * Here we go...!
+	 */
+	set_bit(__DI_INTEROPERABLE, &ddev->flags);
+	di_op_func(&cmd, ddev, DI_DRIVE_CODE_BASE);
+	di_run_command_and_wait(&cmd);
+
+	/* this checks if drive is still working... */
+	di_get_drive_status(ddev);
+}
+
+/*
+ * Ensures that the debug features of the drive firmware are working.
+ */
+static int di_probe_debug_features(struct di_device *ddev)
+{
+	int result;
+
+	/* do nothing on unknown drive models */
+	if (!ddev->drive_code)
+		return -EINVAL;
+
+	result = di_enable_debug_commands(ddev);
+	if (!di_result_ok(result)) {
+		DBG("uhmm, debug commands seem banned...\n");
+		DBG("... let's hard reset the drive!\n");
+
+		di_reset(ddev);
+		result = di_enable_debug_commands(ddev);
+	}
+
+	return di_result_ok(result) ? 0 : -EINVAL;
+}
+
+/*
+ * Probes the existing firmware features and determines the best operation
+ * mode.
+ */
+static void di_probe_firmware(struct di_device *ddev)
+{
+	if (di_probe_debug_features(ddev)) {
+		/* we can't use debug features, thus try to avoid them */
+		drv_printk(KERN_INFO, "firmware: debug features do not work,"
+			   " using standard command set\n");
+		set_bit(__DI_AVOID_DEBUG, &ddev->flags);
+	} else {
+		if (di_has_alien_drive_code(ddev)) {
+			drv_printk(KERN_INFO, "firmware: patched drive\n");
+			/* enable some workarounds if required */
+			di_init_alien_drive_code_quirks(ddev);
+		} else {
+			drv_printk(KERN_INFO, "firmware: unpatched drive\n");
+		}
+	}
+}
+
+/*
+ * Stops the drive's motor, according to a previous schedule.
+ */
+static void di_motor_off(unsigned long ddev0)
+{
+	struct di_device *ddev = (struct di_device *)ddev0;
+	struct di_command *cmd;
+	unsigned long flags;
+
+	/* postpone a bit the motor off if there are pending commands */
+	spin_lock_irqsave(&ddev->lock, flags);
+	if (!ddev->cmd) {
+		ddev->cmd = cmd = &ddev->status;
+		spin_unlock_irqrestore(&ddev->lock, flags);
+		di_op_stopmotor(cmd, ddev);
+		di_prepare_command(cmd, 1);
+	} else {
+		spin_unlock_irqrestore(&ddev->lock, flags);
+		mod_timer(&ddev->motor_off_timer, jiffies + 1*HZ);
+	}
+}
+
+/*
+ * Cancels a previously scheduled motor off.
+ */
+static inline void di_cancel_motor_off(struct di_device *ddev)
+{
+	del_timer(&ddev->motor_off_timer);
+}
+
+/*
+ * Stops the drive's motor after the specified amount of seconds has elapsed.
+ */
+static void di_schedule_motor_off(struct di_device *ddev, unsigned int secs)
+{
+	del_timer(&ddev->motor_off_timer);
+	ddev->motor_off_timer.expires = jiffies + secs*HZ;
+	ddev->motor_off_timer.data = (unsigned long)ddev;
+	add_timer(&ddev->motor_off_timer);
+}
+
+/*
+ * Spins up the drive.
+ */
+static void di_spin_up_drive(struct di_device *ddev, u8 enable_extensions)
+{
+	struct di_command cmd;
+	u32 drive_status;
+	unsigned int attempts = 2;
+
+	/* do nothing if the drive is already spinning */
+	if (di_is_drive_ready(ddev))
+		goto out;
+
+	if (test_bit(__DI_AVOID_DEBUG, &ddev->flags)) {
+		/* don't use debug commands, let's hope a drivechip is there */
+		di_reset(ddev);
+	} else {
+		while (attempts-- > 0) {
+			if (!test_bit(__DI_INTEROPERABLE, &ddev->flags))
+				di_make_interoperable(ddev);
+
+			/*
+			 * We only re-enable the extensions if the drive is not
+			 * in a pending read disk id state. Otherwise, we assume
+			 * that the drive has already accepted the disk.
+			 */
+			drive_status = di_get_drive_status(ddev);
+			if (DI_STATUS(drive_status) !=
+			    DI_STATUS_DISK_ID_NOT_READ) {
+				di_op_enableextensions(&cmd, ddev,
+						       enable_extensions);
+				di_run_command_and_wait(&cmd);
+			}
+
+			/* the spin motor command requires the debug mode */
+			di_enable_debug_commands(ddev);
+			di_op_spinmotor(&cmd, ddev, DI_SPINMOTOR_UP);
+			di_run_command_and_wait(&cmd);
+
+			if (!ddev->drive_status) {
+				di_op_setstatus(&cmd, ddev,
+						DI_STATUS_DISK_ID_NOT_READ+1);
+				cmd.cmdbuf0 |= 0x00000300; /* XXX cheqmate */
+				di_run_command_and_wait(&cmd);
+			} else {
+				if (DI_ERROR(ddev->drive_status) !=
+				    DI_ERROR_MEDIUM_NOT_PRESENT)
+					di_reset(ddev);
+					continue;
+			}
+			break;
+		}
+	}
+out:
+	return;
+}
+
+
+/*
+ * Block layer hooks.
+ *
+ */
+
+static int di_read_toc(struct di_device *ddev)
+{
+	static struct di_disk_id disk_id
+			 __attribute__ ((aligned(DI_DMA_ALIGN+1)));
+	struct di_command cmd;
+	int accepted_media = 0;
+	int retval = 0;
+	const u8 enable_extensions = 1;
+
+	di_cancel_motor_off(ddev);
+
+	/* spin up the drive if needed */
+	if ((ddev->flags & DI_MEDIA_CHANGED))
+		di_spin_up_drive(ddev, enable_extensions);
+
+	/* check that disk id can be read and that the media is appropiate */
+	memset(&disk_id, 0, sizeof(disk_id));
+	di_op_readdiskid(&cmd, ddev, &disk_id);
+	di_run_command_and_wait(&cmd);
+	if (di_command_ok(&cmd)) {
+		if (disk_id.id[0] && memcmp(disk_id.id, "GBL", 3) &&
+		    !di_accept_gods) {
+			di_print_disk_id(&disk_id);
+			drv_printk(KERN_ERR, "sorry, gamecube media"
+				   " support is disabled\n");
+		} else {
+			accepted_media = 1;
+		}
+	} else {
+		set_bit(__DI_MEDIA_CHANGED, &ddev->flags);
+	}
+
+	if (accepted_media) {
+		/*
+		 * This is currently hardcoded. Scream|CT got this number
+		 * by reading up to where the lens physically allowed.
+		 *
+		 * This is currently causing us problems.
+		 * For example, recent 'mount' versions will read 4k from
+		 * the end of the device when guessing filesystem types.
+		 * The end of device we are reporting is not the real one,
+		 * so the drive will fail to read that part if it was not
+		 * burned.
+		 *
+		 * As a temporary solution, specify always a filesystem
+		 * type when using mount, or fill the whole disk when
+		 * burning.
+		 */
+		ddev->nr_sectors = DI_MAX_SECTORS; /* in DVD sectors */
+		clear_bit(__DI_MEDIA_CHANGED, &ddev->flags);
+
+		DBG("media ready for operation\n");
+	} else {
+		ddev->nr_sectors = 0;
+		retval = -ENOMEDIUM;
+
+		di_spin_down_drive(ddev);
+
+		DBG("media NOT ready\n");
+	}
+
+	/* transform to kernel sectors */
+	ddev->nr_sectors <<= (DI_SECTOR_SHIFT - KERNEL_SECTOR_SHIFT);
+	set_capacity(ddev->disk, ddev->nr_sectors);
+
+	return retval;
+}
+
+
+static void di_request_done(struct di_command *cmd)
+{
+	struct di_device *ddev = cmd->ddev;
+	struct request *req;
+	unsigned long flags;
+	int error = (cmd->result & DI_SR_TCINT) ? 0 : -EIO;
+
+	spin_lock_irqsave(&ddev->lock, flags);
+	req = ddev->req;
+	ddev->req = NULL;
+	spin_unlock_irqrestore(&ddev->lock, flags);
+
+	if (req) {
+		spin_lock_irqsave(&ddev->queue_lock, flags);
+		__blk_end_request_cur(req, error);
+		blk_start_queue(ddev->queue);
+		spin_unlock_irqrestore(&ddev->queue_lock, flags);
+	}
+}
+
+static void di_do_request(struct request_queue *q)
+{
+	struct di_device *ddev = q->queuedata;
+	struct di_command *cmd = &ddev->req_cmd;
+	struct request *req;
+	unsigned long start;
+	unsigned long flags;
+	size_t len;
+	int error;
+
+	req = blk_peek_request(q);
+	while (req) {
+		spin_lock_irqsave(&ddev->lock, flags);
+
+		if (ddev->req || ddev->cmd) {
+			blk_stop_queue(q);
+			if (ddev->cmd)
+				set_bit(__DI_START_QUEUE, &ddev->flags);
+			spin_unlock_irqrestore(&ddev->lock, flags);
+			break;
+		}
+
+		blk_start_request(req);
+		error = -EIO;
+
+		if (!blk_fs_request(req))
+			goto done;
+
+		/* it doesn't make sense to write to this device */
+		if (rq_data_dir(req) == WRITE) {
+			drv_printk(KERN_ERR, "write attempted\n");
+			goto done;
+		}
+
+		/* it is not a good idea to open the lid ... */
+		if ((ddev->flags & DI_MEDIA_CHANGED)) {
+			drv_printk(KERN_ERR, "media changed, aborting\n");
+			goto done;
+		}
+
+		/* keep our reads within limits */
+		if (blk_rq_pos(req) +
+				 blk_rq_cur_sectors(req) > ddev->nr_sectors) {
+			drv_printk(KERN_ERR, "reading past end\n");
+			goto done;
+		}
+
+		ddev->req = req;
+		spin_unlock_irqrestore(&ddev->lock, flags);
+
+		/* launch the corresponding read sector command */
+		start = blk_rq_pos(req) << KERNEL_SECTOR_SHIFT;
+		len = blk_rq_cur_bytes(req);
+
+		di_op_readsector(cmd, ddev, start >> 2,
+				 req->buffer, len);
+		cmd->done_data = cmd;
+		cmd->done = di_request_done;
+		di_run_command(cmd);
+		error = 0;
+		break;
+	done:
+		spin_unlock_irqrestore(&ddev->lock, flags);
+		if (!__blk_end_request_cur(req, error))
+			req = blk_peek_request(q);
+	}
+}
+
+/*
+ * Block device hooks.
+ *
+ */
+
+static int di_open(struct block_device *bdev, fmode_t mode)
+{
+	struct di_device *ddev = bdev->bd_disk->private_data;
+	struct di_command *cmd;
+	DECLARE_COMPLETION(complete);
+	unsigned long flags;
+	int retval = 0;
+
+	/* this is a read only device */
+	if (mode & FMODE_WRITE) {
+		retval = -EROFS;
+		goto out;
+	}
+
+	/*
+	 * If we have a pending command, that's a previously scheduled
+	 * motor off. Wait for it to terminate before going on.
+	 */
+	spin_lock_irqsave(&ddev->lock, flags);
+	if (ddev->cmd && ddev->ref_count == 0) {
+		cmd = ddev->cmd;
+		cmd->done_data = &complete;
+		cmd->done = di_wait_done;
+		spin_unlock_irqrestore(&ddev->lock, flags);
+		wait_for_completion(&complete);
+	} else {
+		spin_unlock_irqrestore(&ddev->lock, flags);
+	}
+
+	/* this will take care of validating the media */
+	check_disk_change(bdev);
+	if (!ddev->nr_sectors) {
+		retval = -ENOMEDIUM;
+		goto out;
+	}
+
+	spin_lock_irqsave(&ddev->queue_lock, flags);
+
+	/* honor exclusive open mode */
+	if (ddev->ref_count == -1 ||
+	    (ddev->ref_count && (mode & FMODE_EXCL))) {
+		retval = -EBUSY;
+		goto out_unlock;
+	}
+
+	if ((mode & FMODE_EXCL))
+		ddev->ref_count = -1;
+	else
+		ddev->ref_count++;
+
+out_unlock:
+	spin_unlock_irqrestore(&ddev->queue_lock, flags);
+out:
+	return retval;
+
+}
+
+static int di_release(struct gendisk *disk, fmode_t mode)
+{
+	struct di_device *ddev = disk->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ddev->queue_lock, flags);
+
+	if (ddev->ref_count > 0)
+		ddev->ref_count--;
+	else
+		ddev->ref_count = 0;
+
+	spin_unlock_irqrestore(&ddev->queue_lock, flags);
+
+	if (ddev->ref_count == 0) {
+		/*
+		 * We do not immediately stop the motor, which saves us
+		 * a spin down/spin up in applications that re-open quickly
+		 * the device, like mount when -t is not specified.
+		 */
+		di_schedule_motor_off(ddev, 1);
+
+		set_bit(__DI_MEDIA_CHANGED, &ddev->flags);
+	}
+
+	return 0;
+}
+
+static int di_revalidate_disk(struct gendisk *disk)
+{
+	struct di_device *ddev = disk->private_data;
+	di_read_toc(ddev);
+	return 0;
+}
+
+static int di_media_changed(struct gendisk *disk)
+{
+	struct di_device *ddev = disk->private_data;
+	return (ddev->flags & DI_MEDIA_CHANGED) ? 1 : 0;
+}
+
+static int di_ioctl(struct block_device *bdev, fmode_t mode,
+		    unsigned int cmd, unsigned long arg)
+{
+	switch (cmd) {
+	case CDROMMULTISESSION:
+		/* struct cdrom_multisession */
+		break;
+	case CDROMSTART:
+		break;
+	case CDROMSTOP:
+		break;
+	case CDROMREADTOCHDR:
+		/* struct cdrom_tochdr */
+		break;
+	case CDROMREADTOCENTRY:
+		/* struct cdrom_tocentry */
+		break;
+	case CDROMREADMODE2:
+	case CDROMREADMODE1:
+	case CDROMREADRAW:
+		/* struct cdrom_read (1-2048, 2-2336,RAW-2352) */
+		break;
+	case CDROM_GET_MCN:
+		/* retrieve the universal product code */
+		/* struct cdrom_mcn */
+		break;
+	case CDROMRESET:
+		/* reset the drive */
+		break;
+	case BLKRAGET:
+	case BLKFRAGET:
+	case BLKROGET:
+	case BLKBSZGET:
+	case BLKSSZGET:
+	case BLKSECTGET:
+	case BLKGETSIZE:
+	case BLKGETSIZE64:
+	case BLKFLSBUF:
+		return ioctl_by_bdev(bdev, cmd, arg);
+	default:
+		return -EINVAL;
+	}
+	return -EINVAL;
+}
+
+static struct block_device_operations di_fops = {
+	.owner = THIS_MODULE,
+	.open = di_open,
+	.release = di_release,
+	.revalidate_disk = di_revalidate_disk,
+	.media_changed = di_media_changed,
+	.ioctl = di_ioctl,
+};
+
+/*
+ * Setup routines.
+ *
+ */
+
+static int di_init_irq(struct di_device *ddev)
+{
+	void __iomem *io_base = ddev->io_base;
+	u32 __iomem *sr_reg = io_base + DI_SR;
+	u32 __iomem *cvr_reg = io_base + DI_CVR;
+	u32 sr, cvr;
+	unsigned long flags;
+	int retval;
+
+	init_timer(&ddev->motor_off_timer);
+	ddev->motor_off_timer.function =
+		(void (*)(unsigned long))di_motor_off;
+
+	ddev->flags = 0;
+	set_bit(__DI_MEDIA_CHANGED, &ddev->flags);
+
+	/* calm down things a bit first */
+	di_quiesce(ddev);
+
+	/* request interrupt */
+	retval = request_irq(ddev->irq, di_irq_handler, 0,
+			     DRV_MODULE_NAME, ddev);
+	if (retval) {
+		drv_printk(KERN_ERR, "request of irq%d failed\n", ddev->irq);
+		goto out;
+	}
+
+	spin_lock_irqsave(&ddev->io_lock, flags);
+
+	sr = in_be32(sr_reg);
+	sr |= DI_SR_BRKINT | DI_SR_TCINT | DI_SR_DEINT;
+	sr |= DI_SR_BRKINTMASK | DI_SR_TCINTMASK | DI_SR_DEINTMASK;
+	out_be32(sr_reg, sr);
+
+	cvr = in_be32(cvr_reg);
+	out_be32(cvr_reg, cvr | DI_CVR_CVRINT | DI_CVR_CVRINTMASK);
+
+	spin_unlock_irqrestore(&ddev->io_lock, flags);
+
+	di_retrieve_drive_model(ddev);
+	di_select_drive_code(ddev);
+
+	di_probe_firmware(ddev);
+
+	di_schedule_motor_off(ddev, DI_MOTOR_OFF_TIMEOUT);
+
+out:
+	return retval;
+}
+
+static void di_exit_irq(struct di_device *ddev)
+{
+	/* stop DVD motor */
+	di_cancel_motor_off(ddev);
+	di_spin_down_drive(ddev);
+
+	di_quiesce(ddev);
+
+	free_irq(ddev->irq, ddev);
+}
+
+static int di_init_blk_dev(struct di_device *ddev)
+{
+	struct gendisk *disk;
+	struct request_queue *queue;
+	int retval;
+
+	spin_lock_init(&ddev->lock);
+	spin_lock_init(&ddev->io_lock);
+
+	ddev->ref_count = 0;
+
+	retval = register_blkdev(DI_MAJOR, DI_NAME);
+	if (retval) {
+		drv_printk(KERN_ERR, "error registering major %d\n", DI_MAJOR);
+		goto err_register_blkdev;
+	}
+
+	retval = -ENOMEM;
+	spin_lock_init(&ddev->queue_lock);
+	queue = blk_init_queue(di_do_request, &ddev->queue_lock);
+	if (!queue) {
+		drv_printk(KERN_ERR, "error initializing queue\n");
+		goto err_blk_init_queue;
+	}
+
+	blk_queue_logical_block_size(queue, DI_SECTOR_SIZE);
+	blk_queue_dma_alignment(queue, DI_DMA_ALIGN);
+	blk_queue_max_phys_segments(queue, 1);
+	blk_queue_max_hw_segments(queue, 1);
+	queue->queuedata = ddev;
+	ddev->queue = queue;
+
+	disk = alloc_disk(1);
+	if (!disk) {
+		drv_printk(KERN_ERR, "error allocating disk\n");
+		goto err_alloc_disk;
+	}
+
+	disk->major = DI_MAJOR;
+	disk->first_minor = 0;
+	disk->fops = &di_fops;
+	strcpy(disk->disk_name, DI_NAME);
+	disk->queue = ddev->queue;
+	disk->private_data = ddev;
+	ddev->disk = disk;
+
+	set_disk_ro(ddev->disk, 1);
+	add_disk(ddev->disk);
+
+	retval = 0;
+	goto out;
+
+err_alloc_disk:
+	blk_cleanup_queue(ddev->queue);
+err_blk_init_queue:
+	unregister_blkdev(DI_MAJOR, DI_NAME);
+err_register_blkdev:
+out:
+	return retval;
+}
+
+static void di_exit_blk_dev(struct di_device *ddev)
+{
+	if (ddev->disk) {
+		del_gendisk(ddev->disk);
+		put_disk(ddev->disk);
+	}
+	if (ddev->queue)
+		blk_cleanup_queue(ddev->queue);
+	unregister_blkdev(DI_MAJOR, DI_NAME);
+}
+
+static int di_init_proc(struct di_device *ddev)
+{
+#ifdef CONFIG_PROC_FS
+#endif /* CONFIG_PROC_FS */
+	return 0;
+}
+
+static void di_exit_proc(struct di_device *ddev)
+{
+#ifdef CONFIG_PROC_FS
+#endif /* CONFIG_PROC_FS */
+}
+
+static int di_init(struct di_device *ddev, struct resource *mem, int irq)
+{
+	int retval;
+
+	ddev->io_base = ioremap(mem->start, mem->end - mem->start + 1);
+	ddev->irq = irq;
+
+	retval = di_init_blk_dev(ddev);
+	if (!retval) {
+		retval = di_init_irq(ddev);
+		if (retval)
+			di_exit_blk_dev(ddev);
+		else
+			di_init_proc(ddev);
+	}
+	return retval;
+}
+
+static void di_exit(struct di_device *ddev)
+{
+	di_exit_blk_dev(ddev);
+	di_exit_irq(ddev);
+	di_exit_proc(ddev);
+	if (ddev->io_base) {
+		iounmap(ddev->io_base);
+		ddev->io_base = NULL;
+	}
+}
+
+/*
+ * Driver model helper routines.
+ *
+ */
+
+static int di_do_probe(struct device *dev,
+		       struct resource *mem, int irq)
+{
+	struct di_device *ddev;
+	int retval;
+
+	ddev = kzalloc(sizeof(*ddev), GFP_KERNEL);
+	if (!ddev) {
+		drv_printk(KERN_ERR, "failed to allocate di_device\n");
+		return -ENOMEM;
+	}
+	dev_set_drvdata(dev, ddev);
+	ddev->dev = dev;
+
+	retval = di_init(ddev, mem, irq);
+	if (retval) {
+		dev_set_drvdata(dev, NULL);
+		kfree(ddev);
+	}
+	return retval;
+}
+
+static int di_do_remove(struct device *dev)
+{
+	struct di_device *ddev = dev_get_drvdata(dev);
+
+	if (ddev) {
+		di_exit(ddev);
+		dev_set_drvdata(dev, NULL);
+		kfree(ddev);
+		return 0;
+	}
+	return -ENODEV;
+}
+
+static int di_do_shutdown(struct device *dev)
+{
+	struct di_device *ddev = dev_get_drvdata(dev);
+
+	if (ddev)
+		di_quiesce(ddev);
+	return 0;
+}
+
+/*
+ * OF platform driver hooks.
+ *
+ */
+
+static int __init di_of_probe(struct of_device *odev,
+			      const struct of_device_id *match)
+{
+	struct resource res;
+	int retval;
+
+	retval = of_address_to_resource(odev->node, 0, &res);
+	if (retval) {
+		drv_printk(KERN_ERR, "no io memory range found\n");
+		return -ENODEV;
+	}
+
+	return di_do_probe(&odev->dev,
+			   &res, irq_of_parse_and_map(odev->node, 0));
+}
+
+static int __exit di_of_remove(struct of_device *odev)
+{
+	return di_do_remove(&odev->dev);
+}
+
+static int di_of_shutdown(struct of_device *odev)
+{
+	return di_do_shutdown(&odev->dev);
+}
+
+
+static struct of_device_id di_of_match[] = {
+	{ .compatible = "nintendo,flipper-disk" },
+	{ },
+};
+
+MODULE_DEVICE_TABLE(of, di_of_match);
+
+static struct of_platform_driver di_of_driver = {
+	.owner = THIS_MODULE,
+	.name = DRV_MODULE_NAME,
+	.match_table = di_of_match,
+	.probe = di_of_probe,
+	.remove = di_of_remove,
+	.shutdown = di_of_shutdown,
+};
+
+/*
+ * Module interface hooks.
+ *
+ */
+
+static int __init di_init_module(void)
+{
+	drv_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION,
+		   di_driver_version);
+
+	return of_register_platform_driver(&di_of_driver);
+}
+
+static void __exit di_exit_module(void)
+{
+	of_unregister_platform_driver(&di_of_driver);
+}
+
+module_init(di_init_module);
+module_exit(di_exit_module);
+
+MODULE_AUTHOR(DRV_AUTHOR);
+MODULE_DESCRIPTION(DRV_DESCRIPTION);
+MODULE_LICENSE("GPL");
diff --git a/drivers/block/gcn-sd.c b/drivers/block/gcn-sd.c
new file mode 100644
index 0000000..7e55100
--- /dev/null
+++ b/drivers/block/gcn-sd.c
@@ -0,0 +1,1713 @@
+/*
+ * drivers/block/gcn-sd.c
+ *
+ * MMC/SD card block driver for the Nintendo GameCube/Wii
+ * Copyright (C) 2004-2009 The GameCube Linux Team
+ * Copyright (C) 2004,2005 Rob Reylink
+ * Copyright (C) 2005 Todd Jeffreys
+ * Copyright (C) 2005,2006,2007,2008,2009 Albert Herranz
+ *
+ * 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.
+ *
+ */
+
+/*
+ * This is a block device driver for the Nintendo SD Card Adapter (DOL-019)
+ * and compatible hardware.
+ * The driver has been tested with SPI-enabled MMC cards and SD cards.
+ *
+ * The following table shows the device major and minors needed to access
+ * MMC/SD cards:
+ *
+ * +------+-------------+-------+-------+
+ * | Slot | Target      | Major | Minor |
+ * +======+=============+=======+=======+
+ * | A    | disk        | 61    | 0     |
+ * | A    | partition 1 | 61    | 1     |
+ * | A    | partition 2 | 61    | 2     |
+ * | A    | partition 3 | 61    | 3     |
+ * | A    | partition 4 | 61    | 4     |
+ * | A    | partition 5 | 61    | 5     |
+ * | A    | partition 6 | 61    | 6     |
+ * | A    | partition 7 | 61    | 7     |
+ * +------+-------------+-------+-------+
+ * | B    | disk        | 61    | 8     |
+ * | B    | partition 1 | 61    | 9     |
+ * | B    | partition 2 | 61    | 10    |
+ * | B    | partition 3 | 61    | 11    |
+ * | B    | partition 4 | 61    | 12    |
+ * | B    | partition 5 | 61    | 13    |
+ * | B    | partition 6 | 61    | 14    |
+ * | B    | partition 7 | 61    | 15    |
+ * +------+-------------+-------+-------+
+ *
+ * For example, run "mknod /dev/gcnsdb1 b 61 9" to create a device file
+ * to access the 1st partition on the card inserted in memcard slot B.
+ *
+ */
+
+#define SD_DEBUG
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/kthread.h>
+#include <linux/major.h>
+#include <linux/blkdev.h>
+#include <linux/hdreg.h>
+#include <linux/crc-ccitt.h>
+
+/*
+ * The existing Linux MMC layer does not support SPI operation yet.
+ * Anyway, we try to recycle here some common code.
+ */
+#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/sd.h>
+
+#include <linux/exi.h>
+
+#define DRV_MODULE_NAME "gcn-sd"
+#define DRV_DESCRIPTION "MMC/SD card block driver for the Nintendo GameCube/Wii"
+#define DRV_AUTHOR	"Rob Reylink, " \
+			"Todd Jeffreys, " \
+			"Albert Herranz"
+
+static char sd_driver_version[] = "4.1i";
+
+#define sd_printk(level, format, arg...) \
+	printk(level DRV_MODULE_NAME ": " format , ## arg)
+
+#ifdef SD_DEBUG
+#  define DBG(fmt, args...) \
+	   printk(KERN_ERR "%s: " fmt, __func__ , ## args)
+#else
+#  define DBG(fmt, args...)
+#endif
+
+
+/*
+ *
+ * EXI related definitions.
+ */
+#define SD_SLOTA_CHANNEL	0	/* EXI0xxx */
+#define SD_SLOTA_DEVICE		0	/* chip select, EXI0CSB0 */
+
+#define SD_SLOTB_CHANNEL	1	/* EXI1xxx */
+#define SD_SLOTB_DEVICE		0	/* chip select, EXI1CSB0 */
+
+#define SD_SPI_CLK		16000000
+#define SD_SPI_CLK_IDX		EXI_CLK_16MHZ
+
+
+/*
+ *
+ * MMC/SD related definitions.
+ */
+
+/* cycles in 8 clock units */
+#define SD_IDLE_CYCLES		80
+#define SD_FINISH_CYCLES	8
+
+/* several times in 8 clock units */
+#define MMC_SPI_N_CR		8	/* card response time */
+
+/* data start and stop tokens */
+#define MMC_SPI_TOKEN_START_SINGLE_BLOCK_READ		0xfe
+#define MMC_SPI_TOKEN_START_MULTIPLE_BLOCK_READ		0xfe
+#define MMC_SPI_TOKEN_START_SINGLE_BLOCK_WRITE		0xfe
+#define MMC_SPI_TOKEN_START_MULTIPLE_BLOCK_WRITE	0xfc
+#define MMC_SPI_TOKEN_STOP_MULTIPLE_BLOCK_WRITE		0xfd
+
+/* data response */
+#define DR_SPI_MASK				0x1f
+#define DR_SPI_DATA_ACCEPTED			0x05
+#define DR_SPI_DATA_REJECTED_CRC_ERROR		0x0b
+#define DR_SPI_DATA_REJECTED_WRITE_ERROR	0x0d
+
+/* this is still a missing command in the current MMC framework ... */
+#define MMC_READ_OCR		58
+
+/*
+ * OCR Bit positions to 10s of Vdd mV.
+ */
+static const unsigned short mmc_ocr_bit_to_vdd[] = {
+	150, 155, 160, 165, 170, 180, 190, 200,
+	210, 220, 230, 240, 250, 260, 270, 280,
+	290, 300, 310, 320, 330, 340, 350, 360
+};
+
+static const unsigned int tran_exp[] = {
+	10000, 100000, 1000000, 10000000,
+	0, 0, 0, 0
+};
+
+static const unsigned char tran_mant[] = {
+	0, 10, 12, 13, 15, 20, 25, 30,
+	35, 40, 45, 50, 55, 60, 70, 80,
+};
+
+static const unsigned int tacc_exp[] = {
+	1, 10, 100, 1000, 10000, 100000, 1000000, 10000000,
+};
+
+static const unsigned int tacc_mant[] = {
+	0, 10, 12, 13, 15, 20, 25, 30,
+	35, 40, 45, 50, 55, 60, 70, 80,
+};
+
+/*
+ *
+ * Driver settings.
+ */
+#define MMC_SHIFT		3	/* 8 partitions */
+
+#define SD_MAJOR		61
+#define SD_NAME			"gcnsd"
+
+#define KERNEL_SECTOR_SHIFT     9
+#define KERNEL_SECTOR_SIZE      (1 << KERNEL_SECTOR_SHIFT)	/*512 */
+
+enum {
+	__SD_MEDIA_CHANGED = 0,
+	__SD_BAD_CARD,
+};
+
+
+/*
+ * Raw MMC/SD command.
+ */
+struct sd_command {
+	u8 cmd;
+	u32 arg;
+	u8 crc;
+} __attribute__ ((__packed__));	/* do not add padding, please */
+
+/*
+ * MMC/SD host.
+ *
+ * We have one host for each memory card slot. And a host can only drive a
+ * single card each time.
+ */
+struct sd_host {
+	spinlock_t		lock;
+
+	int refcnt;
+	unsigned long flags;
+#define SD_MEDIA_CHANGED	(1<<__SD_MEDIA_CHANGED)
+#define SD_BAD_CARD		(1<<__SD_BAD_CARD)
+
+	/* card related info */
+	struct mmc_card card;
+
+	/* timeouts in 8 clock cycles */
+	unsigned long read_timeout;
+	unsigned long write_timeout;
+
+	/* operations condition register */
+	u32 ocr_avail;		/* just 3.3V for the GameCube */
+	u32 ocr;
+
+	/* last card response */
+	u8 resp;
+
+	/* frequency */
+	unsigned int clock;
+	u8 exi_clock;
+
+	/* command buffer */
+	struct sd_command cmd;
+
+	spinlock_t 		queue_lock;
+	struct request_queue	*queue;
+
+	struct gendisk 		*disk;
+
+	struct task_struct	*io_thread;
+	struct mutex		io_mutex;
+
+	struct exi_device	*exi_device;
+};
+
+static void sd_kill(struct sd_host *host);
+
+
+static void sd_card_set_bad(struct sd_host *host)
+{
+	set_bit(__SD_BAD_CARD, &host->flags);
+}
+
+static int sd_card_is_bad(struct sd_host *host)
+{
+	return test_bit(__SD_BAD_CARD, &host->flags);
+}
+
+/*
+ *
+ * MMC/SD data structures manipulation.
+ */
+
+/*
+ * FIXME: use a faster method (table)
+ * (the in-kernel crc 16 (ccitt crc) tables seem not compatible with us)
+ */
+static u16 crc_xmodem_update(u16 crc, u8 data)
+{
+	int i;
+
+	crc = crc ^ ((u16) data << 8);
+
+	for (i = 0; i < 8; i++) {
+		if (crc & 0x8000)
+			crc = (crc << 1) ^ 0x1021;
+		else
+			crc <<= 1;
+	}
+
+	return crc;
+}
+
+#define UNSTUFF_BITS(resp, start, size)				\
+	({							\
+	const int __size = size;				\
+	const u32 __mask = (__size < 32 ? 1 << __size : 0) - 1;	\
+	const int __off = 3 - ((start) / 32);			\
+	const int __shft = (start) & 31;			\
+	u32 __res;						\
+								\
+	__res = resp[__off] >> __shft;				\
+	if (__size + __shft > 32)				\
+		__res |= resp[__off-1] << ((32 - __shft) % 32);	\
+	__res & __mask;						\
+	})
+
+/*
+ * Given the decoded CSD structure, decode the raw CID to our CID structure.
+ */
+static void mmc_decode_cid(struct mmc_card *card)
+{
+	u32 *resp = card->raw_cid;
+
+	memset(&card->cid, 0, sizeof(struct mmc_cid));
+
+	if (mmc_card_sd(card)) {
+		card->cid.manfid = UNSTUFF_BITS(resp, 120, 8);
+		card->cid.oemid = UNSTUFF_BITS(resp, 104, 16);
+		card->cid.prod_name[0] = UNSTUFF_BITS(resp, 96, 8);
+		card->cid.prod_name[1] = UNSTUFF_BITS(resp, 88, 8);
+		card->cid.prod_name[2] = UNSTUFF_BITS(resp, 80, 8);
+		card->cid.prod_name[3] = UNSTUFF_BITS(resp, 72, 8);
+		card->cid.prod_name[4] = UNSTUFF_BITS(resp, 64, 8);
+		card->cid.hwrev = UNSTUFF_BITS(resp, 60, 4);
+		card->cid.fwrev = UNSTUFF_BITS(resp, 56, 4);
+		card->cid.serial = UNSTUFF_BITS(resp, 24, 32);
+		card->cid.year = UNSTUFF_BITS(resp, 12, 8);
+		card->cid.month = UNSTUFF_BITS(resp, 8, 4);
+		card->cid.year += 2000;
+	} else {
+		/*
+		 * The selection of the format here is guesswork based upon
+		 * information people have sent to date.
+		 */
+		switch (card->csd.mmca_vsn) {
+		case 0:	/* MMC v1.0 - v1.2 */
+		case 1:	/* MMC v1.4 */
+			card->cid.manfid = UNSTUFF_BITS(resp, 104, 24);
+			card->cid.prod_name[0] = UNSTUFF_BITS(resp, 96, 8);
+			card->cid.prod_name[1] = UNSTUFF_BITS(resp, 88, 8);
+			card->cid.prod_name[2] = UNSTUFF_BITS(resp, 80, 8);
+			card->cid.prod_name[3] = UNSTUFF_BITS(resp, 72, 8);
+			card->cid.prod_name[4] = UNSTUFF_BITS(resp, 64, 8);
+			card->cid.prod_name[5] = UNSTUFF_BITS(resp, 56, 8);
+			card->cid.prod_name[6] = UNSTUFF_BITS(resp, 48, 8);
+			card->cid.hwrev = UNSTUFF_BITS(resp, 44, 4);
+			card->cid.fwrev = UNSTUFF_BITS(resp, 40, 4);
+			card->cid.serial = UNSTUFF_BITS(resp, 16, 24);
+			card->cid.month = UNSTUFF_BITS(resp, 12, 4);
+			card->cid.year = UNSTUFF_BITS(resp, 8, 4);
+			card->cid.year += 1997;
+			break;
+		case 2:	/* MMC v2.0 - v2.2 */
+		case 3:	/* MMC v3.1 - v3.3 */
+			card->cid.manfid = UNSTUFF_BITS(resp, 120, 8);
+			card->cid.oemid = UNSTUFF_BITS(resp, 104, 16);
+			card->cid.prod_name[0] = UNSTUFF_BITS(resp, 96, 8);
+			card->cid.prod_name[1] = UNSTUFF_BITS(resp, 88, 8);
+			card->cid.prod_name[2] = UNSTUFF_BITS(resp, 80, 8);
+			card->cid.prod_name[3] = UNSTUFF_BITS(resp, 72, 8);
+			card->cid.prod_name[4] = UNSTUFF_BITS(resp, 64, 8);
+			card->cid.prod_name[5] = UNSTUFF_BITS(resp, 56, 8);
+			card->cid.serial = UNSTUFF_BITS(resp, 16, 32);
+			card->cid.month = UNSTUFF_BITS(resp, 12, 4);
+			card->cid.year = UNSTUFF_BITS(resp, 8, 4);
+			card->cid.year += 1997;
+			break;
+		default:
+			sd_printk(KERN_ERR, "card has unknown MMCA"
+				  " version %d\n", card->csd.mmca_vsn);
+			break;
+		}
+	}
+}
+
+/*
+ * Given a 128-bit response, decode to our card CSD structure.
+ */
+static void mmc_decode_csd(struct mmc_card *card)
+{
+	struct mmc_csd *csd = &card->csd;
+	unsigned int e, m, csd_struct;
+	u32 *resp = card->raw_csd;
+
+	/*
+	 * We only understand CSD structure v1.0, v1.1 and v2.
+	 * v2 has extra information in bits 15, 11 and 10.
+	 */
+	csd_struct = UNSTUFF_BITS(resp, 126, 2);
+	if (csd_struct != 0 && csd_struct != 1 && csd_struct != 2) {
+		sd_printk(KERN_ERR, "unrecognised CSD structure"
+			  " version %d\n", csd_struct);
+		return;
+	}
+
+	csd->mmca_vsn = UNSTUFF_BITS(resp, 122, 4);
+
+	/* TAAC */
+	m = UNSTUFF_BITS(resp, 115, 4);
+	e = UNSTUFF_BITS(resp, 112, 3);
+	csd->tacc_ns = (tacc_exp[e] * tacc_mant[m] + 9) / 10;
+
+	/* NSAC */
+	csd->tacc_clks = UNSTUFF_BITS(resp, 104, 8) * 100;
+
+	/* TRAN_SPEED */
+	m = UNSTUFF_BITS(resp, 99, 4);
+	e = UNSTUFF_BITS(resp, 96, 3);
+	csd->max_dtr = tran_exp[e] * tran_mant[m];
+
+	/* CCC */
+	csd->cmdclass = UNSTUFF_BITS(resp, 84, 12);
+
+	/* READ_BL_LEN */
+	csd->read_blkbits = UNSTUFF_BITS(resp, 80, 4);
+
+	/* C_SIZE */
+	m = UNSTUFF_BITS(resp, 62, 12);
+
+	/* C_SIZE_MULT */
+	e = UNSTUFF_BITS(resp, 47, 3);
+
+	csd->capacity = (1 + m) << (e + 2);	/* in card blocks */
+}
+
+#if 0
+static void sd_print_cid(struct mmc_cid *cid)
+{
+	sd_printk(KERN_INFO,
+		  "manfid = %d\n"
+		  "oemid = %d\n"
+		  "prod_name = %s\n"
+		  "hwrev = %d\n"
+		  "fwrev = %d\n"
+		  "serial = %08x\n"
+		  "year = %d\n"
+		  "month = %d\n",
+		  cid->manfid,
+		  cid->oemid,
+		  cid->prod_name,
+		  cid->hwrev, cid->fwrev, cid->serial, cid->year, cid->month);
+}
+#endif
+
+/* */
+static inline unsigned int ms_to_cycles(unsigned int ms, unsigned int clock)
+{
+	return ms * (clock / 1000);
+}
+
+/* */
+static unsigned int sd_set_clock(struct sd_host *host, unsigned int clock)
+{
+	if (clock >= 32000000) {
+		host->clock = 32000000;
+		host->exi_clock = EXI_CLK_32MHZ;
+	} else if (clock >= 16000000) {
+		host->clock = 16000000;
+		host->exi_clock = EXI_CLK_16MHZ;
+	} else if (clock >= 8000000) {
+		host->clock = 8000000;
+		host->exi_clock = EXI_CLK_8MHZ;
+	} else if (clock >= 4000000) {
+		host->clock = 4000000;
+		host->exi_clock = EXI_CLK_4MHZ;
+	} else if (clock >= 2000000) {
+		host->clock = 2000000;
+		host->exi_clock = EXI_CLK_2MHZ;
+	} else {
+		host->clock = 1000000;
+		host->exi_clock = EXI_CLK_1MHZ;
+	}
+	return host->clock;
+}
+
+/* */
+static void sd_calc_timeouts(struct sd_host *host)
+{
+	/*
+	 * FIXME: calculate timeouts from card information
+	 * (use safe defaults for now)
+	 */
+	host->read_timeout = ms_to_cycles(100, host->clock);
+	host->write_timeout = ms_to_cycles(250, host->clock);
+}
+
+/*
+ *
+ * SPI I/O support routines, including some handy SPI to EXI language
+ * translations.
+ */
+
+/* */
+static inline void spi_cs_low(struct sd_host *host)
+{
+	exi_dev_take(host->exi_device);
+	exi_dev_select(host->exi_device);
+}
+
+/* */
+static inline void spi_cs_high(struct sd_host *host)
+{
+	exi_dev_deselect(host->exi_device);
+	exi_dev_give(host->exi_device);
+}
+
+/* */
+static inline void spi_write(struct sd_host *host, void *data, size_t len)
+{
+	exi_dev_write(host->exi_device, data, len);
+}
+
+/* */
+static inline void spi_read(struct sd_host *host, void *data, size_t len)
+{
+	/*
+	 * Houston, we have a problem.
+	 *
+	 * The EXI hardware implementation seems to use a shift register which
+	 * outputs data from the MSB to the MOSI line and inputs data from
+	 * the MISO line into the LSB.
+	 * When a read operation is performed, data from the MISO line
+	 * is entered into the shift register LSB as expected. But also the
+	 * data already present in the shift register is sent out through the
+	 * MOSI line from the MSB.
+	 * This is in fact the "feature" that enabled tmbinc to dump the IPL.
+	 *
+	 * When interfacing with SD cards, this causes us a serious problem.
+	 *
+	 * We are required to send all ones (1s) while reading data from
+	 * the SD card. Otherwise, the card can interpret the data sent as
+	 * commands (if they start with the bit pattern 01 for example).
+	 *
+	 * If we use the EXI immediate mode transfer, we can workaround the
+	 * situation by writing all 1s to the DATA register before reading
+	 * (this is indeed automatically done by the EXI layer).
+	 * But we simply can't do that when using EXI DMA transfers (these
+	 * kind of transfers do not allow bidirectional operation).
+	 *
+	 * Given that no EXI DMA read transfers seem reliable, we fallback
+	 * to the "interrupt-driven" immediate mode of the EXI layer.
+	 * This will help reducing CPU monopolization on large reads.
+	 *
+	 */
+	exi_dev_transfer(host->exi_device, data, len, EXI_OP_READ, EXI_CMD_IDI);
+}
+
+/* cycles are expressed in 8 clock cycles */
+static void spi_burn_cycles(struct sd_host *host, int cycles)
+{
+	u8 d;
+
+	while (cycles-- > 0) {
+		d = 0xff;
+		spi_write(host, &d, sizeof(d));
+	}
+}
+
+/* cycles are expressed in 8 clock cycles */
+static int spi_wait_for_resp(struct sd_host *host,
+			     u8 resp, u8 resp_mask, unsigned long cycles)
+{
+	u8 data;
+
+	while (cycles-- > 0) {
+		spi_read(host, &data, sizeof(data));
+		if ((data & resp_mask) == resp) {
+			host->resp = data;
+			return data;
+		}
+	}
+	return -ENODATA;
+}
+
+/*
+ *
+ */
+static int sd_read_data(struct sd_host *host, void *data, size_t len, int token)
+{
+	int retval = 0;
+
+	if (token) {
+		retval = spi_wait_for_resp(host, token, 0xff,
+					   host->read_timeout);
+		if (retval < 0)
+			goto out;
+	}
+	spi_read(host, data, len);
+	retval = 0;
+
+out:
+	return retval;
+}
+
+/*
+ *
+ */
+static int sd_write_data(struct sd_host *host, void *data, size_t len,
+			  int token)
+{
+	u16 crc;
+	u8 t, *d;
+	size_t l;
+	int retval = 0;
+
+	/* FIXME, rewrite this a bit */
+	{
+		crc = 0;
+		d = data;
+		l = len;
+
+		while (l-- > 0)
+			crc = crc_xmodem_update(crc, *d++);
+	}
+
+	/* send the write block token */
+	t = token;
+	spi_write(host, &t, sizeof(t));
+
+	/* send the data */
+	spi_write(host, data, len);
+
+	/* send the crc */
+	spi_write(host, &crc, sizeof(crc));
+
+	/* get the card data response */
+	retval = spi_wait_for_resp(host, 0x01, 0x11, host->write_timeout);
+	if (retval < 0)
+		goto out;
+	if ((retval & DR_SPI_MASK) != DR_SPI_DATA_ACCEPTED) {
+		DBG("data response=%02x\n", retval);
+		retval = -EIO;
+		goto out;
+	}
+
+	/* wait for the busy signal to clear */
+	retval = spi_wait_for_resp(host, 0xff, 0xff, host->write_timeout);
+	if (retval < 0)
+		goto out;
+
+	retval = 0;
+
+out:
+	return retval;
+}
+
+/*
+ *
+ * MMC/SD command transactions related routines.
+ */
+
+/* */
+static inline void sd_cmd(struct sd_command *cmd, u8 opcode, u32 arg)
+{
+	cmd->cmd = 0x40 | opcode;
+	cmd->arg = arg;
+	cmd->crc = 0x01;	/* FIXME, crc is not currently used */
+}
+
+/* */
+static inline void sd_cmd_go_idle_state(struct sd_command *cmd)
+{
+	cmd->cmd = 0x40;
+	cmd->arg = 0;
+	cmd->crc = 0x95;
+}
+
+/* */
+static inline void sd_debug_print_cmd(struct sd_command *cmd)
+{
+	DBG("cmd = %d, arg = %08x, crc = %02x\n",
+	    cmd->cmd & ~0x40, cmd->arg, cmd->crc);
+}
+
+/*
+ *
+ */
+static int sd_start_command(struct sd_host *host, struct sd_command *cmd)
+{
+	int retval = 0;
+
+	/* select the card by driving CS low */
+	spi_cs_low(host);
+
+	/* send the command through the MOSI line */
+	spi_write(host, cmd, sizeof(*cmd));
+
+	/*
+	 * Wait for the card response.
+	 * Card responses come in the MISO line and have the most significant
+	 * bit cleared.
+	 */
+	retval = spi_wait_for_resp(host, 0x00, 0x80, MMC_SPI_N_CR);
+
+	if (retval > 0 && !(retval & 0x01) && cmd->cmd != 0x40)
+		DBG("command = %d, response = 0x%02x\n", cmd->cmd & ~0x40,
+		    retval);
+
+	return retval;
+}
+
+/*
+ *
+ */
+static void sd_end_command(struct sd_host *host)
+{
+	/* wait 8 clock cycles as dictated by the specification */
+	spi_burn_cycles(host, SD_FINISH_CYCLES);
+
+	/* deselect the card by driving CS high */
+	spi_cs_high(host);
+}
+
+/*
+ *
+ */
+static int sd_run_no_data_command(struct sd_host *host, struct sd_command *cmd)
+{
+	int retval;
+
+	/* send command, wait for response, and burn extra cycles */
+	retval = sd_start_command(host, cmd);
+	sd_end_command(host);
+
+	return retval;
+}
+
+/*
+ *
+ */
+static int sd_generic_read(struct sd_host *host,
+			   u8 opcode, u32 arg,
+			   void *data, size_t len, int token)
+{
+	struct sd_command *cmd = &host->cmd;
+	u16 crc, calc_crc = 0xffff;
+	u8 *d;
+	size_t l;
+	int retval;
+
+	/* build raw command */
+	sd_cmd(cmd, opcode, arg);
+
+	/* select card, send command and wait for response */
+	retval = sd_start_command(host, cmd);
+	if (retval < 0)
+		goto out;
+	if (retval != 0x00) {
+		retval = -EIO;
+		goto out;
+	}
+
+	/* wait for read token, then read data */
+	retval = sd_read_data(host, data, len, token);
+	if (retval < 0)
+		goto out;
+
+	/* read trailing crc */
+	spi_read(host, &crc, sizeof(crc));
+
+	retval = 0;
+
+	/* FIXME, rewrite this a bit */
+	{
+		calc_crc = 0;
+		d = data;
+		l = len;
+
+		while (l-- > 0)
+			calc_crc = crc_xmodem_update(calc_crc, *d++);
+
+		if (calc_crc != crc)
+			retval = -EIO;
+	}
+
+out:
+	/* burn extra cycles and deselect card */
+	sd_end_command(host);
+
+	if (retval < 0) {
+		DBG("read, offset=%d, len=%d\n", arg, len);
+		DBG("crc=%04x, calc_crc=%04x, %s\n", crc, calc_crc,
+		    (retval < 0) ? "failed" : "ok");
+	}
+
+	return retval;
+}
+
+/*
+ *
+ */
+static int sd_generic_write(struct sd_host *host,
+			    u8 opcode, u32 arg,
+			    void *data, size_t len, int token)
+{
+	struct sd_command *cmd = &host->cmd;
+	int retval;
+
+	/* build raw command */
+	sd_cmd(cmd, opcode, arg);
+
+	/* select card, send command and wait for response */
+	retval = sd_start_command(host, cmd);
+	if (retval < 0)
+		goto out;
+	if (retval != 0x00) {
+		retval = -EIO;
+		goto out;
+	}
+
+	/* send data token, data and crc, get data response */
+	retval = sd_write_data(host, data, len, token);
+	if (retval < 0)
+		goto out;
+
+	retval = 0;
+
+out:
+	/* burn extra cycles and deselect card */
+	sd_end_command(host);
+
+	if (retval < 0)
+		DBG("write, offset=%d, len=%d\n", arg, len);
+
+	return retval;
+}
+
+/*
+ *
+ */
+static int sd_read_ocr(struct sd_host *host)
+{
+	struct sd_command *cmd = &host->cmd;
+	int retval;
+
+	memset(&host->ocr, 0, sizeof(host->ocr));
+
+	sd_cmd(cmd, MMC_READ_OCR, 0);
+
+	/* select card, send command and wait for response */
+	retval = sd_start_command(host, cmd);
+	if (retval < 0)
+		goto out;
+
+	/* the OCR contents come immediately after the card response */
+	spi_read(host, &host->ocr, sizeof(host->ocr));
+	retval = 0;
+
+out:
+	/* burn extra cycles and deselect card */
+	sd_end_command(host);
+	return retval;
+}
+
+/*
+ *
+ */
+static inline int sd_read_csd(struct sd_host *host)
+{
+	memset(&host->card.raw_csd, 0, sizeof(host->card.raw_csd));
+	return sd_generic_read(host, MMC_SEND_CSD, 0,
+			       &host->card.raw_csd,
+			       sizeof(host->card.raw_csd),
+			       MMC_SPI_TOKEN_START_SINGLE_BLOCK_READ);
+}
+
+/*
+ *
+ */
+static inline int sd_read_cid(struct sd_host *host)
+{
+	memset(&host->card.raw_cid, 0, sizeof(host->card.raw_cid));
+	return sd_generic_read(host, MMC_SEND_CID, 0,
+			       &host->card.raw_cid,
+			       sizeof(host->card.raw_cid),
+			       MMC_SPI_TOKEN_START_SINGLE_BLOCK_READ);
+}
+
+/*
+ *
+ */
+static inline int sd_read_single_block(struct sd_host *host,
+				       unsigned long start,
+				       void *data, size_t len)
+{
+	int retval;
+	int attempts = 3;
+
+	if (test_bit(__SD_MEDIA_CHANGED, &host->flags))
+		attempts = 1;
+
+	while (attempts > 0) {
+		retval = sd_generic_read(host, MMC_READ_SINGLE_BLOCK, start,
+					 data, len,
+					 MMC_SPI_TOKEN_START_SINGLE_BLOCK_READ);
+		if (retval >= 0)
+			break;
+		attempts--;
+		DBG("start=%lu, data=%p, len=%d, retval = %d\n", start, data,
+		    len, retval);
+	}
+	return retval;
+}
+
+/*
+ *
+ */
+static inline int sd_write_single_block(struct sd_host *host,
+					 unsigned long start,
+					 void *data, size_t len)
+{
+	int retval;
+
+	retval = sd_generic_write(host, MMC_WRITE_BLOCK, start,
+				  data, len,
+				  MMC_SPI_TOKEN_START_SINGLE_BLOCK_WRITE);
+	if (retval < 0)
+		DBG("start=%lu, data=%p, len=%d, retval = %d\n", start, data,
+		    len, retval);
+
+	return retval;
+}
+
+/*
+ *
+ */
+static int sd_reset_sequence(struct sd_host *host)
+{
+	struct sd_command *cmd = &host->cmd;
+	u8 d;
+	int i;
+	int retval = 0;
+
+	host->card.state = 0;
+
+	/*
+	 * Wait at least 80 dummy clock cycles with the card deselected
+	 * and with the MOSI line continuously high.
+	 */
+	exi_dev_take(host->exi_device);
+	exi_dev_deselect(host->exi_device);
+	for (i = 0; i < SD_IDLE_CYCLES; i++) {
+		d = 0xff;
+		exi_dev_write(host->exi_device, &d, sizeof(d));
+	}
+	exi_dev_give(host->exi_device);
+
+	/*
+	 * Send a CMD0, card must ack with "idle state" (0x01).
+	 * This puts the card into SPI mode and soft resets it.
+	 * CRC checking is disabled by default.
+	 */
+	for (i = 0; i < 255; i++) {
+		/* CMD0 */
+		sd_cmd_go_idle_state(cmd);
+		retval = sd_run_no_data_command(host, cmd);
+		if (retval < 0) {
+			retval = -ENODEV;
+			goto out;
+		}
+		if (retval == R1_SPI_IDLE)
+			break;
+	}
+	if (retval != R1_SPI_IDLE) {
+		retval = -ENODEV;
+		goto out;
+	}
+
+	/*
+	 * Send a ACMD41 to activate card initialization process.
+	 * SD card must ack with "ok" (0x00).
+	 * MMC card will report "invalid command" (0x04).
+	 */
+	for (i = 0; i < 0xffff; i++) {
+		/* ACMD41 = CMD55 + CMD41 */
+		sd_cmd(cmd, MMC_APP_CMD, 0);
+		retval = sd_run_no_data_command(host, cmd);
+		if (retval < 0) {
+			retval = -ENODEV;
+			goto out;
+		}
+
+		sd_cmd(cmd, SD_APP_OP_COND, 0);
+		retval = sd_run_no_data_command(host, cmd);
+		if (retval < 0) {
+			retval = -ENODEV;
+			goto out;
+		}
+		if (retval == 0x00) {
+			/* we found a SD card */
+			mmc_card_set_present(&host->card);
+			host->card.type = MMC_TYPE_SD;
+			break;
+		}
+		if ((retval & R1_SPI_ILLEGAL_COMMAND)) {
+			/* this looks like a MMC card */
+			break;
+		}
+	}
+
+	/*
+	 * MMC cards require CMD1 to activate card initialization process.
+	 * MMC card must ack with "ok" (0x00)
+	 */
+	if (!mmc_card_sd(&host->card)) {
+		for (i = 0; i < 0xffff; i++) {
+			sd_cmd(cmd, MMC_SEND_OP_COND, 0);
+			retval = sd_run_no_data_command(host, cmd);
+			if (retval < 0) {
+				retval = -ENODEV;
+				goto out;
+			}
+			if (retval == 0x00) {
+				/* we found a MMC card */
+				mmc_card_set_present(&host->card);
+				break;
+			}
+		}
+		if (retval != 0x00) {
+			DBG("MMC card, bad, retval=%02x\n", retval);
+			sd_card_set_bad(host);
+		}
+	}
+
+out:
+	return retval;
+}
+
+/*
+ *
+ */
+static int sd_welcome_card(struct sd_host *host)
+{
+	int retval;
+
+	/* soft reset the card */
+	retval = sd_reset_sequence(host);
+	if (retval < 0 || sd_card_is_bad(host))
+		goto out;
+
+	/* read Operating Conditions Register */
+	retval = sd_read_ocr(host);
+	if (retval < 0)
+		goto err_bad_card;
+
+	/* refuse to drive cards reporting voltage ranges out of scope */
+	if (!(host->ocr & host->ocr_avail)) {
+		sd_printk(KERN_WARNING, "reported OCR (%08x)"
+			  " indicates that it is not safe to use this"
+			  " card with a GameCube\n", host->ocr);
+		retval = -ENODEV;
+		goto err_bad_card;
+	}
+
+	/* read and decode the Card Specific Data */
+	retval = sd_read_csd(host);
+	if (retval < 0)
+		goto err_bad_card;
+	mmc_decode_csd(&host->card);
+
+	/* calculate some card access related timeouts */
+	sd_calc_timeouts(host);
+
+	/* read and decode the Card Identification Data */
+	retval = sd_read_cid(host);
+	if (retval < 0)
+		goto err_bad_card;
+	mmc_decode_cid(&host->card);
+
+	sd_printk(KERN_INFO, "slot%d: descr \"%s\", size %luk, block %ub,"
+		  " serial %08x\n",
+		  to_channel(exi_get_exi_channel(host->exi_device)),
+		  host->card.cid.prod_name,
+		  (unsigned long)((host->card.csd.capacity *
+			  (1 << host->card.csd.read_blkbits)) / 1024),
+		  1 << host->card.csd.read_blkbits,
+		  host->card.cid.serial);
+
+	retval = 0;
+	goto out;
+
+err_bad_card:
+	sd_card_set_bad(host);
+out:
+	return retval;
+}
+
+/*
+ *
+ * Block layer.
+ */
+
+/*
+ * Performs a read request.
+ */
+static int sd_read_request(struct sd_host *host, struct request *req)
+{
+	int i;
+	unsigned long nr_blocks; /* in card blocks */
+	size_t block_len; /* in bytes */
+	unsigned long start;
+	void *buf = req->buffer;
+	int retval;
+
+	/*
+	 * It seems that some cards do not accept single block reads for the
+	 * read block length reported by the card.
+	 * For now, we perform only 512 byte single block reads.
+	 */
+
+	start = blk_rq_pos(req) << KERNEL_SECTOR_SHIFT;
+#if 0
+	nr_blocks = req->current_nr_sectors >>
+			 (host->card.csd.read_blkbits - KERNEL_SECTOR_SHIFT);
+	block_len = 1 << host->card.csd.read_blkbits;
+#else
+	nr_blocks = blk_rq_cur_sectors(req);
+	block_len = 1 << KERNEL_SECTOR_SHIFT;
+#endif
+
+	for (i = 0; i < nr_blocks; i++) {
+		retval = sd_read_single_block(host, start, buf, block_len);
+		if (retval < 0)
+			break;
+
+		start += block_len;
+		buf += block_len;
+	}
+
+	/* number of kernel sectors transferred */
+#if 0
+	retval = i << (host->card.csd.read_blkbits - KERNEL_SECTOR_SHIFT);
+#else
+	retval = i;
+#endif
+
+	return retval;
+}
+
+/*
+ * Performs a write request.
+ */
+static int sd_write_request(struct sd_host *host, struct request *req)
+{
+	int i;
+	unsigned long nr_blocks; /* in card blocks */
+	size_t block_len; /* in bytes */
+	unsigned long start;
+	void *buf = req->buffer;
+	int retval;
+
+	/* FIXME?, maybe should use 2^WRITE_BL_LEN blocks */
+
+	/* kernel sectors and card write blocks are both 512 bytes long */
+	start = blk_rq_pos(req) << KERNEL_SECTOR_SHIFT;
+	nr_blocks = blk_rq_cur_sectors(req);
+	block_len = 1 << KERNEL_SECTOR_SHIFT;
+
+	for (i = 0; i < nr_blocks; i++) {
+		retval = sd_write_single_block(host, start, buf, block_len);
+		if (retval < 0)
+			break;
+
+		start += block_len;
+		buf += block_len;
+	}
+
+	/* number of kernel sectors transferred */
+	retval = i;
+
+	return retval;
+}
+
+/*
+ * Verifies if a request should be dispatched or not.
+ *
+ * Returns:
+ *  <0 in case of error.
+ *  0  if request passes the checks
+ */
+static int sd_check_request(struct sd_host *host, struct request *req)
+{
+	unsigned long nr_sectors;
+
+	if (!blk_fs_request(req))
+		return -EIO;
+
+	if (test_bit(__SD_MEDIA_CHANGED, &host->flags)) {
+		sd_printk(KERN_ERR, "media changed, aborting\n");
+		return -ENOMEDIUM;
+	}
+
+	/* unit is kernel sectors */
+	nr_sectors =
+	    host->card.csd.capacity << (host->card.csd.read_blkbits -
+					KERNEL_SECTOR_SHIFT);
+
+	/* keep our reads within limits */
+	if (blk_rq_pos(req) + blk_rq_cur_sectors(req) > nr_sectors) {
+		sd_printk(KERN_ERR, "reading past end, aborting\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/*
+ * Request dispatcher.
+ */
+static int sd_do_request(struct sd_host *host, struct request *req)
+{
+	int nr_sectors = 0;
+	int error;
+
+	error = sd_check_request(host, req);
+	if (error) {
+		nr_sectors = error;
+		goto out;
+	}
+
+	switch (rq_data_dir(req)) {
+	case WRITE:
+		nr_sectors = sd_write_request(host, req);
+		break;
+	case READ:
+		nr_sectors = sd_read_request(host, req);
+		break;
+	}
+
+out:
+	return nr_sectors;
+}
+
+/*
+ * Input/Output thread.
+ */
+static int sd_io_thread(void *param)
+{
+	struct sd_host *host = param;
+	struct request *req;
+	unsigned long flags;
+	int nr_sectors;
+	int error;
+
+#if 0
+	/*
+	 * We are going to perfom badly due to the read problem explained
+	 * above. At least, be nice with other processes trying to use the
+	 * cpu.
+	 */
+	set_user_nice(current, 0);
+#endif
+
+	current->flags |= PF_NOFREEZE|PF_MEMALLOC;
+
+	mutex_lock(&host->io_mutex);
+	for (;;) {
+		req = NULL;
+		set_current_state(TASK_INTERRUPTIBLE);
+
+		spin_lock_irqsave(&host->queue_lock, flags);
+		if (!blk_queue_plugged(host->queue))
+			req = blk_fetch_request(host->queue);
+		spin_unlock_irqrestore(&host->queue_lock, flags);
+
+		if (!req) {
+			if (kthread_should_stop()) {
+				set_current_state(TASK_RUNNING);
+				break;
+			}
+			mutex_unlock(&host->io_mutex);
+			schedule();
+			mutex_lock(&host->io_mutex);
+			continue;
+		}
+		set_current_state(TASK_INTERRUPTIBLE);
+		nr_sectors = sd_do_request(host, req);
+		error = (nr_sectors < 0) ? nr_sectors : 0;
+
+		spin_lock_irqsave(&host->queue_lock, flags);
+		__blk_end_request(req, error, nr_sectors << 9);
+		spin_unlock_irqrestore(&host->queue_lock, flags);
+	}
+	mutex_unlock(&host->io_mutex);
+
+	return 0;
+}
+
+/*
+ * Block layer request function.
+ * Wakes up the IO thread.
+ */
+static void sd_request_func(struct request_queue *q)
+{
+	struct sd_host *host = q->queuedata;
+	wake_up_process(host->io_thread);
+}
+
+/*
+ *
+ * Driver interface.
+ */
+
+static DECLARE_MUTEX(open_lock);
+
+/*
+ * Opens the drive device.
+ */
+static int sd_open(struct block_device *bdev, fmode_t mode)
+{
+	struct sd_host *host = bdev->bd_disk->private_data;
+	int retval = 0;
+
+	if (!host || !host->exi_device)
+		return -ENXIO;
+
+	/* honor exclusive open mode */
+	if (host->refcnt == -1 ||
+	    (host->refcnt && (mode & FMODE_EXCL))) {
+		retval = -EBUSY;
+		goto out;
+	}
+
+	/* this takes care of revalidating the media if needed */
+	check_disk_change(bdev);
+	if (!host->card.csd.capacity) {
+		retval = -ENOMEDIUM;
+		goto out;
+	}
+
+	down(&open_lock);
+
+	if ((mode & FMODE_EXCL))
+		host->refcnt = -1;
+	else
+		host->refcnt++;
+
+	up(&open_lock);
+
+out:
+	return retval;
+
+}
+
+/*
+ * Releases the drive device.
+ */
+static int sd_release(struct gendisk *disk, fmode_t mode)
+{
+	struct sd_host *host = disk->private_data;
+
+	if (!host)
+		return -ENXIO;
+
+	down(&open_lock);
+
+	if (host->refcnt > 0)
+		host->refcnt--;
+	else
+		host->refcnt = 0;
+
+	up(&open_lock);
+
+	/* lazy removal of unreferenced zombies */
+	if (!host->refcnt && !host->exi_device)
+		kfree(host);
+
+	return 0;
+}
+
+/*
+ * Checks if media changed.
+ */
+static int sd_media_changed(struct gendisk *disk)
+{
+	struct sd_host *host = disk->private_data;
+	unsigned int last_serial;
+	int retval;
+
+	/* report a media change for zombies */
+	if (!host)
+		return 1;
+
+	/* report a media change if someone forced it */
+	if (test_bit(__SD_MEDIA_CHANGED, &host->flags))
+		return 1;
+
+	/* check if the serial number of the card changed */
+	last_serial = host->card.cid.serial;
+	retval = sd_read_cid(host);
+	if (!retval && last_serial == host->card.cid.serial && last_serial)
+		clear_bit(__SD_MEDIA_CHANGED, &host->flags);
+	else
+		set_bit(__SD_MEDIA_CHANGED, &host->flags);
+
+	return (host->flags & SD_MEDIA_CHANGED) ? 1 : 0;
+}
+
+/*
+ * Checks if media is still valid.
+ */
+static int sd_revalidate_disk(struct gendisk *disk)
+{
+	struct sd_host *host = disk->private_data;
+	int retval = 0;
+
+	/* report missing medium for zombies */
+	if (!host) {
+		retval = -ENOMEDIUM;
+		goto out;
+	}
+
+	/* the block layer likes to call us multiple times... */
+	if (!sd_media_changed(host->disk))
+		goto out;
+
+	/* get the card into a known status */
+	retval = sd_welcome_card(host);
+	if (retval < 0 || sd_card_is_bad(host)) {
+		retval = -ENOMEDIUM;
+		goto out;
+	}
+
+	/* inform the block layer about various sizes */
+	blk_queue_logical_block_size(host->queue, 1 << KERNEL_SECTOR_SHIFT);
+	set_capacity(host->disk, host->card.csd.capacity <<
+			 (host->card.csd.read_blkbits - KERNEL_SECTOR_SHIFT));
+
+	clear_bit(__SD_MEDIA_CHANGED, &host->flags);
+
+out:
+	return retval;
+}
+
+static int sd_getgeo(struct block_device *bdev, struct hd_geometry *geo)
+{
+	geo->cylinders = get_capacity(bdev->bd_disk) / (4 * 16);
+	geo->heads = 4;
+	geo->sectors = 16;
+	return 0;
+}
+
+
+static struct block_device_operations sd_fops = {
+	.owner = THIS_MODULE,
+	.open = sd_open,
+	.release = sd_release,
+	.revalidate_disk = sd_revalidate_disk,
+	.media_changed = sd_media_changed,
+	.getgeo = sd_getgeo,
+};
+
+/*
+ * Initializes the block layer interfaces.
+ */
+static int sd_init_blk_dev(struct sd_host *host)
+{
+	struct gendisk *disk;
+	struct request_queue *queue;
+	int channel;
+	int retval;
+
+	channel = to_channel(exi_get_exi_channel(host->exi_device));
+
+	/* queue */
+	retval = -ENOMEM;
+	spin_lock_init(&host->queue_lock);
+	queue = blk_init_queue(sd_request_func, &host->queue_lock);
+	if (!queue) {
+		sd_printk(KERN_ERR, "error initializing queue\n");
+		goto err_blk_init_queue;
+	}
+	blk_queue_dma_alignment(queue, EXI_DMA_ALIGN);
+	blk_queue_max_phys_segments(queue, 1);
+	blk_queue_max_hw_segments(queue, 1);
+	blk_queue_max_sectors(queue, 8);
+	queue_flag_set_unlocked(QUEUE_FLAG_NONROT, queue);
+	queue->queuedata = host;
+	host->queue = queue;
+
+	/* disk */
+	disk = alloc_disk(1 << MMC_SHIFT);
+	if (!disk) {
+		sd_printk(KERN_ERR, "error allocating disk\n");
+		goto err_alloc_disk;
+	}
+	disk->major = SD_MAJOR;
+	disk->first_minor = channel << MMC_SHIFT;
+	disk->fops = &sd_fops;
+	sprintf(disk->disk_name, "%s%c", SD_NAME, 'a' + channel);
+	disk->private_data = host;
+	disk->queue = host->queue;
+	host->disk = disk;
+
+	retval = 0;
+	goto out;
+
+err_alloc_disk:
+	blk_cleanup_queue(host->queue);
+	host->queue = NULL;
+err_blk_init_queue:
+out:
+	return retval;
+}
+
+/*
+ * Exits the block layer interfaces.
+ */
+static void sd_exit_blk_dev(struct sd_host *host)
+{
+	blk_cleanup_queue(host->queue);
+	put_disk(host->disk);
+}
+
+
+/*
+ * Initializes and launches the IO thread.
+ */
+static int sd_init_io_thread(struct sd_host *host)
+{
+	int channel;
+	int result = 0;
+
+	channel = to_channel(exi_get_exi_channel(host->exi_device));
+
+	mutex_init(&host->io_mutex);
+	host->io_thread = kthread_run(sd_io_thread, host,
+				      "ksdiod/%c", 'a' + channel);
+	if (IS_ERR(host->io_thread)) {
+		sd_printk(KERN_ERR, "error creating io thread\n");
+		result = PTR_ERR(host->io_thread);
+	}
+	return result;
+}
+
+/*
+ * Terminates and waits for the IO thread to complete.
+ */
+static void sd_exit_io_thread(struct sd_host *host)
+{
+	if (!IS_ERR(host->io_thread)) {
+		wake_up_process(host->io_thread);
+		kthread_stop(host->io_thread);
+		host->io_thread = ERR_PTR(-EINVAL);
+	}
+}
+
+/*
+ * Initializes a host.
+ */
+static int sd_init(struct sd_host *host)
+{
+	int retval;
+
+	spin_lock_init(&host->lock);
+
+	host->refcnt = 0;
+	set_bit(__SD_MEDIA_CHANGED, &host->flags);
+
+	host->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+	sd_set_clock(host, SD_SPI_CLK);
+	sd_calc_timeouts(host);
+
+	retval = sd_init_blk_dev(host);
+	if (!retval) {
+		retval = sd_revalidate_disk(host->disk);
+		if (retval < 0 || !mmc_card_present(&host->card)) {
+			retval = -ENODEV;
+			goto err_blk_dev;
+		}
+
+		retval = sd_init_io_thread(host);
+		if (retval)
+			goto err_blk_dev;
+
+		add_disk(host->disk);
+	}
+
+	return retval;
+
+err_blk_dev:
+	sd_exit_blk_dev(host);
+	return retval;
+}
+
+/*
+ * Deinitializes (exits) a host.
+ */
+static void sd_exit(struct sd_host *host)
+{
+	del_gendisk(host->disk);
+	sd_exit_io_thread(host);
+	sd_exit_blk_dev(host);
+}
+
+/*
+ * Terminates a host.
+ */
+static void sd_kill(struct sd_host *host)
+{
+	if (host->refcnt > 0) {
+		sd_printk(KERN_ERR, "hey! card removed while in use!\n");
+		set_bit(__SD_MEDIA_CHANGED, &host->flags);
+	}
+
+	sd_exit(host);
+	host->exi_device = NULL;
+
+	/* release the host immediately when not in use */
+	if (!host->refcnt)
+		kfree(host);
+}
+
+
+/*
+ * EXI layer interface.
+ *
+ */
+
+/*
+ * Checks if the given EXI device is a MMC/SD card and makes it available
+ * if true.
+ */
+static int sd_probe(struct exi_device *exi_device)
+{
+	struct sd_host *host;
+	int retval;
+
+	/* don't try to drive a device which already has a real identifier */
+	if (exi_device->eid.id != EXI_ID_NONE)
+		return -ENODEV;
+
+	host = kzalloc(sizeof(*host), GFP_KERNEL);
+	if (!host)
+		return -ENOMEM;
+
+	host->exi_device = exi_device_get(exi_device);
+	WARN_ON(exi_get_drvdata(exi_device));
+	exi_set_drvdata(exi_device, host);
+	retval = sd_init(host);
+	if (retval) {
+		exi_set_drvdata(exi_device, NULL);
+		host->exi_device = NULL;
+		kfree(host);
+		exi_device_put(exi_device);
+	}
+	return retval;
+}
+
+/*
+ * Makes unavailable the MMC/SD card identified by the EXI device `exi_device'.
+ */
+static void sd_remove(struct exi_device *exi_device)
+{
+	struct sd_host *host = exi_get_drvdata(exi_device);
+
+	WARN_ON(!host);
+	WARN_ON(!host->exi_device);
+
+	exi_set_drvdata(exi_device, NULL);
+	if (host)
+		sd_kill(host);
+	exi_device_put(exi_device);
+}
+
+static struct exi_device_id sd_eid_table[] = {
+	[0] = {
+	       .channel = SD_SLOTA_CHANNEL,
+	       .device = SD_SLOTA_DEVICE,
+	       .id = EXI_ID_NONE,
+	       },
+	[1] = {
+	       .channel = SD_SLOTB_CHANNEL,
+	       .device = SD_SLOTB_DEVICE,
+	       .id = EXI_ID_NONE,
+	       },
+	{.id = 0}
+};
+
+static struct exi_driver sd_driver = {
+	.name = DRV_MODULE_NAME,
+	.eid_table = sd_eid_table,
+	.frequency = SD_SPI_CLK_IDX,
+	.probe = sd_probe,
+	.remove = sd_remove,
+};
+
+
+/*
+ * Kernel module interface.
+ *
+ */
+
+/*
+ *
+ */
+static int __init sd_init_module(void)
+{
+	int retval = 0;
+
+	sd_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION,
+		  sd_driver_version);
+
+	if (register_blkdev(SD_MAJOR, DRV_MODULE_NAME)) {
+		sd_printk(KERN_ERR, "unable to register major %d\n", SD_MAJOR);
+		retval = -EIO;
+		goto out;
+	}
+
+	retval = exi_driver_register(&sd_driver);
+
+out:
+	return retval;
+}
+
+/*
+ *
+ */
+static void __exit sd_exit_module(void)
+{
+	unregister_blkdev(SD_MAJOR, DRV_MODULE_NAME);
+	exi_driver_unregister(&sd_driver);
+}
+
+module_init(sd_init_module);
+module_exit(sd_exit_module);
+
+MODULE_AUTHOR(DRV_AUTHOR);
+MODULE_DESCRIPTION(DRV_DESCRIPTION);
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/block/rvl-di.c b/drivers/block/rvl-di.c
new file mode 100644
index 0000000..cc176ed
--- /dev/null
+++ b/drivers/block/rvl-di.c
@@ -0,0 +1,1875 @@
+/*
+ * drivers/block/rvl-di.c
+ *
+ * Nintendo Wii Disk Interface (DI) driver
+ * Copyright (C) 2009 The GameCube Linux Team
+ * Copyright (C) 2009 Albert Herranz
+ *
+ * Based on Nintendo GameCube Disk Interface (DI) driver
+ * Copyright (C) 2005-2009 The GameCube Linux Team
+ * Copyright (C) 2005,2006,2007,2009 Albert Herranz
+ *
+ * Portions based on previous work by Scream|CT.
+ *
+ * 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.
+ *
+ */
+#define DRV_MODULE_NAME "rvl-di"
+#define DRV_DESCRIPTION	"Nintendo Wii Disk Interface (DI) driver"
+#define DRV_AUTHOR	"Albert Herranz"
+
+#define pr_fmt(fmt) DRV_MODULE_NAME ": " fmt
+
+#define DEBUG
+
+#include <linux/blkdev.h>
+#include <linux/cdrom.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/fcntl.h>
+#include <linux/hdreg.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/timer.h>
+#include <linux/io.h>
+
+#include <asm/starlet.h>
+#include <asm/dma-mapping.h>
+
+
+static char di_driver_version[] = "0.1i";
+
+/*
+ * Hardware.
+ */
+#define DI_DMA_ALIGN		0x1f /* 32 bytes */
+
+/* DI Status Register */
+#define DI_SR			0x00
+#define  DI_SR_BRK		(1<<0)
+#define  DI_SR_DEINTMASK	(1<<1)
+#define  DI_SR_DEINT		(1<<2)
+#define  DI_SR_TCINTMASK	(1<<3)
+#define  DI_SR_TCINT		(1<<4)
+#define  DI_SR_BRKINTMASK	(1<<5)
+#define  DI_SR_BRKINT		(1<<6)
+
+/* DI Cover Register */
+#define DI_CVR			0x04
+#define  DI_CVR_CVR		(1<<0)
+#define  DI_CVR_CVRINTMASK	(1<<1)
+#define  DI_CVR_CVRINT		(1<<2)
+
+/* DI Command Buffers */
+#define DI_CMDBUF0		0x08
+#define DI_CMDBUF1		0x0c
+#define DI_CMDBUF2		0x10
+
+/* DI DMA Memory Address Register */
+#define DI_MAR			0x14
+
+/* DI DMA Transfer Length Register */
+#define DI_LENGTH		0x18
+
+/* DI Control Register */
+#define DI_CR			0x1c
+#define  DI_CR_TSTART		(1<<0)
+#define  DI_CR_DMA		(1<<1)
+#define  DI_CR_RW		(1<<2)
+
+/* DI Immediate Data Buffer */
+#define DI_DATA			0x20
+
+/* DI Configuration Register */
+#define DI_CFG			0x24
+
+
+/* drive status, status */
+#define DI_STATUS(s)		((u8)((s)>>24))
+
+#define DI_STATUS_READY			0x00
+#define DI_STATUS_COVER_OPENED		0x01
+#define DI_STATUS_DISK_CHANGE		0x02
+#define DI_STATUS_NO_DISK		0x03
+#define DI_STATUS_MOTOR_STOP		0x04
+#define DI_STATUS_DISK_ID_NOT_READ	0x05
+
+/* drive status, error */
+#define DI_ERROR(s)		((u32)((s)&0x00ffffff))
+
+#define DI_ERROR_NO_ERROR		0x000000
+#define DI_ERROR_MOTOR_STOPPED		0x020400
+#define DI_ERROR_DISK_ID_NOT_READ	0x020401
+#define DI_ERROR_MEDIUM_NOT_PRESENT	0x023a00
+#define DI_ERROR_SEEK_INCOMPLETE	0x030200
+#define DI_ERROR_UNRECOVERABLE_READ	0x031100
+#define DI_ERROR_INVALID_COMMAND	0x052000
+#define DI_ERROR_BLOCK_OUT_OF_RANGE	0x052100
+#define DI_ERROR_INVALID_FIELD		0x052400
+#define DI_ERROR_MEDIUM_CHANGED		0x062800
+
+#define di_may_retry(s)		((DI_STATUS(s) == DI_STATUS_READY || \
+				  DI_STATUS(s) == DI_STATUS_DISK_ID_NOT_READ) \
+				 && \
+				 (DI_ERROR(s) != DI_ERROR_SEEK_INCOMPLETE))
+
+/* DI Sector Size */
+#define DI_SECTOR_SHIFT		11
+#define DI_SECTOR_SIZE		(1 << DI_SECTOR_SHIFT) /*2048*/
+
+
+/* ECMA Standards definitions */
+#if 0
+#define DI_TYPE_DVD_ROM		0x10 /* 0001 0000 ECMA:267,268 */
+#define DI_TYPE_DVD_RAM_26	0x11 /* 0001 0001 ECMA:272 */
+#define DI_TYPE_DVD_RAM_47	0x61 /* 0110 0001 ECMA:330 */
+#define DI_TYPE_DVD_MINUS_R_29	0x12 /* 0001 0010 ECMA:279 */
+#define DI_TYPE_DVD_MINUS_R_47	0x72 /* 0101 0010 ECMA:359 */
+#define DI_TYPE_DVD_MINUS_R_DL	0x62 /* 0110 0010 ECMA:382 */
+#define DI_TYPE_DVD_MINUS_RW	0x23 /* 0010 0011 ECMA:338 */
+#define DI_TYPE_DVD_PLUS_RW_30	0x19 /* 0001 1001 ECMA:274 */
+#define DI_TYPE_DVD_PLUS_RW_47	0x29 /* 0010 1001 ECMA:337 */
+#define DI_TYPE_DVD_PLUS_RW_HS	0x39 /* 0011 1001 ECMA:371 */
+#define DI_TYPE_DVD_PLUS_R	0x1a /* 0001 1010 ECMA:349 */
+#define DI_TYPE_DVD_PLUS_RW_DL	0x1d /* 0001 1101 ECMA:374 */
+#define DI_TYPE_DVD_PLUS_R_DL	0x1e /* 0001 1110 ECMA:364 */
+#endif
+
+#define DI_BOOK_TYPE_DVD_ROM		0x00
+#define DI_BOOK_TYPE_DVD_RAM		0x01
+#define DI_BOOK_TYPE_DVD_MINUS_R	0x02
+#define DI_BOOK_TYPE_DVD_MINUS_RW	0x03
+#define DI_BOOK_TYPE_DVD_PLUS_RW	0x09
+#define DI_BOOK_TYPE_DVD_PLUS_R		0x0a
+#define DI_BOOK_TYPE_DVD_PLUS_RW_DL	0x0d
+#define DI_BOOK_TYPE_DVD_PLUS_R_DL	0x0e
+
+#define DI_DISK_SIZE_80MM		0x01
+#define DI_DISK_SIZE_120MM		0x00
+
+
+
+/* Driver Settings */
+#define DI_NAME			DRV_MODULE_NAME
+#define DI_MAJOR		60
+
+#define DI_COMMAND_TIMEOUT	20 /* seconds */
+#define DI_COMMAND_RETRIES	10 /* times */
+
+#define DI_MOTOR_OFF_TIMEOUT	10
+
+#define KERNEL_SECTOR_SHIFT	9
+#define KERNEL_SECTOR_SIZE	(1 << KERNEL_SECTOR_SHIFT) /*512*/
+
+
+/*
+ * Drive Information.
+ */
+struct di_drive_info {
+	u16				rev;
+	u16				code;
+	u32				date;
+	u8				pad[0x18];
+};
+
+#if 0
+/*
+ * Disk ID.
+ */
+struct di_disk_id {
+	u8				id[32];
+};
+#endif
+
+/*
+ * Physical format information (as per ECMA standards).
+ */
+struct di_phys_format_info {
+#if defined(__BIG_ENDIAN_BITFIELD)
+	/* Byte 0 - Disk Category and Version Number */
+	u8 disk_category:4;
+	u8 version_number:4;
+	/* Byte 1 - Disk size and maximum transfer rate */
+	u8 disk_size:4;
+	u8 max_xfer_rate:4;
+	/* Byte 2 - Disk structure */
+	u8 byte2_bit7_zero:1;
+	u8 disk_type:2;
+	u8 track_path:1;
+	u8 layer_type:4;
+	/* Byte 3 - Recording density */
+	u8 average_channel_bit_length:4;
+	u8 average_track_pitch:4;
+#elif defined(__LITTLE_ENDIAN_BITFIELD)
+	/* Byte 0 - Disk Category and Version Number */
+	u8 version_number:4;
+	u8 disk_category:4;
+	/* Byte 1 - Disk size and maximum transfer rate */
+	u8 max_xfer_rate:4;
+	u8 disk_size:4;
+	/* Byte 2 - Disk structure */
+	u8 layer_type:4;
+	u8 track_path:1;
+	u8 disk_type:2;
+	u8 byte2_bit7_zero:1;
+	/* Byte 3 - Recording density */
+	u8 average_track_pitch:4;
+	u8 average_channel_bit_length:4;
+#endif
+	__be32 first_data_psn;
+	__be32 last_data_psn;
+	/* don't care for now about the rest */
+};
+
+/*
+ * An operation code.
+ */
+struct di_opcode {
+	u16				op;
+#define DI_OP(id, flags)		(((u8)(id)<<8)|((u8)(flags)))
+#define DI_OP_ID(op)		((u8)((op)>>8))
+#define DI_OP_FLAGS(op)		((u8)(op))
+
+#define   DI_DIR_READ		0x00
+#define   DI_DIR_WRITE		DI_CR_RW
+#define   DI_MODE_IMMED		0x00
+#define   DI_MODE_DMA		DI_CR_DMA
+#define   DI_IGNORE_ERRORS	(1<<7)
+
+	char				*name;
+
+	u32				cmdbuf0;
+};
+
+/*
+ * Drive code container.
+ */
+struct di_drive_code {
+	u32				address;
+	size_t				len;
+	void				*code;
+};
+
+struct di_device;
+
+/*
+ * A Disk Interface command.
+ */
+struct di_command {
+	u16				opidx;
+
+	u32				cmdbuf0;
+	u32				cmdbuf1;
+	u32				cmdbuf2;
+
+	void				*data;
+	size_t				len;
+
+	dma_addr_t			dma_addr;
+	size_t				dma_len;
+
+	void				*done_data;
+	void				(*done)(struct di_command *cmd);
+
+	u16				retries;
+	u16				max_retries;
+
+	u32				result;
+
+	struct di_device		*ddev;
+};
+
+#define di_result_ok(result)	((result) == DI_SR_TCINT)
+#define di_command_ok(cmd)	(di_result_ok((cmd)->result))
+
+enum {
+	__DI_MEDIA_CHANGED,
+	__DI_START_QUEUE,
+	__DI_RESETTING,
+};
+
+/*
+ * The Disk Interface device.
+ */
+struct di_device {
+	spinlock_t			lock;
+
+	int				irq;
+
+	spinlock_t			io_lock;
+	void __iomem			*io_base;
+
+	struct di_command		*cmd;
+	struct di_command		*failed_cmd;
+
+	struct di_command		status;
+	u32				drive_status;
+
+	struct gendisk                  *disk;
+	struct request_queue            *queue;
+	spinlock_t			queue_lock;
+
+	struct request                  *req;
+	struct di_command		req_cmd;
+
+	struct di_drive_code		*drive_code;
+
+	u32				model;
+	unsigned long			flags;
+#define DI_MEDIA_CHANGED	(1<<__DI_MEDIA_CHANGED)
+#define DI_START_QUEUE		(1<<__DI_START_QUEUE)
+#define DI_RESETTING		(1<<__DI_RESETTING)
+
+	unsigned long			nr_sectors;
+
+	struct timer_list		motor_off_timer;
+
+#ifdef CONFIG_PROC_FS
+	struct proc_dir_entry		*proc;
+#endif /* CONFIG_PROC_FS */
+
+	int				ref_count;
+
+	struct device			*dev;
+};
+
+
+u8 di_scratch_frame[DI_SECTOR_SIZE]
+		 __attribute__ ((aligned(DI_DMA_ALIGN+1)));
+
+/*
+ * We do not accept original media with this driver, as there is currently no
+ * general need for that.
+ * If you ever develop an application (a media player for example) which works
+ * with original media, just change di_accept_gods and recompile.
+ */
+static const int di_accept_gods;
+
+/*
+ * Drive operations table, incomplete.
+ * We just include here some of the available functions, in no particular
+ * order.
+ */
+#define CMDBUF(a, b, c, d) (((a)<<24)|((b)<<16)|((c)<<8)|(d))
+
+enum {
+	DI_OP_NOP,
+	DI_OP_INQ,
+	DI_OP_STOPMOTOR,
+	DI_OP_READDISKID,
+	DI_OP_READSECTOR,
+	DI_OP_GETSTATUS,
+	DI_OP_READPHYSINFO,
+	DI_OP_LAST
+};
+
+static struct di_opcode di_opcodes[] = {
+	[DI_OP_NOP] = {
+		.op = DI_OP(DI_OP_NOP, 0),
+		.name = "NOP",
+		.cmdbuf0 = 0,
+	},
+	[DI_OP_INQ] = {
+		.op = DI_OP(DI_OP_INQ, DI_DIR_READ | DI_MODE_DMA),
+		.name = "INQ",
+		.cmdbuf0 = 0x12000000,
+	},
+	[DI_OP_STOPMOTOR] = {
+		.op = DI_OP(DI_OP_STOPMOTOR, DI_DIR_READ | DI_MODE_IMMED),
+		.name = "STOPMOTOR",
+		.cmdbuf0 = 0xe3000000,
+	},
+	[DI_OP_READDISKID] = {
+		.op = DI_OP(DI_OP_READDISKID, DI_DIR_READ | DI_MODE_DMA),
+		.name = "READDISKID",
+		.cmdbuf0 = 0xa8000040,
+	},
+	[DI_OP_READSECTOR] = {
+		.op = DI_OP(DI_OP_READSECTOR, DI_DIR_READ | DI_MODE_DMA),
+		.name = "READSECTOR",
+		.cmdbuf0 = 0xd0000000,
+	},
+	[DI_OP_GETSTATUS] = {
+		.op = DI_OP(DI_OP_GETSTATUS, DI_DIR_READ | DI_MODE_IMMED),
+		.name = "GETSTATUS",
+		.cmdbuf0 = 0xe0000000,
+	},
+	[DI_OP_READPHYSINFO] = {
+		.op = DI_OP(DI_OP_READPHYSINFO, DI_DIR_READ | DI_MODE_DMA),
+		.name = "READPHYSINFO",
+		.cmdbuf0 = 0xad000000,
+	},
+};
+
+#define DI_OP_MAXOP		(DI_OP_LAST-1)
+#define DI_OP_CUSTOM		((u16)~0)
+
+
+static void di_reset(struct di_device *ddev);
+static int di_run_command(struct di_command *cmd);
+
+/*
+ * Returns the operation code related data for a command.
+ */
+static inline struct di_opcode *di_get_opcode(struct di_command *cmd)
+{
+	BUG_ON(cmd->opidx > DI_OP_MAXOP && cmd->opidx != DI_OP_CUSTOM);
+
+	if (cmd->opidx == DI_OP_CUSTOM)
+		return cmd->data;
+	else
+		return &di_opcodes[cmd->opidx];
+}
+
+/*
+ * Returns the operation code for a command.
+ */
+static inline u16 di_op(struct di_command *cmd)
+{
+	return di_get_opcode(cmd)->op;
+}
+
+
+/*
+ * Basic initialization for all commands.
+ */
+static void di_op_basic(struct di_command *cmd,
+			struct di_device *ddev, u16 opidx)
+{
+	struct di_opcode *opcode;
+
+	memset(cmd, 0, sizeof(*cmd));
+	cmd->ddev = ddev;
+	cmd->opidx = opidx;
+	cmd->max_retries = cmd->retries = 0;
+	opcode = di_get_opcode(cmd);
+	if (opcode)
+		cmd->cmdbuf0 = opcode->cmdbuf0;
+}
+
+/*
+ * Builds an "Inquiry" command.
+ */
+static void di_op_inq(struct di_command *cmd,
+		      struct di_device *ddev,
+		      struct di_drive_info *drive_info)
+{
+	di_op_basic(cmd, ddev, DI_OP_INQ);
+	cmd->cmdbuf2 = sizeof(*drive_info);
+	cmd->data = drive_info;
+	cmd->len = sizeof(*drive_info);
+}
+
+/*
+ * Builds a "Stop Motor" command.
+ */
+static inline void di_op_stopmotor(struct di_command *cmd,
+				   struct di_device *ddev)
+{
+	di_op_basic(cmd, ddev, DI_OP_STOPMOTOR);
+}
+
+#if 0
+/*
+ * Builds a "Read Disc ID" command.
+ */
+static void di_op_readdiskid(struct di_command *cmd,
+			     struct di_device *ddev,
+			     struct di_disk_id *disk_id)
+{
+	di_op_basic(cmd, ddev, DI_OP_READDISKID);
+	cmd->cmdbuf2 = sizeof(*disk_id);
+	cmd->data = disk_id;
+	cmd->len = sizeof(*disk_id);
+	cmd->max_retries = cmd->retries = DI_COMMAND_RETRIES;
+}
+#endif
+
+/*
+ * Builds a "Read Sector" command.
+ */
+static void di_op_readsector(struct di_command *cmd,
+			     struct di_device *ddev,
+			     u32 sector, void *data, size_t len)
+{
+	di_op_basic(cmd, ddev, DI_OP_READSECTOR);
+	cmd->cmdbuf1 = sector << KERNEL_SECTOR_SHIFT >> DI_SECTOR_SHIFT;
+	cmd->cmdbuf2 = len >> DI_SECTOR_SHIFT;
+	cmd->data = data;
+	cmd->len = len;
+	cmd->max_retries = cmd->retries = DI_COMMAND_RETRIES;
+}
+
+/*
+ * Builds a "Read Physical Info" command.
+ */
+static void di_op_readphysinfo(struct di_command *cmd,
+				struct di_device *ddev,
+				u8 sector, void *data)
+{
+	di_op_basic(cmd, ddev, DI_OP_READPHYSINFO);
+	cmd->cmdbuf0 |= (sector << 8);
+	cmd->data = data;
+	cmd->len = 2048;
+}
+
+
+/*
+ * Builds a "get drive status" command.
+ */
+static inline void di_op_getstatus(struct di_command *cmd,
+				   struct di_device *ddev)
+{
+	di_op_basic(cmd, ddev, DI_OP_GETSTATUS);
+}
+
+/*
+ * Returns the printable form of the status part of a drive status.
+ */
+static char *di_printable_status(u32 drive_status)
+{
+	char *s = "unknown";
+
+	switch (DI_STATUS(drive_status)) {
+	case DI_STATUS_READY:
+		s = "ready";
+		break;
+	case DI_STATUS_COVER_OPENED:
+		s = "cover opened";
+		break;
+	case DI_STATUS_DISK_CHANGE:
+		s = "disk change";
+		break;
+	case DI_STATUS_NO_DISK:
+		s = "no disk";
+		break;
+	case DI_STATUS_MOTOR_STOP:
+		s = "motor stop";
+		break;
+	case DI_STATUS_DISK_ID_NOT_READ:
+		s = "disk id not read";
+		break;
+	}
+	return s;
+}
+
+/*
+ * Returns the printable form of the error part of a drive status.
+ */
+static char *di_printable_error(u32 drive_status)
+{
+	char *s = "unknown";
+
+	switch (DI_ERROR(drive_status)) {
+	case DI_ERROR_NO_ERROR:
+		s = "no error";
+		break;
+	case DI_ERROR_MOTOR_STOPPED:
+		s = "motor stopped";
+		break;
+	case DI_ERROR_DISK_ID_NOT_READ:
+		s = "disk id not read";
+		break;
+	case DI_ERROR_MEDIUM_NOT_PRESENT:
+		s = "medium not present";
+		break;
+	case DI_ERROR_SEEK_INCOMPLETE:
+		s = "seek incomplete";
+		break;
+	case DI_ERROR_UNRECOVERABLE_READ:
+		s = "unrecoverable read";
+		break;
+	case DI_ERROR_INVALID_COMMAND:
+		s = "invalid command";
+		break;
+	case DI_ERROR_BLOCK_OUT_OF_RANGE:
+		s = "block out of range";
+		break;
+	case DI_ERROR_INVALID_FIELD:
+		s = "invalid field";
+		break;
+	case DI_ERROR_MEDIUM_CHANGED:
+		s = "medium changed";
+		break;
+	}
+
+	return s;
+}
+
+static char *di_printable_book_type(u8 book_type)
+{
+	char *s = "unknown";
+
+	switch (book_type) {
+	case DI_BOOK_TYPE_DVD_ROM:
+		s = "DVD-ROM";
+		break;
+	case DI_BOOK_TYPE_DVD_RAM:
+		s = "DVD-RAM";
+		break;
+	case DI_BOOK_TYPE_DVD_MINUS_R:
+		s = "DVD-R";
+		break;
+	case DI_BOOK_TYPE_DVD_MINUS_RW:
+		s = "DVD-RW";
+		break;
+	case DI_BOOK_TYPE_DVD_PLUS_RW:
+		s = "DVD+RW";
+		break;
+	case DI_BOOK_TYPE_DVD_PLUS_R:
+		s = "DVD+R";
+		break;
+	case DI_BOOK_TYPE_DVD_PLUS_RW_DL:
+		s = "DVD+RW DL";
+		break;
+	case DI_BOOK_TYPE_DVD_PLUS_R_DL:
+		s = "DVD+R DL";
+		break;
+	}
+	return s;
+}
+
+static char *di_printable_disk_size(u8 disk_size)
+{
+	char *s = "unknown";
+
+	switch (disk_size) {
+	case DI_DISK_SIZE_80MM:
+		s = "80mm";
+		break;
+	case DI_DISK_SIZE_120MM:
+		s = "120mm";
+		break;
+	}
+	return s;
+}
+
+/*
+ * Prints the given drive status, only if debug enabled.
+ */
+static inline void di_debug_print_drive_status(u32 drive_status)
+{
+	pr_devel("%08x, [%s, %s]\n", drive_status,
+		 di_printable_status(drive_status),
+		 di_printable_error(drive_status));
+}
+
+/*
+ * Prints the given drive status.
+ */
+static void di_print_drive_status(u32 drive_status)
+{
+	pr_info("drive_status=%08x, [%s, %s]\n", drive_status,
+		di_printable_status(drive_status),
+		di_printable_error(drive_status));
+}
+
+#if 0
+/*
+ * Prints the given disk identifier.
+ */
+static void di_print_disk_id(struct di_disk_id *disk_id)
+{
+	pr_info("disk_id = [%s]\n", disk_id->id);
+}
+#endif
+
+/*
+ *
+ * I/O.
+ */
+
+/*
+ * Converts a request direction into a DMA data direction.
+ */
+static inline
+enum dma_data_direction di_opidx_to_dma_dir(struct di_command *cmd)
+{
+	u16 op = di_op(cmd);
+
+	if (unlikely(op & DI_DIR_WRITE))
+		return DMA_TO_DEVICE;
+	else
+		return DMA_FROM_DEVICE;
+}
+
+/*
+ * Starts a DMA transfer.
+ */
+static void di_start_dma_transfer_raw(struct di_device *ddev,
+				      dma_addr_t data, size_t len, int mode)
+{
+	void __iomem *io_base = ddev->io_base;
+	u32 __iomem *sr_reg = io_base + DI_SR;
+	unsigned long flags;
+
+	BUG_ON((data & DI_DMA_ALIGN) != 0 ||
+	       (len & DI_DMA_ALIGN) != 0);
+
+	/* setup address and length of transfer */
+	out_be32(io_base + DI_LENGTH, len);
+	out_be32(io_base + DI_MAR, data);
+
+	/* enable the Transfer Complete interrupt */
+	spin_lock_irqsave(&ddev->io_lock, flags);
+	out_be32(sr_reg, in_be32(sr_reg) | DI_SR_TCINTMASK);
+	spin_unlock_irqrestore(&ddev->io_lock, flags);
+
+	/* start the transfer */
+	out_be32(io_base + DI_CR, DI_CR_TSTART | DI_CR_DMA |
+		 (mode & DI_CR_RW));
+}
+
+/*
+ * Internal. Busy-waits until a DMA transfer finishes or timeouts.
+ */
+static int __wait_for_dma_transfer_or_timeout(u32 __iomem *cr_reg,
+					      int secs)
+{
+	unsigned long timeout = jiffies + secs*HZ;
+
+	/* busy-wait for transfer complete */
+	while ((in_be32(cr_reg) & DI_CR_TSTART) &&
+			time_before(jiffies, timeout))
+		cpu_relax();
+
+	return (in_be32(cr_reg) & DI_CR_TSTART) ? -EBUSY : 0;
+}
+
+#if 0
+/*
+ * Busy-waits until DMA transfers are finished.
+ */
+static void di_wait_for_dma_transfer_raw(struct di_device *ddev)
+{
+	u32 __iomem *cr_reg = ddev->io_base + DI_CR;
+	u32 __iomem *sr_reg = ddev->io_base + DI_SR;
+	unsigned long flags;
+
+	/* we don't want TCINTs to disturb us while waiting */
+	spin_lock_irqsave(&ddev->io_lock, flags);
+	out_be32(sr_reg, in_be32(sr_reg) & ~DI_SR_TCINTMASK);
+	spin_unlock_irqrestore(&ddev->io_lock, flags);
+
+	/* if the drive got stuck, reset it */
+	if (__wait_for_dma_transfer_or_timeout(cr_reg, DI_COMMAND_TIMEOUT)) {
+		pr_devel("dvd stuck!\n");
+		di_reset(ddev);
+	}
+
+	/* ack and enable the Transfer Complete interrupt */
+	spin_lock_irqsave(&ddev->io_lock, flags);
+	out_be32(sr_reg, in_be32(sr_reg) | (DI_SR_TCINT|DI_SR_TCINTMASK));
+	spin_unlock_irqrestore(&ddev->io_lock, flags);
+
+	return;
+}
+#endif
+
+/*
+ * Quiesces the hardware to a calm and known state.
+ */
+static void di_quiesce(struct di_device *ddev)
+{
+	void __iomem *io_base = ddev->io_base;
+	u32 __iomem *cr_reg = io_base + DI_CR;
+	u32 __iomem *sr_reg = io_base + DI_SR;
+	u32 __iomem *cvr_reg = io_base + DI_CVR;
+	u32 sr, cvr;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ddev->io_lock, flags);
+
+	/* ack and mask dvd io interrupts */
+	sr = in_be32(sr_reg);
+	sr |= DI_SR_BRKINT | DI_SR_TCINT | DI_SR_DEINT;
+	sr &= ~(DI_SR_BRKINTMASK | DI_SR_TCINTMASK | DI_SR_DEINTMASK);
+	out_be32(sr_reg, sr);
+
+	/* ack and mask dvd cover interrupts */
+	cvr = in_be32(cvr_reg);
+	out_be32(cvr_reg, (cvr | DI_CVR_CVRINT) & ~DI_CVR_CVRINTMASK);
+
+	spin_unlock_irqrestore(&ddev->io_lock, flags);
+
+	/* busy-wait for transfer complete */
+	__wait_for_dma_transfer_or_timeout(cr_reg, DI_COMMAND_TIMEOUT);
+}
+
+/*
+ * Command engine.
+ *
+ */
+
+/*
+ * Outputs the command buffers, and optionally starts a transfer.
+ */
+static void di_prepare_command(struct di_command *cmd, int tstart)
+{
+	struct di_opcode *opcode = di_get_opcode(cmd);
+	void __iomem *io_base = cmd->ddev->io_base;
+
+	out_be32(io_base + DI_CMDBUF0, cmd->cmdbuf0);
+	out_be32(io_base + DI_CMDBUF1, cmd->cmdbuf1);
+	out_be32(io_base + DI_CMDBUF2, cmd->cmdbuf2);
+
+	cmd->ddev->drive_status = 0;
+
+	if (tstart) {
+		out_be32(io_base + DI_CR, DI_CR_TSTART |
+			 (opcode->op & DI_CR_RW));
+	}
+}
+
+static void di_command_done(struct di_command *cmd);
+
+/*
+ * Starts a command by using the immediate mode.
+ */
+static int di_start_command(struct di_command *cmd)
+{
+	struct di_device *ddev = cmd->ddev;
+	unsigned long flags;
+	int retval = 1;
+
+	spin_lock_irqsave(&ddev->lock, flags);
+
+	BUG_ON(ddev->cmd);
+
+	ddev->cmd = cmd;
+	cmd->dma_len = 0; /* no dma here */
+	di_prepare_command(cmd, 1);
+
+	spin_unlock_irqrestore(&ddev->lock, flags);
+
+	return retval;
+}
+
+/*
+ * Starts a command by using the DMA mode.
+ */
+static int di_start_dma_command(struct di_command *cmd)
+{
+	struct di_device *ddev = cmd->ddev;
+	unsigned long flags;
+	int retval = 1;
+
+	spin_lock_irqsave(&ddev->lock, flags);
+
+	BUG_ON(ddev->cmd);
+	BUG_ON(!cmd->len || !cmd->data);
+
+	ddev->cmd = cmd;
+	cmd->dma_len = cmd->len;
+	cmd->dma_addr = dma_map_single(ddev->dev,
+				       cmd->data, cmd->len,
+				       di_opidx_to_dma_dir(cmd));
+	if (dma_mapping_error(ddev->dev, cmd->dma_addr)) {
+		retval = -EIO;
+		goto done;
+	}
+
+	di_prepare_command(cmd, 0);
+	di_start_dma_transfer_raw(ddev, cmd->dma_addr, cmd->dma_len,
+				  di_op(cmd) & DI_DIR_WRITE);
+
+done:
+	spin_unlock_irqrestore(&ddev->lock, flags);
+
+	return retval;
+}
+
+/*
+ * Completes a "get drive status" command, after a failed command.
+ */
+static void di_complete_getstatus(struct di_command *cmd)
+{
+	struct di_device *ddev = cmd->ddev;
+	void __iomem *io_base = ddev->io_base;
+	u32 __iomem *data_reg = io_base + DI_DATA;
+
+	ddev->drive_status = in_be32(data_reg);
+}
+
+/*
+ * Called after a transfer is completed.
+ */
+static void di_complete_transfer(struct di_device *ddev, u32 result)
+{
+	struct di_command *cmd;
+	struct di_opcode *opcode;
+	u32 drive_status;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ddev->lock, flags);
+
+	/* do nothing if we have nothing to complete */
+	cmd = ddev->cmd;
+	if (!cmd) {
+		spin_unlock_irqrestore(&ddev->lock, flags);
+		goto out;
+	}
+
+	/* free the command slot */
+	ddev->cmd = NULL;
+
+	spin_unlock_irqrestore(&ddev->lock, flags);
+
+	/* deal with caches after a dma transfer */
+	if (cmd->dma_len) {
+		dma_unmap_single(ddev->dev,
+				 cmd->dma_addr, cmd->dma_len,
+				 di_opidx_to_dma_dir(cmd));
+#if 1
+		/*
+		 * Meh...
+		 * Someone (the block layer?) seems to dereference parts of
+		 * req->buffer after having dma-mapped it and while it is
+		 * owned by the low level driver.
+		 *
+		 * This causes a problem in non-coherent systems as those
+		 * parts of the buffer end up with the wrong data if
+		 * dereferenced before the DI dma engine writes to them.
+		 * Force a cache invalidation again to make sure that we
+		 * really get the right data from the device.
+		 *
+		 * Without this countermeasure you can easily get random
+		 * garbage in files while reading them from a iso9660 disc.
+		 */
+		if (likely(di_opidx_to_dma_dir(cmd) == DMA_FROM_DEVICE))
+			__dma_sync(cmd->data, cmd->len, DMA_FROM_DEVICE);
+#endif
+	}
+
+	opcode = di_get_opcode(cmd);
+
+	/*
+	 * If a command fails we check the drive status. Depending on that
+	 * we may or not retry later the command.
+	 */
+	cmd->result = result;
+	if (!di_command_ok(cmd)) {
+		BUG_ON(ddev->failed_cmd != NULL);
+
+		ddev->failed_cmd = cmd;
+
+		/*
+		 * Issue immediately a "get drive status"
+		 * after a failed command.
+		 */
+		cmd = &ddev->status;
+		di_op_getstatus(cmd, ddev);
+		cmd->done = di_complete_getstatus;
+		di_run_command(cmd);
+		goto out;
+	} else {
+		if (cmd->retries != cmd->max_retries) {
+			pr_devel("command %s succeeded after %d retries :-)\n",
+				 opcode->name, cmd->max_retries - cmd->retries);
+		}
+	}
+
+	/* complete a successful command, or the MATSHITA one */
+	di_command_done(cmd);
+
+	spin_lock_irqsave(&ddev->lock, flags);
+	if (ddev->failed_cmd) {
+		cmd = ddev->failed_cmd;
+		ddev->failed_cmd = NULL;
+		spin_unlock_irqrestore(&ddev->lock, flags);
+
+		drive_status = ddev->drive_status;
+		opcode = di_get_opcode(cmd);
+
+		/* retry a previously failed command if appropiate */
+		if (cmd->retries > 0) {
+			if (di_may_retry(drive_status)) {
+				pr_devel("command %s failed, %d retries left\n",
+					 opcode->name, cmd->retries);
+				di_debug_print_drive_status(drive_status);
+
+				cmd->retries--;
+				di_run_command(cmd);
+				goto out;
+			} else {
+				pr_devel("command %s failed,"
+					 " aborting due to drive status\n",
+				    opcode->name);
+			}
+		} else {
+			if (!(opcode->op & DI_IGNORE_ERRORS))
+				pr_devel("command %s failed\n", opcode->name);
+		}
+
+		if (!(opcode->op & DI_IGNORE_ERRORS))
+			di_print_drive_status(drive_status);
+
+		/* complete the failed command */
+		di_command_done(cmd);
+
+		/* update the driver status */
+		switch (DI_ERROR(drive_status)) {
+		case DI_ERROR_MOTOR_STOPPED:
+		case DI_ERROR_MEDIUM_NOT_PRESENT:
+		case DI_ERROR_MEDIUM_CHANGED:
+			set_bit(__DI_MEDIA_CHANGED, &ddev->flags);
+			break;
+		default:
+			break;
+		}
+
+	} else {
+		spin_unlock_irqrestore(&ddev->lock, flags);
+	}
+
+	/* start the block layer queue if someone requested it */
+	if (test_and_clear_bit(__DI_START_QUEUE, &ddev->flags)) {
+		spin_lock_irqsave(&ddev->queue_lock, flags);
+		blk_start_queue(ddev->queue);
+		spin_unlock_irqrestore(&ddev->queue_lock, flags);
+	}
+
+out:
+	return;
+}
+
+/*
+ * Calls any done hooks.
+ */
+static void di_command_done(struct di_command *cmd)
+{
+	struct di_device *ddev = cmd->ddev;
+	unsigned long flags;
+
+	/* if specified, call the completion routine */
+	if (cmd->done)
+		cmd->done(cmd);
+
+	spin_lock_irqsave(&ddev->lock, flags);
+	spin_unlock_irqrestore(&ddev->lock, flags);
+}
+
+/*
+ * Completion routine.
+ */
+static void di_wait_done(struct di_command *cmd)
+{
+	complete(cmd->done_data);
+}
+
+/*
+ * Runs a command.
+ */
+static int di_run_command(struct di_command *cmd)
+{
+	struct di_opcode *opcode = di_get_opcode(cmd);
+	int retval;
+
+	if (cmd->retries > cmd->max_retries)
+		cmd->retries = cmd->max_retries;
+
+	if (!(opcode->op & DI_MODE_DMA))
+		retval = di_start_command(cmd);
+	else
+		retval = di_start_dma_command(cmd);
+	return retval;
+}
+
+/*
+ * Runs a command and waits.
+ * Might sleep if called from user context.
+ */
+static int di_run_command_and_wait(struct di_command *cmd)
+{
+	DECLARE_COMPLETION(complete);
+
+	cmd->done_data = &complete;
+	cmd->done = di_wait_done;
+	if (di_run_command(cmd) > 0)
+		wait_for_completion(&complete);
+	return cmd->result;
+}
+
+/*
+ * Interrupt handler for DI interrupts.
+ */
+static irqreturn_t di_irq_handler(int irq, void *dev0)
+{
+	struct di_device *ddev = dev0;
+	void __iomem *io_base = ddev->io_base;
+	u32 __iomem *sr_reg = io_base + DI_SR;
+	u32 __iomem *cvr_reg = io_base + DI_CVR;
+	u32 sr, cvr, reason, mask;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ddev->io_lock, flags);
+
+	sr = in_be32(sr_reg);
+	mask = sr & (DI_SR_BRKINTMASK | DI_SR_TCINTMASK | DI_SR_DEINTMASK);
+	reason = sr; /* & (mask << 1); */
+	if (reason) {
+		out_be32(sr_reg, sr | reason);
+		spin_unlock_irqrestore(&ddev->io_lock, flags);
+
+		if (reason & DI_SR_BRKINT) {
+			pr_devel("BRKINT\n");
+			di_complete_transfer(ddev, DI_SR_BRKINT);
+		}
+		if (reason & DI_SR_TCINT)
+			di_complete_transfer(ddev, DI_SR_TCINT);
+		if (reason & DI_SR_DEINT)
+			di_complete_transfer(ddev, DI_SR_DEINT);
+
+		spin_lock_irqsave(&ddev->io_lock, flags);
+	}
+
+	cvr = in_be32(cvr_reg);
+	mask = cvr & DI_CVR_CVRINTMASK;
+	reason = cvr; /* & (mask << 1); */
+	if ((reason & DI_CVR_CVRINT)) {
+		out_be32(cvr_reg, cvr | DI_CVR_CVRINT);
+		set_bit(__DI_MEDIA_CHANGED, &ddev->flags);
+		if (!test_and_clear_bit(__DI_RESETTING, &ddev->flags))
+			pr_devel("disk inserted!\n");
+	}
+
+	spin_unlock_irqrestore(&ddev->io_lock, flags);
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * Hard-resets the drive.
+ */
+static void di_reset(struct di_device *ddev)
+{
+	void *hw_diflags;
+	void *hw_resets;
+
+	hw_resets = ioremap(0x0d800194, 4);
+	if (hw_resets) {
+		set_bit(__DI_RESETTING, &ddev->flags);
+		/* reset dvd unit */
+		out_be32(hw_resets, in_be32(hw_resets) & ~(1<<10));
+		mdelay(50);
+		out_be32(hw_resets, in_be32(hw_resets) | (1<<10));
+		iounmap(hw_resets);
+	}
+	pr_devel("drive reset\n");
+	hw_diflags = ioremap(0x0d800180, 4);
+	if (hw_diflags) {
+		/* enable dvd-video */
+		out_be32(hw_diflags, in_be32(hw_diflags) & ~(1<<21));
+		iounmap(hw_diflags);
+	}
+}
+
+
+/*
+ * Misc routines.
+ *
+ */
+
+/*
+ * Retrieves (and prints out) the laser unit model.
+ */
+static u32 di_retrieve_drive_model(struct di_device *ddev)
+{
+	struct di_drive_info *di_drive_info;
+	struct di_command cmd;
+
+	di_drive_info = (struct di_drive_info *)di_scratch_frame;
+	memset(di_drive_info, 0, sizeof(*di_drive_info));
+	di_op_inq(&cmd, ddev, di_drive_info);
+	di_run_command_and_wait(&cmd);
+
+	pr_info("laser unit: rev=%x, code=%x, date=%x\n",
+		di_drive_info->rev, di_drive_info->code,
+		di_drive_info->date);
+
+	ddev->model = di_drive_info->date;
+	return ddev->model;
+}
+
+/*
+ * Gets the current drive status.
+ */
+static u32 di_get_drive_status(struct di_device *ddev)
+{
+	void __iomem *io_base = ddev->io_base;
+	u32 __iomem *data_reg = io_base + DI_DATA;
+	struct di_command cmd;
+	u32 drive_status;
+
+	di_op_getstatus(&cmd, ddev);
+	di_run_command_and_wait(&cmd);
+	drive_status = in_be32(data_reg);
+
+	return drive_status;
+}
+
+/*
+ * Checks if the drive is in a ready state.
+ */
+static int di_is_drive_ready(struct di_device *ddev)
+{
+	u32 drive_status;
+	int result = 0;
+
+	drive_status = di_get_drive_status(ddev);
+	if (DI_STATUS(drive_status) == DI_STATUS_DISK_ID_NOT_READ ||
+	    DI_STATUS(drive_status) == DI_STATUS_READY) {
+		result = 1;
+	}
+
+	return result;
+}
+
+/*
+ * Spins down the drive, immediatelly.
+ */
+static void di_spin_down_drive(struct di_device *ddev)
+{
+	struct di_command cmd;
+
+	di_op_stopmotor(&cmd, ddev);
+	di_run_command_and_wait(&cmd);
+}
+
+/*
+ * Stops the drive's motor, according to a previous schedule.
+ */
+static void di_motor_off(unsigned long ddev0)
+{
+	struct di_device *ddev = (struct di_device *)ddev0;
+	struct di_command *cmd;
+	unsigned long flags;
+
+	/* postpone a bit the motor off if there are pending commands */
+	spin_lock_irqsave(&ddev->lock, flags);
+	if (!ddev->cmd) {
+		ddev->cmd = cmd = &ddev->status;
+		spin_unlock_irqrestore(&ddev->lock, flags);
+		di_op_stopmotor(cmd, ddev);
+		di_prepare_command(cmd, 1);
+	} else {
+		spin_unlock_irqrestore(&ddev->lock, flags);
+		mod_timer(&ddev->motor_off_timer, jiffies + 1*HZ);
+	}
+}
+
+/*
+ * Cancels a previously scheduled motor off.
+ */
+static inline void di_cancel_motor_off(struct di_device *ddev)
+{
+	del_timer(&ddev->motor_off_timer);
+}
+
+/*
+ * Stops the drive's motor after the specified amount of seconds has elapsed.
+ */
+static void di_schedule_motor_off(struct di_device *ddev, unsigned int secs)
+{
+	del_timer(&ddev->motor_off_timer);
+	ddev->motor_off_timer.expires = jiffies + secs*HZ;
+	ddev->motor_off_timer.data = (unsigned long)ddev;
+	add_timer(&ddev->motor_off_timer);
+}
+
+/*
+ * Spins up the drive.
+ */
+static void di_spin_up_drive(struct di_device *ddev)
+{
+	/* do nothing if the drive is already spinning */
+	if (di_is_drive_ready(ddev))
+		goto out;
+
+	di_reset(ddev);
+out:
+	return;
+}
+
+
+/*
+ * Block layer hooks.
+ *
+ */
+
+static int di_read_toc(struct di_device *ddev)
+{
+	struct di_phys_format_info *info;
+	struct di_command cmd;
+	int error;
+
+	di_cancel_motor_off(ddev);
+
+	/* spin up the drive if needed */
+	if ((ddev->flags & DI_MEDIA_CHANGED))
+		di_spin_up_drive(ddev);
+
+	di_op_readphysinfo(&cmd, ddev, 0, di_scratch_frame);
+	di_run_command_and_wait(&cmd);
+	if (di_command_ok(&cmd)) {
+		info = (struct di_phys_format_info *)di_scratch_frame;
+
+		be32_to_cpus(&info->first_data_psn);
+		be32_to_cpus(&info->last_data_psn);
+
+		/* nr_sectors is specified in DI sectors here */
+		ddev->nr_sectors = info->last_data_psn - 
+					info->first_data_psn + 1;
+
+		pr_devel("%s %s disk found (%lu sectors)\n",
+			 di_printable_disk_size(info->disk_size),
+			 di_printable_book_type(info->disk_category),
+			 ddev->nr_sectors);
+		clear_bit(__DI_MEDIA_CHANGED, &ddev->flags);
+		error = 0;
+	} else {
+		ddev->nr_sectors = 0;
+
+		pr_devel("media NOT ready\n");
+		di_spin_down_drive(ddev);
+		error = -ENOMEDIUM;
+	}
+
+	/* transform to kernel sectors */
+	ddev->nr_sectors <<= (DI_SECTOR_SHIFT - KERNEL_SECTOR_SHIFT);
+	set_capacity(ddev->disk, ddev->nr_sectors);
+
+	return error;
+}
+
+
+static void di_request_done(struct di_command *cmd)
+{
+	struct di_device *ddev = cmd->ddev;
+	struct request *req;
+	unsigned long flags;
+	int error = (cmd->result & DI_SR_TCINT) ? 0 : -EIO;
+
+	spin_lock_irqsave(&ddev->lock, flags);
+	req = ddev->req;
+	ddev->req = NULL;
+	spin_unlock_irqrestore(&ddev->lock, flags);
+
+	if (req) {
+		spin_lock_irqsave(&ddev->queue_lock, flags);
+		__blk_end_request_cur(req, error);
+		blk_start_queue(ddev->queue);
+		spin_unlock_irqrestore(&ddev->queue_lock, flags);
+	}
+}
+
+static void di_do_request(struct request_queue *q)
+{
+	struct di_device *ddev = q->queuedata;
+	struct di_command *cmd = &ddev->req_cmd;
+	struct request *req;
+	unsigned long start;
+	unsigned long flags;
+	size_t len;
+	int error;
+
+	req = blk_peek_request(q);
+	while (req) {
+		spin_lock_irqsave(&ddev->lock, flags);
+
+		if (ddev->req || ddev->cmd) {
+			blk_stop_queue(q);
+			if (ddev->cmd)
+				set_bit(__DI_START_QUEUE, &ddev->flags);
+			spin_unlock_irqrestore(&ddev->lock, flags);
+			break;
+		}
+
+		blk_start_request(req);
+		error = -EIO;
+
+		if (!blk_fs_request(req))
+			goto done;
+
+		/* it doesn't make sense to write to this device */
+		if (unlikely(rq_data_dir(req) == WRITE)) {
+			pr_err("write attempted\n");
+			goto done;
+		}
+
+		/* it is not a good idea to open the lid ... */
+		if ((ddev->flags & DI_MEDIA_CHANGED)) {
+			pr_err("media changed, aborting\n");
+			goto done;
+		}
+
+		/* keep our reads within limits */
+		if (blk_rq_pos(req) +
+				blk_rq_cur_sectors(req) > ddev->nr_sectors) {
+			pr_err("reading past end\n");
+			goto done;
+		}
+
+		ddev->req = req;
+		blk_stop_queue(q);
+		spin_unlock_irqrestore(&ddev->lock, flags);
+
+		/* launch the corresponding read sector command */
+		start = blk_rq_pos(req);
+		len = blk_rq_cur_bytes(req);
+		if (len & (DI_SECTOR_SIZE-1))
+			pr_devel("len=%u\n", len);
+
+		di_op_readsector(cmd, ddev, start, req->buffer, len);
+		cmd->done_data = cmd;
+		cmd->done = di_request_done;
+		di_run_command(cmd);
+		error = 0;
+		break;
+	done:
+		spin_unlock_irqrestore(&ddev->lock, flags);
+		if (!__blk_end_request_cur(req, error))
+			req = blk_peek_request(q);
+	}
+}
+
+/*
+ * Block device hooks.
+ *
+ */
+
+static int di_open(struct block_device *bdev, fmode_t mode)
+{
+	struct di_device *ddev = bdev->bd_disk->private_data;
+	struct di_command *cmd;
+	DECLARE_COMPLETION(complete);
+	unsigned long flags;
+	int retval = 0;
+
+	/* this is a read only device */
+	if (mode & FMODE_WRITE) {
+		retval = -EROFS;
+		goto out;
+	}
+
+	/*
+	 * If we have a pending command, that's a previously scheduled
+	 * motor off. Wait for it to terminate before going on.
+	 */
+	spin_lock_irqsave(&ddev->lock, flags);
+	if (ddev->cmd && ddev->ref_count == 0) {
+		cmd = ddev->cmd;
+		cmd->done_data = &complete;
+		cmd->done = di_wait_done;
+		spin_unlock_irqrestore(&ddev->lock, flags);
+		wait_for_completion(&complete);
+	} else {
+		spin_unlock_irqrestore(&ddev->lock, flags);
+	}
+
+	/* this will take care of validating the media */
+	check_disk_change(bdev);
+	if (!ddev->nr_sectors) {
+		retval = -ENOMEDIUM;
+		goto out;
+	}
+
+	spin_lock_irqsave(&ddev->queue_lock, flags);
+
+	/* honor exclusive open mode */
+	if (ddev->ref_count == -1 ||
+	    (ddev->ref_count && (mode & FMODE_EXCL))) {
+		retval = -EBUSY;
+		goto out_unlock;
+	}
+
+	if ((mode & FMODE_EXCL))
+		ddev->ref_count = -1;
+	else
+		ddev->ref_count++;
+
+out_unlock:
+	spin_unlock_irqrestore(&ddev->queue_lock, flags);
+out:
+	return retval;
+
+}
+
+static int di_release(struct gendisk *disk, fmode_t mode)
+{
+	struct di_device *ddev = disk->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ddev->queue_lock, flags);
+
+	if (ddev->ref_count > 0)
+		ddev->ref_count--;
+	else
+		ddev->ref_count = 0;
+
+	spin_unlock_irqrestore(&ddev->queue_lock, flags);
+
+	if (ddev->ref_count == 0) {
+		/*
+		 * We do not immediately stop the motor, which saves us
+		 * a spin down/spin up in applications that re-open quickly
+		 * the device, like mount when -t is not specified.
+		 */
+		di_schedule_motor_off(ddev, 1);
+
+		set_bit(__DI_MEDIA_CHANGED, &ddev->flags);
+	}
+
+	return 0;
+}
+
+static int di_revalidate_disk(struct gendisk *disk)
+{
+	struct di_device *ddev = disk->private_data;
+	di_read_toc(ddev);
+	return 0;
+}
+
+static int di_media_changed(struct gendisk *disk)
+{
+	struct di_device *ddev = disk->private_data;
+	return (ddev->flags & DI_MEDIA_CHANGED) ? 1 : 0;
+}
+
+static int di_ioctl(struct block_device *bdev, fmode_t mode,
+		    unsigned int cmd, unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	int ret;
+	struct gendisk *disk = bdev->bd_disk;
+
+	ret = scsi_cmd_ioctl(disk->queue, disk, mode, cmd, argp);
+	if (ret != -ENOTTY)
+		return ret;
+
+	return -ENOSYS;
+}
+
+static struct block_device_operations di_fops = {
+	.owner = THIS_MODULE,
+	.open = di_open,
+	.release = di_release,
+	.revalidate_disk = di_revalidate_disk,
+	.media_changed = di_media_changed,
+	.ioctl = di_ioctl,
+};
+
+/*
+ * Setup routines.
+ *
+ */
+
+static int di_init_irq(struct di_device *ddev)
+{
+	void __iomem *io_base = ddev->io_base;
+	u32 __iomem *sr_reg = io_base + DI_SR;
+	u32 __iomem *cvr_reg = io_base + DI_CVR;
+	u32 sr, cvr;
+	unsigned long flags;
+	int retval;
+
+	init_timer(&ddev->motor_off_timer);
+	ddev->motor_off_timer.function =
+		(void (*)(unsigned long))di_motor_off;
+
+	ddev->flags = 0;
+	set_bit(__DI_MEDIA_CHANGED, &ddev->flags);
+
+	/* calm down things a bit first */
+	di_quiesce(ddev);
+
+	/* request interrupt */
+	retval = request_irq(ddev->irq, di_irq_handler, 0,
+			     DRV_MODULE_NAME, ddev);
+	if (retval) {
+		pr_err("request of irq%d failed\n", ddev->irq);
+		goto out;
+	}
+
+	spin_lock_irqsave(&ddev->io_lock, flags);
+
+	sr = in_be32(sr_reg);
+	sr |= DI_SR_BRKINT | DI_SR_TCINT | DI_SR_DEINT;
+	sr |= DI_SR_BRKINTMASK | DI_SR_TCINTMASK | DI_SR_DEINTMASK;
+	out_be32(sr_reg, sr);
+
+	cvr = in_be32(cvr_reg);
+	out_be32(cvr_reg, cvr | DI_CVR_CVRINT | DI_CVR_CVRINTMASK);
+
+	spin_unlock_irqrestore(&ddev->io_lock, flags);
+
+	di_retrieve_drive_model(ddev);
+
+	di_schedule_motor_off(ddev, DI_MOTOR_OFF_TIMEOUT);
+
+out:
+	return retval;
+}
+
+static void di_exit_irq(struct di_device *ddev)
+{
+	/* stop DVD motor */
+	di_cancel_motor_off(ddev);
+	di_spin_down_drive(ddev);
+
+	di_quiesce(ddev);
+
+	free_irq(ddev->irq, ddev);
+}
+
+static int di_init_blk_dev(struct di_device *ddev)
+{
+	struct gendisk *disk;
+	struct request_queue *queue;
+	int retval;
+
+	spin_lock_init(&ddev->lock);
+	spin_lock_init(&ddev->io_lock);
+
+	ddev->ref_count = 0;
+
+	retval = register_blkdev(DI_MAJOR, DI_NAME);
+	if (retval) {
+		pr_err("error registering major %d\n", DI_MAJOR);
+		goto err_register_blkdev;
+	}
+
+	retval = -ENOMEM;
+	spin_lock_init(&ddev->queue_lock);
+	queue = blk_init_queue(di_do_request, &ddev->queue_lock);
+	if (!queue) {
+		pr_err("error initializing queue\n");
+		goto err_blk_init_queue;
+	}
+
+	blk_queue_logical_block_size(queue, DI_SECTOR_SIZE);
+	blk_queue_dma_alignment(queue, DI_DMA_ALIGN);
+	/* max number of data segments that the driver deals with */
+	blk_queue_max_phys_segments(queue, 1);
+	/* max number of data segments that the hardware deals with */
+	blk_queue_max_hw_segments(queue, 1);
+	/* max size for a data segment */
+	blk_queue_max_segment_size(queue, 32*1024);
+	queue->queuedata = ddev;
+	ddev->queue = queue;
+
+	disk = alloc_disk(1);
+	if (!disk) {
+		pr_err("error allocating disk\n");
+		goto err_alloc_disk;
+	}
+
+	disk->major = DI_MAJOR;
+	disk->first_minor = 0;
+	disk->fops = &di_fops;
+	strcpy(disk->disk_name, DI_NAME);
+	disk->queue = ddev->queue;
+	disk->private_data = ddev;
+	ddev->disk = disk;
+
+	set_disk_ro(ddev->disk, 1);
+	add_disk(ddev->disk);
+
+	retval = 0;
+	goto out;
+
+err_alloc_disk:
+	blk_cleanup_queue(ddev->queue);
+err_blk_init_queue:
+	unregister_blkdev(DI_MAJOR, DI_NAME);
+err_register_blkdev:
+out:
+	return retval;
+}
+
+static void di_exit_blk_dev(struct di_device *ddev)
+{
+	if (ddev->disk) {
+		del_gendisk(ddev->disk);
+		put_disk(ddev->disk);
+	}
+	if (ddev->queue)
+		blk_cleanup_queue(ddev->queue);
+	unregister_blkdev(DI_MAJOR, DI_NAME);
+}
+
+static int di_init_proc(struct di_device *ddev)
+{
+#ifdef CONFIG_PROC_FS
+#endif /* CONFIG_PROC_FS */
+	return 0;
+}
+
+static void di_exit_proc(struct di_device *ddev)
+{
+#ifdef CONFIG_PROC_FS
+#endif /* CONFIG_PROC_FS */
+}
+
+static int di_init(struct di_device *ddev, struct resource *mem, int irq)
+{
+	int retval;
+
+	ddev->io_base = ioremap(mem->start, mem->end - mem->start + 1);
+	ddev->irq = irq;
+
+	retval = di_init_blk_dev(ddev);
+	if (!retval) {
+		retval = di_init_irq(ddev);
+		if (retval)
+			di_exit_blk_dev(ddev);
+		else
+			di_init_proc(ddev);
+	}
+	return retval;
+}
+
+static void di_exit(struct di_device *ddev)
+{
+	di_exit_blk_dev(ddev);
+	di_exit_irq(ddev);
+	di_exit_proc(ddev);
+	if (ddev->io_base) {
+		iounmap(ddev->io_base);
+		ddev->io_base = NULL;
+	}
+}
+
+/*
+ * Driver model helper routines.
+ *
+ */
+
+static int di_do_probe(struct device *dev,
+		       struct resource *mem, int irq)
+{
+	struct di_device *ddev;
+	int retval;
+
+	ddev = kzalloc(sizeof(*ddev), GFP_KERNEL);
+	if (!ddev) {
+		pr_err("failed to allocate di_device\n");
+		return -ENOMEM;
+	}
+	dev_set_drvdata(dev, ddev);
+	ddev->dev = dev;
+
+	retval = di_init(ddev, mem, irq);
+	if (retval) {
+		dev_set_drvdata(dev, NULL);
+		kfree(ddev);
+	}
+	return retval;
+}
+
+static int di_do_remove(struct device *dev)
+{
+	struct di_device *ddev = dev_get_drvdata(dev);
+
+	if (ddev) {
+		di_exit(ddev);
+		dev_set_drvdata(dev, NULL);
+		kfree(ddev);
+		return 0;
+	}
+	return -ENODEV;
+}
+
+static int di_do_shutdown(struct device *dev)
+{
+	struct di_device *ddev = dev_get_drvdata(dev);
+
+	if (ddev)
+		di_quiesce(ddev);
+	return 0;
+}
+
+/*
+ * OF platform driver hooks.
+ *
+ */
+
+static int __init di_of_probe(struct of_device *odev,
+			      const struct of_device_id *match)
+{
+	struct resource res;
+	int error = -ENODEV;
+
+	if (starlet_get_ipc_flavour() != STARLET_IPC_MINI)
+		goto out;
+
+	error = of_address_to_resource(odev->node, 0, &res);
+	if (error) {
+		pr_err("no io memory range found\n");
+		goto out;
+	}
+
+	error = di_do_probe(&odev->dev, &res,
+			    irq_of_parse_and_map(odev->node, 0));
+out:
+	return error;
+}
+
+static int __exit di_of_remove(struct of_device *odev)
+{
+	return di_do_remove(&odev->dev);
+}
+
+static int di_of_shutdown(struct of_device *odev)
+{
+	return di_do_shutdown(&odev->dev);
+}
+
+
+static struct of_device_id di_of_match[] = {
+	{ .compatible = "nintendo,hollywood-disk" },
+	{ },
+};
+
+MODULE_DEVICE_TABLE(of, di_of_match);
+
+static struct of_platform_driver di_of_driver = {
+	.owner = THIS_MODULE,
+	.name = DRV_MODULE_NAME,
+	.match_table = di_of_match,
+	.probe = di_of_probe,
+	.remove = di_of_remove,
+	.shutdown = di_of_shutdown,
+};
+
+/*
+ * Module interface hooks.
+ *
+ */
+
+static int __init di_init_module(void)
+{
+	pr_info("%s - version %s\n", DRV_DESCRIPTION, di_driver_version);
+
+	return of_register_platform_driver(&di_of_driver);
+}
+
+static void __exit di_exit_module(void)
+{
+	of_unregister_platform_driver(&di_of_driver);
+}
+
+module_init(di_init_module);
+module_exit(di_exit_module);
+
+MODULE_AUTHOR(DRV_AUTHOR);
+MODULE_DESCRIPTION(DRV_DESCRIPTION);
+MODULE_LICENSE("GPL");
diff --git a/drivers/block/rvl-mem2.c b/drivers/block/rvl-mem2.c
new file mode 100644
index 0000000..4906479
--- /dev/null
+++ b/drivers/block/rvl-mem2.c
@@ -0,0 +1,385 @@
+/*
+ * drivers/block/rvl-mem2.c
+ *
+ * Nintendo Wii MEM2 block driver
+ * Copyright (C) 2008-2009 The GameCube Linux Team
+ * Copyright (C) 2008,2009 Albert Herranz
+ *
+ * Based on gcn-aram.c.
+ *
+ * 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/major.h>
+#include <linux/of_platform.h>
+#include <linux/blkdev.h>
+#include <linux/fcntl.h>	/* O_ACCMODE */
+#include <linux/hdreg.h>	/* HDIO_GETGEO */
+#include <linux/io.h>
+
+
+#define DRV_MODULE_NAME "rvl-mem2"
+#define DRV_DESCRIPTION "Nintendo Wii MEM2 block driver"
+#define DRV_AUTHOR      "Albert Herranz"
+
+static char mem2_driver_version[] = "0.1-isobel";
+
+#define drv_printk(level, format, arg...) \
+	printk(level DRV_MODULE_NAME ": " format , ## arg)
+
+
+/*
+ * Driver settings
+ */
+#define MEM2_NAME		DRV_MODULE_NAME
+#define MEM2_MAJOR		Z2RAM_MAJOR
+
+#define MEM2_SECTOR_SIZE	PAGE_SIZE
+
+
+struct mem2_drvdata {
+	spinlock_t			lock;
+
+	void __iomem			*io_base;
+	size_t				size;
+
+	struct block_device_operations	fops;
+	struct gendisk			*disk;
+	struct request_queue		*queue;
+
+	int				ref_count;
+
+	struct device			*dev;
+};
+
+/*
+ *
+ */
+
+/*
+ * Performs block layer requests.
+ */
+static void mem2_do_request(struct request_queue *q)
+{
+	struct mem2_drvdata *drvdata = q->queuedata;
+	struct request *req;
+	unsigned long mem2_addr;
+	size_t len;
+	int error;
+
+	req = blk_fetch_request(q);
+	while (req) {
+		error = -EIO;
+
+		if (!blk_fs_request(req))
+			goto done;
+
+		/* calculate the MEM2 address and length */
+		mem2_addr = blk_rq_pos(req) << 9;
+		len = blk_rq_cur_bytes(req);
+
+		/* give up if the request goes out of bounds */
+		if (mem2_addr + len > drvdata->size) {
+			drv_printk(KERN_ERR, "bad access: block=%lu,"
+				   " size=%zu\n",
+				   (unsigned long)blk_rq_pos(req), len);
+			goto done;
+		}
+
+		switch (rq_data_dir(req)) {
+		case READ:
+			memcpy(req->buffer, drvdata->io_base + mem2_addr, len);
+			break;
+		case WRITE:
+			memcpy(drvdata->io_base + mem2_addr, req->buffer, len);
+			break;
+		}
+		error = 0;
+
+	done:
+		if (!__blk_end_request_cur(req, error));
+			req = blk_fetch_request(q);
+	}
+}
+
+/*
+ * Opens the MEM2 device.
+ */
+static int mem2_open(struct block_device *bdev, fmode_t mode)
+{
+	struct mem2_drvdata *drvdata = bdev->bd_disk->private_data;
+	unsigned long flags;
+	int retval = 0;
+
+	spin_lock_irqsave(&drvdata->lock, flags);
+
+	/* only allow a minor of 0 to be opened */
+	if (MINOR(bdev->bd_dev)) {
+		retval =  -ENODEV;
+		goto out;
+	}
+
+	/* honor exclusive open mode */
+	if (drvdata->ref_count == -1 ||
+	    (drvdata->ref_count && (mode & FMODE_EXCL))) {
+		retval = -EBUSY;
+		goto out;
+	}
+
+	if ((mode & FMODE_EXCL))
+		drvdata->ref_count = -1;
+	else
+		drvdata->ref_count++;
+
+out:
+	spin_unlock_irqrestore(&drvdata->lock, flags);
+	return retval;
+}
+
+/*
+ * Closes the MEM2 device.
+ */
+static int mem2_release(struct gendisk *disk, fmode_t mode)
+{
+	struct mem2_drvdata *drvdata = disk->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&drvdata->lock, flags);
+	if (drvdata->ref_count > 0)
+		drvdata->ref_count--;
+	else
+		drvdata->ref_count = 0;
+	spin_unlock_irqrestore(&drvdata->lock, flags);
+
+	return 0;
+}
+
+static int mem2_getgeo(struct block_device *bdev, struct hd_geometry *geo)
+{
+	geo->cylinders = get_capacity(bdev->bd_disk) / (4 * 16);
+	geo->heads = 4;
+	geo->sectors = 16;
+	return 0;
+}
+
+
+static struct block_device_operations mem2_fops = {
+	.owner = THIS_MODULE,
+	.open = mem2_open,
+	.release = mem2_release,
+	.getgeo = mem2_getgeo,
+};
+
+
+/*
+ *
+ */
+static int mem2_init_blk_dev(struct mem2_drvdata *drvdata)
+{
+	struct gendisk *disk;
+	struct request_queue *queue;
+	int retval;
+
+	drvdata->ref_count = 0;
+
+	retval = register_blkdev(MEM2_MAJOR, MEM2_NAME);
+	if (retval)
+		goto err_register_blkdev;
+
+	retval = -ENOMEM;
+	spin_lock_init(&drvdata->lock);
+	queue = blk_init_queue(mem2_do_request, &drvdata->lock);
+	if (!queue)
+		goto err_blk_init_queue;
+
+	blk_queue_logical_block_size(queue, MEM2_SECTOR_SIZE);
+	blk_queue_max_phys_segments(queue, 1);
+	blk_queue_max_hw_segments(queue, 1);
+	queue_flag_set_unlocked(QUEUE_FLAG_NONROT, queue);
+	queue->queuedata = drvdata;
+	drvdata->queue = queue;
+
+	disk = alloc_disk(1);
+	if (!disk)
+		goto err_alloc_disk;
+
+	disk->major = MEM2_MAJOR;
+	disk->first_minor = 0;
+	disk->fops = &mem2_fops;
+	strcpy(disk->disk_name, MEM2_NAME);
+	disk->queue = drvdata->queue;
+	set_capacity(disk, drvdata->size >> 9);
+	disk->private_data = drvdata;
+	drvdata->disk = disk;
+
+	add_disk(drvdata->disk);
+
+	retval = 0;
+	goto out;
+
+err_alloc_disk:
+	blk_cleanup_queue(drvdata->queue);
+err_blk_init_queue:
+	unregister_blkdev(MEM2_MAJOR, MEM2_NAME);
+err_register_blkdev:
+out:
+	return retval;
+}
+
+/*
+ *
+ */
+static void mem2_exit_blk_dev(struct mem2_drvdata *drvdata)
+{
+	if (drvdata->disk) {
+		del_gendisk(drvdata->disk);
+		put_disk(drvdata->disk);
+	}
+	if (drvdata->queue)
+		blk_cleanup_queue(drvdata->queue);
+	unregister_blkdev(MEM2_MAJOR, MEM2_NAME);
+}
+
+/*
+ *
+ */
+static int mem2_init(struct mem2_drvdata *drvdata, struct resource *mem)
+{
+	int retval;
+	size_t size;
+
+	size = mem->end - mem->start + 1;
+	drvdata->size = size;
+	drvdata->io_base = ioremap(mem->start, size);
+	if (!drvdata->io_base) {
+		drv_printk(KERN_ERR, "failed to ioremap MEM2\n");
+		return -EIO;
+	}
+
+	retval = mem2_init_blk_dev(drvdata);
+	if (retval)
+		iounmap(drvdata->io_base);
+	return retval;
+}
+
+/*
+ *
+ */
+static void mem2_exit(struct mem2_drvdata *drvdata)
+{
+	if (drvdata->io_base)
+		iounmap(drvdata->io_base);
+	mem2_exit_blk_dev(drvdata);
+}
+
+/*
+ *
+ */
+static int mem2_do_probe(struct device *dev, struct resource *mem)
+{
+	struct mem2_drvdata *drvdata;
+	int retval;
+
+	drvdata = kzalloc(sizeof(*drvdata), GFP_KERNEL);
+	if (!drvdata) {
+		drv_printk(KERN_ERR, "failed to allocate mem2_drvdata\n");
+		return -ENOMEM;
+	}
+	dev_set_drvdata(dev, drvdata);
+	drvdata->dev = dev;
+
+	retval = mem2_init(drvdata, mem);
+	if (retval) {
+		dev_set_drvdata(dev, NULL);
+		kfree(drvdata);
+	}
+	return retval;
+}
+
+/*
+ *
+ */
+static int mem2_do_remove(struct device *dev)
+{
+	struct mem2_drvdata *drvdata = dev_get_drvdata(dev);
+
+	if (drvdata) {
+		mem2_exit(drvdata);
+		dev_set_drvdata(dev, NULL);
+		return 0;
+	}
+	return -ENODEV;
+}
+
+/*
+ * Driver model probe function.
+ */
+static int __init mem2_of_probe(struct of_device *odev,
+				const struct of_device_id *match)
+{
+	struct resource res;
+	int retval;
+
+	retval = of_address_to_resource(odev->node, 0, &res);
+	if (retval) {
+		drv_printk(KERN_ERR, "no memory range found\n");
+		return -ENODEV;
+	}
+
+	return mem2_do_probe(&odev->dev, &res);
+}
+
+/*
+ * Driver model remove function.
+ */
+static int __exit mem2_of_remove(struct of_device *odev)
+{
+	return mem2_do_remove(&odev->dev);
+}
+
+static struct of_device_id mem2_of_match[] = {
+	{ .compatible = "nintendo,hollywood-mem2" },
+	{ },
+};
+
+MODULE_DEVICE_TABLE(of, mem2_of_match);
+
+static struct of_platform_driver mem2_of_driver = {
+	.owner = THIS_MODULE,
+	.name = DRV_MODULE_NAME,
+	.match_table = mem2_of_match,
+	.probe = mem2_of_probe,
+	.remove = mem2_of_remove,
+};
+
+/*
+ * Module initialization function.
+ */
+static int __init mem2_init_module(void)
+{
+	drv_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION,
+		   mem2_driver_version);
+
+	return of_register_platform_driver(&mem2_of_driver);
+}
+
+/*
+ * Module deinitialization funtion.
+ */
+static void __exit mem2_exit_module(void)
+{
+	of_unregister_platform_driver(&mem2_of_driver);
+}
+
+module_init(mem2_init_module);
+module_exit(mem2_exit_module);
+
+MODULE_DESCRIPTION(DRV_DESCRIPTION);
+MODULE_AUTHOR(DRV_AUTHOR);
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/block/rvl-stsd.c b/drivers/block/rvl-stsd.c
new file mode 100644
index 0000000..502e2a5
--- /dev/null
+++ b/drivers/block/rvl-stsd.c
@@ -0,0 +1,2295 @@
+/*
+ * drivers/block/rvl-stsd.c
+ *
+ * Block driver for the Nintendo Wii SD front slot.
+ * Copyright (C) 2008-2009 The GameCube Linux Team
+ * Copyright (C) 2008,2009 Albert Herranz
+ *
+ * Based on drivers/block/gcn-sd.c
+ *
+ * Copyright (C) 2004-2008 The GameCube Linux Team
+ * Copyright (C) 2004,2005 Rob Reylink
+ * Copyright (C) 2005 Todd Jeffreys
+ * Copyright (C) 2005,2006,2007,2008 Albert Herranz
+ *
+ * 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.
+ *
+ */
+
+#define DEBUG
+
+/*#define DBG(fmt, arg...)	pr_debug(fmt, ##arg)*/
+#define DBG(fmt, arg...)	drv_printk(KERN_ERR, fmt, ##arg)
+
+#include <linux/blkdev.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/hdreg.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/major.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <asm/starlet.h>
+#include <asm/starlet-ios.h>
+
+/*
+ * We are not a native MMC driver...
+ * But anyway, we try to recycle here some of the available code.
+ */
+#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/sd.h>
+#include <linux/mmc/sdio.h>
+#include "../mmc/host/sdhci.h"
+
+#define DRV_MODULE_NAME "rvl-stsd"
+#define DRV_DESCRIPTION "Block driver for the Nintendo Wii SD front slot"
+#define DRV_AUTHOR      "Albert Herranz"
+
+static char stsd_driver_version[] = "0.4i";
+
+#define drv_printk(level, format, arg...) \
+	printk(level DRV_MODULE_NAME ": " format , ## arg)
+
+/*
+ * Driver settings.
+ */
+#define MMC_SHIFT		3	/* 8 partitions */
+
+#define STSD_MAJOR		62
+#define STSD_NAME		"rvlsd"
+
+#define KERNEL_SECTOR_SHIFT     9
+#define KERNEL_SECTOR_SIZE      (1 << KERNEL_SECTOR_SHIFT)	/*512 */
+
+#define STSD_MAX_SECTORS	16
+
+
+/*
+ * IOS-related constants.
+ */
+
+/* ioctls */
+#define STSD_IOCTL_SETHSR	1
+#define STSD_IOCTL_GETHSR	2
+#define STSD_IOCTL_RESET	4
+#define STSD_IOCTL_SETCLOCK	6
+#define STSD_IOCTL_SENDCMD	7
+#define STSD_IOCTL_GETSTATUS	11
+#define STSD_IOCTL_GETOCR	12
+
+#define STSD_IOCTLV_SENDCMD	7
+
+/* SD command types */
+#define STSD_CMDTYPE_BC		1
+#define STSD_CMDTYPE_BCR	2
+#define STSD_CMDTYPE_AC		3
+#define STSD_CMDTYPE_ADTC	4
+
+/* SD response types */
+#define STSD_RSPTYPE_NONE	0
+#define STSD_RSPTYPE_R1		1
+#define STSD_RSPTYPE_R1B	2
+#define STSD_RSPTYPE_R2		3
+#define STSD_RSPTYPE_R3		4
+#define STSD_RSPTYPE_R4		5
+#define STSD_RSPTYPE_R5		6
+#define STSD_RSPTYPE_R6		7
+#define STSD_RSPTYPE_R7		8
+
+/* card status bits */
+#define STSD_STATUS_CARD_INSERTED	(1<<0)
+#define STSD_STATUS_CARD_INITIALIZED	(1<<16)
+
+/* IOS errors */
+#define STSD_ERR_INVALID_CARD		0xc1000020
+
+/*
+ * Hardware registers.
+ */
+
+/*
+ * Simplified SD Host Controller Specification
+ * Version 2.00
+ * February 8, 2007
+ */
+
+/*
+ * SD Host Standard Registers
+ *
+ */
+
+/* we are recycling the stuff already in "../mmc/host/sdhci.h" */
+
+/* TMCLK*2^a a=[13..27] */
+#define STSD_TIMEOUT_CONTROL_DIV(a)	(((a)-13)&0xf)
+
+static char stsd_dev_sdio_slot0[] = "/dev/sdio/slot0";
+
+/*
+ * Used to get/set the host controller hardware register values through IOS.
+ */
+struct stsd_reg_query {
+	u32	addr;
+	u32	_unk1;
+	u32	_unk2;
+	u32	size;
+	u32	data;
+	u32	_unk3;
+};
+
+/*
+ * Used to send commands to an SD card through IOS.
+ */
+struct stsd_command {
+	u32 opcode;
+	u32 cmdtype;
+	u32 rsptype;
+	u32 arg;
+	u32 blk_count;
+	u32 blk_size;
+	dma_addr_t dma_addr;
+	u32 is_dma;
+	u32 _unk2;
+};
+
+struct stsd_xfer {
+	size_t size;
+	enum dma_data_direction direction;
+
+	struct starlet_ioh_sg in[2], io[1];
+	struct stsd_command *cmd;
+
+	/* one-time initialized members */
+	void *reply;
+	size_t reply_len;
+	dma_addr_t dma_addr;
+	void *bounce_buf;
+	size_t bounce_buf_size;
+	size_t blk_size;
+};
+
+enum {
+	__STSD_MEDIA_CHANGED = 0,
+	__STSD_BAD_CARD,
+	__STSD_MANUAL_SETUP,
+	__STSD_SDHC,
+};
+
+struct stsd_host {
+	spinlock_t	lock;
+	unsigned long	flags;
+#define STSD_MEDIA_CHANGED	(1<<__STSD_MEDIA_CHANGED)
+#define STSD_BAD_CARD		(1<<__STSD_BAD_CARD)
+#define STSD_MANUAL_SETUP	(1<<__STSD_MANUAL_SETUP)
+#define STSD_SDHC		(1<<__STSD_SDHC)
+
+	/* u32		ocr; */
+	unsigned int	f_max;
+	unsigned int	clock;
+	u32		bus_width;
+
+	u16		status;
+
+	/* card related info */
+	struct mmc_card	card;
+
+	int refcnt;
+
+	spinlock_t 		queue_lock;
+	struct request_queue	*queue;
+	struct gendisk 		*disk;
+	unsigned int		max_phys_segments;
+
+	struct stsd_xfer	*xfer;
+
+	struct task_struct	*io_thread;
+	struct mutex		io_mutex;
+
+	int fd;
+	struct device		*dev;
+};
+
+
+static const unsigned int tran_exp[] = {
+	10000,		100000,		1000000,	10000000,
+	0,		0,		0,		0
+};
+
+static const unsigned char tran_mant[] = {
+	0,	10,	12,	13,	15,	20,	25,	30,
+	35,	40,	45,	50,	55,	60,	70,	80,
+};
+
+static const unsigned int tacc_exp[] = {
+	1,	10,	100,	1000,	10000,	100000,	1000000, 10000000,
+};
+
+static const unsigned int tacc_mant[] = {
+	0,	10,	12,	13,	15,	20,	25,	30,
+	35,	40,	45,	50,	55,	60,	70,	80,
+};
+
+
+/*
+ * debug section
+ *
+ */
+
+#if defined(DEBUG) && 0
+
+#define __case_string(_s)	\
+case _s:			\
+	str = #_s;		\
+	break;
+
+static char *stsd_opcode_string(u32 opcode)
+{
+	char *str = "unknown";
+
+	switch (opcode) {
+__case_string(MMC_GO_IDLE_STATE)
+__case_string(MMC_SEND_OP_COND)
+__case_string(MMC_ALL_SEND_CID)
+__case_string(MMC_SET_RELATIVE_ADDR)
+__case_string(MMC_SET_DSR)
+__case_string(MMC_SWITCH)
+__case_string(MMC_SELECT_CARD)
+__case_string(MMC_SEND_EXT_CSD)
+__case_string(MMC_SEND_CSD)
+__case_string(MMC_SEND_CID)
+__case_string(MMC_READ_DAT_UNTIL_STOP)
+__case_string(MMC_STOP_TRANSMISSION)
+__case_string(MMC_SEND_STATUS)
+__case_string(MMC_GO_INACTIVE_STATE)
+__case_string(MMC_SPI_READ_OCR)
+__case_string(MMC_SPI_CRC_ON_OFF)
+__case_string(MMC_SET_BLOCKLEN)
+__case_string(MMC_READ_SINGLE_BLOCK)
+__case_string(MMC_READ_MULTIPLE_BLOCK)
+__case_string(MMC_WRITE_DAT_UNTIL_STOP)
+__case_string(MMC_SET_BLOCK_COUNT)
+__case_string(MMC_WRITE_BLOCK)
+__case_string(MMC_WRITE_MULTIPLE_BLOCK)
+__case_string(MMC_PROGRAM_CID)
+__case_string(MMC_PROGRAM_CSD)
+__case_string(MMC_SET_WRITE_PROT)
+__case_string(MMC_CLR_WRITE_PROT)
+__case_string(MMC_SEND_WRITE_PROT)
+__case_string(MMC_ERASE_GROUP_START)
+__case_string(MMC_ERASE_GROUP_END)
+__case_string(MMC_ERASE)
+__case_string(MMC_FAST_IO)
+__case_string(MMC_GO_IRQ_STATE)
+__case_string(MMC_LOCK_UNLOCK)
+/*__case_string(SD_SEND_RELATIVE_ADDR)*/
+/*__case_string(SD_SEND_IF_COND)*/
+/*__case_string(SD_SWITCH)*/
+__case_string(SD_IO_SEND_OP_COND)
+__case_string(SD_IO_RW_DIRECT)
+__case_string(SD_IO_RW_EXTENDED)
+	}
+
+	return str;
+}
+
+static char *stsd_rsptype_string(u32 rsptype)
+{
+	char *str = "unknown";
+
+	switch (rsptype) {
+__case_string(STSD_RSPTYPE_NONE)
+__case_string(STSD_RSPTYPE_R1)
+__case_string(STSD_RSPTYPE_R1B)
+__case_string(STSD_RSPTYPE_R2)
+__case_string(STSD_RSPTYPE_R3)
+__case_string(STSD_RSPTYPE_R4)
+__case_string(STSD_RSPTYPE_R5)
+__case_string(STSD_RSPTYPE_R6)
+__case_string(STSD_RSPTYPE_R7)
+	}
+
+	return str;
+}
+
+static char *stsd_cmdtype_string(u32 cmdtype)
+{
+	char *str = "unknown";
+
+	switch (cmdtype) {
+__case_string(STSD_CMDTYPE_BC)
+__case_string(STSD_CMDTYPE_BCR)
+__case_string(STSD_CMDTYPE_AC)
+__case_string(STSD_CMDTYPE_ADTC)
+	}
+
+	return str;
+}
+
+static char *stsd_statusbit_string(u32 statusbit)
+{
+	char *str = "unknown";
+
+	switch (statusbit) {
+__case_string(R1_OUT_OF_RANGE)
+__case_string(R1_ADDRESS_ERROR)
+__case_string(R1_BLOCK_LEN_ERROR)
+__case_string(R1_ERASE_SEQ_ERROR)
+__case_string(R1_ERASE_PARAM)
+__case_string(R1_WP_VIOLATION)
+__case_string(R1_CARD_IS_LOCKED)
+__case_string(R1_LOCK_UNLOCK_FAILED)
+__case_string(R1_COM_CRC_ERROR)
+__case_string(R1_ILLEGAL_COMMAND)
+__case_string(R1_CARD_ECC_FAILED)
+__case_string(R1_CC_ERROR)
+__case_string(R1_ERROR)
+__case_string(R1_UNDERRUN)
+__case_string(R1_OVERRUN)
+__case_string(R1_CID_CSD_OVERWRITE)
+__case_string(R1_WP_ERASE_SKIP)
+__case_string(R1_CARD_ECC_DISABLED)
+__case_string(R1_ERASE_RESET)
+__case_string(R1_READY_FOR_DATA)
+__case_string(R1_APP_CMD)
+	}
+
+	return str;
+}
+
+static char *stsd_card_state_string(u32 status)
+{
+	char *str = "unknown";
+
+	switch (R1_CURRENT_STATE(status)) {
+	case 0:
+		str = "IDLE";
+		break;
+	case 1:
+		str = "READY";
+		break;
+	case 2:
+		str = "IDENT";
+		break;
+	case 3:
+		str = "STANDBY";
+		break;
+	case 4:
+		str = "TRANSFER";
+		break;
+	case 5:
+		str = "SEND";
+		break;
+	case 6:
+		str = "RECEIVE";
+		break;
+	case 7:
+		str = "PROGRAM";
+		break;
+	case 8:
+		str = "DISCONNECT";
+		break;
+	}
+
+	return str;
+}
+static void stsd_print_status(u32 status)
+{
+	u32 i, bit;
+
+	drv_printk(KERN_INFO, "card state %s\n",
+		   stsd_card_state_string(status));
+
+	i = 13;
+	for (i = 13; i <= 31; i++) {
+		bit = 1 << i;
+		if ((status & bit))
+			drv_printk(KERN_INFO, "%02d %s\n", i,
+				   stsd_statusbit_string(bit));
+	}
+	bit = 1 << 8;
+	if ((status & bit))
+		drv_printk(KERN_INFO, "%02d %s\n", 8,
+			   stsd_statusbit_string(bit));
+	bit = 1 << 5;
+	if ((status & bit))
+		drv_printk(KERN_INFO, "%02d %s\n", 5,
+			   stsd_statusbit_string(bit));
+}
+
+static void stsd_print_cid(struct mmc_cid *cid)
+{
+	drv_printk(KERN_INFO,
+		  "manfid = %d\n"
+		  "oemid = %d\n"
+		  "prod_name = %s\n"
+		  "hwrev = %d\n"
+		  "fwrev = %d\n"
+		  "serial = %08x\n"
+		  "year = %d\n"
+		  "month = %d\n",
+		  cid->manfid,
+		  cid->oemid,
+		  cid->prod_name,
+		  cid->hwrev, cid->fwrev, cid->serial, cid->year, cid->month);
+}
+
+static void stsd_print_csd(struct mmc_csd *csd)
+{
+	drv_printk(KERN_INFO,
+		  "mmca_vsn = %d\n"
+		  "cmdclass = %d\n"
+		  "tacc_clks = %d\n"
+		  "tacc_ns = %d\n"
+		  "r2w_factor = %d\n"
+		  "max_dtr = %d\n"
+		  "read_blkbits = %d\n"
+		  "write_blkbits = %d\n"
+		  "capacity = %d\n"
+		  "read_partial = %d\n"
+		  "read_misalign = %d\n"
+		  "write_partial = %d\n"
+		  "write_misalign = %d\n",
+		  csd->mmca_vsn,
+		  csd->cmdclass,
+		  csd->tacc_clks,
+		  csd->tacc_ns,
+		  csd->r2w_factor,
+		  csd->max_dtr,
+		  csd->read_blkbits,
+		  csd->write_blkbits,
+		  csd->capacity,
+		  csd->read_partial,
+		  csd->read_misalign,
+		  csd->write_partial,
+		  csd->write_misalign);
+}
+
+static void stsd_dump_hs_regs(struct stsd_host *host)
+{
+	drv_printk(KERN_DEBUG, "============== REGISTER DUMP ==============\n");
+
+	drv_printk(KERN_DEBUG, "Sys addr: 0x%08x | Version:  0x%08x\n",
+		   stsd_hsr_in_u32(host, SDHCI_DMA_ADDRESS),
+		   stsd_hsr_in_u16(host, SDHCI_HOST_VERSION));
+	drv_printk(KERN_DEBUG, "Blk size: 0x%08x | Blk cnt:  0x%08x\n",
+		   stsd_hsr_in_u16(host, SDHCI_BLOCK_SIZE),
+		   stsd_hsr_in_u16(host, SDHCI_BLOCK_COUNT));
+	drv_printk(KERN_DEBUG, "Argument: 0x%08x | Trn mode: 0x%08x\n",
+		   stsd_hsr_in_u32(host, SDHCI_ARGUMENT),
+		   stsd_hsr_in_u16(host, SDHCI_TRANSFER_MODE));
+	drv_printk(KERN_DEBUG, "Present:  0x%08x | Host ctl: 0x%08x\n",
+		   stsd_hsr_in_u32(host, SDHCI_PRESENT_STATE),
+		   stsd_hsr_in_u8(host, SDHCI_HOST_CONTROL));
+	drv_printk(KERN_DEBUG, "Power:    0x%08x | Blk gap:  0x%08x\n",
+		   stsd_hsr_in_u8(host, SDHCI_POWER_CONTROL),
+		   stsd_hsr_in_u8(host, SDHCI_BLOCK_GAP_CONTROL));
+	drv_printk(KERN_DEBUG, "Wake-up:  0x%08x | Clock:    0x%08x\n",
+		   stsd_hsr_in_u8(host, SDHCI_WAKE_UP_CONTROL),
+		   stsd_hsr_in_u16(host, SDHCI_CLOCK_CONTROL));
+	drv_printk(KERN_DEBUG, "Timeout:  0x%08x | Int stat: 0x%08x\n",
+		   stsd_hsr_in_u8(host, SDHCI_TIMEOUT_CONTROL),
+		   stsd_hsr_in_u32(host, SDHCI_INT_STATUS));
+	drv_printk(KERN_DEBUG, "Int enab: 0x%08x | Sig enab: 0x%08x\n",
+		   stsd_hsr_in_u32(host, SDHCI_INT_ENABLE),
+		   stsd_hsr_in_u32(host, SDHCI_SIGNAL_ENABLE));
+	drv_printk(KERN_DEBUG, "AC12 err: 0x%08x | Slot int: 0x%08x\n",
+		   stsd_hsr_in_u16(host, SDHCI_ACMD12_ERR),
+		   stsd_hsr_in_u16(host, SDHCI_SLOT_INT_STATUS));
+	drv_printk(KERN_DEBUG, "Caps:     0x%08x | Max curr: 0x%08x\n",
+		   stsd_hsr_in_u32(host, SDHCI_CAPABILITIES),
+		   stsd_hsr_in_u32(host, SDHCI_MAX_CURRENT));
+
+	drv_printk(KERN_DEBUG, "===========================================\n");
+}
+
+#endif /* DEBUG */
+
+/*
+ *
+ * MMC/SD data structures manipulation.
+ * Borrowed from MMC layer.
+ */
+
+#define UNSTUFF_BITS(resp, start, size)				\
+	({								\
+		const int __size = size;				\
+		const u32 __mask = (__size < 32 ? 1 << __size : 0) - 1;	\
+		const int __off = 3 - ((start) / 32);			\
+		const int __shft = (start) & 31;			\
+		u32 __res;						\
+									\
+		__res = resp[__off] >> __shft;				\
+		if (__size + __shft > 32)				\
+			__res |= resp[__off-1] << ((32 - __shft) % 32);	\
+		__res & __mask;						\
+	})
+
+/*
+ * Given the decoded CSD structure, decode the raw CID to our CID structure.
+ */
+static void mmc_decode_cid(struct mmc_card *card)
+{
+	u32 *resp = card->raw_cid;
+
+	memset(&card->cid, 0, sizeof(struct mmc_cid));
+
+	/*
+	 * SD doesn't currently have a version field so we will
+	 * have to assume we can parse this.
+	 */
+	card->cid.manfid		= UNSTUFF_BITS(resp, 120, 8);
+	card->cid.oemid			= UNSTUFF_BITS(resp, 104, 16);
+	card->cid.prod_name[0]		= UNSTUFF_BITS(resp, 96, 8);
+	card->cid.prod_name[1]		= UNSTUFF_BITS(resp, 88, 8);
+	card->cid.prod_name[2]		= UNSTUFF_BITS(resp, 80, 8);
+	card->cid.prod_name[3]		= UNSTUFF_BITS(resp, 72, 8);
+	card->cid.prod_name[4]		= UNSTUFF_BITS(resp, 64, 8);
+	card->cid.hwrev			= UNSTUFF_BITS(resp, 60, 4);
+	card->cid.fwrev			= UNSTUFF_BITS(resp, 56, 4);
+	card->cid.serial		= UNSTUFF_BITS(resp, 24, 32);
+	card->cid.year			= UNSTUFF_BITS(resp, 12, 8);
+	card->cid.month			= UNSTUFF_BITS(resp, 8, 4);
+
+	card->cid.year += 2000; /* SD cards year offset */
+}
+
+/*
+ * Given a 128-bit response, decode to our card CSD structure.
+ */
+static int mmc_decode_csd(struct mmc_card *card)
+{
+	struct mmc_csd *csd = &card->csd;
+	unsigned int e, m, csd_struct;
+	u32 *resp = card->raw_csd;
+
+	csd_struct = UNSTUFF_BITS(resp, 126, 2);
+
+	switch (csd_struct) {
+	case 0:
+		m = UNSTUFF_BITS(resp, 115, 4);
+		e = UNSTUFF_BITS(resp, 112, 3);
+		csd->tacc_ns	 = (tacc_exp[e] * tacc_mant[m] + 9) / 10;
+		csd->tacc_clks	 = UNSTUFF_BITS(resp, 104, 8) * 100;
+
+		m = UNSTUFF_BITS(resp, 99, 4);
+		e = UNSTUFF_BITS(resp, 96, 3);
+		csd->max_dtr	  = tran_exp[e] * tran_mant[m];
+		csd->cmdclass	  = UNSTUFF_BITS(resp, 84, 12);
+
+		e = UNSTUFF_BITS(resp, 47, 3);
+		m = UNSTUFF_BITS(resp, 62, 12);
+		csd->capacity	  = (1 + m) << (e + 2);
+
+		csd->read_blkbits = UNSTUFF_BITS(resp, 80, 4);
+		csd->read_partial = UNSTUFF_BITS(resp, 79, 1);
+		csd->write_misalign = UNSTUFF_BITS(resp, 78, 1);
+		csd->read_misalign = UNSTUFF_BITS(resp, 77, 1);
+		csd->r2w_factor = UNSTUFF_BITS(resp, 26, 3);
+		csd->write_blkbits = UNSTUFF_BITS(resp, 22, 4);
+		csd->write_partial = UNSTUFF_BITS(resp, 21, 1);
+		break;
+	case 1:
+		/*
+		 * This is a block-addressed SDHC card. Most
+		 * interesting fields are unused and have fixed
+		 * values. To avoid getting tripped by buggy cards,
+		 * we assume those fixed values ourselves.
+		 */
+		mmc_card_set_blockaddr(card);
+
+		csd->tacc_ns	 = 0; /* Unused */
+		csd->tacc_clks	 = 0; /* Unused */
+
+		m = UNSTUFF_BITS(resp, 99, 4);
+		e = UNSTUFF_BITS(resp, 96, 3);
+		csd->max_dtr	  = tran_exp[e] * tran_mant[m];
+		csd->cmdclass	  = UNSTUFF_BITS(resp, 84, 12);
+
+		m = UNSTUFF_BITS(resp, 48, 22);
+		csd->capacity     = (1 + m) << 10;
+
+		csd->read_blkbits = 9;
+		csd->read_partial = 0;
+		csd->write_misalign = 0;
+		csd->read_misalign = 0;
+		csd->r2w_factor = 4; /* Unused */
+		csd->write_blkbits = 9;
+		csd->write_partial = 0;
+		break;
+	default:
+		printk(KERN_ERR "unrecognised CSD structure version %d\n",
+			csd_struct);
+		return -EINVAL;
+	}
+
+	/*stsd_print_csd(csd);*/
+
+	return 0;
+}
+
+/*
+ * REVISIT maybe get rid of this and specify the rsptype directly
+ */
+static u32 stsd_opcode_to_rsptype(u32 opcode)
+{
+	u32 rsptype = STSD_RSPTYPE_R1;
+
+	switch (opcode) {
+	case MMC_GO_IDLE_STATE:
+	case MMC_SET_DSR:
+	case MMC_GO_INACTIVE_STATE:
+		rsptype = STSD_RSPTYPE_NONE;
+		break;
+	case MMC_SWITCH:
+	case MMC_STOP_TRANSMISSION:
+	case MMC_SET_WRITE_PROT:
+	case MMC_CLR_WRITE_PROT:
+	case MMC_ERASE:
+	case MMC_LOCK_UNLOCK:
+		rsptype = STSD_RSPTYPE_R1B;
+		break;
+	case MMC_ALL_SEND_CID:
+	case MMC_SEND_CSD:
+	case MMC_SEND_CID:
+		rsptype = STSD_RSPTYPE_R2;
+		break;
+	case MMC_SEND_OP_COND:
+	case SD_APP_OP_COND:
+		rsptype = STSD_RSPTYPE_R3;
+		break;
+	case MMC_FAST_IO:
+	case SD_IO_SEND_OP_COND:
+		rsptype = STSD_RSPTYPE_R4;
+		break;
+	case MMC_GO_IRQ_STATE:
+	case SD_IO_RW_DIRECT:
+	case SD_IO_RW_EXTENDED:
+		rsptype = STSD_RSPTYPE_R5;
+		break;
+	case SD_SEND_RELATIVE_ADDR:
+		rsptype = STSD_RSPTYPE_R6;
+		break;
+	case SD_SEND_IF_COND:
+		/* WEIRD */
+		/*rsptype = STSD_RSPTYPE_R7;*/
+		rsptype = STSD_RSPTYPE_R6;
+		break;
+	default:
+		break;
+	}
+
+	return rsptype;
+}
+
+static inline void stsd_card_set_bad(struct stsd_host *host)
+{
+	set_bit(__STSD_BAD_CARD, &host->flags);
+}
+
+static inline void stsd_card_unset_bad(struct stsd_host *host)
+{
+	clear_bit(__STSD_BAD_CARD, &host->flags);
+}
+
+static inline int stsd_card_is_bad(struct stsd_host *host)
+{
+	return test_bit(__STSD_BAD_CARD, &host->flags);
+}
+
+static inline void stsd_card_set_sdhc(struct stsd_host *host)
+{
+	set_bit(__STSD_SDHC, &host->flags);
+}
+
+static inline void stsd_card_unset_sdhc(struct stsd_host *host)
+{
+	clear_bit(__STSD_SDHC, &host->flags);
+}
+
+static inline int stsd_card_is_sdhc(struct stsd_host *host)
+{
+	return test_bit(__STSD_SDHC, &host->flags);
+}
+
+static inline void stsd_card_set_manual_setup(struct stsd_host *host)
+{
+	set_bit(__STSD_MANUAL_SETUP, &host->flags);
+}
+
+static inline void stsd_card_unset_manual_setup(struct stsd_host *host)
+{
+	clear_bit(__STSD_MANUAL_SETUP, &host->flags);
+}
+
+static inline int stsd_card_needs_manual_setup(struct stsd_host *host)
+{
+	return test_bit(__STSD_MANUAL_SETUP, &host->flags);
+}
+
+static inline int stsd_card_status_is_inserted(u32 status)
+{
+	return (status & STSD_STATUS_CARD_INSERTED)
+			 == STSD_STATUS_CARD_INSERTED;
+}
+
+static inline int stsd_card_status_is_initialized(u32 status)
+{
+	return (status & STSD_STATUS_CARD_INITIALIZED)
+			 == STSD_STATUS_CARD_INITIALIZED;
+}
+
+/*
+ * Hardware.
+ *
+ */
+
+/*
+ * Handy small buffer routines.
+ * We use a small static aligned buffer to avoid allocations for short-lived
+ * operations involving 1 to 4 byte data transfers to/from IOS.
+ *
+ */
+
+static u32 stsd_small_buf[L1_CACHE_BYTES / sizeof(u32)]
+		    __attribute__ ((aligned(STARLET_IPC_DMA_ALIGN + 1)));
+static const size_t stsd_small_buf_size = sizeof(stsd_small_buf_size);
+static DEFINE_MUTEX(stsd_small_buf_lock);
+
+static u32 *stsd_small_buf_get(void)
+{
+	u32 *buf;
+
+	if (!mutex_trylock(&stsd_small_buf_lock))
+		buf = starlet_kzalloc(stsd_small_buf_size, GFP_NOIO);
+	else {
+		memset(stsd_small_buf, 0, stsd_small_buf_size);
+		buf = stsd_small_buf;
+	}
+
+	return buf;
+}
+
+void stsd_small_buf_put(u32 *buf)
+{
+	if (buf == stsd_small_buf)
+		mutex_unlock(&stsd_small_buf_lock);
+	else
+		starlet_kfree(buf);
+}
+
+
+/*
+ * SD Host Standard Registers accessors.
+ *
+ */
+
+/*
+ * @data must be aligned
+ * @size must be between 1 and 4
+ */
+static int __stsd_hsr_in(struct stsd_host *host,
+			 u32 addr, u32 *data, size_t size)
+{
+	struct stsd_reg_query *query;
+	int error;
+
+	query = starlet_kzalloc(sizeof(*query), GFP_ATOMIC);
+	if (!query)
+		return -ENOMEM;
+
+	query->addr = addr;
+	query->size = size;
+
+	error = starlet_ioctl(host->fd, STSD_IOCTL_GETHSR,
+				  query, sizeof(*query), data, sizeof(*data));
+
+	starlet_kfree(query);
+
+	if (error)
+		DBG("%s: error=%d (%08x)\n", __func__, error, error);
+
+	return error;
+}
+
+static int __stsd_hsr_out(struct stsd_host *host,
+			   u32 addr, u32 *data, size_t size)
+{
+	struct stsd_reg_query *query;
+	int error;
+
+	query = starlet_kzalloc(sizeof(*query), GFP_ATOMIC);
+	if (!query)
+		return -ENOMEM;
+
+	query->addr = addr;
+	query->size = size;
+	query->data = *data;
+
+	error = starlet_ioctl(host->fd, STSD_IOCTL_SETHSR,
+				  query, sizeof(*query), NULL, 0);
+
+	starlet_kfree(query);
+
+	if (error)
+		DBG("%s: error=%d (%08x)\n", __func__, error, error);
+
+	return error;
+}
+
+
+static int stsd_hsr_in(struct stsd_host *host,
+		       u32 reg, void *buf, size_t size)
+{
+	u32 *local_buf;
+	int error;
+
+	/* we do 8, 16 and 32 bits reads */
+	if (size > 4)
+		return -EINVAL;
+
+	local_buf = stsd_small_buf_get();
+	if (!local_buf)
+		return -ENOMEM;
+
+	error = __stsd_hsr_in(host, reg, local_buf, size);
+	if (!error) {
+		switch (size) {
+		case 1:
+			*(u8 *)buf = *local_buf & 0xff;
+			break;
+		case 2:
+			*(u16 *)buf = *local_buf & 0xffff;
+			break;
+		case 4:
+			*(u32 *)buf = *local_buf;
+			break;
+		default:
+			BUG();
+			break;
+		}
+	}
+
+	stsd_small_buf_put(local_buf);
+
+	return error;
+}
+
+static int stsd_hsr_out(struct stsd_host *host,
+			u32 reg, void *buf, size_t size)
+{
+	u32 *local_buf;
+	int error;
+
+	/* we do 8, 16 and 32 bits reads */
+	if (size > 4)
+		return -EINVAL;
+
+	local_buf = stsd_small_buf_get();
+	if (!local_buf)
+		return -ENOMEM;
+
+	switch (size) {
+	case 1:
+		*local_buf = *(u8 *)buf;
+		break;
+	case 2:
+		*local_buf = *(u16 *)buf;
+		break;
+	case 4:
+		*local_buf = *(u32 *)buf;
+		break;
+	default:
+		BUG();
+		break;
+	}
+	error = __stsd_hsr_out(host, reg, local_buf, size);
+
+	stsd_small_buf_put(local_buf);
+
+	return error;
+}
+
+#define __declare_stsd_hsr_wait_for_resp(_type)				\
+static int stsd_hsr_wait_for_resp_##_type(struct stsd_host *host,	\
+				  u32 reg, _type resp, _type resp_mask,	\
+				  unsigned long jiffies)		\
+{									\
+	_type val;							\
+	int error;							\
+									\
+	unsigned long cycles = 10;					\
+	while (cycles-- > 0) {						\
+		error = stsd_hsr_in(host, reg, &val, sizeof(val));	\
+		if (error)						\
+			return error;					\
+		if ((val & resp_mask) == resp)				\
+			return 0;					\
+		mdelay(10);						\
+	}								\
+	return -ENODATA;						\
+}
+
+__declare_stsd_hsr_wait_for_resp(u8);
+__declare_stsd_hsr_wait_for_resp(u16);
+
+#define __declare_stsd_hsr_in(_type)					\
+static inline _type stsd_hsr_in_##_type(struct stsd_host *host, u32 reg) \
+{									\
+	_type val;							\
+									\
+	stsd_hsr_in(host, reg, &val, sizeof(val));			\
+	return val;							\
+}
+
+__declare_stsd_hsr_in(u8);
+__declare_stsd_hsr_in(u16);
+__declare_stsd_hsr_in(u32);
+
+#define __declare_stsd_hsr_out(_type)					\
+static inline void stsd_hsr_out_##_type(struct stsd_host *host, u32 reg,\
+					 _type val)			\
+{									\
+	stsd_hsr_out(host, reg, &val, sizeof(val));			\
+}
+
+__declare_stsd_hsr_out(u8);
+__declare_stsd_hsr_out(u16);
+__declare_stsd_hsr_out(u32);
+
+
+
+/*
+ * Ioctl helpers.
+ *
+ */
+
+static int stsd_ioctl_small_read(struct stsd_host *host, int request,
+				 void *buf, size_t size)
+{
+	void *local_buf;
+	int error;
+
+	/* we do 8, 16 and 32 bits reads */
+	if (size > stsd_small_buf_size) {
+		error = -EINVAL;
+		goto done;
+	}
+
+	local_buf = stsd_small_buf_get();
+	if (!local_buf) {
+		error = -ENOMEM;
+		goto done;
+	}
+
+	error = starlet_ioctl(host->fd, request,
+				   NULL, 0, local_buf, size);
+	if (!error)
+		memcpy(buf, local_buf, size);
+
+	stsd_small_buf_put(local_buf);
+
+done:
+	if (error)
+		DBG("%s: error=%d (%08x)\n", __func__, error, error);
+	return error;
+}
+
+static int stsd_ioctl_small_write(struct stsd_host *host, int request,
+				  void *buf, size_t size)
+{
+	void *local_buf;
+	int error;
+
+	/* we do 8, 16 and 32 bits writes */
+	if (size > stsd_small_buf_size) {
+		error = -EINVAL;
+		goto done;
+	}
+
+	local_buf = stsd_small_buf_get();
+	if (!local_buf) {
+		error = -ENOMEM;
+		goto done;
+	}
+
+	memcpy(local_buf, buf, size);
+	error = starlet_ioctl(host->fd, request,
+				   local_buf, size, NULL, 0);
+
+	stsd_small_buf_put(local_buf);
+
+done:
+	if (error)
+		DBG("%s: error=%d (%08x)\n", __func__, error, error);
+	return error;
+}
+
+
+/*
+ * Hardware interfaces.
+ *
+ */
+
+static int stsd_get_status(struct stsd_host *host, u32 *status)
+{
+	int error;
+
+	error = stsd_ioctl_small_read(host, STSD_IOCTL_GETSTATUS,
+				       status, sizeof(*status));
+	if (error)
+		DBG("%s: error=%d (%08x)\n", __func__, error, error);
+
+	return error;
+}
+
+static void stsd_set_bus_width(struct stsd_host *host, int width)
+{
+	u8 hcr;
+
+	hcr = stsd_hsr_in_u8(host, SDHCI_HOST_CONTROL);
+	if (width == 4) {
+		hcr |= SDHCI_CTRL_4BITBUS;
+	} else {
+		hcr &= ~SDHCI_CTRL_4BITBUS;
+		width = 1;
+	}
+	stsd_hsr_out_u8(host, SDHCI_HOST_CONTROL, hcr);
+	host->bus_width = width;
+}
+
+static int stsd_set_clock(struct stsd_host *host, unsigned int clock)
+{
+	int error;
+	u32 divisor;
+
+	for (divisor = 1; divisor <= 32; divisor <<= 1) {
+		if (host->f_max / divisor <= clock)
+			break;
+	}
+
+	error = stsd_ioctl_small_write(host, STSD_IOCTL_SETCLOCK,
+					&divisor, sizeof(divisor));
+	if (error)
+		DBG("%s: error=%d (%08x)\n", __func__, error, error);
+	else
+		host->clock = clock;
+
+	return error;
+}
+
+static int stsd_reset_card(struct stsd_host *host)
+{
+	struct mmc_card *card = &host->card;
+	int error;
+	u32 status;
+
+	stsd_card_unset_bad(host);
+	stsd_card_unset_sdhc(host);
+	stsd_card_unset_manual_setup(host);
+
+	memset(&card->cid, 0, sizeof(struct mmc_cid));
+	memset(&card->csd, 0, sizeof(struct mmc_csd));
+	host->card.rca = 0;
+
+	error = stsd_ioctl_small_read(host, STSD_IOCTL_RESET,
+				       &status, sizeof(status));
+	if (error) {
+		if (error != STSD_ERR_INVALID_CARD)
+			DBG("%s: error=%d (%08x)\n", __func__, error, error);
+	} else {
+		host->card.rca = status >> 16;
+		host->status = status & 0xffff;
+	}
+
+	return error;
+}
+
+#if 0
+static int stsd_get_ocr(struct stsd_host *host)
+{
+	int error;
+	u32 ocr;
+
+	error = stsd_ioctl_small_read(host, STSD_IOCTL_GETOCR,
+				       &ocr, sizeof(ocr));
+	if (error)
+		DBG("%s: error=%d (%08x)\n", __func__, error, error);
+	else
+		host->ocr = ocr;
+
+	return error;
+}
+#endif
+
+/*
+ * Command engine.
+ *
+ */
+
+static int stsd_send_command(struct stsd_host *host,
+			     u32 opcode, u32 type, u32 arg,
+			     void *buf, size_t buf_len)
+{
+	struct scatterlist in[2], io[1];
+	struct stsd_command *cmd;
+	u32 *reply;
+	size_t reply_len;
+	int error;
+
+	reply_len = 4 * sizeof(u32);
+	if (buf_len > reply_len)
+		return -EINVAL;
+
+	cmd = starlet_kzalloc(sizeof(*cmd), GFP_NOIO);
+	if (!cmd)
+		return -ENOMEM;
+
+	reply = starlet_kzalloc(reply_len, GFP_NOIO);
+	if (!reply) {
+		starlet_kfree(cmd);
+		return -ENOMEM;
+	}
+
+	cmd->opcode = opcode;
+	cmd->arg = arg;
+
+	cmd->cmdtype = type;
+	cmd->rsptype = stsd_opcode_to_rsptype(opcode);
+	if (opcode == MMC_SELECT_CARD && arg == 0)
+		cmd->rsptype = STSD_RSPTYPE_NONE;
+
+	if (stsd_card_needs_manual_setup(host)) {
+		/*
+		 * We need to use ioctlvs, instead of ioctls, to drive
+		 * manually initialized cards.
+		 * This makes IOS "cooperative" :)
+		 */
+		sg_init_table(in, 2);
+		sg_set_buf(&in[0], cmd, sizeof(*cmd));
+		sg_set_buf(&in[1], reply, 0);
+
+		sg_init_table(io, 1);
+		sg_set_buf(&io[0], reply, reply_len);
+
+		error = starlet_ioctlv(host->fd, STSD_IOCTL_SENDCMD,
+					   2, in, 1, io);
+	} else {
+		error = starlet_ioctl(host->fd, STSD_IOCTL_SENDCMD,
+				      cmd, sizeof(*cmd), reply, reply_len);
+	}
+
+	if (error) {
+		DBG("%s: error=%d (%08x), opcode=%d\n", __func__,
+		    error, error, opcode);
+	} else {
+		if (buf)
+			memcpy(buf, reply, buf_len);
+	}
+
+	starlet_kfree(reply);
+	starlet_kfree(cmd);
+
+	return error;
+}
+
+static int stsd_send_app_command(struct stsd_host *host,
+				 u32 opcode, u32 type, u32 arg,
+				 void *buf, size_t buf_len)
+{
+	int error;
+
+	error = stsd_send_command(host, MMC_APP_CMD, STSD_CMDTYPE_AC,
+				  host->card.rca << 16, NULL, 0);
+	if (!error) {
+		error = stsd_send_command(host, opcode, type, arg,
+					  buf, buf_len);
+	}
+	return error;
+}
+
+
+/*
+ * Command helpers.
+ *
+ */
+
+
+static int stsd_cmd_read_cxd(struct stsd_host *host, int request, void *buf)
+{
+	int error;
+	u32 *q, savedq;
+	u8 *p, crc;
+	const size_t size = 128/8*sizeof(u8);
+
+	error = stsd_send_command(host, request, STSD_CMDTYPE_AC,
+				   host->card.rca << 16, buf, size);
+
+	if (!error) {
+		/*
+		 * WEIRD,
+		 * starlet sends CSD and CID contents in a very special way.
+		 *
+		 * If the 128 bit register value is:
+		 *   0123456789abcdef
+		 * starlet will send it as:
+		 *   bcde789a3456f012
+		 * with byte f (the crc field) zeroed.
+		 */
+
+		/* bcde789a3456f012 -> f0123456789abcde */
+		q = buf;
+		savedq = q[0];
+		q[0] = q[3];
+		q[3] = savedq;
+		savedq = q[1];
+		q[1] = q[2];
+		q[2] = savedq;
+
+		/* f0123456789abcde -> 0123456789abcdef */
+		p = buf;
+		crc = p[0];
+		memcpy(p, p+1, size-1);
+		p[size-1] = crc;
+	}
+	return error;
+}
+
+static int stsd_cmd_read_csd(struct stsd_host *host)
+{
+	return stsd_cmd_read_cxd(host, MMC_SEND_CSD, host->card.raw_csd);
+}
+
+static int stsd_cmd_read_cid(struct stsd_host *host)
+{
+	return stsd_cmd_read_cxd(host, MMC_SEND_CID, host->card.raw_cid);
+}
+
+static int stsd_cmd_all_send_cid(struct stsd_host *host)
+{
+	const size_t size = 128/8*sizeof(u8);
+
+	/* WEIRD, don't use CMDTYPE_BCR for MMC_ALL_SEND_CID */
+	return stsd_send_command(host, MMC_ALL_SEND_CID, 0,
+				 host->card.rca << 16,
+				 host->card.raw_cid, size);
+}
+
+static int stsd_cmd_set_relative_addr(struct stsd_host *host, unsigned int rca)
+{
+	int error;
+	u32 reply;
+
+	error = stsd_send_command(host, MMC_SET_RELATIVE_ADDR, STSD_CMDTYPE_AC,
+				  rca, &reply, sizeof(reply));
+	if (!error) {
+		host->card.rca = reply >> 16;
+		/* DBG("rca=%d, new_rca=%x\n", rca, host->card.rca); */
+	}
+	return error;
+}
+
+
+static int stsd_cmd_select_card(struct stsd_host *host)
+{
+	return stsd_send_command(host, MMC_SELECT_CARD, STSD_CMDTYPE_AC,
+				 host->card.rca << 16,
+				 NULL, 0);
+}
+
+static int stsd_cmd_deselect_card(struct stsd_host *host)
+{
+	return stsd_send_command(host, MMC_SELECT_CARD, STSD_CMDTYPE_AC,
+				 0,
+				 NULL, 0);
+}
+
+static int stsd_cmd_set_block_len(struct stsd_host *host, unsigned int len)
+{
+	return stsd_send_command(host, MMC_SET_BLOCKLEN, STSD_CMDTYPE_AC,
+				 len,
+				 NULL, 0);
+}
+
+static int stsd_app_cmd_set_bus_width(struct stsd_host *host, int width)
+{
+	int error;
+	u16 val;
+
+	if (width == 4)
+		val = SD_BUS_WIDTH_4;
+	else
+		val = SD_BUS_WIDTH_1;
+
+	error = stsd_send_app_command(host, SD_APP_SET_BUS_WIDTH,
+				      STSD_CMDTYPE_AC,
+				      val, NULL, 0);
+	if (error)
+		DBG("%s: error=%d (%08x)\n", __func__, error, error);
+
+	return error;
+}
+
+
+
+static int stsd_setup_host_controller(struct stsd_host *host)
+{
+	const u32 mask = SDHCI_INT_RESPONSE | SDHCI_INT_DATA_END |
+				SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE |
+				SDHCI_INT_TIMEOUT | SDHCI_INT_CRC |
+				SDHCI_INT_END_BIT | SDHCI_INT_INDEX |
+				SDHCI_INT_DATA_TIMEOUT | SDHCI_INT_DATA_CRC |
+				SDHCI_INT_ACMD12ERR;
+	u8 rst, pwr, clk_idx;
+	int error;
+
+	/*
+	 * Reset host controller.
+	 */
+
+	/* write 1 to the Reset All bit in the Software Reset register ... */
+	rst = SDHCI_RESET_ALL;
+	stsd_hsr_out_u8(host, SDHCI_SOFTWARE_RESET, rst);
+
+	/* ... then wait for the Reset All bit to be cleared */
+	error = stsd_hsr_wait_for_resp_u8(host, SDHCI_SOFTWARE_RESET,
+					  0, rst,
+					  100*(HZ/1000));
+	if (error) {
+		drv_printk(KERN_ERR, "host controller didn't get out of"
+			   " reset\n");
+		goto done;
+	}
+
+	/*
+	 * Setup interrupt sources.
+	 */
+
+	/* ack the interrupt sources that IOS uses ... */
+	stsd_hsr_out_u32(host, SDHCI_INT_ENABLE, mask);
+	stsd_hsr_in_u32(host, SDHCI_INT_ENABLE);
+	/* ... then unmask them */
+	stsd_hsr_out_u32(host, SDHCI_SIGNAL_ENABLE, mask);
+	stsd_hsr_in_u32(host, SDHCI_SIGNAL_ENABLE);
+
+	/*
+	 * Setup bus power.
+	 */
+
+	/* FIXME, we should use capabilities register here */
+	/* for now use 3.3V setting */
+	pwr = SDHCI_POWER_330;
+
+	/* turn on bus power and use selected voltage setting */
+	stsd_hsr_out_u8(host, SDHCI_POWER_CONTROL, pwr & ~SDHCI_POWER_ON);
+	stsd_hsr_out_u8(host, SDHCI_POWER_CONTROL, pwr | SDHCI_POWER_ON);
+
+	/*
+	 * Initialize clocks.
+	 */
+
+	/* FIXME, we should use capabilities register here */
+	/* for now use index 01h which is base clock divided by 2 */
+	clk_idx = 1;
+
+	/* disable clock signalling... */
+	stsd_hsr_out_u16(host, SDHCI_CLOCK_CONTROL, 0);
+	/* ... then enable internal clock ... */
+	stsd_hsr_out_u16(host, SDHCI_CLOCK_CONTROL,
+				 SDHCI_CLOCK_INT_EN |
+				 (clk_idx << SDHCI_DIVIDER_SHIFT));
+	/* ... and wait until it gets stable */
+	error = stsd_hsr_wait_for_resp_u16(host, SDHCI_CLOCK_CONTROL,
+					   SDHCI_CLOCK_INT_STABLE,
+					   SDHCI_CLOCK_INT_STABLE,
+					   1*HZ);
+	if (error) {
+		drv_printk(KERN_ERR, "internal clock didn't get stable\n");
+		goto done;
+	}
+
+	/* SD clock can be enabled now */
+	stsd_hsr_out_u16(host, SDHCI_CLOCK_CONTROL,
+				 SDHCI_CLOCK_INT_EN |
+				 SDHCI_CLOCK_CARD_EN |
+				 (1 << SDHCI_DIVIDER_SHIFT));
+
+	/*
+	 * Setup timeout.
+	 */
+
+	/* setup timeout to TMCLK * 2^27 */
+	stsd_hsr_out_u8(host, SDHCI_TIMEOUT_CONTROL,
+				STSD_TIMEOUT_CONTROL_DIV(27));
+
+done:
+	if (error)
+		DBG("%s: error=%d (%08x)\n", __func__, error, error);
+
+	return error;
+}
+
+static int stsd_setup_card(struct stsd_host *host)
+{
+	const u8 check_pattern = 0xaa;
+	u32 arg;
+	u32 resp[4];
+	int i;
+	int error;
+
+	/* WEIRD, don't use CMDTYPE_BC for MMC_GO_IDLE_STATE */
+	error = stsd_send_command(host, MMC_GO_IDLE_STATE, 0,
+				  0, NULL, 0);
+	if (error)
+		goto done;
+
+#define STSD_VHS(a)	((((a)&0x0f)<<8))
+#define STSD_VHS_27_36	STSD_VHS(0x1)
+
+	/* WEIRD, don't use CMDTYPE_BC for SD_SEND_IF_COND */
+	arg = STSD_VHS_27_36 | check_pattern;
+	error = stsd_send_command(host, SD_SEND_IF_COND, 0,
+				  arg, &resp, sizeof(resp));
+	if (error)
+		goto done;
+
+	if ((resp[0] & 0xff) != check_pattern) {
+		DBG("arg=0x%x, resp[0]=0x%x\n", arg, resp[0]);
+		error = -ENODEV;
+		goto done;
+	}
+
+	/*
+	 * At this point we have identified a v2.00 SD Memory Card.
+	 *
+	 */
+
+	/*
+	 * Get OCR
+	 */
+
+#define STSD_OCR_HCS	(1<<30)	/* Host Capacity Support */
+#define STSD_OCR_CCS	(1<<30)	/* Card Capacity Support */
+
+	for (i = 0; i < 100; i++) {
+		/* WEIRD, don't use CMDTYPE_BCR for MMC_APP_CMD */
+		error = stsd_send_command(host, MMC_APP_CMD, STSD_CMDTYPE_AC,
+					  0, NULL, 0);
+		if (error)
+			goto done;
+
+		/* WEIRD, don't use CMDTYPE_BCR for SD_APP_OP_COND */
+		error = stsd_send_command(host, SD_APP_OP_COND, 0,
+					  STSD_OCR_HCS|
+					  MMC_VDD_32_33|MMC_VDD_33_34,
+					  &resp, sizeof(resp));
+		if (error)
+			goto done;
+
+		if ((resp[0] & MMC_CARD_BUSY) != 0) {
+			/* card power up completed */
+			break;
+		}
+
+		error = -ETIMEDOUT;
+		mdelay(10);
+	}
+	if (error) {
+		drv_printk(KERN_ERR, "timed out while trying to get OCR\n");
+		goto done;
+	}
+
+	if ((resp[0] & STSD_OCR_CCS) != 0) {
+		/* high capacity card */
+		stsd_card_set_sdhc(host);
+	}
+
+	error = stsd_cmd_all_send_cid(host);
+	if (error)
+		goto done;
+
+	error = stsd_cmd_set_relative_addr(host, 0);
+	if (error)
+		goto done;
+
+done:
+	if (error)
+		DBG("%s: error=%d (%08x)\n", __func__, error, error);
+	return error;
+}
+
+static int stsd_reopen_sdio(struct stsd_host *host)
+{
+	int error = 0;
+
+	starlet_close(host->fd);
+	host->fd = starlet_open(stsd_dev_sdio_slot0, 1);
+	if (host->fd < 0) {
+		drv_printk(KERN_ERR, "unable to re-open %s\n",
+			   stsd_dev_sdio_slot0);
+		error = -ENODEV;
+	}
+	return error;
+}
+
+
+static int stsd_welcome_card(struct stsd_host *host)
+{
+	size_t block_len; /* in bytes */
+	u32 status;
+	int error;
+
+	mutex_lock(&host->io_mutex);
+
+	/*
+	 * Re-open the sdio device if things look wrong.
+	 */
+	error = stsd_get_status(host, &status);
+	if (error == STARLET_EINVAL) {
+		error = stsd_reopen_sdio(host);
+		if (error)
+			goto err_bad_card;
+	}
+
+	/*
+	 * Try a normal initialization sequence first, and revert to
+	 * manual mode if that fails.
+	 */
+
+	stsd_reset_card(host);
+
+	error = stsd_get_status(host, &status);
+	if (error)
+		goto err_bad_card;
+	if (!stsd_card_status_is_inserted(status)) {
+		drv_printk(KERN_ERR, "no card found\n");
+		goto err_bad_card;
+	}
+
+	if (!stsd_card_status_is_initialized(status)) {
+		/* manual initialization, needed for SDHC support */
+		stsd_card_set_manual_setup(host);
+
+		error = stsd_reopen_sdio(host);
+		if (error)
+			goto err_bad_card;
+
+		error = stsd_setup_host_controller(host);
+		if (error)
+			goto err_bad_card;
+
+		error = stsd_setup_card(host);
+		if (error)
+			goto err_bad_card;
+	}
+
+#if 0
+	/* read Operating Conditions Register */
+	error = stsd_get_ocr(host);
+	if (error < 0)
+		goto err_bad_card;
+#endif
+
+	error = stsd_cmd_deselect_card(host);
+	if (error)
+		goto err_bad_card;
+
+	/* read and decode the Card Specific Data */
+	error = stsd_cmd_read_csd(host);
+	if (error)
+		goto err_bad_card;
+	mmc_decode_csd(&host->card);
+
+	/* read and decode the Card Identification Data */
+	error = stsd_cmd_read_cid(host);
+	if (error)
+		goto err_bad_card;
+	mmc_decode_cid(&host->card);
+
+	error = stsd_cmd_select_card(host);
+	if (error)
+		goto err_bad_card;
+
+	stsd_set_clock(host, host->card.csd.max_dtr);
+
+	/* FIXME check if card supports 4 bit bus width */
+	stsd_set_bus_width(host, 4);
+	error = stsd_app_cmd_set_bus_width(host, 4);
+	if (error)
+		goto err_bad_card;
+
+	/* setup block length */
+	block_len = KERNEL_SECTOR_SIZE;
+	error = stsd_cmd_set_block_len(host, block_len);
+	if (error)
+		goto err_bad_card;
+
+#if 0
+	mmc_card_set_present(&host->card);
+#endif
+
+	mutex_unlock(&host->io_mutex);
+
+	drv_printk(KERN_INFO, "descr \"%s\", size %luk, block %ub,"
+		  " serial %08x\n",
+		  host->card.cid.prod_name,
+		  (unsigned long)((host->card.csd.capacity / 1024) *
+			  (1 << host->card.csd.read_blkbits)),
+		  1 << host->card.csd.read_blkbits,
+		  host->card.cid.serial);
+
+	error = 0;
+	goto out;
+
+err_bad_card:
+	mutex_unlock(&host->io_mutex);
+	stsd_card_set_bad(host);
+out:
+	return error;
+}
+
+
+/*
+ * Block layer helper routines.
+ *
+ */
+
+static int stsd_do_block_transfer(struct stsd_host *host, int write,
+				  unsigned long start,
+				  void *buf, size_t nr_blocks)
+{
+	struct stsd_xfer *xfer = host->xfer;
+	struct stsd_command *cmd = xfer->cmd;
+	int error;
+
+	xfer->direction = (write) ? DMA_TO_DEVICE : DMA_FROM_DEVICE;
+	xfer->size = nr_blocks * xfer->blk_size;
+
+	if (xfer->size > xfer->bounce_buf_size) {
+		drv_printk(KERN_ERR, "oops, request size %d > %d\n",
+				xfer->size, xfer->bounce_buf_size);
+		return -ENOMEM;
+	}
+
+	/*
+	 * This is stupid.
+	 * Starlet expects the buffer to be an input iovec (from starlet
+	 * point of view) even for reads. Thus, map the buffer explicitly here.
+	 */
+	if (write)
+		memcpy(xfer->bounce_buf, buf, xfer->size);
+	__dma_sync(xfer->bounce_buf, xfer->size, xfer->direction);
+/*
+	xfer->dma_addr = dma_map_single(host->dev, buf,
+					xfer->size, xfer->direction);
+*/
+
+	starlet_ioh_sg_init_table(xfer->in, 2);
+	starlet_ioh_sg_set_buf(&xfer->in[0], cmd, sizeof(*cmd));
+	starlet_ioh_sg_set_buf(&xfer->in[1], xfer->bounce_buf, xfer->size);
+
+	starlet_ioh_sg_init_table(xfer->io, 1);
+	starlet_ioh_sg_set_buf(&xfer->io[0], xfer->reply, xfer->reply_len);
+
+	cmd->opcode = (write) ? MMC_WRITE_MULTIPLE_BLOCK :
+				MMC_READ_MULTIPLE_BLOCK;
+	cmd->arg = start;
+	cmd->cmdtype = STSD_CMDTYPE_AC; /* STSD_CMDTYPE_ADTC */
+	cmd->rsptype = stsd_opcode_to_rsptype(cmd->opcode);
+	cmd->blk_count = nr_blocks;
+	cmd->blk_size = xfer->blk_size;
+	cmd->dma_addr = xfer->dma_addr; /* bounce buf */
+	cmd->is_dma = 1;
+
+	error = starlet_ioh_ioctlv(host->fd, STSD_IOCTLV_SENDCMD,
+				   2, xfer->in, 1, xfer->io);
+/*
+	dma_unmap_single(host->dev,
+			 xfer->dma_addr, xfer->size, xfer->direction);
+*/
+
+	if (!write)
+		memcpy(buf, xfer->bounce_buf, xfer->size);
+
+	if (error)
+		DBG("%s: error=%d (%08x)\n", __func__, error, error);
+
+	return error;
+}
+
+static int stsd_check_request(struct stsd_host *host, struct request *req)
+{
+	unsigned long nr_sectors;
+
+	if (!blk_fs_request(req))
+		return -EIO;
+
+	if (test_bit(__STSD_MEDIA_CHANGED, &host->flags)) {
+		drv_printk(KERN_ERR, "media changed, aborting\n");
+		return -ENOMEDIUM;
+	}
+
+	/* unit is kernel sectors */
+	nr_sectors =
+	    host->card.csd.capacity << (host->card.csd.read_blkbits -
+					KERNEL_SECTOR_SHIFT);
+
+	/* keep our reads within limits */
+	if (blk_rq_pos(req) + blk_rq_cur_sectors(req) > nr_sectors) {
+		drv_printk(KERN_ERR, "reading past end, aborting\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int stsd_do_request(struct stsd_host *host, struct request *req)
+{
+	unsigned long nr_blocks; /* in card blocks */
+	unsigned long start;
+	int write;
+	int error;
+
+	error = stsd_check_request(host, req);
+	if (error)
+		goto out;
+
+	write = (rq_data_dir(req) == READ) ? 0 : 1;
+
+	start = blk_rq_pos(req);
+	if (!stsd_card_is_sdhc(host))
+		start <<= KERNEL_SECTOR_SHIFT;
+	nr_blocks = blk_rq_cur_sectors(req);
+
+	error = stsd_do_block_transfer(host, write,
+					start, req->buffer, nr_blocks);
+	if (error)
+		DBG("%s: error=%d (%08x), start=%lu, \n", __func__,
+		    error, error, start);
+
+out:
+	return error;
+}
+
+static int stsd_io_thread(void *param)
+{
+	struct stsd_host *host = param;
+	struct request *req;
+	unsigned long flags;
+	int error;
+
+	current->flags |= PF_NOFREEZE|PF_MEMALLOC;
+
+	mutex_lock(&host->io_mutex);
+	for (;;) {
+		req = NULL;
+		set_current_state(TASK_INTERRUPTIBLE);
+
+		spin_lock_irqsave(&host->queue_lock, flags);
+		if (!blk_queue_plugged(host->queue))
+			req = blk_fetch_request(host->queue);
+		spin_unlock_irqrestore(&host->queue_lock, flags);
+
+		if (!req) {
+			if (kthread_should_stop()) {
+				set_current_state(TASK_RUNNING);
+				break;
+			}
+			mutex_unlock(&host->io_mutex);
+			schedule();
+			mutex_lock(&host->io_mutex);
+			continue;
+		}
+		set_current_state(TASK_INTERRUPTIBLE);
+		error = stsd_do_request(host, req);
+
+		spin_lock_irqsave(&host->queue_lock, flags);
+		__blk_end_request_cur(req, error);
+		spin_unlock_irqrestore(&host->queue_lock, flags);
+	}
+	mutex_unlock(&host->io_mutex);
+
+	return 0;
+}
+
+static void stsd_request_func(struct request_queue *q)
+{
+	struct stsd_host *host = q->queuedata;
+	wake_up_process(host->io_thread);
+}
+
+/*
+ * Block device hooks.
+ *
+ */
+
+static DECLARE_MUTEX(open_lock);
+
+static int stsd_open(struct block_device *bdev, fmode_t mode)
+{
+	struct stsd_host *host = bdev->bd_disk->private_data;
+	int error = 0;
+
+	if (!host || host->fd < 0)
+		return -ENXIO;
+
+	/* honor exclusive open mode */
+	if (host->refcnt == -1 ||
+	    (host->refcnt && (mode & FMODE_EXCL))) {
+		error = -EBUSY;
+		goto out;
+	}
+
+	/* this takes care of revalidating the media if needed */
+	check_disk_change(bdev);
+	if (!host->card.csd.capacity) {
+		error = -ENOMEDIUM;
+		goto out;
+	}
+
+	down(&open_lock);
+
+	if ((mode & FMODE_EXCL))
+		host->refcnt = -1;
+	else
+		host->refcnt++;
+
+	up(&open_lock);
+
+out:
+	return error;
+
+}
+
+static int stsd_release(struct gendisk *disk, fmode_t mode)
+{
+	struct stsd_host *host = disk->private_data;
+
+	if (!host)
+		return -ENXIO;
+
+	down(&open_lock);
+
+	if (host->refcnt > 0)
+		host->refcnt--;
+	else
+		host->refcnt = 0;
+
+	up(&open_lock);
+
+	if (!host->refcnt && host->fd == -1)
+		kfree(host);
+
+	return 0;
+}
+
+static int stsd_media_changed(struct gendisk *disk)
+{
+	struct stsd_host *host = disk->private_data;
+	unsigned int last_serial;
+	int error;
+
+	/* report a media change for zombies */
+	if (!host)
+		return 1;
+
+	/* report a media change if someone forced it */
+	if (test_bit(__STSD_MEDIA_CHANGED, &host->flags))
+		return 1;
+
+	/* REVISIT use the starlet provided iotcl to check the status */
+
+	mutex_lock(&host->io_mutex);
+
+	/* check if the serial number of the card changed */
+	last_serial = host->card.cid.serial;
+	error = stsd_cmd_deselect_card(host);
+	if (!error) {
+		error = stsd_cmd_read_cid(host);
+		if (!error)
+			error = stsd_cmd_select_card(host);
+	}
+
+	mutex_unlock(&host->io_mutex);
+
+	if (!error && last_serial == host->card.cid.serial && last_serial)
+		clear_bit(__STSD_MEDIA_CHANGED, &host->flags);
+	else
+		set_bit(__STSD_MEDIA_CHANGED, &host->flags);
+
+	return (host->flags & STSD_MEDIA_CHANGED) ? 1 : 0;
+}
+
+static int stsd_revalidate_disk(struct gendisk *disk)
+{
+	struct stsd_host *host = disk->private_data;
+	int error = 0;
+
+	/* report missing medium for zombies */
+	if (!host) {
+		error = -ENOMEDIUM;
+		goto out;
+	}
+
+	/* the block layer likes to call us multiple times... */
+	if (!stsd_media_changed(host->disk))
+		goto out;
+
+	/* get the card into a known status */
+	error = stsd_welcome_card(host);
+	if (error < 0 || stsd_card_is_bad(host)) {
+		drv_printk(KERN_ERR, "card welcome failed\n");
+		if (stsd_card_is_bad(host))
+			drv_printk(KERN_ERR, "stsd_card_is_bad() true\n");
+		if (error < 0)
+			drv_printk(KERN_ERR, "error = %d\n", error);
+		error = -ENOMEDIUM;
+		/* FALL THROUGH */
+	}
+
+	/* inform the block layer about various sizes */
+	blk_queue_logical_block_size(host->queue, KERNEL_SECTOR_SIZE);
+	set_capacity(host->disk, host->card.csd.capacity <<
+			 (host->card.csd.read_blkbits - KERNEL_SECTOR_SHIFT));
+
+	clear_bit(__STSD_MEDIA_CHANGED, &host->flags);
+
+out:
+	if (error)
+		DBG("%s: error=%d (%08x)\n", __func__, error, error);
+	return error;
+}
+
+static int stsd_getgeo(struct block_device *bdev, struct hd_geometry *geo)
+{
+	geo->cylinders = get_capacity(bdev->bd_disk) / (4 * 16);
+	geo->heads = 4;
+	geo->sectors = 16;
+	return 0;
+}
+
+static struct block_device_operations stsd_fops = {
+	.owner = THIS_MODULE,
+	.open = stsd_open,
+	.release = stsd_release,
+	.revalidate_disk = stsd_revalidate_disk,
+	.media_changed = stsd_media_changed,
+	.getgeo = stsd_getgeo,
+};
+
+/*
+ * Setup routines.
+ *
+ */
+
+static int stsd_init_xfer(struct stsd_host *host)
+{
+	struct stsd_xfer *xfer;
+
+	xfer = starlet_kzalloc(sizeof(*xfer), GFP_KERNEL);
+	if (!xfer)
+		return -ENOMEM;
+
+	xfer->reply_len = 4 * sizeof(u32);
+	xfer->reply = starlet_ioh_kzalloc(xfer->reply_len);
+	if (!xfer->reply) {
+		starlet_kfree(xfer);
+		return -ENOMEM;
+	}
+	xfer->cmd = starlet_ioh_kzalloc(sizeof(*xfer->cmd));
+	if (!xfer->cmd) {
+		starlet_ioh_kfree(xfer->reply);
+		starlet_kfree(xfer);
+		return -ENOMEM;
+	}
+	xfer->bounce_buf_size = STSD_MAX_SECTORS * KERNEL_SECTOR_SIZE;
+	xfer->bounce_buf = starlet_ioh_kzalloc(xfer->bounce_buf_size);
+	if (!xfer->bounce_buf) {
+		starlet_ioh_kfree(xfer->cmd);
+		starlet_ioh_kfree(xfer->reply);
+		starlet_kfree(xfer);
+		return -ENOMEM;
+	}
+	xfer->dma_addr = starlet_ioh_virt_to_phys(xfer->bounce_buf);
+
+	xfer->blk_size = KERNEL_SECTOR_SIZE;
+
+	host->xfer = xfer;
+
+	return 0;
+}
+
+static void stsd_exit_xfer(struct stsd_host *host)
+{
+	struct stsd_xfer *xfer = host->xfer;
+
+	starlet_ioh_kfree(xfer->cmd);
+	starlet_ioh_kfree(xfer->reply);
+	starlet_kfree(host->xfer);
+}
+
+static int stsd_init_blk_dev(struct stsd_host *host)
+{
+	struct gendisk *disk;
+	struct request_queue *queue;
+	int error;
+
+	mutex_init(&host->io_mutex);
+
+	/* queue */
+	error = -ENOMEM;
+	spin_lock_init(&host->queue_lock);
+	queue = blk_init_queue(stsd_request_func, &host->queue_lock);
+	if (!queue) {
+		drv_printk(KERN_ERR, "error initializing queue\n");
+		goto err_blk_init_queue;
+	}
+	host->max_phys_segments = 1;
+	blk_queue_max_phys_segments(queue, host->max_phys_segments);
+	blk_queue_max_hw_segments(queue, host->max_phys_segments);
+	blk_queue_max_sectors(queue, STSD_MAX_SECTORS); /* 16 * 512 = 8K */
+	blk_queue_dma_alignment(queue, STARLET_IPC_DMA_ALIGN);
+	queue_flag_set_unlocked(QUEUE_FLAG_NONROT, queue);
+	queue->queuedata = host;
+	host->queue = queue;
+
+	/* disk */
+	disk = alloc_disk(1 << MMC_SHIFT);
+	if (!disk) {
+		drv_printk(KERN_ERR, "error allocating disk\n");
+		goto err_alloc_disk;
+	}
+	disk->major = STSD_MAJOR;
+	disk->first_minor = 0 << MMC_SHIFT;
+	disk->fops = &stsd_fops;
+	sprintf(disk->disk_name, "%s%c", STSD_NAME, 'a');
+	disk->private_data = host;
+	disk->queue = host->queue;
+	host->disk = disk;
+
+	error = 0;
+	goto out;
+
+err_alloc_disk:
+	blk_cleanup_queue(host->queue);
+	host->queue = NULL;
+err_blk_init_queue:
+out:
+	return error;
+}
+
+static void stsd_exit_blk_dev(struct stsd_host *host)
+{
+	blk_cleanup_queue(host->queue);
+	put_disk(host->disk);
+}
+
+static int stsd_init_io_thread(struct stsd_host *host)
+{
+	int result = 0;
+
+	host->io_thread = kthread_run(stsd_io_thread, host, "ksdio");
+	if (IS_ERR(host->io_thread)) {
+		drv_printk(KERN_ERR, "error creating io thread\n");
+		result = PTR_ERR(host->io_thread);
+	}
+	return result;
+}
+
+static void stsd_exit_io_thread(struct stsd_host *host)
+{
+	if (!IS_ERR(host->io_thread)) {
+		wake_up_process(host->io_thread);
+		kthread_stop(host->io_thread);
+		host->io_thread = ERR_PTR(-EINVAL);
+	}
+}
+
+static int stsd_init(struct stsd_host *host)
+{
+	int error;
+
+	host->refcnt = 0;
+	spin_lock_init(&host->lock);
+	set_bit(__STSD_MEDIA_CHANGED, &host->flags);
+	host->f_max = 25000000; /* 25MHz */
+
+	host->fd = starlet_open(stsd_dev_sdio_slot0, 0);
+	if (host->fd < 0) {
+		drv_printk(KERN_ERR, "unable to open %s\n",
+			   stsd_dev_sdio_slot0);
+		return -ENODEV;
+	}
+
+	error = stsd_init_blk_dev(host);
+	if (error)
+		goto out;
+
+	error = stsd_init_xfer(host);
+	if (error)
+		goto err_blk_dev;
+
+	error = stsd_revalidate_disk(host->disk);
+#if 0
+	if (error < 0 || !mmc_card_present(&host->card)) {
+		error = -ENODEV;
+		goto err_xfer;
+	}
+#endif
+
+	error = stsd_init_io_thread(host);
+	if (error)
+		goto err_xfer;
+
+	add_disk(host->disk);
+
+	return 0;
+
+err_xfer:
+	stsd_exit_xfer(host);
+err_blk_dev:
+	stsd_exit_blk_dev(host);
+out:
+	return error;
+}
+
+static void stsd_exit(struct stsd_host *host)
+{
+	del_gendisk(host->disk);
+	stsd_exit_io_thread(host);
+	stsd_exit_xfer(host);
+	stsd_exit_blk_dev(host);
+	if (host->fd >= 0)
+		starlet_close(host->fd);
+	host->fd = -1;
+
+}
+
+static void stsd_kill(struct stsd_host *host)
+{
+	if (host->refcnt > 0) {
+		drv_printk(KERN_ERR, "hey! card removed while in use!\n");
+		set_bit(__STSD_MEDIA_CHANGED, &host->flags);
+	}
+
+	stsd_exit(host);
+
+	/* release the host immediately when not in use */
+	if (!host->refcnt)
+		kfree(host);
+}
+
+/*
+ * Driver model helper routines.
+ *
+ */
+
+static int __devinit stsd_do_probe(struct device *dev)
+{
+	struct stsd_host *host;
+	int error;
+
+	if (starlet_get_ipc_flavour() != STARLET_IPC_IOS)
+		return -ENODEV;
+
+	host = kzalloc(sizeof(*host), GFP_KERNEL);
+	if (!host) {
+		drv_printk(KERN_ERR, "%s: failed to allocate stsd_host\n",
+			    __func__);
+		return -ENOMEM;
+	}
+	dev_set_drvdata(dev, host);
+	host->dev = dev;
+
+	error = stsd_init(host);
+	if (error) {
+		kfree(host);
+		dev_set_drvdata(dev, NULL);
+	}
+
+	return error;
+}
+
+static int __devexit stsd_do_remove(struct device *dev)
+{
+	struct stsd_host *host = dev_get_drvdata(dev);
+
+	if (!host)
+		return -ENODEV;
+
+	stsd_kill(host);
+	dev_set_drvdata(dev, NULL);
+
+	return 0;
+}
+
+/*
+ * OF platform device routines.
+ *
+ */
+
+static int __init stsd_of_probe(struct of_device *odev,
+				const struct of_device_id *match)
+{
+	return stsd_do_probe(&odev->dev);
+}
+
+static int __exit stsd_of_remove(struct of_device *odev)
+{
+	return stsd_do_remove(&odev->dev);
+}
+
+static struct of_device_id stsd_of_match[] = {
+	{ .compatible = "nintendo,starlet-ios-sd" },
+	{ },
+};
+
+MODULE_DEVICE_TABLE(of, stsd_of_match);
+
+static struct of_platform_driver stsd_of_driver = {
+	.owner = THIS_MODULE,
+	.name = DRV_MODULE_NAME,
+	.match_table = stsd_of_match,
+	.probe = stsd_of_probe,
+	.remove = stsd_of_remove,
+};
+
+
+/*
+ * Kernel module interface.
+ *
+ */
+
+static int __init stsd_init_module(void)
+{
+	drv_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION,
+		   stsd_driver_version);
+
+	if (register_blkdev(STSD_MAJOR, DRV_MODULE_NAME)) {
+		drv_printk(KERN_ERR, "unable to register major %d\n",
+			   STSD_MAJOR);
+		return -EIO;
+	}
+
+	return of_register_platform_driver(&stsd_of_driver);
+}
+
+static void __exit stsd_exit_module(void)
+{
+	of_unregister_platform_driver(&stsd_of_driver);
+	unregister_blkdev(STSD_MAJOR, DRV_MODULE_NAME);
+}
+
+module_init(stsd_init_module);
+module_exit(stsd_exit_module);
+
+MODULE_AUTHOR(DRV_AUTHOR);
+MODULE_DESCRIPTION(DRV_DESCRIPTION);
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/exi/Kconfig b/drivers/exi/Kconfig
new file mode 100644
index 0000000..c810782
--- /dev/null
+++ b/drivers/exi/Kconfig
@@ -0,0 +1,20 @@
+#
+# Nintendo GameCube EXI (Expansion Interface) support.
+#
+
+if GAMECUBE_COMMON
+
+menu "EXI support"
+
+config GAMECUBE_EXI
+	bool "Nintendo GameCube/Wii External Interface (EXI)"
+	default y
+	help
+	  On the External Interface sit the memory card slots,
+	  serial ports I & II, the Mask ROM, RTC, SRAM and UART.
+
+	  If in doubt, say Y here.
+
+endmenu
+
+endif
diff --git a/drivers/exi/Makefile b/drivers/exi/Makefile
new file mode 100644
index 0000000..eb80bce
--- /dev/null
+++ b/drivers/exi/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for the EXI bus core.
+#
+
+obj-$(CONFIG_GAMECUBE_EXI)	+= exi-driver.o exi-hw.o
diff --git a/drivers/exi/exi-driver.c b/drivers/exi/exi-driver.c
new file mode 100644
index 0000000..b6b1ab6
--- /dev/null
+++ b/drivers/exi/exi-driver.c
@@ -0,0 +1,523 @@
+/*
+ * drivers/exi/exi-driver.c
+ *
+ * Nintendo GameCube EXternal Interface (EXI) driver model routines.
+ * Copyright (C) 2004-2009 The GameCube Linux Team
+ * Copyright (C) 2004 Arthur Othieno <a.othieno@bluewin.ch>
+ * Copyright (C) 2004,2005 Todd Jeffreys <todd@voidpointer.org>
+ * Copyright (C) 2005,2006,2007,2008,2009 Albert Herranz
+ *
+ * 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/delay.h>
+#include <linux/exi.h>
+#include <linux/init.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/dma-mapping.h>
+
+#define DRV_MODULE_NAME	"exi"
+#define DRV_DESCRIPTION	"Nintendo GameCube/Wii EXternal Interface (EXI) driver"
+#define DRV_AUTHOR	"Arthur Othieno <a.othieno@bluewin.ch>, " \
+			"Todd Jeffreys <todd@voidpointer.org>, " \
+			"Albert Herranz"
+
+static char exi_driver_version[] = "4.0i";
+
+#define drv_printk(level, format, arg...) \
+	printk(level DRV_MODULE_NAME ": " format , ## arg)
+
+
+struct exi_map_id_to_name {
+	unsigned int id;
+	char *name;
+};
+
+
+static void exi_bus_device_release(struct device *dev);
+static int exi_bus_match(struct device *dev, struct device_driver *drv);
+
+
+static struct bus_type exi_bus_type = {
+	.name = "exi",
+	.match = exi_bus_match,
+};
+EXPORT_SYMBOL(exi_bus_type);
+
+static struct device exi_bus_devices[EXI_MAX_CHANNELS] = {
+	[0] = {
+		.init_name = "exi0",
+		.release = exi_bus_device_release,
+	},
+	[1] = {
+		.init_name = "exi1",
+		.release = exi_bus_device_release,
+	},
+	[2] = {
+		.init_name = "exi2",
+		.release = exi_bus_device_release,
+	},
+};
+
+static struct exi_device exi_devices[EXI_MAX_CHANNELS][EXI_DEVICES_PER_CHANNEL];
+
+static struct exi_map_id_to_name exi_map_id_to_name[] = {
+	{ .id = EXI_ID_NONE, .name = "(external card)" },
+	{ .id = 0xffff1698,  .name = "GameCube Mask ROM/RTC/SRAM/UART" },
+	{ .id = 0xfffff308,  .name = "Wii Mask ROM/RTC/SRAM/UART" },
+	{ .id = 0x00000004,  .name = "Memory Card 59" },
+	{ .id = 0x00000008,  .name = "Memory Card 123" },
+	{ .id = 0x00000010,  .name = "Memory Card 251" },
+	{ .id = 0x00000020,  .name = "Memory Card 507" },
+	{ .id = 0x00000040,  .name = "Memory Card 1019" },
+	{ .id = 0x00000080,  .name = "Memory Card 2043" },
+	{ .id = 0x01010000,  .name = "USB Adapter" },
+	{ .id = 0x01020000,  .name = "NPDP GDEV" },
+	{ .id = 0x02020000,  .name = "Modem" },
+	{ .id = 0x03010000,  .name = "Marlin?" },
+	{ .id = 0x04020200,  .name = "BroadBand Adapter (DOL-015)" },
+	{ .id = 0x04120000,  .name = "AD16" },
+	{ .id = 0x05070000,  .name = "IS Viewer" },
+	{ .id = 0x0a000000,  .name = "Microphone (DOL-022)" },
+	{ .id = 0 }
+};
+
+/*
+ * Internal. Return the friendly name of an exi identifier.
+ */
+static const char *exi_name_id(unsigned int id)
+{
+	struct exi_map_id_to_name *map = exi_map_id_to_name;
+
+	while (map->id) {
+		if (map->id == id)
+			return map->name;
+		map++;
+	}
+	return "Unknown";
+}
+
+/*
+ * Internal. Check if an exi device matches a given exi device id.
+ */
+static int exi_device_match_one(const struct exi_device_id *eid,
+				const struct exi_device *exi_device)
+{
+	/*
+	 * We allow drivers to claim devices that do not provide
+	 * EXI identifiers by matching directly on channel/device.
+	 * These drivers must use EXI_ID_NONE on their eids.
+	 */
+	if (eid->id == exi_device->eid.id || eid->id == EXI_ID_NONE) {
+		/* match against channel and device */
+		if (exi_device->eid.channel == eid->channel &&
+		    exi_device->eid.device == eid->device) {
+			return 1;
+		}
+	}
+	return 0;
+}
+
+/*
+ * Internal. Check if an exi device matches a given set of exi device ids.
+ * Return the exi device identifier or %NULL if there is no match.
+ */
+static const struct exi_device_id *
+exi_device_match(const struct exi_device_id *eids,
+		 const struct exi_device *exi_device)
+{
+	while (eids && eids->id) {
+		if (exi_device_match_one(eids, exi_device))
+			return eids;
+		eids++;
+	}
+	return NULL;
+}
+
+/*
+ * Internal. Used to check if an exi device is supported by an exi driver.
+ */
+static int exi_bus_match(struct device *dev, struct device_driver *drv)
+{
+	struct exi_device *exi_device = to_exi_device(dev);
+	struct exi_driver *exi_driver = to_exi_driver(drv);
+	const struct exi_device_id *eids = exi_driver->eid_table;
+
+	if (eids && exi_device_match(eids, exi_device))
+		return 1;
+	return 0;
+}
+
+/*
+ * Internal. Bus device release.
+ */
+static void exi_bus_device_release(struct device *dev)
+{
+	drv_printk(KERN_WARNING, "exi_bus_device_release called!\n");
+}
+
+static void exi_device_release(struct device *dev);
+
+/*
+ * Internal. Initialize an exi_device structure.
+ */
+static void exi_device_init(struct exi_device *exi_device,
+			    unsigned int channel, unsigned int device)
+{
+	memset(exi_device, 0, sizeof(*exi_device));
+
+	exi_device->eid.id = EXI_ID_INVALID;
+	exi_device->eid.channel = channel;
+	exi_device->eid.device = device;
+	exi_device->frequency = EXI_FREQ_SCAN;
+	exi_device->exi_channel = to_exi_channel(channel);
+
+	exi_device->dev.parent = &exi_bus_devices[channel];
+	exi_device->dev.bus = &exi_bus_type;
+	dev_set_name(&exi_device->dev, "exi%01x:%01x", channel, device);
+	exi_device->dev.platform_data = to_exi_channel(channel);
+	set_dma_ops(&exi_device->dev, &dma_direct_ops);
+	exi_device->dev.release = exi_device_release;
+}
+
+/*
+ * Internal. Device release.
+ */
+static void exi_device_release(struct device *dev)
+{
+	struct exi_device *exi_device = to_exi_device(dev);
+	unsigned int channel, device;
+
+	channel = exi_device->eid.channel;
+	device = exi_device->eid.device;
+
+	exi_device_init(exi_device, channel, device);
+}
+
+/**
+ *	exi_device_get - Increments the reference count of the exi device
+ *	@exi_device:	device being referenced
+ *
+ *	Each live reference to an exi device should be refcounted.
+ *	A pointer to the device with the incremented reference counter
+ *	is returned.
+ */
+struct exi_device *exi_device_get(struct exi_device *exi_device)
+{
+	if (exi_device)
+		get_device(&exi_device->dev);
+	return exi_device;
+}
+EXPORT_SYMBOL(exi_device_get);
+
+/**
+ *	exi_device_put  -  Releases a use of the exi device
+ *	@exi_device:	device that's been disconnected
+ *
+ *	Must be called when a user of a device is finished with it.
+ */
+void exi_device_put(struct exi_device *exi_device)
+{
+	if (exi_device)
+		put_device(&exi_device->dev);
+}
+EXPORT_SYMBOL(exi_device_put);
+
+/**
+ *	exi_get_exi_device  -  Returns a reference to an exi device
+ *	@exi_channel:	exi channel where the device is located
+ *	@device:	device number within the channel
+ */
+struct exi_device *exi_get_exi_device(struct exi_channel *exi_channel,
+				      int device)
+{
+	/* REVISIT, take a ref here? */
+	return &exi_devices[to_channel(exi_channel)][device];
+}
+EXPORT_SYMBOL(exi_get_exi_device);
+
+/*
+ * Internal. Call device driver probe function on match.
+ */
+static int exi_device_probe(struct device *dev)
+{
+	struct exi_device *exi_device = to_exi_device(dev);
+	struct exi_driver *exi_driver = to_exi_driver(dev->driver);
+	const struct exi_device_id *eid;
+	int retval = -ENODEV;
+
+	if (!exi_driver->eid_table)
+		goto out;
+
+	eid = exi_device_match(exi_driver->eid_table, exi_device);
+	if (eid) {
+		exi_device->frequency = exi_driver->frequency;
+		if (exi_driver->probe)
+			retval = exi_driver->probe(exi_device);
+	}
+	if (retval >= 0)
+		retval = 0;
+
+out:
+	return retval;
+}
+
+/*
+ * Internal. Call device driver remove function.
+ */
+static int exi_device_remove(struct device *dev)
+{
+	struct exi_device *exi_device = to_exi_device(dev);
+	struct exi_driver *exi_driver = to_exi_driver(dev->driver);
+
+	if (exi_driver->remove)
+		exi_driver->remove(exi_device);
+
+	return 0;
+}
+
+
+/**
+ *      exi_driver_register - register an EXI device driver.
+ *      @driver: driver structure to register.
+ *
+ *      Registers an EXI device driver with the bus
+ *      and consequently with the driver model core.
+ */
+int exi_driver_register(struct exi_driver *driver)
+{
+	driver->driver.name = driver->name;
+	driver->driver.bus = &exi_bus_type;
+	driver->driver.probe = exi_device_probe;
+	driver->driver.remove = exi_device_remove;
+
+	return driver_register(&driver->driver);
+}
+EXPORT_SYMBOL(exi_driver_register);
+
+/**
+ *      exi_driver_unregister - unregister an EXI device driver.
+ *      @driver: driver structure to unregister.
+ *
+ *      Unregisters an EXI device driver with the bus
+ *      and consequently with the driver model core.
+ */
+void exi_driver_unregister(struct exi_driver *driver)
+{
+	driver_unregister(&driver->driver);
+}
+EXPORT_SYMBOL(exi_driver_unregister);
+
+
+/*
+ * Internal. Re-scan a given device.
+ */
+static void exi_device_rescan(struct exi_device *exi_device)
+{
+	unsigned int id;
+	int error;
+
+	/* now ID the device */
+	id = exi_get_id(exi_device);
+
+	if (exi_device->eid.id != EXI_ID_INVALID) {
+		/* device removed or changed */
+		drv_printk(KERN_INFO, "about to remove [%s] id=0x%08x %s\n",
+			   dev_name(&exi_device->dev),
+			   exi_device->eid.id,
+			   exi_name_id(exi_device->eid.id));
+		device_unregister(&exi_device->dev);
+		drv_printk(KERN_INFO, "remove completed\n");
+		exi_device->eid.id = EXI_ID_INVALID;
+	}
+
+	if (id != EXI_ID_INVALID) {
+		/* a new device has been found */
+		drv_printk(KERN_INFO, "about to add [%s] id=0x%08x %s\n",
+			   dev_name(&exi_device->dev),
+			   id, exi_name_id(id));
+		exi_device->eid.id = id;
+		error = device_register(&exi_device->dev);
+		if (error) {
+			drv_printk(KERN_INFO, "add failed (%d)\n", error);
+			exi_device->eid.id = EXI_ID_INVALID;
+		} else
+			drv_printk(KERN_INFO, "add completed\n");
+	}
+
+	exi_update_ext_status(exi_get_exi_channel(exi_device));
+}
+
+/*
+ * Internal. Re-scan a given exi channel, looking for added, changed and
+ * removed exi devices.
+ */
+static void exi_channel_rescan(struct exi_channel *exi_channel)
+{
+	struct exi_device *exi_device;
+	unsigned int channel, device;
+
+	/* add the exi devices underneath the parents */
+	for (device = 0; device < EXI_DEVICES_PER_CHANNEL; ++device) {
+		channel = to_channel(exi_channel);
+		exi_device = &exi_devices[channel][device];
+		exi_device_rescan(exi_device);
+	}
+}
+
+/*
+ * Internal. Scans all the exi channels looking for exi devices.
+ */
+static void exi_bus_rescan(void)
+{
+	struct exi_channel *exi_channel;
+	unsigned int channel;
+
+	for (channel = 0; channel < EXI_MAX_CHANNELS; ++channel) {
+		exi_channel = to_exi_channel(channel);
+		exi_channel_rescan(exi_channel);
+	}
+}
+
+
+static struct task_struct *exi_bus_task;
+wait_queue_head_t exi_bus_waitq;
+
+/*
+ * Internal. Looks for new, changed or removed devices.
+ */
+static int exi_bus_thread(void *__unused)
+{
+	struct exi_channel *exi_channel;
+	struct exi_device *exi_device;
+	unsigned int channel;
+	int is_loaded, was_loaded;
+
+	while (!kthread_should_stop()) {
+		/* scan the memcard slot channels for device changes */
+		for (channel = 0; channel <= 1; ++channel) {
+			exi_channel = to_exi_channel(channel);
+
+			is_loaded = exi_get_ext_line(exi_channel);
+			was_loaded = (exi_channel->flags & EXI_EXT) ? 1 : 0;
+
+			if (is_loaded ^ was_loaded) {
+				exi_device = &exi_devices[channel][0];
+				exi_device_rescan(exi_device);
+			}
+		}
+
+		sleep_on_timeout(&exi_bus_waitq, HZ);
+	}
+
+	return 0;
+}
+
+/**
+ * exi_quiesce() - quiesce EXI hardware
+ *
+ * Put the EXI hardware into a calm state.
+ */
+void exi_quiesce(void)
+{
+	exi_hw_quiesce();
+}
+
+/*
+ *
+ */
+static int exi_init(struct resource *mem, unsigned int irq)
+{
+	struct exi_channel *exi_channel;
+	struct exi_device *exi_device;
+	unsigned int channel, device;
+	int retval;
+
+	retval = exi_hw_init(DRV_MODULE_NAME, mem, irq);
+	if (retval)
+		goto err_hw_init;
+
+	/* register root devices */
+	for (channel = 0; channel < EXI_MAX_CHANNELS; ++channel) {
+		retval = device_register(&exi_bus_devices[channel]);
+		if (retval)
+			goto err_device_register;
+	}
+
+	/* initialize devices */
+	for (channel = 0; channel < EXI_MAX_CHANNELS; ++channel) {
+		exi_channel = to_exi_channel(channel);
+		for (device = 0; device < EXI_DEVICES_PER_CHANNEL; ++device) {
+			exi_device = &exi_devices[channel][device];
+			exi_device_init(exi_device, channel, device);
+		}
+	}
+
+	/* register the bus */
+	retval = bus_register(&exi_bus_type);
+	if (retval)
+		goto err_bus_register;
+
+	/* now enumerate through the bus and add all detected devices */
+	exi_bus_rescan();
+
+	/* setup a thread to manage plugable devices */
+	init_waitqueue_head(&exi_bus_waitq);
+	exi_bus_task = kthread_run(exi_bus_thread, NULL, "kexid");
+	if (IS_ERR(exi_bus_task))
+		drv_printk(KERN_WARNING, "failed to start exi kernel thread\n");
+
+	return 0;
+
+err_bus_register:
+err_device_register:
+	while (--channel > 0)
+		device_unregister(&exi_bus_devices[channel]);
+	exi_hw_exit(mem, irq);
+err_hw_init:
+	return retval;
+}
+
+/*
+ *
+ */
+static int __init exi_layer_init(void)
+{
+	struct device_node *np;
+	struct resource res;
+	int retval;
+
+	drv_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION,
+		   exi_driver_version);
+
+	np = of_find_compatible_node(NULL, NULL, "nintendo,flipper-exi");
+	if (!np) {
+		np = of_find_compatible_node(NULL, NULL,
+					     "nintendo,hollywood-exi");
+		if (!np)
+			return -ENODEV;
+	}
+
+	retval = of_address_to_resource(np, 0, &res);
+	if (retval) {
+		drv_printk(KERN_ERR, "no io memory range found\n");
+		return -ENOMEM;
+	}
+
+	retval = exi_init(&res, irq_of_parse_and_map(np, 0));
+	of_node_put(np);
+
+	return retval;
+}
+postcore_initcall(exi_layer_init);
+
+MODULE_AUTHOR(DRV_AUTHOR);
+MODULE_DESCRIPTION(DRV_DESCRIPTION);
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/exi/exi-hw.c b/drivers/exi/exi-hw.c
new file mode 100644
index 0000000..d64b8ea
--- /dev/null
+++ b/drivers/exi/exi-hw.c
@@ -0,0 +1,1395 @@
+/*
+ * drivers/exi/exi-hw.c
+ *
+ * Nintendo GameCube EXpansion Interface support. Hardware routines.
+ * Copyright (C) 2004-2009 The GameCube Linux Team
+ * Copyright (C) 2004,2005 Todd Jeffreys <todd@voidpointer.org>
+ * Copyright (C) 2005,2006,2007,2008,2009 Albert Herranz
+ *
+ * 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.
+ *
+ */
+
+/*
+ * IMPLEMENTATION NOTES
+ *
+ * The EXI Layer provides the following primitives:
+ *
+ * 	op			atomic?
+ * 	------------------	-------
+ * 	take			yes
+ * 	give			yes
+ * 	select			yes
+ * 	deselect		yes
+ * 	transfer		yes/no (1)
+ *
+ * These primitives are encapsulated in several APIs.
+ * See include/linux/exi.h for additional information.
+ *
+ * 1. Kernel Contexts
+ *
+ * User, softirq and hardirq contexts are supported, with some limitations.
+ *
+ * Launching EXI operations in softirq or hardirq context requires kernel
+ * coordination to ensure channels are free before use.
+ *
+ * The EXI Layer Event System delivers events in softirq context, but it already
+ * makes provisions to ensure that channels are useable by the event handlers.
+ * Events are delivered only when the channels on the event handler
+ * channel mask are all deselected. This allows one to run EXI commands in
+ * softirq context from the EXI event handlers.
+ *
+ * "take" operations in user context will sleep if necessary until the
+ * channel is "given".
+ *
+ *
+ * 2. Transfers
+ *
+ * The EXI Layer provides a transfer API to perform read and write
+ * operations.
+ * By default, transfers partially or totally suitable for DMA will be
+ * partially or totally processed through DMA. The EXI Layer takes care of
+ * splitting a transfer in several pieces so the best transfer method is
+ * used each time.
+ *
+ * (1) A immediate mode transfer is atomic, but a DMA transfer is not.
+ */
+
+/*#define EXI_DEBUG 1*/
+
+#include <linux/types.h>
+#include <linux/dma-mapping.h>
+#include <linux/wait.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/spinlock.h>
+
+#include <linux/exi.h>
+#include "exi-hw.h"
+
+
+#define drv_printk(level, format, arg...) \
+	printk(level "exi: " format , ## arg)
+
+#ifdef EXI_DEBUG
+#  define DBG(fmt, args...) \
+	   printk(KERN_ERR "%s: " fmt, __func__ , ## args)
+#else
+#  define DBG(fmt, args...)
+#endif
+
+
+static void exi_tasklet(unsigned long param);
+
+
+/* io memory base for EXI */
+static void __iomem *exi_io_mem;
+
+
+/*
+ * These are the available exi channels.
+ */
+static struct exi_channel exi_channels[EXI_MAX_CHANNELS] = {
+	[0] = {
+		.channel = 0,
+		.lock = __SPIN_LOCK_UNLOCKED(exi_channels[0].lock),
+		.io_lock = __SPIN_LOCK_UNLOCKED(exi_channels[0].io_lock),
+		.wait_queue = __WAIT_QUEUE_HEAD_INITIALIZER(
+				exi_channels[0].wait_queue),
+	},
+	[1] = {
+		.channel = 1,
+		.lock = __SPIN_LOCK_UNLOCKED(exi_channels[1].lock),
+		.io_lock = __SPIN_LOCK_UNLOCKED(exi_channels[1].io_lock),
+		.wait_queue = __WAIT_QUEUE_HEAD_INITIALIZER(
+				exi_channels[1].wait_queue),
+	},
+	[2] = {
+		.channel = 2,
+		.lock = __SPIN_LOCK_UNLOCKED(exi_channels[2].lock),
+		.io_lock = __SPIN_LOCK_UNLOCKED(exi_channels[2].io_lock),
+		.wait_queue = __WAIT_QUEUE_HEAD_INITIALIZER(
+				exi_channels[2].wait_queue),
+	},
+};
+
+/* handy iterator for exi channels */
+#define exi_channel_for_each(pos) \
+	for (pos = &exi_channels[0]; pos < &exi_channels[EXI_MAX_CHANNELS]; \
+		pos++)
+
+/* conversions between channel numbers and exi channel structures */
+#define __to_exi_channel(channel) (&exi_channels[channel])
+#define __to_channel(exi_channel) (exi_channel->channel)
+
+/**
+ *	to_exi_channel  -  returns an exi_channel given a channel number
+ *	@channel:	channel number
+ *
+ *	Return the exi_channel structure associated to a given channel.
+ */
+struct exi_channel *to_exi_channel(unsigned int channel)
+{
+	if (channel > EXI_MAX_CHANNELS)
+		return NULL;
+
+	return __to_exi_channel(channel);
+}
+EXPORT_SYMBOL(to_exi_channel);
+
+/**
+ *	to_channel  -  returns a channel number given an exi channel
+ *	@exi_channel:	channel
+ *
+ *	Return the channel number for a given exi_channel structure.
+ */
+unsigned int to_channel(struct exi_channel *exi_channel)
+{
+	BUG_ON(exi_channel == NULL);
+
+	return __to_channel(exi_channel);
+}
+EXPORT_SYMBOL(to_channel);
+
+/**
+ *	exi_channel_owner  -  returns the owner of the given channel
+ *	@exi_channel:	channel
+ *
+ *	Return the device owning a given exi_channel structure.
+ */
+struct exi_device *exi_channel_owner(struct exi_channel *exi_channel)
+{
+	return exi_channel->owner;
+}
+
+
+/*
+ *
+ *
+ */
+
+/**
+ *	exi_select_raw  -  selects a device on an exi channel
+ *	@exi_channel:	channel
+ *	@device:	device number on channel
+ *	@freq:		clock frequency index
+ *
+ *	Select a given device on a specified EXI channel by setting its
+ *	CS line, and use the specified clock frequency when doing transfers.
+ */
+void exi_select_raw(struct exi_channel *exi_channel, unsigned int device,
+		   unsigned int freq)
+{
+	u32 __iomem *csr_reg = exi_channel->io_base + EXI_CSR;
+	u32 csr;
+	unsigned long flags;
+
+	BUG_ON(device > EXI_DEVICES_PER_CHANNEL ||
+	       freq > EXI_MAX_FREQ);
+
+	/*
+	 * Preserve interrupt masks while setting the CS line bits.
+	 */
+	spin_lock_irqsave(&exi_channel->io_lock, flags);
+	csr = in_be32(csr_reg);
+	csr &= (EXI_CSR_EXTINMASK | EXI_CSR_TCINTMASK | EXI_CSR_EXIINTMASK);
+	csr |= ((1<<device) << 7) | (freq << 4);
+	out_be32(csr_reg, csr);
+	spin_unlock_irqrestore(&exi_channel->io_lock, flags);
+}
+EXPORT_SYMBOL(exi_select_raw);
+
+
+/**
+ *	exi_deselect_raw  -  deselects all devices on an exi channel
+ *	@exi_channel:	channel
+ *
+ *	Deselect any device previously selected on the specified EXI
+ *	channel by unsetting all CS lines.
+ */
+void exi_deselect_raw(struct exi_channel *exi_channel)
+{
+	u32 __iomem *csr_reg = exi_channel->io_base + EXI_CSR;
+	u32 csr;
+	unsigned long flags;
+
+	/*
+	 * Preserve interrupt masks while clearing the CS line bits.
+	 */
+	spin_lock_irqsave(&exi_channel->io_lock, flags);
+	csr = in_be32(csr_reg);
+	csr &= (EXI_CSR_EXTINMASK | EXI_CSR_TCINTMASK | EXI_CSR_EXIINTMASK);
+	out_be32(csr_reg, csr);
+	spin_unlock_irqrestore(&exi_channel->io_lock, flags);
+}
+EXPORT_SYMBOL(exi_deselect_raw);
+
+/**
+ *	exi_transfer_raw  -  performs an exi transfer using immediate mode
+ *	@exi_channel:	channel
+ *	@data:		pointer to data being read/writen
+ *	@len:		length of data
+ *	@mode:		direction of transfer (EXI_OP_{READ,READWRITE,WRITE})
+ *
+ *	Read or write data on a given EXI channel.
+ *
+ */
+void exi_transfer_raw(struct exi_channel *exi_channel,
+		      void *data, size_t len, int mode)
+{
+	while (len >= 4) {
+		__exi_transfer_raw_u32(exi_channel, data, mode);
+		exi_channel->stats_xfers++;
+		data += 4;
+		len -= 4;
+	}
+
+	switch (len) {
+	case 1:
+		__exi_transfer_raw_u8(exi_channel, data, mode);
+		exi_channel->stats_xfers++;
+		break;
+	case 2:
+		__exi_transfer_raw_u16(exi_channel, data, mode);
+		exi_channel->stats_xfers++;
+		break;
+	case 3:
+		/* XXX optimize this case */
+		__exi_transfer_raw_u16(exi_channel, data, mode);
+		exi_channel->stats_xfers++;
+		__exi_transfer_raw_u8(exi_channel, data+2, mode);
+		exi_channel->stats_xfers++;
+		break;
+	default:
+		break;
+	}
+}
+EXPORT_SYMBOL(exi_transfer_raw);
+
+/*
+ * Internal. Start a transfer using "interrupt-driven immediate" mode.
+ */
+static void exi_start_idi_transfer_raw(struct exi_channel *exi_channel,
+				       void *data, size_t len, int mode)
+{
+	void __iomem *io_base = exi_channel->io_base;
+	u32 __iomem *csr_reg = io_base + EXI_CSR;
+	u32 val = ~0;
+	unsigned long flags;
+
+	BUG_ON(len < 1 || len > 4);
+
+	exi_channel->stats_idi_xfers++;
+	exi_channel->stats_xfers++;
+
+	if ((mode & EXI_OP_WRITE)) {
+		switch (len) {
+		case 1:
+			val = *((u8 *)data) << 24;
+			break;
+		case 2:
+			val = *((u16 *)data) << 16;
+			break;
+		case 3:
+			val = *((u16 *)data) << 16;
+			val |= *((u8 *)data+2) << 8;
+			break;
+		case 4:
+			val = *((u32 *)data);
+			break;
+		default:
+			break;
+		}
+	}
+
+	out_be32(io_base + EXI_DATA, val);
+
+	/* enable the Transfer Complete interrupt */
+	spin_lock_irqsave(&exi_channel->io_lock, flags);
+	out_be32(csr_reg, in_be32(csr_reg) | EXI_CSR_TCINTMASK);
+	spin_unlock_irqrestore(&exi_channel->io_lock, flags);
+
+	/* start the transfer */
+	out_be32(io_base + EXI_CR,
+		 EXI_CR_TSTART | EXI_CR_TLEN(len) | (mode&0xf));
+}
+
+/*
+ * Internal. Finish a transfer using "interrupt-driven immediate" mode.
+ */
+static void exi_end_idi_transfer_raw(struct exi_channel *exi_channel,
+				     void *data, size_t len, int mode)
+{
+	void __iomem *io_base = exi_channel->io_base;
+	u32 val = ~0;
+
+	BUG_ON(len < 1 || len > 4);
+
+	if ((mode&0xf) != EXI_OP_WRITE) {
+		val = in_be32(io_base + EXI_DATA);
+		switch (len) {
+		case 1:
+			*((u8 *)data) = (u8)(val >> 24);
+			break;
+		case 2:
+			*((u16 *)data) = (u16)(val >> 16);
+			break;
+		case 3:
+			*((u16 *)data) = (u16)(val >> 16);
+			*((u8 *)data+2) = (u8)(val >> 8);
+			break;
+		case 4:
+			*((u32 *)data) = (u32)(val);
+			break;
+		default:
+			break;
+		}
+	}
+}
+
+/*
+ * Internal. Start a transfer using DMA mode.
+ */
+static void exi_start_dma_transfer_raw(struct exi_channel *exi_channel,
+				       dma_addr_t data, size_t len, int mode)
+{
+	void __iomem *io_base = exi_channel->io_base;
+	u32 __iomem *csr_reg = io_base + EXI_CSR;
+	unsigned long flags;
+
+	BUG_ON((data & EXI_DMA_ALIGN) != 0 ||
+	       (len & EXI_DMA_ALIGN) != 0);
+
+	exi_channel->stats_dma_xfers++;
+	exi_channel->stats_xfers++;
+
+	/*
+	 * We clear the DATA register here to avoid confusing some
+	 * special hardware, like SD cards.
+	 * Indeed, we need all 1s here.
+	 */
+	out_be32(io_base + EXI_DATA, ~0);
+
+	/* setup address and length of transfer */
+	out_be32(io_base + EXI_MAR, data);
+	out_be32(io_base + EXI_LENGTH, len);
+
+	/* enable the Transfer Complete interrupt */
+	spin_lock_irqsave(&exi_channel->io_lock, flags);
+	out_be32(csr_reg, in_be32(csr_reg) | EXI_CSR_TCINTMASK);
+	spin_unlock_irqrestore(&exi_channel->io_lock, flags);
+
+	/* start the transfer */
+	out_be32(io_base + EXI_CR, EXI_CR_TSTART | EXI_CR_DMA | (mode&0xf));
+}
+
+
+/*
+ * Internal. Busy-wait until a DMA mode transfer operation completes.
+ */
+static void exi_wait_for_transfer_raw(struct exi_channel *exi_channel)
+{
+	u32 __iomem *cr_reg = exi_channel->io_base + EXI_CR;
+	u32 __iomem *csr_reg = exi_channel->io_base + EXI_CSR;
+	unsigned long flags;
+	unsigned long deadline = jiffies + 2*HZ;
+	int borked = 0;
+
+	/* we don't want TCINTs to disturb us while waiting */
+	spin_lock_irqsave(&exi_channel->io_lock, flags);
+	out_be32(csr_reg, in_be32(csr_reg) & ~EXI_CSR_TCINTMASK);
+	spin_unlock_irqrestore(&exi_channel->io_lock, flags);
+
+	/* busy-wait for transfer complete */
+	while ((in_be32(cr_reg)&EXI_CR_TSTART) && !borked) {
+		cpu_relax();
+		borked = time_after(jiffies, deadline);
+	}
+
+	if (borked) {
+		drv_printk(KERN_ERR, "exi transfer took too long, "
+			   "is your hardware ok?");
+	}
+
+	/* ack the Transfer Complete interrupt */
+	spin_lock_irqsave(&exi_channel->io_lock, flags);
+	out_be32(csr_reg, in_be32(csr_reg) | EXI_CSR_TCINT);
+	spin_unlock_irqrestore(&exi_channel->io_lock, flags);
+}
+
+
+/*
+ *
+ *
+ */
+
+static void exi_command_done(struct exi_command *cmd);
+
+/*
+ * Internal. Initialize an exi_channel structure.
+ */
+void exi_channel_init(struct exi_channel *exi_channel, unsigned int channel)
+{
+	memset(exi_channel, 0, sizeof(*exi_channel));
+	exi_channel->events[EXI_EVENT_IRQ].id = EXI_EVENT_IRQ;
+	exi_channel->events[EXI_EVENT_INSERT].id = EXI_EVENT_INSERT;
+	exi_channel->events[EXI_EVENT_TC].id = EXI_EVENT_TC;
+
+	spin_lock_init(&exi_channel->lock);
+	spin_lock_init(&exi_channel->io_lock);
+	init_waitqueue_head(&exi_channel->wait_queue);
+
+	exi_channel->channel = channel;
+	exi_channel->io_base = exi_io_mem + channel * EXI_CHANNEL_SPACING;
+
+	tasklet_init(&exi_channel->tasklet,
+		     exi_tasklet, (unsigned long)exi_channel);
+}
+
+/*
+ * Internal. Check if an exi channel has delayed work to do.
+ */
+static void exi_check_pending_work(void)
+{
+	struct exi_channel *exi_channel;
+
+	exi_channel_for_each(exi_channel) {
+		if (exi_channel->csr)
+			tasklet_schedule(&exi_channel->tasklet);
+	}
+}
+
+/*
+ * Internal. Finish a DMA transfer.
+ * Caller holds the channel lock.
+ */
+static void exi_end_dma_transfer(struct exi_channel *exi_channel)
+{
+	struct exi_command *cmd;
+
+	cmd = exi_channel->queued_cmd;
+	if (cmd) {
+		BUG_ON(!(exi_channel->flags & EXI_DMABUSY));
+
+		exi_channel->flags &= ~EXI_DMABUSY;
+		dma_unmap_single(&exi_channel->owner->dev,
+				 cmd->dma_addr, cmd->dma_len,
+				 (cmd->opcode == EXI_OP_READ) ?
+				 DMA_FROM_DEVICE : DMA_TO_DEVICE);
+
+		exi_channel->queued_cmd = NULL;
+	}
+}
+
+/*
+ * Internal. Finish an "interrupt-driven immediate" transfer.
+ * Caller holds the channel lock.
+ *
+ * If more data is pending transfer, it schedules a new transfer.
+ * Returns zero if no more transfers are required, non-zero otherwise.
+ *
+ */
+static int exi_end_idi_transfer(struct exi_channel *exi_channel)
+{
+	struct exi_command *cmd;
+	int len, offset;
+	unsigned int balance = 16 /* / sizeof(u32) */;
+
+	cmd = exi_channel->queued_cmd;
+	if (cmd) {
+		BUG_ON((exi_channel->flags & EXI_DMABUSY));
+
+		len = (cmd->bytes_left > 4) ? 4 : cmd->bytes_left;
+		offset = cmd->len - cmd->bytes_left;
+		exi_end_idi_transfer_raw(exi_channel,
+					 cmd->data + offset, len,
+					 cmd->opcode);
+		cmd->bytes_left -= len;
+
+		if (balance && cmd->bytes_left > 0) {
+			offset += len;
+			len = (cmd->bytes_left > balance) ?
+					balance : cmd->bytes_left;
+			exi_transfer_raw(exi_channel,
+					 cmd->data + offset, len, cmd->opcode);
+			cmd->bytes_left -= len;
+		}
+
+		if (cmd->bytes_left > 0) {
+			offset = cmd->len - cmd->bytes_left;
+			len = (cmd->bytes_left > 4) ? 4 : cmd->bytes_left;
+
+			exi_start_idi_transfer_raw(exi_channel,
+						   cmd->data + offset, len,
+						   cmd->opcode);
+		} else {
+			exi_channel->queued_cmd = NULL;
+		}
+	}
+
+	return (exi_channel->queued_cmd) ? 1 : 0;
+}
+
+/*
+ * Internal. Wait until a single transfer completes, and launch callbacks
+ * when the whole transfer is completed.
+ */
+static int exi_wait_for_transfer_one(struct exi_channel *exi_channel)
+{
+	struct exi_command *cmd;
+	unsigned long flags;
+	int pending = 0;
+
+	spin_lock_irqsave(&exi_channel->lock, flags);
+
+	exi_wait_for_transfer_raw(exi_channel);
+
+	cmd = exi_channel->queued_cmd;
+	if (cmd) {
+		if ((exi_channel->flags & EXI_DMABUSY)) {
+			/* dma transfers need just one transfer */
+			exi_end_dma_transfer(exi_channel);
+		} else {
+			pending = exi_end_idi_transfer(exi_channel);
+		}
+
+		spin_unlock_irqrestore(&exi_channel->lock, flags);
+
+		if (!pending)
+			exi_command_done(cmd);
+		goto out;
+	}
+
+	spin_unlock_irqrestore(&exi_channel->lock, flags);
+out:
+	return pending;
+}
+
+#if 0
+/*
+ * Internal. Wait until a full transfer completes and launch callbacks.
+ */
+static void exi_wait_for_transfer(struct exi_channel *exi_channel)
+{
+	while (exi_wait_for_transfer_one(exi_channel))
+		cpu_relax();
+}
+#endif
+
+/*
+ * Internal. Call any done hooks.
+ */
+static void exi_command_done(struct exi_command *cmd)
+{
+	/* if specified, call the completion routine */
+	if (cmd->done)
+		cmd->done(cmd);
+}
+
+/*
+ * Internal. Take a channel.
+ */
+static int exi_take_channel(struct exi_channel *exi_channel,
+			    struct exi_device *exi_device, int wait)
+{
+	unsigned long flags;
+	int result = 0;
+
+	BUG_ON(!exi_device);
+
+	spin_lock_irqsave(&exi_channel->lock, flags);
+	while (exi_channel->owner) {
+		spin_unlock_irqrestore(&exi_channel->lock, flags);
+		if (!wait)
+			return -EBUSY;
+		wait_event(exi_channel->wait_queue,
+			   !exi_channel->owner);
+		spin_lock_irqsave(&exi_channel->lock, flags);
+	}
+	exi_channel->owner = exi_device;
+	spin_unlock_irqrestore(&exi_channel->lock, flags);
+
+	return result;
+}
+
+/*
+ * Internal. Give a channel.
+ */
+static int exi_give_channel(struct exi_channel *exi_channel)
+{
+	WARN_ON(exi_channel->owner == NULL);
+	exi_channel->owner = NULL;
+	wake_up(&exi_channel->wait_queue);
+	return 0;
+}
+
+/*
+ * Internal. Perform the post non-DMA transfer associated to a DMA transfer.
+ */
+static void exi_cmd_post_transfer(struct exi_command *cmd)
+{
+	struct exi_channel *exi_channel = cmd->exi_channel;
+	struct exi_command *post_cmd = &exi_channel->post_cmd;
+
+	DBG("channel=%d\n", exi_channel->channel);
+
+	exi_transfer_raw(exi_channel, post_cmd->data, post_cmd->len,
+			 post_cmd->opcode);
+
+	cmd->done_data = post_cmd->done_data;
+	cmd->done = post_cmd->done;
+	exi_op_nop(post_cmd, exi_channel);
+	exi_command_done(cmd);
+}
+
+
+#define exi_align_next(x) (void *) \
+			  (((unsigned long)(x)+EXI_DMA_ALIGN)&~EXI_DMA_ALIGN)
+#define exi_align_prev(x) (void *) \
+			  ((unsigned long)(x)&~EXI_DMA_ALIGN)
+#define exi_is_aligned(x) (void *) \
+			  (!((unsigned long)(x)&EXI_DMA_ALIGN))
+
+/*
+ * Internal. Perform a transfer.
+ * Caller holds the channel lock.
+ */
+static int exi_cmd_transfer(struct exi_command *cmd)
+{
+	struct exi_channel *exi_channel = cmd->exi_channel;
+	struct exi_command *post_cmd = &exi_channel->post_cmd;
+	void *pre_data, *data, *post_data;
+	unsigned int pre_len, len, post_len;
+	int opcode;
+	int result = 0;
+
+	BUG_ON(!exi_channel->owner);
+
+	len = cmd->len;
+	if (!len)
+		goto done;
+
+	DBG("channel=%d, opcode=%d\n", exi_channel->channel, cmd->opcode);
+
+	opcode = cmd->opcode;
+	data = cmd->data;
+
+	/* interrupt driven immediate transfer... */
+	if ((cmd->flags & EXI_CMD_IDI)) {
+		exi_channel->queued_cmd = cmd;
+		exi_channel->flags &= ~EXI_DMABUSY;
+
+		cmd->bytes_left = cmd->len;
+		len = (cmd->bytes_left > 4) ? 4 : cmd->bytes_left;
+		exi_start_idi_transfer_raw(exi_channel, data, len, opcode);
+
+		result = 1; /* wait */
+		goto done;
+	}
+
+	/*
+	 * We can't do DMA transfers unless we have at least 32 bytes.
+	 * And we won't do DMA transfers if user requests that.
+	 */
+	if (len < EXI_DMA_ALIGN+1 || (cmd->flags & EXI_CMD_NODMA)) {
+		exi_transfer_raw(exi_channel, data, len, opcode);
+		goto done;
+	}
+
+	/*
+	 * |_______________|______...______|_______________| DMA alignment
+	 *     <--pre_len--><---- len -----><-post_len->
+	 *     +-----------+------...------+-----------+
+	 *     | pre_data  | data          | post_data |
+	 *     | non-DMA   | DMA           | non-DMA   |
+	 *     +-----------+------...------+-----------+
+	 *       < 32 bytes  N*32 bytes      < 32 bytes
+	 *     |<--------->|<-----...----->|<--------->|
+	 *     <-------------- cmd->len --------------->
+	 */
+
+	pre_data = data;
+	post_data = exi_align_prev(pre_data+len);
+	data = exi_align_next(pre_data);
+
+	pre_len = data - pre_data;
+	post_len = (pre_data + len) - post_data;
+	len = post_data - data;
+
+	/*
+	 * Coalesce pre and post data transfers if no DMA transfer is possible.
+	 */
+	if (!len) {
+		/*
+		 * Maximum transfer size here is 31+31=62 bytes.
+		 */
+		exi_transfer_raw(exi_channel, pre_data,
+				 pre_len + post_len, opcode);
+		goto done;
+	}
+
+	/*
+	 * The first unaligned chunk can't use DMA.
+	 */
+	if (pre_len > 0) {
+		/*
+		 * Maximum transfer size here is 31 bytes.
+		 */
+		exi_transfer_raw(exi_channel, pre_data, pre_len, opcode);
+	}
+
+	/*
+	 * Perform a DMA transfer on the aligned data, followed by a non-DMA
+	 * data transfer on the remaining data.
+	 */
+	if (post_len > 0) {
+		/*
+		 * Maximum transfer size here will be 31 bytes.
+		 */
+		exi_op_transfer(post_cmd, exi_channel,
+				post_data, post_len, opcode);
+		post_cmd->done_data = cmd->done_data;
+		post_cmd->done = cmd->done;
+		cmd->done_data = NULL;
+		cmd->done = exi_cmd_post_transfer;
+	}
+
+	exi_channel->queued_cmd = cmd;
+	exi_channel->flags |= EXI_DMABUSY;
+
+	cmd->dma_len = len;
+	cmd->dma_addr = dma_map_single(&exi_channel->owner->dev,
+				       data, len,
+				       (cmd->opcode == EXI_OP_READ) ?
+				       DMA_FROM_DEVICE : DMA_TO_DEVICE);
+
+	exi_start_dma_transfer_raw(exi_channel, cmd->dma_addr, len, opcode);
+
+	result = 1; /* wait */
+
+done:
+	return result;
+}
+
+/**
+ *	exi_run_command  -  executes a single exi command
+ *	@cmd:	the command to execute
+ *
+ *	Context: user
+ *
+ *	Run just one command.
+ *
+ */
+static int exi_run_command(struct exi_command *cmd)
+{
+	struct exi_channel *exi_channel = cmd->exi_channel;
+	struct exi_device *exi_device = cmd->exi_device;
+	unsigned long flags;
+	int wait = !(cmd->flags & EXI_CMD_NOWAIT);
+	int result = 0;
+
+	if (cmd->opcode != EXI_OP_TAKE)
+		WARN_ON(exi_channel->owner != exi_device);
+
+	switch (cmd->opcode) {
+	case EXI_OP_NOP:
+		break;
+	case EXI_OP_TAKE:
+		result = exi_take_channel(exi_channel, exi_device, wait);
+		break;
+	case EXI_OP_GIVE:
+		result = exi_give_channel(exi_channel);
+		if (!exi_channel->owner)
+			exi_check_pending_work();
+		break;
+	case EXI_OP_SELECT:
+		exi_select_raw(exi_channel, exi_device->eid.device,
+			       exi_device->frequency);
+		break;
+	case EXI_OP_DESELECT:
+		exi_deselect_raw(exi_channel);
+		break;
+	case EXI_OP_READ:
+	case EXI_OP_READWRITE:
+	case EXI_OP_WRITE:
+		spin_lock_irqsave(&exi_channel->lock, flags);
+		result = exi_cmd_transfer(cmd);
+		spin_unlock_irqrestore(&exi_channel->lock, flags);
+		break;
+	default:
+		result = -ENOSYS;
+		break;
+	}
+
+	/*
+	 * We check for delayed work every time the channel becomes
+	 * idle.
+	 */
+	if (!result)
+		exi_command_done(cmd);
+
+	return result;
+}
+
+
+/*
+ * Internal. Completion routine.
+ */
+static void exi_wait_done(struct exi_command *cmd)
+{
+	complete(cmd->done_data);
+}
+
+/*
+ * Internal. Run a command and wait.
+ * Might sleep if called from user context. Otherwise will busy-wait.
+ */
+static int exi_run_command_and_wait(struct exi_command *cmd)
+{
+	DECLARE_COMPLETION(complete);
+	int result;
+
+	cmd->done_data = &complete;
+	cmd->done = exi_wait_done;
+	result = exi_run_command(cmd);
+	if (result > 0) {
+		wait_for_completion(&complete);
+		result = 0;
+	}
+	return result;
+}
+
+/**
+ *	exi_take  -  reserves an exi channel for exclusive use by a device
+ *	@exi_device:	exi device making the reservation
+ *	@wait:		wait for the operation to complete
+ *
+ *	Reserves the channel of a given EXI device.
+ */
+int exi_take(struct exi_device *exi_device, int wait)
+{
+	struct exi_command cmd;
+
+	exi_op_take(&cmd, exi_device);
+	if (!wait)
+		cmd.flags |= EXI_CMD_NOWAIT;
+	return exi_run_command(&cmd);
+}
+EXPORT_SYMBOL(exi_take);
+
+/**
+ *	exi_give  -  releases an exi channel
+ *	@exi_device:	exi device making the release
+ *
+ *	Releases the channel of a given EXI device.
+ */
+int exi_give(struct exi_device *exi_device)
+{
+	struct exi_command cmd;
+
+	exi_op_give(&cmd, exi_device->exi_channel);
+	return exi_run_command(&cmd);
+}
+EXPORT_SYMBOL(exi_give);
+
+/**
+ *	exi_select  -  selects a exi device
+ *	@exi_device:	exi device being selected
+ *
+ *	Selects a given EXI device.
+ */
+void exi_select(struct exi_device *exi_device)
+{
+	struct exi_command cmd;
+
+	exi_op_select(&cmd, exi_device);
+	exi_run_command(&cmd);
+}
+EXPORT_SYMBOL(exi_select);
+
+/**
+ *	exi_deselect  -  deselects all devices on an exi channel
+ *	@exi_channel:	channel
+ *
+ *	Deselects all EXI devices on the given channel.
+ *
+ */
+void exi_deselect(struct exi_channel *exi_channel)
+{
+	struct exi_command cmd;
+
+	exi_op_deselect(&cmd, exi_channel);
+	exi_run_command(&cmd);
+}
+EXPORT_SYMBOL(exi_deselect);
+
+/**
+ *	exi_transfer  -  Performs a read or write EXI transfer.
+ *	@exi_channel:	channel
+ *	@data:		pointer to data being read/written
+ *	@len:		length of data
+ *	@opcode:	operation code (EXI_OP_{READ,READWRITE,WRITE})
+ *
+ *	Read or write data on a given EXI channel.
+ */
+void exi_transfer(struct exi_channel *exi_channel, void *data, size_t len,
+		   int opcode, unsigned long flags)
+{
+	struct exi_command cmd;
+
+	exi_op_transfer(&cmd, exi_channel, data, len, opcode);
+	cmd.flags |= flags;
+	exi_run_command_and_wait(&cmd);
+}
+EXPORT_SYMBOL(exi_transfer);
+
+/*
+ * Internal. Release several previously reserved channels, according to a
+ * channel mask.
+ */
+static void __give_some_channels(unsigned int channel_mask)
+{
+	struct exi_channel *exi_channel;
+	unsigned int channel;
+
+	for (channel = 0; channel_mask && channel < EXI_MAX_CHANNELS;
+	     channel++) {
+		if ((channel_mask & (1<<channel))) {
+			channel_mask &= ~(1<<channel);
+			exi_channel = __to_exi_channel(channel);
+			exi_channel->owner = NULL;
+		}
+	}
+}
+
+/*
+ * Internal. Try to reserve atomically several channels, according to a
+ * channel mask.
+ */
+static inline int __try_take_some_channels(unsigned int channel_mask,
+					   struct exi_device *exi_device)
+{
+	struct exi_channel *exi_channel;
+	unsigned int channel, taken_channel_mask = 0;
+	unsigned long flags;
+	int result = 0;
+
+	for (channel = 0; channel_mask && channel < EXI_MAX_CHANNELS;
+	     channel++) {
+		if ((channel_mask & (1<<channel))) {
+			channel_mask &= ~(1<<channel);
+			exi_channel = __to_exi_channel(channel);
+			spin_lock_irqsave(&exi_channel->lock, flags);
+			if (exi_channel->owner) {
+				spin_unlock_irqrestore(&exi_channel->lock,
+						       flags);
+				result = -EBUSY;
+				break;
+			}
+			exi_channel->owner = exi_device;
+			taken_channel_mask |= (1<<channel);
+			spin_unlock_irqrestore(&exi_channel->lock, flags);
+		}
+	}
+
+	if (result)
+		__give_some_channels(taken_channel_mask);
+
+	return result;
+}
+
+/*
+ * Internal. Determine if we can trigger an exi event.
+ */
+static inline int exi_can_trigger_event(struct exi_event *event)
+{
+	return !__try_take_some_channels(event->channel_mask, event->owner);
+}
+
+/*
+ * Internal. Finish an exi event invocation.
+ */
+static inline void exi_finish_event(struct exi_event *event)
+{
+	__give_some_channels(event->channel_mask);
+}
+
+/*
+ * Internal. Trigger an exi event.
+ */
+static inline int exi_trigger_event(struct exi_channel *exi_channel,
+				    struct exi_event *event)
+{
+	exi_event_handler_t handler;
+	int result = 0;
+
+	handler = event->handler;
+	if (handler)
+		result = handler(exi_channel, event->id, event->data);
+	return result;
+}
+
+/*
+ * Internal. Conditionally trigger an exi event.
+ */
+static void exi_cond_trigger_event(struct exi_channel *exi_channel,
+				   unsigned int event_id, int csr_mask)
+{
+	struct exi_event *event;
+	unsigned long flags;
+
+	if ((exi_channel->csr & csr_mask)) {
+		event = &exi_channel->events[event_id];
+		if (exi_can_trigger_event(event)) {
+			spin_lock_irqsave(&exi_channel->lock, flags);
+			exi_channel->csr &= ~csr_mask;
+			spin_unlock_irqrestore(&exi_channel->lock, flags);
+			exi_trigger_event(exi_channel, event);
+			exi_finish_event(event);
+		}
+	}
+
+	return;
+}
+
+/*
+ * Internal. Tasklet used to execute delayed work.
+ */
+static void exi_tasklet(unsigned long param)
+{
+	struct exi_channel *exi_channel = (struct exi_channel *)param;
+
+	DBG("channel=%d, csr=%08lx\n", exi_channel->channel, exi_channel->csr);
+
+	if (exi_channel->queued_cmd) {
+		DBG("tasklet while xfer in flight on channel %d, csr = %08lx\n",
+		    exi_channel->channel, exi_channel->csr);
+	}
+
+	/*
+	 * We won't lauch event handlers if any of the channels we
+	 * provided on event registration is in use.
+	 */
+
+	/*exi_cond_trigger_event(exi_channel, EXI_EVENT_TC, EXI_CSR_TCINT);*/
+	exi_cond_trigger_event(exi_channel, EXI_EVENT_IRQ, EXI_CSR_EXIINT);
+	exi_cond_trigger_event(exi_channel, EXI_EVENT_INSERT, EXI_CSR_EXTIN);
+}
+
+/*
+ * Internal. Interrupt handler for EXI interrupts.
+ */
+static irqreturn_t exi_irq_handler(int irq, void *dev_id)
+{
+	struct exi_channel *exi_channel;
+	u32 __iomem *csr_reg;
+	u32 csr, status, mask;
+	unsigned long flags;
+
+	exi_channel_for_each(exi_channel) {
+		csr_reg = exi_channel->io_base + EXI_CSR;
+
+		/*
+		 * Determine if we have pending interrupts on this channel,
+		 * and which ones.
+		 */
+		spin_lock_irqsave(&exi_channel->io_lock, flags);
+
+		csr = in_be32(csr_reg);
+		mask = csr & (EXI_CSR_EXTINMASK |
+			      EXI_CSR_TCINTMASK | EXI_CSR_EXIINTMASK);
+		status = csr & (mask << 1);
+		if (!status) {
+			spin_unlock_irqrestore(&exi_channel->io_lock, flags);
+			continue;
+		}
+
+		/* XXX do not signal TC events for now... */
+		exi_channel->csr |= (status & ~EXI_CSR_TCINT);
+
+		DBG("channel=%d, csr=%08lx\n", exi_channel->channel,
+		    exi_channel->csr);
+
+		/* ack all for this channel */
+		out_be32(csr_reg, csr | status);
+
+		spin_unlock_irqrestore(&exi_channel->io_lock, flags);
+
+		if ((status & EXI_CSR_TCINT))
+			exi_wait_for_transfer_one(exi_channel);
+		if ((status & EXI_CSR_EXTIN))
+			wake_up(&exi_bus_waitq);
+
+		if (exi_channel->csr && !exi_is_taken(exi_channel))
+			tasklet_schedule(&exi_channel->tasklet);
+	}
+	return IRQ_HANDLED;
+}
+
+/*
+ * Internal. Enable an exi event.
+ */
+static int exi_enable_event(struct exi_channel *exi_channel,
+			    unsigned int event_id)
+{
+	u32 __iomem *csr_reg = exi_channel->io_base + EXI_CSR;
+	u32 csr;
+	unsigned long flags;
+
+	spin_lock_irqsave(&exi_channel->io_lock, flags);
+	csr = in_be32(csr_reg);
+
+	/* ack and enable the associated interrupt */
+	switch (event_id) {
+	case EXI_EVENT_INSERT:
+		out_be32(csr_reg, csr | (EXI_CSR_EXTIN | EXI_CSR_EXTINMASK));
+		break;
+	case EXI_EVENT_TC:
+		/*out_be32(csr_reg,
+			   csr | (EXI_CSR_TCINT | EXI_CSR_TCINTMASK));*/
+		break;
+	case EXI_EVENT_IRQ:
+		out_be32(csr_reg, csr | (EXI_CSR_EXIINT | EXI_CSR_EXIINTMASK));
+		break;
+	}
+	spin_unlock_irqrestore(&exi_channel->io_lock, flags);
+	return 0;
+}
+
+/*
+ * Internal. Disable an exi event.
+ */
+static int exi_disable_event(struct exi_channel *exi_channel,
+			     unsigned int event_id)
+{
+	u32 __iomem *csr_reg = exi_channel->io_base + EXI_CSR;
+	u32 csr;
+	unsigned long flags;
+
+	spin_lock_irqsave(&exi_channel->io_lock, flags);
+	csr = in_be32(csr_reg);
+
+	/* ack and disable the associated interrupt */
+	switch (event_id) {
+	case EXI_EVENT_INSERT:
+		out_be32(csr_reg, (csr | EXI_CSR_EXTIN) & ~EXI_CSR_EXTINMASK);
+		break;
+	case EXI_EVENT_TC:
+		/*out_be32(csr_reg,
+			   (csr | EXI_CSR_TCINT) & ~EXI_CSR_TCINTMASK);*/
+		break;
+	case EXI_EVENT_IRQ:
+		out_be32(csr_reg, (csr | EXI_CSR_EXIINT) & ~EXI_CSR_EXIINTMASK);
+		break;
+	}
+	spin_unlock_irqrestore(&exi_channel->io_lock, flags);
+	return 0;
+}
+
+/**
+ *	exi_event_register  -  Registers an event on a given channel.
+ *	@exi_channel:	channel
+ *	@event_id:	event id
+ *	@handler:	event handler
+ *	@data:		data passed to event handler
+ *
+ *	Register a handler to be called whenever a specified event happens
+ *	on the given channel.
+ */
+int exi_event_register(struct exi_channel *exi_channel, unsigned int event_id,
+		       struct exi_device *exi_device,
+		       exi_event_handler_t handler, void *data,
+		       unsigned int channel_mask)
+{
+	struct exi_event *event;
+	int result = 0;
+
+	BUG_ON(event_id > EXI_MAX_EVENTS);
+
+	event = &exi_channel->events[event_id];
+
+	spin_lock(&exi_channel->lock);
+	if (event->handler) {
+		result = -EBUSY;
+		goto out;
+	}
+	event->owner = exi_device;
+	event->handler = handler;
+	event->data = data;
+	event->channel_mask = channel_mask;
+	exi_enable_event(exi_channel, event_id);
+
+out:
+	spin_unlock(&exi_channel->lock);
+	return result;
+}
+EXPORT_SYMBOL(exi_event_register);
+
+/**
+ *	exi_event_unregister  -  Unregisters an event on a given channel.
+ *	@exi_channel:	channel
+ *	@event_id:	event id
+ *
+ *	Unregister a previously registered event handler.
+ */
+int exi_event_unregister(struct exi_channel *exi_channel, unsigned int event_id)
+{
+	struct exi_event *event;
+	int result = 0;
+
+	BUG_ON(event_id > EXI_MAX_EVENTS);
+
+	event = &exi_channel->events[event_id];
+
+	spin_lock(&exi_channel->lock);
+	exi_disable_event(exi_channel, event_id);
+	event->owner = NULL;
+	event->handler = NULL;
+	event->data = NULL;
+	event->channel_mask = 0;
+	spin_unlock(&exi_channel->lock);
+
+	return result;
+}
+EXPORT_SYMBOL(exi_event_unregister);
+
+/*
+ * Internal. Quiesce a channel.
+ */
+static void exi_quiesce_channel(struct exi_channel *exi_channel, u32 csr_mask)
+{
+	/* wait for dma transfers to complete */
+	exi_wait_for_transfer_raw(exi_channel);
+
+	/* ack and mask all interrupts */
+	out_be32(exi_channel->io_base + EXI_CSR,
+		EXI_CSR_TCINT  | EXI_CSR_EXIINT | EXI_CSR_EXTIN | csr_mask);
+}
+
+/*
+ * Internal. Quiesce all channels.
+ */
+static void exi_quiesce_all_channels(u32 csr_mask)
+{
+	struct exi_channel *exi_channel;
+
+	exi_channel_for_each(exi_channel) {
+		exi_quiesce_channel(exi_channel, csr_mask);
+	}
+}
+
+/**
+ *	exi_get_id  -  Returns the EXI ID of a device
+ *	@exi_channel:	channel
+ *	@device:	device number on channel
+ *	@freq:		clock frequency index
+ *
+ *	Returns the EXI ID of an EXI device on a given channel.
+ *	Might sleep.
+ */
+u32 exi_get_id(struct exi_device *exi_device)
+{
+	struct exi_channel *exi_channel = exi_device->exi_channel;
+	u32 __iomem *csr_reg = exi_channel->io_base + EXI_CSR;
+	u32 id = EXI_ID_INVALID;
+	u16 cmd = 0;
+
+	/* ask for the EXI id */
+	exi_dev_take(exi_device);
+	exi_dev_select(exi_device);
+	exi_dev_write(exi_device, &cmd, sizeof(cmd));
+	exi_dev_read(exi_device, &id, sizeof(id));
+	exi_dev_deselect(exi_device);
+	exi_dev_give(exi_device);
+
+	/* "canonicalize" the id */
+	if (!id)
+		id = EXI_ID_INVALID;
+	/*
+	 * We return a EXI_ID_NONE if there is some unidentified device
+	 * inserted in memcard slot A or memcard slot B.
+	 * This, for example, allows the SD/MMC driver to see inserted cards.
+	 */
+	if (id == EXI_ID_INVALID) {
+		if ((__to_channel(exi_channel) == 0 ||
+		     __to_channel(exi_channel) == 1)
+		    && exi_device->eid.device == 0) {
+			if (in_be32(csr_reg) & EXI_CSR_EXT)
+				id = EXI_ID_NONE;
+		}
+	}
+
+	return id;
+}
+EXPORT_SYMBOL(exi_get_id);
+
+/*
+ * Tells if there is a device inserted in one of the memory card slots.
+ */
+int exi_get_ext_line(struct exi_channel *exi_channel)
+{
+	u32 __iomem *csr_reg = exi_channel->io_base + EXI_CSR;
+	return (in_be32(csr_reg) & EXI_CSR_EXT) ? 1 : 0;
+}
+
+/*
+ * Saves the current insertion status of a given channel.
+ */
+void exi_update_ext_status(struct exi_channel *exi_channel)
+{
+	if (exi_get_ext_line(exi_channel))
+		exi_channel->flags |= EXI_EXT;
+	else
+		exi_channel->flags &= ~EXI_EXT;
+}
+
+void exi_hw_quiesce(void)
+{
+	exi_quiesce_all_channels(0);
+}
+
+/*
+ * Pseudo-Internal. Initialize basic channel structures and hardware.
+ */
+int exi_hw_init(char *module_name, struct resource *mem, unsigned int irq)
+{
+	struct exi_channel *exi_channel;
+	int channel;
+	int result;
+
+	exi_io_mem = ioremap(mem->start, mem->end - mem->start + 1);
+	if (!exi_io_mem) {
+		drv_printk(KERN_ERR, "ioremap failed\n");
+		return -ENOMEM;
+	}
+
+	for (channel = 0; channel < EXI_MAX_CHANNELS; channel++) {
+		exi_channel = __to_exi_channel(channel);
+
+		/* initialize a channel structure */
+		exi_channel_init(exi_channel, channel);
+	}
+
+	/* calm down the hardware and allow extractions */
+	exi_quiesce_all_channels(EXI_CSR_EXTINMASK);
+
+	/* register the exi interrupt handler */
+	result = request_irq(irq, exi_irq_handler, 0, module_name, NULL);
+	if (result)
+		drv_printk(KERN_ERR, "failed to register IRQ %d\n", irq);
+
+	return result;
+}
+
+/*
+ * Pseudo-Internal.
+ */
+void exi_hw_exit(struct resource *mem, unsigned int irq)
+{
+	exi_quiesce_all_channels(0);
+	iounmap(exi_io_mem);
+	free_irq(irq, NULL);
+}
diff --git a/drivers/exi/exi-hw.h b/drivers/exi/exi-hw.h
new file mode 100644
index 0000000..d2bd316
--- /dev/null
+++ b/drivers/exi/exi-hw.h
@@ -0,0 +1,197 @@
+/*
+ * drivers/exi/exi-hw.h
+ *
+ * Nintendo GameCube EXpansion Interface support. Hardware routines.
+ * Copyright (C) 2004-2009 The GameCube Linux Team
+ * Copyright (C) 2004,2005 Todd Jeffreys <todd@voidpointer.org>
+ * Copyright (C) 2005,2006,2007,2008,2009 Albert Herranz
+ *
+ * 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.
+ *
+ */
+
+#ifndef __EXI_HW_H
+#define __EXI_HW_H
+
+#include <linux/exi.h>
+#include <linux/interrupt.h>
+#include <linux/resource.h>
+#include <asm/atomic.h>
+
+
+#define EXI_MAX_CHANNELS	3  /* channels on the EXI bus */
+#define EXI_DEVICES_PER_CHANNEL	3  /* number of devices per EXI channel */
+#define EXI_MAX_EVENTS		3  /* types of events on the EXI bus */
+
+#define EXI_CLK_1MHZ		0
+#define EXI_CLK_2MHZ		1
+#define EXI_CLK_4MHZ		2
+#define EXI_CLK_8MHZ		3
+#define EXI_CLK_16MHZ		4
+#define EXI_CLK_32MHZ		5
+
+#define EXI_MAX_FREQ		7
+#define EXI_FREQ_SCAN		EXI_CLK_8MHZ
+
+#define EXI_READ		0
+#define EXI_WRITE		1
+
+#define EXI_IDI_MAX_SIZE	4
+
+
+#define EXI_DMA_ALIGN		0x1f /* 32 bytes */
+
+#define EXI_CHANNEL_SPACING	0x14
+
+#define EXI_CSR			0x00
+#define   EXI_CSR_EXIINTMASK	(1<<0)
+#define   EXI_CSR_EXIINT	(1<<1)
+#define   EXI_CSR_TCINTMASK	(1<<2)
+#define   EXI_CSR_TCINT		(1<<3)
+#define   EXI_CSR_CLKMASK	(0x7<<4)
+#define     EXI_CSR_CLK_1MHZ	(EXI_CLK_1MHZ<<4)
+#define     EXI_CSR_CLK_2MHZ	(EXI_CLK_2MHZ<<4)
+#define     EXI_CSR_CLK_4MHZ	(EXI_CLK_4MHZ<<4)
+#define     EXI_CSR_CLK_8MHZ	(EXI_CLK_8MHZ<<4)
+#define     EXI_CSR_CLK_16MHZ	(EXI_CLK_16MHZ<<4)
+#define     EXI_CSR_CLK_32MHZ	(EXI_CLK_32MHZ<<4)
+#define   EXI_CSR_CSMASK	(0x7<<7)
+#define     EXI_CSR_CS_0	(0x1<<7)  /* Chip Select 001 */
+#define     EXI_CSR_CS_1	(0x2<<7)  /* Chip Select 010 */
+#define     EXI_CSR_CS_2	(0x4<<7)  /* Chip Select 100 */
+#define   EXI_CSR_EXTINMASK	(1<<10)
+#define   EXI_CSR_EXTIN		(1<<11)
+#define   EXI_CSR_EXT		(1<<12)
+
+#define EXI_MAR			0x04
+
+#define EXI_LENGTH		0x08
+
+#define EXI_CR			0x0c
+#define   EXI_CR_TSTART		(1<<0)
+#define   EXI_CR_DMA		(1<<1)
+#define   EXI_CR_READ		(0<<2)
+#define   EXI_CR_WRITE		(1<<2)
+#define   EXI_CR_READ_WRITE	(2<<2)
+#define   EXI_CR_TLEN(len)	(((len)-1)<<4)
+
+#define EXI_DATA		0x10
+
+enum {
+	__EXI_DMABUSY = 0,
+	__EXI_EXT,
+};
+
+/*
+ * For registering event handlers with the exi layer.
+ */
+struct exi_event {
+	int			id;		/* event id */
+	struct exi_device	*owner;		/* device owning of the event */
+	exi_event_handler_t	handler;
+	void			*data;
+	unsigned int		channel_mask;	/* channels used by handler */
+};
+
+/*
+ * This structure represents an exi channel.
+ */
+struct exi_channel {
+	spinlock_t		lock;		/* misc channel lock */
+
+	int			channel;
+	unsigned long		flags;
+#define EXI_DMABUSY	(1<<__EXI_DMABUSY)
+#define EXI_EXT		(1<<__EXI_EXT)
+
+	spinlock_t		io_lock;	/* serializes access to CSR */
+	void __iomem		*io_base;
+
+	struct exi_device	*owner;
+	wait_queue_head_t	wait_queue;
+
+	struct exi_command	*queued_cmd;
+	struct exi_command	post_cmd;
+
+	unsigned long		csr;
+	struct tasklet_struct	tasklet;
+
+	unsigned long		stats_idi_xfers;
+	unsigned long		stats_dma_xfers;
+	unsigned long		stats_xfers;
+
+	struct exi_event	events[EXI_MAX_EVENTS];
+};
+
+extern struct exi_device *exi_channel_owner(struct exi_channel *exi_channel);
+extern int exi_get_ext_line(struct exi_channel *exi_channel);
+extern void exi_update_ext_status(struct exi_channel *exi_channel);
+
+extern void exi_hw_quiesce(void);
+
+extern int exi_hw_init(char *name, struct resource *mem, unsigned int irq);
+extern void exi_hw_exit(struct resource *mem, unsigned int irq);
+
+#define exi_is_taken(x) ((x)->owner)
+
+/*
+ * Internal.
+ * Declare simple transfer functions for single bytes, words and dwords,
+ * and build a general transfer function based on that.
+ */
+#define __declare__exi_transfer_raw(_type, _val, _data, _on_write, _on_read) \
+static inline void __exi_transfer_raw_##_type(struct exi_channel *exi_channel,\
+					      _type * _data, int mode)	\
+{									\
+	u32 __iomem *csr_reg = exi_channel->io_base + EXI_CSR;		\
+	u32 __iomem *data_reg = exi_channel->io_base + EXI_DATA;	\
+	u32 __iomem *cr_reg = exi_channel->io_base + EXI_CR;		\
+	u32 _val = ~0;							\
+	unsigned long flags;						\
+									\
+	/*								\
+	 * On reads we write too some known value to EXIxDATA because	\
+	 * information currently stored there is leaked to the		\
+	 * MOSI line, confusing some hardware.				\
+	 */								\
+	if (((mode&0xf) != EXI_OP_READ)) /* write or read-write */	\
+		_on_write;						\
+	out_be32(data_reg, _val);					\
+									\
+	/* start transfer */						\
+	_val = EXI_CR_TSTART | EXI_CR_TLEN(sizeof(_type)) | (mode&0xf);	\
+	out_be32(cr_reg, _val);						\
+									\
+	/* wait for transfer completion */				\
+	while (in_be32(cr_reg) & EXI_CR_TSTART)				\
+		cpu_relax();						\
+									\
+	/* XXX check if we need that on immediate mode */		\
+	/* assert transfer complete interrupt */			\
+	spin_lock_irqsave(&exi_channel->io_lock, flags);		\
+	out_be32(csr_reg, in_be32(csr_reg) | EXI_CSR_TCINT);		\
+	spin_unlock_irqrestore(&exi_channel->io_lock, flags);		\
+									\
+	if ((mode&0xf) != EXI_OP_WRITE) { /* read or read-write */	\
+		_val = in_be32(data_reg);				\
+		_on_read;						\
+	}								\
+}
+
+#define __declare__exi_transfer_raw_simple(_type) \
+__declare__exi_transfer_raw(						\
+		_type, _v, _d,						\
+		_v = *(_d) << (32 - (8*sizeof(_type))),			\
+		*(_d) = (_type)(_v >> (32 - (8*sizeof(_type))))		\
+	)
+
+__declare__exi_transfer_raw_simple(u8)
+__declare__exi_transfer_raw_simple(u16)
+__declare__exi_transfer_raw_simple(u32)
+
+extern wait_queue_head_t exi_bus_waitq;
+
+#endif /* __EXI_HW_H */
diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index 50de0f5..9091f6b 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -1248,6 +1248,24 @@ fail:
 }
 EXPORT_SYMBOL_GPL(gpio_direction_output);
 
+/**
+ * gpio_direction_is_output - tell if a gpio is configured as an output
+ * @gpio: gpio in question
+ *
+ * Returns a negative errno if the given gpio is not valid.
+ * Returns a positive non-zero if the gpio is configured as an output.
+ * Returns zero otherwise.
+ */
+int gpio_direction_is_output(unsigned gpio)
+{
+	struct gpio_desc *desc = &gpio_desc[gpio];
+
+	if (!gpio_is_valid(gpio))
+		return -EINVAL;
+
+	return test_bit(FLAG_IS_OUT, &desc->flags);
+}
+EXPORT_SYMBOL_GPL(gpio_direction_is_output);
 
 /* I/O calls are only valid after configuration completed; the relevant
  * "is this a valid GPIO" error checks should already have been done.
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 2dd2ce9..6d02df8 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -354,14 +354,26 @@ config I2C_DESIGNWARE
 	  This driver can also be built as a module.  If so, the module
 	  will be called i2c-designware.
 
-config I2C_GPIO
-	tristate "GPIO-based bitbanging I2C"
+config I2C_GPIO_COMMON
+	tristate
 	depends on GENERIC_GPIO
 	select I2C_ALGOBIT
+	default n
+
+config I2C_GPIO
+	tristate "GPIO-based bitbanging I2C"
+	select I2C_GPIO_COMMON
 	help
 	  This is a very simple bitbanging I2C driver utilizing the
 	  arch-neutral GPIO API to control the SCL and SDA lines.
 
+config I2C_GPIO_OF
+	tristate "GPIO-based bitbanging I2C driver with OF bindings"
+	select I2C_GPIO_COMMON
+	help
+	  This option allows the declaration of GPIO-based I2C devices
+	  using a device tree source.
+
 config I2C_HIGHLANDER
 	tristate "Highlander FPGA SMBus interface"
 	depends on SH_HIGHLANDER
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index ff937ac..1e68c5b 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -34,7 +34,9 @@ obj-$(CONFIG_I2C_BLACKFIN_TWI)	+= i2c-bfin-twi.o
 obj-$(CONFIG_I2C_CPM)		+= i2c-cpm.o
 obj-$(CONFIG_I2C_DAVINCI)	+= i2c-davinci.o
 obj-$(CONFIG_I2C_DESIGNWARE)	+= i2c-designware.o
+obj-$(CONFIG_I2C_GPIO_COMMON)	+= i2c-gpio-common.o
 obj-$(CONFIG_I2C_GPIO)		+= i2c-gpio.o
+obj-$(CONFIG_I2C_GPIO_OF)	+= i2c-gpio-of.o
 obj-$(CONFIG_I2C_HIGHLANDER)	+= i2c-highlander.o
 obj-$(CONFIG_I2C_IBM_IIC)	+= i2c-ibm_iic.o
 obj-$(CONFIG_I2C_IMX)		+= i2c-imx.o
diff --git a/drivers/i2c/busses/i2c-gpio-common.c b/drivers/i2c/busses/i2c-gpio-common.c
new file mode 100644
index 0000000..641f5b5
--- /dev/null
+++ b/drivers/i2c/busses/i2c-gpio-common.c
@@ -0,0 +1,213 @@
+/*
+ * Core logic for the bitbanging I2C bus driver using the GPIO API
+ *
+ * Copyright (C) 2007 Atmel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include "i2c-gpio-common.h"
+
+#include <linux/i2c-algo-bit.h>
+#include <linux/gpio.h>
+
+/* Toggle SDA by changing the direction of the pin */
+static void i2c_gpio_setsda_dir(void *data, int state)
+{
+	struct i2c_gpio_platform_data *pdata = data;
+
+	if (state)
+		gpio_direction_input(pdata->sda_pin);
+	else
+		gpio_direction_output(pdata->sda_pin, 0);
+}
+
+/*
+ * Toggle SDA by changing the output value of the pin. This is only
+ * valid for pins configured as open drain (i.e. setting the value
+ * high effectively turns off the output driver.)
+ */
+static void i2c_gpio_setsda_val(void *data, int state)
+{
+	struct i2c_gpio_platform_data *pdata = data;
+
+	gpio_set_value(pdata->sda_pin, state);
+}
+
+/*
+ * Toggle SDA by changing the output value of the pin, first making sure
+ * that the pin is configured as an output.
+ */
+static void i2c_gpio_setsda_val_dir(void *data, int state)
+{
+	struct i2c_gpio_platform_data *pdata = data;
+
+	if (!gpio_direction_is_output(pdata->sda_pin))
+		gpio_direction_output(pdata->sda_pin, state);
+	else
+		gpio_set_value(pdata->sda_pin, state);
+}
+
+/* Toggle SCL by changing the direction of the pin. */
+static void i2c_gpio_setscl_dir(void *data, int state)
+{
+	struct i2c_gpio_platform_data *pdata = data;
+
+	if (state)
+		gpio_direction_input(pdata->scl_pin);
+	else
+		gpio_direction_output(pdata->scl_pin, 0);
+}
+
+/*
+ * Toggle SCL by changing the output value of the pin. This is used
+ * for pins that are configured as open drain and for output-only
+ * pins. The latter case will break the i2c protocol, but it will
+ * often work in practice.
+ */
+static void i2c_gpio_setscl_val(void *data, int state)
+{
+	struct i2c_gpio_platform_data *pdata = data;
+
+	gpio_set_value(pdata->scl_pin, state);
+}
+
+static int i2c_gpio_getsda(void *data)
+{
+	struct i2c_gpio_platform_data *pdata = data;
+
+	return gpio_get_value(pdata->sda_pin);
+}
+
+/*
+ * Read SDA value from the pin, first making sure that the pin is
+ * configured as an input.
+ */
+static int i2c_gpio_getsda_val_dir(void *data)
+{
+	struct i2c_gpio_platform_data *pdata = data;
+
+	if (gpio_direction_is_output(pdata->sda_pin))
+		gpio_direction_input(pdata->sda_pin);
+	return gpio_get_value(pdata->sda_pin);
+}
+
+static int i2c_gpio_getscl(void *data)
+{
+	struct i2c_gpio_platform_data *pdata = data;
+
+	return gpio_get_value(pdata->scl_pin);
+}
+
+int i2c_gpio_adapter_probe(struct i2c_adapter *adap,
+			   struct i2c_gpio_platform_data *pdata,
+			   struct device *parent, int id,
+			   struct module *owner)
+{
+	struct i2c_algo_bit_data *bit_data;
+	int error;
+
+	error = -ENOMEM;
+	bit_data = kzalloc(sizeof(*bit_data), GFP_KERNEL);
+	if (!bit_data)
+		goto err_alloc_bit_data;
+
+	error = gpio_request(pdata->sda_pin, "sda");
+	if (error)
+		goto err_request_sda;
+	error = gpio_request(pdata->scl_pin, "scl");
+	if (error)
+		goto err_request_scl;
+
+	if (pdata->sda_is_open_drain) {
+		gpio_direction_output(pdata->sda_pin, 1);
+		if (pdata->sda_enforce_dir)
+			bit_data->setsda = i2c_gpio_setsda_val_dir;
+		else
+			bit_data->setsda = i2c_gpio_setsda_val;
+	} else {
+		gpio_direction_input(pdata->sda_pin);
+		bit_data->setsda = i2c_gpio_setsda_dir;
+	}
+
+	if (pdata->scl_is_open_drain || pdata->scl_is_output_only) {
+		gpio_direction_output(pdata->scl_pin, 1);
+		bit_data->setscl = i2c_gpio_setscl_val;
+	} else {
+		gpio_direction_input(pdata->scl_pin);
+		bit_data->setscl = i2c_gpio_setscl_dir;
+	}
+
+	if (!pdata->scl_is_output_only)
+		bit_data->getscl = i2c_gpio_getscl;
+	if (pdata->sda_enforce_dir)
+		bit_data->getsda = i2c_gpio_getsda_val_dir;
+	else
+		bit_data->getsda = i2c_gpio_getsda;
+
+	if (pdata->udelay)
+		bit_data->udelay = pdata->udelay;
+	else if (pdata->scl_is_output_only)
+		bit_data->udelay = 50;			/* 10 kHz */
+	else
+		bit_data->udelay = 5;			/* 100 kHz */
+
+	if (pdata->timeout)
+		bit_data->timeout = pdata->timeout;
+	else
+		bit_data->timeout = HZ / 10;		/* 100 ms */
+
+	bit_data->data = pdata;
+
+	adap->owner = owner;
+	snprintf(adap->name, sizeof(adap->name), "i2c-gpio%d", id);
+	adap->algo_data = bit_data;
+	adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
+	adap->dev.parent = parent;
+
+	/*
+	 * If "id" is negative we consider it as zero.
+	 * The reason to do so is to avoid sysfs names that only make
+	 * sense when there are multiple adapters.
+	 */
+	adap->nr = (id != -1) ? id : 0;
+	error = i2c_bit_add_numbered_bus(adap);
+	if (error)
+		goto err_add_bus;
+
+	dev_info(parent, "using pins %u (SDA) and %u (SCL%s)\n",
+		 pdata->sda_pin, pdata->scl_pin,
+		 pdata->scl_is_output_only
+		 ? ", no clock stretching" : "");
+
+	return 0;
+
+err_add_bus:
+	gpio_free(pdata->scl_pin);
+err_request_scl:
+	gpio_free(pdata->sda_pin);
+err_request_sda:
+	kfree(bit_data);
+err_alloc_bit_data:
+	return error;
+}
+EXPORT_SYMBOL(i2c_gpio_adapter_probe);
+
+int i2c_gpio_adapter_remove(struct i2c_adapter *adap,
+			    struct i2c_gpio_platform_data *pdata)
+{
+	i2c_del_adapter(adap);
+	gpio_free(pdata->scl_pin);
+	gpio_free(pdata->sda_pin);
+	kfree(adap->algo_data);
+	kfree(adap);
+
+	return 0;
+}
+EXPORT_SYMBOL(i2c_gpio_adapter_remove);
+
+MODULE_AUTHOR("Haavard Skinnemoen <hskinnemoen@atmel.com>");
+MODULE_DESCRIPTION("Platform-independent bitbanging I2C driver common logic");
+MODULE_LICENSE("GPL");
diff --git a/drivers/i2c/busses/i2c-gpio-common.h b/drivers/i2c/busses/i2c-gpio-common.h
new file mode 100644
index 0000000..888a3e8
--- /dev/null
+++ b/drivers/i2c/busses/i2c-gpio-common.h
@@ -0,0 +1,25 @@
+/*
+ * Bitbanging I2C bus driver using the GPIO API
+ *
+ * Copyright (C) 2007 Atmel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef __I2C_GPIO_COMMON_H
+#define __I2C_GPIO_COMMON_H
+
+#include <linux/i2c.h>
+#include <linux/i2c-gpio.h>
+#include <linux/module.h>
+#include <linux/device.h>
+
+int i2c_gpio_adapter_probe(struct i2c_adapter *adap,
+			   struct i2c_gpio_platform_data *pdata,
+			   struct device *parent, int id,
+			   struct module *owner);
+int i2c_gpio_adapter_remove(struct i2c_adapter *adap,
+			    struct i2c_gpio_platform_data *pdata);
+
+#endif /* __I2C_GPIO_COMMON_H */
diff --git a/drivers/i2c/busses/i2c-gpio-of.c b/drivers/i2c/busses/i2c-gpio-of.c
new file mode 100644
index 0000000..4813f07
--- /dev/null
+++ b/drivers/i2c/busses/i2c-gpio-of.c
@@ -0,0 +1,153 @@
+/*
+ * drivers/i2c/busses/i2c-gpio-of.c
+ *
+ * GPIO-based bitbanging I2C driver with OF bindings
+ * Copyright (C) 2009 Albert Herranz
+ *
+ * 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 "i2c-gpio-common.h"
+
+#include <linux/of_platform.h>
+#include <linux/of_i2c.h>
+#include <linux/of_gpio.h>
+#include <linux/i2c-algo-bit.h>
+#include <linux/init.h>
+#include <linux/module.h>
+
+#define DRV_MODULE_NAME	"i2c-gpio-of"
+#define DRV_DESCRIPTION	"GPIO-based bitbanging I2C driver with OF bindings"
+#define DRV_AUTHOR	"Albert Herranz"
+
+#define drv_printk(level, format, arg...) \
+	printk(level DRV_MODULE_NAME ": " format , ## arg)
+
+/*
+ * OF platform bindings.
+ *
+ */
+
+static int __devinit i2c_gpio_of_probe(struct of_device *odev,
+				       const struct of_device_id *match)
+{
+	struct i2c_gpio_platform_data *pdata;
+	struct i2c_adapter *adap;
+	const unsigned long *prop;
+	int sda_pin, scl_pin;
+	int error = -ENOMEM;
+
+	pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
+	if (!pdata)
+		goto err_alloc_pdata;
+	adap = kzalloc(sizeof(*adap), GFP_KERNEL);
+	if (!adap)
+		goto err_alloc_adap;
+
+	sda_pin = of_get_gpio(odev->node, 0);
+	scl_pin = of_get_gpio(odev->node, 1);
+	if (sda_pin < 0 || scl_pin < 0) {
+		error = -EINVAL;
+		pr_err("%s: invalid GPIO pins, sda=%d/scl=%d\n",
+			 odev->node->full_name, sda_pin, scl_pin);
+		goto err_gpio_pin;
+	}
+
+	pdata->sda_pin = sda_pin;
+	pdata->scl_pin = scl_pin;
+
+	prop = of_get_property(odev->node, "sda-is-open-drain", NULL);
+	if (prop)
+		pdata->sda_is_open_drain = *prop;
+	prop = of_get_property(odev->node, "sda-enforce-dir", NULL);
+	if (prop)
+		pdata->sda_enforce_dir = *prop;
+	prop = of_get_property(odev->node, "scl-is-open-drain", NULL);
+	if (prop)
+		pdata->scl_is_open_drain = *prop;
+	prop = of_get_property(odev->node, "scl-is-output-only", NULL);
+	if (prop)
+		pdata->scl_is_output_only = *prop;
+	prop = of_get_property(odev->node, "udelay", NULL);
+	if (prop)
+		pdata->udelay = *prop;
+	prop = of_get_property(odev->node, "timeout", NULL);
+	if (prop)
+		pdata->timeout =  msecs_to_jiffies(*prop);
+
+	error = i2c_gpio_adapter_probe(adap, pdata, &odev->dev,
+				       odev->node->node, THIS_MODULE);
+	if (error)
+		goto err_probe;
+
+	dev_set_drvdata(&odev->dev, adap);
+
+	of_register_i2c_devices(adap, odev->node);
+
+	return 0;
+
+err_probe:
+err_gpio_pin:
+	kfree(adap);
+err_alloc_adap:
+	kfree(pdata);
+err_alloc_pdata:
+	return error;
+};
+
+static int __devexit i2c_gpio_of_remove(struct of_device *odev)
+{
+	struct i2c_gpio_platform_data *pdata;
+	struct i2c_adapter *adap;
+	struct i2c_algo_bit_data *bit_data;
+
+	adap = dev_get_drvdata(&odev->dev);
+	bit_data = adap->algo_data;
+	pdata = bit_data->data;
+
+	i2c_gpio_adapter_remove(adap, pdata);
+	kfree(pdata);
+	kfree(adap);
+	return 0;
+};
+
+static const struct of_device_id i2c_gpio_of_match[] = {
+	{.compatible = "virtual,i2c-gpio",},
+	{},
+};
+MODULE_DEVICE_TABLE(of, i2c_gpio_of_match);
+
+static struct of_platform_driver i2c_gpio_of_driver = {
+	.match_table	= i2c_gpio_of_match,
+	.probe		= i2c_gpio_of_probe,
+	.remove		= __devexit_p(i2c_gpio_of_remove),
+	.driver		= {
+		.owner	= THIS_MODULE,
+		.name	= DRV_MODULE_NAME,
+	},
+};
+
+static int __init i2c_gpio_of_init(void)
+{
+	int error;
+
+	error = of_register_platform_driver(&i2c_gpio_of_driver);
+	if (error)
+		drv_printk(KERN_ERR, "OF registration failed (%d)\n", error);
+
+	return error;
+}
+
+static void __exit i2c_gpio_of_exit(void)
+{
+	of_unregister_platform_driver(&i2c_gpio_of_driver);
+}
+
+module_init(i2c_gpio_of_init);
+module_exit(i2c_gpio_of_exit);
+
+MODULE_DESCRIPTION(DRV_DESCRIPTION);
+MODULE_AUTHOR(DRV_AUTHOR);
+MODULE_LICENSE("GPL");
diff --git a/drivers/i2c/busses/i2c-gpio.c b/drivers/i2c/busses/i2c-gpio.c
index 32104ea..4cc3697 100644
--- a/drivers/i2c/busses/i2c-gpio.c
+++ b/drivers/i2c/busses/i2c-gpio.c
@@ -7,187 +7,56 @@
  * it under the terms of the GNU General Public License version 2 as
  * published by the Free Software Foundation.
  */
-#include <linux/i2c.h>
-#include <linux/i2c-algo-bit.h>
-#include <linux/i2c-gpio.h>
-#include <linux/init.h>
-#include <linux/module.h>
-#include <linux/platform_device.h>
 
-#include <asm/gpio.h>
+#include "i2c-gpio-common.h"
 
-/* Toggle SDA by changing the direction of the pin */
-static void i2c_gpio_setsda_dir(void *data, int state)
-{
-	struct i2c_gpio_platform_data *pdata = data;
-
-	if (state)
-		gpio_direction_input(pdata->sda_pin);
-	else
-		gpio_direction_output(pdata->sda_pin, 0);
-}
-
-/*
- * Toggle SDA by changing the output value of the pin. This is only
- * valid for pins configured as open drain (i.e. setting the value
- * high effectively turns off the output driver.)
- */
-static void i2c_gpio_setsda_val(void *data, int state)
-{
-	struct i2c_gpio_platform_data *pdata = data;
-
-	gpio_set_value(pdata->sda_pin, state);
-}
-
-/* Toggle SCL by changing the direction of the pin. */
-static void i2c_gpio_setscl_dir(void *data, int state)
-{
-	struct i2c_gpio_platform_data *pdata = data;
-
-	if (state)
-		gpio_direction_input(pdata->scl_pin);
-	else
-		gpio_direction_output(pdata->scl_pin, 0);
-}
+#include <linux/init.h>
+#include <linux/platform_device.h>
 
 /*
- * Toggle SCL by changing the output value of the pin. This is used
- * for pins that are configured as open drain and for output-only
- * pins. The latter case will break the i2c protocol, but it will
- * often work in practice.
+ * Platform bindings.
+ *
  */
-static void i2c_gpio_setscl_val(void *data, int state)
-{
-	struct i2c_gpio_platform_data *pdata = data;
-
-	gpio_set_value(pdata->scl_pin, state);
-}
-
-static int i2c_gpio_getsda(void *data)
-{
-	struct i2c_gpio_platform_data *pdata = data;
-
-	return gpio_get_value(pdata->sda_pin);
-}
-
-static int i2c_gpio_getscl(void *data)
-{
-	struct i2c_gpio_platform_data *pdata = data;
-
-	return gpio_get_value(pdata->scl_pin);
-}
 
 static int __devinit i2c_gpio_probe(struct platform_device *pdev)
 {
 	struct i2c_gpio_platform_data *pdata;
-	struct i2c_algo_bit_data *bit_data;
 	struct i2c_adapter *adap;
-	int ret;
+	int error;
 
 	pdata = pdev->dev.platform_data;
 	if (!pdata)
 		return -ENXIO;
 
-	ret = -ENOMEM;
-	adap = kzalloc(sizeof(struct i2c_adapter), GFP_KERNEL);
+	error = -ENOMEM;
+	adap = kzalloc(sizeof(*adap), GFP_KERNEL);
 	if (!adap)
 		goto err_alloc_adap;
-	bit_data = kzalloc(sizeof(struct i2c_algo_bit_data), GFP_KERNEL);
-	if (!bit_data)
-		goto err_alloc_bit_data;
-
-	ret = gpio_request(pdata->sda_pin, "sda");
-	if (ret)
-		goto err_request_sda;
-	ret = gpio_request(pdata->scl_pin, "scl");
-	if (ret)
-		goto err_request_scl;
-
-	if (pdata->sda_is_open_drain) {
-		gpio_direction_output(pdata->sda_pin, 1);
-		bit_data->setsda = i2c_gpio_setsda_val;
-	} else {
-		gpio_direction_input(pdata->sda_pin);
-		bit_data->setsda = i2c_gpio_setsda_dir;
-	}
-
-	if (pdata->scl_is_open_drain || pdata->scl_is_output_only) {
-		gpio_direction_output(pdata->scl_pin, 1);
-		bit_data->setscl = i2c_gpio_setscl_val;
-	} else {
-		gpio_direction_input(pdata->scl_pin);
-		bit_data->setscl = i2c_gpio_setscl_dir;
-	}
-
-	if (!pdata->scl_is_output_only)
-		bit_data->getscl = i2c_gpio_getscl;
-	bit_data->getsda = i2c_gpio_getsda;
-
-	if (pdata->udelay)
-		bit_data->udelay = pdata->udelay;
-	else if (pdata->scl_is_output_only)
-		bit_data->udelay = 50;			/* 10 kHz */
-	else
-		bit_data->udelay = 5;			/* 100 kHz */
-
-	if (pdata->timeout)
-		bit_data->timeout = pdata->timeout;
-	else
-		bit_data->timeout = HZ / 10;		/* 100 ms */
-
-	bit_data->data = pdata;
-
-	adap->owner = THIS_MODULE;
-	snprintf(adap->name, sizeof(adap->name), "i2c-gpio%d", pdev->id);
-	adap->algo_data = bit_data;
-	adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
-	adap->dev.parent = &pdev->dev;
-
-	/*
-	 * If "dev->id" is negative we consider it as zero.
-	 * The reason to do so is to avoid sysfs names that only make
-	 * sense when there are multiple adapters.
-	 */
-	adap->nr = (pdev->id != -1) ? pdev->id : 0;
-	ret = i2c_bit_add_numbered_bus(adap);
-	if (ret)
-		goto err_add_bus;
+	error = i2c_gpio_adapter_probe(adap, pdata, &pdev->dev, pdev->id,
+				       THIS_MODULE);
+	if (error)
+		goto err_probe;
 
 	platform_set_drvdata(pdev, adap);
 
-	dev_info(&pdev->dev, "using pins %u (SDA) and %u (SCL%s)\n",
-		 pdata->sda_pin, pdata->scl_pin,
-		 pdata->scl_is_output_only
-		 ? ", no clock stretching" : "");
-
 	return 0;
 
-err_add_bus:
-	gpio_free(pdata->scl_pin);
-err_request_scl:
-	gpio_free(pdata->sda_pin);
-err_request_sda:
-	kfree(bit_data);
-err_alloc_bit_data:
+err_probe:
 	kfree(adap);
 err_alloc_adap:
-	return ret;
+	return error;
 }
 
 static int __devexit i2c_gpio_remove(struct platform_device *pdev)
 {
-	struct i2c_gpio_platform_data *pdata;
 	struct i2c_adapter *adap;
+	struct i2c_gpio_platform_data *pdata;
 
 	adap = platform_get_drvdata(pdev);
 	pdata = pdev->dev.platform_data;
 
-	i2c_del_adapter(adap);
-	gpio_free(pdata->scl_pin);
-	gpio_free(pdata->sda_pin);
-	kfree(adap->algo_data);
+	i2c_gpio_adapter_remove(adap, pdata);
 	kfree(adap);
-
 	return 0;
 }
 
@@ -202,13 +71,13 @@ static struct platform_driver i2c_gpio_driver = {
 
 static int __init i2c_gpio_init(void)
 {
-	int ret;
+	int error;
 
-	ret = platform_driver_register(&i2c_gpio_driver);
-	if (ret)
-		printk(KERN_ERR "i2c-gpio: probe failed: %d\n", ret);
+	error = platform_driver_register(&i2c_gpio_driver);
+	if (error)
+		printk(KERN_ERR "i2c-gpio: registration failed (%d)\n", error);
 
-	return ret;
+	return error;
 }
 module_init(i2c_gpio_init);
 
diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig
index cd50c00..03628fb 100644
--- a/drivers/input/Kconfig
+++ b/drivers/input/Kconfig
@@ -180,6 +180,8 @@ source "drivers/input/serio/Kconfig"
 
 source "drivers/input/gameport/Kconfig"
 
+source "drivers/input/si/Kconfig"
+
 endmenu
 
 endmenu
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index ee98b1b..5e8718e 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -351,6 +351,17 @@ config KEYBOARD_SUNKBD
 	  To compile this driver as a module, choose M here: the
 	  module will be called sunkbd.
 
+config KEYBOARD_WII
+	tristate "Nintendo Wii USB keyboard IOS glue"
+	depends on (STARLET_IOS && !USB)
+	help
+	  Say Y here if you have a Nintendo Wii console running Linux and have
+	  a keyboard attached to one of its USB ports.
+	  This driver uses the IOS interface glue to access the USB keyboard.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called rvl-stkbd.
+
 config KEYBOARD_SH_KEYSC
 	tristate "SuperH KEYSC keypad support"
 	depends on SUPERH
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index babad5e..3b91336 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -37,3 +37,4 @@ obj-$(CONFIG_KEYBOARD_TOSA)		+= tosakbd.o
 obj-$(CONFIG_KEYBOARD_TWL4030)		+= twl4030_keypad.o
 obj-$(CONFIG_KEYBOARD_XTKBD)		+= xtkbd.o
 obj-$(CONFIG_KEYBOARD_W90P910)		+= w90p910_keypad.o
+obj-$(CONFIG_KEYBOARD_WII)		+= rvl-stkbd.o
diff --git a/drivers/input/keyboard/rvl-stkbd.c b/drivers/input/keyboard/rvl-stkbd.c
new file mode 100644
index 0000000..c6a259a
--- /dev/null
+++ b/drivers/input/keyboard/rvl-stkbd.c
@@ -0,0 +1,512 @@
+/*
+ * drivers/input/keyboard/rvl-stkbd.c
+ *
+ * Nintendo Wii starlet keyboard driver.
+ * Copyright (C) 2008-2009 The GameCube Linux Team
+ * Copyright (C) 2008,2009 Albert Herranz
+ *
+ * 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.
+ *
+ */
+
+/*
+ * NOTES:
+ * The keyboard driver requires at least IOS30 installed.
+ * LED support is pending.
+ */
+
+#define DEBUG
+
+#define DBG(fmt, arg...)        pr_debug(fmt, ##arg)
+
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <asm/starlet-ios.h>
+
+#define DRV_MODULE_NAME  "rvl-stkbd"
+#define DRV_DESCRIPTION  "Nintendo Wii starlet keyboard driver"
+#define DRV_AUTHOR       "Albert Herranz"
+
+static char stkbd_driver_version[] = "0.2i";
+
+#define drv_printk(level, format, arg...) \
+	 printk(level DRV_MODULE_NAME ": " format , ## arg)
+
+/*
+ * Keyboard events from IOS.
+ */
+#define STKBD_EV_CONNECT	0x00000000
+#define STKBD_EV_DISCONNECT	0x00000001
+#define STKBD_EV_REPORT		0x00000002
+
+struct stkbd_event {
+	u32 type;
+	u32 _unk1;
+	union {
+		struct {
+			u8 modifiers;
+			u8 reserved;
+			u8 keys[6];
+		} report;
+		u8 raw_report[8];
+	};
+} __attribute__((packed));
+
+/*
+ * Keyboard device.
+ */
+
+enum {
+	__STKBD_RUNNING = 1,		/* device is opened and running */
+	__STKBD_WAITING_REPORT		/* waiting for IOS report */
+};
+
+struct stkbd_keyboard {
+	int		fd;
+
+	struct		stkbd_event *event;
+	u8		old_raw_report[8];
+
+	unsigned long	flags;
+#define STKBD_RUNNING		(1 << __STKBD_RUNNING)
+#define STKBD_WAITING_REPORT	(1 << __STKBD_WAITING_REPORT)
+
+	char		name[32];
+	struct input_dev *idev;
+	unsigned int	usage;
+
+	struct device	*dev;
+};
+
+/* device path in IOS for the USB keyboard */
+static char stkbd_dev_path[] = "/dev/usb/kbd";
+
+/*
+ * Keycodes are standard USB keyboard HID keycodes.
+ * We use the same table as in drivers/hid/usbhid/usbkbd.c.
+ */
+
+#define NR_SCANCODES 256
+
+static unsigned char stkbd_keycode[NR_SCANCODES] = {
+/*000*/	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,
+/*004*/	KEY_A,		KEY_B,		KEY_C,		KEY_D,
+/*008*/	KEY_E,		KEY_F,		KEY_G,		KEY_H,
+/*012*/	KEY_I,		KEY_J,		KEY_K,		KEY_L,
+/*016*/	KEY_M,		KEY_N,		KEY_O,		KEY_P,
+/*020*/	KEY_Q,		KEY_R,		KEY_S,		KEY_T,
+/*024*/	KEY_U,		KEY_V,		KEY_W,		KEY_X,
+/*028*/	KEY_Y,		KEY_Z,		KEY_1,		KEY_2,
+/*032*/	KEY_3,		KEY_4,		KEY_5,		KEY_6,
+/*036*/	KEY_7,		KEY_8,		KEY_9,		KEY_0,
+/*040*/	KEY_ENTER,	KEY_ESC,	KEY_BACKSPACE,	KEY_TAB,
+/*044*/	KEY_SPACE,	KEY_MINUS,	KEY_EQUAL,	KEY_LEFTBRACE,
+/*048*/	KEY_RIGHTBRACE,	KEY_BACKSLASH,	KEY_BACKSLASH,	KEY_SEMICOLON,
+/*052*/	KEY_APOSTROPHE,	KEY_GRAVE,	KEY_COMMA,	KEY_DOT,
+/*056*/	KEY_SLASH,	KEY_CAPSLOCK,	KEY_F1,		KEY_F2,
+/*060*/	KEY_F3,		KEY_F4,		KEY_F5,		KEY_F6,
+/*064*/	KEY_F7,		KEY_F8,		KEY_F9,		KEY_F10,
+/*068*/	KEY_F11,	KEY_F12,	KEY_SYSRQ,	KEY_SCROLLLOCK,
+/*072*/	KEY_PAUSE,	KEY_INSERT,	KEY_HOME,	KEY_PAGEUP,
+/*076*/	KEY_DELETE,	KEY_END,	KEY_PAGEDOWN,	KEY_RIGHT,
+/*080*/	KEY_LEFT,	KEY_DOWN,	KEY_UP,		KEY_NUMLOCK,
+/*084*/	KEY_KPSLASH,	KEY_KPASTERISK,	KEY_KPMINUS,	KEY_KPPLUS,
+/*088*/	KEY_KPENTER,	KEY_KP1,	KEY_KP2,	KEY_KP3,
+/*092*/	KEY_KP4,	KEY_KP5,	KEY_KP6,	KEY_KP7,
+/*096*/	KEY_KP8,	KEY_KP9,	KEY_KP0,	KEY_KPDOT,
+/*100*/	KEY_102ND,	KEY_COMPOSE,	KEY_POWER,	KEY_KPEQUAL,
+/*104*/	KEY_F13,	KEY_F14,	KEY_F15,	KEY_F16,
+/*108*/	KEY_F17,	KEY_F18,	KEY_F19,	KEY_F20,
+/*112*/	KEY_F21,	KEY_F22,	KEY_F23,	KEY_F24,
+/*116*/	KEY_OPEN,	KEY_HELP,	KEY_PROPS,	KEY_FRONT,
+/*120*/	KEY_STOP,	KEY_AGAIN,	KEY_UNDO,	KEY_CUT,
+/*124*/	KEY_COPY,	KEY_PASTE,	KEY_FIND,	KEY_MUTE,
+/*128*/	KEY_VOLUMEUP,	KEY_VOLUMEDOWN,	KEY_RESERVED,	KEY_RESERVED,
+/*132*/	KEY_RESERVED,	KEY_KPCOMMA,	KEY_RESERVED,	KEY_RO,
+/*136*/	KEY_KATAKANAHIRAGANA,	KEY_YEN,	KEY_HENKAN,	KEY_MUHENKAN,
+/*140*/	KEY_KPJPCOMMA,	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,
+/*144*/	KEY_HANGEUL,	KEY_HANJA,	KEY_KATAKANA,	KEY_HIRAGANA,
+/*148*/	KEY_ZENKAKUHANKAKU,	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,
+/*152*/	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,
+/*156*/	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,
+/*160*/	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,
+/*164*/	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,
+/*168*/	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,
+/*172*/	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,
+/*176*/	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,
+/*180*/	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,
+/*184*/	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,
+/*188*/	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,
+/*192*/	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,
+/*196*/	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,
+/*200*/	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,
+/*204*/	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,
+/*208*/	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,
+/*212*/	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,
+/*216*/	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,
+/*220*/	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,
+/*224*/	KEY_LEFTCTRL,	KEY_LEFTSHIFT,	KEY_LEFTALT,	KEY_LEFTMETA,
+/*228*/	KEY_RIGHTCTRL,	KEY_RIGHTSHIFT,	KEY_RIGHTALT,	KEY_RIGHTMETA,
+/*232*/	KEY_PLAYPAUSE,	KEY_STOPCD,	KEY_PREVIOUSSONG,	KEY_NEXTSONG,
+/*236*/	KEY_EJECTCD,	KEY_VOLUMEUP,	KEY_VOLUMEDOWN,	KEY_MUTE,
+/*240*/	KEY_WWW,	KEY_BACK,	KEY_FORWARD,	KEY_STOP,
+/*244*/	KEY_FIND,	KEY_SCROLLUP,	KEY_SCROLLDOWN,	KEY_EDIT,
+/*248*/	KEY_SLEEP,	KEY_SCROLLLOCK,	KEY_REFRESH,	KEY_CALC,
+/*252*/	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,	KEY_RESERVED,
+};
+
+
+static int stkbd_wait_for_events(struct stkbd_keyboard *kbd);
+
+static void stkbd_dispatch_report(struct stkbd_keyboard *kbd)
+{
+	struct stkbd_event *event = kbd->event;
+	u8 *new = event->raw_report;
+	u8 *old = kbd->old_raw_report;
+	int i;
+
+	/*
+	 * The report is a standard USB HID report for a keyboard.
+	 * Modifiers come in byte 0 as variable data and map to
+	 * positions 224-231 of the USB HID keyboard/keypad page.
+	 */
+	for (i = 0; i < 8; i++)
+		input_report_key(kbd->idev, stkbd_keycode[i + 224],
+				 (new[0] >> i) & 1);
+
+	/*
+	 * Byte 1 is reserved and ignored here.
+	 * Bytes 2 to 7 contain the indexes of up to 6 keys pressed.
+	 * A value of 01 for an index means "Keyboard ErrorRollOver" and is
+	 * reported when the keyboard is in error (for example, when too
+	 * many keys are simultaneously pressed).
+	 * Index values less than or equal to 03 can be safely ignored.
+	 */
+	for (i = 2; i < 8; i++) {
+		/* released keys are old indexes not found in new array */
+		if (old[i] > 3 && memscan(new + 2, old[i], 6) == new + 8) {
+			if (stkbd_keycode[old[i]])
+				input_report_key(kbd->idev,
+						 stkbd_keycode[old[i]], 0);
+			else
+				drv_printk(KERN_WARNING, "unknown key"
+					   " (scancode %#x) released.", old[i]);
+		}
+
+		/* pressed keys are new indexes not found in old array */
+		if (new[i] > 3 && memscan(old + 2, new[i], 6) == old + 8) {
+			if (stkbd_keycode[new[i]])
+				input_report_key(kbd->idev,
+						 stkbd_keycode[new[i]], 1);
+			else
+				drv_printk(KERN_WARNING, "unknown key"
+					   " (scancode %#x) pressed.", new[i]);
+		}
+	}
+
+	input_sync(kbd->idev);
+	memcpy(old, new, 8);
+}
+
+static int stkbd_dispatch_ipc_request(struct starlet_ipc_request *req)
+{
+	struct stkbd_keyboard *kbd;
+	struct stkbd_event *event;
+	int error;
+
+	/* retrieve the interesting data before freeing the request */
+	kbd = req->done_data;
+	error = req->result;
+	starlet_ipc_free_request(req);
+
+	clear_bit(__STKBD_WAITING_REPORT, &kbd->flags);
+
+	if (kbd->fd == -1)
+		return -ENODEV;
+
+	if (error) {
+		DBG("%s: error=%d (%x)\n", __func__, error, error);
+	} else {
+		event = kbd->event;
+
+		switch (event->type) {
+		case STKBD_EV_CONNECT:
+			drv_printk(KERN_INFO, "keyboard connected\n");
+			break;
+		case STKBD_EV_DISCONNECT:
+			drv_printk(KERN_INFO, "keyboard disconnected\n");
+			break;
+		case STKBD_EV_REPORT:
+			if (test_bit(__STKBD_RUNNING, &kbd->flags))
+				stkbd_dispatch_report(kbd);
+			break;
+		}
+
+		error = stkbd_wait_for_events(kbd);
+	}
+	return error;
+}
+
+static int stkbd_wait_for_events(struct stkbd_keyboard *kbd)
+{
+	struct stkbd_event *event = kbd->event;
+	int error = 0;
+
+	if (!test_and_set_bit(__STKBD_WAITING_REPORT, &kbd->flags)) {
+		error = starlet_ioctl_nowait(kbd->fd, 0,
+						 NULL, 0,
+						 event, sizeof(*event),
+						 stkbd_dispatch_ipc_request,
+						 kbd);
+		if (error)
+			drv_printk(KERN_ERR, "ioctl error %d (%04x)\n",
+				   error, error);
+	}
+	return error;
+}
+
+/*
+ * Input driver hooks.
+ *
+ */
+
+static DEFINE_MUTEX(open_lock);
+
+static int stkbd_open(struct input_dev *idev)
+{
+	struct stkbd_keyboard *kbd = input_get_drvdata(idev);
+	int error = 0;
+
+	if (!kbd)
+		return -ENODEV;
+
+	mutex_lock(&open_lock);
+	kbd->usage++;
+	set_bit(__STKBD_RUNNING, &kbd->flags);
+	mutex_unlock(&open_lock);
+
+	return error;
+}
+
+static void stkbd_close(struct input_dev *idev)
+{
+	struct stkbd_keyboard *kbd = input_get_drvdata(idev);
+
+	if (!kbd)
+		return;
+
+	mutex_lock(&open_lock);
+	kbd->usage--;
+	if (kbd->usage == 0)
+		clear_bit(__STKBD_RUNNING, &kbd->flags);
+	mutex_unlock(&open_lock);
+}
+
+static void stkbd_setup_keyboard(struct input_dev *idev)
+{
+	int i;
+
+	set_bit(EV_KEY, idev->evbit);
+	set_bit(EV_REP, idev->evbit);
+
+	for (i = 0; i < 255; ++i)
+		set_bit(stkbd_keycode[i], idev->keybit);
+	clear_bit(0, idev->keybit);
+}
+
+static int stkbd_init_input_dev(struct stkbd_keyboard *kbd)
+{
+	struct input_dev *idev;
+	int error;
+
+	idev = input_allocate_device();
+	if (!idev) {
+		drv_printk(KERN_ERR, "failed to allocate input_dev\n");
+		return -ENOMEM;
+	}
+
+	idev->dev.parent = kbd->dev;
+
+	strcpy(kbd->name, "USB keyboard");
+	idev->name = kbd->name;
+
+	input_set_drvdata(idev, kbd);
+	kbd->idev = idev;
+
+	stkbd_setup_keyboard(idev);
+
+	idev->open = stkbd_open;
+	idev->close = stkbd_close;
+
+	error = input_register_device(kbd->idev);
+	if (error) {
+		input_free_device(kbd->idev);
+		return error;
+	}
+
+	return 0;
+}
+
+static void stkbd_exit_input_dev(struct stkbd_keyboard *kbd)
+{
+	input_free_device(kbd->idev);
+}
+
+/*
+ * Setup routines.
+ *
+ */
+
+static int stkbd_init(struct stkbd_keyboard *kbd)
+{
+	struct stkbd_event *event;
+	int error;
+
+	event = starlet_kzalloc(sizeof(*event), GFP_KERNEL);
+	if (!event) {
+		drv_printk(KERN_ERR, "failed to allocate stkbd_event\n");
+		error = -ENOMEM;
+		goto err;
+	}
+	kbd->event = event;
+
+	kbd->fd = starlet_open(stkbd_dev_path, 0);
+	if (kbd->fd < 0) {
+		drv_printk(KERN_ERR, "unable to open device %s\n",
+			   stkbd_dev_path);
+		error = kbd->fd;
+		goto err_event;
+	}
+
+	error = stkbd_init_input_dev(kbd);
+	if (error)
+		goto err_fd;
+
+	/* start to grab events from the keyboard */
+	error = stkbd_wait_for_events(kbd);
+	if (error)
+		goto err_input_dev;
+
+	return 0;
+
+err_input_dev:
+	stkbd_exit_input_dev(kbd);
+err_fd:
+	starlet_close(kbd->fd);
+err_event:
+	starlet_kfree(event);
+err:
+	return error;
+}
+
+static void stkbd_exit(struct stkbd_keyboard *kbd)
+{
+	stkbd_exit_input_dev(kbd);
+	starlet_close(kbd->fd);
+	starlet_kfree(kbd->event);
+}
+
+/*
+ * Driver model helper routines.
+ *
+ */
+
+static int stkbd_do_probe(struct device *dev)
+{
+	struct stkbd_keyboard *kbd;
+	int error;
+
+	kbd = kzalloc(sizeof(*kbd), GFP_KERNEL);
+	if (!kbd) {
+		drv_printk(KERN_ERR, "failed to allocate stkbd_keyboard\n");
+		return -ENOMEM;
+	}
+	dev_set_drvdata(dev, kbd);
+	kbd->dev = dev;
+
+	error = stkbd_init(kbd);
+	if (error) {
+		dev_set_drvdata(dev, NULL);
+		kfree(kbd);
+	}
+	return error;
+}
+
+static int stkbd_do_remove(struct device *dev)
+{
+	struct stkbd_keyboard *kbd = dev_get_drvdata(dev);
+
+	if (kbd) {
+		stkbd_exit(kbd);
+		dev_set_drvdata(dev, NULL);
+		kfree(kbd);
+		return 0;
+	}
+	return -ENODEV;
+}
+
+
+/*
+ * OF platform driver hooks.
+ *
+ */
+
+static int __init stkbd_of_probe(struct of_device *odev,
+				  const struct of_device_id *match)
+{
+	return stkbd_do_probe(&odev->dev);
+}
+
+static int __exit stkbd_of_remove(struct of_device *odev)
+{
+	return stkbd_do_remove(&odev->dev);
+}
+
+static struct of_device_id stkbd_of_match[] = {
+	{ .compatible = "nintendo,starlet-ios-keyboard" },
+	{ },
+};
+
+
+MODULE_DEVICE_TABLE(of, stkbd_of_match);
+
+static struct of_platform_driver stkbd_of_driver = {
+	.owner = THIS_MODULE,
+	.name = DRV_MODULE_NAME,
+	.match_table = stkbd_of_match,
+	.probe = stkbd_of_probe,
+	.remove = stkbd_of_remove,
+};
+
+
+/*
+ * Module interface hooks.
+ *
+ */
+
+static int __init stkbd_init_module(void)
+{
+	drv_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION,
+		   stkbd_driver_version);
+
+	return of_register_platform_driver(&stkbd_of_driver);
+}
+
+static void __exit stkbd_exit_module(void)
+{
+	of_unregister_platform_driver(&stkbd_of_driver);
+}
+
+module_init(stkbd_init_module);
+module_exit(stkbd_exit_module);
+
+MODULE_DESCRIPTION(DRV_DESCRIPTION);
+MODULE_AUTHOR(DRV_AUTHOR);
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/si/Kconfig b/drivers/input/si/Kconfig
new file mode 100644
index 0000000..51de017
--- /dev/null
+++ b/drivers/input/si/Kconfig
@@ -0,0 +1,14 @@
+#
+# Nintendo GameCube/Wii Serial Interface (SI) configuration
+#
+
+config GAMECUBE_SI
+	tristate "Nintendo GameCube/Wii Serial Interface (SI) support"
+	depends on GAMECUBE_COMMON
+	---help---
+	  Say Y here if you want to use the standard pads as joysticks or
+	  want to use a keyboard. Everything is autodetected.
+
+	  NOTE: Keyboard detection doesn't work 100%. Building this as a
+	  module is recommended, this way you can unload/load the driver
+	  to re-detect.
diff --git a/drivers/input/si/Makefile b/drivers/input/si/Makefile
new file mode 100644
index 0000000..5087995
--- /dev/null
+++ b/drivers/input/si/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for the Nintendo GameCube/Wii Serial Interface (SI).
+#
+
+obj-$(CONFIG_GAMECUBE_SI)	+= gcn-si.o
diff --git a/drivers/input/si/gcn-keymap.h b/drivers/input/si/gcn-keymap.h
new file mode 100644
index 0000000..7a3345a
--- /dev/null
+++ b/drivers/input/si/gcn-keymap.h
@@ -0,0 +1,79 @@
+/*
+ * keymap for a Datel adapater + US keyboard (not the normal keyboard)
+ * not mapped:
+ *	printscreen / sysreq
+ *	pause / break
+ *	numlock
+ *	windows key 1
+ *	windows key 2
+ *	windows menu key
+ *
+ * cursor keys send both 36 and their own code, so 36 must be RESERVED
+ */
+
+static unsigned char gamecube_keymap[] = {
+	/* 00 */ KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+		 KEY_RESERVED,  KEY_RESERVED,   KEY_HOME,       KEY_END,
+	/* 08 */ KEY_PAGEUP,    KEY_PAGEDOWN,   KEY_SCROLLLOCK, KEY_RESERVED,
+		 KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+	/* 10 */ KEY_A,         KEY_B,          KEY_C,          KEY_D,
+		 KEY_E,         KEY_F,          KEY_G,          KEY_H,
+	/* 18 */ KEY_I,         KEY_J,          KEY_K,          KEY_L,
+		 KEY_M,         KEY_N,          KEY_O,          KEY_P,
+	/* 20 */ KEY_Q,         KEY_R,          KEY_S,          KEY_T,
+		 KEY_U,         KEY_V,          KEY_W,          KEY_X,
+	/* 28 */ KEY_Y,         KEY_Z,          KEY_1,          KEY_2,
+		 KEY_3,         KEY_4,          KEY_5,          KEY_6,
+	/* 30 */ KEY_7,         KEY_8,          KEY_9,          KEY_0,
+		 KEY_MINUS,     KEY_EQUAL,      KEY_RESERVED,   KEY_KPASTERISK,
+	/* 38 */ KEY_LEFTBRACE, KEY_SEMICOLON,  KEY_APOSTROPHE, KEY_RIGHTBRACE,
+		 KEY_COMMA,     KEY_DOT,        KEY_SLASH,      KEY_BACKSLASH,
+	/* 40 */ KEY_F1,        KEY_F2,         KEY_F3,         KEY_F4,
+		 KEY_F5,        KEY_F6,         KEY_F7,         KEY_F8,
+	/* 48 */ KEY_F9,        KEY_F10,        KEY_F11,        KEY_F12,
+		 KEY_ESC,       KEY_INSERT,     KEY_DELETE,     KEY_GRAVE,
+	/* 50 */ KEY_BACKSPACE, KEY_TAB,        KEY_RESERVED,   KEY_CAPSLOCK,
+		 KEY_LEFTSHIFT, KEY_RIGHTSHIFT, KEY_LEFTCTRL,   KEY_LEFTALT,
+	/* 58 */ KEY_RESERVED,  KEY_SPACE,      KEY_RESERVED,   KEY_RESERVED,
+		 KEY_LEFT,      KEY_DOWN,       KEY_UP,         KEY_RIGHT,
+	/* 60 */ KEY_RESERVED,  KEY_ENTER,      KEY_RESERVED,   KEY_RESERVED,
+		 KEY_SEMICOLON, KEY_KPPLUS,     KEY_RESERVED,   KEY_RESERVED,
+	/* 68 */ KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+		 KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+	/* 70 */ KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+		 KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+	/* 78 */ KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+		 KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+	/* 80 */ KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+		 KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+	/* 88 */ KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+		 KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+	/* 90 */ KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+		 KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+	/* 98 */ KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+		 KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+	/* a0 */ KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+		 KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+	/* a8 */ KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+		 KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+	/* b0 */ KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+		 KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+	/* b8 */ KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+		 KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+	/* c0 */ KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+		 KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+	/* c8 */ KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+		 KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+	/* d0 */ KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+		 KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+	/* d8 */ KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+		 KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+	/* e0 */ KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+		 KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+	/* e8 */ KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+		 KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+	/* f0 */ KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+		 KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+	/* f8 */ KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED,
+		 KEY_RESERVED,  KEY_RESERVED,   KEY_RESERVED,   KEY_RESERVED
+};
diff --git a/drivers/input/si/gcn-si.c b/drivers/input/si/gcn-si.c
new file mode 100644
index 0000000..d8b2e8c
--- /dev/null
+++ b/drivers/input/si/gcn-si.c
@@ -0,0 +1,753 @@
+/*
+ * drivers/input/gcn-si.c
+ *
+ * Nintendo GameCube/Wii Serial Interface (SI) driver.
+ * Copyright (C) 2004-2009 The GameCube Linux Team
+ * Copyright (C) 2004 Steven Looman
+ * Copyright (C) 2005,2008,2009 Albert Herranz
+ *
+ * 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.
+ *
+ */
+
+/* #define SI_DEBUG */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/timer.h>
+
+/*
+ * This keymap is for a datel adapter + normal US keyboard.
+ */
+#include "gcn-keymap.h"
+
+/*
+ * Defining HACK_FORCE_KEYBOARD_PORT allows one to specify a port that
+ * will be identified as a keyboard port in case the port gets incorrectly
+ * identified.
+ */
+#define HACK_FORCE_KEYBOARD_PORT
+
+
+#define DRV_MODULE_NAME  "gcn-si"
+#define DRV_DESCRIPTION  "Nintendo GameCube/Wii Serial Interface (SI) driver"
+#define DRV_AUTHOR       "Steven Looman <steven@krx.nl>, " \
+			 "Albert Herranz"
+
+static char si_driver_version[] = "1.0i";
+
+#define drv_printk(level, format, arg...) \
+	 printk(level DRV_MODULE_NAME ": " format , ## arg)
+
+#define SI_MAX_PORTS		4		/* the four controller ports */
+#define SI_REFRESH_TIME	(HZ/100)	/* polling interval */
+
+/*
+ * Hardware registers
+ */
+#define SI_PORT_SPACING	12
+
+#define SICOUTBUF(i)	(0x00 + (i)*SI_PORT_SPACING)
+#define SICINBUFH(i)	(0x04 + (i)*SI_PORT_SPACING)
+#define SICINBUFL(i)	(0x08 + (i)*SI_PORT_SPACING)
+#define SIPOLL		0x30
+#define SICOMCSR	0x34
+#define SISR		0x38
+#define SIEXILK		0x3c
+
+#define ID_PAD		0x0900
+#define ID_KEYBOARD	0x0820
+#define ID_WIRELESS_BIT (1 << 15)
+#define ID_WAVEBIRD_BIT (1 << 8)
+
+#define PAD_START	(1 << 28)
+#define PAD_Y		(1 << 27)
+#define PAD_X		(1 << 26)
+#define PAD_B		(1 << 25)
+#define PAD_A		(1 << 24)
+#define PAD_LT		(1 << 22)
+#define PAD_RT		(1 << 21)
+#define PAD_Z		(1 << 20)
+#define PAD_UP		(1 << 19)
+#define PAD_DOWN	(1 << 18)
+#define PAD_RIGHT	(1 << 17)
+#define PAD_LEFT	(1 << 16)
+
+
+struct si_keyboard_status {
+	unsigned char old[3];
+};
+
+enum si_control_type {
+	CTL_PAD,
+	CTL_KEYBOARD,
+	CTL_UNKNOWN
+};
+
+struct si_drvdata;
+
+struct si_port {
+	unsigned int index;
+	struct si_drvdata *drvdata;
+
+	u32 id; /* SI id */
+
+	enum si_control_type type;
+	unsigned int raw[2];
+
+	struct input_dev *idev;
+	struct timer_list timer;
+	char name[32];
+
+	union {
+		struct si_keyboard_status keyboard;
+	};
+
+};
+
+struct si_drvdata {
+	unsigned long flags;
+#define SI_QUIESCE	(1<<0)
+
+	struct si_port ports[SI_MAX_PORTS];
+
+	void __iomem *io_base;
+
+	struct device *dev;
+};
+
+
+#ifdef HACK_FORCE_KEYBOARD_PORT
+
+static int si_force_keyboard_port = -1;
+
+#ifdef MODULE
+module_param_named(force_keyboard_port, si_force_keyboard_port, int, 0644);
+MODULE_PARM_DESC(force_keyboard_port, "port n becomes a keyboard port if"
+		 " automatic identification fails");
+#else
+static int __init si_force_keyboard_port_setup(char *line)
+{
+	if (sscanf(line, "%d", &si_force_keyboard_port) != 1)
+		si_force_keyboard_port = -1;
+	return 1;
+}
+__setup("force_keyboard_port=", si_force_keyboard_port_setup);
+#endif /* MODULE */
+
+#endif /* HACK_FORCE_KEYBOARD_PORT */
+
+
+/*
+ * Hardware.
+ *
+ */
+
+static void si_reset_all(void __iomem *io_base)
+{
+	int i;
+
+	/* clear all SI registers */
+
+	for (i = 0; i < SI_MAX_PORTS; ++i)
+		out_be32(io_base + SICOUTBUF(i), 0);
+	for (i = 0; i < SI_MAX_PORTS; ++i)
+		out_be32(io_base + SICINBUFH(i), 0);
+	for (i = 0; i < SI_MAX_PORTS; ++i)
+		out_be32(io_base + SICINBUFL(i), 0);
+	out_be32(io_base + SIPOLL, 0);
+	out_be32(io_base + SICOMCSR, 0);
+	out_be32(io_base + SISR, 0);
+
+	/* these too... */
+	out_be32(io_base + 0x80, 0);
+	out_be32(io_base + 0x84, 0);
+	out_be32(io_base + 0x88, 0);
+	out_be32(io_base + 0x8c, 0);
+	out_be32(io_base + 0x90, 0);
+	out_be32(io_base + 0x94, 0);
+	out_be32(io_base + 0x98, 0);
+	out_be32(io_base + 0x9c, 0);
+	out_be32(io_base + 0xa0, 0);
+	out_be32(io_base + 0xa4, 0);
+	out_be32(io_base + 0xa8, 0);
+	out_be32(io_base + 0xac, 0);
+}
+
+static void si_set_rumbling(void __iomem *io_base, unsigned int index,
+			    int rumble)
+{
+	out_be32(io_base + SICOUTBUF(index), 0x00400000 | (rumble) ? 1 : 0);
+	out_be32(io_base + SISR, 0x80000000);
+}
+
+static void si_wait_transfer_done(void __iomem *io_base)
+{
+	unsigned long deadline = jiffies + 2*HZ;
+	int borked = 0;
+
+	while (!(in_be32(io_base + SICOMCSR) & (1 << 31)) && !borked) {
+		cpu_relax();
+		borked = time_after(jiffies, deadline);
+	}
+
+	if (borked) {
+		drv_printk(KERN_ERR, "serial transfer took too long, "
+			   "is your hardware ok?");
+	}
+
+	out_be32(io_base + SICOMCSR,
+		 in_be32(io_base + SICOMCSR) | (1 << 31)); /* ack IRQ */
+}
+
+static u32 si_get_controller_id(void __iomem *io_base,
+					  unsigned int index)
+{
+	out_be32(io_base + SICOUTBUF(index), 0);
+	out_be32(io_base + SIPOLL, 0);
+
+	out_be32(io_base + SISR, 0x80000000);
+	out_be32(io_base + SICOMCSR, 0xd0010001 | index << 1);
+	si_wait_transfer_done(io_base);
+
+	return in_be32(io_base + 0x80) >> 16;
+}
+
+static void si_setup_polling(struct si_drvdata *drvdata)
+{
+	void __iomem *io_base = drvdata->io_base;
+	unsigned long pad_bits = 0;
+	int i;
+
+	for (i = 0; i < SI_MAX_PORTS; ++i) {
+		switch (drvdata->ports[i].type) {
+		case CTL_PAD:
+			out_be32(io_base + SICOUTBUF(i), 0x00400300);
+			break;
+		case CTL_KEYBOARD:
+			out_be32(io_base + SICOUTBUF(i), 0x00540000);
+			break;
+		default:
+			continue;
+		}
+		pad_bits |= 1 << (7 - i);
+	}
+	out_be32(io_base + SIPOLL, 0x00f70200 | pad_bits);
+
+	out_be32(io_base + SISR, 0x80000000);
+	out_be32(io_base + SICOMCSR, 0xc0010801);
+	si_wait_transfer_done(io_base);
+}
+
+static void si_timer(unsigned long data)
+{
+	struct si_port *port = (struct si_port *)data;
+	unsigned int index = port->index;
+	void __iomem *io_base = port->drvdata->io_base;
+	unsigned long raw[2];
+	unsigned char key[3];
+	unsigned char oldkey;
+	int i;
+
+	raw[0] = in_be32(io_base + SICINBUFH(index));
+	raw[1] = in_be32(io_base + SICINBUFL(index));
+
+	switch (port->type) {
+	case CTL_PAD:
+		/* buttons */
+		input_report_key(port->idev, BTN_A, raw[0] & PAD_A);
+		input_report_key(port->idev, BTN_B, raw[0] & PAD_B);
+		input_report_key(port->idev, BTN_X, raw[0] & PAD_X);
+		input_report_key(port->idev, BTN_Y, raw[0] & PAD_Y);
+		input_report_key(port->idev, BTN_Z, raw[0] & PAD_Z);
+		input_report_key(port->idev, BTN_TL,
+				 raw[0] & PAD_LT);
+		input_report_key(port->idev, BTN_TR,
+				 raw[0] & PAD_RT);
+		input_report_key(port->idev, BTN_START,
+				 raw[0] & PAD_START);
+		input_report_key(port->idev, BTN_0, raw[0] & PAD_UP);
+		input_report_key(port->idev, BTN_1, raw[0] & PAD_RIGHT);
+		input_report_key(port->idev, BTN_2, raw[0] & PAD_DOWN);
+		input_report_key(port->idev, BTN_3, raw[0] & PAD_LEFT);
+
+		/* axis */
+		/* a stick */
+		input_report_abs(port->idev, ABS_X,
+				 raw[0] >> 8 & 0xFF);
+		input_report_abs(port->idev, ABS_Y,
+				 0xFF - (raw[0] >> 0 & 0xFF));
+
+		/* b pad */
+		if (raw[0] & PAD_RIGHT)
+			input_report_abs(port->idev, ABS_HAT0X, 1);
+		else if (raw[0] & PAD_LEFT)
+			input_report_abs(port->idev, ABS_HAT0X, -1);
+		else
+			input_report_abs(port->idev, ABS_HAT0X, 0);
+
+		if (raw[0] & PAD_DOWN)
+			input_report_abs(port->idev, ABS_HAT0Y, 1);
+		else if (raw[0] & PAD_UP)
+			input_report_abs(port->idev, ABS_HAT0Y, -1);
+		else
+			input_report_abs(port->idev, ABS_HAT0Y, 0);
+
+		/* c stick */
+		input_report_abs(port->idev, ABS_RX,
+				 raw[1] >> 24 & 0xFF);
+		input_report_abs(port->idev, ABS_RY,
+				 raw[1] >> 16 & 0xFF);
+
+		/* triggers */
+		input_report_abs(port->idev, ABS_BRAKE,
+				 raw[1] >> 8 & 0xFF);
+		input_report_abs(port->idev, ABS_GAS,
+				 raw[1] >> 0 & 0xFF);
+
+		break;
+
+	case CTL_KEYBOARD:
+		/*
+		raw nibbles:
+		  [4]<C>[0][0][0][0][0][0] <1H><1L><2H><2L><3H><3L><X><C>
+		where:
+		  [n] = fixed to n
+		  <nH> <nL> = high / low nibble of n-th key pressed
+			(0 if not pressed)
+		  <X> = <1H> xor <2H> xor <3H>
+		  <C> = counter: 0, 0, 1, 1, 2, 2, ..., F, F, 0, 0, ...
+		*/
+		key[0] = (raw[1] >> 24) & 0xFF;
+		key[1] = (raw[1] >> 16) & 0xFF;
+		key[2] = (raw[1] >>  8) & 0xFF;
+
+		/* check if anything was released */
+		for (i = 0; i < 3; ++i) {
+			oldkey = port->keyboard.old[i];
+			if (oldkey != key[0] &&
+			    oldkey != key[1] && oldkey != key[2])
+				input_report_key(port->idev,
+						 gamecube_keymap[oldkey], 0);
+		}
+
+		/* report keys */
+		for (i = 0; i < 3; ++i) {
+			if (key[i])
+				input_report_key(port->idev,
+						 gamecube_keymap[key[i]], 1);
+			port->keyboard.old[i] = key[i];
+		}
+		break;
+
+	default:
+		break;
+	}
+
+	input_sync(port->idev);
+
+	if (!(port->drvdata->flags & SI_QUIESCE))
+		mod_timer(&port->timer, jiffies + SI_REFRESH_TIME);
+}
+
+/*
+ * Input driver hooks.
+ *
+ */
+
+static int si_open(struct input_dev *idev)
+{
+	struct si_port *port = input_get_drvdata(idev);
+
+	init_timer(&port->timer);
+	port->timer.function = si_timer;
+	port->timer.data = (unsigned long)port;
+	port->timer.expires = jiffies + SI_REFRESH_TIME;
+	add_timer(&port->timer);
+
+	return 0;
+}
+
+static void si_close(struct input_dev *idev)
+{
+	struct si_port *port = input_get_drvdata(idev);
+
+	del_timer(&port->timer);
+}
+
+static int si_event(struct input_dev *idev, unsigned int type,
+		    unsigned int code, int value)
+{
+	struct si_port *port = input_get_drvdata(idev);
+	unsigned int index = port->index;
+	void __iomem *io_base = port->drvdata->io_base;
+
+	if (type == EV_FF) {
+		if (code == FF_RUMBLE)
+			si_set_rumbling(io_base, index, value);
+	}
+
+	return value;
+}
+
+static int si_setup_pad(struct input_dev *idev)
+{
+	struct ff_device *ff;
+	int retval;
+
+	set_bit(EV_KEY, idev->evbit);
+	set_bit(EV_ABS, idev->evbit);
+
+	set_bit(BTN_A, idev->keybit);
+	set_bit(BTN_B, idev->keybit);
+	set_bit(BTN_X, idev->keybit);
+	set_bit(BTN_Y, idev->keybit);
+	set_bit(BTN_Z, idev->keybit);
+	set_bit(BTN_TL, idev->keybit);
+	set_bit(BTN_TR, idev->keybit);
+	set_bit(BTN_START, idev->keybit);
+	set_bit(BTN_0, idev->keybit);
+	set_bit(BTN_1, idev->keybit);
+	set_bit(BTN_2, idev->keybit);
+	set_bit(BTN_3, idev->keybit);
+
+	/* a stick */
+	set_bit(ABS_X, idev->absbit);
+	idev->absmin[ABS_X] = 0;
+	idev->absmax[ABS_X] = 255;
+	idev->absfuzz[ABS_X] = 8;
+	idev->absflat[ABS_X] = 8;
+
+	set_bit(ABS_Y, idev->absbit);
+	idev->absmin[ABS_Y] = 0;
+	idev->absmax[ABS_Y] = 255;
+	idev->absfuzz[ABS_Y] = 8;
+	idev->absflat[ABS_Y] = 8;
+
+	/* b pad */
+	input_set_abs_params(idev, ABS_HAT0X, -1, 1, 0, 0);
+	input_set_abs_params(idev, ABS_HAT0Y, -1, 1, 0, 0);
+
+	/* c stick */
+	set_bit(ABS_RX, idev->absbit);
+	idev->absmin[ABS_RX] = 0;
+	idev->absmax[ABS_RX] = 255;
+	idev->absfuzz[ABS_RX] = 8;
+	idev->absflat[ABS_RX] = 8;
+
+	set_bit(ABS_RY, idev->absbit);
+	idev->absmin[ABS_RY] = 0;
+	idev->absmax[ABS_RY] = 255;
+	idev->absfuzz[ABS_RY] = 8;
+	idev->absflat[ABS_RY] = 8;
+
+	/* triggers */
+	set_bit(ABS_GAS, idev->absbit);
+	idev->absmin[ABS_GAS] = -255;
+	idev->absmax[ABS_GAS] = 255;
+	idev->absfuzz[ABS_GAS] = 16;
+	idev->absflat[ABS_GAS] = 16;
+
+	set_bit(ABS_BRAKE, idev->absbit);
+	idev->absmin[ABS_BRAKE] = -255;
+	idev->absmax[ABS_BRAKE] = 255;
+	idev->absfuzz[ABS_BRAKE] = 16;
+	idev->absflat[ABS_BRAKE] = 16;
+
+	/* rumbling */
+	set_bit(EV_FF, idev->evbit);
+	set_bit(FF_RUMBLE, idev->ffbit);
+	retval = input_ff_create(idev, 1);
+	if (retval)
+		return retval;
+	ff = idev->ff;
+	idev->event = si_event;
+	return 0;
+}
+
+static void si_setup_keyboard(struct input_dev *idev)
+{
+	int i;
+
+	set_bit(EV_KEY, idev->evbit);
+	set_bit(EV_REP, idev->evbit);
+
+	for (i = 0; i < 255; ++i)
+		set_bit(gamecube_keymap[i], idev->keybit);
+}
+
+static int si_port_probe(struct si_port *port)
+{
+	unsigned int index = port->index;
+	void __iomem *io_base = port->drvdata->io_base;
+	struct input_dev *idev;
+	int retval = 0;
+
+	/*
+	 * Determine input device type from SI id.
+	 */
+	port->id = si_get_controller_id(io_base, index);
+	if (port->id == ID_PAD) {
+		port->type = CTL_PAD;
+		strcpy(port->name, "standard pad");
+	} else if (port->id & ID_WIRELESS_BIT) {
+		/* wireless pad */
+		port->type = CTL_PAD;
+		strcpy(port->name, (port->id & ID_WAVEBIRD_BIT) ?
+		       "Nintendo Wavebird" : "wireless pad");
+	} else if (port->id == ID_KEYBOARD) {
+		port->type = CTL_KEYBOARD;
+		strcpy(port->name, "keyboard");
+	} else {
+		port->type = CTL_UNKNOWN;
+		if (port->id) {
+			sprintf(port->name, "unknown (%x)",
+				port->id);
+#ifdef HACK_FORCE_KEYBOARD_PORT
+			if (index+1 == si_force_keyboard_port) {
+				drv_printk(KERN_WARNING,
+					  "port %d forced to keyboard mode\n",
+					  index+1);
+				port->id = ID_KEYBOARD;
+				port->type = CTL_KEYBOARD;
+				strcpy(port->name, "keyboard (forced)");
+			}
+#endif /* HACK_FORCE_KEYBOARD_PORT */
+		} else {
+			strcpy(port->name, "not present");
+		}
+	}
+
+	if (port->type == CTL_UNKNOWN) {
+		retval = -ENODEV;
+		goto done;
+	}
+
+	idev = input_allocate_device();
+	if (!idev) {
+		drv_printk(KERN_ERR, "failed to allocate input_dev\n");
+		retval = -ENOMEM;
+		goto done;
+	}
+
+	idev->open = si_open;
+	idev->close = si_close;
+	idev->name = port->name;
+
+	switch (port->type) {
+	case CTL_PAD:
+		retval = si_setup_pad(idev);
+		break;
+	case CTL_KEYBOARD:
+		si_setup_keyboard(idev);
+		break;
+	default:
+		break;
+	}
+
+	if (retval) {
+		input_free_device(idev);
+		goto done;
+	}
+
+	input_set_drvdata(idev, port);
+	port->idev = idev;
+
+done:
+	return retval;
+}
+
+/*
+ * Setup routines.
+ *
+ */
+
+static int si_init(struct si_drvdata *drvdata, struct resource *mem)
+{
+	struct si_port *port;
+	int index;
+	int retval;
+	int error;
+
+	drvdata->io_base = ioremap(mem->start, mem->end - mem->start + 1);
+
+	si_reset_all(drvdata->io_base);
+	for (index = 0; index < SI_MAX_PORTS; ++index) {
+		port = &drvdata->ports[index];
+
+		memset(port, 0, sizeof(*port));
+		port->index = index;
+		port->drvdata = drvdata;
+
+		retval = si_port_probe(port);
+		if (!retval) {
+			error = input_register_device(port->idev);
+			if (error) {
+				drv_printk(KERN_ERR,
+					   "input device registration failed"
+					   " (%d) for port %d", error, index+1);
+				port->idev = NULL;
+			} else
+				drv_printk(KERN_INFO, "port %d: %s\n",
+					   index+1, port->name);
+		}
+	}
+
+	si_setup_polling(drvdata);
+
+	return 0;
+}
+
+static void si_exit(struct si_drvdata *drvdata)
+{
+	struct si_port *port;
+	int index;
+
+	for (index = 0; index < SI_MAX_PORTS; ++index) {
+		port = &drvdata->ports[index];
+		if (port->idev)
+			input_unregister_device(port->idev);
+	}
+
+	if (drvdata->io_base) {
+		iounmap(drvdata->io_base);
+		drvdata->io_base = NULL;
+	}
+}
+
+/*
+ * Driver model helper routines.
+ *
+ */
+
+static int si_do_probe(struct device *dev, struct resource *mem)
+{
+	struct si_drvdata *drvdata;
+	int retval;
+
+	drvdata = kzalloc(sizeof(*drvdata), GFP_KERNEL);
+	if (!drvdata) {
+		drv_printk(KERN_ERR, "failed to allocate si_drvdata\n");
+		return -ENOMEM;
+	}
+	dev_set_drvdata(dev, drvdata);
+	drvdata->dev = dev;
+
+	retval = si_init(drvdata, mem);
+	if (retval) {
+		dev_set_drvdata(dev, NULL);
+		kfree(drvdata);
+	}
+	return retval;
+}
+
+static int si_do_remove(struct device *dev)
+{
+	struct si_drvdata *drvdata = dev_get_drvdata(dev);
+
+	if (drvdata) {
+		si_exit(drvdata);
+		dev_set_drvdata(dev, NULL);
+		kfree(drvdata);
+		return 0;
+	}
+	return -ENODEV;
+}
+
+static int si_do_shutdown(struct device *dev)
+{
+	struct si_drvdata *drvdata = dev_get_drvdata(dev);
+	int i;
+
+	if (drvdata) {
+		drvdata->flags |= SI_QUIESCE;
+		for (i = 0; i < SI_MAX_PORTS; ++i)
+			del_timer_sync(&drvdata->ports[i].timer);
+		si_reset_all(drvdata->io_base);
+	}
+	return 0;
+}
+
+
+/*
+ * OF platform driver hooks.
+ *
+ */
+
+static int __init si_of_probe(struct of_device *odev,
+			       const struct of_device_id *match)
+{
+	struct resource mem;
+	int retval;
+
+	retval = of_address_to_resource(odev->node, 0, &mem);
+	if (retval) {
+		drv_printk(KERN_ERR, "no io memory range found\n");
+		return -ENODEV;
+	}
+
+	return si_do_probe(&odev->dev, &mem);
+}
+
+static int __exit si_of_remove(struct of_device *odev)
+{
+	return si_do_remove(&odev->dev);
+}
+
+static int si_of_shutdown(struct of_device *odev)
+{
+	return si_do_shutdown(&odev->dev);
+}
+
+static struct of_device_id si_of_match[] = {
+	{ .compatible = "nintendo,flipper-serial" },
+	{ .compatible = "nintendo,hollywood-serial" },
+	{ },
+};
+
+
+MODULE_DEVICE_TABLE(of, si_of_match);
+
+static struct of_platform_driver si_of_driver = {
+	.owner = THIS_MODULE,
+	.name = DRV_MODULE_NAME,
+	.match_table = si_of_match,
+	.probe = si_of_probe,
+	.remove = si_of_remove,
+	.shutdown = si_of_shutdown,
+};
+
+
+/*
+ * Module interface hooks.
+ *
+ */
+
+static int __init si_init_module(void)
+{
+	drv_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION,
+		   si_driver_version);
+
+	return of_register_platform_driver(&si_of_driver);
+}
+
+static void __exit si_exit_module(void)
+{
+	of_unregister_platform_driver(&si_of_driver);
+}
+
+module_init(si_init_module);
+module_exit(si_exit_module);
+
+MODULE_DESCRIPTION(DRV_DESCRIPTION);
+MODULE_AUTHOR(DRV_AUTHOR);
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index df1f86b..d6cd229 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -55,6 +55,28 @@ config ATMEL_TCB_CLKSRC_BLOCK
 	  TC can be used for other purposes, such as PWM generation and
 	  interval timing.
 
+config GAMECUBE_GQR
+	tristate "Nintendo GameCube/Wii Graphic Quantization Registers (GQR)"
+	depends on GAMECUBE_COMMON
+	help
+          This option enables device driver support for the Gekko/Broadway
+	  processors' Graphic Quantization Registers.
+	  These registers are used with the psql and psqst instructions.
+	  The registers will appear in /proc/sys/gqr.
+
+config GAMECUBE_MI
+	tristate "Nintendo GameCube Memory Interface (MI)"
+	depends on GAMECUBE
+	help
+	  If you say yes to this option, support will be included for the
+	  Memory Interface (MI) of the Nintendo GameCube.
+
+	  The MI allows one to setup up to four protected memory regions,
+	  catching invalid accesses to them. The MI catches out of bounds
+	  memory accesses too.
+
+	  If in doubt, say N here.
+
 config IBM_ASM
 	tristate "Device driver for IBM RSA service processor"
 	depends on X86 && PCI && INPUT && EXPERIMENTAL
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index f982d2e..4b74341 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -21,5 +21,7 @@ obj-$(CONFIG_HP_ILO)		+= hpilo.o
 obj-$(CONFIG_ISL29003)		+= isl29003.o
 obj-$(CONFIG_EP93XX_PWM)	+= ep93xx_pwm.o
 obj-$(CONFIG_C2PORT)		+= c2port/
+obj-$(CONFIG_GAMECUBE_GQR)	+= gcn-gqr.o
+obj-$(CONFIG_GAMECUBE_MI)	+= gcn-mi.o
 obj-y				+= eeprom/
 obj-y				+= cb710/
diff --git a/drivers/misc/gcn-gqr.c b/drivers/misc/gcn-gqr.c
new file mode 100644
index 0000000..ff390eb
--- /dev/null
+++ b/drivers/misc/gcn-gqr.c
@@ -0,0 +1,129 @@
+/*
+ * drivers/misc/gcn-gqr.c
+ *
+ * Nintendo GameCube GQR driver
+ * Copyright (C) 2004-2009 The GameCube Linux Team
+ * Copyright (C) 2004 Todd Jeffreys <todd@voidpointer.org>
+ * Copyright (C) 2007,2008,2009 Albert Herranz
+ *
+ * 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/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/ctype.h>
+#include <linux/sysctl.h>
+#include <linux/types.h>
+#include <linux/uaccess.h>
+
+static u32 gqr_values[8];
+static struct ctl_table_header *gqr_table_header;
+
+#define SPR_GQR0 912
+#define SPR_GQR1 913
+#define SPR_GQR2 914
+#define SPR_GQR3 915
+#define SPR_GQR4 916
+#define SPR_GQR5 917
+#define SPR_GQR6 918
+#define SPR_GQR7 919
+
+#define MFSPR_CASE(i) case (i): (*((u32 *)table->data) = mfspr(SPR_GQR##i))
+#define MTSPR_CASE(i) case (i): mtspr(SPR_GQR##i, *((u32 *)table->data))
+
+static int proc_dogqr(struct ctl_table *table, int write,
+		      void __user *buffer, size_t *lenp, loff_t *ppos)
+{
+	int r;
+
+	if (!write) {		/* if they are reading, update the variable */
+		switch (table->data - (void *)gqr_values) {
+			MFSPR_CASE(0); break;
+			MFSPR_CASE(1); break;
+			MFSPR_CASE(2); break;
+			MFSPR_CASE(3); break;
+			MFSPR_CASE(4); break;
+			MFSPR_CASE(5); break;
+			MFSPR_CASE(6); break;
+			MFSPR_CASE(7); break;
+		default:
+			return -EFAULT;	/* shouldn't happen */
+		}
+	}
+
+	r = proc_dointvec(table, write, buffer, lenp, ppos);
+
+	if ((r == 0) && write) {  /* if they are writing, update the reg */
+		switch (table->data - (void *)gqr_values) {
+			MTSPR_CASE(0); break;
+			MTSPR_CASE(1); break;
+			MTSPR_CASE(2); break;
+			MTSPR_CASE(3); break;
+			MTSPR_CASE(4); break;
+			MTSPR_CASE(5); break;
+			MTSPR_CASE(6); break;
+			MTSPR_CASE(7); break;
+		default:
+			return -EFAULT;	/* shouldn't happen */
+		}
+	}
+
+	return r;
+}
+
+#define DECLARE_GQR(i) {  \
+		.ctl_name     = CTL_UNNUMBERED,       \
+		.procname     = "gqr" #i,         \
+		.data         = gqr_values + i,   \
+		.maxlen       = sizeof(int),      \
+		.mode         = 0644,             \
+		.proc_handler = &proc_dogqr       \
+	}
+
+static struct ctl_table gqr_members[] = {
+	DECLARE_GQR(0),
+	DECLARE_GQR(1),
+	DECLARE_GQR(2),
+	DECLARE_GQR(3),
+	DECLARE_GQR(4),
+	DECLARE_GQR(5),
+	DECLARE_GQR(6),
+	DECLARE_GQR(7),
+	{ .ctl_name = 0 }
+};
+
+static struct ctl_table gqr_table[] = {
+	{
+		.ctl_name = CTL_UNNUMBERED,
+		.procname = "gqr",
+		.mode     = 0555,
+		.child    = gqr_members,
+	},
+	{ .ctl_name = 0 }
+};
+
+int __init gcngqr_init(void)
+{
+	gqr_table_header = register_sysctl_table(gqr_table);
+	if (!gqr_table_header) {
+		printk(KERN_ERR "Unable to register GQR sysctl table\n");
+		return -ENOMEM;
+	}
+	return 0;
+}
+
+void __exit gcngqr_exit(void)
+{
+	unregister_sysctl_table(gqr_table_header);
+}
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Todd Jeffreys <todd@voidpointer.org>");
+module_init(gcngqr_init);
+module_exit(gcngqr_exit);
diff --git a/drivers/misc/gcn-mi.c b/drivers/misc/gcn-mi.c
new file mode 100644
index 0000000..3ecc402
--- /dev/null
+++ b/drivers/misc/gcn-mi.c
@@ -0,0 +1,445 @@
+/*
+ * drivers/misc/gcn-mi.c
+ *
+ * Nintendo GameCube Memory Interface (MI) driver.
+ * Copyright (C) 2004-2009 The GameCube Linux Team
+ * Copyright (C) 2004,2005,2007,2008,2009 Albert Herranz
+ *
+ * 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/device.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/proc_fs.h>
+#include <linux/io.h>
+
+#include "gcn-mi.h"
+
+
+#define MI_IRQ	7
+
+#define MI_BASE			0xcc004000
+#define MI_SIZE			0x80
+
+#define MI_PROT_REGION0		((u32 __iomem *)(MI_BASE+0x00))
+#define MI_PROT_REGION1		((u32 __iomem *)(MI_BASE+0x04))
+#define MI_PROT_REGION2		((u32 __iomem *)(MI_BASE+0x08))
+#define MI_PROT_REGION3		((u32 __iomem *)(MI_BASE+0x0c))
+
+#define MI_PROT_TYPE		((u16 __iomem *)(MI_BASE+0x10))
+
+#define MI_IMR			((u16 __iomem *)(MI_BASE+0x1c))
+#define MI_ICR			((u16 __iomem *)(MI_BASE+0x1e))
+
+#define MI_0x4020		((u16 __iomem *)(MI_BASE+0x20))
+
+#define MI_ADDRLO		((u16 __iomem *)(MI_BASE+0x22))
+#define MI_ADDRHI		((u16 __iomem *)(MI_BASE+0x24))
+
+#define MI_PAGE_SHIFT	10
+#define MI_PAGE_MASK	(~((1 << MI_PAGE_SHIFT) - 1))
+#define MI_PAGE_SIZE	(1UL << MI_PAGE_SHIFT)
+
+struct mi_private {
+	struct device *device;
+	int	irq;
+	int	nr_regions;
+	int	regions_bitmap;
+	unsigned long faults[MI_MAX_REGIONS+1];
+	unsigned long last_address;
+	unsigned long last_address_faults;
+	spinlock_t lock;
+#ifdef CONFIG_PROC_FS
+	struct proc_dir_entry *proc_file;
+#endif
+};
+
+static struct mi_private *mi_private;
+
+#define DRV_MODULE_NAME      "gcn-mi"
+#define DRV_DESCRIPTION      "Nintendo GameCube Memory Interface driver"
+#define DRV_AUTHOR           "Albert Herranz"
+
+#define PFX DRV_MODULE_NAME ": "
+#define mi_printk(level, format, arg...) \
+	printk(level PFX format , ## arg)
+
+/*
+ *
+ */
+static irqreturn_t mi_handler(int this_irq, void *data)
+{
+	struct mi_private *priv = (struct mi_private *)data;
+	unsigned long flags;
+	int region, cause, ack;
+	unsigned long address;
+
+	spin_lock_irqsave(&priv->lock, flags);
+
+	address = in_be16(MI_ADDRLO) | (in_be16(MI_ADDRHI)<<16);
+
+	ack = 0;
+	cause = in_be16(MI_ICR);
+
+	/* a fault was detected in some of the registered regions */
+	if ((cause & 0xf) != 0) {
+		for (region = 0; region < MI_MAX_REGIONS; region++) {
+			if ((cause & (1 << region)) != 0) {
+				priv->faults[region]++;
+				mi_printk(KERN_INFO, "bad access on region #%d"
+					  " at 0x%lx\n", region, address);
+			}
+		}
+	}
+
+	/* a fault was detected out of any registered region */
+	if ((cause & (1 << 4)) != 0) {
+		priv->faults[MI_MAX_REGIONS]++;
+		if (address == priv->last_address) {
+			priv->last_address_faults++;
+		} else {
+			if (priv->last_address_faults > 0) {
+#if 0
+				mi_printk(KERN_INFO, "bad access"
+					  " at 0x%lx (%lu times)\n",
+					  priv->last_address,
+					  priv->last_address_faults);
+#endif
+			}
+			priv->last_address = address;
+			priv->last_address_faults = 1;
+		}
+	}
+	ack |= cause;
+	out_be16(MI_ICR, ack); /* ack int */
+	out_be16(MI_0x4020, 0); /* kind of ack */
+
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	return IRQ_HANDLED;
+}
+
+#ifdef CONFIG_PROC_FS
+/*
+ *
+ */
+static int mi_proc_read(char *page, char **start,
+			    off_t off, int count,
+			    int *eof, void *data)
+{
+	struct mi_private *priv = (struct mi_private *)data;
+	int len;
+	int region;
+
+	len = sprintf(page, "# <region> <faults>\n");
+	for (region = 0; region < MI_MAX_REGIONS; region++) {
+		if ((priv->regions_bitmap & (1<<region)) != 0) {
+			len += sprintf(page+len, "%d\t%lu\n",
+				       region, priv->faults[region]);
+		}
+	}
+	len += sprintf(page+len, "%s\t%lu\n",
+		       "none", priv->faults[MI_MAX_REGIONS]);
+
+	return len;
+}
+
+#endif /* CONFIG_PROC_FS */
+
+/*
+ *
+ */
+static int mi_setup_irq(struct mi_private *priv)
+{
+	int retval;
+
+	retval = request_irq(priv->irq, mi_handler, 0, DRV_MODULE_NAME, priv);
+	if (retval)
+		mi_printk(KERN_ERR, "request of irq%d failed\n", priv->irq);
+	else
+		out_be16(MI_IMR, (1<<4)); /* do not mask all MI interrupts */
+
+	return retval;
+}
+
+/*
+ *
+ */
+static int mi_probe(struct device *device, struct resource *mem, int irq)
+{
+	struct mi_private *priv;
+	int retval;
+
+	priv = kmalloc(sizeof(struct mi_private), GFP_KERNEL);
+	if (!priv) {
+		retval = -ENOMEM;
+		goto err;
+	}
+
+	memset(priv, 0, sizeof(*priv));
+	/*
+	int region;
+	priv->nr_regions = 0;
+	priv->regions_bitmap = 0;
+	for( region = 0; region < MI_MAX_REGIONS; region++ ) {
+		priv->faults[region] = 0;
+	}
+	priv->last_address_faults = 0;
+	*/
+	spin_lock_init(&priv->lock);
+
+	priv->device = device;
+	dev_set_drvdata(priv->device, priv);
+
+	priv->irq = irq;
+	retval = mi_setup_irq(priv);
+	if (retval)
+		goto err_setup_irq;
+
+#ifdef CONFIG_PROC_FS
+	{
+	struct platform_device *pdev = to_platform_device(device);
+	priv->proc_file = create_proc_read_entry(dev_name(&pdev->dev),
+						 0444, NULL,
+						 mi_proc_read, priv);
+	}
+#endif /* CONFIG_PROC_FS */
+
+	mi_private = priv;
+
+	return 0;
+
+err_setup_irq:
+	dev_set_drvdata(priv->device, NULL);
+	kfree(priv);
+err:
+	return retval;
+}
+
+/*
+ *
+ */
+static void mi_shutdown(struct mi_private *priv)
+{
+	gcn_mi_region_unprotect_all();
+}
+
+/*
+ *
+ */
+static void mi_remove(struct mi_private *priv)
+{
+#ifdef CONFIG_PROC_FS
+	struct platform_device *pdev = to_platform_device(priv->device);
+	remove_proc_entry(dev_name(&pdev->dev), NULL);
+#endif /* CONFIG_PROC_FS */
+
+	mi_shutdown(priv);
+
+	/* free interrupt handler */
+	free_irq(priv->irq, priv);
+
+	kfree(priv);
+	mi_private = NULL;
+}
+
+/*
+ *
+ */
+static int __init mi_drv_probe(struct device *device)
+{
+	struct platform_device *pdev = to_platform_device(device);
+	struct resource *mem;
+	int irq;
+
+	irq = platform_get_irq(pdev, 0);
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!mem)
+		return -ENODEV;
+
+	mi_printk(KERN_INFO, "%s\n", DRV_DESCRIPTION);
+
+	return mi_probe(device, mem, irq);
+}
+
+/*
+ *
+ */
+static int mi_drv_remove(struct device *device)
+{
+	struct mi_private *priv = dev_get_drvdata(device);
+
+	if (priv) {
+		mi_remove(priv);
+		dev_set_drvdata(device, NULL);
+	}
+
+	return 0;
+}
+
+/*
+ *
+ */
+static void mi_drv_shutdown(struct device *device)
+{
+	struct mi_private *priv = dev_get_drvdata(device);
+
+	if (priv)
+		mi_shutdown(priv);
+}
+
+static struct device_driver mi_device_driver = {
+	.name = "mi",
+	.bus = &platform_bus_type,
+	.probe = mi_drv_probe,
+	.remove = mi_drv_remove,
+	.shutdown = mi_drv_shutdown,
+};
+
+static struct resource mi_resources[] = {
+	[0] = {
+		.start = MI_BASE,
+		.end = MI_BASE + MI_SIZE - 1,
+		.flags = IORESOURCE_MEM,
+	},
+	[1] = {
+		.start = MI_IRQ,
+		.end = MI_IRQ,
+		.flags = IORESOURCE_IRQ,
+	},
+};
+
+static struct platform_device mi_device = {
+	.name = "mi",
+	.id = 0,
+	.num_resources = ARRAY_SIZE(mi_resources),
+	.resource = mi_resources,
+};
+
+/*
+ *
+ */
+static int __init mi_init(void)
+{
+	int retval = 0;
+
+	retval = driver_register(&mi_device_driver);
+	if (!retval)
+		retval = platform_device_register(&mi_device);
+
+	return retval;
+}
+
+/*
+ *
+ */
+static void __exit mi_exit(void)
+{
+	platform_device_unregister(&mi_device);
+	driver_unregister(&mi_device_driver);
+}
+
+module_init(mi_init);
+module_exit(mi_exit);
+
+
+/* public interface */
+
+/*
+ *
+ */
+int gcn_mi_region_protect(unsigned long physlo, unsigned long physhi, int type)
+{
+	struct mi_private *priv = mi_private;
+	int region, free_regions;
+	u16 pagelo, pagehi;
+
+	if (!priv)
+		return -ENODEV;
+
+	if (type < MI_PROT_NONE || type > MI_PROT_RW)
+		return -EINVAL;
+
+	if ((physlo & ~MI_PAGE_MASK) != 0 || (physhi & ~MI_PAGE_MASK) != 0)
+		return -EINVAL;
+
+	free_regions = MI_MAX_REGIONS - priv->nr_regions;
+	if (free_regions <= 0)
+		return -ENOMEM;
+	for (region = 0; region < MI_MAX_REGIONS; region++) {
+		if ((priv->regions_bitmap & (1<<region)) == 0)
+			break;
+	}
+	if (region >= MI_MAX_REGIONS)
+		return -ENOMEM;
+	priv->regions_bitmap |= (1 << region);
+	priv->nr_regions++;
+
+	out_be16(MI_PROT_TYPE,
+		(in_be16(MI_PROT_TYPE) & ~(3 << 2*region))|(type << 2*region));
+	pagelo = physlo >> MI_PAGE_SHIFT;
+	pagehi = (physhi >> MI_PAGE_SHIFT) - 1;
+	out_be32(MI_PROT_REGION0 + 4*region, (pagelo << 16) | pagehi);
+	out_be16(MI_IMR, in_be16(MI_IMR) | (1 << region));
+
+	mi_printk(KERN_INFO, "protected region #%d"
+		  " from 0x%0lx to 0x%0lx with 0x%0x\n", region,
+		  (unsigned long)(pagelo << MI_PAGE_SHIFT),
+		  (unsigned long)(((pagehi+1) << MI_PAGE_SHIFT) - 1),
+		  type);
+
+	return region;
+}
+
+/*
+ *
+ */
+int gcn_mi_region_unprotect(int region)
+{
+	struct mi_private *priv = mi_private;
+
+	if (!priv)
+		return -ENODEV;
+
+	if (region < 0 || region > MI_MAX_REGIONS)
+		return -EINVAL;
+
+	out_be16(MI_IMR, in_be16(MI_IMR) & ~(1 << region));
+	out_be32(MI_PROT_REGION0 + 4*region, 0);
+	out_be16(MI_PROT_TYPE,
+		 in_be16(MI_PROT_TYPE) | (MI_PROT_RW << 2*region));
+
+	if ((priv->regions_bitmap & (1<<region)) != 0)
+		mi_printk(KERN_INFO, "region #%d unprotected\n", region);
+
+	priv->regions_bitmap &= ~(1 << region);
+	priv->nr_regions--;
+
+	return 0;
+}
+
+/*
+ *
+ */
+void gcn_mi_region_unprotect_all(void)
+{
+	int region;
+
+	out_be16(MI_IMR, 0);
+	for (region = 0; region < MI_MAX_REGIONS; region++)
+		gcn_mi_region_unprotect(region);
+}
+
+MODULE_DESCRIPTION(DRV_DESCRIPTION);
+MODULE_AUTHOR(DRV_AUTHOR);
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/gcn-mi.h b/drivers/misc/gcn-mi.h
new file mode 100644
index 0000000..201387b
--- /dev/null
+++ b/drivers/misc/gcn-mi.h
@@ -0,0 +1,30 @@
+/*
+ * drivers/misc/gcn-mi.h
+ *
+ * Nintendo GameCube Memory Interface driver
+ * Copyright (C) 2004-2009 The GameCube Linux Team
+ * Copyright (C) 2004,2005,2008,2009 Albert Herranz
+ *
+ * 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.
+ *
+ */
+
+#ifndef __GCN_MI_H
+#define __GCN_MI_H
+
+#define MI_MAX_REGIONS  4
+
+#define MI_PROT_NONE 0x00
+#define MI_PROT_RO   0x01
+#define MI_PROT_WO   0x02
+#define MI_PROT_RW   0x03
+
+int gcn_mi_region_protect(unsigned long physlo, unsigned long physhi, int type);
+int gcn_mi_region_unprotect(int region);
+void gcn_mi_region_unprotect_all(void);
+
+#endif /* __GCN_MI_H */
+
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index 7dab2e5..7afcf2c 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -1073,6 +1073,7 @@ void mmc_rescan(struct work_struct *work)
 	mmc_claim_host(host);
 
 	mmc_power_up(host);
+	sdio_reset(host);
 	mmc_go_idle(host);
 
 	mmc_send_if_cond(host, host->ocr_avail);
diff --git a/drivers/mmc/core/sdio_cis.c b/drivers/mmc/core/sdio_cis.c
index f85dcd5..9538389 100644
--- a/drivers/mmc/core/sdio_cis.c
+++ b/drivers/mmc/core/sdio_cis.c
@@ -97,26 +97,56 @@ static const unsigned char speed_val[16] =
 static const unsigned int speed_unit[8] =
 	{ 10000, 100000, 1000000, 10000000, 0, 0, 0, 0 };
 
-/* FUNCE tuples with these types get passed to SDIO drivers */
-static const unsigned char funce_type_whitelist[] = {
-	4 /* CISTPL_FUNCE_LAN_NODE_ID used in Broadcom cards */
+
+typedef int (tpl_parse_t)(struct mmc_card *, struct sdio_func *,
+			   const unsigned char *, unsigned);
+
+struct cis_tpl {
+	unsigned char code;
+	unsigned char min_size;
+	tpl_parse_t *parse;
 };
 
-static int cistpl_funce_whitelisted(unsigned char type)
+static int cis_tpl_parse(struct mmc_card *card, struct sdio_func *func,
+			 const char *tpl_descr,
+			 const struct cis_tpl *tpl, int tpl_count,
+			 unsigned char code,
+			 const unsigned char *buf, unsigned size)
 {
-	int i;
+	int i, ret;
 
-	for (i = 0; i < ARRAY_SIZE(funce_type_whitelist); i++) {
-		if (funce_type_whitelist[i] == type)
-			return 1;
+	/* look for a matching code in the table */
+	for (i = 0; i < tpl_count; i++, tpl++) {
+		if (tpl->code == code)
+			break;
 	}
-	return 0;
+	if (i < tpl_count) {
+		if (size >= tpl->min_size) {
+			if (tpl->parse)
+				ret = tpl->parse(card, func, buf, size);
+			else
+				ret = -EILSEQ;	/* known tuple, not parsed */
+		} else {
+			/* invalid tuple */
+			ret = -EINVAL;
+		}
+		if (ret && ret != -EILSEQ && ret != -ENOENT) {
+			printk(KERN_ERR "%s: bad %s tuple 0x%02x (%u bytes)\n",
+			       mmc_hostname(card->host), tpl_descr, code, size);
+		}
+	} else {
+		/* unknown tuple */
+		ret = -ENOENT;
+	}
+
+	return ret;
 }
 
-static int cistpl_funce_common(struct mmc_card *card,
+static int cistpl_funce_common(struct mmc_card *card, struct sdio_func *func,
 			       const unsigned char *buf, unsigned size)
 {
-	if (size < 0x04 || buf[0] != 0)
+	/* Only valid for the common CIS (function 0) */
+	if (func)
 		return -EINVAL;
 
 	/* TPLFE_FN0_BLK_SIZE */
@@ -129,20 +159,24 @@ static int cistpl_funce_common(struct mmc_card *card,
 	return 0;
 }
 
-static int cistpl_funce_func(struct sdio_func *func,
+static int cistpl_funce_func(struct mmc_card *card, struct sdio_func *func,
 			     const unsigned char *buf, unsigned size)
 {
 	unsigned vsn;
 	unsigned min_size;
 
-	/* let SDIO drivers take care of whitelisted FUNCE tuples */
-	if (cistpl_funce_whitelisted(buf[0]))
-		return -EILSEQ;
+	/* Only valid for the individual function's CIS (1-7) */
+	if (!func)
+		return -EINVAL;
 
+	/*
+	 * This tuple has a different length depending on the SDIO spec
+	 * version.
+	 */
 	vsn = func->card->cccr.sdio_vsn;
 	min_size = (vsn == SDIO_SDIO_REV_1_00) ? 28 : 42;
 
-	if (size < min_size || buf[0] != 1)
+	if (size < min_size)
 		return -EINVAL;
 
 	/* TPLFE_MAX_BLK_SIZE */
@@ -157,39 +191,32 @@ static int cistpl_funce_func(struct sdio_func *func,
 	return 0;
 }
 
+/*
+ * Known TPLFE_TYPEs table for CISTPL_FUNCE tuples.
+ *
+ * Note that, unlike PCMCIA, CISTPL_FUNCE tuples are not parsed depending
+ * on the TPLFID_FUNCTION value of the previous CISTPL_FUNCID as on SDIO
+ * TPLFID_FUNCTION is always hardcoded to 0x0C.
+ */
+static const struct cis_tpl cis_tpl_funce_list[] = {
+	{	0x00,	4,	cistpl_funce_common		},
+	{	0x01,	0,	cistpl_funce_func		},
+	{	0x04,	1+1+6,	/* CISTPL_FUNCE_LAN_NODE_ID */	},
+};
+
 static int cistpl_funce(struct mmc_card *card, struct sdio_func *func,
 			const unsigned char *buf, unsigned size)
 {
-	int ret;
-
-	/*
-	 * There should be two versions of the CISTPL_FUNCE tuple,
-	 * one for the common CIS (function 0) and a version used by
-	 * the individual function's CIS (1-7). Yet, the later has a
-	 * different length depending on the SDIO spec version.
-	 */
-	if (func)
-		ret = cistpl_funce_func(func, buf, size);
-	else
-		ret = cistpl_funce_common(card, buf, size);
-
-	if (ret && ret != -EILSEQ) {
-		printk(KERN_ERR "%s: bad CISTPL_FUNCE size %u "
-		       "type %u\n", mmc_hostname(card->host), size, buf[0]);
-	}
+	if (size < 1)
+		return -EINVAL;
 
-	return ret;
+	return cis_tpl_parse(card, func, "CISTPL_FUNCE",
+			     cis_tpl_funce_list,
+			     ARRAY_SIZE(cis_tpl_funce_list),
+			     buf[0], buf, size);
 }
 
-typedef int (tpl_parse_t)(struct mmc_card *, struct sdio_func *,
-			   const unsigned char *, unsigned);
-
-struct cis_tpl {
-	unsigned char code;
-	unsigned char min_size;
-	tpl_parse_t *parse;
-};
-
+/* Known TPL_CODEs table for CIS tuples */
 static const struct cis_tpl cis_tpl_list[] = {
 	{	0x15,	3,	cistpl_vers_1		},
 	{	0x20,	4,	cistpl_manfid		},
@@ -268,46 +295,38 @@ static int sdio_read_cis(struct mmc_card *card, struct sdio_func *func)
 			break;
 		}
 
-		for (i = 0; i < ARRAY_SIZE(cis_tpl_list); i++)
-			if (cis_tpl_list[i].code == tpl_code)
-				break;
-		if (i < ARRAY_SIZE(cis_tpl_list)) {
-			const struct cis_tpl *tpl = cis_tpl_list + i;
-			if (tpl_link < tpl->min_size) {
-				printk(KERN_ERR
-				       "%s: bad CIS tuple 0x%02x"
-				       " (length = %u, expected >= %u)\n",
-				       mmc_hostname(card->host),
-				       tpl_code, tpl_link, tpl->min_size);
-				ret = -EINVAL;
-			} else if (tpl->parse) {
-				ret = tpl->parse(card, func,
-						 this->data, tpl_link);
-			}
+		/* Try to parse the CIS tuple */
+		ret = cis_tpl_parse(card, func, "CIS",
+				    cis_tpl_list, ARRAY_SIZE(cis_tpl_list),
+				    tpl_code, this->data, tpl_link);
+		if (ret == -EILSEQ || ret == -ENOENT) {
 			/*
-			 * We don't need the tuple anymore if it was
-			 * successfully parsed by the SDIO core or if it is
-			 * not going to be parsed by SDIO drivers.
+			 * The tuple is unknown or known but not parsed.
+			 * Queue the tuple for the function driver.
 			 */
-			if (!ret || ret != -EILSEQ)
-				kfree(this);
-		} else {
-			/* unknown tuple */
-			ret = -EILSEQ;
-		}
-
-		if (ret == -EILSEQ) {
-			/* this tuple is unknown to the core or whitelisted */
 			this->next = NULL;
 			this->code = tpl_code;
 			this->size = tpl_link;
 			*prev = this;
 			prev = &this->next;
-			printk(KERN_DEBUG
-			       "%s: queuing CIS tuple 0x%02x length %u\n",
-			       mmc_hostname(card->host), tpl_code, tpl_link);
+
+			if (ret == -ENOENT) {
+				/* warn about unknown tuples */
+				printk(KERN_WARNING "%s: queuing unknown"
+				       " CIS tuple 0x%02x (%u bytes)\n",
+				       mmc_hostname(card->host),
+				       tpl_code, tpl_link);
+			}
+
 			/* keep on analyzing tuples */
 			ret = 0;
+		} else {
+			/*
+			 * We don't need the tuple anymore if it was
+			 * successfully parsed by the SDIO core or if it is
+			 * not going to be queued for a driver.
+			 */
+			kfree(this);
 		}
 
 		ptr += tpl_link;
diff --git a/drivers/mmc/core/sdio_ops.c b/drivers/mmc/core/sdio_ops.c
index 4eb7825..dea36d9 100644
--- a/drivers/mmc/core/sdio_ops.c
+++ b/drivers/mmc/core/sdio_ops.c
@@ -67,13 +67,13 @@ int mmc_send_io_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr)
 	return err;
 }
 
-int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn,
-	unsigned addr, u8 in, u8* out)
+static int mmc_io_rw_direct_host(struct mmc_host *host, int write, unsigned fn,
+	unsigned addr, u8 in, u8 *out)
 {
 	struct mmc_command cmd;
 	int err;
 
-	BUG_ON(!card);
+	BUG_ON(!host);
 	BUG_ON(fn > 7);
 
 	/* sanity check */
@@ -90,11 +90,11 @@ int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn,
 	cmd.arg |= in;
 	cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_AC;
 
-	err = mmc_wait_for_cmd(card->host, &cmd, 0);
+	err = mmc_wait_for_cmd(host, &cmd, 0);
 	if (err)
 		return err;
 
-	if (mmc_host_is_spi(card->host)) {
+	if (mmc_host_is_spi(host)) {
 		/* host driver already reported errors */
 	} else {
 		if (cmd.resp[0] & R5_ERROR)
@@ -106,7 +106,7 @@ int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn,
 	}
 
 	if (out) {
-		if (mmc_host_is_spi(card->host))
+		if (mmc_host_is_spi(host))
 			*out = (cmd.resp[0] >> 8) & 0xFF;
 		else
 			*out = cmd.resp[0] & 0xFF;
@@ -115,6 +115,13 @@ int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn,
 	return 0;
 }
 
+int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn,
+	unsigned addr, u8 in, u8 *out)
+{
+	BUG_ON(!card);
+	return mmc_io_rw_direct_host(card->host, write, fn, addr, in, out);
+}
+
 int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn,
 	unsigned addr, int incr_addr, u8 *buf, unsigned blocks, unsigned blksz)
 {
@@ -182,3 +189,20 @@ int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn,
 	return 0;
 }
 
+int sdio_reset(struct mmc_host *host)
+{
+	int ret;
+	u8 abort;
+
+	/* SDIO Simplified Specification V2.0, 4.4 Reset for SDIO */
+
+	ret = mmc_io_rw_direct_host(host, 0, 0, SDIO_CCCR_ABORT, 0, &abort);
+	if (ret)
+		abort = 0x08;
+	else
+		abort |= 0x08;
+
+	ret = mmc_io_rw_direct_host(host, 1, 0, SDIO_CCCR_ABORT, abort, NULL);
+	return ret;
+}
+
diff --git a/drivers/mmc/core/sdio_ops.h b/drivers/mmc/core/sdio_ops.h
index e2e74b0..12a4d3a 100644
--- a/drivers/mmc/core/sdio_ops.h
+++ b/drivers/mmc/core/sdio_ops.h
@@ -17,6 +17,7 @@ int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn,
 	unsigned addr, u8 in, u8* out);
 int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn,
 	unsigned addr, int incr_addr, u8 *buf, unsigned blocks, unsigned blksz);
+int sdio_reset(struct mmc_host *host);
 
 #endif
 
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 432ae83..1ef4127 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -119,6 +119,16 @@ config MMC_SDHCI_S3C_DMA
 
 	  YMMV.
 
+config MMC_SDHCI_HLWD
+	tristate "Nintendo Wii (Hollywood) SDHCI support"
+	depends on MMC_SDHCI && STARLET_MINI
+	select MMC_SDHCI_IO_ACCESSORS
+	help
+	  This selects the Nintendo Wii Secure Digital Host Controller
+	  Interface contained in the Hollywood chipset.
+
+	  If unsure, say N.
+
 config MMC_OMAP
 	tristate "TI OMAP Multimedia Card Interface support"
 	depends on ARCH_OMAP
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index abcb040..3012e00 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_MMC_RICOH_MMC)	+= ricoh_mmc.o
 obj-$(CONFIG_MMC_SDHCI_OF)	+= sdhci-of.o
 obj-$(CONFIG_MMC_SDHCI_PLTFM)	+= sdhci-pltfm.o
 obj-$(CONFIG_MMC_SDHCI_S3C)	+= sdhci-s3c.o
+obj-$(CONFIG_MMC_SDHCI_HLWD)	+= sdhci-hlwd.o
 obj-$(CONFIG_MMC_WBSD)		+= wbsd.o
 obj-$(CONFIG_MMC_AU1X)		+= au1xmmc.o
 obj-$(CONFIG_MMC_OMAP)		+= omap.o
diff --git a/drivers/mmc/host/sdhci-hlwd.c b/drivers/mmc/host/sdhci-hlwd.c
new file mode 100644
index 0000000..d827ea1
--- /dev/null
+++ b/drivers/mmc/host/sdhci-hlwd.c
@@ -0,0 +1,264 @@
+/*
+ * drivers/mmc/host/sdhci-hlwd.c
+ *
+ * Nintendo Wii Secure Digital Host Controller Interface.
+ * Copyright (C) 2009 The GameCube Linux Team
+ * Copyright (C) 2009 Albert Herranz
+ *
+ * Based on sdhci-of.c
+ *
+ * Copyright (c) 2007 Freescale Semiconductor, Inc.
+ * Copyright (c) 2009 MontaVista Software, Inc.
+ *
+ * Authors: Xiaobo Xie <X.Xie@freescale.com>
+ *          Anton Vorontsov <avorontsov@ru.mvista.com>
+ *
+ * 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/init.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/mmc/host.h>
+#include <asm/starlet.h>
+#include <asm/starlet-mini.h>
+#include "sdhci.h"
+
+#define DRV_MODULE_NAME "sdhci-hlwd"
+#define DRV_DESCRIPTION "Nintendo Wii Secure Digital Host Controller Interface"
+#define DRV_AUTHOR      "Albert Herranz"
+
+static char sdhci_hlwd_driver_version[] = "0.1i";
+
+#define drv_printk(level, format, arg...) \
+	printk(level DRV_MODULE_NAME ": " format , ## arg)
+
+#define DBG(fmt, arg...)	drv_printk(KERN_DEBUG, "%s: " fmt, \
+					   __func__, ## arg)
+
+
+struct sdhci_hlwd_data {
+	unsigned int quirks;
+	struct sdhci_ops ops;
+};
+
+struct sdhci_hlwd_host {
+	u16 xfer_mode_shadow;
+};
+
+static u32 sdhci_hlwd_readl(struct sdhci_host *host, int reg)
+{
+	return in_be32(host->ioaddr + reg);
+}
+
+static u16 sdhci_hlwd_readw(struct sdhci_host *host, int reg)
+{
+	return in_be16(host->ioaddr + (reg ^ 0x2));
+}
+
+static u8 sdhci_hlwd_readb(struct sdhci_host *host, int reg)
+{
+	return in_8(host->ioaddr + (reg ^ 0x3));
+}
+
+static void sdhci_hlwd_writel(struct sdhci_host *host, u32 val, int reg)
+{
+	out_be32(host->ioaddr + reg, val);
+	udelay(5);
+}
+
+static void sdhci_hlwd_writew(struct sdhci_host *host, u16 val, int reg)
+{
+	struct sdhci_hlwd_host *hlwd_host = sdhci_priv(host);
+	int base = reg & ~0x3;
+	int shift = (reg & 0x2) * 8;
+
+	switch (reg) {
+	case SDHCI_TRANSFER_MODE:
+		/*
+		 * Postpone this write, we must do it together with a
+		 * command write that is down below.
+		 */
+		hlwd_host->xfer_mode_shadow = val;
+		return;
+	case SDHCI_COMMAND:
+		sdhci_hlwd_writel(host, val << 16 | hlwd_host->xfer_mode_shadow,
+				  SDHCI_TRANSFER_MODE);
+		return;
+	}
+	clrsetbits_be32(host->ioaddr + base,
+			0xffff << shift, val << shift);
+	udelay(5);
+}
+
+static void sdhci_hlwd_writeb(struct sdhci_host *host, u8 val, int reg)
+{
+	int base = reg & ~0x3;
+	int shift = (reg & 0x3) * 8;
+
+	clrsetbits_be32(host->ioaddr + base , 0xff << shift, val << shift);
+	udelay(5);
+}
+
+static struct sdhci_hlwd_data sdhci_hlwd = {
+	.quirks = SDHCI_QUIRK_32BIT_DMA_ADDR |
+		  SDHCI_QUIRK_32BIT_DMA_SIZE,
+	.ops = {
+		.readl = sdhci_hlwd_readl,
+		.readw = sdhci_hlwd_readw,
+		.readb = sdhci_hlwd_readb,
+		.writel = sdhci_hlwd_writel,
+		.writew = sdhci_hlwd_writew,
+		.writeb = sdhci_hlwd_writeb,
+	},
+};
+
+
+#ifdef CONFIG_PM
+
+static int sdhci_hlwd_suspend(struct of_device *ofdev, pm_message_t state)
+{
+	struct sdhci_host *host = dev_get_drvdata(&ofdev->dev);
+
+	return mmc_suspend_host(host->mmc, state);
+}
+
+static int sdhci_hlwd_resume(struct of_device *ofdev)
+{
+	struct sdhci_host *host = dev_get_drvdata(&ofdev->dev);
+
+	return mmc_resume_host(host->mmc);
+}
+
+#else
+
+#define sdhci_hlwd_suspend NULL
+#define sdhci_hlwd_resume NULL
+
+#endif
+
+static int __devinit sdhci_hlwd_probe(struct of_device *ofdev,
+				      const struct of_device_id *match)
+{
+	struct device_node *np = ofdev->node;
+	struct sdhci_hlwd_data *sdhci_hlwd_data = match->data;
+	struct sdhci_host *host;
+	struct sdhci_hlwd_host *hlwd_host;
+	struct resource res;
+	int error;
+
+	if (starlet_get_ipc_flavour() != STARLET_IPC_MINI) {
+		error = -ENODEV;
+		goto out;
+	}
+
+	if (!of_device_is_available(np)) {
+		error = -ENODEV;
+		goto out;
+	}
+
+	host = sdhci_alloc_host(&ofdev->dev, sizeof(*hlwd_host));
+	if (!host) {
+		DBG("unable to allocate sdhci_host\n");
+		error = -ENODEV;
+		goto out;
+	}
+
+	dev_set_drvdata(&ofdev->dev, host);
+
+	error = of_address_to_resource(np, 0, &res);
+	if (error) {
+		DBG("of_address_to_resource failed (%d)\n", error);
+		goto err_no_address;
+	}
+
+	host->ioaddr = ioremap(res.start, resource_size(&res));
+	if (!host->ioaddr) {
+		DBG("ioremap failed\n");
+		error = -EINVAL;
+		goto err_ioremap;
+	}
+
+	host->irq = irq_of_parse_and_map(np, 0);
+	if (!host->irq) {
+		DBG("irq_of_parse_and_map failed\n");
+		error = -EINVAL;
+		goto err_no_irq;
+	}
+
+	host->hw_name = dev_name(&ofdev->dev);
+	if (sdhci_hlwd_data) {
+		host->quirks = sdhci_hlwd_data->quirks;
+		host->ops = &sdhci_hlwd_data->ops;
+	}
+
+	error = sdhci_add_host(host);
+	if (error) {
+		DBG("sdhci_add_host failed\n");
+		goto err_add_host;
+	}
+
+	return 0;
+
+err_add_host:
+	irq_dispose_mapping(host->irq);
+err_no_irq:
+	iounmap(host->ioaddr);
+err_ioremap:
+err_no_address:
+	sdhci_free_host(host);
+out:
+	return error;
+}
+
+static int __devexit sdhci_hlwd_remove(struct of_device *ofdev)
+{
+	struct sdhci_host *host = dev_get_drvdata(&ofdev->dev);
+
+	sdhci_remove_host(host, 0);
+	irq_dispose_mapping(host->irq);
+	iounmap(host->ioaddr);
+	sdhci_free_host(host);
+	return 0;
+}
+
+static const struct of_device_id sdhci_hlwd_match[] = {
+	{ .compatible = "nintendo,hollywood-sdhci", .data = &sdhci_hlwd, },
+	{},
+};
+MODULE_DEVICE_TABLE(of, sdhci_hlwd_match);
+
+static struct of_platform_driver sdhci_hlwd_driver = {
+	.driver.name = DRV_MODULE_NAME,
+	.match_table = sdhci_hlwd_match,
+	.probe = sdhci_hlwd_probe,
+	.remove = __devexit_p(sdhci_hlwd_remove),
+	.suspend = sdhci_hlwd_suspend,
+	.resume	= sdhci_hlwd_resume,
+};
+
+static int __init sdhci_hlwd_init(void)
+{
+	drv_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION,
+		   sdhci_hlwd_driver_version);
+
+	return of_register_platform_driver(&sdhci_hlwd_driver);
+}
+module_init(sdhci_hlwd_init);
+
+static void __exit sdhci_hlwd_exit(void)
+{
+	of_unregister_platform_driver(&sdhci_hlwd_driver);
+}
+module_exit(sdhci_hlwd_exit);
+
+MODULE_AUTHOR(DRV_AUTHOR);
+MODULE_DESCRIPTION(DRV_DESCRIPTION);
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index b2f71f7..90e68f8 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -270,6 +270,15 @@ config BMAC
 	  To compile this driver as a module, choose M here: the module
 	  will be called bmac.
 
+config GAMECUBE_BBA
+	tristate "Nintendo GameCube ethernet BroadBand Adapter (BBA)"
+	depends on GAMECUBE_EXI && GAMECUBE
+	help
+	  Say Y here to add ethernet support for the Broadband Adapter (BBA).
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called gcn-bba.
+
 config ARIADNE
 	tristate "Ariadne support"
 	depends on ZORRO
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index 246323d..1bc4d98 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -246,6 +246,7 @@ pasemi_mac_driver-objs := pasemi_mac.o pasemi_mac_ethtool.o
 obj-$(CONFIG_MLX4_CORE) += mlx4/
 obj-$(CONFIG_ENC28J60) += enc28j60.o
 obj-$(CONFIG_ETHOC) += ethoc.o
+obj-$(CONFIG_GAMECUBE_BBA) += gcn-bba.o
 
 obj-$(CONFIG_XTENSA_XT2000_SONIC) += xtsonic.o
 
diff --git a/drivers/net/gcn-bba.c b/drivers/net/gcn-bba.c
new file mode 100644
index 0000000..bc79d2c
--- /dev/null
+++ b/drivers/net/gcn-bba.c
@@ -0,0 +1,1263 @@
+/**
+ * drivers/net/gcn-bba.c
+ *
+ * Nintendo GameCube Broadband Adapter (BBA) driver
+ * Copyright (C) 2004-2009 The GameCube Linux Team
+ * Copyright (C) 2005 Todd Jeffreys
+ * Copyright (C) 2004,2005,2006,2007,2008,2009 Albert Herranz
+ *
+ * Based on previous work by Stefan Esser, Franz Lehner, Costis and tmbinc.
+ *
+ * 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.
+ *
+ */
+
+#define BBA_DEBUG
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/string.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/spinlock.h>
+#include <linux/kthread.h>
+#include <linux/wait.h>
+#include <linux/io.h>
+#include <linux/exi.h>
+#include <asm/system.h>
+
+
+#define DRV_MODULE_NAME	"gcn-bba"
+#define DRV_DESCRIPTION	"Nintendo GameCube Broadband Adapter (BBA) driver"
+#define DRV_AUTHOR	"Albert Herranz, " \
+			"Todd Jeffreys"
+
+static char bba_driver_version[] = "1.4i";
+
+
+#define bba_printk(level, format, arg...) \
+	 printk(level DRV_MODULE_NAME ": " format , ## arg)
+
+#ifdef BBA_DEBUG
+#  define DBG(fmt, args...) \
+	   printk(KERN_ERR "%s: " fmt, __func__ , ## args)
+#else
+#  define DBG(fmt, args...)
+#endif
+
+/*
+ * EXpansion Interface glue for the Broadband Adapter.
+ *
+ */
+#define BBA_EXI_ID 0x04020200
+
+#define BBA_EXI_IRQ_CHANNEL 2 /* INT line uses EXI2INTB */
+#define BBA_EXI_CHANNEL     0 /* rest of lines use EXI0xxx */
+#define BBA_EXI_DEVICE      2 /* chip select, EXI0CSB2 */
+#define BBA_EXI_FREQ        5 /* 32MHz */
+
+#define BBA_CMD_IR_MASKALL  0x00
+#define BBA_CMD_IR_MASKNONE 0xf8
+
+static inline void bba_select(void);
+static inline void bba_deselect(void);
+static inline void bba_write(void *data, size_t len);
+static inline void bba_read(void *data, size_t len);
+
+static void bba_ins(int reg, void *val, int len);
+static void bba_outs(int reg, void *val, int len);
+
+/*
+ * Command Registers I/O.
+ */
+
+static inline void bba_cmd_ins_nosel(int reg, void *val, int len)
+{
+	u16 req;
+	req = reg << 8;
+	bba_write(&req, sizeof(req));
+	bba_read(val, len);
+}
+
+static void bba_cmd_ins(int reg, void *val, int len)
+{
+	bba_select();
+	bba_cmd_ins_nosel(reg, val, len);
+	bba_deselect();
+}
+
+static inline void bba_cmd_outs_nosel(int reg, void *val, int len)
+{
+	u16 req;
+	req = (reg << 8) | 0x4000;
+	bba_write(&req, sizeof(req));
+	bba_write(val, len);
+}
+
+static void bba_cmd_outs(int reg, void *val, int len)
+{
+	bba_select();
+	bba_cmd_outs_nosel(reg, val, len);
+	bba_deselect();
+}
+
+static inline u8 bba_cmd_in8(int reg)
+{
+	u8 val;
+	bba_cmd_ins(reg, &val, sizeof(val));
+	return val;
+}
+
+static u8 bba_cmd_in8_slow(int reg)
+{
+	u8 val;
+	bba_select();
+	bba_cmd_ins_nosel(reg, &val, sizeof(val));
+	udelay(200);
+	bba_deselect();
+	return val;
+}
+
+static inline void bba_cmd_out8(int reg, u8 val)
+{
+	bba_cmd_outs(reg, &val, sizeof(val));
+}
+
+
+/*
+ * Registers I/O.
+ */
+
+static inline u8 bba_in8(int reg)
+{
+	u8 val;
+	bba_ins(reg, &val, sizeof(val));
+	return val;
+}
+
+static inline void bba_out8(int reg, u8 val)
+{
+	bba_outs(reg, &val, sizeof(val));
+}
+
+static inline u16 bba_in16(int reg)
+{
+	u16 val;
+	bba_ins(reg, &val, sizeof(val));
+	return le16_to_cpup(&val);
+}
+
+static inline void bba_out16(int reg, u16 val)
+{
+	cpu_to_le16s(&val);
+	bba_outs(reg, &val, sizeof(val));
+}
+
+#define bba_in12(reg)      (bba_in16(reg) & 0x0fff)
+#define bba_out12(reg, val) do { bba_out16(reg, (val)&0x0fff); } while (0)
+
+static inline void bba_ins_nosel(int reg, void *val, int len)
+{
+	u32 req;
+	req = (reg << 8) | 0x80000000;
+	bba_write(&req, sizeof(req));
+	bba_read(val, len);
+}
+
+static void bba_ins(int reg, void *val, int len)
+{
+	bba_select();
+	bba_ins_nosel(reg, val, len);
+	bba_deselect();
+}
+
+static inline void bba_outs_nosel(int reg, void *val, int len)
+{
+	u32 req;
+	req = (reg << 8) | 0xC0000000;
+	bba_write(&req, sizeof(req));
+	bba_write(val, len);
+}
+
+static inline void bba_outs_nosel_continued(void *val, int len)
+{
+	bba_write(val, len);
+}
+
+static void bba_outs(int reg, void *val, int len)
+{
+	bba_select();
+	bba_outs_nosel(reg, val, len);
+	bba_deselect();
+}
+
+
+/*
+ * Macronix mx98728ec supporting bits.
+ *
+ */
+
+#define BBA_NCRA 0x00		/* Network Control Register A, RW */
+#define   BBA_NCRA_RESET	(1<<0)	/* RESET */
+#define   BBA_NCRA_ST0		(1<<1)	/* ST0, Start transmit command/status */
+#define   BBA_NCRA_ST1		(1<<2)	/* ST1,  " */
+#define   BBA_NCRA_SR		(1<<3)	/* SR, Start Receive */
+
+#define BBA_NCRB 0x01		/* Network Control Register B, RW */
+#define   BBA_NCRB_PR		(1<<0)	/* PR, Promiscuous Mode */
+#define   BBA_NCRB_CA		(1<<1)	/* CA, Capture Effect Mode */
+#define   BBA_NCRB_PM		(1<<2)	/* PM, Pass Multicast */
+#define   BBA_NCRB_PB		(1<<3)	/* PB, Pass Bad Frame */
+#define   BBA_NCRB_AB		(1<<4)	/* AB, Accept Broadcast */
+#define   BBA_NCRB_HBD		(1<<5)	/* HBD, reserved */
+#define   BBA_NCRB_RXINTC0	(1<<6)	/* RXINTC, Receive Interrupt Counter */
+#define   BBA_NCRB_RXINTC1	(1<<7)	/*  " */
+#define     BBA_NCRB_1_PACKET_PER_INT	(0<<6)	/* 0 0 */
+#define     BBA_NCRB_2_PACKETS_PER_INT	(1<<6)	/* 0 1 */
+#define     BBA_NCRB_4_PACKETS_PER_INT	(2<<6)	/* 1 0 */
+#define     BBA_NCRB_8_PACKETS_PER_INT	(3<<6)	/* 1 1 */
+
+#define BBA_LTPS 0x04		/* Last Transmitted Packet Status, RO */
+#define BBA_LRPS 0x05		/* Last Received Packet Status, RO */
+
+#define BBA_IMR 0x08		/* Interrupt Mask Register, RW, 00h */
+#define   BBA_IMR_FRAGIM	(1<<0) /* FRAGIM, Fragment Counter Int Mask */
+#define   BBA_IMR_RIM		(1<<1) /* RIM, Receive Interrupt Mask */
+#define   BBA_IMR_TIM		(1<<2) /* TIM, Transmit Interrupt Mask */
+#define   BBA_IMR_REIM		(1<<3) /* REIM, Receive Error Interrupt Mask */
+#define   BBA_IMR_TEIM		(1<<4) /* TEIM, Transmit Error Interrupt Mask */
+#define   BBA_IMR_FIFOEIM	(1<<5) /* FIFOEIM, FIFO Error Interrupt Mask */
+#define   BBA_IMR_BUSEIM	(1<<6) /* BUSEIM, BUS Error Interrupt Mask */
+#define   BBA_IMR_RBFIM		(1<<7) /* RBFIM, RX Buf Full Interrupt Mask */
+
+#define BBA_IR 0x09		/* Interrupt Register, RW, 00h */
+#define   BBA_IR_FRAGI		(1<<0)	/* FRAGI, Fragment Counter Interrupt */
+#define   BBA_IR_RI		(1<<1)	/* RI, Receive Interrupt */
+#define   BBA_IR_TI		(1<<2)	/* TI, Transmit Interrupt */
+#define   BBA_IR_REI		(1<<3)	/* REI, Receive Error Interrupt */
+#define   BBA_IR_TEI		(1<<4)	/* TEI, Transmit Error Interrupt */
+#define   BBA_IR_FIFOEI		(1<<5)	/* FIFOEI, FIFO Error Interrupt */
+#define   BBA_IR_BUSEI		(1<<6)	/* BUSEI, BUS Error Interrupt */
+#define   BBA_IR_RBFI		(1<<7)	/* RBFI, RX Buffer Full Interrupt */
+
+#define BBA_BP   0x0a/*+0x0b*/	/* Boundary Page Pointer Register */
+#define BBA_TLBP 0x0c/*+0x0d*/	/* TX Low Boundary Page Pointer Register */
+#define BBA_TWP  0x0e/*+0x0f*/	/* Transmit Buf Write Page Pointer Register */
+#define BBA_TRP  0x12/*+0x13*/	/* Transmit Buf Read Page Pointer Register */
+#define BBA_RWP  0x16/*+0x17*/	/* Receive Buffer Write Page Pointer Register */
+#define BBA_RRP  0x18/*+0x19*/	/* Receive Buffer Read Page Pointer Register */
+#define BBA_RHBP 0x1a/*+0x1b*/	/* Receive High Boundary Page Ptr Register */
+
+#define BBA_RXINTT    0x14/*+0x15*/	/* Receive Interrupt Timer Register */
+
+#define BBA_NAFR_PAR0 0x20	/* Physical Address Register Byte 0 */
+#define BBA_NAFR_PAR1 0x21	/* Physical Address Register Byte 1 */
+#define BBA_NAFR_PAR2 0x22	/* Physical Address Register Byte 2 */
+#define BBA_NAFR_PAR3 0x23	/* Physical Address Register Byte 3 */
+#define BBA_NAFR_PAR4 0x24	/* Physical Address Register Byte 4 */
+#define BBA_NAFR_PAR5 0x25	/* Physical Address Register Byte 5 */
+
+#define BBA_NWAYC 0x30		/* NWAY Configuration Register, RW, 84h */
+#define   BBA_NWAYC_FD		(1<<0)	/* FD, Full Duplex Mode */
+#define   BBA_NWAYC_PS100	(1<<1)	/* PS100/10, Port Select 100/10 */
+#define   BBA_NWAYC_ANE		(1<<2)	/* ANE, Autonegotiation Enable */
+#define   BBA_NWAYC_ANS_RA	(0x01<<3) /* ANS, Restart Autonegotiation */
+#define   BBA_NWAYC_LTE		(1<<7)	/* LTE, Link Test Enable */
+
+#define BBA_GCA 0x32		/* GMAC Configuration A Register, RW, 00h */
+#define   BBA_GCA_ARXERRB	(1<<3)	/* ARXERRB, Accept RX pkt with error */
+
+#define BBA_MISC 0x3d		/* MISC Control Register 1, RW, 3ch */
+#define   BBA_MISC_BURSTDMA	(1<<0)
+#define   BBA_MISC_DISLDMA	(1<<1)
+
+#define BBA_TXFIFOCNT 0x3e/*0x3f*/	/* Transmit FIFO Counter Register */
+#define BBA_WRTXFIFOD 0x48/*-0x4b*/	/* Write TX FIFO Data Port Register */
+
+#define BBA_MISC2 0x50		/* MISC Control Register 2, RW, 00h */
+#define   BBA_MISC2_HBRLEN0	(1<<0)	/* HBRLEN, Host Burst Read Length */
+#define   BBA_MISC2_HBRLEN1	(1<<1)	/*  " */
+#define   BBA_MISC2_AUTORCVR	(1<<7)	/* Auto RX Full Recovery */
+
+#define BBA_RX_STATUS_BF	(1<<0)
+#define BBA_RX_STATUS_CRC	(1<<1)
+#define BBA_RX_STATUS_FAE	(1<<2)
+#define BBA_RX_STATUS_FO	(1<<3)
+#define BBA_RX_STATUS_RW	(1<<4)
+#define BBA_RX_STATUS_MF	(1<<5)
+#define BBA_RX_STATUS_RF	(1<<6)
+#define BBA_RX_STATUS_RERR	(1<<7)
+
+#define BBA_TX_STATUS_CC0	(1<<0)
+#define BBA_TX_STATUS_CC1	(1<<1)
+#define BBA_TX_STATUS_CC2	(1<<2)
+#define BBA_TX_STATUS_CC3	(1<<3)
+#define  BBA_TX_STATUS_CCMASK	(0x0f)
+#define BBA_TX_STATUS_CRSLOST	(1<<4)
+#define BBA_TX_STATUS_UF	(1<<5)
+#define BBA_TX_STATUS_OWC	(1<<6)
+#define BBA_TX_STATUS_OWN	(1<<7)
+#define BBA_TX_STATUS_TERR	(1<<7)
+
+#define BBA_TX_MAX_PACKET_SIZE	1518	/* 14+1500+4 */
+#define BBA_RX_MAX_PACKET_SIZE	1536	/* 6 pages * 256 bytes */
+
+
+/**
+ *
+ * DRIVER NOTES
+ *
+ * 1. Packet Memory organization
+ *
+ * rx: 15 pages of 256 bytes, 2 full sized packets only (6 pages each)
+ * tx: through FIFO, not using packet memory
+ *
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |1|2|3|4|5|6|7|8|9|A|B|C|D|E|F|
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * ^                           ^
+ * |                           |
+ * TLBP                        RHBP
+ * BP
+ *
+ */
+
+#define BBA_INIT_TLBP	0x00
+#define BBA_INIT_BP	0x01
+#define BBA_INIT_RHBP	0x0f
+#define BBA_INIT_RWP	BBA_INIT_BP
+#define BBA_INIT_RRP	BBA_INIT_BP
+
+enum {
+	__BBA_RBFIM_OFF = 0,
+};
+
+struct bba_descr {
+#if defined(__BIG_ENDIAN_BITFIELD)
+	__u32	status:8,
+		packet_len:12,
+		next_packet_ptr:12;
+#elif defined(__LITTLE_ENDIAN_BITFIELD)
+	__u32	next_packet_ptr:12,
+		packet_len:12,
+		status:8;
+#else
+#error "Unsupported byte order."
+#endif
+} __attribute((packed));
+
+
+struct bba_private {
+	spinlock_t		lock;
+	unsigned long		flags;
+#define BBA_RBFIM_OFF		(1<<__BBA_RBFIM_OFF)
+
+	u32			msg_enable;
+	u8			revid;
+	u8			__0x04_init[2];
+	u8			__0x05_init;
+
+	struct sk_buff		*tx_skb;
+	int			rx_work;
+
+	struct task_struct	*io_thread;
+	wait_queue_head_t	io_waitq;
+
+	struct net_device	*dev;
+	struct net_device_stats	stats;
+
+	struct exi_device	*exi_device;
+};
+
+static int bba_event_handler(struct exi_channel *exi_channel,
+			     unsigned int event, void *dev0);
+static int bba_setup_hardware(struct net_device *dev);
+
+/*
+ * Opens the network device.
+ */
+static int bba_open(struct net_device *dev)
+{
+	struct bba_private *priv = netdev_priv(dev);
+	int retval;
+
+	/* INTs are triggered on EXI channel 2 */
+	retval = exi_event_register(to_exi_channel(BBA_EXI_IRQ_CHANNEL),
+				    EXI_EVENT_IRQ,
+				    priv->exi_device,
+				    bba_event_handler, dev,
+				    (1 << BBA_EXI_CHANNEL));
+	if (retval < 0) {
+		bba_printk(KERN_ERR, "unable to register EXI event %d\n",
+			   EXI_EVENT_IRQ);
+		goto out;
+	}
+
+	/* reset the hardware to a known state */
+	exi_dev_take(priv->exi_device);
+	retval = bba_setup_hardware(dev);
+	exi_dev_give(priv->exi_device);
+
+	/* inform the network layer that we are ready */
+	netif_start_queue(dev);
+out:
+	return retval;
+}
+
+/*
+ * Closes the network device.
+ */
+static int bba_close(struct net_device *dev)
+{
+	struct bba_private *priv = netdev_priv(dev);
+
+	/* do not allow more packets to be queued */
+	netif_carrier_off(dev);
+	netif_stop_queue(dev);
+
+	exi_dev_take(priv->exi_device);
+
+	/* stop receiver */
+	bba_out8(BBA_NCRA, bba_in8(BBA_NCRA) & ~BBA_NCRA_SR);
+
+	/* mask all interrupts */
+	bba_out8(BBA_IMR, 0x00);
+
+	exi_dev_give(priv->exi_device);
+
+	/* unregister exi event */
+	exi_event_unregister(to_exi_channel(BBA_EXI_IRQ_CHANNEL),
+			     EXI_EVENT_IRQ);
+
+	return 0;
+}
+
+/*
+ * Returns the network device statistics.
+ */
+static struct net_device_stats *bba_get_stats(struct net_device *dev)
+{
+	struct bba_private *priv = netdev_priv(dev);
+
+	return &priv->stats;
+}
+
+/*
+ * Starts transmission for a packet.
+ * We can't do real hardware i/o here.
+ */
+static int bba_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct bba_private *priv = netdev_priv(dev);
+	unsigned long flags;
+	int retval = NETDEV_TX_OK;
+
+	/* we are not able to send packets greater than this */
+	if (skb->len > BBA_TX_MAX_PACKET_SIZE) {
+		dev_kfree_skb(skb);
+		priv->stats.tx_dropped++;
+		/* silently drop the package */
+		goto out;
+	}
+
+	spin_lock_irqsave(&priv->lock, flags);
+
+	/*
+	 * If there's no packet pending, store the packet for transmission
+	 * and wake up the io thread. Otherwise, we are busy.
+	 */
+	if (!priv->tx_skb) {
+		priv->tx_skb = skb;
+		dev->trans_start = jiffies;
+		wake_up(&priv->io_waitq);
+	} else {
+		retval = NETDEV_TX_BUSY;
+	}
+
+	/* we can only send one packet at a time through the FIFO */
+	netif_stop_queue(dev);
+
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+out:
+	return retval;
+}
+
+/*
+ * Updates transmission error statistics.
+ * Caller holds the device lock.
+ */
+static int bba_tx_err(u8 status, struct net_device *dev)
+{
+	struct bba_private *priv = netdev_priv(dev);
+	int last_tx_errors = priv->stats.tx_errors;
+
+	if (status & BBA_TX_STATUS_TERR) {
+		if (status & BBA_TX_STATUS_CCMASK) {
+			priv->stats.collisions +=
+			    (status & BBA_TX_STATUS_CCMASK);
+			priv->stats.tx_errors++;
+		}
+		if (status & BBA_TX_STATUS_CRSLOST) {
+			priv->stats.tx_carrier_errors++;
+			priv->stats.tx_errors++;
+		}
+		if (status & BBA_TX_STATUS_UF) {
+			priv->stats.tx_fifo_errors++;
+			priv->stats.tx_errors++;
+		}
+		if (status & BBA_TX_STATUS_OWC) {
+			priv->stats.tx_window_errors++;
+			priv->stats.tx_errors++;
+		}
+	}
+
+	if (last_tx_errors != priv->stats.tx_errors) {
+		if (netif_msg_tx_err(priv)) {
+			bba_printk(KERN_DEBUG, "tx errors, status %8.8x.\n",
+				   status);
+		}
+	}
+	return priv->stats.tx_errors;
+}
+
+/*
+ * Transmits a packet already stored in the driver's internal tx slot.
+ */
+static int bba_tx(struct net_device *dev)
+{
+	struct bba_private *priv = netdev_priv(dev);
+	struct sk_buff *skb;
+	unsigned long flags;
+	int retval = NETDEV_TX_OK;
+
+	static u8 pad[ETH_ZLEN] __attribute__ ((aligned(EXI_DMA_ALIGN+1)));
+	int pad_len;
+
+	exi_dev_take(priv->exi_device);
+
+	/* if the TXFIFO is in use, we'll try it later when free */
+	if (bba_in8(BBA_NCRA) & (BBA_NCRA_ST0 | BBA_NCRA_ST1)) {
+		retval = NETDEV_TX_BUSY;
+		goto out;
+	}
+
+	spin_lock_irqsave(&priv->lock, flags);
+	skb = priv->tx_skb;
+	priv->tx_skb = NULL;
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	/* tell the card about the length of this packet */
+	bba_out12(BBA_TXFIFOCNT, skb->len);
+
+	/*
+	 * Store the packet in the TXFIFO, including padding if needed.
+	 * Packet transmission tries to make use of DMA transfers.
+	 */
+
+	bba_select();
+	bba_outs_nosel(BBA_WRTXFIFOD, skb->data, skb->len);
+	if (skb->len < ETH_ZLEN) {
+		pad_len = ETH_ZLEN - skb->len;
+		memset(pad, 0, pad_len);
+		bba_outs_nosel_continued(pad, pad_len);
+	}
+	bba_deselect();
+
+	/* tell the card to send the packet right now */
+	bba_out8(BBA_NCRA, (bba_in8(BBA_NCRA) | BBA_NCRA_ST1) & ~BBA_NCRA_ST0);
+
+	/* update statistics */
+	priv->stats.tx_bytes += skb->len;
+	priv->stats.tx_packets++;
+
+	/* free this packet and remove it from our transmission "queue" */
+	dev_kfree_skb(skb);
+
+out:
+	exi_dev_give(priv->exi_device);
+
+	return retval;
+}
+
+/*
+ * Updates reception error statistics.
+ * Caller has already taken the exi channel.
+ */
+static int bba_rx_err(u8 status, struct net_device *dev)
+{
+	struct bba_private *priv = netdev_priv(dev);
+	int last_rx_errors = priv->stats.rx_errors;
+
+	if (status == 0xff) {
+		priv->stats.rx_over_errors++;
+		priv->stats.rx_errors++;
+	} else {
+		if (status & BBA_RX_STATUS_RERR) {
+			if (status & BBA_RX_STATUS_CRC) {
+				priv->stats.rx_crc_errors++;
+				priv->stats.rx_errors++;
+			}
+			if (status & BBA_RX_STATUS_FO) {
+				priv->stats.rx_fifo_errors++;
+				priv->stats.rx_errors++;
+			}
+			if (status & BBA_RX_STATUS_RW) {
+				priv->stats.rx_length_errors++;
+				priv->stats.rx_errors++;
+			}
+			if (status & BBA_RX_STATUS_BF) {
+				priv->stats.rx_over_errors++;
+				priv->stats.rx_errors++;
+			}
+			if (status & BBA_RX_STATUS_RF) {
+				priv->stats.rx_length_errors++;
+				priv->stats.rx_errors++;
+			}
+		}
+		if (status & BBA_RX_STATUS_FAE) {
+			priv->stats.rx_frame_errors++;
+			priv->stats.rx_errors++;
+		}
+	}
+
+	if (last_rx_errors != priv->stats.rx_errors) {
+		if (netif_msg_rx_err(priv)) {
+			bba_printk(KERN_DEBUG, "rx errors, status %8.8x.\n",
+				   status);
+		}
+	}
+	return priv->stats.rx_errors;
+}
+
+/*
+ * Reception function. Receives up to @budget packets.
+ */
+static int bba_rx(struct net_device *dev, int budget)
+{
+	struct bba_private *priv = netdev_priv(dev);
+	struct sk_buff *skb;
+	struct bba_descr descr;
+	int lrps, size;
+	unsigned long pos, top;
+	unsigned short rrp, rwp;
+	int received = 0;
+
+	exi_dev_take(priv->exi_device);
+
+	/* get current receiver pointers */
+	rwp = bba_in12(BBA_RWP);
+	rrp = bba_in12(BBA_RRP);
+
+	while (netif_running(dev) && received < budget && rrp != rwp) {
+		bba_ins(rrp << 8, &descr, sizeof(descr));
+		le32_to_cpus((u32 *) &descr);
+
+		size = descr.packet_len - 4;	/* ignore CRC */
+		lrps = descr.status;
+
+		/* abort processing in case of errors */
+		if (size > BBA_RX_MAX_PACKET_SIZE + 4) {
+			DBG("packet too big %d", size);
+			continue;
+		}
+
+		if ((lrps & (BBA_RX_STATUS_RERR | BBA_RX_STATUS_FAE))) {
+			DBG("error %x on received packet\n", lrps);
+			bba_rx_err(lrps, dev);
+			rwp = bba_in12(BBA_RWP);
+			rrp = bba_in12(BBA_RRP);
+			continue;
+		}
+
+		/* allocate a buffer, omitting the CRC (4 bytes) */
+		skb = dev_alloc_skb(size + NET_IP_ALIGN);
+		if (!skb) {
+			priv->stats.rx_dropped++;
+			continue;
+		}
+		skb->dev = dev;
+		skb_reserve(skb, NET_IP_ALIGN);	/* align */
+		skb_put(skb, size);
+
+		pos = (rrp << 8) + 4;	/* skip descriptor */
+		top = (BBA_INIT_RHBP + 1) << 8;
+
+		if ((pos + size) < top) {
+			/* full packet in one chunk */
+			bba_ins(pos, skb->data, size);
+		} else {
+			/* packet wrapped */
+			int chunk_size = top - pos;
+
+			bba_ins(pos, skb->data, chunk_size);
+			rrp = BBA_INIT_RRP;
+			bba_ins(rrp << 8, skb->data + chunk_size,
+				size - chunk_size);
+		}
+
+		skb->protocol = eth_type_trans(skb, dev);
+
+		dev->last_rx = jiffies;
+		priv->stats.rx_bytes += size;
+		priv->stats.rx_packets++;
+
+		netif_rx(skb);
+		received++;
+
+		/* move read pointer to next packet */
+		rrp = descr.next_packet_ptr;
+		bba_out12(BBA_RRP, rrp);
+
+		/* get write pointer and continue */
+		rwp = bba_in12(BBA_RWP);
+	}
+
+	/* there are no more packets pending if we didn't exhaust our budget */
+	if (received < budget)
+		priv->rx_work = 0;
+
+	/* re-enable RBFI if it was disabled before */
+	if (test_and_clear_bit(__BBA_RBFIM_OFF, &priv->flags))
+		bba_out8(BBA_IMR, bba_in8(BBA_IMR) | BBA_IMR_RBFIM);
+
+	exi_dev_give(priv->exi_device);
+
+	return received;
+}
+
+/*
+ * Input/Output thread. Sends and receives packets.
+ */
+static int bba_io_thread(void *bba_priv)
+{
+	struct bba_private *priv = bba_priv;
+/*	struct task_struct *me = current; */
+/*	struct sched_param param = { .sched_priority = MAX_RT_PRIO-1 }; */
+
+/*	sched_setscheduler(me, SCHED_FIFO, &param); */
+
+	set_user_nice(current, -20);
+	current->flags |= PF_NOFREEZE;
+	set_current_state(TASK_RUNNING);
+
+	/*
+	 * XXX We currently do not freeze this thread.
+	 * The bba is often used to access the root filesystem.
+	 */
+
+	while (!kthread_should_stop()) {
+		/*
+		 * We want to get scheduled at least once every 2 minutes
+		 * to avoid a softlockup spurious message...
+		 * "INFO: task kbbaiod blocked for more than 120 seconds."
+		 */
+		wait_event_timeout(priv->io_waitq,
+				   priv->rx_work || priv->tx_skb, 90*HZ);
+		while (priv->rx_work || priv->tx_skb) {
+			if (priv->rx_work)
+				bba_rx(priv->dev, 0x0f);
+			if (priv->tx_skb)
+				bba_tx(priv->dev);
+		}
+	}
+	return 0;
+}
+
+/*
+ * Handles interrupt work from the network device.
+ * Caller has already taken the exi channel.
+ */
+static void bba_interrupt(struct net_device *dev)
+{
+	struct bba_private *priv = netdev_priv(dev);
+	u8 ir, imr, status, lrps, ltps;
+	int loops = 0;
+
+	ir = bba_in8(BBA_IR);
+	imr = bba_in8(BBA_IMR);
+	status = ir & imr;
+
+	/* close possible races with dev_close */
+	if (unlikely(!netif_running(dev))) {
+		bba_out8(BBA_IR, status);
+		bba_out8(BBA_IMR, 0x00);
+		goto out;
+	}
+
+	while (status) {
+		bba_out8(BBA_IR, status);
+
+		/* avoid multiple receive buffer full interrupts */
+		if (status & BBA_IR_RBFI) {
+			bba_out8(BBA_IMR, bba_in8(BBA_IMR) & ~BBA_IMR_RBFIM);
+			set_bit(__BBA_RBFIM_OFF, &priv->flags);
+		}
+
+		if ((status & (BBA_IR_RI | BBA_IR_RBFI))) {
+			priv->rx_work = 1;
+			wake_up(&priv->io_waitq);
+		}
+		if ((status & (BBA_IR_TI|BBA_IR_FIFOEI))) {
+			/* allow more packets to be sent */
+			netif_wake_queue(dev);
+		}
+
+		if ((status & (BBA_IR_RBFI|BBA_IR_REI))) {
+			lrps = bba_in8(BBA_LRPS);
+			bba_rx_err(lrps, dev);
+		}
+		if (status & BBA_IR_TEI) {
+			ltps = bba_in8(BBA_LTPS);
+			bba_tx_err(ltps, dev);
+		}
+
+		if (status & BBA_IR_FIFOEI)
+			DBG("FIFOEI\n");
+		if (status & BBA_IR_BUSEI)
+			DBG("BUSEI\n");
+		if (status & BBA_IR_FRAGI)
+			DBG("FRAGI\n");
+
+		ir = bba_in8(BBA_IR);
+		imr = bba_in8(BBA_IMR);
+		status = ir & imr;
+
+		loops++;
+	}
+
+	if (loops > 3)
+		DBG("a lot of interrupt work (%d loops)\n", loops);
+
+	/* wake up xmit queue in case transmitter is idle */
+	if ((bba_in8(BBA_NCRA) & (BBA_NCRA_ST0 | BBA_NCRA_ST1)) == 0)
+		netif_wake_queue(dev);
+
+out:
+	return;
+}
+
+/*
+ * Retrieves the MAC address of the adapter.
+ * Caller has already taken the exi channel.
+ */
+static void bba_retrieve_ether_addr(struct net_device *dev)
+{
+	bba_ins(BBA_NAFR_PAR0, dev->dev_addr, ETH_ALEN);
+	if (!is_valid_ether_addr(dev->dev_addr))
+		random_ether_addr(dev->dev_addr);
+}
+
+/*
+ * Resets the hardware to a known state.
+ * Caller has already taken the exi channel.
+ */
+static void bba_reset_hardware(struct net_device *dev)
+{
+	struct bba_private *priv = netdev_priv(dev);
+
+	/* unknown, mx register 0x60 */
+	bba_out8(0x60, 0);
+	udelay(1000);
+
+	/* unknown, command register 0x0f */
+	bba_cmd_in8_slow(0x0f);
+	udelay(1000);
+
+	/* software reset (write 1 then write 0) */
+	bba_out8(BBA_NCRA, BBA_NCRA_RESET);
+	udelay(100);
+	bba_out8(BBA_NCRA, 0);
+
+	/* unknown, command register 0x01 */
+	/* XXX obtain bits needed for challenge/response calculation later */
+	priv->revid = bba_cmd_in8(0x01);
+
+	/* unknown, command registers 0x04, 0x05 */
+	bba_cmd_outs(0x04, priv->__0x04_init, 2);
+	bba_cmd_out8(0x05, priv->__0x05_init);
+
+	/*
+	 * These initializations seem to limit the final port speed to 10Mbps
+	 * half duplex. Bypassing them, allows one to set other port speeds.
+	 * But, remember that the bba spi-like bus clock operates at 32MHz.
+	 * ---Albert Herranz
+	 */
+
+	/* unknown, mx registers 0x5b, 0x5c, 0x5e */
+	bba_out8(0x5b, bba_in8(0x5b) & ~(1 << 7));
+	bba_out8(0x5e, 1); /* without this the BBA goes at half the speed */
+	bba_out8(0x5c, bba_in8(0x5c) | 4);
+	udelay(1000);
+
+	/* accept broadcast, assert int for every packet received */
+	bba_out8(BBA_NCRB, BBA_NCRB_AB | BBA_NCRB_1_PACKET_PER_INT);
+
+	/* setup receive interrupt time out, in 40ns units */
+	bba_out8(BBA_RXINTT, 0x00);
+	bba_out8(BBA_RXINTT+1, 0x06); /* 0x0600 = 61us */
+
+	/* auto RX full recovery */
+	bba_out8(BBA_MISC2, BBA_MISC2_AUTORCVR);
+
+	/* initialize packet memory layout */
+	bba_out12(BBA_TLBP, BBA_INIT_TLBP);
+	bba_out12(BBA_BP, BBA_INIT_BP);
+	bba_out12(BBA_RHBP, BBA_INIT_RHBP);
+
+	/* set receive page pointers */
+	bba_out12(BBA_RWP, BBA_INIT_RWP);
+	bba_out12(BBA_RRP, BBA_INIT_RRP);
+
+	/* packet memory won't contain packets with RW, FO, CRC errors */
+	bba_out8(BBA_GCA, BBA_GCA_ARXERRB);
+}
+
+/*
+ * Prepares the hardware for operation.
+ * Caller has already taken the exi channel.
+ */
+static int bba_setup_hardware(struct net_device *dev)
+{
+	/* reset hardware to a sane state */
+	bba_reset_hardware(dev);
+
+	/* start receiver */
+	bba_out8(BBA_NCRA, BBA_NCRA_SR);
+
+	/* clear all interrupts */
+	bba_out8(BBA_IR, 0xFF);
+
+	/* enable all interrupts */
+	bba_out8(BBA_IMR, 0xFF & ~(BBA_IMR_FIFOEIM /*| BBA_IMR_REIM*/));
+
+	/* unknown, short command registers 0x02 */
+	/* XXX enable interrupts on the EXI glue logic */
+	bba_cmd_out8(0x02, BBA_CMD_IR_MASKNONE);
+
+	/* DO NOT clear interrupts on the EXI glue logic !!! */
+	/* we need that initial interrupts for the challenge/response */
+
+	return 0;		/* OK */
+}
+
+/*
+ * Calculates a response for a given challenge.
+ */
+static unsigned long bba_calc_response(unsigned long val,
+				       struct bba_private *priv)
+{
+	u8 revid_0, revid_eth_0, revid_eth_1;
+	u8 i0, i1, i2, i3;
+	u8 c0, c1, c2, c3;
+
+	revid_0 = priv->revid;
+	revid_eth_0 = priv->__0x04_init[0];
+	revid_eth_1 = priv->__0x04_init[1];
+
+	i0 = val >> 24;
+	i1 = val >> 16;
+	i2 = val >> 8;
+	i3 = val;
+
+	c0 = (i0 + i1 * 0xc1 + 0x18 + revid_0) ^ (i3 * i2 + 0x90);
+	c1 = (i1 + i2 + 0x90) ^ (c0 + i0 - 0xc1);
+	c2 = (i2 + 0xc8) ^ (c0 + ((revid_eth_0 + revid_0 * 0x23) ^ 0x19));
+	c3 = (i0 + 0xc1) ^ (i3 + ((revid_eth_1 + 0xc8) ^ 0x90));
+
+	return (c0 << 24) | (c1 << 16) | (c2 << 8) | c3;
+}
+
+/*
+ * Handles IRQ events from the exi layer.
+ *
+ * We are called from softirq context, and with the exi channel kindly taken
+ * for us. We can also safely do exi transfers of less than 32 bytes, which
+ * are guaranteed to not sleep by the exi layer.
+ */
+static int bba_event_handler(struct exi_channel *exi_channel,
+			     unsigned int event, void *dev0)
+{
+	struct net_device *dev = (struct net_device *)dev0;
+	struct bba_private *priv = netdev_priv(dev);
+	register u8 status, mask;
+
+	/* XXX mask all EXI glue interrupts */
+	bba_cmd_out8(0x02, BBA_CMD_IR_MASKALL);
+
+	/* get interrupt status from EXI glue */
+	status = bba_cmd_in8(0x03);
+
+	/* start with the usual case */
+	mask = (1<<7);
+
+	/* normal interrupt from the macronix chip */
+	if (status & mask) {
+		/* call our interrupt handler */
+		bba_interrupt(dev);
+		goto out;
+	}
+
+	/* "killing" interrupt, try to not get one of these! */
+	mask >>= 1;
+	if (status & mask) {
+		DBG("bba: killing interrupt!\n");
+		/* reset the adapter so that we can continue working */
+		bba_setup_hardware(dev);
+		goto out;
+	}
+
+	/* command error interrupt, haven't seen one yet */
+	mask >>= 1;
+	if (status & mask)
+		goto out;
+
+	/* challenge/response interrupt */
+	mask >>= 1;
+	if (status & mask) {
+		unsigned long response;
+		unsigned long challenge;
+
+		/* kids, don't do it without an adult present */
+		bba_cmd_out8(0x05, priv->__0x05_init);
+		bba_cmd_ins(0x08, &challenge, sizeof(challenge));
+		response = bba_calc_response(challenge, priv);
+		bba_cmd_outs(0x09, &response, sizeof(response));
+
+		goto out;
+	}
+
+	/* challenge/response status interrupt */
+	mask >>= 1;
+	if (status & mask) {
+		/* better get a "1" here ... */
+		u8 result = bba_cmd_in8(0x0b);
+		if (result != 1) {
+			bba_printk(KERN_DEBUG,
+				   "challenge failed! (result=%d)\n", result);
+		}
+		goto out;
+	}
+
+	/* should not happen, treat as normal interrupt in any case */
+	DBG("bba: unknown interrupt type = %d\n", status);
+
+out:
+	/* assert interrupt */
+	bba_cmd_out8(0x03, mask);
+
+	/* enable interrupts again */
+	bba_cmd_out8(0x02, BBA_CMD_IR_MASKNONE);
+
+	return 1;
+}
+
+static struct net_device *bba_dev;
+
+static inline void bba_select(void)
+{
+	struct bba_private *priv = netdev_priv(bba_dev);
+	exi_dev_select(priv->exi_device);
+
+}
+
+static inline void bba_deselect(void)
+{
+	struct bba_private *priv = netdev_priv(bba_dev);
+	exi_dev_deselect(priv->exi_device);
+}
+
+static inline void bba_read(void *data, size_t len)
+{
+	struct bba_private *priv = netdev_priv(bba_dev);
+	return exi_dev_read(priv->exi_device, data, len);
+}
+
+static inline void bba_write(void *data, size_t len)
+{
+	struct bba_private *priv = netdev_priv(bba_dev);
+	return exi_dev_write(priv->exi_device, data, len);
+}
+
+static const struct net_device_ops bba_netdev_ops = {
+	.ndo_open		= bba_open,
+	.ndo_stop		= bba_close,
+	.ndo_start_xmit		= bba_start_xmit,
+	.ndo_get_stats		= bba_get_stats,
+	.ndo_change_mtu		= eth_change_mtu,
+	.ndo_validate_addr	= eth_validate_addr,
+	.ndo_set_mac_address	= eth_mac_addr,
+};
+
+/*
+ * Initializes a BroadBand Adapter device.
+ */
+static int __devinit bba_init_device(struct exi_device *exi_device)
+{
+	struct net_device *dev;
+	struct bba_private *priv;
+	int err;
+
+	/* allocate a network device */
+	dev = alloc_etherdev(sizeof(*priv));
+	if (!dev) {
+		bba_printk(KERN_ERR, "unable to allocate net device\n");
+		err = -ENOMEM;
+		goto err_out;
+	}
+	SET_NETDEV_DEV(dev, &exi_device->dev);
+
+	/* we use the event system from the EXI driver, so no irq here */
+	dev->irq = 0;
+
+	/* network device hooks */
+	dev->netdev_ops = &bba_netdev_ops;
+
+	priv = netdev_priv(dev);
+	priv->dev = dev;
+	priv->exi_device = exi_device;
+
+	spin_lock_init(&priv->lock);
+
+	/* initialization values */
+	priv->revid = 0xf0;
+	priv->__0x04_init[0] = 0xd1;
+	priv->__0x04_init[1] = 0x07;
+	priv->__0x05_init = 0x4e;
+
+	/* i/o artifacts */
+	priv->tx_skb = NULL;
+	priv->rx_work = 0;
+	init_waitqueue_head(&priv->io_waitq);
+	priv->io_thread = kthread_run(bba_io_thread, priv, "kbbaiod");
+
+	/* the hardware can't do multicast */
+	dev->flags &= ~IFF_MULTICAST;
+
+	exi_set_drvdata(exi_device, dev);
+	if (bba_dev)
+		free_netdev(bba_dev);
+	bba_dev = dev;
+
+	/* we need to retrieve the MAC address before registration */
+	exi_dev_take(priv->exi_device);
+	bba_reset_hardware(dev);
+	bba_retrieve_ether_addr(dev);
+	exi_dev_give(priv->exi_device);
+
+	/* this makes our device available to the kernel */
+	err = register_netdev(dev);
+	if (err) {
+		bba_printk(KERN_ERR, "cannot register net device, aborting.\n");
+		goto err_out_free_dev;
+	}
+
+	return 0;
+
+err_out_free_dev:
+	exi_set_drvdata(exi_device, NULL);
+	free_netdev(dev);
+	bba_dev = NULL;
+
+err_out:
+	return err;
+}
+
+/*
+ * Removes a BroadBand Adapter device from the system.
+ */
+static void __devexit bba_remove(struct exi_device *exi_device)
+{
+	struct net_device *dev = (struct net_device *)
+				 exi_get_drvdata(exi_device);
+	struct bba_private *priv;
+
+	if (dev) {
+		priv = netdev_priv(dev);
+
+		kthread_stop(priv->io_thread);
+
+		unregister_netdev(dev);
+		free_netdev(dev);
+		exi_set_drvdata(exi_device, NULL);
+		bba_dev = NULL;
+	}
+	exi_device_put(exi_device);
+}
+
+/*
+ * Probes for a BroadBand Adapter device.
+ * Actually, the exi layer has already probed for us.
+ */
+static int __devinit bba_probe(struct exi_device *exi_device)
+{
+	int ret = -ENODEV;
+
+	if (exi_device_get(exi_device))
+		ret = bba_init_device(exi_device);
+
+	return ret;
+}
+
+
+static struct exi_device_id bba_eid_table[] = {
+	[0] = {
+		.channel = BBA_EXI_CHANNEL,
+		.device  = BBA_EXI_DEVICE,
+		.id      = BBA_EXI_ID
+	},
+	{ .id = 0 }
+};
+
+static struct exi_driver bba_driver = {
+	.name = "bba",
+	.eid_table = bba_eid_table,
+	.frequency = BBA_EXI_FREQ,
+	.probe = bba_probe,
+	.remove = bba_remove,
+};
+
+/**
+ *	bba_init_module -  driver initialization routine
+ *
+ *	Initializes the BroadBand Adapter driver module.
+ *
+ */
+static int __init bba_init_module(void)
+{
+	bba_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION,
+		   bba_driver_version);
+
+	return exi_driver_register(&bba_driver);
+}
+
+/**
+ *	bba_exit_module -  driver exit routine
+ *
+ *	Removes the BroadBand Adapter driver module.
+ *
+ */
+static void __exit bba_exit_module(void)
+{
+	exi_driver_unregister(&bba_driver);
+}
+
+module_init(bba_init_module);
+module_exit(bba_exit_module);
+
+MODULE_AUTHOR(DRV_AUTHOR);
+MODULE_DESCRIPTION(DRV_DESCRIPTION);
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c
index ca5ca5a..6a11527 100644
--- a/drivers/net/usb/usbnet.c
+++ b/drivers/net/usb/usbnet.c
@@ -586,7 +586,9 @@ static int unlink_urbs (struct usbnet *dev, struct sk_buff_head *q)
 
 		// during some PM-driven resume scenarios,
 		// these (async) unlinks complete immediately
+		spin_unlock(&q->lock);
 		retval = usb_unlink_urb (urb);
+		spin_lock(&q->lock);
 		if (retval != -EINPROGRESS && retval != 0)
 			devdbg (dev, "unlink urb err, %d", retval);
 		else
diff --git a/drivers/net/wireless/b43/Kconfig b/drivers/net/wireless/b43/Kconfig
index 9da5373..d5439f5 100644
--- a/drivers/net/wireless/b43/Kconfig
+++ b/drivers/net/wireless/b43/Kconfig
@@ -146,3 +146,14 @@ config B43_FORCE_PIO
 	  Say N!
 	  This is only for debugging the PIO engine code. You do
 	  _NOT_ want to enable this.
+
+config B43_DEFAULT_QOS_OFF
+	bool "Start with QoS disabled"
+	depends on B43
+	---help---
+	  This will start the driver with Quality Of Service disabled.
+
+	  Say Y if you are using an OpenFWWF firmware without QoS support
+	  and building the b43 driver in-kernel.
+	  Otherwise, say N.
+
diff --git a/drivers/net/wireless/b43/main.c b/drivers/net/wireless/b43/main.c
index d605634..fb25139 100644
--- a/drivers/net/wireless/b43/main.c
+++ b/drivers/net/wireless/b43/main.c
@@ -68,6 +68,11 @@ MODULE_LICENSE("GPL");
 
 MODULE_FIRMWARE(B43_SUPPORTED_FIRMWARE_ID);
 
+#ifdef CONFIG_B43_DEFAULT_QOS_OFF
+#define B43_QOS_DEFAULT 0
+#else
+#define B43_QOS_DEFAULT 1
+#endif
 
 static int modparam_bad_frames_preempt;
 module_param_named(bad_frames_preempt, modparam_bad_frames_preempt, int, 0444);
@@ -90,7 +95,7 @@ static int modparam_hwtkip;
 module_param_named(hwtkip, modparam_hwtkip, int, 0444);
 MODULE_PARM_DESC(hwtkip, "Enable hardware tkip.");
 
-static int modparam_qos = 1;
+static int modparam_qos = B43_QOS_DEFAULT;
 module_param_named(qos, modparam_qos, int, 0444);
 MODULE_PARM_DESC(qos, "Enable QOS support (default on)");
 
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 3c20dae..c8cb835 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -573,6 +573,15 @@ config RTC_DRV_AB3100
 	  Select this to enable the ST-Ericsson AB3100 Mixed Signal IC RTC
 	  support. This chip contains a battery- and capacitor-backed RTC.
 
+config RTC_DRV_GCN
+	bool "Nintendo GameCube/Wii Real Time Clock and SRAM"
+	depends on GAMECUBE_EXI
+	default y
+	help
+	  If you say yes to this option, support will be included for the
+	  Real Time Clock and SRAM of the Nintendo GameCube/Wii.
+
+	  If in doubt, say Y here.
 
 comment "on-CPU RTC drivers"
 
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index aa3fbd5..742ea6d 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -43,6 +43,7 @@ obj-$(CONFIG_RTC_DRV_EFI)	+= rtc-efi.o
 obj-$(CONFIG_RTC_DRV_EP93XX)	+= rtc-ep93xx.o
 obj-$(CONFIG_RTC_DRV_FM3130)	+= rtc-fm3130.o
 obj-$(CONFIG_RTC_DRV_GENERIC)	+= rtc-generic.o
+obj-$(CONFIG_RTC_DRV_GCN)	+= rtc-gcn.o
 obj-$(CONFIG_RTC_DRV_ISL1208)	+= rtc-isl1208.o
 obj-$(CONFIG_RTC_DRV_M41T80)	+= rtc-m41t80.o
 obj-$(CONFIG_RTC_DRV_M41T94)	+= rtc-m41t94.o
diff --git a/drivers/rtc/rtc-gcn.c b/drivers/rtc/rtc-gcn.c
new file mode 100644
index 0000000..d5bae5c
--- /dev/null
+++ b/drivers/rtc/rtc-gcn.c
@@ -0,0 +1,339 @@
+/*
+ * drivers/rtc/rtc-gcn.c
+ *
+ * Nintendo GameCube/Wii RTC/SRAM driver
+ * Copyright (C) 2004-2009 The GameCube Linux Team
+ * Copyright (C) 2005,2008,2009 Albert Herranz
+ *
+ * Based on gamecube_time.c from Torben Nielsen.
+ *
+ * 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/init.h>
+#include <linux/time.h>
+#include <linux/rtc.h>
+#include <linux/exi.h>
+#include <asm/machdep.h>
+
+#define DRV_MODULE_NAME   "rtc-gcn"
+#define DRV_DESCRIPTION   "Nintendo GameCube/Wii RTC/SRAM driver"
+#define DRV_AUTHOR        "Torben Nielsen, " \
+			  "Albert Herranz"
+
+static char gcnrtc_driver_version[] = "1.0i";
+
+#define drv_printk(level, format, arg...) \
+	printk(level DRV_MODULE_NAME ": " format , ## arg)
+
+
+#define RTC_EXI_GCN_ID	0xffff1698
+#define RTC_EXI_RVL_ID	0xfffff308
+
+#define RTC_EXI_CHANNEL	0
+#define RTC_EXI_DEVICE	1
+#define RTC_EXI_FREQ	3 /* 8MHz */
+
+#define RTC_OFFSET	946684800L
+
+
+struct gcn_sram {
+	u16     csum1;
+	u16     csum2;
+	u32     ead0;
+	u32     ead1;
+	int     bias;
+	s8      horz_display_offset;
+	u8      ntd;
+	u8      language;
+	u8      flags;
+	u8      reserved[44];
+};
+
+struct gcnrtc_drvdata {
+	spinlock_t		lock;
+	struct exi_device	*dev;
+
+	struct rtc_device	*rtc_dev;
+
+	struct gcn_sram         sram;
+};
+
+static struct gcnrtc_drvdata gcnrtc_drvdata;
+
+/*
+ * Hardware interfaces.
+ *
+ */
+
+/*
+ * Loads the SRAM contents.
+ * Context: user.
+ */
+static void sram_load(struct exi_device *dev)
+{
+	struct gcnrtc_drvdata *drvdata = exi_get_drvdata(dev);
+	struct gcn_sram *sram = &drvdata->sram;
+	u32 req;
+
+	exi_dev_take(dev);
+
+	/* select the SRAM device */
+	exi_dev_select(dev);
+
+	/* send the appropriate command */
+	req = 0x20000100;
+	exi_dev_write(dev, &req, sizeof(req));
+
+	/* read the SRAM data */
+	exi_dev_read(dev, sram, sizeof(*sram));
+
+	/* deselect the SRAM device */
+	exi_dev_deselect(dev);
+
+	exi_dev_give(dev);
+
+	return;
+}
+
+/*
+ * Gets the hardware clock date and time.
+ * Context: user.
+ */
+static unsigned long gcnrtc_read_time(struct exi_device *dev)
+{
+	unsigned long a = 0;
+
+	exi_dev_take(dev);
+
+	/* select the SRAM device */
+	exi_dev_select(dev);
+
+	/* send the appropriate command */
+	a = 0x20000000;
+	exi_dev_write(dev, &a, sizeof(a));
+
+	/* read the time and date value */
+	exi_dev_read(dev, &a, sizeof(a));
+
+	/* deselect the RTC device */
+	exi_dev_deselect(dev);
+
+	exi_dev_give(dev);
+
+	return a;
+}
+
+/*
+ * Sets the hardware clock date and time to @aval.
+ * Context: user, interrupt (adjtimex).
+ */
+static int gcnrtc_write_time(struct exi_device *dev, unsigned long aval)
+{
+	u32 req;
+	int retval;
+
+	/*
+	 * We may get called from the timer interrupt. In that case,
+	 * we could fail if the exi channel used to access the RTC
+	 * is busy. If this happens, we just return an error. The timer
+	 * interrupt code is prepared to deal with such case.
+	 */
+
+	retval = exi_dev_try_take(dev);
+	if (!retval) {
+		/* select the RTC device */
+		exi_dev_select(dev);
+
+		/* send the appropriate command */
+		req = 0xa0000000;
+		exi_dev_write(dev, &req, sizeof(req));
+
+		/* set the new time and date value */
+		exi_dev_write(dev, &aval, sizeof(aval));
+
+		/* deselect the RTC device */
+		exi_dev_deselect(dev);
+
+		exi_dev_give(dev);
+	}
+	return retval;
+}
+
+/*
+ * Platform time functions.
+ *
+ */
+
+/*
+ * Platform specific function to return the current date and time.
+ */
+static void gcnrtc_plat_rtc_get_time(struct rtc_time *t)
+{
+	struct gcnrtc_drvdata *drvdata = &gcnrtc_drvdata;
+	unsigned long nowtime;
+
+	if (!drvdata->dev)
+		return;
+
+	nowtime = gcnrtc_read_time(drvdata->dev) +
+		  drvdata->sram.bias + RTC_OFFSET;
+	rtc_time_to_tm(nowtime, t);
+}
+
+/*
+ * Platform specific function to set the current date and time.
+ *
+ */
+static int gcnrtc_plat_rtc_set_time(struct rtc_time *t)
+{
+	struct gcnrtc_drvdata *drvdata = &gcnrtc_drvdata;
+	unsigned long nowtime;
+
+	if (!drvdata->dev)
+		return -ENODEV;
+
+	rtc_tm_to_time(t, &nowtime);
+	return gcnrtc_write_time(drvdata->dev,
+				 nowtime - RTC_OFFSET - drvdata->sram.bias);
+}
+
+/*
+ * RTC class driver.
+ *
+ */
+
+/*
+ *
+ */
+static int gcnrtc_rtc_read_time(struct device *dev, struct rtc_time *t)
+{
+	gcnrtc_plat_rtc_get_time(t);
+	return 0;
+}
+
+/*
+ *
+ */
+static int gcnrtc_rtc_set_time(struct device *dev, struct rtc_time *t)
+{
+	return gcnrtc_plat_rtc_set_time(t);
+}
+
+static const struct rtc_class_ops gcnrtc_ops = {
+	.read_time      = gcnrtc_rtc_read_time,
+	.set_time       = gcnrtc_rtc_set_time,
+};
+
+
+/*
+ * EXI driver.
+ *
+ */
+
+/*
+ *
+ */
+static int gcnrtc_probe(struct exi_device *dev)
+{
+	struct gcnrtc_drvdata *drvdata = &gcnrtc_drvdata;
+	unsigned long flags;
+	int retval = -ENODEV;
+
+	if (exi_device_get(dev)) {
+		spin_lock_init(&drvdata->lock);
+
+		exi_set_drvdata(dev, drvdata);
+		drvdata->dev = dev;
+
+		memset(&drvdata->sram, 0, sizeof(struct gcn_sram));
+		sram_load(dev);
+
+		spin_lock_irqsave(&drvdata->lock, flags);
+		ppc_md.set_rtc_time = gcnrtc_plat_rtc_set_time;
+		ppc_md.get_rtc_time = gcnrtc_plat_rtc_get_time;
+		spin_unlock_irqrestore(&drvdata->lock, flags);
+
+		drvdata->rtc_dev = rtc_device_register(DRV_MODULE_NAME,
+						       &dev->dev,
+						       &gcnrtc_ops,
+						       THIS_MODULE);
+		retval = 0;
+	}
+
+	return retval;
+}
+
+/*
+ *
+ */
+static void gcnrtc_remove(struct exi_device *dev)
+{
+	struct gcnrtc_drvdata *drvdata = exi_get_drvdata(dev);
+	unsigned long flags;
+
+	if (drvdata) {
+		spin_lock_irqsave(&drvdata->lock, flags);
+		ppc_md.set_rtc_time = NULL;
+		ppc_md.get_rtc_time = NULL;
+		spin_unlock_irqrestore(&drvdata->lock, flags);
+
+		if (!IS_ERR(drvdata->rtc_dev))
+			rtc_device_unregister(drvdata->rtc_dev);
+	}
+	exi_device_put(dev);
+}
+
+
+static struct exi_device_id gcnrtc_eid_table[] = {
+	{
+		.channel = RTC_EXI_CHANNEL,
+		.device  = RTC_EXI_DEVICE,
+		.id      = RTC_EXI_GCN_ID
+	},
+	{
+		.channel = RTC_EXI_CHANNEL,
+		.device  = RTC_EXI_DEVICE,
+		.id      = RTC_EXI_RVL_ID
+	},
+	{ },
+};
+
+static struct exi_driver gcnrtc_driver = {
+	.name = DRV_MODULE_NAME,
+	.eid_table = gcnrtc_eid_table,
+	.frequency = RTC_EXI_FREQ,
+	.probe = gcnrtc_probe,
+	.remove = gcnrtc_remove,
+};
+
+
+/*
+ *
+ */
+static int __init gcnrtc_init_module(void)
+{
+	drv_printk(KERN_INFO, "%s - version %s\n",
+		   DRV_DESCRIPTION, gcnrtc_driver_version);
+
+	return exi_driver_register(&gcnrtc_driver);
+}
+
+/*
+ *
+ */
+static void __exit gcnrtc_exit_module(void)
+{
+	exi_driver_unregister(&gcnrtc_driver);
+}
+
+module_init(gcnrtc_init_module);
+module_exit(gcnrtc_exit_module);
+
+MODULE_AUTHOR(DRV_AUTHOR);
+MODULE_DESCRIPTION(DRV_DESCRIPTION);
+MODULE_LICENSE("GPL");
diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig
index e522572..c9e405c 100644
--- a/drivers/serial/Kconfig
+++ b/drivers/serial/Kconfig
@@ -1477,4 +1477,17 @@ config SERIAL_BCM63XX_CONSOLE
 	  If you have enabled the serial port on the bcm63xx CPU
 	  you can make it the console by answering Y to this option.
 
+config SERIAL_USBGECKO
+	bool "USBGecko adapter on the Nintendo GameCube/Wii"
+	depends on GAMECUBE_EXI
+	select SERIAL_CORE
+	help
+	  This is a driver for the USB Gecko adapter for the Nintendo GameCube
+	  and Wii gaming consoles. It provides a console and a tty interface.
+
+	  If you have an adapter like this, say Y here, otherwise say N.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called usbgecko.
+
 endmenu
diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile
index d21d5dd..5a1feab 100644
--- a/drivers/serial/Makefile
+++ b/drivers/serial/Makefile
@@ -81,3 +81,4 @@ obj-$(CONFIG_SERIAL_KS8695) += serial_ks8695.o
 obj-$(CONFIG_KGDB_SERIAL_CONSOLE) += kgdboc.o
 obj-$(CONFIG_SERIAL_QE) += ucc_uart.o
 obj-$(CONFIG_SERIAL_TIMBERDALE)	+= timbuart.o
+obj-$(CONFIG_SERIAL_USBGECKO) += usbgecko.o
diff --git a/drivers/serial/usbgecko.c b/drivers/serial/usbgecko.c
new file mode 100644
index 0000000..223890d
--- /dev/null
+++ b/drivers/serial/usbgecko.c
@@ -0,0 +1,597 @@
+/*
+ * drivers/serial/usbgecko.c
+ *
+ * Console and TTY driver for the USB Gecko adapter.
+ * Copyright (C) 2008-2009 The GameCube Linux Team
+ * Copyright (C) 2008,2009 Albert Herranz
+ *
+ * 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.
+ *
+ */
+
+#define UG_DEBUG
+
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/console.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/kthread.h>
+#include <linux/delay.h>
+
+#include <linux/exi.h>
+
+#define DRV_MODULE_NAME "usbgecko"
+#define DRV_DESCRIPTION "Console and TTY driver for the USB Gecko adapter"
+#define DRV_AUTHOR      "Albert Herranz"
+
+static char ug_driver_version[] = "0.1i";
+
+#define drv_printk(level, format, arg...) \
+	printk(level DRV_MODULE_NAME ": " format , ## arg)
+
+/*
+ *
+ * EXI related definitions.
+ */
+#define UG_SLOTA_CHANNEL	0	/* EXI0xxx */
+#define UG_SLOTA_DEVICE		0	/* chip select, EXI0CSB0 */
+
+#define UG_SLOTB_CHANNEL	1	/* EXI1xxx */
+#define UG_SLOTB_DEVICE		0	/* chip select, EXI1CSB0 */
+
+#define UG_SPI_CLK_IDX		EXI_CLK_32MHZ
+
+
+struct ug_adapter {
+	struct exi_device *exi_device;
+	struct task_struct *poller;
+	struct mutex mutex;
+	int refcnt;
+};
+
+static struct ug_adapter ug_adapters[2];
+
+
+/*
+ *
+ * Hardware interface.
+ */
+
+/*
+ *
+ */
+static void ug_exi_io_transaction(struct exi_device *exi_device, u16 i, u16 *o)
+{
+	u16 data;
+
+	exi_dev_select(exi_device);
+	data = i;
+	exi_dev_readwrite(exi_device, &data, 2);
+	exi_dev_deselect(exi_device);
+	*o = data;
+}
+
+#if 0
+/*
+ *
+ */
+static void ug_io_transaction(struct ug_adapter *adapter, u16 i, u16 *o)
+{
+	struct exi_device *exi_device = adapter->exi_device;
+
+	if (exi_device)
+		ug_exi_io_transaction(exi_device, i, o);
+}
+#endif
+
+/*
+ *
+ */
+static int ug_check_adapter(struct exi_device *exi_device)
+{
+	u16 data;
+
+	exi_dev_take(exi_device);
+	ug_exi_io_transaction(exi_device, 0x9000, &data);
+	exi_dev_give(exi_device);
+
+	return data == 0x0470;
+}
+
+#if 0
+/*
+ *
+ */
+static int ug_is_txfifo_empty(struct ug_adapter *adapter)
+{
+	struct exi_device *exi_device = adapter->exi_device;
+	u16 data;
+
+	if (!exi_device)
+		return 0;
+
+	if (!exi_dev_try_take(exi_device)) {
+		ug_exi_io_transaction(exi_device, 0xC000, &data);
+		exi_dev_give(exi_device);
+		return data & 0x0400;
+	}
+	return 0;
+}
+
+/*
+ *
+ */
+static int ug_is_rxfifo_empty(struct ug_adapter *adapter)
+{
+	struct exi_device *exi_device = adapter->exi_device;
+	u16 data;
+
+	if (!exi_device)
+		return 0;
+
+	if (!exi_dev_try_take(exi_device)) {
+		ug_exi_io_transaction(exi_device, 0xD000, &data);
+		exi_dev_give(exi_device);
+		return data & 0x0400;
+	}
+	return 0;
+}
+
+/*
+ *
+ */
+static int ug_putc(struct ug_adapter *adapter, char c)
+{
+	struct exi_device *exi_device = adapter->exi_device;
+	u16 data;
+
+	if (!exi_device)
+		return 0;
+
+	if (!exi_dev_try_take(exi_device)) {
+		ug_exi_io_transaction(exi_device, 0xB000|(c<<4), &data);
+		exi_dev_give(exi_device);
+		return data & 0x0400;
+	}
+	return 0;
+}
+
+/*
+ *
+ */
+static int ug_getc(struct ug_adapter *adapter, char *c)
+{
+	struct exi_device *exi_device = adapter->exi_device;
+	u16 data;
+
+	if (!exi_device)
+		return 0;
+
+	if (!exi_dev_try_take(exi_device)) {
+		ug_exi_io_transaction(exi_device, 0xA000, &data);
+		exi_dev_give(exi_device);
+		if ((data & 0x0800)) {
+			*c = data & 0xff;
+			return 1;
+		}
+	}
+	return 0;
+}
+#endif
+
+/*
+ *
+ */
+static int ug_safe_putc(struct ug_adapter *adapter, char c)
+{
+	struct exi_device *exi_device = adapter->exi_device;
+	u16 data;
+
+	if (!exi_device)
+		return 0;
+
+	if (!exi_dev_try_take(exi_device)) {
+		ug_exi_io_transaction(exi_device, 0xC000, &data);
+		if ((data & 0x0400))
+			ug_exi_io_transaction(exi_device, 0xB000|(c<<4), &data);
+		exi_dev_give(exi_device);
+		return data & 0x0400;
+	}
+	return 0;
+}
+
+/*
+ *
+ */
+static int ug_safe_getc(struct ug_adapter *adapter, char *c)
+{
+	struct exi_device *exi_device = adapter->exi_device;
+	u16 data;
+
+	if (!exi_device)
+		return 0;
+
+	if (!exi_dev_try_take(exi_device)) {
+		ug_exi_io_transaction(exi_device, 0xD000, &data);
+		if ((data & 0x0400))  {
+			ug_exi_io_transaction(exi_device, 0xA000, &data);
+			exi_dev_give(exi_device);
+			if ((data & 0x0800)) {
+				*c = data & 0xff;
+				return 1;
+			}
+		} else {
+			exi_dev_give(exi_device);
+		}
+	}
+	return 0;
+}
+
+
+/*
+ *
+ * Linux console interface.
+ */
+
+/*
+ *
+ */
+static void ug_console_write(struct console *co, const char *buf,
+			      unsigned int count)
+{
+	struct ug_adapter *adapter = co->data;
+	char *b = (char *)buf;
+
+	while (count--) {
+		if (*b == '\n')
+			ug_safe_putc(adapter, '\r');
+		ug_safe_putc(adapter, *b++);
+	}
+}
+
+/*
+ *
+ */
+static int ug_console_read(struct console *co, char *buf,
+			    unsigned int count)
+{
+	struct ug_adapter *adapter = co->data;
+	int i;
+	char c;
+
+	i = count;
+	while (i--) {
+		ug_safe_getc(adapter, &c);
+		*buf++ = c;
+	}
+	return count;
+}
+
+static struct tty_driver *ug_tty_driver;
+
+static struct tty_driver *ug_console_device(struct console *co, int *index)
+{
+	*index = co->index;
+	return ug_tty_driver;
+}
+
+
+static struct console ug_consoles[] = {
+	{
+		.name   = DRV_MODULE_NAME "0",
+		.write  = ug_console_write,
+		.read   = ug_console_read,
+		.device = ug_console_device,
+		.flags  = CON_PRINTBUFFER | CON_ENABLED,
+		.index  = 0,
+		.data	= &ug_adapters[0],
+	},
+	{
+		.name   = DRV_MODULE_NAME "1",
+		.write  = ug_console_write,
+		.read   = ug_console_read,
+		.device = ug_console_device,
+		.flags  = CON_PRINTBUFFER | CON_ENABLED,
+		.index  = 1,
+		.data	= &ug_adapters[1],
+	},
+};
+
+
+/*
+ *
+ * Linux tty driver.
+ */
+
+static int ug_tty_poller(void *tty_)
+{
+	struct sched_param param = { .sched_priority = 1 };
+	struct tty_struct *tty = tty_;
+	struct ug_adapter *adapter;
+	int count, chunk;
+	const int max_outstanding = 32;
+	char ch;
+
+	sched_setscheduler(current, SCHED_FIFO, &param);
+	set_task_state(current, TASK_RUNNING);
+
+	chunk = 0;
+	while (!kthread_should_stop()) {
+		count = 0;
+		adapter = tty->driver_data;
+		if (adapter)
+			count = ug_safe_getc(adapter, &ch);
+		set_task_state(current, TASK_INTERRUPTIBLE);
+		if (count) {
+			tty_insert_flip_char(tty, ch, TTY_NORMAL);
+			if (chunk++ > max_outstanding) {
+				tty_flip_buffer_push(tty);
+				chunk = 0;
+			}
+		} else {
+			if (chunk) {
+				tty_flip_buffer_push(tty);
+				chunk = 0;
+			}
+			schedule_timeout(1);
+		}
+		set_task_state(current, TASK_RUNNING);
+	}
+
+	return 0;
+}
+
+static int ug_tty_open(struct tty_struct *tty, struct file *filp)
+{
+	struct ug_adapter *adapter;
+	int index;
+	int retval = 0;
+
+	index = tty->index;
+	adapter = &ug_adapters[index];
+
+	mutex_lock(&adapter->mutex);
+
+	if (!adapter->exi_device) {
+		mutex_unlock(&adapter->mutex);
+		return -ENODEV;
+	}
+
+	if (!adapter->refcnt) {
+		adapter->poller = kthread_run(ug_tty_poller, tty, "kugtty");
+		if (IS_ERR(adapter->poller)) {
+			drv_printk(KERN_ERR, "error creating poller thread\n");
+			mutex_unlock(&adapter->mutex);
+			return -ENOMEM;
+		}
+	}
+
+	adapter->refcnt++;
+	tty->driver_data = adapter;
+
+	mutex_unlock(&adapter->mutex);
+
+	return retval;
+}
+
+static void ug_tty_close(struct tty_struct *tty, struct file *filp)
+{
+	struct ug_adapter *adapter;
+	int index;
+
+	index = tty->index;
+	adapter = &ug_adapters[index];
+
+	mutex_lock(&adapter->mutex);
+
+	adapter->refcnt--;
+	if (!adapter->refcnt) {
+		if (!IS_ERR(adapter->poller))
+			kthread_stop(adapter->poller);
+		adapter->poller = ERR_PTR(-EINVAL);
+		tty->driver_data = NULL;
+	}
+
+	mutex_unlock(&adapter->mutex);
+}
+
+static int ug_tty_write(struct tty_struct *tty,
+			 const unsigned char *buf, int count)
+{
+	struct ug_adapter *adapter = tty->driver_data;
+	char *b = (char *)buf;
+	int index;
+	int i;
+
+	if (!adapter)
+		return -ENODEV;
+
+	index = tty->index;
+	adapter = &ug_adapters[index];
+	for (i = 0; i < count; i++)
+		ug_safe_putc(adapter, *b++);
+	return count;
+}
+
+static int ug_tty_write_room(struct tty_struct *tty)
+{
+	return 0x123; /* whatever */
+}
+
+static int ug_tty_chars_in_buffer(struct tty_struct *tty)
+{
+	return 0; /* unbuffered */
+}
+
+
+static const struct tty_operations ug_tty_ops = {
+	.open = ug_tty_open,
+	.close = ug_tty_close,
+	.write = ug_tty_write,
+	.write_room = ug_tty_write_room,
+	.chars_in_buffer = ug_tty_chars_in_buffer,
+};
+
+
+static int ug_tty_init(void)
+{
+	struct tty_driver *driver;
+	int retval;
+
+	driver = alloc_tty_driver(2);
+	if (!driver)
+		return -ENOMEM;
+	driver->name = DRV_MODULE_NAME "con";
+	driver->major = TTY_MAJOR;
+	driver->minor_start = 64;
+	driver->type = TTY_DRIVER_TYPE_SYSCONS;
+	driver->init_termios = tty_std_termios;
+	tty_set_operations(driver, &ug_tty_ops);
+	retval = tty_register_driver(driver);
+	if (retval) {
+		put_tty_driver(driver);
+		return retval;
+	}
+	ug_tty_driver = driver;
+	return 0;
+}
+
+static void ug_tty_exit(void)
+{
+	struct tty_driver *driver = ug_tty_driver;
+
+	ug_tty_driver = NULL;
+	if (driver) {
+		tty_unregister_driver(driver);
+		put_tty_driver(driver);
+	}
+}
+
+
+
+/*
+ *
+ * EXI layer interface.
+ */
+
+/*
+ *
+ */
+static int ug_probe(struct exi_device *exi_device)
+{
+	struct console *console;
+	struct ug_adapter *adapter;
+	unsigned int slot;
+
+	/* don't try to drive a device which already has a real identifier */
+	if (exi_device->eid.id != EXI_ID_NONE)
+		return -ENODEV;
+
+	if (!ug_check_adapter(exi_device))
+		return -ENODEV;
+
+	slot = to_channel(exi_get_exi_channel(exi_device));
+	console = &ug_consoles[slot];
+	adapter = console->data;
+
+	drv_printk(KERN_INFO, "USB Gecko detected in memcard slot-%c\n",
+		   'A'+slot);
+
+	adapter->poller = ERR_PTR(-EINVAL);
+	mutex_init(&adapter->mutex);
+	adapter->refcnt = 0;
+
+	adapter->exi_device = exi_device_get(exi_device);
+	exi_set_drvdata(exi_device, adapter);
+	register_console(console);
+
+	ug_tty_init();
+
+	return 0;
+}
+
+/*
+ * Makes unavailable the USB Gecko adapter identified by the EXI device
+ * `exi_device'.
+ */
+static void ug_remove(struct exi_device *exi_device)
+{
+	struct console *console;
+	struct ug_adapter *adapter;
+	unsigned int slot;
+
+	slot = to_channel(exi_get_exi_channel(exi_device));
+	console = &ug_consoles[slot];
+	adapter = console->data;
+
+	if (adapter->refcnt)
+		drv_printk(KERN_ERR, "adapter removed while in use!\n");
+
+	ug_tty_exit();
+
+	unregister_console(console);
+	exi_set_drvdata(exi_device, NULL);
+	adapter->exi_device = NULL;
+	exi_device_put(exi_device);
+
+	mutex_destroy(&adapter->mutex);
+
+	drv_printk(KERN_INFO, "USB Gecko removed from memcard slot-%c\n",
+		   'A'+slot);
+}
+
+static struct exi_device_id ug_eid_table[] = {
+	[0] = {
+	       .channel = UG_SLOTA_CHANNEL,
+	       .device = UG_SLOTA_DEVICE,
+	       .id = EXI_ID_NONE,
+	       },
+	[1] = {
+	       .channel = UG_SLOTB_CHANNEL,
+	       .device = UG_SLOTB_DEVICE,
+	       .id = EXI_ID_NONE,
+	       },
+	{.id = 0}
+};
+
+static struct exi_driver ug_exi_driver = {
+	.name = DRV_MODULE_NAME,
+	.eid_table = ug_eid_table,
+	.frequency = UG_SPI_CLK_IDX,
+	.probe = ug_probe,
+	.remove = ug_remove,
+};
+
+
+/*
+ *
+ * Module interface.
+ */
+
+static int __init ug_init_module(void)
+{
+	drv_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION,
+		   ug_driver_version);
+
+	return exi_driver_register(&ug_exi_driver);
+}
+
+static void __exit ug_exit_module(void)
+{
+	exi_driver_unregister(&ug_exi_driver);
+}
+
+module_init(ug_init_module);
+module_exit(ug_exit_module);
+
+MODULE_AUTHOR(DRV_AUTHOR);
+MODULE_DESCRIPTION(DRV_DESCRIPTION);
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
index 2407508..0a2a7f4 100644
--- a/drivers/usb/Kconfig
+++ b/drivers/usb/Kconfig
@@ -22,6 +22,8 @@ config USB_ARCH_HAS_HCD
 	default y if PCMCIA && !M32R			# sl811_cs
 	default y if ARM				# SL-811
 	default y if SUPERH				# r8a66597-hcd
+	default y if WII				# rvl-sthcd
+	default y if STARLET_IOS			# rvl-sthcd
 	default PCI
 
 # many non-PCI SOC chips embed OHCI
diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
index be3c9b8..7c782a7 100644
--- a/drivers/usb/Makefile
+++ b/drivers/usb/Makefile
@@ -19,6 +19,7 @@ obj-$(CONFIG_USB_SL811_HCD)	+= host/
 obj-$(CONFIG_USB_ISP1362_HCD)	+= host/
 obj-$(CONFIG_USB_U132_HCD)	+= host/
 obj-$(CONFIG_USB_R8A66597_HCD)	+= host/
+obj-$(CONFIG_USB_WII_HCD)	+= host/
 obj-$(CONFIG_USB_HWA_HCD)	+= host/
 obj-$(CONFIG_USB_ISP1760_HCD)	+= host/
 
diff --git a/drivers/usb/core/buffer.c b/drivers/usb/core/buffer.c
index 3ba2fff..db5ee6b 100644
--- a/drivers/usb/core/buffer.c
+++ b/drivers/usb/core/buffer.c
@@ -115,6 +115,9 @@ void *hcd_buffer_alloc(
 		return kmalloc(size, mem_flags);
 	}
 
+	/* make sure that we allocate correctly aligned dma memory */
+	size = _ALIGN_UP(size, dma_get_cache_alignment());
+
 	for (i = 0; i < HCD_BUFFER_POOLS; i++) {
 		if (size <= pool_max [i])
 			return dma_pool_alloc(hcd->pool [i], mem_flags, dma);
@@ -141,6 +144,9 @@ void hcd_buffer_free(
 		return;
 	}
 
+	/* account for the real size */
+	size = _ALIGN_UP(size, dma_get_cache_alignment());
+
 	for (i = 0; i < HCD_BUFFER_POOLS; i++) {
 		if (size <= pool_max [i]) {
 			dma_pool_free(hcd->pool [i], addr, dma);
diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
index 24e6205..fddf958 100644
--- a/drivers/usb/core/hcd.c
+++ b/drivers/usb/core/hcd.c
@@ -1212,10 +1212,10 @@ EXPORT_SYMBOL_GPL(usb_hcd_unlink_urb_from_ep);
  *
  */
 
-static int hcd_alloc_coherent(struct usb_bus *bus,
-			      gfp_t mem_flags, dma_addr_t *dma_handle,
-			      void **vaddr_handle, size_t size,
-			      enum dma_data_direction dir)
+static int hcd_alloc_local_mem(struct usb_bus *bus,
+			       gfp_t mem_flags, dma_addr_t *dma_handle,
+			       void **vaddr_handle, size_t size,
+			       enum dma_data_direction dir)
 {
 	unsigned char *vaddr;
 
@@ -1242,7 +1242,7 @@ static int hcd_alloc_coherent(struct usb_bus *bus,
 	return 0;
 }
 
-static void hcd_free_coherent(struct usb_bus *bus, dma_addr_t *dma_handle,
+static void hcd_free_local_mem(struct usb_bus *bus, dma_addr_t *dma_handle,
 			      void **vaddr_handle, size_t size,
 			      enum dma_data_direction dir)
 {
@@ -1259,6 +1259,138 @@ static void hcd_free_coherent(struct usb_bus *bus, dma_addr_t *dma_handle,
 	*dma_handle = 0;
 }
 
+/*
+ * HCD_BOUNCE_DMA_MEM exists for platforms that have weird
+ * constraints accessing DMA coherent memory.
+ * In this case, buffer allocations are satisfied from normal memory
+ * and, only at the very last moment, before the actual transfer,
+ * buffers are copied to/from their corresponding DMA coherent bounce
+ * buffers.
+ *
+ * As an illustrative example, the Nintendo Wii video game console
+ * is a NOT_COHERENT_CACHE platform that is unable to safely perform
+ * non-32 bit uncached writes to the top 64MB of RAM (it actually
+ * _corrupts_ RAM if done).
+ * Thus, in that platform, DMA buffers cannot be directly used by kernel
+ * code unless the code guarantees that all write accesses to buffers
+ * are done in 32 bit chunks (which is not the case in the USB subsystem).
+ */
+
+/*
+ * XXX
+ * This is actually generic DMA code that could be moved to a more
+ * appropiate file.
+ *
+ */
+
+static void *dma_memcpy_to_coherent(void *dst, const void *src, size_t len)
+{
+	u32 *q = dst, *p = (void *)src;
+	u8 *s;
+
+	while (len >= 4) {
+		*q++ = *p++;
+		len -= 4;
+	}
+	s = (u8 *)p;
+	switch (len) {
+	case 3:
+		*q = s[0] << 24 | s[1] << 16 | s[2] << 8;
+		break;
+	case 2:
+		*q = s[0] << 24 | s[1] << 16;
+		break;
+	case 1:
+		*q = s[0] << 24;
+		break;
+	default:
+		break;
+	}
+	return dst;
+}
+
+static void *dma_memcpy_from_coherent(void *dst, const void *src, size_t len)
+{
+	u32 *q = dst, *p = (void *)src;
+	u32 v;
+	u8 *d;
+
+	while (len >= 4) {
+		*q++ = *p++;
+		len -= 4;
+	}
+	if (len) {
+		d = (u8 *)q;
+		v = p[0];
+		switch (len) {
+		case 3:
+			d[2] = (v >> 8) & 0xff;
+			/* FALL THROUGH */
+		case 2:
+			d[1] = (v >> 16) & 0xff;
+			/* FALL THROUGH */
+		case 1:
+			d[0] = (v >> 24) & 0xff;
+			break;
+		default:
+			break;
+		}
+	}
+	return dst;
+}
+
+struct dma_coherent_buffer_ctx {
+	unsigned char *vaddr;
+	dma_addr_t dma_handle;
+};
+
+static int dma_bounce_to_coherent(struct device *dev,
+				  gfp_t mem_flags, dma_addr_t *dma_handle,
+				  void **vaddr_handle, size_t size,
+				  enum dma_data_direction dir)
+{
+	struct dma_coherent_buffer_ctx ctx;
+	unsigned char *vaddr;
+	size_t up_size = _ALIGN_UP(size + sizeof(ctx),
+				   dma_get_cache_alignment());
+
+	ctx.vaddr = *vaddr_handle;
+	ctx.dma_handle = *dma_handle;
+
+	vaddr = dma_alloc_coherent(dev, up_size, dma_handle, mem_flags);
+	if (!vaddr)
+		return -ENOMEM;
+
+	memcpy(vaddr + up_size - sizeof(ctx), &ctx, sizeof(ctx));
+
+	if (dir == DMA_TO_DEVICE)
+		dma_memcpy_to_coherent(vaddr, *vaddr_handle, size);
+
+	*vaddr_handle = vaddr;
+	return 0;
+}
+
+static void dma_bounce_from_coherent(struct device *dev, dma_addr_t *dma_handle,
+				     void **vaddr_handle, size_t size,
+				     enum dma_data_direction dir)
+{
+	struct dma_coherent_buffer_ctx ctx;
+	unsigned char *vaddr = *vaddr_handle;
+	size_t up_size = _ALIGN_UP(size + sizeof(ctx),
+				   dma_get_cache_alignment());
+
+	memcpy(&ctx, vaddr + up_size - sizeof(ctx), sizeof(ctx));
+	vaddr = ctx.vaddr;
+
+	if (dir == DMA_FROM_DEVICE)
+		dma_memcpy_from_coherent(vaddr, *vaddr_handle, size);
+
+	dma_free_coherent(dev, up_size, *vaddr_handle, *dma_handle);
+
+	*vaddr_handle = vaddr;
+	*dma_handle = ctx.dma_handle;
+}
+
 static int map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb,
 			   gfp_t mem_flags)
 {
@@ -1273,47 +1405,74 @@ static int map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb,
 	if (is_root_hub(urb->dev))
 		return 0;
 
-	if (usb_endpoint_xfer_control(&urb->ep->desc)
-	    && !(urb->transfer_flags & URB_NO_SETUP_DMA_MAP)) {
-		if (hcd->self.uses_dma)
-			urb->setup_dma = dma_map_single(
-					hcd->self.controller,
-					urb->setup_packet,
-					sizeof(struct usb_ctrlrequest),
-					DMA_TO_DEVICE);
-		else if (hcd->driver->flags & HCD_LOCAL_MEM)
-			ret = hcd_alloc_coherent(
-					urb->dev->bus, mem_flags,
+	if (usb_endpoint_xfer_control(&urb->ep->desc)) {
+		if (hcd->driver->flags & HCD_BOUNCE_DMA_MEM) {
+			if (!(urb->transfer_flags & URB_NO_SETUP_DMA_MAP))
+				urb->setup_dma = 0;
+			ret = dma_bounce_to_coherent(
+					hcd->self.controller, mem_flags,
 					&urb->setup_dma,
 					(void **)&urb->setup_packet,
 					sizeof(struct usb_ctrlrequest),
 					DMA_TO_DEVICE);
+		} else if (!(urb->transfer_flags & URB_NO_SETUP_DMA_MAP)) {
+			if (hcd->self.uses_dma)
+				urb->setup_dma = dma_map_single(
+						hcd->self.controller,
+						urb->setup_packet,
+						sizeof(struct usb_ctrlrequest),
+						DMA_TO_DEVICE);
+			else if (hcd->driver->flags & HCD_LOCAL_MEM)
+				ret = hcd_alloc_local_mem(
+						urb->dev->bus, mem_flags,
+						&urb->setup_dma,
+						(void **)&urb->setup_packet,
+						sizeof(struct usb_ctrlrequest),
+						DMA_TO_DEVICE);
+		}
 	}
 
 	dir = usb_urb_dir_in(urb) ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
-	if (ret == 0 && urb->transfer_buffer_length != 0
-	    && !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP)) {
-		if (hcd->self.uses_dma)
-			urb->transfer_dma = dma_map_single (
-					hcd->self.controller,
-					urb->transfer_buffer,
-					urb->transfer_buffer_length,
-					dir);
-		else if (hcd->driver->flags & HCD_LOCAL_MEM) {
-			ret = hcd_alloc_coherent(
-					urb->dev->bus, mem_flags,
+	if (ret == 0 && urb->transfer_buffer_length != 0) {
+		if (hcd->driver->flags & HCD_BOUNCE_DMA_MEM) {
+			if (!(urb->transfer_flags & URB_NO_SETUP_DMA_MAP))
+				urb->transfer_dma = 0;
+			ret = dma_bounce_to_coherent(
+					hcd->self.controller, mem_flags,
 					&urb->transfer_dma,
 					&urb->transfer_buffer,
 					urb->transfer_buffer_length,
 					dir);
 
-			if (ret && usb_endpoint_xfer_control(&urb->ep->desc)
-			    && !(urb->transfer_flags & URB_NO_SETUP_DMA_MAP))
-				hcd_free_coherent(urb->dev->bus,
-					&urb->setup_dma,
-					(void **)&urb->setup_packet,
-					sizeof(struct usb_ctrlrequest),
-					DMA_TO_DEVICE);
+			if (ret && usb_endpoint_xfer_control(&urb->ep->desc))
+				dma_bounce_from_coherent(hcd->self.controller,
+						&urb->setup_dma,
+						(void **)&urb->setup_packet,
+						sizeof(struct usb_ctrlrequest),
+						DMA_TO_DEVICE);
+		} else if (!(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP)) {
+			if (hcd->self.uses_dma)
+				urb->transfer_dma = dma_map_single(
+						hcd->self.controller,
+						urb->transfer_buffer,
+						urb->transfer_buffer_length,
+						dir);
+			else if (hcd->driver->flags & HCD_LOCAL_MEM) {
+				ret = hcd_alloc_local_mem(
+						urb->dev->bus, mem_flags,
+						&urb->transfer_dma,
+						&urb->transfer_buffer,
+						urb->transfer_buffer_length,
+						dir);
+
+				if (ret &&
+				    usb_endpoint_xfer_control(&urb->ep->desc))
+					hcd_free_local_mem(urb->dev->bus,
+						&urb->setup_dma,
+						(void **)&urb->setup_packet,
+						sizeof(struct usb_ctrlrequest),
+						DMA_TO_DEVICE);
+			}
 		}
 	}
 	return ret;
@@ -1326,32 +1485,49 @@ static void unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb)
 	if (is_root_hub(urb->dev))
 		return;
 
-	if (usb_endpoint_xfer_control(&urb->ep->desc)
-	    && !(urb->transfer_flags & URB_NO_SETUP_DMA_MAP)) {
-		if (hcd->self.uses_dma)
-			dma_unmap_single(hcd->self.controller, urb->setup_dma,
+	if (usb_endpoint_xfer_control(&urb->ep->desc)) {
+		if (hcd->driver->flags & HCD_BOUNCE_DMA_MEM)
+			dma_bounce_from_coherent(hcd->self.controller,
+					&urb->setup_dma,
+					(void **)&urb->setup_packet,
 					sizeof(struct usb_ctrlrequest),
 					DMA_TO_DEVICE);
-		else if (hcd->driver->flags & HCD_LOCAL_MEM)
-			hcd_free_coherent(urb->dev->bus, &urb->setup_dma,
+		else if (!(urb->transfer_flags & URB_NO_SETUP_DMA_MAP)) {
+			if (hcd->self.uses_dma)
+				dma_unmap_single(hcd->self.controller,
+						 urb->setup_dma,
+						 sizeof(struct usb_ctrlrequest),
+						 DMA_TO_DEVICE);
+			else if (hcd->driver->flags & HCD_LOCAL_MEM)
+				hcd_free_local_mem(urb->dev->bus,
+					&urb->setup_dma,
 					(void **)&urb->setup_packet,
 					sizeof(struct usb_ctrlrequest),
 					DMA_TO_DEVICE);
+		}
 	}
 
 	dir = usb_urb_dir_in(urb) ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
-	if (urb->transfer_buffer_length != 0
-	    && !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP)) {
-		if (hcd->self.uses_dma)
-			dma_unmap_single(hcd->self.controller,
-					urb->transfer_dma,
-					urb->transfer_buffer_length,
-					dir);
-		else if (hcd->driver->flags & HCD_LOCAL_MEM)
-			hcd_free_coherent(urb->dev->bus, &urb->transfer_dma,
-					&urb->transfer_buffer,
-					urb->transfer_buffer_length,
-					dir);
+	if (urb->transfer_buffer_length != 0) {
+		if (hcd->driver->flags & HCD_BOUNCE_DMA_MEM)
+			dma_bounce_from_coherent(hcd->self.controller,
+						&urb->transfer_dma,
+						&urb->transfer_buffer,
+						urb->transfer_buffer_length,
+						dir);
+		else if (!(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP)) {
+			if (hcd->self.uses_dma)
+				dma_unmap_single(hcd->self.controller,
+						urb->transfer_dma,
+						urb->transfer_buffer_length,
+						dir);
+			else if (hcd->driver->flags & HCD_LOCAL_MEM)
+				hcd_free_local_mem(urb->dev->bus,
+						&urb->transfer_dma,
+						&urb->transfer_buffer,
+						urb->transfer_buffer_length,
+						dir);
+		}
 	}
 }
 
@@ -1973,6 +2149,10 @@ struct usb_hcd *usb_create_hcd (const struct hc_driver *driver,
 	dev_set_drvdata(dev, hcd);
 	kref_init(&hcd->kref);
 
+	/* pretend we can't do DMA */
+	if (driver->flags & HCD_BOUNCE_DMA_MEM)
+		dev->dma_mask = NULL;
+
 	usb_bus_init(&hcd->self);
 	hcd->self.controller = dev;
 	hcd->self.bus_name = bus_name;
diff --git a/drivers/usb/core/hcd.h b/drivers/usb/core/hcd.h
index bcbe104..673bb66 100644
--- a/drivers/usb/core/hcd.h
+++ b/drivers/usb/core/hcd.h
@@ -169,12 +169,13 @@ struct hc_driver {
 	irqreturn_t	(*irq) (struct usb_hcd *hcd);
 
 	int	flags;
-#define	HCD_MEMORY	0x0001		/* HC regs use memory (else I/O) */
-#define	HCD_LOCAL_MEM	0x0002		/* HC needs local memory */
-#define	HCD_USB11	0x0010		/* USB 1.1 */
-#define	HCD_USB2	0x0020		/* USB 2.0 */
-#define	HCD_USB3	0x0040		/* USB 3.0 */
-#define	HCD_MASK	0x0070
+#define	HCD_MEMORY		0x0001	/* HC regs use memory (else I/O) */
+#define	HCD_LOCAL_MEM		0x0002	/* HC needs local memory */
+#define	HCD_BOUNCE_DMA_MEM	0x0004	/* HC needs bounced DMA memory */
+#define	HCD_USB11		0x0010	/* USB 1.1 */
+#define	HCD_USB2		0x0020	/* USB 2.0 */
+#define	HCD_USB3		0x0040	/* USB 3.0 */
+#define	HCD_MASK		0x0070
 
 	/* called to init HCD and root hub */
 	int	(*reset) (struct usb_hcd *hcd);
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 9b43b22..b1d17ff 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -113,6 +113,14 @@ config USB_EHCI_HCD_PPC_OF
 	  Enables support for the USB controller present on the PowerPC
 	  OpenFirmware platform bus.
 
+config USB_EHCI_HCD_HLWD
+	bool "Nintendo Wii (Hollywood) EHCI USB controller support"
+	depends on USB_EHCI_HCD && STARLET_MINI
+	default y
+	---help---
+	  Say Y here to support the EHCI USB controller found in the
+	  Hollywood chipset of the Nintendo Wii video game console.
+
 config USB_W90X900_EHCI
 	bool "W90X900(W90P910) EHCI support"
 	depends on USB_EHCI_HCD && ARCH_W90X900
@@ -221,6 +229,15 @@ config USB_OHCI_HCD_PPC_OF
 	depends on USB_OHCI_HCD && PPC_OF
 	default USB_OHCI_HCD_PPC_OF_BE || USB_OHCI_HCD_PPC_OF_LE
 
+config USB_OHCI_HCD_HLWD
+	bool "Nintendo Wii (Hollywood) OHCI USB controller support"
+	depends on USB_OHCI_HCD && STARLET_MINI
+	select USB_OHCI_LITTLE_ENDIAN
+	default y
+	---help---
+	  Say Y here to support the OHCI USB controller found in the
+	  Hollywood chipset of the Nintendo Wii video game console.
+
 config USB_OHCI_HCD_PCI
 	bool "OHCI support for PCI-bus USB controllers"
 	depends on USB_OHCI_HCD && PCI && (STB03xxx || PPC_MPC52xx || USB_OHCI_HCD_PPC_OF)
@@ -354,6 +371,25 @@ config USB_R8A66597_HCD
 	  To compile this driver as a module, choose M here: the
 	  module will be called r8a66597-hcd.
 
+config USB_WII_HCD
+	tristate "Nintendo Wii HCD support"
+	depends on USB && STARLET_IOS && !HIGHMEM && EXPERIMENTAL
+	help
+	  The Nintendo Wii includes a USB 1.1 host controller that can be
+	  accessed through the API provided by the starlet subsystem.
+
+	  Enable this option if you plan to use the internal Nintendo Wii
+	  bluetooth dongle or any USB peripheral connected to the external
+	  ports.
+
+	  USB devices using isochronous transfers are not supported.
+	  Use of USB hubs is partially supported.
+
+	  Use completely at you own risk. If unsure, say N.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called rvl-sthcd.
+
 config USB_WHCI_HCD
 	tristate "Wireless USB Host Controller Interface (WHCI) driver (EXPERIMENTAL)"
 	depends on EXPERIMENTAL
diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
index f58b249..03e7f87 100644
--- a/drivers/usb/host/Makefile
+++ b/drivers/usb/host/Makefile
@@ -31,4 +31,5 @@ obj-$(CONFIG_USB_SL811_CS)	+= sl811_cs.o
 obj-$(CONFIG_USB_U132_HCD)	+= u132-hcd.o
 obj-$(CONFIG_USB_R8A66597_HCD)	+= r8a66597-hcd.o
 obj-$(CONFIG_USB_ISP1760_HCD)	+= isp1760.o
+obj-$(CONFIG_USB_WII_HCD)	+= rvl-sthcd.o
 obj-$(CONFIG_USB_HWA_HCD)	+= hwa-hc.o
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index 8198fc0..0711223 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -1122,6 +1122,11 @@ MODULE_LICENSE ("GPL");
 #define OF_PLATFORM_DRIVER	ehci_hcd_ppc_of_driver
 #endif
 
+#ifdef CONFIG_USB_EHCI_HCD_HLWD
+#include "ehci-hlwd.c"
+#define OF_PLATFORM_DRIVER	ehci_hcd_hlwd_driver
+#endif
+
 #ifdef CONFIG_PLAT_ORION
 #include "ehci-orion.c"
 #define	PLATFORM_DRIVER		ehci_orion_driver
diff --git a/drivers/usb/host/ehci-hlwd.c b/drivers/usb/host/ehci-hlwd.c
new file mode 100644
index 0000000..b4dedd1
--- /dev/null
+++ b/drivers/usb/host/ehci-hlwd.c
@@ -0,0 +1,265 @@
+/*
+ * drivers/usb/host/ehci-hlwd.c
+ *
+ * Nintendo Wii (Hollywood) USB Enhanced Host Controller Interface.
+ * Copyright (C) 2009 The GameCube Linux Team
+ * Copyright (C) 2009 Albert Herranz
+ *
+ * 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.
+ *
+ * Based on ehci-ppc-of.c
+ *
+ * EHCI HCD (Host Controller Driver) for USB.
+ *
+ * Bus Glue for PPC On-Chip EHCI driver on the of_platform bus
+ * Tested on AMCC PPC 440EPx
+ *
+ * Valentine Barshak <vbarshak@ru.mvista.com>
+ *
+ * Based on "ehci-ppc-soc.c" by Stefan Roese <sr@denx.de>
+ * and "ohci-ppc-of.c" by Sylvain Munaut <tnt@246tNt.com>
+ *
+ * This file is licenced under the GPL.
+ */
+
+#include <linux/signal.h>
+
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <asm/starlet.h>
+
+#define DRV_MODULE_NAME "ehci-hlwd"
+#define DRV_DESCRIPTION "Nintendo Wii EHCI Host Controller"
+#define DRV_AUTHOR      "Albert Herranz"
+
+#define HLWD_EHCI_CTL		0x0d0400cc
+#define HLWD_EHCI_CTL_INTE	(1<<15)
+
+
+/* called during probe() after chip reset completes */
+static int ehci_hlwd_reset(struct usb_hcd *hcd)
+{
+	struct ehci_hcd	*ehci = hcd_to_ehci(hcd);
+	void __iomem *ehci_ctl;
+	int error;
+
+	dbg_hcs_params(ehci, "reset");
+	dbg_hcc_params(ehci, "reset");
+
+	error = ehci_halt(ehci);
+	if (error)
+		goto out;
+
+	error = ehci_init(hcd);
+	if (error)
+		goto out;
+
+	ehci_ctl = ioremap(HLWD_EHCI_CTL, 4);
+	if (!ehci_ctl) {
+		printk(KERN_ERR __FILE__ ": ioremap failed\n");
+		error = -EBUSY;
+		goto out;
+	}
+
+	/* enable notification of EHCI interrupts */
+	out_be32(ehci_ctl, in_be32(ehci_ctl) | HLWD_EHCI_CTL_INTE);
+	iounmap(ehci_ctl);
+
+	ehci->sbrn = 0x20;
+	error = ehci_reset(ehci);
+	ehci_port_power(ehci, 0);
+out:
+	return error;
+}
+
+static const struct hc_driver ehci_hlwd_hc_driver = {
+	.description		= hcd_name,
+	.product_desc		= "Nintendo Wii EHCI Host Controller",
+	.hcd_priv_size		= sizeof(struct ehci_hcd),
+
+	/*
+	 * generic hardware linkage
+	 */
+	.irq			= ehci_irq,
+	.flags			= HCD_USB2 | HCD_BOUNCE_DMA_MEM,
+
+	/*
+	 * basic lifecycle operations
+	 */
+	.reset			= ehci_hlwd_reset,
+	.start			= ehci_run,
+	.stop			= ehci_stop,
+	.shutdown		= ehci_shutdown,
+
+	/*
+	 * managing i/o requests and associated device resources
+	 */
+	.urb_enqueue		= ehci_urb_enqueue,
+	.urb_dequeue		= ehci_urb_dequeue,
+	.endpoint_disable	= ehci_endpoint_disable,
+	.endpoint_reset		= ehci_endpoint_reset,
+
+	/*
+	 * scheduling support
+	 */
+	.get_frame_number	= ehci_get_frame,
+
+	/*
+	 * root hub support
+	 */
+	.hub_status_data	= ehci_hub_status_data,
+	.hub_control		= ehci_hub_control,
+#ifdef	CONFIG_PM
+	.bus_suspend		= ehci_bus_suspend,
+	.bus_resume		= ehci_bus_resume,
+#endif
+	.relinquish_port	= ehci_relinquish_port,
+	.port_handed_over	= ehci_port_handed_over,
+
+	.clear_tt_buffer_complete	= ehci_clear_tt_buffer_complete,
+};
+
+
+static int __devinit
+ehci_hcd_hlwd_probe(struct of_device *op, const struct of_device_id *match)
+{
+	struct device_node *dn = op->node;
+	struct usb_hcd *hcd;
+	struct ehci_hcd	*ehci = NULL;
+	struct resource res;
+	dma_addr_t coherent_mem_addr;
+	size_t coherent_mem_size;
+	int irq;
+	int error = -ENODEV;
+
+	if (usb_disabled())
+		goto out;
+
+	if (starlet_get_ipc_flavour() != STARLET_IPC_MINI)
+		goto out;
+
+	dev_dbg(&op->dev, "initializing " DRV_MODULE_NAME " USB Controller\n");
+
+	error = of_address_to_resource(dn, 0, &res);
+	if (error)
+		goto out;
+
+	hcd = usb_create_hcd(&ehci_hlwd_hc_driver, &op->dev, DRV_MODULE_NAME);
+	if (!hcd) {
+		error = -ENOMEM;
+		goto out;
+	}
+
+	hcd->rsrc_start = res.start;
+	hcd->rsrc_len = resource_size(&res);
+
+	error = of_address_to_resource(dn, 1, &res);
+	if (error) {
+		/* satisfy coherent memory allocations from mem1 or mem2 */
+		dev_warn(&op->dev, "using normal memory\n");
+	} else {
+		coherent_mem_addr = res.start;
+		coherent_mem_size = res.end - res.start + 1;
+		if (!dma_declare_coherent_memory(&op->dev, coherent_mem_addr,
+						 coherent_mem_addr,
+						 coherent_mem_size,
+						 DMA_MEMORY_MAP |
+						 DMA_MEMORY_EXCLUSIVE)) {
+			dev_err(&op->dev, "error declaring %u bytes of"
+				" coherent memory at 0x%p\n",
+				coherent_mem_size, (void *)coherent_mem_addr);
+			error = -EBUSY;
+			goto err_decl_coherent;
+		}
+	}
+
+	irq = irq_of_parse_and_map(dn, 0);
+	if (irq == NO_IRQ) {
+		printk(KERN_ERR __FILE__ ": irq_of_parse_and_map failed\n");
+		error = -EBUSY;
+		goto err_irq;
+	}
+
+	hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
+	if (!hcd->regs) {
+		printk(KERN_ERR __FILE__ ": ioremap failed\n");
+		error = -EBUSY;
+		goto err_ioremap;
+	}
+
+	ehci = hcd_to_ehci(hcd);
+	ehci->caps = hcd->regs;
+	ehci->regs = hcd->regs +
+			HC_LENGTH(ehci_readl(ehci, &ehci->caps->hc_capbase));
+
+	/* cache this readonly data; minimize chip reads */
+	ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params);
+
+	error = usb_add_hcd(hcd, irq, 0);
+	if (error == 0)
+		return 0;
+
+	iounmap(hcd->regs);
+err_ioremap:
+	irq_dispose_mapping(irq);
+err_irq:
+	dma_release_declared_memory(&op->dev);
+err_decl_coherent:
+	usb_put_hcd(hcd);
+out:
+	return error;
+}
+
+
+static int ehci_hcd_hlwd_remove(struct of_device *op)
+{
+	struct usb_hcd *hcd = dev_get_drvdata(&op->dev);
+
+	dev_set_drvdata(&op->dev, NULL);
+
+	dev_dbg(&op->dev, "stopping " DRV_MODULE_NAME " USB Controller\n");
+
+	usb_remove_hcd(hcd);
+	iounmap(hcd->regs);
+	irq_dispose_mapping(hcd->irq);
+	dma_release_declared_memory(&op->dev);
+	usb_put_hcd(hcd);
+
+	return 0;
+}
+
+
+static int ehci_hcd_hlwd_shutdown(struct of_device *op)
+{
+	struct usb_hcd *hcd = dev_get_drvdata(&op->dev);
+
+	if (hcd->driver->shutdown)
+		hcd->driver->shutdown(hcd);
+
+	return 0;
+}
+
+
+static struct of_device_id ehci_hcd_hlwd_match[] = {
+	{
+		.compatible = "nintendo,hollywood-ehci",
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, ehci_hcd_hlwd_match);
+
+static struct of_platform_driver ehci_hcd_hlwd_driver = {
+	.name		= DRV_MODULE_NAME,
+	.match_table	= ehci_hcd_hlwd_match,
+	.probe		= ehci_hcd_hlwd_probe,
+	.remove		= ehci_hcd_hlwd_remove,
+	.shutdown	= ehci_hcd_hlwd_shutdown,
+	.driver		= {
+		.name	= DRV_MODULE_NAME,
+		.owner	= THIS_MODULE,
+	},
+};
+
diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
index 556c0b4..207e91c 100644
--- a/drivers/usb/host/ehci.h
+++ b/drivers/usb/host/ehci.h
@@ -599,6 +599,24 @@ ehci_port_speed(struct ehci_hcd *ehci, unsigned int portsc)
 #define ehci_big_endian_mmio(e)		0
 #endif
 
+#ifdef CONFIG_USB_EHCI_HCD_HLWD
+
+#include <asm/starlet-mini.h>
+
+static inline unsigned int ehci_readl(const struct ehci_hcd *ehci,
+				      __u32 __iomem *regs)
+{
+	return in_be32(regs);
+}
+
+static inline void ehci_writel(const struct ehci_hcd *ehci,
+			       const unsigned int val, __u32 __iomem *regs)
+{
+	out_be32(regs, val);
+}
+
+#else
+
 /*
  * Big-endian read/write functions are arch-specific.
  * Other arches can be added if/when they're needed.
@@ -632,6 +650,8 @@ static inline void ehci_writel(const struct ehci_hcd *ehci,
 #endif
 }
 
+#endif /* CONFIG_USB_EHCI_HCD_HLWD */
+
 /*
  * On certain ppc-44x SoC there is a HW issue, that could only worked around with
  * explicit suspend/operate of OHCI. This function hereby makes sense only on that arch.
diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c
index 24eb747..d0c854e 100644
--- a/drivers/usb/host/ohci-hcd.c
+++ b/drivers/usb/host/ohci-hcd.c
@@ -1065,6 +1065,11 @@ MODULE_LICENSE ("GPL");
 #define OF_PLATFORM_DRIVER	ohci_hcd_ppc_of_driver
 #endif
 
+#ifdef CONFIG_USB_OHCI_HCD_HLWD
+#include "ohci-hlwd.c"
+#define OF_PLATFORM_DRIVER	ohci_hcd_hlwd_driver
+#endif
+
 #ifdef CONFIG_PPC_PS3
 #include "ohci-ps3.c"
 #define PS3_SYSTEM_BUS_DRIVER	ps3_ohci_driver
diff --git a/drivers/usb/host/ohci-hlwd.c b/drivers/usb/host/ohci-hlwd.c
new file mode 100644
index 0000000..513666e
--- /dev/null
+++ b/drivers/usb/host/ohci-hlwd.c
@@ -0,0 +1,356 @@
+/*
+ * drivers/usb/host/ohci-hlwd.c
+ *
+ * Nintendo Wii (Hollywood) USB Open Host Controller Interface.
+ * Copyright (C) 2009 The GameCube Linux Team
+ * Copyright (C) 2009 Albert Herranz
+ *
+ * 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.
+ *
+ * Based on ohci-ppc-of.c
+ *
+ * OHCI HCD (Host Controller Driver) for USB.
+ *
+ * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at>
+ * (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net>
+ * (C) Copyright 2002 Hewlett-Packard Company
+ * (C) Copyright 2006 Sylvain Munaut <tnt@246tNt.com>
+ *
+ * Bus glue for OHCI HC on the of_platform bus
+ *
+ * Modified for of_platform bus from ohci-sa1111.c
+ *
+ * This file is licenced under the GPL.
+ */
+
+#include <linux/signal.h>
+#include <linux/of_platform.h>
+
+#include <asm/prom.h>
+#include <asm/starlet.h>
+#include <asm/time.h>	/* for get_tbl() */
+
+#define DRV_MODULE_NAME "ohci-hlwd"
+#define DRV_DESCRIPTION "Nintendo Wii OHCI Host Controller"
+#define DRV_AUTHOR      "Albert Herranz"
+
+#define HLWD_EHCI_CTL 0x0d0400cc	/* vendor control register */
+#define HLWD_EHCI_CTL_OH0INTE	(1<<11)	/* oh0 interrupt enable */
+#define HLWD_EHCI_CTL_OH1INTE	(1<<12)	/* oh1 interrupt enable */
+
+#define __spin_event_timeout(condition, timeout_usecs, result, __end_tbl) \
+        for (__end_tbl = get_tbl() + tb_ticks_per_usec * timeout_usecs; \
+             !(result = (condition)) && (int)(__end_tbl - get_tbl()) > 0;)
+
+
+static DEFINE_SPINLOCK(control_quirk_lock);
+
+void ohci_hlwd_control_quirk(struct ohci_hcd *ohci)
+{
+	static struct ed *ed; /* empty ED */
+	struct td *td; /* dummy TD */
+	__hc32 head;
+	__hc32 current;
+	unsigned long ctx;
+	int result;
+	unsigned long flags;
+
+	/*
+	 * One time only.
+	 * Allocate and keep a special empty ED with just a dummy TD.
+	 */
+	if (!ed) {
+		ed = ed_alloc(ohci, GFP_ATOMIC);
+		if (!ed)
+			return;
+
+		td = td_alloc(ohci, GFP_ATOMIC);
+		if (!td) {
+			ed_free(ohci, ed);
+			ed = NULL;
+			return;
+		}
+
+		ed->hwNextED = 0;
+		ed->hwTailP = ed->hwHeadP = cpu_to_hc32(ohci,
+							td->td_dma & ED_MASK);
+		ed->hwINFO |= cpu_to_hc32(ohci, ED_OUT);
+		wmb();
+	}
+
+	spin_lock_irqsave(&control_quirk_lock, flags);
+
+	/*
+	 * The OHCI USB host controllers on the Nintendo Wii
+	 * video game console stop working when new TDs are
+	 * added to a scheduled control ED after a transfer has
+	 * has taken place on it.
+	 *
+	 * Before scheduling any new control TD, we make the
+	 * controller happy by always loading a special control ED
+	 * with a single dummy TD and letting the controller attempt
+	 * the transfer.
+	 * The controller won't do anything with it, as the special
+	 * ED has no TDs, but it will keep the controller from failing
+	 * on the next transfer.
+	 */
+	head = ohci_readl(ohci, &ohci->regs->ed_controlhead);
+	if (head) {
+		/*
+		 * Load the special empty ED and tell the controller to
+		 * process the control list.
+		 */
+		ohci_writel(ohci, ed->dma, &ohci->regs->ed_controlhead);
+		ohci_writel (ohci, ohci->hc_control | OHCI_CTRL_CLE,
+			     &ohci->regs->control);
+		ohci_writel (ohci, OHCI_CLF, &ohci->regs->cmdstatus);
+
+		/* spin until the controller is done with the control list  */
+		current = ohci_readl(ohci, &ohci->regs->ed_controlcurrent);
+		__spin_event_timeout(!current, 10 /* usecs */, result, ctx) {
+			cpu_relax();
+			current = ohci_readl(ohci,
+					     &ohci->regs->ed_controlcurrent);
+		}
+
+		/* restore the old control head and control settings */
+		ohci_writel (ohci, ohci->hc_control, &ohci->regs->control);
+		ohci_writel(ohci, head, &ohci->regs->ed_controlhead);
+	}
+
+	spin_unlock_irqrestore(&control_quirk_lock, flags);
+}
+
+void ohci_hlwd_bulk_quirk(struct ohci_hcd *ohci)
+{
+	/*
+	 * There seem to be issues too with the bulk list processing on the
+	 * OHCI controller found in the Nintendo Wii video game console.
+	 * The exact problem remains still unidentified, but adding a small
+	 * delay seems to workaround it.
+	 *
+	 * As an example, without this quirk the wiimote controller stops
+	 * responding after a few seconds because one of its bulk endpoint
+	 * descriptors gets stuck.
+	 */
+	udelay(250);
+}
+
+static int __devinit ohci_hlwd_start(struct usb_hcd *hcd)
+{
+	struct ohci_hcd	*ohci = hcd_to_ohci(hcd);
+	void __iomem *ehci_ctl;
+	int error;
+
+	error = ohci_init(ohci);
+	if (error)
+		goto out;
+
+	ehci_ctl = ioremap(HLWD_EHCI_CTL, 4);
+	if (!ehci_ctl) {
+		printk(KERN_ERR __FILE__ ": ioremap failed\n");
+		error = -EBUSY;
+		ohci_stop(hcd);
+		goto out;
+	}
+
+	/* enable notification of OHCI interrupts */
+	out_be32(ehci_ctl, in_be32(ehci_ctl) |
+		 0xe0000 | HLWD_EHCI_CTL_OH0INTE | HLWD_EHCI_CTL_OH1INTE);
+	iounmap(ehci_ctl);
+
+	error = ohci_run(ohci);
+	if (error) {
+		err("can't start %s", ohci_to_hcd(ohci)->self.bus_name);
+		ohci_stop(hcd);
+		goto out;
+	}
+
+out:
+	return error;
+}
+
+static const struct hc_driver ohci_hlwd_hc_driver = {
+	.description =		hcd_name,
+	.product_desc =		"Nintendo Wii OHCI Host Controller",
+	.hcd_priv_size =	sizeof(struct ohci_hcd),
+
+	/*
+	 * generic hardware linkage
+	 */
+	.irq =			ohci_irq,
+	.flags =		HCD_USB11 | HCD_BOUNCE_DMA_MEM,
+
+	/*
+	 * basic lifecycle operations
+	 */
+	.start =		ohci_hlwd_start,
+	.stop =			ohci_stop,
+	.shutdown = 		ohci_shutdown,
+
+	/*
+	 * managing i/o requests and associated device resources
+	 */
+	.urb_enqueue =		ohci_urb_enqueue,
+	.urb_dequeue =		ohci_urb_dequeue,
+	.endpoint_disable =	ohci_endpoint_disable,
+
+	/*
+	 * scheduling support
+	 */
+	.get_frame_number =	ohci_get_frame,
+
+	/*
+	 * root hub support
+	 */
+	.hub_status_data =	ohci_hub_status_data,
+	.hub_control =		ohci_hub_control,
+#ifdef	CONFIG_PM
+	.bus_suspend =		ohci_bus_suspend,
+	.bus_resume =		ohci_bus_resume,
+#endif
+	.start_port_reset =	ohci_start_port_reset,
+};
+
+
+static int __devinit
+ohci_hcd_hlwd_probe(struct of_device *op, const struct of_device_id *match)
+{
+	struct device_node *dn = op->node;
+	struct usb_hcd *hcd;
+	struct ohci_hcd	*ohci = NULL;
+	struct resource res;
+	dma_addr_t coherent_mem_addr;
+	size_t coherent_mem_size;
+	int irq;
+	int error = -ENODEV;
+
+	if (usb_disabled())
+		goto out;
+
+	if (starlet_get_ipc_flavour() != STARLET_IPC_MINI)
+		goto out;
+
+	dev_dbg(&op->dev, "initializing " DRV_MODULE_NAME " USB Controller\n");
+
+	error = of_address_to_resource(dn, 0, &res);
+	if (error)
+		goto out;
+
+	hcd = usb_create_hcd(&ohci_hlwd_hc_driver, &op->dev, DRV_MODULE_NAME);
+	if (!hcd) {
+		error = -ENOMEM;
+		goto out;
+	}
+
+	hcd->rsrc_start = res.start;
+	hcd->rsrc_len = resource_size(&res);
+
+	error = of_address_to_resource(dn, 1, &res);
+	if (error) {
+		/* satisfy coherent memory allocations from mem1 or mem2 */
+		dev_warn(&op->dev, "using normal memory\n");
+	} else {
+		coherent_mem_addr = res.start;
+		coherent_mem_size = res.end - res.start + 1;
+		if (!dma_declare_coherent_memory(&op->dev, coherent_mem_addr,
+						 coherent_mem_addr,
+						 coherent_mem_size,
+						 DMA_MEMORY_MAP |
+						 DMA_MEMORY_EXCLUSIVE)) {
+			dev_err(&op->dev, "error declaring %u bytes of"
+				" coherent memory at 0x%p\n",
+				coherent_mem_size, (void *)coherent_mem_addr);
+			error = -EBUSY;
+			goto err_decl_coherent;
+		}
+	}
+
+	irq = irq_of_parse_and_map(dn, 0);
+	if (irq == NO_IRQ) {
+		printk(KERN_ERR __FILE__ ": irq_of_parse_and_map failed\n");
+		error = -EBUSY;
+		goto err_irq;
+	}
+
+	hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
+	if (!hcd->regs) {
+		printk(KERN_ERR __FILE__ ": ioremap failed\n");
+		error = -EBUSY;
+		goto err_ioremap;
+	}
+
+	ohci = hcd_to_ohci(hcd);
+	ohci->flags |= OHCI_QUIRK_WII;
+
+	ohci_hcd_init(ohci);
+
+	error = usb_add_hcd(hcd, irq, IRQF_DISABLED);
+	if (error)
+		goto err_add_hcd;
+
+	return 0;
+
+err_add_hcd:
+	iounmap(hcd->regs);
+err_ioremap:
+	irq_dispose_mapping(irq);
+err_irq:
+	dma_release_declared_memory(&op->dev);
+err_decl_coherent:
+	usb_put_hcd(hcd);
+out:
+	return error;
+}
+
+static int ohci_hcd_hlwd_remove(struct of_device *op)
+{
+	struct usb_hcd *hcd = dev_get_drvdata(&op->dev);
+
+	dev_set_drvdata(&op->dev, NULL);
+
+	dev_dbg(&op->dev, "stopping " DRV_MODULE_NAME " USB Controller\n");
+
+	usb_remove_hcd(hcd);
+	iounmap(hcd->regs);
+	irq_dispose_mapping(hcd->irq);
+	dma_release_declared_memory(&op->dev);
+	usb_put_hcd(hcd);
+
+	return 0;
+}
+
+static int ohci_hcd_hlwd_shutdown(struct of_device *op)
+{
+	struct usb_hcd *hcd = dev_get_drvdata(&op->dev);
+
+	if (hcd->driver->shutdown)
+		hcd->driver->shutdown(hcd);
+
+	return 0;
+}
+
+
+static struct of_device_id ohci_hcd_hlwd_match[] = {
+	{
+		.compatible = "nintendo,hollywood-ohci",
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, ohci_hcd_hlwd_match);
+
+static struct of_platform_driver ohci_hcd_hlwd_driver = {
+	.name		= DRV_MODULE_NAME,
+	.match_table	= ohci_hcd_hlwd_match,
+	.probe		= ohci_hcd_hlwd_probe,
+	.remove		= ohci_hcd_hlwd_remove,
+	.shutdown 	= ohci_hcd_hlwd_shutdown,
+	.driver		= {
+		.name	= DRV_MODULE_NAME,
+		.owner	= THIS_MODULE,
+	},
+};
+
diff --git a/drivers/usb/host/ohci-q.c b/drivers/usb/host/ohci-q.c
index 35288bc..0e0e267 100644
--- a/drivers/usb/host/ohci-q.c
+++ b/drivers/usb/host/ohci-q.c
@@ -618,6 +618,9 @@ static void td_submit_urb (
 			&& ohci_to_hcd(ohci)->self.bandwidth_isoc_reqs == 0;
 		/* FALLTHROUGH */
 	case PIPE_BULK:
+		if (ohci->flags & OHCI_QUIRK_WII)
+			ohci_hlwd_bulk_quirk(ohci);
+
 		info = is_out
 			? TD_T_TOGGLE | TD_CC | TD_DP_OUT
 			: TD_T_TOGGLE | TD_CC | TD_DP_IN;
@@ -649,6 +652,9 @@ static void td_submit_urb (
 	 * any DATA phase works normally, and the STATUS ack is special.
 	 */
 	case PIPE_CONTROL:
+		if (ohci->flags & OHCI_QUIRK_WII)
+			ohci_hlwd_control_quirk(ohci);
+
 		info = TD_CC | TD_DP_SETUP | TD_T_DATA0;
 		td_fill (ohci, info, urb->setup_dma, 8, urb, cnt++);
 		if (data_len > 0) {
diff --git a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h
index 5bf15fe..57e15dc 100644
--- a/drivers/usb/host/ohci.h
+++ b/drivers/usb/host/ohci.h
@@ -16,6 +16,28 @@ typedef __u32 __bitwise __hc32;
 typedef __u16 __bitwise __hc16;
 
 /*
+ * Some platforms have weird constraints when accessing memory.
+ *
+ * For example, the Nintendo Wii video game console is unable to perform
+ * non-32 bit writes to non-cached memory for its second block of 64MB of RAM.
+ * As this platform also requires CONFIG_NOT_COHERENT_CACHE, all memory
+ * allocated using the dma memory allocation functions can only be written
+ * using 32-bit accesses.
+ *
+ * Because of this constraint, as a workaround, we make sure that all
+ * fields in struct ed and td (which are allocated from dma pools) are
+ * always 32 bit fields.
+ * Note that the remaining structs allocated from dma-able memory are already
+ * 32 bit fields.
+ */
+#ifdef CONFIG_USB_OHCI_HCD_HLWD
+#define ohci_fld(type)  u32
+#else
+#define ohci_fld(type)  type
+#endif
+
+
+/*
  * OHCI Endpoint Descriptor (ED) ... holds TD queue
  * See OHCI spec, section 4.2
  *
@@ -51,21 +73,21 @@ struct ed {
 	/* create --> IDLE --> OPER --> ... --> IDLE --> destroy
 	 * usually:  OPER --> UNLINK --> (IDLE | OPER) --> ...
 	 */
-	u8			state;		/* ED_{IDLE,UNLINK,OPER} */
+	ohci_fld(u8)		state;		/* ED_{IDLE,UNLINK,OPER} */
 #define ED_IDLE		0x00		/* NOT linked to HC */
 #define ED_UNLINK	0x01		/* being unlinked from hc */
 #define ED_OPER		0x02		/* IS linked to hc */
 
-	u8			type;		/* PIPE_{BULK,...} */
+	ohci_fld(u8)		type;		/* PIPE_{BULK,...} */
 
 	/* periodic scheduling params (for intr and iso) */
-	u8			branch;
-	u16			interval;
-	u16			load;
-	u16			last_iso;	/* iso only */
+	ohci_fld(u8)		branch;
+	ohci_fld(u16)		interval;
+	ohci_fld(u16)		load;
+	ohci_fld(u16)		last_iso;	/* iso only */
 
 	/* HC may see EDs on rm_list until next frame (frame_no == tick) */
-	u16			tick;
+	ohci_fld(u16)		tick;
 } __attribute__ ((aligned(16)));
 
 #define ED_MASK	((u32)~0x0f)		/* strip hw status in low addr bits */
@@ -118,7 +140,7 @@ struct td {
 	__hc16		hwPSW [MAXPSW];
 
 	/* rest are purely for the driver's use */
-	__u8		index;
+	ohci_fld(__u8)	index;
 	struct ed	*ed;
 	struct td	*td_hash;	/* dma-->td hashtable */
 	struct td	*next_dl_td;
@@ -403,6 +425,7 @@ struct ohci_hcd {
 #define	OHCI_QUIRK_HUB_POWER	0x100			/* distrust firmware power/oc setup */
 #define	OHCI_QUIRK_AMD_ISO	0x200			/* ISO transfers*/
 #define	OHCI_QUIRK_AMD_PREFETCH	0x400			/* pre-fetch for ISO transfer */
+#define	OHCI_QUIRK_WII		0x800			/* Hollywood chipset */
 	// there are also chip quirks/bugs in init logic
 
 	struct work_struct	nec_work;	/* Worker for NEC quirk */
@@ -545,6 +568,37 @@ static inline struct usb_hcd *ohci_to_hcd (const struct ohci_hcd *ohci)
 #define big_endian_mmio(ohci)	0		/* only little endian */
 #endif
 
+#ifdef CONFIG_USB_OHCI_HCD_HLWD
+
+#include <asm/starlet-mini.h>
+
+static inline unsigned int _ohci_readl(const struct ohci_hcd *ohci,
+				       __hc32 __iomem *regs)
+{
+	return in_be32(regs);
+}
+
+static inline void _ohci_writel(const struct ohci_hcd *ohci,
+				const unsigned int val, __hc32 __iomem *regs)
+{
+	out_be32(regs, val);
+}
+
+extern void ohci_hlwd_control_quirk(struct ohci_hcd *ohci);
+extern void ohci_hlwd_bulk_quirk(struct ohci_hcd *ohci);
+
+#else
+
+static inline void ohci_hlwd_control_quirk(struct ohci_hcd *ohci)
+{
+	return;
+}
+
+static inline void ohci_hlwd_bulk_quirk(struct ohci_hcd *ohci)
+{
+	return;
+}
+
 /*
  * Big-endian read/write functions are arch-specific.
  * Other arches can be added if/when they're needed.
@@ -574,6 +628,8 @@ static inline void _ohci_writel (const struct ohci_hcd *ohci,
 #endif
 }
 
+#endif /* CONFIG_USB_OHCI_HCD_HLWD */
+
 #ifdef CONFIG_ARCH_LH7A404
 /* Marc Singer: at the time this code was written, the LH7A404
  * had a problem reading the USB host registers.  This
diff --git a/drivers/usb/host/rvl-sthcd.c b/drivers/usb/host/rvl-sthcd.c
new file mode 100644
index 0000000..7d53e70
--- /dev/null
+++ b/drivers/usb/host/rvl-sthcd.c
@@ -0,0 +1,2377 @@
+/*
+ * drivers/usb/host/rvl-sthcd.c
+ *
+ * USB Host Controller driver for the Nintendo Wii
+ * Copyright (C) 2008-2009 The GameCube Linux Team
+ * Copyright (C) 2008 Maarten ter Huurne
+ * Copyright (C) 2008,2009 Albert Herranz
+ *
+ * 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.
+ *
+ */
+
+/*
+ *
+ * TODO
+ * - cleanup debuging mess
+ *
+ */
+
+#ifdef CONFIG_HIGHMEM
+#error Sorry, this driver cannot currently work if HIGHMEM is y
+#endif
+
+#define DBG(fmt, arg...)	drv_printk(KERN_DEBUG, fmt, ##arg)
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/scatterlist.h>
+#include <linux/usb.h>
+#include <asm/starlet.h>
+#include <asm/starlet-ios.h>
+
+#include "../core/hcd.h"
+#include "../core/hub.h"
+
+#define DRV_MODULE_NAME "rvl-sthcd"
+#define DRV_DESCRIPTION "USB Host Controller driver for the Nintendo Wii"
+#define DRV_AUTHOR      "Maarten ter Huurne, " \
+			"Albert Herranz"
+
+static char sthcd_driver_version[] = "0.5i";
+
+#define drv_printk(level, format, arg...) \
+	printk(level DRV_MODULE_NAME ": " format , ## arg)
+
+
+/* TODO: Use enum instead? */
+#define STHCD_IOCTLV_CONTROLREQ		0
+#define STHCD_IOCTLV_BULKREQ		1
+#define STHCD_IOCTLV_INTRREQ		2
+#define STHCD_IOCTL_SUSPENDDEVICE	5
+#define STHCD_IOCTL_RESUMEDEVICE	6
+#define STHCD_IOCTLV_GETDEVICELIST	12
+#define STHCD_IOCTL_DEVICEREMOVALNOTIFY	26
+#define STHCD_IOCTLV_DEVICEINSERTNOTIFY	27
+
+/*
+ * The Nintendo Wii has only 2 external USB ports (plus 1 internal USB port),
+ * but the starlet API provides access to USB devices in a port independent
+ * way. This is true also for USB devices attached to external hubs.
+ *
+ * Our HCD model currently maps one starlet USB device to one HCD port, thus
+ * we need additional ports here.
+ */
+#define STHCD_MAX_DEVIDS	15
+#define STHCD_MAX_PORTS		STHCD_MAX_DEVIDS
+
+/*
+ * We get error -7008 after performing large transfers.
+ * Using this arbitrary limit makes things work.
+ */
+#define STHCD_MAX_CHUNK_SIZE	(2048)
+
+#define STHCD_PORT_MAX_RESETS	2	/* maximum number of consecutive
+					 * resets allowed for a port */
+#define STHCD_RESCAN_INTERVAL	5	/* seconds */
+
+#define starlet_ioh_sg_entry(sg, ptr) \
+	starlet_ioh_sg_set_buf((sg), (ptr), sizeof(*(ptr)))
+
+
+struct sthcd_hcd;
+struct sthcd_port;
+struct sthcd_oh;
+
+/*
+ * starlet USB device abstraction (udev).
+ *
+ */
+struct sthcd_udev {
+	u16 idVendor;
+	u16 idProduct;
+	int fd;				/* starlet file descriptor */
+
+	u16 devnum;			/* USB address set by kernel */
+
+	struct list_head node;		/* in list of connected devices */
+	struct sthcd_oh *oh;		/* parent Open Host controller */
+
+	struct list_head pep_list;	/* list of private endpoints */
+};
+
+/*
+ * starlet USB device identifier.
+ *
+ */
+struct sthcd_devid {
+	u32 _unk1;
+	u16 idVendor;
+	u16 idProduct;
+};
+
+enum {
+	__STHCD_PORT_INUSE = 0,
+	__STHCD_PORT_DOOMED,
+};
+
+
+/*
+ * "Virtual" HCD USB port.
+ *
+ */
+struct sthcd_port {
+	unsigned long flags;
+#define STHCD_PORT_INUSE	(1 << __STHCD_PORT_INUSE)
+#define STHCD_PORT_DOOMED	(1 << __STHCD_PORT_DOOMED)
+
+	u32 status_change;
+	unsigned nr_resets;
+
+	struct sthcd_udev udev;	/* one udev per port */
+};
+
+/*
+ * starlet Open Host controller abstraction (oh).
+ *
+ */
+struct sthcd_oh {
+	unsigned int index;
+	int fd;					/* starlet file descriptor */
+
+	unsigned int max_devids;
+	struct sthcd_devid *new_devids;
+	struct sthcd_devid *devids;
+	unsigned int nr_devids;			/* actual no of devices */
+
+	struct sthcd_hcd *hcd;			/* parent Host Controller */
+};
+
+/*
+ * Host Controller (hcd).
+ *
+ */
+struct sthcd_hcd {
+	spinlock_t lock;
+
+	struct sthcd_oh oh[2];
+
+	struct sthcd_port *ports;	/* array of ports */
+	unsigned int nr_ports;
+
+	struct list_head device_list;	/* list of connected devices */
+
+	wait_queue_head_t rescan_waitq;	/* wait queue for the rescan task */
+	struct task_struct *rescan_task;
+};
+
+
+/*
+ * Private endpoint (pep).
+ *
+ * A pep takes care of the transfers for an endpoint.
+ */
+
+struct sthcd_ctrl_params_in {
+	struct usb_ctrlrequest req;
+	u8 _unk1; /* timeout? */
+};
+struct sthcd_ctrl_xfer_ctx {
+	struct starlet_ioh_sg in[6];
+	struct sthcd_ctrl_params_in *params_in;
+};
+
+struct sthcd_bulk_intr_params_in {
+	u8 bEndpointAddress;
+	u16 wLength;
+};
+struct sthcd_bulk_intr_xfer_ctx {
+	struct starlet_ioh_sg in[2];
+	struct sthcd_bulk_intr_params_in *params_in;
+};
+
+enum {
+	__STHCD_PEP_DISABLED = 0,
+	__STHCD_PEP_XFERBUSY,		/* pep is actively xferring data */
+};
+
+struct sthcd_pep {
+	unsigned long flags;
+#define STHCD_PEP_DISABLED	(1 << __STHCD_PEP_DISABLED)
+#define STHCD_PEP_XFERBUSY	(1 << __STHCD_PEP_XFERBUSY)
+
+	unsigned long outstanding;
+
+	struct usb_host_endpoint *ep;	/* associated endpoint */
+	struct sthcd_hcd *sthcd;	/* associated hcd */
+
+	/* local copy of endpoint descriptor bmAttributes */
+	__u8	bmAttributes;
+
+	/* xfer context data */
+
+	struct urb *urb;		/* urb being transferred */
+
+	struct sthcd_udev *udev;	/* udev for this urb */
+	struct list_head node;		/* in list of peps for this udev */
+
+	size_t io_xfer_offset;		/* number of bytes transferred */
+	void *io_buf;			/* data buffer */
+	size_t io_buf_len;		/* length of io_buf */
+
+	int request;			/* ioctlv request */
+	union {
+		struct sthcd_bulk_intr_xfer_ctx *bulk_intr;
+		struct sthcd_ctrl_xfer_ctx *ctrl;
+	} ctx;				/* transfer context */
+
+	unsigned int nents_in;		/* number of input sg entries */
+	struct starlet_ioh_sg *in;	/* input sg list */
+	struct starlet_ioh_sg io[1];	/* input/output sg list */
+};
+
+
+/*
+ * Debugging facilities.
+ *
+ */
+
+#if 0
+static inline void print_buffer(void *buf, u32 size)
+{
+	int i;
+	for (i = 0; i < (size + 3) / 4; i += 4) {
+		u32 *data = &((u32 *)buf)[i];
+		printk(KERN_INFO "  %08X %08X %08X %08X\n",
+			data[0], data[1], data[2], data[3]
+			);
+	}
+}
+#endif
+
+/*
+ * Type conversion routines.
+ *
+ */
+
+static inline struct sthcd_hcd *hcd_to_sthcd(struct usb_hcd *hcd)
+{
+	return (struct sthcd_hcd *)(hcd->hcd_priv);
+}
+
+static inline struct usb_hcd *sthcd_to_hcd(struct sthcd_hcd *sthcd)
+{
+	return container_of((void *)sthcd, struct usb_hcd, hcd_priv);
+}
+
+static inline struct sthcd_port *udev_to_port(struct sthcd_udev *_udev)
+{
+	return container_of(_udev, struct sthcd_port, udev);
+}
+
+
+/*
+ * Private End Point abstraction.
+ *
+ */
+
+static inline struct sthcd_pep *ep_to_pep(struct usb_host_endpoint *ep)
+{
+	return ep->hcpriv;
+}
+
+static inline int pep_is_enabled(struct sthcd_pep *pep)
+{
+	return !test_bit(__STHCD_PEP_DISABLED, &pep->flags);
+}
+
+static int sthcd_pep_alloc_ctrl_xfer_ctx(struct sthcd_pep *pep)
+{
+	struct sthcd_ctrl_xfer_ctx *ctx;
+	struct sthcd_ctrl_params_in *params_in;
+	int error;
+
+	ctx = starlet_kzalloc(sizeof(*ctx), GFP_ATOMIC);
+	if (!ctx) {
+		error = -ENOMEM;
+		goto done;
+	}
+
+	params_in = starlet_ioh_kzalloc(sizeof(*params_in));
+	if (!params_in) {
+		starlet_kfree(ctx);
+		error = -ENOMEM;
+		goto done;
+	}
+
+	ctx->params_in = params_in;
+
+	starlet_ioh_sg_init_table(ctx->in, 6);
+	starlet_ioh_sg_entry(&ctx->in[0], &params_in->req.bRequestType);
+	starlet_ioh_sg_entry(&ctx->in[1], &params_in->req.bRequest);
+	starlet_ioh_sg_entry(&ctx->in[2], &params_in->req.wValue);
+	starlet_ioh_sg_entry(&ctx->in[3], &params_in->req.wIndex);
+	starlet_ioh_sg_entry(&ctx->in[4], &params_in->req.wLength);
+	starlet_ioh_sg_entry(&ctx->in[5], &params_in->_unk1);
+
+	pep->ctx.ctrl = ctx;
+
+	pep->nents_in = ARRAY_SIZE(ctx->in);
+	pep->in = ctx->in;
+
+	error = 0;
+
+done:
+	if (error < 0)
+		DBG("%s: error=%d (%x)\n", __func__, error, error);
+	return error;
+}
+
+static void sthcd_pep_free_ctrl_xfer_ctx(struct sthcd_pep *pep)
+{
+	struct sthcd_ctrl_xfer_ctx *ctx = pep->ctx.ctrl;
+
+	if (ctx) {
+		starlet_ioh_kfree(ctx->params_in);
+		starlet_kfree(ctx);
+		pep->ctx.ctrl = NULL;
+	}
+}
+
+static int sthcd_pep_alloc_bulk_intr_xfer_ctx(struct sthcd_pep *pep)
+{
+	struct sthcd_bulk_intr_xfer_ctx *ctx;
+	struct sthcd_bulk_intr_params_in *params_in;
+	int error;
+
+	ctx = starlet_kzalloc(sizeof(*ctx), GFP_ATOMIC);
+	if (!ctx) {
+		error = -ENOMEM;
+		goto done;
+	}
+
+	params_in = starlet_ioh_kzalloc(sizeof(*params_in));
+	if (!params_in) {
+		starlet_kfree(ctx);
+		error = -ENOMEM;
+		goto done;
+	}
+
+	ctx->params_in = params_in;
+
+	starlet_ioh_sg_init_table(ctx->in, 2);
+	starlet_ioh_sg_entry(&ctx->in[0], &params_in->bEndpointAddress);
+	starlet_ioh_sg_entry(&ctx->in[1], &params_in->wLength);
+
+	pep->ctx.bulk_intr = ctx;
+
+	pep->nents_in = ARRAY_SIZE(ctx->in);
+	pep->in = ctx->in;
+
+	error = 0;
+
+done:
+	if (error < 0)
+		DBG("%s: error=%d (%x)\n", __func__, error, error);
+	return error;
+}
+
+static void sthcd_pep_free_bulk_intr_xfer_ctx(struct sthcd_pep *pep)
+{
+	struct sthcd_bulk_intr_xfer_ctx *ctx = pep->ctx.bulk_intr;
+
+	if (ctx) {
+		starlet_ioh_kfree(ctx->params_in);
+		starlet_kfree(ctx);
+		pep->ctx.bulk_intr = NULL;
+	}
+}
+
+static int sthcd_pep_alloc_xfer_ctx(struct sthcd_pep *pep)
+{
+	unsigned int xfer_type = pep->bmAttributes &
+					 USB_ENDPOINT_XFERTYPE_MASK;
+	int error;
+
+	switch (xfer_type) {
+	case USB_ENDPOINT_XFER_CONTROL:
+		error = sthcd_pep_alloc_ctrl_xfer_ctx(pep);
+		break;
+	case USB_ENDPOINT_XFER_BULK:
+	case USB_ENDPOINT_XFER_INT:
+		error = sthcd_pep_alloc_bulk_intr_xfer_ctx(pep);
+		break;
+	default:
+		error = -ENXIO;
+		break;
+	}
+
+	if (error < 0)
+		DBG("%s: error=%d (%x)\n", __func__, error, error);
+	return error;
+}
+
+static void sthcd_pep_free_xfer_ctx(struct sthcd_pep *pep)
+{
+	unsigned int xfer_type = pep->bmAttributes &
+					 USB_ENDPOINT_XFERTYPE_MASK;
+
+	switch (xfer_type) {
+	case USB_ENDPOINT_XFER_CONTROL:
+		sthcd_pep_free_ctrl_xfer_ctx(pep);
+		break;
+	case USB_ENDPOINT_XFER_BULK:
+	case USB_ENDPOINT_XFER_INT:
+		sthcd_pep_free_bulk_intr_xfer_ctx(pep);
+		break;
+	default:
+		DBG("%s: invalid endpoint xfer type %u\n", __func__,
+			xfer_type);
+		break;
+	}
+}
+
+static int sthcd_pep_alloc_xfer_io_buf(struct sthcd_pep *pep, size_t size)
+{
+	/* REVISIT, size must be greater than 0 */
+	size_t io_buf_size = size + 32;
+	int error;
+
+	pep->io_buf = starlet_ioh_kzalloc(io_buf_size);
+	if (!pep->io_buf) {
+		error = -ENOMEM;
+		goto done;
+	}
+
+	starlet_ioh_sg_init_table(pep->io, 1);
+	starlet_ioh_sg_set_buf(&pep->io[0], pep->io_buf, size);
+
+	error = 0;
+
+done:
+	if (error < 0)
+		DBG("%s: error=%d (%x)\n", __func__, error, error);
+	return error;
+}
+
+static void sthcd_pep_free_xfer_io_buf(struct sthcd_pep *pep)
+{
+	if (pep->io_buf) {
+		starlet_ioh_sg_set_buf(&pep->io[0], NULL, 0);
+		starlet_ioh_kfree(pep->io_buf);
+		pep->io_buf = NULL;
+	}
+}
+
+/*
+ *
+ * Context: interrupts disabled, hcd lock held
+ */
+static int sthcd_pep_init(struct sthcd_pep *pep, struct sthcd_hcd *sthcd,
+			  struct usb_host_endpoint *ep)
+{
+	int error;
+
+	BUG_ON(!ep);
+
+	pep->sthcd = sthcd;
+	pep->ep = ep;
+	pep->bmAttributes = ep->desc.bmAttributes;
+
+	error = sthcd_pep_alloc_xfer_ctx(pep);
+	if (error)
+		goto done;
+
+done:
+	if (error < 0)
+		DBG("%s: error=%d (%x)\n", __func__, error, error);
+	return error;
+}
+
+/*
+ *
+ * Context: interrupts disabled, hcd lock held
+ */
+static void sthcd_pep_exit(struct sthcd_pep *pep)
+{
+	BUG_ON(pep->urb);
+	BUG_ON(!pep->ep);
+
+	sthcd_pep_free_xfer_ctx(pep);
+
+	pep->ep = NULL;
+	pep->sthcd = NULL;
+}
+
+/*
+ *
+ * Context: interrupts disabled, hcd lock held
+ */
+static struct sthcd_pep *sthcd_pep_alloc(struct sthcd_hcd *sthcd,
+					 struct usb_host_endpoint *ep)
+{
+	struct sthcd_pep *pep;
+	int error;
+
+	pep = kzalloc(sizeof(*pep), GFP_ATOMIC);
+	if (!pep)
+		return NULL;
+
+	error = sthcd_pep_init(pep, sthcd, ep);
+	if (error) {
+		kfree(pep);
+		return NULL;
+	}
+
+	return pep;
+}
+
+/*
+ *
+ * Context: interrupts disabled, hcd lock held
+ */
+static void sthcd_pep_free(struct sthcd_pep *pep)
+{
+	sthcd_pep_exit(pep);
+	kfree(pep);
+}
+
+/*
+ *
+ * Context: interrupts disabled, hcd lock held
+ */
+static struct sthcd_udev *sthcd_find_udev_by_num(struct sthcd_hcd *sthcd,
+						 u16 devnum)
+{
+	struct sthcd_udev *udev;
+
+	list_for_each_entry(udev, &sthcd->device_list, node) {
+		if (udev->devnum == devnum)
+			return udev;
+	}
+	DBG("%s: udev %u not found\n", __func__, devnum);
+	return NULL;
+}
+
+/*
+ *
+ * Context: interrupts disabled, hcd lock held
+ */
+static int sthcd_pep_takein_urb(struct sthcd_pep *pep, struct urb *urb)
+{
+	struct sthcd_hcd *sthcd = pep->sthcd;
+	struct sthcd_udev *udev;
+	int error = 0;
+
+	if (!pep_is_enabled(pep)) {
+		error = -ESHUTDOWN;
+		goto done;
+	}
+
+	if (pep->urb) {
+		error = -EBUSY;
+		goto done;
+	}
+
+	if (unlikely(!pep->udev)) {
+		BUG_ON(!urb->dev);
+		udev = sthcd_find_udev_by_num(sthcd, urb->dev->devnum);
+		if (!udev) {
+			error = -ENODEV;
+			goto done;
+		}
+		pep->udev = udev;
+		list_add_tail(&pep->node, &udev->pep_list);
+	}
+
+	pep->urb = urb;
+done:
+	if (error < 0)
+		DBG("%s: error=%d (%x)\n", __func__, error, error);
+	return error;
+}
+
+/*
+ *
+ * Context: interrupts disabled, hcd lock held
+ */
+static void sthcd_pep_takeout_urb(struct sthcd_pep *pep)
+{
+	WARN_ON(!pep->urb);
+
+	pep->urb = NULL;
+	if (pep->udev)
+		list_del_init(&pep->node);
+	pep->udev = NULL;
+}
+
+/*
+ *
+ * Context: interrupts disabled, hcd lock held
+ */
+static void sthcd_pep_setup_ctrl_xfer(struct sthcd_pep *pep)
+{
+	struct urb *urb = pep->urb;
+	struct sthcd_ctrl_xfer_ctx *ctx = pep->ctx.ctrl;
+	struct sthcd_ctrl_params_in *params_in;
+
+	params_in = ctx->params_in;
+	memcpy(&params_in->req, urb->setup_packet, sizeof(params_in->req));
+	params_in->req.wLength = cpu_to_le16(pep->io_buf_len);
+}
+
+/*
+ *
+ * Context: interrupts disabled, hcd lock held
+ */
+static void sthcd_pep_setup_bulk_intr_xfer(struct sthcd_pep *pep)
+{
+	struct urb *urb = pep->urb;
+	struct sthcd_bulk_intr_xfer_ctx *ctx = pep->ctx.bulk_intr;
+	struct sthcd_bulk_intr_params_in *params_in;
+
+	params_in = ctx->params_in;
+	params_in->bEndpointAddress = urb->ep->desc.bEndpointAddress;
+	params_in->wLength = pep->io_buf_len;
+}
+
+/*
+ *
+ * Context: interrupts disabled, hcd lock held
+ */
+static int sthcd_pep_setup_xfer(struct sthcd_pep *pep)
+{
+	struct urb *urb = pep->urb;
+	int request;
+	int error = 0;
+
+	switch (usb_pipetype(urb->pipe)) {
+	case PIPE_CONTROL:
+		request = STHCD_IOCTLV_CONTROLREQ;
+		sthcd_pep_setup_ctrl_xfer(pep);
+		break;
+	case PIPE_INTERRUPT:
+		request = STHCD_IOCTLV_INTRREQ;
+		sthcd_pep_setup_bulk_intr_xfer(pep);
+		break;
+	case PIPE_BULK:
+		request = STHCD_IOCTLV_BULKREQ;
+		sthcd_pep_setup_bulk_intr_xfer(pep);
+		break;
+	default:
+		error = -EINVAL;
+		break;
+	}
+
+	if (!error) {
+		pep->request = request;
+		starlet_ioh_sg_set_buf(&pep->io[0],
+					pep->io_buf, pep->io_buf_len);
+	}
+
+	if (error < 0)
+		DBG("%s: error=%d (%x)\n", __func__, error, error);
+	return error;
+}
+
+/*
+ *
+ * Context: interrupts disabled, hcd lock held
+ */
+static int sthcd_pep_setup_next_xfer(struct sthcd_pep *pep)
+{
+	struct urb *urb = pep->urb;
+	int retval = 0;
+	int error;
+
+	if (pep->io_xfer_offset < urb->transfer_buffer_length) {
+		pep->io_buf_len = urb->transfer_buffer_length -
+					 pep->io_xfer_offset;
+		if (pep->io_buf_len > STHCD_MAX_CHUNK_SIZE)
+			pep->io_buf_len = STHCD_MAX_CHUNK_SIZE;
+
+		retval = pep->io_buf_len;
+
+		error = sthcd_pep_setup_xfer(pep);
+		if (error)
+			retval = error;
+	}
+
+	if (retval < 0)
+		DBG("%s: retval=%d (%x)\n", __func__, retval, retval);
+	return retval;
+}
+
+/*
+ *
+ * Context: interrupts disabled, hcd lock held
+ */
+static int sthcd_pep_setup_first_xfer(struct sthcd_pep *pep)
+{
+	struct urb *urb = pep->urb;
+	int retval;
+	int error;
+
+	pep->io_xfer_offset = 0;
+	pep->io_buf_len = urb->transfer_buffer_length;
+	if (pep->io_buf_len > STHCD_MAX_CHUNK_SIZE)
+		pep->io_buf_len = STHCD_MAX_CHUNK_SIZE;
+
+	retval = pep->io_buf_len;
+
+	error = sthcd_pep_setup_xfer(pep);
+	if (error)
+		retval = error;
+
+	if (retval < 0)
+		DBG("%s: retval=%d (%x)\n", __func__, retval, retval);
+	return retval;
+}
+
+/*
+ *
+ * Context: interrupts disabled, hcd lock held
+ */
+static void sthcd_pep_finish_xfer(struct sthcd_pep *pep, int xfer_len)
+{
+	struct urb *urb = pep->urb;
+
+	if (xfer_len <= 0)
+		goto done;
+
+	BUG_ON(!urb);
+	BUG_ON(!pep->io_buf);
+
+	/*
+	 * For IN transfers, copy the received chunk data into the urb
+	 * xfer buffer.
+	 */
+	if (usb_urb_dir_in(urb)) {
+		/* device -> host */
+		BUG_ON(!urb->transfer_buffer);
+		memcpy(urb->transfer_buffer + pep->io_xfer_offset,
+			       pep->io_buf, xfer_len);
+	}
+
+	pep->io_xfer_offset += xfer_len;
+
+done:
+	return;
+}
+
+static int sthcd_pep_xfer_callback(struct starlet_ipc_request *req);
+
+/*
+ *
+ * Context: interrupts disabled, hcd lock held
+ */
+static int sthcd_pep_start_xfer(struct sthcd_pep *pep)
+{
+	struct urb *urb = pep->urb;
+	struct sthcd_udev *udev = pep->udev;
+	int error;
+
+	BUG_ON(!urb);
+
+	/* udev was disconnected */
+	if (unlikely(!udev)) {
+		error = -ENODEV;
+		goto done;
+	}
+
+	if (!pep_is_enabled(pep)) {
+		error = -ESHUTDOWN;
+		goto done;
+	}
+
+	/* for OUT transfers, copy the data to send into the pep xfer buffer */
+	if (pep->io_buf_len > 0) {
+		if (usb_urb_dir_out(urb)) {
+			/* host -> device */
+			BUG_ON(!urb->transfer_buffer);
+			memcpy(pep->io_buf,
+			       urb->transfer_buffer + pep->io_xfer_offset,
+			       pep->io_buf_len);
+		}
+	}
+
+	starlet_ioh_sg_set_buf(&pep->io[0],
+				pep->io_buf, pep->io_buf_len);
+
+	/* start an async transfer */
+	error = starlet_ioh_ioctlv_nowait(udev->fd, pep->request,
+					  pep->nents_in, pep->in, 1, pep->io,
+					  sthcd_pep_xfer_callback, pep);
+	if (!error)
+		pep->outstanding++;
+
+done:
+	if (error < 0)
+		DBG("%s: error=%d (%x)\n", __func__, error, error);
+	return error;
+}
+
+/*
+ *
+ * Context: interrupts disabled, hcd lock held
+ */
+static int sthcd_giveback_urb(struct sthcd_hcd *sthcd,
+			      struct urb *urb, int status)
+__releases(sthcd->lock) __acquires(sthcd->lock)
+{
+	struct usb_hcd *hcd = sthcd_to_hcd(sthcd);
+
+	/*
+	 * Release the hcd lock here as the callback may need to
+	 * hold it again.
+	 */
+	spin_unlock(&sthcd->lock);
+	usb_hcd_giveback_urb(hcd, urb, status);
+	spin_lock(&sthcd->lock);
+
+	return status;
+}
+
+/*
+ *
+ * Context: interrupts disabled, hcd lock held
+ */
+struct urb *sthcd_find_next_urb_in_ep(struct usb_host_endpoint *ep)
+{
+	if (list_empty(&ep->urb_list))
+		return NULL;
+	else
+		return list_first_entry(&ep->urb_list, struct urb, urb_list);
+}
+
+static int sthcd_pep_send_urb(struct sthcd_pep *pep, struct urb *urb);
+
+/*
+ *
+ * Context: interrupts disabled, hcd lock held
+ */
+static int sthcd_pep_cond_send_next_urb(struct sthcd_pep *pep)
+{
+	struct urb *urb;
+	int retval = 0;
+	int error;
+
+	/* schedule next urb if any */
+	urb = sthcd_find_next_urb_in_ep(pep->ep);
+	if (urb) {
+		error = sthcd_pep_send_urb(pep, urb);
+		if (!error) {
+			retval = 1;
+			goto done;
+		} else {
+			retval = error;
+		}
+	}
+done:
+	if (retval < 0)
+		DBG("%s: retval=%d (%x)\n", __func__, retval, retval);
+	return retval;
+}
+
+/*
+ *
+ * Context: interrupts disabled, hcd lock held
+ */
+static int sthcd_pep_send_urb(struct sthcd_pep *pep, struct urb *urb)
+{
+	struct sthcd_port *port = NULL;
+	struct sthcd_hcd *sthcd;
+	struct usb_ctrlrequest *req;
+	u16 typeReq, wValue;
+	int retval, fake;
+	int error;
+
+	/*
+	 * Unconditionally fail urbs targetted at doomed ports.
+	 */
+	if (pep->udev) {
+		port = udev_to_port(pep->udev);
+		if (test_bit(__STHCD_PORT_DOOMED, &port->flags)) {
+			error = -ENODEV;
+			goto done;
+		}
+	}
+
+	if (test_and_set_bit(__STHCD_PEP_XFERBUSY, &pep->flags)) {
+		/*
+		 * There is a pep xfer in progress.
+		 * Our urb is already queued on the usb device, so do nothing
+		 * here and rely on the pep xfer callback to do the actual
+		 * work when it's done with the current urb in flight.
+		 */
+		error = 0;
+		goto done;
+	}
+
+	/* we can have one ongoing urb only */
+	error = sthcd_pep_takein_urb(pep, urb);
+	if (error)
+		goto done;
+
+	urb->hcpriv = urb;	/* mark urb in use */
+
+	retval = sthcd_pep_setup_first_xfer(pep);
+	if (retval < 0) {
+		error = retval;
+		goto err_setup_xfer;
+	}
+
+	fake = 0;
+	if (pep->request == STHCD_IOCTLV_CONTROLREQ) {
+		req = (struct usb_ctrlrequest *)urb->setup_packet;
+		typeReq = (req->bRequestType << 8) | req->bRequest;
+		wValue = le16_to_cpu(req->wValue);
+
+		switch (typeReq) {
+		case DeviceOutRequest | USB_REQ_SET_ADDRESS: /* 0005 */
+			if (urb->dev->devnum != 0) {
+				/* REVISIT, never reached */
+				drv_printk(KERN_WARNING,
+					   "address change %u->%u\n",
+					   urb->dev->devnum, wValue);
+			}
+			/*
+			 * We are guaranteed to have an udev because the takein
+			 * was successful.
+			 */
+			pep->udev->devnum = wValue;
+			urb->actual_length = 0;
+
+			/* clear the port reset count, we have an address */
+			if (wValue) {
+				/*
+				 * We need to retrieve the port again
+				 * as we might have entered the function
+				 * without an udev assigned to the pep.
+				 */
+				port = udev_to_port(pep->udev);
+				port->nr_resets = 0;
+			}
+			fake = 1;
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (fake) {
+		sthcd = pep->sthcd;
+		/* finish this fake urb synchronously... */
+		usb_hcd_unlink_urb_from_ep(sthcd_to_hcd(sthcd), urb);
+		sthcd_giveback_urb(sthcd, urb, 0);
+		/* ... and proceed with the next urb, if applicable */
+		sthcd_pep_cond_send_next_urb(pep);
+	} else {
+		/* allocate an io buffer for this transfer */
+		error = sthcd_pep_alloc_xfer_io_buf(pep, pep->io_buf_len);
+		if (error)
+			goto err_alloc_io_buf;
+
+		/* ... and start the first transfer */
+		error = sthcd_pep_start_xfer(pep);
+		if (error)
+			goto err_start_xfer;
+	}
+
+	return 0;
+
+err_start_xfer:
+	sthcd_pep_free_xfer_io_buf(pep);
+err_alloc_io_buf:
+err_setup_xfer:
+	sthcd_pep_takeout_urb(pep);
+	urb->hcpriv = NULL;
+done:
+	if (error < 0)
+		DBG("%s: error=%d (%x)\n", __func__, error, error);
+	return error;
+}
+
+static void sthcd_pep_print(struct sthcd_pep *pep)
+{
+	struct usb_device *udev;
+	u16 idVendor, idProduct;
+
+	idVendor = idProduct = 0xffff;
+	if (pep->urb) {
+		udev = pep->urb->dev;
+		if (udev) {
+			idVendor = le16_to_cpu(udev->descriptor.idVendor);
+			idProduct = le16_to_cpu(udev->descriptor.idProduct);
+		}
+	}
+	DBG("(%04X:%04X) request=%d,"
+	    " io_buf=%p, io_buf_len=%u, io_xfer_offset=%u\n",
+		idVendor, idProduct,
+		pep->request,
+		pep->io_buf,
+		pep->io_buf_len,
+		pep->io_xfer_offset);
+}
+
+/*
+ *
+ * Context: in interrupt
+ */
+static int sthcd_pep_xfer_callback(struct starlet_ipc_request *req)
+{
+	int xfer_len = req->result;
+	struct sthcd_pep *pep = req->done_data;
+	int status = 0;
+	struct sthcd_port *port;
+	struct sthcd_hcd *sthcd;
+	struct usb_hcd *hcd;
+	struct urb *urb;
+	int retval;
+	unsigned long flags;
+	int error;
+
+	starlet_ipc_free_request(req);
+
+	sthcd = pep->sthcd;
+	spin_lock_irqsave(&sthcd->lock, flags);
+
+	hcd = sthcd_to_hcd(sthcd);
+
+	pep->outstanding--;
+
+	urb = pep->urb;
+	if (!urb) {
+		/*
+		 * starlet completed an URB that was already dequeued.
+		 *
+		 * We must free here the memory used by the pep, including
+		 * I/O buffers, avoiding dereferencing any USB stack data
+		 * pointed by the pep, as it may be invalid now.
+		 */
+		sthcd_pep_free_xfer_io_buf(pep);
+		sthcd_pep_free(pep);
+		goto done;
+	}
+
+	/* sanity checks, determine transfer status and length */
+	if (xfer_len < 0) {
+		status = xfer_len;
+		xfer_len = 0;
+
+		if (status != -7004 && status != -7003 && status != -7005) {
+			drv_printk(KERN_ERR, "request completed"
+				   " with error %d\n", status);
+			sthcd_pep_print(pep);
+		}
+
+		switch (status) {
+		case -7003:
+		case -7004:
+			/* endpoint stall */
+			status = -EPIPE;
+			break;
+		case -7005:
+			/* nak? */
+			status = -ECONNRESET;
+			break;
+		case -7008:
+		case -7022:
+		case -4:
+			/* FALL-THROUGH */
+		default:
+			/*
+			 * We got an unknown, probably un-retryable, error.
+			 * Flag the port as unuseable. The associated
+			 * device will be disconnected ASAP.
+			 */
+			port = udev_to_port(pep->udev);
+			set_bit(__STHCD_PORT_DOOMED, &port->flags);
+			DBG("%s: error %d on port %d, doomed!\n", __func__,
+			    status, port - pep->sthcd->ports + 1);
+
+			/* also, do not use the pep for xfers anymore */
+			set_bit(__STHCD_PEP_DISABLED, &pep->flags);
+			status = -ENODEV;
+			break;
+		}
+	} else {
+		if (usb_pipecontrol(urb->pipe)) {
+			/*
+			 * starlet includes the length of the request
+			 * into the reply for control transfers.
+			 * We need to substract the request size from
+			 * the reply len to get the actual data size.
+			 */
+			xfer_len -= sizeof(struct usb_ctrlrequest);
+			if (xfer_len < 0) {
+				drv_printk(KERN_ERR, "request incomplete,"
+					   " %d bytes short\n",
+					   -xfer_len);
+				status = -EPIPE;
+				xfer_len = 0;
+			}
+		}
+		if (xfer_len > pep->io_buf_len) {
+			DBG("%s: xfer len %u larger than xfer buf"
+			    " len %u\n", __func__,
+			    xfer_len, pep->io_buf_len);
+			xfer_len = pep->io_buf_len;
+		}
+
+	}
+
+	if (xfer_len > 0) {
+		sthcd_pep_finish_xfer(pep, xfer_len);
+
+		/*
+		 * Only schedule the next chunk if we didn't get a short xfer
+		 * and the pep is still active
+		 */
+		if (xfer_len == pep->io_buf_len && pep_is_enabled(pep)) {
+			retval = sthcd_pep_setup_next_xfer(pep);
+			if (retval <= 0) {
+				/* an error happened or all chunks were done */
+				status = retval;
+			} else {
+				/* next xfer */
+				sthcd_pep_start_xfer(pep);
+				goto done;
+			}
+		}
+	}
+
+	sthcd_pep_free_xfer_io_buf(pep);
+	urb->actual_length = pep->io_xfer_offset;
+
+	/* at this point, we are done with this urb */
+	clear_bit(__STHCD_PEP_XFERBUSY, &pep->flags);
+
+	sthcd_pep_takeout_urb(pep);
+
+	BUG_ON(!sthcd);
+	BUG_ON(!urb);
+
+	error = usb_hcd_check_unlink_urb(hcd, urb, status);
+	if (!error) {
+		usb_hcd_unlink_urb_from_ep(hcd, urb);
+
+		/* give back this urb */
+		sthcd_giveback_urb(sthcd, urb, status);
+	} else {
+		/* REVISIT, paranoid */
+		DBG("%s: error checking unlink\n", __func__);
+	}
+
+	/* if applicable, launch the next urb in this endpoint queue */
+	sthcd_pep_cond_send_next_urb(pep);
+
+done:
+	spin_unlock_irqrestore(&sthcd->lock, flags);
+
+	return 0;
+}
+
+
+/*
+ * starlet USB device "udev" abstraction.
+ *
+ *
+ */
+
+
+static struct sthcd_udev *sthcd_get_free_udev(struct sthcd_hcd *sthcd)
+{
+	struct sthcd_port *port;
+	struct sthcd_udev *udev;
+	int i;
+
+	port = sthcd->ports;
+	for (i = 0; i < sthcd->nr_ports; i++, port++) {
+		udev = &port->udev;
+		if (!test_and_set_bit(__STHCD_PORT_INUSE, &port->flags))
+			return udev;
+	}
+	return NULL;
+}
+
+static struct sthcd_udev *sthcd_find_udev_by_ids(struct sthcd_hcd *sthcd,
+						 u16 idVendor, u16 idProduct)
+{
+	struct sthcd_udev *udev;
+
+	list_for_each_entry(udev, &sthcd->device_list, node) {
+		if (udev->idVendor == idVendor && udev->idProduct == idProduct)
+			return udev;
+	}
+	return NULL;
+}
+
+#if 0
+static int sthcd_udev_suspend(struct sthcd_udev *udev)
+{
+	int error;
+
+	error = starlet_ioctl(udev->fd, STHCD_IOCTL_SUSPENDDEVICE,
+			      NULL, 0, NULL, 0);
+	if (error < 0)
+		DBG("%s: error=%d (%x)\n", __func__, error, error);
+	return error;
+}
+
+static int sthcd_udev_resume(struct sthcd_udev *udev)
+{
+	int error;
+
+	error = starlet_ioctl(udev->fd, STHCD_IOCTL_RESUMEDEVICE,
+			      NULL, 0, NULL, 0);
+	if (error < 0)
+		DBG("%s: error=%d (%x)\n", __func__, error, error);
+	return error;
+}
+#endif
+
+static int sthcd_udev_close(struct sthcd_udev *udev)
+{
+	int fd = udev->fd;
+
+	udev->fd = -1;
+	return starlet_close(fd);
+}
+
+static int sthcd_udev_open(struct sthcd_udev *udev)
+{
+	struct sthcd_oh *oh = udev->oh;
+	char pathname[32];
+	int error;
+
+	if (udev->fd != -1) {
+		drv_printk(KERN_WARNING, "udev %04X.%04X already opened,"
+			   " closing it first\n",
+			   udev->idVendor, udev->idProduct);
+		sthcd_udev_close(udev);
+	}
+
+	snprintf(pathname, sizeof(pathname), "/dev/usb/oh%u/%04x/%04x",
+		 oh->index, udev->idVendor, udev->idProduct);
+	error = starlet_open(pathname, 0);
+	if (error < 0) {
+		drv_printk(KERN_ERR, "open %s failed\n", pathname);
+		return error;
+	}
+	udev->fd = error;
+
+	return 0;
+}
+
+static void sthcd_udev_exit(struct sthcd_udev *udev)
+{
+	struct sthcd_hcd *sthcd;
+	struct sthcd_pep *pep;
+	unsigned long flags;
+
+	sthcd = udev->oh->hcd;
+
+	spin_lock_irqsave(&sthcd->lock, flags);
+
+	/* remove from the list of connected devices */
+	list_del_init(&udev->node);
+
+	/* unlink all associated peps */
+	list_for_each_entry(pep, &udev->pep_list, node) {
+		if (pep->udev) {
+			pep->udev = NULL;
+			list_del_init(&pep->node);
+		}
+	}
+
+	spin_unlock_irqrestore(&sthcd->lock, flags);
+
+	sthcd_udev_close(udev);
+
+	udev->idVendor = 0;
+	udev->idProduct = 0;
+	udev->oh = NULL;
+	udev->devnum = 0;
+}
+
+static int sthcd_udev_init(struct sthcd_udev *udev,
+			   struct sthcd_oh *oh,
+			   u16 idVendor, u16 idProduct)
+{
+	struct sthcd_hcd *sthcd = oh->hcd;
+	int error;
+	unsigned long flags;
+
+	INIT_LIST_HEAD(&udev->pep_list);
+
+	udev->idVendor = idVendor;
+	udev->idProduct = idProduct;
+	udev->oh = oh;
+	udev->fd = -1;
+	udev->devnum = 0;
+
+	error = sthcd_udev_open(udev);
+	if (error)
+		return error;
+
+	spin_lock_irqsave(&sthcd->lock, flags);
+	list_add_tail(&udev->node, &sthcd->device_list);
+	spin_unlock_irqrestore(&sthcd->lock, flags);
+
+	return error;
+}
+
+
+/*
+ * Hub emulation routines.
+ *
+ */
+
+#define STHCD_USB_DT_HUB_TOTAL_SIZE \
+	(USB_DT_HUB_NONVAR_SIZE + 2*((STHCD_MAX_PORTS + 1 + 7) / 8))
+
+static struct usb_hub_descriptor sthcd_hub_hub_descr = {
+	.bDescLength		= STHCD_USB_DT_HUB_TOTAL_SIZE,
+	.bDescriptorType	= USB_DT_HUB,
+	.bNbrPorts		= STHCD_MAX_PORTS,
+	.wHubCharacteristics	= 0x0000,
+	.bPwrOn2PwrGood		= 0,
+	.bHubContrCurrent	= 0,
+};
+
+
+static int
+sthcd_hub_control_standard(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
+			   u16 wIndex, char *buf, u16 wLength)
+{
+	int retval = -EINVAL;
+
+	switch (typeReq) {
+	case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: /* 0009 */
+		if (wValue != 1) {
+			drv_printk(KERN_INFO, "invalid configuration %d\n",
+				   wValue);
+		} else {
+			retval = 0;
+		}
+		break;
+	case DeviceRequest | USB_REQ_GET_STATUS: /* 8000 */
+		if (wLength < 2) {
+			retval = -ENOMEM;
+		} else {
+			buf[0] = (1 << USB_DEVICE_SELF_POWERED);
+			buf[1] = 0;
+			retval = 2;
+		}
+		break;
+	default:
+		drv_printk(KERN_WARNING, "%s: request %04X not supported\n",
+			   __func__, typeReq);
+		break;
+	}
+	DBG("%s: retval=%d (%x)\n", __func__, retval, retval);
+	return retval;
+}
+
+static int
+sthcd_hub_control_hub(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
+		      u16 wIndex, void *buf, u16 wLength)
+{
+	struct sthcd_hcd *sthcd = hcd_to_sthcd(hcd);
+	struct usb_hub_status *hub_status;
+	struct usb_hub_descriptor *hub_descr;
+	size_t size, port_array_size;
+	u8 *p;
+	int retval = -EINVAL;
+
+	switch (typeReq) {
+	case GetHubStatus:	/* 0xA000 */
+		size = sizeof(*hub_status);
+		if (wLength < size) {
+			retval = -ENOMEM;
+		} else {
+			hub_status = buf;
+			hub_status->wHubStatus = 0x0000; /* no problems */
+			hub_status->wHubChange = 0x0000; /* no changes */
+			retval = size;
+		}
+		break;
+	case GetHubDescriptor:	/* 0xA006 */
+		/*
+		 * For the DeviceRemovable and PortPwrCtrlMask fields:
+		 *  bit 0 is reserved.
+		 *  bit 1 is the internal (oh1) port, which is non-removable.
+		 *  bit 2..nr_ports+1 are the external (oh0) ports.
+		 */
+		port_array_size = (1 + sthcd->nr_ports + 7) / 8;
+		size = USB_DT_HUB_NONVAR_SIZE + 2*port_array_size;
+
+		if (wLength < size) {
+			retval = -ENOMEM;
+		} else {
+			p = buf;
+
+			memcpy(p, &sthcd_hub_hub_descr, USB_DT_HUB_NONVAR_SIZE);
+			p += USB_DT_HUB_NONVAR_SIZE;
+
+			/* fixup the descriptor with the real number of ports */
+			hub_descr = buf;
+			hub_descr->bDescLength = size;
+			hub_descr->bNbrPorts = sthcd->nr_ports;
+
+			/* DeviceRemovable field, table 11-13 Hub Descriptor */
+			memset(p, 0, port_array_size);
+			*p |= 0x02;	/* port 1 is non-removable */
+			p += port_array_size;
+
+			/* PortPwrCtrlMask field, table 11-13 Hub Descriptor */
+			memset(p, 0xff, port_array_size);
+
+			retval = size;
+		}
+		break;
+	default:
+		drv_printk(KERN_WARNING, "%s: request %04X not supported\n",
+			   __func__, typeReq);
+		break;
+	}
+
+	if (retval < 0)
+		DBG("%s: retval=%d (%x)\n", __func__, retval, retval);
+	return retval;
+}
+
+
+static int
+sthcd_hub_control_port(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
+		       u16 wIndex, void *buf, u16 wLength)
+{
+	struct sthcd_hcd *sthcd = hcd_to_sthcd(hcd);
+	struct sthcd_port *port;
+	unsigned long flags;
+	int retval = 0;
+
+	if (wIndex == 0 || wIndex > sthcd->nr_ports) {
+		DBG("%s: invalid port %u\n", __func__, wIndex);
+		return -EINVAL;
+	}
+
+	spin_lock_irqsave(&sthcd->lock, flags);
+
+	wIndex--;
+	port = &sthcd->ports[wIndex];
+
+	switch (typeReq) {
+	case GetPortStatus:	/* 0xA300 */
+		if (test_bit(__STHCD_PORT_DOOMED, &port->flags)) {
+			/* disconnect */
+			if (!!(port->status_change & USB_PORT_STAT_CONNECTION))
+				port->status_change |=
+					(USB_PORT_STAT_C_CONNECTION<<16);
+			port->status_change &= ~USB_PORT_STAT_CONNECTION;
+		}
+		/* REVISIT wait 50ms before clearing the RESET state */
+		if (port->status_change & USB_PORT_STAT_RESET) {
+			port->nr_resets++;
+			if (port->nr_resets > 2) {
+				DBG("%s: port %d was reset %u time(s),"
+				    " doomed!\n", __func__,
+				    wIndex+1, port->nr_resets);
+				set_bit(__STHCD_PORT_DOOMED, &port->flags);
+			}
+			if (!(port->status_change & USB_PORT_STAT_ENABLE))
+				port->status_change |=
+						(USB_PORT_STAT_C_ENABLE << 16);
+			port->status_change &= ~USB_PORT_STAT_RESET;
+			port->status_change |= (USB_PORT_STAT_ENABLE |
+						(USB_PORT_STAT_C_RESET << 16));
+			port->udev.devnum = 0;
+		}
+		retval = 4;
+		((__le32 *) buf)[0] = cpu_to_le32(port->status_change);
+		break;
+	case ClearPortFeature:	/* 0x2301 */
+		switch (wValue) {
+		case USB_PORT_FEAT_ENABLE:
+			port->status_change &= USB_PORT_STAT_POWER;
+			break;
+		case USB_PORT_FEAT_SUSPEND:
+		case USB_PORT_FEAT_POWER:
+		case USB_PORT_FEAT_C_ENABLE:
+		case USB_PORT_FEAT_C_SUSPEND:
+		case USB_PORT_FEAT_C_CONNECTION:
+		case USB_PORT_FEAT_C_OVER_CURRENT:
+		case USB_PORT_FEAT_C_RESET:	/* 0x14 */
+			break;
+		default:
+			goto error;
+		}
+		port->status_change &= ~(1 << wValue);
+		break;
+	case SetPortFeature:	/* 0x2303 */
+		switch (wValue) {
+		case USB_PORT_FEAT_ENABLE:
+		case USB_PORT_FEAT_SUSPEND:
+		case USB_PORT_FEAT_POWER: /* 0x08 */
+			break;
+		case USB_PORT_FEAT_RESET: /* 0x04 */
+			/* REVISIT, free all related resources here */
+			break;
+		default:
+			goto error;
+		}
+		port->status_change |= 1 << wValue;
+		break;
+	default:
+		drv_printk(KERN_WARNING, "%s: request %04X not supported\n",
+			   __func__, typeReq);
+error:
+		retval = -EPIPE;
+		break;
+	}
+
+	spin_unlock_irqrestore(&sthcd->lock, flags);
+
+	return retval;
+}
+
+static int sthcd_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
+			     u16 wIndex, char *buf, u16 wLength)
+{
+	u8 bmRequestType;
+	int retval = -EINVAL;
+
+	/*
+	 * starlet never answers to requests on device 0/0, so we emulate it.
+	 */
+
+	bmRequestType = typeReq >> 8;
+
+	switch (bmRequestType & USB_TYPE_MASK) {
+	case USB_TYPE_STANDARD:
+		/* generic requests */
+		retval = sthcd_hub_control_standard(hcd, typeReq, wValue,
+						    wIndex, buf, wLength);
+		break;
+	case USB_TYPE_CLASS:
+		/* hub-specific requests */
+		switch (bmRequestType & USB_RECIP_MASK) {
+		case USB_RECIP_DEVICE:
+			/* hub */
+			retval = sthcd_hub_control_hub(hcd, typeReq, wValue,
+						       wIndex, buf, wLength);
+			break;
+		case USB_RECIP_OTHER:
+			/* port */
+			retval = sthcd_hub_control_port(hcd, typeReq, wValue,
+							wIndex, buf, wLength);
+			break;
+		default:
+			drv_printk(KERN_WARNING, "%s: request %04X"
+				   " not supported\n", __func__, typeReq);
+			break;
+		}
+		break;
+	default:
+		drv_printk(KERN_WARNING, "%s: request %04X not supported\n",
+			   __func__, typeReq);
+		break;
+	}
+
+	if (retval > 0)
+		retval = 0;
+	if (retval < 0)
+		DBG("%s: retval=%d (%x)\n", __func__, retval, retval);
+	return retval;
+}
+
+static int sthcd_hub_status_data(struct usb_hcd *hcd, char *buf)
+{
+	struct sthcd_hcd *sthcd = hcd_to_sthcd(hcd);
+	u16 *p = (u16 *)buf;
+	struct sthcd_port *port;
+	unsigned long flags;
+	int i, result;
+
+	if (!HC_IS_RUNNING(hcd->state))
+		return -ESHUTDOWN;
+
+#if 0
+	if (timer_pending(&hcd->rh_timer))
+		return 0;
+#endif
+
+	/* FIXME, this code assumes at least 9 and no more than 15 ports */
+	BUG_ON(sthcd->nr_ports > 15 || sthcd->nr_ports < 8);
+
+	spin_lock_irqsave(&sthcd->lock, flags);
+
+	port = sthcd->ports;
+	for (i = 0, *p = 0; i < sthcd->nr_ports; i++, port++) {
+		if ((port->status_change & 0xffff0000) != 0) {
+			*p |= 1 << (i+1);
+			/* REVISIT */
+		}
+	}
+	*p = le16_to_cpu(*p);
+	result = (*p != 0) ? 2 : 0;
+
+	spin_unlock_irqrestore(&sthcd->lock, flags);
+
+/*	DBG("%s: poll cycle, changes=%04x\n", __func__, *p); */
+
+	return result;
+}
+
+
+/*
+ * "OH" abstraction.
+ *
+ */
+
+static int sthcd_oh_insert_udev(struct sthcd_oh *oh,
+				u16 idVendor, u16 idProduct)
+{
+	struct sthcd_hcd *sthcd = oh->hcd;
+	struct sthcd_udev *udev;
+	struct sthcd_port *port;
+	unsigned long flags;
+	int error;
+
+	drv_printk(KERN_INFO, "inserting device %04X.%04X\n",
+		   idVendor, idProduct);
+
+	udev = sthcd_get_free_udev(sthcd);
+	if (!udev) {
+		drv_printk(KERN_ERR, "no free udevs!\n");
+		return -EBUSY;
+	}
+
+	error = sthcd_udev_init(udev, oh, idVendor, idProduct);
+	if (!error) {
+		spin_lock_irqsave(&sthcd->lock, flags);
+
+		port = udev_to_port(udev);
+		/* notify a connection event */
+		port->status_change = USB_PORT_STAT_POWER |
+					USB_PORT_STAT_CONNECTION |
+					(USB_PORT_STAT_C_CONNECTION<<16);
+
+		spin_unlock_irqrestore(&sthcd->lock, flags);
+	}
+	return error;
+}
+
+static int sthcd_oh_remove_udev(struct sthcd_oh *oh,
+				u16 idVendor, u16 idProduct)
+{
+	struct sthcd_hcd *sthcd = oh->hcd;
+	struct sthcd_udev *udev;
+	struct sthcd_port *port;
+	u32 old_status;
+	unsigned long flags;
+	int error = 0;
+
+	udev = sthcd_find_udev_by_ids(sthcd, idVendor, idProduct);
+	if (!udev) {
+		/* normally reached for ignored hubs */
+		error = -ENODEV;
+	} else {
+		drv_printk(KERN_INFO, "removing device %04X.%04X\n",
+			   idVendor, idProduct);
+		sthcd_udev_exit(udev);
+
+		spin_lock_irqsave(&sthcd->lock, flags);
+
+		port = udev_to_port(udev);
+		clear_bit(__STHCD_PORT_INUSE, &port->flags);
+		clear_bit(__STHCD_PORT_DOOMED, &port->flags);
+		port->nr_resets = 0;
+		/* notify a disconnection event */
+		old_status = port->status_change;
+		port->status_change = USB_PORT_STAT_POWER;
+		if ((old_status & USB_PORT_STAT_CONNECTION) != 0)
+			port->status_change |= (USB_PORT_STAT_C_CONNECTION<<16);
+
+		spin_unlock_irqrestore(&sthcd->lock, flags);
+	}
+	return error;
+}
+
+
+/*
+ * Non-atomic context (synchronous call).
+ */
+static int sthcd_usb_control_msg(int fd,
+				 __u8 request, __u8 requesttype,
+				 __u16 value, __u16 index,
+				 void *data, __u16 size,
+				 int timeout)
+{
+	struct sthcd_ctrl_params_in *params_in;
+	struct starlet_ioh_sg in[6];
+	struct starlet_ioh_sg io[1];
+	int error;
+
+	params_in = starlet_ioh_kzalloc(sizeof(*params_in));
+	if (!params_in) {
+		error = -ENOMEM;
+		goto done;
+	}
+
+	params_in->req.bRequestType = requesttype;
+	params_in->req.bRequest = request;
+	params_in->req.wValue = cpu_to_le16p(&value);
+	params_in->req.wIndex = cpu_to_le16p(&index);
+	params_in->req.wLength = cpu_to_le16p(&size);
+	params_in->_unk1 = timeout; /* seconds? */
+
+	starlet_ioh_sg_init_table(in, 6);
+	starlet_ioh_sg_entry(&in[0], &params_in->req.bRequestType);
+	starlet_ioh_sg_entry(&in[1], &params_in->req.bRequest);
+	starlet_ioh_sg_entry(&in[2], &params_in->req.wValue);
+	starlet_ioh_sg_entry(&in[3], &params_in->req.wIndex);
+	starlet_ioh_sg_entry(&in[4], &params_in->req.wLength);
+	starlet_ioh_sg_entry(&in[5], &params_in->_unk1);
+
+	starlet_ioh_sg_init_table(io, 1);
+	starlet_ioh_sg_set_buf(&io[0], data, size);
+
+	error = starlet_ioh_ioctlv(fd, STHCD_IOCTLV_CONTROLREQ,
+				   6, in, 1, io);
+
+	starlet_ioh_kfree(params_in);
+
+	if (error > 0) {
+		/* adjust size for successful control xfers */
+		error -= sizeof(struct usb_ctrlrequest);
+		if (error < 0)
+			error = -EINVAL;
+	}
+
+done:
+	if (error < 0)
+		DBG("%s: error=%d (%x)\n", __func__, error, error);
+	return error;
+}
+
+
+static int sthcd_oh_check_hub(struct sthcd_oh *oh, u16 idVendor, u16 idProduct)
+{
+	char pathname[32];
+	struct usb_device_descriptor *descriptor;
+	int fd;
+	int i;
+	int retval;
+
+	descriptor = starlet_ioh_kzalloc(USB_DT_DEVICE_SIZE);
+	if (!descriptor) {
+		retval = -ENOMEM;
+		goto done;
+	}
+
+	snprintf(pathname, sizeof(pathname), "/dev/usb/oh%u/%04x/%04x",
+		 oh->index, idVendor, idProduct);
+	retval = starlet_open(pathname, 0);
+	if (retval < 0) {
+		drv_printk(KERN_ERR, "open %s failed\n", pathname);
+		starlet_ioh_kfree(descriptor);
+		goto done;
+	}
+	fd = retval;
+
+	for (i = 0; i < 3; i++) {
+		retval = sthcd_usb_control_msg(fd, USB_REQ_GET_DESCRIPTOR,
+					      USB_DIR_IN,
+					      USB_DT_DEVICE << 8, 0,
+					      descriptor, USB_DT_DEVICE_SIZE,
+					      0);
+		if (retval != -7005)
+			break;
+		DBG("%s: attempt %d, retval=%d (%x)\n", __func__,
+		    i, retval, retval);
+	}
+
+	starlet_close(fd);
+
+	if (retval >= USB_DT_DEVICE_SIZE) {
+		/* tell if a hub was found */
+		retval = (descriptor->bDeviceClass == USB_CLASS_HUB) ? 1 : 0;
+	} else {
+		if (retval >= 0)
+			retval = -EINVAL;	/* short descriptor */
+	}
+
+	starlet_ioh_kfree(descriptor);
+
+done:
+	if (retval < 0)
+		DBG("%s: retval=%d (%x)\n", __func__, retval, retval);
+	return retval;
+}
+
+struct sthcd_getdevicelist_params_in {
+	u8 devid_count;
+	u8 _type;
+};
+struct sthcd_getdevicelist_params_io {
+	u8 devid_count;
+	struct sthcd_devid devids[0];
+};
+
+static int sthcd_get_device_list(struct sthcd_hcd *sthcd, int fd,
+				 struct sthcd_devid *devids, size_t nr_devids)
+{
+	struct starlet_ioh_sg in[2], io[2];
+	struct sthcd_getdevicelist_params_in *params_in;
+	struct sthcd_getdevicelist_params_io *params_io;
+	size_t size = nr_devids * sizeof(struct sthcd_devid);
+	int error;
+
+	if (!nr_devids)
+		return -EINVAL;
+
+	params_in = starlet_ioh_kzalloc(sizeof(*params_in));
+	if (!params_in)
+		return -ENOMEM;
+
+	params_io = starlet_ioh_kzalloc(sizeof(*params_io) + size);
+	if (!params_io) {
+		starlet_ioh_kfree(params_in);
+		return -ENOMEM;
+	}
+
+	params_in->devid_count = nr_devids;
+	params_in->_type = 0;
+
+	starlet_ioh_sg_init_table(in, 2);
+	starlet_ioh_sg_entry(&in[0], &params_in->devid_count);
+	starlet_ioh_sg_entry(&in[1], &params_in->_type);
+
+	starlet_ioh_sg_init_table(io, 2);
+	starlet_ioh_sg_entry(&io[0], &params_io->devid_count);
+	starlet_ioh_sg_set_buf(&io[1], &params_io->devids, size);
+
+	error = starlet_ioh_ioctlv(fd, STHCD_IOCTLV_GETDEVICELIST,
+				   2, in, 2, io);
+
+	if (error < 0) {
+		DBG("%s: error=%d (%x)\n", __func__, error, error);
+	} else {
+		memcpy(devids, params_io->devids, size);
+		error = params_io->devid_count;
+	}
+
+	starlet_ioh_kfree(params_in);
+	starlet_ioh_kfree(params_io);
+
+	return error;
+}
+
+static int sthcd_devid_match(struct sthcd_devid *id1, struct sthcd_devid *id2)
+{
+	return id1->idVendor == id2->idVendor &&
+	       id1->idProduct == id2->idProduct;
+}
+
+static int sthcd_devid_find(struct sthcd_devid *haystack, size_t count,
+			    struct sthcd_devid *needle)
+{
+	unsigned int i;
+
+	for (i = 0; i < count; i++) {
+		if (sthcd_devid_match(&haystack[i], needle))
+			return 1;
+	}
+	return 0;
+}
+
+static int sthcd_oh_rescan(struct sthcd_oh *oh)
+{
+	static unsigned int poll_cycles;
+	struct usb_hcd *hcd = sthcd_to_hcd(oh->hcd);
+	struct sthcd_devid *p;
+	int nr_new_devids, i;
+	int changes;
+	int error;
+
+	error = sthcd_get_device_list(oh->hcd, oh->fd, oh->new_devids,
+				      oh->max_devids);
+	if (error < 0)
+		return error;
+
+	nr_new_devids = error;
+	changes = 0;
+
+	for (i = 0; i < oh->nr_devids; i++) {
+		p = &oh->devids[i];
+		if (!sthcd_devid_find(oh->new_devids, nr_new_devids, p)) {
+			/* removal */
+			error = sthcd_oh_remove_udev(oh, p->idVendor,
+						     p->idProduct);
+			if (!error)
+				changes++;
+		}
+	}
+
+	for (i = 0; i < nr_new_devids; i++) {
+		p = &oh->new_devids[i];
+		if (!sthcd_devid_find(oh->devids, oh->nr_devids, p)) {
+			/* insertion */
+			error = sthcd_oh_check_hub(oh, p->idVendor,
+						   p->idProduct);
+			if (error == 0) {
+				/* not a hub, register the usb device */
+				error = sthcd_oh_insert_udev(oh, p->idVendor,
+							     p->idProduct);
+				if (!error)
+					changes++;
+			} else {
+				drv_printk(KERN_INFO,
+					   "ignoring hub %04X.%04X\n",
+					   p->idVendor, p->idProduct);
+			}
+		}
+	}
+
+	memcpy(oh->devids, oh->new_devids, nr_new_devids * sizeof(*p));
+	oh->nr_devids = nr_new_devids;
+
+	/*
+	 * FIXME
+	 * We ask here the USB layer to explicitly poll for root hub changes
+	 * until we get at least two complete rescan cycles without changes.
+	 *
+	 * Otherwise, for unknown reasons, we end up missing the detection of
+	 * some devices, even if the insertion/removal of these devices is
+	 * properly signaled in port->status_change.
+	 */
+	if (changes) {
+#if 1
+		if (!poll_cycles) {
+			hcd->poll_rh = 1;
+			usb_hcd_poll_rh_status(hcd);
+		}
+		poll_cycles = 2;
+	} else {
+		if (!poll_cycles)
+			hcd->poll_rh = 0;
+		else
+			poll_cycles--;
+#else
+		usb_hcd_poll_rh_status(hcd);
+#endif
+	}
+
+	return 0;
+}
+
+static int sthcd_oh_init(struct sthcd_oh *oh, unsigned int index,
+			 struct sthcd_hcd *sthcd, size_t max_devids)
+{
+	char pathname[16];
+	int error;
+
+	if (index != 0 && index != 1)
+		return -EINVAL;
+
+	snprintf(pathname, sizeof(pathname), "/dev/usb/oh%u", index);
+	error = starlet_open(pathname, 0);
+	if (error < 0)
+		return error;
+
+	oh->fd = error;
+	oh->devids = kzalloc(2 * max_devids * sizeof(struct sthcd_devid),
+			     GFP_KERNEL);
+	if (!oh->devids) {
+		starlet_close(oh->fd);
+		return -ENOMEM;
+	}
+
+	oh->new_devids = oh->devids + max_devids;
+
+	oh->max_devids = max_devids;
+	oh->nr_devids = 0;
+
+	oh->index = index;
+	oh->hcd = sthcd;
+
+	return 0;
+}
+
+static void sthcd_oh_exit(struct sthcd_oh *oh)
+{
+	starlet_close(oh->fd);
+	oh->fd = -1;
+	kfree(oh->devids);
+	oh->devids = NULL;
+}
+
+static int sthcd_rescan_thread(void *arg)
+{
+	struct sthcd_hcd *sthcd = arg;
+	struct sthcd_oh *oh;
+
+	/*
+	 * REVISIT
+	 * We may need to rescan oh1 if bluetooth dongle disconnects.
+	 */
+
+	/* oh1 has non-removable devices only, so just scan it once */
+	sthcd_oh_rescan(&sthcd->oh[1]);
+
+	oh = &sthcd->oh[0];
+
+	while (!kthread_should_stop()) {
+		sthcd_oh_rescan(oh);
+
+		/* re-check again after the configured interval */
+		sleep_on_timeout(&sthcd->rescan_waitq,
+				 STHCD_RESCAN_INTERVAL*HZ);
+	}
+	return 0;
+}
+
+
+/*
+ *
+ *
+ */
+
+static int sthcd_init(struct usb_hcd *hcd)
+{
+	return 0;
+}
+
+static int sthcd_start(struct usb_hcd *hcd)
+{
+	struct sthcd_hcd *sthcd = hcd_to_sthcd(hcd);
+	int error;
+
+	/*
+	 * This is to prevent a spurious error from the kernel usb stack
+	 * as we do not make use of interrupts.
+	 */
+	set_bit(HCD_FLAG_SAW_IRQ, &hcd->flags);
+
+	hcd->uses_new_polling = 1;
+
+	/* oh0 is the external bus */
+	error = sthcd_oh_init(&sthcd->oh[0], 0, sthcd, STHCD_MAX_DEVIDS);
+	if (error < 0) {
+		DBG("%s: error=%d (%x)\n", __func__, error, error);
+		return error;
+	}
+
+	/* oh1 is the internal bus, used only by the bluetooth dongle */
+	error = sthcd_oh_init(&sthcd->oh[1], 1, sthcd, 1);
+	if (error < 0) {
+		DBG("%s: error=%d (%x)\n", __func__, error, error);
+		sthcd_oh_exit(&sthcd->oh[0]);
+		return error;
+	}
+
+	hcd->state = HC_STATE_RUNNING;
+
+	/* device insertion/removal is managed by the rescan thread */
+	sthcd->rescan_task = kthread_run(sthcd_rescan_thread, sthcd, "ksthcd");
+	if (IS_ERR(sthcd->rescan_task))
+		drv_printk(KERN_ERR, "failed to start rescan thread\n");
+
+	return 0;
+}
+
+static void sthcd_stop(struct usb_hcd *hcd)
+{
+	struct sthcd_hcd *sthcd = hcd_to_sthcd(hcd);
+
+	if (!IS_ERR(sthcd->rescan_task)) {
+		kthread_stop(sthcd->rescan_task);
+		sthcd->rescan_task = ERR_PTR(-EINVAL);
+	}
+
+	sthcd_oh_exit(&sthcd->oh[0]);
+	sthcd_oh_exit(&sthcd->oh[1]);
+
+	hcd->state &= ~HC_STATE_RUNNING;
+}
+
+static int sthcd_get_frame_number(struct usb_hcd *hcd)
+{
+	DBG("%s: CALLED\n", __func__);
+	return 0;
+}
+
+
+static int sthcd_urb_enqueue(struct usb_hcd *hcd, struct urb *urb,
+			     gfp_t mem_flags)
+{
+	struct sthcd_hcd *sthcd = hcd_to_sthcd(hcd);
+	struct usb_host_endpoint *ep;
+	struct sthcd_pep *pep;
+	unsigned long flags;
+	int error;
+
+	spin_lock_irqsave(&sthcd->lock, flags);
+
+	/* REVISIT, paranoid */
+	if (urb->status != -EINPROGRESS) {
+		DBG("%s: status != -EINPROGRESS\n", __func__);
+		error = urb->status;
+		goto done;
+	}
+
+	error = usb_hcd_link_urb_to_ep(hcd, urb);
+	if (error)
+		goto done;
+
+	ep = urb->ep;
+
+	/* allocate a pep for each endpoint on first use */
+	if (!ep->hcpriv) {
+		pep = sthcd_pep_alloc(sthcd, ep);
+		if (!pep) {
+			error = -ENOMEM;
+			goto err_linked;
+		}
+		ep->hcpriv = pep;
+	} else {
+		pep = ep->hcpriv;
+	}
+
+	error = sthcd_pep_send_urb(pep, urb);
+	if (!error)
+		goto done;
+
+err_linked:
+	usb_hcd_unlink_urb_from_ep(hcd, urb);
+done:
+	spin_unlock_irqrestore(&sthcd->lock, flags);
+	return error;
+}
+
+static int sthcd_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
+{
+	struct sthcd_hcd *sthcd = hcd_to_sthcd(hcd);
+	struct usb_host_endpoint *ep;
+	struct sthcd_pep *pep;
+	unsigned long flags;
+	int error;
+
+	spin_lock_irqsave(&sthcd->lock, flags);
+
+	error = usb_hcd_check_unlink_urb(hcd, urb, status);
+	if (error)
+		goto done;
+
+	ep = urb->ep;
+	pep = ep_to_pep(ep);
+	if (pep && pep->urb == urb) {
+		/*
+		 * There is an urb in flight.
+		 *
+		 * We deattach the urb from the pep and leave the pep to the
+		 * callback function, which will free it upon completion,
+		 * without further action.
+		 */
+		sthcd_pep_takeout_urb(pep);
+		ep->hcpriv = NULL;
+	}
+
+	usb_hcd_unlink_urb_from_ep(hcd, urb);
+	sthcd_giveback_urb(sthcd, urb, status);
+
+done:
+	spin_unlock_irqrestore(&sthcd->lock, flags);
+
+#if 0
+	if (error < 0)
+		DBG("%s: error=%d (%x)\n", __func__, error, error);
+#endif
+	return error;
+}
+
+static void sthcd_endpoint_disable(struct usb_hcd *hcd,
+				   struct usb_host_endpoint *ep)
+{
+	struct sthcd_hcd *sthcd = hcd_to_sthcd(hcd);
+	struct sthcd_pep *pep;
+	unsigned long flags;
+
+	spin_lock_irqsave(&sthcd->lock, flags);
+	pep = ep->hcpriv;
+
+	/* do nothing if the pep was already freed */
+	if (!pep)
+		goto done;
+
+	if (pep->urb) {
+		/*
+		 * There is an urb in flight.
+		 *
+		 * Disable the private endpoint and take the urb out of it.
+		 * The callback function will take care of freeing the pep
+		 * when the starlet call completes.
+		 */
+		set_bit(__STHCD_PEP_DISABLED, &pep->flags);
+		sthcd_pep_takeout_urb(pep);
+	} else {
+		/* the pep can be freed immediately when no urb is in flight */
+		sthcd_pep_free(pep);
+	}
+	ep->hcpriv = NULL;
+
+done:
+	spin_unlock_irqrestore(&sthcd->lock, flags);
+}
+
+
+static const struct hc_driver starlet_hc_driver = {
+	.description =		DRV_MODULE_NAME,
+	.product_desc =		"Nintendo Wii USB Host Controller",
+	.hcd_priv_size =	sizeof(struct sthcd_hcd),
+
+	.irq =			NULL,
+	.flags =		HCD_USB11,
+
+	/* REVISIT, power management calls not yet supported */
+
+	.reset =		sthcd_init,
+	.start =		sthcd_start,
+	.stop =			sthcd_stop,
+
+	.get_frame_number =	sthcd_get_frame_number,
+
+	.urb_enqueue =		sthcd_urb_enqueue,
+	.urb_dequeue =		sthcd_urb_dequeue,
+	.endpoint_disable =	sthcd_endpoint_disable,
+
+	.hub_status_data =	sthcd_hub_status_data,
+	.hub_control =		sthcd_hub_control,
+};
+
+static int __devinit sthcd_driver_probe(struct device *dev)
+{
+	struct sthcd_hcd *sthcd;
+	struct usb_hcd *hcd;
+	int error = -ENOMEM;
+
+	if (starlet_get_ipc_flavour() != STARLET_IPC_IOS)
+		return -ENODEV;
+
+	/*
+	 * We can't use normal dma as starlet requires MEM2 buffers
+	 * to work properly in all cases.
+	 */
+	dev->dma_mask = NULL;
+
+	hcd = usb_create_hcd(&starlet_hc_driver, dev, DRV_MODULE_NAME);
+	if (!hcd)
+		goto err;
+
+	sthcd = hcd_to_sthcd(hcd);
+	spin_lock_init(&sthcd->lock);
+
+	sthcd->nr_ports = STHCD_MAX_PORTS;
+	sthcd->ports = kzalloc(sthcd->nr_ports * sizeof(struct sthcd_port),
+			       GFP_KERNEL);
+	if (!sthcd->ports)
+		goto err_alloc_ports;
+
+	INIT_LIST_HEAD(&sthcd->device_list);
+	init_waitqueue_head(&sthcd->rescan_waitq);
+
+	error = usb_add_hcd(hcd, 0, 0);
+	if (error) {
+		drv_printk(KERN_INFO, "%s: error %d adding hcd\n",
+			   __func__, error);
+		goto err_add;
+	}
+
+	return 0;
+
+err_add:
+	kfree(sthcd->ports);
+err_alloc_ports:
+	usb_put_hcd(hcd);
+err:
+	return error;
+}
+
+static int __devexit sthcd_driver_remove(struct device *dev)
+{
+	struct usb_hcd *hcd = dev_get_drvdata(dev);
+	usb_remove_hcd(hcd);
+	usb_put_hcd(hcd);
+	return 0;
+}
+
+
+/*
+ * Open Firmware platform device routines
+ *
+ */
+
+static int __init sthcd_of_probe(struct of_device *odev,
+	const struct of_device_id *match)
+{
+	return sthcd_driver_probe(&odev->dev);
+}
+
+static int __exit sthcd_of_remove(struct of_device *odev)
+{
+	return sthcd_driver_remove(&odev->dev);
+}
+
+static struct of_device_id sthcd_of_match[] = {
+	{ .compatible = "nintendo,starlet-ios-hcd" },
+	{ },
+};
+
+MODULE_DEVICE_TABLE(of, sthcd_of_match);
+
+static struct of_platform_driver sthcd_of_driver = {
+	.owner = THIS_MODULE,
+	.name = DRV_MODULE_NAME,
+	.match_table = sthcd_of_match,
+	.probe = sthcd_of_probe,
+	.remove = sthcd_of_remove,
+};
+
+
+/*
+ * Linux module framework
+ *
+ */
+
+static int __init sthcd_module_init(void)
+{
+	if (usb_disabled())
+		return -ENODEV;
+
+	drv_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION,
+		   sthcd_driver_version);
+
+	return of_register_platform_driver(&sthcd_of_driver);
+}
+
+static void __exit sthcd_module_exit(void)
+{
+	of_unregister_platform_driver(&sthcd_of_driver);
+}
+
+module_init(sthcd_module_init);
+module_exit(sthcd_module_exit);
+
+MODULE_AUTHOR(DRV_AUTHOR);
+MODULE_DESCRIPTION(DRV_DESCRIPTION);
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 188e1ba..8aa2e6b 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -1650,6 +1650,25 @@ config CARMINE_DRAM_CUSTOM
 	  Use custom board timings.
 endchoice
 
+config FB_GAMECUBE
+	bool "Nintendo GameCube/Wii frame buffer"
+	depends on FB && GAMECUBE_COMMON
+	select FB_CFB_FILLRECT
+	select FB_CFB_COPYAREA
+	select FB_CFB_IMAGEBLIT
+	help
+	  This is the frame buffer device driver for the Nintendo GameCube.
+
+config WII_AVE_RVL
+	bool "Nintendo Wii audio/video encoder support"
+	depends on FB_GAMECUBE && WII
+	select I2C_GPIO
+	select I2C
+	default y
+	help
+	  Say Y here to support the audio/video encoder found in the
+	  Nintendo Wii video game console.
+
 config FB_AU1100
 	bool "Au1100 LCD Driver"
 	depends on (FB = y) && MIPS && SOC_AU1100
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index 80232e1..161c99c 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -128,6 +128,7 @@ obj-$(CONFIG_XEN_FBDEV_FRONTEND)  += xen-fbfront.o
 obj-$(CONFIG_FB_CARMINE)          += carminefb.o
 obj-$(CONFIG_FB_MB862XX)	  += mb862xx/
 obj-$(CONFIG_FB_MSM)              += msm/
+obj-$(CONFIG_FB_GAMECUBE)	  += gcnfb.o
 
 # Platform or fallback drivers go here
 obj-$(CONFIG_FB_UVESA)            += uvesafb.o
diff --git a/drivers/video/gcnfb.c b/drivers/video/gcnfb.c
new file mode 100644
index 0000000..24f8a82
--- /dev/null
+++ b/drivers/video/gcnfb.c
@@ -0,0 +1,2284 @@
+/*
+ * drivers/video/gcn-vifb.c
+ *
+ * Nintendo GameCube/Wii Video Interface (VI) frame buffer driver
+ * Copyright (C) 2004-2009 The GameCube Linux Team
+ * Copyright (C) 2004 Michael Steil <mist@c64.org>
+ * Copyright (C) 2004,2005 Todd Jeffreys <todd@voidpointer.org>
+ * Copyright (C) 2006,2007,2008,2009 Albert Herranz
+ *
+ * Based on vesafb (c) 1998 Gerd Knorr <kraxel@goldbach.in-berlin.de>
+ *
+ * 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/delay.h>
+#include <linux/errno.h>
+#include <linux/fb.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/string.h>
+#include <linux/tty.h>
+#include <linux/wait.h>
+#include <linux/io.h>
+#ifdef CONFIG_WII_AVE_RVL
+#include <linux/i2c.h>
+#endif
+
+#define DRV_MODULE_NAME   "gcn-vifb"
+#define DRV_DESCRIPTION   "Nintendo GameCube/Wii Video Interface (VI) driver"
+#define DRV_AUTHOR        "Michael Steil <mist@c64.org>, " \
+			  "Todd Jeffreys <todd@voidpointer.org>, " \
+			  "Albert Herranz"
+
+static char vifb_driver_version[] = "2.1i";
+
+#define drv_printk(level, format, arg...) \
+	 printk(level DRV_MODULE_NAME ": " format , ## arg)
+
+
+/*
+ * Hardware registers.
+ */
+
+#define __declare_vi_reg_set_field(reg_size, reg, field_size, field, \
+				   mask, shift) \
+static inline reg_size vi_##reg##_set_##field(reg_size reg, field_size field) \
+{									\
+	reg &= ~(mask << shift);					\
+	reg |= (field & mask) << shift;					\
+	return reg;							\
+}
+
+#define __declare_vi_reg_clear_field(reg_size, reg, field, mask, shift) \
+static inline reg_size vi_##reg##_clear_##field(reg_size reg)	\
+{									\
+	reg &= ~(mask << shift);					\
+	return reg;							\
+}
+
+#define __declare_vi_reg_get_field(reg_size, reg, field_size, field, \
+				   mask, shift) \
+static inline field_size vi_##reg##_get_##field(reg_size reg)	\
+{									\
+	return (reg>>shift)&mask;					\
+}
+
+#define __declare_vi_reg_field(reg_size, reg, field_size, field, \
+			       mask, shift) \
+static inline reg_size vi_##reg##_##field(field_size field)	\
+{									\
+	return (field & mask) << shift;					\
+}
+
+#define __vi_reg_field(reg_size, reg, field_size, field, mask, shift) \
+__declare_vi_reg_set_field(reg_size, reg, field_size, field, mask, shift) \
+__declare_vi_reg_clear_field(reg_size, reg, field, mask, shift) 	  \
+__declare_vi_reg_get_field(reg_size, reg, field_size, field, mask, shift) \
+__declare_vi_reg_field(reg_size, reg, field_size, field, mask, shift)
+
+
+#define VI_VTR			0x00 /* Vertical Timing, 16 bits */
+__vi_reg_field(u16, vtr, u16, acv, 0x3ff, 4);		/* ACtive Video */
+__vi_reg_field(u16, vtr, u8, equ, 0xf, 0);		/* EQUalization pulse */
+
+#define VI_DCR			0x02 /* Display Configuration, 16 bits */
+__vi_reg_field(u16, dcr, u8, fmt, 0x3, 8);		/* Format */
+__vi_reg_field(u16, dcr, u8, le1, 0x3, 6);		/* Latch Enable 1 */
+__vi_reg_field(u16, dcr, u8, le0, 0x3, 4);		/* Latch Enable 0 */
+__vi_reg_field(u16, dcr, u8, dlr, 0x1, 3);		/* 3D mode */
+__vi_reg_field(u16, dcr, u8, nin, 0x1, 2);		/* Non-Interlaced */
+__vi_reg_field(u16, dcr, u8, rst, 0x1, 1);		/* Reset */
+__vi_reg_field(u16, dcr, u8, enb, 0x1, 0);		/* Enable */
+
+#define VI_HTR0			0x04 /* Horizontal Timing 0, 32 bits */
+__vi_reg_field(u32, htr0, u8, hcs, 0x7f, 24);		/* Horz Color Start */
+__vi_reg_field(u32, htr0, u8, hce, 0x7f, 16);		/* Horz Color End */
+__vi_reg_field(u32, htr0, u16, hlw, 0x1ff, 0);		/* Half Line Width */
+
+#define VI_HTR1			0x08 /* Horizontal Timing 1, 32 bits */
+__vi_reg_field(u32, htr1, u16, hbs, 0x3ff, 17);		/* Horz Blank Start */
+__vi_reg_field(u32, htr1, u16, hbe, 0x3ff, 7);		/* Horz Blank End */
+__vi_reg_field(u32, htr1, u8, hsy, 0x7f, 0);		/* Horz Sync Width */
+
+#define VI_VTO			0x0c /* Vertical Timing Odd, 32 bits */
+__vi_reg_field(u32, vto, u16, psb, 0x3ff, 16);		/* Post Blanking */
+__vi_reg_field(u32, vto, u16, prb, 0x3ff, 0);		/* Pre Blanking */
+
+#define VI_VTE			0x10 /* Vertical Timing Even, 32 bits */
+__vi_reg_field(u32, vte, u16, psb, 0x3ff, 16);		/* Post Blanking */
+__vi_reg_field(u32, vte, u16, prb, 0x3ff, 0);		/* Pre Blanking */
+
+#define VI_BBOI			0x14 /* Burst Blanking Odd Interval, 32 bits */
+__vi_reg_field(u32, bboi, u16, be3, 0x7ff, 21);
+__vi_reg_field(u32, bboi, u8, bs3, 0x1f, 16);
+__vi_reg_field(u32, bboi, u16, be1, 0x7ff, 5);
+__vi_reg_field(u32, bboi, u8, bs1, 0x1f, 0);
+
+#define VI_BBEI			0x18 /* Burst Blanking Even Interval, 32 bits */
+__vi_reg_field(u32, bbei, u16, be4, 0x7ff, 21);
+__vi_reg_field(u32, bbei, u8, bs4, 0x1f, 16);
+__vi_reg_field(u32, bbei, u16, be2, 0x7ff, 5);
+__vi_reg_field(u32, bbei, u8, bs2, 0x1f, 0);
+
+#define VI_TFBL			0x1c /* Top Field Base (L), 32 bits */
+__vi_reg_field(u32, tfbl, u8, pob, 0x1, 28);		/* Page Offset Bit */
+__vi_reg_field(u32, tfbl, u8, xof, 0xf, 24);		/* X Offset */
+__vi_reg_field(u32, tfbl, u32, fba, 0xffffff, 0);	/* Frame Buf Address */
+
+#define VI_TFBR			0x20 /* Top Field Base (R), 32 bits */
+__vi_reg_field(u32, tfbr, u8, pob, 0x1, 28);		/* Page Offset Bit */
+__vi_reg_field(u32, tfbr, u32, fba, 0xffffff, 0);	/* Frame Buf Address */
+
+#define VI_BFBL			0x24 /* Bottom Field Base (L), 32 bits */
+__vi_reg_field(u32, bfbl, u8, pob, 0x1, 28);		/* Page Offset Bit */
+__vi_reg_field(u32, bfbl, u8, xof, 0xf, 24);		/* X Offset */
+__vi_reg_field(u32, bfbl, u32, fba, 0xffffff, 0);	/* Frame Buf Address */
+
+#define VI_BFBR			0x28 /* Bottom Field Base (R), 32 bits */
+__vi_reg_field(u32, bfbr, u8, pob, 0x1, 28);		/* Page Offset Bit */
+__vi_reg_field(u32, bfbr, u32, fba, 0xffffff, 0);	/* Frame Buf Address */
+
+#define VI_DPV			0x2c /* Display Position Vertical, 16 bits */
+__vi_reg_field(u16, dpv, u16, val, 0x7ff, 0);
+
+#define VI_DPH			0x2e /* Display Position Horizontal, 16 bits */
+__vi_reg_field(u16, dph, u16, val, 0x7ff, 0);
+
+#define VI_DI0			0x30 /* Display Interrupt 0, 32 bits */
+#define VI_DI1			0x34 /* Display Interrupt 1, 32 bits */
+#define VI_DI2			0x38 /* Display Interrupt 2, 32 bits */
+#define VI_DI3			0x3C /* Display Interrupt 3, 32 bits */
+__vi_reg_field(u32, dix, u8, irq, 0x1, 31);
+__vi_reg_field(u32, dix, u8, enb, 0x1, 28);
+__vi_reg_field(u32, dix, u16, vct, 0x3ff, 16);
+__vi_reg_field(u32, dix, u16, hct, 0x3ff, 0);
+
+#define VI_DL0			0x40 /* Display Latch 0, 32 bits */
+
+#define VI_DL1			0x44 /* Display Latch 1, 32 bits */
+
+#define VI_PCR			0x48 /* Picture Configuration, 16 bits */
+__vi_reg_field(u16, pcr, u8, wpl, 0xff, 8);	/* reads per line in words */
+__vi_reg_field(u16, pcr, u8, std, 0xff, 0);	/* stride per line in words */
+
+#define VI_HSR			0x4a /* Horizontal Scaling, 16 bits */
+__vi_reg_field(u16, hsr, u8, hs_en, 0x1, 12);
+__vi_reg_field(u16, hsr, u16, stp, 0x1ff, 0);
+
+#define VI_FCT0			0x4c /* Filter Coeficient Table 0, 32 bits */
+#define VI_FCT1			0x50 /* Filter Coeficient Table 1, 32 bits */
+#define VI_FCT2			0x54 /* Filter Coeficient Table 2, 32 bits */
+#define VI_FCT3			0x58 /* Filter Coeficient Table 3, 32 bits */
+#define VI_FCT4			0x5c /* Filter Coeficient Table 4, 32 bits */
+#define VI_FCT5			0x60 /* Filter Coeficient Table 5, 32 bits */
+#define VI_FCT6			0x64 /* Filter Coeficient Table 6, 32 bits */
+
+#define VI_AA			0x68 /* Anti-aliasing, 32 bits */
+
+#define VI_CLK			0x6c /* Video Clock, 16 bits */
+__vi_reg_field(u16, clk, u8, _54mhz, 0x1, 0);
+
+#define VI_SEL			0x6e /* DTV Status, 16 bits */
+__vi_reg_field(u16, sel, u8, component, 0x1, 0);
+
+#define VI_HSW			0x70 /* Horizontal Scaling Width, 16 bits */
+__vi_reg_field(u16, hsw, u16, width, 0x3ff, 0);
+
+#define VI_HBE			0x72 /* Horizontal Border End, 16 bits */
+#define VI_HBS			0x74 /* Horizontal Border Start, 16 bits */
+
+#define VI_UNK1			0x76 /* Unknown1, 16 bits */
+#define VI_UNK2			0x78 /* Unknown2, 32 bits */
+#define VI_UNK3			0x7c /* Unknown3, 32 bits */
+
+
+enum {
+	VI_SCAN_DONTCARE = 0,
+	VI_SCAN_INTERLACED,
+	VI_SCAN_PROGRESSIVE,
+};
+enum {
+	VI_RATE_DONTCARE = 0,
+	VI_RATE_50Hz,
+	VI_RATE_60Hz,
+};
+enum {
+	VI_TV_DONTCARE = 0,
+	VI_TV_NTSC,
+	VI_TV_PAL,
+};
+
+
+/*
+ * Video modes and timings.
+ *
+ */
+
+enum {
+	VI_VM_NTSC_480i = 0,
+	VI_VM_NTSC_480p,
+	VI_VM_PAL_576i50,
+	VI_VM_PAL_480i60,
+	VI_VM_PAL_480p,
+};
+
+enum vi_video_format {
+	VI_FMT_NTSC = 0,
+	VI_FMT_PAL,
+	VI_FMT_MPAL,
+	VI_FMT_DEBUG,
+};
+
+enum vi_tv_mode_flags {
+	__PAL_COLOR,	/* vs NTSC_COLOR */
+	__PROGRESSIVE,	/* vs interlaced */
+};
+
+#define VI_VMF_PAL_COLOR	(1<<__PAL_COLOR)
+#define VI_VMF_PROGRESSIVE	(1<<__PROGRESSIVE)
+
+
+#define VI_VERT_ALIGN		0x1	/* in lines-1 */
+#define VI_HORZ_ALIGN		0xf	/* in pixels-1 */
+#define VI_HORZ_WORD_SIZE	32	/* bytes */
+
+
+/*
+ * Video mode timings.
+ */
+struct vi_mode_timings {
+	/* VERTICAL SETTINGS */
+
+	/*
+	 * NTSC 480i
+	 * 1 field = 262.5 lines (242.5 active, 20 blank)
+	 * 1 frame = 2 fields = 2 x 262.5 = 525 lines (485 active, 40 blank)
+	 *
+	 * PAL 576i
+	 * 1 field = 312.5 lines (287.5 active, 25 blank)
+	 * 1 frame = 2 fields = 2 x 312.5 = 625 lines (575 active, 50 blank)
+	 *
+	 * NOTES:
+	 * - the start of sync is considered the start of a line
+	 * - the width of a half line is the width of a line divided by two
+	 *
+	 */
+
+	/*
+	 * Vertical position of the first active video line (0=top).
+	 */
+	unsigned int ypos;
+
+	/*
+	 * Horizontal position in pixels where the vertical blanking
+	 * interval starts. Used for signaling the start of the vertical
+	 * retrace.
+	 */
+	unsigned int htrap;
+
+	/*
+	 * Vertical position in field lines where the vertical blanking
+	 * interval starts. Used for signaling the start of the vertical
+	 * retrace.
+	 */
+	unsigned int vtrap;
+
+	/*
+	 * Active Video, specified in number of field lines.
+	 */
+	u16	acv;
+	/*
+	 * Equalization pulse, specified in number of half lines.
+	 */
+	u8	equ;
+
+	/*
+	 * Pre-blanking, specified in half lines.
+	 */
+	u16	prb_odd;
+	u16	prb_even;
+
+	/*
+	 * Post-blanking, specified in half lines.
+	 */
+	u16	psb_odd;
+	u16	psb_even;
+
+	/*
+	 * NOTE:
+	 * Irrespective of what patent 6,609,977 says:
+	 * - "bs*" seems to tell where the burst blanking for the current
+	 *   field ends
+	 * - "be*" seems to tell where the next burst blanking starts
+	 */
+
+	/*
+	 * Patent says: "Start to burst blanking start in half lines".
+	 */
+	u8	bs1;
+	u8	bs2;
+	u8	bs3;
+	u8	bs4;
+
+	/*
+	 * Patent says: "Start to burst blanking end in half lines".
+	 */
+	u16	be1;
+	u16	be2;
+	u16	be3;
+	u16	be4;
+
+	/* HORIZONTAL SETTINGS */
+
+	/*
+	 * A = Blank Start to Horizontal Sync Start, "Front Porch"
+	 *     right_margin
+	 * B = Horizontal Sync Width
+	 *     hsync_len
+	 * C = Horizontal Sync End to Blank End, "Back Porch"
+	 *     left_margin
+	 * D = Horizontal Line Width
+	 *     hsync_len + left_margin + xres + right_margin
+	 * E = Horizontal Visible Width
+	 *     xres
+	 *
+	 *               :<-----------------D----------------->:
+	 *           :   :     :     :<----------E-------->:   :
+	 *           :<A>:<-B->:<-C->:                     :<A>:<-B->:<-C->:
+	 *           :   :     :     :                     :   :     :     :
+	 *        ___                 __________//_________                 __
+	 *           |               |                     |               |
+	 *           |               |                     |               |
+	 * Blank     |___       _____|                     |___       _____|
+	 *               |     |                               |     |
+	 * Sync          |_____|                               |_____|
+	 *
+	 *
+	 * f = Sync Start to Color Burst Start
+	 * g = Color Burst Width
+	 *
+	 *  :       :             :                  :
+	 *  :<--A-->:<-----B----->:<-------C-------->:
+	 *  :       :             :                  :
+	 *                                             _ Peak white level
+	 *  |                            Color       |
+	 *  |                            Burst       |
+	 *  |_______               ______|||||||||___| _ Blanking level
+	 *          | Sync        |      |||||||||
+	 *          |_____________|                    _ Sync level
+	 *
+	 *  :       :                    :       :   :
+	 *  :       :<---------f-------->:<--g-->:   :
+	 *  :<--------------- A + B + C ------------>:
+	 *  :                                        :
+	 *
+	 */
+
+	/* Half (horizontal) line width, in pixel clocks (D/2)  */
+	u16 hlw;
+
+	/* Horizontal Sync Width, in pixel clocks (B) */
+	u8 hsy;
+
+	/* NOTE
+	 * The color burst interval falls within the back porch,
+	 * i.e. hcs must be greater than B and hce lower than B+C.
+	 */
+
+	/* Horizontal sync start to color burst start in pixel clocks (f) */
+	u8 hcs;
+	/* Horizontal sync start to color burst end in pixel clocks (f+g) */
+	u8 hce;
+
+	/*
+	 * The following two settings depend on the effective horizontal
+	 * line length, as they rely on A or C.
+	 */
+
+	/* Half line to horizontal blank start (D/2 - A)*/
+	u16 hbs;
+
+	/* Horizontal sync start to horizontal blank end (B+C)*/
+	u16 hbe;
+
+};
+
+/*
+ * TV mode.
+ */
+struct vi_tv_mode {
+	char *name;
+	__u32 flags;
+	int width;		/* visible width in pixels */
+	int height;		/* visible height in lines */
+	int lines;		/* total lines */
+};
+
+/*
+ * Video control data structure.
+ */
+struct vi_ctl {
+	spinlock_t lock;
+
+	void __iomem *io_base;
+	unsigned int irq;
+
+	int in_vtrace;
+	wait_queue_head_t vtrace_waitq;
+
+	int visible_page;
+	unsigned long page_address[2];
+	unsigned long flip_pending;
+
+	struct vi_tv_mode *mode;
+	struct vi_mode_timings timings;
+	int has_component_cable:1;	/* at last detection time */
+
+	struct fb_info *info;
+#ifdef CONFIG_WII_AVE_RVL
+	struct i2c_client *i2c_client;
+#endif
+};
+
+
+/*
+ * TV Mode Table
+ */
+static struct vi_tv_mode vi_tv_modes[] = {
+	[VI_VM_NTSC_480i] = {
+		.name = "NTSC 480i",
+		.width = 640,
+		.height = 480,
+		.lines = 525,
+	},
+	[VI_VM_NTSC_480p] = {
+		.name = "NTSC 480p",
+		.flags = VI_VMF_PROGRESSIVE,
+		.width = 640,
+		.height = 480,
+		.lines = 525,
+	},
+	[VI_VM_PAL_576i50] = {
+		.name = "PAL 576i",
+		.flags = VI_VMF_PAL_COLOR,
+		.width = 640,
+		.height = 574,
+		.lines = 625,
+	},
+	[VI_VM_PAL_480i60] = {
+		.name = "PAL 480i 60Hz",
+		.flags = VI_VMF_PAL_COLOR,
+		.width = 640,
+		.height = 480,
+		.lines = 525,
+	},
+	[VI_VM_PAL_480p] = {
+		.name = "PAL 480p",
+		.flags = VI_VMF_PROGRESSIVE|VI_VMF_PAL_COLOR,
+		.width = 640,
+		.height = 480,
+		.lines = 525,
+	},
+};
+
+/*
+ * Filter Coeficient Table
+ */
+static const u32 vi_fct[] = {
+	0x1AE771F0, 0x0DB4A574, 0x00C1188E, 0xC4C0CBE2,
+	0xFCECDECF, 0x13130F08, 0x00080C0F,
+};
+
+
+/*
+ * Default fix and var framebuffer data.
+ */
+static struct fb_fix_screeninfo vifb_fix __devinitdata = {
+	.id = DRV_MODULE_NAME,
+	.type = FB_TYPE_PACKED_PIXELS,
+	.visual = FB_VISUAL_TRUECOLOR,	/* lies, lies, lies, ... */
+	.accel = FB_ACCEL_NONE,
+};
+
+static struct fb_var_screeninfo vifb_var = {
+	.activate = FB_ACTIVATE_NOW,
+	.width = 640,
+	.height = 480,
+	.bits_per_pixel = 16,
+	.vmode = FB_VMODE_INTERLACED,
+};
+
+
+/*
+ * Setup parameters.
+ */
+static int want_ypan = 1;		/* 0..nothing, 1..ypan */
+
+/* use old behaviour for video mode settings */
+static int nostalgic;
+
+static int force_scan;
+static int force_rate;
+static int force_tv;
+
+static u32 pseudo_palette[17];
+
+
+
+/*
+ *
+ *
+ */
+
+#ifdef CONFIG_WII_AVE_RVL
+static int vi_ave_setup(struct vi_ctl *ctl);
+static int vi_ave_get_video_format(struct vi_ctl *ctl,
+				   enum vi_video_format *fmt);
+#endif
+
+/* some glue to the gx side */
+static inline void gcngx_dispatch_vtrace(struct vi_ctl *ctl)
+{
+}
+
+
+/*
+ *
+ * Color space handling.
+ */
+
+/*
+ * RGB to YCbYCr conversion support bits.
+ * We are using here the ITU.BT-601 Y'CbCr standard.
+ *
+ * References:
+ * - "Colour Space Conversions" by Adrian Ford and Alan Roberts, 1998
+ *   (google for coloureq.pdf)
+ *
+ */
+
+#define RGB2YUV_SHIFT   16
+#define RGB2YUV_LUMA    16
+#define RGB2YUV_CHROMA 128
+
+#define Yr ((int)(0.299 * (1<<RGB2YUV_SHIFT)))
+#define Yg ((int)(0.587 * (1<<RGB2YUV_SHIFT)))
+#define Yb ((int)(0.114 * (1<<RGB2YUV_SHIFT)))
+
+#define Ur ((int)(-0.169 * (1<<RGB2YUV_SHIFT)))
+#define Ug ((int)(-0.331 * (1<<RGB2YUV_SHIFT)))
+#define Ub ((int)(0.500 * (1<<RGB2YUV_SHIFT)))
+
+#define Vr ((int)(0.500 * (1<<RGB2YUV_SHIFT)))	/* same as Ub */
+#define Vg ((int)(-0.419 * (1<<RGB2YUV_SHIFT)))
+#define Vb ((int)(-0.081 * (1<<RGB2YUV_SHIFT)))
+
+/*
+ * Converts two 16bpp rgb pixels into a dual yuy2 pixel.
+ */
+static inline uint32_t rgbrgb16toycbycr(uint16_t rgb1, uint16_t rgb2)
+{
+	register int Y1, Cb, Y2, Cr;
+	register int r1, g1, b1;
+	register int r2, g2, b2;
+	register int r, g, b;
+
+	/* fast path, thanks to bohdy */
+	if (!(rgb1 | rgb2))
+		return 0x00800080;	/* black, black */
+
+	/* RGB565 */
+	r1 = ((rgb1 >> 11) & 0x1f);
+	g1 = ((rgb1 >> 5) & 0x3f);
+	b1 = ((rgb1 >> 0) & 0x1f);
+
+	/* fast (approximated) scaling to 8 bits, thanks to Masken */
+	r1 = (r1 << 3) | (r1 >> 2);
+	g1 = (g1 << 2) | (g1 >> 4);
+	b1 = (b1 << 3) | (b1 >> 2);
+
+	Y1 = clamp(((Yr * r1 + Yg * g1 + Yb * b1) >> RGB2YUV_SHIFT)
+		   + RGB2YUV_LUMA, 16, 235);
+	if (rgb1 == rgb2) {
+		/* this is just another fast path */
+		Y2 = Y1;
+		r = r1;
+		g = g1;
+		b = b1;
+	} else {
+		/* same as we did for r1 before */
+		r2 = ((rgb2 >> 11) & 0x1f);
+		g2 = ((rgb2 >> 5) & 0x3f);
+		b2 = ((rgb2 >> 0) & 0x1f);
+		r2 = (r2 << 3) | (r2 >> 2);
+		g2 = (g2 << 2) | (g2 >> 4);
+		b2 = (b2 << 3) | (b2 >> 2);
+
+		Y2 = clamp(((Yr * r2 + Yg * g2 + Yb * b2) >> RGB2YUV_SHIFT)
+			   + RGB2YUV_LUMA,
+			   16, 235);
+
+		r = (r1 + r2) / 2;
+		g = (g1 + g2) / 2;
+		b = (b1 + b2) / 2;
+	}
+
+	Cb = clamp(((Ur * r + Ug * g + Ub * b) >> RGB2YUV_SHIFT)
+		   + RGB2YUV_CHROMA, 16, 240);
+	Cr = clamp(((Vr * r + Vg * g + Vb * b) >> RGB2YUV_SHIFT)
+		   + RGB2YUV_CHROMA, 16, 240);
+
+	return (((uint8_t) Y1) << 24) | (((uint8_t) Cb) << 16) |
+	    (((uint8_t) Y2) << 8) | (((uint8_t) Cr) << 0);
+}
+
+
+/*
+ * Video mode timings calculation.
+ *
+ * Please, refer to the definition of "struct vi_mode_timings" for
+ * a explanation of the different constants involved.
+ *
+ * References:
+ * - http://www.pembers.freeserve.co.uk/World-TV-Standards
+ */
+
+static inline int vi_vmode_is_progressive(__u32 vmode)
+{
+	return (vmode & FB_VMODE_MASK) == FB_VMODE_NONINTERLACED;
+}
+
+static int vi_calc_horz_timings(struct vi_mode_timings *timings,
+				 struct fb_var_screeninfo *var,
+				 u16 width, u16 max_active_width,
+				 u16 A, u8 B, u16 C, u16 D,
+				 u8 f, u16 g)
+{
+	u16 extra_blanking, margin;
+
+	if (width > max_active_width)
+		return -EINVAL;
+
+	/* adjusted horizontal settings */
+	extra_blanking = max_active_width - width;
+	margin = extra_blanking / 2;
+	A += margin;
+	C += extra_blanking - margin;
+
+	timings->hlw = D / 2;
+	timings->hsy = B;
+	timings->hcs = f;
+	timings->hce = f + g;
+	timings->hbs = (D/2) - A;
+	timings->hbe = B + C;
+
+	/*
+	 * Start of the blanking interval, between the first and second fields,
+	 * begins after the last half line of the field.
+	 */
+	timings->htrap = (D / 2) + 1;
+
+	var->left_margin = C;
+	var->right_margin = A;
+	var->hsync_len = B;
+
+	return 0;
+}
+
+static int vi_ntsc_525_calc_horz_timings(struct vi_mode_timings *timings,
+					  struct fb_var_screeninfo *var,
+					  u16 width)
+{
+	u16 max_active_width;
+	u16 A, C, D, g;
+	u8 B, f;
+
+	/* standard horizontal settings for 714 pixels */
+	D = 858;		/* pixel clocks (H=63.556us, 13.5MHz clock) */
+	max_active_width = 714;	/* (52.9us) 714.15 pixel clocks */
+	B = 64;			/* ( 4.7us)  63.45 pixel clocks */
+	f = 71;			/* ( 5.3us)  71.55 pixel clocks */
+	g = 34;			/* ( 2.5us)  33.75 pixel clocks */
+	A = 20;			/* ( 1.5us)  20.25 pixel clocks */
+	C = 60;			/* ( 4.5us)  60.75 pixel clocks */
+
+	return vi_calc_horz_timings(timings, var, width, max_active_width,
+				    A, B, C, D, f, g);
+}
+
+static int vi_calc_vert_timings(struct vi_mode_timings *timings,
+				struct fb_var_screeninfo *var,
+				u16 height, u16 max_active_height,
+				u16 P, u16 Q, u8 equ)
+{
+	u16 extra_blanking, margin, prb, psb;
+	u8 interlace_bias;
+	u8 shift;
+
+	if (height > max_active_height)
+		return -EINVAL;
+
+	extra_blanking = max_active_height - height;	/* in frame lines */
+	margin = extra_blanking / 2;			/* centered margins */
+	prb = margin; 					/* in half lines */
+	psb = extra_blanking - margin;			/* in half lines */
+
+	/*
+	 * Start of the blanking interval, between the first and second fields,
+	 * begins after the last line of the field.
+	 */
+	if (vi_vmode_is_progressive(var->vmode)) {
+		timings->acv = height;
+		timings->vtrap = prb + height;
+		interlace_bias = 0;
+		shift = 1;
+	} else {
+		timings->acv = height / 2;
+		timings->vtrap = (prb + height) / 2;
+		interlace_bias = 1;
+		shift = 0;
+	}
+
+	timings->equ = equ << shift;
+	var->vsync_len = (3 * timings->equ) / 2; /* pre-eq + sync + post-eq */
+
+	/*
+	 * prb_* is specified as the number of half-lines since the end of
+	 * the post-equalizing period.
+	 * psb_* is specified as the number of half-lines from the end of
+	 * the field.
+	 */
+
+	timings->ypos = margin;
+
+	if (timings->ypos & 0x01) {
+		/* odd field (1,3,5,...) */
+		timings->prb_odd = (P + interlace_bias + prb) << shift;
+		timings->psb_odd = (Q - interlace_bias + psb) << shift;
+		timings->prb_even = (P + prb) << shift;
+		timings->psb_even = (Q + psb) << shift;
+	} else {
+		/* even field (2,4,6,...) */
+		timings->prb_even = (P + interlace_bias + prb) << shift;
+		timings->psb_even = (Q - interlace_bias + psb) << shift;
+		timings->prb_odd = (P + prb) << shift;
+		timings->psb_odd = (Q + psb) << shift;
+	}
+
+	var->upper_margin = (Q + prb) / 2;
+	var->lower_margin = (P + psb) / 2;
+
+	return 0;
+}
+
+static int vi_ntsc_525_calc_vert_timings(struct vi_mode_timings *timings,
+					 struct fb_var_screeninfo *var,
+					 u16 height)
+{
+	u16 max_active_height;
+	u16 P, Q;
+	u8 equ;
+
+	/* standard vertical settings for 484 active lines */
+	max_active_height = 484;	/* 2 * 242.5 = 485 (*1) */
+
+	/* blanking interval */
+	/* from start of line 10, field 1 to end of line 20, field 1 */
+	P = 2 * (20-10 + 1);
+	Q = 1;	/* (*1) field line compensation for 484 vs 485 lines */
+
+	equ = 2 * 3;	/* 3 lines of equalization */
+
+	return vi_calc_vert_timings(timings, var, height, max_active_height,
+				    P, Q, equ);
+}
+
+static int vi_pal_625_calc_timings(struct vi_mode_timings *timings,
+				   struct fb_var_screeninfo *var,
+				   unsigned int width, unsigned int height)
+{
+	u16 max_active_height, max_active_width;
+	u16 A, C, D, g, P, Q;
+	u8 B, f, equ;
+	int error;
+
+	/* standard horizontal settings for 702 pixels */
+	D = 864;		/* pixel clocks (H=64us, 13.5MHz clock) */
+	max_active_width = 702; /* (51.95us) 701.32 pixel clocks */
+	B = 64;			/* ( 4.7us)   63.45 pixel clocks */
+	f = 75;			/* ( 5.6us)   75.6  pixel clocks */
+	g = 30;			/* ( 2.25us)  30.38 pixel clocks */
+	A = 22;			/* ( 1.65us)  22.27 pixel clocks */
+	C = 76;			/* ( 5.7us)   76.95 pixel clocks */
+
+	error = vi_calc_horz_timings(timings, var, width, max_active_width,
+				     A, B, C, D, f, g);
+	if (error)
+		goto err_out;
+
+	/* standard vertical settings for 574 active lines */
+	max_active_height = 574;	/* 2 * 287.5 = 575 (*1) */
+
+	/* blanking interval */
+	/* from start of line 6, field 1 to mid of line 23, field 1 */
+	P = (2 * (23-6 + 1)) - 1;
+	Q = 1;	/* (*1) field line compensation for 574 vs 575 lines */
+
+	equ = 2 * 2.5;		/* 2.5 lines of equalization */
+
+	error = vi_calc_vert_timings(timings, var, height, max_active_height,
+				     P, Q, equ);
+	if (error)
+		goto err_out;
+
+	/*
+	 * Location of the 9 lines of burst blanking for each field
+	 * (settings expressed in half lines).
+	 */
+
+	/* from start of line 1, field 1 to end of line 6, field 1 */
+	timings->bs1 = 2 * (6-1 + 1);
+
+	/* from start of line 1, field 1 to end of line 309, field 2 */
+	timings->be1 = 2 * (309-1 + 1);
+
+	/* from mid of line 313, field 2 to end of line 318, field 2 */
+	timings->bs2 = (2 * (318-313 + 1)) - 1;
+
+	/* from mid of line 313, field 2 to end of line 621, field 2 */
+	timings->be2 = (2 * (612-617 + 1)) - 1;
+
+	/* from start of line 1, field 3 to end of line 5, field 3 */
+	timings->bs3 = 2 * (5-1 + 1);
+
+	/* from start of line 1, field 3 to end of line 310, field 4 */
+	timings->be3 = 2 * (310-1 + 1);
+
+	/* from mid of line 313, field 4 to end of line 319, field 4 */
+	timings->bs4 = (2 * (319-313 + 1)) - 1;
+
+	/* from mid of line 313, field 4 to end of line 622, field 4 */
+	timings->be4 = (2 * (622-313 + 1)) - 1;
+
+err_out:
+	return error;
+}
+
+static int vi_ntsc_525_calc_timings(struct vi_mode_timings *timings,
+				    struct fb_var_screeninfo *var,
+				    unsigned int width, unsigned int height)
+{
+	int error;
+
+	error = vi_ntsc_525_calc_horz_timings(timings, var, width);
+	if (error)
+		goto err_out;
+
+	error = vi_ntsc_525_calc_vert_timings(timings, var, height);
+	if (error)
+		goto err_out;
+
+	/*
+	 * Location of the 9 lines of burst blanking for each field
+	 * (settings expressed in half lines).
+	 */
+
+	/* from start of line 4, field 1 to end of line 9, field 1 */
+	timings->bs1 = 2 * (9-4 + 1);
+
+	/* from start of line 4, field 1 to end of line 263, field 2 */
+	timings->be1 = 2 * (263-4 + 1);
+
+	/* from mid of line 266, field 2 to end of line 272, field 2 */
+	timings->bs2 = (2 * (272-266 + 1)) - 1;
+
+	/* from mid of line 266, field 2 to end of line 525, field 2 */
+	timings->be2 = (2 * (525-266 + 1)) - 1;
+
+	/* from start of line 4, field 3 to end of line 9, field 3 */
+	timings->bs3 = 2 * (9-4 + 1);
+
+	/* from start of line 4, field 3 to end of line 263, field 4 */
+	timings->be3 = 2 * (263-4 + 1);
+
+	/* from mid of line 266, field 4 to end of line 272, field 4 */
+	timings->bs4 = (2 * (272-266 + 1)) - 1;
+
+	/* from mid of line 266, field 4 to end of line 525, field 4 */
+	timings->be4 = (2 * (525-266 + 1)) - 1;
+
+err_out:
+	return error;
+}
+
+static int vi_ntsc_525_prog_calc_timings(struct vi_mode_timings *timings,
+					 struct fb_var_screeninfo *var,
+					 unsigned int width,
+					 unsigned int height)
+{
+	int error;
+
+	error = vi_ntsc_525_calc_horz_timings(timings, var, width);
+	if (error)
+		goto err_out;
+
+	error = vi_ntsc_525_calc_vert_timings(timings, var, height);
+	if (error)
+		goto err_out;
+
+	/*
+	 * Location of the 18 lines of burst blanking
+	 * (settings expressed in half lines).
+	 */
+
+	/*
+	 * |0 0 0 0 0 0|0 0 0 1 1 1|1 1 1 1 1 1|
+	 * |1,2,3,4,5,6|7,8,9,0,1,2|3,4,5,6,7,8|
+	 * :pre-equ    :sync       : post-equ  :
+	 */
+
+	/* from start of line 7 to end of line 18 */
+	timings->bs1 = 2 * (18-7 + 1);
+	timings->bs2 = timings->bs3 = timings->bs4 = timings->bs1;
+
+	/* from start of line 7 to end of line 525 (last) */
+	timings->be1 = 2 * (525-7 + 1);
+	timings->be2 = timings->be3 = timings->be4 = timings->be1;
+
+err_out:
+	return error;
+}
+
+
+
+/*
+ * Video hardware support.
+ *
+ */
+
+static inline int vi_has_component_cable(struct vi_ctl *ctl)
+{
+	return vi_sel_get_component(in_be16(ctl->io_base + VI_SEL));
+}
+
+/*
+ * Get video mode reported by hardware.
+ * 0=NTSC, 1=PAL, 2=MPAL, 3=debug
+ */
+static inline enum vi_video_format vi_get_video_format(struct vi_ctl *ctl)
+{
+	return vi_dcr_get_fmt(in_be16(ctl->io_base + VI_DCR));
+}
+
+static inline int vi_video_format_is_ntsc(struct vi_ctl *ctl)
+{
+	return vi_get_video_format(ctl) == VI_FMT_NTSC;
+}
+
+static void vi_reset_video(struct vi_ctl *ctl)
+{
+	void __iomem *io_base = ctl->io_base;
+	u16 dcr;
+
+	dcr = in_be16(io_base + VI_DCR);
+	out_be16(io_base + VI_DCR, vi_dcr_set_rst(dcr, 1));
+	out_be16(io_base + VI_DCR, vi_dcr_clear_rst(dcr));
+}
+
+/*
+ * Try to determine current TV video mode.
+ */
+static void vi_detect_tv_mode(struct vi_ctl *ctl)
+{
+	struct vi_tv_mode *modes = vi_tv_modes, *mode;
+	void __iomem *io_base = ctl->io_base;
+	char *guess = "";
+	enum vi_video_format fmt;
+	int ntsc_idx, pal_idx;
+	u16 dcr;
+	int error;
+
+	dcr = in_be16(io_base + VI_DCR);
+
+	ctl->has_component_cable = vi_has_component_cable(ctl);
+
+	if ((force_scan == VI_SCAN_PROGRESSIVE &&
+					ctl->has_component_cable) ||
+	    (force_scan != VI_SCAN_INTERLACED &&
+					ctl->has_component_cable &&
+					vi_dcr_get_nin(dcr))) {
+		/* progressive modes */
+		ntsc_idx = VI_VM_NTSC_480p;
+		pal_idx = VI_VM_PAL_480p;
+	} else {
+		/* interlaced modes */
+		ntsc_idx = VI_VM_NTSC_480i;
+		if (force_rate == VI_RATE_50Hz ||
+		    (force_rate != VI_RATE_60Hz &&
+		     vi_dcr_get_fmt(dcr) == VI_FMT_PAL))
+			pal_idx = VI_VM_PAL_576i50;
+		else
+			pal_idx = VI_VM_PAL_480i60;
+	}
+
+	if (force_tv == VI_TV_PAL ||
+	    (force_tv != VI_TV_NTSC && pal_idx == VI_VM_PAL_576i50))
+		fmt = VI_FMT_PAL;
+	else if (force_tv == VI_TV_NTSC)
+		fmt = VI_FMT_NTSC;
+	else {
+#ifdef CONFIG_WII_AVE_RVL
+		/*
+		 * Look at the audio/video encoder to detect true PAL vs NTSC.
+		 */
+		error = vi_ave_get_video_format(ctl, &fmt);
+		if (error) {
+			guess = " (initial guess)";
+			if (force_tv == VI_TV_PAL ||
+			    pal_idx == VI_VM_PAL_576i50)
+				fmt = VI_FMT_PAL;
+			else
+				fmt = VI_FMT_NTSC;
+		}
+#else
+		error = 0;
+		fmt = vi_get_video_format(ctl);
+#endif
+	}
+	switch (fmt) {
+	case VI_FMT_PAL:
+		mode = modes + pal_idx;
+		break;
+	case VI_FMT_MPAL:
+	case VI_FMT_DEBUG:
+		/* we currently don't support MPAL or DEBUG, sorry */
+		/* FALLTHROUGH */
+	case VI_FMT_NTSC:
+	default:
+		mode = modes + ntsc_idx;
+		break;
+	}
+
+	ctl->mode = mode;
+
+	drv_printk(KERN_INFO, "%s%s\n", mode->name, guess);
+}
+
+/*
+ * Initialize the video hardware for a given TV mode.
+ */
+static int vi_setup_tv_mode(struct vi_ctl *ctl)
+{
+	void __iomem *io_base = ctl->io_base;
+	struct vi_mode_timings *timings = &ctl->timings;
+	struct fb_var_screeninfo *var = &ctl->info->var;
+	unsigned int bytes_per_pixel = var->bits_per_pixel / 8;
+	struct vi_tv_mode *mode;
+	int has_component_cable;
+	u16 std, ppl;
+
+	/* we need to re-detect the tv mode if the cable type changes */
+	has_component_cable = vi_has_component_cable(ctl);
+	if ((ctl->has_component_cable && !has_component_cable) ||
+	    (!ctl->has_component_cable && has_component_cable))
+		vi_detect_tv_mode(ctl);
+
+	mode = ctl->mode;
+
+	out_be16(io_base + VI_DCR,
+		 vi_dcr_fmt((mode->lines == 625) ? VI_FMT_PAL : VI_FMT_NTSC) |
+		 vi_dcr_nin((mode->flags & VI_VMF_PROGRESSIVE) ?  1 : 0) |
+		 vi_dcr_enb(1));
+
+	out_be16(io_base + VI_VTR,
+		 vi_vtr_equ(timings->equ) | vi_vtr_acv(timings->acv));
+
+	out_be32(io_base + VI_HTR0,
+		 vi_htr0_hcs(timings->hcs) | vi_htr0_hce(timings->hce) |
+		 vi_htr0_hlw(timings->hlw));
+
+	out_be32(io_base + VI_HTR1,
+		 vi_htr1_hbs(timings->hbs) | vi_htr1_hbe(timings->hbe) |
+		 vi_htr1_hsy(timings->hsy));
+
+	out_be32(io_base + VI_VTO,
+		 vi_vto_prb(timings->prb_odd) | vi_vto_psb(timings->psb_odd));
+
+	out_be32(io_base + VI_VTE,
+		 vi_vte_prb(timings->prb_even) | vi_vte_psb(timings->psb_even));
+
+	out_be32(io_base + VI_BBOI,
+		 vi_bboi_bs1(timings->bs1) | vi_bboi_be1(timings->be1) |
+		 vi_bboi_bs3(timings->bs3) | vi_bboi_be3(timings->be3));
+
+	out_be32(io_base + VI_BBEI,
+		 vi_bbei_bs2(timings->bs2) | vi_bbei_be2(timings->be2) |
+		 vi_bbei_bs4(timings->bs4) | vi_bbei_be4(timings->be4));
+
+	/* used only for 3D stuff */
+	out_be32(io_base + VI_TFBR, 0);
+	out_be32(io_base + VI_BFBR, 0);
+
+	std = (var->xres_virtual * bytes_per_pixel) / VI_HORZ_WORD_SIZE;
+	if (!(mode->flags & VI_VMF_PROGRESSIVE))
+		std *= 2;
+	ppl = _ALIGN_UP((var->xoffset & VI_HORZ_ALIGN) + var->xres,
+			VI_HORZ_ALIGN+1);
+	out_be16(io_base + VI_PCR,
+		 vi_pcr_std(std) |
+		 vi_pcr_wpl((ppl * bytes_per_pixel) / VI_HORZ_WORD_SIZE));
+
+	/* scaler is disabled */
+	out_be16(io_base + VI_HSR, vi_hsr_stp(256) | vi_hsr_hs_en(0));
+
+	/* filter coeficient table, anti-aliasing */
+	out_be32(io_base + VI_FCT0, vi_fct[0]);
+	out_be32(io_base + VI_FCT1, vi_fct[1]);
+	out_be32(io_base + VI_FCT2, vi_fct[2]);
+	out_be32(io_base + VI_FCT3, vi_fct[3]);
+	out_be32(io_base + VI_FCT4, vi_fct[4]);
+	out_be32(io_base + VI_FCT5, vi_fct[5]);
+	out_be32(io_base + VI_FCT6, vi_fct[6]);
+	out_be32(io_base + VI_AA, 0x00ff0000);
+
+	/* clock */
+	out_be16(io_base + VI_CLK,
+		 vi_clk__54mhz((mode->flags & VI_VMF_PROGRESSIVE) ? 1 : 0));
+
+	/* superfluous, no scaler */
+	out_be16(io_base + VI_HSW, vi_hsw_width(var->xres));
+
+	/* borders for DEBUG mode encoder, not used in retail consoles */
+	out_be16(io_base + VI_HBE, 0);
+	out_be16(io_base + VI_HBS, 0);
+
+	/* whatever */
+	out_be16(io_base + VI_UNK1, 0x00ff);
+	out_be32(io_base + VI_UNK2, 0x00ff00ff);
+	out_be32(io_base + VI_UNK3, 0x00ff00ff);
+
+#ifdef CONFIG_WII_AVE_RVL
+	vi_ave_setup(ctl);
+#endif
+
+	return 0;
+}
+
+/*
+ * Set the address from where the video encoder will display data on screen.
+ */
+void vi_set_framebuffer(struct vi_ctl *ctl, u32 addr)
+{
+	struct fb_info *info = ctl->info;
+	void __iomem *io_base = ctl->io_base;
+	u32 top, bot;
+	u8 xof;
+
+	top = bot = addr;
+	if (!vi_vmode_is_progressive(info->var.vmode)) {
+		if (ctl->timings.ypos & 0x01)
+			top += info->fix.line_length;
+		else
+			bot += info->fix.line_length;
+	}
+	xof = (top / 2) & VI_HORZ_ALIGN;
+
+	out_be32(io_base + VI_TFBL,
+		 vi_tfbl_pob(1) | vi_tfbl_xof(xof) | vi_tfbl_fba(top >> 5));
+	out_be32(io_base + VI_BFBL, vi_bfbl_pob(1) | vi_bfbl_fba(bot >> 5));
+}
+
+/*
+ * Swap the visible and back pages.
+ */
+static inline void vi_flip_page(struct vi_ctl *ctl)
+{
+	ctl->visible_page ^= 1;
+	vi_set_framebuffer(ctl, ctl->page_address[ctl->visible_page]);
+
+	ctl->flip_pending = 0;
+}
+
+static void vi_enable_interrupts(struct vi_ctl *ctl, int enable)
+{
+	void __iomem *io_base = ctl->io_base;
+
+	if (enable) {
+		/*
+		 * We use DI0 and DI1 to signal the retrace interval.
+		 */
+
+		/* start of the vertical retrace */
+		out_be32(io_base + VI_DI1,
+			 vi_dix_irq(1) | vi_dix_enb(1) |
+			 vi_dix_vct(ctl->timings.vtrap) |
+			 vi_dix_hct(ctl->timings.htrap));
+
+		/* end of the vertical retrace */
+		out_be32(io_base + VI_DI0,
+			 vi_dix_irq(1) | vi_dix_enb(1) |
+			 vi_dix_vct(1) |
+			 vi_dix_hct(1));
+	} else {
+		out_be32(io_base + VI_DI0, 0);
+		out_be32(io_base + VI_DI1, 0);
+	}
+	/* these two are currently not used */
+	out_be32(io_base + VI_DI2, 0);
+	out_be32(io_base + VI_DI3, 0);
+}
+
+static void vi_dispatch_vtrace(struct vi_ctl *ctl)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&ctl->lock, flags);
+	if (ctl->flip_pending)
+		vi_flip_page(ctl);
+	spin_unlock_irqrestore(&ctl->lock, flags);
+
+	wake_up_interruptible(&ctl->vtrace_waitq);
+}
+
+static irqreturn_t vi_irq_handler(int irq, void *dev)
+{
+	struct fb_info *info = dev_get_drvdata((struct device *)dev);
+	struct vi_ctl *ctl = info->par;
+	void __iomem *io_base = ctl->io_base;
+	u32 val;
+
+	/* DI0 and DI1 are used to account for the vertical retrace */
+	val = in_be32(io_base + VI_DI0);
+	if (vi_dix_get_irq(val)) {
+		ctl->in_vtrace = 0;
+		gcngx_dispatch_vtrace(ctl); /* backwards compatibility */
+
+		out_be32(io_base + VI_DI0, vi_dix_clear_irq(val));
+		return IRQ_HANDLED;
+	}
+	val = in_be32(io_base + VI_DI1);
+	if (vi_dix_get_irq(val)) {
+		ctl->in_vtrace = 1;
+		vi_dispatch_vtrace(ctl);
+		gcngx_dispatch_vtrace(ctl); /* backwards compatibility */
+
+		out_be32(io_base + VI_DI1, vi_dix_clear_irq(val));
+		return IRQ_HANDLED;
+	}
+
+	/* currently unused, just in case */
+	val = in_be32(io_base + VI_DI2);
+	if (vi_dix_get_irq(val)) {
+		out_be32(io_base + VI_DI2, vi_dix_clear_irq(val));
+		return IRQ_HANDLED;
+	}
+	val = in_be32(io_base + VI_DI3);
+	if (vi_dix_get_irq(val)) {
+		out_be32(io_base + VI_DI3, vi_dix_clear_irq(val));
+		return IRQ_HANDLED;
+	}
+
+	return IRQ_NONE;
+}
+
+#ifdef CONFIG_WII_AVE_RVL
+
+/*
+ * Audio/Video Encoder hardware support.
+ *
+ */
+
+/*
+ * I/O accessors.
+ */
+
+static int vi_ave_outs(struct i2c_client *client, u8 reg,
+		       void *data, size_t len)
+{
+	struct i2c_adapter *adap = client->adapter;
+	struct i2c_msg msg[1];
+	u8 buf[34];
+	s32 result;
+	int error = -EINVAL;
+
+	if (len > sizeof(buf)-1)
+		goto err_out;
+
+	buf[0] = reg;
+	memcpy(&buf[1], data, len);
+
+	msg[0].addr = client->addr;
+	msg[0].flags = client->flags & I2C_M_TEN;
+	msg[0].len = len+1;
+	msg[0].buf = buf;
+
+	result = i2c_transfer(adap, msg, 1);
+	if (result < 0)
+		error = result;
+	else if (result == 1)
+		error = 0;
+	else
+		error = -EIO;
+
+err_out:
+	if (error)
+		drv_printk(KERN_ERR, "RVL-AVE: "
+			   "error (%d) writing to register %02Xh\n",
+			   error, reg);
+	return error;
+}
+
+static int vi_ave_out8(struct i2c_client *client, u8 reg, u8 data)
+{
+	return vi_ave_outs(client, reg, &data, sizeof(data));
+}
+
+static int vi_ave_out16(struct i2c_client *client, u8 reg, u16 data)
+{
+	cpu_to_be16s(&data);
+	return vi_ave_outs(client, reg, &data, sizeof(data));
+}
+
+static int vi_ave_out32(struct i2c_client *client, u8 reg, u32 data)
+{
+	cpu_to_be32s(&data);
+	return vi_ave_outs(client, reg, &data, sizeof(data));
+}
+
+static int vi_ave_ins(struct i2c_client *client, u8 reg,
+		      void *data, size_t len)
+{
+	struct i2c_adapter *adap = client->adapter;
+	struct i2c_msg msg[2];
+	s32 result;
+	int error;
+
+	msg[0].addr = client->addr;
+	msg[0].flags = client->flags & I2C_M_TEN;
+	msg[0].len = sizeof(reg);
+	msg[0].buf = &reg;
+
+	msg[1].addr = client->addr;
+	msg[1].flags = (client->flags & I2C_M_TEN) | I2C_M_RD;
+	msg[1].len = len;
+	msg[1].buf = data;
+
+	result = i2c_transfer(adap, msg, 2);
+	if (result < 0)
+		error = result;
+	else if (result == 2)
+		error = 0;
+	else
+		error = -EIO;
+
+	if (error)
+		drv_printk(KERN_ERR, "RVL-AVE: "
+			   "error (%d) reading from register %02Xh\n",
+			   error, reg);
+
+	return error;
+}
+
+static int vi_ave_in8(struct i2c_client *client, u8 reg, u8 *data)
+{
+	return vi_ave_ins(client, reg, data, sizeof(*data));
+}
+
+
+/*
+ * Try to detect current video format.
+ */
+static int vi_ave_get_video_format(struct vi_ctl *ctl,
+				   enum vi_video_format *fmt)
+{
+	u8 val = 0xff;
+	int error = -ENODEV;
+
+	if (!ctl->i2c_client)
+		goto err_out;
+
+	error = vi_ave_in8(ctl->i2c_client, 0x01, &val);
+	if (error)
+		goto err_out;
+
+	if ((val & 0x1f) == 2)
+		*fmt = VI_FMT_PAL;
+	else
+		*fmt = VI_FMT_NTSC;
+err_out:
+	return error;
+}
+
+
+static u8 vi_ave_gamma[] = {
+	0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00,
+	0x10, 0x00, 0x10, 0x00, 0x10, 0x20, 0x40, 0x60,
+	0x80, 0xa0, 0xeb, 0x10, 0x00, 0x20, 0x00, 0x40,
+	0x00, 0x60, 0x00, 0x80, 0x00, 0xa0, 0x00, 0xeb,
+	0x00
+};
+
+/*
+ * Initialize the audio/video encoder.
+ */
+static int vi_ave_setup(struct vi_ctl *ctl)
+{
+	struct i2c_client *client;
+	u8 macrovision[26];
+	u8 component, format, pal60;
+
+	if (!ctl->i2c_client)
+		return -ENODEV;
+
+	client = ctl->i2c_client;
+	memset(macrovision, 0, sizeof(macrovision));
+
+	/*
+	 * Magic initialization sequence borrowed from libogc.
+	 */
+
+	vi_ave_out8(client, 0x6a, 1);
+	vi_ave_out8(client, 0x65, 1);
+
+	/*
+	 * NOTE
+	 * We _can't_ use the fmt field in DCR to derive "format" here.
+	 * DCR uses fmt=0 (NTSC) also for PAL 525 modes.
+	 */
+
+	format = 0;		/* default to NTSC */
+	if ((ctl->mode->flags & VI_VMF_PAL_COLOR) != 0)
+		format = 2;	/* PAL */
+	component = (ctl->has_component_cable) ? 1<<5 : 0;
+	vi_ave_out8(client, 0x01, component | format);
+
+	vi_ave_out8(client, 0x00, 0);
+	vi_ave_out16(client, 0x71, 0x8e8e);
+	vi_ave_out8(client, 0x02, 7);
+	vi_ave_out16(client, 0x05, 0x0000);
+	vi_ave_out16(client, 0x08, 0x0000);
+	vi_ave_out32(client, 0x7a, 0x00000000);
+	vi_ave_outs(client, 0x40, macrovision, sizeof(macrovision));
+	vi_ave_out8(client, 0x0a, 0);
+	vi_ave_out8(client, 0x03, 1);
+	vi_ave_outs(client, 0x10, vi_ave_gamma, sizeof(vi_ave_gamma));
+	vi_ave_out8(client, 0x04, 1);
+
+	vi_ave_out32(client, 0x7a, 0x00000000);
+	vi_ave_out16(client, 0x08, 0x0000);
+
+	vi_ave_out8(client, 0x03, 1);
+
+	/* clear bit 1 otherwise red and blue get swapped  */
+	if (ctl->has_component_cable)
+		vi_ave_out8(client, 0x62, 0);
+
+	/* PAL 480i/60 supposedly needs a "filter" */
+	pal60 = !!(format == 2 && ctl->mode->lines == 525);
+	vi_ave_out8(client, 0x6e, pal60);
+
+	return 0;
+}
+
+static struct vi_ctl *first_vi_ctl;
+static struct i2c_client *first_vi_ave;
+
+static int vi_attach_ave(struct vi_ctl *ctl, struct i2c_client *client)
+{
+	if (!ctl)
+		return -ENODEV;
+	if (!client)
+		return -EINVAL;
+
+	spin_lock(&ctl->lock);
+	if (!ctl->i2c_client) {
+		ctl->i2c_client = i2c_use_client(client);
+		spin_unlock(&ctl->lock);
+		drv_printk(KERN_INFO, "AVE-RVL support loaded\n");
+		return 0;
+	}
+	spin_unlock(&ctl->lock);
+	return -EBUSY;
+}
+
+static void vi_dettach_ave(struct vi_ctl *ctl)
+{
+	struct i2c_client *client;
+
+	if (!ctl)
+		return;
+
+	spin_lock(&ctl->lock);
+	if (ctl->i2c_client) {
+		client = ctl->i2c_client;
+		ctl->i2c_client = NULL;
+		spin_unlock(&ctl->lock);
+		i2c_release_client(client);
+		drv_printk(KERN_INFO, "AVE-RVL support unloaded\n");
+		return;
+	}
+	spin_unlock(&ctl->lock);
+}
+
+static int vi_ave_probe(struct i2c_client *client,
+			 const struct i2c_device_id *id)
+{
+	int error;
+
+	/* attach first a/v encoder to first framebuffer */
+	if (!first_vi_ave) {
+		first_vi_ave = client;
+		error = vi_attach_ave(first_vi_ctl, client);
+		if (!error) {
+			/* setup again the video mode using the a/v encoder */
+			vi_detect_tv_mode(first_vi_ctl);
+			vi_setup_tv_mode(first_vi_ctl);
+		}
+	}
+	return 0;
+}
+
+static int vi_ave_remove(struct i2c_client *client)
+{
+	if (first_vi_ave == client)
+		first_vi_ave = NULL;
+	return 0;
+}
+
+static const struct i2c_device_id vi_ave_id[] = {
+	{ "wii-ave-rvl", 0 },
+	{ }
+};
+
+static struct i2c_driver vi_ave_driver = {
+	.driver = {
+		.name	= DRV_MODULE_NAME,
+	},
+	.probe		= vi_ave_probe,
+	.remove		= vi_ave_remove,
+	.id_table	= vi_ave_id,
+};
+
+#endif /* CONFIG_WII_AVE_RVL */
+
+
+/*
+ * Linux framebuffer support routines.
+ *
+ */
+
+/*
+ * This is just a quick, dirty and cheap way of getting right colors on the
+ * linux framebuffer console.
+ */
+unsigned int vifb_writel(unsigned int rgbrgb, void *address)
+{
+	uint16_t *rgb = (uint16_t *)&rgbrgb;
+	return fb_writel_real(rgbrgb16toycbycr(rgb[0], rgb[1]), address);
+}
+
+static int vifb_setcolreg(unsigned regno, unsigned red, unsigned green,
+			   unsigned blue, unsigned transp, struct fb_info *info)
+{
+	/*
+	 *  Set a single color register. The values supplied are
+	 *  already rounded down to the hardware's capabilities
+	 *  (according to the entries in the `var' structure). Return
+	 *  != 0 for invalid regno.
+	 */
+
+	if (regno >= info->cmap.len)
+		return 1;
+
+	switch (info->var.bits_per_pixel) {
+	case 16:
+		if (info->var.red.offset == 10) {
+			/* 1:5:5:5, not used currently */
+			((u32 *) (info->pseudo_palette))[regno] =
+			    ((red & 0xf800) >> 1) |
+			    ((green & 0xf800) >> 6) | ((blue & 0xf800) >> 11);
+		} else {
+			/* 0:5:6:5 */
+			((u32 *) (info->pseudo_palette))[regno] =
+			    ((red & 0xf800)) |
+			    ((green & 0xfc00) >> 5) | ((blue & 0xf800) >> 11);
+		}
+		break;
+	case 8:
+	case 15:
+	case 24:
+	case 32:
+		break;
+	}
+	return 0;
+}
+
+/*
+ * Pan the display by altering the framebuffer address in hardware.
+ */
+static int vifb_pan_display(struct fb_var_screeninfo *var,
+			     struct fb_info *info)
+{
+	struct vi_ctl *ctl = info->par;
+	void __iomem *io_base = ctl->io_base;
+	unsigned int bytes_per_pixel = info->var.bits_per_pixel / 8;
+	unsigned long flags;
+	int offset;
+	u16 ppl;
+
+	ppl = _ALIGN_UP((var->xoffset & VI_HORZ_ALIGN) + var->xres,
+			VI_HORZ_ALIGN+1);
+	out_be16(io_base + VI_PCR,
+		 vi_pcr_set_wpl(in_be16(io_base + VI_PCR),
+				(ppl * bytes_per_pixel) / VI_HORZ_WORD_SIZE));
+
+	offset = (var->yoffset * info->fix.line_length) +
+		 var->xoffset * bytes_per_pixel;
+	vi_set_framebuffer(ctl, info->fix.smem_start + offset);
+
+	spin_lock_irqsave(&ctl->lock, flags);
+	if (info->fix.smem_start + offset >= ctl->page_address[1])
+		ctl->visible_page = 1;
+	else
+		ctl->visible_page = 0;
+	spin_unlock_irqrestore(&ctl->lock, flags);
+
+	return 0;
+}
+
+static int vifb_check_var_timings(struct fb_var_screeninfo *var,
+				  struct fb_info *info)
+{
+	struct vi_ctl *ctl = info->par;
+	struct vi_tv_mode *mode = ctl->mode;
+	struct vi_mode_timings timings;
+	u32 yres = var->yres;
+	int error = -EINVAL;
+
+	if (nostalgic && yres == 576)
+		yres = 574;
+
+	if (vi_vmode_is_progressive(var->vmode)) {
+		/* 480p */
+		error = vi_ntsc_525_prog_calc_timings(&timings, var,
+						      var->xres, var->yres);
+	} else {
+		if (mode->lines == 625)
+			/* 576i */
+			error = vi_pal_625_calc_timings(&timings, var,
+							var->xres, yres);
+		else
+			/* 480i */
+			error = vi_ntsc_525_calc_timings(&timings, var,
+							 var->xres, var->yres);
+	}
+	if (error)
+		goto err_out;
+
+	ctl->timings = timings;
+	var->pixclock = KHZ2PICOS(13.5 * 1000);
+	var->sync = FB_SYNC_BROADCAST;
+
+	error = 0;
+
+err_out:
+	return error;
+}
+
+/*
+ * Check var and eventually tweak it to something supported.
+ * Do not modify par here.
+ */
+static int vifb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
+{
+	struct vi_ctl *ctl = info->par;
+	struct vi_tv_mode *mode = ctl->mode;
+	int error = -EINVAL;
+	unsigned int bytes_per_pixel;
+	__u32 xres, yres, xres_virtual, yres_virtual;
+
+	/* we support only 16bpp */
+	if (var->bits_per_pixel != 16) {
+		drv_printk(KERN_ERR, "unsupported depth %u\n",
+				var->bits_per_pixel);
+		goto err_out;
+	}
+
+	yres = var->yres;
+	if (yres & VI_VERT_ALIGN)
+		yres = _ALIGN_UP(yres, VI_VERT_ALIGN+1);
+	if (yres > mode->height) {
+		if (!nostalgic) {
+			drv_printk(KERN_ERR, "yres %u out of bounds\n", yres);
+			goto err_out;
+		}
+		if (!(mode->height == 574 && yres == 576))
+			yres = mode->height;
+	}
+	if (yres < 16) {
+		/* XXX, fbcon will happily page fault for yres < 13 */
+		yres = 16;
+	}
+	if (!yres)
+		yres = mode->height;
+
+	yres_virtual = var->yres_virtual;
+	if (!yres_virtual || yres_virtual < yres)
+		yres_virtual = yres;
+
+	xres = var->xres;
+	if (xres & VI_HORZ_ALIGN)
+		xres = _ALIGN_UP(xres, VI_HORZ_ALIGN+1);
+	if (xres > mode->width) {
+		drv_printk(KERN_ERR, "xres %u out of bounds\n", var->xres);
+		goto err_out;
+	}
+	if (!xres)
+		xres = mode->width;
+
+	xres_virtual = var->xres_virtual;
+	if (xres_virtual & VI_HORZ_ALIGN)
+		xres_virtual = _ALIGN_UP(xres_virtual, VI_HORZ_ALIGN+1);
+	if (!xres_virtual || xres_virtual < xres)
+		xres_virtual = xres;
+
+	bytes_per_pixel = var->bits_per_pixel / 8;
+	if (xres_virtual * yres_virtual * bytes_per_pixel >
+		info->fix.smem_len) {
+		drv_printk(KERN_ERR, "not enough memory for virtual resolution"
+			   " (%ux%ux%u)\n",
+			   xres_virtual, yres_virtual,
+			   var->bits_per_pixel);
+		goto err_out;
+	}
+
+	var->xres = xres;
+	var->yres = yres;
+	var->xres_virtual = xres_virtual;
+	var->yres_virtual = yres_virtual;
+
+	var->xoffset = 0;
+	var->yoffset = 0;
+
+	var->grayscale = 0;
+
+	/* we support ony 16 bits per pixel */
+	var->red.offset = 11;
+	var->red.length = 5;
+	var->green.offset = 5;
+	var->green.length = 6;
+	var->blue.offset = 0;
+	var->blue.length = 5;
+	var->transp.offset = 0;
+	var->transp.length = 0;
+
+	var->nonstd = 0;	/* lies... */
+
+	/* enable non-interlaced mode if supported */
+	if (force_scan != VI_SCAN_INTERLACED && ctl->has_component_cable) {
+		var->vmode = (mode->flags & VI_VMF_PROGRESSIVE) ?
+					FB_VMODE_NONINTERLACED :
+					FB_VMODE_INTERLACED;
+	} else
+		var->vmode = FB_VMODE_INTERLACED;
+
+	error = vifb_check_var_timings(var, info);
+	if (error)
+		goto err_out;
+
+err_out:
+	return error;
+}
+
+/*
+ * Set the video mode according to info->var.
+ */
+static int vifb_set_par(struct fb_info *info)
+{
+	struct vi_ctl *ctl = info->par;
+	struct fb_var_screeninfo *var = &info->var;
+	unsigned long flags;
+
+	/* horizontal line in bytes */
+	info->fix.line_length = var->xres_virtual * (var->bits_per_pixel / 8);
+
+	ctl->page_address[0] = info->fix.smem_start;
+	if (var->yres * info->fix.line_length <= info->fix.smem_len / 2)
+		ctl->page_address[1] =
+		    info->fix.smem_start + var->yres * info->fix.line_length;
+	else
+		ctl->page_address[1] = info->fix.smem_start;
+
+	/* set page 0 as the visible page and cancel pending flips */
+	spin_lock_irqsave(&ctl->lock, flags);
+	ctl->visible_page = 1;
+	vi_flip_page(ctl);
+	spin_unlock_irqrestore(&ctl->lock, flags);
+
+	info->flags = FBINFO_FLAG_DEFAULT;
+	if (want_ypan) {
+		info->fix.xpanstep = 2;
+		info->fix.ypanstep = 1;
+		info->flags |= FBINFO_HWACCEL_YPAN;
+	} else {
+		info->fix.xpanstep = 0;
+		info->fix.ypanstep = 0;
+	}
+
+	vi_setup_tv_mode(ctl);
+
+	/* enable the video retrace handling */
+	vi_enable_interrupts(ctl, 1);
+
+	return 0;
+}
+
+static int vifb_mmap(struct fb_info *info, struct vm_area_struct *vma)
+{
+	unsigned long off;
+	unsigned long start;
+	u32 len;
+
+	off = vma->vm_pgoff << PAGE_SHIFT;
+
+	/* frame buffer memory */
+	start = info->fix.smem_start;
+	len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.smem_len);
+	start &= PAGE_MASK;
+	if ((vma->vm_end - vma->vm_start + off) > len)
+		return -EINVAL;
+	off += start;
+	vma->vm_pgoff = off >> PAGE_SHIFT;
+
+	/* this is an IO map, tell maydump to skip this VMA */
+	vma->vm_flags |= VM_IO | VM_RESERVED;
+
+	/* we share RAM between the cpu and the video hardware */
+	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+
+	if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
+				vma->vm_end - vma->vm_start,
+				vma->vm_page_prot))
+		return -EAGAIN;
+	return 0;
+}
+
+static int vifb_ioctl(struct fb_info *info,
+		       unsigned int cmd, unsigned long arg)
+{
+	struct vi_ctl *ctl = info->par;
+	void __user *argp;
+	unsigned long flags;
+	int page;
+
+	switch (cmd) {
+	case FBIOWAITRETRACE:
+		interruptible_sleep_on(&ctl->vtrace_waitq);
+		return signal_pending(current) ? -EINTR : 0;
+	case FBIOFLIPHACK:
+		/*
+		 * If arg == NULL then
+		 *   Try to flip the video page as soon as possible.
+		 *   Returns the current visible video page number.
+		 */
+		if (!arg) {
+			spin_lock_irqsave(&ctl->lock, flags);
+			if (ctl->in_vtrace)
+				vi_flip_page(ctl);
+			else
+				ctl->flip_pending = 1;
+			spin_unlock_irqrestore(&ctl->lock, flags);
+			return ctl->visible_page;
+		}
+
+		/*
+		 * If arg != NULL then
+		 *   Wait until the video page number pointed by arg
+		 *   is not visible.
+		 *   Returns the current visible video page number.
+		 */
+		argp = (void __user *)arg;
+		if (copy_from_user(&page, argp, sizeof(int)))
+			return -EFAULT;
+
+		if (page != 0 && page != 1)
+			return -EINVAL;
+
+		spin_lock_irqsave(&ctl->lock, flags);
+		ctl->flip_pending = 0;
+		if (ctl->visible_page == page) {
+			if (ctl->in_vtrace) {
+				vi_flip_page(ctl);
+			} else {
+				ctl->flip_pending = 1;
+				spin_unlock_irqrestore(&ctl->lock, flags);
+				interruptible_sleep_on(&ctl->vtrace_waitq);
+				return signal_pending(current) ?
+					-EINTR : ctl->visible_page;
+			}
+		}
+		spin_unlock_irqrestore(&ctl->lock, flags);
+		return ctl->visible_page;
+	}
+	return -EINVAL;
+}
+
+
+struct fb_ops vifb_ops = {
+	.owner = THIS_MODULE,
+	.fb_setcolreg = vifb_setcolreg,
+	.fb_pan_display = vifb_pan_display,
+	.fb_ioctl = vifb_ioctl,
+	.fb_set_par = vifb_set_par,
+	.fb_check_var = vifb_check_var,
+	.fb_mmap = vifb_mmap,
+	.fb_fillrect = cfb_fillrect,
+	.fb_copyarea = cfb_copyarea,
+	.fb_imageblit = cfb_imageblit,
+};
+
+/*
+ * Driver model helper routines.
+ *
+ */
+
+static int __devinit vifb_do_probe(struct device *dev,
+			 struct resource *mem, unsigned int irq,
+			 unsigned long xfb_start, unsigned long xfb_size)
+{
+	struct fb_info *info;
+	struct vi_ctl *ctl;
+	int video_cmap_len;
+	int error = -EINVAL;
+
+	info = framebuffer_alloc(sizeof(struct vi_ctl), dev);
+	if (!info)
+		goto err_framebuffer_alloc;
+
+	info->fbops = &vifb_ops;
+	info->var = vifb_var;
+	info->fix = vifb_fix;
+
+	ctl = info->par;
+	ctl->info = info;
+
+	/* first things first */
+	ctl->io_base = ioremap(mem->start, mem->end - mem->start + 1);
+	ctl->irq = irq;
+
+	/*
+	 * Location and size of the external framebuffer.
+	 */
+	info->fix.smem_start = xfb_start;
+	info->fix.smem_len = xfb_size;
+
+	if (!request_mem_region(info->fix.smem_start, info->fix.smem_len,
+				DRV_MODULE_NAME)) {
+		drv_printk(KERN_WARNING,
+			   "failed to request video memory at %p\n",
+			   (void *)info->fix.smem_start);
+	}
+
+	info->screen_base = ioremap(info->fix.smem_start, info->fix.smem_len);
+	if (!info->screen_base) {
+		drv_printk(KERN_ERR,
+			   "failed to ioremap video memory at %p (%dk)\n",
+			   (void *)info->fix.smem_start,
+			   info->fix.smem_len / 1024);
+		error = -EIO;
+		goto err_ioremap;
+	}
+
+	spin_lock_init(&ctl->lock);
+	init_waitqueue_head(&ctl->vtrace_waitq);
+
+	vi_reset_video(ctl);
+	vi_detect_tv_mode(ctl);
+
+	if (!nostalgic) {
+		/* by default, start with overscan compensation */
+		info->var.xres = 576;
+		if (ctl->mode->height == 574)
+			info->var.yres = 516;
+		else
+			info->var.yres = 432;
+	} else {
+		info->var.xres = ctl->mode->width;
+		info->var.yres = ctl->mode->height;
+	}
+
+	ctl->visible_page = 0;
+	ctl->flip_pending = 0;
+
+	drv_printk(KERN_INFO,
+		   "framebuffer at 0x%p, mapped to 0x%p, size %dk\n",
+		   (void *)info->fix.smem_start, info->screen_base,
+		   info->fix.smem_len / 1024);
+
+	video_cmap_len = 16;
+	info->pseudo_palette = pseudo_palette;
+	if (fb_alloc_cmap(&info->cmap, video_cmap_len, 0)) {
+		error = -ENOMEM;
+		goto err_alloc_cmap;
+	}
+
+	error = vifb_check_var(&info->var, info);
+	if (error)
+		goto err_check_var;
+
+	drv_printk(KERN_INFO, "mode is %dx%dx%d\n", info->var.xres,
+		   info->var.yres, info->var.bits_per_pixel);
+
+	dev_set_drvdata(dev, info);
+
+	vi_enable_interrupts(ctl, 0);
+
+	error = request_irq(ctl->irq, vi_irq_handler, 0, DRV_MODULE_NAME, dev);
+	if (error) {
+		drv_printk(KERN_ERR, "unable to register IRQ %u\n", ctl->irq);
+		goto err_request_irq;
+	}
+
+	/* now register us */
+	if (register_framebuffer(info) < 0) {
+		error = -EINVAL;
+		goto err_register_framebuffer;
+	}
+
+#ifdef CONFIG_WII_AVE_RVL
+	if (!first_vi_ctl)
+		first_vi_ctl = ctl;
+
+	/* try to attach the a/v encoder now */
+	vi_attach_ave(ctl, first_vi_ave);
+#endif
+
+	printk(KERN_INFO "fb%d: %s frame buffer device\n",
+	       info->node, info->fix.id);
+
+	return 0;
+
+err_register_framebuffer:
+	free_irq(ctl->irq, 0);
+err_check_var:
+err_request_irq:
+	fb_dealloc_cmap(&info->cmap);
+err_alloc_cmap:
+	iounmap(info->screen_base);
+err_ioremap:
+	release_mem_region(info->fix.smem_start, info->fix.smem_len);
+
+	dev_set_drvdata(dev, NULL);
+	iounmap(ctl->io_base);
+	framebuffer_release(info);
+err_framebuffer_alloc:
+	return error;
+}
+
+static int __devexit vifb_do_remove(struct device *dev)
+{
+	struct fb_info *info = dev_get_drvdata(dev);
+	struct vi_ctl *ctl = info->par;
+
+	if (!info)
+		return -ENODEV;
+
+	free_irq(ctl->irq, dev);
+	unregister_framebuffer(info);
+	fb_dealloc_cmap(&info->cmap);
+	iounmap(info->screen_base);
+	release_mem_region(info->fix.smem_start, info->fix.smem_len);
+
+	dev_set_drvdata(dev, NULL);
+	iounmap(ctl->io_base);
+
+#ifdef CONFIG_WII_AVE_RVL
+	vi_dettach_ave(ctl);
+	if (first_vi_ctl == ctl)
+		first_vi_ctl = NULL;
+#endif
+	framebuffer_release(info);
+	return 0;
+}
+
+static int vifb_do_shutdown(struct device *dev)
+{
+	struct fb_info *info = dev_get_drvdata(dev);
+	struct vi_ctl *ctl = info->par;
+	void __iomem *io_base = ctl->io_base;
+
+	vi_enable_interrupts(ctl, 0);
+	vi_reset_video(ctl);
+	out_be16(io_base + VI_DCR, vi_dcr_enb(0));
+
+	return 0;
+}
+
+#ifndef MODULE
+
+static int __devinit vifb_setup(char *options)
+{
+	char *this_opt;
+
+	if (!options || !*options)
+		return 0;
+
+	drv_printk(KERN_INFO, "options: %s\n", options);
+
+	while ((this_opt = strsep(&options, ",")) != NULL) {
+		if (!*this_opt)
+			continue;
+
+		if (!strcmp(this_opt, "redraw"))
+			want_ypan = 0;
+		else if (!strcmp(this_opt, "interlaced"))
+			force_scan = VI_SCAN_INTERLACED;
+		else if (!strcmp(this_opt, "progressive"))
+			force_scan = VI_SCAN_PROGRESSIVE;
+		else if (!strcmp(this_opt, "50Hz"))
+			force_rate = VI_RATE_50Hz;
+		else if (!strcmp(this_opt, "60Hz"))
+			force_rate = VI_RATE_60Hz;
+		else if (!strncmp(this_opt, "tv=", 3)) {
+			if (!strncmp(this_opt + 3, "PAL", 3))
+				force_tv = VI_TV_PAL;
+			else if (!strncmp(this_opt + 3, "NTSC", 4))
+				force_tv = VI_TV_NTSC;
+		} else if (!strcmp(this_opt, "nostalgic"))
+			nostalgic = 1;
+	}
+
+	if (force_scan == VI_SCAN_PROGRESSIVE || force_tv == VI_TV_NTSC) {
+		if (force_rate == VI_RATE_50Hz) {
+			drv_printk(KERN_INFO, "ignoring forced 50Hz setting\n");
+			force_rate = VI_RATE_DONTCARE;
+		}
+	}
+	return 0;
+}
+
+#endif	/* MODULE */
+
+
+/*
+ * OF platform driver hooks.
+ *
+ */
+
+static int __init vifb_of_probe(struct of_device *odev,
+				 const struct of_device_id *match)
+{
+	struct resource res;
+	const unsigned long *prop;
+	unsigned long xfb_start, xfb_size;
+	int retval;
+
+	retval = of_address_to_resource(odev->node, 0, &res);
+	if (retval) {
+		drv_printk(KERN_ERR, "no io memory range found\n");
+		return -ENODEV;
+	}
+
+	prop = of_get_property(odev->node, "xfb-start", NULL);
+	if (!prop) {
+		drv_printk(KERN_ERR, "no xfb start found\n");
+		return -ENODEV;
+	}
+	xfb_start = *prop;
+
+	prop = of_get_property(odev->node, "xfb-size", NULL);
+	if (!prop) {
+		drv_printk(KERN_ERR, "no xfb size found\n");
+		return -ENODEV;
+	}
+	xfb_size = *prop;
+
+	return vifb_do_probe(&odev->dev,
+			     &res, irq_of_parse_and_map(odev->node, 0),
+			     xfb_start, xfb_size);
+}
+
+static int __exit vifb_of_remove(struct of_device *odev)
+{
+	return vifb_do_remove(&odev->dev);
+}
+
+static int vifb_of_shutdown(struct of_device *odev)
+{
+	return vifb_do_shutdown(&odev->dev);
+}
+
+
+static struct of_device_id vifb_of_match[] = {
+	{ .compatible = "nintendo,flipper-video", },
+	{ .compatible = "nintendo,hollywood-video", },
+	{ },
+};
+
+MODULE_DEVICE_TABLE(of, vifb_of_match);
+
+static struct of_platform_driver vifb_of_driver = {
+	.owner = THIS_MODULE,
+	.name = DRV_MODULE_NAME,
+	.match_table = vifb_of_match,
+	.probe = vifb_of_probe,
+	.remove = vifb_of_remove,
+	.shutdown = vifb_of_shutdown,
+};
+
+/*
+ * Module interface hooks
+ *
+ */
+
+static int __init vifb_init_module(void)
+{
+	int error;
+	char *option = NULL;
+
+	drv_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION,
+		   vifb_driver_version);
+
+#ifndef MODULE
+	if (fb_get_options(DRV_MODULE_NAME, &option))
+		return -ENODEV;
+	if (!option) {
+		/* for backwards compatibility */
+		if (fb_get_options("gcnfb", &option))
+			return -ENODEV;
+	}
+	error = vifb_setup(option);
+#endif
+
+#ifdef CONFIG_WII_AVE_RVL
+	error = i2c_add_driver(&vi_ave_driver);
+	if (error)
+		drv_printk(KERN_ERR, "failed to register AVE (%d)\n", error);
+#endif
+
+	return of_register_platform_driver(&vifb_of_driver);
+}
+
+static void __exit vifb_exit_module(void)
+{
+	of_unregister_platform_driver(&vifb_of_driver);
+#ifdef CONFIG_WII_AVE_RVL
+	i2c_del_driver(&vi_ave_driver);
+#endif
+}
+
+module_init(vifb_init_module);
+module_exit(vifb_exit_module);
+
+MODULE_DESCRIPTION(DRV_DESCRIPTION);
+MODULE_AUTHOR(DRV_AUTHOR);
+MODULE_LICENSE("GPL");
diff --git a/drivers/video/logo/Kconfig b/drivers/video/logo/Kconfig
index 39ac49e..6fa54c6 100644
--- a/drivers/video/logo/Kconfig
+++ b/drivers/video/logo/Kconfig
@@ -42,6 +42,11 @@ config LOGO_DEC_CLUT224
 	depends on MACH_DECSTATION || ALPHA
 	default y
 
+config LOGO_GAMECUBE_CLUT224
+	bool "224-color GameCube Linux logo"
+	depends on GAMECUBE
+	default y
+
 config LOGO_MAC_CLUT224
 	bool "224-color Macintosh Linux logo"
 	depends on MAC
diff --git a/drivers/video/logo/Makefile b/drivers/video/logo/Makefile
index 3b43781..8d73a67 100644
--- a/drivers/video/logo/Makefile
+++ b/drivers/video/logo/Makefile
@@ -7,6 +7,7 @@ obj-$(CONFIG_LOGO_LINUX_CLUT224)	+= logo_linux_clut224.o
 obj-$(CONFIG_LOGO_BLACKFIN_CLUT224)	+= logo_blackfin_clut224.o
 obj-$(CONFIG_LOGO_BLACKFIN_VGA16)	+= logo_blackfin_vga16.o
 obj-$(CONFIG_LOGO_DEC_CLUT224)		+= logo_dec_clut224.o
+obj-$(CONFIG_LOGO_GAMECUBE_CLUT224)	+= logo_gcn_clut224.o
 obj-$(CONFIG_LOGO_MAC_CLUT224)		+= logo_mac_clut224.o
 obj-$(CONFIG_LOGO_PARISC_CLUT224)	+= logo_parisc_clut224.o
 obj-$(CONFIG_LOGO_SGI_CLUT224)		+= logo_sgi_clut224.o
diff --git a/drivers/video/logo/logo.c b/drivers/video/logo/logo.c
index ea7a8cc..e7b485a 100644
--- a/drivers/video/logo/logo.c
+++ b/drivers/video/logo/logo.c
@@ -75,6 +75,10 @@ const struct linux_logo * __init_refok fb_find_logo(int depth)
 		/* DEC Linux logo on MIPS/MIPS64 or ALPHA */
 		logo = &logo_dec_clut224;
 #endif
+#ifdef CONFIG_LOGO_GAMECUBE_CLUT224
+		/* GameCube Linux logo */
+		logo = &logo_gcn_clut224;
+#endif
 #ifdef CONFIG_LOGO_MAC_CLUT224
 		/* Macintosh Linux logo on m68k */
 		if (MACH_IS_MAC)
diff --git a/drivers/video/logo/logo_gcn_clut224.ppm b/drivers/video/logo/logo_gcn_clut224.ppm
new file mode 100644
index 0000000..08e12b3
--- /dev/null
+++ b/drivers/video/logo/logo_gcn_clut224.ppm
@@ -0,0 +1,1123 @@
+P3
+80 80
+255
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+6 6 6  6 6 6  10 10 10  10 10 10  10 10 10  6 6 6
+6 6 6  6 6 6  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  6 6 6  10 10 10  14 14 14
+22 22 22  26 26 26  30 30 30  34 34 34  30 30 30  30 30 30
+26 26 26  18 18 17  14 14 14  10 10 10  6 6 6  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  6 6 6  14 14 14  26 26 26  42 42 42
+54 54 55  66 66 66  78 78 78  78 78 78  78 78 78  74 74 74
+66 66 66  54 54 55  42 42 42  26 26 26  18 18 17  10 10 10
+6 6 6  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  10 10 10  22 22 22  42 42 42  66 66 66  87 86 85
+66 66 66  38 38 38  38 38 38  22 22 22  26 26 26  34 34 34
+54 54 55  66 66 66  87 86 85  70 70 70  46 46 46  26 26 26
+14 14 14  6 6 6  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+10 10 10  26 26 26  50 50 50  82 82 82  58 58 58  6 6 6
+2 2 6  2 2 6  2 2 6  2 2 6  2 2 6  2 2 6
+2 2 6  2 2 6  6 6 6  54 54 55  87 86 85  66 66 66
+38 38 38  18 18 17  6 6 6  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  6 6 6
+22 22 22  50 50 50  78 78 78  34 34 34  2 2 6  2 2 6
+2 2 6  2 2 6  2 2 6  2 2 6  2 2 6  2 2 6
+2 2 6  2 2 6  2 2 6  2 2 6  6 6 6  70 70 70
+78 78 78  46 46 46  22 22 22  6 6 6  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  6 6 6  18 18 17
+42 42 42  82 82 82  26 26 26  2 2 6  2 2 6  2 2 6
+2 2 6  2 2 6  2 2 6  2 2 6  2 2 6  2 2 6
+2 2 6  14 14 14  46 46 46  34 34 34  6 6 6  2 2 6
+42 42 42  78 78 78  42 42 42  18 18 17  6 6 6  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  10 10 10  30 30 30
+66 66 66  58 58 58  2 2 6  2 2 6  2 2 6  2 2 6
+2 2 6  2 2 6  2 2 6  2 2 6  2 2 6  2 2 6
+2 2 6  26 26 26  87 86 85  102 98 90  46 46 46  10 10 10
+2 2 6  58 58 58  70 70 70  34 34 34  10 10 10  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  14 14 14  42 42 42
+87 86 85  10 10 10  2 2 6  2 2 6  2 2 6  2 2 6
+2 2 6  2 2 6  2 2 6  2 2 6  2 2 6  2 2 6
+2 2 6  30 30 30  94 94 98  94 94 94  58 58 58  26 26 26
+2 2 6  6 6 6  78 78 78  54 54 55  22 22 22  6 6 6
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  6 6 6  22 22 22  62 62 62
+62 62 62  2 2 6  2 2 6  2 2 6  2 2 6  2 2 6
+2 2 6  2 2 6  2 2 6  2 2 6  2 2 6  2 2 6
+2 2 6  26 26 26  54 54 55  38 38 38  18 18 17  10 10 10
+2 2 6  2 2 6  34 34 34  82 82 82  38 38 38  14 14 14
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  6 6 6  30 30 30  78 78 78
+30 30 30  2 2 6  2 2 6  2 2 6  2 2 6  2 2 6
+2 2 6  2 2 6  2 2 6  2 2 6  2 2 6  2 2 6
+2 2 6  10 10 10  10 10 10  2 2 6  2 2 6  2 2 6
+2 2 6  2 2 6  2 2 6  78 78 78  50 50 50  18 18 17
+6 6 6  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  10 10 10  38 38 38  87 86 85
+14 14 14  2 2 6  2 2 6  2 2 6  2 2 6  2 2 6
+2 2 6  2 2 6  2 2 6  2 2 6  2 2 6  2 2 6
+2 2 6  2 2 6  2 2 6  2 2 6  2 2 6  2 2 6
+2 2 6  2 2 6  2 2 2  54 54 55  66 66 66  26 26 26
+6 6 6  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  14 14 14  42 42 42  82 82 82
+2 2 6  2 2 6  2 2 6  6 6 6  10 10 10  2 2 6
+2 2 6  2 2 6  2 2 6  2 2 6  2 2 6  2 2 6
+2 2 6  6 6 6  14 14 14  10 10 10  2 2 6  2 2 6
+2 2 6  2 2 6  2 2 6  18 18 17  82 82 82  34 34 34
+10 10 10  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  14 14 14  46 46 46  87 86 85
+2 2 6  2 2 6  6 6 6  6 6 6  22 22 22  34 34 34
+6 6 6  2 2 6  2 2 6  2 2 6  2 2 6  2 2 6
+18 18 17  34 34 34  10 10 10  50 50 50  22 22 22  2 2 6
+2 2 6  2 2 6  2 2 6  10 10 10  87 86 85  42 42 42
+14 14 14  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  14 14 14  46 46 46  87 86 85
+2 2 6  2 2 6  38 38 38  118 118 118  94 94 94  22 22 22
+22 22 22  2 2 6  2 2 6  2 2 6  14 14 14  87 86 85
+141 139 143  160 158 162  152 152 152  38 38 38  26 26 26  6 6 6
+2 2 6  2 2 6  2 2 6  2 2 6  87 86 85  46 46 46
+14 14 14  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  14 14 14  46 46 46  87 86 85
+2 2 6  14 14 14  134 133 135  199 199 199  194 193 197  118 118 118
+10 10 10  2 2 6  2 2 6  6 6 6  102 98 90  190 190 190
+209 208 212  219 220 223  215 214 215  134 133 135  14 14 14  6 6 6
+2 2 6  2 2 6  2 2 6  2 2 6  87 86 85  50 50 50
+18 18 17  6 6 6  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  14 14 14  46 46 46  87 86 85
+2 2 6  54 54 55  219 220 223  199 199 199  219 220 223  246 246 246
+58 58 58  2 2 6  2 2 6  30 30 30  209 208 212  254 254 254
+173 173 174  121 121 122  219 220 223  236 233 239  74 74 74  2 2 6
+2 2 6  2 2 6  2 2 6  2 2 6  70 70 70  58 58 58
+22 22 22  6 6 6  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  14 14 14  46 46 46  82 82 82
+2 2 6  104 103 100  173 173 174  26 26 26  90 90 90  229 226 233
+121 121 122  10 10 10  14 14 14  46 46 46  229 231 235  190 190 190
+6 6 6  74 74 74  90 90 90  239 238 242  160 158 162  2 2 6
+2 2 6  2 2 6  2 2 6  2 2 6  70 70 70  58 58 58
+22 22 22  6 6 6  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  14 14 14  42 42 42  87 86 85
+6 6 6  118 118 118  104 103 100  6 6 6  70 70 70  152 152 152
+122 122 130  18 18 17  38 38 38  54 54 55  219 220 223  104 103 100
+2 2 2  14 14 14  46 46 46  190 190 190  199 199 199  2 2 2
+2 2 6  2 2 6  2 2 6  2 2 6  74 74 74  62 62 62
+22 22 22  6 6 6  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  14 14 14  42 42 42  94 94 94
+14 14 14  104 103 100  122 122 130  2 2 2  18 18 17  118 118 118
+124 98 46  122 94 10  122 94 10  101 78 10  163 163 162  107 107 108
+2 2 6  2 2 2  2 2 2  194 193 197  194 193 197  6 6 6
+2 2 6  2 2 6  2 2 6  2 2 6  74 74 74  62 62 62
+22 22 22  6 6 6  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  10 10 10  38 38 38  90 90 90
+14 14 14  62 62 62  209 208 212  26 26 26  44 31 8  158 118 10
+226 171 11  238 183 13  214 174 14  188 146 14  214 174 14  174 146 62
+26 26 26  2 2 2  70 70 70  246 246 246  141 139 143  2 2 2
+2 2 6  2 2 6  2 2 6  2 2 6  70 70 70  66 66 66
+26 26 26  6 6 6  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  10 10 10  38 38 38  87 86 85
+14 14 14  6 6 6  199 199 199  190 166 114  186 134 10  226 171 11
+238 178 14  234 193 17  234 193 17  236 202 20  246 206 46  242 210 18
+234 193 17  188 146 14  218 194 134  210 206 186  42 42 42  2 2 6
+2 2 6  2 2 6  2 2 6  2 2 6  50 50 50  74 74 74
+30 30 30  6 6 6  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  10 10 10  34 34 34  87 86 85
+14 14 14  2 2 2  122 86 26  194 134 10  226 166 10  238 183 13
+226 186 14  234 193 17  236 202 20  246 216 61  246 217 39  246 217 39
+246 214 22  242 210 18  242 210 18  226 186 14  122 86 26  2 2 6
+2 2 6  2 2 6  2 2 6  2 2 6  50 50 50  82 82 82
+34 34 34  10 10 10  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  10 10 10  34 34 34  82 82 82
+30 30 30  63 43 6  182 122 6  204 146 10  231 174 11  238 183 13
+234 193 17  236 202 20  242 210 18  246 216 61  246 217 39  246 214 22
+246 214 22  242 210 18  226 186 14  214 174 14  188 146 14  6 6 6
+2 2 6  2 2 6  2 2 6  2 2 6  26 26 26  94 94 94
+42 42 42  14 14 14  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  10 10 10  30 30 30  78 78 78
+50 50 50  104 70 6  186 134 10  218 158 10  238 178 14  242 186 14
+236 202 20  236 202 20  246 216 61  246 217 39  246 217 39  246 214 22
+242 210 18  198 154 10  198 138 10  218 158 10  154 114 10  2 2 6
+2 2 6  2 2 6  2 2 6  2 2 6  6 6 6  90 90 90
+54 54 55  18 18 17  6 6 6  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  10 10 10  30 30 30  78 78 78
+46 46 46  22 22 22  138 93 6  198 154 10  238 183 13  238 183 13
+236 202 20  242 210 18  246 214 22  246 214 22  242 210 18  204 166 16
+186 134 10  210 150 10  216 162 10  210 150 10  101 78 10  2 2 6
+6 6 6  54 54 55  14 14 14  2 2 6  2 2 6  62 62 62
+74 74 74  30 30 30  10 10 10  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  10 10 10  34 34 34  78 78 78
+50 50 50  6 6 6  94 70 30  138 102 14  188 146 14  226 186 14
+236 202 20  234 193 17  214 174 14  188 146 14  173 119 7  186 134 10
+210 150 10  214 154 10  202 150 34  182 158 106  102 98 90  2 2 2
+2 2 6  78 78 78  118 118 118  54 54 55  2 2 6  22 22 22
+90 90 90  46 46 46  18 18 17  6 6 6  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  10 10 10  38 38 38  87 86 85
+50 50 50  6 6 6  132 131 125  174 154 114  160 108 10  173 119 7
+198 154 10  188 146 14  198 138 10  198 138 10  204 146 10  204 146 10
+198 138 10  190 166 114  194 193 197  199 199 199  173 173 174  14 14 14
+2 2 6  18 18 17  118 118 118  118 118 118  22 22 22  2 2 6
+74 74 74  70 70 70  30 30 30  10 10 10  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  6 6 6  18 18 17  50 50 50  104 103 100
+26 26 26  10 10 10  141 139 143  190 190 190  174 154 114  160 108 10
+198 138 10  188 146 14  198 138 10  194 134 10  182 122 6  190 142 34
+186 174 146  185 185 187  199 199 199  219 220 223  215 214 215  66 66 66
+2 2 6  2 2 6  50 50 50  62 62 62  6 6 6  2 2 2
+10 10 10  90 90 90  50 50 50  18 18 17  6 6 6  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  10 10 10  34 34 34  74 74 74  74 74 74
+2 2 2  6 6 6  141 139 143  199 199 199  190 190 190  163 163 162
+154 122 54  160 108 10  160 108 10  164 122 42  174 154 114  185 185 187
+194 193 197  209 208 212  250 250 250  254 254 254  250 250 250  182 182 182
+6 6 6  2 2 6  2 2 6  2 2 6  2 2 6  2 2 6
+2 2 6  62 62 62  74 74 74  34 34 34  14 14 14  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  10 10 10  22 22 22  54 54 55  94 94 94  18 18 17
+2 2 6  50 50 50  229 231 235  219 220 223  194 193 197  190 190 190
+190 190 190  190 190 190  190 190 190  190 190 190  190 190 190  194 193 197
+215 214 215  242 242 242  254 254 254  254 254 254  250 250 250  254 254 254
+82 82 82  2 2 6  2 2 6  2 2 6  2 2 6  2 2 6
+2 2 6  14 14 14  87 86 85  54 54 55  22 22 22  6 6 6
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+6 6 6  18 18 17  46 46 46  90 90 90  46 46 46  18 18 17
+6 6 6  182 182 182  254 254 254  250 250 250  210 206 186  190 190 190
+190 190 190  190 190 190  190 190 190  190 190 190  210 206 186  229 231 235
+254 254 254  250 250 250  250 250 250  254 254 254  254 254 254  254 254 254
+198 200 208  14 14 14  2 2 6  2 2 6  2 2 6  2 2 6
+2 2 6  2 2 6  42 42 42  87 86 85  42 42 42  18 18 17
+6 6 6  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  6 6 6
+14 14 14  38 38 38  74 74 74  66 66 66  2 2 6  6 6 6
+90 90 90  250 250 250  250 250 250  254 254 254  239 238 242  199 199 199
+190 190 190  190 190 190  194 193 197  219 220 223  183 174 199  134 114 171
+207 201 219  254 254 254  254 254 254  254 254 254  250 250 250  254 254 254
+250 250 250  82 82 82  2 2 2  2 2 6  2 2 6  2 2 6
+2 2 6  2 2 6  2 2 6  78 78 78  70 70 70  34 34 34
+14 14 14  6 6 6  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  14 14 14
+34 34 34  66 66 66  78 78 78  6 6 6  2 2 2  18 18 17
+219 220 223  254 254 254  254 254 254  250 250 250  254 254 254  246 246 246
+222 223 236  229 231 235  229 226 233  140 131 164  109 96 159  109 96 159
+120 106 166  170 156 193  236 233 239  254 254 254  254 254 254  254 254 254
+254 254 254  182 182 182  2 2 6  2 2 6  2 2 6  2 2 6
+2 2 6  2 2 6  2 2 6  18 18 17  90 90 90  62 62 62
+30 30 30  10 10 10  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  10 10 10  26 26 26
+58 58 58  90 90 90  18 18 17  2 2 6  2 2 6  107 107 108
+254 254 254  254 254 254  254 254 254  250 250 250  250 250 250  254 254 254
+250 250 250  189 183 204  121 102 164  116 94 159  117 98 162  114 103 163
+114 103 163  120 106 166  136 121 175  146 139 165  107 107 108  118 118 118
+141 139 143  182 182 182  219 220 223  2 2 6  2 2 6  2 2 6
+2 2 6  2 2 6  2 2 6  2 2 6  18 18 17  94 94 94
+54 54 55  26 26 26  10 10 10  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  6 6 6  22 22 22  50 50 50
+90 90 90  26 26 26  2 2 6  2 2 2  14 14 14  199 199 199
+250 250 250  254 254 254  254 254 254  254 254 254  254 254 254  229 226 233
+147 132 180  109 90 154  158 147 190  198 200 208  137 128 178  120 106 166
+121 102 164  120 106 166  130 120 175  130 120 175  127 110 169  76 72 88
+58 58 58  58 58 58  74 74 74  118 118 118  194 193 197  2 2 6
+2 2 6  2 2 6  2 2 6  2 2 6  2 2 6  38 38 38
+87 86 85  50 50 50  22 22 22  6 6 6  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  6 6 6  14 14 14  38 38 38  82 82 82
+34 34 34  2 2 6  2 2 6  2 2 2  42 42 42  194 193 197
+246 246 246  254 254 254  254 254 254  254 254 254  200 193 212  122 98 164
+113 86 148  109 96 159  128 114 172  158 147 190  130 120 175  120 106 166
+127 110 169  127 110 169  130 120 175  130 120 175  142 128 179  140 131 164
+102 98 122  58 50 84  54 54 55  62 62 62  74 74 74  134 133 135
+236 233 239  2 2 2  2 2 6  2 2 6  2 2 6  2 2 6
+38 38 38  82 82 82  42 42 42  14 14 14  6 6 6  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  10 10 10  26 26 26  62 62 62  66 66 66
+2 2 6  2 2 6  2 2 6  6 6 6  70 70 70  169 165 179
+210 206 186  229 231 235  236 233 239  146 139 165  105 86 155  105 86 155
+109 96 159  109 90 154  109 96 159  114 103 163  114 103 163  120 106 166
+120 106 166  128 114 172  130 120 175  137 128 178  137 128 178  142 134 183
+155 141 181  140 131 164  70 70 70  58 50 84  66 66 66  84 82 94
+118 118 118  229 231 235  2 2 6  2 2 6  2 2 6  2 2 6
+2 2 6  62 62 62  66 66 66  30 30 30  10 10 10  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  14 14 14  42 42 42  82 82 82  18 18 17
+2 2 6  2 2 6  2 2 6  10 10 10  94 94 94  182 182 182
+219 220 223  200 193 212  122 98 164  102 82 152  105 86 155  109 90 154
+109 96 159  109 90 154  109 96 159  114 103 163  114 103 163  127 110 169
+128 114 172  128 114 172  130 120 175  137 128 178  142 128 179  148 140 189
+148 140 189  158 147 190  158 147 190  118 110 134  76 72 88  94 90 106
+94 94 98  132 131 125  2 2 6  2 2 6  2 2 6  2 2 6
+2 2 6  6 6 6  87 86 85  46 46 46  18 18 17  6 6 6
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  6 6 6  22 22 22  54 54 55  70 70 70  2 2 6
+2 2 6  10 10 10  2 2 2  22 22 22  173 173 174  236 233 239
+155 141 181  102 82 152  102 82 152  102 82 152  109 90 154  105 86 155
+109 90 154  87 78 126  58 50 84  45 38 66  45 38 66  54 46 76
+82 70 114  114 103 163  130 120 175  136 121 175  137 128 178  142 128 179
+142 134 183  148 140 189  158 147 190  158 147 190  146 139 165  118 106 138
+126 122 142  87 86 85  199 199 199  2 2 6  2 2 6  2 2 6
+2 2 6  2 2 6  62 62 62  66 66 66  26 26 26  10 10 10
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  10 10 10  30 30 30  74 74 74  50 50 50  2 2 6
+26 26 26  26 26 26  2 2 6  107 107 108  207 201 219  120 106 166
+103 83 144  102 82 152  105 86 155  102 82 152  94 82 138  87 78 126
+34 26 54  6 6 6  6 6 6  2 2 6  2 2 6  2 2 2
+2 2 2  10 6 18  64 60 89  130 120 175  137 128 178  137 128 178
+142 128 179  148 140 189  142 134 183  146 140 180  142 134 183  148 140 189
+155 141 181  110 106 126  146 146 147  18 18 17  2 2 6  2 2 6
+2 2 6  2 2 6  18 18 17  87 86 85  42 42 42  14 14 14
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  14 14 14  42 42 42  90 90 90  22 22 22  2 2 6
+42 42 42  2 2 2  78 78 78  166 150 188  102 82 152  99 78 147
+111 77 141  102 82 152  99 78 147  105 86 155  87 78 126  10 10 10
+6 6 6  2 2 6  2 2 6  6 2 2  2 2 2  2 2 2
+2 2 6  6 6 6  2 2 6  45 38 66  130 120 175  136 121 175
+137 128 178  137 128 178  137 128 178  137 128 178  137 128 178  142 128 179
+142 134 183  140 131 164  138 131 154  38 38 38  10 10 10  2 2 6
+2 2 6  2 2 6  2 2 6  78 78 78  58 58 58  22 22 22
+6 6 6  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+6 6 6  18 18 17  54 54 55  82 82 82  2 2 6  26 26 26
+22 22 22  216 211 227  127 110 169  97 74 146  99 78 147  99 78 147
+102 82 152  102 82 152  99 78 147  94 82 138  34 26 54  2 2 6
+2 2 6  6 2 7  50 50 50  30 30 30  6 6 6  6 2 2
+2 2 2  2 2 2  2 2 6  2 2 6  87 78 126  130 120 175
+128 114 172  130 120 175  130 120 175  136 121 175  137 128 178  130 120 175
+130 120 175  137 128 178  142 134 183  213 206 226  38 38 38  2 2 6
+2 2 6  2 2 6  2 2 6  46 46 46  78 78 78  30 30 30
+10 10 10  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+10 10 10  30 30 30  74 74 74  58 58 58  2 2 6  42 42 42
+189 183 204  105 86 155  97 74 146  97 74 146  93 70 140  97 74 146
+105 86 155  99 78 147  102 82 152  94 82 138  10 6 18  6 2 7
+6 6 6  2 2 2  10 6 18  50 50 50  54 54 55  18 18 17
+6 2 2  6 2 7  6 2 7  2 2 6  54 46 76  120 106 166
+127 110 169  127 110 169  127 110 169  128 114 172  127 110 169  128 114 172
+128 114 172  127 110 169  128 114 172  130 120 175  38 38 38  14 14 14
+2 2 6  2 2 6  2 2 6  6 6 6  87 86 85  46 46 46
+14 14 14  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  6 6 6
+14 14 14  42 42 42  90 90 90  18 18 17  18 18 17  26 26 26
+183 174 199  122 98 164  97 74 146  99 78 147  97 74 146  97 74 146
+99 78 147  99 78 147  102 82 152  99 78 147  14 14 14  26 26 26
+26 26 26  6 2 7  6 2 7  6 2 7  22 22 22  66 66 66
+30 30 30  2 2 6  2 2 6  2 2 6  58 50 84  120 106 166
+114 103 163  114 103 163  114 103 163  114 103 163  114 103 163  114 103 163
+114 103 163  120 106 166  109 90 154  134 120 165  10 10 10  34 34 34
+2 2 6  2 2 6  2 2 6  2 2 6  74 74 74  58 58 58
+22 22 22  6 6 6  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  10 10 10
+26 26 26  66 66 66  82 82 82  2 2 6  38 38 38  6 2 7
+196 186 211  154 134 184  127 110 169  99 78 147  97 74 146  97 74 146
+99 78 147  99 78 147  99 78 147  102 82 152  45 38 66  58 50 84
+54 54 55  22 22 22  6 2 7  6 2 7  6 2 7  10 10 10
+14 14 14  6 2 2  2 2 2  10 6 18  94 82 138  109 96 159
+109 96 159  109 96 159  109 96 159  109 96 159  109 96 159  109 96 159
+109 96 159  105 86 155  116 94 159  147 132 180  2 2 6  46 46 46
+2 2 6  2 2 6  2 2 6  2 2 6  42 42 42  74 74 74
+30 30 30  10 10 10  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  6 6 6  14 14 14
+42 42 42  90 90 90  26 26 26  6 6 6  42 42 42  2 2 6
+200 193 212  154 134 184  154 134 184  136 121 175  105 86 155  97 74 146
+97 74 146  99 78 147  99 78 147  97 74 146  95 75 136  40 30 56
+94 90 106  104 103 100  50 50 50  26 26 26  6 2 7  6 2 7
+2 2 6  6 2 2  10 6 18  64 60 89  109 90 154  105 86 155
+105 86 155  105 86 155  105 86 155  105 86 155  105 86 155  102 82 152
+109 90 154  116 94 159  116 94 159  157 140 187  2 2 6  46 46 46
+2 2 6  2 2 6  2 2 6  2 2 6  10 10 10  87 86 85
+38 38 38  10 10 10  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  10 10 10  26 26 26
+66 66 66  82 82 82  2 2 6  22 22 22  18 18 17  6 2 7
+207 201 219  154 134 184  147 132 180  147 132 180  147 126 185  114 90 155
+97 74 146  97 74 146  111 77 141  130 90 130  99 78 147  95 75 136
+58 50 84  84 82 94  87 86 85  38 38 38  6 2 7  6 2 7
+10 6 18  34 26 54  86 66 120  102 82 152  99 78 147  102 82 152
+102 82 152  102 82 152  102 82 152  99 78 147  99 78 147  109 90 154
+114 90 155  116 94 159  116 94 159  170 156 193  2 2 6  38 38 38
+2 2 6  2 2 6  2 2 6  2 2 6  6 6 6  87 86 85
+46 46 46  14 14 14  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  6 6 6  18 18 17  46 46 46
+87 86 85  18 18 17  2 2 6  34 34 34  10 10 10  10 6 18
+213 206 226  162 146 182  183 174 199  147 132 180  142 134 183  147 132 180
+126 102 165  97 74 146  97 74 146  130 90 130  166 110 126  118 78 130
+93 70 140  97 74 146  86 66 120  70 56 108  70 56 108  70 56 108
+87 78 126  94 82 138  99 78 147  99 78 147  99 78 147  99 78 147
+97 74 146  97 74 146  97 74 146  102 82 152  114 90 155  116 94 159
+116 94 159  122 98 164  122 98 164  177 168 195  6 6 6  30 30 30
+2 2 6  2 2 6  2 2 6  2 2 6  2 2 6  82 82 82
+54 54 55  18 18 17  6 6 6  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  10 10 10  26 26 26  66 66 66
+62 62 62  2 2 6  2 2 2  38 38 38  10 10 10  22 22 22
+226 218 234  170 156 193  207 201 219  194 193 197  146 139 165  147 126 185
+147 132 180  134 114 171  99 78 147  99 78 147  118 78 130  113 86 148
+97 74 146  97 74 146  97 74 146  97 74 146  97 74 146  99 78 147
+97 74 146  97 74 146  97 74 146  97 74 146  97 74 146  93 70 140
+97 74 146  93 70 140  105 86 155  109 90 154  93 70 140  116 94 159
+122 98 164  122 98 164  126 102 165  189 183 204  10 10 10  30 30 30
+2 2 6  2 2 6  2 2 6  2 2 6  2 2 6  66 66 66
+58 58 58  22 22 22  6 6 6  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  10 10 10  38 38 38  78 78 78
+6 6 6  2 2 6  2 2 6  46 46 46  14 14 14  42 42 42
+229 226 233  170 156 193  183 174 199  132 131 125  198 200 208  158 147 190
+140 131 164  142 134 183  136 121 175  113 86 148  95 75 136  97 74 146
+97 74 146  95 75 136  93 70 140  93 70 140  97 74 146  102 82 152
+97 74 146  93 70 140  93 70 140  93 70 140  93 70 140  93 70 140
+97 74 146  105 86 155  99 78 147  88 65 130  92 71 128  116 94 159
+122 98 164  126 102 165  127 106 167  196 186 211  22 22 22  14 14 14
+2 2 6  2 2 6  2 2 6  2 2 6  2 2 6  66 66 66
+62 62 62  22 22 22  6 6 6  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  6 6 6  18 18 17  50 50 50  74 74 74
+2 2 6  2 2 6  14 14 14  70 70 70  34 34 34  62 62 62
+236 233 239  170 156 193  194 193 197  46 42 70  200 193 212  199 199 199
+177 162 197  137 128 178  147 132 180  147 126 185  121 102 164  97 74 146
+93 70 140  93 70 140  97 74 146  93 70 140  93 70 140  88 65 130
+88 65 130  93 70 140  93 70 140  93 70 140  93 70 140  97 74 146
+113 86 148  97 74 146  86 66 120  92 71 128  93 70 140  122 98 164
+126 102 165  127 106 167  127 106 167  207 201 219  30 30 30  2 2 6
+2 2 6  2 2 6  2 2 6  2 2 6  2 2 6  66 66 66
+62 62 62  22 22 22  6 6 6  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  6 6 6  18 18 17  54 54 55  62 62 62
+2 2 6  2 2 6  2 2 6  30 30 30  46 46 46  74 74 74
+254 254 254  158 147 190  236 233 239  199 199 199  215 214 215  190 190 190
+163 163 162  183 174 199  138 131 154  142 128 179  147 126 185  127 110 169
+99 78 147  93 70 140  97 74 146  93 70 140  93 70 140  88 65 130
+88 65 130  88 65 130  93 70 140  88 65 130  102 82 152  105 86 155
+92 71 128  86 66 120  86 66 120  92 71 128  95 75 136  126 102 165
+127 106 167  127 106 167  127 106 167  213 206 226  30 30 30  2 2 6
+2 2 6  2 2 6  2 2 6  2 2 6  2 2 6  66 66 66
+58 58 58  22 22 22  6 6 6  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  6 6 6  22 22 22  58 58 58  62 62 62
+2 2 6  2 2 6  2 2 2  6 2 7  30 30 30  78 78 78
+250 250 250  162 146 182  207 201 219  222 223 236  229 231 235  173 173 174
+38 38 38  194 193 197  189 183 204  146 139 165  137 128 178  147 132 180
+136 121 175  105 86 155  88 65 130  93 70 140  93 70 140  88 65 130
+88 65 130  88 65 130  88 65 130  105 86 155  102 82 152  88 65 130
+86 66 120  88 65 130  82 70 114  92 71 128  99 78 147  127 106 167
+126 102 165  126 102 165  127 110 169  226 218 234  14 14 14  22 22 22
+26 26 26  18 18 17  6 6 6  2 2 6  2 2 2  82 82 82
+54 54 55  18 18 17  6 6 6  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  6 6 6  26 26 26  62 62 62  107 107 108
+74 54 14  186 134 10  216 162 10  122 94 10  6 6 6  62 62 62
+239 238 242  170 156 193  155 141 181  200 193 212  222 223 236  229 231 235
+134 133 135  198 200 208  194 193 197  183 174 199  152 152 152  136 121 175
+142 128 179  142 128 179  117 98 162  88 65 130  88 65 130  93 70 140
+88 65 130  99 78 147  109 90 154  114 90 155  95 75 136  86 66 120
+92 71 128  88 65 130  92 71 128  95 75 136  103 83 144  120 106 166
+127 106 167  127 110 169  155 141 181  160 158 162  2 2 2  2 2 2
+6 6 6  18 18 17  66 66 66  38 38 38  6 6 6  94 94 94
+50 50 50  18 18 17  6 6 6  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  6 6 6
+10 10 10  10 10 10  18 18 17  38 38 38  78 78 78  132 131 125
+218 158 10  242 186 14  246 190 14  246 190 14  158 118 10  10 10 10
+90 90 90  226 218 234  166 150 188  157 140 187  189 183 204  216 211 227
+229 231 235  219 220 223  173 173 174  74 74 74  189 183 204  169 165 179
+134 120 165  136 121 175  137 128 178  127 110 169  93 70 140  88 65 130
+102 82 152  113 86 148  114 90 155  114 90 155  92 71 128  86 66 120
+92 71 128  95 75 136  92 71 128  95 75 136  117 98 162  127 106 167
+127 110 169  177 162 197  6 2 7  6 2 7  2 2 6  2 2 6
+2 2 6  2 2 6  38 38 38  46 46 46  22 22 22  107 107 108
+54 54 55  18 18 17  6 6 6  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  6 6 6  14 14 14  22 22 22
+30 30 30  38 38 38  50 50 50  70 70 70  107 107 108  190 142 34
+226 171 11  242 186 14  246 190 14  246 190 14  246 190 14  154 114 10
+6 6 6  74 74 74  236 233 239  136 121 175  157 140 187  177 162 197
+216 211 227  236 233 239  209 208 212  66 66 66  169 165 179  194 193 197
+177 168 195  138 131 154  136 121 175  137 128 178  127 106 167  102 82 152
+109 90 154  109 90 154  109 90 154  116 94 159  99 78 147  86 66 120
+92 71 128  95 75 136  103 83 144  116 94 159  127 106 167  134 120 165
+196 186 211  44 31 8  2 2 2  2 2 6  2 2 6  2 2 6
+2 2 6  6 6 6  30 30 30  26 26 26  204 166 16  154 142 90
+66 66 66  26 26 26  6 6 6  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  6 6 6  18 18 17  38 38 38  58 58 58
+78 78 78  87 86 85  102 98 90  121 121 122  174 146 62  210 150 10
+231 174 11  246 186 14  246 190 14  246 190 14  246 186 14  234 193 17
+101 78 10  2 2 6  46 46 46  215 214 215  177 168 195  148 140 189
+158 147 190  222 223 236  239 238 242  215 214 215  215 214 215  185 185 187
+122 122 130  183 174 199  138 131 154  136 121 175  127 110 169  113 86 148
+109 90 154  114 90 155  116 94 159  122 98 164  99 78 147  95 75 136
+87 78 126  118 78 130  116 94 159  122 98 164  136 121 175  213 206 226
+204 166 16  18 18 17  2 2 6  2 2 6  2 2 6  2 2 6
+2 2 6  2 2 6  6 6 6  122 94 10  236 202 20  234 193 17
+82 82 82  34 34 34  10 10 10  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  14 14 14  38 38 38  70 70 70  154 122 54
+190 142 34  204 146 10  198 138 10  198 138 10  214 154 10  226 171 11
+242 186 14  246 190 14  246 190 14  246 190 14  246 190 14  246 186 14
+226 171 11  44 31 8  6 2 7  22 22 22  160 158 162  200 193 212
+157 140 187  157 140 187  207 201 219  209 208 212  222 223 236  194 193 197
+30 30 30  146 139 165  146 139 165  134 120 165  134 114 171  114 90 155
+116 94 159  122 98 164  122 98 164  122 98 164  111 77 141  92 71 128
+109 90 154  122 98 164  126 102 165  142 128 179  229 226 233  242 186 14
+214 154 10  44 31 8  2 2 2  2 2 6  2 2 6  2 2 6
+2 2 6  2 2 6  63 43 6  231 174 11  234 193 17  238 183 13
+114 102 78  38 38 38  14 14 14  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  6 6 6  22 22 22  54 54 55  154 122 54  214 154 10
+226 171 11  231 174 11  226 171 11  226 171 11  238 178 14  242 186 14
+246 190 14  246 190 14  246 190 14  246 190 14  246 190 14  246 190 14
+242 198 14  188 146 14  10 10 10  6 2 7  6 2 7  118 118 118
+216 211 227  157 140 187  154 134 184  183 174 199  215 214 215  222 223 236
+169 165 179  173 173 174  138 131 154  128 114 172  134 120 165  116 94 159
+122 98 164  122 98 164  121 102 164  126 102 165  103 83 144  116 94 159
+127 106 167  127 106 167  154 134 184  236 233 239  238 178 14  238 178 14
+210 150 10  138 93 6  14 14 14  2 2 6  2 2 6  2 2 6
+6 6 6  63 43 6  198 138 10  238 178 14  238 178 14  238 183 13
+126 114 90  58 58 58  22 22 22  6 6 6  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  10 10 10  30 30 30  70 70 70  179 134 43  226 171 11
+238 183 13  242 186 14  242 186 14  246 186 14  246 190 14  246 190 14
+246 190 14  246 190 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  234 193 17  94 70 30  2 2 2  2 2 6  2 2 6
+66 66 66  229 226 233  170 156 193  147 132 180  170 156 193  207 201 219
+219 220 223  219 220 223  138 131 154  128 114 172  134 114 171  117 98 162
+121 102 164  121 102 164  121 102 164  120 106 166  116 94 159  127 106 167
+128 114 172  169 165 179  160 158 162  214 166 58  231 174 11  231 174 11
+218 158 10  194 134 10  160 108 10  118 82 10  101 78 10  118 82 10
+166 114 6  198 138 10  226 171 11  238 183 13  242 186 14  242 186 14
+162 146 94  78 78 78  34 34 34  14 14 14  6 6 6  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  6 6 6  30 30 30  78 78 78  190 142 34  226 166 10
+238 183 13  246 190 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  246 190 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  242 198 14  204 166 16  18 18 17  2 2 6  2 2 6
+2 2 6  38 38 38  160 158 162  177 168 195  142 134 183  155 141 181
+200 193 212  222 223 236  146 139 165  128 114 172  136 121 175  122 98 164
+122 98 164  122 98 164  126 102 165  114 90 155  122 98 164  128 114 172
+196 186 211  210 206 186  200 193 212  206 162 42  226 171 11  238 178 14
+226 166 10  204 146 10  204 146 10  194 134 10  186 134 10  198 138 10
+210 150 10  231 174 11  242 186 14  246 190 14  246 190 14  242 186 14
+226 171 11  126 114 90  62 62 62  30 30 30  14 14 14  6 6 6
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  10 10 10  30 30 30  78 78 78  179 134 43  226 166 10
+238 183 13  246 190 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  246 190 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  246 190 14  242 198 14  138 102 14  2 2 6  2 2 6
+2 2 6  2 2 6  78 78 78  250 250 250  196 186 211  147 132 180
+154 134 184  207 201 219  169 165 179  136 121 175  136 121 175  122 98 164
+126 102 165  122 98 164  114 90 155  121 102 164  134 120 165  213 206 226
+250 250 250  219 220 223  194 193 197  190 150 46  216 162 10  238 178 14
+231 174 11  226 166 10  218 158 10  214 154 10  214 154 10  218 158 10
+226 171 11  238 183 13  246 190 14  246 190 14  246 190 14  246 190 14
+242 186 14  206 162 42  102 98 90  58 58 58  30 30 30  14 14 14
+6 6 6  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  10 10 10  30 30 30  74 74 74  179 134 43  218 158 10
+238 178 14  246 190 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  246 190 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  246 186 14  242 198 14  226 186 14  63 43 6  2 2 6
+2 2 6  2 2 6  22 22 22  239 238 242  254 254 254  213 206 226
+157 140 187  147 132 180  155 141 181  136 121 175  136 121 175  121 102 164
+117 98 162  116 94 159  127 106 167  147 126 185  226 218 234  250 250 250
+250 250 250  222 223 236  190 190 190  179 134 43  218 158 10  238 178 14
+238 183 13  238 178 14  231 174 11  226 166 10  226 171 11  231 174 11
+238 178 14  242 186 14  246 190 14  246 190 14  246 190 14  246 190 14
+238 183 13  238 183 13  206 162 42  107 107 108  66 66 66  34 34 34
+14 14 14  6 6 6  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  6 6 6  26 26 26  70 70 70  162 134 66  210 150 10
+238 178 14  246 190 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  246 190 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  246 190 14  246 186 14  234 193 17  188 146 14  14 14 14
+2 2 6  2 2 6  46 46 46  246 246 246  254 254 254  254 254 254
+229 226 233  157 140 187  147 132 180  136 121 175  127 110 169  116 94 159
+121 102 164  121 102 164  162 146 182  236 233 239  254 254 254  254 254 254
+254 254 254  219 220 223  87 86 85  160 108 10  218 158 10  238 178 14
+242 186 14  246 186 14  242 186 14  238 183 13  238 183 13  242 186 14
+242 186 14  242 186 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  246 190 14  242 186 14  226 171 11  142 122 74  66 66 66
+30 30 30  10 10 10  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  6 6 6  26 26 26  70 70 70  162 134 66  210 150 10
+238 178 14  246 190 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  246 190 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  246 190 14  246 190 14  246 190 14  234 193 17  122 94 10
+30 30 30  107 107 108  219 220 223  254 254 254  250 250 250  250 250 250
+250 250 250  216 211 227  118 106 138  147 132 180  134 114 171  126 102 165
+127 106 167  177 168 195  250 250 250  254 254 254  254 254 254  254 254 254
+242 242 242  82 82 82  14 14 14  160 108 10  218 158 10  238 178 14
+242 186 14  246 190 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  246 190 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  246 190 14  246 190 14  246 190 14  238 183 13  162 134 66
+46 46 46  18 18 17  6 6 6  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  10 10 10  30 30 30  78 78 78  162 134 66  210 150 10
+238 178 14  242 186 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  246 190 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  246 190 14  246 190 14  246 190 14  242 198 14  214 174 14
+186 174 146  254 254 254  254 254 254  254 254 254  254 254 254  254 254 254
+254 254 254  250 250 250  250 250 250  196 186 211  136 121 175  134 114 171
+200 193 212  254 254 254  254 254 254  254 254 254  250 250 250  219 220 223
+58 58 58  2 2 2  18 18 17  166 114 6  218 158 10  231 174 11
+246 186 14  246 190 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  246 190 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  246 190 14  246 190 14  246 186 14  242 186 14  190 150 46
+54 54 55  22 22 22  6 6 6  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  14 14 14  38 38 38  87 86 85  179 134 43  214 154 10
+238 178 14  246 186 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  246 190 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  246 190 14  246 190 14  246 190 14  246 190 14  234 193 17
+188 146 14  215 214 215  250 250 250  254 254 254  254 254 254  254 254 254
+254 254 254  254 254 254  254 254 254  250 250 250  216 211 227  226 218 234
+250 250 250  254 254 254  254 254 254  250 250 250  173 173 174  26 26 26
+2 2 2  2 2 2  44 31 8  160 108 10  216 162 10  242 186 14
+246 186 14  246 190 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  246 190 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  246 190 14  246 190 14  238 178 14  226 166 10  142 122 74
+46 46 46  18 18 17  6 6 6  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+6 6 6  18 18 17  50 50 50  107 107 108  186 134 10  226 166 10
+242 186 14  246 190 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  246 190 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  246 190 14  246 190 14  246 190 14  246 190 14  226 186 14
+216 162 10  141 110 47  229 226 233  254 254 254  254 254 254  250 250 250
+254 254 254  254 254 254  254 254 254  250 250 250  250 250 250  254 254 254
+254 254 254  250 250 250  199 199 199  62 62 62  2 2 2  2 2 2
+2 2 2  2 2 6  44 31 8  160 108 10  216 162 10  238 183 13
+246 186 14  246 190 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  246 190 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  242 186 14  231 174 11  214 154 10  154 122 54  66 66 66
+30 30 30  10 10 10  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+6 6 6  22 22 22  54 54 55  154 122 54  204 146 10  231 174 11
+242 186 14  246 186 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  246 190 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  246 190 14  246 190 14  246 190 14  242 186 14  238 178 14
+216 162 10  160 108 10  63 43 6  134 133 135  219 220 223  250 250 250
+254 254 254  254 254 254  254 254 254  250 250 250  242 242 242  209 208 212
+141 139 143  66 66 66  6 6 6  6 2 7  2 2 6  2 2 6
+2 2 6  2 2 2  63 43 6  160 108 10  218 158 10  238 178 14
+246 190 14  246 190 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  246 190 14  246 190 14  246 190 14  246 190 14  238 183 13
+231 174 11  218 158 10  190 142 34  126 114 90  70 70 70  38 38 38
+18 18 17  6 6 6  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+6 6 6  22 22 22  62 62 62  164 122 42  204 146 10  226 166 10
+238 178 14  238 183 13  238 183 13  242 186 14  246 186 14  246 190 14
+246 190 14  246 190 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  246 190 14  246 190 14  246 190 14  246 190 14  238 178 14
+216 162 10  173 119 7  82 54 6  2 2 2  6 6 6  30 30 30
+54 54 55  62 62 62  50 50 50  38 38 38  18 18 17  6 6 6
+6 2 2  2 2 2  2 2 2  2 2 6  2 2 6  2 2 6
+2 2 6  6 6 6  82 54 6  166 114 6  214 154 10  238 178 14
+246 190 14  246 190 14  246 190 14  246 190 14  246 190 14  246 190 14
+246 190 14  246 190 14  238 183 13  238 183 13  226 171 11  210 150 10
+179 134 43  126 114 90  78 78 78  50 50 50  34 34 34  18 18 17
+6 6 6  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+6 6 6  18 18 17  46 46 46  164 122 42  194 134 10  198 138 10
+218 158 10  216 162 10  226 166 10  226 171 11  231 174 11  238 178 14
+238 183 13  238 183 13  242 186 14  246 186 14  246 190 14  246 190 14
+246 190 14  246 190 14  246 190 14  246 190 14  242 186 14  231 174 11
+210 150 10  160 108 10  104 70 6  10 10 10  6 2 2  2 2 2
+2 2 2  2 2 2  6 2 2  6 2 2  2 2 2  2 2 6
+2 2 6  2 2 6  2 2 6  2 2 6  2 2 6  2 2 6
+2 2 6  6 6 6  90 62 6  166 114 6  204 146 10  231 174 11
+242 186 14  246 190 14  246 190 14  246 190 14  246 186 14  242 186 14
+238 183 13  231 174 11  226 166 10  214 154 10  179 134 43  126 114 90
+87 86 85  58 58 58  38 38 38  22 22 22  10 10 10  6 6 6
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  14 14 14  34 34 34  66 66 66  141 110 47  158 118 10
+166 114 6  182 122 6  186 134 10  198 138 10  204 146 10  204 146 10
+210 150 10  216 162 10  226 166 10  231 174 11  238 183 13  246 186 14
+246 186 14  246 186 14  246 186 14  246 186 14  238 183 13  218 158 10
+186 134 10  154 98 6  104 70 6  14 14 14  2 2 2  2 2 6
+2 2 6  2 2 6  2 2 6  2 2 6  2 2 6  2 2 6
+2 2 6  2 2 6  2 2 6  2 2 6  2 2 6  2 2 6
+2 2 6  6 6 6  82 54 6  154 98 6  186 134 10  216 162 10
+238 178 14  238 183 13  246 186 14  242 186 14  238 183 13  231 174 11
+226 166 10  204 146 10  186 134 10  154 122 54  90 90 90  62 62 62
+42 42 42  22 22 22  14 14 14  6 6 6  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  6 6 6  18 18 17  34 34 34  62 62 62  78 78 78
+102 98 90  126 114 90  141 110 47  160 108 10  160 108 10  166 114 6
+173 119 7  182 122 6  186 134 10  198 138 10  210 150 10  216 162 10
+226 171 11  238 178 14  238 178 14  231 174 11  216 162 10  198 138 10
+160 108 10  127 82 6  90 62 6  10 10 10  2 2 6  2 2 6
+18 18 17  38 38 38  38 38 38  38 38 38  38 38 38  38 38 38
+38 38 38  38 38 38  38 38 38  38 38 38  26 26 26  2 2 6
+2 2 2  6 6 6  63 43 6  138 93 6  173 119 7  204 146 10
+216 162 10  231 174 11  231 174 11  231 174 11  216 162 10  210 150 10
+186 134 10  160 108 10  126 114 90  82 82 82  50 50 50  30 30 30
+14 14 14  6 6 6  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  6 6 6  14 14 14  22 22 22  34 34 34
+42 42 42  58 58 58  74 74 74  87 86 85  102 98 90  122 102 70
+124 98 46  122 86 26  138 93 6  154 98 6  160 108 10  182 122 6
+186 134 10  194 134 10  204 146 10  204 146 10  182 122 6  160 108 10
+134 86 6  104 70 6  44 31 8  54 54 55  107 107 108  102 98 90
+87 86 85  82 82 82  78 78 78  78 78 78  78 78 78  78 78 78
+78 78 78  78 78 78  78 78 78  82 82 82  87 86 85  94 94 94
+107 107 108  104 103 100  85 65 33  127 82 6  160 108 10  182 122 6
+186 134 10  204 146 10  204 146 10  204 146 10  194 134 10  173 119 7
+154 98 6  107 107 108  66 66 66  42 42 42  22 22 22  10 10 10
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  6 6 6  10 10 10
+14 14 14  22 22 22  30 30 30  38 38 38  50 50 50  62 62 62
+74 74 74  90 90 90  102 98 90  114 102 78  122 86 26  127 82 6
+138 93 6  154 98 6  154 98 6  154 98 6  134 86 6  118 82 10
+104 70 6  85 65 33  102 98 90  82 82 82  58 58 58  46 46 46
+38 38 38  34 34 34  34 34 34  34 34 34  34 34 34  34 34 34
+34 34 34  34 34 34  34 34 34  34 34 34  38 38 38  42 42 42
+54 54 55  82 82 82  91 83 66  90 62 6  127 82 6  160 108 10
+166 114 6  166 114 6  173 119 7  166 114 6  154 98 6  122 86 26
+102 98 90  62 62 62  34 34 34  18 18 17  6 6 6  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  6 6 6  6 6 6  10 10 10  18 18 17  22 22 22
+30 30 30  42 42 42  50 50 50  70 70 70  87 86 85  102 98 90
+104 84 56  104 70 6  104 70 6  104 70 6  104 70 6  90 62 6
+82 54 6  94 94 94  62 62 62  38 38 38  22 22 22  14 14 14
+10 10 10  10 10 10  10 10 10  10 10 10  10 10 10  10 10 10
+6 6 6  10 10 10  10 10 10  10 10 10  10 10 10  14 14 14
+22 22 22  42 42 42  70 70 70  91 83 66  82 54 6  104 70 6
+127 82 6  138 93 6  134 86 6  118 82 10  104 84 56  87 86 85
+58 58 58  30 30 30  14 14 14  6 6 6  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  6 6 6
+10 10 10  14 14 14  18 18 17  26 26 26  38 38 38  54 54 55
+74 74 74  87 86 85  91 83 66  91 83 66  91 83 66  87 86 85
+78 78 78  50 50 50  30 30 30  14 14 14  6 6 6  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+6 6 6  18 18 17  34 34 34  58 58 58  78 78 78  78 78 78
+91 83 66  91 83 66  91 83 66  91 83 66  74 74 74  50 50 50
+26 26 26  14 14 14  10 6 18  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  6 6 6  6 6 6  14 14 14  18 18 17
+30 30 30  38 38 38  46 46 46  50 50 50  46 46 46  42 42 42
+30 30 30  18 18 17  10 10 10  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  6 6 6  14 14 14  26 26 26  38 38 38  50 50 50
+54 54 55  58 58 58  54 54 55  42 42 42  30 30 30  18 18 17
+10 10 10  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  6 6 6
+6 6 6  10 10 10  14 14 14  18 18 17  18 18 17  14 14 14
+10 10 10  6 6 6  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  6 6 6  14 14 14  18 18 17
+22 22 22  22 22 22  18 18 17  14 14 14  10 10 10  6 6 6
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2  2 2 2  2 2 2  2 2 2  2 2 2
+2 2 2  2 2 2
diff --git a/include/asm-generic/gpio.h b/include/asm-generic/gpio.h
index 66d6106..3e74f68 100644
--- a/include/asm-generic/gpio.h
+++ b/include/asm-generic/gpio.h
@@ -119,6 +119,7 @@ extern void gpio_free(unsigned gpio);
 
 extern int gpio_direction_input(unsigned gpio);
 extern int gpio_direction_output(unsigned gpio, int value);
+extern int gpio_direction_is_output(unsigned gpio);
 
 extern int gpio_get_value_cansleep(unsigned gpio);
 extern void gpio_set_value_cansleep(unsigned gpio, int value);
diff --git a/include/linux/exi.h b/include/linux/exi.h
new file mode 100644
index 0000000..737dcd8
--- /dev/null
+++ b/include/linux/exi.h
@@ -0,0 +1,343 @@
+/*
+ * include/linux/exi.h
+ *
+ * Nintendo GameCube EXpansion Interface definitions
+ * Copyright (C) 2004-2009 The GameCube Linux Team
+ * Copyright (C) 2004 Arthur Othieno <a.othieno@bluewin.ch>
+ * Copyright (C) 2004,2005 Todd Jeffreys <todd@voidpointer.org>
+ * Copyright (C) 2005,2007,2008,2009 Albert Herranz
+ *
+ * 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.
+ *
+ */
+
+#ifndef __EXI_H
+#define __EXI_H
+
+#ifdef CONFIG_GAMECUBE_EXI
+
+#include <linux/device.h>
+#include <linux/io.h>
+
+
+struct exi_channel;
+
+/*
+ *
+ */
+struct exi_device_id {
+	unsigned int		channel;
+#define	EXI_CHANNEL_ANY	(~0)
+
+	unsigned int		device;
+#define	EXI_DEVICE_ANY	(~0)
+
+	u32			id;
+#define	EXI_ID_INVALID	(~0)
+#define	EXI_ID_NONE	(EXI_ID_INVALID-1)
+};
+
+/*
+ *
+ */
+struct exi_device {
+	struct exi_channel	*exi_channel;
+
+	struct exi_device_id	eid;
+	int			frequency;
+
+	unsigned		flags;
+#define EXI_DEV_DYING		(1<<0)
+
+	struct device		dev;
+};
+
+#define to_exi_device(n) container_of(n, struct exi_device, dev)
+
+struct exi_device *exi_get_exi_device(struct exi_channel *exi_channel,
+				      int device);
+
+/*
+ *
+ */
+struct exi_driver {
+	char			*name;
+	struct exi_device_id	*eid_table;
+	int			frequency;
+
+	int  (*probe)  (struct exi_device *dev);
+	void (*remove) (struct exi_device *dev);
+
+	struct device_driver driver;
+};
+
+#define to_exi_driver(n) container_of(n, struct exi_driver, driver)
+
+
+/*
+ * EXpansion Interface devices and drivers.
+ *
+ */
+extern struct exi_device *exi_device_get(struct exi_device *exi_device);
+extern void exi_device_put(struct exi_device *exi_device);
+
+extern int  exi_driver_register(struct exi_driver *exi_driver);
+extern void exi_driver_unregister(struct exi_driver *exi_driver);
+
+static inline void *exi_get_drvdata(struct exi_device *exi_dev)
+{
+	return dev_get_drvdata(&exi_dev->dev);
+}
+
+static inline void exi_set_drvdata(struct exi_device *exi_dev, void *data)
+{
+	dev_set_drvdata(&exi_dev->dev, data);
+}
+
+static inline int exi_is_dying(struct exi_device *exi_device)
+{
+	return exi_device->flags & EXI_DEV_DYING;
+}
+
+static inline int exi_set_dying(struct exi_device *exi_device, int status)
+{
+	if (status)
+		exi_device->flags |= EXI_DEV_DYING;
+	else
+		exi_device->flags &= ~EXI_DEV_DYING;
+
+	return exi_is_dying(exi_device);
+}
+
+extern u32 exi_get_id(struct exi_device *exi_device);
+
+extern void exi_quiesce(void);
+
+/*
+ * EXpansion Interface channels.
+ *
+ */
+
+extern void exi_channel_init(struct exi_channel *exi_channel,
+			     unsigned int channel);
+
+extern struct exi_channel *to_exi_channel(unsigned int channel);
+extern unsigned int to_channel(struct exi_channel *exi_channel);
+
+static inline struct exi_channel *exi_get_exi_channel(struct exi_device *dev)
+{
+	return dev->exi_channel;
+}
+
+#define EXI_EVENT_IRQ     0
+#define EXI_EVENT_INSERT  1
+#define EXI_EVENT_TC      2
+
+typedef int (*exi_event_handler_t)(struct exi_channel *exi_channel,
+				   unsigned int event_id, void *data);
+
+extern int exi_event_register(struct exi_channel *exi_channel,
+			      unsigned int event_id,
+			      struct exi_device *exi_device,
+			      exi_event_handler_t handler, void *data,
+			      unsigned int channel_mask);
+extern int exi_event_unregister(struct exi_channel *exi_channel,
+				unsigned int event_id);
+
+
+/*
+ * Commands.
+ *
+ *
+ */
+struct exi_command {
+	int			opcode;
+#define EXI_OP_READ      (0x00<<2) /* same as in EXIxCR */
+#define EXI_OP_WRITE     (0x01<<2) /* same as in EXIxCR */
+#define EXI_OP_READWRITE (0x02<<2) /* same as in EXIxCR */
+
+#define EXI_OP_TAKE	 0x0100
+#define EXI_OP_GIVE	 0x0200
+#define EXI_OP_SELECT    0x0400
+#define EXI_OP_DESELECT  0x0800
+
+#define EXI_OP_NOP       -1
+
+	unsigned long		flags;
+#define EXI_CMD_NOWAIT (1<<0)
+#define EXI_CMD_NODMA  (1<<1)
+#define EXI_CMD_IDI    (1<<2)
+
+	void			*data;
+	size_t			len;
+	size_t			bytes_left;
+
+	dma_addr_t		dma_addr;
+	size_t			dma_len;
+
+	void			*done_data;
+	void			(*done)(struct exi_command *cmd);
+
+	struct exi_channel	*exi_channel;
+	struct exi_device	*exi_device;
+};
+
+#include "../drivers/exi/exi-hw.h"
+
+static inline void exi_op_basic(struct exi_command *cmd,
+				struct exi_channel *exi_channel)
+{
+	memset(cmd, 0, sizeof(*cmd));
+	cmd->exi_channel = exi_channel;
+	cmd->exi_device = exi_channel_owner(exi_channel);
+}
+
+static inline void exi_op_nop(struct exi_command *cmd,
+			      struct exi_channel *exi_channel)
+{
+	exi_op_basic(cmd, exi_channel);
+	cmd->opcode = EXI_OP_NOP;
+}
+
+static inline void exi_op_take(struct exi_command *cmd,
+			       struct exi_device *exi_device)
+{
+	exi_op_basic(cmd, exi_device->exi_channel);
+	cmd->opcode = EXI_OP_TAKE;
+	cmd->exi_device = exi_device;
+}
+
+static inline void exi_op_give(struct exi_command *cmd,
+			       struct exi_channel *exi_channel)
+{
+	exi_op_basic(cmd, exi_channel);
+	cmd->opcode = EXI_OP_GIVE;
+}
+
+static inline void exi_op_select(struct exi_command *cmd,
+				 struct exi_device *exi_device)
+{
+	exi_op_basic(cmd, exi_device->exi_channel);
+	cmd->opcode = EXI_OP_SELECT;
+	cmd->exi_device = exi_device;
+}
+
+static inline void exi_op_deselect(struct exi_command *cmd,
+				   struct exi_channel *exi_channel)
+{
+	exi_op_basic(cmd, exi_channel);
+	cmd->opcode = EXI_OP_DESELECT;
+}
+
+static inline void exi_op_transfer(struct exi_command *cmd,
+				   struct exi_channel *exi_channel,
+				   void *data, size_t len, int opcode)
+{
+	exi_op_basic(cmd, exi_channel);
+	cmd->opcode = opcode;
+	cmd->data = data;
+	cmd->len = len;
+}
+
+
+/*
+ * EXpansion Interface interfaces.
+ *
+ */
+
+/*
+ * Raw.
+ */
+extern void exi_select_raw(struct exi_channel *exi_channel,
+			   unsigned int device, unsigned int freq);
+extern void exi_deselect_raw(struct exi_channel *exi_channel);
+
+#define exi_transfer_u8_raw  __exi_transfer_raw_u8
+#define exi_transfer_u16_raw __exi_transfer_raw_u16
+#define exi_transfer_u32_raw __exi_transfer_raw_u32
+
+extern void exi_transfer_raw(struct exi_channel *exi_channel,
+			     void *data, size_t len, int mode);
+extern void exi_dma_transfer_raw(struct exi_channel *channel,
+				 dma_addr_t data, size_t len, int mode);
+
+/*
+ * Standard.
+ */
+
+int exi_take(struct exi_device *exi_device, int wait);
+int exi_give(struct exi_device *exi_device);
+void exi_select(struct exi_device *exi_device);
+void exi_deselect(struct exi_channel *exi_channel);
+void exi_transfer(struct exi_channel *exi_channel,
+		  void *data, size_t len, int opcode, unsigned long flags);
+
+static inline int exi_dev_take(struct exi_device *exi_device)
+{
+	return exi_take(exi_device, 1);
+}
+
+static inline int exi_dev_try_take(struct exi_device *exi_device)
+{
+	return exi_take(exi_device, 0);
+}
+
+static inline int exi_dev_give(struct exi_device *exi_device)
+{
+	return exi_give(exi_device);
+}
+
+static inline void exi_dev_select(struct exi_device *exi_device)
+{
+	exi_select(exi_device);
+}
+
+static inline void exi_dev_deselect(struct exi_device *exi_device)
+{
+	exi_deselect(exi_device->exi_channel);
+}
+
+static inline void exi_dev_transfer(struct exi_device *exi_device,
+		      void *data, size_t len, int opcode, unsigned long flags)
+{
+	exi_transfer(exi_device->exi_channel, data, len, opcode, flags);
+}
+
+static inline void exi_dev_read(struct exi_device *dev, void *data, size_t len)
+{
+	exi_dev_transfer(dev, data, len, EXI_OP_READ, 0);
+}
+
+static inline void exi_dev_write(struct exi_device *dev, void *data, size_t len)
+{
+	exi_dev_transfer(dev, data, len, EXI_OP_WRITE, 0);
+}
+
+static inline void exi_dev_readwrite(struct exi_device *dev, void *data,
+				     size_t len)
+{
+	exi_dev_transfer(dev, data, len, EXI_OP_READWRITE, 0);
+}
+
+static inline int exi_dev_set_freq(struct exi_device *dev, unsigned int freq)
+{
+	BUG_ON(freq > EXI_MAX_FREQ);
+
+	dev->frequency = freq;
+
+	return freq;
+}
+
+#else
+
+static inline void exi_quiesce(void)
+{
+}
+
+#endif /* CONFIG_GAMECUBE_EXI */
+
+#endif /* __EXI_H */
+
diff --git a/include/linux/fb.h b/include/linux/fb.h
index 862e7d4..70e3d0f 100644
--- a/include/linux/fb.h
+++ b/include/linux/fb.h
@@ -38,6 +38,11 @@ struct dentry;
 #define FBIOPUT_MODEINFO        0x4617
 #define FBIOGET_DISPINFO        0x4618
 
+#define FBIOWAITRETRACE         0x4619
+#define FBIOWAITPEFINISH        0x4620
+#define FBIOVIRTTOPHYS          0x4621
+#define FBIOFLIP                0x4622
+#define FBIOFLIPHACK            0x4623 /* libsdl */
 
 #define FB_TYPE_PACKED_PIXELS		0	/* Packed Pixels	*/
 #define FB_TYPE_PLANES			1	/* Non interleaved planes */
@@ -907,7 +912,13 @@ struct fb_info {
 #define fb_readq __raw_readq
 #define fb_writeb __raw_writeb
 #define fb_writew __raw_writew
-#define fb_writel __raw_writel
+#ifndef CONFIG_FB_GAMECUBE	/* XXX Why? O' why? */
+#  define fb_writel __raw_writel
+#else
+   extern unsigned int vifb_writel(unsigned int, void *);
+#  define fb_writel(b, addr) vifb_writel(b, addr)
+#  define fb_writel_real(b, addr) (*(/*volatile*/ u32 __iomem *)(addr) = (b))
+#endif
 #define fb_writeq __raw_writeq
 #define fb_memset memset_io
 
diff --git a/include/linux/gpio.h b/include/linux/gpio.h
index 059bd18..8a1ec97 100644
--- a/include/linux/gpio.h
+++ b/include/linux/gpio.h
@@ -51,6 +51,11 @@ static inline int gpio_direction_output(unsigned gpio, int value)
 	return -ENOSYS;
 }
 
+static inline int gpio_direction_is_output(unsigned gpio)
+{
+	return -EINVAL;
+}
+
 static inline int gpio_get_value(unsigned gpio)
 {
 	/* GPIO can never have been requested or set as {in,out}put */
diff --git a/include/linux/i2c-gpio.h b/include/linux/i2c-gpio.h
index c1bcb1f..bc4f01c 100644
--- a/include/linux/i2c-gpio.h
+++ b/include/linux/i2c-gpio.h
@@ -33,6 +33,7 @@ struct i2c_gpio_platform_data {
 	unsigned int	sda_is_open_drain:1;
 	unsigned int	scl_is_open_drain:1;
 	unsigned int	scl_is_output_only:1;
+	unsigned int	sda_enforce_dir:1;
 };
 
 #endif /* _LINUX_I2C_GPIO_H */
diff --git a/include/linux/kernel.h b/include/linux/kernel.h
index f4e3184..e4aa214 100644
--- a/include/linux/kernel.h
+++ b/include/linux/kernel.h
@@ -381,7 +381,7 @@ static inline char *pack_hex_byte(char *buf, u8 byte)
 #define pr_info(fmt, ...) \
         printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)
 #define pr_cont(fmt, ...) \
-	printk(KERN_CONT fmt, ##__VA_ARGS__)
+	printk(KERN_CONT pr_fmt(fmt), ##__VA_ARGS__)
 
 /* pr_devel() should produce zero code unless DEBUG is defined */
 #ifdef DEBUG
diff --git a/include/linux/kexec.h b/include/linux/kexec.h
index adc34f2..01f9095 100644
--- a/include/linux/kexec.h
+++ b/include/linux/kexec.h
@@ -52,10 +52,11 @@
  */
 
 typedef unsigned long kimage_entry_t;
-#define IND_DESTINATION  0x1
-#define IND_INDIRECTION  0x2
-#define IND_DONE         0x4
-#define IND_SOURCE       0x8
+#define IND_DESTINATION  0x01
+#define IND_INDIRECTION  0x02
+#define IND_DONE         0x04
+#define IND_SOURCE       0x08
+#define IND_NOALLOC      0x10 /* special case for memory preserving code */
 
 #define KEXEC_SEGMENT_MAX 16
 struct kexec_segment {
@@ -125,6 +126,8 @@ extern asmlinkage long compat_sys_kexec_load(unsigned long entry,
 #endif
 extern struct page *kimage_alloc_control_pages(struct kimage *image,
 						unsigned int order);
+extern int kimage_add_preserved_region(struct kimage *image, unsigned long to,
+				       unsigned long from, int length);
 extern void crash_kexec(struct pt_regs *);
 int kexec_should_crash(struct task_struct *);
 void crash_save_cpu(struct pt_regs *regs, int cpu);
diff --git a/include/linux/linux_logo.h b/include/linux/linux_logo.h
index ca5bd91..c1e8586 100644
--- a/include/linux/linux_logo.h
+++ b/include/linux/linux_logo.h
@@ -38,6 +38,7 @@ extern const struct linux_logo logo_linux_clut224;
 extern const struct linux_logo logo_blackfin_vga16;
 extern const struct linux_logo logo_blackfin_clut224;
 extern const struct linux_logo logo_dec_clut224;
+extern const struct linux_logo logo_gcn_clut224;
 extern const struct linux_logo logo_mac_clut224;
 extern const struct linux_logo logo_parisc_clut224;
 extern const struct linux_logo logo_sgi_clut224;
diff --git a/kernel/kexec.c b/kernel/kexec.c
index f336e21..3e4f721 100644
--- a/kernel/kexec.c
+++ b/kernel/kexec.c
@@ -580,18 +580,29 @@ static int kimage_set_destination(struct kimage *image,
 }
 
 
-static int kimage_add_page(struct kimage *image, unsigned long page)
+static inline int kimage_add_page_with_flags(struct kimage *image,
+					     unsigned long page, int flags)
 {
 	int result;
 
 	page &= PAGE_MASK;
-	result = kimage_add_entry(image, page | IND_SOURCE);
+	result = kimage_add_entry(image, page | (flags & ~PAGE_MASK));
 	if (result == 0)
 		image->destination += PAGE_SIZE;
 
 	return result;
 }
 
+static int kimage_add_page(struct kimage *image, unsigned long page)
+{
+	return kimage_add_page_with_flags(image, page, IND_SOURCE);
+}
+
+static int kimage_add_page_noalloc(struct kimage *image, unsigned long page)
+{
+	return kimage_add_page_with_flags(image, page, IND_SOURCE|IND_NOALLOC);
+}
+
 
 static void kimage_free_extra_pages(struct kimage *image)
 {
@@ -610,6 +621,27 @@ static void kimage_terminate(struct kimage *image)
 	*image->entry = IND_DONE;
 }
 
+int kimage_add_preserved_region(struct kimage *image, unsigned long to,
+				       unsigned long from, int length)
+{
+	int result = 0;
+
+	if (length > 0) {
+		result = kimage_set_destination(image, to);
+		if (result < 0)
+			goto out;
+		while (length > 0) {
+			result = kimage_add_page_noalloc(image, from);
+			if (result < 0)
+				goto out;
+			from += PAGE_SIZE;
+			length -= PAGE_SIZE;
+		}
+	}
+out:
+	return result;
+}
+
 #define for_each_kimage_entry(image, ptr, entry) \
 	for (ptr = &image->head; (entry = *ptr) && !(entry & IND_DONE); \
 		ptr = (entry & IND_INDIRECTION)? \
@@ -641,9 +673,11 @@ static void kimage_free(struct kimage *image)
 			 * done with it.
 			 */
 			ind = entry;
+		} else if (entry & IND_SOURCE) {
+			/* free only entries that we really allocated */
+			if (!(entry & IND_NOALLOC))
+				kimage_free_entry(entry);
 		}
-		else if (entry & IND_SOURCE)
-			kimage_free_entry(entry);
 	}
 	/* Free the final indirection page */
 	if (ind & IND_INDIRECTION)
diff --git a/sound/ppc/Kconfig b/sound/ppc/Kconfig
index 0519c60..e2faff8 100644
--- a/sound/ppc/Kconfig
+++ b/sound/ppc/Kconfig
@@ -49,4 +49,22 @@ config SND_PS3_DEFAULT_START_DELAY
 	depends on SND_PS3
 	default "2000"
 
+config SND_GAMECUBE
+	tristate "Nintendo GameCube/Wii"
+	depends on SND && GAMECUBE_COMMON
+	help
+	  Say Y here to include support for audio on the Nintendo GameCube/Wii.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-gcn.
+
+config SND_GAMECUBE_MIC
+	tristate "Nintendo GameCube Microphone (DOL-022)"
+	depends on SND && GAMECUBE_EXI && EXPERIMENTAL
+	help
+	  If you say yes to this option, support will be included for the
+	  Nintendo GameCube Microphone (DOL-022).
+
+	  If in doubt, say N here.
+
 endif	# SND_PPC
diff --git a/sound/ppc/Makefile b/sound/ppc/Makefile
index 679c45a..fa77f53 100644
--- a/sound/ppc/Makefile
+++ b/sound/ppc/Makefile
@@ -4,7 +4,10 @@
 #
 
 snd-powermac-objs := powermac.o pmac.o awacs.o burgundy.o daca.o tumbler.o keywest.o beep.o
+snd-gcn-objs := gcn-ai.o
 
 # Toplevel Module Dependency
 obj-$(CONFIG_SND_POWERMAC)	+= snd-powermac.o
 obj-$(CONFIG_SND_PS3)		+= snd_ps3.o
+obj-$(CONFIG_SND_GAMECUBE)	+= snd-gcn.o
+obj-$(CONFIG_SND_GAMECUBE_MIC)	+= gcn-mic.o
diff --git a/sound/ppc/gcn-ai.c b/sound/ppc/gcn-ai.c
new file mode 100644
index 0000000..0269137
--- /dev/null
+++ b/sound/ppc/gcn-ai.c
@@ -0,0 +1,598 @@
+/*
+ * sound/ppc/gcn-ai.c
+ *
+ * Nintendo GameCube/Wii Audio Interface (AI) driver
+ * Copyright (C) 2004-2009 The GameCube Linux Team
+ * Copyright (C) 2007,2008,2009 Albert Herranz
+ *
+ * Based on work from mist, kirin, groepaz, Steve_-, isobel and others.
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/of_platform.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#define SNDRV_GET_ID
+#include <sound/initval.h>
+
+
+#define DRV_MODULE_NAME  "gcn-ai"
+#define DRV_DESCRIPTION  "Nintendo GameCube/Wii Audio Interface (AI) driver"
+#define DRV_AUTHOR       "Michael Steil, " \
+			 "(kirin), " \
+			 "(groepaz), " \
+			 "Steven Looman, " \
+			 "Albert Herranz"
+
+static char ai_driver_version[] = "1.0i";
+
+#define drv_printk(level, format, arg...) \
+	 printk(level DRV_MODULE_NAME ": " format , ## arg)
+
+
+/*
+ * Hardware.
+ *
+ */
+
+/*
+ * DSP registers.
+ */
+#define AI_DSP_CSR		0x0a	/* 16 bits */
+#define  AI_CSR_RES		(1<<0)
+#define  AI_CSR_PIINT		(1<<1)
+#define  AI_CSR_HALT		(1<<2)
+#define  AI_CSR_AIDINT		(1<<3)
+#define  AI_CSR_AIDINTMASK	(1<<4)
+#define  AI_CSR_ARINT		(1<<5)
+#define  AI_CSR_ARINTMASK	(1<<6)
+#define  AI_CSR_DSPINT		(1<<7)
+#define  AI_CSR_DSPINTMASK	(1<<8)
+#define  AI_CSR_DSPDMA		(1<<9)
+#define  AI_CSR_RESETXXX	(1<<11)
+
+#define AI_DSP_DMA_ADDRH	0x30	/* 16 bits */
+
+#define AI_DSP_DMA_ADDRL	0x32	/* 16 bits */
+
+#define AI_DSP_DMA_CTLLEN	0x36	/* 16 bits */
+#define  AI_CTLLEN_PLAY		(1<<15)
+
+#define AI_DSP_DMA_LEFT		0x3a	/* 16 bits */
+
+/*
+ * AI registers.
+ */
+#define AI_AICR			0x00	/* 32 bits */
+#define  AI_AICR_RATE       (1<<6)
+
+
+/*
+ * Sound chip.
+ */
+struct snd_gcn {
+	struct snd_card			*card;
+	struct snd_pcm			*pcm;
+	struct snd_pcm_substream	*playback_substream;
+	struct snd_pcm_substream	*capture_substream;
+
+	int		start_play;
+	int		stop_play;
+
+	dma_addr_t	dma_addr;
+	size_t		period_size;
+	int		nperiods;
+	int		cur_period;
+
+	void __iomem	*dsp_base;
+	void __iomem	*ai_base;
+	unsigned int	irq;
+
+	struct device	*dev;
+};
+
+
+/*
+ * Hardware functions.
+ *
+ */
+
+static void ai_dsp_load_sample(void __iomem *dsp_base,
+			       void *addr, size_t size)
+{
+	u32 daddr = (unsigned long)addr;
+
+	out_be16(dsp_base + AI_DSP_DMA_ADDRH, daddr >> 16);
+	out_be16(dsp_base + AI_DSP_DMA_ADDRL, daddr & 0xffff);
+	out_be16(dsp_base + AI_DSP_DMA_CTLLEN,
+		 (in_be16(dsp_base + AI_DSP_DMA_CTLLEN) & AI_CTLLEN_PLAY) |
+		 size >> 5);
+}
+
+static void ai_dsp_start_sample(void __iomem *dsp_base)
+{
+	out_be16(dsp_base + AI_DSP_DMA_CTLLEN,
+		 in_be16(dsp_base + AI_DSP_DMA_CTLLEN) | AI_CTLLEN_PLAY);
+}
+
+static void ai_dsp_stop_sample(void __iomem *dsp_base)
+{
+	out_be16(dsp_base + AI_DSP_DMA_CTLLEN,
+		 in_be16(dsp_base + AI_DSP_DMA_CTLLEN) & ~AI_CTLLEN_PLAY);
+}
+
+static int ai_dsp_get_remaining_byte_count(void __iomem *dsp_base)
+{
+	return in_be16(dsp_base + AI_DSP_DMA_LEFT) << 5;
+}
+
+static void ai_enable_interrupts(void __iomem *dsp_base)
+{
+	unsigned long flags;
+
+	/* enable AI DMA and DSP interrupts */
+	local_irq_save(flags);
+	out_be16(dsp_base + AI_DSP_CSR,
+		 in_be16(dsp_base + AI_DSP_CSR) |
+		 AI_CSR_AIDINTMASK | AI_CSR_PIINT);
+	local_irq_restore(flags);
+}
+
+static void ai_disable_interrupts(void __iomem *dsp_base)
+{
+	unsigned long flags;
+
+	/* disable AI interrupts */
+	local_irq_save(flags);
+	out_be16(dsp_base + AI_DSP_CSR,
+		 in_be16(dsp_base + AI_DSP_CSR) & ~AI_CSR_AIDINTMASK);
+	local_irq_restore(flags);
+}
+
+static void ai_set_rate(void __iomem *ai_base, int fortyeight)
+{
+	/* set rate to 48KHz or 32KHz */
+	if (fortyeight)
+		out_be32(ai_base + AI_AICR,
+			 in_be32(ai_base + AI_AICR) & ~AI_AICR_RATE);
+	else
+		out_be32(ai_base + AI_AICR,
+			 in_be32(ai_base + AI_AICR) | AI_AICR_RATE);
+}
+
+
+static int index = SNDRV_DEFAULT_IDX1;	/* index 0-MAX */
+static char *id = SNDRV_DEFAULT_STR1;	/* ID for this card */
+
+static struct snd_gcn *gcn_audio;
+
+static struct snd_pcm_hardware snd_gcn_playback = {
+	.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+		 SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID),
+	.formats = SNDRV_PCM_FMTBIT_S16_BE,
+	.rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000,
+	.rate_min = 32000,
+	.rate_max = 48000,
+	.channels_min = 2,
+	.channels_max = 2,
+	.buffer_bytes_max = 32768,
+	.period_bytes_min = 32,
+	.period_bytes_max = 32768,
+	.periods_min = 1,
+	.periods_max = 1024,
+};
+
+static int snd_gcn_open(struct snd_pcm_substream *substream)
+{
+	struct snd_gcn *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	chip->playback_substream = substream;
+	runtime->hw = snd_gcn_playback;
+
+	/* align to 32 bytes */
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+				   32);
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+				   32);
+
+	return 0;
+}
+
+static int snd_gcn_close(struct snd_pcm_substream *substream)
+{
+	struct snd_gcn *chip = snd_pcm_substream_chip(substream);
+
+	chip->playback_substream = NULL;
+	return 0;
+}
+
+static int snd_gcn_hw_params(struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+}
+
+static int snd_gcn_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_gcn_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_gcn *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	/* set requested sample rate */
+	switch (runtime->rate) {
+	case 32000:
+		ai_set_rate(chip->ai_base, 0);
+		break;
+	case 48000:
+		ai_set_rate(chip->ai_base, 1);
+		break;
+	default:
+		drv_printk(KERN_ERR, "unsupported rate %i\n", runtime->rate);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int snd_gcn_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_gcn *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		/* do something to start the PCM engine */
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			chip->period_size = snd_pcm_lib_period_bytes(substream);
+			chip->nperiods = snd_pcm_lib_buffer_bytes(substream) /
+					 chip->period_size;
+			chip->cur_period = 0;
+			chip->stop_play = 0;
+			chip->start_play = 1;
+
+			chip->dma_addr = dma_map_single(chip->dev,
+							runtime->dma_area,
+							chip->period_size,
+							DMA_TO_DEVICE);
+			ai_dsp_load_sample(chip->dsp_base, runtime->dma_area,
+					   chip->period_size);
+			ai_dsp_start_sample(chip->dsp_base);
+		}
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		chip->stop_play = 1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static snd_pcm_uframes_t snd_gcn_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_gcn *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int left, bytes;
+
+	left = ai_dsp_get_remaining_byte_count(chip->dsp_base);
+	bytes = chip->period_size * (chip->cur_period + 1);
+
+	return bytes_to_frames(runtime, bytes - left);
+}
+
+static irqreturn_t snd_gcn_interrupt(int irq, void *dev)
+{
+	struct snd_gcn *chip = dev;
+	void *addr;
+	unsigned long flags;
+	u16 csr;
+
+	/*
+	 * This is a shared interrupt. Do nothing if it ain't ours.
+	 */
+	csr = in_be16(chip->dsp_base + AI_DSP_CSR);
+	if (!(csr & AI_CSR_AIDINT))
+		return IRQ_NONE;
+
+	if (chip->start_play) {
+		chip->start_play = 0;
+	} else {
+		/* stop current sample */
+		ai_dsp_stop_sample(chip->dsp_base);
+		dma_unmap_single(chip->dev, chip->dma_addr, chip->period_size,
+				 DMA_TO_DEVICE);
+
+		/* load next sample if we are not stopping */
+		if (!chip->stop_play) {
+			if (chip->cur_period < (chip->nperiods - 1))
+				chip->cur_period++;
+			else
+				chip->cur_period = 0;
+
+			addr = chip->playback_substream->runtime->dma_area
+				   + (chip->cur_period * chip->period_size);
+			chip->dma_addr = dma_map_single(chip->dev,
+							addr,
+							chip->period_size,
+							DMA_TO_DEVICE);
+			ai_dsp_load_sample(chip->dsp_base, addr,
+					   chip->period_size);
+			ai_dsp_start_sample(chip->dsp_base);
+
+			snd_pcm_period_elapsed(chip->playback_substream);
+		}
+	}
+	/*
+	 * Ack the AI DMA interrupt, going through lengths to only ack
+	 * the audio part.
+	 */
+	local_irq_save(flags);
+	csr = in_be16(chip->dsp_base + AI_DSP_CSR);
+	csr &= ~(AI_CSR_PIINT | AI_CSR_ARINT | AI_CSR_DSPINT);
+	out_be16(chip->dsp_base + AI_DSP_CSR, csr);
+	local_irq_restore(flags);
+
+	return IRQ_HANDLED;
+}
+
+
+static struct snd_pcm_ops snd_gcn_playback_ops = {
+	.open = snd_gcn_open,
+	.close = snd_gcn_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = snd_gcn_hw_params,
+	.hw_free = snd_gcn_hw_free,
+	.prepare = snd_gcn_prepare,
+	.trigger = snd_gcn_trigger,
+	.pointer = snd_gcn_pointer,
+};
+
+static int __devinit snd_gcn_new_pcm(struct snd_gcn *chip)
+{
+	struct snd_pcm *pcm;
+	int retval;
+
+	retval = snd_pcm_new(chip->card, chip->card->shortname, 0, 1, 0, &pcm);
+	if (retval < 0)
+		return retval;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+			&snd_gcn_playback_ops);
+
+	/* preallocate 64k buffer */
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
+					      snd_dma_continuous_data
+					      (GFP_KERNEL), 64 * 1024,
+					      64 * 1024);
+
+	pcm->info_flags = 0;
+	pcm->private_data = chip;
+	strcpy(pcm->name, chip->card->shortname);
+
+	chip->pcm = pcm;
+
+	return 0;
+}
+
+static void ai_shutdown(struct snd_gcn *chip)
+{
+	ai_dsp_stop_sample(chip->dsp_base);
+	ai_disable_interrupts(chip->dsp_base);
+}
+
+static int __devinit ai_init(struct snd_gcn *chip,
+		   struct resource *dsp, struct resource *ai,
+		   unsigned int irq)
+{
+	struct snd_card *card;
+	int retval;
+
+	chip->dsp_base = ioremap(dsp->start, dsp->end - dsp->start + 1);
+	chip->ai_base = ioremap(ai->start, ai->end - ai->start + 1);
+	chip->irq = irq;
+
+	chip->stop_play = 1;
+	card = chip->card;
+
+	strcpy(card->driver, DRV_MODULE_NAME);
+	strcpy(card->shortname, card->driver);
+	sprintf(card->longname, "Nintendo GameCube Audio Interface");
+
+	/* PCM */
+	retval = snd_gcn_new_pcm(chip);
+	if (retval < 0)
+		goto err_new_pcm;
+
+	retval = request_irq(chip->irq, snd_gcn_interrupt,
+			     IRQF_DISABLED | IRQF_SHARED,
+			     card->shortname, chip);
+	if (retval) {
+		drv_printk(KERN_ERR, "unable to request IRQ %d\n", chip->irq);
+		goto err_request_irq;
+	}
+	ai_enable_interrupts(chip->dsp_base);
+
+	gcn_audio = chip;
+	retval = snd_card_register(card);
+	if (retval) {
+		drv_printk(KERN_ERR, "failed to register card\n");
+		goto err_card_register;
+	}
+
+	return 0;
+
+err_card_register:
+	ai_disable_interrupts(chip->dsp_base);
+	free_irq(chip->irq, chip);
+err_request_irq:
+err_new_pcm:
+	iounmap(chip->dsp_base);
+	iounmap(chip->ai_base);
+	return retval;
+}
+
+static void ai_exit(struct snd_gcn *chip)
+{
+	ai_dsp_stop_sample(chip->dsp_base);
+	ai_disable_interrupts(chip->dsp_base);
+
+	free_irq(chip->irq, chip);
+	iounmap(chip->dsp_base);
+	iounmap(chip->ai_base);
+}
+
+
+/*
+ * Device interfaces.
+ *
+ */
+
+static int ai_do_shutdown(struct device *dev)
+{
+	struct snd_gcn *chip;
+
+	chip = dev_get_drvdata(dev);
+	if (chip) {
+		ai_shutdown(chip);
+		return 0;
+	}
+	return -ENODEV;
+}
+
+static int __devinit ai_do_probe(struct device *dev,
+		       struct resource *dsp, struct resource *ai,
+		       unsigned int irq)
+{
+	struct snd_card *card;
+	struct snd_gcn *chip;
+	int retval;
+
+	retval = snd_card_create(index, id, THIS_MODULE, sizeof(struct snd_gcn), &card);
+	if (retval < 0) {
+		drv_printk(KERN_ERR, "failed to allocate card\n");
+		return -ENOMEM;
+	}
+	chip = (struct snd_gcn *)card->private_data;
+	memset(chip, 0, sizeof(*chip));
+	chip->card = card;
+	dev_set_drvdata(dev, chip);
+	chip->dev = dev;
+
+	retval = ai_init(chip, dsp, ai, irq);
+	if (retval)
+		snd_card_free(card);
+
+	return retval;
+}
+
+static int ai_do_remove(struct device *dev)
+{
+	struct snd_gcn *chip;
+
+	chip = dev_get_drvdata(dev);
+	if (chip) {
+		ai_exit(chip);
+		dev_set_drvdata(dev, NULL);
+		snd_card_free(chip->card);
+		return 0;
+	}
+	return -ENODEV;
+}
+
+/*
+ * OF Platform device interfaces.
+ *
+ */
+
+static int __init ai_of_probe(struct of_device *odev,
+			      const struct of_device_id *match)
+{
+	struct resource dsp, ai;
+	int retval;
+
+	retval = of_address_to_resource(odev->node, 0, &dsp);
+	if (retval) {
+		drv_printk(KERN_ERR, "no dsp io memory range found\n");
+		return -ENODEV;
+	}
+	retval = of_address_to_resource(odev->node, 1, &ai);
+	if (retval) {
+		drv_printk(KERN_ERR, "no ai io memory range found\n");
+		return -ENODEV;
+	}
+
+	return ai_do_probe(&odev->dev,
+			   &dsp, &ai, irq_of_parse_and_map(odev->node, 0));
+}
+
+static int __exit ai_of_remove(struct of_device *odev)
+{
+	return ai_do_remove(&odev->dev);
+}
+
+static int ai_of_shutdown(struct of_device *odev)
+{
+	return ai_do_shutdown(&odev->dev);
+}
+
+
+static struct of_device_id ai_of_match[] = {
+	{ .compatible = "nintendo,flipper-audio" },
+	{ .compatible = "nintendo,hollywood-audio" },
+	{ },
+};
+
+MODULE_DEVICE_TABLE(of, ai_of_match);
+
+static struct of_platform_driver ai_of_driver = {
+	.owner = THIS_MODULE,
+	.name = DRV_MODULE_NAME,
+	.match_table = ai_of_match,
+	.probe = ai_of_probe,
+	.remove = ai_of_remove,
+	.shutdown = ai_of_shutdown,
+};
+
+/*
+ * Module interfaces.
+ *
+ */
+
+static int __init ai_init_module(void)
+{
+	drv_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION,
+		   ai_driver_version);
+
+	return of_register_platform_driver(&ai_of_driver);
+}
+
+static void __exit ai_exit_module(void)
+{
+	of_unregister_platform_driver(&ai_of_driver);
+}
+
+module_init(ai_init_module);
+module_exit(ai_exit_module);
+
+MODULE_DESCRIPTION(DRV_DESCRIPTION);
+MODULE_AUTHOR(DRV_AUTHOR);
+MODULE_LICENSE("GPL");
+
diff --git a/sound/ppc/gcn-mic.c b/sound/ppc/gcn-mic.c
new file mode 100644
index 0000000..47a163f
--- /dev/null
+++ b/sound/ppc/gcn-mic.c
@@ -0,0 +1,832 @@
+/*
+ * sound/ppc/gcn-mic.c
+ *
+ * Nintendo Microphone (DOL-022) driver
+ * Copyright (C) 2006-2009 The GameCube Linux Team
+ * Copyright (C) 2006,2007,2008,2009 Albert Herranz
+ *
+ * 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.
+ *
+ */
+
+#define MIC_DEBUG
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kthread.h>
+#include <linux/delay.h>
+#include <linux/freezer.h>
+#include <linux/proc_fs.h>
+#include <linux/exi.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#define SNDRV_GET_ID
+#include <sound/initval.h>
+
+#define DRV_MODULE_NAME "gcn-mic"
+#define DRV_DESCRIPTION "Nintendo Microphone (DOL-022) driver"
+#define DRV_AUTHOR      "Albert Herranz"
+
+MODULE_AUTHOR(DRV_AUTHOR);
+MODULE_DESCRIPTION(DRV_DESCRIPTION);
+MODULE_LICENSE("GPL");
+
+static char mic_driver_version[] = "0.1i";
+
+#define mic_printk(level, format, arg...) \
+	printk(level DRV_MODULE_NAME ": " format , ## arg)
+
+#ifdef MIC_DEBUG
+#  define DBG(fmt, args...) \
+	   printk(KERN_ERR "%s: " fmt, __func__ , ## args)
+#else
+#  define DBG(fmt, args...)
+#endif
+
+
+#define MIC_EXI_ID		0x0a000000
+
+#define MIC_SLOTA_CHANNEL	0	/* EXI0xxx */
+#define MIC_SLOTA_DEVICE	0	/* chip select, EXI0CSB0 */
+
+#define MIC_SLOTB_CHANNEL	1	/* EXI1xxx */
+#define MIC_SLOTB_DEVICE	0	/* chip select, EXI1CSB0 */
+
+#define MIC_SPI_CLK_IDX		EXI_CLK_16MHZ
+
+
+struct mic_device {
+	spinlock_t lock;
+	unsigned long flags;
+
+	u16 status;
+	u16 control;
+#define MIC_CTL_RATE_MASK	(0x3<<11)
+#define MIC_CTL_RATE_11025	(0x0<<11)
+#define MIC_CTL_RATE_22050	(0x1<<11)
+#define MIC_CTL_RATE_44100	(0x2<<11)
+#define MIC_CTL_PERIOD_MASK	(0x3<<13)
+#define MIC_CTL_PERIOD_32	(0x0<<13)
+#define MIC_CTL_PERIOD_64	(0x1<<13)
+#define MIC_CTL_PERIOD_128	(0x2<<13)
+#define MIC_CTL_START_SAMPLING	(1<<15)
+
+	struct task_struct      *io_thread;
+	wait_queue_head_t       io_waitq;
+	atomic_t		io_pending;
+
+	struct snd_card *card;
+	struct snd_pcm *pcm;
+
+	struct snd_pcm_substream *c_substream;
+	u8	*c_orig, *c_cur;
+	int	c_left;
+
+	int running;
+
+#ifdef CONFIG_PROC_FS
+	struct proc_dir_entry           *proc;
+#endif /* CONFIG_PROC_FS */
+
+	int refcnt;
+	struct exi_device *exi_device;
+};
+
+
+/*
+ *
+ */
+static void mic_hey(struct mic_device *dev)
+{
+	struct exi_device *exi_device = dev->exi_device;
+	u8 cmd = 0xff;
+
+	exi_dev_select(exi_device);
+	exi_dev_write(exi_device, &cmd, sizeof(cmd));
+	exi_dev_deselect(exi_device);
+}
+
+/*
+ *
+ */
+static int mic_get_status(struct mic_device *dev)
+{
+	struct exi_device *exi_device = dev->exi_device;
+	u8 cmd = 0x40;
+
+	exi_dev_select(exi_device);
+	exi_dev_write(exi_device, &cmd, sizeof(cmd));
+	exi_dev_read(exi_device, &dev->status, sizeof(dev->status));
+	exi_dev_deselect(exi_device);
+
+	return dev->status;
+}
+
+/*
+ *
+ */
+static void mic_control(struct mic_device *dev)
+{
+	struct exi_device *exi_device = dev->exi_device;
+	u8 cmd[3];
+
+	cmd[0] = 0x80;
+	cmd[1] = dev->control >> 8;
+	cmd[2] = dev->control & 0xff;
+
+	DBG("control 0x80%02x%02x\n", cmd[1], cmd[2]);
+
+	exi_dev_select(exi_device);
+	exi_dev_write(exi_device, cmd, sizeof(cmd));
+	exi_dev_deselect(exi_device);
+
+}
+
+/*
+ *
+ */
+static void mic_read_period(struct mic_device *dev, void *buf, size_t len)
+{
+	struct exi_device *exi_device = dev->exi_device;
+	u8 cmd = 0x20;
+
+	exi_dev_select(exi_device);
+	exi_dev_write(exi_device, &cmd, sizeof(cmd));
+	exi_dev_read(exi_device, buf, len);
+	exi_dev_deselect(exi_device);
+
+/*	DBG("mic cmd 0x20\n"); */
+}
+
+/*
+ *
+ */
+static void mic_enable_sampling(struct mic_device *dev, int enable)
+{
+	if (enable)
+		dev->control |= MIC_CTL_START_SAMPLING;
+	else
+		dev->control &= ~MIC_CTL_START_SAMPLING;
+}
+
+/*
+ *
+ */
+static int mic_set_sample_rate(struct mic_device *dev, int rate)
+{
+	u16 control;
+
+	switch (rate) {
+	case 11025:
+		control = MIC_CTL_RATE_11025;
+		break;
+	case 22050:
+		control = MIC_CTL_RATE_22050;
+		break;
+	case 44100:
+		control = MIC_CTL_RATE_44100;
+		break;
+	default:
+		mic_printk(KERN_ERR, "unsupported rate: %d\n", rate);
+		return -EINVAL;
+	}
+	dev->control &= ~MIC_CTL_RATE_MASK;
+	dev->control |= control;
+	return 0;
+}
+
+/*
+ *
+ */
+static int mic_set_period(struct mic_device *dev, int period_bytes)
+{
+	u16 control;
+
+	switch (period_bytes) {
+	case 32:
+		control = MIC_CTL_PERIOD_32;
+		break;
+	case 64:
+		control = MIC_CTL_PERIOD_64;
+		break;
+	case 128:
+		control = MIC_CTL_PERIOD_128;
+		break;
+	default:
+		mic_printk(KERN_ERR, "unsupported period: %d bytes\n",
+			   period_bytes);
+		return -EINVAL;
+	}
+	dev->control &= ~MIC_CTL_PERIOD_MASK;
+	dev->control |= control;
+	return 0;
+}
+
+/*
+ * /proc support
+ *
+ */
+
+/*
+ *
+ */
+static int mic_init_proc(struct mic_device *dev)
+{
+	return 0;
+}
+
+/*
+ *
+ */
+static void mic_exit_proc(struct mic_device *dev)
+{
+}
+
+
+
+/*
+ * Driver
+ *
+ */
+
+static int index = SNDRV_DEFAULT_IDX1;
+static char *id = SNDRV_DEFAULT_STR1;
+
+static struct snd_pcm_hardware mic_snd_capture = {
+#if 0
+	.info = (SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_NONINTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_MMAP_VALID),
+#endif
+	.info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_NONINTERLEAVED),
+	.formats = SNDRV_PCM_FMTBIT_S16_BE,
+	.rates = SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_22050 |
+		SNDRV_PCM_RATE_44100,
+	.rate_min = 11025,
+	.rate_max = 44100,
+	.channels_min = 1,
+	.channels_max = 1,
+	.buffer_bytes_max = 32768,
+	.period_bytes_min = 32,
+	.period_bytes_max = 128,
+	.periods_min = 1,
+	.periods_max = 1024,
+};
+
+#if 0
+static unsigned int period_bytes[] = { 32, 64, 128 };
+static struct snd_pcm_hw_constraint_list constraints_period_bytes = {
+	.count = ARRAY_SIZE(period_bytes),
+	.list = period_bytes,
+	.mask = 0,
+};
+#endif
+
+/*
+ *
+ */
+static void mic_wakeup_io_thread(struct mic_device *dev)
+{
+	if (!IS_ERR(dev->io_thread)) {
+		atomic_inc(&dev->io_pending);
+		wake_up(&dev->io_waitq);
+	}
+}
+
+/*
+ *
+ */
+static void mic_stop_io_thread(struct mic_device *dev)
+{
+	if (!IS_ERR(dev->io_thread)) {
+		atomic_inc(&dev->io_pending);
+		kthread_stop(dev->io_thread);
+	}
+}
+
+/*
+ * Input/Output thread. Receives audio samples from the microphone.
+ */
+static int mic_io_thread(void *param)
+{
+	struct mic_device *dev = param;
+	struct snd_pcm_substream *substream;
+	int period_bytes;
+	u16 status;
+
+	set_user_nice(current, -20);
+	set_current_state(TASK_RUNNING);
+
+	for (;;) {
+		wait_event(dev->io_waitq, atomic_read(&dev->io_pending) > 0);
+		atomic_dec(&dev->io_pending);
+
+		if (kthread_should_stop())
+			break;
+
+		if (try_to_freeze())
+			continue;
+
+		exi_dev_take(dev->exi_device);
+		status = mic_get_status(dev);
+		if (dev->running) {
+			substream = dev->c_substream;
+
+			if (!dev->c_left) {
+				dev->c_cur = dev->c_orig;
+				dev->c_left =
+					snd_pcm_lib_buffer_bytes(substream);
+			}
+
+			period_bytes = snd_pcm_lib_period_bytes(substream);
+			if (period_bytes > dev->c_left)
+				period_bytes = dev->c_left;
+			mic_read_period(dev, dev->c_cur, period_bytes);
+			dev->c_cur += period_bytes;
+			dev->c_left -= period_bytes;
+
+			exi_dev_give(dev->exi_device);
+			snd_pcm_period_elapsed(substream);
+			exi_dev_take(dev->exi_device);
+
+			if (status & 0x0200) {
+				DBG("0x0200\n");
+				mic_hey(dev);
+				mic_enable_sampling(dev, 1);
+				mic_control(dev);
+			}
+		} else {
+			/* mic_enable_sampling(dev, 0); */
+			dev->control = 0;
+			mic_control(dev);
+		}
+		exi_dev_give(dev->exi_device);
+	}
+	return 0;
+}
+
+/*
+ *
+ */
+static int mic_event_handler(struct exi_channel *exi_channel,
+			     unsigned int event, void *dev0)
+{
+	struct mic_device *dev = (struct mic_device *)dev0;
+
+	/* exi channel is not taken, no exi operations here please */
+	mic_wakeup_io_thread(dev);
+
+	return 0;
+}
+
+static int hw_rule_period_bytes_by_rate(struct snd_pcm_hw_params *params,
+					struct snd_pcm_hw_rule *rule)
+{
+	struct snd_interval *period_bytes =
+		hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_BYTES);
+	struct snd_interval *rate =
+		hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+
+	DBG("rate: min %d, max %d\n", rate->min, rate->max);
+
+	if (rate->min == rate->max) {
+		if (rate->min >= 44100) {
+			struct snd_interval t = {
+				.min = 128,
+				.max = 128,
+				.integer = 1,
+			};
+			return snd_interval_refine(period_bytes, &t);
+		} else if (rate->min >= 22050) {
+			struct snd_interval t = {
+				.min = 32,
+				.max = 32,
+				.integer = 1,
+			};
+			return snd_interval_refine(period_bytes, &t);
+		} else {
+			struct snd_interval t = {
+				.min = 32,
+				.max = 32,
+				.integer = 1,
+			};
+			return snd_interval_refine(period_bytes, &t);
+		}
+	}
+	return 0;
+}
+
+static int mic_snd_pcm_capture_open(struct snd_pcm_substream *substream)
+{
+	struct mic_device *dev = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned long flags;
+	int retval;
+
+	DBG("enter\n");
+
+	spin_lock_irqsave(&dev->lock, flags);
+	dev->running = 0;
+	dev->c_substream = substream;
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	runtime->hw = mic_snd_capture;
+
+#if 0
+	/* only 32, 64 and 128 */
+	retval = snd_pcm_hw_constraint_list(runtime, 0,
+					    SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+					    &constraints_period_bytes);
+	if (retval < 0)
+		return retval;
+#endif
+	snd_pcm_hw_rule_add(runtime, 0,
+			    SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+			    hw_rule_period_bytes_by_rate, 0,
+			    SNDRV_PCM_HW_PARAM_RATE, -1);
+
+	/* align to 32 bytes */
+	retval = snd_pcm_hw_constraint_step(runtime, 0,
+					    SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+					    32);
+	return retval;
+
+}
+
+static int mic_snd_pcm_capture_close(struct snd_pcm_substream *substream)
+{
+	struct mic_device *dev = snd_pcm_substream_chip(substream);
+	unsigned long flags;
+
+DBG("enter\n");
+
+	spin_lock_irqsave(&dev->lock, flags);
+	dev->running = 0;
+	dev->c_substream = NULL;
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	mic_wakeup_io_thread(dev);
+
+	return 0;
+}
+
+static int mic_snd_pcm_hw_params(struct snd_pcm_substream *substream,
+			     struct snd_pcm_hw_params *hw_params)
+{
+DBG("enter\n");
+
+	return snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+}
+
+static int mic_snd_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+DBG("enter\n");
+
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+static int mic_snd_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct mic_device *dev = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned long flags;
+	int retval;
+
+DBG("enter\n");
+
+	mic_printk(KERN_INFO, "rate=%d, channels=%d, sample_bits=%d\n",
+			runtime->rate, runtime->channels,
+			runtime->sample_bits);
+	mic_printk(KERN_INFO, "format=%d, access=%d\n",
+			runtime->format, runtime->access);
+	mic_printk(KERN_INFO, "buffer_bytes=%d, period_bytes=%d\n",
+			snd_pcm_lib_buffer_bytes(substream),
+			snd_pcm_lib_period_bytes(substream));
+
+	spin_lock_irqsave(&dev->lock, flags);
+	dev->c_orig = runtime->dma_area;
+	dev->c_left = 0;
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	retval = mic_set_sample_rate(dev, runtime->rate);
+	if (retval < 0)
+		return retval;
+
+	retval = mic_set_period(dev, snd_pcm_lib_period_bytes(substream));
+
+	return retval;
+}
+
+static int mic_snd_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct mic_device *dev = snd_pcm_substream_chip(substream);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		if (!dev->running) {
+			DBG("trigger start\n");
+			dev->running = 1;
+			exi_dev_take(dev->exi_device);
+			mic_hey(dev);
+			mic_enable_sampling(dev, 1);
+			mic_control(dev);
+			exi_dev_give(dev->exi_device);
+		}
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		DBG("trigger stop\n");
+		dev->running = 0;
+		break;
+	}
+	return 0;
+}
+
+static snd_pcm_uframes_t
+mic_snd_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct mic_device *dev = snd_pcm_substream_chip(substream);
+	size_t ptr;
+
+	if (!dev->running || !dev->c_left)
+		return 0;
+
+	ptr = dev->c_cur - dev->c_orig;
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+
+static struct snd_pcm_ops mic_snd_pcm_capture_ops = {
+	.open =        mic_snd_pcm_capture_open,
+	.close =       mic_snd_pcm_capture_close,
+	.ioctl =       snd_pcm_lib_ioctl,
+	.hw_params =   mic_snd_pcm_hw_params,
+	.hw_free =     mic_snd_pcm_hw_free,
+	.prepare =     mic_snd_pcm_prepare,
+	.trigger =     mic_snd_pcm_trigger,
+	.pointer =     mic_snd_pcm_pointer,
+};
+
+/*
+ *
+ */
+static int mic_snd_new_pcm(struct mic_device *dev)
+{
+	struct snd_pcm *pcm;
+	int retval;
+
+DBG("enter\n");
+
+	retval = snd_pcm_new(dev->card, dev->card->shortname, 0, 0, 1, &pcm);
+	if (retval < 0)
+		return retval;
+
+	pcm->private_data = dev;
+	strcpy(pcm->name, dev->card->shortname);
+	dev->pcm = pcm;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+			&mic_snd_pcm_capture_ops);
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
+					      snd_dma_continuous_data
+					      (GFP_KERNEL),
+					      32*1024, 32*1024);
+	return 0;
+}
+
+/*
+ *
+ */
+static int mic_init_snd(struct mic_device *dev)
+{
+	struct snd_card *card;
+	int retval = -ENOMEM;
+
+DBG("enter\n");
+
+	retval = snd_card_create(index, id, THIS_MODULE, 0, &card);
+	if (retval < 0) {
+		mic_printk(KERN_ERR, "unable to create sound card\n");
+		goto err_card;
+	}
+
+	strcpy(card->driver, DRV_MODULE_NAME);
+	strcpy(card->shortname, DRV_MODULE_NAME);
+	strcpy(card->longname, "Nintendo GameCube Microphone");
+
+	dev->card = card;
+
+	retval = mic_snd_new_pcm(dev);
+	if (retval < 0)
+		goto err_new_pcm;
+
+	retval = snd_card_register(card);
+	if (retval) {
+		mic_printk(KERN_ERR, "unable to register sound card\n");
+		goto err_card_register;
+	}
+
+	return 0;
+
+err_card_register:
+err_new_pcm:
+	snd_card_free(card);
+	dev->card = NULL;
+err_card:
+	return retval;
+}
+
+/*
+ *
+ */
+static void mic_exit_snd(struct mic_device *dev)
+{
+DBG("enter\n");
+
+	if (dev->card) {
+		snd_card_disconnect(dev->card);
+		snd_card_free_when_closed(dev->card);
+
+		dev->card = NULL;
+		dev->pcm = NULL;
+		dev->c_substream = NULL;
+	}
+}
+
+/*
+ *
+ */
+static int mic_init(struct mic_device *dev)
+{
+	struct exi_device *exi_device = dev->exi_device;
+	struct exi_channel *exi_channel = exi_get_exi_channel(exi_device);
+	int channel;
+	int retval = -ENOMEM;
+
+DBG("enter\n");
+
+	spin_lock_init(&dev->lock);
+
+	dev->running = 0;
+
+	retval = mic_init_snd(dev);
+	if (retval)
+		goto err_init_snd;
+
+	init_waitqueue_head(&dev->io_waitq);
+	channel = to_channel(exi_get_exi_channel(dev->exi_device));
+	dev->io_thread = kthread_run(mic_io_thread, dev, "kmicd/%d", channel);
+	if (IS_ERR(dev->io_thread)) {
+		mic_printk(KERN_ERR, "error creating io thread\n");
+		goto err_io_thread;
+	}
+
+	retval = exi_event_register(exi_channel, EXI_EVENT_IRQ,
+				    exi_device,
+				    mic_event_handler, dev,
+				    0 /*(1 << to_channel(exi_channel))*/);
+	if (retval) {
+		mic_printk(KERN_ERR, "error registering exi event\n");
+		goto err_event_register;
+	}
+
+	retval = mic_init_proc(dev);
+	if (retval)
+		goto err_init_proc;
+
+	return 0;
+
+err_init_proc:
+	exi_event_unregister(exi_channel, EXI_EVENT_IRQ);
+err_event_register:
+	mic_stop_io_thread(dev);
+err_io_thread:
+	mic_exit_snd(dev);
+err_init_snd:
+	return retval;
+
+}
+
+/*
+ *
+ */
+static void mic_exit(struct mic_device *dev)
+{
+	struct exi_device *exi_device = dev->exi_device;
+	struct exi_channel *exi_channel = exi_get_exi_channel(exi_device);
+
+DBG("enter\n");
+
+	dev->running = 0;
+
+	mic_exit_proc(dev);
+
+	exi_event_unregister(exi_channel, EXI_EVENT_IRQ);
+
+	if (!IS_ERR(dev->io_thread))
+		mic_stop_io_thread(dev);
+
+	mic_exit_snd(dev);
+}
+
+/*
+ *
+ */
+static int mic_probe(struct exi_device *exi_device)
+{
+	struct mic_device *dev;
+	int retval;
+
+	/* we only care about the microphone */
+	if (exi_device->eid.id != MIC_EXI_ID)
+		return -ENODEV;
+
+	DBG("Microphone inserted\n");
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev)
+		return -ENOMEM;
+
+	dev->exi_device = exi_device_get(exi_device);
+	exi_set_drvdata(exi_device, dev);
+
+	retval = mic_init(dev);
+	if (retval) {
+		exi_set_drvdata(exi_device, NULL);
+		exi_device_put(exi_device);
+		dev->exi_device = NULL;
+		kfree(dev);
+	}
+
+	return retval;
+}
+
+/*
+ *
+ */
+static void mic_remove(struct exi_device *exi_device)
+{
+	struct mic_device *dev = exi_get_drvdata(exi_device);
+
+	DBG("Microphone removed\n");
+
+	if (dev) {
+		mic_exit(dev);
+		if (dev->exi_device)
+			exi_device_put(dev->exi_device);
+		dev->exi_device = NULL;
+		kfree(dev);
+	}
+	exi_set_drvdata(exi_device, NULL);
+}
+
+static struct exi_device_id mic_eid_table[] = {
+	[0] = {
+	       .channel = MIC_SLOTA_CHANNEL,
+	       .device = MIC_SLOTA_DEVICE,
+	       .id = MIC_EXI_ID,
+	       },
+	[1] = {
+	       .channel = MIC_SLOTB_CHANNEL,
+	       .device = MIC_SLOTB_DEVICE,
+	       .id = MIC_EXI_ID,
+	       },
+	{.id = 0}
+};
+
+static struct exi_driver mic_driver = {
+	.name = DRV_MODULE_NAME,
+	.eid_table = mic_eid_table,
+	.frequency = MIC_SPI_CLK_IDX,
+	.probe = mic_probe,
+	.remove = mic_remove,
+};
+
+static int __init mic_init_module(void)
+{
+	int retval = 0;
+
+	mic_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION,
+		  mic_driver_version);
+
+	retval = exi_driver_register(&mic_driver);
+
+	return retval;
+}
+
+static void __exit mic_exit_module(void)
+{
+	exi_driver_unregister(&mic_driver);
+}
+
+module_init(mic_init_module);
+module_exit(mic_exit_module);
+
