gamecube: add new machine,kernel patches and defconfig bump PR of linux_2.6.28.bb
authorBernhard Guillon <Bernhard.Guillon@opensimpad.org>
Wed, 4 Mar 2009 19:53:29 +0000 (20:53 +0100)
committerBernhard Guillon <Bernhard.Guillon@opensimpad.org>
Wed, 4 Mar 2009 19:53:29 +0000 (20:53 +0100)
This an initial support for the linux gamecube port. The patches are taken from [1].

There is still some work to do:
-add dol support for zImage
-add the possibility to create a bootable iso
-add SDLoader
-create or reuse initramfs

1 http://sourceforge.net/projects/gc-linux/

conf/machine/gamecube.conf [new file with mode: 0644]
packages/linux/linux/defconfig [new file with mode: 0644]
packages/linux/linux/patch-2.6.28-gc [new file with mode: 0644]
packages/linux/linux_2.6.28.bb

diff --git a/conf/machine/gamecube.conf b/conf/machine/gamecube.conf
new file mode 100644 (file)
index 0000000..30323ba
--- /dev/null
@@ -0,0 +1,11 @@
+#@TYPE: Machine
+#@Name: Nintendo GameCube
+#@DESCRIPTION: Machine configuration for the Nintendo GameCube
+
+TARGET_ARCH = "powerpc"
+
+PREFERRED_PROVIDER_virtual/kernel ?= "linux"
+
+MACHINE_FEATURES = "kernel26 ext2"
+
+KERNEL_IMAGETYPE = "zImage"
diff --git a/packages/linux/linux/defconfig b/packages/linux/linux/defconfig
new file mode 100644 (file)
index 0000000..7e3e549
--- /dev/null
@@ -0,0 +1,989 @@
+#
+# Automatically generated make config: don't edit
+# Linux kernel version: 2.6.28
+# Mon Jan 12 20:23:50 2009
+#
+# CONFIG_PPC64 is not set
+
+#
+# Processor support
+#
+CONFIG_6xx=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_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_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_HAVE_SETUP_PER_CPU_AREA is not set
+CONFIG_IRQ_PER_CPU=y
+CONFIG_STACKTRACE_SUPPORT=y
+CONFIG_HAVE_LATENCYTOP_SUPPORT=y
+CONFIG_LOCKDEP_SUPPORT=y
+CONFIG_RWSEM_XCHGADD_ALGORITHM=y
+CONFIG_ARCH_HAS_ILOG2_U32=y
+CONFIG_GENERIC_HWEIGHT=y
+CONFIG_GENERIC_CALIBRATE_DELAY=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_NO_NO_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_DEFAULT_UIMAGE is not set
+# CONFIG_PPC_DCR_NATIVE is not set
+# CONFIG_PPC_DCR_MMIO is not set
+CONFIG_DEFCONFIG_LIST="/lib/modules/$UNAME_RELEASE/.config"
+
+#
+# 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
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_LOG_BUF_SHIFT=14
+# CONFIG_CGROUPS is not set
+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_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_CC_OPTIMIZE_FOR_SIZE is not set
+CONFIG_SYSCTL=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_COMPAT_BRK=y
+CONFIG_BASE_FULL=y
+CONFIG_FUTEX=y
+CONFIG_ANON_INODES=y
+CONFIG_EPOLL=y
+CONFIG_SIGNALFD=y
+CONFIG_TIMERFD=y
+CONFIG_EVENTFD=y
+CONFIG_SHMEM=y
+CONFIG_AIO=y
+# CONFIG_VM_EVENT_COUNTERS is not set
+CONFIG_SLAB=y
+# CONFIG_SLUB is not set
+# CONFIG_SLOB is not set
+# CONFIG_PROFILING is not set
+CONFIG_TRACEPOINTS=y
+CONFIG_MARKERS=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_GENERIC_DMA_COHERENT is not set
+CONFIG_SLABINFO=y
+CONFIG_RT_MUTEXES=y
+# CONFIG_TINY_SHMEM is not set
+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_KMOD=y
+CONFIG_BLOCK=y
+CONFIG_LBD=y
+# CONFIG_BLK_DEV_IO_TRACE is not set
+# CONFIG_LSF is not set
+# 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_CLASSIC_RCU=y
+# CONFIG_FREEZER is not set
+
+#
+# Platform support
+#
+CONFIG_PPC_MULTIPLATFORM=y
+CONFIG_CLASSIC32=y
+# 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_RSW=y
+CONFIG_GAMECUBE_UDBG=y
+CONFIG_USBGECKO_UDBG=y
+# CONFIG_GAMECUBE_VIDEO_UDBG 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
+
+#
+# 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_PREEMPT_RCU is not set
+CONFIG_BINFMT_ELF=y
+# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set
+# CONFIG_HAVE_AOUT is not set
+CONFIG_BINFMT_MISC=m
+# CONFIG_IOMMU_HELPER 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_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_RESOURCES_64BIT 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_UNEVICTABLE_LRU=y
+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_START_BOOL is not set
+CONFIG_CONSISTENT_START=0xff100000
+# 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=y
+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_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_NET_SCHED is not set
+
+#
+# Network testing
+#
+# CONFIG_NET_PKTGEN 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_PHONET is not set
+# CONFIG_WIRELESS 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_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_DI=y
+CONFIG_GAMECUBE_ARAM=y
+CONFIG_GAMECUBE_SD=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=y
+# CONFIG_EEPROM_93CX6 is not set
+# CONFIG_ENCLOSURE_SERVICES is not set
+# CONFIG_C2PORT 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_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_NETDEV_1000 is not set
+# CONFIG_NETDEV_10000 is not set
+
+#
+# Wireless LAN
+#
+# CONFIG_WLAN_PRE80211 is not set
+# CONFIG_WLAN_80211 is not set
+# CONFIG_IWLWIFI_LEDS 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 is not set
+# 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_SUNKBD is not set
+# CONFIG_KEYBOARD_LKKBD is not set
+# CONFIG_KEYBOARD_XTKBD is not set
+# CONFIG_KEYBOARD_NEWTON is not set
+# CONFIG_KEYBOARD_STOWAWAY 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_LEGACY_PTYS=y
+CONFIG_LEGACY_PTY_COUNT=64
+# 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
+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_THERMAL_HWMON 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
+
+#
+# Multimedia devices
+#
+
+#
+# Multimedia core support
+#
+# CONFIG_VIDEO_DEV is not set
+# CONFIG_DVB_CORE is not set
+# CONFIG_VIDEO_MEDIA is not set
+
+#
+# Multimedia drivers
+#
+# CONFIG_DAB 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_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_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_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_HID_DEBUG is not set
+# CONFIG_HIDRAW is not set
+# CONFIG_HID_PID is not set
+
+#
+# Special HID drivers
+#
+CONFIG_HID_COMPAT=y
+# 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_PPC is not set
+# CONFIG_DMADEVICES is not set
+# CONFIG_UIO is not set
+# 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_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_FILE_LOCKING=y
+# CONFIG_XFS_FS is not set
+# CONFIG_OCFS2_FS is not set
+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
+
+#
+# CD-ROM/DVD Filesystems
+#
+CONFIG_ISO9660_FS=y
+CONFIG_JOLIET=y
+# CONFIG_ZISOFS is not set
+# CONFIG_UDF_FS is not set
+# CONFIG_GCDVD_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
+
+#
+# Miscellaneous filesystems
+#
+# 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_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_SUNRPC_REGISTER_V4 is not set
+# 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
+
+#
+# Library routines
+#
+CONFIG_BITREVERSE=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_PLIST=y
+CONFIG_HAS_IOMEM=y
+CONFIG_HAS_IOPORT=y
+CONFIG_HAS_DMA=y
+CONFIG_HAVE_LMB=y
+
+#
+# Kernel hacking
+#
+# CONFIG_PRINTK_TIME is not set
+CONFIG_ENABLE_WARN_DEPRECATED=y
+CONFIG_ENABLE_MUST_CHECK=y
+CONFIG_FRAME_WARN=1024
+# CONFIG_MAGIC_SYSRQ 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_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_RT_MUTEXES is not set
+# CONFIG_RT_MUTEX_TESTER is not set
+CONFIG_DEBUG_SPINLOCK=y
+CONFIG_DEBUG_MUTEXES=y
+# CONFIG_DEBUG_SPINLOCK_SLEEP is not set
+# 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_BOOT_PRINTK_DELAY 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_FAULT_INJECTION is not set
+CONFIG_LATENCYTOP=y
+CONFIG_SYSCTL_SYSCALL_CHECK=y
+CONFIG_NOP_TRACER=y
+CONFIG_HAVE_FUNCTION_TRACER=y
+CONFIG_RING_BUFFER=y
+CONFIG_TRACING=y
+
+#
+# Tracers
+#
+# CONFIG_FUNCTION_TRACER is not set
+# CONFIG_PREEMPT_TRACER is not set
+# CONFIG_SCHED_TRACER is not set
+CONFIG_CONTEXT_SWITCH_TRACER=y
+CONFIG_BOOT_TRACER=y
+# CONFIG_STACK_TRACER is not set
+# CONFIG_DYNAMIC_PRINTK_DEBUG is not set
+# CONFIG_SAMPLES is not set
+CONFIG_HAVE_ARCH_KGDB=y
+# CONFIG_KGDB is not set
+# CONFIG_DEBUG_STACKOVERFLOW is not set
+# CONFIG_DEBUG_STACK_USAGE is not set
+# CONFIG_DEBUG_PAGEALLOC 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/packages/linux/linux/patch-2.6.28-gc b/packages/linux/linux/patch-2.6.28-gc
new file mode 100644 (file)
index 0000000..e136663
--- /dev/null
@@ -0,0 +1,32569 @@
+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/arch/powerpc/Kconfig b/arch/powerpc/Kconfig
+index 525c13a..79074d6 100644
+--- a/arch/powerpc/Kconfig
++++ b/arch/powerpc/Kconfig
+@@ -584,7 +584,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 15eb278..fc6a8da 100644
+--- a/arch/powerpc/Kconfig.debug
++++ b/arch/powerpc/Kconfig.debug
+@@ -224,6 +224,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 3d3daa6..d685ea2 100644
+--- a/arch/powerpc/boot/Makefile
++++ b/arch/powerpc/boot/Makefile
+@@ -60,17 +60,16 @@ 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 treeboot-ebony.c prpmc2800.c \
+-              ps3-head.S ps3-hvcall.S ps3.c treeboot-bamboo.c cuboot-8xx.c \
+               cuboot-pq2.c cuboot-sequoia.c treeboot-walnut.c \
+               cuboot-bamboo.c cuboot-mpc7448hpc2.c cuboot-taishan.c \
+               fixed-head.S ep88xc.c ep405.c cuboot-c2k.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-acadia.c gamecube.c wii.c
+ src-boot := $(src-wlib) $(src-plat) empty.c
+ src-boot := $(addprefix $(obj)/, $(src-boot))
+@@ -272,6 +271,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
+ # For 32-bit powermacs, build the COFF and miboot images
+ # as well as the ELF images.
+diff --git a/arch/powerpc/boot/dts/gamecube.dts b/arch/powerpc/boot/dts/gamecube.dts
+new file mode 100644
+index 0000000..c45c97e
+--- /dev/null
++++ b/arch/powerpc/boot/dts/gamecube.dts
+@@ -0,0 +1,129 @@
++/*
++ * 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.
++ *
++ */
++
++/ {
++      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 = "/exi@0c006800/usbgecko@0c006814";
++      };
++
++      memory {
++              device_type = "memory";
++              /* 24M minus framebuffer memory area (640*576*2*2) */
++              reg = <00000000 01698000>;
++      };
++
++      cpus {
++              #address-cells = <1>;
++              #size-cells = <0>;
++
++              PowerPC,gekko@0 {
++                      device_type = "cpu";
++                      reg = <0>;
++                      clock-frequency = <1cf7c580>; /* 486MHz */
++                      bus-frequency = <9a7ec80>; /* 162MHz core-to-bus 3x */
++                      timebase-frequency = <269fb20>; /* 162MHz / 4 */
++                      /* Following required by dtc but not used */
++                      i-cache-line-size = <20>;
++                      d-cache-line-size = <20>;
++                      i-cache-size = <8000>;
++                      d-cache-size = <8000>;
++              };
++      };
++
++      pic: pic@0c003000 {
++              #interrupt-cells = <1>;
++              compatible = "nintendo,flipper-pic";
++              reg = <0c003000 8>;
++              interrupt-controller;
++      };
++
++      /* External Interface bus */
++      exi@0c006800 {
++              #address-cells = <1>;
++              #size-cells = <1>;
++              compatible = "nintendo,flipper-exi";
++              reg = <0c006800 40>;
++              interrupts = <04>;
++              interrupt-parent = <&pic>;
++
++              udbg_console: usbgecko@0c006814 {
++                      compatible = "usbgecko,usbgecko";
++                      reg = <0c006814 14>;
++                      virtual-reg = <cc006814>;
++              };
++      };
++
++      /* devices contained int the flipper chipset */
++      soc {
++              #address-cells = <1>;
++              #size-cells = <1>;
++              #interrupt-cells = <1>;
++              model = "flipper";
++              compatible = "nintendo,flipper";
++              clock-frequency = <9a7ec80>; /* 162MHz */
++              ranges = <0c000000 0c000000 00010000>;
++
++              video@0c002000 {
++                      compatible = "nintendo,flipper-video";
++                      reg = <0c002000 100>;
++                      interrupts = <08>;
++                      interrupt-parent = <&pic>;
++                      xfb-start = <01698000>; /* end-of-ram - xfb-size */
++                      xfb-size = <168000>;
++              };
++
++              resetswitch@0c003000 {
++                      compatible = "nintendo,flipper-resetswitch";
++                      reg = <0c003000 4>;
++                      interrupts = <01>;
++                      interrupt-parent = <&pic>;
++              };
++
++              auxram@0c005000 {
++                      compatible = "nintendo,flipper-auxram";
++                      reg = <0c005000 200>;   /* DSP */
++                      interrupts = <06>;
++                      interrupt-parent = <&pic>;
++              };
++
++              audio@0c005000 {
++                      compatible = "nintendo,flipper-audio";
++                      reg = <0c005000 200     /* DSP */
++                             0c006c00 20>;    /* AI */
++                      interrupts = <06>;
++                      interrupt-parent = <&pic>;
++              };
++
++              disk@0c006000 {
++                      compatible = "nintendo,flipper-disk";
++                      reg = <0c006000 40>;
++                      interrupts = <02>;
++                      interrupt-parent = <&pic>;
++              };
++
++              serial@0c006400 {
++                      compatible = "nintendo,flipper-serial";
++                      reg = <0c006400 100>;
++                      interrupts = <03>;
++                      interrupt-parent = <&pic>;
++              };
++        };
++};
++
+diff --git a/arch/powerpc/boot/dts/wii.dts b/arch/powerpc/boot/dts/wii.dts
+new file mode 100644
+index 0000000..fdfbc2c
+--- /dev/null
++++ b/arch/powerpc/boot/dts/wii.dts
+@@ -0,0 +1,162 @@
++/*
++ * 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.
++ *
++ */
++
++
++/memreserve/ 01698000-017fffff;       /* framebuffer, see video@0c002000 */
++/memreserve/ 01800000-0fffffff;       /* memory hole */
++/memreserve/ 10000000-10003fff;       /* DSP */
++
++/ {
++      model = "NintendoWii";
++      compatible = "nintendo,wii";
++      #address-cells = <1>;
++      #size-cells = <1>;
++
++      chosen {
++              /* ramdisk */
++              /* bootargs = "nobats root=/dev/ram0 video=gcnfb:tv=NTSC 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/rvlsda2 video=gcnfb:tv=NTSC force_keyboard_port=4 placeholder_for_additional_kernel_options_targetted_at_hexedit_lovers";
++              linux,stdout-path = "/exi@0d006800/usbgecko@0d006814";
++      };
++
++      memory {
++              device_type = "memory";
++              /* mem1 + hole + mem2 - ioh */
++              reg = <00000000 133e0000>;
++      };
++
++      cpus {
++              #cpus = <1>;
++              #address-cells = <1>;
++              #size-cells = <0>;
++
++              PowerPC,broadway@0 {
++                      device_type = "cpu";
++                      reg = <0>;
++                      clock-frequency = <2b73a840>; /* 729MHz */
++                      bus-frequency = <e7be2c0>; /* 243MHz core-to-bus 3x */
++                      timebase-frequency = <39ef8b0>; /* 243MHz / 4 */
++                      /* Following required by dtc but not used */
++                      i-cache-line-size = <20>;
++                      d-cache-line-size = <20>;
++                      i-cache-size = <8000>;
++                      d-cache-size = <8000>;
++              };
++      };
++
++      pic: pic@0c003000 {
++              #interrupt-cells = <1>;
++              compatible = "nintendo,flipper-pic";
++              reg = <0c003000 8>;
++              interrupt-controller;
++      };
++
++      /* External Interface bus */
++      exi@0d006800 {
++              #address-cells = <1>;
++              #size-cells = <1>;
++              compatible = "nintendo,hollywood-exi";
++              reg = <0d006800 40>;
++              interrupts = <04>;
++              interrupt-parent = <&pic>;
++
++              udbg_console: usbgecko@0d006814 {
++                      compatible = "usbgecko,usbgecko";
++                      reg = <0d006814 14>;
++                      virtual-reg = <cd006814>;
++              };
++      };
++
++      /* devices contained in the hollywood chipset */
++      soc {
++              #address-cells = <1>;
++              #size-cells = <1>;
++              #interrupt-cells = <1>;
++              model = "hollywood";
++              compatible = "nintendo,hollywood";
++              clock-frequency = <e7be2c0>; /* 243MHz */
++              ranges = <0c000000 0c000000 00010000
++                        0d000000 0d000000 00010000
++                        0d800000 0d800000 00001000
++                        133e0000 133e0000 00020000>;
++
++              video@0c002000 {
++                      compatible = "nintendo,hollywood-video";
++                      reg = <0c002000 100>;
++                      interrupts = <08>;
++                      interrupt-parent = <&pic>;
++                      xfb-start = <01698000>; /* end-of-mem1 - xfb-size */
++                      xfb-size = <168000>;    /* 640x576x2 x 2 bytes */
++              };
++
++              resetswitch@0c003000 {
++                      compatible = "nintendo,hollywood-resetswitch";
++                      reg = <0c003000 4>;
++                      interrupts = <01>;
++                      interrupt-parent = <&pic>;
++              };
++
++              audio@0c005000 {
++                      compatible = "nintendo,hollywood-audio";
++                      reg = <0c005000 200     /* DSP */
++                             0d006c00 20>;    /* AI */
++                      interrupts = <06>;
++                      interrupt-parent = <&pic>;
++              };
++
++              serial@0d006400 {
++                      compatible = "nintendo,hollywood-serial";
++                      reg = <0d006400 100>;
++                      interrupts = <03>;
++                      interrupt-parent = <&pic>;
++              };
++
++              gpio0: starlet-gpio@0d8000c0 {
++                      compatible = "nintendo,starlet-gpio";
++                      reg = <0d8000c0 4>;
++                      gpio-controller;
++                      #gpio-cells = <2>;
++              };
++
++              starlet-ipc@0d000000 {
++                      compatible = "nintendo,starlet-ipc";
++                      reg = <0d000000 40      /* IPC */
++                             133e0000 20000>; /* MEM2 ioh 128K */
++                      interrupts = <0e>;
++                      interrupt-parent = <&pic>;
++
++              };
++
++              starlet-es {
++                      compatible = "nintendo,starlet-es";
++              };
++
++              starlet-sd {
++                      compatible = "nintendo,starlet-sd";
++              };
++
++              starlet-keyboard {
++                      compatible = "nintendo,starlet-keyboard";
++              };
++
++              starlet-hcd {
++                      compatible = "nintendo,starlet-hcd";
++              };
++      };
++};
+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..b485eab
+--- /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 = finddevice("/exi/usbgecko");
++      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..b702514
+--- /dev/null
++++ b/arch/powerpc/boot/wii.c
+@@ -0,0 +1,78 @@
++/*
++ * 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\
++      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/configs/gamecube_defconfig b/arch/powerpc/configs/gamecube_defconfig
+new file mode 100644
+index 0000000..7e3e549
+--- /dev/null
++++ b/arch/powerpc/configs/gamecube_defconfig
+@@ -0,0 +1,989 @@
++#
++# Automatically generated make config: don't edit
++# Linux kernel version: 2.6.28
++# Mon Jan 12 20:23:50 2009
++#
++# CONFIG_PPC64 is not set
++
++#
++# Processor support
++#
++CONFIG_6xx=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_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_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_HAVE_SETUP_PER_CPU_AREA is not set
++CONFIG_IRQ_PER_CPU=y
++CONFIG_STACKTRACE_SUPPORT=y
++CONFIG_HAVE_LATENCYTOP_SUPPORT=y
++CONFIG_LOCKDEP_SUPPORT=y
++CONFIG_RWSEM_XCHGADD_ALGORITHM=y
++CONFIG_ARCH_HAS_ILOG2_U32=y
++CONFIG_GENERIC_HWEIGHT=y
++CONFIG_GENERIC_CALIBRATE_DELAY=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_NO_NO_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_DEFAULT_UIMAGE is not set
++# CONFIG_PPC_DCR_NATIVE is not set
++# CONFIG_PPC_DCR_MMIO is not set
++CONFIG_DEFCONFIG_LIST="/lib/modules/$UNAME_RELEASE/.config"
++
++#
++# 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
++CONFIG_IKCONFIG=y
++CONFIG_IKCONFIG_PROC=y
++CONFIG_LOG_BUF_SHIFT=14
++# CONFIG_CGROUPS is not set
++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_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_CC_OPTIMIZE_FOR_SIZE is not set
++CONFIG_SYSCTL=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_COMPAT_BRK=y
++CONFIG_BASE_FULL=y
++CONFIG_FUTEX=y
++CONFIG_ANON_INODES=y
++CONFIG_EPOLL=y
++CONFIG_SIGNALFD=y
++CONFIG_TIMERFD=y
++CONFIG_EVENTFD=y
++CONFIG_SHMEM=y
++CONFIG_AIO=y
++# CONFIG_VM_EVENT_COUNTERS is not set
++CONFIG_SLAB=y
++# CONFIG_SLUB is not set
++# CONFIG_SLOB is not set
++# CONFIG_PROFILING is not set
++CONFIG_TRACEPOINTS=y
++CONFIG_MARKERS=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_GENERIC_DMA_COHERENT is not set
++CONFIG_SLABINFO=y
++CONFIG_RT_MUTEXES=y
++# CONFIG_TINY_SHMEM is not set
++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_KMOD=y
++CONFIG_BLOCK=y
++CONFIG_LBD=y
++# CONFIG_BLK_DEV_IO_TRACE is not set
++# CONFIG_LSF is not set
++# 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_CLASSIC_RCU=y
++# CONFIG_FREEZER is not set
++
++#
++# Platform support
++#
++CONFIG_PPC_MULTIPLATFORM=y
++CONFIG_CLASSIC32=y
++# 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_RSW=y
++CONFIG_GAMECUBE_UDBG=y
++CONFIG_USBGECKO_UDBG=y
++# CONFIG_GAMECUBE_VIDEO_UDBG 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
++
++#
++# 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_PREEMPT_RCU is not set
++CONFIG_BINFMT_ELF=y
++# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set
++# CONFIG_HAVE_AOUT is not set
++CONFIG_BINFMT_MISC=m
++# CONFIG_IOMMU_HELPER 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_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_RESOURCES_64BIT 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_UNEVICTABLE_LRU=y
++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_START_BOOL is not set
++CONFIG_CONSISTENT_START=0xff100000
++# 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=y
++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_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_NET_SCHED is not set
++
++#
++# Network testing
++#
++# CONFIG_NET_PKTGEN 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_PHONET is not set
++# CONFIG_WIRELESS 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_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_DI=y
++CONFIG_GAMECUBE_ARAM=y
++CONFIG_GAMECUBE_SD=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=y
++# CONFIG_EEPROM_93CX6 is not set
++# CONFIG_ENCLOSURE_SERVICES is not set
++# CONFIG_C2PORT 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_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_NETDEV_1000 is not set
++# CONFIG_NETDEV_10000 is not set
++
++#
++# Wireless LAN
++#
++# CONFIG_WLAN_PRE80211 is not set
++# CONFIG_WLAN_80211 is not set
++# CONFIG_IWLWIFI_LEDS 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 is not set
++# 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_SUNKBD is not set
++# CONFIG_KEYBOARD_LKKBD is not set
++# CONFIG_KEYBOARD_XTKBD is not set
++# CONFIG_KEYBOARD_NEWTON is not set
++# CONFIG_KEYBOARD_STOWAWAY 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_LEGACY_PTYS=y
++CONFIG_LEGACY_PTY_COUNT=64
++# 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
++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_THERMAL_HWMON 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
++
++#
++# Multimedia devices
++#
++
++#
++# Multimedia core support
++#
++# CONFIG_VIDEO_DEV is not set
++# CONFIG_DVB_CORE is not set
++# CONFIG_VIDEO_MEDIA is not set
++
++#
++# Multimedia drivers
++#
++# CONFIG_DAB 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_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_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_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_HID_DEBUG is not set
++# CONFIG_HIDRAW is not set
++# CONFIG_HID_PID is not set
++
++#
++# Special HID drivers
++#
++CONFIG_HID_COMPAT=y
++# 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_PPC is not set
++# CONFIG_DMADEVICES is not set
++# CONFIG_UIO is not set
++# 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_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_FILE_LOCKING=y
++# CONFIG_XFS_FS is not set
++# CONFIG_OCFS2_FS is not set
++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
++
++#
++# CD-ROM/DVD Filesystems
++#
++CONFIG_ISO9660_FS=y
++CONFIG_JOLIET=y
++# CONFIG_ZISOFS is not set
++# CONFIG_UDF_FS is not set
++# CONFIG_GCDVD_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
++
++#
++# Miscellaneous filesystems
++#
++# 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_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_SUNRPC_REGISTER_V4 is not set
++# 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
++
++#
++# Library routines
++#
++CONFIG_BITREVERSE=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_PLIST=y
++CONFIG_HAS_IOMEM=y
++CONFIG_HAS_IOPORT=y
++CONFIG_HAS_DMA=y
++CONFIG_HAVE_LMB=y
++
++#
++# Kernel hacking
++#
++# CONFIG_PRINTK_TIME is not set
++CONFIG_ENABLE_WARN_DEPRECATED=y
++CONFIG_ENABLE_MUST_CHECK=y
++CONFIG_FRAME_WARN=1024
++# CONFIG_MAGIC_SYSRQ 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_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_RT_MUTEXES is not set
++# CONFIG_RT_MUTEX_TESTER is not set
++CONFIG_DEBUG_SPINLOCK=y
++CONFIG_DEBUG_MUTEXES=y
++# CONFIG_DEBUG_SPINLOCK_SLEEP is not set
++# 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_BOOT_PRINTK_DELAY 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_FAULT_INJECTION is not set
++CONFIG_LATENCYTOP=y
++CONFIG_SYSCTL_SYSCALL_CHECK=y
++CONFIG_NOP_TRACER=y
++CONFIG_HAVE_FUNCTION_TRACER=y
++CONFIG_RING_BUFFER=y
++CONFIG_TRACING=y
++
++#
++# Tracers
++#
++# CONFIG_FUNCTION_TRACER is not set
++# CONFIG_PREEMPT_TRACER is not set
++# CONFIG_SCHED_TRACER is not set
++CONFIG_CONTEXT_SWITCH_TRACER=y
++CONFIG_BOOT_TRACER=y
++# CONFIG_STACK_TRACER is not set
++# CONFIG_DYNAMIC_PRINTK_DEBUG is not set
++# CONFIG_SAMPLES is not set
++CONFIG_HAVE_ARCH_KGDB=y
++# CONFIG_KGDB is not set
++# CONFIG_DEBUG_STACKOVERFLOW is not set
++# CONFIG_DEBUG_STACK_USAGE is not set
++# CONFIG_DEBUG_PAGEALLOC 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..64d7f19
+--- /dev/null
++++ b/arch/powerpc/configs/wii_defconfig
+@@ -0,0 +1,1224 @@
++#
++# Automatically generated make config: don't edit
++# Linux kernel version: 2.6.28
++# Mon Jan 12 20:19:45 2009
++#
++# CONFIG_PPC64 is not set
++
++#
++# Processor support
++#
++CONFIG_6xx=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_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_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_HAVE_SETUP_PER_CPU_AREA is not set
++CONFIG_IRQ_PER_CPU=y
++CONFIG_STACKTRACE_SUPPORT=y
++CONFIG_HAVE_LATENCYTOP_SUPPORT=y
++CONFIG_LOCKDEP_SUPPORT=y
++CONFIG_RWSEM_XCHGADD_ALGORITHM=y
++CONFIG_ARCH_HAS_ILOG2_U32=y
++CONFIG_GENERIC_HWEIGHT=y
++CONFIG_GENERIC_CALIBRATE_DELAY=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_NO_NO_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_DEFAULT_UIMAGE is not set
++# CONFIG_PPC_DCR_NATIVE is not set
++# CONFIG_PPC_DCR_MMIO is not set
++CONFIG_DEFCONFIG_LIST="/lib/modules/$UNAME_RELEASE/.config"
++
++#
++# 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=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
++CONFIG_IKCONFIG=y
++CONFIG_IKCONFIG_PROC=y
++CONFIG_LOG_BUF_SHIFT=14
++# CONFIG_CGROUPS is not set
++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_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_CC_OPTIMIZE_FOR_SIZE is not set
++CONFIG_SYSCTL=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_COMPAT_BRK=y
++CONFIG_BASE_FULL=y
++CONFIG_FUTEX=y
++CONFIG_ANON_INODES=y
++CONFIG_EPOLL=y
++CONFIG_SIGNALFD=y
++CONFIG_TIMERFD=y
++CONFIG_EVENTFD=y
++CONFIG_SHMEM=y
++CONFIG_AIO=y
++# CONFIG_VM_EVENT_COUNTERS is not set
++CONFIG_SLAB=y
++# CONFIG_SLUB is not set
++# CONFIG_SLOB is not set
++# CONFIG_PROFILING is not set
++CONFIG_TRACEPOINTS=y
++CONFIG_MARKERS=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_GENERIC_DMA_COHERENT is not set
++CONFIG_SLABINFO=y
++CONFIG_RT_MUTEXES=y
++# CONFIG_TINY_SHMEM is not set
++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_KMOD=y
++CONFIG_BLOCK=y
++CONFIG_LBD=y
++# CONFIG_BLK_DEV_IO_TRACE is not set
++# CONFIG_LSF is not set
++# 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_CLASSIC_RCU=y
++# CONFIG_FREEZER is not set
++
++#
++# Platform support
++#
++CONFIG_PPC_MULTIPLATFORM=y
++CONFIG_CLASSIC32=y
++# 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_FLIPPER_PIC=y
++CONFIG_GAMECUBE_COMMON=y
++CONFIG_GAMECUBE_RSW=y
++CONFIG_GAMECUBE_UDBG=y
++CONFIG_USBGECKO_UDBG=y
++# CONFIG_GAMECUBE_VIDEO_UDBG is not set
++CONFIG_WII_GPIO=y
++# 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
++
++#
++# 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_PREEMPT_RCU is not set
++CONFIG_BINFMT_ELF=y
++# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set
++# CONFIG_HAVE_AOUT is not set
++CONFIG_BINFMT_MISC=m
++# CONFIG_IOMMU_HELPER 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_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_RESOURCES_64BIT 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_UNEVICTABLE_LRU=y
++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_START_BOOL is not set
++CONFIG_CONSISTENT_START=0xff100000
++# 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_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_NET_SCHED is not set
++
++#
++# Network testing
++#
++# CONFIG_NET_PKTGEN 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 is not set
++# CONFIG_BT_BNEP is not set
++CONFIG_BT_HIDP=y
++
++#
++# Bluetooth device drivers
++#
++CONFIG_BT_HCIBTUSB=y
++# 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_AF_RXRPC is not set
++# CONFIG_PHONET is not set
++CONFIG_WIRELESS=y
++# CONFIG_CFG80211 is not set
++CONFIG_WIRELESS_OLD_REGULATORY=y
++# CONFIG_WIRELESS_EXT is not set
++# CONFIG_MAC80211 is not set
++# CONFIG_IEEE80211 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_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_OF_GPIO=y
++# CONFIG_PARPORT is not set
++CONFIG_BLK_DEV=y
++# CONFIG_BLK_DEV_FD is not set
++CONFIG_GAMECUBE_SD=y
++CONFIG_WII_SD=y
++CONFIG_WII_MEM2=m
++# 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_EEPROM_93CX6 is not set
++# CONFIG_ENCLOSURE_SERVICES is not set
++# CONFIG_C2PORT 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
++
++#
++# Some SCSI devices (e.g. CD jukebox) support multiple LUNs
++#
++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_SCSI_DEBUG is not set
++# CONFIG_SCSI_DH 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_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_NETDEV_1000 is not set
++# CONFIG_NETDEV_10000 is not set
++
++#
++# Wireless LAN
++#
++# CONFIG_WLAN_PRE80211 is not set
++# CONFIG_WLAN_80211 is not set
++# CONFIG_IWLWIFI_LEDS is not set
++
++#
++# 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_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_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_ATKBD is not set
++# CONFIG_KEYBOARD_SUNKBD is not set
++# CONFIG_KEYBOARD_LKKBD is not set
++# CONFIG_KEYBOARD_XTKBD is not set
++# CONFIG_KEYBOARD_NEWTON is not set
++# CONFIG_KEYBOARD_STOWAWAY is not set
++# CONFIG_KEYBOARD_GPIO 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_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
++
++#
++# 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_LEGACY_PTYS=y
++CONFIG_LEGACY_PTY_COUNT=64
++# 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 is not set
++
++#
++# EXI support
++#
++CONFIG_GAMECUBE_EXI=y
++# CONFIG_SPI 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:
++#
++
++#
++# PCI GPIO expanders:
++#
++
++#
++# SPI GPIO expanders:
++#
++# CONFIG_W1 is not set
++# CONFIG_POWER_SUPPLY is not set
++# CONFIG_HWMON is not set
++# CONFIG_THERMAL is not set
++# CONFIG_THERMAL_HWMON 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
++
++#
++# Multimedia devices
++#
++
++#
++# Multimedia core support
++#
++# CONFIG_VIDEO_DEV is not set
++# CONFIG_DVB_CORE is not set
++# CONFIG_VIDEO_MEDIA is not set
++
++#
++# Multimedia drivers
++#
++# CONFIG_DAB 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_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_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_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 is not set
++# CONFIG_SND_SOC is not set
++# CONFIG_SOUND_PRIME is not set
++CONFIG_HID_SUPPORT=y
++CONFIG_HID=y
++# CONFIG_HID_DEBUG is not set
++# 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_COMPAT=y
++CONFIG_HID_A4TECH=m
++CONFIG_HID_APPLE=m
++CONFIG_HID_BELKIN=m
++CONFIG_HID_BRIGHT=m
++CONFIG_HID_CHERRY=m
++CONFIG_HID_CHICONY=m
++CONFIG_HID_CYPRESS=m
++CONFIG_HID_DELL=m
++CONFIG_HID_EZKEY=m
++CONFIG_HID_GYRATION=m
++CONFIG_HID_LOGITECH=m
++# CONFIG_LOGITECH_FF is not set
++# CONFIG_LOGIRUMBLEPAD2_FF is not set
++CONFIG_HID_MICROSOFT=m
++CONFIG_HID_MONTEREY=m
++CONFIG_HID_PANTHERLORD=m
++# CONFIG_PANTHERLORD_FF is not set
++CONFIG_HID_PETALYNX=m
++CONFIG_HID_SAMSUNG=m
++CONFIG_HID_SONY=m
++CONFIG_HID_SUNPLUS=m
++CONFIG_THRUSTMASTER_FF=m
++CONFIG_ZEROPLUS_FF=m
++CONFIG_USB_SUPPORT=y
++CONFIG_USB_ARCH_HAS_HCD=y
++# CONFIG_USB_ARCH_HAS_OHCI is not set
++# CONFIG_USB_ARCH_HAS_EHCI is not set
++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_ISP116X_HCD is not set
++# CONFIG_USB_ISP1760_HCD is not set
++# 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 information
++#
++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_DPCM 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_PHIDGET is not set
++# CONFIG_USB_IDMOUSE is not set
++# CONFIG_USB_FTDI_ELAN is not set
++# CONFIG_USB_APPLEDISPLAY 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
++# 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_PPC is not set
++# CONFIG_DMADEVICES is not set
++# CONFIG_UIO is not set
++# 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_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_FILE_LOCKING=y
++# CONFIG_XFS_FS is not set
++# CONFIG_OCFS2_FS is not set
++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
++
++#
++# CD-ROM/DVD Filesystems
++#
++CONFIG_ISO9660_FS=y
++CONFIG_JOLIET=y
++# CONFIG_ZISOFS is not set
++# CONFIG_UDF_FS is not set
++# CONFIG_GCDVD_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
++
++#
++# Miscellaneous filesystems
++#
++# 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_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_SUNRPC_REGISTER_V4 is not set
++# CONFIG_RPCSEC_GSS_KRB5 is not set
++# CONFIG_RPCSEC_GSS_SPKM3 is not set
++# CONFIG_SMB_FS is not set
++# CONFIG_CIFS 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
++
++#
++# Library routines
++#
++CONFIG_BITREVERSE=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_PLIST=y
++CONFIG_HAS_IOMEM=y
++CONFIG_HAS_IOPORT=y
++CONFIG_HAS_DMA=y
++CONFIG_HAVE_LMB=y
++
++#
++# Kernel hacking
++#
++# CONFIG_PRINTK_TIME is not set
++CONFIG_ENABLE_WARN_DEPRECATED=y
++CONFIG_ENABLE_MUST_CHECK=y
++CONFIG_FRAME_WARN=1024
++CONFIG_MAGIC_SYSRQ=y
++# 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_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_RT_MUTEXES is not set
++# CONFIG_RT_MUTEX_TESTER is not set
++CONFIG_DEBUG_SPINLOCK=y
++# CONFIG_DEBUG_MUTEXES is not set
++# CONFIG_DEBUG_SPINLOCK_SLEEP is not set
++# CONFIG_DEBUG_LOCKING_API_SELFTESTS is not set
++CONFIG_STACKTRACE=y
++# CONFIG_DEBUG_KOBJECT is not set
++# CONFIG_DEBUG_BUGVERBOSE is not set
++# 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_BOOT_PRINTK_DELAY 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_FAULT_INJECTION is not set
++CONFIG_LATENCYTOP=y
++CONFIG_SYSCTL_SYSCALL_CHECK=y
++CONFIG_NOP_TRACER=y
++CONFIG_HAVE_FUNCTION_TRACER=y
++CONFIG_RING_BUFFER=y
++CONFIG_TRACING=y
++
++#
++# Tracers
++#
++# CONFIG_FUNCTION_TRACER is not set
++# CONFIG_PREEMPT_TRACER is not set
++# CONFIG_SCHED_TRACER is not set
++CONFIG_CONTEXT_SWITCH_TRACER=y
++CONFIG_BOOT_TRACER=y
++# CONFIG_STACK_TRACER is not set
++# CONFIG_DYNAMIC_PRINTK_DEBUG is not set
++# CONFIG_SAMPLES is not set
++CONFIG_HAVE_ARCH_KGDB=y
++# CONFIG_KGDB is not set
++# CONFIG_DEBUG_STACKOVERFLOW is not set
++# CONFIG_DEBUG_STACK_USAGE is not set
++# CONFIG_DEBUG_PAGEALLOC 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_PPC_LIB_RHEAP=y
++# CONFIG_VIRTUALIZATION is not set
+diff --git a/arch/powerpc/include/asm/starlet.h b/arch/powerpc/include/asm/starlet.h
+new file mode 100644
+index 0000000..b8f77c7
+--- /dev/null
++++ b/arch/powerpc/include/asm/starlet.h
+@@ -0,0 +1,234 @@
++/*
++ * arch/powerpc/include/asm/starlet.h
++ *
++ * Nintendo Wii starlet processor 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 __ASM_POWERPC_STARLET_H
++#define __ASM_POWERPC_STARLET_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_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_close(int fd);
++
++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_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_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-stm.c */
++
++extern void starlet_stm_restart(void);
++extern void starlet_stm_power_off(void);
++
++#endif /* __ASM_POWERPC_STARLET_H */
+diff --git a/arch/powerpc/include/asm/udbg.h b/arch/powerpc/include/asm/udbg.h
+index 6418cee..11ecd37 100644
+--- a/arch/powerpc/include/asm/udbg.h
++++ b/arch/powerpc/include/asm/udbg.h
+@@ -50,6 +50,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 7e87195..daf94a5 100644
+--- a/arch/powerpc/kernel/cputable.c
++++ b/arch/powerpc/kernel/cputable.c
+@@ -644,11 +644,11 @@ 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,
+               .icache_bsize           = 32,
+               .dcache_bsize           = 32,
+@@ -658,11 +658,11 @@ static struct cpu_spec __initdata cpu_specs[] = {
+               .machine_check          = machine_check_generic,
+               .platform               = "ppc750",
+       },
+-      {       /* 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,
+               .icache_bsize           = 32,
+               .dcache_bsize           = 32,
+diff --git a/arch/powerpc/kernel/head_32.S b/arch/powerpc/kernel/head_32.S
+index 0c32682..ae6825a 100644
+--- a/arch/powerpc/kernel/head_32.S
++++ b/arch/powerpc/kernel/head_32.S
+@@ -159,6 +159,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
+@@ -1253,6 +1256,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/prom_init_check.sh b/arch/powerpc/kernel/prom_init_check.sh
+index ea3a2ec..980cd4e 100644
+--- a/arch/powerpc/kernel/prom_init_check.sh
++++ b/arch/powerpc/kernel/prom_init_check.sh
+@@ -17,7 +17,7 @@
+ # it to the list below:
+ WHITELIST="add_reloc_offset __bss_start __bss_stop copy_and_flush
+-_end enter_prom memcpy memset reloc_offset __secondary_hold
++_end enter_prom memcmp memcpy memset reloc_offset __secondary_hold
+ __secondary_hold_acknowledge __secondary_hold_spinloop __start
+ strcmp strcpy strlcpy strlen strncmp strstr logo_linux_clut224
+ reloc_got2 kernstart_addr memstart_addr"
+diff --git a/arch/powerpc/kernel/udbg.c b/arch/powerpc/kernel/udbg.c
+index 7d6c9bb..a85bfb0 100644
+--- a/arch/powerpc/kernel/udbg.c
++++ b/arch/powerpc/kernel/udbg.c
+@@ -59,6 +59,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 c31d6d2..62f8954 100644
+--- a/arch/powerpc/mm/pgtable_32.c
++++ b/arch/powerpc/mm/pgtable_32.c
+@@ -199,9 +199,19 @@ __ioremap(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;
++              }
+       }
+       if (size == 0)
+diff --git a/arch/powerpc/platforms/Kconfig.cputype b/arch/powerpc/platforms/Kconfig.cputype
+index 548efa5..36e041c 100644
+--- a/arch/powerpc/platforms/Kconfig.cputype
++++ b/arch/powerpc/platforms/Kconfig.cputype
+@@ -250,7 +250,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 4f9f818..d5a76c3 100644
+--- a/arch/powerpc/platforms/embedded6xx/Kconfig
++++ b/arch/powerpc/platforms/embedded6xx/Kconfig
+@@ -90,3 +90,90 @@ 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
++      help
++        Select WII if configuring for the Nintendo Wii.
++        More information at: <http://gc-linux.sourceforge.net/>
++
++config FLIPPER_PIC
++      bool
++      default n
++
++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 WII_GPIO
++      bool "Nintendo Wii GPIO support"
++      depends on GPIOLIB
++      default y
++      help
++        If you say yes to this option, support will be included for the
++        Nintendo Wii GPIO lines that 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..8a080ba 100644
+--- a/arch/powerpc/platforms/embedded6xx/Makefile
++++ b/arch/powerpc/platforms/embedded6xx/Makefile
+@@ -7,3 +7,12 @@ 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 \
++                                      starlet-ipc.o starlet-malloc.o \
++                                      starlet-stm.o starlet-es.o
++obj-$(CONFIG_FLIPPER_PIC)     += flipper-pic.o
++obj-$(CONFIG_USBGECKO_UDBG)   += usbgecko_udbg.o
++obj-$(CONFIG_GAMECUBE_VIDEO_UDBG)     += gcnvi_udbg.o
++obj-$(CONFIG_GAMECUBE_RSW)    += gcn-rsw.o
++obj-$(CONFIG_WII_GPIO)                += starlet-gpio.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..d836103
+--- /dev/null
++++ b/arch/powerpc/platforms/embedded6xx/flipper-pic.c
+@@ -0,0 +1,217 @@
++/*
++ * 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.
++ *
++ */
++
++#include <linux/kernel.h>
++#include <linux/init.h>
++#include <linux/irq.h>
++#include <linux/of.h>
++#include <asm/io.h>
++
++#include "flipper-pic.h"
++
++
++#define DRV_MODULE_NAME "flipper-pic"
++
++#define drv_printk(level, format, arg...) \
++       printk(level DRV_MODULE_NAME ": " format , ## arg)
++
++
++/*
++ * 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);
++      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.
++ *
++ */
++
++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) {
++              drv_printk(KERN_ERR, "no io memory range found\n");
++              return NULL;
++      }
++      io_base = ioremap(res.start, res.end - res.start + 1);
++
++      drv_printk(KERN_INFO, "controller at 0x%08x mapped to 0x%p\n",
++                 res.start, io_base);
++
++      /* mask and ack all IRQs */
++      out_be32(io_base + FLIPPER_IMR, 0x00000000);
++      out_be32(io_base + FLIPPER_ICR, 0xffffffff);
++
++      irq_host = irq_alloc_host(np, IRQ_HOST_MAP_LINEAR, FLIPPER_NR_IRQS,
++                                &flipper_irq_host_ops, -1);
++      if (!irq_host) {
++              drv_printk(KERN_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.
++ *
++ */
++
++/*
++ * 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);
++              drv_printk(KERN_INFO, "%x\n", 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..483bee7
+--- /dev/null
++++ b/arch/powerpc/platforms/embedded6xx/flipper-pic.h
+@@ -0,0 +1,41 @@
++/*
++ * 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_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/starlet-es.c b/arch/powerpc/platforms/embedded6xx/starlet-es.c
+new file mode 100644
+index 0000000..a975f64
+--- /dev/null
++++ b/arch/powerpc/platforms/embedded6xx/starlet-es.c
+@@ -0,0 +1,592 @@
++/*
++ * arch/powerpc/platforms/embedded6xx/starlet-es.c
++ *
++ * Nintendo Wii starlet ES 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 DEBUG
++
++#include <linux/kernel.h>
++#include <linux/of_platform.h>
++#include <linux/scatterlist.h>
++#include <asm/starlet.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.2i";
++
++#define DBG(fmt, arg...)      pr_debug(fmt, ##arg)
++
++#define drv_printk(level, format, arg...) \
++       printk(level DRV_MODULE_NAME ": " format , ## arg)
++
++struct starlet_es_device {
++      int fd;
++
++      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));
++
++#if 0
++struct starlet_es_ticket {
++      char issuer[0x40];
++      u8 fill[63]; /* TODO: not really fill */
++      u8 title_key[16];
++      u8 fill2;
++      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));
++#endif
++
++/*
++ * /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_KERNEL);
++      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);
++}
++
++#if 0
++static void es_small_buf_dump(void)
++{
++      int i;
++      size_t nelems = sizeof(es_small_buf) / sizeof(u32);
++
++      drv_printk(KERN_INFO, "es_small_buf[%d]= {\n", nelems);
++      for (i = 0; i < nelems; i++)
++              drv_printk(KERN_INFO, "%08x, ", es_small_buf[i]);
++      drv_printk(KERN_INFO, "\n}\n");
++
++}
++#endif
++
++/*
++ *
++ *
++ */
++
++static struct starlet_es_device *starlet_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);
++
++/**
++ *
++ */
++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;
++}
++
++/**
++ *
++ */
++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;
++}
++
++/**
++ *
++ */
++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_KERNEL);
++      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(es_dev->fd, ES_IOCTLV_GETTICKETVIEWCOUNT,
++                                 1, in, 1, io);
++      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;
++}
++
++/**
++ *
++ */
++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_KERNEL);
++      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(es_dev->fd, ES_IOCTLV_GETTICKETVIEWS,
++                                 2, in, 1, io);
++      if (error)
++              DBG("%s: error=%d (%08x)\n", __func__, error, error);
++
++      es_small_buf_put(count_buf);
++      starlet_kfree(title_buf);
++
++      return error;
++}
++
++/**
++ *
++ */
++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_KERNEL);
++      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;
++}
++
++
++
++/*
++ * Setup routines.
++ *
++ */
++
++#define STARLET_ES_IOS_MIN 30
++#define STARLET_ES_IOS_MAX 36
++
++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;
++      int found, i;
++      int error;
++
++      error = starlet_es_get_title_count(&count);
++      if (error)
++              return error;
++
++      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_launch_title(struct starlet_es_device *es_dev, u64 title)
++{
++      struct starlet_es_ticket_view *views;
++      unsigned long count;
++      int error;
++
++      error = starlet_es_get_ticket_view_count(title, &count);
++      if (error)
++              return error;
++
++      views = starlet_kzalloc(sizeof(*views)*count, GFP_KERNEL);
++      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 IOS%u\n", (u32)(title & 0xffffffff));
++      error = starlet_es_launch_title_view(title, views); /* first view */
++
++      starlet_kfree(views);
++
++      return error;
++}
++
++static int starlet_es_load_preferred(struct starlet_es_device *es_dev,
++                                       u64 ios_min, u64 ios_max)
++{
++      u64 title;
++      int error;
++
++      error = starlet_es_find_newest_title(es_dev, &title, ios_min, ios_max);
++      if (!error)
++              return -EINVAL;
++      if (error > 0)
++              error = starlet_es_launch_title(es_dev, title);
++
++      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(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;
++
++      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-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-gpio.c b/arch/powerpc/platforms/embedded6xx/starlet-gpio.c
+new file mode 100644
+index 0000000..abdae88
+--- /dev/null
++++ b/arch/powerpc/platforms/embedded6xx/starlet-gpio.c
+@@ -0,0 +1,134 @@
++/*
++ * arch/powerpc/platforms/embedded6xx/starlet-gpio.c
++ *
++ * Nintendo Wii starlet 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>
++
++struct stgpio_chip {
++      struct of_mm_gpio_chip mmchip;
++      spinlock_t lock;
++};
++
++struct stgpio_regs {
++      __be32 out, dir, in;
++};
++
++
++static inline struct stgpio_chip *
++to_stgpio_chip(struct of_mm_gpio_chip *mm_gc)
++{
++      return container_of(mm_gc, struct stgpio_chip, mmchip);
++}
++
++static int stgpio_get(struct gpio_chip *gc, unsigned int gpio)
++{
++      struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
++      struct stgpio_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 stgpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
++{
++      struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
++      struct stgpio_chip *st_gc = to_stgpio_chip(mm_gc);
++      struct stgpio_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 stgpio_dir_in(struct gpio_chip *gc, unsigned int gpio)
++{
++      struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
++      struct stgpio_regs __iomem *regs = mm_gc->regs;
++      u32 pin_mask = 1 << (31 - gpio);
++
++      clrbits32(&regs->dir, pin_mask);
++
++      return 0;
++}
++
++static int stgpio_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 stgpio_regs __iomem *regs = mm_gc->regs;
++      u32 pin_mask = 1 << (31 - gpio);
++
++      setbits32(&regs->dir, pin_mask);
++      stgpio_set(gc, gpio, val);
++
++      return 0;
++}
++
++int stgpio_add32(struct device_node *np)
++{
++      struct of_mm_gpio_chip *mm_gc;
++      struct of_gpio_chip *of_gc;
++      struct gpio_chip *gc;
++      struct stgpio_chip *st_gc;
++
++      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;
++
++      of_gc->gpio_cells = 1;
++
++      gc->ngpio = 32;
++      gc->direction_input = stgpio_dir_in;
++      gc->direction_output = stgpio_dir_out;
++      gc->get = stgpio_get;
++      gc->set = stgpio_set;
++
++      return of_mm_gpiochip_add(np, mm_gc);
++}
++
++static int stgpio_init(void)
++{
++      struct device_node *np;
++      int error;
++
++      for_each_compatible_node(np, NULL, "nintendo,starlet-gpio") {
++              error = stgpio_add32(np);
++              if (error < 0)
++                      printk(KERN_ERR "starlet-gpio: error %d adding gpios"
++                                      " for %s\n", error, np->full_name);
++      }
++      return 0; /* whatever */
++}
++arch_initcall(stgpio_init);
++
+diff --git a/arch/powerpc/platforms/embedded6xx/starlet-ipc.c b/arch/powerpc/platforms/embedded6xx/starlet-ipc.c
+new file mode 100644
+index 0000000..fa91b7a
+--- /dev/null
++++ b/arch/powerpc/platforms/embedded6xx/starlet-ipc.c
+@@ -0,0 +1,1488 @@
++/*
++ * 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/starlet.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.2i";
++
++#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_TSTART      (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_INT         (1<<3)  /* interrupt ack */
++#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_eoi(void __iomem *io_base)
++{
++      starlet_ipc_update_csr(io_base, STARLET_IPC_CSR_INT);
++}
++
++/*
++ * 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_TSTART);
++}
++
++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--;
++      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");
++                      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) {
++                                      starlet_ipc_complete_request(req);
++                                      req->result = 0;
++                              }
++                              starlet_ipc_eoi(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_eoi(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);
++}
++
++/*
++ *
++ * 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);
++
++/**
++ *
++ */
++int starlet_open(const char *pathname, int flags)
++{
++#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_KERNEL);
++      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_KERNEL);
++                      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 = 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: error=%d (%x)\n", __func__, error, error);
++      return error;
++}
++EXPORT_SYMBOL_GPL(starlet_open);
++
++/*
++ *
++ */
++int starlet_close(int fd)
++{
++      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_KERNEL);
++      if (req) {
++              req->cmd = STARLET_IOS_CLOSE;
++              req->fd = fd;
++              error = starlet_ipc_call(req);
++              starlet_ipc_free_request(req);
++      }
++      return error;
++}
++EXPORT_SYMBOL_GPL(starlet_close);
++
++
++/*
++ * 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;
++}
++
++/**
++ *
++ */
++int starlet_ioctl(int fd, int request,
++                    void *ibuf, size_t ilen,
++                    void *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_prepare(req, fd, request,
++                                        ibuf, ilen, obuf, olen);
++      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_ioctl);
++
++/**
++ *
++ */
++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;
++}
++
++/**
++ *
++ */
++int starlet_ioctlv(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)
++              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_ioctlv);
++
++/**
++ *
++ */
++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(req);
++      }
++      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;
++
++      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-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..d9c9dcd
+--- /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.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-stm.c b/arch/powerpc/platforms/embedded6xx/starlet-stm.c
+new file mode 100644
+index 0000000..a765964
+--- /dev/null
++++ b/arch/powerpc/platforms/embedded6xx/starlet-stm.c
+@@ -0,0 +1,93 @@
++/*
++ * 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.h>
++
++
++/*
++ * /dev/stm/immediate
++ *
++ */
++
++#define STARLET_STM_HOTRESET  0x2001
++#define STARLET_STM_SHUTDOWN  0x2003
++
++#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)
++{
++      u32 *buf = starlet_stm_buf;
++      size_t len = sizeof(starlet_stm_buf);
++      int fd;
++      int error;
++
++      /* REVISIT, use polled ipc calls here */
++
++
++      drv_printk(KERN_INFO, "trying IPC restart...\n");
++
++      fd = starlet_open(dev_stm_immediate, 0);
++      if (fd < 0) {
++              drv_printk(KERN_ERR, "failed to open %s\n", dev_stm_immediate);
++              error = fd;
++              goto done;
++      }
++
++      *buf = value;
++      error = starlet_ioctl(fd, request, buf, len, buf, len);
++      if (error < 0)
++              drv_printk(KERN_ERR, "ioctl %d failed\n", request);
++      starlet_close(fd);
++
++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..0b17477
+--- /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_IO);
++
++      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..7a7176d
+--- /dev/null
++++ b/arch/powerpc/platforms/embedded6xx/wii.c
+@@ -0,0 +1,115 @@
++/*
++ * 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 <asm/io.h>
++#include <asm/machdep.h>
++#include <asm/prom.h>
++#include <asm/time.h>
++#include <asm/starlet.h>
++#include <asm/udbg.h>
++
++#include "flipper-pic.h"
++#include "gcnvi_udbg.h"
++#include "usbgecko_udbg.h"
++
++
++static void wii_restart(char *cmd)
++{
++      starlet_stm_restart();
++      local_irq_disable();
++      /* spin until power button pressed */
++      for (;;)
++              cpu_relax();
++}
++
++static void wii_power_off(void)
++{
++      starlet_stm_power_off();
++      local_irq_disable();
++      /* spin until power button pressed */
++      for (;;)
++              cpu_relax();
++}
++
++static void wii_halt(void)
++{
++      wii_restart(NULL);
++}
++
++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");
++}
++
++static void __init wii_setup_arch(void)
++{
++      ug_udbg_init();
++      gcnvi_udbg_init();
++}
++
++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)
++{
++      /* currently not used */
++}
++
++static int wii_kexec_prepare(struct kimage *image)
++{
++      return 0;
++}
++#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,
++      .restart                = wii_restart,
++      .power_off              = wii_power_off,
++      .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_kexec_prepare,
++      .machine_kexec          = default_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..c5ed8b5
+--- /dev/null
++++ b/arch/powerpc/platforms/embedded6xx/wii_dev.c
+@@ -0,0 +1,44 @@
++/*
++ * 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", },
++      { },
++};
++
++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 2f557f5..08d2933 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/gpio/Kconfig"
+diff --git a/drivers/Makefile b/drivers/Makefile
+index fceb71a..d53f062 100644
+--- a/drivers/Makefile
++++ b/drivers/Makefile
+@@ -97,6 +97,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_VIRTIO)          += virtio/
+diff --git a/drivers/block/Kconfig b/drivers/block/Kconfig
+index 0344a8a..7dd9143 100644
+--- a/drivers/block/Kconfig
++++ b/drivers/block/Kconfig
+@@ -56,6 +56,93 @@ 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 GAMECUBE_DVD
++      tristate "Nintendo Gamecube DVD"
++      depends on GAMECUBE && !GAMECUBE_DI && BROKEN
++      help
++        This enables support for using the mini-DVD drive on 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-dvd
++
++config GAMECUBE_MEMCARD
++      tristate "Nintendo GameCube/Wii memory card (EXPERIMENTAL)"
++      depends on GAMECUBE_EXI && EXPERIMENTAL && BROKEN
++      help
++        This enables support for using memory cards compatible with 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-memcard.
++
++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 WII
++      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 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 204332b..1af7546 100644
+--- a/drivers/block/Makefile
++++ b/drivers/block/Makefile
+@@ -11,6 +11,13 @@ obj-$(CONFIG_AMIGA_FLOPPY)  += amiflop.o
+ obj-$(CONFIG_PS3_DISK)                += ps3disk.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_GAMECUBE_DVD)    += gcn-dvd/
++obj-$(CONFIG_GAMECUBE_MEMCARD)        += gcn-memcard.o
++obj-$(CONFIG_WII_MEM2)                += rvl-mem2.o
++obj-$(CONFIG_WII_SD)          += rvl-stsd.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..bc2774d
+--- /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                      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;
++      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 current request being serviced */
++      req = drvdata->req;
++      drvdata->req = NULL;
++
++      spin_unlock_irqrestore(&drvdata->io_lock, flags);
++
++      if (req) {
++              __blk_end_request(req, 0, req->current_nr_sectors << 9);
++              dma_unmap_single(drvdata->dev,
++                               drvdata->dma_addr, drvdata->dma_len,
++                               rq_dir_to_dma_dir(req));
++              spin_lock(&drvdata->lock);
++              blk_start_queue(drvdata->queue);
++              spin_unlock(&drvdata->lock);
++      } else {
++              drv_printk(KERN_ERR, "ignoring interrupt, no request\n");
++      }
++
++      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;
++
++      req = elv_next_request(q);
++      while (req) {
++              spin_lock_irqsave(&drvdata->io_lock, flags);
++
++              /* we schedule a single request each time */
++              if (drvdata->req) {
++                      spin_unlock_irqrestore(&drvdata->io_lock, flags);
++                      blk_stop_queue(q);
++                      break;
++              }
++
++              blkdev_dequeue_request(req);
++
++              /* ignore requests that we can't handle */
++              if (!blk_fs_request(req)) {
++                      spin_unlock_irqrestore(&drvdata->io_lock, flags);
++                      continue;
++              }
++
++              /* store the request being handled */
++              drvdata->req = req;
++              blk_stop_queue(q);
++
++              spin_unlock_irqrestore(&drvdata->io_lock, flags);
++
++              /* calculate the ARAM address and length */
++              aram_addr = req->sector << 9;
++              len = req->current_nr_sectors << 9;
++
++              /* 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)req->sector,
++                                  len);
++                      /* XXX correct? the request is already dequeued */
++                      end_request(req, 0);
++                      continue;
++              }
++
++              BUG_ON(req->nr_phys_segments != 1);
++
++              /* perform DMA mappings */
++              drvdata->dma_len = len;
++              drvdata->dma_addr = dma_map_single(drvdata->dev,
++                                                 req->buffer, len,
++                                                 rq_dir_to_dma_dir(req));
++
++              /* start the DMA transfer */
++              aram_start_dma_transfer(drvdata, aram_addr);
++              break;
++      }
++}
++
++/*
++ * 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->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;
++}
++
++static int aram_release(struct gendisk *disk, fmode_t mode)
++{
++      struct aram_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 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->lock);
++      spin_lock_init(&drvdata->io_lock);
++      queue = blk_init_queue(aram_do_request, &drvdata->lock);
++      if (!queue)
++              goto err_blk_init_queue;
++
++      blk_queue_hardsect_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->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..9f6564f
+--- /dev/null
++++ b/drivers/block/gcn-di/gcn-di.c
+@@ -0,0 +1,2363 @@
++/*
++ * 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) {
++              __blk_end_request(req, error, req->current_nr_sectors << 9);
++              spin_lock_irqsave(&ddev->queue_lock, flags);
++              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;
++
++      while ((req = elv_next_request(q))) {
++              /* keep our reads within limits */
++              if (req->sector + req->current_nr_sectors > ddev->nr_sectors) {
++                      drv_printk(KERN_ERR, "reading past end\n");
++                      end_request(req, 0);
++                      continue;
++              }
++
++              /* it doesn't make sense to write to this device */
++              if (rq_data_dir(req) == WRITE) {
++                      drv_printk(KERN_ERR, "write attempted\n");
++                      end_request(req, 0);
++                      continue;
++              }
++
++              /* it is not a good idea to open the lid ... */
++              if ((ddev->flags & DI_MEDIA_CHANGED)) {
++                      drv_printk(KERN_ERR, "media changed, aborting\n");
++                      end_request(req, 0);
++                      continue;
++              }
++
++              spin_lock_irqsave(&ddev->lock, flags);
++
++              /* we can schedule just a single request each time */
++              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;
++              }
++
++              blkdev_dequeue_request(req);
++
++              /* ignore requests that we can't handle */
++              if (!blk_fs_request(req)) {
++                      spin_unlock_irqrestore(&ddev->lock, flags);
++                      continue;
++              }
++
++              /* store the request being handled ... */
++              ddev->req = req;
++              blk_stop_queue(q);
++
++              spin_unlock_irqrestore(&ddev->lock, flags);
++
++              /* ... and launch the corresponding read sector command */
++              start = req->sector << KERNEL_SECTOR_SHIFT;
++              len = req->current_nr_sectors << KERNEL_SECTOR_SHIFT;
++
++              di_op_readsector(cmd, ddev, start >> 2,
++                               req->buffer, len);
++              cmd->done_data = cmd;
++              cmd->done = di_request_done;
++              di_run_command(cmd);
++      }
++}
++
++/*
++ * 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_hardsect_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-dvd/Makefile b/drivers/block/gcn-dvd/Makefile
+new file mode 100644
+index 0000000..1f3ebca
+--- /dev/null
++++ b/drivers/block/gcn-dvd/Makefile
+@@ -0,0 +1,3 @@
++obj-$(CONFIG_GAMECUBE_DVD) := gcn-dvd.o
++
++gcn-dvd-objs := main.o request.o
+diff --git a/drivers/block/gcn-dvd/main.c b/drivers/block/gcn-dvd/main.c
+new file mode 100644
+index 0000000..d07333e
+--- /dev/null
++++ b/drivers/block/gcn-dvd/main.c
+@@ -0,0 +1,810 @@
++/*
++ * drivers/block/gcn-dvd/main.c
++ *
++ * Nintendo GameCube DVD driver 
++ * Copyright (C) 2005 The GameCube Linux Team
++ *
++ * 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/major.h>
++#include <linux/vmalloc.h>
++#include <linux/init.h>
++#include <linux/module.h>
++#include <linux/moduleparam.h>
++#include <linux/blkdev.h>
++#include <linux/delay.h>
++#include <linux/cdrom.h>
++#include <linux/wait.h>
++#include <asm/setup.h>
++#include <asm/bitops.h>
++#include <asm/pgtable.h>
++#include <asm/cacheflush.h>
++#include <asm/cache.h>
++#include <linux/interrupt.h>
++#include <linux/dma-mapping.h>
++
++#include <linux/fcntl.h>        /* O_ACCMODE */
++#include <linux/hdreg.h>  /* HDIO_GETGEO */
++
++#include <asm/io.h>
++
++#include "request.h"
++
++#define DEVICE_NAME "DVD"
++#define DVD_MAJOR 60
++
++#define LINUX_SECTOR_SIZE  512
++#define LINUX_SECTOR_SHIFT 9
++#define DVD_SECTOR_SIZE    2048
++#define DVD_SECTOR_SHIFT   11
++#define DVD_MAX_SECTORS    712880
++
++#define DMA_ALIGNMENT_MASK 0x1F
++
++#define DVD_REGISTER_INIT    ((void* __iomem)0xCC003024)
++
++#define DVD_REGISTER_BLOCK_BASE   0xCC006000
++#define DVD_REGISTER_BLOCK_LENGTH 0x40
++
++#define DVD_GAMECODE_U32     0x80000000 /* Gamecode */
++#define DVD_COMPANY_U16      0x80000004 /* Game company id */
++#define DVD_DISK_ID_U8       0x80000006 /* Disk id */
++#define DVD_DISK_VERSION_U8  0x80000007 /* Disk version */
++
++#define DVD_IRQ                       2
++
++/* DI Status Register */
++#define DI_DISR              ((void * __iomem)0xCC006000)
++#define  DI_DISR_BRKINT      (1<<6)
++#define  DI_DISR_BRKINTMASK  (1<<5)
++#define  DI_DISR_TCINT       (1<<4)
++#define  DI_DISR_TCINTMASK   (1<<3)
++#define  DI_DISR_DEINT       (1<<2)
++#define  DI_DISR_DEINTMASK   (1<<1)
++#define  DI_DISR_BRK         (1<<0)
++
++/* DI Cover Register */
++#define DI_DICVR             ((void * __iomem)0xCC006004)
++#define  DI_DICVR_CVRINT     (1<<2)
++#define  DI_DICVR_CVRINTMASK (1<<1)
++#define  DI_DICVR_CVR        (1<<0)
++
++/* DI Command Buffer 0 */
++#define DI_DICMDBUF0         ((void * __iomem)0xCC006008)
++#define DI_DICMDBUF0_CMD     24
++#define DI_DICMDBUF0_SUBCMD1 16
++#define DI_DICMDBUF0_SUBCMD2 0
++
++/* DI Command Buffer 1 */
++#define DI_DICMDBUF1         ((void * __iomem)0xCC00600C)
++
++/* DI Command Buffer 2 */ 
++#define DI_DICMDBUF2         ((void * __iomem)0xCC006010)
++
++/* DMA Memory Address Register */
++#define DI_DIMAR             ((void * __iomem)0xCC006014)
++
++/* DI DMA Transfer Length Register */
++#define DI_DILENGTH          ((void * __iomem)0xCC006018)
++
++/* DI Control Register */
++#define DI_DICR              ((void * __iomem)0xCC00601C)
++#define  DI_DICR_RW          (1<<2)
++#define  DI_DICR_DMA         (1<<1)
++#define  DI_DICR_TSTART      (1<<0)
++
++/* DI Immediate Data Buffer */
++#define DI_DIIMMBUF          ((void * __iomem)0xCC006020)
++
++#define DI_CMD_REZERO        0x01
++#define DI_CMD_INQUIRY       0x12
++#define DI_CMD_READ          0xA8
++#define DI_CMD_SEEK          0xAB
++#define DI_CMD_READTOC       0x43
++#define DI_CMD_STOP          0xE3
++/* This is a fake command, don't send to the hardware */
++#define DI_CMD_INITIALIZE    0xFF
++
++#define IS_CMD_TYPE(cmd,type) (((cmd) >> DI_DICMDBUF0_CMD) == (type))
++
++static struct gendisk *dvd_gendisk;
++static struct request_queue *dvd_queue;
++static spinlock_t dvd_queue_lock = SPIN_LOCK_UNLOCKED; 
++static struct _taginterrupt_queue
++{
++      spinlock_t lock;
++      int drive_initialized;
++      struct list_head queue;
++} interrupt_queue;
++
++static struct _tagdvdinfo {
++      spinlock_t lock;
++      unsigned long refCount;
++      unsigned int media_changed;
++      unsigned long numDVDSectors;
++      unsigned long numLinuxSectors;
++
++      /* disc info */
++      struct _tagdvdid
++      { 
++              u32 gamecode;
++              u16 company;
++              u8 id;
++              u8 version;
++      } disc;
++} dvd_info;
++
++#define LOCK(flags)   spin_lock_irqsave(&dvd_info.lock,flags)
++#define UNLOCK(flags) spin_unlock_irqrestore(&dvd_info.lock,flags)
++
++/* Must be 0x20 in size = 32 bytes */
++#pragma pack(1)
++
++struct gc_dvd_drive_info
++{
++      u32 head;
++      u32 middle;
++      u32 last;
++      u8 padding[20];
++};
++
++struct gc_dvd_disc_info
++{
++      u16 revision;
++      u16 device_code;
++      u32 release_date;
++      u8  padding[24];
++};
++
++#pragma pack()
++
++struct dma_buffer
++{
++      size_t size;
++      void *ptr;
++      void *alignedPtr;
++      dma_addr_t handle;
++};
++
++#ifdef DEBUG
++#define DPRINTK(fmt,args...) printk(KERN_INFO "%s (%u): " fmt,__FUNCTION__,__LINE__,## args)
++#else
++#define DPRINTK(fmt,args...)
++#endif
++
++static int alloc_dma_buffer(size_t size,struct dma_buffer *pRet)
++{
++      /* allocate space for the aligned memory */
++      pRet->size = DMA_ALIGNMENT_MASK + size;
++      if (!(pRet->ptr = kmalloc(pRet->size,GFP_KERNEL | GFP_DMA))) {
++              DPRINTK("Cannot allocate DMA memory of size %i\n",pRet->size);
++              return -ENOMEM;
++      }
++  
++      /* align the pointer */
++      if ((unsigned long)pRet->ptr & DMA_ALIGNMENT_MASK) {
++              pRet->alignedPtr = (void*)(((unsigned long)pRet->ptr + DMA_ALIGNMENT_MASK) & ~DMA_ALIGNMENT_MASK);
++              /* adjust the size if not aligned */
++              pRet->size -= (pRet->alignedPtr - pRet->ptr);
++      }
++      else {
++              pRet->alignedPtr = pRet->ptr;
++      }
++      /* get the dma mapping */
++      pRet->handle = virt_to_phys(pRet->alignedPtr);
++      return 0;
++}
++
++inline static void sync_dma_buffer(struct dma_buffer *pRet)
++{
++      __dma_sync(pRet->alignedPtr, pRet->size, DMA_FROM_DEVICE);
++}
++
++inline static void free_dma_buffer(struct dma_buffer *pRet)
++{
++      kfree(pRet->ptr);
++}
++
++/* hardware talk */
++static void gc_dvd_enable_interrupts(int enable)
++{
++      unsigned long outval;
++      /* enable main interrupts */
++      if (enable) {
++              outval = DI_DISR_BRKINT | DI_DISR_TCINT | DI_DISR_DEINT |
++                      DI_DISR_BRKINTMASK | DI_DISR_TCINTMASK | DI_DISR_DEINTMASK;
++      }
++      else {
++              outval = DI_DISR_BRKINT | DI_DISR_TCINT | DI_DISR_DEINT;
++      }
++      
++      writel(outval,DI_DISR);
++      
++      /* This enables cover interrupts */
++      if (enable) {
++              outval = DI_DICVR_CVRINT | DI_DICVR_CVRINTMASK;
++      }
++      else {
++              outval = DI_DICVR_CVRINT;
++      }
++      
++      writel(outval,DI_DICVR);
++}
++
++static void gc_dvd_execute_queue_command(struct gc_dvd_command *cmd)
++{
++      u32 val;
++      /* if they're doing a read and the drive is not initialized */
++      if (!interrupt_queue.drive_initialized && 
++          IS_CMD_TYPE(cmd->r_DI_DICMDBUF0,DI_CMD_READ)) {
++              /* insert an initialize queue item BEFORE this one */
++              static struct gc_dvd_command init_cmd = {
++                      .r_DI_DICMDBUF0 = DI_CMD_INITIALIZE << DI_DICMDBUF0_CMD,
++                      .completion_routine = NULL,
++                      .param = NULL,
++              };
++              
++              /* insert before this item */
++              list_add_tail(&init_cmd.list,&cmd->list);
++              /* now execute the initialize routine */
++              val = (readl(DVD_REGISTER_INIT) & ~4) | 1;
++              writel(val,DVD_REGISTER_INIT);
++              udelay(100);
++              val |= (4 | 1);
++              writel(val,DVD_REGISTER_INIT);
++              udelay(100);
++              /* it will return an interrupt when we're done, our 
++                 queue item will pick it up */
++      }
++      else if (cmd) {
++              writel(cmd->r_DI_DICMDBUF0,DI_DICMDBUF0);
++              writel(cmd->r_DI_DICMDBUF1,DI_DICMDBUF1);
++              writel(cmd->r_DI_DICMDBUF2,DI_DICMDBUF2);
++              writel((unsigned long)cmd->r_DI_DIMAR,DI_DIMAR);
++              writel(cmd->r_DI_DILENGTH,DI_DILENGTH);
++              writel(cmd->r_DI_DICR,DI_DICR);
++      }
++}
++
++static int gc_dvd_queue_command(struct gc_dvd_command *cmd)
++{
++      unsigned long flags;
++      int execute_immediately;
++  
++      spin_lock_irqsave(&interrupt_queue.lock,flags);
++  
++      cmd->int_status = is_still_running;
++      /* add to the tail of the list */
++      execute_immediately = list_empty(&interrupt_queue.queue);
++
++      list_add_tail(&cmd->list,&interrupt_queue.queue);
++  
++      if (execute_immediately) {
++              gc_dvd_execute_queue_command(cmd);
++      }
++      /* release lock so interrupt handler can get it */
++      spin_unlock_irqrestore(&interrupt_queue.lock,flags);
++      return 0;
++}
++
++/* This function is called from an IRQ context, we just wake up the queue */
++static void gc_dvd_queue_completion_wake_up(struct gc_dvd_command *cmd)
++{
++      wake_up_interruptible(((wait_queue_head_t*)cmd->param));
++}
++
++static int gc_dvd_execute_blocking_command(unsigned int di_cmd,unsigned int sz,struct dma_buffer *buf)
++{
++      struct gc_dvd_command cmd;
++      wait_queue_head_t wait_queue;
++    
++      if ((sz > 0) && alloc_dma_buffer(sz,buf)) {
++              return -ENOMEM;
++      }
++      
++      init_waitqueue_head(&wait_queue);
++
++      cmd.flags = 0;
++      cmd.r_DI_DICMDBUF0 = di_cmd;
++      cmd.r_DI_DICMDBUF1 = 0;
++      cmd.r_DI_DICMDBUF2 = sz;
++      cmd.r_DI_DIMAR     = (sz > 0) ? (void*)buf->handle : NULL;
++      cmd.r_DI_DILENGTH  = sz;
++      cmd.r_DI_DICR      = DI_DICR_TSTART | ((sz > 0) ? DI_DICR_DMA : 0);
++      cmd.completion_routine = gc_dvd_queue_completion_wake_up;
++      cmd.param = &wait_queue;
++
++      gc_dvd_queue_command(&cmd);
++      /* wait for it to finish */
++      while (wait_event_interruptible(wait_queue,cmd.int_status != 
++                                      is_still_running)) ;
++      
++      return cmd.int_status;
++}
++
++static inline void gc_dvd_stop_motor(void)
++{
++      gc_dvd_execute_blocking_command(DI_CMD_STOP << DI_DICMDBUF0_CMD,0,NULL);
++}
++
++static int gc_dvd_inquiry(void)
++{
++  
++      /* get status on disk */
++      struct dma_buffer buf;
++      struct gc_dvd_drive_info *pdi;
++      int is;
++  
++      is = gc_dvd_execute_blocking_command(DI_CMD_INQUIRY << DI_DICMDBUF0_CMD,
++                                           sizeof(struct gc_dvd_drive_info),
++                                           &buf);
++      if (is == -ENOMEM) {
++              return is;
++      }
++      else if (is != is_transfer_complete) {
++              printk(KERN_ERR "Gamecube DVD: error in inquiry cmd\n");
++              is = -ENODEV;
++      }
++      else {
++              sync_dma_buffer(&buf);
++              
++              pdi = (struct gc_dvd_drive_info*)buf.alignedPtr;
++              printk(KERN_INFO "Gamecube DVD: 0x%x, 0x%x,0x%x\n",pdi->head,pdi->middle,pdi->last);
++              
++              is = 0;
++      }
++      
++      free_dma_buffer(&buf);
++      return is;
++}
++
++static int gc_dvd_read_toc(void)
++{
++      struct dma_buffer buf;
++      struct gc_dvd_disc_info *pdi;
++      int i;
++
++      i = gc_dvd_execute_blocking_command(DI_CMD_READ << DI_DICMDBUF0_CMD | 0x40,
++                                          sizeof(struct gc_dvd_disc_info),
++                                          &buf);
++      if (i != is_transfer_complete) {
++              dvd_info.numDVDSectors = 0;
++
++              if (i != -ENOMEM) {
++                      free_dma_buffer(&buf);
++              }
++              else {
++                      i = -ENOMEDIUM;
++              }
++              
++              printk(KERN_ERR "Gamecube DVD: error reading TOC - missing medium?\n");
++      }
++      else {
++              sync_dma_buffer(&buf);
++              
++              pdi = (struct gc_dvd_disc_info*)buf.alignedPtr;
++              printk(KERN_INFO "Gamecube DVD: revision: %u, device_code %u, release_date: %u\n",pdi->revision,pdi->device_code,pdi->release_date);
++              
++              dvd_info.numDVDSectors = DVD_MAX_SECTORS;
++              /* reset media_changed flag */
++              dvd_info.media_changed = 0;
++
++              free_dma_buffer(&buf);
++
++              i = 0;
++      }
++      /* inform the kernel of the size */
++      dvd_info.numLinuxSectors = dvd_info.numDVDSectors << (DVD_SECTOR_SHIFT - LINUX_SECTOR_SHIFT);
++      set_capacity(dvd_gendisk,dvd_info.numLinuxSectors);  
++      return i;
++}
++
++/* Handlers */
++static int gc_dvd_revalidate(struct gendisk *disk)
++{
++      gc_dvd_read_toc();
++      return 0;
++}
++
++static int gc_dvd_open(struct inode *inode,struct file *filp)
++{
++      unsigned long flags;
++      /* we are read only */
++      if (filp->f_mode & FMODE_WRITE) {
++              return -EROFS;
++      }
++      
++      /* check the disc, only allow minor of 0 to be opened */
++      if (iminor(inode)) {
++              return -ENODEV;
++      }
++  
++      /* update information about the disc */
++      LOCK(flags);
++      if (dvd_info.refCount > 0) {
++              /* we only let one at a time */
++              UNLOCK(flags);
++              return -EBUSY;
++      }
++      else {
++              check_disk_change(inode->i_bdev);
++              /* revalidate should be called if necessary, check results here */
++              if (dvd_info.numDVDSectors == 0) {
++                      UNLOCK(flags);
++                      return -ENOMEDIUM;
++              }
++      }
++      dvd_info.refCount++;
++      UNLOCK(flags);
++      return 0;
++}
++static int gc_dvd_release(struct inode *inode,struct file *filp)
++{
++      unsigned long flags;
++
++      gc_dvd_stop_motor();
++
++      LOCK(flags);
++      dvd_info.refCount--;
++      /* force a media change so we re-read the toc and initialize the disc */
++      dvd_info.media_changed = 1;
++      UNLOCK(flags);
++      return 0;
++}
++
++static int gc_dvd_ioctl(struct inode *inode,struct file *filp,
++                      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(inode->i_bdev,cmd,arg);
++      default:
++              return -ENOTTY;
++      }
++      return -ENOTTY;
++}
++
++static int gc_dvd_media_changed(struct gendisk *disk)
++{
++      /* return 1 if the disc has changed */
++      return (dvd_info.media_changed ? 1 : 0);
++}
++
++static void gc_dvd_read_request_callback(struct gc_dvd_command *cmd)
++{
++      unsigned long flags;
++      struct request *req = (struct request*)cmd->param;
++      struct request_queue *rqueue = req->q;
++      int status;
++  
++      /* since this was performed via DMA, invalidate the cache */
++      if (cmd->int_status == is_transfer_complete) {
++              __dma_sync(req->buffer, cmd->r_DI_DILENGTH, DMA_FROM_DEVICE);
++      }
++      /* free this item so another request can get it */
++      gc_dvd_request_release_data(cmd);
++      /* now end the request and send back to block layer */
++      spin_lock_irqsave(rqueue->queue_lock,flags);
++      status = is_transfer_complete;
++      if (!end_that_request_first(req,
++                                  status, req->current_nr_sectors)) {
++              add_disk_randomness(req->rq_disk);
++              end_that_request_last(req, status);
++      }
++      /* start queue back up */
++      blk_start_queue(rqueue);
++      spin_unlock_irqrestore(rqueue->queue_lock,flags);
++}
++
++static void gc_dvd_do_request(request_queue_t *q)
++{
++      struct request *req;
++      unsigned long start;
++      unsigned long len;
++      struct gc_dvd_command *cmd;
++  
++      while ((req = elv_next_request(q))) {
++              /* check if they are reading beyond the limits */
++              if ((req->sector + req->current_nr_sectors) > dvd_info.numLinuxSectors) {
++                      printk(KERN_ERR "Gamecube DVD: reading past end\n");
++                      end_request(req,0);
++              }
++              else if (rq_data_dir(req) == WRITE) {
++                      printk(KERN_ERR "Gamecube DVD: write attempted\n");
++                      end_request(req,0);
++              }
++              else if (dvd_info.media_changed) {
++                      DPRINTK("media changed in read routine, aborting\n");
++                      end_request(req,0);
++              }
++              else if (req->current_nr_sectors >= (1 << (DVD_SECTOR_SHIFT - LINUX_SECTOR_SHIFT))) {
++                      /* now schedule the read */
++                      if (gc_dvd_request_get_data(&cmd) || !cmd) {
++                              /* we're full, stop the queue */
++                              blk_stop_queue(q);
++                              return;
++                      }
++                      else {
++                              /* remove item from the queue */
++                              blkdev_dequeue_request(req);
++
++                              /* setup my structure */
++                              start = req->sector << LINUX_SECTOR_SHIFT;
++                              len   = req->current_nr_sectors << LINUX_SECTOR_SHIFT;
++                              
++                              cmd->flags = 0;
++                              cmd->r_DI_DICMDBUF0 = DI_CMD_READ << DI_DICMDBUF0_CMD;
++                              cmd->r_DI_DICMDBUF1 = start >> (DVD_SECTOR_SHIFT - LINUX_SECTOR_SHIFT);
++                              cmd->r_DI_DICMDBUF2 = len;
++                              cmd->r_DI_DIMAR     = (void*)virt_to_phys(req->buffer);
++                              cmd->r_DI_DILENGTH  = len;
++                              cmd->r_DI_DICR      = DI_DICR_TSTART | DI_DICR_DMA;
++                              cmd->completion_routine = gc_dvd_read_request_callback;
++                              cmd->param = req;
++                              gc_dvd_queue_command(cmd);
++                      }
++              }
++      }
++}
++
++static struct block_device_operations dvd_fops =
++{
++      .owner  = THIS_MODULE,
++      .open   = gc_dvd_open,
++      .release = gc_dvd_release,
++      .revalidate_disk = gc_dvd_revalidate,
++      .media_changed = gc_dvd_media_changed,
++      .ioctl = gc_dvd_ioctl,
++};
++
++static irqreturn_t gc_dvd_irq_handler(int irq,void *dev_id,struct pt_regs *regs)
++{
++      unsigned int reason;
++      unsigned long flags;
++      struct gc_dvd_command *cur_item;
++#define REASON_FLAG_COVER 0x80000000
++      /* try the main status */
++      if ((reason=readl(DI_DISR)) & (DI_DISR_BRKINT | DI_DISR_TCINT | DI_DISR_DEINT)) {
++              /* acknowledge the interrupt */
++              writel(reason | DI_DISR_BRKINT | DI_DISR_TCINT | DI_DISR_DEINT,DI_DISR);
++      }
++      else if ((reason=readl(DI_DICVR)) & (DI_DICVR_CVRINT))  {
++              /* acknowlegde the interrupt */
++              writel(reason | DI_DICVR_CVRINT,DI_DICVR);
++              /* set media changed flag */
++              if (!(reason & DI_DICVR_CVR)) {
++                      dvd_info.media_changed = 1;
++              }
++              /* set flag to be used later on */
++              reason |= REASON_FLAG_COVER;
++      }
++      else {
++              /* not for us, get out of here */
++              return IRQ_NONE;
++      }
++      /* ok we have an interrupt, now process our queue */
++      /* lock our structure */
++      spin_lock_irqsave(&interrupt_queue.lock,flags);
++      /* now look at our structure and call appropriate callback if necessary */
++      if (!list_empty(&interrupt_queue.queue)) {
++              /* first unlink the queue item */
++              cur_item = (struct gc_dvd_command*)interrupt_queue.queue.next;
++              list_del(&cur_item->list);
++              /* do special checks on the command type to keep track of drive state */
++              if (IS_CMD_TYPE(cur_item->r_DI_DICMDBUF0,DI_CMD_STOP)) {
++                      interrupt_queue.drive_initialized = 0;
++              }
++              else if (IS_CMD_TYPE(cur_item->r_DI_DICMDBUF0,DI_CMD_INITIALIZE)) {
++                      interrupt_queue.drive_initialized = 1;
++              }
++              /* now execute the next request if we have one */
++              if (!list_empty(&interrupt_queue.queue)) {
++                      gc_dvd_execute_queue_command((struct gc_dvd_command*)
++                                                   interrupt_queue.queue.next);
++              }
++              /* unlock the lock */
++              spin_unlock_irqrestore(&interrupt_queue.lock,flags);
++              /* determine the correct interrupt status */
++              if (reason & REASON_FLAG_COVER) {
++                      if (reason & DI_DICVR_CVR) {
++                              cur_item->int_status = is_cover_opened;
++                      }
++                      else {
++                              cur_item->int_status = is_cover_closed;
++                      }
++              }
++              else if (reason & DI_DISR_TCINT) {
++                      cur_item->int_status = is_transfer_complete;
++              }
++              else if (reason & DI_DISR_DEINT) {
++                      cur_item->int_status = is_error;
++              }
++              else if (reason & DI_DISR_BRKINT) {
++                      cur_item->int_status = is_break;
++              }
++              /* call the callback */
++              if (cur_item->completion_routine) {
++                      cur_item->completion_routine(cur_item);
++              }
++      }
++      else {
++              spin_unlock_irqrestore(&interrupt_queue.lock,flags);
++              DPRINTK("Received interrupt but nothing was waiting for it\n");
++      }
++  
++      return IRQ_HANDLED;
++}
++
++static int __init gc_dvd_init(void)
++{
++      int ret;
++
++      printk(KERN_INFO "Gamecube DVD driver: init\n");
++
++      /* initialize the refcount */
++      memset(&dvd_info,0,sizeof(dvd_info));
++      dvd_info.media_changed = 1;
++
++      /* initialize the interrupt_queue */
++      spin_lock_init(&interrupt_queue.lock);
++      interrupt_queue.drive_initialized = 0;
++      INIT_LIST_HEAD(&interrupt_queue.queue);
++
++      spin_lock_init(&dvd_info.lock);
++  
++      /* initial the request queue */
++      gc_dvd_request_init();
++      /* first reserve our memory region to we can query hardware */
++      if (check_mem_region(DVD_REGISTER_BLOCK_BASE,DVD_REGISTER_BLOCK_LENGTH) ||
++          !request_mem_region(DVD_REGISTER_BLOCK_BASE,DVD_REGISTER_BLOCK_LENGTH,"Gamecube DVD")) {
++              printk(KERN_ERR "Couldn't reserve memory area for DVD\n");
++              return -ENOMEM;
++      }    
++      
++      if ((ret=request_irq(DVD_IRQ,gc_dvd_irq_handler,SA_INTERRUPT,"Gamecube DVD",0))) {
++              printk(KERN_ERR "Unable to reserve DVD IRQ\n");
++              goto delete_mem_region;
++      }
++      
++      /* enable interrupts */
++      gc_dvd_enable_interrupts(1);
++      /* query the drive first */
++      if ((ret=gc_dvd_inquiry())) {
++              goto delete_irq;
++      }
++      /* now stop the dvd motor */
++      gc_dvd_stop_motor();
++    
++      if ((ret=register_blkdev(DVD_MAJOR,DEVICE_NAME))) {
++              goto delete_irq;
++      }
++      
++      if (!(dvd_gendisk = alloc_disk(1))) {
++              ret = -ENOMEM;
++              goto unreg_blkdev;
++      }
++
++      if (!(dvd_queue = blk_init_queue(gc_dvd_do_request,&dvd_queue_lock))) {
++              ret = -ENOMEM;
++              goto delete_gendisk;
++      }
++      
++      dvd_gendisk->major = DVD_MAJOR;
++      dvd_gendisk->first_minor = 0;
++      dvd_gendisk->fops = &dvd_fops;
++      strcpy(dvd_gendisk->disk_name,"dvd");
++
++      dvd_gendisk->queue = dvd_queue;
++
++      /* ok now setup the desired parameters, like block size, hardsect size,
++         max hardware sectors to read at once, read ahead, etc */
++      /* Hardware sector size */
++      blk_queue_hardsect_size(dvd_queue,DVD_SECTOR_SIZE);
++      /* Maximum sectors that can be read per request, hardware limit */
++      blk_queue_max_phys_segments(dvd_queue,1);
++      blk_queue_max_hw_segments(dvd_queue,1);
++      /* Max size of coalesced segment */
++      /* blk_queue_max_segment_size(dvd_queue,size); */
++      /* Set the dma alignment */
++      blk_queue_dma_alignment(dvd_queue,DMA_ALIGNMENT_MASK);
++
++      set_disk_ro(dvd_gendisk,1);  
++      add_disk(dvd_gendisk);
++
++      return 0;
++
++ delete_gendisk:
++      del_gendisk(dvd_gendisk);
++      put_disk(dvd_gendisk);
++      dvd_gendisk = NULL;
++ unreg_blkdev:
++      unregister_blkdev(DVD_MAJOR,DEVICE_NAME);
++ delete_irq:
++      free_irq(DVD_IRQ,0);
++ delete_mem_region:
++      release_mem_region(DVD_REGISTER_BLOCK_BASE,DVD_REGISTER_BLOCK_LENGTH);
++      return ret;
++}
++
++static void __exit gc_dvd_exit(void)
++{
++      printk(KERN_INFO "Gamecube DVD driver: exit\n");
++
++      /* TODO send a break/interrupt to the device */
++      gc_dvd_stop_motor();
++      gc_dvd_enable_interrupts(0);
++
++      free_irq(DVD_IRQ, 0);
++
++      release_mem_region(DVD_REGISTER_BLOCK_BASE,DVD_REGISTER_BLOCK_LENGTH);
++  
++      blk_unregister_region(MKDEV(DVD_MAJOR,0),256);
++      unregister_blkdev(DVD_MAJOR,DEVICE_NAME);
++
++      if (dvd_gendisk) {
++              del_gendisk(dvd_gendisk);
++              put_disk(dvd_gendisk);
++    
++              dvd_gendisk = NULL;
++      }
++
++      if (dvd_queue) {
++              blk_cleanup_queue(dvd_queue);
++  
++              dvd_queue = NULL;
++      }
++}
++
++MODULE_AUTHOR("Scream|CT");
++MODULE_DESCRIPTION("Gamecube DVD driver");
++MODULE_LICENSE("GPL");
++
++module_init(gc_dvd_init);
++module_exit(gc_dvd_exit);
+diff --git a/drivers/block/gcn-dvd/request.c b/drivers/block/gcn-dvd/request.c
+new file mode 100644
+index 0000000..19203e3
+--- /dev/null
++++ b/drivers/block/gcn-dvd/request.c
+@@ -0,0 +1,70 @@
++/*
++ * drivers/block/gcn-dvd/request.c
++ *
++ * Nintendo GameCube DVD driver 
++ * Copyright (C) 2005 The GameCube Linux Team
++ *
++ * 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/types.h>
++#include <linux/errno.h>
++#include <linux/list.h>
++#include <linux/spinlock.h>
++#include "request.h"
++
++struct data_item
++{
++      struct list_head list;
++      struct gc_dvd_command data;
++};
++
++// define array of data items
++static struct data_item data[MAX_ITEMS];
++static spinlock_t lock;
++static struct list_head lfree;
++
++void gc_dvd_request_init(void)
++{
++      int i;
++  
++      INIT_LIST_HEAD(&lfree);
++      spin_lock_init(&lock);
++  
++      for (i=0;i<MAX_ITEMS;++i) {
++              list_add_tail(&data[i].list,&lfree);
++      }
++}
++
++int gc_dvd_request_get_data(struct gc_dvd_command **ppcmd)
++{
++      unsigned long flags;
++      struct data_item *pdi;
++      // get the first object and remove from the free list
++      spin_lock_irqsave(&lock,flags);
++      if (list_empty(&lfree)) {
++              spin_unlock_irqrestore(&lock,flags);
++              return -ENOMEM;
++      }
++      pdi = (struct data_item*)lfree.next;
++      list_del(&pdi->list);
++      spin_unlock_irqrestore(&lock,flags);
++      // return it
++      *ppcmd = &pdi->data;
++      return 0;
++}
++
++void gc_dvd_request_release_data(struct gc_dvd_command *pcmd)
++{
++      unsigned long flags;
++      struct data_item *pdi;
++      /* return the item */
++      spin_lock_irqsave(&lock,flags);
++      pdi = (struct data_item*)((u8*)pcmd - sizeof(struct list_head));
++      list_add_tail(&pdi->list,&lfree);
++      spin_unlock_irqrestore(&lock,flags);
++}
+diff --git a/drivers/block/gcn-dvd/request.h b/drivers/block/gcn-dvd/request.h
+new file mode 100644
+index 0000000..f0fdd0b
+--- /dev/null
++++ b/drivers/block/gcn-dvd/request.h
+@@ -0,0 +1,45 @@
++/*
++ * drivers/block/gcn-dvd/request.h
++ *
++ * Nintendo GameCube DVD driver 
++ * Copyright (C) 2005 The GameCube Linux Team
++ *
++ * 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 __request___
++#define __request___
++
++#define MAX_ITEMS 8
++
++enum gc_dvd_interrupt_status { is_still_running,
++                             is_transfer_complete,
++                             is_error,
++                             is_break,
++                             is_cover_closed,
++                             is_cover_opened};
++
++struct gc_dvd_command
++{
++  struct list_head list;
++  u32 flags;
++  enum gc_dvd_interrupt_status int_status;
++  u32 r_DI_DICMDBUF0;
++  u32 r_DI_DICMDBUF1;
++  u32 r_DI_DICMDBUF2;
++  void *r_DI_DIMAR;
++  u32 r_DI_DILENGTH;
++  u32 r_DI_DICR;
++  void *param;
++  void (*completion_routine)(struct gc_dvd_command *cmd);
++};
++
++void gc_dvd_request_init(void);
++int  gc_dvd_request_get_data(struct gc_dvd_command **ppcmd);
++void gc_dvd_request_release_data(struct gc_dvd_command *pcmd);
++
++#endif
+diff --git a/drivers/block/gcn-memcard.c b/drivers/block/gcn-memcard.c
+new file mode 100644
+index 0000000..e14e614
+--- /dev/null
++++ b/drivers/block/gcn-memcard.c
+@@ -0,0 +1,569 @@
++/*
++ * drivers/block/gcn-memcard.c
++ *
++ * Nintendo GameCube Memory Card block driver
++ * Copyright (C) 2004 The GameCube Linux Team
++ *
++ * Based on work 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.
++ *
++ */
++#define DEVICE_NAME "memcard"
++
++#include <linux/major.h>
++#include <linux/vmalloc.h>
++#include <linux/init.h>
++#include <linux/module.h>
++#include <linux/moduleparam.h>
++#include <linux/blkdev.h>
++#include <linux/delay.h>
++#include <linux/interrupt.h>
++#include <linux/fcntl.h>      /* O_ACCMODE */
++#include <linux/hdreg.h>      /* HDIO_GETGEO */
++
++#include <linux/exi.h>
++
++#include <asm/setup.h>
++#include <asm/bitops.h>
++#include <asm/pgtable.h>
++#include <asm/cacheflush.h>
++
++#define MEMCARD_MAX_UNITS 2
++
++static int major_num = 0;
++module_param(major_num, int, 0);
++
++#define TRUE                  (1)
++#define FALSE                 (0)
++
++static int curr_card_sector[MEMCARD_MAX_UNITS] = { -1, -1 };
++static int card_sector_mask[MEMCARD_MAX_UNITS] = { 0, 0 };
++static int current_device = -1;
++static spinlock_t memcard_lock = SPIN_LOCK_UNLOCKED;
++
++
++
++static struct block_device_operations memcard_fops;
++static struct gendisk *memcard_gendisk[MEMCARD_MAX_UNITS];
++
++static unsigned char *card_sector_buffer[MEMCARD_MAX_UNITS];
++
++#define MEMCARD_READ                          1
++#define MEMCARD_WRITE                         0
++#define ALLOW_WRITE
++#define CARD_SECTOR_SIZE 0x2000
++
++/*#define CARD_DBG    printk*/
++#define CARD_DBG(format, arg...); { }
++
++/* MemCard commands originally by Costis */
++
++
++#define CARD_FILENAME     32
++#define CARD_MAXFILES    127
++#define CARD_MAXICONS      8
++#define CARD_READSIZE    512
++#define CARD_SECTORSIZE 8192
++#define CARD_SLOTA         0
++#define CARD_SLOTB         1
++#define CARD_SYSAREA       5
++#define CARD_WRITESIZE   128
++
++
++#define EXI_CONTROL_TYPE_READ        0
++#define EXI_CONTROL_TYPE_WRITE       1
++#define EXI_STATUS0   *(unsigned long*)0xCC006800
++
++#define EXI_DMABUF0   *(unsigned long*)0xCC006804
++#define EXI_DMALEN0   *(unsigned long*)0xCC006808
++#define EXI_DMACNT0   *(unsigned long*)0xCC00680C
++
++#define EXI_CONTROL_DMA              2
++#define EXI_CONTROL_ENABLE           1
++
++static int exi_probe(unsigned long channel)
++{
++      if (*(unsigned long *) (&EXI_STATUS0 + (channel * 5)) & 0x1000)
++              return 1;
++      else
++              return 0;
++}
++
++static unsigned long exi_retrieve_id(unsigned long channel,
++                                   unsigned long device)
++{
++      unsigned long tID;
++
++      if (exi_probe(channel)) {
++
++              /* Select the specified EXI channel and device. */
++              exi_select(channel, device, 0);
++
++              /* Send the EXI ID command (0x0000) */
++              tID = 0;
++              exi_write(channel, (unsigned char *) &tID, 2);
++              /* Read the actual ID data (4 bytes) */
++              exi_read(channel, (unsigned char *) &tID, 4);
++              /* Deselect the selected EXI device. */
++              exi_deselect(channel);
++
++              return tID;
++      } else {
++              return 0;
++      }
++}
++
++
++/*
++  Determines if a memcard is present in the given slot.
++*/
++
++static int card_is_present(unsigned long channel)
++{
++
++      unsigned long id;
++
++      id = exi_retrieve_id(channel, 0);
++
++      if (id & 0xffff0000 || id & 3)
++              return 0;
++
++      return id;
++}
++
++
++/**
++   Channel must be from 0 to 2.
++   Buffer must be aligned to a 32-bit offset.
++   Size must be a multiple of 32 bytes.
++   Type must be either EXI_CONTROL_TYPE_READ or EXI_CONTROL_TYPE_WRITE. 
++*/
++static void exi_dma(unsigned long channel, unsigned char *abuffer,
++                  unsigned long size, unsigned long type)
++{
++      /* EXI DMA Operation */
++      *(unsigned long *) (&EXI_DMABUF0 + (channel * 5)) =
++          (unsigned long) abuffer & 0x3FFFFE0;
++      *(unsigned long *) (&EXI_DMALEN0 + (channel * 5)) =
++          size & 0x3FFFFE0;
++      *(unsigned long *) (&EXI_DMACNT0 + (channel * 5)) =
++          EXI_CONTROL_ENABLE | EXI_CONTROL_DMA | (type << 2);
++
++      /* Wait until the EXI DMA operation has been completed. */
++      while (*(volatile unsigned long *) (&EXI_DMACNT0 + channel * 5) &
++             EXI_CONTROL_ENABLE);
++
++      return;
++}
++
++
++static unsigned char card_read_status(unsigned long channel)
++{
++      unsigned char cbuf[4];
++
++      /* Select the specified EXI channel and device. */
++      exi_select(channel, 0, 4);
++
++      /* Send the EXI ID command (0x83xx) */
++      cbuf[0] = 0x83;         /* Command Byte */
++      cbuf[1] = 0x00;
++      exi_write(channel, cbuf, 2);
++      /* Read the actual ID data (2 bytes) */
++      exi_read(channel, cbuf, 1);
++
++      /* Deselect the selected EXI device. */
++      exi_deselect(channel);
++
++      return cbuf[0];
++}
++
++static void card_read_array(unsigned long channel, unsigned char *abuf,
++                          unsigned long address, unsigned long size)
++{
++      int i;
++      unsigned char cbuf[4];
++      unsigned char *bbuf = abuf;
++
++      for (i = 0; i < size / CARD_READSIZE; i++) {
++
++              /* Check if the card is ready */
++              while (!(card_read_status(channel) & 1));
++
++              /* Select the specified EXI channel and device. */
++              exi_select(channel, 0, 4);
++
++              /* Send the EXI Sector Read command (0x52xxxxxx) */
++              cbuf[0] = 0x52; /* Command Byte */
++              cbuf[1] = (address >> 17) & 0x3F;
++              cbuf[2] = (address >> 9) & 0xFF;
++              cbuf[3] = (address >> 7) & 3;
++              exi_write(channel, cbuf, 4);
++
++              cbuf[0] = address & 0x7F;
++              exi_write(channel, cbuf, 1);
++
++              cbuf[0] = 0;
++              cbuf[1] = 0;
++              cbuf[2] = 0;
++              cbuf[3] = 0;
++              exi_write(channel, cbuf, 4);
++
++              exi_dma(channel, bbuf, CARD_READSIZE,
++                      EXI_CONTROL_TYPE_READ);
++
++              /* Deselect the selected EXI device. */
++              exi_deselect(channel);
++
++              address += CARD_READSIZE;
++              bbuf += CARD_READSIZE;
++      }
++}
++
++static void card_sector_erase(unsigned long channel, unsigned long sector)
++{
++      unsigned char cbuf[3];
++
++      /* Check if the card is ready */
++      while (!(card_read_status(channel) & 1));
++
++      /* Select the specified EXI channel and device. */
++      exi_select(channel, 0, 4);
++
++      /* Send the EXI Sector Erase command (0xF1xxxx) */
++      cbuf[0] = 0xF1;         // Command Byte
++      cbuf[1] = (sector >> 17) & 0x7F;
++      cbuf[2] = (sector >> 9) & 0xFF;
++      exi_write(channel, cbuf, 3);
++
++      /* Deselect the selected EXI device. */
++      exi_deselect(channel);
++
++      /* Wait till the erase is finished */
++      while (card_read_status(channel) & 0x8000);
++}
++
++static void card_sector_program(unsigned long channel, unsigned char *abuf,
++                              unsigned long address, unsigned long size)
++{
++      int i;
++      unsigned char cbuf[4];
++      unsigned char *bbuf = abuf;
++
++      for (i = 0; i < size / CARD_WRITESIZE; i++) {
++
++              /* Check if the card is ready */
++              while (!(card_read_status(channel) & 1));
++
++              /* Select the specified EXI channel and device. */
++              exi_select(channel, 0, 4);
++
++              /* Send the EXI Sector Program command (0xF2xxxxxx) */
++              cbuf[0] = 0xF2; /* Command Byte */
++              cbuf[1] = (address >> 17) & 0x3F;
++              cbuf[2] = (address >> 9) & 0xFF;
++              cbuf[3] = (address >> 7) & 3;
++              exi_write(channel, cbuf, 4);
++
++              cbuf[0] = address & 0x7F;
++              exi_write(channel, cbuf, 1);
++
++              exi_dma(channel, bbuf, CARD_WRITESIZE,
++                      EXI_CONTROL_TYPE_WRITE);
++
++              /* Deselect the selected EXI device. */
++              exi_deselect(channel);
++
++              /* Wait till the write is finished */
++              while (card_read_status(channel) & 0x8000);
++
++              address += CARD_WRITESIZE;
++              bbuf += CARD_WRITESIZE;
++      }
++}
++
++/**
++   Block device handling
++ */
++
++static int memcard_buffersize(int channel)
++{
++      return card_is_present(channel) << 17;
++}
++
++static void card_flush(int channel)
++{
++      int i, mask;
++      unsigned long start = curr_card_sector[channel] * CARD_SECTOR_SIZE;
++      CARD_DBG("card_flush(%d)\n", channel);
++      if (curr_card_sector[channel] == -1)
++              return;
++      CARD_DBG("Flush channel = %d mask = %x\n", channel,
++               card_sector_mask[channel]);
++      for (i = 0, mask = ~card_sector_mask[channel]; i < 16; i++) {
++              if (mask & 1) {
++                      CARD_DBG("card_flush: read block %d\n", i);
++              }
++      }
++      CARD_DBG("card_flush: writing out ch = %d, start = %08lx\n",
++               channel, start);
++      flush_dcache_range((unsigned long) card_sector_buffer[channel],
++                         (unsigned long) card_sector_buffer[channel] +
++                         CARD_SECTOR_SIZE);
++
++      card_sector_erase(channel, start);
++      card_sector_program(channel, card_sector_buffer[channel], start,
++                          CARD_SECTOR_SIZE);
++      card_sector_mask[channel] = 0;
++      curr_card_sector[channel] = -1;
++}
++
++static void do_memcard_request(request_queue_t * q)
++{
++      struct request *req;
++      blk_stop_queue(q);
++      spin_lock(&memcard_lock);
++
++      while ((req = elv_next_request(q)) != NULL) {
++              unsigned long start = req->sector << 9;
++              unsigned long len = req->current_nr_sectors << 9;
++              int channel = req->rq_disk->first_minor;
++
++              if (start + len > memcard_buffersize(channel)) {
++                      printk(KERN_ERR DEVICE_NAME
++                             ": bad access: block=%lu, count=%u\n",
++                             (unsigned long) req->sector,
++                             req->current_nr_sectors);
++                      end_request(req, 0);
++                      continue;
++              }
++
++              if (rq_data_dir(req) == READ) {
++                      CARD_DBG
++                          ("do_memcard_request: READ(%s,%lu,%lu), channel = %d\n",
++                           req->rq_disk->disk_name,
++                           (unsigned long) req->sector,
++                           (unsigned long) req->current_nr_sectors,
++                           channel);
++
++                      card_flush(channel);
++                      flush_dcache_range((unsigned long) req->buffer,
++                                         (unsigned long) req->buffer +
++                                         len);
++                      card_read_array(channel, req->buffer, start, len);
++                      invalidate_dcache_range((unsigned long) req->
++                                              buffer,
++                                              (unsigned long) req->
++                                              buffer + len);
++
++              } else {
++                      int i;
++                      CARD_DBG
++                          ("do_memcard_request: WRITE channel = %d\n",
++                           channel);
++                      for (i = 0; i < req->current_nr_sectors; i++) {
++                              int new_card_sector =
++                                  (req->sector + i) >> 4;
++                              int card_sector_idx =
++                                  (req->sector + i) & 0xf;
++                              if (new_card_sector !=
++                                  curr_card_sector[channel]) {
++                                      card_flush(channel);
++                                      curr_card_sector[channel] =
++                                          new_card_sector;
++                              }
++                              card_sector_mask[channel] |=
++                                  1 << card_sector_idx;
++                              CARD_DBG("update block %d, sector %d\n",
++                                       card_sector_idx, new_card_sector);
++                              memcpy(card_sector_buffer[channel] +
++                                     (card_sector_idx << 9),
++                                     req->buffer + (i << 9), 1 << 9);
++                              if (card_sector_idx == 0xF)
++                                      card_flush(channel);
++                      }
++              }
++
++
++              end_request(req, 1);
++      }
++
++      spin_unlock(&memcard_lock);
++      blk_start_queue(q);
++
++}
++
++
++static int memcard_open(struct inode *inode, struct file *filp)
++{
++      CARD_DBG("MEMCARD Open device\n");
++
++      int device;
++      int rc = -ENOMEM;
++
++      device = iminor(inode);
++
++      if (current_device != -1 && current_device != device) {
++              rc = -EBUSY;
++              goto err_out;
++      }
++
++      if (memcard_buffersize(device) == 0) {
++              rc = -ENODEV;
++              goto err_out;
++      }
++
++      return 0;
++
++      err_out:
++      CARD_DBG("MEMCARD Open device Error %d\n", rc);
++
++      return rc;
++
++}
++
++static int memcard_ioctl(struct inode *inode, struct file *file,
++                       unsigned int cmd, unsigned long arg)
++{
++      CARD_DBG("MEMCARD IOCTL\n");
++
++      if (cmd == HDIO_GETGEO) {
++              long size;
++              struct hd_geometry geo;
++              /*
++               * get geometry: we have to fake one...  trim the size to a
++               * multiple of 1024 (0.5M): tell we have 32 sectors, 32 heads,
++               * whatever cylinders.
++               */
++              size = memcard_buffersize(iminor(inode));
++              geo.heads = 32;
++              geo.sectors = 32;
++              geo.start = 0;
++              geo.cylinders = size / (geo.heads * geo.sectors);
++
++              if (copy_to_user((void *) arg, &geo, sizeof(geo)))
++                      return -EFAULT;
++              return 0;
++      }
++
++      return -EINVAL;
++}
++
++static int memcard_release(struct inode *inode, struct file *filp)
++{
++      CARD_DBG("MEMCARD Close device\n");
++      card_flush(iminor(inode));
++      if (current_device == -1)
++              return 0;
++
++      return 0;
++}
++
++
++static int memcard_revalidate(struct gendisk *disk)
++{
++      CARD_DBG("MEMCARD Revalidate\n");
++      set_capacity(disk, memcard_buffersize(disk->first_minor) >> 9);
++      return 0;
++}
++
++static struct block_device_operations memcard_fops = {
++      .owner = THIS_MODULE,
++      .open = memcard_open,
++      .release = memcard_release,
++      .revalidate_disk = memcard_revalidate,
++      .ioctl = memcard_ioctl,
++};
++
++
++static struct request_queue *memcard_queue[MEMCARD_MAX_UNITS];
++
++int __init memcard_init(void)
++{
++      int ret;
++      int i;
++
++      CARD_DBG("MemCard Block Device Driver Init\n");
++
++      ret = -EBUSY;
++      major_num = register_blkdev(major_num, DEVICE_NAME);
++      if (major_num <= 0)
++              goto err;
++
++      ret = -ENOMEM;
++      for (i = 0; i < MEMCARD_MAX_UNITS; i++) {
++              memcard_gendisk[i] = alloc_disk(1);
++              if (!memcard_gendisk[i])
++                      goto out_disk;
++      }
++
++      for (i = 0; i < MEMCARD_MAX_UNITS; i++) {
++              memcard_queue[i] =
++                  blk_init_queue(do_memcard_request, &memcard_lock);
++              if (!memcard_queue[i])
++                      goto out_queue;
++      }
++
++      for (i = 0; i < MEMCARD_MAX_UNITS; i++) {
++              memcard_gendisk[i]->major = major_num;
++              memcard_gendisk[i]->first_minor = i;
++              memcard_gendisk[i]->fops = &memcard_fops;
++              sprintf(memcard_gendisk[i]->disk_name, "memcard%d", i);
++              strcpy(memcard_gendisk[i]->devfs_name,
++                     memcard_gendisk[i]->disk_name);
++
++              memcard_gendisk[i]->queue = memcard_queue[i];
++              set_capacity(memcard_gendisk[i],
++                           memcard_buffersize(i) >> 9);
++
++              add_disk(memcard_gendisk[i]);
++      }
++
++      spin_lock_init(&memcard_lock);
++      
++      for (i = 0; i < MEMCARD_MAX_UNITS; i++) {
++        card_sector_buffer[i] = kmalloc(CARD_SECTOR_SIZE, GFP_KERNEL);
++      }
++
++      return 0;
++
++      out_queue:
++      for (i = 0; i < MEMCARD_MAX_UNITS; i++)
++              put_disk(memcard_gendisk[i]);
++      out_disk:
++      unregister_blkdev(major_num, DEVICE_NAME);
++      err:
++      return ret;
++}
++
++
++void __exit memcard_cleanup(void)
++{
++      int i;
++      blk_unregister_region(MKDEV(major_num, 0), 256);
++      for (i = 0; i < MEMCARD_MAX_UNITS; i++) {
++              del_gendisk(memcard_gendisk[i]);
++              put_disk(memcard_gendisk[i]);
++      }
++
++      if (unregister_blkdev(major_num, DEVICE_NAME) != 0)
++              printk(KERN_ERR DEVICE_NAME
++                     ": unregister of device failed\n");
++
++      for (i = 0; i < MEMCARD_MAX_UNITS; i++) {
++              blk_cleanup_queue(memcard_queue[i]);
++              kfree(card_sector_buffer[i]);
++      }
++
++
++      CARD_DBG("Removed gc_memcard\n");
++      return;
++}
++
++MODULE_LICENSE("GPL");
++MODULE_AUTHOR("Torben Nielsen");
++module_init(memcard_init);
++module_exit(memcard_cleanup);
+diff --git a/drivers/block/gcn-sd.c b/drivers/block/gcn-sd.c
+new file mode 100644
+index 0000000..be489bc
+--- /dev/null
++++ b/drivers/block/gcn-sd.c
+@@ -0,0 +1,1709 @@
++/*
++ * 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 = req->sector << 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 = req->current_nr_sectors;
++      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 = req->sector << KERNEL_SECTOR_SHIFT;
++      nr_blocks = req->current_nr_sectors;
++      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
++ *  >0 if request can be ignored
++ */
++static int sd_check_request(struct sd_host *host, struct request *req)
++{
++      unsigned long nr_sectors;
++
++      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 (req->sector + req->current_nr_sectors > nr_sectors) {
++              sd_printk(KERN_ERR, "reading past end, aborting\n");
++              return -EINVAL;
++      }
++
++      if (!blk_fs_request(req))
++              return 1;
++
++      return 0;
++}
++
++/*
++ * Request dispatcher.
++ */
++static int sd_do_request(struct sd_host *host, struct request *req)
++{
++      int retval;
++
++      retval = sd_check_request(host, req);
++      if (retval)
++              return 0;
++
++      switch (rq_data_dir(req)) {
++      case WRITE:
++              retval = sd_write_request(host, req);
++              break;
++      case READ:
++              retval = sd_read_request(host, req);
++              break;
++      }
++
++      return retval;
++}
++
++/*
++ * 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 = elv_next_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_hardsect_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->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-mem2.c b/drivers/block/rvl-mem2.c
+new file mode 100644
+index 0000000..01339f5
+--- /dev/null
++++ b/drivers/block/rvl-mem2.c
+@@ -0,0 +1,384 @@
++/*
++ * 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 = elv_next_request(q);
++      while (req) {
++              if (blk_fs_request(req)) {
++                      /* calculate the MEM2 address and length */
++                      mem2_addr = req->sector << 9;
++                      len = req->current_nr_sectors << 9;
++
++                      /* give up if the request goes out of bounds */
++                      if (mem2_addr + len > drvdata->size) {
++                              drv_printk(KERN_ERR, "bad access: block=%lu,"
++                                          " size=%u\n",
++                                          (unsigned long)req->sector, len);
++                              error = -EIO;
++                      } else {
++                              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;
++                      }
++                      blk_end_request(req, error, len);
++              } else {
++                      end_request(req, 0);
++              }
++              req = elv_next_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_hardsect_size(queue, MEM2_SECTOR_SIZE);
++      blk_queue_max_phys_segments(queue, 1);
++      blk_queue_max_hw_segments(queue, 1);
++      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..d3b7044
+--- /dev/null
++++ b/drivers/block/rvl-stsd.c
+@@ -0,0 +1,2294 @@
++/*
++ * 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>
++
++/*
++ * 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.3i";
++
++#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;
++}
++
++/*
++ * Returns >0 if a request should be dispatched.
++ */
++static int stsd_check_request(struct stsd_host *host, struct request *req)
++{
++      unsigned long nr_sectors;
++
++      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 (req->sector + req->current_nr_sectors > nr_sectors) {
++              drv_printk(KERN_ERR, "reading past end, aborting\n");
++              return -EINVAL;
++      }
++
++      if (!blk_fs_request(req))
++              return 0;
++
++      return 1;
++}
++
++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 uptodate;
++      int error;
++
++      uptodate = stsd_check_request(host, req);
++      if (uptodate <= 0)
++              return uptodate;
++
++      write = (rq_data_dir(req) == READ) ? 0 : 1;
++
++      start = req->sector;
++      if (!stsd_card_is_sdhc(host))
++              start <<= KERNEL_SECTOR_SHIFT;
++      nr_blocks = req->current_nr_sectors;
++
++      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);
++
++      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 = elv_next_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(req, error, blk_rq_bytes(req));
++              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_hardsect_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->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;
++
++      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-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..ae173a5
+--- /dev/null
++++ b/drivers/exi/exi-driver.c
+@@ -0,0 +1,515 @@
++/*
++ * 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>
++
++#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] = {
++              .bus_id = "exi0",
++              .release = exi_bus_device_release,
++              .parent = NULL
++      },
++      [1] = {
++              .bus_id = "exi1",
++              .release = exi_bus_device_release,
++               .parent = NULL
++      },
++      [2] = {
++              .bus_id = "exi2",
++              .release = exi_bus_device_release,
++              .parent = NULL
++      },
++};
++
++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;
++      sprintf(exi_device->dev.bus_id, "exi%01x:%01x", channel, device);
++      exi_device->dev.platform_data = to_exi_channel(channel);
++      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",
++                         exi_device->dev.bus_id,
++                         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",
++                         exi_device->dev.bus_id,
++                         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;
++}
++
++/*
++ *
++ */
++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;
++
++      /* 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 root devices */
++      for (channel = 0; channel < EXI_MAX_CHANNELS; ++channel) {
++              retval = device_register(&exi_bus_devices[channel]);
++              if (retval)
++                      goto err_device_register;
++      }
++
++      /* 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..de3414b
+--- /dev/null
++++ b/drivers/exi/exi-hw.c
+@@ -0,0 +1,1408 @@
++/*
++ * 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)
++{
++      static u8 exi_aligned_transfer_buf[EXI_DMA_ALIGN+1]
++                               __attribute__ ((aligned(EXI_DMA_ALIGN+1)));
++      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.
++               */
++
++              /*
++               * On transfer sizes greater than or equal to 32 bytes
++               * we can optimize the transfer by performing a 32-byte
++               * DMA transfer using a specially aligned temporary buffer,
++               * followed by a non-DMA transfer for the remaining bytes.
++               */
++              if (pre_len + post_len > EXI_DMA_ALIGN) {
++                      post_len = pre_len + post_len - (EXI_DMA_ALIGN+1);
++                      post_data = pre_data + EXI_DMA_ALIGN+1;
++                      len = EXI_DMA_ALIGN+1;
++                      data = exi_aligned_transfer_buf;
++                      memcpy(data, pre_data, EXI_DMA_ALIGN+1);
++                      pre_len = 0;
++              } else {
++                      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;
++}
++
++/*
++ * 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..a2382bf
+--- /dev/null
++++ b/drivers/exi/exi-hw.h
+@@ -0,0 +1,195 @@
++/*
++ * 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 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/input/Kconfig b/drivers/input/Kconfig
+index 5f9d860..bcf4d1f 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 efd70a9..ed0a67a 100644
+--- a/drivers/input/keyboard/Kconfig
++++ b/drivers/input/keyboard/Kconfig
+@@ -314,6 +314,17 @@ config KEYBOARD_BFIN
+         To compile this driver as a module, choose M here: the
+         module will be called bf54x-keys.
++config KEYBOARD_WII
++      tristate "Nintendo Wii USB keyboard IOS glue"
++      depends on (WII && !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 0edc8f2..2918d70 100644
+--- a/drivers/input/keyboard/Makefile
++++ b/drivers/input/keyboard/Makefile
+@@ -27,3 +27,4 @@ obj-$(CONFIG_KEYBOARD_HP7XX)         += jornada720_kbd.o
+ obj-$(CONFIG_KEYBOARD_MAPLE)          += maple_keyb.o
+ obj-$(CONFIG_KEYBOARD_BFIN)           += bf54x-keys.o
+ obj-$(CONFIG_KEYBOARD_SH_KEYSC)               += sh_keysc.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..272f772
+--- /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.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.1i";
++
++#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-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..731437f
+--- /dev/null
++++ b/drivers/input/si/gcn-si.c
+@@ -0,0 +1,730 @@
++/*
++ * 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 {
++      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_psi_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(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);
++
++      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;
++
++      si_reset(io_base);
++
++      /*
++       * 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);
++
++      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;
++}
++
++
++/*
++ * 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 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,
++};
++
++
++/*
++ * 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 fee7304..1b07a82 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 817f7f5..79387aa 100644
+--- a/drivers/misc/Makefile
++++ b/drivers/misc/Makefile
+@@ -33,3 +33,5 @@ obj-$(CONFIG_SGI_XP)         += sgi-xp/
+ obj-$(CONFIG_SGI_GRU)         += sgi-gru/
+ obj-$(CONFIG_HP_ILO)          += hpilo.o
+ obj-$(CONFIG_C2PORT)          += c2port/
++obj-$(CONFIG_GAMECUBE_GQR)    += gcn-gqr.o
++obj-$(CONFIG_GAMECUBE_MI)     += gcn-mi.o
+diff --git a/drivers/misc/gcn-gqr.c b/drivers/misc/gcn-gqr.c
+new file mode 100644
+index 0000000..6648265
+--- /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(ctl_table *table, int write, struct file *file,
++                    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, file, 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 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 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..864017d
+--- /dev/null
++++ b/drivers/misc/gcn-mi.c
+@@ -0,0 +1,443 @@
++/*
++ * 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(pdev->dev.bus_id, 0444, NULL,
++                                               mi_proc_read, priv);
++      priv->proc_file->owner = THIS_MODULE;
++#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(pdev->dev.bus_id, 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/net/Kconfig b/drivers/net/Kconfig
+index 231eeaf..a56c769 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 017383a..79854fa 100644
+--- a/drivers/net/Makefile
++++ b/drivers/net/Makefile
+@@ -226,6 +226,7 @@ obj-$(CONFIG_PASEMI_MAC) += pasemi_mac_driver.o
+ pasemi_mac_driver-objs := pasemi_mac.o pasemi_mac_ethtool.o
+ obj-$(CONFIG_MLX4_CORE) += mlx4/
+ obj-$(CONFIG_ENC28J60) += enc28j60.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..2f67968
+--- /dev/null
++++ b/drivers/net/gcn-bba.c
+@@ -0,0 +1,1252 @@
++/**
++ * 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;
++#else
++      __u32   next_packet_ptr:12,
++              packet_len : 12,
++              status : 8;
++#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 = (struct bba_private *)dev->priv;
++      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 = (struct bba_private *)dev->priv;
++
++      /* 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 = (struct bba_private *)dev->priv;
++
++      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 = (struct bba_private *)dev->priv;
++      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 = (struct bba_private *)dev->priv;
++      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 = (struct bba_private *)dev->priv;
++      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 = (struct bba_private *)dev->priv;
++      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 = (struct bba_private *)dev->priv;
++      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 */
++              bba_out12(BBA_RRP, rrp = descr.next_packet_ptr);
++
++              /* 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 = (struct bba_private *)dev->priv;
++      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 = (struct bba_private *)dev->priv;
++
++      /* 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;
++      revid_0 = priv->revid;
++      revid_eth_0 = priv->__0x04_init[0];
++      revid_eth_1 = priv->__0x04_init[1];
++
++      u8 i0, i1, i2, i3;
++      i0 = val >> 24;
++      i1 = val >> 16;
++      i2 = val >> 8;
++      i3 = val;
++
++      u8 c0, c1, c2, c3;
++      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 = (struct bba_private *)dev->priv;
++      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 = (struct bba_private *)bba_dev->priv;
++      exi_dev_select(priv->exi_device);
++
++}
++
++static inline void bba_deselect(void)
++{
++      struct bba_private *priv = (struct bba_private *)bba_dev->priv;
++      exi_dev_deselect(priv->exi_device);
++}
++
++static inline void bba_read(void *data, size_t len)
++{
++      struct bba_private *priv = (struct bba_private *)bba_dev->priv;
++      return exi_dev_read(priv->exi_device, data, len);
++}
++
++static inline void bba_write(void *data, size_t len)
++{
++      struct bba_private *priv = (struct bba_private *)bba_dev->priv;
++      return exi_dev_write(priv->exi_device, data, len);
++}
++
++/*
++ * 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->open = bba_open;
++      dev->stop = bba_close;
++      dev->hard_start_xmit = bba_start_xmit;
++      dev->get_stats = bba_get_stats;
++
++      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 = (struct bba_private *)dev->priv;
++
++              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 02d25c7..1fcf0d3 100644
+--- a/drivers/net/usb/usbnet.c
++++ b/drivers/net/usb/usbnet.c
+@@ -522,7 +522,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/rtc/Kconfig b/drivers/rtc/Kconfig
+index 123092d..fd42af5 100644
+--- a/drivers/rtc/Kconfig
++++ b/drivers/rtc/Kconfig
+@@ -497,6 +497,16 @@ config RTC_DRV_WM8350
+         This driver can also be built as a module. If so, the module
+         will be called "rtc-wm8350".
++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"
+ config RTC_DRV_OMAP
+diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
+index 6e79c91..10a907f 100644
+--- a/drivers/rtc/Makefile
++++ b/drivers/rtc/Makefile
+@@ -36,6 +36,7 @@ obj-$(CONFIG_RTC_DRV_DS1742) += rtc-ds1742.o
+ obj-$(CONFIG_RTC_DRV_DS3234)  += rtc-ds3234.o
+ obj-$(CONFIG_RTC_DRV_EP93XX)  += rtc-ep93xx.o
+ obj-$(CONFIG_RTC_DRV_FM3130)  += rtc-fm3130.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 579d63a..fc383ae 100644
+--- a/drivers/serial/Kconfig
++++ b/drivers/serial/Kconfig
+@@ -1372,4 +1372,17 @@ config SPORT_BAUD_RATE
+       default 19200 if (SERIAL_SPORT_BAUD_RATE_19200)
+       default 9600 if (SERIAL_SPORT_BAUD_RATE_9600)
++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 0c17c8d..a00d73e 100644
+--- a/drivers/serial/Makefile
++++ b/drivers/serial/Makefile
+@@ -73,3 +73,4 @@ obj-$(CONFIG_SERIAL_OF_PLATFORM) += of_serial.o
+ obj-$(CONFIG_SERIAL_KS8695) += serial_ks8695.o
+ obj-$(CONFIG_KGDB_SERIAL_CONSOLE) += kgdboc.o
+ obj-$(CONFIG_SERIAL_QE) += ucc_uart.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 289d81a..cdadf99 100644
+--- a/drivers/usb/Kconfig
++++ b/drivers/usb/Kconfig
+@@ -22,6 +22,7 @@ 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 PCI
+ # many non-PCI SOC chips embed OHCI
+diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
+index 8b7c419..ba41ba1 100644
+--- a/drivers/usb/Makefile
++++ b/drivers/usb/Makefile
+@@ -16,6 +16,7 @@ obj-$(CONFIG_USB_UHCI_HCD)   += host/
+ obj-$(CONFIG_USB_SL811_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_C67X00_HCD)  += c67x00/
+diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
+index f3a75a9..60c8f71 100644
+--- a/drivers/usb/host/Kconfig
++++ b/drivers/usb/host/Kconfig
+@@ -294,6 +294,25 @@ config SUPERH_ON_CHIP_R8A66597
+          This driver enables support for the on-chip R8A66597 in the
+          SH7366 and SH7723 processors.
++config USB_WII_HCD
++      tristate "Nintendo Wii HCD support"
++      depends on USB && WII && !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 23be222..98209ee 100644
+--- a/drivers/usb/host/Makefile
++++ b/drivers/usb/host/Makefile
+@@ -21,4 +21,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/rvl-sthcd.c b/drivers/usb/host/rvl-sthcd.c
+new file mode 100644
+index 0000000..8f68297
+--- /dev/null
++++ b/drivers/usb/host/rvl-sthcd.c
+@@ -0,0 +1,2373 @@
++/*
++ * 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 "../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.4i";
++
++#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;
++
++      /*
++       * 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-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 3f3ce13..3e86331 100644
+--- a/drivers/video/Kconfig
++++ b/drivers/video/Kconfig
+@@ -1704,6 +1704,21 @@ 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 FB_GAMECUBE_GX
++      bool "Nintendo GameCube hardware accelerated graphics support"
++      depends on FB_GAMECUBE && GAMECUBE && BROKEN
++      help
++        Say Y here to support the 3D hardware found in the Nintendo GameCube.
++
+ 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 e39e33e..45c7d0c 100644
+--- a/drivers/video/Makefile
++++ b/drivers/video/Makefile
+@@ -123,6 +123,8 @@ obj-$(CONFIG_FB_OMAP)             += omap/
+ obj-$(CONFIG_XEN_FBDEV_FRONTEND)  += xen-fbfront.o
+ obj-$(CONFIG_FB_CARMINE)          += carminefb.o
+ obj-$(CONFIG_FB_MB862XX)        += mb862xx/
++obj-$(CONFIG_FB_GAMECUBE)       += gcnfb.o
++obj-$(CONFIG_FB_GAMECUBE_GX)    += gcngx.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..d29fa01
+--- /dev/null
++++ b/drivers/video/gcnfb.c
+@@ -0,0 +1,1087 @@
++/*
++ * 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>
++
++#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[] = "1.0i";
++
++#define drv_printk(level, format, arg...) \
++       printk(level DRV_MODULE_NAME ": " format , ## arg)
++
++
++/*
++ * Hardware registers.
++ */
++#define VI_DCR                        0x02
++#define VI_HTR0                       0x04
++#define VI_TFBL                       0x1c
++#define VI_TFBR                       0x20
++#define VI_BFBL                       0x24
++#define VI_BFBR                       0x28
++#define VI_DPV                        0x2c
++
++#define VI_DI0                        0x30
++#define VI_DI1                        0x34
++#define VI_DI2                        0x38
++#define VI_DI3                        0x3C
++#define VI_DI_INT             (1 << 31)
++#define VI_DI_ENB             (1 << 28)
++#define VI_DI_VCT_SHIFT       16
++#define VI_DI_VCT_MASK                0x03FF0000
++#define VI_DI_HCT_SHIFT       0
++#define VI_DI_HCT_MASK                0x000003FF
++
++#define VI_VISEL              0x6e
++#define VI_VISEL_PROGRESSIVE  (1 << 0)
++
++
++/*
++ * 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 fb_info *info;
++};
++
++
++/*
++ * Video mode handling
++ */
++
++struct vi_video_mode {
++      char *name;
++      const u32 *regs;
++      int width;
++      int height;
++      int lines;
++};
++
++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 const u32 vi_Mode640x480NtscProgressiveYUV16[32] = {
++      0x1e0c0005, 0x476901ad, 0x02ea5140, 0x00060030,
++      0x00060030, 0x81d881d8, 0x81d881d8, 0x10000000,
++      0x00000000, 0x00000000, 0x00000000, 0x037702b6,
++      0x90010001, 0x00000000, 0x00000000, 0x00000000,
++      0x00000000, 0x00000000, 0x28280100, 0x1ae771f0,
++      0x0db4a574, 0x00c1188e, 0xc4c0cbe2, 0xfcecdecf,
++      0x13130f08, 0x00080c0f, 0x00ff0000, 0x00010001,
++      0x02800000, 0x000000ff, 0x00ff00ff, 0x00ff00ff,
++};
++
++static const u32 vi_Mode640X576Pal50YUV16[32] = {
++      0x11F50101, 0x4B6A01B0, 0x02F85640, 0x00010023,
++      0x00000024, 0x4D2B4D6D, 0x4D8A4D4C, 0x0066D480,
++      0x00000000, 0x0066D980, 0x00000000, 0x00C901F3,
++      0x913901B1, 0x90010001, 0x00010001, 0x00010001,
++      0x00000000, 0x00000000, 0x28500100, 0x1AE771F0,
++      0x0DB4A574, 0x00C1188E, 0xC4C0CBE2, 0xFCECDECF,
++      0x13130F08, 0x00080C0F, 0x00FF0000, 0x00000000,
++      0x02800000, 0x000000FF, 0x00FF00FF, 0x00FF00FF
++};
++
++static const u32 vi_Mode640X480Pal60YUV16[32] = {
++      0x0F060001, 0x476901AD, 0x02EA5140, 0x00030018,
++      0x00020019, 0x410C410C, 0x40ED40ED, 0x0066D480,
++      0x00000000, 0x0066D980, 0x00000000, 0x00C9010F,
++      0x910701AE, 0x90010001, 0x00010001, 0x00010001,
++      0x00000000, 0x00000000, 0x28500100, 0x1AE771F0,
++      0x0DB4A574, 0x00C1188E, 0xC4C0CBE2, 0xFCECDECF,
++      0x13130F08, 0x00080C0F, 0x00FF0000, 0x00000000,
++      0x02800000, 0x000000FF, 0x00FF00FF, 0x00FF00FF
++};
++
++static struct vi_video_mode vi_video_modes[] = {
++#define VI_VM_NTSC                0
++      [VI_VM_NTSC] = {
++              .name = "NTSC/PAL60 480i",
++              .regs = vi_Mode640X480NtscYUV16,
++              .width = 640,
++              .height = 480,
++              .lines = 525,
++      },
++#define VI_VM_NTSC_PROGRESSIVE    (VI_VM_NTSC+1)
++      [VI_VM_NTSC_PROGRESSIVE] = {
++              .name = "NTSC 480p",
++              .regs = vi_Mode640x480NtscProgressiveYUV16,
++              .width = 640,
++              .height = 480,
++              .lines = 525,
++      },
++#define VI_VM_PAL50               (VI_VM_NTSC_PROGRESSIVE+1)
++      [VI_VM_PAL50] = {
++              .name = "PAL50 576i",
++              .regs = vi_Mode640X576Pal50YUV16,
++              .width = 640,
++              .height = 576,
++              .lines = 625,
++      },
++#define VI_VM_PAL60               (VI_VM_PAL50+1)
++      [VI_VM_PAL60] = {
++              /* this seems to be actually the same as NTSC 480i */
++              .name = "PAL60 480i",
++              .regs = vi_Mode640X480Pal60YUV16,
++              .width = 640,
++              .height = 480,
++              .lines = 525,
++      },
++};
++
++
++static struct fb_fix_screeninfo vifb_fix = {
++      .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 = {
++      .bits_per_pixel = 16,
++      .activate = FB_ACTIVATE_NOW,
++      .height = -1,
++      .width = -1,
++      .right_margin = 32,
++      .upper_margin = 16,
++      .lower_margin = 4,
++      .vsync_len = 4,
++      .vmode = FB_VMODE_INTERLACED,
++};
++
++/*
++ * setup parameters
++ */
++static struct vi_video_mode *vi_current_video_mode;
++static int ypan = 1;          /* 0..nothing, 1..ypan */
++
++/* FIXME: is this really needed? */
++static u32 pseudo_palette[17];
++
++
++/* some glue to the gx side */
++static inline void gcngx_dispatch_vtrace(struct vi_ctl *ctl)
++{
++#ifdef CONFIG_FB_GAMECUBE_GX
++      gcngx_vtrace(ctl);
++#endif
++}
++
++
++/*
++ *
++ * 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 hardware support.
++ */
++
++/*
++ * Get video mode reported by hardware.
++ * 0=NTSC, 1=PAL, 2=MPAL, 3=debug
++ */
++static inline int vi_get_mode(struct vi_ctl *ctl)
++{
++      return (in_be16(ctl->io_base + VI_DCR) >> 8) & 3;
++}
++
++static inline int vi_is_mode_ntsc(struct vi_ctl *ctl)
++{
++      return vi_get_mode(ctl) == 0;
++}
++
++static inline int vi_is_mode_progressive(__u32 vmode)
++{
++      return (vmode & FB_VMODE_MASK) == FB_VMODE_NONINTERLACED;
++}
++
++static inline int vi_can_do_progressive(struct vi_ctl *ctl)
++{
++      return in_be16(ctl->io_base + VI_VISEL) & VI_VISEL_PROGRESSIVE;
++}
++
++static void vi_guess_mode(struct vi_ctl *ctl)
++{
++      void __iomem *io_base = ctl->io_base;
++      u16 mode;
++
++      if (vi_current_video_mode == NULL) {
++              /* auto detection */
++              if (in_be32(io_base + VI_HTR0) == 0x4B6A01B0) {
++                      /* PAL50 */
++                      vi_current_video_mode = vi_video_modes + VI_VM_PAL50;
++              } else {
++                      /* NTSC/PAL60 */
++                      mode = vi_get_mode(ctl);
++                      switch (mode) {
++                      case 0: /* NTSC */
++                              /* check if we can support progressive */
++                              vi_current_video_mode =
++                                  vi_video_modes +
++                                  (vi_can_do_progressive(ctl) ?
++                                   VI_VM_NTSC_PROGRESSIVE : VI_VM_NTSC);
++                              break;
++                              /* XXX this code is never reached */
++                      case 1: /* PAL60 */
++                              vi_current_video_mode =
++                                  vi_video_modes + VI_VM_PAL60;
++                              break;
++                      default:        /* MPAL or DEBUG, we don't support */
++                              break;
++                      }
++              }
++      }
++
++      /* if we get here something wrong happened */
++      if (vi_current_video_mode == NULL) {
++              drv_printk(KERN_DEBUG, "failed to guess video mode,"
++                         "using NTSC\n");
++              vi_current_video_mode = vi_video_modes + VI_VM_NTSC;
++      }
++}
++
++/*
++ * 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;
++
++      /* set top field */
++      out_be32(io_base + VI_TFBL, 0x10000000 | (addr >> 5));
++
++      /* set bottom field */
++      if (!vi_is_mode_progressive(info->var.vmode))
++              addr += info->fix.line_length;
++      out_be32(io_base + VI_BFBL, 0x10000000 | (addr >> 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;
++      u16 vtrap, htrap;
++
++      if (enable) {
++              /*
++               * The vertical retrace happens while the beam moves from
++               * the last drawn dot in the last line to the first dot in
++               * the first line.
++               */
++
++              /* XXX should we incorporate this in the video mode struct ? */
++              vtrap = vi_current_video_mode->lines;
++              htrap = vi_is_mode_ntsc(ctl) ? 430 : 433;
++
++              /* non-progressive needs interlacing */
++              if (!(vi_is_mode_progressive(ctl->info->var.vmode)
++                  && vi_can_do_progressive(ctl))) {
++                      vtrap /= 2;
++              }
++
++              /* first dot, first line */
++              out_be32(io_base + VI_DI0,
++                      VI_DI_INT | VI_DI_ENB |
++                     (1 << VI_DI_VCT_SHIFT) | (1 << VI_DI_HCT_SHIFT));
++              /* last dot, last line */
++              out_be32(io_base + VI_DI1,
++                      VI_DI_INT | VI_DI_ENB |
++                     (vtrap << VI_DI_VCT_SHIFT) | (htrap << VI_DI_HCT_SHIFT));
++      } 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 (val & VI_DI_INT) {
++              ctl->in_vtrace = 0;
++              gcngx_dispatch_vtrace(ctl); /* backwards compatibility */
++
++              out_be32(io_base + VI_DI0, val & ~VI_DI_INT);
++              return IRQ_HANDLED;
++      }
++      val = in_be32(io_base + VI_DI1);
++      if (val & VI_DI_INT) {
++              ctl->in_vtrace = 1;
++              vi_dispatch_vtrace(ctl);
++              gcngx_dispatch_vtrace(ctl); /* backwards compatibility */
++
++              out_be32(io_base + VI_DI1, val & ~VI_DI_INT);
++              return IRQ_HANDLED;
++      }
++
++      /* currently unused, just in case */
++      val = in_be32(io_base + VI_DI2);
++      if (val & VI_DI_INT) {
++              out_be32(io_base + VI_DI2, val & ~VI_DI_INT);
++              return IRQ_HANDLED;
++      }
++      val = in_be32(io_base + VI_DI3);
++      if (val & VI_DI_INT) {
++              out_be32(io_base + VI_DI3, val & ~VI_DI_INT);
++              return IRQ_HANDLED;
++      }
++
++      return IRQ_NONE;
++}
++
++/*
++ * 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);
++}
++
++/*
++ * Restore the video hardware to sane defaults.
++ */
++int vifb_restorefb(struct fb_info *info)
++{
++      struct vi_ctl *ctl = info->par;
++      void __iomem *io_base = ctl->io_base;
++      int i;
++      unsigned long flags;
++
++      /* 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);
++
++      /* initialize video registers */
++      for (i = 0; i < 7; i++) {
++              out_be32(io_base + i * sizeof(__u32),
++                      vi_current_video_mode->regs[i]);
++      }
++      out_be32(io_base + VI_TFBR,
++              vi_current_video_mode->regs[VI_TFBR / sizeof(__u32)]);
++      out_be32(io_base + VI_BFBR,
++              vi_current_video_mode->regs[VI_BFBR / sizeof(__u32)]);
++      out_be32(io_base + VI_DPV,
++              vi_current_video_mode->regs[VI_DPV / sizeof(__u32)]);
++      for (i = 16; i < 32; i++) {
++              out_be32(io_base + i * sizeof(__u32),
++                      vi_current_video_mode->regs[i]);
++      }
++
++      /* enable the video retrace handling */
++      vi_enable_interrupts(ctl, 1);
++
++      return 0;
++}
++EXPORT_SYMBOL(vifb_restorefb);
++
++/*
++ * FIXME: do we really need this?
++ */
++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;
++      unsigned long flags;
++      int offset;
++
++      offset = (var->yoffset * info->fix.line_length) +
++          var->xoffset * (var->bits_per_pixel / 8);
++      vi_set_framebuffer(ctl, info->fix.smem_start + offset);
++
++      spin_lock_irqsave(&ctl->lock, flags);
++      ctl->visible_page = (offset) ? 1 : 0;
++      spin_unlock_irqrestore(&ctl->lock, flags);
++
++      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;
++      }
++#ifdef CONFIG_FB_GAMECUBE_GX
++      /* see if the GX module will handle it */
++      return gcngx_ioctl(info, cmd, arg);
++#else
++      return -EINVAL;
++#endif
++}
++
++/*
++ * Set the video mode according to info->var.
++ */
++static int vifb_set_par(struct fb_info *info)
++{
++      /* just load sane default here */
++      vifb_restorefb(info);
++      return 0;
++}
++
++/*
++ * 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;
++
++      /* check bpp */
++      if (var->bits_per_pixel != 16 ||        /* check bpp */
++          var->xres_virtual != vi_current_video_mode->width ||
++          var->xres != vi_current_video_mode->width ||
++          /* XXX isobel, do not break old sdl */
++          var->yres_virtual > 2 * vi_current_video_mode->height ||
++          var->yres > vi_current_video_mode->height ||
++          (vi_is_mode_progressive(var->vmode) &&
++           !vi_can_do_progressive(ctl))) {    /* trying to set progressive? */
++              return -EINVAL;
++      }
++      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;
++}
++
++
++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 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 err = -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 thing needed */
++      ctl->io_base = ioremap(mem->start, mem->end - mem->start + 1);
++      ctl->irq = irq;
++
++      vi_guess_mode(ctl);
++
++      info->var.xres = vi_current_video_mode->width;
++      info->var.yres = vi_current_video_mode->height;
++
++      /* enable non-interlaced if it supports progressive */
++      if (vi_can_do_progressive(ctl))
++              info->var.vmode = FB_VMODE_NONINTERLACED;
++
++      /* horizontal line in bytes */
++      info->fix.line_length = info->var.xres * (info->var.bits_per_pixel / 8);
++
++      /*
++       * 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);
++              err = -EIO;
++              goto err_ioremap;
++      }
++
++      spin_lock_init(&ctl->lock);
++      init_waitqueue_head(&ctl->vtrace_waitq);
++
++      ctl->visible_page = 0;
++      ctl->page_address[0] = info->fix.smem_start;
++      ctl->page_address[1] =
++          info->fix.smem_start + info->var.yres * info->fix.line_length;
++
++      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);
++      drv_printk(KERN_INFO,
++                 "mode is %dx%dx%d, linelength=%d, pages=%d\n",
++                 info->var.xres, info->var.yres,
++                 info->var.bits_per_pixel,
++                 info->fix.line_length,
++                 info->fix.smem_len / (info->fix.line_length*info->var.yres));
++
++      info->var.xres_virtual = info->var.xres;
++      info->var.yres_virtual = info->fix.smem_len / info->fix.line_length;
++
++      if (ypan && info->var.yres_virtual > info->var.yres) {
++              drv_printk(KERN_INFO, "scrolling: pan,  yres_virtual=%d\n",
++                         info->var.yres_virtual);
++      } else {
++              drv_printk(KERN_INFO, "scrolling: redraw, yres_virtual=%d\n",
++                         info->var.yres_virtual);
++              info->var.yres_virtual = info->var.yres;
++              ypan = 0;
++      }
++
++      info->fix.ypanstep = ypan ? 1 : 0;
++      info->fix.ywrapstep = 0;
++      if (!ypan)
++              info->fbops->fb_pan_display = NULL;
++
++      /* use some dummy values for timing to make fbset happy */
++      info->var.pixclock = 10000000 / info->var.xres * 1000 / info->var.yres;
++      info->var.left_margin = (info->var.xres / 8) & 0xf8;
++      info->var.hsync_len = (info->var.xres / 8) & 0xf8;
++
++      /* we support ony 16 bits per pixel */
++      info->var.red.offset = 11;
++      info->var.red.length = 5;
++      info->var.green.offset = 5;
++      info->var.green.length = 6;
++      info->var.blue.offset = 0;
++      info->var.blue.length = 5;
++      info->var.transp.offset = 0;
++      info->var.transp.length = 0;
++      video_cmap_len = 16;
++
++      info->pseudo_palette = pseudo_palette;
++      if (fb_alloc_cmap(&info->cmap, video_cmap_len, 0)) {
++              err = -ENOMEM;
++              goto err_alloc_cmap;
++      }
++
++      info->flags = FBINFO_FLAG_DEFAULT | (ypan) ? FBINFO_HWACCEL_YPAN : 0;
++
++      dev_set_drvdata(dev, info);
++
++      vi_enable_interrupts(ctl, 0);
++
++      err = request_irq(ctl->irq, vi_irq_handler, 0, DRV_MODULE_NAME, dev);
++      if (err) {
++              drv_printk(KERN_ERR, "unable to register IRQ %u\n", ctl->irq);
++              goto err_request_irq;
++      }
++
++      /* now register us */
++      if (register_framebuffer(info) < 0) {
++              err = -EINVAL;
++              goto err_register_framebuffer;
++      }
++
++      /* setup the framebuffer address */
++      vifb_restorefb(info);
++
++#ifdef CONFIG_FB_GAMECUBE_GX
++      err = gcngx_init(info);
++      if (err)
++              goto err_gcngx_init;
++#endif
++
++      drv_printk(KERN_INFO, "fb%d: %s frame buffer device\n",
++                 info->node, info->fix.id);
++
++      return 0;
++
++#ifdef CONFIG_FB_GAMECUBE_GX
++err_gcngx_init:
++      unregister_framebuffer(info);
++#endif
++err_register_framebuffer:
++      free_irq(ctl->irq, 0);
++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 err;
++}
++
++static int vifb_do_remove(struct device *dev)
++{
++      struct fb_info *info = dev_get_drvdata(dev);
++      struct vi_ctl *ctl = info->par;
++
++      if (!info)
++              return -ENODEV;
++
++#ifdef CONFIG_FB_GAMECUBE_GX
++      gcngx_exit(info);
++#endif
++      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);
++      framebuffer_release(info);
++      return 0;
++}
++
++#ifndef MODULE
++
++static int __devinit vifb_setup(char *options)
++{
++      char *this_opt;
++
++      if (!options || !*options)
++              return 0;
++
++      drv_printk(KERN_DEBUG, "options: %s\n", options);
++
++      while ((this_opt = strsep(&options, ",")) != NULL) {
++              if (!*this_opt)
++                      continue;
++
++              if (!strcmp(this_opt, "redraw"))
++                      ypan = 0;
++              else if (!strcmp(this_opt, "ypan"))
++                      ypan = 1;
++              else if (!strcmp(this_opt, "ywrap"))
++                      ypan = 2;
++              else if (!strncmp(this_opt, "tv=", 3)) {
++                      if (!strncmp(this_opt + 3, "PAL", 3))
++                              vi_current_video_mode =
++                                  vi_video_modes + VI_VM_PAL50;
++                      else if (!strncmp(this_opt + 3, "NTSC", 4))
++                              vi_current_video_mode =
++                                  vi_video_modes + VI_VM_NTSC;
++              }
++      }
++      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 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,
++};
++
++/*
++ * Module interface hooks
++ *
++ */
++
++static int __init vifb_init_module(void)
++{
++#ifndef MODULE
++      char *option = NULL;
++
++      if (fb_get_options(DRV_MODULE_NAME, &option)) {
++              /* for backwards compatibility */
++              if (fb_get_options("gcnfb", &option))
++                      return -ENODEV;
++      }
++      vifb_setup(option);
++#endif
++
++      drv_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION,
++                 vifb_driver_version);
++
++      return of_register_platform_driver(&vifb_of_driver);
++}
++
++static void __exit vifb_exit_module(void)
++{
++      of_unregister_platform_driver(&vifb_of_driver);
++}
++
++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/gcngx.c b/drivers/video/gcngx.c
+new file mode 100644
+index 0000000..3246617
+--- /dev/null
++++ b/drivers/video/gcngx.c
+@@ -0,0 +1,691 @@
++/*
++ * drivers/video/gcngx.c
++ *
++ * Nintendo GameCube GX driver extension
++ * Copyright (C) 2004-2007 The GameCube Linux Team
++ * Copyright (C) 2004,2005 Todd Jeffreys <todd@voidpointer.org>
++ * Copyright (C) 2007 Albert Herranz
++ *
++ * Parts borrowed heavily from libogc.  This driver would not have
++ * been possible with this library.  Thanks!
++ *
++ * 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.
++ *
++ */
++
++#ifdef CONFIG_FB_GAMECUBE_GX
++
++#include <linux/module.h>
++#include <linux/kernel.h>
++#include <linux/errno.h>
++#include <linux/mm.h>
++#include <linux/fb.h>
++#include <linux/console.h>
++#include <linux/vt_kern.h>
++#include <linux/interrupt.h>
++#include <linux/wait.h>
++#include <asm/pgtable.h>
++#include <asm/atomic.h>
++#include <asm/cacheflush.h>
++
++#error This driver is broken since ARCH=powerpc changes. A rewrite is needed. 
++
++#ifdef CONFIG_PPC_MERGE
++#include <platforms/embedded6xx/gamecube.h>
++#else
++#include <platforms/gamecube.h>
++#endif
++
++#include "gcngx.h"
++
++/* Function definitions */
++static inline void  __GX_AckFifoInt(int isOver);
++static inline void  __GX_WriteFifoIntEnable(int over,int under);
++static void gcngx_munmap(struct vm_area_struct *vma);
++static void gcngx_free_munmap(struct vm_area_struct *vma);
++static void gcngx_destroy_fifo(void);
++static void gcngx_init_fifo(void);
++
++extern void vi_set_framebuffer(struct vi_ctl *ctl, u32 addr);
++extern int gcnfb_restorefb(struct fb_info *info);
++
++extern struct fb_ops gcnfb_ops;
++
++/* Defines */
++#define mtwpar(v) mtspr(921,v)
++#define mfwpar(v) mfspr(921)
++
++#define GX_ENABLE 1
++#define GX_DISABLE 0
++#define GX_TRUE 1
++#define GX_FALSE 0
++#define _SHIFTL(v, s, w)      \
++    ((u32) (((u32)(v) & ((0x01 << (w)) - 1)) << (s)))
++#define _SHIFTR(v, s, w)      \
++    ((u32)(((u32)(v) >> (s)) & ((0x01 << (w)) - 1)))
++
++#define IRQ_VIDEO 8
++#define IRQ_PE_TOKEN 9
++#define IRQ_PE_FINISH 10
++#define IRQ_CP_FIFO   11
++
++#define VIDEO_MMAP_BASE    0x0C000000
++#define VIDEO_MMAP_LENGTH  0x9000
++
++#define KMALLOC_BASE 0x0D000000
++
++#define VIDEO_PE_INTERRUPT            ((void __iomem *)0xcc00100a)
++#define VIDEO_PE_TOKEN                        ((void __iomem *)0xcc00100e)
++#define VIDEO_PE_INTERRUPT_TOKEN_ENABLE     (1 << 0)
++#define VIDEO_PE_INTERRUPT_FINISH_ENABLE    (1 << 1)
++#define VIDEO_PE_INTERRUPT_TOKEN_INTERRUPT  (1 << 2)
++#define VIDEO_PE_INTERRUPT_FINISH_INTERRUPT (1 << 3)
++
++#define       gcngx_disable_pe_interrupts() out_be16(VIDEO_PE_INTERRUPT,in_be16(VIDEO_PE_INTERRUPT) & ~(VIDEO_PE_INTERRUPT_TOKEN_ENABLE | VIDEO_PE_INTERRUPT_FINISH_ENABLE))
++#define gcngx_enable_pe_interrupts() { out_be16(VIDEO_PE_INTERRUPT,in_be16(VIDEO_PE_INTERRUPT) | (VIDEO_PE_INTERRUPT_TOKEN_ENABLE | VIDEO_PE_INTERRUPT_FINISH_ENABLE | VIDEO_PE_INTERRUPT_TOKEN_INTERRUPT | VIDEO_PE_INTERRUPT_FINISH_INTERRUPT)); out_be16(VIDEO_PE_TOKEN, 0); }
++
++#define VIDEO_CP_SR           ((volatile u16 __iomem *)0xcc000000)
++#define VIDEO_CP_SR_OVERFLOW  (1 << 0)
++#define VIDEO_CP_SR_UNDERFLOW (1 << 1)
++
++#define VIDEO_CP_CR           ((volatile u16 __iomem *)0xcc000002)
++#define VIDEO_CP_CR_GP_FIFO_READ_ENABLE  (1 << 0)
++#define VIDEO_CP_CR_CP_IRQ_ENABLE        (1 << 1)
++#define VIDEO_CP_CR_OVERFLOW_IRQ_ENABLE  (1 << 2)
++#define VIDEO_CP_CR_UNDERFLOW_IRQ_ENABLE (1 << 3)
++#define VIDEO_CP_CR_GP_LINK_ENABLE       (1 << 4)
++#define VIDEO_CP_CR_MASK                 (0x1F)
++
++#define SIG_PE_FINISH       (SIGRTMIN+14)
++#define SIG_PE_TOKEN        (SIGRTMIN+15)
++#define SIG_VTRACE_COMPLETE (SIGRTMIN+16)
++
++#define FIFO_PUTU8(x)  (*((volatile u8*) WGPIPE) = (x))
++#define FIFO_PUTU32(x) (*((volatile u32*)WGPIPE) = (x))
++
++#define LOAD_BP_REG(x) do { FIFO_PUTU8(0x61); FIFO_PUTU32(x); } while (0)
++
++/* Static data */
++static struct task_struct *mmap_task;
++static int overflow;
++static u32 xfb[2];
++static int currentFB = 0;
++static int flipRequest = 0;
++static u8 *mmap_fifo_base;
++static u8 *phys_fifo_base;
++static const u32 fifo_len       = GCN_GX_FIFO_SIZE;
++static struct vm_operations_struct gcngx_vm_ops =
++{
++      .close = gcngx_munmap,
++};
++static struct vm_operations_struct gcngx_vm_free_ops = 
++{
++      .close = gcngx_free_munmap,
++};
++
++static volatile u32* const _piReg = (volatile u32*)0xCC003000;
++static volatile u16* const _cpReg = (volatile u16*)0xCC000000;
++static volatile u16* const _peReg = (volatile u16*)0xCC001000;
++static volatile u16* const _memReg = (volatile u16*)0xCC004000;
++static volatile u32* const WGPIPE  = (volatile u32*)0xCC008000;
++
++static irqreturn_t gcfb_fifo_irq_handler(int irq,void *dev_id)
++{
++      /* now handle the int */
++      u16 val = in_be16(VIDEO_CP_SR);
++      
++      /* ENABLE_RUMBLE(); */
++      
++      if (val & VIDEO_CP_SR_OVERFLOW)
++      {
++              /* fifo overflow, must halt the current application */
++              if (mmap_task)
++              {
++                      printk(KERN_INFO "Man you are writing too fast!  Slow down!  I will make you!\n");
++                      set_task_state(mmap_task,TASK_UNINTERRUPTIBLE);
++                      overflow = 1;
++              }
++              __GX_AckFifoInt(1);
++              __GX_WriteFifoIntEnable(GX_DISABLE,GX_ENABLE);
++              return IRQ_HANDLED;
++      }
++      else if (val & VIDEO_CP_SR_UNDERFLOW)
++      {
++              /* underflow, resume the current application */
++              if (mmap_task && overflow)
++              {
++                      printk(KERN_INFO "OK dude, the GX has crunched the data, you can resume now\n");
++                      set_task_state(mmap_task,TASK_RUNNING);
++                      overflow = 0;
++              }
++              __GX_AckFifoInt(0);
++              __GX_WriteFifoIntEnable(GX_ENABLE,GX_DISABLE);
++              return IRQ_HANDLED;
++      }
++      return IRQ_NONE;
++}
++
++void gcngx_vtrace(struct vi_ctl *ctl)
++{
++      struct siginfo sig;
++      /* ok flip the image if we have a flip request.
++         send signal on completion */
++      if (mmap_task && flipRequest)
++      {
++              /* do the flip! */
++              flipRequest = 0;
++              /* setup the signal info and flip buffer pointers */
++              currentFB = currentFB ? 0 : 1;
++              sig.si_errno  = xfb[currentFB];
++              /* inform the hardware */
++              vi_set_framebuffer(ctl, xfb[currentFB]);
++              /* notify the process */
++              sig.si_signo = SIG_VTRACE_COMPLETE;
++              sig.si_code  = 0;
++              send_sig_info(SIG_VTRACE_COMPLETE,&sig,mmap_task);
++      }
++}
++
++static irqreturn_t gcfb_pe_finish_irq_handler(int irq,void *dev_id)
++{
++      u16 val;
++      struct siginfo sig;
++      /* ack the interrupt */
++      val = in_be16(VIDEO_PE_INTERRUPT) | VIDEO_PE_INTERRUPT_FINISH_INTERRUPT;
++      out_be16(VIDEO_PE_INTERRUPT, val);
++      
++      /* send SIG_PE_FINISH to the process */
++      if (mmap_task)
++      {
++              sig.si_signo = SIG_PE_FINISH;
++              sig.si_errno = 0;
++              sig.si_code  = 0;
++              send_sig_info(SIG_PE_FINISH,&sig,mmap_task);
++      }
++      return IRQ_HANDLED;
++}
++
++static irqreturn_t gcfb_pe_token_irq_handler(int irq,void *dev_id)
++{
++      u16 val;
++      struct siginfo sig;
++      /* ack the interrupt */
++      val = in_be16(VIDEO_PE_INTERRUPT) | VIDEO_PE_INTERRUPT_TOKEN_INTERRUPT;
++      out_be16(VIDEO_PE_INTERRUPT, val);
++      /* send SIG_PE_TOKEN to the process */
++      if (mmap_task)
++      {
++              sig.si_signo = SIG_PE_TOKEN;
++              sig.si_errno = 0;
++              sig.si_code  = _peReg[7];
++              send_sig_info(SIG_PE_TOKEN,&sig,mmap_task);
++      }
++      return IRQ_HANDLED;
++}
++
++/**
++ *
++ */
++static u32 gcngx_uvirt_to_phys(u32 virt)
++{
++      pgd_t *dir;
++      pmd_t *pmd;
++      pte_t *pte;
++      u32 ret = 0;
++      struct mm_struct *mm = get_task_mm(current);
++      u32 offset = virt & (PAGE_SIZE - 1);
++      virt &= PAGE_MASK;
++
++      if (!mm) {
++              return 0;
++      }
++      down_read(&mm->mmap_sem);
++      /* convert to kernel address */
++      if ((dir = pgd_offset(mm, virt)) && pgd_present(*dir)) {
++              if ((pmd = pmd_offset(dir, virt)) && pmd_present(*pmd)) {
++                      pte = pte_offset_kernel(pmd, virt);
++                      if (pte && pte_present(*pte)) {
++                              ret =
++                                  (u32) page_address(pte_page(*pte)) + offset;
++                              /* ok now we have the kern addr, map to phys */
++                              ret = virt_to_phys((void *)ret);
++                      }
++              }
++      }
++
++      up_read(&mm->mmap_sem);
++      mmput(mm);
++      return ret;
++}
++
++int gcngx_ioctl(struct fb_info *info,
++              unsigned int cmd, unsigned long arg)
++{
++      u32 phys;
++      void __user *argp;
++
++      if (cmd == FBIOFLIP)
++      {
++              flipRequest = 1;
++              return 0;
++      } else if (cmd == FBIOVIRTTOPHYS) {
++              argp = (void __user *)arg;
++              if (copy_from_user(&phys, argp, sizeof(void *)))
++                      return -EFAULT;
++
++              phys = gcngx_uvirt_to_phys(phys);
++
++              if (copy_to_user(argp, &phys, sizeof(void *)))
++                      return -EFAULT;
++              return 0;
++      }
++      return -EINVAL;
++}
++
++static void *mymalloc(unsigned int len)
++{
++      struct page *page;
++      void *p = kmalloc(len,GFP_KERNEL);
++      if (p && len)
++      {
++              /* reserve all the memory so remap_page_range works */
++              for (page=virt_to_page(p);page<virt_to_page(p+len);++page) {
++                      SetPageReserved(page);
++                      SetPageLocked(page);
++              }
++      }
++      return p;       
++}
++
++static void myfree(void *p)
++{
++      struct page *page;
++      u32 len;
++      if (p)
++      {
++              len = ksize(p);
++              for (page=virt_to_page(p);page<virt_to_page(p+len);++page) {
++                      ClearPageReserved(page);
++                      ClearPageLocked(page);
++              }
++              kfree(p);
++      }
++}
++
++static void gcngx_free_munmap(struct vm_area_struct *vma)
++{
++      if (vma->vm_private_data)
++      {
++              myfree(vma->vm_private_data);
++              vma->vm_private_data = NULL;
++      }
++}
++
++static void gcngx_munmap(struct vm_area_struct *vma)
++{
++      struct fb_info *info = (struct fb_info*)vma->vm_private_data;
++      struct vc_data *vc;
++      
++      gcngx_destroy_fifo();
++      
++      /* nobody has up mapped anymore */
++      mmap_task = NULL;
++      overflow = 0;
++      
++      /* restore the framebuffer */
++      gcnfb_restorefb(info);
++#ifdef CONFIG_FRAMEBUFFER_CONSOLE
++      acquire_console_sem();
++      vc = vc_cons[fg_console].d;
++      update_screen(vc);
++      unblank_screen();
++      release_console_sem();
++#endif
++}
++
++int gcngx_mmap(struct fb_info *info, struct vm_area_struct *vma)
++{
++      struct file *file = vma->vm_file;
++      int ret;
++//    static spinlock_t lock = SPIN_LOCK_UNLOCKED;
++      u32 phys;
++      u32 len;
++
++
++      len = vma->vm_end - vma->vm_start;
++      
++      if (vma->vm_pgoff == (VIDEO_MMAP_BASE >> PAGE_SHIFT) &&
++          len == VIDEO_MMAP_LENGTH)
++      {
++              /* our special case, map the memory info */
++              vma->vm_flags |= VM_IO;
++              vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
++              if (io_remap_pfn_range(vma,vma->vm_start,
++                                      VIDEO_MMAP_BASE >> PAGE_SHIFT,
++                                      len,
++                                      vma->vm_page_prot))
++              {
++                      return -EINVAL;
++              }
++              vma->vm_ops   = &gcngx_vm_ops;
++              vma->vm_private_data = info;
++              /* store task for when fifo is overflown */
++              mmap_task = current;
++              overflow = 0;
++              /* init the fifo before we return */
++              gcngx_init_fifo();
++              return 0;
++      }
++      else if (vma->vm_pgoff >= (KMALLOC_BASE >> PAGE_SHIFT))
++      {
++              /* ok kmalloc the memory now */
++              vma->vm_private_data = mymalloc(len);
++              if (!vma->vm_private_data)
++              {
++                      return -ENOMEM;
++              }
++              /* now setup the mapping */
++              phys = virt_to_phys(vma->vm_private_data);
++              vma->vm_flags |= (VM_RESERVED | VM_LOCKED);
++              if (remap_pfn_range(vma,vma->vm_start,
++                                   phys >> PAGE_SHIFT,len,vma->vm_page_prot))
++              {
++                      kfree(vma->vm_private_data);
++                      return -EINVAL;
++              }
++              vma->vm_ops = &gcngx_vm_free_ops;
++              /* now write the physical mapping in the first u32 */
++              *((u32*)vma->vm_private_data) = phys;
++              /* return successful */
++              return 0;
++      }
++
++      /* call the frame buffer mmap method */
++      if (file->f_op->mmap)
++      {
++              /*
++               * FIXME
++               * This seems to be broken.
++               * fb_mmap might sleep and we're getting a lock here.
++               */
++//            spin_lock(&lock);
++              /* reset our mmap since the fb driver will call it */
++              gcnfb_ops.fb_mmap = NULL;
++              ret = file->f_op->mmap(file,vma);
++              /* reset our mmap */
++              gcnfb_ops.fb_mmap = gcngx_mmap;
++//            spin_unlock(&lock);
++              return ret;
++      }
++      return -EINVAL;
++}
++
++static inline void  __GX_AckFifoInt(int isOver)
++{
++      if (isOver)
++              _cpReg[2] |= (1 << 0);
++      else
++              _cpReg[2] |= (1 << 1);
++}
++
++static inline void  __GX_Flush(void)
++{
++      /* write 8 32 bit values to the WGPIPE */
++      *WGPIPE = 0;
++      *WGPIPE = 0;
++      *WGPIPE = 0;
++      *WGPIPE = 0;
++      *WGPIPE = 0;
++      *WGPIPE = 0;
++      *WGPIPE = 0;
++      *WGPIPE = 0;
++}
++
++static inline void  __GX_WriteFifoIntEnable(int over, int under)
++{
++      u16 val = _cpReg[1] & ~(VIDEO_CP_CR_GP_FIFO_READ_ENABLE | 
++                              VIDEO_CP_CR_CP_IRQ_ENABLE |
++                              VIDEO_CP_CR_GP_LINK_ENABLE);
++      
++      if (over)  val |= VIDEO_CP_CR_OVERFLOW_IRQ_ENABLE;
++      if (under) val |= VIDEO_CP_CR_UNDERFLOW_IRQ_ENABLE;
++      
++      _cpReg[1] = val;
++      /* ack it just for fun */
++      _cpReg[2] = 0x3;
++}
++
++static inline void  __GX_FifoReadEnable(int enable)
++{
++      if (enable) 
++              _cpReg[1] |= VIDEO_CP_CR_GP_FIFO_READ_ENABLE;
++      else
++              _cpReg[1] &= ~VIDEO_CP_CR_GP_FIFO_READ_ENABLE;
++}
++
++static inline void __GX_FifoLink(u8 enable)
++{
++      if (enable)
++              _cpReg[1] |= VIDEO_CP_CR_GP_LINK_ENABLE;
++      else
++              _cpReg[1] &= ~VIDEO_CP_CR_GP_LINK_ENABLE;
++}
++
++static void __GX_EnableWriteGatherPipe(u8 enable)
++{
++      u32 flags;
++      if (enable)
++      {
++              mtwpar(0x0C008000);
++      }
++
++      asm ("isync");
++      asm ("sync");
++
++      flags = mfspr(920);
++      if (enable)
++      {
++              flags |= 0x40000000;
++      }
++      else
++      {
++              flags &= ~0x40000000;
++      }
++      
++      mtspr(920,flags);
++      asm ("isync");
++      asm ("sync");
++}
++
++static inline void __GX_DrawDone(void)
++{
++      LOAD_BP_REG(0x45000002);
++      __GX_Flush();
++}
++
++static void gcngx_destroy_fifo()
++{
++      gcngx_disable_pe_interrupts();
++      _peReg[7] = 0;
++
++      __GX_DrawDone();
++      /* wait for the buffer to empty? */
++      __GX_WriteFifoIntEnable(GX_DISABLE,GX_DISABLE);
++      __GX_FifoReadEnable(0);
++      __GX_FifoLink(GX_FALSE);
++
++      __GX_EnableWriteGatherPipe(0);
++}
++
++struct fifo_info
++{
++      u8 *base;
++      u8 *end;
++      u32 length;
++      u8 *lo_water_mark;
++      u8 *hi_water_mark;
++      u8 *write_ptr;
++      u8 *read_ptr;
++};
++
++static void gcngx_init_fifo(void)
++{
++      struct fifo_info fi;
++      int i;
++      
++      fi.base = phys_fifo_base;
++      fi.end  = phys_fifo_base + fifo_len - 4;
++      fi.length = fifo_len;
++      fi.lo_water_mark = phys_fifo_base + ((fifo_len / 2) & ~31);
++      fi.hi_water_mark = phys_fifo_base + fifo_len - (16*1024);
++      fi.write_ptr = phys_fifo_base;
++      fi.read_ptr  = phys_fifo_base;
++      
++      /* reset currentFB pointer */
++      currentFB = 0;
++      flipRequest = 0;
++
++      /* printk(KERN_INFO "Initializing Flipper FIFO at %p of length %u\n",
++         fi.base,fifo_len); */
++      
++      __GX_FifoLink(GX_FALSE);
++      __GX_WriteFifoIntEnable(GX_DISABLE,GX_DISABLE);
++      __GX_FifoReadEnable(0);
++
++      /* clear the fifo */
++      for (i=0;i<fifo_len/4;++i)
++      {
++              ((u32*)mmap_fifo_base)[i] = 0;
++      }
++      /* flush it */
++      flush_dcache_range((u32)mmap_fifo_base,
++                         (u32)(mmap_fifo_base+fifo_len));
++      
++      _peReg[7] = 0;
++      
++      /* fifo base start */
++      _piReg[3] = (u32)fi.base;
++      /* fifo base end */
++      _piReg[4] = (u32)fi.end;
++      /* fifo write pointer */
++      _piReg[5] = (u32)fi.write_ptr;
++
++      /* init and flush the write gather pipe */
++      __GX_EnableWriteGatherPipe(1);
++      __GX_Flush();
++
++      /* wait for all data to be flushed */
++      while (mfwpar() & 1);
++      _piReg[3] = (u32)fi.base;
++      _piReg[4] = (u32)fi.end;
++      _piReg[5] = (u32)fi.write_ptr;
++      while (mfwpar() & 1);
++
++      /* setup fifo base */
++      _cpReg[16] = _SHIFTL(fi.base,0,16);
++      _cpReg[17] = _SHIFTR(fi.base,16,16);
++      
++      /* setup fifo end */
++      _cpReg[18] = _SHIFTL(fi.end,0,16);
++      _cpReg[19] = _SHIFTR(fi.end,16,16);
++      
++      /* setup hiwater mark */
++      _cpReg[20] = _SHIFTL(fi.hi_water_mark,0,16);
++      _cpReg[21] = _SHIFTR(fi.hi_water_mark,16,16);
++      
++      /* setup lowater mark */
++      _cpReg[22] = _SHIFTL(fi.lo_water_mark,0,16);
++      _cpReg[23] = _SHIFTR(fi.lo_water_mark,16,16);
++      
++      /* setup rd<->wd dist */
++      /*_cpReg[24] = _SHIFTL((pad)[7],0,16);
++        _cpReg[25] = _SHIFTR((pad)[7],16,16);*/
++      _cpReg[24] = 0;
++      _cpReg[25] = 0;
++      
++      /* setup wt ptr */
++      _cpReg[26] = _SHIFTL(fi.write_ptr,0,16);
++      _cpReg[27] = _SHIFTR(fi.write_ptr,16,16);
++      
++      /* setup rd ptr */
++      _cpReg[28] = _SHIFTL(fi.read_ptr,0,16);
++      _cpReg[29] = _SHIFTR(fi.read_ptr,16,16);
++
++      asm ("sync");
++      asm ("isync");
++      /* enable the write gather pipe */
++      __GX_WriteFifoIntEnable(GX_ENABLE,GX_DISABLE);
++      __GX_FifoLink(GX_TRUE);
++      __GX_FifoReadEnable(1);
++      /* enable interrupts */
++      gcngx_enable_pe_interrupts();
++
++      asm("sync");
++      asm("isync");
++}
++
++int gcngx_init(struct fb_info *info)
++{
++      int err;
++      /* compute framebuffer pointers */
++      xfb[0] = (u32)info->fix.smem_start;
++      xfb[1] = (u32)info->fix.smem_start + info->fix.smem_len/2;
++      /* disable the interrupts */
++      gcngx_disable_pe_interrupts();
++      __GX_WriteFifoIntEnable(GX_DISABLE,GX_DISABLE);
++
++      /* map the fifo area */
++      phys_fifo_base = (u8*)GCN_GX_FIFO_START;
++      if (!request_mem_region((u32)phys_fifo_base,fifo_len,"GX FIFO")) {
++              printk(KERN_ERR "Cannot reserve fifo memory area at %p\n",phys_fifo_base);
++              return -EIO;
++      }
++      if (!(mmap_fifo_base = ioremap((u32)phys_fifo_base,fifo_len))) {
++              printk(KERN_ERR "Cannot map the fifo area at %p\n",phys_fifo_base);
++              err = -EIO;
++              goto free_mem;
++      }
++      
++      if ((err=request_irq(IRQ_PE_TOKEN,gcfb_pe_token_irq_handler,IRQF_DISABLED,"PE Token",0)))
++      {
++              goto free_iounmap;
++      }
++      if ((err=request_irq(IRQ_PE_FINISH,gcfb_pe_finish_irq_handler,IRQF_DISABLED,"PE Finish",0)))
++      {
++              goto free_pe_token;
++      }
++      if ((err=request_irq(IRQ_CP_FIFO,gcfb_fifo_irq_handler,IRQF_DISABLED,"CP FIFO",0)))
++      {
++              goto free_pe_finish;
++      }
++      return 0;
++
++ free_pe_finish:
++      free_irq(IRQ_PE_FINISH,0);
++ free_pe_token:
++      free_irq(IRQ_PE_TOKEN,0);
++ free_iounmap:
++      iounmap(mmap_fifo_base);
++ free_mem:
++      release_mem_region((u32)phys_fifo_base,fifo_len);
++      
++      return err;
++}
++
++void gcngx_exit(struct fb_info *info)
++{
++      gcngx_destroy_fifo();
++      
++      free_irq(IRQ_PE_FINISH,0);
++      free_irq(IRQ_PE_TOKEN,0);
++      free_irq(IRQ_CP_FIFO,0);
++
++      iounmap(mmap_fifo_base);
++      release_mem_region((u32)phys_fifo_base,fifo_len);
++}
++
++#endif /* CONFIG_FB_GAMECUBE_GX */
++
+diff --git a/drivers/video/gcngx.h b/drivers/video/gcngx.h
+new file mode 100644
+index 0000000..9722349
+--- /dev/null
++++ b/drivers/video/gcngx.h
+@@ -0,0 +1,17 @@
++#ifndef __GCGX__
++#define __GCGX__
++
++#ifdef CONFIG_FB_GAMECUBE_GX
++
++int gcngx_mmap(struct fb_info *info, struct vm_area_struct *vma);
++int gcngx_ioctl(struct fb_info *info, unsigned int cmd,unsigned long arg);
++
++int gcngx_init(struct fb_info *info);
++void gcngx_exit(struct fb_info *info);
++
++struct vi_ctl;
++void gcngx_vtrace(struct vi_ctl *ctl);
++
++#endif
++
++#endif
+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 b91251d..a88ea9f 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 2e85a2b..bd608bf 100644
+--- a/drivers/video/logo/logo.c
++++ b/drivers/video/logo/logo.c
+@@ -27,6 +27,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;
+@@ -90,6 +91,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/fs/Kconfig b/fs/Kconfig
+index 522469a..696a86f 100644
+--- a/fs/Kconfig
++++ b/fs/Kconfig
+@@ -485,6 +485,18 @@ config UDF_NLS
+       default y
+       depends on (UDF_FS=m && NLS) || (UDF_FS=y && NLS=y)
++config GCDVD_FS
++      tristate "GCDVD file system support"
++      depends on BROKEN
++      help 
++       This is the filesystem used on commercial Gamecube games.  Say Y if 
++       you intend to mount GC DVD discs.  
++
++       To compile this file system support as a module, choose M here: the
++       module will be called gcdvdfs
++
++       If unsure, say N.
++
+ endmenu
+ endif # BLOCK
+diff --git a/fs/Makefile b/fs/Makefile
+index d9f8afe..b36b461 100644
+--- a/fs/Makefile
++++ b/fs/Makefile
+@@ -83,6 +83,7 @@ obj-$(CONFIG_MINIX_FS)               += minix/
+ obj-$(CONFIG_FAT_FS)          += fat/
+ obj-$(CONFIG_BFS_FS)          += bfs/
+ obj-$(CONFIG_ISO9660_FS)      += isofs/
++obj-$(CONFIG_GCDVD_FS)                += gcdvdfs/
+ obj-$(CONFIG_HFSPLUS_FS)      += hfsplus/ # Before hfs to find wrapped HFS+
+ obj-$(CONFIG_HFS_FS)          += hfs/
+ obj-$(CONFIG_ECRYPT_FS)               += ecryptfs/
+diff --git a/fs/gcdvdfs/Makefile b/fs/gcdvdfs/Makefile
+new file mode 100644
+index 0000000..2ac5b5a
+--- /dev/null
++++ b/fs/gcdvdfs/Makefile
+@@ -0,0 +1,6 @@
++obj-$(CONFIG_GCDVD_FS) := gcdvdfs.o
++
++gcdvdfs-objs-y := main.o inode.o dir.o namei.o fst.o dol.o
++
++gcdvdfs-objs   := $(gcdvdfs-objs-y)
++
+diff --git a/fs/gcdvdfs/dir.c b/fs/gcdvdfs/dir.c
+new file mode 100644
+index 0000000..51c7cff
+--- /dev/null
++++ b/fs/gcdvdfs/dir.c
+@@ -0,0 +1,120 @@
++/*
++ * fs/gcdvdfs/dir.c
++ *
++ * Nintendo GameCube Filesystem driver 
++ * Copyright (C) 2006 The GameCube Linux Team
++ *
++ * 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/fs.h>
++#include "fst.h"
++#include "dir.h"
++#include "inode.h"
++#include "namei.h"
++
++struct readdir_data
++{
++  struct gc_dvdfs_fst *fst;
++  struct file *filp;
++  filldir_t filldir;
++  void *dirent;
++  u32 idx;
++};
++
++static int gc_dvdfs_readdir_callback(struct gc_dvdfs_file_entry *pfe,void *param)
++{
++  struct readdir_data *rdd = (struct readdir_data*)param;
++  int len;
++  const char *pname;
++  
++  if ((gc_dvdfs_valid_file_entry(rdd->fst,pfe) == 0) &&
++      (rdd->idx == rdd->filp->f_pos))
++  {
++    /* add this item to the list now */
++    pname = FILENAME(rdd->fst,pfe);
++    len = strlen(pname);
++    
++    if (rdd->filldir(rdd->dirent,pname,len,rdd->filp->f_pos,
++                   PFE_TO_INO(rdd->fst,pfe),
++                   (pfe->type == FST_DIRECTORY) ? DT_DIR : DT_REG) < 0)
++    {
++      /* failure, so stop enumerating */
++      return -1;
++    }
++    rdd->filp->f_pos++;
++  }
++  /* increment count */
++  rdd->idx++;
++  return 0;
++}
++
++static int gc_dvdfs_readdir(struct file *filp,void *dirent,filldir_t filldir)
++{
++  struct inode *inode = filp->f_dentry->d_inode;
++  struct gc_dvdfs_fst *fst = (struct gc_dvdfs_fst*)inode->i_sb->s_fs_info;
++  struct gc_dvdfs_file_entry *pfe;
++  struct readdir_data rdd;
++  int i;
++  
++  /* first entry is . */
++  switch ((u32)filp->f_pos)
++  {
++  case 0:
++    if (filldir(dirent,".",1,0,inode->i_ino,DT_DIR) < 0)
++      return 0;
++    filp->f_pos = 1;
++    /* fall through */
++  case 1:
++    if (filldir(dirent,"..",2,1,parent_ino(filp->f_dentry),DT_DIR) < 0)
++      return 0;
++    filp->f_pos = 2;
++    /* fall through */
++  default:
++    if (inode->i_ino == ROOT_INO)
++    {
++      for (i=filp->f_pos-2;i<num_root_dir_entries;++i)
++      {
++      if (filldir(dirent,root_dir_entries[i].name,
++                  root_dir_entries[i].name_length,filp->f_pos,
++                  root_dir_entries[i].ino,
++                  root_dir_entries[i].filldir_type) < 0)
++      {
++        return 0;
++      }
++      
++      filp->f_pos++;
++      }
++    }
++    else if (inode->i_ino >= DATA_INO)
++    {
++      /* use enumerate, the function will check for the FST_DIRECTORY flag */
++      pfe = INO_TO_PFE(fst,inode->i_ino);
++      
++      rdd.fst = fst;
++      rdd.filp = filp;
++      rdd.filldir = filldir;
++      rdd.dirent = dirent;
++      rdd.idx = 2;
++      
++      gc_dvdfs_enumerate(fst,pfe,gc_dvdfs_readdir_callback,&rdd);
++    }
++  }
++  return 0;
++}
++
++struct file_operations gc_dvdfs_dir_operations = 
++{
++  .read = generic_read_dir,
++  .readdir = gc_dvdfs_readdir,
++};
++
++struct inode_operations gc_dvdfs_dir_inode_operations = 
++{
++  .lookup = gc_dvdfs_lookup,
++};
++
+diff --git a/fs/gcdvdfs/dir.h b/fs/gcdvdfs/dir.h
+new file mode 100644
+index 0000000..f415510
+--- /dev/null
++++ b/fs/gcdvdfs/dir.h
+@@ -0,0 +1,20 @@
++/*
++ * fs/gcdvdfs/dir.h
++ *
++ * Nintendo GameCube Filesystem driver 
++ * Copyright (C) 2006 The GameCube Linux Team
++ *
++ * 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 __dir__gc__
++#define __dir__gc__
++
++extern struct file_operations gc_dvdfs_dir_operations;
++extern struct inode_operations gc_dvdfs_dir_inode_operations;
++
++#endif
+diff --git a/fs/gcdvdfs/dol.c b/fs/gcdvdfs/dol.c
+new file mode 100644
+index 0000000..c2dc6ac
+--- /dev/null
++++ b/fs/gcdvdfs/dol.c
+@@ -0,0 +1,63 @@
++/*
++ * fs/gcdvdfs/dol.c
++ *
++ * Nintendo GameCube Filesystem driver 
++ * Copyright (C) 2006 The GameCube Linux Team
++ *
++ * 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/types.h>
++#include "fst.h"
++#include "dol.h"
++
++void gc_dvdfs_fix_raw_dol_header(struct gc_dvdfs_dol_header *pdh)
++{
++#ifdef __LITTLE_ENDIAN
++#define SWAP(a) (a) = be32_to_cpu(a)
++  unsigned int i;
++  
++  for (i=0;i<7;++i)
++  {
++    SWAP(pdh->text_file_pos[i]);
++    SWAP(pdh->text_mem_pos[i]);
++    SWAP(pdh->text_section_size[i]);
++  }
++  
++  for (i=0;i<11;++i)
++  {
++    SWAP(pdh->data_file_pos[i]);
++    SWAP(pdh->data_mem_pos[i]);
++    SWAP(pdh->data_section_size[i]);
++  }
++  
++  SWAP(pdh->bss_mem_address);
++  SWAP(pdh->bss_size);
++  SWAP(pdh->entry_point);
++#endif
++}
++
++u32 gc_dvdfs_get_dol_file_size(struct gc_dvdfs_dol_header *pdh)
++{
++  unsigned int tmp;
++  unsigned int i;
++  unsigned int max = 0;
++
++  for (i=0;i<7;++i)
++  {
++    tmp = pdh->text_file_pos[i] + pdh->text_section_size[i];
++    if (tmp > max)
++      max = tmp;
++  }
++  for (i=0;i<11;++i)
++  {
++    tmp = pdh->data_file_pos[i] + pdh->data_section_size[i];
++    if (tmp > max)
++      max = tmp;
++  }
++  return max;
++}
+diff --git a/fs/gcdvdfs/dol.h b/fs/gcdvdfs/dol.h
+new file mode 100644
+index 0000000..32c6b43
+--- /dev/null
++++ b/fs/gcdvdfs/dol.h
+@@ -0,0 +1,20 @@
++/*
++ * fs/gcdvdfs/dol.h
++ *
++ * Nintendo GameCube Filesystem driver 
++ * Copyright (C) 2006 The GameCube Linux Team
++ *
++ * 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 __dol__h
++#define __dol__h
++
++void gc_dvdfs_fix_raw_dol_header(struct gc_dvdfs_dol_header *pdh);
++
++u32 gc_dvdfs_get_dol_file_size(struct gc_dvdfs_dol_header *pdh);
++
++#endif
+diff --git a/fs/gcdvdfs/fst.c b/fs/gcdvdfs/fst.c
+new file mode 100644
+index 0000000..a2f8cda
+--- /dev/null
++++ b/fs/gcdvdfs/fst.c
+@@ -0,0 +1,105 @@
++/*
++ * fs/gcdvdfs/fst.c
++ *
++ * Nintendo GameCube Filesystem driver 
++ * Copyright (C) 2006 The GameCube Linux Team
++ *
++ * 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/types.h>
++#include <linux/errno.h>
++#include <linux/fs.h>
++#include <linux/init.h>
++#include "fst.h"
++
++int gc_dvdfs_valid_file_entry(struct gc_dvdfs_fst *fst,struct gc_dvdfs_file_entry *pfe)
++{
++  unsigned int foffset = FILENAME_OFFSET(pfe);
++
++  if (((pfe->type != FST_FILE) && (pfe->type != FST_DIRECTORY)) ||
++      (foffset >= fst->str_table_size))
++  {
++    return -EINVAL;
++  }
++  return 0;
++}
++
++int gc_dvdfs_enumerate(struct gc_dvdfs_fst *fst,struct gc_dvdfs_file_entry *pfe,int (*callback)(struct gc_dvdfs_file_entry *pfe,void *param),void *param)
++{
++  /* get the filename */
++  unsigned int i;
++  unsigned int entries;
++  int r;
++
++  /* only enumerate directories */
++  if (pfe->type != FST_DIRECTORY)
++  {
++    return -EINVAL;
++  }
++
++  entries = pfe->dir.offset_next;
++  i=((unsigned int)pfe - (unsigned int)fst->root)/sizeof(struct gc_dvdfs_file_entry) + 1;
++  /* check if out of bounds */
++  if (i >= MAX_ENTRIES(fst) || entries > MAX_ENTRIES(fst))
++  {
++    return -EINVAL;
++  }
++
++  /* loop through the files */
++  while (i < entries)
++  {
++    /* do the callback */
++    if ((r=callback(fst->root + i,param)) < 0)
++    {
++      return r;
++    }
++    
++    if (fst->root[i].type == FST_DIRECTORY)
++    {
++      if (fst->root[i].dir.offset_next <= i)
++      {
++      /* we're going backwards or looping, abort */
++      return -EINVAL;
++      }
++      i = fst->root[i].dir.offset_next;
++    }
++    else
++    {
++      ++i;
++    }
++  }
++  return 0;
++}
++
++static int gc_dvdfs_get_directory_info_callback(struct gc_dvdfs_file_entry *pfe,void *param)
++{
++  struct gc_dvdfs_directory_info *di = (struct gc_dvdfs_directory_info*)param;
++  
++  if (gc_dvdfs_valid_file_entry(di->fst,pfe) == 0)
++  {
++    if (pfe->type == FST_FILE)
++    {
++      di->total_files++;
++      di->total_file_size += pfe->file.length;
++    }
++    else if (pfe->type == FST_DIRECTORY)
++    {
++      di->total_directories++;
++    }
++  }
++  return 0;
++}
++
++int gc_dvdfs_get_directory_info(struct gc_dvdfs_directory_info *di)
++{
++  di->total_files = 0;
++  di->total_directories = 0;
++  di->total_file_size = 0;
++
++  return gc_dvdfs_enumerate(di->fst,di->pfe,gc_dvdfs_get_directory_info_callback,di);
++}
+diff --git a/fs/gcdvdfs/fst.h b/fs/gcdvdfs/fst.h
+new file mode 100644
+index 0000000..ca3f5e6
+--- /dev/null
++++ b/fs/gcdvdfs/fst.h
+@@ -0,0 +1,126 @@
++/*
++ * fs/gcdvdfs/fst.h
++ *
++ * Nintendo GameCube Filesystem driver 
++ * Copyright (C) 2006 The GameCube Linux Team
++ *
++ * 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 __fst__h
++#define __fst__h
++
++#define FST_OFFSET 0x0424
++
++#define FST_FILE      0
++#define FST_DIRECTORY 1
++
++#pragma pack(1)
++struct gc_dvdfs_disc_header
++{
++  u32 game_code;
++  u16 maker_code;
++  u8 disc_id;
++  u8 version;
++  u8 streaming;
++  u8 streamBufSize;
++  u8 padding1[22];
++  u8 game_name[992];
++  u32 offset_dh_bin;
++  u32 addr_debug_monitor;
++  u8 padding2[24];
++  u32 offset_bootfile;
++  u32 offset_fst;
++  u32 fst_size;
++  u32 max_fst_size;
++  u32 user_position;
++  u32 user_length;
++  u8 padding[7];
++};
++
++struct gc_dvdfs_file_entry
++{
++  u8 type;
++  u8 offset_filename[3];
++  union
++  {
++    struct
++    {
++      u32 offset;
++      u32 length;               /* if root, number of entries */
++    } file;
++    struct
++    {
++      u32 offset_parent;
++      u32 offset_next;
++    } dir;
++  };
++};
++
++#define APPLOADER_OFFSET 0x2440
++struct gc_dvdfs_apploader
++{
++  u8 version[10];
++  u8 padding[6];
++  u32 entry_point;
++  u32 size;
++};
++
++struct gc_dvdfs_dol_header
++{
++  u32 text_file_pos[7];
++  u32 data_file_pos[11];
++  u32 text_mem_pos[7];
++  u32 data_mem_pos[11];
++  u32 text_section_size[7];
++  u32 data_section_size[11];
++  u32 bss_mem_address;
++  u32 bss_size;
++  u32 entry_point;
++};
++
++#pragma pack()
++
++struct gc_dvdfs_fst;
++
++struct gc_dvdfs_directory_info
++{
++  struct gc_dvdfs_fst *fst;
++  struct gc_dvdfs_file_entry *pfe;
++
++  u32 total_files;
++  u32 total_directories;
++  u32 total_file_size;
++};
++
++struct gc_dvdfs_fst
++{
++  struct gc_dvdfs_file_entry *root;
++  const char *str_table;
++  u32 size;
++  u32 str_table_size;
++  u32 dol_length;
++  u32 dol_offset;
++  u32 total_files;
++  u32 total_directories;
++  u32 total_file_size;
++  struct gc_dvdfs_apploader apploader;
++  struct gc_dvdfs_dol_header dol_header;
++};
++
++#define MAX_ENTRIES(fst) (fst)->root->dir.offset_next
++#define FILENAME_OFFSET(pfe) (((pfe)->offset_filename[0] << 16) | \
++                              ((pfe)->offset_filename[1] << 8) | \
++                              (pfe)->offset_filename[2])
++
++#define FILENAME(fst,pfe) ((fst)->str_table + FILENAME_OFFSET(pfe))
++
++int gc_dvdfs_valid_file_entry(struct gc_dvdfs_fst *fst,struct gc_dvdfs_file_entry *pfe);
++int gc_dvdfs_enumerate(struct gc_dvdfs_fst *fst,struct gc_dvdfs_file_entry *pfe,int (*callback)(struct gc_dvdfs_file_entry *pfe,void *param),void *param);
++
++int gc_dvdfs_get_directory_info(struct gc_dvdfs_directory_info *di);
++
++#endif
+diff --git a/fs/gcdvdfs/inode.c b/fs/gcdvdfs/inode.c
+new file mode 100644
+index 0000000..ee490e1
+--- /dev/null
++++ b/fs/gcdvdfs/inode.c
+@@ -0,0 +1,105 @@
++/*
++ * fs/gcdvdfs/inode.c
++ *
++ * Nintendo GameCube Filesystem driver 
++ * Copyright (C) 2006 The GameCube Linux Team
++ *
++ * 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/fs.h>
++#include <linux/mm.h>
++#include <linux/types.h>
++#include <linux/errno.h>
++#include <linux/vfs.h>
++#include <linux/highmem.h>
++#include <linux/blkdev.h>
++
++#include "fst.h"
++#include "inode.h"
++#include "dir.h"
++#include "namei.h"
++
++extern int gc_dvdfs_read_into_memory(struct super_block *s,u32 offset,
++                                   u32 size,unsigned char *data);
++
++static int gc_dvdfs_readpage(struct file *file,struct page *page)
++{
++  struct inode *inode = (struct inode*)page->mapping->host;
++  struct gc_dvdfs_fst *fst = (struct gc_dvdfs_fst*)inode->i_sb->s_fs_info;
++  struct gc_dvdfs_file_entry *pfe;
++  void *buf;
++  loff_t offset;
++  loff_t block_base;
++  loff_t len;
++  int ret = 0;
++  
++  switch (inode->i_ino)
++  {
++  case ROOT_INO:
++    return -EIO;
++  case APPLOADER_INO:
++    block_base = APPLOADER_OFFSET;
++    break;
++  case BOOTDOL_INO:
++    block_base = fst->dol_offset;
++    break;
++  default:
++    pfe = INO_TO_PFE(fst,inode->i_ino);
++    block_base = pfe->file.offset;
++    break;
++  }
++  
++  get_page(page);
++  ClearPageUptodate(page);
++  ClearPageError(page);
++  /* do a lock here  */
++  
++  offset = (page->index << PAGE_CACHE_SHIFT);
++  block_base += offset;
++  /* now map into kernel space and fill the page */
++  kmap(page);
++  buf = page_address(page);
++  if (offset < inode->i_size) 
++  {
++    len = min((loff_t)PAGE_SIZE,inode->i_size - offset);
++    if (gc_dvdfs_read_into_memory(inode->i_sb,block_base,len,buf))
++    {
++      ret = -EIO;
++    }
++  }
++  flush_dcache_page(page);
++  kunmap(page);
++  
++  /* unlock */
++  
++  if (ret)
++  {
++    SetPageError(page);
++  }
++  else
++  {
++    SetPageUptodate(page);
++  }
++  page_cache_release(page);
++  
++  unlock_page(page);
++  return ret;
++}
++
++struct address_space_operations gc_dvdfs_addr_operations =
++{
++  .readpage = gc_dvdfs_readpage,
++};
++
++struct root_dir_entry root_dir_entries[] = {
++  { "apploader", 9, DT_REG, APPLOADER_INO },
++  { "boot.dol" , 8, DT_REG, BOOTDOL_INO },
++  { "data"     , 4, DT_DIR, DATA_INO }
++};
++
++unsigned int num_root_dir_entries = sizeof(root_dir_entries) / sizeof(root_dir_entries[0]);
+diff --git a/fs/gcdvdfs/inode.h b/fs/gcdvdfs/inode.h
+new file mode 100644
+index 0000000..9e33e76
+--- /dev/null
++++ b/fs/gcdvdfs/inode.h
+@@ -0,0 +1,37 @@
++/*
++ * fs/gcdvdfs/inode.h
++ *
++ * Nintendo GameCube Filesystem driver 
++ * Copyright (C) 2006 The GameCube Linux Team
++ *
++ * 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 __inode_fst__
++#define __inode_fst__
++
++#define ROOT_INO 1
++#define APPLOADER_INO 2
++#define BOOTDOL_INO 3
++#define DATA_INO 4
++
++#define PFE_TO_INO(fst,pfe) (((unsigned long)(pfe) - (unsigned long)(fst)->root)/sizeof(struct gc_dvdfs_file_entry)+DATA_INO)
++#define INO_TO_PFE(fst,ino) ((fst)->root + ((ino)-DATA_INO))
++
++struct root_dir_entry
++{
++  const char *name;
++  unsigned int name_length;
++  unsigned int filldir_type;
++  unsigned int ino;
++};
++
++extern struct root_dir_entry root_dir_entries[];
++extern unsigned int num_root_dir_entries;
++
++extern struct address_space_operations gc_dvdfs_addr_operations;
++
++#endif
+diff --git a/fs/gcdvdfs/main.c b/fs/gcdvdfs/main.c
+new file mode 100644
+index 0000000..a69807b
+--- /dev/null
++++ b/fs/gcdvdfs/main.c
+@@ -0,0 +1,346 @@
++/*
++ * fs/gcdvdfs/main.c
++ *
++ * Nintendo GameCube Filesystem driver 
++ * Copyright (C) 2006 The GameCube Linux Team
++ *
++ * 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/types.h>
++#include <linux/errno.h>
++#include <linux/vmalloc.h>
++#include <linux/fs.h>
++#include <linux/init.h>
++#include <linux/genhd.h>
++#include <linux/buffer_head.h>
++#include <linux/statfs.h>
++#include "fst.h"
++#include "inode.h"
++#include "dir.h"
++#include "dol.h"
++
++#define GC_DVD_SECTOR_SIZE 2048
++#define GC_DVD_MAX_SECTORS 712880
++
++#define FST_MAGIC 0x78B39EA1
++#define DEBUG
++
++int gc_dvdfs_read_into_memory(struct super_block *s,u32 offset,u32 size,unsigned char *data)
++{
++  struct buffer_head *bh;
++  u32 sector;
++  u32 sector_end;
++  u32 byte_start;
++  u32 byte_len;
++  unsigned char *start;
++
++  sector       = offset          >> s->s_blocksize_bits;
++  sector_end   = (offset + size - 1) >> s->s_blocksize_bits;
++  while (sector <= sector_end)
++  {
++    if (!(bh = sb_bread(s,sector)))
++    {
++      return -EINVAL;
++    }
++    /* store data into the memory buffer */
++    byte_start = sector << s->s_blocksize_bits;
++    byte_len   = bh->b_size;
++    start      = bh->b_data;
++    /* check if not in the middle */
++    if (byte_start < offset)
++    {
++      start      = bh->b_data + (offset - byte_start);
++      byte_len  -= (offset - byte_start);
++    }
++    /* check if we will overflow */
++    if (byte_len > size)
++    {
++      byte_len = size;
++    }
++    /* now copy the data */
++    memcpy(data,start,byte_len);
++    data += byte_len;
++    size -= byte_len;
++    /* move the sector along */
++    sector += (bh->b_size >> s->s_blocksize_bits);
++    brelse(bh);
++  }
++#ifdef DEBUG
++  if (size != 0)
++  {
++    printk(KERN_INFO "WARNING - read_into_memory still has a size value %u\n",
++         size);
++  }
++#endif
++  return 0;
++}
++
++static void gc_dvdfs_read_inode(struct inode *i)
++{
++  struct gc_dvdfs_directory_info di;
++  unsigned int size;
++  
++  di.fst = (struct gc_dvdfs_fst*)i->i_sb->s_fs_info;
++  /* load the defaults for all inodes */
++  i->i_mtime.tv_sec = i->i_atime.tv_sec = i->i_ctime.tv_sec = 0;
++  i->i_mtime.tv_nsec = i->i_atime.tv_nsec = i->i_ctime.tv_nsec = 0;
++  i->i_uid = 0;
++  i->i_gid = 0;
++  /* load based on type */
++  if (i->i_ino == ROOT_INO)
++  {
++    i->i_fop = &gc_dvdfs_dir_operations;
++    i->i_op = &gc_dvdfs_dir_inode_operations;
++    i->i_mode = S_IFDIR | 0111;
++    i->i_nlink = 3;
++    size = 0;
++    }
++  else
++  {
++    if ((i->i_ino >= DATA_INO) &&
++      (di.pfe = INO_TO_PFE(di.fst,i->i_ino)) &&
++      (di.pfe->type == FST_DIRECTORY))
++    {
++      i->i_fop = &gc_dvdfs_dir_operations;
++      i->i_op  = &gc_dvdfs_dir_inode_operations;
++      i->i_mode  = S_IFDIR | 0111;
++      /* compute the number of links */
++      gc_dvdfs_get_directory_info(&di);
++      i->i_nlink = 2 + di.total_directories;
++      
++      size = sizeof(struct gc_dvdfs_file_entry);
++    }
++    else
++    {
++      i->i_fop = &generic_ro_fops;
++      i->i_data.a_ops = &gc_dvdfs_addr_operations;
++      i->i_mode = S_IFREG;
++      i->i_nlink = 1;
++      switch (i->i_ino)
++      {
++      case APPLOADER_INO:
++      size = di.fst->apploader.size;
++      break;
++      case BOOTDOL_INO:
++      size = di.fst->dol_length;
++      break;
++      default:
++      size = di.pfe->file.length;
++      break;
++      }
++    }     
++  }
++  
++  i->i_mode |= 0444;
++  i_size_write(i,size);
++}
++
++static int gc_dvdfs_statfs(struct dentry * dentry,struct kstatfs *sfs)
++{
++  struct super_block *sb = dentry->d_sb;
++  struct gc_dvdfs_fst *fst = (struct gc_dvdfs_fst*)sb->s_fs_info;
++
++  sfs->f_type   = sb->s_magic;
++  sfs->f_bsize  = sb->s_blocksize;
++  sfs->f_blocks = fst->total_file_size >> sb->s_blocksize_bits;
++  sfs->f_bfree  = 0;
++  sfs->f_bavail = 0;
++  sfs->f_files  = fst->total_files;
++  sfs->f_ffree  = 0;
++  /* sfs->f_fsid   = 0; */
++  sfs->f_namelen = 256;
++  sfs->f_frsize = 0;
++  return 0;
++}
++
++static void gc_dvdfs_free_fst(struct gc_dvdfs_fst *fst)
++{
++  if (fst)
++  {
++    vfree(fst);
++  }
++}
++
++static void gc_dvdfs_put_super(struct super_block *sb)
++{
++  /* ok free my FST */
++  if (sb->s_fs_info)
++  {
++    gc_dvdfs_free_fst((struct gc_dvdfs_fst*)sb->s_fs_info);
++    sb->s_fs_info = NULL;
++  }
++}
++
++static struct super_operations gcdvdfs_ops = {
++  .read_inode = gc_dvdfs_read_inode,
++  .put_super  = gc_dvdfs_put_super,
++  .statfs     = gc_dvdfs_statfs 
++};
++
++static int gc_dvdfs_validate_fst(struct gc_dvdfs_fst *fst)
++{
++  unsigned int entries;
++  unsigned int i;
++
++  /* make sure the FST is completely valid */
++  if (fst->root->type != FST_DIRECTORY)
++  {
++    printk(KERN_ERR "gcdvdfs: Root entry is not a directory!\n");
++    return -EINVAL;
++  }
++  
++  entries = be32_to_cpu(fst->root->dir.offset_next);
++  if (entries >= (fst->size / sizeof(struct gc_dvdfs_file_entry)))
++  {
++    printk(KERN_ERR "gcdvdfs: Too many entries, will overflow the FST!\n");
++    return -EINVAL;
++  }
++  /* ok let's convert all the data to native format and compute total size*/
++  for (i=0;i<entries;++i)
++  {
++#ifdef __LITTLE_ENDIAN
++    fst->root[i].file.length = be32_to_cpu(fst->root[i].file.length);
++    fst->root[i].file.offset = be32_to_cpu(fst->root[i].file.offset);
++#endif
++    if (fst->root[i].type == FST_FILE)
++    {
++      fst->total_files++;
++      fst->total_file_size += fst->root[i].file.length;
++    }
++    else if (fst->root[i].type == FST_DIRECTORY)
++    {
++      fst->total_directories++;
++    }
++  }
++
++  return 0;
++}
++
++static int gc_dvdfs_fill_super(struct super_block *s,void *data,int silent)
++{
++  struct buffer_head *bh;
++  struct gc_dvdfs_disc_header *dh;
++  struct gc_dvdfs_fst *fst;
++  unsigned int fst_offset;
++  unsigned int fst_size;
++  unsigned int dol_offset;
++  
++  sb_min_blocksize(s,GC_DVD_SECTOR_SIZE);
++  /* s->s_maxbytes = 4*GC_DVD_SECTOR_SIZE; */
++  
++  /* read the header and check results */
++  if (!(bh = sb_bread(s,0)))
++  {
++    return -EINVAL;
++  }
++  else if (bh->b_size < sizeof(struct gc_dvdfs_disc_header))
++  {
++    brelse(bh);
++    return -EINVAL;
++  }
++
++  /* read the FST into memory */
++  dh = (struct gc_dvdfs_disc_header*)bh->b_data;
++  fst_offset = be32_to_cpu(dh->offset_fst);
++  fst_size   = be32_to_cpu(dh->fst_size);
++  dol_offset = be32_to_cpu(dh->offset_bootfile);
++  brelse(bh);
++  
++  /* now allocate the fst */
++  if (!(fst = vmalloc(sizeof(struct gc_dvdfs_fst) + fst_size)))
++  {
++    return -ENOMEM;
++  }
++  fst->size = fst_size;
++  fst->root = (struct gc_dvdfs_file_entry*)((unsigned int)fst + sizeof(struct gc_dvdfs_fst));
++  /* now try to read the fst */
++  if (gc_dvdfs_read_into_memory(s,fst_offset,fst_size,(unsigned char*)fst->root))
++  {
++    printk(KERN_ERR "gcdvdfs: Unable to read FST into memory\n");
++    goto fst_error;
++  }  
++  /* now try to read the apploader */
++  if (gc_dvdfs_read_into_memory(s,APPLOADER_OFFSET,sizeof(struct gc_dvdfs_apploader),(unsigned char*)&fst->apploader))
++  {
++    printk(KERN_ERR "gcdvdfs: Unable to read apploader into memory\n");
++    goto fst_error;
++  }
++  /* fix up the apploader */
++  fst->apploader.entry_point = be32_to_cpu(fst->apploader.entry_point);
++  fst->apploader.size        = be32_to_cpu(fst->apploader.size);
++  /* now try to read the dol header */
++  if (gc_dvdfs_read_into_memory(s,dol_offset,sizeof(struct gc_dvdfs_dol_header),(unsigned char*)&fst->dol_header))
++  {
++    printk(KERN_ERR "gcdvdfs: Unable to read DOL Header\n");
++    goto fst_error;
++  }
++  gc_dvdfs_fix_raw_dol_header(&fst->dol_header);
++  
++  fst->dol_offset = dol_offset;
++  fst->dol_length = gc_dvdfs_get_dol_file_size(&fst->dol_header);
++  /* compute the location of the string table */
++  fst_offset = be32_to_cpu(fst->root->dir.offset_next) * sizeof(struct gc_dvdfs_file_entry);
++  fst->str_table = (unsigned char*)fst->root + fst_offset;
++  fst->str_table_size = fst->size - fst_offset;
++  fst->total_files = 0;
++  fst->total_directories = 0;
++  fst->total_file_size = 0;
++  /* now validate the fst */
++  if (gc_dvdfs_validate_fst(fst))
++  {
++    goto fst_error;
++  }
++
++  /* store my FST in s->s_fs_info */
++  s->s_flags   |= MS_RDONLY | MS_NOSUID | MS_NODEV | MS_NOATIME | MS_NODIRATIME;
++  s->s_fs_info  = fst;
++  s->s_magic    = FST_MAGIC;
++  s->s_op       = &gcdvdfs_ops;
++  if (!(s->s_root = d_alloc_root(iget(s,ROOT_INO))))
++  {
++    goto fst_error;
++  }
++  return 0;
++
++ fst_error:
++  gc_dvdfs_free_fst(fst);
++  return -EINVAL;
++}
++
++static int gc_dvdfs_get_sb(struct file_system_type *fs_type,int flags,const char *dev_name,void *data,struct vfsmount *mnt)
++{
++  return get_sb_bdev(fs_type,flags,dev_name,data,gc_dvdfs_fill_super,mnt);
++}
++
++static struct file_system_type gcdvdfs_type = {
++  .owner    = THIS_MODULE,
++  .name     = "gcdvdfs",
++  .get_sb   = gc_dvdfs_get_sb, 
++  /*.kill_sb  = gc_dvdfs_kill_sb, */
++  .kill_sb  = kill_block_super,
++  .fs_flags = FS_REQUIRES_DEV 
++};
++
++static int __init gc_dvdfs_init(void)
++{
++  printk(KERN_INFO "Gamecube DVD filesystem: by Todd Jeffreys\n");
++  return register_filesystem(&gcdvdfs_type);
++}
++
++static void __exit gc_dvdfs_exit(void)
++{
++  unregister_filesystem(&gcdvdfs_type);
++}
++
++MODULE_LICENSE("GPL");
++MODULE_AUTHOR("Todd Jeffreys");
++MODULE_DESCRIPTION("Gamecube DVD filesystem");
++  
++module_init(gc_dvdfs_init);
++module_exit(gc_dvdfs_exit);
+diff --git a/fs/gcdvdfs/namei.c b/fs/gcdvdfs/namei.c
+new file mode 100644
+index 0000000..2488406
+--- /dev/null
++++ b/fs/gcdvdfs/namei.c
+@@ -0,0 +1,100 @@
++/*
++ * fs/gcdvdfs/namei.c
++ *
++ * Nintendo GameCube Filesystem driver 
++ * Copyright (C) 2006 The GameCube Linux Team
++ *
++ * 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/types.h>
++#include <linux/errno.h>
++#include <linux/fs.h>
++#include <linux/init.h>
++#include <linux/genhd.h>
++#include <linux/buffer_head.h>
++#include <linux/statfs.h>
++#include "fst.h"
++#include "inode.h"
++
++/*
++  i is the parent directory
++  dentry I must complete
++  nid is lookup nameidata
++
++  Complete by calling d_add(dentry,inode)
++  
++  If name is not found, inode must be set to null.
++
++  Return an error only when the lookup fails due to hardware/other error
++
++*/
++
++struct gc_dvdfs_lookup_data
++{
++  struct gc_dvdfs_fst *fst;
++  const char *pname;
++  unsigned long ino;
++};
++
++static int gc_dvdfs_lookup_callback(struct gc_dvdfs_file_entry *pfe,void *param)
++{
++  struct gc_dvdfs_lookup_data *data = (struct gc_dvdfs_lookup_data*)param;
++
++  if (gc_dvdfs_valid_file_entry(data->fst,pfe) == 0)
++  {
++    if (strcmp(FILENAME(data->fst,pfe),data->pname) == 0)
++    {
++      data->ino = PFE_TO_INO(data->fst,pfe);
++      /* this will stop enumeration */
++      return -1;
++    }
++  }
++  return 0;
++}
++
++struct dentry *gc_dvdfs_lookup(struct inode *dir,struct dentry *dentry,struct nameidata *nid)
++{
++  struct inode *inode = NULL;
++  struct gc_dvdfs_lookup_data data;
++  unsigned int i;
++  
++  data.ino = 0;
++  data.pname = dentry->d_name.name;
++  /* handle special case of the root directory */
++  if (dir->i_ino == ROOT_INO)
++  {
++    for (i=0;i<num_root_dir_entries;++i)
++    {
++      if (strcmp(data.pname,root_dir_entries[i].name) == 0)
++      {
++      data.ino = root_dir_entries[i].ino;
++      break;
++      }
++    }
++  }
++  else if (dir->i_ino >= DATA_INO)
++  {
++    data.fst = (struct gc_dvdfs_fst*)dir->i_sb->s_fs_info;
++    
++    gc_dvdfs_enumerate(data.fst,INO_TO_PFE(data.fst,dir->i_ino),
++                     gc_dvdfs_lookup_callback,&data);
++  }
++
++  /* now convert the inode number into the structure */
++  if (data.ino)
++  {
++    if (!(inode = iget(dir->i_sb,data.ino)))
++    {
++      return ERR_PTR(-EACCES);
++    }
++  }  
++  
++  d_add(dentry,inode);
++  return NULL;
++}
+diff --git a/fs/gcdvdfs/namei.h b/fs/gcdvdfs/namei.h
+new file mode 100644
+index 0000000..19e3da9
+--- /dev/null
++++ b/fs/gcdvdfs/namei.h
+@@ -0,0 +1,18 @@
++/*
++ * fs/gcdvdfs/namei.h
++ *
++ * Nintendo GameCube Filesystem driver 
++ * Copyright (C) 2006 The GameCube Linux Team
++ *
++ * 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 __gc_lookup__
++#define __gc_lookup__
++
++struct dentry *gc_dvdfs_lookup(struct inode *dir,struct dentry *dentry,struct nameidata *nid);
++
++#endif
+diff --git a/include/linux/exi.h b/include/linux/exi.h
+new file mode 100644
+index 0000000..e5fc002
+--- /dev/null
++++ b/include/linux/exi.h
+@@ -0,0 +1,331 @@
++/*
++ * 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
++
++#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);
++
++/*
++ * 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;
++}
++
++#endif /* __EXI_H */
++
+diff --git a/include/linux/fb.h b/include/linux/fb.h
+index 75a81ea..98ffee5 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 */
+@@ -896,7 +901,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/sound/ppc/Kconfig b/sound/ppc/Kconfig
+index 777de2b..82c5ad3 100644
+--- a/sound/ppc/Kconfig
++++ b/sound/ppc/Kconfig
+@@ -48,4 +48,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..92dc264
+--- /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;
++
++      card = snd_card_new(index, id, THIS_MODULE, sizeof(struct snd_gcn));
++      if (!card) {
++              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..9445a46
+--- /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");
++
++      card = snd_card_new(index, id, THIS_MODULE, 0);
++      if (!card) {
++              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);
++
index 1914309..5a59dac 100644 (file)
@@ -1,6 +1,6 @@
 require linux.inc
 
-PR = "r5"
+PR = "r6"
 
 # Mark archs/machines that this kernel supports
 DEFAULT_PREFERENCE = "-1"
@@ -10,6 +10,7 @@ DEFAULT_PREFERENCE_ronetix-pm9263 = "28"
 DEFAULT_PREFERENCE_stb225 = "28"
 DEFAULT_PREFERENCE_collie = "1"
 DEFAULT_PREFERENCE_tosa = "1"
+DEFAULT_PREFERENCE_gamecube = "1"
 
 SRC_URI = "${KERNELORG_MIRROR}/pub/linux/kernel/v2.6/linux-2.6.28.tar.bz2 \
            file://defconfig"
@@ -59,6 +60,8 @@ SRC_URI_append_tosa = " \
        file://commit-ddfb33c;patch=1 \
        file://commit-f34ee79;patch=1 \
        "
-
+SRC_URI_append_gamecube = " \
+       file://patch-2.6.28-gc;patch=1 \
+       "
 
 S = "${WORKDIR}/linux-2.6.28/"