diff --git a/Documentation/exi.txt b/Documentation/exi.txt new file mode 100644 index 0000000..a330c56 --- /dev/null +++ b/Documentation/exi.txt @@ -0,0 +1,81 @@ +The Expansion Interface (EXI) +----------------------------- + +[Introductory information goes here...] + + +Device drivers +-------------- + +Outlined below is the recommended practice when writing EXI device drivers. + +The bus driver already handles quite an amount of stuff, although some of +it might have to be implemented by individual device drivers. If in doubt, +see include/linux/exi.h. + + +Registration +------------ + +Declare a struct exi_driver. Initialize at least the name, id_table, +probe and remove fields: + + + static struct exi_device_id frob_id_tbl[] __devinitdata = { + { .dev_id = EXI_ID_FROB0, }, + { .dev_id = EXI_ID_FROB1, }, + { .dev_id = EXI_ID_FROB2, }, + }; + + static struct exi_driver frob_driver = { + .name = "frob", + .id_table = frob_id_tbl, + .probe = frob_probe, + .remove = __devexit_p(frob_remove), + }; + + +`name' distinguishes the driver from others registered with the bus. +It should be short, unique, yet remain informative. + +`id_table' is a pointer to a table of device IDs the driver claims to +support. These should be taken directly from include/linux/exi_ids.h. +This table should be marked __devinitdata. + +`probe' is a pointer to a function that's called once the driver is bound +to a device it claims to support. This should be marked __devinit. + +`remove' is a pointer to a function that's called when either the driver +unregisters with the bus, or a device bound to that specific driver is +physically unplugged from the bus. This should be marked __devexit and +created with __devexit_p(). + +From within the driver's initialization function, register the driver with +the bus by calling exi_driver_register() with the driver structure declared +previously: + + static int __init frob_init(void) + { + return exi_driver_register(&frob_driver); + } + + +Deregistration +-------------- + +If the driver may be compiled as a loadable kernel module, then all you +have to do is call exi_driver_unregister() in the driver's exit function: + + static void __exit frob_exit(void) + { + exi_driver_unregister(&frob_driver); + } + +Device Private Data +------------------- + +The functions exi_set_drvdata()/exi_get_drvdata() are available for the +sole purpose of setting and getting driver private data for an EXI device. +These are simply helpers around the driver-model functions that do the +actual work. Use them. That way, you don't have to worry (or worry less) +about changes to the driver-model API. diff --git a/Documentation/powerpc/dts-bindings/gpio/i2c.txt b/Documentation/powerpc/dts-bindings/gpio/i2c.txt new file mode 100644 index 0000000..8a8cd98 --- /dev/null +++ b/Documentation/powerpc/dts-bindings/gpio/i2c.txt @@ -0,0 +1,42 @@ +GPIO-based I2C + +Required properties: +- compatible : should be "virtual,i2c-gpio". +- gpios : should specify GPIOs used for SDA and SCL lines, in that order. +- sda-is-open-drain : should be non-zero if SDA gpio is open-drain. +- sda-enforce-dir : should be non-zero if SDA gpio must be configured for + input before reading and for output before writing. +- scl-is-open-drain : should be non-zero if SCL gpio is open-drain. +- scl-is-output-only : should be non-zero if SCL is an output gpio only. +- udelay : signal toggle delay. SCL frequency is (500 / udelay) kHz +- timeout : clock stretching timeout in milliseconds. + +Example: + +gpio0: starlet-gpio@0d8000c0 { + compatible = "nintendo,starlet-gpio"; + reg = <0d8000c0 4>; + gpio-controller; + #gpio-cells = <2>; +}; + +i2c-video { + #address-cells = <1>; + #size-cells = <0>; + compatible = "virtual,i2c-gpio"; + + gpios = <&gpio0 10 0 /* SDA line */ + &gpio0 11 0 /* SCL line */ + >; + sda-is-open-drain = <1>; + sda-enforce-dir = <1>; + scl-is-open-drain = <1>; + scl-is-output-only = <1>; + udelay = <2>; + + audio-video-encoder { + compatible = "nintendo,wii-ave-rvl"; + reg = <70>; + }; +}; + diff --git a/arch/powerpc/Kconfig b/arch/powerpc/Kconfig index 2ba14e7..84b2566 100644 --- a/arch/powerpc/Kconfig +++ b/arch/powerpc/Kconfig @@ -679,7 +679,7 @@ config PPC_PCI_CHOICE config PCI bool "PCI support" if PPC_PCI_CHOICE default y if !40x && !CPM2 && !8xx && !PPC_83xx \ - && !PPC_85xx && !PPC_86xx + && !PPC_85xx && !PPC_86xx && !GAMECUBE_COMMON default PCI_PERMEDIA if !4xx && !CPM2 && !8xx default PCI_QSPAN if !4xx && !CPM2 && 8xx select ARCH_SUPPORTS_MSI diff --git a/arch/powerpc/Kconfig.debug b/arch/powerpc/Kconfig.debug index 3b10051..11e385b 100644 --- a/arch/powerpc/Kconfig.debug +++ b/arch/powerpc/Kconfig.debug @@ -254,6 +254,14 @@ config PPC_EARLY_DEBUG_CPM using a CPM-based serial port. This assumes that the bootwrapper has run, and set up the CPM in a particular way. +config PPC_EARLY_DEBUG_USBGECKO + bool "Early debugging through the USB Gecko adapter" + depends on GAMECUBE_COMMON + select USBGECKO_UDBG + help + Select this to enable early debugging for Nintendo GameCube/Wii + consoles via an external USB Gecko adapter. + endchoice config PPC_EARLY_DEBUG_44x_PHYSLOW diff --git a/arch/powerpc/boot/Makefile b/arch/powerpc/boot/Makefile index 7bfc8ad..fbaf101 100644 --- a/arch/powerpc/boot/Makefile +++ b/arch/powerpc/boot/Makefile @@ -66,7 +66,7 @@ src-wlib := string.S crt0.S crtsavres.S stdio.c main.c \ gunzip_util.c elf_util.c $(zlib) devtree.c oflib.c ofconsole.c \ 4xx.c ebony.c mv64x60.c mpsc.c mv64x60_i2c.c cuboot.c bamboo.c \ cpm-serial.c stdlib.c mpc52xx-psc.c planetcore.c uartlite.c \ - fsl-soc.c mpc8xx.c pq2.c + fsl-soc.c mpc8xx.c pq2.c ugecon.c src-plat := of.c cuboot-52xx.c cuboot-824x.c cuboot-83xx.c cuboot-85xx.c holly.c \ cuboot-ebony.c cuboot-hotfoot.c treeboot-ebony.c prpmc2800.c \ ps3-head.S ps3-hvcall.S ps3.c treeboot-bamboo.c cuboot-8xx.c \ @@ -76,7 +76,8 @@ src-plat := of.c cuboot-52xx.c cuboot-824x.c cuboot-83xx.c cuboot-85xx.c holly.c cuboot-katmai.c cuboot-rainier.c redboot-8xx.c ep8248e.c \ cuboot-warp.c cuboot-85xx-cpm2.c cuboot-yosemite.c simpleboot.c \ virtex405-head.S virtex.c redboot-83xx.c cuboot-sam440ep.c \ - cuboot-acadia.c cuboot-amigaone.c cuboot-kilauea.c + cuboot-acadia.c cuboot-amigaone.c cuboot-kilauea.c \ + gamecube.c wii.c src-boot := $(src-wlib) $(src-plat) empty.c src-boot := $(addprefix $(obj)/, $(src-boot)) @@ -254,6 +255,8 @@ image-$(CONFIG_KSI8560) += cuImage.ksi8560 image-$(CONFIG_STORCENTER) += cuImage.storcenter image-$(CONFIG_MPC7448HPC2) += cuImage.mpc7448hpc2 image-$(CONFIG_PPC_C2K) += cuImage.c2k +image-$(CONFIG_GAMECUBE) += dtbImage.gamecube +image-$(CONFIG_WII) += dtbImage.wii # Board port in arch/powerpc/platform/amigaone/Kconfig image-$(CONFIG_AMIGAONE) += cuImage.amigaone diff --git a/arch/powerpc/boot/dts/gamecube.dts b/arch/powerpc/boot/dts/gamecube.dts new file mode 100644 index 0000000..aa94173 --- /dev/null +++ b/arch/powerpc/boot/dts/gamecube.dts @@ -0,0 +1,135 @@ +/* + * arch/powerpc/boot/dts/gamecube.dts + * + * Nintendo GameCube platform device tree source + * Copyright (C) 2007-2009 The GameCube Linux Team + * Copyright (C) 2007,2008,2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +/dts-v1/; + +/ { + model = "NintendoGameCube"; + compatible = "nintendo,gamecube"; + #address-cells = <1>; + #size-cells = <1>; + + chosen { + bootargs = "root=/dev/nfs nfsroot=192.168.001.253:/nfsroot/cube,nfsvers=3,udp ip=on video=gcn-vifb:tv=auto force_keyboard_port=4"; + linux,stdout-path = &USBGECKO0; + }; + + aliases { + ugecon = &USBGECKO0; + }; + + memory { + device_type = "memory"; + /* 24M minus framebuffer memory area (640*576*2*2) */ + reg = <0x00000000 0x01698000>; + }; + + cpus { + #address-cells = <1>; + #size-cells = <0>; + + PowerPC,gekko@0 { + device_type = "cpu"; + reg = <0>; + clock-frequency = <486000000>; /* 486MHz */ + bus-frequency = <162000000>; /* 162MHz core-to-bus 3x */ + timebase-frequency = <40500000>; /* 162MHz / 4 */ + /* Following required by dtc but not used */ + i-cache-line-size = <32>; + d-cache-line-size = <32>; + i-cache-size = <32768>; + d-cache-size = <32768>; + }; + }; + + /* devices contained int the flipper chipset */ + soc { + #address-cells = <1>; + #size-cells = <1>; + #interrupt-cells = <1>; + model = "flipper"; + compatible = "nintendo,flipper"; + clock-frequency = <162000000>; /* 162MHz */ + ranges = <0x0c000000 0x0c000000 0x00010000>; + + video@0c002000 { + compatible = "nintendo,flipper-video"; + reg = <0x0c002000 0x100>; + interrupts = <8>; + interrupt-parent = <&pic>; + xfb-start = <0x01698000>; /* end-of-ram - xfb-size */ + xfb-size = <0x168000>; + }; + + pic: pic@0c003000 { + #interrupt-cells = <1>; + compatible = "nintendo,flipper-pic"; + reg = <0x0c003000 0x8>; + interrupt-controller; + }; + + resetswitch@0c003000 { + compatible = "nintendo,flipper-resetswitch"; + reg = <0x0c003000 0x4>; + interrupts = <1>; + interrupt-parent = <&pic>; + }; + + auxram@0c005000 { + compatible = "nintendo,flipper-auxram"; + reg = <0x0c005000 0x200>; /* DSP */ + interrupts = <6>; + interrupt-parent = <&pic>; + }; + + audio@0c005000 { + compatible = "nintendo,flipper-audio"; + reg = <0x0c005000 0x200 /* DSP */ + 0x0c006c00 0x20>; /* AI */ + interrupts = <6>; + interrupt-parent = <&pic>; + }; + + disk@0c006000 { + compatible = "nintendo,flipper-disk"; + reg = <0x0c006000 0x40>; + interrupts = <2>; + interrupt-parent = <&pic>; + }; + + serial@0c006400 { + compatible = "nintendo,flipper-serial"; + reg = <0x0c006400 0x100>; + interrupts = <3>; + interrupt-parent = <&pic>; + }; + + /* External Interface bus */ + exi@0c006800 { + #address-cells = <1>; + #size-cells = <1>; + compatible = "nintendo,flipper-exi"; + reg = <0x0c006800 0x40>; + interrupts = <4>; + interrupt-parent = <&pic>; + + USBGECKO0: usbgecko@0c006814 { + compatible = "usbgecko,usbgecko"; + reg = <0x0c006814 0x14>; + virtual-reg = <0xcc006814>; + }; + }; + }; +}; + diff --git a/arch/powerpc/boot/dts/wii.dts b/arch/powerpc/boot/dts/wii.dts new file mode 100644 index 0000000..2095f2b --- /dev/null +++ b/arch/powerpc/boot/dts/wii.dts @@ -0,0 +1,262 @@ +/* + * arch/powerpc/boot/dts/wii.dts + * + * Nintendo Wii platform device tree source + * Copyright (C) 2008-2009 The GameCube Linux Team + * Copyright (C) 2008,2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +/dts-v1/; + +/memreserve/ 0x01694000 0x0004000; /* lowmem stub save area */ +/memreserve/ 0x01698000 0x0168000; /* framebuffer, see video@0c002000 */ +/memreserve/ 0x01800000 0xe800000; /* memory hole */ +/memreserve/ 0x10000000 0x0004000; /* DSP */ + +/ { + model = "NintendoWii"; + compatible = "nintendo,wii"; + #address-cells = <1>; + #size-cells = <1>; + + chosen { + /* ramdisk */ + /* bootargs = "nobats root=/dev/ram0 video=gcnfb ip=on force_keyboard_port=4"; */ + + /* nfsroot */ + /* bootargs = "nobats root=/dev/nfs nfsroot=192.168.001.253:/nfsroot/cube ip=on video=gcnfb:tv=NTSC force_keyboard_port=4"; */ + + /* root filesystem on 2nd partition of SD card, whiite style */ + bootargs = "nobats root=/dev/mmcblk0p2 rootwait force_keyboard_port=4 video=gcnfb:60Hz,tv=NTSC placeholder_for_additional_kernel_options_targetted_at_hexedit_lovers"; + linux,stdout-path = &USBGECKO0; + }; + + aliases { + ugecon = &USBGECKO0; + }; + + lowmem-stub { + reg = <0x00000000 0x4000>; + save-area = <0x01694000>; /* must be memreserve'd */ + }; + + memory { + device_type = "memory"; + /* mem1 + hole + mem2 - ioh */ + reg = <0x00000000 0x133e0000>; + }; + + cpus { + #cpus = <1>; + #address-cells = <1>; + #size-cells = <0>; + + PowerPC,broadway@0 { + device_type = "cpu"; + reg = <0>; + clock-frequency = <729000000>; /* 729MHz */ + bus-frequency = <243000000>; /* 243MHz core-to-bus 3x */ + timebase-frequency = <60750000>; /* 243MHz / 4 */ + /* Following required by dtc but not used */ + i-cache-line-size = <32>; + d-cache-line-size = <32>; + i-cache-size = <32768>; + d-cache-size = <32768>; + }; + }; + + /* devices contained in the hollywood chipset */ + soc { + #address-cells = <1>; + #size-cells = <1>; + #interrupt-cells = <1>; + model = "hollywood"; + compatible = "nintendo,hollywood"; + clock-frequency = <243000000>; /* 243MHz */ + ranges = <0x0c000000 0x0c000000 0x00010000 + 0x0d000000 0x0d000000 0x00010000 + 0x0d040000 0x0d040000 0x00050000 + 0x0d800000 0x0d800000 0x00007000 + 0x133e0000 0x133e0000 0x00c20000>; + + video@0c002000 { + compatible = "nintendo,hollywood-video"; + reg = <0x0c002000 0x100>; + interrupts = <8>; + interrupt-parent = <&PIC0>; + xfb-start = <0x01698000>; /* end-of-mem1 - xfb-size */ + xfb-size = <0x168000>; /* 640x576x2 x 2 bytes */ + }; + + PIC0: pic0@0c003000 { + #interrupt-cells = <1>; + compatible = "nintendo,flipper-pic"; + reg = <0x0c003000 0x8>; + interrupt-controller; + }; + + resetswitch@0c003000 { + compatible = "nintendo,hollywood-resetswitch"; + reg = <0x0c003000 0x4>; + interrupts = <1>; + interrupt-parent = <&PIC0>; + }; + + audio@0c005000 { + compatible = "nintendo,hollywood-audio"; + reg = <0x0c005000 0x200 /* DSP */ + 0x0d006c00 0x20>; /* AI */ + interrupts = <6>; + interrupt-parent = <&PIC0>; + }; + + serial@0d006400 { + compatible = "nintendo,hollywood-serial"; + reg = <0x0d006400 0x100>; + interrupts = <3>; + interrupt-parent = <&PIC0>; + }; + + /* External Interface bus */ + exi@0d006800 { + #address-cells = <1>; + #size-cells = <1>; + compatible = "nintendo,hollywood-exi"; + reg = <0x0d006800 0x40>; + interrupts = <4>; + interrupt-parent = <&PIC0>; + + USBGECKO0: usbgecko@0d006814 { + compatible = "usbgecko,usbgecko"; + reg = <0x0d006814 0x14>; + virtual-reg = <0xcd006814>; + }; + }; + + /* + * Firmware interfaces accessible through Nintendo IOS IPC. + */ + ios@0d000000 { + compatible = "nintendo,starlet-ios-ipc"; + reg = <0x0d000000 0x40 /* IPC */ + 0x133e0000 0x20000>; /* MEM2 ioh 128K */ + interrupts = <14>; + interrupt-parent = <&PIC0>; + + starlet-ios-es { + compatible = "nintendo,starlet-ios-es"; + }; + + starlet-ios-sd { + compatible = "nintendo,starlet-ios-sd"; + }; + + starlet-ios-hcd { + compatible = "nintendo,starlet-ios-hcd"; + }; + + starlet-ios-keyboard { + compatible = "nintendo,starlet-ios-keyboard"; + }; + }; + + /* + * Hardware accessible through Team Twiizers 'mini' firmware + * replacement for Starlet. + */ + mini@0d000000 { + #address-cells = <1>; + #size-cells = <1>; + #interrupt-cells = <1>; + compatible = "twiizers,starlet-mini-ipc"; + reg = <0x0d000000 0x40 /* IPC */ + 0x13fffffc 0x4>; /* mini header pointer */ + + ranges = <0x0d040000 0x0d040000 0x00050000 + 0x0d800000 0x0d800000 0x00007000 + 0x133e0000 0x133e0000 0x00c20000>; + + PIC1: pic1@0d800030 { + #interrupt-cells = <1>; + compatible = "nintendo,hollywood-pic"; + reg = <0x0d800030 0x8>; + interrupt-controller; + interrupts = <14>; + interrupt-parent = <&PIC0>; + }; + disk@0d806000 { + compatible = "nintendo,hollywood-disk"; + reg = <0x0d806000 0x40>; + interrupts = <2>; + interrupt-parent = <&PIC0>; + }; + ehci@0d040000 { + compatible = "nintendo,hollywood-ehci"; + reg = <0x0d040000 0x100 + 0x133e0000 0x80000>; /* 512 KB */ + interrupts = <4>; + interrupt-parent = <&PIC1>; + }; + ohci0@0d050000 { + compatible = "nintendo,hollywood-ohci"; + reg = <0x0d050000 0x100 + 0x13460000 0x80000>; /* 512 KB */ + interrupts = <5>; + interrupt-parent = <&PIC1>; + }; + ohci1@0d060000 { + compatible = "nintendo,hollywood-ohci"; + reg = <0x0d060000 0x100 + 0x134e0000 0x80000>; /* 512 KB */ + interrupts = <6>; + interrupt-parent = <&PIC1>; + }; + sdhc0@0d070000 { + compatible = "nintendo,hollywood-sdhci"; + reg = <0x0d070000 0x200>; + interrupts = <7>; + interrupt-parent = <&PIC1>; + }; + sdhc1@0d080000 { + compatible = "nintendo,hollywood-sdhci"; + reg = <0x0d080000 0x200>; + interrupts = <8>; + interrupt-parent = <&PIC1>; + }; + }; + + gpio0: hollywood-gpio@0d8000c0 { + compatible = "nintendo,hollywood-gpio"; + reg = <0x0d8000c0 0x4>; + gpio-controller; + #gpio-cells = <2>; + }; + + i2c-video { + #address-cells = <1>; + #size-cells = <0>; + compatible = "virtual,i2c-gpio"; + + gpios = <&gpio0 16 0 /* 31-15 */ + &gpio0 17 0 /* 31-14 */ + >; + sda-is-open-drain = <1>; + sda-enforce-dir = <1>; + scl-is-open-drain = <1>; + scl-is-output-only = <1>; + udelay = <2>; + + audio-video-encoder { + compatible = "nintendo,wii-ave-rvl"; + reg = <0x70>; + }; + }; + }; +}; + diff --git a/arch/powerpc/boot/gamecube.c b/arch/powerpc/boot/gamecube.c new file mode 100644 index 0000000..f3e3c0d --- /dev/null +++ b/arch/powerpc/boot/gamecube.c @@ -0,0 +1,77 @@ +/* + * arch/powerpc/boot/gamecube.c + * + * Nintendo GameCube/Wii platforms + * Copyright (C) 2004-2009 The GameCube Linux Team + * Copyright (C) 2008,2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#include +#include "stdio.h" +#include "types.h" +#include "io.h" +#include "ops.h" + +#include "ugecon.h" + +BSS_STACK(8192); + +/* + * We enter with the MMU enabled and some legacy memory mappings active. + * + * We leave the MMU enabled, but we switch to an identity mapped memory + * scheme as expected by the start code. + * + */ +asm ("\n\ +.text\n\ +.globl _zimage_start\n\ +_zimage_start:\n\ +\n\ + isync\n\ + /* IBAT3,DBAT3 for first 16Mbytes */\n\ + li 8, 0x01ff /* 16MB */\n\ + li 9, 0x0002 /* rw */\n\ + mtspr 0x216, 8 /* IBAT3U */\n\ + mtspr 0x217, 9 /* IBAT3L */\n\ + mtspr 0x21e, 8 /* DBAT3U */\n\ + mtspr 0x21f, 9 /* DBAT3L */\n\ +\n\ + sync\n\ + isync\n\ +\n\ + li 3, 0\n\ + li 4, 0\n\ + li 5, 0\n\ +\n\ + bcl- 20,4*cr7+so,1f\n\ +1:\n\ + mflr 8\n\ + clrlwi 8, 8, 3\n\ + addi 8, 8, 2f - 1b\n\ + mtlr 8\n\ + blr\n\ +2:\n\ + b _zimage_start_lib\n\ +"); + +/* + * + */ +void platform_init(unsigned long r3, unsigned long r4, unsigned long r5) +{ + u32 heapsize = 16*1024*1024 - (u32)_end; + + simple_alloc_init(_end, heapsize, 32, 64); + fdt_init(_dtb_start); + + if (!ug_grab_io_base() && ug_is_adapter_present()) + console_ops.write = ug_console_write; +} + diff --git a/arch/powerpc/boot/ugecon.c b/arch/powerpc/boot/ugecon.c new file mode 100644 index 0000000..704f374 --- /dev/null +++ b/arch/powerpc/boot/ugecon.c @@ -0,0 +1,128 @@ +/* + * arch/powerpc/boot/ugecon.c + * + * USB Gecko bootwrapper console. + * Copyright (C) 2008-2009 The GameCube Linux Team + * Copyright (C) 2008,2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#include +#include "stdio.h" +#include "types.h" +#include "io.h" +#include "ops.h" + + +#define EXI_CLK_32MHZ 5 + +#define EXI_CSR 0x00 +#define EXI_CSR_CLKMASK (0x7<<4) +#define EXI_CSR_CLK_32MHZ (EXI_CLK_32MHZ<<4) +#define EXI_CSR_CSMASK (0x7<<7) +#define EXI_CSR_CS_0 (0x1<<7) /* Chip Select 001 */ + +#define EXI_CR 0x0c +#define EXI_CR_TSTART (1<<0) +#define EXI_CR_WRITE (1<<2) +#define EXI_CR_READ_WRITE (2<<2) +#define EXI_CR_TLEN(len) (((len)-1)<<4) + +#define EXI_DATA 0x10 + + +/* virtual address base for input/output, retrieved from device tree */ +static void *ug_io_base; + + +static u32 ug_io_transaction(u32 in) +{ + u32 *csr_reg = ug_io_base + EXI_CSR; + u32 *data_reg = ug_io_base + EXI_DATA; + u32 *cr_reg = ug_io_base + EXI_CR; + u32 csr, data, cr; + + /* select */ + csr = EXI_CSR_CLK_32MHZ | EXI_CSR_CS_0; + out_be32(csr_reg, csr); + + /* read/write */ + data = in; + out_be32(data_reg, data); + cr = EXI_CR_TLEN(2) | EXI_CR_READ_WRITE | EXI_CR_TSTART; + out_be32(cr_reg, cr); + + while (in_be32(cr_reg) & EXI_CR_TSTART) + barrier(); + + /* deselect */ + out_be32(csr_reg, 0); + + data = in_be32(data_reg); + return data; +} + +static int ug_is_txfifo_ready(void) +{ + return ug_io_transaction(0xc0000000) & 0x04000000; +} + +static void ug_raw_putc(char ch) +{ + ug_io_transaction(0xb0000000 | (ch << 20)); +} + +static void ug_putc(char ch) +{ + int count = 16; + + if (!ug_io_base) + return; + + while (!ug_is_txfifo_ready() && count--) + barrier(); + if (count) + ug_raw_putc(ch); +} + +void ug_console_write(const char *buf, int len) +{ + char *b = (char *)buf; + + while (len--) { + if (*b == '\n') + ug_putc('\r'); + ug_putc(*b++); + } +} + +int ug_is_adapter_present(void) +{ + if (!ug_io_base) + return 0; + + return ug_io_transaction(0x90000000) == 0x04700000; +} + +int ug_grab_io_base(void) +{ + u32 v; + void *devp; + + devp = find_node_by_alias("ugecon"); + if (devp == NULL) + goto err_out; + if (getprop(devp, "virtual-reg", &v, sizeof(v)) != sizeof(v)) + goto err_out; + + ug_io_base = (u8 *)v; + return 0; + +err_out: + return -1; +} diff --git a/arch/powerpc/boot/ugecon.h b/arch/powerpc/boot/ugecon.h new file mode 100644 index 0000000..1fdb590 --- /dev/null +++ b/arch/powerpc/boot/ugecon.h @@ -0,0 +1,25 @@ +/* + * arch/powerpc/boot/ugecon.h + * + * USB Gecko early bootwrapper console. + * Copyright (C) 2008-2009 The GameCube Linux Team + * Copyright (C) 2008,2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#ifndef __UGECON_H +#define __UGECON_H + +extern int ug_grab_io_base(void); +extern int ug_is_adapter_present(void); + +extern void ug_putc(char ch); +extern void ug_console_write(const char *buf, int len); + +#endif /* __UGECON_H */ + diff --git a/arch/powerpc/boot/wii.c b/arch/powerpc/boot/wii.c new file mode 100644 index 0000000..e1c53de --- /dev/null +++ b/arch/powerpc/boot/wii.c @@ -0,0 +1,313 @@ +/* + * arch/powerpc/boot/wii.c + * + * Nintendo Wii platform + * Copyright (C) 2004-2009 The GameCube Linux Team + * Copyright (C) 2008,2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#include +#include "stdio.h" +#include "types.h" +#include "io.h" +#include "ops.h" + +#include "ugecon.h" + + +BSS_STACK(8192); + +/* + * We enter with the MMU enabled and some legacy memory mappings active. + * + * We leave the MMU enabled, but we switch to an identity mapped memory + * scheme as expected by the start code. + * + */ +asm ("\n\ +.text\n\ +.globl _zimage_start\n\ +_zimage_start:\n\ +\n\ + mfmsr 9\n\ + andi. 0, 9, (1<<4)|(1<<5) /* MSR_DR|MSR_IR */\n\ + bcl 20, 31, 1f\n\ +1: \n\ + mflr 8\n\ + clrlwi 8, 8, 3 /* convert to a real address */\n\ + addi 8, 8, _mmu_off - 1b\n\ + andc 9, 9, 0\n\ + mtspr 0x01a, 8 /* SRR0 */\n\ + mtspr 0x01b, 9 /* SRR1 */\n\ + sync\n\ + rfi\n\ +_mmu_off: \n\ + /* MMU disabled */\n\ +\n\ + /* Setup BATs */\n\ + isync\n\ + li 8, 0\n\ + mtspr 0x210, 8 /* IBAT0U */\n\ + mtspr 0x211, 8 /* IBAT0L */\n\ + mtspr 0x212, 8 /* IBAT1U */\n\ + mtspr 0x213, 8 /* IBAT1L */\n\ + mtspr 0x214, 8 /* IBAT2U */\n\ + mtspr 0x215, 8 /* IBAT2L */\n\ + mtspr 0x216, 8 /* IBAT3U */\n\ + mtspr 0x217, 8 /* IBAT3L */\n\ + mtspr 0x218, 8 /* DBAT0U */\n\ + mtspr 0x219, 8 /* DBAT0L */\n\ + mtspr 0x21a, 8 /* DBAT1U */\n\ + mtspr 0x21b, 8 /* DBAT1L */\n\ + mtspr 0x21c, 8 /* DBAT2U */\n\ + mtspr 0x21d, 8 /* DBAT2L */\n\ + mtspr 0x21e, 8 /* DBAT3U */\n\ + mtspr 0x21f, 8 /* DBAT3L */\n\ +\n\ + mtspr 0x230, 8 /* IBAT4U */\n\ + mtspr 0x231, 8 /* IBAT4L */\n\ + mtspr 0x232, 8 /* IBAT5U */\n\ + mtspr 0x233, 8 /* IBAT5L */\n\ + mtspr 0x234, 8 /* IBAT6U */\n\ + mtspr 0x235, 8 /* IBAT6L */\n\ + mtspr 0x236, 8 /* IBAT7U */\n\ + mtspr 0x237, 8 /* IBAT7L */\n\ + mtspr 0x238, 8 /* DBAT4U */\n\ + mtspr 0x239, 8 /* DBAT4L */\n\ + mtspr 0x23a, 8 /* DBAT5U */\n\ + mtspr 0x23b, 8 /* DBAT5L */\n\ + mtspr 0x23c, 8 /* DBAT6U */\n\ + mtspr 0x23d, 8 /* DBAT6L */\n\ + mtspr 0x23e, 8 /* DBAT7U */\n\ + mtspr 0x23f, 8 /* DBAT7L */\n\ +\n\ + isync\n\ + li 8, 0x01ff /* first 16MiB */\n\ + li 9, 0x0002 /* rw */\n\ + mtspr 0x210, 8 /* IBAT0U */\n\ + mtspr 0x211, 9 /* IBAT0L */\n\ + mtspr 0x218, 8 /* DBAT0U */\n\ + mtspr 0x219, 9 /* DBAT0L */\n\ +\n\ + lis 8, 0xcc00 /* I/O mem */\n\ + ori 8, 8, 0x3ff /* 32MiB */\n\ + lis 9, 0x0c00\n\ + ori 9, 9, 0x002a /* uncached, guarded, rw */\n\ + mtspr 0x21a, 8 /* DBAT1U */\n\ + mtspr 0x21b, 9 /* DBAT1L */\n\ +\n\ + lis 8, 0x0100 /* next 8MiB */\n\ + ori 8, 8, 0x00ff /* 8MiB */\n\ + lis 9, 0x0100 /* next 8MiB */\n\ + ori 9, 9, 0x0002 /* rw */\n\ + mtspr 0x214, 8 /* IBAT2U */\n\ + mtspr 0x215, 9 /* IBAT2L */\n\ + mtspr 0x21c, 8 /* DBAT2U */\n\ + mtspr 0x21d, 9 /* DBAT2L */\n\ +\n\ + lis 8, 0x1000 /* MEM2 */\n\ + ori 8, 8, 0x07ff /* 64MiB */\n\ + lis 9, 0x1000\n\ + ori 9, 9, 0x0002 /* rw */\n\ + mtspr 0x216, 8 /* IBAT3U */\n\ + mtspr 0x217, 9 /* IBAT3L */\n\ + mtspr 0x21e, 8 /* DBAT3U */\n\ + mtspr 0x21f, 9 /* DBAT3L */\n\ +\n\ + sync\n\ + isync\n\ +\n\ + /* enable high BATs */\n\ + lis 8, 0x8200\n\ + mtspr 0x3f3, 8 /* HID4 */\n\ +\n\ + /* enable caches */\n\ + mfspr 8, 0x3f0\n\ + ori 8, 8, 0xc000\n\ + mtspr 0x3f0, 8 /* HID0 */\n\ + isync\n\ +\n\ + li 3, 0\n\ + li 4, 0\n\ + li 5, 0\n\ +\n\ + bcl 20, 31, 1f\n\ +1: \n\ + mflr 8\n\ + addi 8, 8, _mmu_on - 1b\n\ + mfmsr 9\n\ + ori 9, 9, (1<<4)|(1<<5) /* MSR_DR|MSR_IR */\n\ + mtspr 0x01a, 8 /* SRR0 */\n\ + mtspr 0x01b, 9 /* SRR1 */\n\ + sync\n\ + rfi\n\ +_mmu_on: \n\ + /* turn on the front blue led (aka: yay! we got here!) */\n\ + lis 8, 0xcd00\n\ + ori 8, 8, 0x00c0\n\ + lwz 9, 0(8)\n\ + ori 9, 9, 0x20\n\ + stw 9, 0(8)\n\ + b _zimage_start_lib\n\ +"); + +static int save_lowmem_stub(void) +{ + void *src, *dst; + size_t size; + u32 reg[2]; + u32 v; + void *devp; + + devp = finddevice("/lowmem-stub"); + if (devp == NULL) { + printf("lowmem-stub: none\n"); + goto err_out; + } + + if (getprop(devp, "reg", reg, sizeof(reg)) != sizeof(reg)) { + printf("unable to find %s property\n", "reg"); + goto err_out; + } + src = (void *)reg[0]; + size = reg[1]; + if (getprop(devp, "save-area", &v, sizeof(v)) != sizeof(v)) { + printf("unable to find %s property\n", "save-area"); + goto err_out; + } + dst = (void *)v; + + printf("lowmem-stub: relocating from %08lX to %08lX (%u bytes)\n", + (unsigned long)src, (unsigned long)dst, size); + memcpy(dst, src, size); + flush_cache(dst, size); + + return 0; +err_out: + return -1; +} + +/* + * + * + */ + +struct mipc_infohdr { + char magic[3]; + u8 version; + u32 mem2_boundary; + u32 ipc_in; + size_t ipc_in_size; + u32 ipc_out; + size_t ipc_out_size; +}; + +static int mipc_check_address(u32 pa) +{ + if (pa < 0x10000000 || pa > 0x14000000) + return -EINVAL; + return 0; +} + +static void platform_fixups(void) +{ + struct mipc_infohdr **hdrp, *hdr; + u32 reg[4]; + u32 mem2_boundary, top; + void *devp; + + /* + * The mini header pointer is specified in the second "reg" entry + * of the starlet-mini-ipc node. + */ + devp = find_node_by_compatible(NULL, "twiizers,starlet-mini-ipc"); + if (!devp) { + printf("unable to find %s node\n", "twiizers,starlet-mini-ipc"); + goto err_out; + } + if (getprop(devp, "reg", ®, sizeof(reg)) != sizeof(reg)) { + printf("unable to find %s property\n", "reg"); + goto err_out; + } + hdrp = (struct mipc_infohdr **)reg[2]; + if (mipc_check_address((u32)hdrp)) { + printf("mini: invalid hdrp %08X\n", (u32)hdrp); + goto err_out; + } + + hdr = *hdrp; + if (mipc_check_address((u32)hdr)) { + printf("mini: invalid hdr %08X\n", (u32)hdr); + goto err_out; + } + if (memcmp(hdr->magic, "IPC", 3)) { + printf("mini: invalid magic, asuming ios\n"); + goto err_out; + } + mem2_boundary = hdr->mem2_boundary; + if (mipc_check_address(mem2_boundary)) { + printf("mini: invalid mem2_boundary %08X\n", mem2_boundary); + goto err_out; + } + + top = mem2_boundary; + printf("top of mem @ %08X (%s)\n", top, "current"); + + /* fixup local memory for EHCI controller */ + devp = NULL; + while ((devp = find_node_by_compatible(devp, + "nintendo,hollywood-ehci"))) { + if (getprop(devp, "reg", ®, sizeof(reg)) == sizeof(reg)) { + top -= reg[3]; + printf("ehci %08X -> %08X\n", reg[2], top); + reg[2] = top; + setprop(devp, "reg", ®, sizeof(reg)); + } + } + + /* fixup local memory for OHCI controllers */ + devp = NULL; + while ((devp = find_node_by_compatible(devp, + "nintendo,hollywood-ohci"))) { + if (getprop(devp, "reg", ®, sizeof(reg)) == sizeof(reg)) { + top -= reg[3]; + printf("ohci %08X -> %08X\n", reg[2], top); + reg[2] = top; + setprop(devp, "reg", ®, sizeof(reg)); + } + } + + /* fixup available memory */ + dt_fixup_memory(0, top); + + printf("top of mem @ %08X (%s)\n", top, "final"); + + return; + +err_out: + return; +} + +/* + * + */ +void platform_init(unsigned long r3, unsigned long r4, unsigned long r5) +{ + u32 heapsize = 24*1024*1024 - (u32)_end; + + simple_alloc_init(_end, heapsize, 32, 64); + fdt_init(_dtb_start); + + if (!ug_grab_io_base() && ug_is_adapter_present()) + console_ops.write = ug_console_write; + + platform_ops.fixups = platform_fixups; + save_lowmem_stub(); +} + diff --git a/arch/powerpc/boot/wrapper b/arch/powerpc/boot/wrapper index ac9e9a5..0e9ce10 100755 --- a/arch/powerpc/boot/wrapper +++ b/arch/powerpc/boot/wrapper @@ -230,6 +230,9 @@ xpedite52*) link_address='0x1400000' platformo=$object/cuboot-85xx.o ;; +wii|gamecube) + link_address='0x600000' + ;; esac vmz="$tmpdir/`basename \"$kernel\"`.$ext" diff --git a/arch/powerpc/configs/gamecube_defconfig b/arch/powerpc/configs/gamecube_defconfig new file mode 100644 index 0000000..25d4446 --- /dev/null +++ b/arch/powerpc/configs/gamecube_defconfig @@ -0,0 +1,1083 @@ +# +# Automatically generated make config: don't edit +# Linux kernel version: 2.6.32 +# Sun Dec 6 20:15:08 2009 +# +# CONFIG_PPC64 is not set + +# +# Processor support +# +CONFIG_PPC_BOOK3S_32=y +# CONFIG_PPC_85xx is not set +# CONFIG_PPC_8xx is not set +# CONFIG_40x is not set +# CONFIG_44x is not set +# CONFIG_E200 is not set +CONFIG_PPC_BOOK3S=y +CONFIG_6xx=y +CONFIG_PPC_FPU=y +# CONFIG_ALTIVEC is not set +CONFIG_PPC_STD_MMU=y +CONFIG_PPC_STD_MMU_32=y +# CONFIG_PPC_MM_SLICES is not set +CONFIG_PPC_HAVE_PMU_SUPPORT=y +CONFIG_PPC_PERF_CTRS=y +# CONFIG_SMP is not set +CONFIG_NOT_COHERENT_CACHE=y +CONFIG_PPC32=y +CONFIG_WORD_SIZE=32 +# CONFIG_ARCH_PHYS_ADDR_T_64BIT is not set +CONFIG_MMU=y +CONFIG_GENERIC_CMOS_UPDATE=y +CONFIG_GENERIC_TIME=y +CONFIG_GENERIC_TIME_VSYSCALL=y +CONFIG_GENERIC_CLOCKEVENTS=y +CONFIG_GENERIC_HARDIRQS=y +CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ=y +# CONFIG_HAVE_SETUP_PER_CPU_AREA is not set +# CONFIG_NEED_PER_CPU_EMBED_FIRST_CHUNK is not set +CONFIG_IRQ_PER_CPU=y +CONFIG_STACKTRACE_SUPPORT=y +CONFIG_HAVE_LATENCYTOP_SUPPORT=y +CONFIG_TRACE_IRQFLAGS_SUPPORT=y +CONFIG_LOCKDEP_SUPPORT=y +CONFIG_RWSEM_XCHGADD_ALGORITHM=y +CONFIG_ARCH_HAS_ILOG2_U32=y +CONFIG_GENERIC_HWEIGHT=y +CONFIG_GENERIC_FIND_NEXT_BIT=y +# CONFIG_ARCH_NO_VIRT_TO_BUS is not set +CONFIG_PPC=y +CONFIG_EARLY_PRINTK=y +CONFIG_GENERIC_NVRAM=y +CONFIG_SCHED_OMIT_FRAME_POINTER=y +CONFIG_ARCH_MAY_HAVE_PC_FDC=y +CONFIG_PPC_OF=y +CONFIG_OF=y +# CONFIG_PPC_UDBG_16550 is not set +# CONFIG_GENERIC_TBSYNC is not set +CONFIG_AUDIT_ARCH=y +CONFIG_GENERIC_BUG=y +CONFIG_DTC=y +# CONFIG_DEFAULT_UIMAGE is not set +# CONFIG_PPC_DCR_NATIVE is not set +# CONFIG_PPC_DCR_MMIO is not set +CONFIG_ARCH_SUPPORTS_DEBUG_PAGEALLOC=y +CONFIG_DEFCONFIG_LIST="/lib/modules/$UNAME_RELEASE/.config" +CONFIG_CONSTRUCTORS=y + +# +# General setup +# +CONFIG_EXPERIMENTAL=y +CONFIG_BROKEN_ON_SMP=y +CONFIG_LOCK_KERNEL=y +CONFIG_INIT_ENV_ARG_LIMIT=32 +CONFIG_LOCALVERSION="-isobel-gcn" +CONFIG_LOCALVERSION_AUTO=y +CONFIG_SWAP=y +CONFIG_SYSVIPC=y +CONFIG_SYSVIPC_SYSCTL=y +# CONFIG_POSIX_MQUEUE is not set +# CONFIG_BSD_PROCESS_ACCT is not set +# CONFIG_TASKSTATS is not set +# CONFIG_AUDIT is not set + +# +# RCU Subsystem +# +CONFIG_TREE_RCU=y +# CONFIG_TREE_PREEMPT_RCU is not set +# CONFIG_RCU_TRACE is not set +CONFIG_RCU_FANOUT=32 +# CONFIG_RCU_FANOUT_EXACT is not set +# CONFIG_TREE_RCU_TRACE is not set +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_LOG_BUF_SHIFT=14 +CONFIG_GROUP_SCHED=y +CONFIG_FAIR_GROUP_SCHED=y +# CONFIG_RT_GROUP_SCHED is not set +CONFIG_USER_SCHED=y +# CONFIG_CGROUP_SCHED is not set +# CONFIG_CGROUPS is not set +CONFIG_SYSFS_DEPRECATED=y +CONFIG_SYSFS_DEPRECATED_V2=y +# CONFIG_RELAY is not set +# CONFIG_NAMESPACES is not set +CONFIG_BLK_DEV_INITRD=y +CONFIG_INITRAMFS_SOURCE="" +CONFIG_RD_GZIP=y +# CONFIG_RD_BZIP2 is not set +# CONFIG_RD_LZMA is not set +# CONFIG_CC_OPTIMIZE_FOR_SIZE is not set +CONFIG_SYSCTL=y +CONFIG_ANON_INODES=y +CONFIG_EMBEDDED=y +CONFIG_SYSCTL_SYSCALL=y +CONFIG_KALLSYMS=y +CONFIG_KALLSYMS_ALL=y +# CONFIG_KALLSYMS_EXTRA_PASS is not set +CONFIG_HOTPLUG=y +CONFIG_PRINTK=y +CONFIG_BUG=y +# CONFIG_ELF_CORE is not set +CONFIG_BASE_FULL=y +CONFIG_FUTEX=y +CONFIG_EPOLL=y +CONFIG_SIGNALFD=y +CONFIG_TIMERFD=y +CONFIG_EVENTFD=y +CONFIG_SHMEM=y +CONFIG_AIO=y +CONFIG_HAVE_PERF_EVENTS=y + +# +# Kernel Performance Events And Counters +# +CONFIG_PERF_EVENTS=y +CONFIG_EVENT_PROFILE=y +CONFIG_PERF_COUNTERS=y +# CONFIG_DEBUG_PERF_USE_VMALLOC is not set +# CONFIG_VM_EVENT_COUNTERS is not set +CONFIG_COMPAT_BRK=y +CONFIG_SLAB=y +# CONFIG_SLUB is not set +# CONFIG_SLOB is not set +# CONFIG_PROFILING is not set +CONFIG_TRACEPOINTS=y +CONFIG_HAVE_OPROFILE=y +# CONFIG_KPROBES is not set +CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y +CONFIG_HAVE_IOREMAP_PROT=y +CONFIG_HAVE_KPROBES=y +CONFIG_HAVE_KRETPROBES=y +CONFIG_HAVE_ARCH_TRACEHOOK=y +CONFIG_HAVE_DMA_ATTRS=y +CONFIG_HAVE_DMA_API_DEBUG=y + +# +# GCOV-based kernel profiling +# +# CONFIG_GCOV_KERNEL is not set +CONFIG_SLOW_WORK=y +# CONFIG_SLOW_WORK_DEBUG is not set +# CONFIG_HAVE_GENERIC_DMA_COHERENT is not set +CONFIG_SLABINFO=y +CONFIG_RT_MUTEXES=y +CONFIG_BASE_SMALL=0 +CONFIG_MODULES=y +# CONFIG_MODULE_FORCE_LOAD is not set +CONFIG_MODULE_UNLOAD=y +# CONFIG_MODULE_FORCE_UNLOAD is not set +# CONFIG_MODVERSIONS is not set +# CONFIG_MODULE_SRCVERSION_ALL is not set +CONFIG_BLOCK=y +CONFIG_LBDAF=y +# CONFIG_BLK_DEV_BSG is not set +# CONFIG_BLK_DEV_INTEGRITY is not set + +# +# IO Schedulers +# +CONFIG_IOSCHED_NOOP=y +CONFIG_IOSCHED_AS=y +CONFIG_IOSCHED_DEADLINE=y +CONFIG_IOSCHED_CFQ=y +CONFIG_DEFAULT_AS=y +# CONFIG_DEFAULT_DEADLINE is not set +# CONFIG_DEFAULT_CFQ is not set +# CONFIG_DEFAULT_NOOP is not set +CONFIG_DEFAULT_IOSCHED="anticipatory" +# CONFIG_FREEZER is not set + +# +# Platform support +# +# CONFIG_PPC_CHRP is not set +# CONFIG_MPC5121_ADS is not set +# CONFIG_MPC5121_GENERIC is not set +# CONFIG_PPC_MPC52xx is not set +# CONFIG_PPC_PMAC is not set +# CONFIG_PPC_CELL is not set +# CONFIG_PPC_CELL_NATIVE is not set +# CONFIG_PPC_82xx is not set +# CONFIG_PQ2ADS is not set +# CONFIG_PPC_83xx is not set +# CONFIG_PPC_86xx is not set +CONFIG_EMBEDDED6xx=y +# CONFIG_LINKSTATION is not set +# CONFIG_STORCENTER is not set +# CONFIG_MPC7448HPC2 is not set +# CONFIG_PPC_HOLLY is not set +# CONFIG_PPC_PRPMC2800 is not set +# CONFIG_PPC_C2K is not set +CONFIG_GAMECUBE=y +# CONFIG_WII is not set +CONFIG_FLIPPER_PIC=y +CONFIG_GAMECUBE_COMMON=y +CONFIG_GAMECUBE_UDBG=y +CONFIG_USBGECKO_UDBG=y +# CONFIG_GAMECUBE_VIDEO_UDBG is not set +CONFIG_GAMECUBE_RSW=y +# CONFIG_AMIGAONE is not set +# CONFIG_PPC_OF_BOOT_TRAMPOLINE is not set +# CONFIG_IPIC is not set +# CONFIG_MPIC is not set +# CONFIG_MPIC_WEIRD is not set +# CONFIG_PPC_I8259 is not set +# CONFIG_PPC_RTAS is not set +# CONFIG_MMIO_NVRAM is not set +# CONFIG_PPC_MPC106 is not set +# CONFIG_PPC_970_NAP is not set +# CONFIG_PPC_INDIRECT_IO is not set +# CONFIG_GENERIC_IOMAP is not set +# CONFIG_CPU_FREQ is not set +# CONFIG_TAU is not set +# CONFIG_FSL_ULI1575 is not set +# CONFIG_SIMPLE_GPIO is not set + +# +# Kernel options +# +# CONFIG_HIGHMEM is not set +# CONFIG_NO_HZ is not set +# CONFIG_HIGH_RES_TIMERS is not set +CONFIG_GENERIC_CLOCKEVENTS_BUILD=y +# CONFIG_HZ_100 is not set +CONFIG_HZ_250=y +# CONFIG_HZ_300 is not set +# CONFIG_HZ_1000 is not set +CONFIG_HZ=250 +# CONFIG_SCHED_HRTICK is not set +# CONFIG_PREEMPT_NONE is not set +# CONFIG_PREEMPT_VOLUNTARY is not set +CONFIG_PREEMPT=y +CONFIG_BINFMT_ELF=y +# CONFIG_HAVE_AOUT is not set +CONFIG_BINFMT_MISC=m +# CONFIG_IOMMU_HELPER is not set +# CONFIG_SWIOTLB is not set +CONFIG_ARCH_ENABLE_MEMORY_HOTPLUG=y +CONFIG_ARCH_HAS_WALK_MEMORY=y +CONFIG_ARCH_ENABLE_MEMORY_HOTREMOVE=y +CONFIG_KEXEC=y +# CONFIG_CRASH_DUMP is not set +CONFIG_MAX_ACTIVE_REGIONS=32 +CONFIG_ARCH_FLATMEM_ENABLE=y +CONFIG_ARCH_POPULATES_NODE_MAP=y +CONFIG_SELECT_MEMORY_MODEL=y +CONFIG_FLATMEM_MANUAL=y +# CONFIG_DISCONTIGMEM_MANUAL is not set +# CONFIG_SPARSEMEM_MANUAL is not set +CONFIG_FLATMEM=y +CONFIG_FLAT_NODE_MEM_MAP=y +CONFIG_PAGEFLAGS_EXTENDED=y +CONFIG_SPLIT_PTLOCK_CPUS=4 +# CONFIG_MIGRATION is not set +# CONFIG_PHYS_ADDR_T_64BIT is not set +CONFIG_ZONE_DMA_FLAG=1 +CONFIG_BOUNCE=y +CONFIG_VIRT_TO_BUS=y +CONFIG_HAVE_MLOCK=y +CONFIG_HAVE_MLOCKED_PAGE_BIT=y +# CONFIG_KSM is not set +CONFIG_DEFAULT_MMAP_MIN_ADDR=4096 +CONFIG_PPC_4K_PAGES=y +# CONFIG_PPC_16K_PAGES is not set +# CONFIG_PPC_64K_PAGES is not set +# CONFIG_PPC_256K_PAGES is not set +CONFIG_FORCE_MAX_ZONEORDER=11 +CONFIG_PROC_DEVICETREE=y +# CONFIG_CMDLINE_BOOL is not set +CONFIG_EXTRA_TARGETS="" +# CONFIG_PM is not set +# CONFIG_SECCOMP is not set +CONFIG_ISA_DMA_API=y + +# +# Bus options +# +CONFIG_ZONE_DMA=y +CONFIG_GENERIC_ISA_DMA=y +# CONFIG_PCI is not set +# CONFIG_PCI_DOMAINS is not set +# CONFIG_PCI_SYSCALL is not set +# CONFIG_ARCH_SUPPORTS_MSI is not set +# CONFIG_PCCARD is not set +# CONFIG_HAS_RAPIDIO is not set + +# +# Advanced setup +# +CONFIG_ADVANCED_OPTIONS=y +# CONFIG_LOWMEM_SIZE_BOOL is not set +CONFIG_LOWMEM_SIZE=0x30000000 +# CONFIG_PAGE_OFFSET_BOOL is not set +CONFIG_PAGE_OFFSET=0xc0000000 +# CONFIG_KERNEL_START_BOOL is not set +CONFIG_KERNEL_START=0xc0000000 +CONFIG_PHYSICAL_START=0x00000000 +# CONFIG_TASK_SIZE_BOOL is not set +CONFIG_TASK_SIZE=0xc0000000 +# CONFIG_CONSISTENT_SIZE_BOOL is not set +CONFIG_CONSISTENT_SIZE=0x00200000 +CONFIG_NET=y + +# +# Networking options +# +CONFIG_PACKET=y +# CONFIG_PACKET_MMAP is not set +CONFIG_UNIX=y +# CONFIG_NET_KEY is not set +CONFIG_INET=y +# CONFIG_IP_MULTICAST is not set +# CONFIG_IP_ADVANCED_ROUTER is not set +CONFIG_IP_FIB_HASH=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +# CONFIG_IP_PNP_BOOTP is not set +CONFIG_IP_PNP_RARP=y +# CONFIG_NET_IPIP is not set +# CONFIG_NET_IPGRE is not set +# CONFIG_ARPD is not set +# CONFIG_SYN_COOKIES is not set +# CONFIG_INET_AH is not set +# CONFIG_INET_ESP is not set +# CONFIG_INET_IPCOMP is not set +# CONFIG_INET_XFRM_TUNNEL is not set +# CONFIG_INET_TUNNEL is not set +# CONFIG_INET_XFRM_MODE_TRANSPORT is not set +# CONFIG_INET_XFRM_MODE_TUNNEL is not set +# CONFIG_INET_XFRM_MODE_BEET is not set +# CONFIG_INET_LRO is not set +# CONFIG_INET_DIAG is not set +# CONFIG_TCP_CONG_ADVANCED is not set +CONFIG_TCP_CONG_CUBIC=y +CONFIG_DEFAULT_TCP_CONG="cubic" +# CONFIG_TCP_MD5SIG is not set +# CONFIG_IPV6 is not set +# CONFIG_NETWORK_SECMARK is not set +# CONFIG_NETFILTER is not set +# CONFIG_IP_DCCP is not set +# CONFIG_IP_SCTP is not set +# CONFIG_RDS is not set +# CONFIG_TIPC is not set +# CONFIG_ATM is not set +# CONFIG_BRIDGE is not set +# CONFIG_NET_DSA is not set +# CONFIG_VLAN_8021Q is not set +# CONFIG_DECNET is not set +# CONFIG_LLC2 is not set +# CONFIG_IPX is not set +# CONFIG_ATALK is not set +# CONFIG_X25 is not set +# CONFIG_LAPB is not set +# CONFIG_ECONET is not set +# CONFIG_WAN_ROUTER is not set +# CONFIG_PHONET is not set +# CONFIG_IEEE802154 is not set +# CONFIG_NET_SCHED is not set +# CONFIG_DCB is not set + +# +# Network testing +# +# CONFIG_NET_PKTGEN is not set +# CONFIG_NET_DROP_MONITOR is not set +# CONFIG_HAMRADIO is not set +# CONFIG_CAN is not set +# CONFIG_IRDA is not set +# CONFIG_BT is not set +# CONFIG_AF_RXRPC is not set +# CONFIG_WIRELESS is not set +# CONFIG_WIMAX is not set +# CONFIG_RFKILL is not set +# CONFIG_NET_9P is not set + +# +# Device Drivers +# + +# +# Generic Driver Options +# +CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +# CONFIG_DEVTMPFS is not set +# CONFIG_STANDALONE is not set +CONFIG_PREVENT_FIRMWARE_BUILD=y +# CONFIG_FW_LOADER is not set +# CONFIG_DEBUG_DRIVER is not set +# CONFIG_DEBUG_DEVRES is not set +# CONFIG_SYS_HYPERVISOR is not set +# CONFIG_CONNECTOR is not set +# CONFIG_MTD is not set +CONFIG_OF_DEVICE=y +# CONFIG_PARPORT is not set +CONFIG_BLK_DEV=y +# CONFIG_BLK_DEV_FD is not set +CONFIG_GAMECUBE_SD=y +CONFIG_GAMECUBE_ARAM=y +CONFIG_GAMECUBE_DI=y +# CONFIG_BLK_DEV_COW_COMMON is not set +CONFIG_BLK_DEV_LOOP=y +# CONFIG_BLK_DEV_CRYPTOLOOP is not set +CONFIG_BLK_DEV_NBD=m +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_COUNT=2 +CONFIG_BLK_DEV_RAM_SIZE=4096 +# CONFIG_BLK_DEV_XIP is not set +# CONFIG_CDROM_PKTCDVD is not set +# CONFIG_ATA_OVER_ETH is not set +# CONFIG_BLK_DEV_HD is not set +CONFIG_MISC_DEVICES=y +CONFIG_GAMECUBE_GQR=m +CONFIG_GAMECUBE_MI=m +# CONFIG_ENCLOSURE_SERVICES is not set +# CONFIG_C2PORT is not set + +# +# EEPROM support +# +# CONFIG_EEPROM_93CX6 is not set +CONFIG_HAVE_IDE=y +# CONFIG_IDE is not set + +# +# SCSI device support +# +# CONFIG_RAID_ATTRS is not set +# CONFIG_SCSI is not set +# CONFIG_SCSI_DMA is not set +# CONFIG_SCSI_NETLINK is not set +# CONFIG_ATA is not set +# CONFIG_MD is not set +# CONFIG_MACINTOSH_DRIVERS is not set +CONFIG_NETDEVICES=y +# CONFIG_DUMMY is not set +# CONFIG_BONDING is not set +# CONFIG_MACVLAN is not set +# CONFIG_EQUALIZER is not set +# CONFIG_TUN is not set +# CONFIG_VETH is not set +# CONFIG_PHYLIB is not set +CONFIG_NET_ETHERNET=y +# CONFIG_MII is not set +CONFIG_GAMECUBE_BBA=y +# CONFIG_ETHOC is not set +# CONFIG_DNET is not set +# CONFIG_IBM_NEW_EMAC_ZMII is not set +# CONFIG_IBM_NEW_EMAC_RGMII is not set +# CONFIG_IBM_NEW_EMAC_TAH is not set +# CONFIG_IBM_NEW_EMAC_EMAC4 is not set +# CONFIG_IBM_NEW_EMAC_NO_FLOW_CTRL is not set +# CONFIG_IBM_NEW_EMAC_MAL_CLR_ICINTSTAT is not set +# CONFIG_IBM_NEW_EMAC_MAL_COMMON_ERR is not set +# CONFIG_B44 is not set +# CONFIG_KS8842 is not set +# CONFIG_KS8851_MLL is not set +# CONFIG_XILINX_EMACLITE is not set +# CONFIG_NETDEV_1000 is not set +# CONFIG_NETDEV_10000 is not set +# CONFIG_WLAN is not set + +# +# Enable WiMAX (Networking options) to see the WiMAX drivers +# +# CONFIG_WAN is not set +# CONFIG_PPP is not set +# CONFIG_SLIP is not set +# CONFIG_NETCONSOLE is not set +# CONFIG_NETPOLL is not set +# CONFIG_NET_POLL_CONTROLLER is not set +# CONFIG_ISDN is not set +# CONFIG_PHONE is not set + +# +# Input device support +# +CONFIG_INPUT=y +CONFIG_INPUT_FF_MEMLESS=m +# CONFIG_INPUT_POLLDEV is not set + +# +# Userland interfaces +# +# CONFIG_INPUT_MOUSEDEV is not set +CONFIG_INPUT_JOYDEV=y +CONFIG_INPUT_EVDEV=y +# CONFIG_INPUT_EVBUG is not set + +# +# Input Device Drivers +# +CONFIG_INPUT_KEYBOARD=y +# CONFIG_KEYBOARD_ATKBD is not set +# CONFIG_KEYBOARD_LKKBD is not set +# CONFIG_KEYBOARD_NEWTON is not set +# CONFIG_KEYBOARD_OPENCORES is not set +# CONFIG_KEYBOARD_STOWAWAY is not set +# CONFIG_KEYBOARD_SUNKBD is not set +# CONFIG_KEYBOARD_XTKBD is not set +# CONFIG_INPUT_MOUSE is not set +CONFIG_INPUT_JOYSTICK=y +# CONFIG_JOYSTICK_ANALOG is not set +# CONFIG_JOYSTICK_A3D is not set +# CONFIG_JOYSTICK_ADI is not set +# CONFIG_JOYSTICK_COBRA is not set +# CONFIG_JOYSTICK_GF2K is not set +# CONFIG_JOYSTICK_GRIP is not set +# CONFIG_JOYSTICK_GRIP_MP is not set +# CONFIG_JOYSTICK_GUILLEMOT is not set +# CONFIG_JOYSTICK_INTERACT is not set +# CONFIG_JOYSTICK_SIDEWINDER is not set +# CONFIG_JOYSTICK_TMDC is not set +# CONFIG_JOYSTICK_IFORCE is not set +# CONFIG_JOYSTICK_WARRIOR is not set +# CONFIG_JOYSTICK_MAGELLAN is not set +# CONFIG_JOYSTICK_SPACEORB is not set +# CONFIG_JOYSTICK_SPACEBALL is not set +# CONFIG_JOYSTICK_STINGER is not set +# CONFIG_JOYSTICK_TWIDJOY is not set +# CONFIG_JOYSTICK_ZHENHUA is not set +# CONFIG_JOYSTICK_JOYDUMP is not set +# CONFIG_INPUT_TABLET is not set +# CONFIG_INPUT_TOUCHSCREEN is not set +# CONFIG_INPUT_MISC is not set + +# +# Hardware I/O ports +# +CONFIG_SERIO=y +# CONFIG_SERIO_I8042 is not set +# CONFIG_SERIO_SERPORT is not set +# CONFIG_SERIO_LIBPS2 is not set +# CONFIG_SERIO_RAW is not set +# CONFIG_SERIO_XILINX_XPS_PS2 is not set +# CONFIG_GAMEPORT is not set +CONFIG_GAMECUBE_SI=y + +# +# Character devices +# +CONFIG_VT=y +CONFIG_CONSOLE_TRANSLATIONS=y +CONFIG_VT_CONSOLE=y +CONFIG_HW_CONSOLE=y +# CONFIG_VT_HW_CONSOLE_BINDING is not set +# CONFIG_DEVKMEM is not set +# CONFIG_SERIAL_NONSTANDARD is not set + +# +# Serial drivers +# +# CONFIG_SERIAL_8250 is not set + +# +# Non-8250 serial port support +# +# CONFIG_SERIAL_UARTLITE is not set +# CONFIG_SERIAL_USBGECKO is not set +CONFIG_UNIX98_PTYS=y +# CONFIG_DEVPTS_MULTIPLE_INSTANCES is not set +CONFIG_LEGACY_PTYS=y +CONFIG_LEGACY_PTY_COUNT=64 +# CONFIG_HVC_UDBG is not set +# CONFIG_IPMI_HANDLER is not set +# CONFIG_HW_RANDOM is not set +# CONFIG_NVRAM is not set +# CONFIG_R3964 is not set +# CONFIG_RAW_DRIVER is not set +# CONFIG_TCG_TPM is not set +# CONFIG_I2C is not set + +# +# EXI support +# +CONFIG_GAMECUBE_EXI=y +# CONFIG_SPI is not set + +# +# PPS support +# +# CONFIG_PPS is not set +CONFIG_ARCH_WANT_OPTIONAL_GPIOLIB=y +# CONFIG_GPIOLIB is not set +# CONFIG_W1 is not set +# CONFIG_POWER_SUPPLY is not set +# CONFIG_HWMON is not set +# CONFIG_THERMAL is not set +# CONFIG_WATCHDOG is not set +CONFIG_SSB_POSSIBLE=y + +# +# Sonics Silicon Backplane +# +# CONFIG_SSB is not set + +# +# Multifunction device drivers +# +# CONFIG_MFD_CORE is not set +# CONFIG_MFD_SM501 is not set +# CONFIG_HTC_PASIC3 is not set +# CONFIG_MFD_TMIO is not set +# CONFIG_REGULATOR is not set +# CONFIG_MEDIA_SUPPORT is not set + +# +# Graphics support +# +# CONFIG_VGASTATE is not set +# CONFIG_VIDEO_OUTPUT_CONTROL is not set +CONFIG_FB=y +# CONFIG_FIRMWARE_EDID is not set +# CONFIG_FB_DDC is not set +# CONFIG_FB_BOOT_VESA_SUPPORT is not set +CONFIG_FB_CFB_FILLRECT=y +CONFIG_FB_CFB_COPYAREA=y +CONFIG_FB_CFB_IMAGEBLIT=y +# CONFIG_FB_CFB_REV_PIXELS_IN_BYTE is not set +# CONFIG_FB_SYS_FILLRECT is not set +# CONFIG_FB_SYS_COPYAREA is not set +# CONFIG_FB_SYS_IMAGEBLIT is not set +# CONFIG_FB_FOREIGN_ENDIAN is not set +# CONFIG_FB_SYS_FOPS is not set +# CONFIG_FB_SVGALIB is not set +# CONFIG_FB_MACMODES is not set +# CONFIG_FB_BACKLIGHT is not set +# CONFIG_FB_MODE_HELPERS is not set +# CONFIG_FB_TILEBLITTING is not set + +# +# Frame buffer hardware drivers +# +# CONFIG_FB_OF is not set +# CONFIG_FB_VGA16 is not set +# CONFIG_FB_S1D13XXX is not set +CONFIG_FB_GAMECUBE=y +# CONFIG_FB_IBM_GXT4500 is not set +# CONFIG_FB_VIRTUAL is not set +# CONFIG_FB_METRONOME is not set +# CONFIG_FB_MB862XX is not set +# CONFIG_FB_BROADSHEET is not set +# CONFIG_BACKLIGHT_LCD_SUPPORT is not set + +# +# Display device support +# +# CONFIG_DISPLAY_SUPPORT is not set + +# +# Console display driver support +# +# CONFIG_VGA_CONSOLE is not set +CONFIG_DUMMY_CONSOLE=y +CONFIG_FRAMEBUFFER_CONSOLE=y +# CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY is not set +# CONFIG_FRAMEBUFFER_CONSOLE_ROTATION is not set +# CONFIG_FONTS is not set +CONFIG_FONT_8x8=y +CONFIG_FONT_8x16=y +CONFIG_LOGO=y +# CONFIG_LOGO_LINUX_MONO is not set +# CONFIG_LOGO_LINUX_VGA16 is not set +# CONFIG_LOGO_LINUX_CLUT224 is not set +CONFIG_LOGO_GAMECUBE_CLUT224=y +CONFIG_SOUND=y +CONFIG_SOUND_OSS_CORE=y +CONFIG_SOUND_OSS_CORE_PRECLAIM=y +CONFIG_SND=y +CONFIG_SND_TIMER=y +CONFIG_SND_PCM=y +CONFIG_SND_SEQUENCER=y +# CONFIG_SND_SEQ_DUMMY is not set +CONFIG_SND_OSSEMUL=y +CONFIG_SND_MIXER_OSS=y +CONFIG_SND_PCM_OSS=y +CONFIG_SND_PCM_OSS_PLUGINS=y +CONFIG_SND_SEQUENCER_OSS=y +# CONFIG_SND_DYNAMIC_MINORS is not set +CONFIG_SND_SUPPORT_OLD_API=y +# CONFIG_SND_VERBOSE_PROCFS is not set +# CONFIG_SND_VERBOSE_PRINTK is not set +# CONFIG_SND_DEBUG is not set +# CONFIG_SND_RAWMIDI_SEQ is not set +# CONFIG_SND_OPL3_LIB_SEQ is not set +# CONFIG_SND_OPL4_LIB_SEQ is not set +# CONFIG_SND_SBAWE_SEQ is not set +# CONFIG_SND_EMU10K1_SEQ is not set +CONFIG_SND_DRIVERS=y +# CONFIG_SND_DUMMY is not set +# CONFIG_SND_VIRMIDI is not set +# CONFIG_SND_MTPAV is not set +# CONFIG_SND_SERIAL_U16550 is not set +# CONFIG_SND_MPU401 is not set +CONFIG_SND_PPC=y +CONFIG_SND_GAMECUBE=y +CONFIG_SND_GAMECUBE_MIC=m +# CONFIG_SND_SOC is not set +# CONFIG_SOUND_PRIME is not set +CONFIG_HID_SUPPORT=y +CONFIG_HID=y +# CONFIG_HIDRAW is not set +# CONFIG_HID_PID is not set + +# +# Special HID drivers +# +# CONFIG_USB_SUPPORT is not set +# CONFIG_MMC is not set +# CONFIG_MEMSTICK is not set +# CONFIG_NEW_LEDS is not set +# CONFIG_ACCESSIBILITY is not set +# CONFIG_EDAC is not set +CONFIG_RTC_LIB=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_HCTOSYS=y +CONFIG_RTC_HCTOSYS_DEVICE="rtc0" +# CONFIG_RTC_DEBUG is not set + +# +# RTC interfaces +# +CONFIG_RTC_INTF_SYSFS=y +CONFIG_RTC_INTF_PROC=y +CONFIG_RTC_INTF_DEV=y +# CONFIG_RTC_INTF_DEV_UIE_EMUL is not set +# CONFIG_RTC_DRV_TEST is not set + +# +# SPI RTC drivers +# + +# +# Platform RTC drivers +# +# CONFIG_RTC_DRV_CMOS is not set +# CONFIG_RTC_DRV_DS1286 is not set +# CONFIG_RTC_DRV_DS1511 is not set +# CONFIG_RTC_DRV_DS1553 is not set +# CONFIG_RTC_DRV_DS1742 is not set +# CONFIG_RTC_DRV_STK17TA8 is not set +# CONFIG_RTC_DRV_M48T86 is not set +# CONFIG_RTC_DRV_M48T35 is not set +# CONFIG_RTC_DRV_M48T59 is not set +# CONFIG_RTC_DRV_BQ4802 is not set +# CONFIG_RTC_DRV_V3020 is not set +CONFIG_RTC_DRV_GCN=y + +# +# on-CPU RTC drivers +# +CONFIG_RTC_DRV_GENERIC=y +# CONFIG_DMADEVICES is not set +# CONFIG_AUXDISPLAY is not set +# CONFIG_UIO is not set + +# +# TI VLYNQ +# +# CONFIG_STAGING is not set + +# +# File systems +# +CONFIG_EXT2_FS=y +# CONFIG_EXT2_FS_XATTR is not set +# CONFIG_EXT2_FS_XIP is not set +CONFIG_EXT3_FS=y +# CONFIG_EXT3_DEFAULTS_TO_ORDERED is not set +# CONFIG_EXT3_FS_XATTR is not set +# CONFIG_EXT4_FS is not set +CONFIG_JBD=y +# CONFIG_JBD_DEBUG is not set +# CONFIG_REISERFS_FS is not set +# CONFIG_JFS_FS is not set +# CONFIG_FS_POSIX_ACL is not set +# CONFIG_XFS_FS is not set +# CONFIG_GFS2_FS is not set +# CONFIG_OCFS2_FS is not set +# CONFIG_BTRFS_FS is not set +# CONFIG_NILFS2_FS is not set +CONFIG_FILE_LOCKING=y +CONFIG_FSNOTIFY=y +CONFIG_DNOTIFY=y +CONFIG_INOTIFY=y +CONFIG_INOTIFY_USER=y +# CONFIG_QUOTA is not set +# CONFIG_AUTOFS_FS is not set +# CONFIG_AUTOFS4_FS is not set +# CONFIG_FUSE_FS is not set + +# +# Caches +# +# CONFIG_FSCACHE is not set + +# +# CD-ROM/DVD Filesystems +# +CONFIG_ISO9660_FS=y +CONFIG_JOLIET=y +# CONFIG_ZISOFS is not set +# CONFIG_UDF_FS is not set + +# +# DOS/FAT/NT Filesystems +# +CONFIG_FAT_FS=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_CODEPAGE=437 +CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1" +# CONFIG_NTFS_FS is not set + +# +# Pseudo filesystems +# +CONFIG_PROC_FS=y +CONFIG_PROC_KCORE=y +CONFIG_PROC_SYSCTL=y +# CONFIG_PROC_PAGE_MONITOR is not set +CONFIG_SYSFS=y +CONFIG_TMPFS=y +# CONFIG_TMPFS_POSIX_ACL is not set +# CONFIG_HUGETLB_PAGE is not set +# CONFIG_CONFIGFS_FS is not set +CONFIG_MISC_FILESYSTEMS=y +# CONFIG_ADFS_FS is not set +# CONFIG_AFFS_FS is not set +# CONFIG_HFS_FS is not set +# CONFIG_HFSPLUS_FS is not set +# CONFIG_BEFS_FS is not set +# CONFIG_BFS_FS is not set +# CONFIG_EFS_FS is not set +# CONFIG_CRAMFS is not set +# CONFIG_SQUASHFS is not set +# CONFIG_VXFS_FS is not set +# CONFIG_MINIX_FS is not set +# CONFIG_OMFS_FS is not set +# CONFIG_HPFS_FS is not set +# CONFIG_QNX4FS_FS is not set +# CONFIG_ROMFS_FS is not set +# CONFIG_SYSV_FS is not set +# CONFIG_UFS_FS is not set +CONFIG_NETWORK_FILESYSTEMS=y +CONFIG_NFS_FS=y +CONFIG_NFS_V3=y +# CONFIG_NFS_V3_ACL is not set +# CONFIG_NFS_V4 is not set +CONFIG_ROOT_NFS=y +# CONFIG_NFSD is not set +CONFIG_LOCKD=y +CONFIG_LOCKD_V4=y +CONFIG_NFS_COMMON=y +CONFIG_SUNRPC=y +# CONFIG_RPCSEC_GSS_KRB5 is not set +# CONFIG_RPCSEC_GSS_SPKM3 is not set +# CONFIG_SMB_FS is not set +CONFIG_CIFS=y +# CONFIG_CIFS_STATS is not set +# CONFIG_CIFS_WEAK_PW_HASH is not set +# CONFIG_CIFS_XATTR is not set +# CONFIG_CIFS_DEBUG2 is not set +# CONFIG_CIFS_EXPERIMENTAL is not set +# CONFIG_NCP_FS is not set +# CONFIG_CODA_FS is not set +# CONFIG_AFS_FS is not set + +# +# Partition Types +# +# CONFIG_PARTITION_ADVANCED is not set +CONFIG_MSDOS_PARTITION=y +CONFIG_NLS=y +CONFIG_NLS_DEFAULT="iso8859-1" +CONFIG_NLS_CODEPAGE_437=y +# CONFIG_NLS_CODEPAGE_737 is not set +# CONFIG_NLS_CODEPAGE_775 is not set +# CONFIG_NLS_CODEPAGE_850 is not set +# CONFIG_NLS_CODEPAGE_852 is not set +# CONFIG_NLS_CODEPAGE_855 is not set +# CONFIG_NLS_CODEPAGE_857 is not set +# CONFIG_NLS_CODEPAGE_860 is not set +# CONFIG_NLS_CODEPAGE_861 is not set +# CONFIG_NLS_CODEPAGE_862 is not set +# CONFIG_NLS_CODEPAGE_863 is not set +# CONFIG_NLS_CODEPAGE_864 is not set +# CONFIG_NLS_CODEPAGE_865 is not set +# CONFIG_NLS_CODEPAGE_866 is not set +# CONFIG_NLS_CODEPAGE_869 is not set +# CONFIG_NLS_CODEPAGE_936 is not set +# CONFIG_NLS_CODEPAGE_950 is not set +# CONFIG_NLS_CODEPAGE_932 is not set +# CONFIG_NLS_CODEPAGE_949 is not set +# CONFIG_NLS_CODEPAGE_874 is not set +# CONFIG_NLS_ISO8859_8 is not set +# CONFIG_NLS_CODEPAGE_1250 is not set +# CONFIG_NLS_CODEPAGE_1251 is not set +# CONFIG_NLS_ASCII is not set +CONFIG_NLS_ISO8859_1=y +# CONFIG_NLS_ISO8859_2 is not set +# CONFIG_NLS_ISO8859_3 is not set +# CONFIG_NLS_ISO8859_4 is not set +# CONFIG_NLS_ISO8859_5 is not set +# CONFIG_NLS_ISO8859_6 is not set +# CONFIG_NLS_ISO8859_7 is not set +# CONFIG_NLS_ISO8859_9 is not set +# CONFIG_NLS_ISO8859_13 is not set +# CONFIG_NLS_ISO8859_14 is not set +# CONFIG_NLS_ISO8859_15 is not set +# CONFIG_NLS_KOI8_R is not set +# CONFIG_NLS_KOI8_U is not set +# CONFIG_NLS_UTF8 is not set +# CONFIG_DLM is not set +CONFIG_BINARY_PRINTF=y + +# +# Library routines +# +CONFIG_BITREVERSE=y +CONFIG_GENERIC_FIND_LAST_BIT=y +CONFIG_CRC_CCITT=y +# CONFIG_CRC16 is not set +# CONFIG_CRC_T10DIF is not set +# CONFIG_CRC_ITU_T is not set +CONFIG_CRC32=y +# CONFIG_CRC7 is not set +# CONFIG_LIBCRC32C is not set +CONFIG_ZLIB_INFLATE=y +CONFIG_DECOMPRESS_GZIP=y +CONFIG_HAS_IOMEM=y +CONFIG_HAS_IOPORT=y +CONFIG_HAS_DMA=y +CONFIG_HAVE_LMB=y +CONFIG_NLATTR=y +CONFIG_GENERIC_ATOMIC64=y + +# +# Kernel hacking +# +CONFIG_PRINTK_TIME=y +CONFIG_ENABLE_WARN_DEPRECATED=y +CONFIG_ENABLE_MUST_CHECK=y +CONFIG_FRAME_WARN=1024 +# CONFIG_MAGIC_SYSRQ is not set +# CONFIG_STRIP_ASM_SYMS is not set +# CONFIG_UNUSED_SYMBOLS is not set +CONFIG_DEBUG_FS=y +# CONFIG_HEADERS_CHECK is not set +CONFIG_DEBUG_KERNEL=y +# CONFIG_DEBUG_SHIRQ is not set +CONFIG_DETECT_SOFTLOCKUP=y +# CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC is not set +CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC_VALUE=0 +# CONFIG_DETECT_HUNG_TASK is not set +CONFIG_SCHED_DEBUG=y +CONFIG_SCHEDSTATS=y +# CONFIG_TIMER_STATS is not set +# CONFIG_DEBUG_OBJECTS is not set +# CONFIG_DEBUG_SLAB is not set +# CONFIG_DEBUG_KMEMLEAK is not set +CONFIG_DEBUG_PREEMPT=y +# CONFIG_DEBUG_RT_MUTEXES is not set +# CONFIG_RT_MUTEX_TESTER is not set +CONFIG_DEBUG_SPINLOCK=y +CONFIG_DEBUG_MUTEXES=y +# CONFIG_DEBUG_LOCK_ALLOC is not set +# CONFIG_PROVE_LOCKING is not set +# CONFIG_LOCK_STAT is not set +CONFIG_DEBUG_SPINLOCK_SLEEP=y +# CONFIG_DEBUG_LOCKING_API_SELFTESTS is not set +CONFIG_STACKTRACE=y +# CONFIG_DEBUG_KOBJECT is not set +CONFIG_DEBUG_BUGVERBOSE=y +# CONFIG_DEBUG_INFO is not set +# CONFIG_DEBUG_VM is not set +# CONFIG_DEBUG_WRITECOUNT is not set +# CONFIG_DEBUG_MEMORY_INIT is not set +# CONFIG_DEBUG_LIST is not set +# CONFIG_DEBUG_SG is not set +# CONFIG_DEBUG_NOTIFIERS is not set +# CONFIG_DEBUG_CREDENTIALS is not set +# CONFIG_RCU_TORTURE_TEST is not set +# CONFIG_RCU_CPU_STALL_DETECTOR is not set +# CONFIG_BACKTRACE_SELF_TEST is not set +# CONFIG_DEBUG_BLOCK_EXT_DEVT is not set +# CONFIG_DEBUG_FORCE_WEAK_PER_CPU is not set +# CONFIG_FAULT_INJECTION is not set +CONFIG_LATENCYTOP=y +CONFIG_SYSCTL_SYSCALL_CHECK=y +# CONFIG_DEBUG_PAGEALLOC is not set +CONFIG_NOP_TRACER=y +CONFIG_HAVE_FUNCTION_TRACER=y +CONFIG_HAVE_FUNCTION_GRAPH_TRACER=y +CONFIG_HAVE_DYNAMIC_FTRACE=y +CONFIG_HAVE_FTRACE_MCOUNT_RECORD=y +CONFIG_TRACER_MAX_TRACE=y +CONFIG_RING_BUFFER=y +CONFIG_EVENT_TRACING=y +CONFIG_CONTEXT_SWITCH_TRACER=y +CONFIG_TRACING=y +CONFIG_GENERIC_TRACER=y +CONFIG_TRACING_SUPPORT=y +CONFIG_FTRACE=y +# CONFIG_FUNCTION_TRACER is not set +# CONFIG_IRQSOFF_TRACER is not set +# CONFIG_PREEMPT_TRACER is not set +CONFIG_SCHED_TRACER=y +CONFIG_BOOT_TRACER=y +CONFIG_BRANCH_PROFILE_NONE=y +# CONFIG_PROFILE_ANNOTATED_BRANCHES is not set +# CONFIG_PROFILE_ALL_BRANCHES is not set +# CONFIG_STACK_TRACER is not set +# CONFIG_KMEMTRACE is not set +# CONFIG_WORKQUEUE_TRACER is not set +# CONFIG_BLK_DEV_IO_TRACE is not set +# CONFIG_FTRACE_STARTUP_TEST is not set +# CONFIG_RING_BUFFER_BENCHMARK is not set +# CONFIG_DYNAMIC_DEBUG is not set +# CONFIG_DMA_API_DEBUG is not set +# CONFIG_SAMPLES is not set +CONFIG_HAVE_ARCH_KGDB=y +# CONFIG_KGDB is not set +# CONFIG_PPC_DISABLE_WERROR is not set +CONFIG_PPC_WERROR=y +CONFIG_PRINT_STACK_DEPTH=64 +# CONFIG_DEBUG_STACKOVERFLOW is not set +# CONFIG_DEBUG_STACK_USAGE is not set +# CONFIG_PPC_EMULATED_STATS is not set +# CONFIG_CODE_PATCHING_SELFTEST is not set +# CONFIG_FTR_FIXUP_SELFTEST is not set +# CONFIG_MSI_BITMAP_SELFTEST is not set +# CONFIG_XMON is not set +# CONFIG_IRQSTACKS is not set +# CONFIG_VIRQ_DEBUG is not set +# CONFIG_BDI_SWITCH is not set +# CONFIG_BOOTX_TEXT is not set +CONFIG_PPC_EARLY_DEBUG=y +# CONFIG_PPC_EARLY_DEBUG_LPAR is not set +# CONFIG_PPC_EARLY_DEBUG_G5 is not set +# CONFIG_PPC_EARLY_DEBUG_RTAS_PANEL is not set +# CONFIG_PPC_EARLY_DEBUG_RTAS_CONSOLE is not set +# CONFIG_PPC_EARLY_DEBUG_MAPLE is not set +# CONFIG_PPC_EARLY_DEBUG_ISERIES is not set +# CONFIG_PPC_EARLY_DEBUG_PAS_REALMODE is not set +# CONFIG_PPC_EARLY_DEBUG_BEAT is not set +# CONFIG_PPC_EARLY_DEBUG_44x is not set +# CONFIG_PPC_EARLY_DEBUG_40x is not set +# CONFIG_PPC_EARLY_DEBUG_CPM is not set +CONFIG_PPC_EARLY_DEBUG_USBGECKO=y + +# +# Security options +# +# CONFIG_KEYS is not set +# CONFIG_SECURITY is not set +# CONFIG_SECURITYFS is not set +# CONFIG_SECURITY_FILE_CAPABILITIES is not set +# CONFIG_CRYPTO is not set +# CONFIG_PPC_CLOCK is not set +# CONFIG_VIRTUALIZATION is not set diff --git a/arch/powerpc/configs/wii_defconfig b/arch/powerpc/configs/wii_defconfig new file mode 100644 index 0000000..7a5ab2b --- /dev/null +++ b/arch/powerpc/configs/wii_defconfig @@ -0,0 +1,1622 @@ +# +# Automatically generated make config: don't edit +# Linux kernel version: 2.6.32 +# Sun Dec 6 20:39:03 2009 +# +# CONFIG_PPC64 is not set + +# +# Processor support +# +CONFIG_PPC_BOOK3S_32=y +# CONFIG_PPC_85xx is not set +# CONFIG_PPC_8xx is not set +# CONFIG_40x is not set +# CONFIG_44x is not set +# CONFIG_E200 is not set +CONFIG_PPC_BOOK3S=y +CONFIG_6xx=y +CONFIG_PPC_FPU=y +# CONFIG_ALTIVEC is not set +CONFIG_PPC_STD_MMU=y +CONFIG_PPC_STD_MMU_32=y +# CONFIG_PPC_MM_SLICES is not set +CONFIG_PPC_HAVE_PMU_SUPPORT=y +CONFIG_PPC_PERF_CTRS=y +# CONFIG_SMP is not set +CONFIG_NOT_COHERENT_CACHE=y +CONFIG_PPC32=y +CONFIG_WORD_SIZE=32 +# CONFIG_ARCH_PHYS_ADDR_T_64BIT is not set +CONFIG_MMU=y +CONFIG_GENERIC_CMOS_UPDATE=y +CONFIG_GENERIC_TIME=y +CONFIG_GENERIC_TIME_VSYSCALL=y +CONFIG_GENERIC_CLOCKEVENTS=y +CONFIG_GENERIC_HARDIRQS=y +CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ=y +# CONFIG_HAVE_SETUP_PER_CPU_AREA is not set +# CONFIG_NEED_PER_CPU_EMBED_FIRST_CHUNK is not set +CONFIG_IRQ_PER_CPU=y +CONFIG_STACKTRACE_SUPPORT=y +CONFIG_HAVE_LATENCYTOP_SUPPORT=y +CONFIG_TRACE_IRQFLAGS_SUPPORT=y +CONFIG_LOCKDEP_SUPPORT=y +CONFIG_RWSEM_XCHGADD_ALGORITHM=y +CONFIG_ARCH_HAS_ILOG2_U32=y +CONFIG_GENERIC_HWEIGHT=y +CONFIG_GENERIC_FIND_NEXT_BIT=y +CONFIG_GENERIC_GPIO=y +# CONFIG_ARCH_NO_VIRT_TO_BUS is not set +CONFIG_PPC=y +CONFIG_EARLY_PRINTK=y +CONFIG_GENERIC_NVRAM=y +CONFIG_SCHED_OMIT_FRAME_POINTER=y +CONFIG_ARCH_MAY_HAVE_PC_FDC=y +CONFIG_PPC_OF=y +CONFIG_OF=y +# CONFIG_PPC_UDBG_16550 is not set +# CONFIG_GENERIC_TBSYNC is not set +CONFIG_AUDIT_ARCH=y +CONFIG_GENERIC_BUG=y +CONFIG_DTC=y +# CONFIG_DEFAULT_UIMAGE is not set +# CONFIG_PPC_DCR_NATIVE is not set +# CONFIG_PPC_DCR_MMIO is not set +CONFIG_ARCH_SUPPORTS_DEBUG_PAGEALLOC=y +CONFIG_DEFCONFIG_LIST="/lib/modules/$UNAME_RELEASE/.config" +CONFIG_CONSTRUCTORS=y + +# +# General setup +# +CONFIG_EXPERIMENTAL=y +CONFIG_BROKEN_ON_SMP=y +CONFIG_LOCK_KERNEL=y +CONFIG_INIT_ENV_ARG_LIMIT=32 +CONFIG_LOCALVERSION="-isobel-wii" +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_SWAP=y +CONFIG_SYSVIPC=y +CONFIG_SYSVIPC_SYSCTL=y +# CONFIG_POSIX_MQUEUE is not set +# CONFIG_BSD_PROCESS_ACCT is not set +# CONFIG_TASKSTATS is not set +# CONFIG_AUDIT is not set + +# +# RCU Subsystem +# +CONFIG_TREE_RCU=y +# CONFIG_TREE_PREEMPT_RCU is not set +# CONFIG_RCU_TRACE is not set +CONFIG_RCU_FANOUT=32 +# CONFIG_RCU_FANOUT_EXACT is not set +# CONFIG_TREE_RCU_TRACE is not set +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_LOG_BUF_SHIFT=14 +CONFIG_GROUP_SCHED=y +CONFIG_FAIR_GROUP_SCHED=y +# CONFIG_RT_GROUP_SCHED is not set +CONFIG_USER_SCHED=y +# CONFIG_CGROUP_SCHED is not set +# CONFIG_CGROUPS is not set +CONFIG_SYSFS_DEPRECATED=y +CONFIG_SYSFS_DEPRECATED_V2=y +# CONFIG_RELAY is not set +# CONFIG_NAMESPACES is not set +CONFIG_BLK_DEV_INITRD=y +CONFIG_INITRAMFS_SOURCE="" +CONFIG_RD_GZIP=y +# CONFIG_RD_BZIP2 is not set +# CONFIG_RD_LZMA is not set +# CONFIG_CC_OPTIMIZE_FOR_SIZE is not set +CONFIG_SYSCTL=y +CONFIG_ANON_INODES=y +CONFIG_EMBEDDED=y +CONFIG_SYSCTL_SYSCALL=y +CONFIG_KALLSYMS=y +CONFIG_KALLSYMS_ALL=y +# CONFIG_KALLSYMS_EXTRA_PASS is not set +CONFIG_HOTPLUG=y +CONFIG_PRINTK=y +CONFIG_BUG=y +# CONFIG_ELF_CORE is not set +CONFIG_BASE_FULL=y +CONFIG_FUTEX=y +CONFIG_EPOLL=y +CONFIG_SIGNALFD=y +CONFIG_TIMERFD=y +CONFIG_EVENTFD=y +CONFIG_SHMEM=y +CONFIG_AIO=y +CONFIG_HAVE_PERF_EVENTS=y + +# +# Kernel Performance Events And Counters +# +CONFIG_PERF_EVENTS=y +CONFIG_EVENT_PROFILE=y +CONFIG_PERF_COUNTERS=y +# CONFIG_DEBUG_PERF_USE_VMALLOC is not set +# CONFIG_VM_EVENT_COUNTERS is not set +CONFIG_COMPAT_BRK=y +CONFIG_SLAB=y +# CONFIG_SLUB is not set +# CONFIG_SLOB is not set +# CONFIG_PROFILING is not set +CONFIG_TRACEPOINTS=y +CONFIG_HAVE_OPROFILE=y +# CONFIG_KPROBES is not set +CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y +CONFIG_HAVE_IOREMAP_PROT=y +CONFIG_HAVE_KPROBES=y +CONFIG_HAVE_KRETPROBES=y +CONFIG_HAVE_ARCH_TRACEHOOK=y +CONFIG_HAVE_DMA_ATTRS=y +CONFIG_HAVE_DMA_API_DEBUG=y + +# +# GCOV-based kernel profiling +# +# CONFIG_GCOV_KERNEL is not set +CONFIG_SLOW_WORK=y +# CONFIG_SLOW_WORK_DEBUG is not set +CONFIG_HAVE_GENERIC_DMA_COHERENT=y +CONFIG_SLABINFO=y +CONFIG_RT_MUTEXES=y +CONFIG_BASE_SMALL=0 +CONFIG_MODULES=y +# CONFIG_MODULE_FORCE_LOAD is not set +CONFIG_MODULE_UNLOAD=y +# CONFIG_MODULE_FORCE_UNLOAD is not set +# CONFIG_MODVERSIONS is not set +# CONFIG_MODULE_SRCVERSION_ALL is not set +CONFIG_BLOCK=y +CONFIG_LBDAF=y +# CONFIG_BLK_DEV_BSG is not set +# CONFIG_BLK_DEV_INTEGRITY is not set + +# +# IO Schedulers +# +CONFIG_IOSCHED_NOOP=y +CONFIG_IOSCHED_AS=y +CONFIG_IOSCHED_DEADLINE=y +CONFIG_IOSCHED_CFQ=y +CONFIG_DEFAULT_AS=y +# CONFIG_DEFAULT_DEADLINE is not set +# CONFIG_DEFAULT_CFQ is not set +# CONFIG_DEFAULT_NOOP is not set +CONFIG_DEFAULT_IOSCHED="anticipatory" +# CONFIG_FREEZER is not set + +# +# Platform support +# +# CONFIG_PPC_CHRP is not set +# CONFIG_MPC5121_ADS is not set +# CONFIG_MPC5121_GENERIC is not set +# CONFIG_PPC_MPC52xx is not set +# CONFIG_PPC_PMAC is not set +# CONFIG_PPC_CELL is not set +# CONFIG_PPC_CELL_NATIVE is not set +# CONFIG_PPC_82xx is not set +# CONFIG_PQ2ADS is not set +# CONFIG_PPC_83xx is not set +# CONFIG_PPC_86xx is not set +CONFIG_EMBEDDED6xx=y +# CONFIG_LINKSTATION is not set +# CONFIG_STORCENTER is not set +# CONFIG_MPC7448HPC2 is not set +# CONFIG_PPC_HOLLY is not set +# CONFIG_PPC_PRPMC2800 is not set +# CONFIG_PPC_C2K is not set +# CONFIG_GAMECUBE is not set +CONFIG_WII=y +CONFIG_STARLET_IOS=y +CONFIG_STARLET_MINI=y +CONFIG_FLIPPER_PIC=y +CONFIG_HLWD_PIC=y +CONFIG_GAMECUBE_COMMON=y +CONFIG_GAMECUBE_UDBG=y +CONFIG_USBGECKO_UDBG=y +# CONFIG_GAMECUBE_VIDEO_UDBG is not set +CONFIG_GAMECUBE_RSW=y +CONFIG_HLWD_GPIO=y +# CONFIG_AMIGAONE is not set +# CONFIG_PPC_OF_BOOT_TRAMPOLINE is not set +# CONFIG_IPIC is not set +# CONFIG_MPIC is not set +# CONFIG_MPIC_WEIRD is not set +# CONFIG_PPC_I8259 is not set +# CONFIG_PPC_RTAS is not set +# CONFIG_MMIO_NVRAM is not set +# CONFIG_PPC_MPC106 is not set +# CONFIG_PPC_970_NAP is not set +# CONFIG_PPC_INDIRECT_IO is not set +# CONFIG_GENERIC_IOMAP is not set +# CONFIG_CPU_FREQ is not set +# CONFIG_TAU is not set +# CONFIG_FSL_ULI1575 is not set +# CONFIG_SIMPLE_GPIO is not set + +# +# Kernel options +# +# CONFIG_HIGHMEM is not set +# CONFIG_NO_HZ is not set +# CONFIG_HIGH_RES_TIMERS is not set +CONFIG_GENERIC_CLOCKEVENTS_BUILD=y +# CONFIG_HZ_100 is not set +CONFIG_HZ_250=y +# CONFIG_HZ_300 is not set +# CONFIG_HZ_1000 is not set +CONFIG_HZ=250 +# CONFIG_SCHED_HRTICK is not set +# CONFIG_PREEMPT_NONE is not set +# CONFIG_PREEMPT_VOLUNTARY is not set +CONFIG_PREEMPT=y +CONFIG_BINFMT_ELF=y +# CONFIG_HAVE_AOUT is not set +CONFIG_BINFMT_MISC=m +# CONFIG_IOMMU_HELPER is not set +# CONFIG_SWIOTLB is not set +CONFIG_ARCH_ENABLE_MEMORY_HOTPLUG=y +CONFIG_ARCH_HAS_WALK_MEMORY=y +CONFIG_ARCH_ENABLE_MEMORY_HOTREMOVE=y +CONFIG_KEXEC=y +# CONFIG_CRASH_DUMP is not set +CONFIG_MAX_ACTIVE_REGIONS=32 +CONFIG_ARCH_FLATMEM_ENABLE=y +CONFIG_ARCH_POPULATES_NODE_MAP=y +CONFIG_SELECT_MEMORY_MODEL=y +CONFIG_FLATMEM_MANUAL=y +# CONFIG_DISCONTIGMEM_MANUAL is not set +# CONFIG_SPARSEMEM_MANUAL is not set +CONFIG_FLATMEM=y +CONFIG_FLAT_NODE_MEM_MAP=y +CONFIG_PAGEFLAGS_EXTENDED=y +CONFIG_SPLIT_PTLOCK_CPUS=4 +# CONFIG_MIGRATION is not set +# CONFIG_PHYS_ADDR_T_64BIT is not set +CONFIG_ZONE_DMA_FLAG=1 +CONFIG_BOUNCE=y +CONFIG_VIRT_TO_BUS=y +CONFIG_HAVE_MLOCK=y +CONFIG_HAVE_MLOCKED_PAGE_BIT=y +# CONFIG_KSM is not set +CONFIG_DEFAULT_MMAP_MIN_ADDR=4096 +CONFIG_PPC_4K_PAGES=y +# CONFIG_PPC_16K_PAGES is not set +# CONFIG_PPC_64K_PAGES is not set +# CONFIG_PPC_256K_PAGES is not set +CONFIG_FORCE_MAX_ZONEORDER=11 +CONFIG_PROC_DEVICETREE=y +# CONFIG_CMDLINE_BOOL is not set +CONFIG_EXTRA_TARGETS="" +# CONFIG_PM is not set +# CONFIG_SECCOMP is not set +CONFIG_ISA_DMA_API=y + +# +# Bus options +# +CONFIG_ZONE_DMA=y +CONFIG_GENERIC_ISA_DMA=y +# CONFIG_PCI is not set +# CONFIG_PCI_DOMAINS is not set +# CONFIG_PCI_SYSCALL is not set +# CONFIG_ARCH_SUPPORTS_MSI is not set +# CONFIG_PCCARD is not set +# CONFIG_HAS_RAPIDIO is not set + +# +# Advanced setup +# +CONFIG_ADVANCED_OPTIONS=y +# CONFIG_LOWMEM_SIZE_BOOL is not set +CONFIG_LOWMEM_SIZE=0x30000000 +# CONFIG_PAGE_OFFSET_BOOL is not set +CONFIG_PAGE_OFFSET=0xc0000000 +# CONFIG_KERNEL_START_BOOL is not set +CONFIG_KERNEL_START=0xc0000000 +CONFIG_PHYSICAL_START=0x00000000 +# CONFIG_TASK_SIZE_BOOL is not set +CONFIG_TASK_SIZE=0xc0000000 +# CONFIG_CONSISTENT_SIZE_BOOL is not set +CONFIG_CONSISTENT_SIZE=0x00200000 +CONFIG_NET=y + +# +# Networking options +# +CONFIG_PACKET=y +# CONFIG_PACKET_MMAP is not set +CONFIG_UNIX=y +# CONFIG_NET_KEY is not set +CONFIG_INET=y +# CONFIG_IP_MULTICAST is not set +# CONFIG_IP_ADVANCED_ROUTER is not set +CONFIG_IP_FIB_HASH=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +# CONFIG_IP_PNP_BOOTP is not set +CONFIG_IP_PNP_RARP=y +# CONFIG_NET_IPIP is not set +# CONFIG_NET_IPGRE is not set +# CONFIG_ARPD is not set +# CONFIG_SYN_COOKIES is not set +# CONFIG_INET_AH is not set +# CONFIG_INET_ESP is not set +# CONFIG_INET_IPCOMP is not set +# CONFIG_INET_XFRM_TUNNEL is not set +# CONFIG_INET_TUNNEL is not set +# CONFIG_INET_XFRM_MODE_TRANSPORT is not set +# CONFIG_INET_XFRM_MODE_TUNNEL is not set +# CONFIG_INET_XFRM_MODE_BEET is not set +# CONFIG_INET_LRO is not set +# CONFIG_INET_DIAG is not set +# CONFIG_TCP_CONG_ADVANCED is not set +CONFIG_TCP_CONG_CUBIC=y +CONFIG_DEFAULT_TCP_CONG="cubic" +# CONFIG_TCP_MD5SIG is not set +# CONFIG_IPV6 is not set +# CONFIG_NETWORK_SECMARK is not set +# CONFIG_NETFILTER is not set +# CONFIG_IP_DCCP is not set +# CONFIG_IP_SCTP is not set +# CONFIG_RDS is not set +# CONFIG_TIPC is not set +# CONFIG_ATM is not set +# CONFIG_BRIDGE is not set +# CONFIG_NET_DSA is not set +# CONFIG_VLAN_8021Q is not set +# CONFIG_DECNET is not set +# CONFIG_LLC2 is not set +# CONFIG_IPX is not set +# CONFIG_ATALK is not set +# CONFIG_X25 is not set +# CONFIG_LAPB is not set +# CONFIG_ECONET is not set +# CONFIG_WAN_ROUTER is not set +# CONFIG_PHONET is not set +# CONFIG_IEEE802154 is not set +# CONFIG_NET_SCHED is not set +# CONFIG_DCB is not set + +# +# Network testing +# +# CONFIG_NET_PKTGEN is not set +# CONFIG_NET_DROP_MONITOR is not set +# CONFIG_HAMRADIO is not set +# CONFIG_CAN is not set +# CONFIG_IRDA is not set +CONFIG_BT=y +CONFIG_BT_L2CAP=y +# CONFIG_BT_SCO is not set +CONFIG_BT_RFCOMM=y +CONFIG_BT_RFCOMM_TTY=y +CONFIG_BT_BNEP=y +CONFIG_BT_BNEP_MC_FILTER=y +# CONFIG_BT_BNEP_PROTO_FILTER is not set +CONFIG_BT_HIDP=y + +# +# Bluetooth device drivers +# +CONFIG_BT_HCIBTUSB=y +# CONFIG_BT_HCIBTSDIO is not set +# CONFIG_BT_HCIUART is not set +# CONFIG_BT_HCIBCM203X is not set +# CONFIG_BT_HCIBPA10X is not set +# CONFIG_BT_HCIBFUSB is not set +# CONFIG_BT_HCIVHCI is not set +# CONFIG_BT_MRVL is not set +# CONFIG_AF_RXRPC is not set +CONFIG_WIRELESS=y +CONFIG_CFG80211=y +# CONFIG_NL80211_TESTMODE is not set +# CONFIG_CFG80211_DEVELOPER_WARNINGS is not set +# CONFIG_CFG80211_REG_DEBUG is not set +CONFIG_CFG80211_DEFAULT_PS=y +CONFIG_CFG80211_DEFAULT_PS_VALUE=1 +# CONFIG_CFG80211_DEBUGFS is not set +CONFIG_WIRELESS_OLD_REGULATORY=y +CONFIG_WIRELESS_EXT=y +CONFIG_WIRELESS_EXT_SYSFS=y +# CONFIG_LIB80211 is not set +CONFIG_MAC80211=y +# CONFIG_MAC80211_RC_PID is not set +CONFIG_MAC80211_RC_MINSTREL=y +# CONFIG_MAC80211_RC_DEFAULT_PID is not set +CONFIG_MAC80211_RC_DEFAULT_MINSTREL=y +CONFIG_MAC80211_RC_DEFAULT="minstrel" +# CONFIG_MAC80211_MESH is not set +# CONFIG_MAC80211_LEDS is not set +# CONFIG_MAC80211_DEBUGFS is not set +# CONFIG_MAC80211_DEBUG_MENU is not set +# CONFIG_WIMAX is not set +# CONFIG_RFKILL is not set +# CONFIG_NET_9P is not set + +# +# Device Drivers +# + +# +# Generic Driver Options +# +CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +# CONFIG_DEVTMPFS is not set +# CONFIG_STANDALONE is not set +CONFIG_PREVENT_FIRMWARE_BUILD=y +CONFIG_FW_LOADER=y +# CONFIG_FIRMWARE_IN_KERNEL is not set +CONFIG_EXTRA_FIRMWARE="" +# CONFIG_DEBUG_DRIVER is not set +# CONFIG_DEBUG_DEVRES is not set +# CONFIG_SYS_HYPERVISOR is not set +# CONFIG_CONNECTOR is not set +# CONFIG_MTD is not set +CONFIG_OF_DEVICE=y +CONFIG_OF_GPIO=y +CONFIG_OF_I2C=y +# CONFIG_PARPORT is not set +CONFIG_BLK_DEV=y +# CONFIG_BLK_DEV_FD is not set +CONFIG_GAMECUBE_SD=m +CONFIG_WII_MEM2=m +CONFIG_WII_SD=y +CONFIG_WII_DI=y +# CONFIG_BLK_DEV_COW_COMMON is not set +CONFIG_BLK_DEV_LOOP=y +# CONFIG_BLK_DEV_CRYPTOLOOP is not set +# CONFIG_BLK_DEV_NBD is not set +# CONFIG_BLK_DEV_UB is not set +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_COUNT=2 +CONFIG_BLK_DEV_RAM_SIZE=4096 +# CONFIG_BLK_DEV_XIP is not set +# CONFIG_CDROM_PKTCDVD is not set +# CONFIG_ATA_OVER_ETH is not set +# CONFIG_BLK_DEV_HD is not set +CONFIG_MISC_DEVICES=y +CONFIG_GAMECUBE_GQR=m +# CONFIG_ICS932S401 is not set +# CONFIG_ENCLOSURE_SERVICES is not set +# CONFIG_ISL29003 is not set +# CONFIG_C2PORT is not set + +# +# EEPROM support +# +# CONFIG_EEPROM_AT24 is not set +# CONFIG_EEPROM_LEGACY is not set +# CONFIG_EEPROM_MAX6875 is not set +# CONFIG_EEPROM_93CX6 is not set +CONFIG_HAVE_IDE=y +# CONFIG_IDE is not set + +# +# SCSI device support +# +# CONFIG_RAID_ATTRS is not set +CONFIG_SCSI=y +CONFIG_SCSI_DMA=y +# CONFIG_SCSI_TGT is not set +# CONFIG_SCSI_NETLINK is not set +CONFIG_SCSI_PROC_FS=y + +# +# SCSI support type (disk, tape, CD-ROM) +# +CONFIG_BLK_DEV_SD=y +# CONFIG_CHR_DEV_ST is not set +# CONFIG_CHR_DEV_OSST is not set +# CONFIG_BLK_DEV_SR is not set +# CONFIG_CHR_DEV_SG is not set +# CONFIG_CHR_DEV_SCH is not set +CONFIG_SCSI_MULTI_LUN=y +# CONFIG_SCSI_CONSTANTS is not set +# CONFIG_SCSI_LOGGING is not set +# CONFIG_SCSI_SCAN_ASYNC is not set +CONFIG_SCSI_WAIT_SCAN=m + +# +# SCSI Transports +# +# CONFIG_SCSI_SPI_ATTRS is not set +# CONFIG_SCSI_FC_ATTRS is not set +# CONFIG_SCSI_ISCSI_ATTRS is not set +# CONFIG_SCSI_SAS_LIBSAS is not set +# CONFIG_SCSI_SRP_ATTRS is not set +CONFIG_SCSI_LOWLEVEL=y +# CONFIG_ISCSI_TCP is not set +# CONFIG_LIBFC is not set +# CONFIG_LIBFCOE is not set +# CONFIG_SCSI_DEBUG is not set +# CONFIG_SCSI_DH is not set +# CONFIG_SCSI_OSD_INITIATOR is not set +# CONFIG_ATA is not set +# CONFIG_MD is not set +# CONFIG_MACINTOSH_DRIVERS is not set +CONFIG_NETDEVICES=y +# CONFIG_DUMMY is not set +# CONFIG_BONDING is not set +# CONFIG_MACVLAN is not set +# CONFIG_EQUALIZER is not set +# CONFIG_TUN is not set +# CONFIG_VETH is not set +# CONFIG_PHYLIB is not set +CONFIG_NET_ETHERNET=y +CONFIG_MII=y +# CONFIG_ETHOC is not set +# CONFIG_DNET is not set +# CONFIG_IBM_NEW_EMAC_ZMII is not set +# CONFIG_IBM_NEW_EMAC_RGMII is not set +# CONFIG_IBM_NEW_EMAC_TAH is not set +# CONFIG_IBM_NEW_EMAC_EMAC4 is not set +# CONFIG_IBM_NEW_EMAC_NO_FLOW_CTRL is not set +# CONFIG_IBM_NEW_EMAC_MAL_CLR_ICINTSTAT is not set +# CONFIG_IBM_NEW_EMAC_MAL_COMMON_ERR is not set +# CONFIG_B44 is not set +# CONFIG_KS8842 is not set +# CONFIG_KS8851_MLL is not set +# CONFIG_XILINX_EMACLITE is not set +# CONFIG_NETDEV_1000 is not set +# CONFIG_NETDEV_10000 is not set +CONFIG_WLAN=y +# CONFIG_WLAN_PRE80211 is not set +CONFIG_WLAN_80211=y +# CONFIG_LIBERTAS is not set +# CONFIG_LIBERTAS_THINFIRM is not set +# CONFIG_AT76C50X_USB is not set +# CONFIG_USB_ZD1201 is not set +# CONFIG_USB_NET_RNDIS_WLAN is not set +# CONFIG_RTL8187 is not set +# CONFIG_MAC80211_HWSIM is not set +# CONFIG_P54_COMMON is not set +# CONFIG_ATH_COMMON is not set +# CONFIG_HOSTAP is not set +CONFIG_B43=y +CONFIG_B43_SDIO=y +CONFIG_B43_PIO=y +# CONFIG_B43_PHY_LP is not set +# CONFIG_B43_DEBUG is not set +CONFIG_B43_DEFAULT_QOS_OFF=y +# CONFIG_B43LEGACY is not set +# CONFIG_ZD1211RW is not set +# CONFIG_RT2X00 is not set +# CONFIG_WL12XX is not set +# CONFIG_IWM is not set + +# +# Enable WiMAX (Networking options) to see the WiMAX drivers +# + +# +# USB Network Adapters +# +# CONFIG_USB_CATC is not set +# CONFIG_USB_KAWETH is not set +# CONFIG_USB_PEGASUS is not set +# CONFIG_USB_RTL8150 is not set +CONFIG_USB_USBNET=y +CONFIG_USB_NET_AX8817X=y +# CONFIG_USB_NET_CDCETHER is not set +# CONFIG_USB_NET_CDC_EEM is not set +# CONFIG_USB_NET_DM9601 is not set +# CONFIG_USB_NET_SMSC95XX is not set +# CONFIG_USB_NET_GL620A is not set +# CONFIG_USB_NET_NET1080 is not set +# CONFIG_USB_NET_PLUSB is not set +# CONFIG_USB_NET_MCS7830 is not set +# CONFIG_USB_NET_RNDIS_HOST is not set +# CONFIG_USB_NET_CDC_SUBSET is not set +# CONFIG_USB_NET_ZAURUS is not set +# CONFIG_USB_NET_INT51X1 is not set +# CONFIG_WAN is not set +# CONFIG_PPP is not set +# CONFIG_SLIP is not set +# CONFIG_NETCONSOLE is not set +# CONFIG_NETPOLL is not set +# CONFIG_NET_POLL_CONTROLLER is not set +# CONFIG_ISDN is not set +# CONFIG_PHONE is not set + +# +# Input device support +# +CONFIG_INPUT=y +CONFIG_INPUT_FF_MEMLESS=m +# CONFIG_INPUT_POLLDEV is not set + +# +# Userland interfaces +# +CONFIG_INPUT_MOUSEDEV=y +# CONFIG_INPUT_MOUSEDEV_PSAUX is not set +CONFIG_INPUT_MOUSEDEV_SCREEN_X=640 +CONFIG_INPUT_MOUSEDEV_SCREEN_Y=480 +CONFIG_INPUT_JOYDEV=y +CONFIG_INPUT_EVDEV=y +# CONFIG_INPUT_EVBUG is not set + +# +# Input Device Drivers +# +CONFIG_INPUT_KEYBOARD=y +# CONFIG_KEYBOARD_ADP5588 is not set +# CONFIG_KEYBOARD_ATKBD is not set +# CONFIG_QT2160 is not set +# CONFIG_KEYBOARD_LKKBD is not set +# CONFIG_KEYBOARD_GPIO is not set +# CONFIG_KEYBOARD_MATRIX is not set +# CONFIG_KEYBOARD_MAX7359 is not set +# CONFIG_KEYBOARD_NEWTON is not set +# CONFIG_KEYBOARD_OPENCORES is not set +# CONFIG_KEYBOARD_STOWAWAY is not set +# CONFIG_KEYBOARD_SUNKBD is not set +# CONFIG_KEYBOARD_XTKBD is not set +CONFIG_INPUT_MOUSE=y +# CONFIG_MOUSE_PS2 is not set +# CONFIG_MOUSE_SERIAL is not set +# CONFIG_MOUSE_APPLETOUCH is not set +# CONFIG_MOUSE_BCM5974 is not set +# CONFIG_MOUSE_VSXXXAA is not set +# CONFIG_MOUSE_GPIO is not set +# CONFIG_MOUSE_SYNAPTICS_I2C is not set +CONFIG_INPUT_JOYSTICK=y +# CONFIG_JOYSTICK_ANALOG is not set +# CONFIG_JOYSTICK_A3D is not set +# CONFIG_JOYSTICK_ADI is not set +# CONFIG_JOYSTICK_COBRA is not set +# CONFIG_JOYSTICK_GF2K is not set +# CONFIG_JOYSTICK_GRIP is not set +# CONFIG_JOYSTICK_GRIP_MP is not set +# CONFIG_JOYSTICK_GUILLEMOT is not set +# CONFIG_JOYSTICK_INTERACT is not set +# CONFIG_JOYSTICK_SIDEWINDER is not set +# CONFIG_JOYSTICK_TMDC is not set +# CONFIG_JOYSTICK_IFORCE is not set +# CONFIG_JOYSTICK_WARRIOR is not set +# CONFIG_JOYSTICK_MAGELLAN is not set +# CONFIG_JOYSTICK_SPACEORB is not set +# CONFIG_JOYSTICK_SPACEBALL is not set +# CONFIG_JOYSTICK_STINGER is not set +# CONFIG_JOYSTICK_TWIDJOY is not set +# CONFIG_JOYSTICK_ZHENHUA is not set +# CONFIG_JOYSTICK_JOYDUMP is not set +# CONFIG_JOYSTICK_XPAD is not set +# CONFIG_INPUT_TABLET is not set +# CONFIG_INPUT_TOUCHSCREEN is not set +CONFIG_INPUT_MISC=y +# CONFIG_INPUT_ATI_REMOTE is not set +# CONFIG_INPUT_ATI_REMOTE2 is not set +# CONFIG_INPUT_KEYSPAN_REMOTE is not set +# CONFIG_INPUT_POWERMATE is not set +# CONFIG_INPUT_YEALINK is not set +# CONFIG_INPUT_CM109 is not set +CONFIG_INPUT_UINPUT=y +# CONFIG_INPUT_GPIO_ROTARY_ENCODER is not set + +# +# Hardware I/O ports +# +CONFIG_SERIO=y +# CONFIG_SERIO_I8042 is not set +# CONFIG_SERIO_SERPORT is not set +# CONFIG_SERIO_LIBPS2 is not set +# CONFIG_SERIO_RAW is not set +# CONFIG_SERIO_XILINX_XPS_PS2 is not set +# CONFIG_GAMEPORT is not set +CONFIG_GAMECUBE_SI=y + +# +# Character devices +# +CONFIG_VT=y +CONFIG_CONSOLE_TRANSLATIONS=y +CONFIG_VT_CONSOLE=y +CONFIG_HW_CONSOLE=y +# CONFIG_VT_HW_CONSOLE_BINDING is not set +# CONFIG_DEVKMEM is not set +# CONFIG_SERIAL_NONSTANDARD is not set + +# +# Serial drivers +# +# CONFIG_SERIAL_8250 is not set + +# +# Non-8250 serial port support +# +# CONFIG_SERIAL_UARTLITE is not set +# CONFIG_SERIAL_USBGECKO is not set +CONFIG_UNIX98_PTYS=y +# CONFIG_DEVPTS_MULTIPLE_INSTANCES is not set +CONFIG_LEGACY_PTYS=y +CONFIG_LEGACY_PTY_COUNT=64 +# CONFIG_HVC_UDBG is not set +# CONFIG_IPMI_HANDLER is not set +# CONFIG_HW_RANDOM is not set +CONFIG_NVRAM=y +# CONFIG_R3964 is not set +# CONFIG_RAW_DRIVER is not set +# CONFIG_TCG_TPM is not set +CONFIG_I2C=y +CONFIG_I2C_BOARDINFO=y +CONFIG_I2C_COMPAT=y +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_HELPER_AUTO=y +CONFIG_I2C_ALGOBIT=y + +# +# I2C Hardware Bus support +# + +# +# I2C system bus drivers (mostly embedded / system-on-chip) +# +CONFIG_I2C_GPIO_COMMON=y +CONFIG_I2C_GPIO=y +CONFIG_I2C_GPIO_OF=y +# CONFIG_I2C_MPC is not set +# CONFIG_I2C_OCORES is not set +# CONFIG_I2C_SIMTEC is not set + +# +# External I2C/SMBus adapter drivers +# +# CONFIG_I2C_PARPORT_LIGHT is not set +# CONFIG_I2C_TAOS_EVM is not set +# CONFIG_I2C_TINY_USB is not set + +# +# Other I2C/SMBus bus drivers +# +# CONFIG_I2C_PCA_PLATFORM is not set +# CONFIG_I2C_STUB is not set + +# +# Miscellaneous I2C Chip support +# +# CONFIG_DS1682 is not set +# CONFIG_SENSORS_TSL2550 is not set +# CONFIG_I2C_DEBUG_CORE is not set +# CONFIG_I2C_DEBUG_ALGO is not set +# CONFIG_I2C_DEBUG_BUS is not set +# CONFIG_I2C_DEBUG_CHIP is not set + +# +# EXI support +# +CONFIG_GAMECUBE_EXI=y +# CONFIG_SPI is not set + +# +# PPS support +# +# CONFIG_PPS is not set +CONFIG_ARCH_WANT_OPTIONAL_GPIOLIB=y +CONFIG_GPIOLIB=y +# CONFIG_DEBUG_GPIO is not set +CONFIG_GPIO_SYSFS=y + +# +# Memory mapped GPIO expanders: +# +# CONFIG_GPIO_XILINX is not set + +# +# I2C GPIO expanders: +# +# CONFIG_GPIO_MAX732X is not set +# CONFIG_GPIO_PCA953X is not set +# CONFIG_GPIO_PCF857X is not set + +# +# PCI GPIO expanders: +# + +# +# SPI GPIO expanders: +# + +# +# AC97 GPIO expanders: +# +# CONFIG_W1 is not set +# CONFIG_POWER_SUPPLY is not set +# CONFIG_HWMON is not set +# CONFIG_THERMAL is not set +# CONFIG_WATCHDOG is not set +CONFIG_SSB_POSSIBLE=y + +# +# Sonics Silicon Backplane +# +CONFIG_SSB=y +CONFIG_SSB_BLOCKIO=y +CONFIG_SSB_SDIOHOST_POSSIBLE=y +CONFIG_SSB_SDIOHOST=y +# CONFIG_SSB_SILENT is not set +CONFIG_SSB_DEBUG=y + +# +# Multifunction device drivers +# +# CONFIG_MFD_CORE is not set +# CONFIG_MFD_SM501 is not set +# CONFIG_HTC_PASIC3 is not set +# CONFIG_TPS65010 is not set +# CONFIG_TWL4030_CORE is not set +# CONFIG_MFD_TMIO is not set +# CONFIG_PMIC_DA903X is not set +# CONFIG_MFD_WM8400 is not set +# CONFIG_MFD_WM831X is not set +# CONFIG_MFD_WM8350_I2C is not set +# CONFIG_MFD_PCF50633 is not set +# CONFIG_AB3100_CORE is not set +# CONFIG_REGULATOR is not set +# CONFIG_MEDIA_SUPPORT is not set + +# +# Graphics support +# +# CONFIG_VGASTATE is not set +# CONFIG_VIDEO_OUTPUT_CONTROL is not set +CONFIG_FB=y +# CONFIG_FIRMWARE_EDID is not set +# CONFIG_FB_DDC is not set +# CONFIG_FB_BOOT_VESA_SUPPORT is not set +CONFIG_FB_CFB_FILLRECT=y +CONFIG_FB_CFB_COPYAREA=y +CONFIG_FB_CFB_IMAGEBLIT=y +# CONFIG_FB_CFB_REV_PIXELS_IN_BYTE is not set +# CONFIG_FB_SYS_FILLRECT is not set +# CONFIG_FB_SYS_COPYAREA is not set +# CONFIG_FB_SYS_IMAGEBLIT is not set +# CONFIG_FB_FOREIGN_ENDIAN is not set +# CONFIG_FB_SYS_FOPS is not set +# CONFIG_FB_SVGALIB is not set +# CONFIG_FB_MACMODES is not set +# CONFIG_FB_BACKLIGHT is not set +# CONFIG_FB_MODE_HELPERS is not set +# CONFIG_FB_TILEBLITTING is not set + +# +# Frame buffer hardware drivers +# +# CONFIG_FB_OF is not set +# CONFIG_FB_VGA16 is not set +# CONFIG_FB_S1D13XXX is not set +CONFIG_FB_GAMECUBE=y +CONFIG_WII_AVE_RVL=y +# CONFIG_FB_IBM_GXT4500 is not set +# CONFIG_FB_VIRTUAL is not set +# CONFIG_FB_METRONOME is not set +# CONFIG_FB_MB862XX is not set +# CONFIG_FB_BROADSHEET is not set +# CONFIG_BACKLIGHT_LCD_SUPPORT is not set + +# +# Display device support +# +# CONFIG_DISPLAY_SUPPORT is not set + +# +# Console display driver support +# +# CONFIG_VGA_CONSOLE is not set +CONFIG_DUMMY_CONSOLE=y +CONFIG_FRAMEBUFFER_CONSOLE=y +# CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY is not set +# CONFIG_FRAMEBUFFER_CONSOLE_ROTATION is not set +# CONFIG_FONTS is not set +CONFIG_FONT_8x8=y +CONFIG_FONT_8x16=y +# CONFIG_LOGO is not set +CONFIG_SOUND=y +CONFIG_SOUND_OSS_CORE=y +CONFIG_SOUND_OSS_CORE_PRECLAIM=y +CONFIG_SND=y +CONFIG_SND_TIMER=y +CONFIG_SND_PCM=y +CONFIG_SND_HWDEP=y +CONFIG_SND_RAWMIDI=y +CONFIG_SND_SEQUENCER=y +# CONFIG_SND_SEQ_DUMMY is not set +CONFIG_SND_OSSEMUL=y +CONFIG_SND_MIXER_OSS=y +CONFIG_SND_PCM_OSS=y +CONFIG_SND_PCM_OSS_PLUGINS=y +CONFIG_SND_SEQUENCER_OSS=y +# CONFIG_SND_DYNAMIC_MINORS is not set +CONFIG_SND_SUPPORT_OLD_API=y +# CONFIG_SND_VERBOSE_PROCFS is not set +# CONFIG_SND_VERBOSE_PRINTK is not set +# CONFIG_SND_DEBUG is not set +CONFIG_SND_RAWMIDI_SEQ=y +# CONFIG_SND_OPL3_LIB_SEQ is not set +# CONFIG_SND_OPL4_LIB_SEQ is not set +# CONFIG_SND_SBAWE_SEQ is not set +# CONFIG_SND_EMU10K1_SEQ is not set +CONFIG_SND_DRIVERS=y +# CONFIG_SND_DUMMY is not set +# CONFIG_SND_VIRMIDI is not set +# CONFIG_SND_MTPAV is not set +# CONFIG_SND_SERIAL_U16550 is not set +# CONFIG_SND_MPU401 is not set +CONFIG_SND_PPC=y +CONFIG_SND_GAMECUBE=y +CONFIG_SND_GAMECUBE_MIC=m +CONFIG_SND_USB=y +CONFIG_SND_USB_AUDIO=y +CONFIG_SND_USB_USX2Y=m +CONFIG_SND_USB_CAIAQ=m +# CONFIG_SND_USB_CAIAQ_INPUT is not set +# CONFIG_SND_SOC is not set +# CONFIG_SOUND_PRIME is not set +CONFIG_HID_SUPPORT=y +CONFIG_HID=y +# CONFIG_HIDRAW is not set + +# +# USB Input Devices +# +CONFIG_USB_HID=y +# CONFIG_HID_PID is not set +# CONFIG_USB_HIDDEV is not set + +# +# Special HID drivers +# +CONFIG_HID_A4TECH=m +CONFIG_HID_APPLE=m +CONFIG_HID_BELKIN=m +CONFIG_HID_CHERRY=m +CONFIG_HID_CHICONY=m +CONFIG_HID_CYPRESS=m +CONFIG_HID_DRAGONRISE=m +CONFIG_DRAGONRISE_FF=y +CONFIG_HID_EZKEY=m +CONFIG_HID_KYE=m +CONFIG_HID_GYRATION=m +CONFIG_HID_TWINHAN=m +CONFIG_HID_KENSINGTON=m +CONFIG_HID_LOGITECH=m +CONFIG_LOGITECH_FF=y +CONFIG_LOGIRUMBLEPAD2_FF=y +CONFIG_HID_MICROSOFT=m +CONFIG_HID_MONTEREY=m +CONFIG_HID_NTRIG=m +CONFIG_HID_PANTHERLORD=m +CONFIG_PANTHERLORD_FF=y +CONFIG_HID_PETALYNX=m +CONFIG_HID_SAMSUNG=m +CONFIG_HID_SONY=m +CONFIG_HID_SUNPLUS=m +CONFIG_HID_GREENASIA=m +CONFIG_GREENASIA_FF=y +CONFIG_HID_SMARTJOYPLUS=m +CONFIG_SMARTJOYPLUS_FF=y +CONFIG_HID_TOPSEED=m +CONFIG_HID_THRUSTMASTER=m +CONFIG_THRUSTMASTER_FF=y +CONFIG_HID_WACOM=m +CONFIG_HID_ZEROPLUS=m +CONFIG_ZEROPLUS_FF=y +CONFIG_USB_SUPPORT=y +CONFIG_USB_ARCH_HAS_HCD=y +CONFIG_USB_ARCH_HAS_OHCI=y +CONFIG_USB_ARCH_HAS_EHCI=y +CONFIG_USB=y +# CONFIG_USB_DEBUG is not set +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y + +# +# Miscellaneous USB options +# +CONFIG_USB_DEVICEFS=y +CONFIG_USB_DEVICE_CLASS=y +# CONFIG_USB_DYNAMIC_MINORS is not set +# CONFIG_USB_OTG is not set +# CONFIG_USB_OTG_WHITELIST is not set +# CONFIG_USB_OTG_BLACKLIST_HUB is not set +# CONFIG_USB_MON is not set +# CONFIG_USB_WUSB is not set +# CONFIG_USB_WUSB_CBAF is not set + +# +# USB Host Controller Drivers +# +# CONFIG_USB_C67X00_HCD is not set +CONFIG_USB_EHCI_HCD=y +# CONFIG_USB_EHCI_ROOT_HUB_TT is not set +# CONFIG_USB_EHCI_TT_NEWSCHED is not set +# CONFIG_USB_EHCI_HCD_PPC_OF is not set +CONFIG_USB_EHCI_HCD_HLWD=y +# CONFIG_USB_OXU210HP_HCD is not set +# CONFIG_USB_ISP116X_HCD is not set +# CONFIG_USB_ISP1760_HCD is not set +# CONFIG_USB_ISP1362_HCD is not set +CONFIG_USB_OHCI_HCD=y +# CONFIG_USB_OHCI_HCD_PPC_OF_BE is not set +# CONFIG_USB_OHCI_HCD_PPC_OF_LE is not set +# CONFIG_USB_OHCI_HCD_PPC_OF is not set +CONFIG_USB_OHCI_HCD_HLWD=y +# CONFIG_USB_OHCI_HCD_SSB is not set +# CONFIG_USB_OHCI_BIG_ENDIAN_DESC is not set +# CONFIG_USB_OHCI_BIG_ENDIAN_MMIO is not set +CONFIG_USB_OHCI_LITTLE_ENDIAN=y +# CONFIG_USB_SL811_HCD is not set +# CONFIG_USB_R8A66597_HCD is not set +CONFIG_USB_WII_HCD=y +# CONFIG_USB_HWA_HCD is not set + +# +# USB Device Class drivers +# +# CONFIG_USB_ACM is not set +# CONFIG_USB_PRINTER is not set +# CONFIG_USB_WDM is not set +# CONFIG_USB_TMC is not set + +# +# NOTE: USB_STORAGE depends on SCSI but BLK_DEV_SD may +# + +# +# also be needed; see USB_STORAGE Help for more info +# +CONFIG_USB_STORAGE=y +# CONFIG_USB_STORAGE_DEBUG is not set +# CONFIG_USB_STORAGE_DATAFAB is not set +# CONFIG_USB_STORAGE_FREECOM is not set +# CONFIG_USB_STORAGE_ISD200 is not set +# CONFIG_USB_STORAGE_USBAT is not set +# CONFIG_USB_STORAGE_SDDR09 is not set +# CONFIG_USB_STORAGE_SDDR55 is not set +# CONFIG_USB_STORAGE_JUMPSHOT is not set +# CONFIG_USB_STORAGE_ALAUDA is not set +# CONFIG_USB_STORAGE_ONETOUCH is not set +# CONFIG_USB_STORAGE_KARMA is not set +# CONFIG_USB_STORAGE_CYPRESS_ATACB is not set +CONFIG_USB_LIBUSUAL=y + +# +# USB Imaging devices +# +# CONFIG_USB_MDC800 is not set +# CONFIG_USB_MICROTEK is not set + +# +# USB port drivers +# +# CONFIG_USB_SERIAL is not set + +# +# USB Miscellaneous drivers +# +# CONFIG_USB_EMI62 is not set +# CONFIG_USB_EMI26 is not set +# CONFIG_USB_ADUTUX is not set +# CONFIG_USB_SEVSEG is not set +# CONFIG_USB_RIO500 is not set +# CONFIG_USB_LEGOTOWER is not set +# CONFIG_USB_LCD is not set +# CONFIG_USB_BERRY_CHARGE is not set +# CONFIG_USB_LED is not set +# CONFIG_USB_CYPRESS_CY7C63 is not set +# CONFIG_USB_CYTHERM is not set +# CONFIG_USB_IDMOUSE is not set +# CONFIG_USB_FTDI_ELAN is not set +# CONFIG_USB_APPLEDISPLAY is not set +# CONFIG_USB_SISUSBVGA is not set +# CONFIG_USB_LD is not set +# CONFIG_USB_TRANCEVIBRATOR is not set +# CONFIG_USB_IOWARRIOR is not set +# CONFIG_USB_TEST is not set +# CONFIG_USB_ISIGHTFW is not set +# CONFIG_USB_VST is not set +# CONFIG_USB_GADGET is not set + +# +# OTG and related infrastructure +# +# CONFIG_USB_GPIO_VBUS is not set +# CONFIG_NOP_USB_XCEIV is not set +CONFIG_MMC=y +# CONFIG_MMC_DEBUG is not set +# CONFIG_MMC_UNSAFE_RESUME is not set + +# +# MMC/SD/SDIO Card Drivers +# +CONFIG_MMC_BLOCK=y +CONFIG_MMC_BLOCK_BOUNCE=y +# CONFIG_SDIO_UART is not set +# CONFIG_MMC_TEST is not set + +# +# MMC/SD/SDIO Host Controller Drivers +# +CONFIG_MMC_SDHCI=y +CONFIG_MMC_SDHCI_IO_ACCESSORS=y +# CONFIG_MMC_SDHCI_OF is not set +# CONFIG_MMC_SDHCI_PLTFM is not set +CONFIG_MMC_SDHCI_HLWD=y +# CONFIG_MMC_WBSD is not set +# CONFIG_MMC_AT91 is not set +# CONFIG_MMC_ATMELMCI is not set +# CONFIG_MEMSTICK is not set +# CONFIG_NEW_LEDS is not set +# CONFIG_ACCESSIBILITY is not set +# CONFIG_EDAC is not set +CONFIG_RTC_LIB=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_HCTOSYS=y +CONFIG_RTC_HCTOSYS_DEVICE="rtc0" +# CONFIG_RTC_DEBUG is not set + +# +# RTC interfaces +# +CONFIG_RTC_INTF_SYSFS=y +CONFIG_RTC_INTF_PROC=y +CONFIG_RTC_INTF_DEV=y +# CONFIG_RTC_INTF_DEV_UIE_EMUL is not set +# CONFIG_RTC_DRV_TEST is not set + +# +# I2C RTC drivers +# +# CONFIG_RTC_DRV_DS1307 is not set +# CONFIG_RTC_DRV_DS1374 is not set +# CONFIG_RTC_DRV_DS1672 is not set +# CONFIG_RTC_DRV_MAX6900 is not set +# CONFIG_RTC_DRV_RS5C372 is not set +# CONFIG_RTC_DRV_ISL1208 is not set +# CONFIG_RTC_DRV_X1205 is not set +# CONFIG_RTC_DRV_PCF8563 is not set +# CONFIG_RTC_DRV_PCF8583 is not set +# CONFIG_RTC_DRV_M41T80 is not set +# CONFIG_RTC_DRV_S35390A is not set +# CONFIG_RTC_DRV_FM3130 is not set +# CONFIG_RTC_DRV_RX8581 is not set +# CONFIG_RTC_DRV_RX8025 is not set + +# +# SPI RTC drivers +# + +# +# Platform RTC drivers +# +# CONFIG_RTC_DRV_CMOS is not set +# CONFIG_RTC_DRV_DS1286 is not set +# CONFIG_RTC_DRV_DS1511 is not set +# CONFIG_RTC_DRV_DS1553 is not set +# CONFIG_RTC_DRV_DS1742 is not set +# CONFIG_RTC_DRV_STK17TA8 is not set +# CONFIG_RTC_DRV_M48T86 is not set +# CONFIG_RTC_DRV_M48T35 is not set +# CONFIG_RTC_DRV_M48T59 is not set +# CONFIG_RTC_DRV_BQ4802 is not set +# CONFIG_RTC_DRV_V3020 is not set +CONFIG_RTC_DRV_GCN=y + +# +# on-CPU RTC drivers +# +CONFIG_RTC_DRV_GENERIC=y +# CONFIG_DMADEVICES is not set +# CONFIG_AUXDISPLAY is not set +# CONFIG_UIO is not set + +# +# TI VLYNQ +# +# CONFIG_STAGING is not set + +# +# File systems +# +CONFIG_EXT2_FS=y +# CONFIG_EXT2_FS_XATTR is not set +# CONFIG_EXT2_FS_XIP is not set +CONFIG_EXT3_FS=y +# CONFIG_EXT3_DEFAULTS_TO_ORDERED is not set +# CONFIG_EXT3_FS_XATTR is not set +# CONFIG_EXT4_FS is not set +CONFIG_JBD=y +# CONFIG_JBD_DEBUG is not set +# CONFIG_REISERFS_FS is not set +# CONFIG_JFS_FS is not set +# CONFIG_FS_POSIX_ACL is not set +# CONFIG_XFS_FS is not set +# CONFIG_GFS2_FS is not set +# CONFIG_OCFS2_FS is not set +# CONFIG_BTRFS_FS is not set +# CONFIG_NILFS2_FS is not set +CONFIG_FILE_LOCKING=y +CONFIG_FSNOTIFY=y +CONFIG_DNOTIFY=y +CONFIG_INOTIFY=y +CONFIG_INOTIFY_USER=y +# CONFIG_QUOTA is not set +# CONFIG_AUTOFS_FS is not set +# CONFIG_AUTOFS4_FS is not set +CONFIG_FUSE_FS=m +CONFIG_CUSE=m + +# +# Caches +# +# CONFIG_FSCACHE is not set + +# +# CD-ROM/DVD Filesystems +# +CONFIG_ISO9660_FS=y +CONFIG_JOLIET=y +# CONFIG_ZISOFS is not set +# CONFIG_UDF_FS is not set + +# +# DOS/FAT/NT Filesystems +# +CONFIG_FAT_FS=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_CODEPAGE=437 +CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1" +# CONFIG_NTFS_FS is not set + +# +# Pseudo filesystems +# +CONFIG_PROC_FS=y +CONFIG_PROC_KCORE=y +CONFIG_PROC_SYSCTL=y +# CONFIG_PROC_PAGE_MONITOR is not set +CONFIG_SYSFS=y +CONFIG_TMPFS=y +# CONFIG_TMPFS_POSIX_ACL is not set +# CONFIG_HUGETLB_PAGE is not set +# CONFIG_CONFIGFS_FS is not set +CONFIG_MISC_FILESYSTEMS=y +# CONFIG_ADFS_FS is not set +# CONFIG_AFFS_FS is not set +# CONFIG_HFS_FS is not set +# CONFIG_HFSPLUS_FS is not set +# CONFIG_BEFS_FS is not set +# CONFIG_BFS_FS is not set +# CONFIG_EFS_FS is not set +# CONFIG_CRAMFS is not set +# CONFIG_SQUASHFS is not set +# CONFIG_VXFS_FS is not set +# CONFIG_MINIX_FS is not set +# CONFIG_OMFS_FS is not set +# CONFIG_HPFS_FS is not set +# CONFIG_QNX4FS_FS is not set +# CONFIG_ROMFS_FS is not set +# CONFIG_SYSV_FS is not set +# CONFIG_UFS_FS is not set +CONFIG_NETWORK_FILESYSTEMS=y +CONFIG_NFS_FS=y +CONFIG_NFS_V3=y +# CONFIG_NFS_V3_ACL is not set +# CONFIG_NFS_V4 is not set +CONFIG_ROOT_NFS=y +# CONFIG_NFSD is not set +CONFIG_LOCKD=y +CONFIG_LOCKD_V4=y +CONFIG_NFS_COMMON=y +CONFIG_SUNRPC=y +# CONFIG_RPCSEC_GSS_KRB5 is not set +# CONFIG_RPCSEC_GSS_SPKM3 is not set +# CONFIG_SMB_FS is not set +CONFIG_CIFS=m +# CONFIG_CIFS_STATS is not set +# CONFIG_CIFS_WEAK_PW_HASH is not set +# CONFIG_CIFS_XATTR is not set +# CONFIG_CIFS_DEBUG2 is not set +# CONFIG_CIFS_EXPERIMENTAL is not set +# CONFIG_NCP_FS is not set +# CONFIG_CODA_FS is not set +# CONFIG_AFS_FS is not set + +# +# Partition Types +# +# CONFIG_PARTITION_ADVANCED is not set +CONFIG_MSDOS_PARTITION=y +CONFIG_NLS=y +CONFIG_NLS_DEFAULT="iso8859-1" +CONFIG_NLS_CODEPAGE_437=y +# CONFIG_NLS_CODEPAGE_737 is not set +# CONFIG_NLS_CODEPAGE_775 is not set +# CONFIG_NLS_CODEPAGE_850 is not set +# CONFIG_NLS_CODEPAGE_852 is not set +# CONFIG_NLS_CODEPAGE_855 is not set +# CONFIG_NLS_CODEPAGE_857 is not set +# CONFIG_NLS_CODEPAGE_860 is not set +# CONFIG_NLS_CODEPAGE_861 is not set +# CONFIG_NLS_CODEPAGE_862 is not set +# CONFIG_NLS_CODEPAGE_863 is not set +# CONFIG_NLS_CODEPAGE_864 is not set +# CONFIG_NLS_CODEPAGE_865 is not set +# CONFIG_NLS_CODEPAGE_866 is not set +# CONFIG_NLS_CODEPAGE_869 is not set +# CONFIG_NLS_CODEPAGE_936 is not set +# CONFIG_NLS_CODEPAGE_950 is not set +# CONFIG_NLS_CODEPAGE_932 is not set +# CONFIG_NLS_CODEPAGE_949 is not set +# CONFIG_NLS_CODEPAGE_874 is not set +# CONFIG_NLS_ISO8859_8 is not set +# CONFIG_NLS_CODEPAGE_1250 is not set +# CONFIG_NLS_CODEPAGE_1251 is not set +# CONFIG_NLS_ASCII is not set +CONFIG_NLS_ISO8859_1=y +# CONFIG_NLS_ISO8859_2 is not set +# CONFIG_NLS_ISO8859_3 is not set +# CONFIG_NLS_ISO8859_4 is not set +# CONFIG_NLS_ISO8859_5 is not set +# CONFIG_NLS_ISO8859_6 is not set +# CONFIG_NLS_ISO8859_7 is not set +# CONFIG_NLS_ISO8859_9 is not set +# CONFIG_NLS_ISO8859_13 is not set +# CONFIG_NLS_ISO8859_14 is not set +# CONFIG_NLS_ISO8859_15 is not set +# CONFIG_NLS_KOI8_R is not set +# CONFIG_NLS_KOI8_U is not set +# CONFIG_NLS_UTF8 is not set +# CONFIG_DLM is not set +CONFIG_BINARY_PRINTF=y + +# +# Library routines +# +CONFIG_BITREVERSE=y +CONFIG_GENERIC_FIND_LAST_BIT=y +CONFIG_CRC_CCITT=y +CONFIG_CRC16=y +# CONFIG_CRC_T10DIF is not set +# CONFIG_CRC_ITU_T is not set +CONFIG_CRC32=y +# CONFIG_CRC7 is not set +# CONFIG_LIBCRC32C is not set +CONFIG_ZLIB_INFLATE=y +CONFIG_DECOMPRESS_GZIP=y +CONFIG_HAS_IOMEM=y +CONFIG_HAS_IOPORT=y +CONFIG_HAS_DMA=y +CONFIG_HAVE_LMB=y +CONFIG_NLATTR=y +CONFIG_GENERIC_ATOMIC64=y + +# +# Kernel hacking +# +CONFIG_PRINTK_TIME=y +CONFIG_ENABLE_WARN_DEPRECATED=y +CONFIG_ENABLE_MUST_CHECK=y +CONFIG_FRAME_WARN=1024 +CONFIG_MAGIC_SYSRQ=y +# CONFIG_STRIP_ASM_SYMS is not set +# CONFIG_UNUSED_SYMBOLS is not set +CONFIG_DEBUG_FS=y +# CONFIG_HEADERS_CHECK is not set +CONFIG_DEBUG_KERNEL=y +# CONFIG_DEBUG_SHIRQ is not set +CONFIG_DETECT_SOFTLOCKUP=y +# CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC is not set +CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC_VALUE=0 +# CONFIG_DETECT_HUNG_TASK is not set +CONFIG_SCHED_DEBUG=y +CONFIG_SCHEDSTATS=y +# CONFIG_TIMER_STATS is not set +# CONFIG_DEBUG_OBJECTS is not set +# CONFIG_DEBUG_SLAB is not set +# CONFIG_DEBUG_KMEMLEAK is not set +CONFIG_DEBUG_PREEMPT=y +# CONFIG_DEBUG_RT_MUTEXES is not set +# CONFIG_RT_MUTEX_TESTER is not set +CONFIG_DEBUG_SPINLOCK=y +CONFIG_DEBUG_MUTEXES=y +# CONFIG_DEBUG_LOCK_ALLOC is not set +# CONFIG_PROVE_LOCKING is not set +# CONFIG_LOCK_STAT is not set +CONFIG_DEBUG_SPINLOCK_SLEEP=y +# CONFIG_DEBUG_LOCKING_API_SELFTESTS is not set +CONFIG_STACKTRACE=y +# CONFIG_DEBUG_KOBJECT is not set +CONFIG_DEBUG_BUGVERBOSE=y +# CONFIG_DEBUG_INFO is not set +# CONFIG_DEBUG_VM is not set +# CONFIG_DEBUG_WRITECOUNT is not set +# CONFIG_DEBUG_MEMORY_INIT is not set +# CONFIG_DEBUG_LIST is not set +# CONFIG_DEBUG_SG is not set +# CONFIG_DEBUG_NOTIFIERS is not set +# CONFIG_DEBUG_CREDENTIALS is not set +# CONFIG_RCU_TORTURE_TEST is not set +# CONFIG_RCU_CPU_STALL_DETECTOR is not set +# CONFIG_BACKTRACE_SELF_TEST is not set +# CONFIG_DEBUG_BLOCK_EXT_DEVT is not set +# CONFIG_DEBUG_FORCE_WEAK_PER_CPU is not set +# CONFIG_FAULT_INJECTION is not set +CONFIG_LATENCYTOP=y +CONFIG_SYSCTL_SYSCALL_CHECK=y +# CONFIG_DEBUG_PAGEALLOC is not set +CONFIG_NOP_TRACER=y +CONFIG_HAVE_FUNCTION_TRACER=y +CONFIG_HAVE_FUNCTION_GRAPH_TRACER=y +CONFIG_HAVE_DYNAMIC_FTRACE=y +CONFIG_HAVE_FTRACE_MCOUNT_RECORD=y +CONFIG_TRACER_MAX_TRACE=y +CONFIG_RING_BUFFER=y +CONFIG_EVENT_TRACING=y +CONFIG_CONTEXT_SWITCH_TRACER=y +CONFIG_TRACING=y +CONFIG_GENERIC_TRACER=y +CONFIG_TRACING_SUPPORT=y +CONFIG_FTRACE=y +# CONFIG_FUNCTION_TRACER is not set +# CONFIG_IRQSOFF_TRACER is not set +# CONFIG_PREEMPT_TRACER is not set +CONFIG_SCHED_TRACER=y +CONFIG_BOOT_TRACER=y +CONFIG_BRANCH_PROFILE_NONE=y +# CONFIG_PROFILE_ANNOTATED_BRANCHES is not set +# CONFIG_PROFILE_ALL_BRANCHES is not set +# CONFIG_STACK_TRACER is not set +# CONFIG_KMEMTRACE is not set +# CONFIG_WORKQUEUE_TRACER is not set +# CONFIG_BLK_DEV_IO_TRACE is not set +# CONFIG_FTRACE_STARTUP_TEST is not set +# CONFIG_RING_BUFFER_BENCHMARK is not set +# CONFIG_DYNAMIC_DEBUG is not set +CONFIG_DMA_API_DEBUG=y +# CONFIG_SAMPLES is not set +CONFIG_HAVE_ARCH_KGDB=y +# CONFIG_KGDB is not set +# CONFIG_PPC_DISABLE_WERROR is not set +CONFIG_PPC_WERROR=y +CONFIG_PRINT_STACK_DEPTH=64 +# CONFIG_DEBUG_STACKOVERFLOW is not set +# CONFIG_DEBUG_STACK_USAGE is not set +# CONFIG_PPC_EMULATED_STATS is not set +# CONFIG_CODE_PATCHING_SELFTEST is not set +# CONFIG_FTR_FIXUP_SELFTEST is not set +# CONFIG_MSI_BITMAP_SELFTEST is not set +# CONFIG_XMON is not set +# CONFIG_IRQSTACKS is not set +# CONFIG_VIRQ_DEBUG is not set +# CONFIG_BDI_SWITCH is not set +# CONFIG_BOOTX_TEXT is not set +CONFIG_PPC_EARLY_DEBUG=y +# CONFIG_PPC_EARLY_DEBUG_LPAR is not set +# CONFIG_PPC_EARLY_DEBUG_G5 is not set +# CONFIG_PPC_EARLY_DEBUG_RTAS_PANEL is not set +# CONFIG_PPC_EARLY_DEBUG_RTAS_CONSOLE is not set +# CONFIG_PPC_EARLY_DEBUG_MAPLE is not set +# CONFIG_PPC_EARLY_DEBUG_ISERIES is not set +# CONFIG_PPC_EARLY_DEBUG_PAS_REALMODE is not set +# CONFIG_PPC_EARLY_DEBUG_BEAT is not set +# CONFIG_PPC_EARLY_DEBUG_44x is not set +# CONFIG_PPC_EARLY_DEBUG_40x is not set +# CONFIG_PPC_EARLY_DEBUG_CPM is not set +CONFIG_PPC_EARLY_DEBUG_USBGECKO=y + +# +# Security options +# +# CONFIG_KEYS is not set +# CONFIG_SECURITY is not set +# CONFIG_SECURITYFS is not set +# CONFIG_SECURITY_FILE_CAPABILITIES is not set +CONFIG_CRYPTO=y + +# +# Crypto core or helper +# +CONFIG_CRYPTO_ALGAPI=y +CONFIG_CRYPTO_ALGAPI2=y +CONFIG_CRYPTO_AEAD2=y +CONFIG_CRYPTO_BLKCIPHER=y +CONFIG_CRYPTO_BLKCIPHER2=y +CONFIG_CRYPTO_HASH2=y +CONFIG_CRYPTO_RNG2=y +CONFIG_CRYPTO_PCOMP=y +CONFIG_CRYPTO_MANAGER=y +CONFIG_CRYPTO_MANAGER2=y +# CONFIG_CRYPTO_GF128MUL is not set +# CONFIG_CRYPTO_NULL is not set +CONFIG_CRYPTO_WORKQUEUE=y +# CONFIG_CRYPTO_CRYPTD is not set +# CONFIG_CRYPTO_AUTHENC is not set +# CONFIG_CRYPTO_TEST is not set + +# +# Authenticated Encryption with Associated Data +# +# CONFIG_CRYPTO_CCM is not set +# CONFIG_CRYPTO_GCM is not set +# CONFIG_CRYPTO_SEQIV is not set + +# +# Block modes +# +# CONFIG_CRYPTO_CBC is not set +# CONFIG_CRYPTO_CTR is not set +# CONFIG_CRYPTO_CTS is not set +CONFIG_CRYPTO_ECB=y +# CONFIG_CRYPTO_LRW is not set +# CONFIG_CRYPTO_PCBC is not set +# CONFIG_CRYPTO_XTS is not set + +# +# Hash modes +# +# CONFIG_CRYPTO_HMAC is not set +# CONFIG_CRYPTO_XCBC is not set +# CONFIG_CRYPTO_VMAC is not set + +# +# Digest +# +# CONFIG_CRYPTO_CRC32C is not set +# CONFIG_CRYPTO_GHASH is not set +# CONFIG_CRYPTO_MD4 is not set +# CONFIG_CRYPTO_MD5 is not set +# CONFIG_CRYPTO_MICHAEL_MIC is not set +# CONFIG_CRYPTO_RMD128 is not set +# CONFIG_CRYPTO_RMD160 is not set +# CONFIG_CRYPTO_RMD256 is not set +# CONFIG_CRYPTO_RMD320 is not set +# CONFIG_CRYPTO_SHA1 is not set +# CONFIG_CRYPTO_SHA256 is not set +# CONFIG_CRYPTO_SHA512 is not set +# CONFIG_CRYPTO_TGR192 is not set +# CONFIG_CRYPTO_WP512 is not set + +# +# Ciphers +# +CONFIG_CRYPTO_AES=y +# CONFIG_CRYPTO_ANUBIS is not set +CONFIG_CRYPTO_ARC4=y +# CONFIG_CRYPTO_BLOWFISH is not set +# CONFIG_CRYPTO_CAMELLIA is not set +# CONFIG_CRYPTO_CAST5 is not set +# CONFIG_CRYPTO_CAST6 is not set +# CONFIG_CRYPTO_DES is not set +# CONFIG_CRYPTO_FCRYPT is not set +# CONFIG_CRYPTO_KHAZAD is not set +# CONFIG_CRYPTO_SALSA20 is not set +# CONFIG_CRYPTO_SEED is not set +# CONFIG_CRYPTO_SERPENT is not set +# CONFIG_CRYPTO_TEA is not set +# CONFIG_CRYPTO_TWOFISH is not set + +# +# Compression +# +# CONFIG_CRYPTO_DEFLATE is not set +# CONFIG_CRYPTO_ZLIB is not set +# CONFIG_CRYPTO_LZO is not set + +# +# Random Number Generation +# +# CONFIG_CRYPTO_ANSI_CPRNG is not set +# CONFIG_CRYPTO_HW is not set +# CONFIG_PPC_CLOCK is not set +CONFIG_PPC_LIB_RHEAP=y +# CONFIG_VIRTUALIZATION is not set diff --git a/arch/powerpc/include/asm/dma-mapping.h b/arch/powerpc/include/asm/dma-mapping.h index e281dae..e35e0f0 100644 --- a/arch/powerpc/include/asm/dma-mapping.h +++ b/arch/powerpc/include/asm/dma-mapping.h @@ -17,6 +17,7 @@ #include #include #include +#include #define DMA_ERROR_CODE (~(dma_addr_t)0x0) diff --git a/arch/powerpc/include/asm/starlet-ios.h b/arch/powerpc/include/asm/starlet-ios.h new file mode 100644 index 0000000..926ee2d --- /dev/null +++ b/arch/powerpc/include/asm/starlet-ios.h @@ -0,0 +1,253 @@ +/* + * arch/powerpc/include/asm/starlet-ios.h + * + * Nintendo Wii Starlet IOS definitions + * Copyright (C) 2008-2009 The GameCube Linux Team + * Copyright (C) 2008,2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#ifndef __STARLET_IOS_H +#define __STARLET_IOS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define STARLET_TITLE_HBC_HAXX 0x0001000148415858ULL +#define STARLET_TITLE_HBC_JODI 0x000100014A4F4449ULL + +#define STARLET_EINVAL -4 + +#define STARLET_IPC_DMA_ALIGN 0x1f /* 32 bytes */ + +struct starlet_ipc_request; + +/* input/output heap */ +struct starlet_ioh { + spinlock_t lock; + rh_info_t *rheap; + unsigned long base_phys; + void *base; + size_t size; +}; + +/* pseudo-scatterlist support for the input/output heap */ +struct starlet_ioh_sg { + void *buf; + size_t len; + dma_addr_t dma_addr; +}; + +/* inter-process communication device abstraction */ +struct starlet_ipc_device { + unsigned long flags; + + void __iomem *io_base; + int irq; + + struct dma_pool *dma_pool; /* to allocate requests */ + struct starlet_ioh *ioh; /* to allocate special io buffers */ + + unsigned int random_id; + + spinlock_t list_lock; + struct list_head outstanding_list; + unsigned long nr_outstanding; + struct list_head pending_list; + unsigned long nr_pending; + + struct timer_list timer; + + struct starlet_ipc_request *req; /* for requests causing a ios reboot */ + + struct device *dev; +}; + +/* iovec entry suitable for ioctlv */ +struct starlet_iovec { + dma_addr_t dma_addr; + u32 dma_len; +}; + +typedef int (*starlet_ipc_callback_t)(struct starlet_ipc_request *req); + +struct starlet_ipc_request { + /* begin starlet firmware request format */ + u32 cmd; /* 0x00 */ + s32 result; /* 0x04 */ + union { /* 0x08 */ + s32 fd; + u32 req_cmd; + }; + union { + struct { + dma_addr_t pathname; /* 0x0c */ + u32 mode; /* 0x10 */ + } open; + struct { + u32 request; /* 0x0c */ + dma_addr_t ibuf; /* 0x10 */ + u32 ilen; /* 0x14 */ + dma_addr_t obuf; /* 0x18 */ + u32 olen; /* 0x1c */ + } ioctl; + struct { + u32 request; /* 0x0c */ + u32 argc_in; /* 0x10 */ + u32 argc_io; /* 0x14 */ + dma_addr_t iovec_da; /* 0x18 */ + } ioctlv; + u32 argv[5]; /* 0x0c,0x10,0x14,0x18,0x1c */ + }; + /* end starlet firmware request format */ + + /* + * A signature is used to discard bogus requests from earlier + * IPC instances. + */ + unsigned int sig; + + dma_addr_t dma_addr; /* request dma address */ + + /* ioctlv related data */ + struct starlet_iovec *iovec; + size_t iovec_size; + + unsigned sgl_nents_in; + unsigned sgl_nents_io; + union { + struct scatterlist *sgl_in; + struct starlet_ioh_sg *ioh_sgl_in; + }; + union { + struct scatterlist *sgl_io; + struct starlet_ioh_sg *ioh_sgl_io; + }; + + void *done_data; + starlet_ipc_callback_t done; + + starlet_ipc_callback_t complete; + + unsigned long jiffies; + + struct list_head node; /* for queueing */ + + struct starlet_ipc_device *ipc_dev; +}; + + + +/* from starlet-malloc.c */ + +extern int starlet_malloc_lib_bootstrap(struct resource *mem); + +extern void *starlet_kzalloc(size_t size, gfp_t flags); +extern void starlet_kfree(void *ptr); + +extern void *starlet_ioh_kzalloc(size_t size); +extern void starlet_ioh_kfree(void *ptr); + +extern unsigned long starlet_ioh_virt_to_phys(void *ptr); + +extern void starlet_ioh_sg_init_table(struct starlet_ioh_sg *sgl, + unsigned int nents); +extern void starlet_ioh_sg_set_buf(struct starlet_ioh_sg *sg, + void *buf, size_t len); + +#define starlet_ioh_for_each_sg(sgl, sg, nr, __i) \ + for (__i = 0, sg = (sgl); __i < nr; __i++, sg++) + +extern int starlet_ioh_dma_map_sg(struct device *dev, + struct starlet_ioh_sg *sgl, int nents, + enum dma_data_direction direction); +extern void starlet_ioh_dma_unmap_sg(struct device *dev, + struct starlet_ioh_sg *sgl, int nents, + enum dma_data_direction direction); +/* from starlet-ipc.c */ + +extern struct starlet_ipc_device *starlet_ipc_get_device(void); + +extern struct starlet_ipc_request * +starlet_ipc_alloc_request(struct starlet_ipc_device *ipc_dev, gfp_t flags); +extern void starlet_ipc_free_request(struct starlet_ipc_request *req); + + +extern int starlet_open(const char *pathname, int flags); +extern int starlet_open_polled(const char *pathname, int flags, + unsigned long usecs); +extern int starlet_close(int fd); +extern int starlet_close_polled(int fd, unsigned long usecs); + +extern int starlet_ioctl(int fd, int request, + void *ibuf, size_t ilen, + void *obuf, size_t olen); +extern int starlet_ioctl_nowait(int fd, int request, + void *ibuf, size_t ilen, + void *obuf, size_t olen, + starlet_ipc_callback_t callback, + void *arg); +extern int starlet_ioctl_polled(int fd, int request, + void *ibuf, size_t ilen, + void *obuf, size_t olen, unsigned long usecs); + +extern int starlet_ioctlv(int fd, int request, + unsigned int nents_in, + struct scatterlist *sgl_in, + unsigned int nents_out, + struct scatterlist *sgl_out); +extern int starlet_ioctlv_nowait(int fd, int request, + unsigned int nents_in, + struct scatterlist *sgl_in, + unsigned int nents_out, + struct scatterlist *sgl_out, + starlet_ipc_callback_t callback, + void *arg); +extern int starlet_ioctlv_polled(int fd, int request, + unsigned int nents_in, + struct scatterlist *sgl_in, + unsigned int nents_out, + struct scatterlist *sgl_out, + unsigned long usecs); +extern int starlet_ioctlv_and_reboot(int fd, int request, + unsigned int nents_in, + struct scatterlist *sgl_in, + unsigned int nents_out, + struct scatterlist *sgl_out); + +extern int starlet_ioh_ioctlv(int fd, int request, + unsigned int nents_in, + struct starlet_ioh_sg *ioh_sgl_in, + unsigned int nents_io, + struct starlet_ioh_sg *ioh_sgl_io); +extern int starlet_ioh_ioctlv_nowait(int fd, int request, + unsigned int nents_in, + struct starlet_ioh_sg *ioh_sgl_in, + unsigned int nents_io, + struct starlet_ioh_sg *ioh_sgl_io, + starlet_ipc_callback_t callback, + void *arg); + +/* from starlet-es.c */ + +extern int starlet_es_reload_ios_and_discard(void); +extern int starlet_es_reload_ios_and_launch(u64 title); + +/* from starlet-stm.c */ + +extern void starlet_stm_restart(void); +extern void starlet_stm_power_off(void); + +#endif /* __STARLET_IOS_H */ diff --git a/arch/powerpc/include/asm/starlet-mini.h b/arch/powerpc/include/asm/starlet-mini.h new file mode 100644 index 0000000..603038e --- /dev/null +++ b/arch/powerpc/include/asm/starlet-mini.h @@ -0,0 +1,173 @@ +/* + * arch/powerpc/include/asm/starlet-mini.h + * + * Definitions for the 'mini' firmware replacement for Starlet + * Copyright (C) 2009 The GameCube Linux Team + * Copyright (C) 2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#ifndef __STARLET_MINI_H +#define __STARLET_MINI_H + +#ifdef CONFIG_STARLET_MINI + +/* + * mini ipc call numbering scheme + */ + +#define _MIPC_FAST 0x01 +#define _MIPC_SLOW 0x00 + +#define _MIPC_DEV_SYS 0x00 +#define _MIPC_DEV_NAND 0x01 +#define _MIPC_DEV_SDHC 0x02 +#define _MIPC_DEV_KEYS 0x03 +#define _MIPC_DEV_AES 0x04 +#define _MIPC_DEV_BOOT2 0x05 +#define _MIPC_DEV_PPC 0x06 +#define _MIPC_DEV_SDMMC 0x07 + +#define _MIPC_SYS_PING 0x0000 +#define _MIPC_SYS_JUMP 0x0001 +#define _MIPC_SYS_GETVERS 0x0002 +#define _MIPC_SYS_GETGITS 0x0003 +#define _MIPC_SYS_WRITE32 0x0100 +#define _MIPC_SYS_WRITE16 0x0101 +#define _MIPC_SYS_WRITE8 0x0102 +#define _MIPC_SYS_READ32 0x0103 +#define _MIPC_SYS_READ16 0x0104 +#define _MIPC_SYS_READ8 0x0105 +#define _MIPC_SYS_SET32 0x0106 +#define _MIPC_SYS_SET16 0x0107 +#define _MIPC_SYS_SET8 0x0108 +#define _MIPC_SYS_CLEAR32 0x0109 +#define _MIPC_SYS_CLEAR16 0x010a +#define _MIPC_SYS_CLEAR8 0x010b +#define _MIPC_SYS_MASK32 0x010c +#define _MIPC_SYS_MASK16 0x010d +#define _MIPC_SYS_MASK8 0x010e + +#define _MIPC_NAND_RESET 0x0000 +#define _MIPC_NAND_GETID 0x0001 +#define _MIPC_NAND_READ 0x0002 +#define _MIPC_NAND_WRITE 0x0003 +#define _MIPC_NAND_ERASE 0x0004 +#define _MIPC_NAND_STATUS 0x0005 + +#define _MIPC_SDHC_DISCOVER 0x0000 +#define _MIPC_SDHC_EXIT 0x0001 + +#define _MIPC_SDMMC_ACK 0x0000 +#define _MIPC_SDMMC_READ 0x0001 +#define _MIPC_SDMMC_WRITE 0x0002 +#define _MIPC_SDMMC_STATE 0x0003 +#define _MIPC_SDMMC_SIZE 0x0004 + +#define _MIPC_KEYS_GETOTP 0x0000 +#define _MIPC_KEYS_GETEEP 0x0001 + +#define _MIPC_AES_RESET 0x0000 +#define _MIPC_AES_SETIV 0x0001 +#define _MIPC_AES_SETKEY 0x0002 +#define _MIPC_AES_DECRYPT 0x0003 + +#define _MIPC_BOOT2_RUN 0x0000 +#define _MIPC_BOOT2_TMD 0x0001 + +#define _MIPC_PPC_BOOT 0x0000 + + +/* + * + */ + +#define _MIPC_MODEBITS 8 +#define _MIPC_DEVBITS 8 +#define _MIPC_NRBITS 16 + +#define _MIPC_MODEMASK ((1 << _MIPC_MODEBITS)-1) +#define _MIPC_DEVMASK ((1 << _MIPC_DEVBITS)-1) +#define _MIPC_NRMASK ((1 << _MIPC_NRBITS)-1) + +#define _MIPC_MODESHIFT (_MIPC_DEVSHIFT + _MIPC_DEVBITS) +#define _MIPC_DEVSHIFT (_MIPC_NRSHIFT + _MIPC_NRBITS) +#define _MIPC_NRSHIFT 0 + +#define _MIPC(mode, dev, nr) \ + (((mode) << _MIPC_MODESHIFT) | \ + ((dev) << _MIPC_DEVSHIFT) | \ + ((nr) << _MIPC_NRSHIFT)) + +#define _MIPC_FAST_SYS(nr) _MIPC(_MIPC_FAST, _MIPC_DEV_SYS, nr) + +#define MIPC_SYS_PING _MIPC_FAST_SYS(_MIPC_SYS_PING) +#define MIPC_SYS_WRITE32 _MIPC_FAST_SYS(_MIPC_SYS_WRITE32) +#define MIPC_SYS_WRITE16 _MIPC_FAST_SYS(_MIPC_SYS_WRITE16) +#define MIPC_SYS_WRITE8 _MIPC_FAST_SYS(_MIPC_SYS_WRITE8) +#define MIPC_SYS_READ32 _MIPC_FAST_SYS(_MIPC_SYS_READ32) +#define MIPC_SYS_READ16 _MIPC_FAST_SYS(_MIPC_SYS_READ16) +#define MIPC_SYS_READ8 _MIPC_FAST_SYS(_MIPC_SYS_READ8) +#define MIPC_SYS_SET32 _MIPC_FAST_SYS(_MIPC_SYS_SET32) +#define MIPC_SYS_SET16 _MIPC_FAST_SYS(_MIPC_SYS_SET16) +#define MIPC_SYS_SET8 _MIPC_FAST_SYS(_MIPC_SYS_SET8) +#define MIPC_SYS_CLEAR32 _MIPC_FAST_SYS(_MIPC_SYS_CLEAR32) +#define MIPC_SYS_CLEAR16 _MIPC_FAST_SYS(_MIPC_SYS_CLEAR16) +#define MIPC_SYS_CLEAR8 _MIPC_FAST_SYS(_MIPC_SYS_CLEAR8) +#define MIPC_SYS_MASK32 _MIPC_FAST_SYS(_MIPC_SYS_MASK32) +#define MIPC_SYS_MASK16 _MIPC_FAST_SYS(_MIPC_SYS_MASK16) +#define MIPC_SYS_MASK8 _MIPC_FAST_SYS(_MIPC_SYS_MASK8) + +#define MIPC_REQ_MAX_ARGS 6 + +struct mipc_infohdr { + char magic[3]; + u8 version; + phys_addr_t mem2_boundary; + phys_addr_t ipc_in; + size_t ipc_in_size; + phys_addr_t ipc_out; + size_t ipc_out_size; +}; + +struct mipc_device; +struct mipc_req; + +extern int mipc_discover(struct mipc_infohdr **hdrp); + +extern u32 mipc_in_be32(const volatile u32 __iomem *addr); +extern u16 mipc_in_be16(const volatile u16 __iomem *addr); +extern u8 mipc_in_8(const volatile u8 __iomem *addr); + +extern void mipc_out_be32(const volatile u32 __iomem *addr, u32 val); +extern void mipc_out_be16(const volatile u16 __iomem *addr, u16 val); +extern void mipc_out_8(const volatile u8 __iomem *addr, u8 val); + +extern void mipc_clear_bit(int nr, volatile unsigned long *addr); +extern void mipc_set_bit(int nr, volatile unsigned long *addr); +extern void mipc_clrsetbits_be32(const volatile u32 __iomem *addr, + u32 clear, u32 set); + +extern void mipc_wmb(void); + +extern void __iomem *mipc_ioremap(phys_addr_t addr, unsigned long size); +extern void mipc_iounmap(volatile void __iomem *addr); + +#else + +struct mipc_infohdr; + +static inline int mipc_discover(struct mipc_infohdr **hdrp) +{ + return -ENODEV; +} + +#endif /* CONFIG_STARLET_MINI */ + +#endif /* __STARLET_MINI_H */ + diff --git a/arch/powerpc/include/asm/starlet.h b/arch/powerpc/include/asm/starlet.h new file mode 100644 index 0000000..1e7e9f6 --- /dev/null +++ b/arch/powerpc/include/asm/starlet.h @@ -0,0 +1,27 @@ +/* + * arch/powerpc/include/asm/starlet.h + * + * Definitions for the Starlet co-processor + * Copyright (C) 2009 The GameCube Linux Team + * Copyright (C) 2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#ifndef __STARLET_H +#define __STARLET_H + + +enum starlet_ipc_flavour { + STARLET_IPC_IOS, + STARLET_IPC_MINI, +}; + +int starlet_discover_ipc_flavour(void); +enum starlet_ipc_flavour starlet_get_ipc_flavour(void); + +#endif /* __STARLET_H */ diff --git a/arch/powerpc/include/asm/udbg.h b/arch/powerpc/include/asm/udbg.h index cd21e5e..11ae699 100644 --- a/arch/powerpc/include/asm/udbg.h +++ b/arch/powerpc/include/asm/udbg.h @@ -51,6 +51,7 @@ extern void __init udbg_init_btext(void); extern void __init udbg_init_44x_as1(void); extern void __init udbg_init_40x_realmode(void); extern void __init udbg_init_cpm(void); +extern void __init udbg_init_usbgecko(void); #endif /* __KERNEL__ */ #endif /* _ASM_POWERPC_UDBG_H */ diff --git a/arch/powerpc/kernel/cputable.c b/arch/powerpc/kernel/cputable.c index 03c862b..85841e8 100644 --- a/arch/powerpc/kernel/cputable.c +++ b/arch/powerpc/kernel/cputable.c @@ -697,13 +697,13 @@ static struct cpu_spec __initdata cpu_specs[] = { .machine_check = machine_check_generic, .platform = "ppc750", }, - { /* 750CL */ - .pvr_mask = 0xfffff0f0, - .pvr_value = 0x00087010, - .cpu_name = "750CL", - .cpu_features = CPU_FTRS_750CL, + { /* 745/755 */ + .pvr_mask = 0xfffff000, + .pvr_value = 0x00083000, + .cpu_name = "745/755", + .cpu_features = CPU_FTRS_750, .cpu_user_features = COMMON_USER | PPC_FEATURE_PPC_LE, - .mmu_features = MMU_FTR_HPTE_TABLE | MMU_FTR_USE_HIGH_BATS, + .mmu_features = MMU_FTR_HPTE_TABLE, .icache_bsize = 32, .dcache_bsize = 32, .num_pmcs = 4, @@ -711,16 +711,14 @@ static struct cpu_spec __initdata cpu_specs[] = { .cpu_setup = __setup_cpu_750, .machine_check = machine_check_generic, .platform = "ppc750", - .oprofile_cpu_type = "ppc/750", - .oprofile_type = PPC_OPROFILE_G4, }, - { /* 745/755 */ - .pvr_mask = 0xfffff000, - .pvr_value = 0x00083000, - .cpu_name = "745/755", - .cpu_features = CPU_FTRS_750, + { /* 750CL (and "Broadway") */ + .pvr_mask = 0xfffff0e0, + .pvr_value = 0x00087000, + .cpu_name = "750CL", + .cpu_features = CPU_FTRS_750CL, .cpu_user_features = COMMON_USER | PPC_FEATURE_PPC_LE, - .mmu_features = MMU_FTR_HPTE_TABLE, + .mmu_features = MMU_FTR_HPTE_TABLE | MMU_FTR_USE_HIGH_BATS, .icache_bsize = 32, .dcache_bsize = 32, .num_pmcs = 4, @@ -728,6 +726,8 @@ static struct cpu_spec __initdata cpu_specs[] = { .cpu_setup = __setup_cpu_750, .machine_check = machine_check_generic, .platform = "ppc750", + .oprofile_cpu_type = "ppc/750", + .oprofile_type = PPC_OPROFILE_G4, }, { /* 750FX rev 1.x */ .pvr_mask = 0xffffff00, diff --git a/arch/powerpc/kernel/dma.c b/arch/powerpc/kernel/dma.c index 6215062..83d5232 100644 --- a/arch/powerpc/kernel/dma.c +++ b/arch/powerpc/kernel/dma.c @@ -27,6 +27,9 @@ void *dma_direct_alloc_coherent(struct device *dev, size_t size, { void *ret; #ifdef CONFIG_NOT_COHERENT_CACHE + if (dma_alloc_from_coherent(dev, size, dma_handle, &ret)) + return ret; + ret = __dma_alloc_coherent(dev, size, dma_handle, flag); if (ret == NULL) return NULL; @@ -54,6 +57,8 @@ void dma_direct_free_coherent(struct device *dev, size_t size, void *vaddr, dma_addr_t dma_handle) { #ifdef CONFIG_NOT_COHERENT_CACHE + if (dma_release_from_coherent(dev, get_order(size), vaddr)) + return; __dma_free_coherent(size, vaddr); #else free_pages((unsigned long)vaddr, get_order(size)); diff --git a/arch/powerpc/kernel/head_32.S b/arch/powerpc/kernel/head_32.S index 829c3fe..83a0b79 100644 --- a/arch/powerpc/kernel/head_32.S +++ b/arch/powerpc/kernel/head_32.S @@ -164,6 +164,9 @@ __after_mmu_off: #ifdef CONFIG_PPC_EARLY_DEBUG_CPM bl setup_cpm_bat #endif +#ifdef CONFIG_PPC_EARLY_DEBUG_USBGECKO + bl setup_usbgecko_bat +#endif /* * Call setup_cpu for CPU 0 and initialize 6xx Idle @@ -1203,6 +1206,24 @@ setup_cpm_bat: blr #endif +#ifdef CONFIG_PPC_EARLY_DEBUG_USBGECKO +setup_usbgecko_bat: + /* prepare a BAT for early io */ + lis r8, 0x0c00 + ori r8, r8, 0x002a /* uncached, guarded ,rw */ + lis r11, 0xcc00 + ori r11, r11, 0x3 /* 128K */ +#ifdef CONFIG_WII + oris r8, r8, 0x0100 + oris r11, r11, 0x0100 +#endif + mtspr SPRN_DBAT1L, r8 + mtspr SPRN_DBAT1U, r11 + sync + isync + blr +#endif + #ifdef CONFIG_8260 /* Jump into the system reset for the rom. * We first disable the MMU, and then jump to the ROM reset address. diff --git a/arch/powerpc/kernel/udbg.c b/arch/powerpc/kernel/udbg.c index fc9af47..e39cad8 100644 --- a/arch/powerpc/kernel/udbg.c +++ b/arch/powerpc/kernel/udbg.c @@ -60,6 +60,8 @@ void __init udbg_early_init(void) udbg_init_40x_realmode(); #elif defined(CONFIG_PPC_EARLY_DEBUG_CPM) udbg_init_cpm(); +#elif defined(CONFIG_PPC_EARLY_DEBUG_USBGECKO) + udbg_init_usbgecko(); #endif #ifdef CONFIG_PPC_EARLY_DEBUG diff --git a/arch/powerpc/mm/pgtable_32.c b/arch/powerpc/mm/pgtable_32.c index cb96cb2..dea5930 100644 --- a/arch/powerpc/mm/pgtable_32.c +++ b/arch/powerpc/mm/pgtable_32.c @@ -192,9 +192,19 @@ __ioremap_caller(phys_addr_t addr, unsigned long size, unsigned long flags, * mem_init() sets high_memory so only do the check after that. */ if (mem_init_done && (p < virt_to_phys(high_memory))) { - printk("__ioremap(): phys addr 0x%llx is RAM lr %p\n", - (unsigned long long)p, __builtin_return_address(0)); - return NULL; + /* + * On some systems, though, we may want to remap normal RAM + * that we have memreserve'd at the device tree. + * But we can't do that safely if we are using BATs. + * + */ + if (!__map_without_bats) { + printk(KERN_WARNING + "__ioremap(): phys addr 0x%llx is RAM lr %p\n", + (unsigned long long)p, + __builtin_return_address(0)); + return NULL; + } } #endif diff --git a/arch/powerpc/platforms/Kconfig.cputype b/arch/powerpc/platforms/Kconfig.cputype index e382cae..80d934b 100644 --- a/arch/powerpc/platforms/Kconfig.cputype +++ b/arch/powerpc/platforms/Kconfig.cputype @@ -312,7 +312,7 @@ config NR_CPUS config NOT_COHERENT_CACHE bool - depends on 4xx || 8xx || E200 || PPC_MPC512x + depends on 4xx || 8xx || E200 || PPC_MPC512x || GAMECUBE_COMMON default y config CHECK_CACHE_COHERENCY diff --git a/arch/powerpc/platforms/embedded6xx/Kconfig b/arch/powerpc/platforms/embedded6xx/Kconfig index 291ac9d..d9fce8f 100644 --- a/arch/powerpc/platforms/embedded6xx/Kconfig +++ b/arch/powerpc/platforms/embedded6xx/Kconfig @@ -90,3 +90,111 @@ config MPC10X_OPENPIC config MPC10X_STORE_GATHERING bool "Enable MPC10x store gathering" depends on MPC10X_BRIDGE + +config GAMECUBE + bool "Nintendo-GameCube" + depends on EMBEDDED6xx + select GAMECUBE_COMMON + help + Select GAMECUBE if configuring for the Nintendo GameCube. + More information at: + +config WII + bool "Nintendo-Wii" + depends on EMBEDDED6xx + select GAMECUBE_COMMON + select PPC_LIB_RHEAP if STARLET_IOS + select USB_ARCH_HAS_EHCI if STARLET_MINI + select USB_ARCH_HAS_OHCI if STARLET_MINI + select HAVE_GENERIC_DMA_COHERENT + help + Select WII if configuring for the Nintendo Wii. + More information at: + +config STARLET_IOS + bool "Nintendo Wii Starlet IOS support" + depends on WII + default y + +config STARLET_MINI + bool "Team Twiizers 'mini' firmware support" + depends on WII && EXPERIMENTAL + default y + +config FLIPPER_PIC + bool + depends on GAMECUBE_COMMON + default y + +config HLWD_PIC + bool + depends on STARLET_MINI + default y + +config GAMECUBE_COMMON + bool + select NOT_COHERENT_CACHE + select FLIPPER_PIC + default n + +config GAMECUBE_UDBG + bool "Nintendo GameCube/Wii udbg support" + depends on GAMECUBE_COMMON + default n + help + If you say yes to this option, you will be able to choose between + several udbg drivers available for the Nintendo GameCube/Wii. + + If in doubt, say N here. + +choice + prompt "Nintendo GameCube/Wii udbg drivers" + depends on GAMECUBE_UDBG + +config USBGECKO_UDBG + bool "USB Gecko udbg console for the Nintendo GameCube/Wii" + help + If you say yes to this option, support will be included for the + USB Gecko adapter as an udbg console. + The USB Gecko is a EXI to USB Serial converter that can be plugged + into a memcard slot in the Nintendo GameCube/Wii. + + This driver bypasses the EXI layer completely. + + If in doubt, say N here. + +config GAMECUBE_VIDEO_UDBG + bool "Nintendo GameCube/Wii framebuffer udbg console" + select FONTS + select FONT_8x16 + help + If you say yes to this option, support will be included for a + framebuffer based udbg console for the Nintendo GameCube/Wii. + + If in doubt, say N here. + +endchoice + +config GAMECUBE_RSW + bool "Nintendo GameCube/Wii reset switch/button" + depends on GAMECUBE_COMMON + default y + help + If you say yes to this option, support will be included for the + reset switch/button of the Nintendo GameCube/Wii. + + If in doubt, say Y here. + +config HLWD_GPIO + bool "Nintendo Wii (Hollywood) GPIO support" + depends on GPIOLIB && WII + default y + help + If you say yes to this option, support will be included for the + GPIOs provided by the Hollywood chipset of the Nintendo Wii + video game console. + Those GPIO lines control, for example, the sensor bar IR leds, + the front led, or the eject switch of the disk unit. + + If in doubt, say Y here. + diff --git a/arch/powerpc/platforms/embedded6xx/Makefile b/arch/powerpc/platforms/embedded6xx/Makefile index 0773c08..19c14fa 100644 --- a/arch/powerpc/platforms/embedded6xx/Makefile +++ b/arch/powerpc/platforms/embedded6xx/Makefile @@ -7,3 +7,14 @@ obj-$(CONFIG_STORCENTER) += storcenter.o obj-$(CONFIG_PPC_HOLLY) += holly.o obj-$(CONFIG_PPC_PRPMC2800) += prpmc2800.o obj-$(CONFIG_PPC_C2K) += c2k.o +obj-$(CONFIG_GAMECUBE) += gamecube.o gamecube_dev.o +obj-$(CONFIG_WII) += wii.o wii_dev.o +obj-$(CONFIG_HLWD_GPIO) += hlwd-gpio.o +obj-$(CONFIG_STARLET_MINI) += starlet-mipc.o +obj-$(CONFIG_STARLET_IOS) += starlet-ipc.o starlet-malloc.o \ + starlet-stm.o starlet-es.o +obj-$(CONFIG_FLIPPER_PIC) += flipper-pic.o +obj-$(CONFIG_HLWD_PIC) += hlwd-pic.o +obj-$(CONFIG_USBGECKO_UDBG) += usbgecko_udbg.o +obj-$(CONFIG_GAMECUBE_VIDEO_UDBG) += gcnvi_udbg.o +obj-$(CONFIG_GAMECUBE_RSW) += gcn-rsw.o diff --git a/arch/powerpc/platforms/embedded6xx/flipper-pic.c b/arch/powerpc/platforms/embedded6xx/flipper-pic.c new file mode 100644 index 0000000..8239d5e --- /dev/null +++ b/arch/powerpc/platforms/embedded6xx/flipper-pic.c @@ -0,0 +1,229 @@ +/* + * arch/powerpc/platforms/embedded6xx/flipper-pic.c + * + * Nintendo GameCube/Wii interrupt controller support. + * Copyright (C) 2004-2009 The GameCube Linux Team + * Copyright (C) 2007,2008,2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ +#define DRV_MODULE_NAME "flipper-pic" +#define pr_fmt(fmt) DRV_MODULE_NAME ": " fmt + +#include +#include +#include +#include +#include + +#include "flipper-pic.h" + +/* + * IRQ chip hooks. + * + */ + +static void flipper_pic_mask_and_ack(unsigned int virq) +{ + int irq = virq_to_hw(virq); + void __iomem *io_base = get_irq_chip_data(virq); + + clear_bit(irq, io_base + FLIPPER_IMR); + set_bit(irq, io_base + FLIPPER_ICR); +} + +static void flipper_pic_ack(unsigned int virq) +{ + int irq = virq_to_hw(virq); + void __iomem *io_base = get_irq_chip_data(virq); + + set_bit(irq, io_base + FLIPPER_ICR); +} + +static void flipper_pic_mask(unsigned int virq) +{ + int irq = virq_to_hw(virq); + void __iomem *io_base = get_irq_chip_data(virq); + + clear_bit(irq, io_base + FLIPPER_IMR); +} + +static void flipper_pic_unmask(unsigned int virq) +{ + int irq = virq_to_hw(virq); + void __iomem *io_base = get_irq_chip_data(virq); + + set_bit(irq, io_base + FLIPPER_IMR); +} + + +static struct irq_chip flipper_pic = { + .typename = "flipper-pic", + .ack = flipper_pic_ack, + .mask_ack = flipper_pic_mask_and_ack, + .mask = flipper_pic_mask, + .unmask = flipper_pic_unmask, +}; + +/* + * IRQ host hooks. + * + */ + +static struct irq_host *flipper_irq_host; + +static int flipper_pic_map(struct irq_host *h, unsigned int virq, + irq_hw_number_t hwirq) +{ + set_irq_chip_data(virq, h->host_data); + get_irq_desc(virq)->status |= IRQ_LEVEL; + set_irq_chip_and_handler(virq, &flipper_pic, handle_level_irq); + return 0; +} + +static void flipper_pic_unmap(struct irq_host *h, unsigned int irq) +{ + set_irq_chip_data(irq, NULL); + set_irq_chip(irq, NULL); +} + +static int flipper_pic_match(struct irq_host *h, struct device_node *np) +{ + return 1; +} + + +static struct irq_host_ops flipper_irq_host_ops = { + .map = flipper_pic_map, + .unmap = flipper_pic_unmap, + .match = flipper_pic_match, +}; + +/* + * Platform hooks. + * + */ + +static void __flipper_quiesce(void __iomem *io_base) +{ + /* mask and ack all IRQs */ + out_be32(io_base + FLIPPER_IMR, 0x00000000); + out_be32(io_base + FLIPPER_ICR, 0xffffffff); +} + +struct irq_host * __init flipper_pic_init(struct device_node *np) +{ + struct irq_host *irq_host; + struct resource res; + void __iomem *io_base; + int retval; + + retval = of_address_to_resource(np, 0, &res); + if (retval) { + pr_err("no io memory range found\n"); + return NULL; + } + io_base = ioremap(res.start, res.end - res.start + 1); + + pr_info("controller at 0x%08x mapped to 0x%p\n", res.start, io_base); + + __flipper_quiesce(io_base); + + irq_host = irq_alloc_host(np, IRQ_HOST_MAP_LINEAR, FLIPPER_NR_IRQS, + &flipper_irq_host_ops, -1); + if (!irq_host) { + pr_err("failed to allocate irq_host\n"); + return NULL; + } + + irq_host->host_data = io_base; + + return irq_host; +} + +unsigned int flipper_pic_get_irq(void) +{ + void __iomem *io_base = flipper_irq_host->host_data; + int irq; + u32 irq_status; + + irq_status = in_be32(io_base + FLIPPER_ICR) & + in_be32(io_base + FLIPPER_IMR); + if (irq_status == 0) + return -1; /* no more IRQs pending */ + + __asm__ __volatile__("cntlzw %0,%1" : "=r"(irq) : "r"(irq_status)); + return irq_linear_revmap(flipper_irq_host, 31 - irq); +} + +/* + * Probe function. + * + */ + +void __init flipper_pic_probe(void) +{ + struct device_node *np; + + np = of_find_compatible_node(NULL, NULL, "nintendo,flipper-pic"); + BUG_ON(!np); + + flipper_irq_host = flipper_pic_init(np); + BUG_ON(!flipper_irq_host); + + irq_set_default_host(flipper_irq_host); + + of_node_put(np); +} + +/* + * Misc functions related to the flipper chipset. + * + */ + +/** + * flipper_quiesce() - quiesce flipper irq controller + * + * Mask and ack all interrupt sources. + * + */ +void flipper_quiesce(void) +{ + void __iomem *io_base = flipper_irq_host->host_data; + + __flipper_quiesce(io_base); +} + +/* + * Resets the platform. + */ +void flipper_platform_reset(void) +{ + void __iomem *io_base; + + if (flipper_irq_host && flipper_irq_host->host_data) { + io_base = flipper_irq_host->host_data; + out_8(io_base + FLIPPER_RESET, 0x00); + } +} + +/* + * Returns non-zero if the reset button is pressed. + */ +int flipper_is_reset_button_pressed(void) +{ + void __iomem *io_base; + u32 icr; + + if (flipper_irq_host && flipper_irq_host->host_data) { + io_base = flipper_irq_host->host_data; + icr = in_be32(io_base + FLIPPER_ICR); + return !(icr & FLIPPER_ICR_RSS); + } + return 0; +} + diff --git a/arch/powerpc/platforms/embedded6xx/flipper-pic.h b/arch/powerpc/platforms/embedded6xx/flipper-pic.h new file mode 100644 index 0000000..81212ce --- /dev/null +++ b/arch/powerpc/platforms/embedded6xx/flipper-pic.h @@ -0,0 +1,42 @@ +/* + * arch/powerpc/platforms/embedded6xx/flipper-pic.h + * + * Nintendo GameCube/Wii interrupt controller support. + * Copyright (C) 2004-2009 The GameCube Linux Team + * Copyright (C) 2007,2008,2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#ifndef __FLIPPER_PIC_H +#define __FLIPPER_PIC_H + +#define FLIPPER_NR_IRQS 32 + +/* + * Each interrupt has a corresponding bit in both + * the Interrupt Cause (ICR) and Interrupt Mask (IMR) registers. + * + * Enabling/disabling an interrupt line involves asserting/clearing + * the corresponding bit in IMR. ACK'ing a request simply involves + * asserting the corresponding bit in ICR. + */ +#define FLIPPER_ICR 0x00 +#define FLIPPER_ICR_RSS (1<<16) /* reset switch state */ + +#define FLIPPER_IMR 0x04 + +#define FLIPPER_RESET 0x24 + +unsigned int flipper_pic_get_irq(void); +void __init flipper_pic_probe(void); + +void flipper_quiesce(void); +void flipper_platform_reset(void); +int flipper_is_reset_button_pressed(void); + +#endif diff --git a/arch/powerpc/platforms/embedded6xx/gamecube.c b/arch/powerpc/platforms/embedded6xx/gamecube.c new file mode 100644 index 0000000..b3b4db9 --- /dev/null +++ b/arch/powerpc/platforms/embedded6xx/gamecube.c @@ -0,0 +1,111 @@ +/* + * arch/powerpc/platforms/embedded6xx/gamecube.c + * + * Nintendo GameCube board-specific support + * Copyright (C) 2004-2009 The GameCube Linux Team + * Copyright (C) 2007,2008,2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#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 +#include +#include + +#include + +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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 , " \ + "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 +#include +#include +#include +#include +#include +#include + +#include "gcnvi_udbg.h" + +/* + * Console settings. + * + */ +#define SCREEN_WIDTH 640 +#define SCREEN_HEIGHT 480 + +#define FONT_XSIZE 8 +#define FONT_YSIZE 16 +#define FONT_XFACTOR 1 +#define FONT_YFACTOR 1 +#define FONT_XGAP 2 +#define FONT_YGAP 0 + +#define COLOR_WHITE 0xFF80FF80 +#define COLOR_BLACK 0x00800080 + +struct console_data { + unsigned char *framebuffer; + int xres, yres, stride; + + const unsigned char *font; + + int cursor_x, cursor_y; + int foreground, background; + + int border_left, border_right, border_top, border_bottom; + + int scrolled_lines; +}; + +static struct console_data *default_console; + +#if 0 +static int console_set_color(int background, int foreground) +{ + default_console->foreground = foreground; + default_console->background = background; + return 0; +} +#endif + +static void console_drawc(struct console_data *con, int x, int y, + unsigned char c) +{ + int ax, ay; + unsigned long *ptr; + unsigned long color, color2x[2]; + int bits; + + x >>= 1; + ptr = (unsigned long *)(con->framebuffer + con->stride * y + x * 4); + + for (ay = 0; ay < FONT_YSIZE; ay++) { +#if FONT_XFACTOR == 2 + for (ax = 0; ax < 8; ax++) { + if ((con->font[c * FONT_YSIZE + ay] << ax) & 0x80) + color = con->foreground; + else + color = con->background; +#if FONT_YFACTOR == 2 + /* pixel doubling: we write u32 */ + ptr[ay * 2 * con->stride / 4 + ax] = color; + /* line doubling */ + ptr[(ay * 2 + 1) * con->stride / 4 + ax] = color; +#else + ptr[ay * con->stride / 4 + ax] = color; +#endif + } +#else + for (ax = 0; ax < 4; ax++) { + bits = (con->font[c * FONT_YSIZE + ay] << (ax * 2)); + if (bits & 0x80) + color2x[0] = con->foreground; + else + color2x[0] = con->background; + if (bits & 0x40) + color2x[1] = con->foreground; + else + color2x[1] = con->background; + ptr[ay * con->stride / 4 + ax] = + (color2x[0] & 0xFFFF00FF) | + (color2x[1] & 0x0000FF00); + } +#endif + } +} + +static void console_putc(struct console_data *con, char c) +{ + int cnt; + unsigned long *ptr; + + switch (c) { + case '\n': + con->cursor_y += FONT_YSIZE * FONT_YFACTOR + FONT_YGAP; + con->cursor_x = con->border_left; + break; + default: + console_drawc(con, con->cursor_x, con->cursor_y, c); + con->cursor_x += FONT_XSIZE * FONT_XFACTOR + FONT_XGAP; + if ((con->cursor_x + (FONT_XSIZE * FONT_XFACTOR)) > + con->border_right) { + con->cursor_y += FONT_YSIZE * FONT_YFACTOR + FONT_YGAP; + con->cursor_x = con->border_left; + } + } + if ((con->cursor_y + FONT_YSIZE * FONT_YFACTOR) >= con->border_bottom) { + memcpy(con->framebuffer, + con->framebuffer + + con->stride * (FONT_YSIZE * FONT_YFACTOR + FONT_YGAP), + con->stride * con->yres - FONT_YSIZE); + cnt = (con->stride * (FONT_YSIZE*FONT_YFACTOR + FONT_YGAP)) / 4; + ptr = (unsigned long *)(con->framebuffer + + con->stride * (con->yres - FONT_YSIZE)); + while (cnt--) + *ptr++ = con->background; + con->cursor_y -= FONT_YSIZE * FONT_YFACTOR + FONT_YGAP; + } +} + +static void console_init(struct console_data *con, void *framebuffer, + int xres, int yres, int stride) +{ + int c; + unsigned long *p; + + con->framebuffer = framebuffer; + con->xres = xres; + con->yres = yres; + con->border_left = 0; + con->border_top = 0; + con->border_right = con->xres; + con->border_bottom = con->yres; + con->stride = stride; + con->cursor_x = con->cursor_y = 0; + + con->font = font_vga_8x16.data; + + con->foreground = COLOR_WHITE; + con->background = COLOR_BLACK; + + con->scrolled_lines = 0; + + /* clear screen */ + c = con->xres * con->yres / 2; + p = (unsigned long *)con->framebuffer; + while (c--) + *p++ = con->background; + + default_console = con; +} + +/* + * Video hardware setup. + * + */ + +/* Hardware registers */ +#define VI_TFBL 0x1c +#define VI_TFBR 0x20 +#define VI_BFBL 0x24 +#define VI_BFBR 0x28 +#define VI_DPV 0x2c + +/* NTSC settings (640x480) */ +static const u32 vi_Mode640X480NtscYUV16[32] = { + 0x0F060001, 0x476901AD, 0x02EA5140, 0x00030018, + 0x00020019, 0x410C410C, 0x40ED40ED, 0x00435A4E, + 0x00000000, 0x00435A4E, 0x00000000, 0x00000000, + 0x110701AE, 0x10010001, 0x00010001, 0x00010001, + 0x00000000, 0x00000000, 0x28500100, 0x1AE771F0, + 0x0DB4A574, 0x00C1188E, 0xC4C0CBE2, 0xFCECDECF, + 0x13130F08, 0x00080C0F, 0x00FF0000, 0x00000000, + 0x02800000, 0x000000FF, 0x00FF00FF, 0x00FF00FF +}; + +static void vi_setup_video(void __iomem *io_base, unsigned long xfb_start) +{ + const u32 *regs = vi_Mode640X480NtscYUV16; + int i; + + /* initialize video registers */ + for (i = 0; i < 7; i++) + out_be32(io_base + i * sizeof(__u32), regs[i]); + + out_be32(io_base + VI_TFBR, regs[VI_TFBR / sizeof(__u32)]); + out_be32(io_base + VI_BFBR, regs[VI_BFBR / sizeof(__u32)]); + out_be32(io_base + VI_DPV, regs[VI_DPV / sizeof(__u32)]); + for (i = 16; i < 32; i++) + out_be32(io_base + i * sizeof(__u32), regs[i]); + + /* set framebuffer address, interlaced mode */ + out_be32(io_base + VI_TFBL, 0x10000000 | (xfb_start >> 5)); + xfb_start += 2 * SCREEN_WIDTH; /* line length */ + out_be32(io_base + VI_BFBL, 0x10000000 | (xfb_start >> 5)); +} + +/* + * Retrieves and prepares the virtual address needed to access the hardware. + */ +static void __iomem *vi_setup_io_base(struct device_node *np) +{ + phys_addr_t paddr; + const unsigned int *reg; + void *io_base = NULL; + + reg = of_get_property(np, "reg", NULL); + if (reg) { + paddr = of_translate_address(np, reg); + if (paddr) + io_base = ioremap(paddr, reg[1]); + } + return io_base; +} + +/* + * udbg functions. + * + */ + +/* OF bindings */ +static struct of_device_id gcnvi_udbg_ids[] __initdata = { + { .compatible = "nintendo,hollywood-video", }, + { .compatible = "nintendo,gamecube-video", }, +}; + +static struct console_data gcnvi_udbg_console; + +/* + * Transmits a character. + */ +void gcnvi_udbg_putc(char ch) +{ + if (default_console) + console_putc(default_console, ch); +} + +/* + * Initializes udbg support. + */ +void __init gcnvi_udbg_init(void) +{ + unsigned long xfb_start = 0, xfb_size = 0; + struct device_node *np = NULL; + const unsigned long *prop; + void *screen_base; + void *io_base; + + for_each_matching_node(np, gcnvi_udbg_ids) { + if (np) + break; + } + if (!np) + return; + + prop = of_get_property(np, "xfb-start", NULL); + if (prop) { + xfb_start = *prop; + prop = of_get_property(np, "xfb-size", NULL); + if (prop) + xfb_size = *prop; + } + io_base = vi_setup_io_base(np); + + of_node_put(np); + + if (!prop || !io_base) + return; + + if (xfb_size < 2 * SCREEN_WIDTH * SCREEN_HEIGHT) + return; + + screen_base = ioremap_nocache(xfb_start, xfb_size); + if (!screen_base) + return; + + vi_setup_video(io_base, xfb_start); + console_init(&gcnvi_udbg_console, screen_base, + SCREEN_WIDTH, SCREEN_HEIGHT, 2 * SCREEN_WIDTH); + + udbg_putc = gcnvi_udbg_putc; + printk(KERN_INFO "gcnvi_udbg: ready\n"); +} diff --git a/arch/powerpc/platforms/embedded6xx/gcnvi_udbg.h b/arch/powerpc/platforms/embedded6xx/gcnvi_udbg.h new file mode 100644 index 0000000..daba549 --- /dev/null +++ b/arch/powerpc/platforms/embedded6xx/gcnvi_udbg.h @@ -0,0 +1,30 @@ +/* + * arch/powerpc/platforms/embedded6xx/gcnvi_udbg.h + * + * Nintendo GameCube/Wii framebuffer udbg output support. + * Copyright (C) 2008-2009 The GameCube Linux Team + * Copyright (C) 2008,2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#ifndef __GCNVI_UDBG_H +#define __GCNVI_UDBG_H + +#ifdef CONFIG_GAMECUBE_VIDEO_UDBG + +extern void __init gcnvi_udbg_init(void); + +#else + +static inline void __init gcnvi_udbg_init(void) +{ +} + +#endif /* CONFIG_GAMECUBE_VIDEO_UDBG */ + +#endif /* __GCNVI_UDBG_H */ diff --git a/arch/powerpc/platforms/embedded6xx/hlwd-gpio.c b/arch/powerpc/platforms/embedded6xx/hlwd-gpio.c new file mode 100644 index 0000000..023b71c --- /dev/null +++ b/arch/powerpc/platforms/embedded6xx/hlwd-gpio.c @@ -0,0 +1,150 @@ +/* + * arch/powerpc/platforms/embedded6xx/hlwd-gpio.c + * + * Nintendo Wii (Hollywood) GPIO driver + * Copyright (C) 2008-2009 The GameCube Linux Team + * Copyright (C) 2008,2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include + +#define DRV_MODULE_NAME "hlwd-gpio" + +#define drv_printk(level, format, arg...) \ + printk(level DRV_MODULE_NAME ": " format , ## arg) + + +struct hlwd_gpio_chip { + struct of_mm_gpio_chip mmchip; + spinlock_t lock; +}; + +struct hlwd_gpio_regs { + __be32 out, dir, in; +}; + + +static inline struct hlwd_gpio_chip * +to_hlwd_gpio_chip(struct of_mm_gpio_chip *mm_gc) +{ + return container_of(mm_gc, struct hlwd_gpio_chip, mmchip); +} + +static int hlwd_gpio_get(struct gpio_chip *gc, unsigned int gpio) +{ + struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc); + struct hlwd_gpio_regs __iomem *regs = mm_gc->regs; + u32 pin_mask = 1 << (31 - gpio); + unsigned int val; + + val = !!(in_be32(®s->in) & pin_mask); + + pr_debug("%s: gpio: %d val: %d\n", __func__, gpio, val); + + return val; +} + +static void hlwd_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val) +{ + struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc); + struct hlwd_gpio_chip *st_gc = to_hlwd_gpio_chip(mm_gc); + struct hlwd_gpio_regs __iomem *regs = mm_gc->regs; + u32 pin_mask = 1 << (31 - gpio); + u32 data; + unsigned long flags; + + spin_lock_irqsave(&st_gc->lock, flags); + data = in_be32(®s->in) & ~pin_mask; + if (val) + data |= pin_mask; + out_be32(®s->out, data); + spin_unlock_irqrestore(&st_gc->lock, flags); + + pr_debug("%s: gpio: %d val: %d\n", __func__, gpio, val); +} + +static int hlwd_gpio_dir_in(struct gpio_chip *gc, unsigned int gpio) +{ + struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc); + struct hlwd_gpio_regs __iomem *regs = mm_gc->regs; + u32 pin_mask = 1 << (31 - gpio); + + clrbits32(®s->dir, pin_mask); + + return 0; +} + +static int hlwd_gpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val) +{ + struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc); + struct hlwd_gpio_regs __iomem *regs = mm_gc->regs; + u32 pin_mask = 1 << (31 - gpio); + + setbits32(®s->dir, pin_mask); + hlwd_gpio_set(gc, gpio, val); + + return 0; +} + +int hlwd_gpio_add32(struct device_node *np) +{ + struct of_mm_gpio_chip *mm_gc; + struct of_gpio_chip *of_gc; + struct gpio_chip *gc; + struct hlwd_gpio_chip *st_gc; + const unsigned long *prop; + int error; + + st_gc = kzalloc(sizeof(*st_gc), GFP_KERNEL); + if (!st_gc) + return -ENOMEM; + + spin_lock_init(&st_gc->lock); + mm_gc = &st_gc->mmchip; + of_gc = &mm_gc->of_gc; + gc = &of_gc->gc; + + prop = of_get_property(np, "#gpio-cells", NULL); + if (prop && *prop >= 2) + of_gc->gpio_cells = *prop; + else + of_gc->gpio_cells = 2; /* gpio pin number, flags */ + + gc->ngpio = 32; + gc->direction_input = hlwd_gpio_dir_in; + gc->direction_output = hlwd_gpio_dir_out; + gc->get = hlwd_gpio_get; + gc->set = hlwd_gpio_set; + + error = of_mm_gpiochip_add(np, mm_gc); + if (!error) + drv_printk(KERN_INFO, "%s: added %u gpios at %p\n", + np->name, gc->ngpio, mm_gc->regs); + return error; +} + +static int hlwd_gpio_init(void) +{ + struct device_node *np; + int error; + + for_each_compatible_node(np, NULL, "nintendo,hollywood-gpio") { + error = hlwd_gpio_add32(np); + if (error < 0) + drv_printk(KERN_ERR, "error %d adding gpios" + " for %s\n", error, np->full_name); + } + return 0; /* whatever */ +} +arch_initcall(hlwd_gpio_init); + diff --git a/arch/powerpc/platforms/embedded6xx/hlwd-pic.c b/arch/powerpc/platforms/embedded6xx/hlwd-pic.c new file mode 100644 index 0000000..b024800 --- /dev/null +++ b/arch/powerpc/platforms/embedded6xx/hlwd-pic.c @@ -0,0 +1,238 @@ +/* + * arch/powerpc/platforms/embedded6xx/hlwd-pic.c + * + * Nintendo Wii "Hollywood" interrupt controller support. + * Copyright (C) 2009 The GameCube Linux Team + * Copyright (C) 2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ +#define DRV_MODULE_NAME "hlwd-pic" +#define pr_fmt(fmt) DRV_MODULE_NAME ": " fmt + +#include +#include +#include +#include +#include + +#include "hlwd-pic.h" + +#define HLWD_NR_IRQS 32 + +/* + * Each interrupt has a corresponding bit in both + * the Interrupt Cause (ICR) and Interrupt Mask (IMR) registers. + * + * Enabling/disabling an interrupt line involves asserting/clearing + * the corresponding bit in IMR. ACK'ing a request simply involves + * asserting the corresponding bit in ICR. + */ +#define HW_BROADWAY_ICR 0x00 +#define HW_BROADWAY_IMR 0x04 + + +/* + * IRQ chip hooks. + * + */ + +static void hlwd_pic_mask_and_ack(unsigned int virq) +{ + int irq = virq_to_hw(virq); + void __iomem *io_base = get_irq_chip_data(virq); + + clear_bit(irq, io_base + HW_BROADWAY_IMR); + set_bit(irq, io_base + HW_BROADWAY_ICR); +} + +static void hlwd_pic_ack(unsigned int virq) +{ + int irq = virq_to_hw(virq); + void __iomem *io_base = get_irq_chip_data(virq); + + set_bit(irq, io_base + HW_BROADWAY_ICR); +} + +static void hlwd_pic_mask(unsigned int virq) +{ + int irq = virq_to_hw(virq); + void __iomem *io_base = get_irq_chip_data(virq); + + clear_bit(irq, io_base + HW_BROADWAY_IMR); +} + +static void hlwd_pic_unmask(unsigned int virq) +{ + int irq = virq_to_hw(virq); + void __iomem *io_base = get_irq_chip_data(virq); + + set_bit(irq, io_base + HW_BROADWAY_IMR); +} + + +static struct irq_chip hlwd_pic = { + .typename = "hlwd-pic", + .ack = hlwd_pic_ack, + .mask_ack = hlwd_pic_mask_and_ack, + .mask = hlwd_pic_mask, + .unmask = hlwd_pic_unmask, +}; + +/* + * IRQ host hooks. + * + */ + +static struct irq_host *hlwd_irq_host; + +static int hlwd_pic_map(struct irq_host *h, unsigned int virq, + irq_hw_number_t hwirq) +{ + set_irq_chip_data(virq, h->host_data); + get_irq_desc(virq)->status |= IRQ_LEVEL; + set_irq_chip_and_handler(virq, &hlwd_pic, handle_level_irq); + return 0; +} + +static void hlwd_pic_unmap(struct irq_host *h, unsigned int irq) +{ + set_irq_chip_data(irq, NULL); + set_irq_chip(irq, NULL); +} + +static struct irq_host_ops hlwd_irq_host_ops = { + .map = hlwd_pic_map, + .unmap = hlwd_pic_unmap, +}; + +static unsigned int __hlwd_pic_get_irq(struct irq_host *h) +{ + void __iomem *io_base = h->host_data; + int irq; + u32 irq_status; + + irq_status = in_be32(io_base + HW_BROADWAY_ICR) & + in_be32(io_base + HW_BROADWAY_IMR); + if (irq_status == 0) + return NO_IRQ_IGNORE; /* no more IRQs pending */ + + __asm__ __volatile__("cntlzw %0,%1" : "=r"(irq) : "r"(irq_status)); + return irq_linear_revmap(h, 31 - irq); +} + +static void hlwd_pic_irq_cascade(unsigned int cascade_virq, + struct irq_desc *desc) +{ + struct irq_host *irq_host = get_irq_data(cascade_virq); + unsigned int virq; + + spin_lock(&desc->lock); + desc->chip->mask(cascade_virq); /* IRQ_LEVEL */ + spin_unlock(&desc->lock); + + virq = __hlwd_pic_get_irq(irq_host); + if (virq != NO_IRQ_IGNORE) + generic_handle_irq(virq); + else + pr_err("spurious interrupt!\n"); + + spin_lock(&desc->lock); + desc->chip->ack(cascade_virq); /* IRQ_LEVEL */ + if (!(desc->status & IRQ_DISABLED) && desc->chip->unmask) + desc->chip->unmask(cascade_virq); + spin_unlock(&desc->lock); +} + +/* + * Platform hooks. + * + */ + +static void __hlwd_quiesce(void __iomem *io_base) +{ + /* mask and ack all IRQs */ + out_be32(io_base + HW_BROADWAY_IMR, 0); + out_be32(io_base + HW_BROADWAY_ICR, ~0); +} + +struct irq_host *hlwd_pic_init(struct device_node *np) +{ + struct irq_host *irq_host; + struct resource res; + void __iomem *io_base; + int retval; + + retval = of_address_to_resource(np, 0, &res); + if (retval) { + pr_err("no io memory range found\n"); + return NULL; + } + io_base = ioremap(res.start, resource_size(&res)); + if (!io_base) { + pr_err("ioremap failed\n"); + return NULL; + } + + pr_info("controller at 0x%08x mapped to 0x%p\n", res.start, io_base); + + __hlwd_quiesce(io_base); + + irq_host = irq_alloc_host(np, IRQ_HOST_MAP_LINEAR, HLWD_NR_IRQS, + &hlwd_irq_host_ops, NO_IRQ_IGNORE); + if (!irq_host) { + pr_err("failed to allocate irq_host\n"); + return NULL; + } + irq_host->host_data = io_base; + + return irq_host; +} + +unsigned int hlwd_pic_get_irq(void) +{ + return __hlwd_pic_get_irq(hlwd_irq_host); +} + +/* + * Probe function. + * + */ + +void hlwd_pic_probe(void) +{ + struct irq_host *host; + struct device_node *np; + const u32 *interrupts; + int cascade_virq; + + for_each_compatible_node(np, NULL, "nintendo,hollywood-pic") { + interrupts = of_get_property(np, "interrupts", NULL); + if (interrupts) { + host = hlwd_pic_init(np); + BUG_ON(!host); + cascade_virq = irq_of_parse_and_map(np, 0); + set_irq_data(cascade_virq, host); + set_irq_chained_handler(cascade_virq, + hlwd_pic_irq_cascade); + } + } +} + +/** + * hlwd_quiesce() - quiesce hollywood irq controller + * + * Mask and ack all interrupt sources. + * + */ +void hlwd_quiesce(void) +{ + void __iomem *io_base = hlwd_irq_host->host_data; + + __hlwd_quiesce(io_base); +} + diff --git a/arch/powerpc/platforms/embedded6xx/hlwd-pic.h b/arch/powerpc/platforms/embedded6xx/hlwd-pic.h new file mode 100644 index 0000000..d2e5a09 --- /dev/null +++ b/arch/powerpc/platforms/embedded6xx/hlwd-pic.h @@ -0,0 +1,22 @@ +/* + * arch/powerpc/platforms/embedded6xx/hlwd-pic.h + * + * Nintendo Wii "Hollywood" interrupt controller support. + * Copyright (C) 2009 The GameCube Linux Team + * Copyright (C) 2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#ifndef __HLWD_PIC_H +#define __HLWD_PIC_H + +extern unsigned int hlwd_pic_get_irq(void); +extern void hlwd_pic_probe(void); +extern void hlwd_quiesce(void); + +#endif diff --git a/arch/powerpc/platforms/embedded6xx/starlet-es.c b/arch/powerpc/platforms/embedded6xx/starlet-es.c new file mode 100644 index 0000000..d9e4ff5 --- /dev/null +++ b/arch/powerpc/platforms/embedded6xx/starlet-es.c @@ -0,0 +1,680 @@ +/* + * arch/powerpc/platforms/embedded6xx/starlet-es.c + * + * Nintendo Wii starlet ES driver + * Copyright (C) 2008-2009 The GameCube Linux Team + * Copyright (C) 2008,2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#define DEBUG + +#include +#include +#include +#include +#include + +#define DRV_MODULE_NAME "starlet-es" +#define DRV_DESCRIPTION "Nintendo Wii starlet ES driver" +#define DRV_AUTHOR "Albert Herranz" + +static const char starlet_es_driver_version[] = "0.3i"; + +#define DBG(fmt, arg...) pr_debug(fmt, ##arg) + +#define drv_printk(level, format, arg...) \ + printk(level DRV_MODULE_NAME ": " format , ## arg) + + +#define STARLET_ES_IOS_MIN 30 +#define STARLET_ES_IOS_MAX 36 + +#define STARLET_ES_TIMEOUT 1000000 /* usecs */ + + +struct starlet_es_device { + int fd; + u64 ios_title; + + struct device *dev; +}; + +struct starlet_es_ticket_limit { + u32 tag; + u32 value; +} __attribute__((packed)); + +struct starlet_es_ticket_view { + u32 view; + u64 ticketid; + u32 devicetype; + u64 title; + u16 access_mask; + u8 reserved[0x3c]; + u8 cidx_mask[0x40]; + u16 padding; + struct starlet_es_ticket_limit limits[8]; +} __attribute__((packed)); + +/* + * /dev/es + * + */ + +#define ES_IOCTLV_LAUNCHTITLE 0x08 +#define ES_IOCTLV_GETTITLECOUNT 0x0e +#define ES_IOCTLV_GETTITLES 0x0f +#define ES_IOCTLV_GETTICKETVIEWCOUNT 0x12 +#define ES_IOCTLV_GETTICKETVIEWS 0x13 + +static const char dev_es[] = "/dev/es"; + +/* + * Handy small buffer routines. + * We use a small static aligned buffer to avoid allocations for short-lived + * operations involving 1 to 4 byte data transfers to/from IOS. + * + */ + +static u32 es_small_buf[L1_CACHE_BYTES / sizeof(u32)] + __attribute__ ((aligned(STARLET_IPC_DMA_ALIGN + 1))); +static const size_t es_small_buf_size = sizeof(es_small_buf_size); +static DEFINE_MUTEX(es_small_buf_lock); + +static u32 *es_small_buf_get(void) +{ + u32 *buf; + + if (!mutex_trylock(&es_small_buf_lock)) + buf = starlet_kzalloc(es_small_buf_size, GFP_ATOMIC); + else { + memset(es_small_buf, 0, es_small_buf_size); + buf = es_small_buf; + } + + return buf; +} + +static void es_small_buf_put(u32 *buf) +{ + if (buf == es_small_buf) + mutex_unlock(&es_small_buf_lock); + else + starlet_kfree(buf); +} + +/* + * + * + */ + +static struct starlet_es_device *starlet_es_device_instance; + +/** + * starlet_es_get_device() - get ES device handle + * + * Returns the handle for the Encryption Services (ES) device instance. + */ +struct starlet_es_device *starlet_es_get_device(void) +{ + if (!starlet_es_device_instance) + drv_printk(KERN_ERR, "uninitialized device instance!\n"); + return starlet_es_device_instance; +} +EXPORT_SYMBOL_GPL(starlet_es_get_device); + +/* + * + */ +static int starlet_es_get_title_count(unsigned long *count) +{ + struct starlet_es_device *es_dev = starlet_es_get_device(); + struct scatterlist io[1]; + u32 *count_buf; + int error; + + if (!es_dev) + return -ENODEV; + + count_buf = es_small_buf_get(); + if (!count_buf) + return -ENOMEM; + + *count_buf = 0; + sg_init_one(io, count_buf, sizeof(*count_buf)); + + error = starlet_ioctlv(es_dev->fd, ES_IOCTLV_GETTITLECOUNT, + 0, NULL, 1, io); + if (error) + DBG("%s: error=%d (%08x)\n", __func__, error, error); + else + *count = *count_buf; + + es_small_buf_put(count_buf); + + return error; +} + +/* + * + */ +static int starlet_es_get_titles(u64 *titles, unsigned long count) +{ + struct starlet_es_device *es_dev = starlet_es_get_device(); + struct scatterlist in[1], io[1]; + u32 *count_buf; + int error; + + if (!es_dev) + return -ENODEV; + + count_buf = es_small_buf_get(); + if (!count_buf) + return -ENOMEM; + + *count_buf = count; + sg_init_one(in, count_buf, sizeof(*count_buf)); + sg_init_one(io, titles, sizeof(*titles)*count); + + error = starlet_ioctlv(es_dev->fd, ES_IOCTLV_GETTITLES, + 1, in, 1, io); + if (error) + DBG("%s: error=%d (%08x)\n", __func__, error, error); + + es_small_buf_put(count_buf); + + return error; +} + +/* + * This call may be used in a non-sleeping context + */ +static int starlet_es_get_ticket_view_count(u64 title, unsigned long *count) +{ + struct starlet_es_device *es_dev = starlet_es_get_device(); + struct scatterlist in[1], io[1]; + u64 *title_buf; + u32 *count_buf; + int error; + + if (!es_dev) + return -ENODEV; + + title_buf = starlet_kzalloc(sizeof(*title_buf), GFP_ATOMIC); + if (!title_buf) + return -ENOMEM; + + count_buf = es_small_buf_get(); + if (!count_buf) { + starlet_kfree(title_buf); + return -ENOMEM; + } + + *title_buf = title; + sg_init_one(in, title_buf, sizeof(*title_buf)); + sg_init_one(io, count_buf, sizeof(*count_buf)); + + error = starlet_ioctlv_polled(es_dev->fd, ES_IOCTLV_GETTICKETVIEWCOUNT, + 1, in, 1, io, STARLET_ES_TIMEOUT); + if (error) + DBG("%s: error=%d (%08x)\n", __func__, error, error); + else + *count = *count_buf; + + starlet_kfree(title_buf); + es_small_buf_put(count_buf); + + return error; +} + +/* + * This call may be used in a non-sleeping context + */ +static int starlet_es_get_ticket_views(u64 title, + struct starlet_es_ticket_view *views, + unsigned long count) +{ + struct starlet_es_device *es_dev = starlet_es_get_device(); + struct scatterlist in[2], io[1]; + u32 *count_buf; + u64 *title_buf; + int error; + + if (!es_dev) + return -ENODEV; + + title_buf = starlet_kzalloc(sizeof(*title_buf), GFP_ATOMIC); + if (!title_buf) + return -ENOMEM; + + count_buf = es_small_buf_get(); + if (!count_buf) { + starlet_kfree(title_buf); + return -ENOMEM; + } + + *title_buf = title; + *count_buf = count; + sg_init_table(in, 2); + sg_set_buf(&in[0], title_buf, sizeof(*title_buf)); + sg_set_buf(&in[1], count_buf, sizeof(*count_buf)); + + sg_init_one(io, views, sizeof(*views)*count); + + error = starlet_ioctlv_polled(es_dev->fd, ES_IOCTLV_GETTICKETVIEWS, + 2, in, 1, io, STARLET_ES_TIMEOUT); + if (error) + DBG("%s: error=%d (%08x)\n", __func__, error, error); + + es_small_buf_put(count_buf); + starlet_kfree(title_buf); + + return error; +} + +/* + * This call may be used in a non-sleeping context + */ +static int starlet_es_launch_title_view(u64 title, + struct starlet_es_ticket_view *view) +{ + struct starlet_es_device *es_dev = starlet_es_get_device(); + struct scatterlist in[2]; + u64 *title_buf; + int error; + + if (!es_dev) + return -ENODEV; + + title_buf = starlet_kzalloc(sizeof(*title_buf), GFP_ATOMIC); + if (!title_buf) + return -ENOMEM; + + *title_buf = title; + sg_init_table(in, 2); + sg_set_buf(&in[0], title_buf, sizeof(*title_buf)); + sg_set_buf(&in[1], view, sizeof(*view)); + + error = starlet_ioctlv_and_reboot(es_dev->fd, + ES_IOCTLV_LAUNCHTITLE, + 2, in, 0, NULL); + if (error) + DBG("%s: error=%d (%08x)\n", __func__, error, error); + + starlet_kfree(title_buf); + + return error; +} + +/* + * This call may be used in a non-sleeping context + */ +static int starlet_es_launch_title(struct starlet_es_device *es_dev, u64 title) +{ + struct starlet_es_ticket_view *views; + unsigned long count = 0; + int error; + + error = starlet_es_get_ticket_view_count(title, &count); + if (error) + return error; + if (!count) + return -ENOENT; + + views = starlet_kzalloc(sizeof(*views)*count, GFP_ATOMIC); + if (!views) { + DBG("%s: out of memory\n", __func__); + return -ENOMEM; + } + + error = starlet_es_get_ticket_views(title, views, count); + if (error) { + starlet_kfree(views); + return error; + } + + drv_printk(KERN_INFO, "launching title %u-%u\n", + (u32)(title >> 32), (u32)title); + error = starlet_es_launch_title_view(title, views); /* first view */ + + starlet_kfree(views); + + return error; +} + +/* + * This call may be used in a non-sleeping context + */ +static int starlet_es_reopen(struct starlet_es_device *es_dev) +{ + int error; + + error = starlet_open_polled(dev_es, 0, STARLET_ES_TIMEOUT); + if (error < 0) { + drv_printk(KERN_ERR, "unable to reopen %s (%d)\n", + dev_es, error); + goto out; + } + es_dev->fd = error; + error = 0; +out: + return error; +} + +/* + * This call may be used in a non-sleeping context + */ +static int __starlet_es_reload_ios(struct starlet_es_device *es_dev) +{ + int error = -EINVAL; + + if (!es_dev->ios_title) { + drv_printk(KERN_ERR, "no IOS previously loaded\n"); + goto out; + } + + error = starlet_es_launch_title(es_dev, es_dev->ios_title); + if (!error) + es_dev->fd = -1; +out: + return error; +} + +/* + * This call may be used in a non-sleeping context + */ +static int starlet_es_reload_ios(struct starlet_es_device *es_dev) +{ + int error; + + error = __starlet_es_reload_ios(es_dev); + if (!error) + error = starlet_es_reopen(es_dev); + + if (error) + DBG("%s: error=%d (%08x)\n", __func__, error, error); + return error; +} + + +/** + * starlet_es_reload_ios_and_discard() - reload IOS and stop using it + * + * Reloads the version of IOS loaded at boot time. + * All IOS dependent devices will fail after this call unless they are + * reinitialized. + * + * This call may be used in a non-sleeping context + */ +int starlet_es_reload_ios_and_discard(void) +{ + struct starlet_es_device *es_dev = starlet_es_get_device(); + int error = -EINVAL; + + if (!es_dev) + goto err_out; + + error = starlet_es_reload_ios(es_dev); +err_out: + return error; +} +EXPORT_SYMBOL_GPL(starlet_es_reload_ios_and_discard); + +/** + * starlet_es_reload_ios_and_launch() - reload IOS and launch a title + * + * Reload the version of IOS loaded at boot time and launch a title. + * If the title loaded is a non-IOS title, this function will not return and + * is equivalent to a platform restart. + * + * This call may be used in a non-sleeping context + */ +int starlet_es_reload_ios_and_launch(u64 title) +{ + struct starlet_es_device *es_dev = starlet_es_get_device(); + int error = -EINVAL; + + if (!es_dev) + goto err_out; + + error = starlet_es_reload_ios(es_dev); + if (!error) + error = starlet_es_launch_title(es_dev, title); +err_out: + return error; +} +EXPORT_SYMBOL_GPL(starlet_es_reload_ios_and_launch); + +static int starlet_es_find_newest_title(struct starlet_es_device *es_dev, + u64 *title, + u64 title_min, u64 title_max) +{ + u64 *titles; + u64 candidate; + unsigned long count = 0; + int found, i; + int error; + + error = starlet_es_get_title_count(&count); + if (error) + return error; + if (!count) + return -ENOENT; + + titles = starlet_kzalloc(sizeof(*titles)*count, GFP_KERNEL); + if (!titles) { + DBG("%s: out of memory\n", __func__); + return -ENOMEM; + } + + error = starlet_es_get_titles(titles, count); + if (error) { + starlet_kfree(titles); + return error; + } + + found = 0; + candidate = title_min; + for (i = 0; i < count; i++) { + if (titles[i] > candidate && titles[i] <= title_max) { + candidate = titles[i]; + found = 1; + } + } + + starlet_kfree(titles); + + if (!found) + return 0; + + *title = candidate; + + return 1; +} + +static int starlet_es_load_preferred_ios(struct starlet_es_device *es_dev, + u64 ios_min, u64 ios_max) +{ + u64 title = 0; + int error; + + error = starlet_es_find_newest_title(es_dev, &title, ios_min, ios_max); + if (!error) + return -ENOENT; + if (error > 0) { + es_dev->ios_title = title; + error = starlet_es_launch_title(es_dev, title); + if (!error) + error = starlet_es_reopen(es_dev); + } + + return error; +} + +static int starlet_nwc24_stop_scheduler(void) +{ + void *obuf; + const size_t osize = 0x20; + int fd; + int error = 0; + + obuf = es_small_buf_get(); + if (!obuf) + return -ENOMEM; + + fd = starlet_open("/dev/net/kd/request", 0); + if (fd >= 0) { + error = starlet_ioctl(fd, 1, NULL, 0, obuf, osize); + starlet_close(fd); + } + + es_small_buf_put(obuf); + + if (error) + DBG("%s: error=%d (%08x)\n", __func__, error, error); + return error; +} + +static int starlet_es_init(struct starlet_es_device *es_dev) +{ + u64 ios_min, ios_max; + int error; + + error = starlet_open(dev_es, 0); + if (error >= 0) { + starlet_es_device_instance = es_dev; + es_dev->fd = error; + + ios_min = 0x100000000ULL | STARLET_ES_IOS_MIN; + ios_max = 0x100000000ULL | STARLET_ES_IOS_MAX; + + error = starlet_es_load_preferred_ios(es_dev, ios_min, ios_max); + if (error) + drv_printk(KERN_WARNING, "unable to load preferred" + " IOS version (min %llx, max %llx)\n", + ios_min, ios_max); + } + + /* + * Try to disable the Nintendo Wifi Connect 24 scheduler. + * And do this even if we failed to load our preferred IOS. + * + * When the scheduler kicks in, starlet IPC calls from Broadway fail. + */ + starlet_nwc24_stop_scheduler(); + + return error; +} + +static void starlet_es_exit(struct starlet_es_device *es_dev) +{ + starlet_es_device_instance = NULL; + starlet_close(es_dev->fd); + es_dev->fd = -1; +} + + +/* + * Driver model helper routines. + * + */ + +static int starlet_es_do_probe(struct device *dev) +{ + struct starlet_es_device *es_dev; + int retval; + + if (starlet_get_ipc_flavour() != STARLET_IPC_IOS) + return -ENODEV; + + es_dev = kzalloc(sizeof(*es_dev), GFP_KERNEL); + if (!es_dev) { + drv_printk(KERN_ERR, "failed to allocate es_dev\n"); + return -ENOMEM; + } + dev_set_drvdata(dev, es_dev); + es_dev->dev = dev; + + retval = starlet_es_init(es_dev); + if (retval) { + dev_set_drvdata(dev, NULL); + kfree(es_dev); + } + return retval; +} + +static int starlet_es_do_remove(struct device *dev) +{ + struct starlet_es_device *es_dev = dev_get_drvdata(dev); + + if (es_dev) { + starlet_es_exit(es_dev); + dev_set_drvdata(dev, NULL); + kfree(es_dev); + return 0; + } + return -ENODEV; +} + +/* + * OF platform driver hooks. + * + */ + +static int starlet_es_of_probe(struct of_device *odev, + const struct of_device_id *dev_id) +{ + return starlet_es_do_probe(&odev->dev); +} + +static int starlet_es_of_remove(struct of_device *odev) +{ + return starlet_es_do_remove(&odev->dev); +} + +static struct of_device_id starlet_es_of_match[] = { + { .compatible = "nintendo,starlet-ios-es" }, + { }, +}; + +MODULE_DEVICE_TABLE(of, starlet_es_of_match); + +static struct of_platform_driver starlet_es_of_driver = { + .owner = THIS_MODULE, + .name = DRV_MODULE_NAME, + .match_table = starlet_es_of_match, + .probe = starlet_es_of_probe, + .remove = starlet_es_of_remove, +}; + +/* + * Kernel module interface hooks. + * + */ + +static int __init starlet_es_init_module(void) +{ + drv_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION, + starlet_es_driver_version); + + return of_register_platform_driver(&starlet_es_of_driver); +} + +static void __exit starlet_es_exit_module(void) +{ + of_unregister_platform_driver(&starlet_es_of_driver); +} + +module_init(starlet_es_init_module); +module_exit(starlet_es_exit_module); + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_AUTHOR(DRV_AUTHOR); +MODULE_LICENSE("GPL"); + diff --git a/arch/powerpc/platforms/embedded6xx/starlet-ipc.c b/arch/powerpc/platforms/embedded6xx/starlet-ipc.c new file mode 100644 index 0000000..9dd50ad --- /dev/null +++ b/arch/powerpc/platforms/embedded6xx/starlet-ipc.c @@ -0,0 +1,1605 @@ +/* + * arch/powerpc/platforms/embedded6xx/starlet-ipc.c + * + * Nintendo Wii starlet IPC driver + * Copyright (C) 2008-2009 The GameCube Linux Team + * Copyright (C) 2008,2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#define DEBUG + +/*#define DBG(fmt, arg...) pr_debug(fmt, ##arg)*/ +#define DBG(fmt, arg...) drv_printk(KERN_INFO, fmt, ##arg) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define DRV_MODULE_NAME "starlet-ipc" +#define DRV_DESCRIPTION "Nintendo Wii starlet IPC driver" +#define DRV_AUTHOR "Albert Herranz" + +static char starlet_ipc_driver_version[] = "0.3i"; + +#define drv_printk(level, format, arg...) \ + printk(level DRV_MODULE_NAME ": " format , ## arg) + +/* + * Hardware registers + */ +#define STARLET_IPC_TXBUF 0x00 /* data from cpu to starlet */ + +#define STARLET_IPC_CSR 0x04 +#define STARLET_IPC_CSR_TXSTART (1<<0) /* start transmit */ +#define STARLET_IPC_CSR_TBEI (1<<1) /* tx buf empty int */ +#define STARLET_IPC_CSR_RBFI (1<<2) /* rx buf full int */ +#define STARLET_IPC_CSR_RXRDY (1<<3) /* receiver ready */ +#define STARLET_IPC_CSR_RBFIMASK (1<<4) /* rx buf full int mask */ +#define STARLET_IPC_CSR_TBEIMASK (1<<5) /* tx buf empty int mask */ + +#define STARLET_IPC_RXBUF 0x08 /* data from starlet to cpu */ + +#define STARLET_IPC_ISR 0x30 + +/* IOS calls */ +#define STARLET_IOS_OPEN 0x01 +#define STARLET_IOS_CLOSE 0x02 +#define STARLET_IOS_IOCTL 0x06 +#define STARLET_IOS_IOCTLV 0x07 + + +/* starlet_ipc_device flags */ +enum { + __TX_INUSE = 0, /* tx buffer in use flag */ + __REBOOT, /* request causes IOS reboot */ +}; + +/* + * + * Hardware. + */ + +/* + * Update control and status register. + */ +static inline void starlet_ipc_update_csr(void __iomem *io_base, u32 val) +{ + u32 csr; + + csr = in_be32(io_base + STARLET_IPC_CSR); + /* preserve interrupt masks */ + csr &= STARLET_IPC_CSR_RBFIMASK | STARLET_IPC_CSR_TBEIMASK; + csr |= val; + out_be32(io_base + STARLET_IPC_CSR, csr); +} + +/* + * Put data for starlet in the transmit fifo. + */ +static inline void starlet_ipc_sendto(void __iomem *io_base, u32 data) +{ + out_be32(io_base + STARLET_IPC_TXBUF, data); +} + +/* + * Get data from starlet out the receive fifo. + */ +static inline u32 starlet_ipc_recvfrom(void __iomem *io_base) +{ + return in_be32(io_base + STARLET_IPC_RXBUF); +} + +/* + * Issue an end-of-interrupt sequence. + */ +static void starlet_ipc_rx_ready(void __iomem *io_base) +{ + starlet_ipc_update_csr(io_base, STARLET_IPC_CSR_RXRDY); +} + +/* + * Calm the hardware down. + */ +static void starlet_ipc_quiesce(struct starlet_ipc_device *ipc_dev) +{ + u32 csr; + + /* ack and disable MBOX? and REPLY interrupts */ + csr = in_be32(ipc_dev->io_base + STARLET_IPC_CSR); + csr &= ~(STARLET_IPC_CSR_TBEIMASK | STARLET_IPC_CSR_RBFIMASK); + csr |= STARLET_IPC_CSR_TBEI | STARLET_IPC_CSR_RBFI; + out_be32(ipc_dev->io_base + STARLET_IPC_CSR, csr); +} + +/* + * Request routines. + * + */ + +#if 0 + +#define __case_string(_s) \ +case _s: \ + str = #_s; \ + break; + +static char *stipc_cmd_string(u32 cmd) +{ + char *str = "unknown"; + + switch (cmd) { +__case_string(STARLET_IOS_OPEN) +__case_string(STARLET_IOS_CLOSE) +__case_string(STARLET_IOS_IOCTL) +__case_string(STARLET_IOS_IOCTLV) + } + return str; +} + +static void starlet_ipc_pretty_print_request(struct starlet_ipc_request *req) +{ + drv_printk(KERN_INFO, "\n" + " struct starlet_ipc_request = {\n" + " cmd = %s (0x%08x)\n" + " result = %d (0x%08x)%s\n" + " seconds_elapsed = %u\n" + " dma_addr = %p\n" + " };\n" + , + stipc_cmd_string(req->cmd), req->cmd, + req->result, req->result, + (req->result == 0xdeadbeef) ? " /* pending */" : "", + jiffies_to_msecs(jiffies - req->jiffies) / 1000, + (void *)req->dma_addr + ); +} + +#endif + +static void starlet_ipc_debug_print_request(struct starlet_ipc_request *req) +{ +#if 0 + DBG("cmd=%x, result=%x, fd=%x, dma_addr=%p\n", + req->cmd, req->result, req->fd, (void *)req->dma_addr); +#endif +} + +struct starlet_ipc_request * +starlet_ipc_alloc_request(struct starlet_ipc_device *ipc_dev, gfp_t flags) +{ + struct starlet_ipc_request *req; + dma_addr_t dma_addr; + + req = dma_pool_alloc(ipc_dev->dma_pool, flags, &dma_addr); + if (req) { + memset(req, 0, sizeof(*req)); + req->ipc_dev = ipc_dev; + req->result = 0xdeadbeef; + req->sig = ipc_dev->random_id; + req->dma_addr = dma_addr; + INIT_LIST_HEAD(&req->node); + } + return req; +} + +void starlet_ipc_free_request(struct starlet_ipc_request *req) +{ + dma_pool_free(req->ipc_dev->dma_pool, req, req->dma_addr); +} + +static void starlet_ipc_start_request(struct starlet_ipc_request *req) +{ + struct starlet_ipc_device *ipc_dev = req->ipc_dev; + void __iomem *io_base = ipc_dev->io_base; + unsigned long flags; + + starlet_ipc_debug_print_request(req); + + spin_lock_irqsave(&ipc_dev->list_lock, flags); + list_add_tail(&req->node, &ipc_dev->outstanding_list); + ipc_dev->nr_outstanding++; + req->jiffies = jiffies; + spin_unlock_irqrestore(&ipc_dev->list_lock, flags); + + starlet_ipc_sendto(io_base, (u32) req->dma_addr); + starlet_ipc_update_csr(io_base, STARLET_IPC_CSR_TXSTART); +} + +static void starlet_ipc_complete_request(struct starlet_ipc_request *req) +{ + struct starlet_ipc_device *ipc_dev = req->ipc_dev; + unsigned long flags; + + spin_lock_irqsave(&ipc_dev->list_lock, flags); + list_del_init(&req->node); + ipc_dev->nr_outstanding--; + req->jiffies = 0; + spin_unlock_irqrestore(&ipc_dev->list_lock, flags); + + starlet_ipc_debug_print_request(req); + + /* per request completion callback */ + if (req->complete) + req->complete(req); + + /* async callback */ + if (req->done) + req->done(req); +} + +static void starlet_ipc_submit_request(struct starlet_ipc_request *req) +{ + struct starlet_ipc_device *ipc_dev = req->ipc_dev; + unsigned long flags; + + if (test_and_set_bit(__TX_INUSE, &ipc_dev->flags)) { + spin_lock_irqsave(&ipc_dev->list_lock, flags); + list_add_tail(&req->node, &ipc_dev->pending_list); + ipc_dev->nr_pending++; + spin_unlock_irqrestore(&ipc_dev->list_lock, flags); + } else + starlet_ipc_start_request(req); +} + +static struct starlet_ipc_request * +starlet_ipc_find_request_by_bus_addr(struct starlet_ipc_device *ipc_dev, + dma_addr_t req_bus_addr) +{ + struct starlet_ipc_request *req; + unsigned long flags; + + spin_lock_irqsave(&ipc_dev->list_lock, flags); + list_for_each_entry(req, &ipc_dev->outstanding_list, node) { + if (req && req->sig != ipc_dev->random_id) { + drv_printk(KERN_ERR, "IPC trash detected\n"); + /* leak memory, we can't safely use it */ + ipc_dev->nr_outstanding = 0; + INIT_LIST_HEAD(&ipc_dev->outstanding_list); + INIT_LIST_HEAD(&req->node); + spin_unlock_irqrestore(&ipc_dev->list_lock, flags); + return NULL; + } + if (req && req_bus_addr == req->dma_addr) { + spin_unlock_irqrestore(&ipc_dev->list_lock, flags); + return req; + } + } + spin_unlock_irqrestore(&ipc_dev->list_lock, flags); + return NULL; +} + +/* + * Interrupt handlers. + * + */ + +/* + * Transmit Buffer Empty Interrupt dispatcher. + */ +static int starlet_ipc_dispatch_tbei(struct starlet_ipc_device *ipc_dev) +{ + void __iomem *io_base = ipc_dev->io_base; + struct starlet_ipc_request *req = NULL; + struct list_head *pending = &ipc_dev->pending_list; + unsigned long flags; + + spin_lock_irqsave(&ipc_dev->list_lock, flags); + if (!list_empty(pending)) { + req = list_entry(pending->next, struct starlet_ipc_request, + node); + list_del_init(&req->node); + ipc_dev->nr_pending--; + } + spin_unlock_irqrestore(&ipc_dev->list_lock, flags); + if (req) + starlet_ipc_start_request(req); + else { + if (!test_and_clear_bit(__TX_INUSE, &ipc_dev->flags)) { + /* we get two consecutive TBEIs on reboot */ + if (test_and_clear_bit(__REBOOT, &ipc_dev->flags)) { + req = ipc_dev->req; + ipc_dev->req = NULL; + if (req) { + req->result = 0; + starlet_ipc_complete_request(req); + } + starlet_ipc_rx_ready(io_base); + } + } + } + + return IRQ_HANDLED; +} + +/* + * Receive Buffer Full Interrupt dispatcher. + */ +static int starlet_ipc_dispatch_rbfi(struct starlet_ipc_device *ipc_dev) +{ + void __iomem *io_base = ipc_dev->io_base; + struct starlet_ipc_request *req; + unsigned long req_bus_addr; + + req_bus_addr = starlet_ipc_recvfrom(io_base); + if (!req_bus_addr) + return IRQ_NONE; + + req = starlet_ipc_find_request_by_bus_addr(ipc_dev, req_bus_addr); + if (req) + starlet_ipc_complete_request(req); + else + drv_printk(KERN_WARNING, "unknown request, bus=%p\n", + (void *)req_bus_addr); + starlet_ipc_rx_ready(io_base); + return IRQ_HANDLED; +} + +typedef int (*ipc_handler_t) (struct starlet_ipc_device *); + +static int +starlet_ipc_cond_dispatch_irq(struct starlet_ipc_device *ipc_dev, + u32 irqmask, u32 irq, ipc_handler_t handler) +{ + void __iomem *io_base = ipc_dev->io_base; + u32 csr; + int retval = IRQ_NONE; + + csr = in_be32(io_base + STARLET_IPC_CSR); + if ((csr & (irqmask | irq)) == (irqmask | irq)) { + /* early ack */ + starlet_ipc_update_csr(io_base, irq); + out_be32(io_base + STARLET_IPC_ISR, 0x40000000); /* huh? */ + retval = handler(ipc_dev); + } + return retval; +} + +static irqreturn_t starlet_ipc_handler(int irq, void *data) +{ + struct starlet_ipc_device *ipc_dev = (struct starlet_ipc_device *)data; + int handled = 0; + int retval; + + /* starlet acked a request */ + retval = starlet_ipc_cond_dispatch_irq(ipc_dev, + STARLET_IPC_CSR_TBEIMASK, + STARLET_IPC_CSR_TBEI, + starlet_ipc_dispatch_tbei); + if (retval == IRQ_HANDLED) + handled++; + + /* starlet delivered a reply */ + retval = starlet_ipc_cond_dispatch_irq(ipc_dev, + STARLET_IPC_CSR_RBFIMASK, + STARLET_IPC_CSR_RBFI, + starlet_ipc_dispatch_rbfi); + if (retval == IRQ_HANDLED) + handled++; + + if (!handled) + return IRQ_NONE; + + return IRQ_HANDLED; +} + +/* + * IPC Calls. + * + */ + +static int starlet_ipc_call_done(struct starlet_ipc_request *req) +{ + complete(req->done_data); + return 0; +} + +static int starlet_ipc_call(struct starlet_ipc_request *req) +{ + DECLARE_COMPLETION(complete); + + req->done_data = &complete; + req->done = starlet_ipc_call_done; + starlet_ipc_submit_request(req); + wait_for_completion(&complete); + return req->result; +} + +static void starlet_ipc_call_nowait(struct starlet_ipc_request *req, + starlet_ipc_callback_t callback, void *arg) +{ + req->done_data = arg; + req->done = callback; + starlet_ipc_submit_request(req); +} + +static DEFINE_SPINLOCK(starlet_ipc_poll_lock); + +#define __spin_event_timeout(condition, usecs, result, __end_ts) \ + for (__end_ts = get_tbl() + tb_ticks_per_usec * usecs; \ + !(result = (condition)) && __end_ts - get_tbl() > 0;) + +static int __starlet_ipc_poll_req(struct starlet_ipc_request *req, + unsigned long usecs) +{ + struct starlet_ipc_device *ipc_dev = req->ipc_dev; + unsigned long counter; + int result; + + __spin_event_timeout(req->jiffies == 0 && + !test_bit(__REBOOT, &ipc_dev->flags), + usecs, result, counter) + starlet_ipc_handler(0, ipc_dev); + + if (!result) + req->result = -ETIME; + + /* debug */ + if (req->result < 0) + drv_printk(KERN_ERR, "%s: result %d\n", __func__, + req->result); + return req->result; +} + +static int starlet_ipc_call_polled(struct starlet_ipc_request *req, + unsigned long usecs) +{ + unsigned long flags; + int error; + + req->done = NULL; + spin_lock_irqsave(&starlet_ipc_poll_lock, flags); + starlet_ipc_submit_request(req); + error = __starlet_ipc_poll_req(req, usecs); + spin_unlock_irqrestore(&starlet_ipc_poll_lock, flags); + return error; +} + +/* + * + * IOS High level interfaces. + */ + +static struct starlet_ipc_device *starlet_ipc_device_instance; + +/** + * + */ +struct starlet_ipc_device *starlet_ipc_get_device(void) +{ + if (!starlet_ipc_device_instance) + drv_printk(KERN_ERR, "uninitialized device instance!\n"); + return starlet_ipc_device_instance; +} +EXPORT_SYMBOL_GPL(starlet_ipc_get_device); + +/** + * + */ +static int _starlet_open(const char *pathname, int flags, + gfp_t gfp_flags, int poll, unsigned long usecs) +{ +#define STSD_OPEN_BUF_SIZE 64 + static char open_buf[STSD_OPEN_BUF_SIZE] + __attribute__ ((aligned(STARLET_IPC_DMA_ALIGN + 1))); + static DEFINE_MUTEX(open_buf_lock); + + struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device(); + struct starlet_ipc_request *req; + dma_addr_t dma_addr; + char *local_pathname = NULL; + size_t len; + int error = -ENOMEM; + + if (!ipc_dev) + return -ENODEV; + + req = starlet_ipc_alloc_request(ipc_dev, gfp_flags); + if (req) { + len = strlen(pathname) + 1; + if (len < sizeof(open_buf)) { + if (mutex_trylock(&open_buf_lock)) + local_pathname = open_buf; + } + if (!local_pathname) { + local_pathname = starlet_kzalloc(len, gfp_flags); + if (!local_pathname) { + starlet_ipc_free_request(req); + return -ENOMEM; + } + } + + strncpy(local_pathname, pathname, len-1); + local_pathname[len-1] = 0; + dma_addr = dma_map_single(ipc_dev->dev, local_pathname, len, + DMA_TO_DEVICE); + + req->cmd = STARLET_IOS_OPEN; + req->open.pathname = dma_addr; /* bus address */ + req->open.mode = flags; + error = (poll) ? starlet_ipc_call_polled(req, usecs) : + starlet_ipc_call(req); + + dma_unmap_single(ipc_dev->dev, dma_addr, len, DMA_TO_DEVICE); + + if (local_pathname == open_buf) + mutex_unlock(&open_buf_lock); + else + starlet_kfree(local_pathname); + + starlet_ipc_free_request(req); + } + if (error < 0) + DBG("%s: %s: error=%d (%x)\n", __func__, pathname, + error, error); + return error; +} + +int starlet_open(const char *pathname, int flags) +{ + return _starlet_open(pathname, flags, GFP_KERNEL, 0, 0); +} +EXPORT_SYMBOL_GPL(starlet_open); + +int starlet_open_polled(const char *pathname, int flags, + unsigned long usecs) +{ + return _starlet_open(pathname, flags, GFP_ATOMIC, 1, usecs); +} +EXPORT_SYMBOL_GPL(starlet_open_polled); + +/* + * + */ +static int _starlet_close(int fd, gfp_t gfp_flags, int poll, + unsigned long usecs) +{ + struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device(); + struct starlet_ipc_request *req; + int error = -ENOMEM; + + if (!ipc_dev) + return -ENODEV; + + req = starlet_ipc_alloc_request(ipc_dev, gfp_flags); + if (req) { + req->cmd = STARLET_IOS_CLOSE; + req->fd = fd; + error = (poll) ? starlet_ipc_call_polled(req, usecs) : + starlet_ipc_call(req); + starlet_ipc_free_request(req); + } + return error; +} + +int starlet_close(int fd) +{ + return _starlet_close(fd, GFP_KERNEL, 0, 0); +} +EXPORT_SYMBOL_GPL(starlet_close); + +int starlet_close_polled(int fd, unsigned long usecs) +{ + return _starlet_close(fd, GFP_ATOMIC, 1, usecs); +} +EXPORT_SYMBOL_GPL(starlet_close_polled); + + + +/* + * starlet_ioctl* + * + */ + +static int starlet_ioctl_dma_complete(struct starlet_ipc_request *req) +{ + return 0; +} + +/* + * + */ +int starlet_ioctl_dma_prepare(struct starlet_ipc_request *req, + int fd, int request, + dma_addr_t ibuf, size_t ilen, + dma_addr_t obuf, size_t olen) +{ + struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device(); + + if (!ipc_dev) + return -ENODEV; + + req->cmd = STARLET_IOS_IOCTL; + req->fd = fd; + req->ioctl.request = (u32) request; + req->ioctl.ibuf = ibuf; + req->ioctl.ilen = ilen; + req->ioctl.obuf = obuf; + req->ioctl.olen = olen; + req->complete = starlet_ioctl_dma_complete; + + return 0; +} + +/* + * + */ +int starlet_ioctl_dma(int fd, int request, + dma_addr_t ibuf, size_t ilen, + dma_addr_t obuf, size_t olen) +{ + struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device(); + struct starlet_ipc_request *req; + int error; + + req = starlet_ipc_alloc_request(ipc_dev, GFP_ATOMIC); + if (!req) + return -ENOMEM; + + error = starlet_ioctl_dma_prepare(req, fd, request, + ibuf, ilen, + obuf, olen); + if (!error) + error = starlet_ipc_call(req); + starlet_ipc_free_request(req); + + if (error) + DBG("%s: error=%d (%x)\n", __func__, error, error); + return error; +} +EXPORT_SYMBOL_GPL(starlet_ioctl_dma); + +/** + * + */ +int starlet_ioctl_dma_nowait(int fd, int request, + dma_addr_t ibuf, size_t ilen, + dma_addr_t obuf, size_t olen, + starlet_ipc_callback_t callback, void *arg) +{ + struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device(); + struct starlet_ipc_request *req; + int error; + + req = starlet_ipc_alloc_request(ipc_dev, GFP_ATOMIC); + if (!req) + return -ENOMEM; + + error = starlet_ioctl_dma_prepare(req, + fd, request, + ibuf, ilen, + obuf, olen); + if (!error) + starlet_ipc_call_nowait(req, callback, arg); + else + starlet_ipc_free_request(req); + + if (error) + DBG("%s: error=%d (%x)\n", __func__, error, error); + return error; +} +EXPORT_SYMBOL_GPL(starlet_ioctl_dma_nowait); + +static int starlet_ioctl_complete(struct starlet_ipc_request *req) +{ + struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device(); + dma_addr_t ibuf_ba, obuf_ba; + size_t ilen, olen; + + ibuf_ba = req->ioctl.ibuf; + ilen = req->ioctl.ilen; + obuf_ba = req->ioctl.obuf; + olen = req->ioctl.olen; + + if (ibuf_ba) + dma_unmap_single(ipc_dev->dev, ibuf_ba, ilen, DMA_TO_DEVICE); + if (obuf_ba) + dma_unmap_single(ipc_dev->dev, obuf_ba, olen, DMA_FROM_DEVICE); + + return 0; +} + +/* + * + */ +int starlet_ioctl_prepare(struct starlet_ipc_request *req, + int fd, int request, + void *ibuf, size_t ilen, + void *obuf, size_t olen) +{ + struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device(); + dma_addr_t ibuf_ba, obuf_ba; + int error; + + if (!ipc_dev) + return -ENODEV; + + BUG_ON(!IS_ALIGNED((unsigned long)ibuf, STARLET_IPC_DMA_ALIGN+1)); + BUG_ON(!IS_ALIGNED((unsigned long)obuf, STARLET_IPC_DMA_ALIGN+1)); + + ibuf_ba = (ibuf) ? dma_map_single(ipc_dev->dev, ibuf, ilen, + DMA_TO_DEVICE) : 0; + obuf_ba = (obuf) ? dma_map_single(ipc_dev->dev, obuf, olen, + DMA_FROM_DEVICE) : 0; + + error = starlet_ioctl_dma_prepare(req, fd, request, + ibuf_ba, ilen, obuf_ba, olen); + if (!error) { + req->complete = starlet_ioctl_complete; + if (ibuf) + dma_unmap_single(ipc_dev->dev, ibuf_ba, ilen, + DMA_TO_DEVICE); + if (obuf) + dma_unmap_single(ipc_dev->dev, obuf_ba, olen, + DMA_FROM_DEVICE); + } + return error; +} + +/** + * + */ +static int _starlet_ioctl(int fd, int request, + void *ibuf, size_t ilen, + void *obuf, size_t olen, + int poll, unsigned long usecs) +{ + struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device(); + struct starlet_ipc_request *req; + int error; + + req = starlet_ipc_alloc_request(ipc_dev, GFP_ATOMIC); + if (!req) + return -ENOMEM; + + error = starlet_ioctl_prepare(req, fd, request, + ibuf, ilen, obuf, olen); + if (!error) + error = (poll) ? starlet_ipc_call_polled(req, usecs) : + starlet_ipc_call(req); + starlet_ipc_free_request(req); + + if (error < 0) + DBG("%s: error=%d (%x)\n", __func__, error, error); + return error; +} + +int starlet_ioctl(int fd, int request, + void *ibuf, size_t ilen, + void *obuf, size_t olen) +{ + return _starlet_ioctl(fd, request, ibuf, ilen, obuf, olen, 0, 0); +} +EXPORT_SYMBOL_GPL(starlet_ioctl); + +int starlet_ioctl_polled(int fd, int request, + void *ibuf, size_t ilen, + void *obuf, size_t olen, + unsigned long usecs) +{ + return _starlet_ioctl(fd, request, ibuf, ilen, obuf, olen, 1, usecs); +} +EXPORT_SYMBOL_GPL(starlet_ioctl_polled); + +/** + * + */ +int starlet_ioctl_nowait(int fd, int request, + void *ibuf, size_t ilen, + void *obuf, size_t olen, + starlet_ipc_callback_t callback, void *arg) +{ + struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device(); + struct starlet_ipc_request *req; + int error; + + req = starlet_ipc_alloc_request(ipc_dev, GFP_ATOMIC); + if (!req) + return -ENOMEM; + + error = starlet_ioctl_prepare(req, fd, request, + ibuf, ilen, obuf, olen); + if (!error) + starlet_ipc_call_nowait(req, callback, arg); + else + starlet_ipc_free_request(req); + + if (error) + DBG("%s: error=%d (%x)\n", __func__, error, error); + return error; +} +EXPORT_SYMBOL_GPL(starlet_ioctl_nowait); + + +static int starlet_ioctlv_complete(struct starlet_ipc_request *req) +{ + struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device(); + struct starlet_iovec *iovec = req->iovec; + dma_addr_t iovec_da = req->ioctlv.iovec_da; + size_t iovec_size = req->iovec_size; + +#if 0 + unsigned int nents; + DBG("%s: nents_in=%u, nents_io=%u\n", __func__, + req->sgl_nents_in, req->sgl_nents_io); +#endif + + if (req->sgl_nents_in > 0) + dma_unmap_sg(ipc_dev->dev, req->sgl_in, req->sgl_nents_in, + DMA_TO_DEVICE); + if (req->sgl_nents_io > 0) + dma_unmap_sg(ipc_dev->dev, req->sgl_io, req->sgl_nents_io, + DMA_BIDIRECTIONAL); + if (iovec) { + dma_unmap_single(ipc_dev->dev, + iovec_da, iovec_size, DMA_TO_DEVICE); + +#if 0 + struct starlet_iovec *p; + p = iovec; + nents = req->sgl_nents_in; + while (nents--) { + DBG("%s: in: dma_addr=%p, dma_len=%u\n", __func__, + (void *)p->dma_addr, p->dma_len); + p++; + } + nents = req->sgl_nents_io; + while (nents--) { + DBG("%s: io: dma_addr=%p, dma_len=%u\n", __func__, + (void *)p->dma_addr, p->dma_len); + p++; + } +#endif + + starlet_kfree(iovec); + } + return 0; +} + +/* + * + */ +int starlet_ioctlv_prepare(struct starlet_ipc_request *req, + int fd, int request, + unsigned int nents_in, + struct scatterlist *sgl_in, + unsigned int nents_io, + struct scatterlist *sgl_io) +{ + struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device(); + struct starlet_iovec *iovec, *p; + dma_addr_t iovec_da = 0; + size_t iovec_size = 0; + struct scatterlist *sg; + unsigned int nents, i; + + if (!ipc_dev) + return -ENODEV; + + BUG_ON(nents_in > 0 && !sgl_in); + BUG_ON(nents_io > 0 && !sgl_io); + + nents = nents_in + nents_io; + if (nents > 0) { + iovec_size = nents * sizeof(*iovec); + iovec = starlet_kzalloc(iovec_size, GFP_ATOMIC); + if (!iovec) + return -ENOMEM; + } else { + iovec = NULL; + } + + p = iovec; + if (nents_in > 0) { + nents_in = dma_map_sg(ipc_dev->dev, sgl_in, nents_in, + DMA_TO_DEVICE); + for_each_sg(sgl_in, sg, nents_in, i) { +#if 0 + DBG("%s: in: dma_addr=%p, dma_len=%u\n", __func__, + (void *)sg_dma_address(sg), sg_dma_len(sg)); +#endif + p->dma_addr = sg_dma_address(sg); + p->dma_len = sg_dma_len(sg); + p++; + } + } + if (nents_io > 0) { + nents_io = dma_map_sg(ipc_dev->dev, sgl_io, nents_io, + DMA_BIDIRECTIONAL); + for_each_sg(sgl_io, sg, nents_io, i) { +#if 0 + DBG("%s: io: dma_addr=%p, dma_len=%u\n", __func__, + (void *)sg_dma_address(sg), sg_dma_len(sg)); +#endif + p->dma_addr = sg_dma_address(sg); + p->dma_len = sg_dma_len(sg); + p++; + } + } + + if (iovec) + iovec_da = dma_map_single(ipc_dev->dev, + iovec, iovec_size, + DMA_TO_DEVICE); + + req->iovec = iovec; + req->iovec_size = iovec_size; + req->sgl_nents_in = nents_in; + req->sgl_in = sgl_in; + req->sgl_nents_io = nents_io; + req->sgl_io = sgl_io; + + req->cmd = STARLET_IOS_IOCTLV; + req->fd = fd; + req->ioctlv.request = request; + req->ioctlv.argc_in = nents_in; + req->ioctlv.argc_io = nents_io; + req->ioctlv.iovec_da = iovec_da; + req->complete = starlet_ioctlv_complete; + +#if 0 + DBG("%s: fd=%d, request=%d," + " argc_in=%u, argc_io=%u, iovec_da=%08x\n" , __func__, + req->fd, req->ioctlv.request, + req->ioctlv.argc_in, req->ioctlv.argc_io, + req->ioctlv.iovec_da); +#endif + return 0; +} + +/** + * + */ +static int _starlet_ioctlv(int fd, int request, + unsigned int nents_in, + struct scatterlist *sgl_in, + unsigned int nents_io, + struct scatterlist *sgl_io, + int poll, unsigned long usecs) +{ + struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device(); + struct starlet_ipc_request *req; + int error; + + req = starlet_ipc_alloc_request(ipc_dev, GFP_ATOMIC); + if (!req) + return -ENOMEM; + + error = starlet_ioctlv_prepare(req, fd, request, + nents_in, sgl_in, + nents_io, sgl_io); + if (!error) + error = (poll) ? starlet_ipc_call_polled(req, usecs) : + starlet_ipc_call(req); + starlet_ipc_free_request(req); + + if (error < 0) + DBG("%s: error=%d (%x)\n", __func__, error, error); + return error; +} + +int starlet_ioctlv(int fd, int request, + unsigned int nents_in, + struct scatterlist *sgl_in, + unsigned int nents_io, + struct scatterlist *sgl_io) +{ + return _starlet_ioctlv(fd, request, + nents_in, sgl_in, nents_io, sgl_io, 0, 0); +} +EXPORT_SYMBOL_GPL(starlet_ioctlv); + +int starlet_ioctlv_polled(int fd, int request, + unsigned int nents_in, + struct scatterlist *sgl_in, + unsigned int nents_io, + struct scatterlist *sgl_io, + unsigned long usecs) +{ + return _starlet_ioctlv(fd, request, + nents_in, sgl_in, nents_io, sgl_io, 1, usecs); +} +EXPORT_SYMBOL_GPL(starlet_ioctlv_polled); + +/** + * + */ +int starlet_ioctlv_nowait(int fd, int request, + unsigned int nents_in, + struct scatterlist *sgl_in, + unsigned int nents_io, + struct scatterlist *sgl_io, + starlet_ipc_callback_t callback, void *arg) +{ + struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device(); + struct starlet_ipc_request *req; + int error; + + req = starlet_ipc_alloc_request(ipc_dev, GFP_ATOMIC); + if (!req) + return -ENOMEM; + + error = starlet_ioctlv_prepare(req, fd, request, + nents_in, sgl_in, + nents_io, sgl_io); + if (!error) + starlet_ipc_call_nowait(req, callback, arg); + else + starlet_ipc_free_request(req); + + if (error < 0) + DBG("%s: error=%d (%x)\n", __func__, error, error); + return error; +} +EXPORT_SYMBOL_GPL(starlet_ioctlv_nowait); + +/** + * + */ +int starlet_ioctlv_and_reboot(int fd, int request, + unsigned int nents_in, + struct scatterlist *sgl_in, + unsigned int nents_io, + struct scatterlist *sgl_io) +{ + struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device(); + struct starlet_ipc_request *req; + int error; + + req = starlet_ipc_alloc_request(ipc_dev, GFP_ATOMIC); + if (!req) + return -ENOMEM; + + error = starlet_ioctlv_prepare(req, fd, request, + nents_in, sgl_in, + nents_io, sgl_io); + if (!error) { + ipc_dev->req = req; + set_bit(__REBOOT, &ipc_dev->flags); + error = starlet_ipc_call_polled(req, 10000000 /* usecs */); + } + starlet_ipc_free_request(req); + + if (error < 0) + DBG("%s: error=%d (%x)\n", __func__, error, error); + return error; +} +EXPORT_SYMBOL_GPL(starlet_ioctlv_and_reboot); + +/* + * starlet_ioh_ioctlv* + * + */ + +static int starlet_ioh_ioctlv_complete(struct starlet_ipc_request *req) +{ + struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device(); + struct starlet_iovec *iovec = req->iovec; + dma_addr_t iovec_da = req->ioctlv.iovec_da; + size_t iovec_size = req->iovec_size; + +#if 0 + unsigned int nents; + DBG("%s: nents_in=%u, nents_io=%u\n", __func__, + req->sgl_nents_in, req->sgl_nents_io); +#endif + + if (req->sgl_nents_in > 0) + starlet_ioh_dma_unmap_sg(ipc_dev->dev, + req->ioh_sgl_in, req->sgl_nents_in, + DMA_TO_DEVICE); + if (req->sgl_nents_io > 0) + starlet_ioh_dma_unmap_sg(ipc_dev->dev, + req->ioh_sgl_io, req->sgl_nents_io, + DMA_BIDIRECTIONAL); + if (iovec) { + dma_unmap_single(ipc_dev->dev, + iovec_da, iovec_size, DMA_TO_DEVICE); + + { /* begin debug */ +#if 0 + struct starlet_iovec *p; + p = iovec; + nents = req->sgl_nents_in; + while (nents--) { + DBG("%s: in: dma_addr=%p, dma_len=%u\n", __func__, + (void *)p->dma_addr, p->dma_len); + p++; + } + nents = req->sgl_nents_io; + while (nents--) { + DBG("%s: io: dma_addr=%p, dma_len=%u\n", __func__, + (void *)p->dma_addr, p->dma_len); + p++; + } +#endif + } /* end debug */ + + starlet_kfree(iovec); + } + return 0; +} + +/* + * + */ +int starlet_ioh_ioctlv_prepare(struct starlet_ipc_request *req, + int fd, int request, + unsigned int nents_in, + struct starlet_ioh_sg *ioh_sgl_in, + unsigned int nents_io, + struct starlet_ioh_sg *ioh_sgl_io) +{ + struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device(); + struct starlet_iovec *iovec, *p; + dma_addr_t iovec_da = 0; + size_t iovec_size = 0; + struct starlet_ioh_sg *ioh_sg; + unsigned int nents, i; + + if (!ipc_dev) + return -ENODEV; + + BUG_ON(nents_in > 0 && !ioh_sgl_in); + BUG_ON(nents_io > 0 && !ioh_sgl_io); + + nents = nents_in + nents_io; + if (nents > 0) { + iovec_size = nents * sizeof(*iovec); + iovec = starlet_kzalloc(iovec_size, GFP_ATOMIC); + if (!iovec) + return -ENOMEM; + } else { + iovec = NULL; + } + + p = iovec; + if (nents_in > 0) { + nents_in = starlet_ioh_dma_map_sg(ipc_dev->dev, + ioh_sgl_in, nents_in, + DMA_TO_DEVICE); + starlet_ioh_for_each_sg(ioh_sgl_in, ioh_sg, nents_in, i) { +#if 0 + DBG("%s: in: dma_addr=%p, dma_len=%u\n", __func__, + (void *)ioh_sg->dma_addr, ioh_sg->len); +#endif + p->dma_addr = ioh_sg->dma_addr; + p->dma_len = ioh_sg->len; + p++; + } + } + if (nents_io > 0) { + nents_io = starlet_ioh_dma_map_sg(ipc_dev->dev, + ioh_sgl_io, nents_io, + DMA_BIDIRECTIONAL); + starlet_ioh_for_each_sg(ioh_sgl_io, ioh_sg, nents_io, i) { +#if 0 + DBG("%s: io: dma_addr=%p, dma_len=%u\n", __func__, + (void *)ioh_sg->dma_addr, ioh_sg->len); +#endif + p->dma_addr = ioh_sg->dma_addr; + p->dma_len = ioh_sg->len; + p++; + } + } + + if (iovec) + iovec_da = dma_map_single(ipc_dev->dev, + iovec, iovec_size, + DMA_TO_DEVICE); + + req->iovec = iovec; + req->iovec_size = iovec_size; + req->sgl_nents_in = nents_in; + req->ioh_sgl_in = ioh_sgl_in; + req->sgl_nents_io = nents_io; + req->ioh_sgl_io = ioh_sgl_io; + + req->cmd = STARLET_IOS_IOCTLV; + req->fd = fd; + req->ioctlv.request = request; + req->ioctlv.argc_in = nents_in; + req->ioctlv.argc_io = nents_io; + req->ioctlv.iovec_da = iovec_da; + req->complete = starlet_ioh_ioctlv_complete; + + return 0; +} + +/** + * + */ +int starlet_ioh_ioctlv(int fd, int request, + unsigned int nents_in, + struct starlet_ioh_sg *ioh_sgl_in, + unsigned int nents_io, + struct starlet_ioh_sg *ioh_sgl_io) +{ + struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device(); + struct starlet_ipc_request *req; + int error; + + req = starlet_ipc_alloc_request(ipc_dev, GFP_ATOMIC); + if (!req) + return -ENOMEM; + + error = starlet_ioh_ioctlv_prepare(req, fd, request, + nents_in, ioh_sgl_in, + nents_io, ioh_sgl_io); + if (!error) + error = starlet_ipc_call(req); + starlet_ipc_free_request(req); + + if (error < 0) + DBG("%s: error=%d (%x)\n", __func__, error, error); + return error; +} +EXPORT_SYMBOL_GPL(starlet_ioh_ioctlv); + +/** + * + */ +int starlet_ioh_ioctlv_nowait(int fd, int request, + unsigned int nents_in, + struct starlet_ioh_sg *ioh_sgl_in, + unsigned int nents_io, + struct starlet_ioh_sg *ioh_sgl_io, + starlet_ipc_callback_t callback, void *arg) +{ + struct starlet_ipc_device *ipc_dev = starlet_ipc_get_device(); + struct starlet_ipc_request *req; + int error; + + req = starlet_ipc_alloc_request(ipc_dev, GFP_ATOMIC); + if (!req) + return -ENOMEM; + + error = starlet_ioh_ioctlv_prepare(req, fd, request, + nents_in, ioh_sgl_in, + nents_io, ioh_sgl_io); + if (!error) + starlet_ipc_call_nowait(req, callback, arg); + else + starlet_ipc_free_request(req); + + if (error < 0) + DBG("%s: error=%d (%x)\n", __func__, error, error); + return error; +} +EXPORT_SYMBOL_GPL(starlet_ioh_ioctlv_nowait); + + +/* + * This "watchdog" code may be used to detect misbehaving requests. + * + * Note that some requests can take a lot of time to complete. + * For example, a keyboard event, which is delivered every time a key is + * pressed or released (or a keyboard is connected/disconnected), may take an + * arbitrary amount of time to arrive. + * + */ + +#define STARLET_IPC_WATCHDOG_TIME (60 * HZ) + +static void starlet_ipc_watchdog(unsigned long arg) +{ +#if 0 + struct starlet_ipc_device *ipc_dev = (struct starlet_ipc_device *)arg; + struct starlet_ipc_request *req; + unsigned long flags; + + spin_lock_irqsave(&ipc_dev->list_lock, flags); + list_for_each_entry(req, &ipc_dev->outstanding_list, node) { + if (req && + time_after(jiffies, + req->jiffies + STARLET_IPC_WATCHDOG_TIME)) { + drv_printk(KERN_INFO, "request on the outstanding" + " list for too long\n"); + starlet_ipc_pretty_print_request(req); + } + } + spin_unlock_irqrestore(&ipc_dev->list_lock, flags); + + mod_timer(&ipc_dev->timer, jiffies + STARLET_IPC_WATCHDOG_TIME); +#endif +} + +/* + * Setup routines. + * + */ + +/* + * Place here any desired hardware cleanups while drivers get written. + */ +static void starlet_fixups(void) +{ + static u32 buf[8] + __attribute__ ((aligned(STARLET_IPC_DMA_ALIGN + 1))); + struct scatterlist in[6], io[1]; + int fd; + void *gpio; + + /* close any open file descriptors, just in case */ + for (fd = 0; fd < 24; fd++) + starlet_close(fd); + + /* + * Hey! We are super-green. And you? + */ + + /* try to stop the dvd unit motor */ + fd = starlet_open("/dev/di", 0); + if (fd >= 0) { + buf[0] = 0xe3000000; /* stop motor command */ + buf[1] = 0; + buf[2] = 0; + starlet_ioctl(fd, buf[0], + buf, sizeof(buf), + buf, sizeof(buf)); + starlet_close(fd); + } + + /* try to disconnect the wiimote */ + fd = starlet_open("/dev/usb/oh1/57e/305", 2); + if (fd >= 0) { + /* + * This assumes big endianness and 4 byte dma alignment. + */ + buf[0] = 0x20000000; /* bmRequestType 0x20 */ + buf[1] = 0x00000000; /* bRequest 0x00 */ + buf[2] = 0x00000000; /* wValue 0x00, 0x00 */ + buf[3] = 0x00000000; /* wIndex 0x00, 0x00 */ + buf[4] = 0x03000000; /* wLength 0x03, 0x00 */ + buf[5] = 0x00000000; /* timeout? 0x00 */ + buf[6] = 0x030c0000; /* payload 0x03, 0x0c, 0x00 */ + sg_init_table(in, 6); + sg_set_buf(&in[0], &buf[0], 1); + sg_set_buf(&in[1], &buf[1], 1); + sg_set_buf(&in[2], &buf[2], 2); + sg_set_buf(&in[3], &buf[3], 2); + sg_set_buf(&in[4], &buf[4], 2); + sg_set_buf(&in[5], &buf[5], 1); + sg_init_table(io, 1); + sg_set_buf(&io[0], &buf[6], 3); + starlet_ioctlv(fd, 0, 6, in, 1, io); + starlet_close(fd); + } + + /* + * Try to turn off the front led and sensor bar. + * (not strictly starlet-only stuff but anyway...) + */ + gpio = ioremap(0x0d8000c0, 4); + if (gpio) { + out_be32(gpio, in_be32(gpio) & ~0x120); + iounmap(gpio); + } +} + +static int starlet_ipc_init(struct starlet_ipc_device *ipc_dev, + struct resource *mem, int irq) +{ + size_t size, io_size; + int error; + + ipc_dev->random_id = get_random_int(); + + error = starlet_malloc_lib_bootstrap(&mem[1]); + if (error) + return error; + + io_size = mem[0].end - mem[0].start + 1; + ipc_dev->io_base = ioremap(mem[0].start, io_size); + ipc_dev->irq = irq; + + size = max((size_t)64, sizeof(struct starlet_ipc_request)); + ipc_dev->dma_pool = dma_pool_create(DRV_MODULE_NAME, + ipc_dev->dev, + size, STARLET_IPC_DMA_ALIGN + 1, 0); + if (!ipc_dev->dma_pool) { + drv_printk(KERN_ERR, "dma_pool_create failed\n"); + iounmap(ipc_dev->io_base); + return -ENOMEM; + } + spin_lock_init(&ipc_dev->list_lock); + INIT_LIST_HEAD(&ipc_dev->pending_list); + INIT_LIST_HEAD(&ipc_dev->outstanding_list); + + starlet_ipc_device_instance = ipc_dev; + + init_timer(&ipc_dev->timer); + ipc_dev->timer.function = starlet_ipc_watchdog; + ipc_dev->timer.data = (unsigned long)ipc_dev; + ipc_dev->timer.expires = jiffies + STARLET_IPC_WATCHDOG_TIME; + add_timer(&ipc_dev->timer); + + error = request_irq(ipc_dev->irq, starlet_ipc_handler, 0, + DRV_MODULE_NAME, ipc_dev); + if (error) { + drv_printk(KERN_ERR, "request of IRQ %d failed\n", irq); + starlet_ipc_device_instance = NULL; + dma_pool_destroy(ipc_dev->dma_pool); + iounmap(ipc_dev->io_base); + return error; + } + + /* ack and enable RBFI and TBEI interrupts */ + out_be32(ipc_dev->io_base + STARLET_IPC_CSR, + STARLET_IPC_CSR_TBEIMASK | STARLET_IPC_CSR_RBFIMASK | + STARLET_IPC_CSR_TBEI | STARLET_IPC_CSR_RBFI); + + starlet_fixups(); + + return error; +} + +static void starlet_ipc_exit(struct starlet_ipc_device *ipc_dev) +{ + starlet_ipc_device_instance = NULL; + starlet_ipc_quiesce(ipc_dev); + + del_timer(&ipc_dev->timer); + + free_irq(ipc_dev->irq, ipc_dev); + dma_pool_destroy(ipc_dev->dma_pool); + iounmap(ipc_dev->io_base); + ipc_dev->io_base = NULL; +} + + +/* + * Driver model helper routines. + * + */ + +static int starlet_ipc_do_probe(struct device *dev, struct resource *mem, + int irq) +{ + struct starlet_ipc_device *ipc_dev; + int retval; + + if (starlet_get_ipc_flavour() != STARLET_IPC_IOS) + return -ENODEV; + + ipc_dev = kzalloc(sizeof(*ipc_dev), GFP_KERNEL); + if (!ipc_dev) { + drv_printk(KERN_ERR, "failed to allocate ipc_dev\n"); + return -ENOMEM; + } + dev_set_drvdata(dev, ipc_dev); + ipc_dev->dev = dev; + + retval = starlet_ipc_init(ipc_dev, mem, irq); + if (retval) { + dev_set_drvdata(dev, NULL); + kfree(ipc_dev); + } + return retval; +} + +static int starlet_ipc_do_remove(struct device *dev) +{ + struct starlet_ipc_device *ipc_dev = dev_get_drvdata(dev); + + if (ipc_dev) { + starlet_ipc_exit(ipc_dev); + dev_set_drvdata(dev, NULL); + kfree(ipc_dev); + return 0; + } + return -ENODEV; +} + +static int starlet_ipc_do_shutdown(struct device *dev) +{ + struct starlet_ipc_device *ipc_dev = dev_get_drvdata(dev); + + if (ipc_dev) { + /* + * We can't shutdown IPC as we need it to reboot the + * machine. + * Thus, no starlet_ipc_quiesce(ipc_dev); here, sorry. + */ + return 0; + } + return -ENODEV; +} + +/* + * OF platform driver hooks. + * + */ + +static int starlet_ipc_of_probe(struct of_device *odev, + const struct of_device_id *dev_id) +{ + struct resource mem[2]; + int error; + + error = of_address_to_resource(odev->node, 0, &mem[0]); + if (error) { + drv_printk(KERN_ERR, "no io memory range found\n"); + return -ENODEV; + } + error = of_address_to_resource(odev->node, 1, &mem[1]); + if (error) { + drv_printk(KERN_ERR, "missing ioh memory area (%d)\n", error); + return -ENODEV; + } + + return starlet_ipc_do_probe(&odev->dev, mem, + irq_of_parse_and_map(odev->node, 0)); +} + +static int starlet_ipc_of_remove(struct of_device *odev) +{ + return starlet_ipc_do_remove(&odev->dev); +} + +static int starlet_ipc_of_shutdown(struct of_device *odev) +{ + return starlet_ipc_do_shutdown(&odev->dev); +} + +static struct of_device_id starlet_ipc_of_match[] = { + { .compatible = "nintendo,starlet-ios-ipc" }, + { }, +}; + +MODULE_DEVICE_TABLE(of, starlet_ipc_of_match); + +static struct of_platform_driver starlet_ipc_of_driver = { + .owner = THIS_MODULE, + .name = DRV_MODULE_NAME, + .match_table = starlet_ipc_of_match, + .probe = starlet_ipc_of_probe, + .remove = starlet_ipc_of_remove, + .shutdown = starlet_ipc_of_shutdown, +}; + +/* + * Kernel module interface hooks. + * + */ + +static int __init starlet_ipc_init_module(void) +{ + drv_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION, + starlet_ipc_driver_version); + + return of_register_platform_driver(&starlet_ipc_of_driver); +} + +static void __exit starlet_ipc_exit_module(void) +{ + of_unregister_platform_driver(&starlet_ipc_of_driver); +} + +module_init(starlet_ipc_init_module); +module_exit(starlet_ipc_exit_module); + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_AUTHOR(DRV_AUTHOR); +MODULE_LICENSE("GPL"); + diff --git a/arch/powerpc/platforms/embedded6xx/starlet-malloc.c b/arch/powerpc/platforms/embedded6xx/starlet-malloc.c new file mode 100644 index 0000000..3985898 --- /dev/null +++ b/arch/powerpc/platforms/embedded6xx/starlet-malloc.c @@ -0,0 +1,365 @@ +/* + * arch/powerpc/platforms/embedded6xx/starlet-malloc.c + * + * Nintendo Wii starlet memory allocation library + * Copyright (C) 2008-2009 The GameCube Linux Team + * Copyright (C) 2008,2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +/* + * Notes from the trenches: + * + * writes from broadway to mem2 + * - 8 or 16 bit writes to mem2 modify 64 bits + * - writing 0xaa results in 0xaaffffffaaffffff being written + * - writing 0xaabb results in 0xaabbffffaabbffff being written + * - 32 bit writes work fine + * writes from starlet to mem1 + * - data must be 4 byte aligned, length must be 4 byte aligned + * + * write protected area (reads after writes do not return written info) + * 0x13620000 - 0x14000000 + * + */ + +#define DEBUG + +#define DBG(fmt, arg...) pr_debug(fmt, ##arg) + +#include +#include +#include + + +#define LIB_MODULE_NAME "starlet-malloc" +#define LIB_DESCRIPTION "Nintendo Wii starlet malloc library" +#define LIB_AUTHOR "Albert Herranz" + +static char starlet_malloc_lib_version[] = "0.1i"; + +#define drv_printk(level, format, arg...) \ + printk(level LIB_MODULE_NAME ": " format , ## arg) + + +#define STARLET_IOH_ALIGN 31 + +/* + * Simple aligned kzalloc and free. + * + * Based on the idea proposed by Satya Kiran Popuri + * http://www.cs.uic.edu/~spopuri/amalloc.html + */ + +/** + * + */ +static void *kzalloc_aligned(size_t size, gfp_t flags, size_t align) +{ + void *ptr, *aligned_ptr; + size_t aligned_size; + + /* not a power of two */ + if (align & (align - 1)) + return NULL; + + /* worst case allocation size */ + aligned_size = size + align - 1; + + /* add extra space to store allocation delta */ + aligned_size += sizeof(size_t); + + /* allocate all space */ + ptr = kzalloc(aligned_size, flags); + if (!ptr) + return NULL; + + /* calculate the aligned address, making room for the delta value */ + aligned_ptr = PTR_ALIGN(ptr + sizeof(size_t), align); + + /* save the delta before the address returned to caller */ + *((size_t *)aligned_ptr - 1) = aligned_ptr - ptr; + + return aligned_ptr; +} + +static void kfree_aligned(void *aligned_ptr) +{ + void *ptr; + size_t delta; + + if (!aligned_ptr) + return; + + /* retrieve extra allocation delta */ + delta = *((size_t *)aligned_ptr - 1); + + /* calculate original allocation area start */ + ptr = aligned_ptr - delta; + + kfree(ptr); +} + + +/** + * + */ +void *starlet_kzalloc(size_t size, gfp_t flags) +{ + return kzalloc_aligned(size, flags, STARLET_IPC_DMA_ALIGN+1); +} + +/** + * + */ +void starlet_kfree(void *ptr) +{ + kfree_aligned(ptr); +} + + + +/* + * Functions for special input/output buffer allocations. + * + * Starlet seems to have a limitation when doing non-32 bit writes to MEM1. + * This can cause up to a 3 byte data loss when starlet delivers + * data of an unaligned size. + * Writes to MEM2 don't have such a limitation. + * + * We use special buffers when we need to retrieve data of an unaligned size + * from starlet. + * + */ + +static int starlet_ioh_init(struct starlet_ioh *ioh, struct resource *mem) +{ + size_t size = mem->end - mem->start + 1; + rh_info_t *rheap; + int error = -ENOMEM; + + ioh->base = ioremap_flags(mem->start, size, _PAGE_GUARDED); + if (!ioh->base) { + drv_printk(KERN_ERR, "unable to ioremap ioh area\n"); + goto err; + } + ioh->base_phys = mem->start; + ioh->size = size; + + { + void *first = NULL, *last = NULL; + u32 *p; + + p = ioh->base + size; + do { + p--; + *p = 0xdeadbabe; + } while (p != ioh->base); + __dma_sync(ioh->base, size, DMA_TO_DEVICE); + + p = ioh->base + size; + do { + p--; + if (*p != 0xdeadbabe) { + if (!last) + last = p; + first = p; + } + } while (p != ioh->base); + + if (first) + drv_printk(KERN_INFO, "unreliable writes from" + " %p to %p\n", first, last); + } + + rheap = rh_create(STARLET_IOH_ALIGN+1); + if (IS_ERR(rheap)) { + error = PTR_ERR(rheap); + goto err_rh_create; + } + ioh->rheap = rheap; + + error = rh_attach_region(rheap, 0, size); + if (error) + goto err_rh_attach_region; + + spin_lock_init(&ioh->lock); + + drv_printk(KERN_INFO, "ioh at 0x%08lx, mapped to 0x%p, size %uk\n", + ioh->base_phys, ioh->base, ioh->size / 1024); + + return 0; + +err_rh_create: + iounmap(ioh->base); +err_rh_attach_region: + rh_destroy(ioh->rheap); +err: + return error; +} + +static struct starlet_ioh *starlet_ioh; + +/** + * + */ +static struct starlet_ioh *starlet_ioh_get(void) +{ + if (unlikely(!starlet_ioh)) + drv_printk(KERN_ERR, "uninitialized ioh instance!\n"); + return starlet_ioh; +} + +unsigned long starlet_ioh_virt_to_phys(void *ptr) +{ + struct starlet_ioh *ioh = starlet_ioh_get(); + unsigned long offset; + + if (!ioh || !ptr) + return 0; + + offset = ptr - ioh->base; + return ioh->base_phys + offset; +} + +/** + * + */ +void *starlet_ioh_kzalloc_aligned(size_t size, size_t align) +{ + struct starlet_ioh *ioh = starlet_ioh_get(); + unsigned long offset; + void *ptr; + unsigned long flags; + + if (!ioh) + return NULL; + + spin_lock_irqsave(&ioh->lock, flags); + offset = rh_alloc_align(ioh->rheap, size, align, NULL); + spin_unlock_irqrestore(&ioh->lock, flags); + + if (IS_ERR_VALUE(offset)) + return NULL; + + ptr = ioh->base + offset; + memset(ptr, 0, size); + + return ptr; +} + +/** + * + */ +void *starlet_ioh_kzalloc(size_t size) +{ + return starlet_ioh_kzalloc_aligned(size, STARLET_IOH_ALIGN+1); +} + +/** + * + */ +void starlet_ioh_kfree(void *ptr) +{ + struct starlet_ioh *ioh = starlet_ioh_get(); + unsigned long offset; + unsigned long flags; + + if (!ioh || !ptr) + return; + + offset = ptr - ioh->base; + + spin_lock_irqsave(&ioh->lock, flags); + rh_free(ioh->rheap, offset); + spin_unlock_irqrestore(&ioh->lock, flags); +} + +int starlet_ioh_dma_map_sg(struct device *dev, struct starlet_ioh_sg *sgl, + int nents, enum dma_data_direction direction) +{ + struct starlet_ioh_sg *sg; + int i; + + BUG_ON(direction == DMA_NONE); + + starlet_ioh_for_each_sg(sgl, sg, nents, i) { + if (!sg->buf || sg->len == 0) + continue; + __dma_sync(sg->buf, sg->len, direction); + } + return nents; +} + +void starlet_ioh_dma_unmap_sg(struct device *dev, struct starlet_ioh_sg *sgl, + int nents, enum dma_data_direction direction) +{ + /* nothing to do */ +} + +void starlet_ioh_sg_init_table(struct starlet_ioh_sg *sgl, unsigned int nents) +{ + memset(sgl, 0, nents * sizeof(*sgl)); +} + +/** + * + * @buf: must have been allocated using one of the starlet_ioh_* alloc + * functions. + */ +void starlet_ioh_sg_set_buf(struct starlet_ioh_sg *sg, void *buf, size_t len) +{ + struct starlet_ioh *ioh = starlet_ioh_get(); + unsigned long offset; + + if (buf && len) { + offset = buf - ioh->base; + + sg->buf = buf; + sg->len = len; + sg->dma_addr = ioh->base_phys + offset; + } else { + sg->buf = NULL; + sg->len = 0; + sg->dma_addr = 0; + } +} + + +/** + * + */ +int starlet_malloc_lib_bootstrap(struct resource *mem) +{ + struct starlet_ioh *ioh; + int error; + + if (starlet_ioh) { + drv_printk(KERN_WARNING, "already bootstrapped\n"); + return 0; + } + + drv_printk(KERN_INFO, "%s - version %s\n", LIB_DESCRIPTION, + starlet_malloc_lib_version); + + ioh = kzalloc(sizeof(*ioh), GFP_KERNEL); + if (!ioh) { + drv_printk(KERN_ERR, "failed to allocate ioh\n"); + return -ENOMEM; + } + + error = starlet_ioh_init(ioh, mem); + if (error) + kfree(ioh); + else + starlet_ioh = ioh; + + return error; +} + + diff --git a/arch/powerpc/platforms/embedded6xx/starlet-mipc.c b/arch/powerpc/platforms/embedded6xx/starlet-mipc.c new file mode 100644 index 0000000..3ff5a8d --- /dev/null +++ b/arch/powerpc/platforms/embedded6xx/starlet-mipc.c @@ -0,0 +1,1013 @@ +/* + * arch/powerpc/platforms/embedded6xx/starlet-mipc.c + * + * IPC driver for the 'mini' firmware replacement for Starlet + * Copyright (C) 2009 The GameCube Linux Team + * Copyright (C) 2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ +#define DRV_MODULE_NAME "starlet-mipc" +#define pr_fmt(fmt) DRV_MODULE_NAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include /* for mdelay() */ +#include /* for _PAGE_KERNEL_NC */ +#include /* for get_tbl() */ +#include + +#include "hlwd-pic.h" + + +#define DRV_DESCRIPTION "IPC driver for 'mini'" +#define DRV_AUTHOR "Albert Herranz" + +static char mipc_driver_version[] = "0.4i"; + + +/* + * Hardware registers + */ +#define MIPC_TXBUF 0x00 /* data from cpu to starlet */ + +#define MIPC_CSR 0x04 +#define MIPC_CSR_TXSTART (1<<0) /* start transmit */ +#define MIPC_CSR_TBEI (1<<1) /* tx buf empty int */ +#define MIPC_CSR_RBFI (1<<2) /* rx buf full int */ +#define MIPC_CSR_RXRDY (1<<3) /* receiver ready */ +#define MIPC_CSR_RBFIMASK (1<<4) /* rx buf full int mask */ +#define MIPC_CSR_TBEIMASK (1<<5) /* tx buf empty int mask */ + +#define MIPC_RXBUF 0x08 /* data from starlet to cpu */ + + +#define MIPC_MIN_VER 1 +#define MIPC_MAX_VER 1 + +#define MIPC_INITIAL_TAG 1 + +#define MIPC_SYS_IO_TIMEOUT (250*1000) /* usecs */ +#define MIPC_DEV_TIMEOUT (10*1000*1000) /* usecs */ + + +/* + * Firmware request. + * + */ +struct mipc_req { + union { + struct { + u8 flags; + u8 device; + u16 req; + }; + u32 code; + }; + u32 tag; + u32 args[MIPC_REQ_MAX_ARGS]; +} __attribute__ ((packed)); + +/* + * + */ +struct mipc_device { + void __iomem *io_base; + int irq; + + struct device *dev; + + spinlock_t call_lock; /* serialize firmware calls */ + spinlock_t io_lock; /* serialize access to io registers */ + + struct mipc_infohdr *hdr; + + struct mipc_req *in_ring; + size_t in_ring_size; + volatile u16 intail_idx; + + struct mipc_req *out_ring; + size_t out_ring_size; + volatile u16 outhead_idx; + + u32 tag; +}; + +#define __spin_event_timeout(condition, timeout_usecs, result, __end_tbl) \ + for (__end_tbl = get_tbl() + tb_ticks_per_usec * timeout_usecs; \ + !(result = (condition)) && (int)(__end_tbl - get_tbl()) > 0;) + +/* + * Update control and status register. + */ +static inline void mipc_update_csr(void __iomem *io_base, u32 val) +{ + u32 csr; + + csr = in_be32(io_base + MIPC_CSR); + /* preserve interrupt masks */ + csr &= MIPC_CSR_RBFIMASK | MIPC_CSR_TBEIMASK; + csr |= val; + out_be32(io_base + MIPC_CSR, csr); +} + +static u16 mipc_peek_outtail(void __iomem *io_base) +{ + return in_be32(io_base + MIPC_RXBUF) & 0xffff; +} + +static u16 mipc_peek_inhead(void __iomem *io_base) +{ + return in_be32(io_base + MIPC_RXBUF) >> 16; +} + +static u16 mipc_peek_first_intail(void __iomem *io_base) +{ + return in_be32(io_base + MIPC_TXBUF) & 0xffff; +} + +static u16 mipc_peek_first_outhead(void __iomem *io_base) +{ + return in_be32(io_base + MIPC_TXBUF) >> 16; +} + +static void mipc_poke_intail(struct mipc_device *ipc_dev, u16 val) +{ + void __iomem *io_base = ipc_dev->io_base; + unsigned long flags; + + spin_lock_irqsave(&ipc_dev->io_lock, flags); + out_be32(io_base + MIPC_TXBUF, + (in_be32(io_base + MIPC_TXBUF) & 0xffff0000) | val); + spin_unlock_irqrestore(&ipc_dev->io_lock, flags); +} + +static void mipc_poke_outhead(struct mipc_device *ipc_dev, u16 val) +{ + void __iomem *io_base = ipc_dev->io_base; + unsigned long flags; + + spin_lock_irqsave(&ipc_dev->io_lock, flags); + out_be32(io_base + MIPC_TXBUF, + (in_be32(io_base + MIPC_TXBUF) & 0x0000ffff) | val<<16); + spin_unlock_irqrestore(&ipc_dev->io_lock, flags); +} + + + +static u16 mipc_get_next_intail(struct mipc_device *ipc_dev) +{ + return (ipc_dev->intail_idx + 1) & (ipc_dev->in_ring_size - 1); +} + +static u16 mipc_get_next_outhead(struct mipc_device *ipc_dev) +{ + return (ipc_dev->outhead_idx + 1) & (ipc_dev->out_ring_size - 1); +} + +static void mipc_print_req(struct mipc_req *req) +{ + int i; + + pr_info("req %pP = {\n", req); + pr_cont("code = %08X, tag = %08X\n", req->code, req->tag); + for (i = 0; i < MIPC_REQ_MAX_ARGS; i++) + pr_cont("arg[%d] = %08X\n", i, req->args[i]); + pr_cont("}\n"); +} + +#ifdef DEBUG_RINGS +static void mipc_dump_ring(struct mipc_req *req, size_t count) +{ + int i; + + for (i = 0; i < count; i++) + pr_devel("%d: %X (%08X)\n", i, req[i].tag, req[i].code); +} +#endif + +static void mipc_print_status(struct mipc_device *ipc_dev) +{ + size_t in_size, out_size; + + in_size = ipc_dev->in_ring_size * sizeof(*ipc_dev->in_ring); + out_size = ipc_dev->out_ring_size * sizeof(*ipc_dev->out_ring); + + pr_info("ppc: intail_idx=%u, outhead_idx=%u\n", + ipc_dev->intail_idx, ipc_dev->outhead_idx); + pr_cont("arm: inhead_idx=%u, outtail_idx=%u\n", + mipc_peek_inhead(ipc_dev->io_base), + mipc_peek_outtail(ipc_dev->io_base)); + pr_cont("in_ring=%uK@%p, out_ring=%uK@%p\n", + in_size / 1024, ipc_dev->in_ring, + out_size / 1024, ipc_dev->out_ring); +} + +static int mipc_send_req(struct mipc_device *ipc_dev, unsigned long timeout, + struct mipc_req *req) +{ + void __iomem *io_base = ipc_dev->io_base; + struct mipc_req *firm_req; + unsigned long ctx; + int result; + int error = 0; + + if (mipc_peek_inhead(io_base) == mipc_get_next_intail(ipc_dev)) { + pr_err("%s queue full\n", "ppc->arm ipc"); + __spin_event_timeout(mipc_peek_inhead(io_base) != + mipc_get_next_intail(ipc_dev), + timeout, result, ctx) { + /* busy wait */ + cpu_relax(); + } + if (!result) { + pr_err("%s queue drain timed out\n", "ppc->arm ipc"); + error = -EIO; + goto out; + } + } + + firm_req = ipc_dev->in_ring + ipc_dev->intail_idx; + *firm_req = *req; + ipc_dev->intail_idx = mipc_get_next_intail(ipc_dev); + mipc_poke_intail(ipc_dev, ipc_dev->intail_idx); + mipc_update_csr(ipc_dev->io_base, MIPC_CSR_TXSTART); +out: + if (error) + pr_devel("exit %d\n", error); + return error; +} + +static int __mipc_recv_req(struct mipc_device *ipc_dev, unsigned long timeout, + struct mipc_req *req) +{ + void __iomem *io_base = ipc_dev->io_base; + struct mipc_req *firm_req; + unsigned long ctx; + int result; + int error = 0; + + __spin_event_timeout(mipc_peek_outtail(io_base) != ipc_dev->outhead_idx, + timeout, result, ctx) { + /* busy wait */ + cpu_relax(); + } + if (mipc_peek_outtail(io_base) == ipc_dev->outhead_idx) { + error = -EIO; + goto out; + } + firm_req = ipc_dev->out_ring + ipc_dev->outhead_idx; + *req = *firm_req; + ipc_dev->outhead_idx = mipc_get_next_outhead(ipc_dev); + mipc_poke_outhead(ipc_dev, ipc_dev->outhead_idx); +out: + return error; +} + +static int mipc_recv_req(struct mipc_device *ipc_dev, unsigned long timeout, + struct mipc_req *req) +{ + int error; + + error = __mipc_recv_req(ipc_dev, timeout, req); + if (error) + pr_devel("arm->ppc ipc request timed out (%d)\n", error); + return error; +} + +static int mipc_recv_tagged(struct mipc_device *ipc_dev, + unsigned long timeout, + u32 code, u32 tag, + struct mipc_req *req) +{ + unsigned long ctx; + int result; + int error; + + error = mipc_recv_req(ipc_dev, timeout, req); + if (error) + goto out; + + __spin_event_timeout(req->code == code && req->tag == tag, + timeout, result, ctx) { + pr_devel("expected: code=%08X, tag=%08X\n", code, tag); + mipc_print_req(req); + pr_devel("+++ status\n"); + mipc_print_status(ipc_dev); +#ifdef DEBUG_RINGS + pr_devel("+++ in_ring\n"); + mipc_dump_ring(ipc_dev->in_ring, ipc_dev->in_ring_size); + pr_devel("+++ out_ring\n"); + mipc_dump_ring(ipc_dev->out_ring, ipc_dev->out_ring_size); +#endif + + error = mipc_recv_req(ipc_dev, timeout, req); + if (error) + goto out; + } + if (!result) { + pr_err("%s: recv timed out\n", __func__); + error = -EIO; + goto out; + } else + error = 0; + +out: + if (error) + pr_devel("exit %d\n", error); + return error; +} + +static void __mipc_fill_req(struct mipc_req *req, u32 code) +{ + memset(req, 0, sizeof(*req)); + req->code = code; +} + +static int mipc_sendrecv_call(struct mipc_device *ipc_dev, + unsigned long timeout, + struct mipc_req *req, struct mipc_req *resp) +{ + unsigned long flags; + int error; + + spin_lock_irqsave(&ipc_dev->call_lock, flags); + req->tag = ipc_dev->tag++; + error = mipc_send_req(ipc_dev, timeout, req); + if (error) + goto out; + error = mipc_recv_tagged(ipc_dev, timeout, req->code, req->tag, resp); +out: + spin_unlock_irqrestore(&ipc_dev->call_lock, flags); + + return error; +} + +static int mipc_sendrecv1_call(struct mipc_device *ipc_dev, + unsigned long timeout, + struct mipc_req *resp, u32 code, u32 arg) +{ + struct mipc_req req; + + __mipc_fill_req(&req, code); + req.args[0] = arg; + return mipc_sendrecv_call(ipc_dev, timeout, &req, resp); +} + + +static int mipc_send_call(struct mipc_device *ipc_dev, unsigned long timeout, + struct mipc_req *req) +{ + unsigned long flags; + int error; + + spin_lock_irqsave(&ipc_dev->call_lock, flags); + req->tag = ipc_dev->tag++; + error = mipc_send_req(ipc_dev, timeout, req); + spin_unlock_irqrestore(&ipc_dev->call_lock, flags); + + return error; +} + +static int mipc_send2_call(struct mipc_device *ipc_dev, unsigned long timeout, + u32 code, u32 arg1, u32 arg2) +{ + struct mipc_req req; + + __mipc_fill_req(&req, code); + req.args[0] = arg1; + req.args[1] = arg2; + return mipc_send_call(ipc_dev, timeout, &req); +} + +static int mipc_send3_call(struct mipc_device *ipc_dev, unsigned long timeout, + u32 code, u32 arg1, u32 arg2, u32 arg3) +{ + struct mipc_req req; + + __mipc_fill_req(&req, code); + req.args[0] = arg1; + req.args[1] = arg2; + req.args[2] = arg3; + return mipc_send_call(ipc_dev, timeout, &req); +} + +static int mipc_flush_send(struct mipc_device *ipc_dev, unsigned long timeout) +{ + void __iomem *io_base = ipc_dev->io_base; + unsigned long ctx; + int result; + int error = 0; + + __spin_event_timeout(mipc_peek_inhead(io_base) == ipc_dev->intail_idx, + timeout, result, ctx) { + /* busy wait */ + cpu_relax(); + } + if (!result) { + pr_err("%s: flush timed out\n", __func__); + error = -EIO; + goto out; + } +out: + if (error) + pr_devel("exit %d\n", error); + return error; +} + +static void mipc_flush_recv(struct mipc_device *ipc_dev, + unsigned long timeout) +{ + struct mipc_req req; + int error; + + do { + error = __mipc_recv_req(ipc_dev, timeout, &req); + } while (!error); +} + + + +static struct mipc_device *mipc_device_instance; + +struct mipc_device *mipc_get_device(void) +{ + if (!mipc_device_instance) + pr_err("uninitialized device instance!\n"); + return mipc_device_instance; +} + +static int mipc_ping(struct mipc_device *ipc_dev, unsigned long timeout) +{ + struct mipc_req resp; + int error; + + error = mipc_sendrecv1_call(ipc_dev, timeout, &resp, MIPC_SYS_PING, 0); + if (error) + pr_devel("exit %d\n", error); + return error; +} + +#define __declare_ipc_send2_accessor(_name, _suffix, _size, _call) \ +void mipc_##_name##_suffix(_size a, void __iomem *addr) \ +{ \ + struct mipc_device *ipc_dev = mipc_get_device(); \ + int error; \ + \ + error = mipc_send2_call(ipc_dev, MIPC_SYS_IO_TIMEOUT, _call, \ + (u32)addr, a); \ + if (!error) \ + return; \ + \ + pr_devel(__stringify(_name, _suffix) "(%p,%x)\n", addr, a); \ + BUG(); \ +} + +#define __declare_ipc_send3_accessor(_name, _suffix, _size, _call) \ +void mipc_##_name##_suffix(_size a, _size b, void __iomem *addr) \ +{ \ + struct mipc_device *ipc_dev = mipc_get_device(); \ + int error; \ + \ + error = mipc_send3_call(ipc_dev, MIPC_SYS_IO_TIMEOUT, _call, \ + (u32)addr, a, b); \ + if (!error) \ + return; \ + \ + pr_devel(__stringify(_name, _suffix) "(%p,%x,%x)\n", addr, a, b);\ + BUG(); \ +} + +#define __declare_ipc_sendrecv1_accessor(_name, _suffix, _size, _call) \ +_size mipc_##_name##_suffix(void __iomem *addr) \ +{ \ + struct mipc_device *ipc_dev = mipc_get_device(); \ + struct mipc_req resp; \ + int error; \ + \ + error = mipc_sendrecv1_call(ipc_dev, MIPC_SYS_IO_TIMEOUT, \ + &resp, _call, (u32)addr); \ + if (!error) \ + return resp.args[0]; \ + \ + pr_devel(__stringify(_name, _suffix) "(%p)\n", addr); \ + BUG(); \ + return 0; \ +} + +__declare_ipc_sendrecv1_accessor(read, l, unsigned int, MIPC_SYS_READ32) +__declare_ipc_sendrecv1_accessor(read, w, unsigned short, MIPC_SYS_READ16) +__declare_ipc_sendrecv1_accessor(read, b, unsigned char, MIPC_SYS_READ8) + +__declare_ipc_send2_accessor(write, l, unsigned int, MIPC_SYS_WRITE32) +__declare_ipc_send2_accessor(write, w, unsigned short, MIPC_SYS_WRITE16) +__declare_ipc_send2_accessor(write, b, unsigned char, MIPC_SYS_WRITE8) + +__declare_ipc_send2_accessor(setbit, l, unsigned int, MIPC_SYS_SET32) +__declare_ipc_send2_accessor(clearbit, l, unsigned int, MIPC_SYS_CLEAR32) +__declare_ipc_send3_accessor(clrsetbits, l, unsigned int, MIPC_SYS_MASK32) + +void mipc_wmb(void) +{ + struct mipc_device *ipc_dev = mipc_get_device(); + int error; + + error = mipc_ping(ipc_dev, MIPC_SYS_IO_TIMEOUT); + if (!error) + return; + + pr_devel(__stringify(_name, _suffix) "()\n"); + BUG(); +} + +void __iomem *mipc_ioremap(phys_addr_t addr, unsigned long size) +{ + return (void __iomem *)addr; +} + +void mipc_iounmap(volatile void __iomem *addr) +{ + /* nothing to do */ +} + +/* + * + * + */ + +#define BITOP_MASK(nr) (1UL << ((nr) % BITS_PER_LONG)) +#define BITOP_WORD(nr) ((nr) / BITS_PER_LONG) + +void mipc_clear_bit(int nr, volatile unsigned long *addr) +{ + unsigned long mask = BITOP_MASK(nr); + unsigned long *p = ((unsigned long *)addr) + BITOP_WORD(nr); + + mipc_clearbitl(mask, p); +} + +void mipc_set_bit(int nr, volatile unsigned long *addr) +{ + unsigned long mask = BITOP_MASK(nr); + unsigned long *p = ((unsigned long *)addr) + BITOP_WORD(nr); + + mipc_setbitl(mask, p); +} + +void mipc_clrsetbits_be32(const volatile u32 __iomem *addr, u32 clear, u32 set) +{ + mipc_clrsetbitsl(clear, set, (void __iomem *)addr); +} + +u32 mipc_in_be32(const volatile u32 __iomem *addr) +{ + return mipc_readl((void __iomem *)addr); +} + +void mipc_out_be32(const volatile u32 __iomem *addr, u32 val) +{ + mipc_writel(val, (void __iomem *)addr); +} + +u16 mipc_in_be16(const volatile u16 __iomem *addr) +{ + return mipc_readw((void __iomem *)addr); +} + +void mipc_out_be16(const volatile u16 __iomem *addr, u16 val) +{ + mipc_writew(val, (void __iomem *)addr); +} + +u8 mipc_in_8(const volatile u8 __iomem *addr) +{ + return mipc_readb((void __iomem *)addr); +} + +void mipc_out_8(const volatile u8 __iomem *addr, u8 val) +{ + mipc_writeb(val, (void __iomem *)addr); +} + + + + +static int mipc_check_address(phys_addr_t pa) +{ + if (pa < 0x10000000 || pa > 0x14000000) + return -EINVAL; + return 0; +} + +int mipc_discover(struct mipc_infohdr **hdrp) +{ + struct mipc_infohdr *hdr; + char magic[4]; + phys_addr_t *p; + int error; + + /* REVISIT, infohdr pointer should come from dts */ + + /* grab mini information header location */ + p = ioremap(0x13fffffc, 4); + if (!p) { + pr_err("unable to ioremap mini ipc header ptr\n"); + error = -ENOMEM; + goto out; + } + /* check that the header pointer points to MEM2 */ + if (mipc_check_address(*p)) { + pr_devel("wrong mini ipc header address %pP\n", (void *)*p); + error = -ENODEV; + goto out_unmap_p; + } + + hdr = (struct mipc_infohdr *)ioremap_prot(*p, sizeof(*hdr), + PAGE_KERNEL); + if (!hdr) { + pr_err("unable to ioremap mini ipc header\n"); + error = -ENOMEM; + goto out_unmap_p; + } + __dma_sync(hdr, sizeof(*hdr), DMA_FROM_DEVICE); + + memcpy(magic, hdr->magic, 3); + magic[3] = 0; + if (memcmp(magic, "IPC", 3)) { + pr_devel("wrong magic \"%s\"\n", magic); + error = -ENODEV; + goto out_unmap_hdr; + } + if (hdr->version < MIPC_MIN_VER && hdr->version > MIPC_MAX_VER) { + pr_err("unsupported mini ipc version %d" + " (min %d, max %d)\n", hdr->version, + MIPC_MIN_VER, MIPC_MAX_VER); + error = -ENODEV; + goto out_unmap_hdr; + } + if (mipc_check_address(hdr->mem2_boundary)) { + pr_err("invalid mem2_boundary %pP\n", + (void *)hdr->mem2_boundary); + error = -EINVAL; + goto out_unmap_hdr; + } + if (mipc_check_address(hdr->ipc_in)) { + pr_err("invalid ipc_in %pP\n", (void *)hdr->ipc_in); + error = -EINVAL; + goto out_unmap_hdr; + } + if (mipc_check_address(hdr->ipc_out)) { + pr_err("invalid ipc_out %pP\n", (void *)hdr->ipc_out); + error = -EINVAL; + goto out_unmap_hdr; + } + + *hdrp = hdr; + error = 0; + goto out_unmap_p; + +out_unmap_hdr: + iounmap(hdr); +out_unmap_p: + iounmap(p); +out: + return error; +} + +static void mipc_print_infohdr(struct mipc_infohdr *hdr) +{ + pr_info("magic=%c%c%c, version=%d, mem2_boundary=%pP\n", + hdr->magic[0], hdr->magic[1], hdr->magic[2], + hdr->version, + (void *)hdr->mem2_boundary); + pr_cont("ipc_in[%u] @ %pP, ipc_out[%u] @ %pP\n", + hdr->ipc_in_size, (void *)hdr->ipc_in, + hdr->ipc_out_size, (void *)hdr->ipc_out); +} + +static int mipc_do_simple_tests = 0; + +#ifndef MODULE +static int __init mipc_simple_tests_setup(char *str) +{ + if (*str) + return 0; + mipc_do_simple_tests = 1; + return 1; +} +__setup("mipc_simple_tests", mipc_simple_tests_setup); +#endif + +static unsigned long tbl_to_ns(unsigned long tbl) +{ + return (tbl * 1000) / tb_ticks_per_usec; +} + +static void mipc_simple_tests(struct mipc_device *ipc_dev) +{ + void __iomem *io_base = ipc_dev->io_base; + void *gpio; + unsigned long t0; + unsigned long t_read, t_write; + unsigned long t_mipc_read, t_mipc_write, t_mipc_ping; + u32 val; + int i; + + gpio = mipc_ioremap(0x0d8000c0, 4); + if (!gpio) { + pr_err("ioremap failed\n"); + return; + } + + for (i = 0; i < 64000; i++) { + t0 = get_tbl(); + in_be32(io_base + MIPC_CSR); + t_read = get_tbl() - t0; + + t0 = get_tbl(); + out_be32(io_base + MIPC_CSR, 0); + t_write = get_tbl() - t0; + + t0 = get_tbl(); + val = mipc_readl(gpio); + t_mipc_read = get_tbl() - t0; + + t0 = get_tbl(); + mipc_writel(val & ~0x20, gpio); + t_mipc_write = get_tbl() - t0; + + t0 = get_tbl(); + mipc_ping(ipc_dev, MIPC_SYS_IO_TIMEOUT); + t_mipc_ping = get_tbl() - t0; + } + + pr_info("io timings in timebase ticks" + " (1 usec = %lu ticks)\n", tb_ticks_per_usec); + pr_cont("mmio: read=%lu (%lu ns), write=%lu (%lu ns)\n", + t_read, tbl_to_ns(t_read), t_write, tbl_to_ns(t_write)); + pr_cont("mipc: read=%lu (%lu ns), write=%lu (%lu ns)\n", + t_mipc_read, tbl_to_ns(t_mipc_read), + t_mipc_write, tbl_to_ns(t_mipc_write)); + pr_cont("mipc: ping=%lu (%lu ns)\n", + t_mipc_ping, tbl_to_ns(t_mipc_ping)); + + mipc_iounmap(gpio); +} + +static void mipc_shutdown_mini_devs(struct mipc_device *ipc_dev) +{ + struct mipc_req resp; + int error; + + error = mipc_sendrecv1_call(ipc_dev, MIPC_DEV_TIMEOUT, &resp, + _MIPC(_MIPC_SLOW, _MIPC_DEV_SDHC, + _MIPC_SDHC_EXIT), 0); + if (error) + pr_err("unable to shutdown mini SDHC subsystem\n"); +} + +static void mipc_starlet_fixups(struct mipc_device *ipc_dev) +{ + void __iomem *gpio; + + /* + * Try to turn off the front led and sensor bar. + * (not strictly starlet-only stuff but anyway...) + */ + gpio = mipc_ioremap(0x0d8000c0, 4); + if (gpio) { + mipc_clearbitl(0x120, gpio); + mipc_iounmap(gpio); + } + + /* tell 'mini' to relinquish control of hardware */ + mipc_shutdown_mini_devs(ipc_dev); +} + +static void mipc_init_ahbprot(struct mipc_device *ipc_dev) +{ + void __iomem *hw_ahbprot = (void __iomem *)0x0d800064; + u32 initial_ahbprot, ahbprot; + + initial_ahbprot = mipc_readl(hw_ahbprot); + if (initial_ahbprot != 0xffffffff) { + pr_debug("AHBPROT=%08X (before)\n", initial_ahbprot); + mipc_writel(0xffffffff, hw_ahbprot); + } + + ahbprot = mipc_readl(hw_ahbprot); + if (initial_ahbprot != ahbprot) + pr_debug("AHBPROT=%08X (after)\n", ahbprot); + if (ahbprot != 0xffffffff) + pr_err("failed to set AHBPROT\n"); +} + +static int mipc_init(struct mipc_device *ipc_dev, struct resource *mem, int irq) +{ + struct mipc_infohdr *hdr; + void __iomem *io_base; + size_t io_size, in_size, out_size; + int error; + + error = mipc_discover(&hdr); + if (error) { + pr_err("unable to find mini ipc instance\n"); + goto out; + } + + spin_lock_init(&ipc_dev->call_lock); + spin_lock_init(&ipc_dev->io_lock); + + io_size = mem[0].end - mem[0].start + 1; + io_base = ipc_dev->io_base = ioremap(mem[0].start, io_size); + ipc_dev->irq = irq; + + ipc_dev->hdr = hdr; + + mipc_print_infohdr(hdr); + + in_size = hdr->ipc_in_size * sizeof(*ipc_dev->in_ring); + ipc_dev->in_ring = ioremap(hdr->ipc_in, in_size); + ipc_dev->in_ring_size = hdr->ipc_in_size; + ipc_dev->intail_idx = mipc_peek_first_intail(io_base); + + out_size = hdr->ipc_out_size * sizeof(*ipc_dev->out_ring); + ipc_dev->out_ring = ioremap(hdr->ipc_out, out_size); + ipc_dev->out_ring_size = hdr->ipc_out_size; + ipc_dev->outhead_idx = mipc_peek_first_outhead(io_base); + + ipc_dev->tag = MIPC_INITIAL_TAG; + mipc_device_instance = ipc_dev; + + mipc_print_status(ipc_dev); + + mipc_flush_send(ipc_dev, 5*1000); + mipc_flush_recv(ipc_dev, 5*1000); + error = mipc_ping(ipc_dev, 1*1000*1000); + if (error) + goto out; + + pr_info("ping OK\n"); + if (mipc_do_simple_tests) + mipc_simple_tests(ipc_dev); + + mipc_init_ahbprot(ipc_dev); + mipc_starlet_fixups(ipc_dev); + +out: + return error; +} + +static void mipc_exit(struct mipc_device *ipc_dev) +{ + if (ipc_dev->in_ring) + iounmap(ipc_dev->in_ring); + if (ipc_dev->out_ring) + iounmap(ipc_dev->out_ring); +} + + +/* + * Driver model helper routines. + * + */ + +static int mipc_do_probe(struct device *dev, struct resource *mem, int irq) +{ + struct mipc_device *ipc_dev; + int error; + + ipc_dev = kzalloc(sizeof(*ipc_dev), GFP_KERNEL); + if (!ipc_dev) { + pr_err("failed to allocate ipc_dev\n"); + error = -ENOMEM; + goto out; + } + dev_set_drvdata(dev, ipc_dev); + ipc_dev->dev = dev; + + error = mipc_init(ipc_dev, mem, irq); + if (error) { + dev_set_drvdata(dev, NULL); + kfree(ipc_dev); + goto out; + } + + pr_info("ready\n"); + hlwd_pic_probe(); + +out: + return error; +} + +static int mipc_do_remove(struct device *dev) +{ + struct mipc_device *ipc_dev = dev_get_drvdata(dev); + int error = 0; + + if (!ipc_dev) { + error = -ENODEV; + goto out; + } + + mipc_exit(ipc_dev); + dev_set_drvdata(dev, NULL); + kfree(ipc_dev); +out: + return error; +} + +static int mipc_do_shutdown(struct device *dev) +{ + struct mipc_device *ipc_dev = dev_get_drvdata(dev); + int error = 0; + + if (!ipc_dev) { + error = -ENODEV; + goto out; + } +out: + return error; +} + +/* + * OF platform driver hooks. + * + */ + +static int mipc_of_probe(struct of_device *odev, + const struct of_device_id *dev_id) +{ + struct resource mem[2]; + int error; + + error = of_address_to_resource(odev->node, 0, &mem[0]); + if (error) { + pr_err("no io memory range found (%d)\n", error); + goto out; + } + + error = mipc_do_probe(&odev->dev, mem, + irq_of_parse_and_map(odev->node, 0)); +out: + return error; +} + +static int mipc_of_remove(struct of_device *odev) +{ + return mipc_do_remove(&odev->dev); +} + +static int mipc_of_shutdown(struct of_device *odev) +{ + return mipc_do_shutdown(&odev->dev); +} + +static struct of_device_id mipc_of_match[] = { + { .compatible = "twiizers,starlet-mini-ipc" }, + { }, +}; + +MODULE_DEVICE_TABLE(of, mipc_of_match); + +static struct of_platform_driver mipc_of_driver = { + .owner = THIS_MODULE, + .name = DRV_MODULE_NAME, + .match_table = mipc_of_match, + .probe = mipc_of_probe, + .remove = mipc_of_remove, + .shutdown = mipc_of_shutdown, +}; + +/* + * Kernel module interface hooks. + * + */ + +static int __init mipc_init_module(void) +{ + pr_info("%s - version %s\n", DRV_DESCRIPTION, mipc_driver_version); + + return of_register_platform_driver(&mipc_of_driver); +} + +static void __exit mipc_exit_module(void) +{ + of_unregister_platform_driver(&mipc_of_driver); +} + +module_init(mipc_init_module); +module_exit(mipc_exit_module); + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_AUTHOR(DRV_AUTHOR); +MODULE_LICENSE("GPL"); + diff --git a/arch/powerpc/platforms/embedded6xx/starlet-stm.c b/arch/powerpc/platforms/embedded6xx/starlet-stm.c new file mode 100644 index 0000000..b79df58 --- /dev/null +++ b/arch/powerpc/platforms/embedded6xx/starlet-stm.c @@ -0,0 +1,106 @@ +/* + * arch/powerpc/platforms/embedded6xx/starlet-stm.c + * + * Nintendo Wii starlet STM routines + * Copyright (C) 2008-2009 The GameCube Linux Team + * Copyright (C) 2008,2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#define DBG(fmt, arg...) drv_printk(KERN_INFO, fmt, ##arg) + +#include +#include +#include + + +/* + * /dev/stm/immediate + * + */ + +#define STARLET_STM_HOTRESET 0x2001 +#define STARLET_STM_SHUTDOWN 0x2003 + +#define STARLET_STM_TIMEOUT 1000000 /* usecs */ + +#define STARLET_DEV_STM_IMMEDIATE "/dev/stm/immediate" + +#define drv_printk(level, format, arg...) \ + printk(level "starlet-stm: " format , ## arg) + + +static const char dev_stm_immediate[] = STARLET_DEV_STM_IMMEDIATE; + +/* private aligned buffer for restart/power_off operations */ +static u32 starlet_stm_buf[(STARLET_IPC_DMA_ALIGN+1)/sizeof(u32)] + __attribute__ ((aligned(STARLET_IPC_DMA_ALIGN+1))); + +/* + * + */ +static void starlet_stm_common_restart(int request, u32 value) +{ + static int already_in_restart; + u32 *buf = starlet_stm_buf; + size_t len = sizeof(starlet_stm_buf); + int fd; + int error; + + if (already_in_restart) { + drv_printk(KERN_ERR, "previous restart attempt failed," + " halting\n"); + goto halt; + } + + drv_printk(KERN_INFO, "trying IPC restart...\n"); + already_in_restart = 1; + + fd = starlet_open_polled(dev_stm_immediate, 0, STARLET_STM_TIMEOUT); + if (fd < 0) { + drv_printk(KERN_ERR, "failed to open %s\n", dev_stm_immediate); + error = fd; + goto done; + } + + *buf = value; + error = starlet_ioctl_polled(fd, request, buf, len, buf, len, + STARLET_STM_TIMEOUT); + if (error < 0) { + drv_printk(KERN_ERR, "ioctl %d failed\n", request); + starlet_close_polled(fd, STARLET_STM_TIMEOUT); + } else { +halt: + for (;;) + cpu_relax(); + } + +done: + if (error < 0) + DBG("%s: error=%d (%x)\n", __func__, error, error); +} + +/* + * + */ +void starlet_stm_restart(void) +{ + starlet_stm_common_restart(STARLET_STM_HOTRESET, 0); +} +/*EXPORT_SYMBOL_GPL(starlet_stm_restart);*/ + +/* + * + */ +void starlet_stm_power_off(void) +{ + starlet_stm_common_restart(STARLET_STM_SHUTDOWN, 0); +} +/*EXPORT_SYMBOL_GPL(starlet_stm_power_off);*/ + + diff --git a/arch/powerpc/platforms/embedded6xx/usbgecko_udbg.c b/arch/powerpc/platforms/embedded6xx/usbgecko_udbg.c new file mode 100644 index 0000000..872ed5a --- /dev/null +++ b/arch/powerpc/platforms/embedded6xx/usbgecko_udbg.c @@ -0,0 +1,318 @@ +/* + * arch/powerpc/platforms/embedded6xx/usbgecko_udbg.c + * + * udbg serial input/output routines for the USB Gecko adapter. + * Copyright (C) 2008-2009 The GameCube Linux Team + * Copyright (C) 2008,2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#include +#include +#include + +#include + +#include "usbgecko_udbg.h" + + +#define EXI_CLK_32MHZ 5 + +#define EXI_CSR 0x00 +#define EXI_CSR_CLKMASK (0x7<<4) +#define EXI_CSR_CLK_32MHZ (EXI_CLK_32MHZ<<4) +#define EXI_CSR_CSMASK (0x7<<7) +#define EXI_CSR_CS_0 (0x1<<7) /* Chip Select 001 */ + +#define EXI_CR 0x0c +#define EXI_CR_TSTART (1<<0) +#define EXI_CR_WRITE (1<<2) +#define EXI_CR_READ_WRITE (2<<2) +#define EXI_CR_TLEN(len) (((len)-1)<<4) + +#define EXI_DATA 0x10 + +#define UG_READ_ATTEMPTS 100 +#define UG_WRITE_ATTEMPTS 100 + + +static void __iomem *ug_io_base; + +/* + * Performs one input/output transaction between the spi host and the usbgecko. + */ +static u32 ug_io_transaction(u32 in) +{ + u32 __iomem *csr_reg = ug_io_base + EXI_CSR; + u32 __iomem *data_reg = ug_io_base + EXI_DATA; + u32 __iomem *cr_reg = ug_io_base + EXI_CR; + u32 csr, data, cr; + + /* select */ + csr = EXI_CSR_CLK_32MHZ | EXI_CSR_CS_0; + out_be32(csr_reg, csr); + + /* read/write */ + data = in; + out_be32(data_reg, data); + cr = EXI_CR_TLEN(2) | EXI_CR_READ_WRITE | EXI_CR_TSTART; + out_be32(cr_reg, cr); + + while (in_be32(cr_reg) & EXI_CR_TSTART) + barrier(); + + /* deselect */ + out_be32(csr_reg, 0); + + /* result */ + data = in_be32(data_reg); + + return data; +} + +/* + * Returns true if an usbgecko adapter is found. + */ +static int ug_is_adapter_present(void) +{ + if (!ug_io_base) + return 0; + + return ug_io_transaction(0x90000000) == 0x04700000; +} + +/* + * Returns true if the TX fifo is ready for transmission. + */ +static int ug_is_txfifo_ready(void) +{ + return ug_io_transaction(0xc0000000) & 0x04000000; +} + +/* + * Tries to transmit a character. + * If the TX fifo is not ready the result is undefined. + */ +static void ug_raw_putc(char ch) +{ + ug_io_transaction(0xb0000000 | (ch << 20)); +} + +/* + * Transmits a character. + * It silently fails if the TX fifo is not ready after a number of retries. + */ +static void ug_putc(char ch) +{ + int count = UG_WRITE_ATTEMPTS; + + if (!ug_io_base) + return; + + if (ch == '\n') + ug_putc('\r'); + + while (!ug_is_txfifo_ready() && count--) + barrier(); + if (count) + ug_raw_putc(ch); +} + +#if 0 +/* + * Trasmits a null terminated character string. + */ +static void ug_puts(char *s) +{ + while (*s) + ug_putc(*s++); +} +#endif + +/* + * Returns true if the RX fifo is ready for transmission. + */ +static int ug_is_rxfifo_ready(void) +{ + return ug_io_transaction(0xd0000000) & 0x04000000; +} + +/* + * Tries to receive a character. + * If a character is unavailable the function returns -1. + */ +static int ug_raw_getc(void) +{ + u32 data = ug_io_transaction(0xa0000000); + if (data & 0x08000000) + return (data >> 16) & 0xff; + else + return -1; +} + +/* + * Receives a character. + * It fails if the RX fifo is not ready after a number of retries. + */ +static int ug_getc(void) +{ + int count = UG_READ_ATTEMPTS; + + if (!ug_io_base) + return -1; + + while (!ug_is_rxfifo_ready() && count--) + barrier(); + return ug_raw_getc(); +} + +/* + * udbg functions. + * + */ + +/* + * Transmits a character. + */ +void ug_udbg_putc(char ch) +{ + ug_putc(ch); +} + +/* + * Receives a character. Waits until a character is available. + */ +static int ug_udbg_getc(void) +{ + int ch; + + while ((ch = ug_getc()) == -1) + barrier(); + return ch; +} + +/* + * Receives a character. If a character is not available, returns -1. + */ +static int ug_udbg_getc_poll(void) +{ + if (!ug_is_rxfifo_ready()) + return -1; + return ug_getc(); +} + +/* + * Retrieves and prepares the virtual address needed to access the hardware. + */ +static void __iomem *ug_udbg_setup_io_base(struct device_node *np) +{ + phys_addr_t paddr; + const unsigned int *reg; + + reg = of_get_property(np, "reg", NULL); + if (reg) { + paddr = of_translate_address(np, reg); + if (paddr) { + ug_io_base = ioremap(paddr, reg[1]); + return ug_io_base; + } + } + return NULL; +} + +/* + * USB Gecko udbg support initialization. + */ +void __init ug_udbg_init(void) +{ + struct device_node *np = NULL; + struct device_node *stdout; + const char *path; + + if (ug_io_base) + udbg_printf("%s: early -> final\n", __func__); + + if (!of_chosen) { + udbg_printf("%s: missing of_chosen\n", __func__); + goto done; + } + + path = of_get_property(of_chosen, "linux,stdout-path", NULL); + if (!path) { + udbg_printf("%s: missing %s property", __func__, + "linux,stdout-path"); + goto done; + } + + stdout = of_find_node_by_path(path); + if (!stdout) { + udbg_printf("%s: missing path %s", __func__, path); + goto done; + } + + for (np = NULL; + (np = of_find_compatible_node(np, NULL, "usbgecko,usbgecko"));) + if (np == stdout) + break; + + of_node_put(stdout); + if (!np) { + udbg_printf("%s: stdout is not an usbgecko", __func__); + goto done; + } + + if (!ug_udbg_setup_io_base(np)) { + udbg_printf("%s: failed to setup io base", __func__); + goto done; + } + + if (!ug_is_adapter_present()) { + udbg_printf("usbgecko_udbg: not found\n"); + ug_io_base = NULL; + } else { + udbg_putc = ug_udbg_putc; + udbg_getc = ug_udbg_getc; + udbg_getc_poll = ug_udbg_getc_poll; + udbg_printf("usbgecko_udbg: ready\n"); + } + +done: + if (np) + of_node_put(np); + return; +} + +#ifdef CONFIG_PPC_EARLY_DEBUG_USBGECKO + +/* + * USB Gecko early debug support initialization for udbg. + * + */ +void __init udbg_init_usbgecko(void) +{ + unsigned long vaddr, paddr; + +#if defined(CONFIG_GAMECUBE) + paddr = 0x0c000000; +#elif defined(CONFIG_WII) + paddr = 0x0d000000; +#else +#error Invalid platform for USB Gecko based early debugging. +#endif + + vaddr = 0xc0000000 | paddr; + setbat(1, vaddr, paddr, 128*1024, PAGE_KERNEL_NCG); + + ug_io_base = (void __iomem *)(vaddr | 0x6814); + + udbg_putc = ug_udbg_putc; + udbg_getc = ug_udbg_getc; + udbg_getc_poll = ug_udbg_getc_poll; +} + +#endif /* CONFIG_PPC_EARLY_DEBUG_USBGECKO */ diff --git a/arch/powerpc/platforms/embedded6xx/usbgecko_udbg.h b/arch/powerpc/platforms/embedded6xx/usbgecko_udbg.h new file mode 100644 index 0000000..98034ee --- /dev/null +++ b/arch/powerpc/platforms/embedded6xx/usbgecko_udbg.h @@ -0,0 +1,36 @@ +/* + * arch/powerpc/platforms/embedded6xx/usbgecko_udbg.h + * + * udbg serial input/output routines for the USB Gecko adapter. + * Copyright (C) 2008-2009 The GameCube Linux Team + * Copyright (C) 2008,2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#ifndef __USBGECKO_UDBG_H +#define __USBGECKO_UDBG_H + +#ifdef CONFIG_USBGECKO_UDBG + +extern void __init ug_udbg_init(void); + +#else + +static inline void __init ug_udbg_init(void) +{ +} + +#endif /* CONFIG_USBGECKO_UDBG */ + +#ifdef CONFIG_PPC_EARLY_DEBUG_USBGECKO + +void __init udbg_init_usbgecko(void); + +#endif /* CONFIG_PPC_EARLY_DEBUG_USBGECKO */ + +#endif /* __USBGECKO_UDBG_H */ diff --git a/arch/powerpc/platforms/embedded6xx/wii.c b/arch/powerpc/platforms/embedded6xx/wii.c new file mode 100644 index 0000000..45275bd --- /dev/null +++ b/arch/powerpc/platforms/embedded6xx/wii.c @@ -0,0 +1,299 @@ +/* + * arch/powerpc/platforms/embedded6xx/wii.c + * + * Nintendo Wii board-specific support + * Copyright (C) 2008-2009 The GameCube Linux Team + * Copyright (C) 2008,2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "flipper-pic.h" +#include "gcnvi_udbg.h" +#include "usbgecko_udbg.h" + + +static enum starlet_ipc_flavour starlet_ipc_flavour; + +static void wii_death_loop(void) +{ + local_irq_disable(); + for (;;) + cpu_relax(); +} + +#ifdef CONFIG_STARLET_IOS + +static void wii_ios_restart(char *cmd) +{ + local_irq_disable(); + + /* try first to launch The Homebrew Channel... */ + starlet_es_reload_ios_and_launch(STARLET_TITLE_HBC_JODI); + starlet_es_reload_ios_and_launch(STARLET_TITLE_HBC_HAXX); + /* ..and if that fails, try an assisted restart */ + starlet_stm_restart(); + + /* fallback to spinning until the power button pressed */ + for (;;) + cpu_relax(); +} + +static void wii_ios_power_off(void) +{ + local_irq_disable(); + + /* try an assisted poweroff */ + starlet_stm_power_off(); + + /* fallback to spinning until the power button pressed */ + for (;;) + cpu_relax(); +} + +#else + +static void wii_ios_restart(char *cmd) +{ + wii_death_loop(); +} + +static void wii_ios_power_off(void) +{ + wii_death_loop(); +} + +#endif /* CONFIG_STARLET_IOS */ + +#ifdef CONFIG_STARLET_MINI + +static void wii_restart(char *cmd) +{ + void __iomem *hw_resets; + + local_irq_disable(); + + hw_resets = ioremap(0x0d800194, 4); + if (hw_resets) { + /* clear the system reset pin to cause a reset */ + clear_bit(0, hw_resets); + iounmap(hw_resets); + } + + for (;;) + cpu_relax(); +} + +static void wii_power_off(void) +{ + void __iomem *gpio; + + local_irq_disable(); + + gpio = ioremap(0x0d8000e0, 3*4); + if (gpio) { + /* make sure that the poweroff GPIO is configured as output */ + out_be32(gpio + 4, in_be32(gpio + 4) | 0x2); + + /* drive the poweroff GPIO high */ + out_be32(gpio + 0, in_be32(gpio + 0) | 0x2); + iounmap(gpio); + } + + for (;;) + cpu_relax(); +} + +#else + +static void wii_restart(char *cmd) +{ + wii_death_loop(); +} + +static void wii_power_off(void) +{ + wii_death_loop(); +} + +#endif /* CONFIG_STARLET_MINI */ + +static void wii_halt(void) +{ + if (ppc_md.restart) + ppc_md.restart(NULL); + wii_death_loop(); +} + +static void wii_show_cpuinfo(struct seq_file *m) +{ + seq_printf(m, "vendor\t\t: IBM\n"); + seq_printf(m, "machine\t\t: Nintendo Wii\n"); +} + +int starlet_discover_ipc_flavour(void) +{ + struct mipc_infohdr *hdrp; + int error; + + error = mipc_discover(&hdrp); + if (!error) { + starlet_ipc_flavour = STARLET_IPC_MINI; + ppc_md.restart = wii_restart; + ppc_md.power_off = wii_power_off; + } else { + starlet_ipc_flavour = STARLET_IPC_IOS; + ppc_md.restart = wii_ios_restart; + ppc_md.power_off = wii_ios_power_off; + } + + return 0; +} + +enum starlet_ipc_flavour starlet_get_ipc_flavour(void) +{ + return starlet_ipc_flavour; +} + +static void __init wii_setup_arch(void) +{ + ug_udbg_init(); + gcnvi_udbg_init(); + + starlet_discover_ipc_flavour(); +} + +static void __init wii_init_early(void) +{ +} + +static int __init wii_probe(void) +{ + unsigned long dt_root; + + dt_root = of_get_flat_dt_root(); + if (!of_flat_dt_is_compatible(dt_root, "nintendo,wii")) + return 0; + + return 1; +} + +#ifdef CONFIG_KEXEC +static void wii_shutdown(void) +{ + exi_quiesce(); + flipper_quiesce(); +} + +static int restore_lowmem_stub(struct kimage *image) +{ + struct device_node *node; + struct resource res; + const unsigned long *prop; + unsigned long dst, src; + size_t size; + int error; + + node = of_find_node_by_name(NULL, "lowmem-stub"); + if (!node) { + printk(KERN_ERR "unable to find node %s\n", "lowmem-stub"); + error = -ENODEV; + goto out; + } + + error = of_address_to_resource(node, 0, &res); + if (error) { + printk(KERN_ERR "no lowmem-stub range found\n"); + goto out_put; + } + dst = res.start; + size = res.end - res.start + 1; + + prop = of_get_property(node, "save-area", NULL); + if (!prop) { + printk(KERN_ERR "unable to find %s property\n", "save-area"); + error = -EINVAL; + goto out_put; + } + src = *prop; + + printk(KERN_DEBUG "lowmem-stub: preparing restore from %08lX to %08lX" + " (%u bytes)\n", src, dst, size); + + /* schedule a copy of the lowmem stub to its original location */ + error = kimage_add_preserved_region(image, dst, src, PAGE_ALIGN(size)); + +out_put: + of_node_put(node); +out: + return error; +} + +static int wii_machine_kexec_prepare(struct kimage *image) +{ + int error; + + error = restore_lowmem_stub(image); + if (error) + printk(KERN_ERR "%s: error %d\n", __func__, error); + return error; +} + +static void wii_machine_kexec(struct kimage *image) +{ + local_irq_disable(); + +#ifdef CONFIG_STARLET_IOS + /* + * Reload IOS to make sure that I/O resources are freed before + * the final kexec phase. + */ + if (starlet_get_ipc_flavour() == STARLET_IPC_IOS) + starlet_es_reload_ios_and_discard(); +#endif + + default_machine_kexec(image); +} + +#endif /* CONFIG_KEXEC */ + + +define_machine(wii) { + .name = "wii", + .probe = wii_probe, + .setup_arch = wii_setup_arch, + .init_early = wii_init_early, + .show_cpuinfo = wii_show_cpuinfo, + .halt = wii_halt, + .init_IRQ = flipper_pic_probe, + .get_irq = flipper_pic_get_irq, + .calibrate_decr = generic_calibrate_decr, + .progress = udbg_progress, +#ifdef CONFIG_KEXEC + .machine_shutdown = wii_shutdown, + .machine_kexec_prepare = wii_machine_kexec_prepare, + .machine_kexec = wii_machine_kexec, +#endif +}; + diff --git a/arch/powerpc/platforms/embedded6xx/wii_dev.c b/arch/powerpc/platforms/embedded6xx/wii_dev.c new file mode 100644 index 0000000..c16f774 --- /dev/null +++ b/arch/powerpc/platforms/embedded6xx/wii_dev.c @@ -0,0 +1,50 @@ +/* + * arch/powerpc/platforms/embedded6xx/wii_dev.c + * + * Nintendo Wii platform device setup. + * Copyright (C) 2008-2009 The GameCube Linux Team + * Copyright (C) 2008,2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#include +#include +#include + +#include + +static struct of_device_id wii_of_bus[] = { + { .compatible = "nintendo,hollywood", }, +#ifdef CONFIG_STARLET_IOS + { .compatible = "nintendo,starlet-ios-ipc", }, +#endif +#ifdef CONFIG_STARLET_MINI + { .compatible = "twiizers,starlet-mini-ipc", }, +#endif + { }, +}; + +static int __init wii_device_probe(void) +{ + struct device_node *np; + + if (!machine_is(wii)) + return 0; + + of_platform_bus_probe(NULL, wii_of_bus, NULL); + + np = of_find_compatible_node(NULL, NULL, "nintendo,hollywood-mem2"); + if (np) { + of_platform_device_create(np, NULL, NULL); + of_node_put(np); + } + + return 0; +} +device_initcall(wii_device_probe); + diff --git a/drivers/Kconfig b/drivers/Kconfig index 48bbdbe..6087a67 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -50,6 +50,8 @@ source "drivers/char/Kconfig" source "drivers/i2c/Kconfig" +source "drivers/exi/Kconfig" + source "drivers/spi/Kconfig" source "drivers/pps/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index 8b0b948..8b6073b 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -105,6 +105,8 @@ obj-$(CONFIG_DMA_ENGINE) += dma/ obj-$(CONFIG_DCA) += dca/ obj-$(CONFIG_HID) += hid/ obj-$(CONFIG_PPC_PS3) += ps3/ +obj-$(CONFIG_GAMECUBE_EXI) += exi/ +obj-$(CONFIG_GAMECUBE_SI) += input/si/ obj-$(CONFIG_OF) += of/ obj-$(CONFIG_SSB) += ssb/ obj-$(CONFIG_VLYNQ) += vlynq/ diff --git a/drivers/block/Kconfig b/drivers/block/Kconfig index 1d886e0..d5150d5 100644 --- a/drivers/block/Kconfig +++ b/drivers/block/Kconfig @@ -63,6 +63,82 @@ config AMIGA_Z2RAM To compile this driver as a module, choose M here: the module will be called z2ram. +config GAMECUBE_SD + tristate "Nintendo GameCube/Wii MMC/SD card" + depends on GAMECUBE_EXI + help + This enables support for using SD and MMC cards through + the Nintendo SD Card Adapter (DOL-019) or compatible hardware. + + You probably want to compile FAT support, and the required + codepages, or mount will complain. See Filesystems -> DOS/FAT/NT + filesystems and Filesystems -> Native Language Support + + Say Y if you want to include this driver in the kernel. + + To compile this driver as a module, choose M here: the + module will be called gcn-sd. + +config GAMECUBE_ARAM + tristate "Nintendo GameCube Auxiliary RAM (ARAM)" + depends on GAMECUBE + help + This enables support for using the 16MB of ARAM found in the + Nintendo GameCube as a block device. + Say Y if you want to include this driver in the kernel. + + To compile this driver as a module, choose M here: the + module will be called gcn-aram. + +config GAMECUBE_DI + tristate "Nintendo GameCube Disk Interface (DI)" + depends on GAMECUBE + help + This enables support for using the DVD drive unit found + in the Nintendo GameCube. + Say Y if you want to include this driver in the kernel. + + To compile this driver as a module, choose M here: the + module will be called gcn-di. + +config WII_MEM2 + tristate "Nintendo Wii MEM2" + depends on WII + help + This enables support for using the MEM2 found in the + Nintendo Wii as a block device. + Say Y if you want to include this driver in the kernel. + + To compile this driver as a module, choose M here: the + module will be called rvl-mem2. + +config WII_SD + tristate "Nintendo Wii front slot MMC/SD" + depends on STARLET_IOS + help + This enables support for MMC/SD cards using the front SD card + slot of the Nintendo Wii. + + You probably want to compile FAT support, and the required + codepages, or mount will complain. See Filesystems -> DOS/FAT/NT + filesystems and Filesystems -> Native Language Support + + Say Y if you want to include this driver in the kernel. + + To compile this driver as a module, choose M here: the + module will be called rvl-stsd. + +config WII_DI + tristate "Nintendo Wii Disk Interface (DI)" + depends on STARLET_MINI + help + This enables support for using the DVD drive unit found + in the Nintendo Wii. + Say Y if you want to include this driver in the kernel. + + To compile this driver as a module, choose M here: the + module will be called rvl-di. + config BLK_DEV_XD tristate "XT hard disk support" depends on ISA && ISA_DMA_API diff --git a/drivers/block/Makefile b/drivers/block/Makefile index cdaa3f8..a2c1b07 100644 --- a/drivers/block/Makefile +++ b/drivers/block/Makefile @@ -13,6 +13,12 @@ obj-$(CONFIG_PS3_DISK) += ps3disk.o obj-$(CONFIG_PS3_VRAM) += ps3vram.o obj-$(CONFIG_ATARI_FLOPPY) += ataflop.o obj-$(CONFIG_AMIGA_Z2RAM) += z2ram.o +obj-$(CONFIG_GAMECUBE_SD) += gcn-sd.o +obj-$(CONFIG_GAMECUBE_ARAM) += gcn-aram.o +obj-$(CONFIG_GAMECUBE_DI) += gcn-di/ +obj-$(CONFIG_WII_MEM2) += rvl-mem2.o +obj-$(CONFIG_WII_SD) += rvl-stsd.o +obj-$(CONFIG_WII_DI) += rvl-di.o obj-$(CONFIG_BLK_DEV_RAM) += brd.o obj-$(CONFIG_BLK_DEV_LOOP) += loop.o obj-$(CONFIG_BLK_DEV_XD) += xd.o diff --git a/drivers/block/gcn-aram.c b/drivers/block/gcn-aram.c new file mode 100644 index 0000000..8cbbf22 --- /dev/null +++ b/drivers/block/gcn-aram.c @@ -0,0 +1,597 @@ +/* + * drivers/block/gcn-aram.c + * + * Nintendo GameCube Auxiliary RAM (ARAM) block driver + * Copyright (C) 2004-2009 The GameCube Linux Team + * Copyright (C) 2005 Todd Jeffreys + * 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 +#include +#include /* O_ACCMODE */ +#include /* HDIO_GETGEO */ +#include +#include +#include +#include +#include + + +#define DRV_MODULE_NAME "gcn-aram" +#define DRV_DESCRIPTION "Nintendo GameCube Auxiliary RAM (ARAM) block driver" +#define DRV_AUTHOR "Todd Jeffreys , " \ + "Albert Herranz" + +static char aram_driver_version[] = "4.0i"; + +#define drv_printk(level, format, arg...) \ + printk(level DRV_MODULE_NAME ": " format , ## arg) + + +/* + * Hardware. + */ +#define ARAM_DMA_ALIGN 0x1f /* 32 bytes */ + +#define DSP_CSR 0x00a +#define DSP_CSR_RES (1<<0) +#define DSP_CSR_PIINT (1<<1) +#define DSP_CSR_HALT (1<<2) +#define DSP_CSR_AIDINT (1<<3) +#define DSP_CSR_AIDINTMASK (1<<4) +#define DSP_CSR_ARINT (1<<5) +#define DSP_CSR_ARINTMASK (1<<6) +#define DSP_CSR_DSPINT (1<<7) +#define DSP_CSR_DSPINTMASK (1<<8) +#define DSP_CSR_DSPDMA (1<<9) +#define DSP_CSR_RESETXXX (1<<11) + +#define AR_SIZE 0x012 + +#define AR_MODE 0x016 +#define AR_MODE_ACCELERATOR (1 << 0) + +#define AR_REFRESH 0x01a + +#define AR_DMA_MMADDR 0x020 + +#define AR_DMA_ARADDR 0x024 + +#define AR_DMA_CNT_H 0x028 +#define AR_READ (1 << 31) +#define AR_WRITE 0 + +#define AR_DMA_CNT_L 0x02a + +#define AR_DMA_CNT AR_DMA_CNT_H + +/* + * Driver settings + */ +#define ARAM_NAME DRV_MODULE_NAME +#define ARAM_MAJOR Z2RAM_MAJOR /* we share the major */ + +#define ARAM_SECTOR_SIZE PAGE_SIZE + +#define ARAM_BUFFERSIZE (16*1024*1024) + +/* + * Driver data. + */ +struct aram_drvdata { + spinlock_t queue_lock; + + spinlock_t io_lock; + void __iomem *io_base; + int irq; + + struct block_device_operations fops; + struct gendisk *disk; + struct request_queue *queue; + + struct request *req; /* protected by ->io_lock */ + dma_addr_t dma_addr; + size_t dma_len; + + int ref_count; + + struct device *dev; +}; + + +static inline enum dma_data_direction rq_dir_to_dma_dir(struct request *req) +{ + if (rq_data_dir(req) == READ) + return DMA_FROM_DEVICE; + else + return DMA_TO_DEVICE; +} + +static inline int rq_dir_to_aram_dir(struct request *req) +{ + if (rq_data_dir(req) == READ) + return AR_READ; + else + return AR_WRITE; +} + +static void aram_start_dma_transfer(struct aram_drvdata *drvdata, + unsigned long aram_addr) +{ + void __iomem *io_base = drvdata->io_base; + dma_addr_t dma_addr = drvdata->dma_addr; + size_t dma_len = drvdata->dma_len; + + /* DMA transfers require proper alignment */ + BUG_ON((dma_addr & ARAM_DMA_ALIGN) != 0 || + (dma_len & ARAM_DMA_ALIGN) != 0); + + out_be32(io_base + AR_DMA_MMADDR, dma_addr); + out_be32(io_base + AR_DMA_ARADDR, aram_addr); + + /* writing the low-word kicks off the DMA */ + out_be32(io_base + AR_DMA_CNT, + rq_dir_to_aram_dir(drvdata->req) | dma_len); +} + +static irqreturn_t aram_irq_handler(int irq, void *dev0) +{ + struct aram_drvdata *drvdata = dev0; + struct request *req; + u16 __iomem *csr_reg = drvdata->io_base + DSP_CSR; + u16 csr; + unsigned long flags; + + spin_lock_irqsave(&drvdata->io_lock, flags); + + csr = in_be16(csr_reg); + + /* + * Do nothing if the interrupt is not targetted for us. + * We share this interrupt with the sound driver. + */ + if (!(csr & DSP_CSR_ARINT)) { + spin_unlock_irqrestore(&drvdata->io_lock, flags); + return IRQ_NONE; + } + + /* strictly ack the ARAM interrupt, and nothing more */ + csr &= ~(DSP_CSR_AIDINT | DSP_CSR_DSPINT); + out_be16(csr_reg, csr); + + /* pick up request in service */ + req = drvdata->req; + drvdata->req = NULL; + if (drvdata->dma_len) { + dma_unmap_single(drvdata->dev, + drvdata->dma_addr, drvdata->dma_len, + rq_dir_to_dma_dir(req)); + drvdata->dma_len = 0; + } + + spin_unlock_irqrestore(&drvdata->io_lock, flags); + + if (!req) { + drv_printk(KERN_ERR, "ignoring interrupt, no request\n"); + goto out; + } + + spin_lock(&drvdata->queue_lock); + __blk_end_request_cur(req, 0); + blk_start_queue(drvdata->queue); + spin_unlock(&drvdata->queue_lock); + +out: + return IRQ_HANDLED; +} + +static void aram_do_request(struct request_queue *q) +{ + struct aram_drvdata *drvdata = q->queuedata; + struct request *req; + unsigned long aram_addr; + size_t len; + unsigned long flags; + int error; + + req = blk_peek_request(q); + while (req) { + spin_lock_irqsave(&drvdata->io_lock, flags); + if (drvdata->req) { + blk_stop_queue(q); + spin_unlock_irqrestore(&drvdata->io_lock, flags); + break; + } + + blk_start_request(req); + error = -EIO; + + if (!blk_fs_request(req)) + goto done; + + /* calculate the ARAM address and length */ + aram_addr = blk_rq_pos(req) << 9; + len = blk_rq_cur_bytes(req); + + /* give up if the request goes out of bounds */ + if (aram_addr + len > ARAM_BUFFERSIZE) { + drv_printk(KERN_ERR, "bad access: block=%lu," + " size=%u\n", (unsigned long)blk_rq_pos(req), + len); + goto done; + } + + drvdata->req = req; + spin_unlock_irqrestore(&drvdata->io_lock, flags); + + /* perform DMA mappings and start the transfer */ + drvdata->dma_len = len; + drvdata->dma_addr = dma_map_single(drvdata->dev, + req->buffer, len, + rq_dir_to_dma_dir(req)); + aram_start_dma_transfer(drvdata, aram_addr); + + /* one request at a time */ + break; + done: + spin_unlock_irqrestore(&drvdata->io_lock, flags); + if (!__blk_end_request_cur(req, error)) + req = blk_peek_request(q); + } +} + +/* + * Block device hooks. + * + */ + +static int aram_open(struct block_device *bdev, fmode_t mode) +{ + struct aram_drvdata *drvdata = bdev->bd_disk->private_data; + unsigned long flags; + int retval = 0; + + spin_lock_irqsave(&drvdata->queue_lock, flags); + + /* only allow a minor of 0 to be opened */ + if (MINOR(bdev->bd_dev)) { + retval = -ENODEV; + goto out; + } + + /* honor exclusive open mode */ + if (drvdata->ref_count == -1 || + (drvdata->ref_count && (mode & FMODE_EXCL))) { + retval = -EBUSY; + goto out; + } + + if ((mode & FMODE_EXCL)) + drvdata->ref_count = -1; + else + drvdata->ref_count++; + +out: + spin_unlock_irqrestore(&drvdata->queue_lock, flags); + return retval; +} + +static int aram_release(struct gendisk *disk, fmode_t mode) +{ + struct aram_drvdata *drvdata = disk->private_data; + unsigned long flags; + + spin_lock_irqsave(&drvdata->queue_lock, flags); + if (drvdata->ref_count > 0) + drvdata->ref_count--; + else + drvdata->ref_count = 0; + spin_unlock_irqrestore(&drvdata->queue_lock, flags); + + return 0; +} + +static int aram_getgeo(struct block_device *bdev, struct hd_geometry *geo) +{ + geo->cylinders = get_capacity(bdev->bd_disk) / (4 * 16); + geo->heads = 4; + geo->sectors = 16; + return 0; +} + +static struct block_device_operations aram_fops = { + .owner = THIS_MODULE, + .open = aram_open, + .release = aram_release, + .getgeo = aram_getgeo, +}; + + +/* + * Setup routines. + * + */ + +static int aram_init_blk_dev(struct aram_drvdata *drvdata) +{ + struct gendisk *disk; + struct request_queue *queue; + int retval; + + drvdata->ref_count = 0; + + retval = register_blkdev(ARAM_MAJOR, ARAM_NAME); + if (retval) + goto err_register_blkdev; + + retval = -ENOMEM; + spin_lock_init(&drvdata->queue_lock); + spin_lock_init(&drvdata->io_lock); + queue = blk_init_queue(aram_do_request, &drvdata->queue_lock); + if (!queue) + goto err_blk_init_queue; + + blk_queue_logical_block_size(queue, ARAM_SECTOR_SIZE); + blk_queue_dma_alignment(queue, ARAM_DMA_ALIGN); + blk_queue_max_phys_segments(queue, 1); + blk_queue_max_hw_segments(queue, 1); + queue_flag_set_unlocked(QUEUE_FLAG_NONROT, queue); + queue->queuedata = drvdata; + drvdata->queue = queue; + + disk = alloc_disk(1); + if (!disk) + goto err_alloc_disk; + + disk->major = ARAM_MAJOR; + disk->first_minor = 0; + disk->fops = &aram_fops; + strcpy(disk->disk_name, ARAM_NAME); + disk->queue = drvdata->queue; + set_capacity(disk, ARAM_BUFFERSIZE >> 9); + disk->private_data = drvdata; + drvdata->disk = disk; + + add_disk(drvdata->disk); + + retval = 0; + goto out; + +err_alloc_disk: + blk_cleanup_queue(drvdata->queue); +err_blk_init_queue: + unregister_blkdev(ARAM_MAJOR, ARAM_NAME); +err_register_blkdev: +out: + return retval; +} + +static void aram_exit_blk_dev(struct aram_drvdata *drvdata) +{ + if (drvdata->disk) { + del_gendisk(drvdata->disk); + put_disk(drvdata->disk); + } + if (drvdata->queue) + blk_cleanup_queue(drvdata->queue); + unregister_blkdev(ARAM_MAJOR, ARAM_NAME); +} + +static void aram_quiesce(struct aram_drvdata *drvdata) +{ + u16 __iomem *csr_reg = drvdata->io_base + DSP_CSR; + u16 csr; + unsigned long flags; + + /* + * Disable ARAM interrupts, but do not accidentally ack non-ARAM ones. + */ + spin_lock_irqsave(&drvdata->io_lock, flags); + csr = in_be16(csr_reg); + csr &= ~(DSP_CSR_AIDINT | DSP_CSR_DSPINT | DSP_CSR_ARINTMASK); + out_be16(csr_reg, csr); + spin_unlock_irqrestore(&drvdata->io_lock, flags); + + /* wait until pending transfers are finished */ + while (in_be16(csr_reg) & DSP_CSR_DSPDMA) + cpu_relax(); +} + +static int aram_init_irq(struct aram_drvdata *drvdata) +{ + u16 __iomem *csr_reg = drvdata->io_base + DSP_CSR; + u16 csr; + unsigned long flags; + int retval; + + retval = request_irq(drvdata->irq, aram_irq_handler, + IRQF_DISABLED | IRQF_SHARED, + DRV_MODULE_NAME, drvdata); + if (retval) { + drv_printk(KERN_ERR, "request of IRQ %d failed\n", + drvdata->irq); + goto out; + } + + /* + * Enable ARAM interrupts, and route them to the processor. + * Make sure to preserve the AI and DSP interrupts. + */ + spin_lock_irqsave(&drvdata->io_lock, flags); + csr = in_be16(csr_reg); + csr |= (DSP_CSR_ARINT | DSP_CSR_ARINTMASK | DSP_CSR_PIINT); + csr &= ~(DSP_CSR_AIDINT | DSP_CSR_DSPINT); + out_be16(csr_reg, csr); + spin_unlock_irqrestore(&drvdata->io_lock, flags); + +out: + return retval; +} + +static void aram_exit_irq(struct aram_drvdata *drvdata) +{ + aram_quiesce(drvdata); + + free_irq(drvdata->irq, drvdata); +} + +static int aram_init(struct aram_drvdata *drvdata, + struct resource *mem, int irq) +{ + int retval; + + drvdata->io_base = ioremap(mem->start, mem->end - mem->start + 1); + drvdata->irq = irq; + + retval = aram_init_blk_dev(drvdata); + if (!retval) { + retval = aram_init_irq(drvdata); + if (retval) + aram_exit_blk_dev(drvdata); + } + return retval; +} + +static void aram_exit(struct aram_drvdata *drvdata) +{ + aram_exit_blk_dev(drvdata); + aram_exit_irq(drvdata); + if (drvdata->io_base) { + iounmap(drvdata->io_base); + drvdata->io_base = NULL; + } +} + +/* + * Driver model helper routines. + * + */ + +static int aram_do_probe(struct device *dev, struct resource *mem, + int irq) +{ + struct aram_drvdata *drvdata; + int retval; + + drvdata = kzalloc(sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) { + drv_printk(KERN_ERR, "failed to allocate aram_drvdata\n"); + return -ENOMEM; + } + dev_set_drvdata(dev, drvdata); + drvdata->dev = dev; + + retval = aram_init(drvdata, mem, irq); + if (retval) { + dev_set_drvdata(dev, NULL); + kfree(drvdata); + } + return retval; +} + +static int aram_do_remove(struct device *dev) +{ + struct aram_drvdata *drvdata = dev_get_drvdata(dev); + + if (drvdata) { + aram_exit(drvdata); + dev_set_drvdata(dev, NULL); + kfree(drvdata); + return 0; + } + return -ENODEV; +} + +static int aram_do_shutdown(struct device *dev) +{ + struct aram_drvdata *drvdata = dev_get_drvdata(dev); + + if (drvdata) + aram_quiesce(drvdata); + return 0; +} + + +/* + * OF platform device routines. + * + */ + +static int __init aram_of_probe(struct of_device *odev, + const struct of_device_id *match) +{ + struct resource res; + int retval; + + retval = of_address_to_resource(odev->node, 0, &res); + if (retval) { + drv_printk(KERN_ERR, "no io memory range found\n"); + return -ENODEV; + } + + return aram_do_probe(&odev->dev, + &res, irq_of_parse_and_map(odev->node, 0)); +} + +static int __exit aram_of_remove(struct of_device *odev) +{ + return aram_do_remove(&odev->dev); +} + +static int aram_of_shutdown(struct of_device *odev) +{ + return aram_do_shutdown(&odev->dev); +} + + +static struct of_device_id aram_of_match[] = { + { .compatible = "nintendo,flipper-auxram" }, + { }, +}; + + +MODULE_DEVICE_TABLE(of, aram_of_match); + +static struct of_platform_driver aram_of_driver = { + .owner = THIS_MODULE, + .name = DRV_MODULE_NAME, + .match_table = aram_of_match, + .probe = aram_of_probe, + .remove = aram_of_remove, + .shutdown = aram_of_shutdown, +}; + +/* + * Module interfaces. + * + */ + +static int __init aram_init_module(void) +{ + drv_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION, + aram_driver_version); + + return of_register_platform_driver(&aram_of_driver); +} + +static void __exit aram_exit_module(void) +{ + of_unregister_platform_driver(&aram_of_driver); +} + +module_init(aram_init_module); +module_exit(aram_exit_module); + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_AUTHOR(DRV_AUTHOR); +MODULE_LICENSE("GPL"); + diff --git a/drivers/block/gcn-di/Makefile b/drivers/block/gcn-di/Makefile new file mode 100644 index 0000000..09a410d --- /dev/null +++ b/drivers/block/gcn-di/Makefile @@ -0,0 +1,50 @@ +obj-$(CONFIG_GAMECUBE_DI) += gcn-di.o + +$(obj)/gcn-di.o: $(obj)/drive_20010608.h \ + $(obj)/drive_20010831.h \ + $(obj)/drive_20020402.h \ + $(obj)/drive_20020823.h + +#CONFIG_GAMECUBE_DI_BUILD_FIRMWARE=y + +ifeq ($(CONFIG_GAMECUBE_DI_BUILD_FIRMWARE),y) + +ASMN102 = mn10200-linux-as +LDMN102 = mn10200-linux-ld +OCMN102 = mn10200-linux-objcopy + +quiet_cmd_build_difw = BLD FW $@ +cmd_build_difw = \ + $(CPP) -DDRIVE_MODEL=$(DRIVE_MODEL) $< > $(obj)/$(@F).s; \ + $(ASMN102) -o $(obj)/$(@F).o $(obj)/$(@F).s; \ + $(LDMN102) --section-start absolute=0 -Ttext=0x40d000 \ + -o $(obj)/$(@F).elf -e 0x40d000 $(obj)/$(@F).o; \ + $(OCMN102) -I elf32-mn10200 -O binary $(obj)/$(@F).elf \ + $(obj)/$(@F).bin; \ + (echo -n "static "; cat $(obj)/$(@F).bin | scripts/bin2c "$(subst .h,,$(@F))_firmware") > $@; \ + rm -f $(obj)/$(@F).o $(obj)/$(@F).elf $(obj)/$(@F).bin $(obj)/$(@F).s + + +targets += drive_20010608.h +$(obj)/drive_20010608.h: DRIVE_MODEL := 0x20010608 +$(obj)/drive_20010608.h: $(src)/drive_all.S FORCE + $(call if_changed,build_difw) + +targets += drive_20010831.h +$(obj)/drive_20010831.h: DRIVE_MODEL := 0x20010831 +$(obj)/drive_20010831.h: $(src)/drive_all.S FORCE + $(call if_changed,build_difw) + +targets += drive_20020402.h +$(obj)/drive_20020402.h: DRIVE_MODEL := 0x20020402 +$(obj)/drive_20020402.h: $(src)/drive_all.S FORCE + $(call if_changed,build_difw) + +targets += drive_20020823.h +$(obj)/drive_20020823.h: DRIVE_MODEL := 0x20020823 +$(obj)/drive_20020823.h: $(src)/drive_all.S FORCE + $(call if_changed,build_difw) + +endif + + diff --git a/drivers/block/gcn-di/drive_20010608.h b/drivers/block/gcn-di/drive_20010608.h new file mode 100644 index 0000000..7c3e9d8 --- /dev/null +++ b/drivers/block/gcn-di/drive_20010608.h @@ -0,0 +1,25 @@ +static const char drive_20010608_firmware[] = + "\xf7\x10\xff\xf7\xf4\x74\x25\xd0\x40\xf7\x20\x4c\x80\xf4\x74\x42" + "\x9d\x08\xf7\x20\xd6\xfc\xf4\x74\x45\xb1\x08\xf7\x20\xd2\xfc\x80" + "\x0c\xc4\xda\xfc\xfe\xc8\xda\xfc\xf5\x00\x01\xe9\x70\xc8\xda\xfc" + "\xf5\x00\x02\xe9\x75\xf4\x74\x02\xed\x40\x80\x02\xf0\x20\xc8\x78" + "\x80\xc0\x90\x81\xdc\xa8\x80\xf5\x30\x00\xf4\x44\x41\xd1\x40\xf8" + "\xaa\x00\x10\xf4\xd0\x3c\xd1\x40\xf0\x01\xdc\xa8\x80\xf5\x30\x00" + "\xf7\x48\xaa\x00\xe9\x07\xf4\xc4\x41\xd1\x40\x10\xfe\xf7\x48\xee" + "\x00\xe8\x0c\xd8\x55\xe9\x25\xcc\xa9\x80\xfd\x79\x00\xea\x0c\xcc" + "\xa9\x80\xc4\xa4\x81\xcc\xaa\x80\xc4\x88\x81\xdc\xa8\x80\xf8\xe0" + "\x00\x10\xa0\xf5\x10\x01\xf5\x10\x02\xf5\x10\x03\xfe\xc8\xda\xfc" + "\xf7\x00\xfe\xff\xf7\x31\xd2\xfc\xea\x0b\xc8\xda\xfc\xf7\x00\xfd" + "\xff\xf7\x31\xd6\xfc\xc4\xda\xfc\xcc\x44\xfc\xf7\x00\xfe\xff\xc4" + "\x44\xfc\xf4\x7d\x45\xb1\x08\xe9\x07\xf4\x75\x0c\xd1\x40\xea\x0c" + "\xf4\x7d\x42\x9d\x08\xe9\x05\xf4\x75\x35\xd1\x40\xf2\x7c\xd0\x04" + "\xcc\x5b\x80\xd8\x01\xe9\x02\x7c\x04\x51\x20\x71\x34\xf4\x7d\xb9" + "\x85\x08\xe9\x13\x80\x00\x85\x00\xd8\x00\xe8\x02\x85\x0c\xc5\xda" + "\xfc\xf4\x75\x40\xd1\x40\x14\xfe\x80\x01\xea\xea\xf7\x10\xff\xf7" + "\xf4\xc9\x40\xd1\x40\xd9\x00\xe8\x17\x21\xf4\x79\x00\xf0\x00\xe9" + "\x05\x80\x00\xf5\x10\x09\xd9\x06\xe9\x06\x61\x06\xd5\x06\x41\x06" + "\xf4\xe0\x1b\xe0\xc7\xf4\xe0\x96\xcc\xc7\x00\x00\x74\x0a\x08\x00" + "\x01\x00\x00\x00" + ; + +const int drive_20010608_firmware_size = 324; diff --git a/drivers/block/gcn-di/drive_20010831.h b/drivers/block/gcn-di/drive_20010831.h new file mode 100644 index 0000000..16df341 --- /dev/null +++ b/drivers/block/gcn-di/drive_20010831.h @@ -0,0 +1,25 @@ +static const char drive_20010831_firmware[] = + "\xf7\x10\xff\xf7\xf4\x74\x25\xd0\x40\xf7\x20\x4c\x80\xf4\x74\x39" + "\x9e\x08\xf7\x20\xd6\xfc\xf4\x74\x02\xb3\x08\xf7\x20\xd2\xfc\x80" + "\x0c\xc4\xda\xfc\xfe\xc8\xda\xfc\xf5\x00\x01\xe9\x70\xc8\xda\xfc" + "\xf5\x00\x02\xe9\x75\xf4\x74\x02\xed\x40\x80\x02\xf0\x20\xc8\x78" + "\x80\xc0\x92\x81\xdc\xaa\x80\xf5\x30\x00\xf4\x44\x41\xd1\x40\xf8" + "\xaa\x00\x10\xf4\xd0\x3c\xd1\x40\xf0\x01\xdc\xaa\x80\xf5\x30\x00" + "\xf7\x48\xaa\x00\xe9\x07\xf4\xc4\x41\xd1\x40\x10\xfe\xf7\x48\xee" + "\x00\xe8\x0c\xd8\x55\xe9\x25\xcc\xab\x80\xfd\x79\x00\xea\x0c\xcc" + "\xab\x80\xc4\xa6\x81\xcc\xac\x80\xc4\x8a\x81\xdc\xaa\x80\xf8\xe0" + "\x00\x10\xa0\xf5\x10\x01\xf5\x10\x02\xf5\x10\x03\xfe\xc8\xda\xfc" + "\xf7\x00\xfe\xff\xf7\x31\xd2\xfc\xea\x0b\xc8\xda\xfc\xf7\x00\xfd" + "\xff\xf7\x31\xd6\xfc\xc4\xda\xfc\xcc\x44\xfc\xf7\x00\xfe\xff\xc4" + "\x44\xfc\xf4\x7d\x02\xb3\x08\xe9\x07\xf4\x75\x0c\xd1\x40\xea\x0c" + "\xf4\x7d\x39\x9e\x08\xe9\x05\xf4\x75\x35\xd1\x40\xf2\x7c\xd0\x04" + "\xcc\x5b\x80\xd8\x01\xe9\x02\x7c\x04\x51\x20\x71\x34\xf4\x7d\x7f" + "\x86\x08\xe9\x13\x80\x00\x85\x00\xd8\x00\xe8\x02\x85\x0c\xc5\xda" + "\xfc\xf4\x75\x40\xd1\x40\x14\xfe\x80\x01\xea\xea\xf7\x10\xff\xf7" + "\xf4\xc9\x40\xd1\x40\xd9\x00\xe8\x17\x21\xf4\x79\x00\xf0\x00\xe9" + "\x05\x80\x00\xf5\x10\x09\xd9\x06\xe9\x06\x61\x06\xd5\x06\x41\x06" + "\xf4\xe0\xd8\xe1\xc7\xf4\xe0\x4a\xce\xc7\x00\x00\xa4\x0a\x08\x00" + "\x01\x00\x00\x00" + ; + +const int drive_20010831_firmware_size = 324; diff --git a/drivers/block/gcn-di/drive_20020402.h b/drivers/block/gcn-di/drive_20020402.h new file mode 100644 index 0000000..dc80971 --- /dev/null +++ b/drivers/block/gcn-di/drive_20020402.h @@ -0,0 +1,25 @@ +static const char drive_20020402_firmware[] = + "\xf7\x10\xff\xf7\xf4\x74\x25\xd0\x40\xf7\x20\x4c\x80\xf4\x74\xd6" + "\x9c\x08\xf7\x20\xd6\xfc\xf4\x74\x28\xae\x08\xf7\x20\xd2\xfc\x80" + "\x0c\xc4\xda\xfc\xfe\xc8\xda\xfc\xf5\x00\x01\xe9\x70\xc8\xda\xfc" + "\xf5\x00\x02\xe9\x75\xf4\x74\xf9\xec\x40\x80\x02\xf0\x20\xc8\x84" + "\x80\xc0\x9c\x81\xdc\xb4\x80\xf5\x30\x00\xf4\x44\x41\xd1\x40\xf8" + "\xaa\x00\x10\xf4\xd0\x3c\xd1\x40\xf0\x01\xdc\xb4\x80\xf5\x30\x00" + "\xf7\x48\xaa\x00\xe9\x07\xf4\xc4\x41\xd1\x40\x10\xfe\xf7\x48\xee" + "\x00\xe8\x0c\xd8\x55\xe9\x25\xcc\xb5\x80\xfd\x79\x00\xea\x0c\xcc" + "\xb5\x80\xc4\xb0\x81\xcc\xb6\x80\xc4\x94\x81\xdc\xb4\x80\xf8\xe0" + "\x00\x10\xa0\xf5\x10\x01\xf5\x10\x02\xf5\x10\x03\xfe\xc8\xda\xfc" + "\xf7\x00\xfe\xff\xf7\x31\xd2\xfc\xea\x0b\xc8\xda\xfc\xf7\x00\xfd" + "\xff\xf7\x31\xd6\xfc\xc4\xda\xfc\xcc\x44\xfc\xf7\x00\xfe\xff\xc4" + "\x44\xfc\xf4\x7d\x28\xae\x08\xe9\x07\xf4\x75\x0c\xd1\x40\xea\x0c" + "\xf4\x7d\xd6\x9c\x08\xe9\x05\xf4\x75\x35\xd1\x40\xf2\x7c\xd0\x04" + "\xcc\x5b\x80\xd8\x01\xe9\x02\x7c\x04\x51\x20\x71\x34\xf4\x7d\xc1" + "\x85\x08\xe9\x13\x80\x00\x85\x00\xd8\x00\xe8\x02\x85\x0c\xc5\xda" + "\xfc\xf4\x75\x40\xd1\x40\x14\xfe\x80\x01\xea\xea\xf7\x10\xff\xf7" + "\xf4\xc9\x40\xd1\x40\xd9\x00\xe8\x17\x21\xf4\x79\x00\xf0\x00\xe9" + "\x05\x80\x00\xf5\x10\x09\xd9\x06\xe9\x06\x61\x06\xd5\x06\x41\x06" + "\xf4\xe0\xfe\xdc\xc7\xf4\xe0\x14\xcc\xc7\x00\x00\x74\x0a\x08\x00" + "\x01\x00\x00\x00" + ; + +const int drive_20020402_firmware_size = 324; diff --git a/drivers/block/gcn-di/drive_20020823.h b/drivers/block/gcn-di/drive_20020823.h new file mode 100644 index 0000000..f20b5cc --- /dev/null +++ b/drivers/block/gcn-di/drive_20020823.h @@ -0,0 +1,25 @@ +static const char drive_20020823_firmware[] = + "\xf7\x10\xff\xf7\xf4\x74\x25\xd0\x40\xf7\x20\x4c\x80\xf4\x74\x32" + "\x9d\x08\xf7\x20\xd6\xfc\xf4\x74\x75\xae\x08\xf7\x20\xd2\xfc\x80" + "\x0c\xc4\xda\xfc\xfe\xc8\xda\xfc\xf5\x00\x01\xe9\x70\xc8\xda\xfc" + "\xf5\x00\x02\xe9\x75\xf4\x74\xf5\xec\x40\x80\x02\xf0\x20\xc8\x80" + "\x80\xc0\x98\x81\xdc\xb0\x80\xf5\x30\x00\xf4\x44\x41\xd1\x40\xf8" + "\xaa\x00\x10\xf4\xd0\x3c\xd1\x40\xf0\x01\xdc\xb0\x80\xf5\x30\x00" + "\xf7\x48\xaa\x00\xe9\x07\xf4\xc4\x41\xd1\x40\x10\xfe\xf7\x48\xee" + "\x00\xe8\x0c\xd8\x55\xe9\x25\xcc\xb1\x80\xfd\x79\x00\xea\x0c\xcc" + "\xb1\x80\xc4\xac\x81\xcc\xb2\x80\xc4\x90\x81\xdc\xb0\x80\xf8\xe0" + "\x00\x10\xa0\xf5\x10\x01\xf5\x10\x02\xf5\x10\x03\xfe\xc8\xda\xfc" + "\xf7\x00\xfe\xff\xf7\x31\xd2\xfc\xea\x0b\xc8\xda\xfc\xf7\x00\xfd" + "\xff\xf7\x31\xd6\xfc\xc4\xda\xfc\xcc\x44\xfc\xf7\x00\xfe\xff\xc4" + "\x44\xfc\xf4\x7d\x75\xae\x08\xe9\x07\xf4\x75\x0c\xd1\x40\xea\x0c" + "\xf4\x7d\x32\x9d\x08\xe9\x05\xf4\x75\x35\xd1\x40\xf2\x7c\xd0\x04" + "\xcc\x5b\x80\xd8\x01\xe9\x02\x7c\x04\x51\x20\x71\x34\xf4\x7d\xc1" + "\x85\x08\xe9\x13\x80\x00\x85\x00\xd8\x00\xe8\x02\x85\x0c\xc5\xda" + "\xfc\xf4\x75\x40\xd1\x40\x14\xfe\x80\x01\xea\xea\xf7\x10\xff\xf7" + "\xf4\xc9\x40\xd1\x40\xd9\x00\xe8\x17\x21\xf4\x79\x00\xf0\x00\xe9" + "\x05\x80\x00\xf5\x10\x09\xd9\x06\xe9\x06\x61\x06\xd5\x06\x41\x06" + "\xf4\xe0\x4b\xdd\xc7\xf4\xe0\x6d\xcc\xc7\x00\x00\x74\x0a\x08\x00" + "\x01\x00\x00\x00" + ; + +const int drive_20020823_firmware_size = 324; diff --git a/drivers/block/gcn-di/drive_all.S b/drivers/block/gcn-di/drive_all.S new file mode 100644 index 0000000..e9d91a0 --- /dev/null +++ b/drivers/block/gcn-di/drive_all.S @@ -0,0 +1,415 @@ +/* + * DVD+/-R compatible "cactus" firmware extensions + * Copyright (C) 2005-2009 The GameCube Linux Team + * Copyright (C) 2005,2006,2009 Albert Herranz + * + * Originally based on analysis of Cobra 1.0 drive code released by tmbinc + * on dextrose.com. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +/* + * This code is compatible with binutils 2.15 limited mn10200 support. + * And it intentionally lacks the audio fix and DRE recovery features. + * + */ + + +.equ UNICR, 0xfc44 +.equ UNID, (1<<0) +.equ ADB0, 0xfcd2 +.equ ADB1, 0xfcd6 +.equ ADBCTL, 0xfcda +.equ ADB0CK, (1<<0) +.equ ADB1CK, (1<<1) +.equ ADB0ON, (1<<2) +.equ ADB1ON, (1<<3) + + +.equ irq_handler_vector, 0x804c /* 04, 06, 08, Panasonic Q */ +.equ irq_depth, 0x805b /* 04, 06, 08, Panasonic Q */ + +.equ fake_command, 0xaa +.equ set_drive_status_command, 0xee /* same as in gcn-di.c */ +.equ enable_extensions_command, 0x55 /* same as in gcn-di.c */ + +.equ get_drive_status_command, 0xe0 + + +#if DRIVE_MODEL == 0x20020402 /* 04 */ + + .equ cmdbuf0, 0x80b4 + .equ drive_status, 0x81b0 + .equ drive_status2, 0x8194 + + .equ bert, 0x8084 + .equ ernie, 0x819c + .equ cactus, 0x40ecf9 + + .section absolute + + .equ adb1_break_address, 0x089cd6 + .org 0x089d4e + adb1_fixup_exit: + + .equ adb0_break_address, 0x08ae28 + .org 0x08ae33 + adb0_fixup_exit: + + .equ disable_extensions_when_called_from, 0x0885c1 /* 04, 08 */ + +#elif DRIVE_MODEL == 0x20010608 /* 06 */ + + .equ cmdbuf0, 0x80a8 + .equ drive_status, 0x81a4 + .equ drive_status2, 0x8188 + + .equ bert, 0x8078 + .equ ernie, 0x8190 + .equ cactus, 0x40ed02 + + .section absolute + + .equ adb1_break_address, 0x089d42 + .org 0x089dd0 + adb1_fixup_exit: + + .equ adb0_break_address, 0x08b145 + .org 0x08b150 + adb0_fixup_exit: + + .equ disable_extensions_when_called_from, 0x0885b9 + +#elif DRIVE_MODEL == 0x20020823 /* 08 */ + + .equ cmdbuf0, 0x80b0 + .equ drive_status, 0x81ac + .equ drive_status2, 0x8190 + + .equ bert, 0x8080 + .equ ernie, 0x8198 + .equ cactus, 0x40ecf5 + + .section absolute + + .equ adb1_break_address, 0x089d32 + .org 0x089da7 + adb1_fixup_exit: + + .equ adb0_break_address, 0x08ae75 + .org 0x08ae80 + adb0_fixup_exit: + + .equ disable_extensions_when_called_from, 0x0885c1 /* 04, 08 */ + +#elif DRIVE_MODEL == 0x20010831 /* Panasonic Q */ + + .equ cmdbuf0, 0x80aa + .equ drive_status, 0x81a6 + .equ drive_status2, 0x818a + + .equ bert, 0x8078 + .equ ernie, 0x8192 + .equ cactus, 0x40ed02 + + .section absolute + + .equ adb1_break_address, 0x089e39 + .org 0x089f84 + adb1_fixup_exit: + + .equ adb0_break_address, 0x08b302 + .org 0x08b30d + adb0_fixup_exit: + + .equ disable_extensions_when_called_from, 0x08867f + +#else + #error Sorry, unsupported drive. +#endif + + + .section .text + .global _start + .global _exit + +/* + * We are launched now through the 'func' debug command. + */ +_start: +_main: + /* disable interrupts, do not disturb */ + and 0xf7ff, psw + + /* replace the current irq handler with ours */ + mov our_irq_handler, a0 + mov a0, (irq_handler_vector) + + /* setup our extending functions ... */ + mov adb1_break_address, a0 + mov a0, (ADB1) + mov adb0_break_address, a0 + mov a0, (ADB0) + + /* ... and enable them */ + mov ADB1ON|ADB0ON, d0 + movb d0, (ADBCTL) + + rts + + +our_irq_handler: + /* check for Address Break 0 */ + mov (ADBCTL), d0 + and ADB0CK, d0 + bne adb0_break_handler + + /* check for Address Break 1 */ + mov (ADBCTL), d0 + and ADB1CK, d0 + bne adb1_break_handler + + /* XXX not sure about this one... */ +// mov 0x0c, d0 +// movb d0, (0x819a) + + /* tell the drive to please accept the disk */ + mov cactus, a0 + mov 2, d0 + bset d0, (a0) + + /* this seems to avoid errors if the drive idles for too long */ + mov (bert), d0 + mov d0, (ernie) + + /* save current command ... */ + mov cmdbuf0, a0 + movbu (0, a0), d0 + movb d0, (saved_cmdbuf0) + + /* ... and place a temporary fake command, to detect new commands */ + mov fake_command, d0 + movb d0, (a0) + + /* call the original handler */ + mov (saved_irq_handler), a0 + jsr (a0) + + /* if our fake command changed, we assume a new command has arrived */ + mov cmdbuf0, a0 + movbu (0, a0), d0 + cmp fake_command, d0 + bne extra_command_parser + + /* if there is no new command, restore the previously saved command */ + movb (saved_cmdbuf0), d0 + movb d0, (a0) + + rts + + +extra_command_parser: + /* "set drive status" command */ + cmp set_drive_status_command, d0 + beq set_drive_status + + /* "enable extensions" command */ + cmp enable_extensions_command, d0 + bne done + +enable_or_disable_extensions: + /* 0x55, 0xZZ, 0x00, 0x00 */ + /* ZZ=0 disable, otherwise enable */ + movbu (cmdbuf0+1), d0 + jsr di_enable_or_disable_extensions + jmp get_drive_status + +set_drive_status: + /* 0xee, 0xZZ, 0xYY, 0x00 */ + /* ZZ=drive_status, YY=drive_status2 */ + movbu (cmdbuf0+1), d0 + movb d0, (drive_status) + movbu (cmdbuf0+2), d0 + movb d0, (drive_status2) + + +get_drive_status: + /* + * This saves us an invalid command error and updates the status + * accordingly. In fact, our extended command becomes a "get status" + * command. + */ + mov cmdbuf0, a0 + mov get_drive_status_command, d0 + movb d0, (a0) + sub d0, d0 + movb d0, (1,a0) + movb d0, (2,a0) + movb d0, (3,a0) + +done: + rts + + +/* + * This is how the stacks look like when our interrupt handler is called. + * + * Our interrupt handler is in fact not the real interrupt handler, but + * just a subroutine called by the real interrupt handler. + * That's why we just RTS and not RTI from our interrupt handler. + * + * | | | | + * 00| d0 0| <- old a3 | | + * 02| 8| | | + * 04| d1 6| | | + * 06| 4| | | + * 08| d2 2| | | + * 0a| 0| | | + * 0c| d3 8| | | + * 0e| 6| | | + * 10| a0 4| | | + * 12| 2| | | + * 14| a1 0| | | + * 16| 8| | | + * 18| a2 6| | | + * 1a| 4| | | + * 1c| MDR 2| | | + * 1e| PSW | | | + * 20| PC lo | | PC lo | <- a3 + * 22| PC hi | | PC hi | + * : : | old a3 | + * | ... | | | + * +--------+ +--------+ <- (0x8ea1c) for drive 04 + * normal context stack interrupt context stack + * + */ + +adb0_break_handler: + mov (ADBCTL), d0 + and ~ADB0CK, d0 + mov (ADB0), a1 + jmp address_break_handler + +adb1_break_handler: + mov (ADBCTL), d0 + and ~ADB1CK, d0 + mov (ADB1), a1 + +address_break_handler: + /* ack the interrupt */ + movb d0, (ADBCTL) + movbu (UNICR), d0 + and ~UNID, d0 + movb d0, (UNICR) + + cmp adb0_break_address, a1 + bne 1f + mov adb0_fixup, a1 + jmp 2f + +1: + cmp adb1_break_address, a1 + bne 2f + mov adb1_fixup, a1 + +2: + /* point to the previous stack pointer */ + mov a3, a0 + add 4, a0 + + /* + * Special case. When entering interrupt context the first time, + * the old stack is pushed in the interrupt stack before calling us. + */ + movbu (irq_depth), d0 + cmp 1, d0 + bne 1f + mov (4, a3), a0 /* get the old stack pointer */ +1: + /* overwrite the original return address (look at the stack layout) */ + mov a1, (0x20, a0) + + /* + * We disable the extensions when an original disc is found. + * + * We do that by checking if we were called from a piece of + * code reached only when original discs are inserted. Tricky. + */ + + /* 0x20 + 0x10 + 0x04 = 0x34 */ + mov (0x34, a0), a1 + cmp disable_extensions_when_called_from, a1 + bne 9f /* else, do nothing */ + +di_disable_extensions: + mov 0, d0 + +di_enable_or_disable_extensions: + /* enable additional media if extensions are enabled */ + mov 0, d1 + cmp 0, d0 + beq 1f + mov ADB0ON|ADB1ON, d1 +1: + movb d1, (ADBCTL) + + mov enable_extensions, a1 + movb d0, (a1) + +9: + rts + +di_enable_extensions: + mov 1, d0 + jmp di_enable_or_disable_extensions + + +adb0_fixup: + /* disable interrupts, XXX really needed here...? */ + and 0xf7ff, psw + + /* check if we need to tweak things or not */ + movbu (enable_extensions), d1 + cmp 0, d1 + beq 1f + + /* deal with the dvd seed */ + mov (a0), d1 + cmp 0x00f000, d1 /* controller setup */ + bne 2f + mov 0x00, d0 /* seed 0x00 for normal DVD */ + movb d0, (0x09, a0) +2: + /* skip the extra field */ + cmp 0x06, d1 /* transfer sector buffer */ + bne 1f + mov (0x06, a0), d1 + add 6, d1 /* skip it */ + mov d1, (0x06, a0) +1: + jmp adb0_fixup_exit + + +adb1_fixup: + jmp adb1_fixup_exit + + +.align 2 +saved_irq_handler: +#if DRIVE_MODEL != 0x20010831 + .long 0x00080A74 /* 04, 06, 08 */ +#else + .long 0x00080AA4 /* Panasonic Q */ +#endif +enable_extensions: + .byte 0x01 +saved_cmdbuf0: + .byte 0x00 + +_exit: + diff --git a/drivers/block/gcn-di/gcn-di.c b/drivers/block/gcn-di/gcn-di.c new file mode 100644 index 0000000..2e3a023 --- /dev/null +++ b/drivers/block/gcn-di/gcn-di.c @@ -0,0 +1,2361 @@ +/* + * drivers/block/gcn-di/gcn-di.c + * + * Nintendo GameCube Disk Interface (DI) driver + * Copyright (C) 2005-2009 The GameCube Linux Team + * Copyright (C) 2005,2006,2007,2009 Albert Herranz + * + * Portions based on previous work by Scream|CT. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DI_DEBUG + +#define DRV_MODULE_NAME "gcn-di" +#define DRV_DESCRIPTION "Nintendo GameCube Disk Interface (DI) driver" +#define DRV_AUTHOR "Albert Herranz" + +static char di_driver_version[] = "1.0i"; + +#define drv_printk(level, format, arg...) \ + printk(level DRV_MODULE_NAME ": " format , ## arg) + +#ifdef DI_DEBUG +# define DBG(fmt, args...) \ + printk(KERN_ERR "%s: " fmt, __func__ , ## args) +#else +# define DBG(fmt, args...) +#endif + + +/* + * Hardware. + */ +#define DI_DMA_ALIGN 0x1f /* 32 bytes */ + +/* DI Status Register */ +#define DI_SR 0x00 +#define DI_SR_BRK (1<<0) +#define DI_SR_DEINTMASK (1<<1) +#define DI_SR_DEINT (1<<2) +#define DI_SR_TCINTMASK (1<<3) +#define DI_SR_TCINT (1<<4) +#define DI_SR_BRKINTMASK (1<<5) +#define DI_SR_BRKINT (1<<6) + +/* DI Cover Register */ +#define DI_CVR 0x04 +#define DI_CVR_CVR (1<<0) +#define DI_CVR_CVRINTMASK (1<<1) +#define DI_CVR_CVRINT (1<<2) + +/* DI Command Buffers */ +#define DI_CMDBUF0 0x08 +#define DI_CMDBUF1 0x0c +#define DI_CMDBUF2 0x10 + +/* DI DMA Memory Address Register */ +#define DI_MAR 0x14 + +/* DI DMA Transfer Length Register */ +#define DI_LENGTH 0x18 + +/* DI Control Register */ +#define DI_CR 0x1c +#define DI_CR_TSTART (1<<0) +#define DI_CR_DMA (1<<1) +#define DI_CR_RW (1<<2) + +/* DI Immediate Data Buffer */ +#define DI_DATA 0x20 + +/* DI Configuration Register */ +#define DI_CFG 0x24 + + +/* drive status, status */ +#define DI_STATUS(s) ((u8)((s)>>24)) + +#define DI_STATUS_READY 0x00 +#define DI_STATUS_COVER_OPENED 0x01 +#define DI_STATUS_DISK_CHANGE 0x02 +#define DI_STATUS_NO_DISK 0x03 +#define DI_STATUS_MOTOR_STOP 0x04 +#define DI_STATUS_DISK_ID_NOT_READ 0x05 + +/* drive status, error */ +#define DI_ERROR(s) ((u32)((s)&0x00ffffff)) + +#define DI_ERROR_NO_ERROR 0x000000 +#define DI_ERROR_MOTOR_STOPPED 0x020400 +#define DI_ERROR_DISK_ID_NOT_READ 0x020401 +#define DI_ERROR_MEDIUM_NOT_PRESENT 0x023a00 +#define DI_ERROR_SEEK_INCOMPLETE 0x030200 +#define DI_ERROR_UNRECOVERABLE_READ 0x031100 +#define DI_ERROR_INVALID_COMMAND 0x052000 +#define DI_ERROR_BLOCK_OUT_OF_RANGE 0x052100 +#define DI_ERROR_INVALID_FIELD 0x052400 +#define DI_ERROR_MEDIUM_CHANGED 0x062800 + +#define di_may_retry(s) ((DI_STATUS(s) == DI_STATUS_READY || \ + DI_STATUS(s) == DI_STATUS_DISK_ID_NOT_READ) \ + && \ + (DI_ERROR(s) != DI_ERROR_SEEK_INCOMPLETE)) + +/* DI Sector Size */ +#define DI_SECTOR_SHIFT 11 +#define DI_SECTOR_SIZE (1 << DI_SECTOR_SHIFT) /*2048*/ +#define DI_MAX_SECTORS 712880 + + +/* Driver Settings */ +#define DI_NAME DRV_MODULE_NAME +#define DI_MAJOR 60 + +#define DI_COMMAND_TIMEOUT 20 /* seconds */ +#define DI_COMMAND_RETRIES 10 /* times */ + +#define DI_MOTOR_OFF_TIMEOUT 10 + +#define KERNEL_SECTOR_SHIFT 9 +#define KERNEL_SECTOR_SIZE (1 << KERNEL_SECTOR_SHIFT) /*512*/ + + +/* + * Drive Information. + */ +struct di_drive_info { + u16 rev; + u16 code; + u32 date; + u8 pad[0x18]; +}; + +/* + * Disk ID. + */ +struct di_disk_id { + u8 id[32]; +}; + +/* + * An operation code. + */ +struct di_opcode { + u16 op; +#define DI_OP(id, flags) (((u8)(id)<<8)|((u8)(flags))) +#define DI_OP_ID(op) ((u8)((op)>>8)) +#define DI_OP_FLAGS(op) ((u8)(op)) + +#define DI_DIR_READ 0x00 +#define DI_DIR_WRITE DI_CR_RW +#define DI_MODE_IMMED 0x00 +#define DI_MODE_DMA DI_CR_DMA +#define DI_IGNORE_ERRORS (1<<7) + + char *name; + + u32 cmdbuf0; +}; + +/* + * Drive code container. + */ +struct di_drive_code { + u32 address; + size_t len; + void *code; +}; + +struct di_device; + +/* + * A Disk Interface command. + */ +struct di_command { + u16 opidx; + + u32 cmdbuf0; + u32 cmdbuf1; + u32 cmdbuf2; + + void *data; + size_t len; + + dma_addr_t dma_addr; + size_t dma_len; + + void *done_data; + void (*done)(struct di_command *cmd); + + u16 retries; + u16 max_retries; + + u32 result; + + struct di_device *ddev; +}; + +#define di_result_ok(result) ((result) == DI_SR_TCINT) +#define di_command_ok(cmd) (di_result_ok((cmd)->result)) + +enum { + __DI_INTEROPERABLE = 0, + __DI_MEDIA_CHANGED, + __DI_START_QUEUE, + __DI_RESETTING, + __DI_AVOID_DEBUG, +}; + +/* + * The Disk Interface device. + */ +struct di_device { + spinlock_t lock; + + int irq; + + spinlock_t io_lock; + void __iomem *io_base; + + struct di_command *cmd; + struct di_command *failed_cmd; + + struct di_command status; + u32 drive_status; + + struct gendisk *disk; + struct request_queue *queue; + spinlock_t queue_lock; + + struct request *req; + struct di_command req_cmd; + + struct di_drive_code *drive_code; + + u32 model; + unsigned long flags; +#define DI_INTEROPERABLE (1<<__DI_INTEROPERABLE) +#define DI_MEDIA_CHANGED (1<<__DI_MEDIA_CHANGED) +#define DI_START_QUEUE (1<<__DI_START_QUEUE) +#define DI_RESETTING (1<<__DI_RESETTING) +#define DI_AVOID_DEBUG (1<<__DI_AVOID_DEBUG) + + unsigned long nr_sectors; + + struct timer_list motor_off_timer; + +#ifdef CONFIG_PROC_FS + struct proc_dir_entry *proc; +#endif /* CONFIG_PROC_FS */ + + int ref_count; + + struct device *dev; +}; + + +static struct di_drive_info di_drive_info + __attribute__ ((aligned(DI_DMA_ALIGN+1))); + +/* + * We do not accept original media with this driver, as there is currently no + * general need for that. + * If you ever develop an application (a media player for example) which works + * with original media, just change di_accept_gods and recompile. + */ +static const int di_accept_gods; + +/* + * Drive firmware extensions. + * + */ + +#define DI_DRIVE_CODE_BASE 0x40d000 +#define DI_DRIVE_IRQ_VECTOR 0x00804c + +/* + * Drive 04 (20020402) firmware extensions. + */ + +#include "drive_20020402.h" + +static struct di_drive_code drive_20020402 = { + .address = DI_DRIVE_CODE_BASE, + .len = sizeof(drive_20020402_firmware), + .code = (u8 *)drive_20020402_firmware, +}; + +/* + * Drive 06 (20010608) firmware extensions. + */ + +#include "drive_20010608.h" + +static struct di_drive_code drive_20010608 = { + .address = DI_DRIVE_CODE_BASE, + .len = sizeof(drive_20010608_firmware), + .code = (u8 *)drive_20010608_firmware, +}; + +/* + * Drive 08 (20020823) firmware extensions. + */ + +#include "drive_20020823.h" + +static struct di_drive_code drive_20020823 = { + .address = DI_DRIVE_CODE_BASE, + .len = sizeof(drive_20020823_firmware), + .code = (u8 *)drive_20020823_firmware, +}; + +/* + * Panasonic Q (20010831) firmware extensions. + */ + +#include "drive_20010831.h" + +static struct di_drive_code drive_20010831 = { + .address = DI_DRIVE_CODE_BASE, + .len = sizeof(drive_20010831_firmware), + .code = (u8 *)drive_20010831_firmware, +}; + + +/* + * Drive operations table, incomplete. + * We just include here some of the available functions, in no particular + * order. + */ +#define CMDBUF(a, b, c, d) (((a)<<24)|((b)<<16)|((c)<<8)|(d)) + +static struct di_opcode di_opcodes[] = { + +#define DI_OP_NOP 0 + [DI_OP_NOP] = { + .op = DI_OP(DI_OP_NOP, 0), + .name = "NOP", + .cmdbuf0 = 0, + }, + +#define DI_OP_INQ (DI_OP_NOP+1) + [DI_OP_INQ] = { + .op = DI_OP(DI_OP_INQ, DI_DIR_READ | DI_MODE_DMA), + .name = "INQ", + .cmdbuf0 = 0x12000000, + }, + +#define DI_OP_STOPMOTOR (DI_OP_INQ+1) + [DI_OP_STOPMOTOR] = { + .op = DI_OP(DI_OP_STOPMOTOR, DI_DIR_READ | DI_MODE_IMMED), + .name = "STOPMOTOR", + .cmdbuf0 = 0xe3000000, + }, + +#define DI_OP_READDISKID (DI_OP_STOPMOTOR+1) + [DI_OP_READDISKID] = { + .op = DI_OP(DI_OP_READDISKID, DI_DIR_READ | DI_MODE_DMA), + .name = "READDISKID", + .cmdbuf0 = 0xa8000040, + }, + +#define DI_OP_READSECTOR (DI_OP_READDISKID+1) + [DI_OP_READSECTOR] = { + .op = DI_OP(DI_OP_READSECTOR, DI_DIR_READ | DI_MODE_DMA), + .name = "READSECTOR", + .cmdbuf0 = 0xa8000000, + }, + +#define DI_OP_ENABLE1 (DI_OP_READSECTOR+1) + [DI_OP_ENABLE1] = { + .op = DI_OP(DI_OP_ENABLE1, DI_DIR_READ | DI_MODE_IMMED), + .name = "MATSHITA", + .cmdbuf0 = 0, + }, + +#define DI_OP_ENABLE2 (DI_OP_ENABLE1+1) + [DI_OP_ENABLE2] = { + .op = DI_OP(DI_OP_ENABLE2, DI_DIR_READ | DI_MODE_IMMED), + .name = "DVD-GAME", + .cmdbuf0 = 0, + }, + +/* + * The following commands are available in debug mode only. + */ + +#define DI_OP_READMEM (DI_OP_ENABLE2+1) + [DI_OP_READMEM] = { + .op = DI_OP(DI_OP_READMEM, DI_DIR_READ | DI_MODE_IMMED), + .name = "READMEM", + .cmdbuf0 = 0xfe010000, + }, + +#define DI_OP_WRITEMEM (DI_OP_READMEM+1) + [DI_OP_WRITEMEM] = { + .op = DI_OP(DI_OP_WRITEMEM, DI_DIR_READ | DI_MODE_DMA), + .name = "WRITEMEM", + .cmdbuf0 = 0xfe010100, + }, + +#define DI_OP_FUNC (DI_OP_WRITEMEM+1) + [DI_OP_FUNC] = { + .op = DI_OP(DI_OP_FUNC, DI_DIR_READ | DI_MODE_IMMED), + .name = "FUNC", + .cmdbuf0 = 0xfe120000, + }, + +#define DI_OP_GETSTATUS (DI_OP_FUNC+1) + [DI_OP_GETSTATUS] = { + .op = DI_OP(DI_OP_GETSTATUS, DI_DIR_READ | DI_MODE_IMMED), + .name = "GETSTATUS", + .cmdbuf0 = 0xe0000000, + }, + +/* thanks to blackcheck for pointing this one */ +#define DI_OP_SPINMOTOR (DI_OP_GETSTATUS+1) + [DI_OP_SPINMOTOR] = { + .op = DI_OP(DI_OP_SPINMOTOR, DI_DIR_READ | DI_MODE_IMMED), + .name = "SPINMOTOR", + .cmdbuf0 = 0xfe110001, +#define DI_SPINMOTOR_MASK 0x0000ff00 +#define DI_SPINMOTOR_DOWN 0x00000000 +#define DI_SPINMOTOR_UP 0x00000100 +#define DI_SPINMOTOR_CHECKDISK 0x00008000 + }, + +/* + * The following commands are part of the firmware extensions. + */ + +#define DI_OP_SETSTATUS (DI_OP_SPINMOTOR+1) + [DI_OP_SETSTATUS] = { + .op = DI_OP(DI_OP_SETSTATUS, DI_DIR_READ | DI_MODE_IMMED), + .name = "SETSTATUS", + .cmdbuf0 = 0xee000000, +#define DI_SETSTATUS_MASK 0x00ff0000 +#define DI_SETSTATUS_SHIFT 16 + }, + +#define DI_OP_ENABLEEXTENSIONS (DI_OP_SETSTATUS+1) + [DI_OP_ENABLEEXTENSIONS] = { + .op = DI_OP(DI_OP_ENABLEEXTENSIONS, DI_DIR_READ|DI_MODE_IMMED| + DI_IGNORE_ERRORS), + .name = "ENABLEEXTENSIONS", + .cmdbuf0 = 0x55000000, +#define DI_ENABLEEXTENSIONS_MASK 0x00ff0000 +#define DI_ENABLEEXTENSIONS_SHIFT 16 + }, + +#define DI_OP_MAXOP DI_OP_ENABLEEXTENSIONS +}; + +#define DI_OP_CUSTOM ((u16)~0) + + +static void di_reset(struct di_device *ddev); +static int di_run_command(struct di_command *cmd); + +/* + * Returns the operation code related data for a command. + */ +static inline struct di_opcode *di_get_opcode(struct di_command *cmd) +{ + BUG_ON(cmd->opidx > DI_OP_MAXOP && cmd->opidx != DI_OP_CUSTOM); + + if (cmd->opidx == DI_OP_CUSTOM) + return cmd->data; + else + return &di_opcodes[cmd->opidx]; +} + +/* + * Returns the operation code for a command. + */ +static inline u16 di_op(struct di_command *cmd) +{ + return di_get_opcode(cmd)->op; +} + + +/* + * Basic initialization for all commands. + */ +static void di_op_basic(struct di_command *cmd, + struct di_device *ddev, u16 opidx) +{ + struct di_opcode *opcode; + + memset(cmd, 0, sizeof(*cmd)); + cmd->ddev = ddev; + cmd->opidx = opidx; + cmd->max_retries = cmd->retries = 0; + opcode = di_get_opcode(cmd); + if (opcode) + cmd->cmdbuf0 = opcode->cmdbuf0; +} + +/* + * Builds an "Inquiry" command. + */ +static void di_op_inq(struct di_command *cmd, + struct di_device *ddev, + struct di_drive_info *drive_info) +{ + di_op_basic(cmd, ddev, DI_OP_INQ); + cmd->cmdbuf2 = sizeof(*drive_info); + cmd->data = drive_info; + cmd->len = sizeof(*drive_info); +} + +/* + * Builds a "Stop Motor" command. + */ +static inline void di_op_stopmotor(struct di_command *cmd, + struct di_device *ddev) +{ + di_op_basic(cmd, ddev, DI_OP_STOPMOTOR); +} + +/* + * Builds a "Read Disc ID" command. + */ +static void di_op_readdiskid(struct di_command *cmd, + struct di_device *ddev, + struct di_disk_id *disk_id) +{ + di_op_basic(cmd, ddev, DI_OP_READDISKID); + cmd->cmdbuf2 = sizeof(*disk_id); + cmd->data = disk_id; + cmd->len = sizeof(*disk_id); + cmd->max_retries = cmd->retries = DI_COMMAND_RETRIES; +} + +/* + * Builds a "Read Sector" command. + */ +static void di_op_readsector(struct di_command *cmd, + struct di_device *ddev, + u32 sector, void *data, size_t len) +{ + di_op_basic(cmd, ddev, DI_OP_READSECTOR); + cmd->cmdbuf1 = sector; + cmd->cmdbuf2 = len; + cmd->data = data; + cmd->len = len; + cmd->max_retries = cmd->retries = DI_COMMAND_RETRIES; +} + +/* + * Builds the first enable command. + */ +static void di_op_enable1(struct di_command *cmd, struct di_device *ddev) +{ + di_op_basic(cmd, ddev, DI_OP_ENABLE1); + cmd->cmdbuf0 = CMDBUF(0xff, 0x01, 'M', 'A'); + cmd->cmdbuf1 = CMDBUF('T', 'S', 'H', 'I'); + cmd->cmdbuf2 = CMDBUF('T', 'A', 0x02, 0x00); +} + +/* + * Builds the second enable command. + */ +static void di_op_enable2(struct di_command *cmd, struct di_device *ddev) +{ + di_op_basic(cmd, ddev, DI_OP_ENABLE2); + cmd->cmdbuf0 = CMDBUF(0xff, 0x00, 'D', 'V'); + cmd->cmdbuf1 = CMDBUF('D', '-', 'G', 'A'); + cmd->cmdbuf2 = CMDBUF('M', 'E', 0x03, 0x00); +} + +/* + * Builds a "Read Memory" command. + * Requires debug mode enabled. + */ +static inline void di_op_readmem(struct di_command *cmd, + struct di_device *ddev) +{ + di_op_basic(cmd, ddev, DI_OP_READMEM); + cmd->cmdbuf2 = 0x00010000; +} + +/* + * Builds a "Invoke func" command. + * Requires debug mode enabled. + */ +static inline void di_op_func(struct di_command *cmd, + struct di_device *ddev, u32 address) +{ + di_op_basic(cmd, ddev, DI_OP_FUNC); + cmd->cmdbuf1 = address; + cmd->cmdbuf2 = CMDBUF('f', 'u', 'n', 'c'); +} + +/* + * Builds a "Write Memory" command. + * Requires debug mode enabled. + */ +static inline void di_op_writemem(struct di_command *cmd, + struct di_device *ddev) +{ + di_op_basic(cmd, ddev, DI_OP_WRITEMEM); +} + +/* + * Builds a "get drive status" command. + */ +static inline void di_op_getstatus(struct di_command *cmd, + struct di_device *ddev) +{ + di_op_basic(cmd, ddev, DI_OP_GETSTATUS); +} + +/* + * Builds a "spin motor" command. + * Requires debug mode enabled. + */ +static void di_op_spinmotor(struct di_command *cmd, + struct di_device *ddev, u32 flags) +{ + di_op_basic(cmd, ddev, DI_OP_SPINMOTOR); + cmd->cmdbuf0 |= (flags & DI_SPINMOTOR_MASK); +} + +/* + * Builds a "set drive status" command. + * Requires debug mode enabled. + */ +static void di_op_setstatus(struct di_command *cmd, + struct di_device *ddev, u8 status) +{ + di_op_basic(cmd, ddev, DI_OP_SETSTATUS); + cmd->cmdbuf0 |= ((status << DI_SETSTATUS_SHIFT) & DI_SETSTATUS_MASK); +} + +/* + * Builds a "enable extensions" command. + * The extended firmware will transparently disable the extensions when + * original media is found. + * Requires debug mode enabled. + */ +static void di_op_enableextensions(struct di_command *cmd, + struct di_device *ddev, u8 enable) +{ + di_op_basic(cmd, ddev, DI_OP_ENABLEEXTENSIONS); + cmd->cmdbuf0 |= ((enable << DI_ENABLEEXTENSIONS_SHIFT) & + DI_ENABLEEXTENSIONS_MASK); +} + +/* + * Builds a customized command. + */ +static inline void di_op_custom(struct di_command *cmd, + struct di_device *ddev, + struct di_opcode *opcode) +{ + di_op_basic(cmd, ddev, DI_OP_NOP); + cmd->opidx = DI_OP_CUSTOM; + cmd->data = opcode; +} + + +/* + * Returns the printable form of the status part of a drive status. + */ +static char *di_printable_status(u32 drive_status) +{ + char *s = "unknown"; + + switch (DI_STATUS(drive_status)) { + case DI_STATUS_READY: + s = "ready"; + break; + case DI_STATUS_COVER_OPENED: + s = "cover opened"; + break; + case DI_STATUS_DISK_CHANGE: + s = "disk change"; + break; + case DI_STATUS_NO_DISK: + s = "no disk"; + break; + case DI_STATUS_MOTOR_STOP: + s = "motor stop"; + break; + case DI_STATUS_DISK_ID_NOT_READ: + s = "disk id not read"; + break; + } + return s; +} + +/* + * Returns the printable form of the error part of a drive status. + */ +static char *di_printable_error(u32 drive_status) +{ + char *s = "unknown"; + + switch (DI_ERROR(drive_status)) { + case DI_ERROR_NO_ERROR: + s = "no error"; + break; + case DI_ERROR_MOTOR_STOPPED: + s = "motor stopped"; + break; + case DI_ERROR_DISK_ID_NOT_READ: + s = "disk id not read"; + break; + case DI_ERROR_MEDIUM_NOT_PRESENT: + s = "medium not present"; + break; + case DI_ERROR_SEEK_INCOMPLETE: + s = "seek incomplete"; + break; + case DI_ERROR_UNRECOVERABLE_READ: + s = "unrecoverable read"; + break; + case DI_ERROR_INVALID_COMMAND: + s = "invalid command"; + break; + case DI_ERROR_BLOCK_OUT_OF_RANGE: + s = "block out of range"; + break; + case DI_ERROR_INVALID_FIELD: + s = "invalid field"; + break; + case DI_ERROR_MEDIUM_CHANGED: + s = "medium changed"; + break; + } + + return s; +} + +/* + * Prints the given drive status, only if debug enabled. + */ +static inline void di_debug_print_drive_status(u32 drive_status) +{ + DBG("%08x, [%s, %s]\n", drive_status, + di_printable_status(drive_status), + di_printable_error(drive_status)); +} + +/* + * Prints the given drive status. + */ +static void di_print_drive_status(u32 drive_status) +{ + drv_printk(KERN_INFO, "drive_status=%08x, [%s, %s]\n", drive_status, + di_printable_status(drive_status), + di_printable_error(drive_status)); +} + +/* + * Prints the given disk identifier. + */ +static void di_print_disk_id(struct di_disk_id *disk_id) +{ + drv_printk(KERN_INFO, "disk_id = [%s]\n", disk_id->id); +} + +/* + * + * I/O. + */ + +/* + * Converts a request direction into a DMA data direction. + */ +static inline +enum dma_data_direction di_opidx_to_dma_dir(struct di_command *cmd) +{ + u16 op = di_op(cmd); + + if ((op & DI_DIR_WRITE)) + return DMA_TO_DEVICE; + else + return DMA_FROM_DEVICE; +} + +/* + * Starts a DMA transfer. + */ +static void di_start_dma_transfer_raw(struct di_device *ddev, + dma_addr_t data, size_t len, int mode) +{ + void __iomem *io_base = ddev->io_base; + u32 __iomem *sr_reg = io_base + DI_SR; + unsigned long flags; + + BUG_ON((data & DI_DMA_ALIGN) != 0 || + (len & DI_DMA_ALIGN) != 0); + + /* setup address and length of transfer */ + out_be32(io_base + DI_LENGTH, len); + out_be32(io_base + DI_MAR, data); + + /* enable the Transfer Complete interrupt */ + spin_lock_irqsave(&ddev->io_lock, flags); + out_be32(sr_reg, in_be32(sr_reg) | DI_SR_TCINTMASK); + spin_unlock_irqrestore(&ddev->io_lock, flags); + + /* start the transfer */ + out_be32(io_base + DI_CR, DI_CR_TSTART | DI_CR_DMA | (mode&0x4)); +} + +/* + * Internal. Busy-waits until a DMA transfer finishes or timeouts. + */ +static int __wait_for_dma_transfer_or_timeout(u32 __iomem *cr_reg, + int secs) +{ + unsigned long timeout = jiffies + secs*HZ; + + /* busy-wait for transfer complete */ + while ((in_be32(cr_reg) & DI_CR_TSTART) && + time_before(jiffies, timeout)) + cpu_relax(); + + return (in_be32(cr_reg) & DI_CR_TSTART) ? -EBUSY : 0; +} + +/* + * Busy-waits until DMA transfers are finished. + */ +static void di_wait_for_dma_transfer_raw(struct di_device *ddev) +{ + u32 __iomem *cr_reg = ddev->io_base + DI_CR; + u32 __iomem *sr_reg = ddev->io_base + DI_SR; + unsigned long flags; + + /* we don't want TCINTs to disturb us while waiting */ + spin_lock_irqsave(&ddev->io_lock, flags); + out_be32(sr_reg, in_be32(sr_reg) & ~DI_SR_TCINTMASK); + spin_unlock_irqrestore(&ddev->io_lock, flags); + + /* if the drive got stuck, reset it */ + if (__wait_for_dma_transfer_or_timeout(cr_reg, DI_COMMAND_TIMEOUT)) { + DBG("dvd stuck!\n"); + di_reset(ddev); + } + + /* ack and enable the Transfer Complete interrupt */ + spin_lock_irqsave(&ddev->io_lock, flags); + out_be32(sr_reg, in_be32(sr_reg) | (DI_SR_TCINT|DI_SR_TCINTMASK)); + spin_unlock_irqrestore(&ddev->io_lock, flags); + + return; +} + +/* + * Quiesces the hardware to a calm and known state. + */ +static void di_quiesce(struct di_device *ddev) +{ + void __iomem *io_base = ddev->io_base; + u32 __iomem *cr_reg = io_base + DI_CR; + u32 __iomem *sr_reg = io_base + DI_SR; + u32 __iomem *cvr_reg = io_base + DI_CVR; + u32 sr, cvr; + unsigned long flags; + + spin_lock_irqsave(&ddev->io_lock, flags); + + /* ack and mask dvd io interrupts */ + sr = in_be32(sr_reg); + sr |= DI_SR_BRKINT | DI_SR_TCINT | DI_SR_DEINT; + sr &= ~(DI_SR_BRKINTMASK | DI_SR_TCINTMASK | DI_SR_DEINTMASK); + out_be32(sr_reg, sr); + + /* ack and mask dvd cover interrupts */ + cvr = in_be32(cvr_reg); + out_be32(cvr_reg, (cvr | DI_CVR_CVRINT) & ~DI_CVR_CVRINTMASK); + + spin_unlock_irqrestore(&ddev->io_lock, flags); + + /* busy-wait for transfer complete */ + __wait_for_dma_transfer_or_timeout(cr_reg, DI_COMMAND_TIMEOUT); +} + +/* + * Command engine. + * + */ + +/* + * Outputs the command buffers, and optionally starts a transfer. + */ +static void di_prepare_command(struct di_command *cmd, int tstart) +{ + struct di_opcode *opcode = di_get_opcode(cmd); + void __iomem *io_base = cmd->ddev->io_base; + + /*DBG("buf0 = 0x%08x, buf1 = 0x%08x, buf2 = 0x%08x\n", + cmd->cmdbuf0, cmd->cmdbuf1, cmd->cmdbuf2);*/ + + out_be32(io_base + DI_CMDBUF0, cmd->cmdbuf0); + out_be32(io_base + DI_CMDBUF1, cmd->cmdbuf1); + out_be32(io_base + DI_CMDBUF2, cmd->cmdbuf2); + + cmd->ddev->drive_status = 0; + + if (tstart) + out_be32(io_base + DI_CR, DI_CR_TSTART | (opcode->op & 0x6)); +} + +static void di_command_done(struct di_command *cmd); + +/* + * Starts a command by using the immediate mode. + */ +static int di_start_command(struct di_command *cmd) +{ + struct di_device *ddev = cmd->ddev; + unsigned long flags; + int retval = 1; + + spin_lock_irqsave(&ddev->lock, flags); + + BUG_ON(ddev->cmd); + + ddev->cmd = cmd; + cmd->dma_len = 0; /* no dma here */ + di_prepare_command(cmd, 1); + + spin_unlock_irqrestore(&ddev->lock, flags); + + return retval; +} + +/* + * Starts a command by using the DMA mode. + */ +static int di_start_dma_command(struct di_command *cmd) +{ + struct di_device *ddev = cmd->ddev; + unsigned long flags; + int retval = 1; + + spin_lock_irqsave(&ddev->lock, flags); + + BUG_ON(ddev->cmd); + + ddev->cmd = cmd; + cmd->dma_len = cmd->len; + cmd->dma_addr = dma_map_single(ddev->dev, + cmd->data, cmd->len, + di_opidx_to_dma_dir(cmd)); + + di_prepare_command(cmd, 0); + di_start_dma_transfer_raw(ddev, cmd->dma_addr, cmd->dma_len, + di_op(cmd) & DI_DIR_WRITE); + + spin_unlock_irqrestore(&ddev->lock, flags); + + return retval; +} + +/* + * Completes a "get drive status" command, after a failed command. + */ +static void di_complete_getstatus(struct di_command *cmd) +{ + struct di_device *ddev = cmd->ddev; + void __iomem *io_base = ddev->io_base; + u32 __iomem *data_reg = io_base + DI_DATA; + + ddev->drive_status = in_be32(data_reg); +} + +/* + * Called after a transfer is completed. + */ +static void di_complete_transfer(struct di_device *ddev, u32 result) +{ + struct di_command *cmd; + struct di_opcode *opcode; + u32 drive_status; + unsigned long flags; + + spin_lock_irqsave(&ddev->lock, flags); + + /* do nothing if we have nothing to complete */ + cmd = ddev->cmd; + if (!cmd) { + spin_unlock_irqrestore(&ddev->lock, flags); + goto out; + } + + /* free the command slot */ + ddev->cmd = NULL; + spin_unlock_irqrestore(&ddev->lock, flags); + + /* deal with caches after a dma transfer */ + if (cmd->dma_len) { + dma_unmap_single(ddev->dev, + cmd->dma_addr, cmd->dma_len, + di_opidx_to_dma_dir(cmd)); + } + + opcode = di_get_opcode(cmd); + + /* + * If a command fails we check the drive status. Depending on that + * we may or not retry later the command. + */ + cmd->result = result; + if (!di_command_ok(cmd)) { + /* the MATSHITA command always reports failure, ignore it */ + if (DI_OP_ID(opcode->op) != DI_OP_ENABLE1) { + BUG_ON(ddev->failed_cmd != NULL); + + ddev->failed_cmd = cmd; + + /* + * Issue immediately a "get drive status" + * after a failed command. + */ + cmd = &ddev->status; + di_op_getstatus(cmd, ddev); + cmd->done = di_complete_getstatus; + di_run_command(cmd); + goto out; + } + } else { + if (cmd->retries != cmd->max_retries) { + DBG("command %s succeeded after %d retries :-)\n", + opcode->name, cmd->max_retries - cmd->retries); + } + } + + /* complete a successful command, or the MATSHITA one */ + di_command_done(cmd); + + spin_lock_irqsave(&ddev->lock, flags); + if (ddev->failed_cmd) { + cmd = ddev->failed_cmd; + ddev->failed_cmd = NULL; + spin_unlock_irqrestore(&ddev->lock, flags); + + drive_status = ddev->drive_status; + opcode = di_get_opcode(cmd); + + /* retry a previously failed command if appropiate */ + if (cmd->retries > 0) { + if (di_may_retry(drive_status)) { + DBG("command %s failed, %d retries left\n", + opcode->name, cmd->retries); + di_debug_print_drive_status(drive_status); + + cmd->retries--; + di_run_command(cmd); + goto out; + } else { + DBG("command %s failed," + " aborting due to drive status\n", + opcode->name); + } + } else { + if (!(opcode->op & DI_IGNORE_ERRORS)) + DBG("command %s failed\n", opcode->name); + } + + if (!(opcode->op & DI_IGNORE_ERRORS)) + di_print_drive_status(drive_status); + + /* complete the failed command */ + di_command_done(cmd); + + /* update the driver status */ + switch (DI_ERROR(drive_status)) { + case DI_ERROR_MOTOR_STOPPED: + case DI_ERROR_MEDIUM_NOT_PRESENT: + case DI_ERROR_MEDIUM_CHANGED: + set_bit(__DI_MEDIA_CHANGED, &ddev->flags); + break; + default: + break; + } + + } else { + spin_unlock_irqrestore(&ddev->lock, flags); + } + + /* start the block layer queue if someone requested it */ + if (test_and_clear_bit(__DI_START_QUEUE, &ddev->flags)) { + spin_lock_irqsave(&ddev->queue_lock, flags); + blk_start_queue(ddev->queue); + spin_unlock_irqrestore(&ddev->queue_lock, flags); + } + +out: + return; +} + +/* + * Calls any done hooks. + */ +static void di_command_done(struct di_command *cmd) +{ + /* if specified, call the completion routine */ + if (cmd->done) + cmd->done(cmd); +} + +/* + * Completion routine. + */ +static void di_wait_done(struct di_command *cmd) +{ + complete(cmd->done_data); +} + +/* + * Runs a command. + */ +static int di_run_command(struct di_command *cmd) +{ + struct di_opcode *opcode = di_get_opcode(cmd); + int retval; + + if (cmd->retries > cmd->max_retries) + cmd->retries = cmd->max_retries; + + if (!(opcode->op & DI_MODE_DMA)) + retval = di_start_command(cmd); + else + retval = di_start_dma_command(cmd); + return retval; +} + +/* + * Runs a command and waits. + * Might sleep if called from user context. + */ +static int di_run_command_and_wait(struct di_command *cmd) +{ + DECLARE_COMPLETION(complete); + + cmd->done_data = &complete; + cmd->done = di_wait_done; + if (di_run_command(cmd) > 0) + wait_for_completion(&complete); + return cmd->result; +} + +/* + * Interrupt handler for DI interrupts. + */ +static irqreturn_t di_irq_handler(int irq, void *dev0) +{ + struct di_device *ddev = dev0; + void __iomem *io_base = ddev->io_base; + u32 __iomem *sr_reg = io_base + DI_SR; + u32 __iomem *cvr_reg = io_base + DI_CVR; + u32 sr, cvr, reason, mask; + unsigned long flags; + + spin_lock_irqsave(&ddev->io_lock, flags); + + sr = in_be32(sr_reg); + mask = sr & (DI_SR_BRKINTMASK | DI_SR_TCINTMASK | DI_SR_DEINTMASK); + reason = sr; /* & (mask << 1); */ + if (reason) { + out_be32(sr_reg, sr | reason); + spin_unlock_irqrestore(&ddev->io_lock, flags); + + if (reason & DI_SR_TCINT) + di_complete_transfer(ddev, DI_SR_TCINT); + if (reason & DI_SR_BRKINT) { + DBG("BRKINT\n"); + di_complete_transfer(ddev, DI_SR_BRKINT); + } + if (reason & DI_SR_DEINT) + di_complete_transfer(ddev, DI_SR_DEINT); + + spin_lock_irqsave(&ddev->io_lock, flags); + } + + cvr = in_be32(cvr_reg); + mask = cvr & DI_CVR_CVRINTMASK; + reason = cvr; /* & (mask << 1); */ + if ((reason & DI_CVR_CVRINT)) { + out_be32(cvr_reg, cvr | DI_CVR_CVRINT); + set_bit(__DI_MEDIA_CHANGED, &ddev->flags); + if (test_and_clear_bit(__DI_RESETTING, &ddev->flags)) { + if (!test_bit(__DI_AVOID_DEBUG, &ddev->flags)) { + if (ddev->flags & DI_INTEROPERABLE) { + DBG("extensions loaded" + " and hopefully working\n"); + } + } + } else { + DBG("dvd cover interrupt\n"); + } + } + + spin_unlock_irqrestore(&ddev->io_lock, flags); + + return IRQ_HANDLED; +} + +/* + * Hard-resets the drive. + */ +static void di_reset(struct di_device *ddev) +{ + u32 __iomem *reset_reg = (u32 __iomem *)0xcc003024; + u32 reset; + +#define FLIPPER_RESET_DVD 0x00000004 + + /* set flags, but preserve the alien firmware flag */ + ddev->flags = (ddev->flags & DI_AVOID_DEBUG) | + DI_RESETTING | DI_MEDIA_CHANGED; + + reset = in_be32(reset_reg); + out_be32(reset_reg, (reset & ~FLIPPER_RESET_DVD) | 1); + mdelay(500); + out_be32(reset_reg, (reset | FLIPPER_RESET_DVD) | 1); + mdelay(500); + + DBG("drive reset\n"); +} + + +/* + * Misc routines. + * + */ + +/* + * Retrieves (and prints out) the laser unit model. + */ +static u32 di_retrieve_drive_model(struct di_device *ddev) +{ + struct di_command cmd; + + memset(&di_drive_info, 0, sizeof(di_drive_info)); + di_op_inq(&cmd, ddev, &di_drive_info); + di_run_command_and_wait(&cmd); + + drv_printk(KERN_INFO, "laser unit: rev=%x, code=%x, date=%x\n", + di_drive_info.rev, di_drive_info.code, + di_drive_info.date); + + ddev->model = di_drive_info.date; + return ddev->model; +} + +/* + * Gets the current drive status. + */ +static u32 di_get_drive_status(struct di_device *ddev) +{ + void __iomem *io_base = ddev->io_base; + u32 __iomem *data_reg = io_base + DI_DATA; + struct di_command cmd; + u32 drive_status; + + di_op_getstatus(&cmd, ddev); + di_run_command_and_wait(&cmd); + drive_status = in_be32(data_reg); + + return drive_status; +} + +/* + * Checks if the drive is in a ready state. + */ +static int di_is_drive_ready(struct di_device *ddev) +{ + u32 drive_status; + int result = 0; + + drive_status = di_get_drive_status(ddev); + if (DI_STATUS(drive_status) == DI_STATUS_DISK_ID_NOT_READ || + DI_STATUS(drive_status) == DI_STATUS_READY) { + result = 1; + } + + return result; +} + +/* + * + * Firmware handling. + */ + +/* + * Reads a long word from drive addressable memory. + * Requires debug mode enabled. + */ +static int di_fw_read_meml(struct di_device *ddev, + unsigned long *data, unsigned long address) +{ + void __iomem *io_base = ddev->io_base; + struct di_command cmd; + int result = -1; + + di_op_readmem(&cmd, ddev); + cmd.cmdbuf1 = address; + di_run_command_and_wait(&cmd); + if (di_command_ok(&cmd)) { + *data = in_be32(io_base + DI_DATA); + result = 0; + } + return result; +} + +/* + * Retrieves the current active interrupt handler for the drive. + * Requires debug mode enabled. + */ +static unsigned long di_fw_get_irq_handler(struct di_device *ddev) +{ + unsigned long data = 0; + + di_fw_read_meml(ddev, &data, DI_DRIVE_IRQ_VECTOR); + return data; +} + +/* + * Patches drive addressable memory. + * Requires debug mode enabled. + */ +static void di_fw_patch_mem(struct di_device *ddev, u32 address, + void *data, size_t len) +{ + struct di_command cmd; + struct di_opcode opcode; + int chunk_size; + const int max_chunk_size = 3 * sizeof(cmd.cmdbuf0); + + while (len > 0) { + /* we can write in groups of 12 bytes at max */ + if (len > max_chunk_size) + chunk_size = max_chunk_size; + else + chunk_size = len; + + /* prepare for writing to drive's memory ... */ + di_op_writemem(&cmd, ddev); + cmd.cmdbuf1 = address; + cmd.cmdbuf2 = chunk_size << 16; + di_run_command_and_wait(&cmd); + if (!di_command_ok(&cmd)) + break; + + /* ... and actually write to it */ + opcode.op = DI_OP(DI_OP_CUSTOM, DI_DIR_READ | DI_MODE_IMMED); + opcode.name = "custom write"; + di_op_custom(&cmd, ddev, &opcode); + memcpy(&cmd.cmdbuf0, data, chunk_size); + di_run_command(&cmd); + + /* + * We can't rely on drive operating as expected here, so we + * explicitly poll for end of transfer and timeout eventually. + * Anyway, we assume everything was ok. + */ + di_wait_for_dma_transfer_raw(ddev); + di_complete_transfer(ddev, DI_SR_TCINT); + + /* ok, next chunk */ + address += chunk_size; + data += chunk_size; + len -= chunk_size; + } +} + +/* + * Runs a series of patches. + * Requires debug mode enabled. + */ +static void di_fw_patch(struct di_device *ddev, + struct di_drive_code *section, int nr_sections) +{ + while (nr_sections > 0) { + di_fw_patch_mem(ddev, section->address, + section->code, section->len); + section++; + nr_sections--; + } +} + +/* + * Selects the appropiate drive code for each drive. + */ +static int di_select_drive_code(struct di_device *ddev) +{ + ddev->drive_code = NULL; + + switch (ddev->model) { + case 0x20020402: + ddev->drive_code = &drive_20020402; + break; + case 0x20010608: + ddev->drive_code = &drive_20010608; + break; + case 0x20020823: + ddev->drive_code = &drive_20020823; + break; + case 0x20010831: + ddev->drive_code = &drive_20010831; + break; + default: + drv_printk(KERN_ERR, "sorry, drive %x is not yet" + " supported\n", + di_drive_info.date); + break; + } + + return (ddev->drive_code) ? 0 : -EINVAL; +} + +/* + * + */ +static u8 parking_code[] = { + 0xa0, /* sub d0, d0 */ + 0xc4, 0xda, 0xfc, /* movb d0, (ADBCTL) */ + 0xf4, 0x74, 0x74, 0x0a, 0x08, /* mov 0x080a74, a0 */ /* fixup */ + 0xf7, 0x20, 0x4c, 0x80, /* mov a0, (0x804c) */ + 0xfe, /* rts */ +}; + +/* + * Parks (disables) any existing alien drive code. + * Requires debug mode enabled. + */ +static u32 di_park_firmware(struct di_device *ddev) +{ + struct di_command cmd; + u32 irq_handler, original_irq_handler; + u32 load_address; + + /* calculate an appropiate load address for the parking code */ + irq_handler = le32_to_cpu(di_fw_get_irq_handler(ddev)); + load_address = (irq_handler >= 0x400000) ? 0x008502 : 0x40c600; + + /* get the original interrupt handler */ + irq_handler = (ddev->model != 0x20010831) ? 0x00080A74 : 0x00080AA4; + original_irq_handler = irq_handler; + + /* fix the parking code to match our drive model */ + cpu_to_le32s(&irq_handler); + memcpy(parking_code + 6, &irq_handler, 3); + + /* load and call it */ + di_fw_patch_mem(ddev, load_address, parking_code, sizeof(parking_code)); + di_op_func(&cmd, ddev, load_address); + di_run_command_and_wait(&cmd); + + /* + * Check if the parking code did its job. + * The drive should be running now under the original irq handler. + */ + irq_handler = le32_to_cpu(di_fw_get_irq_handler(ddev)); + if (irq_handler != original_irq_handler) { + drv_printk(KERN_ERR, "parking failed!\n"); + di_reset(ddev); + } else { + DBG("parking done, irq handler = %08x\n", irq_handler); + } + + /* drive is not patched anymore here */ + clear_bit(__DI_INTEROPERABLE, &ddev->flags); + + return di_get_drive_status(ddev); +} + +/* + * Spins down the drive, immediatelly. + */ +static void di_spin_down_drive(struct di_device *ddev) +{ + struct di_command cmd; + + di_op_stopmotor(&cmd, ddev); + di_run_command_and_wait(&cmd); +} + +/* + * Enables the "debug" command set. + */ +static int di_enable_debug_commands(struct di_device *ddev) +{ + struct di_command cmd; + + /* send these two consecutive enable commands */ + di_op_enable1(&cmd, ddev); + di_run_command_and_wait(&cmd); + di_op_enable2(&cmd, ddev); + return di_run_command_and_wait(&cmd); +} + +/* + * Tries to determine if firmware extensions are currently installed. + * Requires debug mode enabled. + */ +static int di_has_alien_drive_code(struct di_device *ddev) +{ + unsigned long address; + int result = 1; + + /* + * We assume that alien drive code is in place if the interrupt handler + * is not pointing to ROM address space. + */ + address = di_fw_get_irq_handler(ddev); + address = le32_to_cpu(address); + if (address) { + if ((address & 0xffff0000) == 0x00080000) + result = 0; + } + return result; +} + +/* + * Enables some workarounds and/or special behaviours depending on + * the drive firmware found. + * Requires debug mode enabled. + */ +static void di_init_alien_drive_code_quirks(struct di_device *ddev) +{ + unsigned long fingerprint; + + /* + * Test if a xenogc/duoq is installed. + */ + if (!di_fw_read_meml(ddev, &fingerprint, 0x40c60a)) { + if (fingerprint == 0xf710fff7) { + drv_printk(KERN_INFO, "drivechip: xenogc/duoq\n"); + set_bit(__DI_AVOID_DEBUG, &ddev->flags); + } + } +} + +/* + * Configures the drive to accept DVD-R and DVD+R media. + */ +static void di_make_interoperable(struct di_device *ddev) +{ + struct di_command cmd; + + if (!ddev->drive_code || test_bit(__DI_AVOID_DEBUG, &ddev->flags)) + return; + + /* calm things down */ + di_spin_down_drive(ddev); + + /* disable any alien drive code */ + di_enable_debug_commands(ddev); + di_park_firmware(ddev); + + /* re-enable debug commands */ + di_enable_debug_commands(ddev); + + /* load our own drive code extensions */ + di_fw_patch(ddev, ddev->drive_code, 1); + + /* + * The drive will become interoperable now. + * Here we go...! + */ + set_bit(__DI_INTEROPERABLE, &ddev->flags); + di_op_func(&cmd, ddev, DI_DRIVE_CODE_BASE); + di_run_command_and_wait(&cmd); + + /* this checks if drive is still working... */ + di_get_drive_status(ddev); +} + +/* + * Ensures that the debug features of the drive firmware are working. + */ +static int di_probe_debug_features(struct di_device *ddev) +{ + int result; + + /* do nothing on unknown drive models */ + if (!ddev->drive_code) + return -EINVAL; + + result = di_enable_debug_commands(ddev); + if (!di_result_ok(result)) { + DBG("uhmm, debug commands seem banned...\n"); + DBG("... let's hard reset the drive!\n"); + + di_reset(ddev); + result = di_enable_debug_commands(ddev); + } + + return di_result_ok(result) ? 0 : -EINVAL; +} + +/* + * Probes the existing firmware features and determines the best operation + * mode. + */ +static void di_probe_firmware(struct di_device *ddev) +{ + if (di_probe_debug_features(ddev)) { + /* we can't use debug features, thus try to avoid them */ + drv_printk(KERN_INFO, "firmware: debug features do not work," + " using standard command set\n"); + set_bit(__DI_AVOID_DEBUG, &ddev->flags); + } else { + if (di_has_alien_drive_code(ddev)) { + drv_printk(KERN_INFO, "firmware: patched drive\n"); + /* enable some workarounds if required */ + di_init_alien_drive_code_quirks(ddev); + } else { + drv_printk(KERN_INFO, "firmware: unpatched drive\n"); + } + } +} + +/* + * Stops the drive's motor, according to a previous schedule. + */ +static void di_motor_off(unsigned long ddev0) +{ + struct di_device *ddev = (struct di_device *)ddev0; + struct di_command *cmd; + unsigned long flags; + + /* postpone a bit the motor off if there are pending commands */ + spin_lock_irqsave(&ddev->lock, flags); + if (!ddev->cmd) { + ddev->cmd = cmd = &ddev->status; + spin_unlock_irqrestore(&ddev->lock, flags); + di_op_stopmotor(cmd, ddev); + di_prepare_command(cmd, 1); + } else { + spin_unlock_irqrestore(&ddev->lock, flags); + mod_timer(&ddev->motor_off_timer, jiffies + 1*HZ); + } +} + +/* + * Cancels a previously scheduled motor off. + */ +static inline void di_cancel_motor_off(struct di_device *ddev) +{ + del_timer(&ddev->motor_off_timer); +} + +/* + * Stops the drive's motor after the specified amount of seconds has elapsed. + */ +static void di_schedule_motor_off(struct di_device *ddev, unsigned int secs) +{ + del_timer(&ddev->motor_off_timer); + ddev->motor_off_timer.expires = jiffies + secs*HZ; + ddev->motor_off_timer.data = (unsigned long)ddev; + add_timer(&ddev->motor_off_timer); +} + +/* + * Spins up the drive. + */ +static void di_spin_up_drive(struct di_device *ddev, u8 enable_extensions) +{ + struct di_command cmd; + u32 drive_status; + unsigned int attempts = 2; + + /* do nothing if the drive is already spinning */ + if (di_is_drive_ready(ddev)) + goto out; + + if (test_bit(__DI_AVOID_DEBUG, &ddev->flags)) { + /* don't use debug commands, let's hope a drivechip is there */ + di_reset(ddev); + } else { + while (attempts-- > 0) { + if (!test_bit(__DI_INTEROPERABLE, &ddev->flags)) + di_make_interoperable(ddev); + + /* + * We only re-enable the extensions if the drive is not + * in a pending read disk id state. Otherwise, we assume + * that the drive has already accepted the disk. + */ + drive_status = di_get_drive_status(ddev); + if (DI_STATUS(drive_status) != + DI_STATUS_DISK_ID_NOT_READ) { + di_op_enableextensions(&cmd, ddev, + enable_extensions); + di_run_command_and_wait(&cmd); + } + + /* the spin motor command requires the debug mode */ + di_enable_debug_commands(ddev); + di_op_spinmotor(&cmd, ddev, DI_SPINMOTOR_UP); + di_run_command_and_wait(&cmd); + + if (!ddev->drive_status) { + di_op_setstatus(&cmd, ddev, + DI_STATUS_DISK_ID_NOT_READ+1); + cmd.cmdbuf0 |= 0x00000300; /* XXX cheqmate */ + di_run_command_and_wait(&cmd); + } else { + if (DI_ERROR(ddev->drive_status) != + DI_ERROR_MEDIUM_NOT_PRESENT) + di_reset(ddev); + continue; + } + break; + } + } +out: + return; +} + + +/* + * Block layer hooks. + * + */ + +static int di_read_toc(struct di_device *ddev) +{ + static struct di_disk_id disk_id + __attribute__ ((aligned(DI_DMA_ALIGN+1))); + struct di_command cmd; + int accepted_media = 0; + int retval = 0; + const u8 enable_extensions = 1; + + di_cancel_motor_off(ddev); + + /* spin up the drive if needed */ + if ((ddev->flags & DI_MEDIA_CHANGED)) + di_spin_up_drive(ddev, enable_extensions); + + /* check that disk id can be read and that the media is appropiate */ + memset(&disk_id, 0, sizeof(disk_id)); + di_op_readdiskid(&cmd, ddev, &disk_id); + di_run_command_and_wait(&cmd); + if (di_command_ok(&cmd)) { + if (disk_id.id[0] && memcmp(disk_id.id, "GBL", 3) && + !di_accept_gods) { + di_print_disk_id(&disk_id); + drv_printk(KERN_ERR, "sorry, gamecube media" + " support is disabled\n"); + } else { + accepted_media = 1; + } + } else { + set_bit(__DI_MEDIA_CHANGED, &ddev->flags); + } + + if (accepted_media) { + /* + * This is currently hardcoded. Scream|CT got this number + * by reading up to where the lens physically allowed. + * + * This is currently causing us problems. + * For example, recent 'mount' versions will read 4k from + * the end of the device when guessing filesystem types. + * The end of device we are reporting is not the real one, + * so the drive will fail to read that part if it was not + * burned. + * + * As a temporary solution, specify always a filesystem + * type when using mount, or fill the whole disk when + * burning. + */ + ddev->nr_sectors = DI_MAX_SECTORS; /* in DVD sectors */ + clear_bit(__DI_MEDIA_CHANGED, &ddev->flags); + + DBG("media ready for operation\n"); + } else { + ddev->nr_sectors = 0; + retval = -ENOMEDIUM; + + di_spin_down_drive(ddev); + + DBG("media NOT ready\n"); + } + + /* transform to kernel sectors */ + ddev->nr_sectors <<= (DI_SECTOR_SHIFT - KERNEL_SECTOR_SHIFT); + set_capacity(ddev->disk, ddev->nr_sectors); + + return retval; +} + + +static void di_request_done(struct di_command *cmd) +{ + struct di_device *ddev = cmd->ddev; + struct request *req; + unsigned long flags; + int error = (cmd->result & DI_SR_TCINT) ? 0 : -EIO; + + spin_lock_irqsave(&ddev->lock, flags); + req = ddev->req; + ddev->req = NULL; + spin_unlock_irqrestore(&ddev->lock, flags); + + if (req) { + spin_lock_irqsave(&ddev->queue_lock, flags); + __blk_end_request_cur(req, error); + blk_start_queue(ddev->queue); + spin_unlock_irqrestore(&ddev->queue_lock, flags); + } +} + +static void di_do_request(struct request_queue *q) +{ + struct di_device *ddev = q->queuedata; + struct di_command *cmd = &ddev->req_cmd; + struct request *req; + unsigned long start; + unsigned long flags; + size_t len; + int error; + + req = blk_peek_request(q); + while (req) { + spin_lock_irqsave(&ddev->lock, flags); + + if (ddev->req || ddev->cmd) { + blk_stop_queue(q); + if (ddev->cmd) + set_bit(__DI_START_QUEUE, &ddev->flags); + spin_unlock_irqrestore(&ddev->lock, flags); + break; + } + + blk_start_request(req); + error = -EIO; + + if (!blk_fs_request(req)) + goto done; + + /* it doesn't make sense to write to this device */ + if (rq_data_dir(req) == WRITE) { + drv_printk(KERN_ERR, "write attempted\n"); + goto done; + } + + /* it is not a good idea to open the lid ... */ + if ((ddev->flags & DI_MEDIA_CHANGED)) { + drv_printk(KERN_ERR, "media changed, aborting\n"); + goto done; + } + + /* keep our reads within limits */ + if (blk_rq_pos(req) + + blk_rq_cur_sectors(req) > ddev->nr_sectors) { + drv_printk(KERN_ERR, "reading past end\n"); + goto done; + } + + ddev->req = req; + spin_unlock_irqrestore(&ddev->lock, flags); + + /* launch the corresponding read sector command */ + start = blk_rq_pos(req) << KERNEL_SECTOR_SHIFT; + len = blk_rq_cur_bytes(req); + + di_op_readsector(cmd, ddev, start >> 2, + req->buffer, len); + cmd->done_data = cmd; + cmd->done = di_request_done; + di_run_command(cmd); + error = 0; + break; + done: + spin_unlock_irqrestore(&ddev->lock, flags); + if (!__blk_end_request_cur(req, error)) + req = blk_peek_request(q); + } +} + +/* + * Block device hooks. + * + */ + +static int di_open(struct block_device *bdev, fmode_t mode) +{ + struct di_device *ddev = bdev->bd_disk->private_data; + struct di_command *cmd; + DECLARE_COMPLETION(complete); + unsigned long flags; + int retval = 0; + + /* this is a read only device */ + if (mode & FMODE_WRITE) { + retval = -EROFS; + goto out; + } + + /* + * If we have a pending command, that's a previously scheduled + * motor off. Wait for it to terminate before going on. + */ + spin_lock_irqsave(&ddev->lock, flags); + if (ddev->cmd && ddev->ref_count == 0) { + cmd = ddev->cmd; + cmd->done_data = &complete; + cmd->done = di_wait_done; + spin_unlock_irqrestore(&ddev->lock, flags); + wait_for_completion(&complete); + } else { + spin_unlock_irqrestore(&ddev->lock, flags); + } + + /* this will take care of validating the media */ + check_disk_change(bdev); + if (!ddev->nr_sectors) { + retval = -ENOMEDIUM; + goto out; + } + + spin_lock_irqsave(&ddev->queue_lock, flags); + + /* honor exclusive open mode */ + if (ddev->ref_count == -1 || + (ddev->ref_count && (mode & FMODE_EXCL))) { + retval = -EBUSY; + goto out_unlock; + } + + if ((mode & FMODE_EXCL)) + ddev->ref_count = -1; + else + ddev->ref_count++; + +out_unlock: + spin_unlock_irqrestore(&ddev->queue_lock, flags); +out: + return retval; + +} + +static int di_release(struct gendisk *disk, fmode_t mode) +{ + struct di_device *ddev = disk->private_data; + unsigned long flags; + + spin_lock_irqsave(&ddev->queue_lock, flags); + + if (ddev->ref_count > 0) + ddev->ref_count--; + else + ddev->ref_count = 0; + + spin_unlock_irqrestore(&ddev->queue_lock, flags); + + if (ddev->ref_count == 0) { + /* + * We do not immediately stop the motor, which saves us + * a spin down/spin up in applications that re-open quickly + * the device, like mount when -t is not specified. + */ + di_schedule_motor_off(ddev, 1); + + set_bit(__DI_MEDIA_CHANGED, &ddev->flags); + } + + return 0; +} + +static int di_revalidate_disk(struct gendisk *disk) +{ + struct di_device *ddev = disk->private_data; + di_read_toc(ddev); + return 0; +} + +static int di_media_changed(struct gendisk *disk) +{ + struct di_device *ddev = disk->private_data; + return (ddev->flags & DI_MEDIA_CHANGED) ? 1 : 0; +} + +static int di_ioctl(struct block_device *bdev, fmode_t mode, + unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case CDROMMULTISESSION: + /* struct cdrom_multisession */ + break; + case CDROMSTART: + break; + case CDROMSTOP: + break; + case CDROMREADTOCHDR: + /* struct cdrom_tochdr */ + break; + case CDROMREADTOCENTRY: + /* struct cdrom_tocentry */ + break; + case CDROMREADMODE2: + case CDROMREADMODE1: + case CDROMREADRAW: + /* struct cdrom_read (1-2048, 2-2336,RAW-2352) */ + break; + case CDROM_GET_MCN: + /* retrieve the universal product code */ + /* struct cdrom_mcn */ + break; + case CDROMRESET: + /* reset the drive */ + break; + case BLKRAGET: + case BLKFRAGET: + case BLKROGET: + case BLKBSZGET: + case BLKSSZGET: + case BLKSECTGET: + case BLKGETSIZE: + case BLKGETSIZE64: + case BLKFLSBUF: + return ioctl_by_bdev(bdev, cmd, arg); + default: + return -EINVAL; + } + return -EINVAL; +} + +static struct block_device_operations di_fops = { + .owner = THIS_MODULE, + .open = di_open, + .release = di_release, + .revalidate_disk = di_revalidate_disk, + .media_changed = di_media_changed, + .ioctl = di_ioctl, +}; + +/* + * Setup routines. + * + */ + +static int di_init_irq(struct di_device *ddev) +{ + void __iomem *io_base = ddev->io_base; + u32 __iomem *sr_reg = io_base + DI_SR; + u32 __iomem *cvr_reg = io_base + DI_CVR; + u32 sr, cvr; + unsigned long flags; + int retval; + + init_timer(&ddev->motor_off_timer); + ddev->motor_off_timer.function = + (void (*)(unsigned long))di_motor_off; + + ddev->flags = 0; + set_bit(__DI_MEDIA_CHANGED, &ddev->flags); + + /* calm down things a bit first */ + di_quiesce(ddev); + + /* request interrupt */ + retval = request_irq(ddev->irq, di_irq_handler, 0, + DRV_MODULE_NAME, ddev); + if (retval) { + drv_printk(KERN_ERR, "request of irq%d failed\n", ddev->irq); + goto out; + } + + spin_lock_irqsave(&ddev->io_lock, flags); + + sr = in_be32(sr_reg); + sr |= DI_SR_BRKINT | DI_SR_TCINT | DI_SR_DEINT; + sr |= DI_SR_BRKINTMASK | DI_SR_TCINTMASK | DI_SR_DEINTMASK; + out_be32(sr_reg, sr); + + cvr = in_be32(cvr_reg); + out_be32(cvr_reg, cvr | DI_CVR_CVRINT | DI_CVR_CVRINTMASK); + + spin_unlock_irqrestore(&ddev->io_lock, flags); + + di_retrieve_drive_model(ddev); + di_select_drive_code(ddev); + + di_probe_firmware(ddev); + + di_schedule_motor_off(ddev, DI_MOTOR_OFF_TIMEOUT); + +out: + return retval; +} + +static void di_exit_irq(struct di_device *ddev) +{ + /* stop DVD motor */ + di_cancel_motor_off(ddev); + di_spin_down_drive(ddev); + + di_quiesce(ddev); + + free_irq(ddev->irq, ddev); +} + +static int di_init_blk_dev(struct di_device *ddev) +{ + struct gendisk *disk; + struct request_queue *queue; + int retval; + + spin_lock_init(&ddev->lock); + spin_lock_init(&ddev->io_lock); + + ddev->ref_count = 0; + + retval = register_blkdev(DI_MAJOR, DI_NAME); + if (retval) { + drv_printk(KERN_ERR, "error registering major %d\n", DI_MAJOR); + goto err_register_blkdev; + } + + retval = -ENOMEM; + spin_lock_init(&ddev->queue_lock); + queue = blk_init_queue(di_do_request, &ddev->queue_lock); + if (!queue) { + drv_printk(KERN_ERR, "error initializing queue\n"); + goto err_blk_init_queue; + } + + blk_queue_logical_block_size(queue, DI_SECTOR_SIZE); + blk_queue_dma_alignment(queue, DI_DMA_ALIGN); + blk_queue_max_phys_segments(queue, 1); + blk_queue_max_hw_segments(queue, 1); + queue->queuedata = ddev; + ddev->queue = queue; + + disk = alloc_disk(1); + if (!disk) { + drv_printk(KERN_ERR, "error allocating disk\n"); + goto err_alloc_disk; + } + + disk->major = DI_MAJOR; + disk->first_minor = 0; + disk->fops = &di_fops; + strcpy(disk->disk_name, DI_NAME); + disk->queue = ddev->queue; + disk->private_data = ddev; + ddev->disk = disk; + + set_disk_ro(ddev->disk, 1); + add_disk(ddev->disk); + + retval = 0; + goto out; + +err_alloc_disk: + blk_cleanup_queue(ddev->queue); +err_blk_init_queue: + unregister_blkdev(DI_MAJOR, DI_NAME); +err_register_blkdev: +out: + return retval; +} + +static void di_exit_blk_dev(struct di_device *ddev) +{ + if (ddev->disk) { + del_gendisk(ddev->disk); + put_disk(ddev->disk); + } + if (ddev->queue) + blk_cleanup_queue(ddev->queue); + unregister_blkdev(DI_MAJOR, DI_NAME); +} + +static int di_init_proc(struct di_device *ddev) +{ +#ifdef CONFIG_PROC_FS +#endif /* CONFIG_PROC_FS */ + return 0; +} + +static void di_exit_proc(struct di_device *ddev) +{ +#ifdef CONFIG_PROC_FS +#endif /* CONFIG_PROC_FS */ +} + +static int di_init(struct di_device *ddev, struct resource *mem, int irq) +{ + int retval; + + ddev->io_base = ioremap(mem->start, mem->end - mem->start + 1); + ddev->irq = irq; + + retval = di_init_blk_dev(ddev); + if (!retval) { + retval = di_init_irq(ddev); + if (retval) + di_exit_blk_dev(ddev); + else + di_init_proc(ddev); + } + return retval; +} + +static void di_exit(struct di_device *ddev) +{ + di_exit_blk_dev(ddev); + di_exit_irq(ddev); + di_exit_proc(ddev); + if (ddev->io_base) { + iounmap(ddev->io_base); + ddev->io_base = NULL; + } +} + +/* + * Driver model helper routines. + * + */ + +static int di_do_probe(struct device *dev, + struct resource *mem, int irq) +{ + struct di_device *ddev; + int retval; + + ddev = kzalloc(sizeof(*ddev), GFP_KERNEL); + if (!ddev) { + drv_printk(KERN_ERR, "failed to allocate di_device\n"); + return -ENOMEM; + } + dev_set_drvdata(dev, ddev); + ddev->dev = dev; + + retval = di_init(ddev, mem, irq); + if (retval) { + dev_set_drvdata(dev, NULL); + kfree(ddev); + } + return retval; +} + +static int di_do_remove(struct device *dev) +{ + struct di_device *ddev = dev_get_drvdata(dev); + + if (ddev) { + di_exit(ddev); + dev_set_drvdata(dev, NULL); + kfree(ddev); + return 0; + } + return -ENODEV; +} + +static int di_do_shutdown(struct device *dev) +{ + struct di_device *ddev = dev_get_drvdata(dev); + + if (ddev) + di_quiesce(ddev); + return 0; +} + +/* + * OF platform driver hooks. + * + */ + +static int __init di_of_probe(struct of_device *odev, + const struct of_device_id *match) +{ + struct resource res; + int retval; + + retval = of_address_to_resource(odev->node, 0, &res); + if (retval) { + drv_printk(KERN_ERR, "no io memory range found\n"); + return -ENODEV; + } + + return di_do_probe(&odev->dev, + &res, irq_of_parse_and_map(odev->node, 0)); +} + +static int __exit di_of_remove(struct of_device *odev) +{ + return di_do_remove(&odev->dev); +} + +static int di_of_shutdown(struct of_device *odev) +{ + return di_do_shutdown(&odev->dev); +} + + +static struct of_device_id di_of_match[] = { + { .compatible = "nintendo,flipper-disk" }, + { }, +}; + +MODULE_DEVICE_TABLE(of, di_of_match); + +static struct of_platform_driver di_of_driver = { + .owner = THIS_MODULE, + .name = DRV_MODULE_NAME, + .match_table = di_of_match, + .probe = di_of_probe, + .remove = di_of_remove, + .shutdown = di_of_shutdown, +}; + +/* + * Module interface hooks. + * + */ + +static int __init di_init_module(void) +{ + drv_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION, + di_driver_version); + + return of_register_platform_driver(&di_of_driver); +} + +static void __exit di_exit_module(void) +{ + of_unregister_platform_driver(&di_of_driver); +} + +module_init(di_init_module); +module_exit(di_exit_module); + +MODULE_AUTHOR(DRV_AUTHOR); +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_LICENSE("GPL"); diff --git a/drivers/block/gcn-sd.c b/drivers/block/gcn-sd.c new file mode 100644 index 0000000..7e55100 --- /dev/null +++ b/drivers/block/gcn-sd.c @@ -0,0 +1,1713 @@ +/* + * drivers/block/gcn-sd.c + * + * MMC/SD card block driver for the Nintendo GameCube/Wii + * Copyright (C) 2004-2009 The GameCube Linux Team + * Copyright (C) 2004,2005 Rob Reylink + * Copyright (C) 2005 Todd Jeffreys + * Copyright (C) 2005,2006,2007,2008,2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +/* + * This is a block device driver for the Nintendo SD Card Adapter (DOL-019) + * and compatible hardware. + * The driver has been tested with SPI-enabled MMC cards and SD cards. + * + * The following table shows the device major and minors needed to access + * MMC/SD cards: + * + * +------+-------------+-------+-------+ + * | Slot | Target | Major | Minor | + * +======+=============+=======+=======+ + * | A | disk | 61 | 0 | + * | A | partition 1 | 61 | 1 | + * | A | partition 2 | 61 | 2 | + * | A | partition 3 | 61 | 3 | + * | A | partition 4 | 61 | 4 | + * | A | partition 5 | 61 | 5 | + * | A | partition 6 | 61 | 6 | + * | A | partition 7 | 61 | 7 | + * +------+-------------+-------+-------+ + * | B | disk | 61 | 8 | + * | B | partition 1 | 61 | 9 | + * | B | partition 2 | 61 | 10 | + * | B | partition 3 | 61 | 11 | + * | B | partition 4 | 61 | 12 | + * | B | partition 5 | 61 | 13 | + * | B | partition 6 | 61 | 14 | + * | B | partition 7 | 61 | 15 | + * +------+-------------+-------+-------+ + * + * For example, run "mknod /dev/gcnsdb1 b 61 9" to create a device file + * to access the 1st partition on the card inserted in memcard slot B. + * + */ + +#define SD_DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * The existing Linux MMC layer does not support SPI operation yet. + * Anyway, we try to recycle here some common code. + */ +#include +#include +#include +#include + +#include + +#define DRV_MODULE_NAME "gcn-sd" +#define DRV_DESCRIPTION "MMC/SD card block driver for the Nintendo GameCube/Wii" +#define DRV_AUTHOR "Rob Reylink, " \ + "Todd Jeffreys, " \ + "Albert Herranz" + +static char sd_driver_version[] = "4.1i"; + +#define sd_printk(level, format, arg...) \ + printk(level DRV_MODULE_NAME ": " format , ## arg) + +#ifdef SD_DEBUG +# define DBG(fmt, args...) \ + printk(KERN_ERR "%s: " fmt, __func__ , ## args) +#else +# define DBG(fmt, args...) +#endif + + +/* + * + * EXI related definitions. + */ +#define SD_SLOTA_CHANNEL 0 /* EXI0xxx */ +#define SD_SLOTA_DEVICE 0 /* chip select, EXI0CSB0 */ + +#define SD_SLOTB_CHANNEL 1 /* EXI1xxx */ +#define SD_SLOTB_DEVICE 0 /* chip select, EXI1CSB0 */ + +#define SD_SPI_CLK 16000000 +#define SD_SPI_CLK_IDX EXI_CLK_16MHZ + + +/* + * + * MMC/SD related definitions. + */ + +/* cycles in 8 clock units */ +#define SD_IDLE_CYCLES 80 +#define SD_FINISH_CYCLES 8 + +/* several times in 8 clock units */ +#define MMC_SPI_N_CR 8 /* card response time */ + +/* data start and stop tokens */ +#define MMC_SPI_TOKEN_START_SINGLE_BLOCK_READ 0xfe +#define MMC_SPI_TOKEN_START_MULTIPLE_BLOCK_READ 0xfe +#define MMC_SPI_TOKEN_START_SINGLE_BLOCK_WRITE 0xfe +#define MMC_SPI_TOKEN_START_MULTIPLE_BLOCK_WRITE 0xfc +#define MMC_SPI_TOKEN_STOP_MULTIPLE_BLOCK_WRITE 0xfd + +/* data response */ +#define DR_SPI_MASK 0x1f +#define DR_SPI_DATA_ACCEPTED 0x05 +#define DR_SPI_DATA_REJECTED_CRC_ERROR 0x0b +#define DR_SPI_DATA_REJECTED_WRITE_ERROR 0x0d + +/* this is still a missing command in the current MMC framework ... */ +#define MMC_READ_OCR 58 + +/* + * OCR Bit positions to 10s of Vdd mV. + */ +static const unsigned short mmc_ocr_bit_to_vdd[] = { + 150, 155, 160, 165, 170, 180, 190, 200, + 210, 220, 230, 240, 250, 260, 270, 280, + 290, 300, 310, 320, 330, 340, 350, 360 +}; + +static const unsigned int tran_exp[] = { + 10000, 100000, 1000000, 10000000, + 0, 0, 0, 0 +}; + +static const unsigned char tran_mant[] = { + 0, 10, 12, 13, 15, 20, 25, 30, + 35, 40, 45, 50, 55, 60, 70, 80, +}; + +static const unsigned int tacc_exp[] = { + 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, +}; + +static const unsigned int tacc_mant[] = { + 0, 10, 12, 13, 15, 20, 25, 30, + 35, 40, 45, 50, 55, 60, 70, 80, +}; + +/* + * + * Driver settings. + */ +#define MMC_SHIFT 3 /* 8 partitions */ + +#define SD_MAJOR 61 +#define SD_NAME "gcnsd" + +#define KERNEL_SECTOR_SHIFT 9 +#define KERNEL_SECTOR_SIZE (1 << KERNEL_SECTOR_SHIFT) /*512 */ + +enum { + __SD_MEDIA_CHANGED = 0, + __SD_BAD_CARD, +}; + + +/* + * Raw MMC/SD command. + */ +struct sd_command { + u8 cmd; + u32 arg; + u8 crc; +} __attribute__ ((__packed__)); /* do not add padding, please */ + +/* + * MMC/SD host. + * + * We have one host for each memory card slot. And a host can only drive a + * single card each time. + */ +struct sd_host { + spinlock_t lock; + + int refcnt; + unsigned long flags; +#define SD_MEDIA_CHANGED (1<<__SD_MEDIA_CHANGED) +#define SD_BAD_CARD (1<<__SD_BAD_CARD) + + /* card related info */ + struct mmc_card card; + + /* timeouts in 8 clock cycles */ + unsigned long read_timeout; + unsigned long write_timeout; + + /* operations condition register */ + u32 ocr_avail; /* just 3.3V for the GameCube */ + u32 ocr; + + /* last card response */ + u8 resp; + + /* frequency */ + unsigned int clock; + u8 exi_clock; + + /* command buffer */ + struct sd_command cmd; + + spinlock_t queue_lock; + struct request_queue *queue; + + struct gendisk *disk; + + struct task_struct *io_thread; + struct mutex io_mutex; + + struct exi_device *exi_device; +}; + +static void sd_kill(struct sd_host *host); + + +static void sd_card_set_bad(struct sd_host *host) +{ + set_bit(__SD_BAD_CARD, &host->flags); +} + +static int sd_card_is_bad(struct sd_host *host) +{ + return test_bit(__SD_BAD_CARD, &host->flags); +} + +/* + * + * MMC/SD data structures manipulation. + */ + +/* + * FIXME: use a faster method (table) + * (the in-kernel crc 16 (ccitt crc) tables seem not compatible with us) + */ +static u16 crc_xmodem_update(u16 crc, u8 data) +{ + int i; + + crc = crc ^ ((u16) data << 8); + + for (i = 0; i < 8; i++) { + if (crc & 0x8000) + crc = (crc << 1) ^ 0x1021; + else + crc <<= 1; + } + + return crc; +} + +#define UNSTUFF_BITS(resp, start, size) \ + ({ \ + const int __size = size; \ + const u32 __mask = (__size < 32 ? 1 << __size : 0) - 1; \ + const int __off = 3 - ((start) / 32); \ + const int __shft = (start) & 31; \ + u32 __res; \ + \ + __res = resp[__off] >> __shft; \ + if (__size + __shft > 32) \ + __res |= resp[__off-1] << ((32 - __shft) % 32); \ + __res & __mask; \ + }) + +/* + * Given the decoded CSD structure, decode the raw CID to our CID structure. + */ +static void mmc_decode_cid(struct mmc_card *card) +{ + u32 *resp = card->raw_cid; + + memset(&card->cid, 0, sizeof(struct mmc_cid)); + + if (mmc_card_sd(card)) { + card->cid.manfid = UNSTUFF_BITS(resp, 120, 8); + card->cid.oemid = UNSTUFF_BITS(resp, 104, 16); + card->cid.prod_name[0] = UNSTUFF_BITS(resp, 96, 8); + card->cid.prod_name[1] = UNSTUFF_BITS(resp, 88, 8); + card->cid.prod_name[2] = UNSTUFF_BITS(resp, 80, 8); + card->cid.prod_name[3] = UNSTUFF_BITS(resp, 72, 8); + card->cid.prod_name[4] = UNSTUFF_BITS(resp, 64, 8); + card->cid.hwrev = UNSTUFF_BITS(resp, 60, 4); + card->cid.fwrev = UNSTUFF_BITS(resp, 56, 4); + card->cid.serial = UNSTUFF_BITS(resp, 24, 32); + card->cid.year = UNSTUFF_BITS(resp, 12, 8); + card->cid.month = UNSTUFF_BITS(resp, 8, 4); + card->cid.year += 2000; + } else { + /* + * The selection of the format here is guesswork based upon + * information people have sent to date. + */ + switch (card->csd.mmca_vsn) { + case 0: /* MMC v1.0 - v1.2 */ + case 1: /* MMC v1.4 */ + card->cid.manfid = UNSTUFF_BITS(resp, 104, 24); + card->cid.prod_name[0] = UNSTUFF_BITS(resp, 96, 8); + card->cid.prod_name[1] = UNSTUFF_BITS(resp, 88, 8); + card->cid.prod_name[2] = UNSTUFF_BITS(resp, 80, 8); + card->cid.prod_name[3] = UNSTUFF_BITS(resp, 72, 8); + card->cid.prod_name[4] = UNSTUFF_BITS(resp, 64, 8); + card->cid.prod_name[5] = UNSTUFF_BITS(resp, 56, 8); + card->cid.prod_name[6] = UNSTUFF_BITS(resp, 48, 8); + card->cid.hwrev = UNSTUFF_BITS(resp, 44, 4); + card->cid.fwrev = UNSTUFF_BITS(resp, 40, 4); + card->cid.serial = UNSTUFF_BITS(resp, 16, 24); + card->cid.month = UNSTUFF_BITS(resp, 12, 4); + card->cid.year = UNSTUFF_BITS(resp, 8, 4); + card->cid.year += 1997; + break; + case 2: /* MMC v2.0 - v2.2 */ + case 3: /* MMC v3.1 - v3.3 */ + card->cid.manfid = UNSTUFF_BITS(resp, 120, 8); + card->cid.oemid = UNSTUFF_BITS(resp, 104, 16); + card->cid.prod_name[0] = UNSTUFF_BITS(resp, 96, 8); + card->cid.prod_name[1] = UNSTUFF_BITS(resp, 88, 8); + card->cid.prod_name[2] = UNSTUFF_BITS(resp, 80, 8); + card->cid.prod_name[3] = UNSTUFF_BITS(resp, 72, 8); + card->cid.prod_name[4] = UNSTUFF_BITS(resp, 64, 8); + card->cid.prod_name[5] = UNSTUFF_BITS(resp, 56, 8); + card->cid.serial = UNSTUFF_BITS(resp, 16, 32); + card->cid.month = UNSTUFF_BITS(resp, 12, 4); + card->cid.year = UNSTUFF_BITS(resp, 8, 4); + card->cid.year += 1997; + break; + default: + sd_printk(KERN_ERR, "card has unknown MMCA" + " version %d\n", card->csd.mmca_vsn); + break; + } + } +} + +/* + * Given a 128-bit response, decode to our card CSD structure. + */ +static void mmc_decode_csd(struct mmc_card *card) +{ + struct mmc_csd *csd = &card->csd; + unsigned int e, m, csd_struct; + u32 *resp = card->raw_csd; + + /* + * We only understand CSD structure v1.0, v1.1 and v2. + * v2 has extra information in bits 15, 11 and 10. + */ + csd_struct = UNSTUFF_BITS(resp, 126, 2); + if (csd_struct != 0 && csd_struct != 1 && csd_struct != 2) { + sd_printk(KERN_ERR, "unrecognised CSD structure" + " version %d\n", csd_struct); + return; + } + + csd->mmca_vsn = UNSTUFF_BITS(resp, 122, 4); + + /* TAAC */ + m = UNSTUFF_BITS(resp, 115, 4); + e = UNSTUFF_BITS(resp, 112, 3); + csd->tacc_ns = (tacc_exp[e] * tacc_mant[m] + 9) / 10; + + /* NSAC */ + csd->tacc_clks = UNSTUFF_BITS(resp, 104, 8) * 100; + + /* TRAN_SPEED */ + m = UNSTUFF_BITS(resp, 99, 4); + e = UNSTUFF_BITS(resp, 96, 3); + csd->max_dtr = tran_exp[e] * tran_mant[m]; + + /* CCC */ + csd->cmdclass = UNSTUFF_BITS(resp, 84, 12); + + /* READ_BL_LEN */ + csd->read_blkbits = UNSTUFF_BITS(resp, 80, 4); + + /* C_SIZE */ + m = UNSTUFF_BITS(resp, 62, 12); + + /* C_SIZE_MULT */ + e = UNSTUFF_BITS(resp, 47, 3); + + csd->capacity = (1 + m) << (e + 2); /* in card blocks */ +} + +#if 0 +static void sd_print_cid(struct mmc_cid *cid) +{ + sd_printk(KERN_INFO, + "manfid = %d\n" + "oemid = %d\n" + "prod_name = %s\n" + "hwrev = %d\n" + "fwrev = %d\n" + "serial = %08x\n" + "year = %d\n" + "month = %d\n", + cid->manfid, + cid->oemid, + cid->prod_name, + cid->hwrev, cid->fwrev, cid->serial, cid->year, cid->month); +} +#endif + +/* */ +static inline unsigned int ms_to_cycles(unsigned int ms, unsigned int clock) +{ + return ms * (clock / 1000); +} + +/* */ +static unsigned int sd_set_clock(struct sd_host *host, unsigned int clock) +{ + if (clock >= 32000000) { + host->clock = 32000000; + host->exi_clock = EXI_CLK_32MHZ; + } else if (clock >= 16000000) { + host->clock = 16000000; + host->exi_clock = EXI_CLK_16MHZ; + } else if (clock >= 8000000) { + host->clock = 8000000; + host->exi_clock = EXI_CLK_8MHZ; + } else if (clock >= 4000000) { + host->clock = 4000000; + host->exi_clock = EXI_CLK_4MHZ; + } else if (clock >= 2000000) { + host->clock = 2000000; + host->exi_clock = EXI_CLK_2MHZ; + } else { + host->clock = 1000000; + host->exi_clock = EXI_CLK_1MHZ; + } + return host->clock; +} + +/* */ +static void sd_calc_timeouts(struct sd_host *host) +{ + /* + * FIXME: calculate timeouts from card information + * (use safe defaults for now) + */ + host->read_timeout = ms_to_cycles(100, host->clock); + host->write_timeout = ms_to_cycles(250, host->clock); +} + +/* + * + * SPI I/O support routines, including some handy SPI to EXI language + * translations. + */ + +/* */ +static inline void spi_cs_low(struct sd_host *host) +{ + exi_dev_take(host->exi_device); + exi_dev_select(host->exi_device); +} + +/* */ +static inline void spi_cs_high(struct sd_host *host) +{ + exi_dev_deselect(host->exi_device); + exi_dev_give(host->exi_device); +} + +/* */ +static inline void spi_write(struct sd_host *host, void *data, size_t len) +{ + exi_dev_write(host->exi_device, data, len); +} + +/* */ +static inline void spi_read(struct sd_host *host, void *data, size_t len) +{ + /* + * Houston, we have a problem. + * + * The EXI hardware implementation seems to use a shift register which + * outputs data from the MSB to the MOSI line and inputs data from + * the MISO line into the LSB. + * When a read operation is performed, data from the MISO line + * is entered into the shift register LSB as expected. But also the + * data already present in the shift register is sent out through the + * MOSI line from the MSB. + * This is in fact the "feature" that enabled tmbinc to dump the IPL. + * + * When interfacing with SD cards, this causes us a serious problem. + * + * We are required to send all ones (1s) while reading data from + * the SD card. Otherwise, the card can interpret the data sent as + * commands (if they start with the bit pattern 01 for example). + * + * If we use the EXI immediate mode transfer, we can workaround the + * situation by writing all 1s to the DATA register before reading + * (this is indeed automatically done by the EXI layer). + * But we simply can't do that when using EXI DMA transfers (these + * kind of transfers do not allow bidirectional operation). + * + * Given that no EXI DMA read transfers seem reliable, we fallback + * to the "interrupt-driven" immediate mode of the EXI layer. + * This will help reducing CPU monopolization on large reads. + * + */ + exi_dev_transfer(host->exi_device, data, len, EXI_OP_READ, EXI_CMD_IDI); +} + +/* cycles are expressed in 8 clock cycles */ +static void spi_burn_cycles(struct sd_host *host, int cycles) +{ + u8 d; + + while (cycles-- > 0) { + d = 0xff; + spi_write(host, &d, sizeof(d)); + } +} + +/* cycles are expressed in 8 clock cycles */ +static int spi_wait_for_resp(struct sd_host *host, + u8 resp, u8 resp_mask, unsigned long cycles) +{ + u8 data; + + while (cycles-- > 0) { + spi_read(host, &data, sizeof(data)); + if ((data & resp_mask) == resp) { + host->resp = data; + return data; + } + } + return -ENODATA; +} + +/* + * + */ +static int sd_read_data(struct sd_host *host, void *data, size_t len, int token) +{ + int retval = 0; + + if (token) { + retval = spi_wait_for_resp(host, token, 0xff, + host->read_timeout); + if (retval < 0) + goto out; + } + spi_read(host, data, len); + retval = 0; + +out: + return retval; +} + +/* + * + */ +static int sd_write_data(struct sd_host *host, void *data, size_t len, + int token) +{ + u16 crc; + u8 t, *d; + size_t l; + int retval = 0; + + /* FIXME, rewrite this a bit */ + { + crc = 0; + d = data; + l = len; + + while (l-- > 0) + crc = crc_xmodem_update(crc, *d++); + } + + /* send the write block token */ + t = token; + spi_write(host, &t, sizeof(t)); + + /* send the data */ + spi_write(host, data, len); + + /* send the crc */ + spi_write(host, &crc, sizeof(crc)); + + /* get the card data response */ + retval = spi_wait_for_resp(host, 0x01, 0x11, host->write_timeout); + if (retval < 0) + goto out; + if ((retval & DR_SPI_MASK) != DR_SPI_DATA_ACCEPTED) { + DBG("data response=%02x\n", retval); + retval = -EIO; + goto out; + } + + /* wait for the busy signal to clear */ + retval = spi_wait_for_resp(host, 0xff, 0xff, host->write_timeout); + if (retval < 0) + goto out; + + retval = 0; + +out: + return retval; +} + +/* + * + * MMC/SD command transactions related routines. + */ + +/* */ +static inline void sd_cmd(struct sd_command *cmd, u8 opcode, u32 arg) +{ + cmd->cmd = 0x40 | opcode; + cmd->arg = arg; + cmd->crc = 0x01; /* FIXME, crc is not currently used */ +} + +/* */ +static inline void sd_cmd_go_idle_state(struct sd_command *cmd) +{ + cmd->cmd = 0x40; + cmd->arg = 0; + cmd->crc = 0x95; +} + +/* */ +static inline void sd_debug_print_cmd(struct sd_command *cmd) +{ + DBG("cmd = %d, arg = %08x, crc = %02x\n", + cmd->cmd & ~0x40, cmd->arg, cmd->crc); +} + +/* + * + */ +static int sd_start_command(struct sd_host *host, struct sd_command *cmd) +{ + int retval = 0; + + /* select the card by driving CS low */ + spi_cs_low(host); + + /* send the command through the MOSI line */ + spi_write(host, cmd, sizeof(*cmd)); + + /* + * Wait for the card response. + * Card responses come in the MISO line and have the most significant + * bit cleared. + */ + retval = spi_wait_for_resp(host, 0x00, 0x80, MMC_SPI_N_CR); + + if (retval > 0 && !(retval & 0x01) && cmd->cmd != 0x40) + DBG("command = %d, response = 0x%02x\n", cmd->cmd & ~0x40, + retval); + + return retval; +} + +/* + * + */ +static void sd_end_command(struct sd_host *host) +{ + /* wait 8 clock cycles as dictated by the specification */ + spi_burn_cycles(host, SD_FINISH_CYCLES); + + /* deselect the card by driving CS high */ + spi_cs_high(host); +} + +/* + * + */ +static int sd_run_no_data_command(struct sd_host *host, struct sd_command *cmd) +{ + int retval; + + /* send command, wait for response, and burn extra cycles */ + retval = sd_start_command(host, cmd); + sd_end_command(host); + + return retval; +} + +/* + * + */ +static int sd_generic_read(struct sd_host *host, + u8 opcode, u32 arg, + void *data, size_t len, int token) +{ + struct sd_command *cmd = &host->cmd; + u16 crc, calc_crc = 0xffff; + u8 *d; + size_t l; + int retval; + + /* build raw command */ + sd_cmd(cmd, opcode, arg); + + /* select card, send command and wait for response */ + retval = sd_start_command(host, cmd); + if (retval < 0) + goto out; + if (retval != 0x00) { + retval = -EIO; + goto out; + } + + /* wait for read token, then read data */ + retval = sd_read_data(host, data, len, token); + if (retval < 0) + goto out; + + /* read trailing crc */ + spi_read(host, &crc, sizeof(crc)); + + retval = 0; + + /* FIXME, rewrite this a bit */ + { + calc_crc = 0; + d = data; + l = len; + + while (l-- > 0) + calc_crc = crc_xmodem_update(calc_crc, *d++); + + if (calc_crc != crc) + retval = -EIO; + } + +out: + /* burn extra cycles and deselect card */ + sd_end_command(host); + + if (retval < 0) { + DBG("read, offset=%d, len=%d\n", arg, len); + DBG("crc=%04x, calc_crc=%04x, %s\n", crc, calc_crc, + (retval < 0) ? "failed" : "ok"); + } + + return retval; +} + +/* + * + */ +static int sd_generic_write(struct sd_host *host, + u8 opcode, u32 arg, + void *data, size_t len, int token) +{ + struct sd_command *cmd = &host->cmd; + int retval; + + /* build raw command */ + sd_cmd(cmd, opcode, arg); + + /* select card, send command and wait for response */ + retval = sd_start_command(host, cmd); + if (retval < 0) + goto out; + if (retval != 0x00) { + retval = -EIO; + goto out; + } + + /* send data token, data and crc, get data response */ + retval = sd_write_data(host, data, len, token); + if (retval < 0) + goto out; + + retval = 0; + +out: + /* burn extra cycles and deselect card */ + sd_end_command(host); + + if (retval < 0) + DBG("write, offset=%d, len=%d\n", arg, len); + + return retval; +} + +/* + * + */ +static int sd_read_ocr(struct sd_host *host) +{ + struct sd_command *cmd = &host->cmd; + int retval; + + memset(&host->ocr, 0, sizeof(host->ocr)); + + sd_cmd(cmd, MMC_READ_OCR, 0); + + /* select card, send command and wait for response */ + retval = sd_start_command(host, cmd); + if (retval < 0) + goto out; + + /* the OCR contents come immediately after the card response */ + spi_read(host, &host->ocr, sizeof(host->ocr)); + retval = 0; + +out: + /* burn extra cycles and deselect card */ + sd_end_command(host); + return retval; +} + +/* + * + */ +static inline int sd_read_csd(struct sd_host *host) +{ + memset(&host->card.raw_csd, 0, sizeof(host->card.raw_csd)); + return sd_generic_read(host, MMC_SEND_CSD, 0, + &host->card.raw_csd, + sizeof(host->card.raw_csd), + MMC_SPI_TOKEN_START_SINGLE_BLOCK_READ); +} + +/* + * + */ +static inline int sd_read_cid(struct sd_host *host) +{ + memset(&host->card.raw_cid, 0, sizeof(host->card.raw_cid)); + return sd_generic_read(host, MMC_SEND_CID, 0, + &host->card.raw_cid, + sizeof(host->card.raw_cid), + MMC_SPI_TOKEN_START_SINGLE_BLOCK_READ); +} + +/* + * + */ +static inline int sd_read_single_block(struct sd_host *host, + unsigned long start, + void *data, size_t len) +{ + int retval; + int attempts = 3; + + if (test_bit(__SD_MEDIA_CHANGED, &host->flags)) + attempts = 1; + + while (attempts > 0) { + retval = sd_generic_read(host, MMC_READ_SINGLE_BLOCK, start, + data, len, + MMC_SPI_TOKEN_START_SINGLE_BLOCK_READ); + if (retval >= 0) + break; + attempts--; + DBG("start=%lu, data=%p, len=%d, retval = %d\n", start, data, + len, retval); + } + return retval; +} + +/* + * + */ +static inline int sd_write_single_block(struct sd_host *host, + unsigned long start, + void *data, size_t len) +{ + int retval; + + retval = sd_generic_write(host, MMC_WRITE_BLOCK, start, + data, len, + MMC_SPI_TOKEN_START_SINGLE_BLOCK_WRITE); + if (retval < 0) + DBG("start=%lu, data=%p, len=%d, retval = %d\n", start, data, + len, retval); + + return retval; +} + +/* + * + */ +static int sd_reset_sequence(struct sd_host *host) +{ + struct sd_command *cmd = &host->cmd; + u8 d; + int i; + int retval = 0; + + host->card.state = 0; + + /* + * Wait at least 80 dummy clock cycles with the card deselected + * and with the MOSI line continuously high. + */ + exi_dev_take(host->exi_device); + exi_dev_deselect(host->exi_device); + for (i = 0; i < SD_IDLE_CYCLES; i++) { + d = 0xff; + exi_dev_write(host->exi_device, &d, sizeof(d)); + } + exi_dev_give(host->exi_device); + + /* + * Send a CMD0, card must ack with "idle state" (0x01). + * This puts the card into SPI mode and soft resets it. + * CRC checking is disabled by default. + */ + for (i = 0; i < 255; i++) { + /* CMD0 */ + sd_cmd_go_idle_state(cmd); + retval = sd_run_no_data_command(host, cmd); + if (retval < 0) { + retval = -ENODEV; + goto out; + } + if (retval == R1_SPI_IDLE) + break; + } + if (retval != R1_SPI_IDLE) { + retval = -ENODEV; + goto out; + } + + /* + * Send a ACMD41 to activate card initialization process. + * SD card must ack with "ok" (0x00). + * MMC card will report "invalid command" (0x04). + */ + for (i = 0; i < 0xffff; i++) { + /* ACMD41 = CMD55 + CMD41 */ + sd_cmd(cmd, MMC_APP_CMD, 0); + retval = sd_run_no_data_command(host, cmd); + if (retval < 0) { + retval = -ENODEV; + goto out; + } + + sd_cmd(cmd, SD_APP_OP_COND, 0); + retval = sd_run_no_data_command(host, cmd); + if (retval < 0) { + retval = -ENODEV; + goto out; + } + if (retval == 0x00) { + /* we found a SD card */ + mmc_card_set_present(&host->card); + host->card.type = MMC_TYPE_SD; + break; + } + if ((retval & R1_SPI_ILLEGAL_COMMAND)) { + /* this looks like a MMC card */ + break; + } + } + + /* + * MMC cards require CMD1 to activate card initialization process. + * MMC card must ack with "ok" (0x00) + */ + if (!mmc_card_sd(&host->card)) { + for (i = 0; i < 0xffff; i++) { + sd_cmd(cmd, MMC_SEND_OP_COND, 0); + retval = sd_run_no_data_command(host, cmd); + if (retval < 0) { + retval = -ENODEV; + goto out; + } + if (retval == 0x00) { + /* we found a MMC card */ + mmc_card_set_present(&host->card); + break; + } + } + if (retval != 0x00) { + DBG("MMC card, bad, retval=%02x\n", retval); + sd_card_set_bad(host); + } + } + +out: + return retval; +} + +/* + * + */ +static int sd_welcome_card(struct sd_host *host) +{ + int retval; + + /* soft reset the card */ + retval = sd_reset_sequence(host); + if (retval < 0 || sd_card_is_bad(host)) + goto out; + + /* read Operating Conditions Register */ + retval = sd_read_ocr(host); + if (retval < 0) + goto err_bad_card; + + /* refuse to drive cards reporting voltage ranges out of scope */ + if (!(host->ocr & host->ocr_avail)) { + sd_printk(KERN_WARNING, "reported OCR (%08x)" + " indicates that it is not safe to use this" + " card with a GameCube\n", host->ocr); + retval = -ENODEV; + goto err_bad_card; + } + + /* read and decode the Card Specific Data */ + retval = sd_read_csd(host); + if (retval < 0) + goto err_bad_card; + mmc_decode_csd(&host->card); + + /* calculate some card access related timeouts */ + sd_calc_timeouts(host); + + /* read and decode the Card Identification Data */ + retval = sd_read_cid(host); + if (retval < 0) + goto err_bad_card; + mmc_decode_cid(&host->card); + + sd_printk(KERN_INFO, "slot%d: descr \"%s\", size %luk, block %ub," + " serial %08x\n", + to_channel(exi_get_exi_channel(host->exi_device)), + host->card.cid.prod_name, + (unsigned long)((host->card.csd.capacity * + (1 << host->card.csd.read_blkbits)) / 1024), + 1 << host->card.csd.read_blkbits, + host->card.cid.serial); + + retval = 0; + goto out; + +err_bad_card: + sd_card_set_bad(host); +out: + return retval; +} + +/* + * + * Block layer. + */ + +/* + * Performs a read request. + */ +static int sd_read_request(struct sd_host *host, struct request *req) +{ + int i; + unsigned long nr_blocks; /* in card blocks */ + size_t block_len; /* in bytes */ + unsigned long start; + void *buf = req->buffer; + int retval; + + /* + * It seems that some cards do not accept single block reads for the + * read block length reported by the card. + * For now, we perform only 512 byte single block reads. + */ + + start = blk_rq_pos(req) << KERNEL_SECTOR_SHIFT; +#if 0 + nr_blocks = req->current_nr_sectors >> + (host->card.csd.read_blkbits - KERNEL_SECTOR_SHIFT); + block_len = 1 << host->card.csd.read_blkbits; +#else + nr_blocks = blk_rq_cur_sectors(req); + block_len = 1 << KERNEL_SECTOR_SHIFT; +#endif + + for (i = 0; i < nr_blocks; i++) { + retval = sd_read_single_block(host, start, buf, block_len); + if (retval < 0) + break; + + start += block_len; + buf += block_len; + } + + /* number of kernel sectors transferred */ +#if 0 + retval = i << (host->card.csd.read_blkbits - KERNEL_SECTOR_SHIFT); +#else + retval = i; +#endif + + return retval; +} + +/* + * Performs a write request. + */ +static int sd_write_request(struct sd_host *host, struct request *req) +{ + int i; + unsigned long nr_blocks; /* in card blocks */ + size_t block_len; /* in bytes */ + unsigned long start; + void *buf = req->buffer; + int retval; + + /* FIXME?, maybe should use 2^WRITE_BL_LEN blocks */ + + /* kernel sectors and card write blocks are both 512 bytes long */ + start = blk_rq_pos(req) << KERNEL_SECTOR_SHIFT; + nr_blocks = blk_rq_cur_sectors(req); + block_len = 1 << KERNEL_SECTOR_SHIFT; + + for (i = 0; i < nr_blocks; i++) { + retval = sd_write_single_block(host, start, buf, block_len); + if (retval < 0) + break; + + start += block_len; + buf += block_len; + } + + /* number of kernel sectors transferred */ + retval = i; + + return retval; +} + +/* + * Verifies if a request should be dispatched or not. + * + * Returns: + * <0 in case of error. + * 0 if request passes the checks + */ +static int sd_check_request(struct sd_host *host, struct request *req) +{ + unsigned long nr_sectors; + + if (!blk_fs_request(req)) + return -EIO; + + if (test_bit(__SD_MEDIA_CHANGED, &host->flags)) { + sd_printk(KERN_ERR, "media changed, aborting\n"); + return -ENOMEDIUM; + } + + /* unit is kernel sectors */ + nr_sectors = + host->card.csd.capacity << (host->card.csd.read_blkbits - + KERNEL_SECTOR_SHIFT); + + /* keep our reads within limits */ + if (blk_rq_pos(req) + blk_rq_cur_sectors(req) > nr_sectors) { + sd_printk(KERN_ERR, "reading past end, aborting\n"); + return -EINVAL; + } + + return 0; +} + +/* + * Request dispatcher. + */ +static int sd_do_request(struct sd_host *host, struct request *req) +{ + int nr_sectors = 0; + int error; + + error = sd_check_request(host, req); + if (error) { + nr_sectors = error; + goto out; + } + + switch (rq_data_dir(req)) { + case WRITE: + nr_sectors = sd_write_request(host, req); + break; + case READ: + nr_sectors = sd_read_request(host, req); + break; + } + +out: + return nr_sectors; +} + +/* + * Input/Output thread. + */ +static int sd_io_thread(void *param) +{ + struct sd_host *host = param; + struct request *req; + unsigned long flags; + int nr_sectors; + int error; + +#if 0 + /* + * We are going to perfom badly due to the read problem explained + * above. At least, be nice with other processes trying to use the + * cpu. + */ + set_user_nice(current, 0); +#endif + + current->flags |= PF_NOFREEZE|PF_MEMALLOC; + + mutex_lock(&host->io_mutex); + for (;;) { + req = NULL; + set_current_state(TASK_INTERRUPTIBLE); + + spin_lock_irqsave(&host->queue_lock, flags); + if (!blk_queue_plugged(host->queue)) + req = blk_fetch_request(host->queue); + spin_unlock_irqrestore(&host->queue_lock, flags); + + if (!req) { + if (kthread_should_stop()) { + set_current_state(TASK_RUNNING); + break; + } + mutex_unlock(&host->io_mutex); + schedule(); + mutex_lock(&host->io_mutex); + continue; + } + set_current_state(TASK_INTERRUPTIBLE); + nr_sectors = sd_do_request(host, req); + error = (nr_sectors < 0) ? nr_sectors : 0; + + spin_lock_irqsave(&host->queue_lock, flags); + __blk_end_request(req, error, nr_sectors << 9); + spin_unlock_irqrestore(&host->queue_lock, flags); + } + mutex_unlock(&host->io_mutex); + + return 0; +} + +/* + * Block layer request function. + * Wakes up the IO thread. + */ +static void sd_request_func(struct request_queue *q) +{ + struct sd_host *host = q->queuedata; + wake_up_process(host->io_thread); +} + +/* + * + * Driver interface. + */ + +static DECLARE_MUTEX(open_lock); + +/* + * Opens the drive device. + */ +static int sd_open(struct block_device *bdev, fmode_t mode) +{ + struct sd_host *host = bdev->bd_disk->private_data; + int retval = 0; + + if (!host || !host->exi_device) + return -ENXIO; + + /* honor exclusive open mode */ + if (host->refcnt == -1 || + (host->refcnt && (mode & FMODE_EXCL))) { + retval = -EBUSY; + goto out; + } + + /* this takes care of revalidating the media if needed */ + check_disk_change(bdev); + if (!host->card.csd.capacity) { + retval = -ENOMEDIUM; + goto out; + } + + down(&open_lock); + + if ((mode & FMODE_EXCL)) + host->refcnt = -1; + else + host->refcnt++; + + up(&open_lock); + +out: + return retval; + +} + +/* + * Releases the drive device. + */ +static int sd_release(struct gendisk *disk, fmode_t mode) +{ + struct sd_host *host = disk->private_data; + + if (!host) + return -ENXIO; + + down(&open_lock); + + if (host->refcnt > 0) + host->refcnt--; + else + host->refcnt = 0; + + up(&open_lock); + + /* lazy removal of unreferenced zombies */ + if (!host->refcnt && !host->exi_device) + kfree(host); + + return 0; +} + +/* + * Checks if media changed. + */ +static int sd_media_changed(struct gendisk *disk) +{ + struct sd_host *host = disk->private_data; + unsigned int last_serial; + int retval; + + /* report a media change for zombies */ + if (!host) + return 1; + + /* report a media change if someone forced it */ + if (test_bit(__SD_MEDIA_CHANGED, &host->flags)) + return 1; + + /* check if the serial number of the card changed */ + last_serial = host->card.cid.serial; + retval = sd_read_cid(host); + if (!retval && last_serial == host->card.cid.serial && last_serial) + clear_bit(__SD_MEDIA_CHANGED, &host->flags); + else + set_bit(__SD_MEDIA_CHANGED, &host->flags); + + return (host->flags & SD_MEDIA_CHANGED) ? 1 : 0; +} + +/* + * Checks if media is still valid. + */ +static int sd_revalidate_disk(struct gendisk *disk) +{ + struct sd_host *host = disk->private_data; + int retval = 0; + + /* report missing medium for zombies */ + if (!host) { + retval = -ENOMEDIUM; + goto out; + } + + /* the block layer likes to call us multiple times... */ + if (!sd_media_changed(host->disk)) + goto out; + + /* get the card into a known status */ + retval = sd_welcome_card(host); + if (retval < 0 || sd_card_is_bad(host)) { + retval = -ENOMEDIUM; + goto out; + } + + /* inform the block layer about various sizes */ + blk_queue_logical_block_size(host->queue, 1 << KERNEL_SECTOR_SHIFT); + set_capacity(host->disk, host->card.csd.capacity << + (host->card.csd.read_blkbits - KERNEL_SECTOR_SHIFT)); + + clear_bit(__SD_MEDIA_CHANGED, &host->flags); + +out: + return retval; +} + +static int sd_getgeo(struct block_device *bdev, struct hd_geometry *geo) +{ + geo->cylinders = get_capacity(bdev->bd_disk) / (4 * 16); + geo->heads = 4; + geo->sectors = 16; + return 0; +} + + +static struct block_device_operations sd_fops = { + .owner = THIS_MODULE, + .open = sd_open, + .release = sd_release, + .revalidate_disk = sd_revalidate_disk, + .media_changed = sd_media_changed, + .getgeo = sd_getgeo, +}; + +/* + * Initializes the block layer interfaces. + */ +static int sd_init_blk_dev(struct sd_host *host) +{ + struct gendisk *disk; + struct request_queue *queue; + int channel; + int retval; + + channel = to_channel(exi_get_exi_channel(host->exi_device)); + + /* queue */ + retval = -ENOMEM; + spin_lock_init(&host->queue_lock); + queue = blk_init_queue(sd_request_func, &host->queue_lock); + if (!queue) { + sd_printk(KERN_ERR, "error initializing queue\n"); + goto err_blk_init_queue; + } + blk_queue_dma_alignment(queue, EXI_DMA_ALIGN); + blk_queue_max_phys_segments(queue, 1); + blk_queue_max_hw_segments(queue, 1); + blk_queue_max_sectors(queue, 8); + queue_flag_set_unlocked(QUEUE_FLAG_NONROT, queue); + queue->queuedata = host; + host->queue = queue; + + /* disk */ + disk = alloc_disk(1 << MMC_SHIFT); + if (!disk) { + sd_printk(KERN_ERR, "error allocating disk\n"); + goto err_alloc_disk; + } + disk->major = SD_MAJOR; + disk->first_minor = channel << MMC_SHIFT; + disk->fops = &sd_fops; + sprintf(disk->disk_name, "%s%c", SD_NAME, 'a' + channel); + disk->private_data = host; + disk->queue = host->queue; + host->disk = disk; + + retval = 0; + goto out; + +err_alloc_disk: + blk_cleanup_queue(host->queue); + host->queue = NULL; +err_blk_init_queue: +out: + return retval; +} + +/* + * Exits the block layer interfaces. + */ +static void sd_exit_blk_dev(struct sd_host *host) +{ + blk_cleanup_queue(host->queue); + put_disk(host->disk); +} + + +/* + * Initializes and launches the IO thread. + */ +static int sd_init_io_thread(struct sd_host *host) +{ + int channel; + int result = 0; + + channel = to_channel(exi_get_exi_channel(host->exi_device)); + + mutex_init(&host->io_mutex); + host->io_thread = kthread_run(sd_io_thread, host, + "ksdiod/%c", 'a' + channel); + if (IS_ERR(host->io_thread)) { + sd_printk(KERN_ERR, "error creating io thread\n"); + result = PTR_ERR(host->io_thread); + } + return result; +} + +/* + * Terminates and waits for the IO thread to complete. + */ +static void sd_exit_io_thread(struct sd_host *host) +{ + if (!IS_ERR(host->io_thread)) { + wake_up_process(host->io_thread); + kthread_stop(host->io_thread); + host->io_thread = ERR_PTR(-EINVAL); + } +} + +/* + * Initializes a host. + */ +static int sd_init(struct sd_host *host) +{ + int retval; + + spin_lock_init(&host->lock); + + host->refcnt = 0; + set_bit(__SD_MEDIA_CHANGED, &host->flags); + + host->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; + sd_set_clock(host, SD_SPI_CLK); + sd_calc_timeouts(host); + + retval = sd_init_blk_dev(host); + if (!retval) { + retval = sd_revalidate_disk(host->disk); + if (retval < 0 || !mmc_card_present(&host->card)) { + retval = -ENODEV; + goto err_blk_dev; + } + + retval = sd_init_io_thread(host); + if (retval) + goto err_blk_dev; + + add_disk(host->disk); + } + + return retval; + +err_blk_dev: + sd_exit_blk_dev(host); + return retval; +} + +/* + * Deinitializes (exits) a host. + */ +static void sd_exit(struct sd_host *host) +{ + del_gendisk(host->disk); + sd_exit_io_thread(host); + sd_exit_blk_dev(host); +} + +/* + * Terminates a host. + */ +static void sd_kill(struct sd_host *host) +{ + if (host->refcnt > 0) { + sd_printk(KERN_ERR, "hey! card removed while in use!\n"); + set_bit(__SD_MEDIA_CHANGED, &host->flags); + } + + sd_exit(host); + host->exi_device = NULL; + + /* release the host immediately when not in use */ + if (!host->refcnt) + kfree(host); +} + + +/* + * EXI layer interface. + * + */ + +/* + * Checks if the given EXI device is a MMC/SD card and makes it available + * if true. + */ +static int sd_probe(struct exi_device *exi_device) +{ + struct sd_host *host; + int retval; + + /* don't try to drive a device which already has a real identifier */ + if (exi_device->eid.id != EXI_ID_NONE) + return -ENODEV; + + host = kzalloc(sizeof(*host), GFP_KERNEL); + if (!host) + return -ENOMEM; + + host->exi_device = exi_device_get(exi_device); + WARN_ON(exi_get_drvdata(exi_device)); + exi_set_drvdata(exi_device, host); + retval = sd_init(host); + if (retval) { + exi_set_drvdata(exi_device, NULL); + host->exi_device = NULL; + kfree(host); + exi_device_put(exi_device); + } + return retval; +} + +/* + * Makes unavailable the MMC/SD card identified by the EXI device `exi_device'. + */ +static void sd_remove(struct exi_device *exi_device) +{ + struct sd_host *host = exi_get_drvdata(exi_device); + + WARN_ON(!host); + WARN_ON(!host->exi_device); + + exi_set_drvdata(exi_device, NULL); + if (host) + sd_kill(host); + exi_device_put(exi_device); +} + +static struct exi_device_id sd_eid_table[] = { + [0] = { + .channel = SD_SLOTA_CHANNEL, + .device = SD_SLOTA_DEVICE, + .id = EXI_ID_NONE, + }, + [1] = { + .channel = SD_SLOTB_CHANNEL, + .device = SD_SLOTB_DEVICE, + .id = EXI_ID_NONE, + }, + {.id = 0} +}; + +static struct exi_driver sd_driver = { + .name = DRV_MODULE_NAME, + .eid_table = sd_eid_table, + .frequency = SD_SPI_CLK_IDX, + .probe = sd_probe, + .remove = sd_remove, +}; + + +/* + * Kernel module interface. + * + */ + +/* + * + */ +static int __init sd_init_module(void) +{ + int retval = 0; + + sd_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION, + sd_driver_version); + + if (register_blkdev(SD_MAJOR, DRV_MODULE_NAME)) { + sd_printk(KERN_ERR, "unable to register major %d\n", SD_MAJOR); + retval = -EIO; + goto out; + } + + retval = exi_driver_register(&sd_driver); + +out: + return retval; +} + +/* + * + */ +static void __exit sd_exit_module(void) +{ + unregister_blkdev(SD_MAJOR, DRV_MODULE_NAME); + exi_driver_unregister(&sd_driver); +} + +module_init(sd_init_module); +module_exit(sd_exit_module); + +MODULE_AUTHOR(DRV_AUTHOR); +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_LICENSE("GPL"); + diff --git a/drivers/block/rvl-di.c b/drivers/block/rvl-di.c new file mode 100644 index 0000000..cc176ed --- /dev/null +++ b/drivers/block/rvl-di.c @@ -0,0 +1,1875 @@ +/* + * drivers/block/rvl-di.c + * + * Nintendo Wii Disk Interface (DI) driver + * Copyright (C) 2009 The GameCube Linux Team + * Copyright (C) 2009 Albert Herranz + * + * Based on Nintendo GameCube Disk Interface (DI) driver + * Copyright (C) 2005-2009 The GameCube Linux Team + * Copyright (C) 2005,2006,2007,2009 Albert Herranz + * + * Portions based on previous work by Scream|CT. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ +#define DRV_MODULE_NAME "rvl-di" +#define DRV_DESCRIPTION "Nintendo Wii Disk Interface (DI) driver" +#define DRV_AUTHOR "Albert Herranz" + +#define pr_fmt(fmt) DRV_MODULE_NAME ": " fmt + +#define DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +static char di_driver_version[] = "0.1i"; + +/* + * Hardware. + */ +#define DI_DMA_ALIGN 0x1f /* 32 bytes */ + +/* DI Status Register */ +#define DI_SR 0x00 +#define DI_SR_BRK (1<<0) +#define DI_SR_DEINTMASK (1<<1) +#define DI_SR_DEINT (1<<2) +#define DI_SR_TCINTMASK (1<<3) +#define DI_SR_TCINT (1<<4) +#define DI_SR_BRKINTMASK (1<<5) +#define DI_SR_BRKINT (1<<6) + +/* DI Cover Register */ +#define DI_CVR 0x04 +#define DI_CVR_CVR (1<<0) +#define DI_CVR_CVRINTMASK (1<<1) +#define DI_CVR_CVRINT (1<<2) + +/* DI Command Buffers */ +#define DI_CMDBUF0 0x08 +#define DI_CMDBUF1 0x0c +#define DI_CMDBUF2 0x10 + +/* DI DMA Memory Address Register */ +#define DI_MAR 0x14 + +/* DI DMA Transfer Length Register */ +#define DI_LENGTH 0x18 + +/* DI Control Register */ +#define DI_CR 0x1c +#define DI_CR_TSTART (1<<0) +#define DI_CR_DMA (1<<1) +#define DI_CR_RW (1<<2) + +/* DI Immediate Data Buffer */ +#define DI_DATA 0x20 + +/* DI Configuration Register */ +#define DI_CFG 0x24 + + +/* drive status, status */ +#define DI_STATUS(s) ((u8)((s)>>24)) + +#define DI_STATUS_READY 0x00 +#define DI_STATUS_COVER_OPENED 0x01 +#define DI_STATUS_DISK_CHANGE 0x02 +#define DI_STATUS_NO_DISK 0x03 +#define DI_STATUS_MOTOR_STOP 0x04 +#define DI_STATUS_DISK_ID_NOT_READ 0x05 + +/* drive status, error */ +#define DI_ERROR(s) ((u32)((s)&0x00ffffff)) + +#define DI_ERROR_NO_ERROR 0x000000 +#define DI_ERROR_MOTOR_STOPPED 0x020400 +#define DI_ERROR_DISK_ID_NOT_READ 0x020401 +#define DI_ERROR_MEDIUM_NOT_PRESENT 0x023a00 +#define DI_ERROR_SEEK_INCOMPLETE 0x030200 +#define DI_ERROR_UNRECOVERABLE_READ 0x031100 +#define DI_ERROR_INVALID_COMMAND 0x052000 +#define DI_ERROR_BLOCK_OUT_OF_RANGE 0x052100 +#define DI_ERROR_INVALID_FIELD 0x052400 +#define DI_ERROR_MEDIUM_CHANGED 0x062800 + +#define di_may_retry(s) ((DI_STATUS(s) == DI_STATUS_READY || \ + DI_STATUS(s) == DI_STATUS_DISK_ID_NOT_READ) \ + && \ + (DI_ERROR(s) != DI_ERROR_SEEK_INCOMPLETE)) + +/* DI Sector Size */ +#define DI_SECTOR_SHIFT 11 +#define DI_SECTOR_SIZE (1 << DI_SECTOR_SHIFT) /*2048*/ + + +/* ECMA Standards definitions */ +#if 0 +#define DI_TYPE_DVD_ROM 0x10 /* 0001 0000 ECMA:267,268 */ +#define DI_TYPE_DVD_RAM_26 0x11 /* 0001 0001 ECMA:272 */ +#define DI_TYPE_DVD_RAM_47 0x61 /* 0110 0001 ECMA:330 */ +#define DI_TYPE_DVD_MINUS_R_29 0x12 /* 0001 0010 ECMA:279 */ +#define DI_TYPE_DVD_MINUS_R_47 0x72 /* 0101 0010 ECMA:359 */ +#define DI_TYPE_DVD_MINUS_R_DL 0x62 /* 0110 0010 ECMA:382 */ +#define DI_TYPE_DVD_MINUS_RW 0x23 /* 0010 0011 ECMA:338 */ +#define DI_TYPE_DVD_PLUS_RW_30 0x19 /* 0001 1001 ECMA:274 */ +#define DI_TYPE_DVD_PLUS_RW_47 0x29 /* 0010 1001 ECMA:337 */ +#define DI_TYPE_DVD_PLUS_RW_HS 0x39 /* 0011 1001 ECMA:371 */ +#define DI_TYPE_DVD_PLUS_R 0x1a /* 0001 1010 ECMA:349 */ +#define DI_TYPE_DVD_PLUS_RW_DL 0x1d /* 0001 1101 ECMA:374 */ +#define DI_TYPE_DVD_PLUS_R_DL 0x1e /* 0001 1110 ECMA:364 */ +#endif + +#define DI_BOOK_TYPE_DVD_ROM 0x00 +#define DI_BOOK_TYPE_DVD_RAM 0x01 +#define DI_BOOK_TYPE_DVD_MINUS_R 0x02 +#define DI_BOOK_TYPE_DVD_MINUS_RW 0x03 +#define DI_BOOK_TYPE_DVD_PLUS_RW 0x09 +#define DI_BOOK_TYPE_DVD_PLUS_R 0x0a +#define DI_BOOK_TYPE_DVD_PLUS_RW_DL 0x0d +#define DI_BOOK_TYPE_DVD_PLUS_R_DL 0x0e + +#define DI_DISK_SIZE_80MM 0x01 +#define DI_DISK_SIZE_120MM 0x00 + + + +/* Driver Settings */ +#define DI_NAME DRV_MODULE_NAME +#define DI_MAJOR 60 + +#define DI_COMMAND_TIMEOUT 20 /* seconds */ +#define DI_COMMAND_RETRIES 10 /* times */ + +#define DI_MOTOR_OFF_TIMEOUT 10 + +#define KERNEL_SECTOR_SHIFT 9 +#define KERNEL_SECTOR_SIZE (1 << KERNEL_SECTOR_SHIFT) /*512*/ + + +/* + * Drive Information. + */ +struct di_drive_info { + u16 rev; + u16 code; + u32 date; + u8 pad[0x18]; +}; + +#if 0 +/* + * Disk ID. + */ +struct di_disk_id { + u8 id[32]; +}; +#endif + +/* + * Physical format information (as per ECMA standards). + */ +struct di_phys_format_info { +#if defined(__BIG_ENDIAN_BITFIELD) + /* Byte 0 - Disk Category and Version Number */ + u8 disk_category:4; + u8 version_number:4; + /* Byte 1 - Disk size and maximum transfer rate */ + u8 disk_size:4; + u8 max_xfer_rate:4; + /* Byte 2 - Disk structure */ + u8 byte2_bit7_zero:1; + u8 disk_type:2; + u8 track_path:1; + u8 layer_type:4; + /* Byte 3 - Recording density */ + u8 average_channel_bit_length:4; + u8 average_track_pitch:4; +#elif defined(__LITTLE_ENDIAN_BITFIELD) + /* Byte 0 - Disk Category and Version Number */ + u8 version_number:4; + u8 disk_category:4; + /* Byte 1 - Disk size and maximum transfer rate */ + u8 max_xfer_rate:4; + u8 disk_size:4; + /* Byte 2 - Disk structure */ + u8 layer_type:4; + u8 track_path:1; + u8 disk_type:2; + u8 byte2_bit7_zero:1; + /* Byte 3 - Recording density */ + u8 average_track_pitch:4; + u8 average_channel_bit_length:4; +#endif + __be32 first_data_psn; + __be32 last_data_psn; + /* don't care for now about the rest */ +}; + +/* + * An operation code. + */ +struct di_opcode { + u16 op; +#define DI_OP(id, flags) (((u8)(id)<<8)|((u8)(flags))) +#define DI_OP_ID(op) ((u8)((op)>>8)) +#define DI_OP_FLAGS(op) ((u8)(op)) + +#define DI_DIR_READ 0x00 +#define DI_DIR_WRITE DI_CR_RW +#define DI_MODE_IMMED 0x00 +#define DI_MODE_DMA DI_CR_DMA +#define DI_IGNORE_ERRORS (1<<7) + + char *name; + + u32 cmdbuf0; +}; + +/* + * Drive code container. + */ +struct di_drive_code { + u32 address; + size_t len; + void *code; +}; + +struct di_device; + +/* + * A Disk Interface command. + */ +struct di_command { + u16 opidx; + + u32 cmdbuf0; + u32 cmdbuf1; + u32 cmdbuf2; + + void *data; + size_t len; + + dma_addr_t dma_addr; + size_t dma_len; + + void *done_data; + void (*done)(struct di_command *cmd); + + u16 retries; + u16 max_retries; + + u32 result; + + struct di_device *ddev; +}; + +#define di_result_ok(result) ((result) == DI_SR_TCINT) +#define di_command_ok(cmd) (di_result_ok((cmd)->result)) + +enum { + __DI_MEDIA_CHANGED, + __DI_START_QUEUE, + __DI_RESETTING, +}; + +/* + * The Disk Interface device. + */ +struct di_device { + spinlock_t lock; + + int irq; + + spinlock_t io_lock; + void __iomem *io_base; + + struct di_command *cmd; + struct di_command *failed_cmd; + + struct di_command status; + u32 drive_status; + + struct gendisk *disk; + struct request_queue *queue; + spinlock_t queue_lock; + + struct request *req; + struct di_command req_cmd; + + struct di_drive_code *drive_code; + + u32 model; + unsigned long flags; +#define DI_MEDIA_CHANGED (1<<__DI_MEDIA_CHANGED) +#define DI_START_QUEUE (1<<__DI_START_QUEUE) +#define DI_RESETTING (1<<__DI_RESETTING) + + unsigned long nr_sectors; + + struct timer_list motor_off_timer; + +#ifdef CONFIG_PROC_FS + struct proc_dir_entry *proc; +#endif /* CONFIG_PROC_FS */ + + int ref_count; + + struct device *dev; +}; + + +u8 di_scratch_frame[DI_SECTOR_SIZE] + __attribute__ ((aligned(DI_DMA_ALIGN+1))); + +/* + * We do not accept original media with this driver, as there is currently no + * general need for that. + * If you ever develop an application (a media player for example) which works + * with original media, just change di_accept_gods and recompile. + */ +static const int di_accept_gods; + +/* + * Drive operations table, incomplete. + * We just include here some of the available functions, in no particular + * order. + */ +#define CMDBUF(a, b, c, d) (((a)<<24)|((b)<<16)|((c)<<8)|(d)) + +enum { + DI_OP_NOP, + DI_OP_INQ, + DI_OP_STOPMOTOR, + DI_OP_READDISKID, + DI_OP_READSECTOR, + DI_OP_GETSTATUS, + DI_OP_READPHYSINFO, + DI_OP_LAST +}; + +static struct di_opcode di_opcodes[] = { + [DI_OP_NOP] = { + .op = DI_OP(DI_OP_NOP, 0), + .name = "NOP", + .cmdbuf0 = 0, + }, + [DI_OP_INQ] = { + .op = DI_OP(DI_OP_INQ, DI_DIR_READ | DI_MODE_DMA), + .name = "INQ", + .cmdbuf0 = 0x12000000, + }, + [DI_OP_STOPMOTOR] = { + .op = DI_OP(DI_OP_STOPMOTOR, DI_DIR_READ | DI_MODE_IMMED), + .name = "STOPMOTOR", + .cmdbuf0 = 0xe3000000, + }, + [DI_OP_READDISKID] = { + .op = DI_OP(DI_OP_READDISKID, DI_DIR_READ | DI_MODE_DMA), + .name = "READDISKID", + .cmdbuf0 = 0xa8000040, + }, + [DI_OP_READSECTOR] = { + .op = DI_OP(DI_OP_READSECTOR, DI_DIR_READ | DI_MODE_DMA), + .name = "READSECTOR", + .cmdbuf0 = 0xd0000000, + }, + [DI_OP_GETSTATUS] = { + .op = DI_OP(DI_OP_GETSTATUS, DI_DIR_READ | DI_MODE_IMMED), + .name = "GETSTATUS", + .cmdbuf0 = 0xe0000000, + }, + [DI_OP_READPHYSINFO] = { + .op = DI_OP(DI_OP_READPHYSINFO, DI_DIR_READ | DI_MODE_DMA), + .name = "READPHYSINFO", + .cmdbuf0 = 0xad000000, + }, +}; + +#define DI_OP_MAXOP (DI_OP_LAST-1) +#define DI_OP_CUSTOM ((u16)~0) + + +static void di_reset(struct di_device *ddev); +static int di_run_command(struct di_command *cmd); + +/* + * Returns the operation code related data for a command. + */ +static inline struct di_opcode *di_get_opcode(struct di_command *cmd) +{ + BUG_ON(cmd->opidx > DI_OP_MAXOP && cmd->opidx != DI_OP_CUSTOM); + + if (cmd->opidx == DI_OP_CUSTOM) + return cmd->data; + else + return &di_opcodes[cmd->opidx]; +} + +/* + * Returns the operation code for a command. + */ +static inline u16 di_op(struct di_command *cmd) +{ + return di_get_opcode(cmd)->op; +} + + +/* + * Basic initialization for all commands. + */ +static void di_op_basic(struct di_command *cmd, + struct di_device *ddev, u16 opidx) +{ + struct di_opcode *opcode; + + memset(cmd, 0, sizeof(*cmd)); + cmd->ddev = ddev; + cmd->opidx = opidx; + cmd->max_retries = cmd->retries = 0; + opcode = di_get_opcode(cmd); + if (opcode) + cmd->cmdbuf0 = opcode->cmdbuf0; +} + +/* + * Builds an "Inquiry" command. + */ +static void di_op_inq(struct di_command *cmd, + struct di_device *ddev, + struct di_drive_info *drive_info) +{ + di_op_basic(cmd, ddev, DI_OP_INQ); + cmd->cmdbuf2 = sizeof(*drive_info); + cmd->data = drive_info; + cmd->len = sizeof(*drive_info); +} + +/* + * Builds a "Stop Motor" command. + */ +static inline void di_op_stopmotor(struct di_command *cmd, + struct di_device *ddev) +{ + di_op_basic(cmd, ddev, DI_OP_STOPMOTOR); +} + +#if 0 +/* + * Builds a "Read Disc ID" command. + */ +static void di_op_readdiskid(struct di_command *cmd, + struct di_device *ddev, + struct di_disk_id *disk_id) +{ + di_op_basic(cmd, ddev, DI_OP_READDISKID); + cmd->cmdbuf2 = sizeof(*disk_id); + cmd->data = disk_id; + cmd->len = sizeof(*disk_id); + cmd->max_retries = cmd->retries = DI_COMMAND_RETRIES; +} +#endif + +/* + * Builds a "Read Sector" command. + */ +static void di_op_readsector(struct di_command *cmd, + struct di_device *ddev, + u32 sector, void *data, size_t len) +{ + di_op_basic(cmd, ddev, DI_OP_READSECTOR); + cmd->cmdbuf1 = sector << KERNEL_SECTOR_SHIFT >> DI_SECTOR_SHIFT; + cmd->cmdbuf2 = len >> DI_SECTOR_SHIFT; + cmd->data = data; + cmd->len = len; + cmd->max_retries = cmd->retries = DI_COMMAND_RETRIES; +} + +/* + * Builds a "Read Physical Info" command. + */ +static void di_op_readphysinfo(struct di_command *cmd, + struct di_device *ddev, + u8 sector, void *data) +{ + di_op_basic(cmd, ddev, DI_OP_READPHYSINFO); + cmd->cmdbuf0 |= (sector << 8); + cmd->data = data; + cmd->len = 2048; +} + + +/* + * Builds a "get drive status" command. + */ +static inline void di_op_getstatus(struct di_command *cmd, + struct di_device *ddev) +{ + di_op_basic(cmd, ddev, DI_OP_GETSTATUS); +} + +/* + * Returns the printable form of the status part of a drive status. + */ +static char *di_printable_status(u32 drive_status) +{ + char *s = "unknown"; + + switch (DI_STATUS(drive_status)) { + case DI_STATUS_READY: + s = "ready"; + break; + case DI_STATUS_COVER_OPENED: + s = "cover opened"; + break; + case DI_STATUS_DISK_CHANGE: + s = "disk change"; + break; + case DI_STATUS_NO_DISK: + s = "no disk"; + break; + case DI_STATUS_MOTOR_STOP: + s = "motor stop"; + break; + case DI_STATUS_DISK_ID_NOT_READ: + s = "disk id not read"; + break; + } + return s; +} + +/* + * Returns the printable form of the error part of a drive status. + */ +static char *di_printable_error(u32 drive_status) +{ + char *s = "unknown"; + + switch (DI_ERROR(drive_status)) { + case DI_ERROR_NO_ERROR: + s = "no error"; + break; + case DI_ERROR_MOTOR_STOPPED: + s = "motor stopped"; + break; + case DI_ERROR_DISK_ID_NOT_READ: + s = "disk id not read"; + break; + case DI_ERROR_MEDIUM_NOT_PRESENT: + s = "medium not present"; + break; + case DI_ERROR_SEEK_INCOMPLETE: + s = "seek incomplete"; + break; + case DI_ERROR_UNRECOVERABLE_READ: + s = "unrecoverable read"; + break; + case DI_ERROR_INVALID_COMMAND: + s = "invalid command"; + break; + case DI_ERROR_BLOCK_OUT_OF_RANGE: + s = "block out of range"; + break; + case DI_ERROR_INVALID_FIELD: + s = "invalid field"; + break; + case DI_ERROR_MEDIUM_CHANGED: + s = "medium changed"; + break; + } + + return s; +} + +static char *di_printable_book_type(u8 book_type) +{ + char *s = "unknown"; + + switch (book_type) { + case DI_BOOK_TYPE_DVD_ROM: + s = "DVD-ROM"; + break; + case DI_BOOK_TYPE_DVD_RAM: + s = "DVD-RAM"; + break; + case DI_BOOK_TYPE_DVD_MINUS_R: + s = "DVD-R"; + break; + case DI_BOOK_TYPE_DVD_MINUS_RW: + s = "DVD-RW"; + break; + case DI_BOOK_TYPE_DVD_PLUS_RW: + s = "DVD+RW"; + break; + case DI_BOOK_TYPE_DVD_PLUS_R: + s = "DVD+R"; + break; + case DI_BOOK_TYPE_DVD_PLUS_RW_DL: + s = "DVD+RW DL"; + break; + case DI_BOOK_TYPE_DVD_PLUS_R_DL: + s = "DVD+R DL"; + break; + } + return s; +} + +static char *di_printable_disk_size(u8 disk_size) +{ + char *s = "unknown"; + + switch (disk_size) { + case DI_DISK_SIZE_80MM: + s = "80mm"; + break; + case DI_DISK_SIZE_120MM: + s = "120mm"; + break; + } + return s; +} + +/* + * Prints the given drive status, only if debug enabled. + */ +static inline void di_debug_print_drive_status(u32 drive_status) +{ + pr_devel("%08x, [%s, %s]\n", drive_status, + di_printable_status(drive_status), + di_printable_error(drive_status)); +} + +/* + * Prints the given drive status. + */ +static void di_print_drive_status(u32 drive_status) +{ + pr_info("drive_status=%08x, [%s, %s]\n", drive_status, + di_printable_status(drive_status), + di_printable_error(drive_status)); +} + +#if 0 +/* + * Prints the given disk identifier. + */ +static void di_print_disk_id(struct di_disk_id *disk_id) +{ + pr_info("disk_id = [%s]\n", disk_id->id); +} +#endif + +/* + * + * I/O. + */ + +/* + * Converts a request direction into a DMA data direction. + */ +static inline +enum dma_data_direction di_opidx_to_dma_dir(struct di_command *cmd) +{ + u16 op = di_op(cmd); + + if (unlikely(op & DI_DIR_WRITE)) + return DMA_TO_DEVICE; + else + return DMA_FROM_DEVICE; +} + +/* + * Starts a DMA transfer. + */ +static void di_start_dma_transfer_raw(struct di_device *ddev, + dma_addr_t data, size_t len, int mode) +{ + void __iomem *io_base = ddev->io_base; + u32 __iomem *sr_reg = io_base + DI_SR; + unsigned long flags; + + BUG_ON((data & DI_DMA_ALIGN) != 0 || + (len & DI_DMA_ALIGN) != 0); + + /* setup address and length of transfer */ + out_be32(io_base + DI_LENGTH, len); + out_be32(io_base + DI_MAR, data); + + /* enable the Transfer Complete interrupt */ + spin_lock_irqsave(&ddev->io_lock, flags); + out_be32(sr_reg, in_be32(sr_reg) | DI_SR_TCINTMASK); + spin_unlock_irqrestore(&ddev->io_lock, flags); + + /* start the transfer */ + out_be32(io_base + DI_CR, DI_CR_TSTART | DI_CR_DMA | + (mode & DI_CR_RW)); +} + +/* + * Internal. Busy-waits until a DMA transfer finishes or timeouts. + */ +static int __wait_for_dma_transfer_or_timeout(u32 __iomem *cr_reg, + int secs) +{ + unsigned long timeout = jiffies + secs*HZ; + + /* busy-wait for transfer complete */ + while ((in_be32(cr_reg) & DI_CR_TSTART) && + time_before(jiffies, timeout)) + cpu_relax(); + + return (in_be32(cr_reg) & DI_CR_TSTART) ? -EBUSY : 0; +} + +#if 0 +/* + * Busy-waits until DMA transfers are finished. + */ +static void di_wait_for_dma_transfer_raw(struct di_device *ddev) +{ + u32 __iomem *cr_reg = ddev->io_base + DI_CR; + u32 __iomem *sr_reg = ddev->io_base + DI_SR; + unsigned long flags; + + /* we don't want TCINTs to disturb us while waiting */ + spin_lock_irqsave(&ddev->io_lock, flags); + out_be32(sr_reg, in_be32(sr_reg) & ~DI_SR_TCINTMASK); + spin_unlock_irqrestore(&ddev->io_lock, flags); + + /* if the drive got stuck, reset it */ + if (__wait_for_dma_transfer_or_timeout(cr_reg, DI_COMMAND_TIMEOUT)) { + pr_devel("dvd stuck!\n"); + di_reset(ddev); + } + + /* ack and enable the Transfer Complete interrupt */ + spin_lock_irqsave(&ddev->io_lock, flags); + out_be32(sr_reg, in_be32(sr_reg) | (DI_SR_TCINT|DI_SR_TCINTMASK)); + spin_unlock_irqrestore(&ddev->io_lock, flags); + + return; +} +#endif + +/* + * Quiesces the hardware to a calm and known state. + */ +static void di_quiesce(struct di_device *ddev) +{ + void __iomem *io_base = ddev->io_base; + u32 __iomem *cr_reg = io_base + DI_CR; + u32 __iomem *sr_reg = io_base + DI_SR; + u32 __iomem *cvr_reg = io_base + DI_CVR; + u32 sr, cvr; + unsigned long flags; + + spin_lock_irqsave(&ddev->io_lock, flags); + + /* ack and mask dvd io interrupts */ + sr = in_be32(sr_reg); + sr |= DI_SR_BRKINT | DI_SR_TCINT | DI_SR_DEINT; + sr &= ~(DI_SR_BRKINTMASK | DI_SR_TCINTMASK | DI_SR_DEINTMASK); + out_be32(sr_reg, sr); + + /* ack and mask dvd cover interrupts */ + cvr = in_be32(cvr_reg); + out_be32(cvr_reg, (cvr | DI_CVR_CVRINT) & ~DI_CVR_CVRINTMASK); + + spin_unlock_irqrestore(&ddev->io_lock, flags); + + /* busy-wait for transfer complete */ + __wait_for_dma_transfer_or_timeout(cr_reg, DI_COMMAND_TIMEOUT); +} + +/* + * Command engine. + * + */ + +/* + * Outputs the command buffers, and optionally starts a transfer. + */ +static void di_prepare_command(struct di_command *cmd, int tstart) +{ + struct di_opcode *opcode = di_get_opcode(cmd); + void __iomem *io_base = cmd->ddev->io_base; + + out_be32(io_base + DI_CMDBUF0, cmd->cmdbuf0); + out_be32(io_base + DI_CMDBUF1, cmd->cmdbuf1); + out_be32(io_base + DI_CMDBUF2, cmd->cmdbuf2); + + cmd->ddev->drive_status = 0; + + if (tstart) { + out_be32(io_base + DI_CR, DI_CR_TSTART | + (opcode->op & DI_CR_RW)); + } +} + +static void di_command_done(struct di_command *cmd); + +/* + * Starts a command by using the immediate mode. + */ +static int di_start_command(struct di_command *cmd) +{ + struct di_device *ddev = cmd->ddev; + unsigned long flags; + int retval = 1; + + spin_lock_irqsave(&ddev->lock, flags); + + BUG_ON(ddev->cmd); + + ddev->cmd = cmd; + cmd->dma_len = 0; /* no dma here */ + di_prepare_command(cmd, 1); + + spin_unlock_irqrestore(&ddev->lock, flags); + + return retval; +} + +/* + * Starts a command by using the DMA mode. + */ +static int di_start_dma_command(struct di_command *cmd) +{ + struct di_device *ddev = cmd->ddev; + unsigned long flags; + int retval = 1; + + spin_lock_irqsave(&ddev->lock, flags); + + BUG_ON(ddev->cmd); + BUG_ON(!cmd->len || !cmd->data); + + ddev->cmd = cmd; + cmd->dma_len = cmd->len; + cmd->dma_addr = dma_map_single(ddev->dev, + cmd->data, cmd->len, + di_opidx_to_dma_dir(cmd)); + if (dma_mapping_error(ddev->dev, cmd->dma_addr)) { + retval = -EIO; + goto done; + } + + di_prepare_command(cmd, 0); + di_start_dma_transfer_raw(ddev, cmd->dma_addr, cmd->dma_len, + di_op(cmd) & DI_DIR_WRITE); + +done: + spin_unlock_irqrestore(&ddev->lock, flags); + + return retval; +} + +/* + * Completes a "get drive status" command, after a failed command. + */ +static void di_complete_getstatus(struct di_command *cmd) +{ + struct di_device *ddev = cmd->ddev; + void __iomem *io_base = ddev->io_base; + u32 __iomem *data_reg = io_base + DI_DATA; + + ddev->drive_status = in_be32(data_reg); +} + +/* + * Called after a transfer is completed. + */ +static void di_complete_transfer(struct di_device *ddev, u32 result) +{ + struct di_command *cmd; + struct di_opcode *opcode; + u32 drive_status; + unsigned long flags; + + spin_lock_irqsave(&ddev->lock, flags); + + /* do nothing if we have nothing to complete */ + cmd = ddev->cmd; + if (!cmd) { + spin_unlock_irqrestore(&ddev->lock, flags); + goto out; + } + + /* free the command slot */ + ddev->cmd = NULL; + + spin_unlock_irqrestore(&ddev->lock, flags); + + /* deal with caches after a dma transfer */ + if (cmd->dma_len) { + dma_unmap_single(ddev->dev, + cmd->dma_addr, cmd->dma_len, + di_opidx_to_dma_dir(cmd)); +#if 1 + /* + * Meh... + * Someone (the block layer?) seems to dereference parts of + * req->buffer after having dma-mapped it and while it is + * owned by the low level driver. + * + * This causes a problem in non-coherent systems as those + * parts of the buffer end up with the wrong data if + * dereferenced before the DI dma engine writes to them. + * Force a cache invalidation again to make sure that we + * really get the right data from the device. + * + * Without this countermeasure you can easily get random + * garbage in files while reading them from a iso9660 disc. + */ + if (likely(di_opidx_to_dma_dir(cmd) == DMA_FROM_DEVICE)) + __dma_sync(cmd->data, cmd->len, DMA_FROM_DEVICE); +#endif + } + + opcode = di_get_opcode(cmd); + + /* + * If a command fails we check the drive status. Depending on that + * we may or not retry later the command. + */ + cmd->result = result; + if (!di_command_ok(cmd)) { + BUG_ON(ddev->failed_cmd != NULL); + + ddev->failed_cmd = cmd; + + /* + * Issue immediately a "get drive status" + * after a failed command. + */ + cmd = &ddev->status; + di_op_getstatus(cmd, ddev); + cmd->done = di_complete_getstatus; + di_run_command(cmd); + goto out; + } else { + if (cmd->retries != cmd->max_retries) { + pr_devel("command %s succeeded after %d retries :-)\n", + opcode->name, cmd->max_retries - cmd->retries); + } + } + + /* complete a successful command, or the MATSHITA one */ + di_command_done(cmd); + + spin_lock_irqsave(&ddev->lock, flags); + if (ddev->failed_cmd) { + cmd = ddev->failed_cmd; + ddev->failed_cmd = NULL; + spin_unlock_irqrestore(&ddev->lock, flags); + + drive_status = ddev->drive_status; + opcode = di_get_opcode(cmd); + + /* retry a previously failed command if appropiate */ + if (cmd->retries > 0) { + if (di_may_retry(drive_status)) { + pr_devel("command %s failed, %d retries left\n", + opcode->name, cmd->retries); + di_debug_print_drive_status(drive_status); + + cmd->retries--; + di_run_command(cmd); + goto out; + } else { + pr_devel("command %s failed," + " aborting due to drive status\n", + opcode->name); + } + } else { + if (!(opcode->op & DI_IGNORE_ERRORS)) + pr_devel("command %s failed\n", opcode->name); + } + + if (!(opcode->op & DI_IGNORE_ERRORS)) + di_print_drive_status(drive_status); + + /* complete the failed command */ + di_command_done(cmd); + + /* update the driver status */ + switch (DI_ERROR(drive_status)) { + case DI_ERROR_MOTOR_STOPPED: + case DI_ERROR_MEDIUM_NOT_PRESENT: + case DI_ERROR_MEDIUM_CHANGED: + set_bit(__DI_MEDIA_CHANGED, &ddev->flags); + break; + default: + break; + } + + } else { + spin_unlock_irqrestore(&ddev->lock, flags); + } + + /* start the block layer queue if someone requested it */ + if (test_and_clear_bit(__DI_START_QUEUE, &ddev->flags)) { + spin_lock_irqsave(&ddev->queue_lock, flags); + blk_start_queue(ddev->queue); + spin_unlock_irqrestore(&ddev->queue_lock, flags); + } + +out: + return; +} + +/* + * Calls any done hooks. + */ +static void di_command_done(struct di_command *cmd) +{ + struct di_device *ddev = cmd->ddev; + unsigned long flags; + + /* if specified, call the completion routine */ + if (cmd->done) + cmd->done(cmd); + + spin_lock_irqsave(&ddev->lock, flags); + spin_unlock_irqrestore(&ddev->lock, flags); +} + +/* + * Completion routine. + */ +static void di_wait_done(struct di_command *cmd) +{ + complete(cmd->done_data); +} + +/* + * Runs a command. + */ +static int di_run_command(struct di_command *cmd) +{ + struct di_opcode *opcode = di_get_opcode(cmd); + int retval; + + if (cmd->retries > cmd->max_retries) + cmd->retries = cmd->max_retries; + + if (!(opcode->op & DI_MODE_DMA)) + retval = di_start_command(cmd); + else + retval = di_start_dma_command(cmd); + return retval; +} + +/* + * Runs a command and waits. + * Might sleep if called from user context. + */ +static int di_run_command_and_wait(struct di_command *cmd) +{ + DECLARE_COMPLETION(complete); + + cmd->done_data = &complete; + cmd->done = di_wait_done; + if (di_run_command(cmd) > 0) + wait_for_completion(&complete); + return cmd->result; +} + +/* + * Interrupt handler for DI interrupts. + */ +static irqreturn_t di_irq_handler(int irq, void *dev0) +{ + struct di_device *ddev = dev0; + void __iomem *io_base = ddev->io_base; + u32 __iomem *sr_reg = io_base + DI_SR; + u32 __iomem *cvr_reg = io_base + DI_CVR; + u32 sr, cvr, reason, mask; + unsigned long flags; + + spin_lock_irqsave(&ddev->io_lock, flags); + + sr = in_be32(sr_reg); + mask = sr & (DI_SR_BRKINTMASK | DI_SR_TCINTMASK | DI_SR_DEINTMASK); + reason = sr; /* & (mask << 1); */ + if (reason) { + out_be32(sr_reg, sr | reason); + spin_unlock_irqrestore(&ddev->io_lock, flags); + + if (reason & DI_SR_BRKINT) { + pr_devel("BRKINT\n"); + di_complete_transfer(ddev, DI_SR_BRKINT); + } + if (reason & DI_SR_TCINT) + di_complete_transfer(ddev, DI_SR_TCINT); + if (reason & DI_SR_DEINT) + di_complete_transfer(ddev, DI_SR_DEINT); + + spin_lock_irqsave(&ddev->io_lock, flags); + } + + cvr = in_be32(cvr_reg); + mask = cvr & DI_CVR_CVRINTMASK; + reason = cvr; /* & (mask << 1); */ + if ((reason & DI_CVR_CVRINT)) { + out_be32(cvr_reg, cvr | DI_CVR_CVRINT); + set_bit(__DI_MEDIA_CHANGED, &ddev->flags); + if (!test_and_clear_bit(__DI_RESETTING, &ddev->flags)) + pr_devel("disk inserted!\n"); + } + + spin_unlock_irqrestore(&ddev->io_lock, flags); + + return IRQ_HANDLED; +} + +/* + * Hard-resets the drive. + */ +static void di_reset(struct di_device *ddev) +{ + void *hw_diflags; + void *hw_resets; + + hw_resets = ioremap(0x0d800194, 4); + if (hw_resets) { + set_bit(__DI_RESETTING, &ddev->flags); + /* reset dvd unit */ + out_be32(hw_resets, in_be32(hw_resets) & ~(1<<10)); + mdelay(50); + out_be32(hw_resets, in_be32(hw_resets) | (1<<10)); + iounmap(hw_resets); + } + pr_devel("drive reset\n"); + hw_diflags = ioremap(0x0d800180, 4); + if (hw_diflags) { + /* enable dvd-video */ + out_be32(hw_diflags, in_be32(hw_diflags) & ~(1<<21)); + iounmap(hw_diflags); + } +} + + +/* + * Misc routines. + * + */ + +/* + * Retrieves (and prints out) the laser unit model. + */ +static u32 di_retrieve_drive_model(struct di_device *ddev) +{ + struct di_drive_info *di_drive_info; + struct di_command cmd; + + di_drive_info = (struct di_drive_info *)di_scratch_frame; + memset(di_drive_info, 0, sizeof(*di_drive_info)); + di_op_inq(&cmd, ddev, di_drive_info); + di_run_command_and_wait(&cmd); + + pr_info("laser unit: rev=%x, code=%x, date=%x\n", + di_drive_info->rev, di_drive_info->code, + di_drive_info->date); + + ddev->model = di_drive_info->date; + return ddev->model; +} + +/* + * Gets the current drive status. + */ +static u32 di_get_drive_status(struct di_device *ddev) +{ + void __iomem *io_base = ddev->io_base; + u32 __iomem *data_reg = io_base + DI_DATA; + struct di_command cmd; + u32 drive_status; + + di_op_getstatus(&cmd, ddev); + di_run_command_and_wait(&cmd); + drive_status = in_be32(data_reg); + + return drive_status; +} + +/* + * Checks if the drive is in a ready state. + */ +static int di_is_drive_ready(struct di_device *ddev) +{ + u32 drive_status; + int result = 0; + + drive_status = di_get_drive_status(ddev); + if (DI_STATUS(drive_status) == DI_STATUS_DISK_ID_NOT_READ || + DI_STATUS(drive_status) == DI_STATUS_READY) { + result = 1; + } + + return result; +} + +/* + * Spins down the drive, immediatelly. + */ +static void di_spin_down_drive(struct di_device *ddev) +{ + struct di_command cmd; + + di_op_stopmotor(&cmd, ddev); + di_run_command_and_wait(&cmd); +} + +/* + * Stops the drive's motor, according to a previous schedule. + */ +static void di_motor_off(unsigned long ddev0) +{ + struct di_device *ddev = (struct di_device *)ddev0; + struct di_command *cmd; + unsigned long flags; + + /* postpone a bit the motor off if there are pending commands */ + spin_lock_irqsave(&ddev->lock, flags); + if (!ddev->cmd) { + ddev->cmd = cmd = &ddev->status; + spin_unlock_irqrestore(&ddev->lock, flags); + di_op_stopmotor(cmd, ddev); + di_prepare_command(cmd, 1); + } else { + spin_unlock_irqrestore(&ddev->lock, flags); + mod_timer(&ddev->motor_off_timer, jiffies + 1*HZ); + } +} + +/* + * Cancels a previously scheduled motor off. + */ +static inline void di_cancel_motor_off(struct di_device *ddev) +{ + del_timer(&ddev->motor_off_timer); +} + +/* + * Stops the drive's motor after the specified amount of seconds has elapsed. + */ +static void di_schedule_motor_off(struct di_device *ddev, unsigned int secs) +{ + del_timer(&ddev->motor_off_timer); + ddev->motor_off_timer.expires = jiffies + secs*HZ; + ddev->motor_off_timer.data = (unsigned long)ddev; + add_timer(&ddev->motor_off_timer); +} + +/* + * Spins up the drive. + */ +static void di_spin_up_drive(struct di_device *ddev) +{ + /* do nothing if the drive is already spinning */ + if (di_is_drive_ready(ddev)) + goto out; + + di_reset(ddev); +out: + return; +} + + +/* + * Block layer hooks. + * + */ + +static int di_read_toc(struct di_device *ddev) +{ + struct di_phys_format_info *info; + struct di_command cmd; + int error; + + di_cancel_motor_off(ddev); + + /* spin up the drive if needed */ + if ((ddev->flags & DI_MEDIA_CHANGED)) + di_spin_up_drive(ddev); + + di_op_readphysinfo(&cmd, ddev, 0, di_scratch_frame); + di_run_command_and_wait(&cmd); + if (di_command_ok(&cmd)) { + info = (struct di_phys_format_info *)di_scratch_frame; + + be32_to_cpus(&info->first_data_psn); + be32_to_cpus(&info->last_data_psn); + + /* nr_sectors is specified in DI sectors here */ + ddev->nr_sectors = info->last_data_psn - + info->first_data_psn + 1; + + pr_devel("%s %s disk found (%lu sectors)\n", + di_printable_disk_size(info->disk_size), + di_printable_book_type(info->disk_category), + ddev->nr_sectors); + clear_bit(__DI_MEDIA_CHANGED, &ddev->flags); + error = 0; + } else { + ddev->nr_sectors = 0; + + pr_devel("media NOT ready\n"); + di_spin_down_drive(ddev); + error = -ENOMEDIUM; + } + + /* transform to kernel sectors */ + ddev->nr_sectors <<= (DI_SECTOR_SHIFT - KERNEL_SECTOR_SHIFT); + set_capacity(ddev->disk, ddev->nr_sectors); + + return error; +} + + +static void di_request_done(struct di_command *cmd) +{ + struct di_device *ddev = cmd->ddev; + struct request *req; + unsigned long flags; + int error = (cmd->result & DI_SR_TCINT) ? 0 : -EIO; + + spin_lock_irqsave(&ddev->lock, flags); + req = ddev->req; + ddev->req = NULL; + spin_unlock_irqrestore(&ddev->lock, flags); + + if (req) { + spin_lock_irqsave(&ddev->queue_lock, flags); + __blk_end_request_cur(req, error); + blk_start_queue(ddev->queue); + spin_unlock_irqrestore(&ddev->queue_lock, flags); + } +} + +static void di_do_request(struct request_queue *q) +{ + struct di_device *ddev = q->queuedata; + struct di_command *cmd = &ddev->req_cmd; + struct request *req; + unsigned long start; + unsigned long flags; + size_t len; + int error; + + req = blk_peek_request(q); + while (req) { + spin_lock_irqsave(&ddev->lock, flags); + + if (ddev->req || ddev->cmd) { + blk_stop_queue(q); + if (ddev->cmd) + set_bit(__DI_START_QUEUE, &ddev->flags); + spin_unlock_irqrestore(&ddev->lock, flags); + break; + } + + blk_start_request(req); + error = -EIO; + + if (!blk_fs_request(req)) + goto done; + + /* it doesn't make sense to write to this device */ + if (unlikely(rq_data_dir(req) == WRITE)) { + pr_err("write attempted\n"); + goto done; + } + + /* it is not a good idea to open the lid ... */ + if ((ddev->flags & DI_MEDIA_CHANGED)) { + pr_err("media changed, aborting\n"); + goto done; + } + + /* keep our reads within limits */ + if (blk_rq_pos(req) + + blk_rq_cur_sectors(req) > ddev->nr_sectors) { + pr_err("reading past end\n"); + goto done; + } + + ddev->req = req; + blk_stop_queue(q); + spin_unlock_irqrestore(&ddev->lock, flags); + + /* launch the corresponding read sector command */ + start = blk_rq_pos(req); + len = blk_rq_cur_bytes(req); + if (len & (DI_SECTOR_SIZE-1)) + pr_devel("len=%u\n", len); + + di_op_readsector(cmd, ddev, start, req->buffer, len); + cmd->done_data = cmd; + cmd->done = di_request_done; + di_run_command(cmd); + error = 0; + break; + done: + spin_unlock_irqrestore(&ddev->lock, flags); + if (!__blk_end_request_cur(req, error)) + req = blk_peek_request(q); + } +} + +/* + * Block device hooks. + * + */ + +static int di_open(struct block_device *bdev, fmode_t mode) +{ + struct di_device *ddev = bdev->bd_disk->private_data; + struct di_command *cmd; + DECLARE_COMPLETION(complete); + unsigned long flags; + int retval = 0; + + /* this is a read only device */ + if (mode & FMODE_WRITE) { + retval = -EROFS; + goto out; + } + + /* + * If we have a pending command, that's a previously scheduled + * motor off. Wait for it to terminate before going on. + */ + spin_lock_irqsave(&ddev->lock, flags); + if (ddev->cmd && ddev->ref_count == 0) { + cmd = ddev->cmd; + cmd->done_data = &complete; + cmd->done = di_wait_done; + spin_unlock_irqrestore(&ddev->lock, flags); + wait_for_completion(&complete); + } else { + spin_unlock_irqrestore(&ddev->lock, flags); + } + + /* this will take care of validating the media */ + check_disk_change(bdev); + if (!ddev->nr_sectors) { + retval = -ENOMEDIUM; + goto out; + } + + spin_lock_irqsave(&ddev->queue_lock, flags); + + /* honor exclusive open mode */ + if (ddev->ref_count == -1 || + (ddev->ref_count && (mode & FMODE_EXCL))) { + retval = -EBUSY; + goto out_unlock; + } + + if ((mode & FMODE_EXCL)) + ddev->ref_count = -1; + else + ddev->ref_count++; + +out_unlock: + spin_unlock_irqrestore(&ddev->queue_lock, flags); +out: + return retval; + +} + +static int di_release(struct gendisk *disk, fmode_t mode) +{ + struct di_device *ddev = disk->private_data; + unsigned long flags; + + spin_lock_irqsave(&ddev->queue_lock, flags); + + if (ddev->ref_count > 0) + ddev->ref_count--; + else + ddev->ref_count = 0; + + spin_unlock_irqrestore(&ddev->queue_lock, flags); + + if (ddev->ref_count == 0) { + /* + * We do not immediately stop the motor, which saves us + * a spin down/spin up in applications that re-open quickly + * the device, like mount when -t is not specified. + */ + di_schedule_motor_off(ddev, 1); + + set_bit(__DI_MEDIA_CHANGED, &ddev->flags); + } + + return 0; +} + +static int di_revalidate_disk(struct gendisk *disk) +{ + struct di_device *ddev = disk->private_data; + di_read_toc(ddev); + return 0; +} + +static int di_media_changed(struct gendisk *disk) +{ + struct di_device *ddev = disk->private_data; + return (ddev->flags & DI_MEDIA_CHANGED) ? 1 : 0; +} + +static int di_ioctl(struct block_device *bdev, fmode_t mode, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int ret; + struct gendisk *disk = bdev->bd_disk; + + ret = scsi_cmd_ioctl(disk->queue, disk, mode, cmd, argp); + if (ret != -ENOTTY) + return ret; + + return -ENOSYS; +} + +static struct block_device_operations di_fops = { + .owner = THIS_MODULE, + .open = di_open, + .release = di_release, + .revalidate_disk = di_revalidate_disk, + .media_changed = di_media_changed, + .ioctl = di_ioctl, +}; + +/* + * Setup routines. + * + */ + +static int di_init_irq(struct di_device *ddev) +{ + void __iomem *io_base = ddev->io_base; + u32 __iomem *sr_reg = io_base + DI_SR; + u32 __iomem *cvr_reg = io_base + DI_CVR; + u32 sr, cvr; + unsigned long flags; + int retval; + + init_timer(&ddev->motor_off_timer); + ddev->motor_off_timer.function = + (void (*)(unsigned long))di_motor_off; + + ddev->flags = 0; + set_bit(__DI_MEDIA_CHANGED, &ddev->flags); + + /* calm down things a bit first */ + di_quiesce(ddev); + + /* request interrupt */ + retval = request_irq(ddev->irq, di_irq_handler, 0, + DRV_MODULE_NAME, ddev); + if (retval) { + pr_err("request of irq%d failed\n", ddev->irq); + goto out; + } + + spin_lock_irqsave(&ddev->io_lock, flags); + + sr = in_be32(sr_reg); + sr |= DI_SR_BRKINT | DI_SR_TCINT | DI_SR_DEINT; + sr |= DI_SR_BRKINTMASK | DI_SR_TCINTMASK | DI_SR_DEINTMASK; + out_be32(sr_reg, sr); + + cvr = in_be32(cvr_reg); + out_be32(cvr_reg, cvr | DI_CVR_CVRINT | DI_CVR_CVRINTMASK); + + spin_unlock_irqrestore(&ddev->io_lock, flags); + + di_retrieve_drive_model(ddev); + + di_schedule_motor_off(ddev, DI_MOTOR_OFF_TIMEOUT); + +out: + return retval; +} + +static void di_exit_irq(struct di_device *ddev) +{ + /* stop DVD motor */ + di_cancel_motor_off(ddev); + di_spin_down_drive(ddev); + + di_quiesce(ddev); + + free_irq(ddev->irq, ddev); +} + +static int di_init_blk_dev(struct di_device *ddev) +{ + struct gendisk *disk; + struct request_queue *queue; + int retval; + + spin_lock_init(&ddev->lock); + spin_lock_init(&ddev->io_lock); + + ddev->ref_count = 0; + + retval = register_blkdev(DI_MAJOR, DI_NAME); + if (retval) { + pr_err("error registering major %d\n", DI_MAJOR); + goto err_register_blkdev; + } + + retval = -ENOMEM; + spin_lock_init(&ddev->queue_lock); + queue = blk_init_queue(di_do_request, &ddev->queue_lock); + if (!queue) { + pr_err("error initializing queue\n"); + goto err_blk_init_queue; + } + + blk_queue_logical_block_size(queue, DI_SECTOR_SIZE); + blk_queue_dma_alignment(queue, DI_DMA_ALIGN); + /* max number of data segments that the driver deals with */ + blk_queue_max_phys_segments(queue, 1); + /* max number of data segments that the hardware deals with */ + blk_queue_max_hw_segments(queue, 1); + /* max size for a data segment */ + blk_queue_max_segment_size(queue, 32*1024); + queue->queuedata = ddev; + ddev->queue = queue; + + disk = alloc_disk(1); + if (!disk) { + pr_err("error allocating disk\n"); + goto err_alloc_disk; + } + + disk->major = DI_MAJOR; + disk->first_minor = 0; + disk->fops = &di_fops; + strcpy(disk->disk_name, DI_NAME); + disk->queue = ddev->queue; + disk->private_data = ddev; + ddev->disk = disk; + + set_disk_ro(ddev->disk, 1); + add_disk(ddev->disk); + + retval = 0; + goto out; + +err_alloc_disk: + blk_cleanup_queue(ddev->queue); +err_blk_init_queue: + unregister_blkdev(DI_MAJOR, DI_NAME); +err_register_blkdev: +out: + return retval; +} + +static void di_exit_blk_dev(struct di_device *ddev) +{ + if (ddev->disk) { + del_gendisk(ddev->disk); + put_disk(ddev->disk); + } + if (ddev->queue) + blk_cleanup_queue(ddev->queue); + unregister_blkdev(DI_MAJOR, DI_NAME); +} + +static int di_init_proc(struct di_device *ddev) +{ +#ifdef CONFIG_PROC_FS +#endif /* CONFIG_PROC_FS */ + return 0; +} + +static void di_exit_proc(struct di_device *ddev) +{ +#ifdef CONFIG_PROC_FS +#endif /* CONFIG_PROC_FS */ +} + +static int di_init(struct di_device *ddev, struct resource *mem, int irq) +{ + int retval; + + ddev->io_base = ioremap(mem->start, mem->end - mem->start + 1); + ddev->irq = irq; + + retval = di_init_blk_dev(ddev); + if (!retval) { + retval = di_init_irq(ddev); + if (retval) + di_exit_blk_dev(ddev); + else + di_init_proc(ddev); + } + return retval; +} + +static void di_exit(struct di_device *ddev) +{ + di_exit_blk_dev(ddev); + di_exit_irq(ddev); + di_exit_proc(ddev); + if (ddev->io_base) { + iounmap(ddev->io_base); + ddev->io_base = NULL; + } +} + +/* + * Driver model helper routines. + * + */ + +static int di_do_probe(struct device *dev, + struct resource *mem, int irq) +{ + struct di_device *ddev; + int retval; + + ddev = kzalloc(sizeof(*ddev), GFP_KERNEL); + if (!ddev) { + pr_err("failed to allocate di_device\n"); + return -ENOMEM; + } + dev_set_drvdata(dev, ddev); + ddev->dev = dev; + + retval = di_init(ddev, mem, irq); + if (retval) { + dev_set_drvdata(dev, NULL); + kfree(ddev); + } + return retval; +} + +static int di_do_remove(struct device *dev) +{ + struct di_device *ddev = dev_get_drvdata(dev); + + if (ddev) { + di_exit(ddev); + dev_set_drvdata(dev, NULL); + kfree(ddev); + return 0; + } + return -ENODEV; +} + +static int di_do_shutdown(struct device *dev) +{ + struct di_device *ddev = dev_get_drvdata(dev); + + if (ddev) + di_quiesce(ddev); + return 0; +} + +/* + * OF platform driver hooks. + * + */ + +static int __init di_of_probe(struct of_device *odev, + const struct of_device_id *match) +{ + struct resource res; + int error = -ENODEV; + + if (starlet_get_ipc_flavour() != STARLET_IPC_MINI) + goto out; + + error = of_address_to_resource(odev->node, 0, &res); + if (error) { + pr_err("no io memory range found\n"); + goto out; + } + + error = di_do_probe(&odev->dev, &res, + irq_of_parse_and_map(odev->node, 0)); +out: + return error; +} + +static int __exit di_of_remove(struct of_device *odev) +{ + return di_do_remove(&odev->dev); +} + +static int di_of_shutdown(struct of_device *odev) +{ + return di_do_shutdown(&odev->dev); +} + + +static struct of_device_id di_of_match[] = { + { .compatible = "nintendo,hollywood-disk" }, + { }, +}; + +MODULE_DEVICE_TABLE(of, di_of_match); + +static struct of_platform_driver di_of_driver = { + .owner = THIS_MODULE, + .name = DRV_MODULE_NAME, + .match_table = di_of_match, + .probe = di_of_probe, + .remove = di_of_remove, + .shutdown = di_of_shutdown, +}; + +/* + * Module interface hooks. + * + */ + +static int __init di_init_module(void) +{ + pr_info("%s - version %s\n", DRV_DESCRIPTION, di_driver_version); + + return of_register_platform_driver(&di_of_driver); +} + +static void __exit di_exit_module(void) +{ + of_unregister_platform_driver(&di_of_driver); +} + +module_init(di_init_module); +module_exit(di_exit_module); + +MODULE_AUTHOR(DRV_AUTHOR); +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_LICENSE("GPL"); diff --git a/drivers/block/rvl-mem2.c b/drivers/block/rvl-mem2.c new file mode 100644 index 0000000..4906479 --- /dev/null +++ b/drivers/block/rvl-mem2.c @@ -0,0 +1,385 @@ +/* + * drivers/block/rvl-mem2.c + * + * Nintendo Wii MEM2 block driver + * Copyright (C) 2008-2009 The GameCube Linux Team + * Copyright (C) 2008,2009 Albert Herranz + * + * Based on gcn-aram.c. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include /* O_ACCMODE */ +#include /* HDIO_GETGEO */ +#include + + +#define DRV_MODULE_NAME "rvl-mem2" +#define DRV_DESCRIPTION "Nintendo Wii MEM2 block driver" +#define DRV_AUTHOR "Albert Herranz" + +static char mem2_driver_version[] = "0.1-isobel"; + +#define drv_printk(level, format, arg...) \ + printk(level DRV_MODULE_NAME ": " format , ## arg) + + +/* + * Driver settings + */ +#define MEM2_NAME DRV_MODULE_NAME +#define MEM2_MAJOR Z2RAM_MAJOR + +#define MEM2_SECTOR_SIZE PAGE_SIZE + + +struct mem2_drvdata { + spinlock_t lock; + + void __iomem *io_base; + size_t size; + + struct block_device_operations fops; + struct gendisk *disk; + struct request_queue *queue; + + int ref_count; + + struct device *dev; +}; + +/* + * + */ + +/* + * Performs block layer requests. + */ +static void mem2_do_request(struct request_queue *q) +{ + struct mem2_drvdata *drvdata = q->queuedata; + struct request *req; + unsigned long mem2_addr; + size_t len; + int error; + + req = blk_fetch_request(q); + while (req) { + error = -EIO; + + if (!blk_fs_request(req)) + goto done; + + /* calculate the MEM2 address and length */ + mem2_addr = blk_rq_pos(req) << 9; + len = blk_rq_cur_bytes(req); + + /* give up if the request goes out of bounds */ + if (mem2_addr + len > drvdata->size) { + drv_printk(KERN_ERR, "bad access: block=%lu," + " size=%zu\n", + (unsigned long)blk_rq_pos(req), len); + goto done; + } + + switch (rq_data_dir(req)) { + case READ: + memcpy(req->buffer, drvdata->io_base + mem2_addr, len); + break; + case WRITE: + memcpy(drvdata->io_base + mem2_addr, req->buffer, len); + break; + } + error = 0; + + done: + if (!__blk_end_request_cur(req, error)); + req = blk_fetch_request(q); + } +} + +/* + * Opens the MEM2 device. + */ +static int mem2_open(struct block_device *bdev, fmode_t mode) +{ + struct mem2_drvdata *drvdata = bdev->bd_disk->private_data; + unsigned long flags; + int retval = 0; + + spin_lock_irqsave(&drvdata->lock, flags); + + /* only allow a minor of 0 to be opened */ + if (MINOR(bdev->bd_dev)) { + retval = -ENODEV; + goto out; + } + + /* honor exclusive open mode */ + if (drvdata->ref_count == -1 || + (drvdata->ref_count && (mode & FMODE_EXCL))) { + retval = -EBUSY; + goto out; + } + + if ((mode & FMODE_EXCL)) + drvdata->ref_count = -1; + else + drvdata->ref_count++; + +out: + spin_unlock_irqrestore(&drvdata->lock, flags); + return retval; +} + +/* + * Closes the MEM2 device. + */ +static int mem2_release(struct gendisk *disk, fmode_t mode) +{ + struct mem2_drvdata *drvdata = disk->private_data; + unsigned long flags; + + spin_lock_irqsave(&drvdata->lock, flags); + if (drvdata->ref_count > 0) + drvdata->ref_count--; + else + drvdata->ref_count = 0; + spin_unlock_irqrestore(&drvdata->lock, flags); + + return 0; +} + +static int mem2_getgeo(struct block_device *bdev, struct hd_geometry *geo) +{ + geo->cylinders = get_capacity(bdev->bd_disk) / (4 * 16); + geo->heads = 4; + geo->sectors = 16; + return 0; +} + + +static struct block_device_operations mem2_fops = { + .owner = THIS_MODULE, + .open = mem2_open, + .release = mem2_release, + .getgeo = mem2_getgeo, +}; + + +/* + * + */ +static int mem2_init_blk_dev(struct mem2_drvdata *drvdata) +{ + struct gendisk *disk; + struct request_queue *queue; + int retval; + + drvdata->ref_count = 0; + + retval = register_blkdev(MEM2_MAJOR, MEM2_NAME); + if (retval) + goto err_register_blkdev; + + retval = -ENOMEM; + spin_lock_init(&drvdata->lock); + queue = blk_init_queue(mem2_do_request, &drvdata->lock); + if (!queue) + goto err_blk_init_queue; + + blk_queue_logical_block_size(queue, MEM2_SECTOR_SIZE); + blk_queue_max_phys_segments(queue, 1); + blk_queue_max_hw_segments(queue, 1); + queue_flag_set_unlocked(QUEUE_FLAG_NONROT, queue); + queue->queuedata = drvdata; + drvdata->queue = queue; + + disk = alloc_disk(1); + if (!disk) + goto err_alloc_disk; + + disk->major = MEM2_MAJOR; + disk->first_minor = 0; + disk->fops = &mem2_fops; + strcpy(disk->disk_name, MEM2_NAME); + disk->queue = drvdata->queue; + set_capacity(disk, drvdata->size >> 9); + disk->private_data = drvdata; + drvdata->disk = disk; + + add_disk(drvdata->disk); + + retval = 0; + goto out; + +err_alloc_disk: + blk_cleanup_queue(drvdata->queue); +err_blk_init_queue: + unregister_blkdev(MEM2_MAJOR, MEM2_NAME); +err_register_blkdev: +out: + return retval; +} + +/* + * + */ +static void mem2_exit_blk_dev(struct mem2_drvdata *drvdata) +{ + if (drvdata->disk) { + del_gendisk(drvdata->disk); + put_disk(drvdata->disk); + } + if (drvdata->queue) + blk_cleanup_queue(drvdata->queue); + unregister_blkdev(MEM2_MAJOR, MEM2_NAME); +} + +/* + * + */ +static int mem2_init(struct mem2_drvdata *drvdata, struct resource *mem) +{ + int retval; + size_t size; + + size = mem->end - mem->start + 1; + drvdata->size = size; + drvdata->io_base = ioremap(mem->start, size); + if (!drvdata->io_base) { + drv_printk(KERN_ERR, "failed to ioremap MEM2\n"); + return -EIO; + } + + retval = mem2_init_blk_dev(drvdata); + if (retval) + iounmap(drvdata->io_base); + return retval; +} + +/* + * + */ +static void mem2_exit(struct mem2_drvdata *drvdata) +{ + if (drvdata->io_base) + iounmap(drvdata->io_base); + mem2_exit_blk_dev(drvdata); +} + +/* + * + */ +static int mem2_do_probe(struct device *dev, struct resource *mem) +{ + struct mem2_drvdata *drvdata; + int retval; + + drvdata = kzalloc(sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) { + drv_printk(KERN_ERR, "failed to allocate mem2_drvdata\n"); + return -ENOMEM; + } + dev_set_drvdata(dev, drvdata); + drvdata->dev = dev; + + retval = mem2_init(drvdata, mem); + if (retval) { + dev_set_drvdata(dev, NULL); + kfree(drvdata); + } + return retval; +} + +/* + * + */ +static int mem2_do_remove(struct device *dev) +{ + struct mem2_drvdata *drvdata = dev_get_drvdata(dev); + + if (drvdata) { + mem2_exit(drvdata); + dev_set_drvdata(dev, NULL); + return 0; + } + return -ENODEV; +} + +/* + * Driver model probe function. + */ +static int __init mem2_of_probe(struct of_device *odev, + const struct of_device_id *match) +{ + struct resource res; + int retval; + + retval = of_address_to_resource(odev->node, 0, &res); + if (retval) { + drv_printk(KERN_ERR, "no memory range found\n"); + return -ENODEV; + } + + return mem2_do_probe(&odev->dev, &res); +} + +/* + * Driver model remove function. + */ +static int __exit mem2_of_remove(struct of_device *odev) +{ + return mem2_do_remove(&odev->dev); +} + +static struct of_device_id mem2_of_match[] = { + { .compatible = "nintendo,hollywood-mem2" }, + { }, +}; + +MODULE_DEVICE_TABLE(of, mem2_of_match); + +static struct of_platform_driver mem2_of_driver = { + .owner = THIS_MODULE, + .name = DRV_MODULE_NAME, + .match_table = mem2_of_match, + .probe = mem2_of_probe, + .remove = mem2_of_remove, +}; + +/* + * Module initialization function. + */ +static int __init mem2_init_module(void) +{ + drv_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION, + mem2_driver_version); + + return of_register_platform_driver(&mem2_of_driver); +} + +/* + * Module deinitialization funtion. + */ +static void __exit mem2_exit_module(void) +{ + of_unregister_platform_driver(&mem2_of_driver); +} + +module_init(mem2_init_module); +module_exit(mem2_exit_module); + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_AUTHOR(DRV_AUTHOR); +MODULE_LICENSE("GPL"); + diff --git a/drivers/block/rvl-stsd.c b/drivers/block/rvl-stsd.c new file mode 100644 index 0000000..502e2a5 --- /dev/null +++ b/drivers/block/rvl-stsd.c @@ -0,0 +1,2295 @@ +/* + * drivers/block/rvl-stsd.c + * + * Block driver for the Nintendo Wii SD front slot. + * Copyright (C) 2008-2009 The GameCube Linux Team + * Copyright (C) 2008,2009 Albert Herranz + * + * Based on drivers/block/gcn-sd.c + * + * Copyright (C) 2004-2008 The GameCube Linux Team + * Copyright (C) 2004,2005 Rob Reylink + * Copyright (C) 2005 Todd Jeffreys + * Copyright (C) 2005,2006,2007,2008 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#define DEBUG + +/*#define DBG(fmt, arg...) pr_debug(fmt, ##arg)*/ +#define DBG(fmt, arg...) drv_printk(KERN_ERR, fmt, ##arg) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * We are not a native MMC driver... + * But anyway, we try to recycle here some of the available code. + */ +#include +#include +#include +#include +#include +#include "../mmc/host/sdhci.h" + +#define DRV_MODULE_NAME "rvl-stsd" +#define DRV_DESCRIPTION "Block driver for the Nintendo Wii SD front slot" +#define DRV_AUTHOR "Albert Herranz" + +static char stsd_driver_version[] = "0.4i"; + +#define drv_printk(level, format, arg...) \ + printk(level DRV_MODULE_NAME ": " format , ## arg) + +/* + * Driver settings. + */ +#define MMC_SHIFT 3 /* 8 partitions */ + +#define STSD_MAJOR 62 +#define STSD_NAME "rvlsd" + +#define KERNEL_SECTOR_SHIFT 9 +#define KERNEL_SECTOR_SIZE (1 << KERNEL_SECTOR_SHIFT) /*512 */ + +#define STSD_MAX_SECTORS 16 + + +/* + * IOS-related constants. + */ + +/* ioctls */ +#define STSD_IOCTL_SETHSR 1 +#define STSD_IOCTL_GETHSR 2 +#define STSD_IOCTL_RESET 4 +#define STSD_IOCTL_SETCLOCK 6 +#define STSD_IOCTL_SENDCMD 7 +#define STSD_IOCTL_GETSTATUS 11 +#define STSD_IOCTL_GETOCR 12 + +#define STSD_IOCTLV_SENDCMD 7 + +/* SD command types */ +#define STSD_CMDTYPE_BC 1 +#define STSD_CMDTYPE_BCR 2 +#define STSD_CMDTYPE_AC 3 +#define STSD_CMDTYPE_ADTC 4 + +/* SD response types */ +#define STSD_RSPTYPE_NONE 0 +#define STSD_RSPTYPE_R1 1 +#define STSD_RSPTYPE_R1B 2 +#define STSD_RSPTYPE_R2 3 +#define STSD_RSPTYPE_R3 4 +#define STSD_RSPTYPE_R4 5 +#define STSD_RSPTYPE_R5 6 +#define STSD_RSPTYPE_R6 7 +#define STSD_RSPTYPE_R7 8 + +/* card status bits */ +#define STSD_STATUS_CARD_INSERTED (1<<0) +#define STSD_STATUS_CARD_INITIALIZED (1<<16) + +/* IOS errors */ +#define STSD_ERR_INVALID_CARD 0xc1000020 + +/* + * Hardware registers. + */ + +/* + * Simplified SD Host Controller Specification + * Version 2.00 + * February 8, 2007 + */ + +/* + * SD Host Standard Registers + * + */ + +/* we are recycling the stuff already in "../mmc/host/sdhci.h" */ + +/* TMCLK*2^a a=[13..27] */ +#define STSD_TIMEOUT_CONTROL_DIV(a) (((a)-13)&0xf) + +static char stsd_dev_sdio_slot0[] = "/dev/sdio/slot0"; + +/* + * Used to get/set the host controller hardware register values through IOS. + */ +struct stsd_reg_query { + u32 addr; + u32 _unk1; + u32 _unk2; + u32 size; + u32 data; + u32 _unk3; +}; + +/* + * Used to send commands to an SD card through IOS. + */ +struct stsd_command { + u32 opcode; + u32 cmdtype; + u32 rsptype; + u32 arg; + u32 blk_count; + u32 blk_size; + dma_addr_t dma_addr; + u32 is_dma; + u32 _unk2; +}; + +struct stsd_xfer { + size_t size; + enum dma_data_direction direction; + + struct starlet_ioh_sg in[2], io[1]; + struct stsd_command *cmd; + + /* one-time initialized members */ + void *reply; + size_t reply_len; + dma_addr_t dma_addr; + void *bounce_buf; + size_t bounce_buf_size; + size_t blk_size; +}; + +enum { + __STSD_MEDIA_CHANGED = 0, + __STSD_BAD_CARD, + __STSD_MANUAL_SETUP, + __STSD_SDHC, +}; + +struct stsd_host { + spinlock_t lock; + unsigned long flags; +#define STSD_MEDIA_CHANGED (1<<__STSD_MEDIA_CHANGED) +#define STSD_BAD_CARD (1<<__STSD_BAD_CARD) +#define STSD_MANUAL_SETUP (1<<__STSD_MANUAL_SETUP) +#define STSD_SDHC (1<<__STSD_SDHC) + + /* u32 ocr; */ + unsigned int f_max; + unsigned int clock; + u32 bus_width; + + u16 status; + + /* card related info */ + struct mmc_card card; + + int refcnt; + + spinlock_t queue_lock; + struct request_queue *queue; + struct gendisk *disk; + unsigned int max_phys_segments; + + struct stsd_xfer *xfer; + + struct task_struct *io_thread; + struct mutex io_mutex; + + int fd; + struct device *dev; +}; + + +static const unsigned int tran_exp[] = { + 10000, 100000, 1000000, 10000000, + 0, 0, 0, 0 +}; + +static const unsigned char tran_mant[] = { + 0, 10, 12, 13, 15, 20, 25, 30, + 35, 40, 45, 50, 55, 60, 70, 80, +}; + +static const unsigned int tacc_exp[] = { + 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, +}; + +static const unsigned int tacc_mant[] = { + 0, 10, 12, 13, 15, 20, 25, 30, + 35, 40, 45, 50, 55, 60, 70, 80, +}; + + +/* + * debug section + * + */ + +#if defined(DEBUG) && 0 + +#define __case_string(_s) \ +case _s: \ + str = #_s; \ + break; + +static char *stsd_opcode_string(u32 opcode) +{ + char *str = "unknown"; + + switch (opcode) { +__case_string(MMC_GO_IDLE_STATE) +__case_string(MMC_SEND_OP_COND) +__case_string(MMC_ALL_SEND_CID) +__case_string(MMC_SET_RELATIVE_ADDR) +__case_string(MMC_SET_DSR) +__case_string(MMC_SWITCH) +__case_string(MMC_SELECT_CARD) +__case_string(MMC_SEND_EXT_CSD) +__case_string(MMC_SEND_CSD) +__case_string(MMC_SEND_CID) +__case_string(MMC_READ_DAT_UNTIL_STOP) +__case_string(MMC_STOP_TRANSMISSION) +__case_string(MMC_SEND_STATUS) +__case_string(MMC_GO_INACTIVE_STATE) +__case_string(MMC_SPI_READ_OCR) +__case_string(MMC_SPI_CRC_ON_OFF) +__case_string(MMC_SET_BLOCKLEN) +__case_string(MMC_READ_SINGLE_BLOCK) +__case_string(MMC_READ_MULTIPLE_BLOCK) +__case_string(MMC_WRITE_DAT_UNTIL_STOP) +__case_string(MMC_SET_BLOCK_COUNT) +__case_string(MMC_WRITE_BLOCK) +__case_string(MMC_WRITE_MULTIPLE_BLOCK) +__case_string(MMC_PROGRAM_CID) +__case_string(MMC_PROGRAM_CSD) +__case_string(MMC_SET_WRITE_PROT) +__case_string(MMC_CLR_WRITE_PROT) +__case_string(MMC_SEND_WRITE_PROT) +__case_string(MMC_ERASE_GROUP_START) +__case_string(MMC_ERASE_GROUP_END) +__case_string(MMC_ERASE) +__case_string(MMC_FAST_IO) +__case_string(MMC_GO_IRQ_STATE) +__case_string(MMC_LOCK_UNLOCK) +/*__case_string(SD_SEND_RELATIVE_ADDR)*/ +/*__case_string(SD_SEND_IF_COND)*/ +/*__case_string(SD_SWITCH)*/ +__case_string(SD_IO_SEND_OP_COND) +__case_string(SD_IO_RW_DIRECT) +__case_string(SD_IO_RW_EXTENDED) + } + + return str; +} + +static char *stsd_rsptype_string(u32 rsptype) +{ + char *str = "unknown"; + + switch (rsptype) { +__case_string(STSD_RSPTYPE_NONE) +__case_string(STSD_RSPTYPE_R1) +__case_string(STSD_RSPTYPE_R1B) +__case_string(STSD_RSPTYPE_R2) +__case_string(STSD_RSPTYPE_R3) +__case_string(STSD_RSPTYPE_R4) +__case_string(STSD_RSPTYPE_R5) +__case_string(STSD_RSPTYPE_R6) +__case_string(STSD_RSPTYPE_R7) + } + + return str; +} + +static char *stsd_cmdtype_string(u32 cmdtype) +{ + char *str = "unknown"; + + switch (cmdtype) { +__case_string(STSD_CMDTYPE_BC) +__case_string(STSD_CMDTYPE_BCR) +__case_string(STSD_CMDTYPE_AC) +__case_string(STSD_CMDTYPE_ADTC) + } + + return str; +} + +static char *stsd_statusbit_string(u32 statusbit) +{ + char *str = "unknown"; + + switch (statusbit) { +__case_string(R1_OUT_OF_RANGE) +__case_string(R1_ADDRESS_ERROR) +__case_string(R1_BLOCK_LEN_ERROR) +__case_string(R1_ERASE_SEQ_ERROR) +__case_string(R1_ERASE_PARAM) +__case_string(R1_WP_VIOLATION) +__case_string(R1_CARD_IS_LOCKED) +__case_string(R1_LOCK_UNLOCK_FAILED) +__case_string(R1_COM_CRC_ERROR) +__case_string(R1_ILLEGAL_COMMAND) +__case_string(R1_CARD_ECC_FAILED) +__case_string(R1_CC_ERROR) +__case_string(R1_ERROR) +__case_string(R1_UNDERRUN) +__case_string(R1_OVERRUN) +__case_string(R1_CID_CSD_OVERWRITE) +__case_string(R1_WP_ERASE_SKIP) +__case_string(R1_CARD_ECC_DISABLED) +__case_string(R1_ERASE_RESET) +__case_string(R1_READY_FOR_DATA) +__case_string(R1_APP_CMD) + } + + return str; +} + +static char *stsd_card_state_string(u32 status) +{ + char *str = "unknown"; + + switch (R1_CURRENT_STATE(status)) { + case 0: + str = "IDLE"; + break; + case 1: + str = "READY"; + break; + case 2: + str = "IDENT"; + break; + case 3: + str = "STANDBY"; + break; + case 4: + str = "TRANSFER"; + break; + case 5: + str = "SEND"; + break; + case 6: + str = "RECEIVE"; + break; + case 7: + str = "PROGRAM"; + break; + case 8: + str = "DISCONNECT"; + break; + } + + return str; +} +static void stsd_print_status(u32 status) +{ + u32 i, bit; + + drv_printk(KERN_INFO, "card state %s\n", + stsd_card_state_string(status)); + + i = 13; + for (i = 13; i <= 31; i++) { + bit = 1 << i; + if ((status & bit)) + drv_printk(KERN_INFO, "%02d %s\n", i, + stsd_statusbit_string(bit)); + } + bit = 1 << 8; + if ((status & bit)) + drv_printk(KERN_INFO, "%02d %s\n", 8, + stsd_statusbit_string(bit)); + bit = 1 << 5; + if ((status & bit)) + drv_printk(KERN_INFO, "%02d %s\n", 5, + stsd_statusbit_string(bit)); +} + +static void stsd_print_cid(struct mmc_cid *cid) +{ + drv_printk(KERN_INFO, + "manfid = %d\n" + "oemid = %d\n" + "prod_name = %s\n" + "hwrev = %d\n" + "fwrev = %d\n" + "serial = %08x\n" + "year = %d\n" + "month = %d\n", + cid->manfid, + cid->oemid, + cid->prod_name, + cid->hwrev, cid->fwrev, cid->serial, cid->year, cid->month); +} + +static void stsd_print_csd(struct mmc_csd *csd) +{ + drv_printk(KERN_INFO, + "mmca_vsn = %d\n" + "cmdclass = %d\n" + "tacc_clks = %d\n" + "tacc_ns = %d\n" + "r2w_factor = %d\n" + "max_dtr = %d\n" + "read_blkbits = %d\n" + "write_blkbits = %d\n" + "capacity = %d\n" + "read_partial = %d\n" + "read_misalign = %d\n" + "write_partial = %d\n" + "write_misalign = %d\n", + csd->mmca_vsn, + csd->cmdclass, + csd->tacc_clks, + csd->tacc_ns, + csd->r2w_factor, + csd->max_dtr, + csd->read_blkbits, + csd->write_blkbits, + csd->capacity, + csd->read_partial, + csd->read_misalign, + csd->write_partial, + csd->write_misalign); +} + +static void stsd_dump_hs_regs(struct stsd_host *host) +{ + drv_printk(KERN_DEBUG, "============== REGISTER DUMP ==============\n"); + + drv_printk(KERN_DEBUG, "Sys addr: 0x%08x | Version: 0x%08x\n", + stsd_hsr_in_u32(host, SDHCI_DMA_ADDRESS), + stsd_hsr_in_u16(host, SDHCI_HOST_VERSION)); + drv_printk(KERN_DEBUG, "Blk size: 0x%08x | Blk cnt: 0x%08x\n", + stsd_hsr_in_u16(host, SDHCI_BLOCK_SIZE), + stsd_hsr_in_u16(host, SDHCI_BLOCK_COUNT)); + drv_printk(KERN_DEBUG, "Argument: 0x%08x | Trn mode: 0x%08x\n", + stsd_hsr_in_u32(host, SDHCI_ARGUMENT), + stsd_hsr_in_u16(host, SDHCI_TRANSFER_MODE)); + drv_printk(KERN_DEBUG, "Present: 0x%08x | Host ctl: 0x%08x\n", + stsd_hsr_in_u32(host, SDHCI_PRESENT_STATE), + stsd_hsr_in_u8(host, SDHCI_HOST_CONTROL)); + drv_printk(KERN_DEBUG, "Power: 0x%08x | Blk gap: 0x%08x\n", + stsd_hsr_in_u8(host, SDHCI_POWER_CONTROL), + stsd_hsr_in_u8(host, SDHCI_BLOCK_GAP_CONTROL)); + drv_printk(KERN_DEBUG, "Wake-up: 0x%08x | Clock: 0x%08x\n", + stsd_hsr_in_u8(host, SDHCI_WAKE_UP_CONTROL), + stsd_hsr_in_u16(host, SDHCI_CLOCK_CONTROL)); + drv_printk(KERN_DEBUG, "Timeout: 0x%08x | Int stat: 0x%08x\n", + stsd_hsr_in_u8(host, SDHCI_TIMEOUT_CONTROL), + stsd_hsr_in_u32(host, SDHCI_INT_STATUS)); + drv_printk(KERN_DEBUG, "Int enab: 0x%08x | Sig enab: 0x%08x\n", + stsd_hsr_in_u32(host, SDHCI_INT_ENABLE), + stsd_hsr_in_u32(host, SDHCI_SIGNAL_ENABLE)); + drv_printk(KERN_DEBUG, "AC12 err: 0x%08x | Slot int: 0x%08x\n", + stsd_hsr_in_u16(host, SDHCI_ACMD12_ERR), + stsd_hsr_in_u16(host, SDHCI_SLOT_INT_STATUS)); + drv_printk(KERN_DEBUG, "Caps: 0x%08x | Max curr: 0x%08x\n", + stsd_hsr_in_u32(host, SDHCI_CAPABILITIES), + stsd_hsr_in_u32(host, SDHCI_MAX_CURRENT)); + + drv_printk(KERN_DEBUG, "===========================================\n"); +} + +#endif /* DEBUG */ + +/* + * + * MMC/SD data structures manipulation. + * Borrowed from MMC layer. + */ + +#define UNSTUFF_BITS(resp, start, size) \ + ({ \ + const int __size = size; \ + const u32 __mask = (__size < 32 ? 1 << __size : 0) - 1; \ + const int __off = 3 - ((start) / 32); \ + const int __shft = (start) & 31; \ + u32 __res; \ + \ + __res = resp[__off] >> __shft; \ + if (__size + __shft > 32) \ + __res |= resp[__off-1] << ((32 - __shft) % 32); \ + __res & __mask; \ + }) + +/* + * Given the decoded CSD structure, decode the raw CID to our CID structure. + */ +static void mmc_decode_cid(struct mmc_card *card) +{ + u32 *resp = card->raw_cid; + + memset(&card->cid, 0, sizeof(struct mmc_cid)); + + /* + * SD doesn't currently have a version field so we will + * have to assume we can parse this. + */ + card->cid.manfid = UNSTUFF_BITS(resp, 120, 8); + card->cid.oemid = UNSTUFF_BITS(resp, 104, 16); + card->cid.prod_name[0] = UNSTUFF_BITS(resp, 96, 8); + card->cid.prod_name[1] = UNSTUFF_BITS(resp, 88, 8); + card->cid.prod_name[2] = UNSTUFF_BITS(resp, 80, 8); + card->cid.prod_name[3] = UNSTUFF_BITS(resp, 72, 8); + card->cid.prod_name[4] = UNSTUFF_BITS(resp, 64, 8); + card->cid.hwrev = UNSTUFF_BITS(resp, 60, 4); + card->cid.fwrev = UNSTUFF_BITS(resp, 56, 4); + card->cid.serial = UNSTUFF_BITS(resp, 24, 32); + card->cid.year = UNSTUFF_BITS(resp, 12, 8); + card->cid.month = UNSTUFF_BITS(resp, 8, 4); + + card->cid.year += 2000; /* SD cards year offset */ +} + +/* + * Given a 128-bit response, decode to our card CSD structure. + */ +static int mmc_decode_csd(struct mmc_card *card) +{ + struct mmc_csd *csd = &card->csd; + unsigned int e, m, csd_struct; + u32 *resp = card->raw_csd; + + csd_struct = UNSTUFF_BITS(resp, 126, 2); + + switch (csd_struct) { + case 0: + m = UNSTUFF_BITS(resp, 115, 4); + e = UNSTUFF_BITS(resp, 112, 3); + csd->tacc_ns = (tacc_exp[e] * tacc_mant[m] + 9) / 10; + csd->tacc_clks = UNSTUFF_BITS(resp, 104, 8) * 100; + + m = UNSTUFF_BITS(resp, 99, 4); + e = UNSTUFF_BITS(resp, 96, 3); + csd->max_dtr = tran_exp[e] * tran_mant[m]; + csd->cmdclass = UNSTUFF_BITS(resp, 84, 12); + + e = UNSTUFF_BITS(resp, 47, 3); + m = UNSTUFF_BITS(resp, 62, 12); + csd->capacity = (1 + m) << (e + 2); + + csd->read_blkbits = UNSTUFF_BITS(resp, 80, 4); + csd->read_partial = UNSTUFF_BITS(resp, 79, 1); + csd->write_misalign = UNSTUFF_BITS(resp, 78, 1); + csd->read_misalign = UNSTUFF_BITS(resp, 77, 1); + csd->r2w_factor = UNSTUFF_BITS(resp, 26, 3); + csd->write_blkbits = UNSTUFF_BITS(resp, 22, 4); + csd->write_partial = UNSTUFF_BITS(resp, 21, 1); + break; + case 1: + /* + * This is a block-addressed SDHC card. Most + * interesting fields are unused and have fixed + * values. To avoid getting tripped by buggy cards, + * we assume those fixed values ourselves. + */ + mmc_card_set_blockaddr(card); + + csd->tacc_ns = 0; /* Unused */ + csd->tacc_clks = 0; /* Unused */ + + m = UNSTUFF_BITS(resp, 99, 4); + e = UNSTUFF_BITS(resp, 96, 3); + csd->max_dtr = tran_exp[e] * tran_mant[m]; + csd->cmdclass = UNSTUFF_BITS(resp, 84, 12); + + m = UNSTUFF_BITS(resp, 48, 22); + csd->capacity = (1 + m) << 10; + + csd->read_blkbits = 9; + csd->read_partial = 0; + csd->write_misalign = 0; + csd->read_misalign = 0; + csd->r2w_factor = 4; /* Unused */ + csd->write_blkbits = 9; + csd->write_partial = 0; + break; + default: + printk(KERN_ERR "unrecognised CSD structure version %d\n", + csd_struct); + return -EINVAL; + } + + /*stsd_print_csd(csd);*/ + + return 0; +} + +/* + * REVISIT maybe get rid of this and specify the rsptype directly + */ +static u32 stsd_opcode_to_rsptype(u32 opcode) +{ + u32 rsptype = STSD_RSPTYPE_R1; + + switch (opcode) { + case MMC_GO_IDLE_STATE: + case MMC_SET_DSR: + case MMC_GO_INACTIVE_STATE: + rsptype = STSD_RSPTYPE_NONE; + break; + case MMC_SWITCH: + case MMC_STOP_TRANSMISSION: + case MMC_SET_WRITE_PROT: + case MMC_CLR_WRITE_PROT: + case MMC_ERASE: + case MMC_LOCK_UNLOCK: + rsptype = STSD_RSPTYPE_R1B; + break; + case MMC_ALL_SEND_CID: + case MMC_SEND_CSD: + case MMC_SEND_CID: + rsptype = STSD_RSPTYPE_R2; + break; + case MMC_SEND_OP_COND: + case SD_APP_OP_COND: + rsptype = STSD_RSPTYPE_R3; + break; + case MMC_FAST_IO: + case SD_IO_SEND_OP_COND: + rsptype = STSD_RSPTYPE_R4; + break; + case MMC_GO_IRQ_STATE: + case SD_IO_RW_DIRECT: + case SD_IO_RW_EXTENDED: + rsptype = STSD_RSPTYPE_R5; + break; + case SD_SEND_RELATIVE_ADDR: + rsptype = STSD_RSPTYPE_R6; + break; + case SD_SEND_IF_COND: + /* WEIRD */ + /*rsptype = STSD_RSPTYPE_R7;*/ + rsptype = STSD_RSPTYPE_R6; + break; + default: + break; + } + + return rsptype; +} + +static inline void stsd_card_set_bad(struct stsd_host *host) +{ + set_bit(__STSD_BAD_CARD, &host->flags); +} + +static inline void stsd_card_unset_bad(struct stsd_host *host) +{ + clear_bit(__STSD_BAD_CARD, &host->flags); +} + +static inline int stsd_card_is_bad(struct stsd_host *host) +{ + return test_bit(__STSD_BAD_CARD, &host->flags); +} + +static inline void stsd_card_set_sdhc(struct stsd_host *host) +{ + set_bit(__STSD_SDHC, &host->flags); +} + +static inline void stsd_card_unset_sdhc(struct stsd_host *host) +{ + clear_bit(__STSD_SDHC, &host->flags); +} + +static inline int stsd_card_is_sdhc(struct stsd_host *host) +{ + return test_bit(__STSD_SDHC, &host->flags); +} + +static inline void stsd_card_set_manual_setup(struct stsd_host *host) +{ + set_bit(__STSD_MANUAL_SETUP, &host->flags); +} + +static inline void stsd_card_unset_manual_setup(struct stsd_host *host) +{ + clear_bit(__STSD_MANUAL_SETUP, &host->flags); +} + +static inline int stsd_card_needs_manual_setup(struct stsd_host *host) +{ + return test_bit(__STSD_MANUAL_SETUP, &host->flags); +} + +static inline int stsd_card_status_is_inserted(u32 status) +{ + return (status & STSD_STATUS_CARD_INSERTED) + == STSD_STATUS_CARD_INSERTED; +} + +static inline int stsd_card_status_is_initialized(u32 status) +{ + return (status & STSD_STATUS_CARD_INITIALIZED) + == STSD_STATUS_CARD_INITIALIZED; +} + +/* + * Hardware. + * + */ + +/* + * Handy small buffer routines. + * We use a small static aligned buffer to avoid allocations for short-lived + * operations involving 1 to 4 byte data transfers to/from IOS. + * + */ + +static u32 stsd_small_buf[L1_CACHE_BYTES / sizeof(u32)] + __attribute__ ((aligned(STARLET_IPC_DMA_ALIGN + 1))); +static const size_t stsd_small_buf_size = sizeof(stsd_small_buf_size); +static DEFINE_MUTEX(stsd_small_buf_lock); + +static u32 *stsd_small_buf_get(void) +{ + u32 *buf; + + if (!mutex_trylock(&stsd_small_buf_lock)) + buf = starlet_kzalloc(stsd_small_buf_size, GFP_NOIO); + else { + memset(stsd_small_buf, 0, stsd_small_buf_size); + buf = stsd_small_buf; + } + + return buf; +} + +void stsd_small_buf_put(u32 *buf) +{ + if (buf == stsd_small_buf) + mutex_unlock(&stsd_small_buf_lock); + else + starlet_kfree(buf); +} + + +/* + * SD Host Standard Registers accessors. + * + */ + +/* + * @data must be aligned + * @size must be between 1 and 4 + */ +static int __stsd_hsr_in(struct stsd_host *host, + u32 addr, u32 *data, size_t size) +{ + struct stsd_reg_query *query; + int error; + + query = starlet_kzalloc(sizeof(*query), GFP_ATOMIC); + if (!query) + return -ENOMEM; + + query->addr = addr; + query->size = size; + + error = starlet_ioctl(host->fd, STSD_IOCTL_GETHSR, + query, sizeof(*query), data, sizeof(*data)); + + starlet_kfree(query); + + if (error) + DBG("%s: error=%d (%08x)\n", __func__, error, error); + + return error; +} + +static int __stsd_hsr_out(struct stsd_host *host, + u32 addr, u32 *data, size_t size) +{ + struct stsd_reg_query *query; + int error; + + query = starlet_kzalloc(sizeof(*query), GFP_ATOMIC); + if (!query) + return -ENOMEM; + + query->addr = addr; + query->size = size; + query->data = *data; + + error = starlet_ioctl(host->fd, STSD_IOCTL_SETHSR, + query, sizeof(*query), NULL, 0); + + starlet_kfree(query); + + if (error) + DBG("%s: error=%d (%08x)\n", __func__, error, error); + + return error; +} + + +static int stsd_hsr_in(struct stsd_host *host, + u32 reg, void *buf, size_t size) +{ + u32 *local_buf; + int error; + + /* we do 8, 16 and 32 bits reads */ + if (size > 4) + return -EINVAL; + + local_buf = stsd_small_buf_get(); + if (!local_buf) + return -ENOMEM; + + error = __stsd_hsr_in(host, reg, local_buf, size); + if (!error) { + switch (size) { + case 1: + *(u8 *)buf = *local_buf & 0xff; + break; + case 2: + *(u16 *)buf = *local_buf & 0xffff; + break; + case 4: + *(u32 *)buf = *local_buf; + break; + default: + BUG(); + break; + } + } + + stsd_small_buf_put(local_buf); + + return error; +} + +static int stsd_hsr_out(struct stsd_host *host, + u32 reg, void *buf, size_t size) +{ + u32 *local_buf; + int error; + + /* we do 8, 16 and 32 bits reads */ + if (size > 4) + return -EINVAL; + + local_buf = stsd_small_buf_get(); + if (!local_buf) + return -ENOMEM; + + switch (size) { + case 1: + *local_buf = *(u8 *)buf; + break; + case 2: + *local_buf = *(u16 *)buf; + break; + case 4: + *local_buf = *(u32 *)buf; + break; + default: + BUG(); + break; + } + error = __stsd_hsr_out(host, reg, local_buf, size); + + stsd_small_buf_put(local_buf); + + return error; +} + +#define __declare_stsd_hsr_wait_for_resp(_type) \ +static int stsd_hsr_wait_for_resp_##_type(struct stsd_host *host, \ + u32 reg, _type resp, _type resp_mask, \ + unsigned long jiffies) \ +{ \ + _type val; \ + int error; \ + \ + unsigned long cycles = 10; \ + while (cycles-- > 0) { \ + error = stsd_hsr_in(host, reg, &val, sizeof(val)); \ + if (error) \ + return error; \ + if ((val & resp_mask) == resp) \ + return 0; \ + mdelay(10); \ + } \ + return -ENODATA; \ +} + +__declare_stsd_hsr_wait_for_resp(u8); +__declare_stsd_hsr_wait_for_resp(u16); + +#define __declare_stsd_hsr_in(_type) \ +static inline _type stsd_hsr_in_##_type(struct stsd_host *host, u32 reg) \ +{ \ + _type val; \ + \ + stsd_hsr_in(host, reg, &val, sizeof(val)); \ + return val; \ +} + +__declare_stsd_hsr_in(u8); +__declare_stsd_hsr_in(u16); +__declare_stsd_hsr_in(u32); + +#define __declare_stsd_hsr_out(_type) \ +static inline void stsd_hsr_out_##_type(struct stsd_host *host, u32 reg,\ + _type val) \ +{ \ + stsd_hsr_out(host, reg, &val, sizeof(val)); \ +} + +__declare_stsd_hsr_out(u8); +__declare_stsd_hsr_out(u16); +__declare_stsd_hsr_out(u32); + + + +/* + * Ioctl helpers. + * + */ + +static int stsd_ioctl_small_read(struct stsd_host *host, int request, + void *buf, size_t size) +{ + void *local_buf; + int error; + + /* we do 8, 16 and 32 bits reads */ + if (size > stsd_small_buf_size) { + error = -EINVAL; + goto done; + } + + local_buf = stsd_small_buf_get(); + if (!local_buf) { + error = -ENOMEM; + goto done; + } + + error = starlet_ioctl(host->fd, request, + NULL, 0, local_buf, size); + if (!error) + memcpy(buf, local_buf, size); + + stsd_small_buf_put(local_buf); + +done: + if (error) + DBG("%s: error=%d (%08x)\n", __func__, error, error); + return error; +} + +static int stsd_ioctl_small_write(struct stsd_host *host, int request, + void *buf, size_t size) +{ + void *local_buf; + int error; + + /* we do 8, 16 and 32 bits writes */ + if (size > stsd_small_buf_size) { + error = -EINVAL; + goto done; + } + + local_buf = stsd_small_buf_get(); + if (!local_buf) { + error = -ENOMEM; + goto done; + } + + memcpy(local_buf, buf, size); + error = starlet_ioctl(host->fd, request, + local_buf, size, NULL, 0); + + stsd_small_buf_put(local_buf); + +done: + if (error) + DBG("%s: error=%d (%08x)\n", __func__, error, error); + return error; +} + + +/* + * Hardware interfaces. + * + */ + +static int stsd_get_status(struct stsd_host *host, u32 *status) +{ + int error; + + error = stsd_ioctl_small_read(host, STSD_IOCTL_GETSTATUS, + status, sizeof(*status)); + if (error) + DBG("%s: error=%d (%08x)\n", __func__, error, error); + + return error; +} + +static void stsd_set_bus_width(struct stsd_host *host, int width) +{ + u8 hcr; + + hcr = stsd_hsr_in_u8(host, SDHCI_HOST_CONTROL); + if (width == 4) { + hcr |= SDHCI_CTRL_4BITBUS; + } else { + hcr &= ~SDHCI_CTRL_4BITBUS; + width = 1; + } + stsd_hsr_out_u8(host, SDHCI_HOST_CONTROL, hcr); + host->bus_width = width; +} + +static int stsd_set_clock(struct stsd_host *host, unsigned int clock) +{ + int error; + u32 divisor; + + for (divisor = 1; divisor <= 32; divisor <<= 1) { + if (host->f_max / divisor <= clock) + break; + } + + error = stsd_ioctl_small_write(host, STSD_IOCTL_SETCLOCK, + &divisor, sizeof(divisor)); + if (error) + DBG("%s: error=%d (%08x)\n", __func__, error, error); + else + host->clock = clock; + + return error; +} + +static int stsd_reset_card(struct stsd_host *host) +{ + struct mmc_card *card = &host->card; + int error; + u32 status; + + stsd_card_unset_bad(host); + stsd_card_unset_sdhc(host); + stsd_card_unset_manual_setup(host); + + memset(&card->cid, 0, sizeof(struct mmc_cid)); + memset(&card->csd, 0, sizeof(struct mmc_csd)); + host->card.rca = 0; + + error = stsd_ioctl_small_read(host, STSD_IOCTL_RESET, + &status, sizeof(status)); + if (error) { + if (error != STSD_ERR_INVALID_CARD) + DBG("%s: error=%d (%08x)\n", __func__, error, error); + } else { + host->card.rca = status >> 16; + host->status = status & 0xffff; + } + + return error; +} + +#if 0 +static int stsd_get_ocr(struct stsd_host *host) +{ + int error; + u32 ocr; + + error = stsd_ioctl_small_read(host, STSD_IOCTL_GETOCR, + &ocr, sizeof(ocr)); + if (error) + DBG("%s: error=%d (%08x)\n", __func__, error, error); + else + host->ocr = ocr; + + return error; +} +#endif + +/* + * Command engine. + * + */ + +static int stsd_send_command(struct stsd_host *host, + u32 opcode, u32 type, u32 arg, + void *buf, size_t buf_len) +{ + struct scatterlist in[2], io[1]; + struct stsd_command *cmd; + u32 *reply; + size_t reply_len; + int error; + + reply_len = 4 * sizeof(u32); + if (buf_len > reply_len) + return -EINVAL; + + cmd = starlet_kzalloc(sizeof(*cmd), GFP_NOIO); + if (!cmd) + return -ENOMEM; + + reply = starlet_kzalloc(reply_len, GFP_NOIO); + if (!reply) { + starlet_kfree(cmd); + return -ENOMEM; + } + + cmd->opcode = opcode; + cmd->arg = arg; + + cmd->cmdtype = type; + cmd->rsptype = stsd_opcode_to_rsptype(opcode); + if (opcode == MMC_SELECT_CARD && arg == 0) + cmd->rsptype = STSD_RSPTYPE_NONE; + + if (stsd_card_needs_manual_setup(host)) { + /* + * We need to use ioctlvs, instead of ioctls, to drive + * manually initialized cards. + * This makes IOS "cooperative" :) + */ + sg_init_table(in, 2); + sg_set_buf(&in[0], cmd, sizeof(*cmd)); + sg_set_buf(&in[1], reply, 0); + + sg_init_table(io, 1); + sg_set_buf(&io[0], reply, reply_len); + + error = starlet_ioctlv(host->fd, STSD_IOCTL_SENDCMD, + 2, in, 1, io); + } else { + error = starlet_ioctl(host->fd, STSD_IOCTL_SENDCMD, + cmd, sizeof(*cmd), reply, reply_len); + } + + if (error) { + DBG("%s: error=%d (%08x), opcode=%d\n", __func__, + error, error, opcode); + } else { + if (buf) + memcpy(buf, reply, buf_len); + } + + starlet_kfree(reply); + starlet_kfree(cmd); + + return error; +} + +static int stsd_send_app_command(struct stsd_host *host, + u32 opcode, u32 type, u32 arg, + void *buf, size_t buf_len) +{ + int error; + + error = stsd_send_command(host, MMC_APP_CMD, STSD_CMDTYPE_AC, + host->card.rca << 16, NULL, 0); + if (!error) { + error = stsd_send_command(host, opcode, type, arg, + buf, buf_len); + } + return error; +} + + +/* + * Command helpers. + * + */ + + +static int stsd_cmd_read_cxd(struct stsd_host *host, int request, void *buf) +{ + int error; + u32 *q, savedq; + u8 *p, crc; + const size_t size = 128/8*sizeof(u8); + + error = stsd_send_command(host, request, STSD_CMDTYPE_AC, + host->card.rca << 16, buf, size); + + if (!error) { + /* + * WEIRD, + * starlet sends CSD and CID contents in a very special way. + * + * If the 128 bit register value is: + * 0123456789abcdef + * starlet will send it as: + * bcde789a3456f012 + * with byte f (the crc field) zeroed. + */ + + /* bcde789a3456f012 -> f0123456789abcde */ + q = buf; + savedq = q[0]; + q[0] = q[3]; + q[3] = savedq; + savedq = q[1]; + q[1] = q[2]; + q[2] = savedq; + + /* f0123456789abcde -> 0123456789abcdef */ + p = buf; + crc = p[0]; + memcpy(p, p+1, size-1); + p[size-1] = crc; + } + return error; +} + +static int stsd_cmd_read_csd(struct stsd_host *host) +{ + return stsd_cmd_read_cxd(host, MMC_SEND_CSD, host->card.raw_csd); +} + +static int stsd_cmd_read_cid(struct stsd_host *host) +{ + return stsd_cmd_read_cxd(host, MMC_SEND_CID, host->card.raw_cid); +} + +static int stsd_cmd_all_send_cid(struct stsd_host *host) +{ + const size_t size = 128/8*sizeof(u8); + + /* WEIRD, don't use CMDTYPE_BCR for MMC_ALL_SEND_CID */ + return stsd_send_command(host, MMC_ALL_SEND_CID, 0, + host->card.rca << 16, + host->card.raw_cid, size); +} + +static int stsd_cmd_set_relative_addr(struct stsd_host *host, unsigned int rca) +{ + int error; + u32 reply; + + error = stsd_send_command(host, MMC_SET_RELATIVE_ADDR, STSD_CMDTYPE_AC, + rca, &reply, sizeof(reply)); + if (!error) { + host->card.rca = reply >> 16; + /* DBG("rca=%d, new_rca=%x\n", rca, host->card.rca); */ + } + return error; +} + + +static int stsd_cmd_select_card(struct stsd_host *host) +{ + return stsd_send_command(host, MMC_SELECT_CARD, STSD_CMDTYPE_AC, + host->card.rca << 16, + NULL, 0); +} + +static int stsd_cmd_deselect_card(struct stsd_host *host) +{ + return stsd_send_command(host, MMC_SELECT_CARD, STSD_CMDTYPE_AC, + 0, + NULL, 0); +} + +static int stsd_cmd_set_block_len(struct stsd_host *host, unsigned int len) +{ + return stsd_send_command(host, MMC_SET_BLOCKLEN, STSD_CMDTYPE_AC, + len, + NULL, 0); +} + +static int stsd_app_cmd_set_bus_width(struct stsd_host *host, int width) +{ + int error; + u16 val; + + if (width == 4) + val = SD_BUS_WIDTH_4; + else + val = SD_BUS_WIDTH_1; + + error = stsd_send_app_command(host, SD_APP_SET_BUS_WIDTH, + STSD_CMDTYPE_AC, + val, NULL, 0); + if (error) + DBG("%s: error=%d (%08x)\n", __func__, error, error); + + return error; +} + + + +static int stsd_setup_host_controller(struct stsd_host *host) +{ + const u32 mask = SDHCI_INT_RESPONSE | SDHCI_INT_DATA_END | + SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE | + SDHCI_INT_TIMEOUT | SDHCI_INT_CRC | + SDHCI_INT_END_BIT | SDHCI_INT_INDEX | + SDHCI_INT_DATA_TIMEOUT | SDHCI_INT_DATA_CRC | + SDHCI_INT_ACMD12ERR; + u8 rst, pwr, clk_idx; + int error; + + /* + * Reset host controller. + */ + + /* write 1 to the Reset All bit in the Software Reset register ... */ + rst = SDHCI_RESET_ALL; + stsd_hsr_out_u8(host, SDHCI_SOFTWARE_RESET, rst); + + /* ... then wait for the Reset All bit to be cleared */ + error = stsd_hsr_wait_for_resp_u8(host, SDHCI_SOFTWARE_RESET, + 0, rst, + 100*(HZ/1000)); + if (error) { + drv_printk(KERN_ERR, "host controller didn't get out of" + " reset\n"); + goto done; + } + + /* + * Setup interrupt sources. + */ + + /* ack the interrupt sources that IOS uses ... */ + stsd_hsr_out_u32(host, SDHCI_INT_ENABLE, mask); + stsd_hsr_in_u32(host, SDHCI_INT_ENABLE); + /* ... then unmask them */ + stsd_hsr_out_u32(host, SDHCI_SIGNAL_ENABLE, mask); + stsd_hsr_in_u32(host, SDHCI_SIGNAL_ENABLE); + + /* + * Setup bus power. + */ + + /* FIXME, we should use capabilities register here */ + /* for now use 3.3V setting */ + pwr = SDHCI_POWER_330; + + /* turn on bus power and use selected voltage setting */ + stsd_hsr_out_u8(host, SDHCI_POWER_CONTROL, pwr & ~SDHCI_POWER_ON); + stsd_hsr_out_u8(host, SDHCI_POWER_CONTROL, pwr | SDHCI_POWER_ON); + + /* + * Initialize clocks. + */ + + /* FIXME, we should use capabilities register here */ + /* for now use index 01h which is base clock divided by 2 */ + clk_idx = 1; + + /* disable clock signalling... */ + stsd_hsr_out_u16(host, SDHCI_CLOCK_CONTROL, 0); + /* ... then enable internal clock ... */ + stsd_hsr_out_u16(host, SDHCI_CLOCK_CONTROL, + SDHCI_CLOCK_INT_EN | + (clk_idx << SDHCI_DIVIDER_SHIFT)); + /* ... and wait until it gets stable */ + error = stsd_hsr_wait_for_resp_u16(host, SDHCI_CLOCK_CONTROL, + SDHCI_CLOCK_INT_STABLE, + SDHCI_CLOCK_INT_STABLE, + 1*HZ); + if (error) { + drv_printk(KERN_ERR, "internal clock didn't get stable\n"); + goto done; + } + + /* SD clock can be enabled now */ + stsd_hsr_out_u16(host, SDHCI_CLOCK_CONTROL, + SDHCI_CLOCK_INT_EN | + SDHCI_CLOCK_CARD_EN | + (1 << SDHCI_DIVIDER_SHIFT)); + + /* + * Setup timeout. + */ + + /* setup timeout to TMCLK * 2^27 */ + stsd_hsr_out_u8(host, SDHCI_TIMEOUT_CONTROL, + STSD_TIMEOUT_CONTROL_DIV(27)); + +done: + if (error) + DBG("%s: error=%d (%08x)\n", __func__, error, error); + + return error; +} + +static int stsd_setup_card(struct stsd_host *host) +{ + const u8 check_pattern = 0xaa; + u32 arg; + u32 resp[4]; + int i; + int error; + + /* WEIRD, don't use CMDTYPE_BC for MMC_GO_IDLE_STATE */ + error = stsd_send_command(host, MMC_GO_IDLE_STATE, 0, + 0, NULL, 0); + if (error) + goto done; + +#define STSD_VHS(a) ((((a)&0x0f)<<8)) +#define STSD_VHS_27_36 STSD_VHS(0x1) + + /* WEIRD, don't use CMDTYPE_BC for SD_SEND_IF_COND */ + arg = STSD_VHS_27_36 | check_pattern; + error = stsd_send_command(host, SD_SEND_IF_COND, 0, + arg, &resp, sizeof(resp)); + if (error) + goto done; + + if ((resp[0] & 0xff) != check_pattern) { + DBG("arg=0x%x, resp[0]=0x%x\n", arg, resp[0]); + error = -ENODEV; + goto done; + } + + /* + * At this point we have identified a v2.00 SD Memory Card. + * + */ + + /* + * Get OCR + */ + +#define STSD_OCR_HCS (1<<30) /* Host Capacity Support */ +#define STSD_OCR_CCS (1<<30) /* Card Capacity Support */ + + for (i = 0; i < 100; i++) { + /* WEIRD, don't use CMDTYPE_BCR for MMC_APP_CMD */ + error = stsd_send_command(host, MMC_APP_CMD, STSD_CMDTYPE_AC, + 0, NULL, 0); + if (error) + goto done; + + /* WEIRD, don't use CMDTYPE_BCR for SD_APP_OP_COND */ + error = stsd_send_command(host, SD_APP_OP_COND, 0, + STSD_OCR_HCS| + MMC_VDD_32_33|MMC_VDD_33_34, + &resp, sizeof(resp)); + if (error) + goto done; + + if ((resp[0] & MMC_CARD_BUSY) != 0) { + /* card power up completed */ + break; + } + + error = -ETIMEDOUT; + mdelay(10); + } + if (error) { + drv_printk(KERN_ERR, "timed out while trying to get OCR\n"); + goto done; + } + + if ((resp[0] & STSD_OCR_CCS) != 0) { + /* high capacity card */ + stsd_card_set_sdhc(host); + } + + error = stsd_cmd_all_send_cid(host); + if (error) + goto done; + + error = stsd_cmd_set_relative_addr(host, 0); + if (error) + goto done; + +done: + if (error) + DBG("%s: error=%d (%08x)\n", __func__, error, error); + return error; +} + +static int stsd_reopen_sdio(struct stsd_host *host) +{ + int error = 0; + + starlet_close(host->fd); + host->fd = starlet_open(stsd_dev_sdio_slot0, 1); + if (host->fd < 0) { + drv_printk(KERN_ERR, "unable to re-open %s\n", + stsd_dev_sdio_slot0); + error = -ENODEV; + } + return error; +} + + +static int stsd_welcome_card(struct stsd_host *host) +{ + size_t block_len; /* in bytes */ + u32 status; + int error; + + mutex_lock(&host->io_mutex); + + /* + * Re-open the sdio device if things look wrong. + */ + error = stsd_get_status(host, &status); + if (error == STARLET_EINVAL) { + error = stsd_reopen_sdio(host); + if (error) + goto err_bad_card; + } + + /* + * Try a normal initialization sequence first, and revert to + * manual mode if that fails. + */ + + stsd_reset_card(host); + + error = stsd_get_status(host, &status); + if (error) + goto err_bad_card; + if (!stsd_card_status_is_inserted(status)) { + drv_printk(KERN_ERR, "no card found\n"); + goto err_bad_card; + } + + if (!stsd_card_status_is_initialized(status)) { + /* manual initialization, needed for SDHC support */ + stsd_card_set_manual_setup(host); + + error = stsd_reopen_sdio(host); + if (error) + goto err_bad_card; + + error = stsd_setup_host_controller(host); + if (error) + goto err_bad_card; + + error = stsd_setup_card(host); + if (error) + goto err_bad_card; + } + +#if 0 + /* read Operating Conditions Register */ + error = stsd_get_ocr(host); + if (error < 0) + goto err_bad_card; +#endif + + error = stsd_cmd_deselect_card(host); + if (error) + goto err_bad_card; + + /* read and decode the Card Specific Data */ + error = stsd_cmd_read_csd(host); + if (error) + goto err_bad_card; + mmc_decode_csd(&host->card); + + /* read and decode the Card Identification Data */ + error = stsd_cmd_read_cid(host); + if (error) + goto err_bad_card; + mmc_decode_cid(&host->card); + + error = stsd_cmd_select_card(host); + if (error) + goto err_bad_card; + + stsd_set_clock(host, host->card.csd.max_dtr); + + /* FIXME check if card supports 4 bit bus width */ + stsd_set_bus_width(host, 4); + error = stsd_app_cmd_set_bus_width(host, 4); + if (error) + goto err_bad_card; + + /* setup block length */ + block_len = KERNEL_SECTOR_SIZE; + error = stsd_cmd_set_block_len(host, block_len); + if (error) + goto err_bad_card; + +#if 0 + mmc_card_set_present(&host->card); +#endif + + mutex_unlock(&host->io_mutex); + + drv_printk(KERN_INFO, "descr \"%s\", size %luk, block %ub," + " serial %08x\n", + host->card.cid.prod_name, + (unsigned long)((host->card.csd.capacity / 1024) * + (1 << host->card.csd.read_blkbits)), + 1 << host->card.csd.read_blkbits, + host->card.cid.serial); + + error = 0; + goto out; + +err_bad_card: + mutex_unlock(&host->io_mutex); + stsd_card_set_bad(host); +out: + return error; +} + + +/* + * Block layer helper routines. + * + */ + +static int stsd_do_block_transfer(struct stsd_host *host, int write, + unsigned long start, + void *buf, size_t nr_blocks) +{ + struct stsd_xfer *xfer = host->xfer; + struct stsd_command *cmd = xfer->cmd; + int error; + + xfer->direction = (write) ? DMA_TO_DEVICE : DMA_FROM_DEVICE; + xfer->size = nr_blocks * xfer->blk_size; + + if (xfer->size > xfer->bounce_buf_size) { + drv_printk(KERN_ERR, "oops, request size %d > %d\n", + xfer->size, xfer->bounce_buf_size); + return -ENOMEM; + } + + /* + * This is stupid. + * Starlet expects the buffer to be an input iovec (from starlet + * point of view) even for reads. Thus, map the buffer explicitly here. + */ + if (write) + memcpy(xfer->bounce_buf, buf, xfer->size); + __dma_sync(xfer->bounce_buf, xfer->size, xfer->direction); +/* + xfer->dma_addr = dma_map_single(host->dev, buf, + xfer->size, xfer->direction); +*/ + + starlet_ioh_sg_init_table(xfer->in, 2); + starlet_ioh_sg_set_buf(&xfer->in[0], cmd, sizeof(*cmd)); + starlet_ioh_sg_set_buf(&xfer->in[1], xfer->bounce_buf, xfer->size); + + starlet_ioh_sg_init_table(xfer->io, 1); + starlet_ioh_sg_set_buf(&xfer->io[0], xfer->reply, xfer->reply_len); + + cmd->opcode = (write) ? MMC_WRITE_MULTIPLE_BLOCK : + MMC_READ_MULTIPLE_BLOCK; + cmd->arg = start; + cmd->cmdtype = STSD_CMDTYPE_AC; /* STSD_CMDTYPE_ADTC */ + cmd->rsptype = stsd_opcode_to_rsptype(cmd->opcode); + cmd->blk_count = nr_blocks; + cmd->blk_size = xfer->blk_size; + cmd->dma_addr = xfer->dma_addr; /* bounce buf */ + cmd->is_dma = 1; + + error = starlet_ioh_ioctlv(host->fd, STSD_IOCTLV_SENDCMD, + 2, xfer->in, 1, xfer->io); +/* + dma_unmap_single(host->dev, + xfer->dma_addr, xfer->size, xfer->direction); +*/ + + if (!write) + memcpy(buf, xfer->bounce_buf, xfer->size); + + if (error) + DBG("%s: error=%d (%08x)\n", __func__, error, error); + + return error; +} + +static int stsd_check_request(struct stsd_host *host, struct request *req) +{ + unsigned long nr_sectors; + + if (!blk_fs_request(req)) + return -EIO; + + if (test_bit(__STSD_MEDIA_CHANGED, &host->flags)) { + drv_printk(KERN_ERR, "media changed, aborting\n"); + return -ENOMEDIUM; + } + + /* unit is kernel sectors */ + nr_sectors = + host->card.csd.capacity << (host->card.csd.read_blkbits - + KERNEL_SECTOR_SHIFT); + + /* keep our reads within limits */ + if (blk_rq_pos(req) + blk_rq_cur_sectors(req) > nr_sectors) { + drv_printk(KERN_ERR, "reading past end, aborting\n"); + return -EINVAL; + } + + return 0; +} + +static int stsd_do_request(struct stsd_host *host, struct request *req) +{ + unsigned long nr_blocks; /* in card blocks */ + unsigned long start; + int write; + int error; + + error = stsd_check_request(host, req); + if (error) + goto out; + + write = (rq_data_dir(req) == READ) ? 0 : 1; + + start = blk_rq_pos(req); + if (!stsd_card_is_sdhc(host)) + start <<= KERNEL_SECTOR_SHIFT; + nr_blocks = blk_rq_cur_sectors(req); + + error = stsd_do_block_transfer(host, write, + start, req->buffer, nr_blocks); + if (error) + DBG("%s: error=%d (%08x), start=%lu, \n", __func__, + error, error, start); + +out: + return error; +} + +static int stsd_io_thread(void *param) +{ + struct stsd_host *host = param; + struct request *req; + unsigned long flags; + int error; + + current->flags |= PF_NOFREEZE|PF_MEMALLOC; + + mutex_lock(&host->io_mutex); + for (;;) { + req = NULL; + set_current_state(TASK_INTERRUPTIBLE); + + spin_lock_irqsave(&host->queue_lock, flags); + if (!blk_queue_plugged(host->queue)) + req = blk_fetch_request(host->queue); + spin_unlock_irqrestore(&host->queue_lock, flags); + + if (!req) { + if (kthread_should_stop()) { + set_current_state(TASK_RUNNING); + break; + } + mutex_unlock(&host->io_mutex); + schedule(); + mutex_lock(&host->io_mutex); + continue; + } + set_current_state(TASK_INTERRUPTIBLE); + error = stsd_do_request(host, req); + + spin_lock_irqsave(&host->queue_lock, flags); + __blk_end_request_cur(req, error); + spin_unlock_irqrestore(&host->queue_lock, flags); + } + mutex_unlock(&host->io_mutex); + + return 0; +} + +static void stsd_request_func(struct request_queue *q) +{ + struct stsd_host *host = q->queuedata; + wake_up_process(host->io_thread); +} + +/* + * Block device hooks. + * + */ + +static DECLARE_MUTEX(open_lock); + +static int stsd_open(struct block_device *bdev, fmode_t mode) +{ + struct stsd_host *host = bdev->bd_disk->private_data; + int error = 0; + + if (!host || host->fd < 0) + return -ENXIO; + + /* honor exclusive open mode */ + if (host->refcnt == -1 || + (host->refcnt && (mode & FMODE_EXCL))) { + error = -EBUSY; + goto out; + } + + /* this takes care of revalidating the media if needed */ + check_disk_change(bdev); + if (!host->card.csd.capacity) { + error = -ENOMEDIUM; + goto out; + } + + down(&open_lock); + + if ((mode & FMODE_EXCL)) + host->refcnt = -1; + else + host->refcnt++; + + up(&open_lock); + +out: + return error; + +} + +static int stsd_release(struct gendisk *disk, fmode_t mode) +{ + struct stsd_host *host = disk->private_data; + + if (!host) + return -ENXIO; + + down(&open_lock); + + if (host->refcnt > 0) + host->refcnt--; + else + host->refcnt = 0; + + up(&open_lock); + + if (!host->refcnt && host->fd == -1) + kfree(host); + + return 0; +} + +static int stsd_media_changed(struct gendisk *disk) +{ + struct stsd_host *host = disk->private_data; + unsigned int last_serial; + int error; + + /* report a media change for zombies */ + if (!host) + return 1; + + /* report a media change if someone forced it */ + if (test_bit(__STSD_MEDIA_CHANGED, &host->flags)) + return 1; + + /* REVISIT use the starlet provided iotcl to check the status */ + + mutex_lock(&host->io_mutex); + + /* check if the serial number of the card changed */ + last_serial = host->card.cid.serial; + error = stsd_cmd_deselect_card(host); + if (!error) { + error = stsd_cmd_read_cid(host); + if (!error) + error = stsd_cmd_select_card(host); + } + + mutex_unlock(&host->io_mutex); + + if (!error && last_serial == host->card.cid.serial && last_serial) + clear_bit(__STSD_MEDIA_CHANGED, &host->flags); + else + set_bit(__STSD_MEDIA_CHANGED, &host->flags); + + return (host->flags & STSD_MEDIA_CHANGED) ? 1 : 0; +} + +static int stsd_revalidate_disk(struct gendisk *disk) +{ + struct stsd_host *host = disk->private_data; + int error = 0; + + /* report missing medium for zombies */ + if (!host) { + error = -ENOMEDIUM; + goto out; + } + + /* the block layer likes to call us multiple times... */ + if (!stsd_media_changed(host->disk)) + goto out; + + /* get the card into a known status */ + error = stsd_welcome_card(host); + if (error < 0 || stsd_card_is_bad(host)) { + drv_printk(KERN_ERR, "card welcome failed\n"); + if (stsd_card_is_bad(host)) + drv_printk(KERN_ERR, "stsd_card_is_bad() true\n"); + if (error < 0) + drv_printk(KERN_ERR, "error = %d\n", error); + error = -ENOMEDIUM; + /* FALL THROUGH */ + } + + /* inform the block layer about various sizes */ + blk_queue_logical_block_size(host->queue, KERNEL_SECTOR_SIZE); + set_capacity(host->disk, host->card.csd.capacity << + (host->card.csd.read_blkbits - KERNEL_SECTOR_SHIFT)); + + clear_bit(__STSD_MEDIA_CHANGED, &host->flags); + +out: + if (error) + DBG("%s: error=%d (%08x)\n", __func__, error, error); + return error; +} + +static int stsd_getgeo(struct block_device *bdev, struct hd_geometry *geo) +{ + geo->cylinders = get_capacity(bdev->bd_disk) / (4 * 16); + geo->heads = 4; + geo->sectors = 16; + return 0; +} + +static struct block_device_operations stsd_fops = { + .owner = THIS_MODULE, + .open = stsd_open, + .release = stsd_release, + .revalidate_disk = stsd_revalidate_disk, + .media_changed = stsd_media_changed, + .getgeo = stsd_getgeo, +}; + +/* + * Setup routines. + * + */ + +static int stsd_init_xfer(struct stsd_host *host) +{ + struct stsd_xfer *xfer; + + xfer = starlet_kzalloc(sizeof(*xfer), GFP_KERNEL); + if (!xfer) + return -ENOMEM; + + xfer->reply_len = 4 * sizeof(u32); + xfer->reply = starlet_ioh_kzalloc(xfer->reply_len); + if (!xfer->reply) { + starlet_kfree(xfer); + return -ENOMEM; + } + xfer->cmd = starlet_ioh_kzalloc(sizeof(*xfer->cmd)); + if (!xfer->cmd) { + starlet_ioh_kfree(xfer->reply); + starlet_kfree(xfer); + return -ENOMEM; + } + xfer->bounce_buf_size = STSD_MAX_SECTORS * KERNEL_SECTOR_SIZE; + xfer->bounce_buf = starlet_ioh_kzalloc(xfer->bounce_buf_size); + if (!xfer->bounce_buf) { + starlet_ioh_kfree(xfer->cmd); + starlet_ioh_kfree(xfer->reply); + starlet_kfree(xfer); + return -ENOMEM; + } + xfer->dma_addr = starlet_ioh_virt_to_phys(xfer->bounce_buf); + + xfer->blk_size = KERNEL_SECTOR_SIZE; + + host->xfer = xfer; + + return 0; +} + +static void stsd_exit_xfer(struct stsd_host *host) +{ + struct stsd_xfer *xfer = host->xfer; + + starlet_ioh_kfree(xfer->cmd); + starlet_ioh_kfree(xfer->reply); + starlet_kfree(host->xfer); +} + +static int stsd_init_blk_dev(struct stsd_host *host) +{ + struct gendisk *disk; + struct request_queue *queue; + int error; + + mutex_init(&host->io_mutex); + + /* queue */ + error = -ENOMEM; + spin_lock_init(&host->queue_lock); + queue = blk_init_queue(stsd_request_func, &host->queue_lock); + if (!queue) { + drv_printk(KERN_ERR, "error initializing queue\n"); + goto err_blk_init_queue; + } + host->max_phys_segments = 1; + blk_queue_max_phys_segments(queue, host->max_phys_segments); + blk_queue_max_hw_segments(queue, host->max_phys_segments); + blk_queue_max_sectors(queue, STSD_MAX_SECTORS); /* 16 * 512 = 8K */ + blk_queue_dma_alignment(queue, STARLET_IPC_DMA_ALIGN); + queue_flag_set_unlocked(QUEUE_FLAG_NONROT, queue); + queue->queuedata = host; + host->queue = queue; + + /* disk */ + disk = alloc_disk(1 << MMC_SHIFT); + if (!disk) { + drv_printk(KERN_ERR, "error allocating disk\n"); + goto err_alloc_disk; + } + disk->major = STSD_MAJOR; + disk->first_minor = 0 << MMC_SHIFT; + disk->fops = &stsd_fops; + sprintf(disk->disk_name, "%s%c", STSD_NAME, 'a'); + disk->private_data = host; + disk->queue = host->queue; + host->disk = disk; + + error = 0; + goto out; + +err_alloc_disk: + blk_cleanup_queue(host->queue); + host->queue = NULL; +err_blk_init_queue: +out: + return error; +} + +static void stsd_exit_blk_dev(struct stsd_host *host) +{ + blk_cleanup_queue(host->queue); + put_disk(host->disk); +} + +static int stsd_init_io_thread(struct stsd_host *host) +{ + int result = 0; + + host->io_thread = kthread_run(stsd_io_thread, host, "ksdio"); + if (IS_ERR(host->io_thread)) { + drv_printk(KERN_ERR, "error creating io thread\n"); + result = PTR_ERR(host->io_thread); + } + return result; +} + +static void stsd_exit_io_thread(struct stsd_host *host) +{ + if (!IS_ERR(host->io_thread)) { + wake_up_process(host->io_thread); + kthread_stop(host->io_thread); + host->io_thread = ERR_PTR(-EINVAL); + } +} + +static int stsd_init(struct stsd_host *host) +{ + int error; + + host->refcnt = 0; + spin_lock_init(&host->lock); + set_bit(__STSD_MEDIA_CHANGED, &host->flags); + host->f_max = 25000000; /* 25MHz */ + + host->fd = starlet_open(stsd_dev_sdio_slot0, 0); + if (host->fd < 0) { + drv_printk(KERN_ERR, "unable to open %s\n", + stsd_dev_sdio_slot0); + return -ENODEV; + } + + error = stsd_init_blk_dev(host); + if (error) + goto out; + + error = stsd_init_xfer(host); + if (error) + goto err_blk_dev; + + error = stsd_revalidate_disk(host->disk); +#if 0 + if (error < 0 || !mmc_card_present(&host->card)) { + error = -ENODEV; + goto err_xfer; + } +#endif + + error = stsd_init_io_thread(host); + if (error) + goto err_xfer; + + add_disk(host->disk); + + return 0; + +err_xfer: + stsd_exit_xfer(host); +err_blk_dev: + stsd_exit_blk_dev(host); +out: + return error; +} + +static void stsd_exit(struct stsd_host *host) +{ + del_gendisk(host->disk); + stsd_exit_io_thread(host); + stsd_exit_xfer(host); + stsd_exit_blk_dev(host); + if (host->fd >= 0) + starlet_close(host->fd); + host->fd = -1; + +} + +static void stsd_kill(struct stsd_host *host) +{ + if (host->refcnt > 0) { + drv_printk(KERN_ERR, "hey! card removed while in use!\n"); + set_bit(__STSD_MEDIA_CHANGED, &host->flags); + } + + stsd_exit(host); + + /* release the host immediately when not in use */ + if (!host->refcnt) + kfree(host); +} + +/* + * Driver model helper routines. + * + */ + +static int __devinit stsd_do_probe(struct device *dev) +{ + struct stsd_host *host; + int error; + + if (starlet_get_ipc_flavour() != STARLET_IPC_IOS) + return -ENODEV; + + host = kzalloc(sizeof(*host), GFP_KERNEL); + if (!host) { + drv_printk(KERN_ERR, "%s: failed to allocate stsd_host\n", + __func__); + return -ENOMEM; + } + dev_set_drvdata(dev, host); + host->dev = dev; + + error = stsd_init(host); + if (error) { + kfree(host); + dev_set_drvdata(dev, NULL); + } + + return error; +} + +static int __devexit stsd_do_remove(struct device *dev) +{ + struct stsd_host *host = dev_get_drvdata(dev); + + if (!host) + return -ENODEV; + + stsd_kill(host); + dev_set_drvdata(dev, NULL); + + return 0; +} + +/* + * OF platform device routines. + * + */ + +static int __init stsd_of_probe(struct of_device *odev, + const struct of_device_id *match) +{ + return stsd_do_probe(&odev->dev); +} + +static int __exit stsd_of_remove(struct of_device *odev) +{ + return stsd_do_remove(&odev->dev); +} + +static struct of_device_id stsd_of_match[] = { + { .compatible = "nintendo,starlet-ios-sd" }, + { }, +}; + +MODULE_DEVICE_TABLE(of, stsd_of_match); + +static struct of_platform_driver stsd_of_driver = { + .owner = THIS_MODULE, + .name = DRV_MODULE_NAME, + .match_table = stsd_of_match, + .probe = stsd_of_probe, + .remove = stsd_of_remove, +}; + + +/* + * Kernel module interface. + * + */ + +static int __init stsd_init_module(void) +{ + drv_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION, + stsd_driver_version); + + if (register_blkdev(STSD_MAJOR, DRV_MODULE_NAME)) { + drv_printk(KERN_ERR, "unable to register major %d\n", + STSD_MAJOR); + return -EIO; + } + + return of_register_platform_driver(&stsd_of_driver); +} + +static void __exit stsd_exit_module(void) +{ + of_unregister_platform_driver(&stsd_of_driver); + unregister_blkdev(STSD_MAJOR, DRV_MODULE_NAME); +} + +module_init(stsd_init_module); +module_exit(stsd_exit_module); + +MODULE_AUTHOR(DRV_AUTHOR); +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_LICENSE("GPL"); + diff --git a/drivers/exi/Kconfig b/drivers/exi/Kconfig new file mode 100644 index 0000000..c810782 --- /dev/null +++ b/drivers/exi/Kconfig @@ -0,0 +1,20 @@ +# +# Nintendo GameCube EXI (Expansion Interface) support. +# + +if GAMECUBE_COMMON + +menu "EXI support" + +config GAMECUBE_EXI + bool "Nintendo GameCube/Wii External Interface (EXI)" + default y + help + On the External Interface sit the memory card slots, + serial ports I & II, the Mask ROM, RTC, SRAM and UART. + + If in doubt, say Y here. + +endmenu + +endif diff --git a/drivers/exi/Makefile b/drivers/exi/Makefile new file mode 100644 index 0000000..eb80bce --- /dev/null +++ b/drivers/exi/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for the EXI bus core. +# + +obj-$(CONFIG_GAMECUBE_EXI) += exi-driver.o exi-hw.o diff --git a/drivers/exi/exi-driver.c b/drivers/exi/exi-driver.c new file mode 100644 index 0000000..b6b1ab6 --- /dev/null +++ b/drivers/exi/exi-driver.c @@ -0,0 +1,523 @@ +/* + * drivers/exi/exi-driver.c + * + * Nintendo GameCube EXternal Interface (EXI) driver model routines. + * Copyright (C) 2004-2009 The GameCube Linux Team + * Copyright (C) 2004 Arthur Othieno + * Copyright (C) 2004,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. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#define DRV_MODULE_NAME "exi" +#define DRV_DESCRIPTION "Nintendo GameCube/Wii EXternal Interface (EXI) driver" +#define DRV_AUTHOR "Arthur Othieno , " \ + "Todd Jeffreys , " \ + "Albert Herranz" + +static char exi_driver_version[] = "4.0i"; + +#define drv_printk(level, format, arg...) \ + printk(level DRV_MODULE_NAME ": " format , ## arg) + + +struct exi_map_id_to_name { + unsigned int id; + char *name; +}; + + +static void exi_bus_device_release(struct device *dev); +static int exi_bus_match(struct device *dev, struct device_driver *drv); + + +static struct bus_type exi_bus_type = { + .name = "exi", + .match = exi_bus_match, +}; +EXPORT_SYMBOL(exi_bus_type); + +static struct device exi_bus_devices[EXI_MAX_CHANNELS] = { + [0] = { + .init_name = "exi0", + .release = exi_bus_device_release, + }, + [1] = { + .init_name = "exi1", + .release = exi_bus_device_release, + }, + [2] = { + .init_name = "exi2", + .release = exi_bus_device_release, + }, +}; + +static struct exi_device exi_devices[EXI_MAX_CHANNELS][EXI_DEVICES_PER_CHANNEL]; + +static struct exi_map_id_to_name exi_map_id_to_name[] = { + { .id = EXI_ID_NONE, .name = "(external card)" }, + { .id = 0xffff1698, .name = "GameCube Mask ROM/RTC/SRAM/UART" }, + { .id = 0xfffff308, .name = "Wii Mask ROM/RTC/SRAM/UART" }, + { .id = 0x00000004, .name = "Memory Card 59" }, + { .id = 0x00000008, .name = "Memory Card 123" }, + { .id = 0x00000010, .name = "Memory Card 251" }, + { .id = 0x00000020, .name = "Memory Card 507" }, + { .id = 0x00000040, .name = "Memory Card 1019" }, + { .id = 0x00000080, .name = "Memory Card 2043" }, + { .id = 0x01010000, .name = "USB Adapter" }, + { .id = 0x01020000, .name = "NPDP GDEV" }, + { .id = 0x02020000, .name = "Modem" }, + { .id = 0x03010000, .name = "Marlin?" }, + { .id = 0x04020200, .name = "BroadBand Adapter (DOL-015)" }, + { .id = 0x04120000, .name = "AD16" }, + { .id = 0x05070000, .name = "IS Viewer" }, + { .id = 0x0a000000, .name = "Microphone (DOL-022)" }, + { .id = 0 } +}; + +/* + * Internal. Return the friendly name of an exi identifier. + */ +static const char *exi_name_id(unsigned int id) +{ + struct exi_map_id_to_name *map = exi_map_id_to_name; + + while (map->id) { + if (map->id == id) + return map->name; + map++; + } + return "Unknown"; +} + +/* + * Internal. Check if an exi device matches a given exi device id. + */ +static int exi_device_match_one(const struct exi_device_id *eid, + const struct exi_device *exi_device) +{ + /* + * We allow drivers to claim devices that do not provide + * EXI identifiers by matching directly on channel/device. + * These drivers must use EXI_ID_NONE on their eids. + */ + if (eid->id == exi_device->eid.id || eid->id == EXI_ID_NONE) { + /* match against channel and device */ + if (exi_device->eid.channel == eid->channel && + exi_device->eid.device == eid->device) { + return 1; + } + } + return 0; +} + +/* + * Internal. Check if an exi device matches a given set of exi device ids. + * Return the exi device identifier or %NULL if there is no match. + */ +static const struct exi_device_id * +exi_device_match(const struct exi_device_id *eids, + const struct exi_device *exi_device) +{ + while (eids && eids->id) { + if (exi_device_match_one(eids, exi_device)) + return eids; + eids++; + } + return NULL; +} + +/* + * Internal. Used to check if an exi device is supported by an exi driver. + */ +static int exi_bus_match(struct device *dev, struct device_driver *drv) +{ + struct exi_device *exi_device = to_exi_device(dev); + struct exi_driver *exi_driver = to_exi_driver(drv); + const struct exi_device_id *eids = exi_driver->eid_table; + + if (eids && exi_device_match(eids, exi_device)) + return 1; + return 0; +} + +/* + * Internal. Bus device release. + */ +static void exi_bus_device_release(struct device *dev) +{ + drv_printk(KERN_WARNING, "exi_bus_device_release called!\n"); +} + +static void exi_device_release(struct device *dev); + +/* + * Internal. Initialize an exi_device structure. + */ +static void exi_device_init(struct exi_device *exi_device, + unsigned int channel, unsigned int device) +{ + memset(exi_device, 0, sizeof(*exi_device)); + + exi_device->eid.id = EXI_ID_INVALID; + exi_device->eid.channel = channel; + exi_device->eid.device = device; + exi_device->frequency = EXI_FREQ_SCAN; + exi_device->exi_channel = to_exi_channel(channel); + + exi_device->dev.parent = &exi_bus_devices[channel]; + exi_device->dev.bus = &exi_bus_type; + dev_set_name(&exi_device->dev, "exi%01x:%01x", channel, device); + exi_device->dev.platform_data = to_exi_channel(channel); + set_dma_ops(&exi_device->dev, &dma_direct_ops); + exi_device->dev.release = exi_device_release; +} + +/* + * Internal. Device release. + */ +static void exi_device_release(struct device *dev) +{ + struct exi_device *exi_device = to_exi_device(dev); + unsigned int channel, device; + + channel = exi_device->eid.channel; + device = exi_device->eid.device; + + exi_device_init(exi_device, channel, device); +} + +/** + * exi_device_get - Increments the reference count of the exi device + * @exi_device: device being referenced + * + * Each live reference to an exi device should be refcounted. + * A pointer to the device with the incremented reference counter + * is returned. + */ +struct exi_device *exi_device_get(struct exi_device *exi_device) +{ + if (exi_device) + get_device(&exi_device->dev); + return exi_device; +} +EXPORT_SYMBOL(exi_device_get); + +/** + * exi_device_put - Releases a use of the exi device + * @exi_device: device that's been disconnected + * + * Must be called when a user of a device is finished with it. + */ +void exi_device_put(struct exi_device *exi_device) +{ + if (exi_device) + put_device(&exi_device->dev); +} +EXPORT_SYMBOL(exi_device_put); + +/** + * exi_get_exi_device - Returns a reference to an exi device + * @exi_channel: exi channel where the device is located + * @device: device number within the channel + */ +struct exi_device *exi_get_exi_device(struct exi_channel *exi_channel, + int device) +{ + /* REVISIT, take a ref here? */ + return &exi_devices[to_channel(exi_channel)][device]; +} +EXPORT_SYMBOL(exi_get_exi_device); + +/* + * Internal. Call device driver probe function on match. + */ +static int exi_device_probe(struct device *dev) +{ + struct exi_device *exi_device = to_exi_device(dev); + struct exi_driver *exi_driver = to_exi_driver(dev->driver); + const struct exi_device_id *eid; + int retval = -ENODEV; + + if (!exi_driver->eid_table) + goto out; + + eid = exi_device_match(exi_driver->eid_table, exi_device); + if (eid) { + exi_device->frequency = exi_driver->frequency; + if (exi_driver->probe) + retval = exi_driver->probe(exi_device); + } + if (retval >= 0) + retval = 0; + +out: + return retval; +} + +/* + * Internal. Call device driver remove function. + */ +static int exi_device_remove(struct device *dev) +{ + struct exi_device *exi_device = to_exi_device(dev); + struct exi_driver *exi_driver = to_exi_driver(dev->driver); + + if (exi_driver->remove) + exi_driver->remove(exi_device); + + return 0; +} + + +/** + * exi_driver_register - register an EXI device driver. + * @driver: driver structure to register. + * + * Registers an EXI device driver with the bus + * and consequently with the driver model core. + */ +int exi_driver_register(struct exi_driver *driver) +{ + driver->driver.name = driver->name; + driver->driver.bus = &exi_bus_type; + driver->driver.probe = exi_device_probe; + driver->driver.remove = exi_device_remove; + + return driver_register(&driver->driver); +} +EXPORT_SYMBOL(exi_driver_register); + +/** + * exi_driver_unregister - unregister an EXI device driver. + * @driver: driver structure to unregister. + * + * Unregisters an EXI device driver with the bus + * and consequently with the driver model core. + */ +void exi_driver_unregister(struct exi_driver *driver) +{ + driver_unregister(&driver->driver); +} +EXPORT_SYMBOL(exi_driver_unregister); + + +/* + * Internal. Re-scan a given device. + */ +static void exi_device_rescan(struct exi_device *exi_device) +{ + unsigned int id; + int error; + + /* now ID the device */ + id = exi_get_id(exi_device); + + if (exi_device->eid.id != EXI_ID_INVALID) { + /* device removed or changed */ + drv_printk(KERN_INFO, "about to remove [%s] id=0x%08x %s\n", + dev_name(&exi_device->dev), + exi_device->eid.id, + exi_name_id(exi_device->eid.id)); + device_unregister(&exi_device->dev); + drv_printk(KERN_INFO, "remove completed\n"); + exi_device->eid.id = EXI_ID_INVALID; + } + + if (id != EXI_ID_INVALID) { + /* a new device has been found */ + drv_printk(KERN_INFO, "about to add [%s] id=0x%08x %s\n", + dev_name(&exi_device->dev), + id, exi_name_id(id)); + exi_device->eid.id = id; + error = device_register(&exi_device->dev); + if (error) { + drv_printk(KERN_INFO, "add failed (%d)\n", error); + exi_device->eid.id = EXI_ID_INVALID; + } else + drv_printk(KERN_INFO, "add completed\n"); + } + + exi_update_ext_status(exi_get_exi_channel(exi_device)); +} + +/* + * Internal. Re-scan a given exi channel, looking for added, changed and + * removed exi devices. + */ +static void exi_channel_rescan(struct exi_channel *exi_channel) +{ + struct exi_device *exi_device; + unsigned int channel, device; + + /* add the exi devices underneath the parents */ + for (device = 0; device < EXI_DEVICES_PER_CHANNEL; ++device) { + channel = to_channel(exi_channel); + exi_device = &exi_devices[channel][device]; + exi_device_rescan(exi_device); + } +} + +/* + * Internal. Scans all the exi channels looking for exi devices. + */ +static void exi_bus_rescan(void) +{ + struct exi_channel *exi_channel; + unsigned int channel; + + for (channel = 0; channel < EXI_MAX_CHANNELS; ++channel) { + exi_channel = to_exi_channel(channel); + exi_channel_rescan(exi_channel); + } +} + + +static struct task_struct *exi_bus_task; +wait_queue_head_t exi_bus_waitq; + +/* + * Internal. Looks for new, changed or removed devices. + */ +static int exi_bus_thread(void *__unused) +{ + struct exi_channel *exi_channel; + struct exi_device *exi_device; + unsigned int channel; + int is_loaded, was_loaded; + + while (!kthread_should_stop()) { + /* scan the memcard slot channels for device changes */ + for (channel = 0; channel <= 1; ++channel) { + exi_channel = to_exi_channel(channel); + + is_loaded = exi_get_ext_line(exi_channel); + was_loaded = (exi_channel->flags & EXI_EXT) ? 1 : 0; + + if (is_loaded ^ was_loaded) { + exi_device = &exi_devices[channel][0]; + exi_device_rescan(exi_device); + } + } + + sleep_on_timeout(&exi_bus_waitq, HZ); + } + + return 0; +} + +/** + * exi_quiesce() - quiesce EXI hardware + * + * Put the EXI hardware into a calm state. + */ +void exi_quiesce(void) +{ + exi_hw_quiesce(); +} + +/* + * + */ +static int exi_init(struct resource *mem, unsigned int irq) +{ + struct exi_channel *exi_channel; + struct exi_device *exi_device; + unsigned int channel, device; + int retval; + + retval = exi_hw_init(DRV_MODULE_NAME, mem, irq); + if (retval) + goto err_hw_init; + + /* register root devices */ + for (channel = 0; channel < EXI_MAX_CHANNELS; ++channel) { + retval = device_register(&exi_bus_devices[channel]); + if (retval) + goto err_device_register; + } + + /* initialize devices */ + for (channel = 0; channel < EXI_MAX_CHANNELS; ++channel) { + exi_channel = to_exi_channel(channel); + for (device = 0; device < EXI_DEVICES_PER_CHANNEL; ++device) { + exi_device = &exi_devices[channel][device]; + exi_device_init(exi_device, channel, device); + } + } + + /* register the bus */ + retval = bus_register(&exi_bus_type); + if (retval) + goto err_bus_register; + + /* now enumerate through the bus and add all detected devices */ + exi_bus_rescan(); + + /* setup a thread to manage plugable devices */ + init_waitqueue_head(&exi_bus_waitq); + exi_bus_task = kthread_run(exi_bus_thread, NULL, "kexid"); + if (IS_ERR(exi_bus_task)) + drv_printk(KERN_WARNING, "failed to start exi kernel thread\n"); + + return 0; + +err_bus_register: +err_device_register: + while (--channel > 0) + device_unregister(&exi_bus_devices[channel]); + exi_hw_exit(mem, irq); +err_hw_init: + return retval; +} + +/* + * + */ +static int __init exi_layer_init(void) +{ + struct device_node *np; + struct resource res; + int retval; + + drv_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION, + exi_driver_version); + + np = of_find_compatible_node(NULL, NULL, "nintendo,flipper-exi"); + if (!np) { + np = of_find_compatible_node(NULL, NULL, + "nintendo,hollywood-exi"); + if (!np) + return -ENODEV; + } + + retval = of_address_to_resource(np, 0, &res); + if (retval) { + drv_printk(KERN_ERR, "no io memory range found\n"); + return -ENOMEM; + } + + retval = exi_init(&res, irq_of_parse_and_map(np, 0)); + of_node_put(np); + + return retval; +} +postcore_initcall(exi_layer_init); + +MODULE_AUTHOR(DRV_AUTHOR); +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_LICENSE("GPL"); + diff --git a/drivers/exi/exi-hw.c b/drivers/exi/exi-hw.c new file mode 100644 index 0000000..d64b8ea --- /dev/null +++ b/drivers/exi/exi-hw.c @@ -0,0 +1,1395 @@ +/* + * drivers/exi/exi-hw.c + * + * Nintendo GameCube EXpansion Interface support. Hardware routines. + * Copyright (C) 2004-2009 The GameCube Linux Team + * Copyright (C) 2004,2005 Todd Jeffreys + * 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 +#include +#include +#include +#include +#include + +#include +#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<io_lock, flags); +} +EXPORT_SYMBOL(exi_select_raw); + + +/** + * exi_deselect_raw - deselects all devices on an exi channel + * @exi_channel: channel + * + * Deselect any device previously selected on the specified EXI + * channel by unsetting all CS lines. + */ +void exi_deselect_raw(struct exi_channel *exi_channel) +{ + u32 __iomem *csr_reg = exi_channel->io_base + EXI_CSR; + u32 csr; + unsigned long flags; + + /* + * Preserve interrupt masks while clearing the CS line bits. + */ + spin_lock_irqsave(&exi_channel->io_lock, flags); + csr = in_be32(csr_reg); + csr &= (EXI_CSR_EXTINMASK | EXI_CSR_TCINTMASK | EXI_CSR_EXIINTMASK); + out_be32(csr_reg, csr); + spin_unlock_irqrestore(&exi_channel->io_lock, flags); +} +EXPORT_SYMBOL(exi_deselect_raw); + +/** + * exi_transfer_raw - performs an exi transfer using immediate mode + * @exi_channel: channel + * @data: pointer to data being read/writen + * @len: length of data + * @mode: direction of transfer (EXI_OP_{READ,READWRITE,WRITE}) + * + * Read or write data on a given EXI channel. + * + */ +void exi_transfer_raw(struct exi_channel *exi_channel, + void *data, size_t len, int mode) +{ + while (len >= 4) { + __exi_transfer_raw_u32(exi_channel, data, mode); + exi_channel->stats_xfers++; + data += 4; + len -= 4; + } + + switch (len) { + case 1: + __exi_transfer_raw_u8(exi_channel, data, mode); + exi_channel->stats_xfers++; + break; + case 2: + __exi_transfer_raw_u16(exi_channel, data, mode); + exi_channel->stats_xfers++; + break; + case 3: + /* XXX optimize this case */ + __exi_transfer_raw_u16(exi_channel, data, mode); + exi_channel->stats_xfers++; + __exi_transfer_raw_u8(exi_channel, data+2, mode); + exi_channel->stats_xfers++; + break; + default: + break; + } +} +EXPORT_SYMBOL(exi_transfer_raw); + +/* + * Internal. Start a transfer using "interrupt-driven immediate" mode. + */ +static void exi_start_idi_transfer_raw(struct exi_channel *exi_channel, + void *data, size_t len, int mode) +{ + void __iomem *io_base = exi_channel->io_base; + u32 __iomem *csr_reg = io_base + EXI_CSR; + u32 val = ~0; + unsigned long flags; + + BUG_ON(len < 1 || len > 4); + + exi_channel->stats_idi_xfers++; + exi_channel->stats_xfers++; + + if ((mode & EXI_OP_WRITE)) { + switch (len) { + case 1: + val = *((u8 *)data) << 24; + break; + case 2: + val = *((u16 *)data) << 16; + break; + case 3: + val = *((u16 *)data) << 16; + val |= *((u8 *)data+2) << 8; + break; + case 4: + val = *((u32 *)data); + break; + default: + break; + } + } + + out_be32(io_base + EXI_DATA, val); + + /* enable the Transfer Complete interrupt */ + spin_lock_irqsave(&exi_channel->io_lock, flags); + out_be32(csr_reg, in_be32(csr_reg) | EXI_CSR_TCINTMASK); + spin_unlock_irqrestore(&exi_channel->io_lock, flags); + + /* start the transfer */ + out_be32(io_base + EXI_CR, + EXI_CR_TSTART | EXI_CR_TLEN(len) | (mode&0xf)); +} + +/* + * Internal. Finish a transfer using "interrupt-driven immediate" mode. + */ +static void exi_end_idi_transfer_raw(struct exi_channel *exi_channel, + void *data, size_t len, int mode) +{ + void __iomem *io_base = exi_channel->io_base; + u32 val = ~0; + + BUG_ON(len < 1 || len > 4); + + if ((mode&0xf) != EXI_OP_WRITE) { + val = in_be32(io_base + EXI_DATA); + switch (len) { + case 1: + *((u8 *)data) = (u8)(val >> 24); + break; + case 2: + *((u16 *)data) = (u16)(val >> 16); + break; + case 3: + *((u16 *)data) = (u16)(val >> 16); + *((u8 *)data+2) = (u8)(val >> 8); + break; + case 4: + *((u32 *)data) = (u32)(val); + break; + default: + break; + } + } +} + +/* + * Internal. Start a transfer using DMA mode. + */ +static void exi_start_dma_transfer_raw(struct exi_channel *exi_channel, + dma_addr_t data, size_t len, int mode) +{ + void __iomem *io_base = exi_channel->io_base; + u32 __iomem *csr_reg = io_base + EXI_CSR; + unsigned long flags; + + BUG_ON((data & EXI_DMA_ALIGN) != 0 || + (len & EXI_DMA_ALIGN) != 0); + + exi_channel->stats_dma_xfers++; + exi_channel->stats_xfers++; + + /* + * We clear the DATA register here to avoid confusing some + * special hardware, like SD cards. + * Indeed, we need all 1s here. + */ + out_be32(io_base + EXI_DATA, ~0); + + /* setup address and length of transfer */ + out_be32(io_base + EXI_MAR, data); + out_be32(io_base + EXI_LENGTH, len); + + /* enable the Transfer Complete interrupt */ + spin_lock_irqsave(&exi_channel->io_lock, flags); + out_be32(csr_reg, in_be32(csr_reg) | EXI_CSR_TCINTMASK); + spin_unlock_irqrestore(&exi_channel->io_lock, flags); + + /* start the transfer */ + out_be32(io_base + EXI_CR, EXI_CR_TSTART | EXI_CR_DMA | (mode&0xf)); +} + + +/* + * Internal. Busy-wait until a DMA mode transfer operation completes. + */ +static void exi_wait_for_transfer_raw(struct exi_channel *exi_channel) +{ + u32 __iomem *cr_reg = exi_channel->io_base + EXI_CR; + u32 __iomem *csr_reg = exi_channel->io_base + EXI_CSR; + unsigned long flags; + unsigned long deadline = jiffies + 2*HZ; + int borked = 0; + + /* we don't want TCINTs to disturb us while waiting */ + spin_lock_irqsave(&exi_channel->io_lock, flags); + out_be32(csr_reg, in_be32(csr_reg) & ~EXI_CSR_TCINTMASK); + spin_unlock_irqrestore(&exi_channel->io_lock, flags); + + /* busy-wait for transfer complete */ + while ((in_be32(cr_reg)&EXI_CR_TSTART) && !borked) { + cpu_relax(); + borked = time_after(jiffies, deadline); + } + + if (borked) { + drv_printk(KERN_ERR, "exi transfer took too long, " + "is your hardware ok?"); + } + + /* ack the Transfer Complete interrupt */ + spin_lock_irqsave(&exi_channel->io_lock, flags); + out_be32(csr_reg, in_be32(csr_reg) | EXI_CSR_TCINT); + spin_unlock_irqrestore(&exi_channel->io_lock, flags); +} + + +/* + * + * + */ + +static void exi_command_done(struct exi_command *cmd); + +/* + * Internal. Initialize an exi_channel structure. + */ +void exi_channel_init(struct exi_channel *exi_channel, unsigned int channel) +{ + memset(exi_channel, 0, sizeof(*exi_channel)); + exi_channel->events[EXI_EVENT_IRQ].id = EXI_EVENT_IRQ; + exi_channel->events[EXI_EVENT_INSERT].id = EXI_EVENT_INSERT; + exi_channel->events[EXI_EVENT_TC].id = EXI_EVENT_TC; + + spin_lock_init(&exi_channel->lock); + spin_lock_init(&exi_channel->io_lock); + init_waitqueue_head(&exi_channel->wait_queue); + + exi_channel->channel = channel; + exi_channel->io_base = exi_io_mem + channel * EXI_CHANNEL_SPACING; + + tasklet_init(&exi_channel->tasklet, + exi_tasklet, (unsigned long)exi_channel); +} + +/* + * Internal. Check if an exi channel has delayed work to do. + */ +static void exi_check_pending_work(void) +{ + struct exi_channel *exi_channel; + + exi_channel_for_each(exi_channel) { + if (exi_channel->csr) + tasklet_schedule(&exi_channel->tasklet); + } +} + +/* + * Internal. Finish a DMA transfer. + * Caller holds the channel lock. + */ +static void exi_end_dma_transfer(struct exi_channel *exi_channel) +{ + struct exi_command *cmd; + + cmd = exi_channel->queued_cmd; + if (cmd) { + BUG_ON(!(exi_channel->flags & EXI_DMABUSY)); + + exi_channel->flags &= ~EXI_DMABUSY; + dma_unmap_single(&exi_channel->owner->dev, + cmd->dma_addr, cmd->dma_len, + (cmd->opcode == EXI_OP_READ) ? + DMA_FROM_DEVICE : DMA_TO_DEVICE); + + exi_channel->queued_cmd = NULL; + } +} + +/* + * Internal. Finish an "interrupt-driven immediate" transfer. + * Caller holds the channel lock. + * + * If more data is pending transfer, it schedules a new transfer. + * Returns zero if no more transfers are required, non-zero otherwise. + * + */ +static int exi_end_idi_transfer(struct exi_channel *exi_channel) +{ + struct exi_command *cmd; + int len, offset; + unsigned int balance = 16 /* / sizeof(u32) */; + + cmd = exi_channel->queued_cmd; + if (cmd) { + BUG_ON((exi_channel->flags & EXI_DMABUSY)); + + len = (cmd->bytes_left > 4) ? 4 : cmd->bytes_left; + offset = cmd->len - cmd->bytes_left; + exi_end_idi_transfer_raw(exi_channel, + cmd->data + offset, len, + cmd->opcode); + cmd->bytes_left -= len; + + if (balance && cmd->bytes_left > 0) { + offset += len; + len = (cmd->bytes_left > balance) ? + balance : cmd->bytes_left; + exi_transfer_raw(exi_channel, + cmd->data + offset, len, cmd->opcode); + cmd->bytes_left -= len; + } + + if (cmd->bytes_left > 0) { + offset = cmd->len - cmd->bytes_left; + len = (cmd->bytes_left > 4) ? 4 : cmd->bytes_left; + + exi_start_idi_transfer_raw(exi_channel, + cmd->data + offset, len, + cmd->opcode); + } else { + exi_channel->queued_cmd = NULL; + } + } + + return (exi_channel->queued_cmd) ? 1 : 0; +} + +/* + * Internal. Wait until a single transfer completes, and launch callbacks + * when the whole transfer is completed. + */ +static int exi_wait_for_transfer_one(struct exi_channel *exi_channel) +{ + struct exi_command *cmd; + unsigned long flags; + int pending = 0; + + spin_lock_irqsave(&exi_channel->lock, flags); + + exi_wait_for_transfer_raw(exi_channel); + + cmd = exi_channel->queued_cmd; + if (cmd) { + if ((exi_channel->flags & EXI_DMABUSY)) { + /* dma transfers need just one transfer */ + exi_end_dma_transfer(exi_channel); + } else { + pending = exi_end_idi_transfer(exi_channel); + } + + spin_unlock_irqrestore(&exi_channel->lock, flags); + + if (!pending) + exi_command_done(cmd); + goto out; + } + + spin_unlock_irqrestore(&exi_channel->lock, flags); +out: + return pending; +} + +#if 0 +/* + * Internal. Wait until a full transfer completes and launch callbacks. + */ +static void exi_wait_for_transfer(struct exi_channel *exi_channel) +{ + while (exi_wait_for_transfer_one(exi_channel)) + cpu_relax(); +} +#endif + +/* + * Internal. Call any done hooks. + */ +static void exi_command_done(struct exi_command *cmd) +{ + /* if specified, call the completion routine */ + if (cmd->done) + cmd->done(cmd); +} + +/* + * Internal. Take a channel. + */ +static int exi_take_channel(struct exi_channel *exi_channel, + struct exi_device *exi_device, int wait) +{ + unsigned long flags; + int result = 0; + + BUG_ON(!exi_device); + + spin_lock_irqsave(&exi_channel->lock, flags); + while (exi_channel->owner) { + spin_unlock_irqrestore(&exi_channel->lock, flags); + if (!wait) + return -EBUSY; + wait_event(exi_channel->wait_queue, + !exi_channel->owner); + spin_lock_irqsave(&exi_channel->lock, flags); + } + exi_channel->owner = exi_device; + spin_unlock_irqrestore(&exi_channel->lock, flags); + + return result; +} + +/* + * Internal. Give a channel. + */ +static int exi_give_channel(struct exi_channel *exi_channel) +{ + WARN_ON(exi_channel->owner == NULL); + exi_channel->owner = NULL; + wake_up(&exi_channel->wait_queue); + return 0; +} + +/* + * Internal. Perform the post non-DMA transfer associated to a DMA transfer. + */ +static void exi_cmd_post_transfer(struct exi_command *cmd) +{ + struct exi_channel *exi_channel = cmd->exi_channel; + struct exi_command *post_cmd = &exi_channel->post_cmd; + + DBG("channel=%d\n", exi_channel->channel); + + exi_transfer_raw(exi_channel, post_cmd->data, post_cmd->len, + post_cmd->opcode); + + cmd->done_data = post_cmd->done_data; + cmd->done = post_cmd->done; + exi_op_nop(post_cmd, exi_channel); + exi_command_done(cmd); +} + + +#define exi_align_next(x) (void *) \ + (((unsigned long)(x)+EXI_DMA_ALIGN)&~EXI_DMA_ALIGN) +#define exi_align_prev(x) (void *) \ + ((unsigned long)(x)&~EXI_DMA_ALIGN) +#define exi_is_aligned(x) (void *) \ + (!((unsigned long)(x)&EXI_DMA_ALIGN)) + +/* + * Internal. Perform a transfer. + * Caller holds the channel lock. + */ +static int exi_cmd_transfer(struct exi_command *cmd) +{ + struct exi_channel *exi_channel = cmd->exi_channel; + struct exi_command *post_cmd = &exi_channel->post_cmd; + void *pre_data, *data, *post_data; + unsigned int pre_len, len, post_len; + int opcode; + int result = 0; + + BUG_ON(!exi_channel->owner); + + len = cmd->len; + if (!len) + goto done; + + DBG("channel=%d, opcode=%d\n", exi_channel->channel, cmd->opcode); + + opcode = cmd->opcode; + data = cmd->data; + + /* interrupt driven immediate transfer... */ + if ((cmd->flags & EXI_CMD_IDI)) { + exi_channel->queued_cmd = cmd; + exi_channel->flags &= ~EXI_DMABUSY; + + cmd->bytes_left = cmd->len; + len = (cmd->bytes_left > 4) ? 4 : cmd->bytes_left; + exi_start_idi_transfer_raw(exi_channel, data, len, opcode); + + result = 1; /* wait */ + goto done; + } + + /* + * We can't do DMA transfers unless we have at least 32 bytes. + * And we won't do DMA transfers if user requests that. + */ + if (len < EXI_DMA_ALIGN+1 || (cmd->flags & EXI_CMD_NODMA)) { + exi_transfer_raw(exi_channel, data, len, opcode); + goto done; + } + + /* + * |_______________|______...______|_______________| DMA alignment + * <--pre_len--><---- len -----><-post_len-> + * +-----------+------...------+-----------+ + * | pre_data | data | post_data | + * | non-DMA | DMA | non-DMA | + * +-----------+------...------+-----------+ + * < 32 bytes N*32 bytes < 32 bytes + * |<--------->|<-----...----->|<--------->| + * <-------------- cmd->len ---------------> + */ + + pre_data = data; + post_data = exi_align_prev(pre_data+len); + data = exi_align_next(pre_data); + + pre_len = data - pre_data; + post_len = (pre_data + len) - post_data; + len = post_data - data; + + /* + * Coalesce pre and post data transfers if no DMA transfer is possible. + */ + if (!len) { + /* + * Maximum transfer size here is 31+31=62 bytes. + */ + exi_transfer_raw(exi_channel, pre_data, + pre_len + post_len, opcode); + goto done; + } + + /* + * The first unaligned chunk can't use DMA. + */ + if (pre_len > 0) { + /* + * Maximum transfer size here is 31 bytes. + */ + exi_transfer_raw(exi_channel, pre_data, pre_len, opcode); + } + + /* + * Perform a DMA transfer on the aligned data, followed by a non-DMA + * data transfer on the remaining data. + */ + if (post_len > 0) { + /* + * Maximum transfer size here will be 31 bytes. + */ + exi_op_transfer(post_cmd, exi_channel, + post_data, post_len, opcode); + post_cmd->done_data = cmd->done_data; + post_cmd->done = cmd->done; + cmd->done_data = NULL; + cmd->done = exi_cmd_post_transfer; + } + + exi_channel->queued_cmd = cmd; + exi_channel->flags |= EXI_DMABUSY; + + cmd->dma_len = len; + cmd->dma_addr = dma_map_single(&exi_channel->owner->dev, + data, len, + (cmd->opcode == EXI_OP_READ) ? + DMA_FROM_DEVICE : DMA_TO_DEVICE); + + exi_start_dma_transfer_raw(exi_channel, cmd->dma_addr, len, opcode); + + result = 1; /* wait */ + +done: + return result; +} + +/** + * exi_run_command - executes a single exi command + * @cmd: the command to execute + * + * Context: user + * + * Run just one command. + * + */ +static int exi_run_command(struct exi_command *cmd) +{ + struct exi_channel *exi_channel = cmd->exi_channel; + struct exi_device *exi_device = cmd->exi_device; + unsigned long flags; + int wait = !(cmd->flags & EXI_CMD_NOWAIT); + int result = 0; + + if (cmd->opcode != EXI_OP_TAKE) + WARN_ON(exi_channel->owner != exi_device); + + switch (cmd->opcode) { + case EXI_OP_NOP: + break; + case EXI_OP_TAKE: + result = exi_take_channel(exi_channel, exi_device, wait); + break; + case EXI_OP_GIVE: + result = exi_give_channel(exi_channel); + if (!exi_channel->owner) + exi_check_pending_work(); + break; + case EXI_OP_SELECT: + exi_select_raw(exi_channel, exi_device->eid.device, + exi_device->frequency); + break; + case EXI_OP_DESELECT: + exi_deselect_raw(exi_channel); + break; + case EXI_OP_READ: + case EXI_OP_READWRITE: + case EXI_OP_WRITE: + spin_lock_irqsave(&exi_channel->lock, flags); + result = exi_cmd_transfer(cmd); + spin_unlock_irqrestore(&exi_channel->lock, flags); + break; + default: + result = -ENOSYS; + break; + } + + /* + * We check for delayed work every time the channel becomes + * idle. + */ + if (!result) + exi_command_done(cmd); + + return result; +} + + +/* + * Internal. Completion routine. + */ +static void exi_wait_done(struct exi_command *cmd) +{ + complete(cmd->done_data); +} + +/* + * Internal. Run a command and wait. + * Might sleep if called from user context. Otherwise will busy-wait. + */ +static int exi_run_command_and_wait(struct exi_command *cmd) +{ + DECLARE_COMPLETION(complete); + int result; + + cmd->done_data = &complete; + cmd->done = exi_wait_done; + result = exi_run_command(cmd); + if (result > 0) { + wait_for_completion(&complete); + result = 0; + } + return result; +} + +/** + * exi_take - reserves an exi channel for exclusive use by a device + * @exi_device: exi device making the reservation + * @wait: wait for the operation to complete + * + * Reserves the channel of a given EXI device. + */ +int exi_take(struct exi_device *exi_device, int wait) +{ + struct exi_command cmd; + + exi_op_take(&cmd, exi_device); + if (!wait) + cmd.flags |= EXI_CMD_NOWAIT; + return exi_run_command(&cmd); +} +EXPORT_SYMBOL(exi_take); + +/** + * exi_give - releases an exi channel + * @exi_device: exi device making the release + * + * Releases the channel of a given EXI device. + */ +int exi_give(struct exi_device *exi_device) +{ + struct exi_command cmd; + + exi_op_give(&cmd, exi_device->exi_channel); + return exi_run_command(&cmd); +} +EXPORT_SYMBOL(exi_give); + +/** + * exi_select - selects a exi device + * @exi_device: exi device being selected + * + * Selects a given EXI device. + */ +void exi_select(struct exi_device *exi_device) +{ + struct exi_command cmd; + + exi_op_select(&cmd, exi_device); + exi_run_command(&cmd); +} +EXPORT_SYMBOL(exi_select); + +/** + * exi_deselect - deselects all devices on an exi channel + * @exi_channel: channel + * + * Deselects all EXI devices on the given channel. + * + */ +void exi_deselect(struct exi_channel *exi_channel) +{ + struct exi_command cmd; + + exi_op_deselect(&cmd, exi_channel); + exi_run_command(&cmd); +} +EXPORT_SYMBOL(exi_deselect); + +/** + * exi_transfer - Performs a read or write EXI transfer. + * @exi_channel: channel + * @data: pointer to data being read/written + * @len: length of data + * @opcode: operation code (EXI_OP_{READ,READWRITE,WRITE}) + * + * Read or write data on a given EXI channel. + */ +void exi_transfer(struct exi_channel *exi_channel, void *data, size_t len, + int opcode, unsigned long flags) +{ + struct exi_command cmd; + + exi_op_transfer(&cmd, exi_channel, data, len, opcode); + cmd.flags |= flags; + exi_run_command_and_wait(&cmd); +} +EXPORT_SYMBOL(exi_transfer); + +/* + * Internal. Release several previously reserved channels, according to a + * channel mask. + */ +static void __give_some_channels(unsigned int channel_mask) +{ + struct exi_channel *exi_channel; + unsigned int channel; + + for (channel = 0; channel_mask && channel < EXI_MAX_CHANNELS; + channel++) { + if ((channel_mask & (1<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<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<lock, flags); + } + } + + if (result) + __give_some_channels(taken_channel_mask); + + return result; +} + +/* + * Internal. Determine if we can trigger an exi event. + */ +static inline int exi_can_trigger_event(struct exi_event *event) +{ + return !__try_take_some_channels(event->channel_mask, event->owner); +} + +/* + * Internal. Finish an exi event invocation. + */ +static inline void exi_finish_event(struct exi_event *event) +{ + __give_some_channels(event->channel_mask); +} + +/* + * Internal. Trigger an exi event. + */ +static inline int exi_trigger_event(struct exi_channel *exi_channel, + struct exi_event *event) +{ + exi_event_handler_t handler; + int result = 0; + + handler = event->handler; + if (handler) + result = handler(exi_channel, event->id, event->data); + return result; +} + +/* + * Internal. Conditionally trigger an exi event. + */ +static void exi_cond_trigger_event(struct exi_channel *exi_channel, + unsigned int event_id, int csr_mask) +{ + struct exi_event *event; + unsigned long flags; + + if ((exi_channel->csr & csr_mask)) { + event = &exi_channel->events[event_id]; + if (exi_can_trigger_event(event)) { + spin_lock_irqsave(&exi_channel->lock, flags); + exi_channel->csr &= ~csr_mask; + spin_unlock_irqrestore(&exi_channel->lock, flags); + exi_trigger_event(exi_channel, event); + exi_finish_event(event); + } + } + + return; +} + +/* + * Internal. Tasklet used to execute delayed work. + */ +static void exi_tasklet(unsigned long param) +{ + struct exi_channel *exi_channel = (struct exi_channel *)param; + + DBG("channel=%d, csr=%08lx\n", exi_channel->channel, exi_channel->csr); + + if (exi_channel->queued_cmd) { + DBG("tasklet while xfer in flight on channel %d, csr = %08lx\n", + exi_channel->channel, exi_channel->csr); + } + + /* + * We won't lauch event handlers if any of the channels we + * provided on event registration is in use. + */ + + /*exi_cond_trigger_event(exi_channel, EXI_EVENT_TC, EXI_CSR_TCINT);*/ + exi_cond_trigger_event(exi_channel, EXI_EVENT_IRQ, EXI_CSR_EXIINT); + exi_cond_trigger_event(exi_channel, EXI_EVENT_INSERT, EXI_CSR_EXTIN); +} + +/* + * Internal. Interrupt handler for EXI interrupts. + */ +static irqreturn_t exi_irq_handler(int irq, void *dev_id) +{ + struct exi_channel *exi_channel; + u32 __iomem *csr_reg; + u32 csr, status, mask; + unsigned long flags; + + exi_channel_for_each(exi_channel) { + csr_reg = exi_channel->io_base + EXI_CSR; + + /* + * Determine if we have pending interrupts on this channel, + * and which ones. + */ + spin_lock_irqsave(&exi_channel->io_lock, flags); + + csr = in_be32(csr_reg); + mask = csr & (EXI_CSR_EXTINMASK | + EXI_CSR_TCINTMASK | EXI_CSR_EXIINTMASK); + status = csr & (mask << 1); + if (!status) { + spin_unlock_irqrestore(&exi_channel->io_lock, flags); + continue; + } + + /* XXX do not signal TC events for now... */ + exi_channel->csr |= (status & ~EXI_CSR_TCINT); + + DBG("channel=%d, csr=%08lx\n", exi_channel->channel, + exi_channel->csr); + + /* ack all for this channel */ + out_be32(csr_reg, csr | status); + + spin_unlock_irqrestore(&exi_channel->io_lock, flags); + + if ((status & EXI_CSR_TCINT)) + exi_wait_for_transfer_one(exi_channel); + if ((status & EXI_CSR_EXTIN)) + wake_up(&exi_bus_waitq); + + if (exi_channel->csr && !exi_is_taken(exi_channel)) + tasklet_schedule(&exi_channel->tasklet); + } + return IRQ_HANDLED; +} + +/* + * Internal. Enable an exi event. + */ +static int exi_enable_event(struct exi_channel *exi_channel, + unsigned int event_id) +{ + u32 __iomem *csr_reg = exi_channel->io_base + EXI_CSR; + u32 csr; + unsigned long flags; + + spin_lock_irqsave(&exi_channel->io_lock, flags); + csr = in_be32(csr_reg); + + /* ack and enable the associated interrupt */ + switch (event_id) { + case EXI_EVENT_INSERT: + out_be32(csr_reg, csr | (EXI_CSR_EXTIN | EXI_CSR_EXTINMASK)); + break; + case EXI_EVENT_TC: + /*out_be32(csr_reg, + csr | (EXI_CSR_TCINT | EXI_CSR_TCINTMASK));*/ + break; + case EXI_EVENT_IRQ: + out_be32(csr_reg, csr | (EXI_CSR_EXIINT | EXI_CSR_EXIINTMASK)); + break; + } + spin_unlock_irqrestore(&exi_channel->io_lock, flags); + return 0; +} + +/* + * Internal. Disable an exi event. + */ +static int exi_disable_event(struct exi_channel *exi_channel, + unsigned int event_id) +{ + u32 __iomem *csr_reg = exi_channel->io_base + EXI_CSR; + u32 csr; + unsigned long flags; + + spin_lock_irqsave(&exi_channel->io_lock, flags); + csr = in_be32(csr_reg); + + /* ack and disable the associated interrupt */ + switch (event_id) { + case EXI_EVENT_INSERT: + out_be32(csr_reg, (csr | EXI_CSR_EXTIN) & ~EXI_CSR_EXTINMASK); + break; + case EXI_EVENT_TC: + /*out_be32(csr_reg, + (csr | EXI_CSR_TCINT) & ~EXI_CSR_TCINTMASK);*/ + break; + case EXI_EVENT_IRQ: + out_be32(csr_reg, (csr | EXI_CSR_EXIINT) & ~EXI_CSR_EXIINTMASK); + break; + } + spin_unlock_irqrestore(&exi_channel->io_lock, flags); + return 0; +} + +/** + * exi_event_register - Registers an event on a given channel. + * @exi_channel: channel + * @event_id: event id + * @handler: event handler + * @data: data passed to event handler + * + * Register a handler to be called whenever a specified event happens + * on the given channel. + */ +int exi_event_register(struct exi_channel *exi_channel, unsigned int event_id, + struct exi_device *exi_device, + exi_event_handler_t handler, void *data, + unsigned int channel_mask) +{ + struct exi_event *event; + int result = 0; + + BUG_ON(event_id > EXI_MAX_EVENTS); + + event = &exi_channel->events[event_id]; + + spin_lock(&exi_channel->lock); + if (event->handler) { + result = -EBUSY; + goto out; + } + event->owner = exi_device; + event->handler = handler; + event->data = data; + event->channel_mask = channel_mask; + exi_enable_event(exi_channel, event_id); + +out: + spin_unlock(&exi_channel->lock); + return result; +} +EXPORT_SYMBOL(exi_event_register); + +/** + * exi_event_unregister - Unregisters an event on a given channel. + * @exi_channel: channel + * @event_id: event id + * + * Unregister a previously registered event handler. + */ +int exi_event_unregister(struct exi_channel *exi_channel, unsigned int event_id) +{ + struct exi_event *event; + int result = 0; + + BUG_ON(event_id > EXI_MAX_EVENTS); + + event = &exi_channel->events[event_id]; + + spin_lock(&exi_channel->lock); + exi_disable_event(exi_channel, event_id); + event->owner = NULL; + event->handler = NULL; + event->data = NULL; + event->channel_mask = 0; + spin_unlock(&exi_channel->lock); + + return result; +} +EXPORT_SYMBOL(exi_event_unregister); + +/* + * Internal. Quiesce a channel. + */ +static void exi_quiesce_channel(struct exi_channel *exi_channel, u32 csr_mask) +{ + /* wait for dma transfers to complete */ + exi_wait_for_transfer_raw(exi_channel); + + /* ack and mask all interrupts */ + out_be32(exi_channel->io_base + EXI_CSR, + EXI_CSR_TCINT | EXI_CSR_EXIINT | EXI_CSR_EXTIN | csr_mask); +} + +/* + * Internal. Quiesce all channels. + */ +static void exi_quiesce_all_channels(u32 csr_mask) +{ + struct exi_channel *exi_channel; + + exi_channel_for_each(exi_channel) { + exi_quiesce_channel(exi_channel, csr_mask); + } +} + +/** + * exi_get_id - Returns the EXI ID of a device + * @exi_channel: channel + * @device: device number on channel + * @freq: clock frequency index + * + * Returns the EXI ID of an EXI device on a given channel. + * Might sleep. + */ +u32 exi_get_id(struct exi_device *exi_device) +{ + struct exi_channel *exi_channel = exi_device->exi_channel; + u32 __iomem *csr_reg = exi_channel->io_base + EXI_CSR; + u32 id = EXI_ID_INVALID; + u16 cmd = 0; + + /* ask for the EXI id */ + exi_dev_take(exi_device); + exi_dev_select(exi_device); + exi_dev_write(exi_device, &cmd, sizeof(cmd)); + exi_dev_read(exi_device, &id, sizeof(id)); + exi_dev_deselect(exi_device); + exi_dev_give(exi_device); + + /* "canonicalize" the id */ + if (!id) + id = EXI_ID_INVALID; + /* + * We return a EXI_ID_NONE if there is some unidentified device + * inserted in memcard slot A or memcard slot B. + * This, for example, allows the SD/MMC driver to see inserted cards. + */ + if (id == EXI_ID_INVALID) { + if ((__to_channel(exi_channel) == 0 || + __to_channel(exi_channel) == 1) + && exi_device->eid.device == 0) { + if (in_be32(csr_reg) & EXI_CSR_EXT) + id = EXI_ID_NONE; + } + } + + return id; +} +EXPORT_SYMBOL(exi_get_id); + +/* + * Tells if there is a device inserted in one of the memory card slots. + */ +int exi_get_ext_line(struct exi_channel *exi_channel) +{ + u32 __iomem *csr_reg = exi_channel->io_base + EXI_CSR; + return (in_be32(csr_reg) & EXI_CSR_EXT) ? 1 : 0; +} + +/* + * Saves the current insertion status of a given channel. + */ +void exi_update_ext_status(struct exi_channel *exi_channel) +{ + if (exi_get_ext_line(exi_channel)) + exi_channel->flags |= EXI_EXT; + else + exi_channel->flags &= ~EXI_EXT; +} + +void exi_hw_quiesce(void) +{ + exi_quiesce_all_channels(0); +} + +/* + * Pseudo-Internal. Initialize basic channel structures and hardware. + */ +int exi_hw_init(char *module_name, struct resource *mem, unsigned int irq) +{ + struct exi_channel *exi_channel; + int channel; + int result; + + exi_io_mem = ioremap(mem->start, mem->end - mem->start + 1); + if (!exi_io_mem) { + drv_printk(KERN_ERR, "ioremap failed\n"); + return -ENOMEM; + } + + for (channel = 0; channel < EXI_MAX_CHANNELS; channel++) { + exi_channel = __to_exi_channel(channel); + + /* initialize a channel structure */ + exi_channel_init(exi_channel, channel); + } + + /* calm down the hardware and allow extractions */ + exi_quiesce_all_channels(EXI_CSR_EXTINMASK); + + /* register the exi interrupt handler */ + result = request_irq(irq, exi_irq_handler, 0, module_name, NULL); + if (result) + drv_printk(KERN_ERR, "failed to register IRQ %d\n", irq); + + return result; +} + +/* + * Pseudo-Internal. + */ +void exi_hw_exit(struct resource *mem, unsigned int irq) +{ + exi_quiesce_all_channels(0); + iounmap(exi_io_mem); + free_irq(irq, NULL); +} diff --git a/drivers/exi/exi-hw.h b/drivers/exi/exi-hw.h new file mode 100644 index 0000000..d2bd316 --- /dev/null +++ b/drivers/exi/exi-hw.h @@ -0,0 +1,197 @@ +/* + * drivers/exi/exi-hw.h + * + * Nintendo GameCube EXpansion Interface support. Hardware routines. + * Copyright (C) 2004-2009 The GameCube Linux Team + * Copyright (C) 2004,2005 Todd Jeffreys + * 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 +#include +#include +#include + + +#define EXI_MAX_CHANNELS 3 /* channels on the EXI bus */ +#define EXI_DEVICES_PER_CHANNEL 3 /* number of devices per EXI channel */ +#define EXI_MAX_EVENTS 3 /* types of events on the EXI bus */ + +#define EXI_CLK_1MHZ 0 +#define EXI_CLK_2MHZ 1 +#define EXI_CLK_4MHZ 2 +#define EXI_CLK_8MHZ 3 +#define EXI_CLK_16MHZ 4 +#define EXI_CLK_32MHZ 5 + +#define EXI_MAX_FREQ 7 +#define EXI_FREQ_SCAN EXI_CLK_8MHZ + +#define EXI_READ 0 +#define EXI_WRITE 1 + +#define EXI_IDI_MAX_SIZE 4 + + +#define EXI_DMA_ALIGN 0x1f /* 32 bytes */ + +#define EXI_CHANNEL_SPACING 0x14 + +#define EXI_CSR 0x00 +#define EXI_CSR_EXIINTMASK (1<<0) +#define EXI_CSR_EXIINT (1<<1) +#define EXI_CSR_TCINTMASK (1<<2) +#define EXI_CSR_TCINT (1<<3) +#define EXI_CSR_CLKMASK (0x7<<4) +#define EXI_CSR_CLK_1MHZ (EXI_CLK_1MHZ<<4) +#define EXI_CSR_CLK_2MHZ (EXI_CLK_2MHZ<<4) +#define EXI_CSR_CLK_4MHZ (EXI_CLK_4MHZ<<4) +#define EXI_CSR_CLK_8MHZ (EXI_CLK_8MHZ<<4) +#define EXI_CSR_CLK_16MHZ (EXI_CLK_16MHZ<<4) +#define EXI_CSR_CLK_32MHZ (EXI_CLK_32MHZ<<4) +#define EXI_CSR_CSMASK (0x7<<7) +#define EXI_CSR_CS_0 (0x1<<7) /* Chip Select 001 */ +#define EXI_CSR_CS_1 (0x2<<7) /* Chip Select 010 */ +#define EXI_CSR_CS_2 (0x4<<7) /* Chip Select 100 */ +#define EXI_CSR_EXTINMASK (1<<10) +#define EXI_CSR_EXTIN (1<<11) +#define EXI_CSR_EXT (1<<12) + +#define EXI_MAR 0x04 + +#define EXI_LENGTH 0x08 + +#define EXI_CR 0x0c +#define EXI_CR_TSTART (1<<0) +#define EXI_CR_DMA (1<<1) +#define EXI_CR_READ (0<<2) +#define EXI_CR_WRITE (1<<2) +#define EXI_CR_READ_WRITE (2<<2) +#define EXI_CR_TLEN(len) (((len)-1)<<4) + +#define EXI_DATA 0x10 + +enum { + __EXI_DMABUSY = 0, + __EXI_EXT, +}; + +/* + * For registering event handlers with the exi layer. + */ +struct exi_event { + int id; /* event id */ + struct exi_device *owner; /* device owning of the event */ + exi_event_handler_t handler; + void *data; + unsigned int channel_mask; /* channels used by handler */ +}; + +/* + * This structure represents an exi channel. + */ +struct exi_channel { + spinlock_t lock; /* misc channel lock */ + + int channel; + unsigned long flags; +#define EXI_DMABUSY (1<<__EXI_DMABUSY) +#define EXI_EXT (1<<__EXI_EXT) + + spinlock_t io_lock; /* serializes access to CSR */ + void __iomem *io_base; + + struct exi_device *owner; + wait_queue_head_t wait_queue; + + struct exi_command *queued_cmd; + struct exi_command post_cmd; + + unsigned long csr; + struct tasklet_struct tasklet; + + unsigned long stats_idi_xfers; + unsigned long stats_dma_xfers; + unsigned long stats_xfers; + + struct exi_event events[EXI_MAX_EVENTS]; +}; + +extern struct exi_device *exi_channel_owner(struct exi_channel *exi_channel); +extern int exi_get_ext_line(struct exi_channel *exi_channel); +extern void exi_update_ext_status(struct exi_channel *exi_channel); + +extern void exi_hw_quiesce(void); + +extern int exi_hw_init(char *name, struct resource *mem, unsigned int irq); +extern void exi_hw_exit(struct resource *mem, unsigned int irq); + +#define exi_is_taken(x) ((x)->owner) + +/* + * Internal. + * Declare simple transfer functions for single bytes, words and dwords, + * and build a general transfer function based on that. + */ +#define __declare__exi_transfer_raw(_type, _val, _data, _on_write, _on_read) \ +static inline void __exi_transfer_raw_##_type(struct exi_channel *exi_channel,\ + _type * _data, int mode) \ +{ \ + u32 __iomem *csr_reg = exi_channel->io_base + EXI_CSR; \ + u32 __iomem *data_reg = exi_channel->io_base + EXI_DATA; \ + u32 __iomem *cr_reg = exi_channel->io_base + EXI_CR; \ + u32 _val = ~0; \ + unsigned long flags; \ + \ + /* \ + * On reads we write too some known value to EXIxDATA because \ + * information currently stored there is leaked to the \ + * MOSI line, confusing some hardware. \ + */ \ + if (((mode&0xf) != EXI_OP_READ)) /* write or read-write */ \ + _on_write; \ + out_be32(data_reg, _val); \ + \ + /* start transfer */ \ + _val = EXI_CR_TSTART | EXI_CR_TLEN(sizeof(_type)) | (mode&0xf); \ + out_be32(cr_reg, _val); \ + \ + /* wait for transfer completion */ \ + while (in_be32(cr_reg) & EXI_CR_TSTART) \ + cpu_relax(); \ + \ + /* XXX check if we need that on immediate mode */ \ + /* assert transfer complete interrupt */ \ + spin_lock_irqsave(&exi_channel->io_lock, flags); \ + out_be32(csr_reg, in_be32(csr_reg) | EXI_CSR_TCINT); \ + spin_unlock_irqrestore(&exi_channel->io_lock, flags); \ + \ + if ((mode&0xf) != EXI_OP_WRITE) { /* read or read-write */ \ + _val = in_be32(data_reg); \ + _on_read; \ + } \ +} + +#define __declare__exi_transfer_raw_simple(_type) \ +__declare__exi_transfer_raw( \ + _type, _v, _d, \ + _v = *(_d) << (32 - (8*sizeof(_type))), \ + *(_d) = (_type)(_v >> (32 - (8*sizeof(_type)))) \ + ) + +__declare__exi_transfer_raw_simple(u8) +__declare__exi_transfer_raw_simple(u16) +__declare__exi_transfer_raw_simple(u32) + +extern wait_queue_head_t exi_bus_waitq; + +#endif /* __EXI_HW_H */ diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 50de0f5..9091f6b 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -1248,6 +1248,24 @@ fail: } EXPORT_SYMBOL_GPL(gpio_direction_output); +/** + * gpio_direction_is_output - tell if a gpio is configured as an output + * @gpio: gpio in question + * + * Returns a negative errno if the given gpio is not valid. + * Returns a positive non-zero if the gpio is configured as an output. + * Returns zero otherwise. + */ +int gpio_direction_is_output(unsigned gpio) +{ + struct gpio_desc *desc = &gpio_desc[gpio]; + + if (!gpio_is_valid(gpio)) + return -EINVAL; + + return test_bit(FLAG_IS_OUT, &desc->flags); +} +EXPORT_SYMBOL_GPL(gpio_direction_is_output); /* I/O calls are only valid after configuration completed; the relevant * "is this a valid GPIO" error checks should already have been done. diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 2dd2ce9..6d02df8 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -354,14 +354,26 @@ config I2C_DESIGNWARE This driver can also be built as a module. If so, the module will be called i2c-designware. -config I2C_GPIO - tristate "GPIO-based bitbanging I2C" +config I2C_GPIO_COMMON + tristate depends on GENERIC_GPIO select I2C_ALGOBIT + default n + +config I2C_GPIO + tristate "GPIO-based bitbanging I2C" + select I2C_GPIO_COMMON help This is a very simple bitbanging I2C driver utilizing the arch-neutral GPIO API to control the SCL and SDA lines. +config I2C_GPIO_OF + tristate "GPIO-based bitbanging I2C driver with OF bindings" + select I2C_GPIO_COMMON + help + This option allows the declaration of GPIO-based I2C devices + using a device tree source. + config I2C_HIGHLANDER tristate "Highlander FPGA SMBus interface" depends on SH_HIGHLANDER diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index ff937ac..1e68c5b 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -34,7 +34,9 @@ obj-$(CONFIG_I2C_BLACKFIN_TWI) += i2c-bfin-twi.o obj-$(CONFIG_I2C_CPM) += i2c-cpm.o obj-$(CONFIG_I2C_DAVINCI) += i2c-davinci.o obj-$(CONFIG_I2C_DESIGNWARE) += i2c-designware.o +obj-$(CONFIG_I2C_GPIO_COMMON) += i2c-gpio-common.o obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o +obj-$(CONFIG_I2C_GPIO_OF) += i2c-gpio-of.o obj-$(CONFIG_I2C_HIGHLANDER) += i2c-highlander.o obj-$(CONFIG_I2C_IBM_IIC) += i2c-ibm_iic.o obj-$(CONFIG_I2C_IMX) += i2c-imx.o diff --git a/drivers/i2c/busses/i2c-gpio-common.c b/drivers/i2c/busses/i2c-gpio-common.c new file mode 100644 index 0000000..641f5b5 --- /dev/null +++ b/drivers/i2c/busses/i2c-gpio-common.c @@ -0,0 +1,213 @@ +/* + * Core logic for the bitbanging I2C bus driver using the GPIO API + * + * Copyright (C) 2007 Atmel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "i2c-gpio-common.h" + +#include +#include + +/* Toggle SDA by changing the direction of the pin */ +static void i2c_gpio_setsda_dir(void *data, int state) +{ + struct i2c_gpio_platform_data *pdata = data; + + if (state) + gpio_direction_input(pdata->sda_pin); + else + gpio_direction_output(pdata->sda_pin, 0); +} + +/* + * Toggle SDA by changing the output value of the pin. This is only + * valid for pins configured as open drain (i.e. setting the value + * high effectively turns off the output driver.) + */ +static void i2c_gpio_setsda_val(void *data, int state) +{ + struct i2c_gpio_platform_data *pdata = data; + + gpio_set_value(pdata->sda_pin, state); +} + +/* + * Toggle SDA by changing the output value of the pin, first making sure + * that the pin is configured as an output. + */ +static void i2c_gpio_setsda_val_dir(void *data, int state) +{ + struct i2c_gpio_platform_data *pdata = data; + + if (!gpio_direction_is_output(pdata->sda_pin)) + gpio_direction_output(pdata->sda_pin, state); + else + gpio_set_value(pdata->sda_pin, state); +} + +/* Toggle SCL by changing the direction of the pin. */ +static void i2c_gpio_setscl_dir(void *data, int state) +{ + struct i2c_gpio_platform_data *pdata = data; + + if (state) + gpio_direction_input(pdata->scl_pin); + else + gpio_direction_output(pdata->scl_pin, 0); +} + +/* + * Toggle SCL by changing the output value of the pin. This is used + * for pins that are configured as open drain and for output-only + * pins. The latter case will break the i2c protocol, but it will + * often work in practice. + */ +static void i2c_gpio_setscl_val(void *data, int state) +{ + struct i2c_gpio_platform_data *pdata = data; + + gpio_set_value(pdata->scl_pin, state); +} + +static int i2c_gpio_getsda(void *data) +{ + struct i2c_gpio_platform_data *pdata = data; + + return gpio_get_value(pdata->sda_pin); +} + +/* + * Read SDA value from the pin, first making sure that the pin is + * configured as an input. + */ +static int i2c_gpio_getsda_val_dir(void *data) +{ + struct i2c_gpio_platform_data *pdata = data; + + if (gpio_direction_is_output(pdata->sda_pin)) + gpio_direction_input(pdata->sda_pin); + return gpio_get_value(pdata->sda_pin); +} + +static int i2c_gpio_getscl(void *data) +{ + struct i2c_gpio_platform_data *pdata = data; + + return gpio_get_value(pdata->scl_pin); +} + +int i2c_gpio_adapter_probe(struct i2c_adapter *adap, + struct i2c_gpio_platform_data *pdata, + struct device *parent, int id, + struct module *owner) +{ + struct i2c_algo_bit_data *bit_data; + int error; + + error = -ENOMEM; + bit_data = kzalloc(sizeof(*bit_data), GFP_KERNEL); + if (!bit_data) + goto err_alloc_bit_data; + + error = gpio_request(pdata->sda_pin, "sda"); + if (error) + goto err_request_sda; + error = gpio_request(pdata->scl_pin, "scl"); + if (error) + goto err_request_scl; + + if (pdata->sda_is_open_drain) { + gpio_direction_output(pdata->sda_pin, 1); + if (pdata->sda_enforce_dir) + bit_data->setsda = i2c_gpio_setsda_val_dir; + else + bit_data->setsda = i2c_gpio_setsda_val; + } else { + gpio_direction_input(pdata->sda_pin); + bit_data->setsda = i2c_gpio_setsda_dir; + } + + if (pdata->scl_is_open_drain || pdata->scl_is_output_only) { + gpio_direction_output(pdata->scl_pin, 1); + bit_data->setscl = i2c_gpio_setscl_val; + } else { + gpio_direction_input(pdata->scl_pin); + bit_data->setscl = i2c_gpio_setscl_dir; + } + + if (!pdata->scl_is_output_only) + bit_data->getscl = i2c_gpio_getscl; + if (pdata->sda_enforce_dir) + bit_data->getsda = i2c_gpio_getsda_val_dir; + else + bit_data->getsda = i2c_gpio_getsda; + + if (pdata->udelay) + bit_data->udelay = pdata->udelay; + else if (pdata->scl_is_output_only) + bit_data->udelay = 50; /* 10 kHz */ + else + bit_data->udelay = 5; /* 100 kHz */ + + if (pdata->timeout) + bit_data->timeout = pdata->timeout; + else + bit_data->timeout = HZ / 10; /* 100 ms */ + + bit_data->data = pdata; + + adap->owner = owner; + snprintf(adap->name, sizeof(adap->name), "i2c-gpio%d", id); + adap->algo_data = bit_data; + adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD; + adap->dev.parent = parent; + + /* + * If "id" is negative we consider it as zero. + * The reason to do so is to avoid sysfs names that only make + * sense when there are multiple adapters. + */ + adap->nr = (id != -1) ? id : 0; + error = i2c_bit_add_numbered_bus(adap); + if (error) + goto err_add_bus; + + dev_info(parent, "using pins %u (SDA) and %u (SCL%s)\n", + pdata->sda_pin, pdata->scl_pin, + pdata->scl_is_output_only + ? ", no clock stretching" : ""); + + return 0; + +err_add_bus: + gpio_free(pdata->scl_pin); +err_request_scl: + gpio_free(pdata->sda_pin); +err_request_sda: + kfree(bit_data); +err_alloc_bit_data: + return error; +} +EXPORT_SYMBOL(i2c_gpio_adapter_probe); + +int i2c_gpio_adapter_remove(struct i2c_adapter *adap, + struct i2c_gpio_platform_data *pdata) +{ + i2c_del_adapter(adap); + gpio_free(pdata->scl_pin); + gpio_free(pdata->sda_pin); + kfree(adap->algo_data); + kfree(adap); + + return 0; +} +EXPORT_SYMBOL(i2c_gpio_adapter_remove); + +MODULE_AUTHOR("Haavard Skinnemoen "); +MODULE_DESCRIPTION("Platform-independent bitbanging I2C driver common logic"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/i2c-gpio-common.h b/drivers/i2c/busses/i2c-gpio-common.h new file mode 100644 index 0000000..888a3e8 --- /dev/null +++ b/drivers/i2c/busses/i2c-gpio-common.h @@ -0,0 +1,25 @@ +/* + * Bitbanging I2C bus driver using the GPIO API + * + * Copyright (C) 2007 Atmel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef __I2C_GPIO_COMMON_H +#define __I2C_GPIO_COMMON_H + +#include +#include +#include +#include + +int i2c_gpio_adapter_probe(struct i2c_adapter *adap, + struct i2c_gpio_platform_data *pdata, + struct device *parent, int id, + struct module *owner); +int i2c_gpio_adapter_remove(struct i2c_adapter *adap, + struct i2c_gpio_platform_data *pdata); + +#endif /* __I2C_GPIO_COMMON_H */ diff --git a/drivers/i2c/busses/i2c-gpio-of.c b/drivers/i2c/busses/i2c-gpio-of.c new file mode 100644 index 0000000..4813f07 --- /dev/null +++ b/drivers/i2c/busses/i2c-gpio-of.c @@ -0,0 +1,153 @@ +/* + * drivers/i2c/busses/i2c-gpio-of.c + * + * GPIO-based bitbanging I2C driver with OF bindings + * Copyright (C) 2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "i2c-gpio-common.h" + +#include +#include +#include +#include +#include +#include + +#define DRV_MODULE_NAME "i2c-gpio-of" +#define DRV_DESCRIPTION "GPIO-based bitbanging I2C driver with OF bindings" +#define DRV_AUTHOR "Albert Herranz" + +#define drv_printk(level, format, arg...) \ + printk(level DRV_MODULE_NAME ": " format , ## arg) + +/* + * OF platform bindings. + * + */ + +static int __devinit i2c_gpio_of_probe(struct of_device *odev, + const struct of_device_id *match) +{ + struct i2c_gpio_platform_data *pdata; + struct i2c_adapter *adap; + const unsigned long *prop; + int sda_pin, scl_pin; + int error = -ENOMEM; + + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) + goto err_alloc_pdata; + adap = kzalloc(sizeof(*adap), GFP_KERNEL); + if (!adap) + goto err_alloc_adap; + + sda_pin = of_get_gpio(odev->node, 0); + scl_pin = of_get_gpio(odev->node, 1); + if (sda_pin < 0 || scl_pin < 0) { + error = -EINVAL; + pr_err("%s: invalid GPIO pins, sda=%d/scl=%d\n", + odev->node->full_name, sda_pin, scl_pin); + goto err_gpio_pin; + } + + pdata->sda_pin = sda_pin; + pdata->scl_pin = scl_pin; + + prop = of_get_property(odev->node, "sda-is-open-drain", NULL); + if (prop) + pdata->sda_is_open_drain = *prop; + prop = of_get_property(odev->node, "sda-enforce-dir", NULL); + if (prop) + pdata->sda_enforce_dir = *prop; + prop = of_get_property(odev->node, "scl-is-open-drain", NULL); + if (prop) + pdata->scl_is_open_drain = *prop; + prop = of_get_property(odev->node, "scl-is-output-only", NULL); + if (prop) + pdata->scl_is_output_only = *prop; + prop = of_get_property(odev->node, "udelay", NULL); + if (prop) + pdata->udelay = *prop; + prop = of_get_property(odev->node, "timeout", NULL); + if (prop) + pdata->timeout = msecs_to_jiffies(*prop); + + error = i2c_gpio_adapter_probe(adap, pdata, &odev->dev, + odev->node->node, THIS_MODULE); + if (error) + goto err_probe; + + dev_set_drvdata(&odev->dev, adap); + + of_register_i2c_devices(adap, odev->node); + + return 0; + +err_probe: +err_gpio_pin: + kfree(adap); +err_alloc_adap: + kfree(pdata); +err_alloc_pdata: + return error; +}; + +static int __devexit i2c_gpio_of_remove(struct of_device *odev) +{ + struct i2c_gpio_platform_data *pdata; + struct i2c_adapter *adap; + struct i2c_algo_bit_data *bit_data; + + adap = dev_get_drvdata(&odev->dev); + bit_data = adap->algo_data; + pdata = bit_data->data; + + i2c_gpio_adapter_remove(adap, pdata); + kfree(pdata); + kfree(adap); + return 0; +}; + +static const struct of_device_id i2c_gpio_of_match[] = { + {.compatible = "virtual,i2c-gpio",}, + {}, +}; +MODULE_DEVICE_TABLE(of, i2c_gpio_of_match); + +static struct of_platform_driver i2c_gpio_of_driver = { + .match_table = i2c_gpio_of_match, + .probe = i2c_gpio_of_probe, + .remove = __devexit_p(i2c_gpio_of_remove), + .driver = { + .owner = THIS_MODULE, + .name = DRV_MODULE_NAME, + }, +}; + +static int __init i2c_gpio_of_init(void) +{ + int error; + + error = of_register_platform_driver(&i2c_gpio_of_driver); + if (error) + drv_printk(KERN_ERR, "OF registration failed (%d)\n", error); + + return error; +} + +static void __exit i2c_gpio_of_exit(void) +{ + of_unregister_platform_driver(&i2c_gpio_of_driver); +} + +module_init(i2c_gpio_of_init); +module_exit(i2c_gpio_of_exit); + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_AUTHOR(DRV_AUTHOR); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/i2c-gpio.c b/drivers/i2c/busses/i2c-gpio.c index 32104ea..4cc3697 100644 --- a/drivers/i2c/busses/i2c-gpio.c +++ b/drivers/i2c/busses/i2c-gpio.c @@ -7,187 +7,56 @@ * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ -#include -#include -#include -#include -#include -#include -#include +#include "i2c-gpio-common.h" -/* Toggle SDA by changing the direction of the pin */ -static void i2c_gpio_setsda_dir(void *data, int state) -{ - struct i2c_gpio_platform_data *pdata = data; - - if (state) - gpio_direction_input(pdata->sda_pin); - else - gpio_direction_output(pdata->sda_pin, 0); -} - -/* - * Toggle SDA by changing the output value of the pin. This is only - * valid for pins configured as open drain (i.e. setting the value - * high effectively turns off the output driver.) - */ -static void i2c_gpio_setsda_val(void *data, int state) -{ - struct i2c_gpio_platform_data *pdata = data; - - gpio_set_value(pdata->sda_pin, state); -} - -/* Toggle SCL by changing the direction of the pin. */ -static void i2c_gpio_setscl_dir(void *data, int state) -{ - struct i2c_gpio_platform_data *pdata = data; - - if (state) - gpio_direction_input(pdata->scl_pin); - else - gpio_direction_output(pdata->scl_pin, 0); -} +#include +#include /* - * Toggle SCL by changing the output value of the pin. This is used - * for pins that are configured as open drain and for output-only - * pins. The latter case will break the i2c protocol, but it will - * often work in practice. + * Platform bindings. + * */ -static void i2c_gpio_setscl_val(void *data, int state) -{ - struct i2c_gpio_platform_data *pdata = data; - - gpio_set_value(pdata->scl_pin, state); -} - -static int i2c_gpio_getsda(void *data) -{ - struct i2c_gpio_platform_data *pdata = data; - - return gpio_get_value(pdata->sda_pin); -} - -static int i2c_gpio_getscl(void *data) -{ - struct i2c_gpio_platform_data *pdata = data; - - return gpio_get_value(pdata->scl_pin); -} static int __devinit i2c_gpio_probe(struct platform_device *pdev) { struct i2c_gpio_platform_data *pdata; - struct i2c_algo_bit_data *bit_data; struct i2c_adapter *adap; - int ret; + int error; pdata = pdev->dev.platform_data; if (!pdata) return -ENXIO; - ret = -ENOMEM; - adap = kzalloc(sizeof(struct i2c_adapter), GFP_KERNEL); + error = -ENOMEM; + adap = kzalloc(sizeof(*adap), GFP_KERNEL); if (!adap) goto err_alloc_adap; - bit_data = kzalloc(sizeof(struct i2c_algo_bit_data), GFP_KERNEL); - if (!bit_data) - goto err_alloc_bit_data; - - ret = gpio_request(pdata->sda_pin, "sda"); - if (ret) - goto err_request_sda; - ret = gpio_request(pdata->scl_pin, "scl"); - if (ret) - goto err_request_scl; - - if (pdata->sda_is_open_drain) { - gpio_direction_output(pdata->sda_pin, 1); - bit_data->setsda = i2c_gpio_setsda_val; - } else { - gpio_direction_input(pdata->sda_pin); - bit_data->setsda = i2c_gpio_setsda_dir; - } - - if (pdata->scl_is_open_drain || pdata->scl_is_output_only) { - gpio_direction_output(pdata->scl_pin, 1); - bit_data->setscl = i2c_gpio_setscl_val; - } else { - gpio_direction_input(pdata->scl_pin); - bit_data->setscl = i2c_gpio_setscl_dir; - } - - if (!pdata->scl_is_output_only) - bit_data->getscl = i2c_gpio_getscl; - bit_data->getsda = i2c_gpio_getsda; - - if (pdata->udelay) - bit_data->udelay = pdata->udelay; - else if (pdata->scl_is_output_only) - bit_data->udelay = 50; /* 10 kHz */ - else - bit_data->udelay = 5; /* 100 kHz */ - - if (pdata->timeout) - bit_data->timeout = pdata->timeout; - else - bit_data->timeout = HZ / 10; /* 100 ms */ - - bit_data->data = pdata; - - adap->owner = THIS_MODULE; - snprintf(adap->name, sizeof(adap->name), "i2c-gpio%d", pdev->id); - adap->algo_data = bit_data; - adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD; - adap->dev.parent = &pdev->dev; - - /* - * If "dev->id" is negative we consider it as zero. - * The reason to do so is to avoid sysfs names that only make - * sense when there are multiple adapters. - */ - adap->nr = (pdev->id != -1) ? pdev->id : 0; - ret = i2c_bit_add_numbered_bus(adap); - if (ret) - goto err_add_bus; + error = i2c_gpio_adapter_probe(adap, pdata, &pdev->dev, pdev->id, + THIS_MODULE); + if (error) + goto err_probe; platform_set_drvdata(pdev, adap); - dev_info(&pdev->dev, "using pins %u (SDA) and %u (SCL%s)\n", - pdata->sda_pin, pdata->scl_pin, - pdata->scl_is_output_only - ? ", no clock stretching" : ""); - return 0; -err_add_bus: - gpio_free(pdata->scl_pin); -err_request_scl: - gpio_free(pdata->sda_pin); -err_request_sda: - kfree(bit_data); -err_alloc_bit_data: +err_probe: kfree(adap); err_alloc_adap: - return ret; + return error; } static int __devexit i2c_gpio_remove(struct platform_device *pdev) { - struct i2c_gpio_platform_data *pdata; struct i2c_adapter *adap; + struct i2c_gpio_platform_data *pdata; adap = platform_get_drvdata(pdev); pdata = pdev->dev.platform_data; - i2c_del_adapter(adap); - gpio_free(pdata->scl_pin); - gpio_free(pdata->sda_pin); - kfree(adap->algo_data); + i2c_gpio_adapter_remove(adap, pdata); kfree(adap); - return 0; } @@ -202,13 +71,13 @@ static struct platform_driver i2c_gpio_driver = { static int __init i2c_gpio_init(void) { - int ret; + int error; - ret = platform_driver_register(&i2c_gpio_driver); - if (ret) - printk(KERN_ERR "i2c-gpio: probe failed: %d\n", ret); + error = platform_driver_register(&i2c_gpio_driver); + if (error) + printk(KERN_ERR "i2c-gpio: registration failed (%d)\n", error); - return ret; + return error; } module_init(i2c_gpio_init); diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig index cd50c00..03628fb 100644 --- a/drivers/input/Kconfig +++ b/drivers/input/Kconfig @@ -180,6 +180,8 @@ source "drivers/input/serio/Kconfig" source "drivers/input/gameport/Kconfig" +source "drivers/input/si/Kconfig" + endmenu endmenu diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index ee98b1b..5e8718e 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -351,6 +351,17 @@ config KEYBOARD_SUNKBD To compile this driver as a module, choose M here: the module will be called sunkbd. +config KEYBOARD_WII + tristate "Nintendo Wii USB keyboard IOS glue" + depends on (STARLET_IOS && !USB) + help + Say Y here if you have a Nintendo Wii console running Linux and have + a keyboard attached to one of its USB ports. + This driver uses the IOS interface glue to access the USB keyboard. + + To compile this driver as a module, choose M here: the + module will be called rvl-stkbd. + config KEYBOARD_SH_KEYSC tristate "SuperH KEYSC keypad support" depends on SUPERH diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index babad5e..3b91336 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -37,3 +37,4 @@ obj-$(CONFIG_KEYBOARD_TOSA) += tosakbd.o obj-$(CONFIG_KEYBOARD_TWL4030) += twl4030_keypad.o obj-$(CONFIG_KEYBOARD_XTKBD) += xtkbd.o obj-$(CONFIG_KEYBOARD_W90P910) += w90p910_keypad.o +obj-$(CONFIG_KEYBOARD_WII) += rvl-stkbd.o diff --git a/drivers/input/keyboard/rvl-stkbd.c b/drivers/input/keyboard/rvl-stkbd.c new file mode 100644 index 0000000..c6a259a --- /dev/null +++ b/drivers/input/keyboard/rvl-stkbd.c @@ -0,0 +1,512 @@ +/* + * drivers/input/keyboard/rvl-stkbd.c + * + * Nintendo Wii starlet keyboard driver. + * Copyright (C) 2008-2009 The GameCube Linux Team + * Copyright (C) 2008,2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +/* + * NOTES: + * The keyboard driver requires at least IOS30 installed. + * LED support is pending. + */ + +#define DEBUG + +#define DBG(fmt, arg...) pr_debug(fmt, ##arg) + +#include +#include +#include +#include +#include +#include +#include + +#define DRV_MODULE_NAME "rvl-stkbd" +#define DRV_DESCRIPTION "Nintendo Wii starlet keyboard driver" +#define DRV_AUTHOR "Albert Herranz" + +static char stkbd_driver_version[] = "0.2i"; + +#define drv_printk(level, format, arg...) \ + printk(level DRV_MODULE_NAME ": " format , ## arg) + +/* + * Keyboard events from IOS. + */ +#define STKBD_EV_CONNECT 0x00000000 +#define STKBD_EV_DISCONNECT 0x00000001 +#define STKBD_EV_REPORT 0x00000002 + +struct stkbd_event { + u32 type; + u32 _unk1; + union { + struct { + u8 modifiers; + u8 reserved; + u8 keys[6]; + } report; + u8 raw_report[8]; + }; +} __attribute__((packed)); + +/* + * Keyboard device. + */ + +enum { + __STKBD_RUNNING = 1, /* device is opened and running */ + __STKBD_WAITING_REPORT /* waiting for IOS report */ +}; + +struct stkbd_keyboard { + int fd; + + struct stkbd_event *event; + u8 old_raw_report[8]; + + unsigned long flags; +#define STKBD_RUNNING (1 << __STKBD_RUNNING) +#define STKBD_WAITING_REPORT (1 << __STKBD_WAITING_REPORT) + + char name[32]; + struct input_dev *idev; + unsigned int usage; + + struct device *dev; +}; + +/* device path in IOS for the USB keyboard */ +static char stkbd_dev_path[] = "/dev/usb/kbd"; + +/* + * Keycodes are standard USB keyboard HID keycodes. + * We use the same table as in drivers/hid/usbhid/usbkbd.c. + */ + +#define NR_SCANCODES 256 + +static unsigned char stkbd_keycode[NR_SCANCODES] = { +/*000*/ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/*004*/ KEY_A, KEY_B, KEY_C, KEY_D, +/*008*/ KEY_E, KEY_F, KEY_G, KEY_H, +/*012*/ KEY_I, KEY_J, KEY_K, KEY_L, +/*016*/ KEY_M, KEY_N, KEY_O, KEY_P, +/*020*/ KEY_Q, KEY_R, KEY_S, KEY_T, +/*024*/ KEY_U, KEY_V, KEY_W, KEY_X, +/*028*/ KEY_Y, KEY_Z, KEY_1, KEY_2, +/*032*/ KEY_3, KEY_4, KEY_5, KEY_6, +/*036*/ KEY_7, KEY_8, KEY_9, KEY_0, +/*040*/ KEY_ENTER, KEY_ESC, KEY_BACKSPACE, KEY_TAB, +/*044*/ KEY_SPACE, KEY_MINUS, KEY_EQUAL, KEY_LEFTBRACE, +/*048*/ KEY_RIGHTBRACE, KEY_BACKSLASH, KEY_BACKSLASH, KEY_SEMICOLON, +/*052*/ KEY_APOSTROPHE, KEY_GRAVE, KEY_COMMA, KEY_DOT, +/*056*/ KEY_SLASH, KEY_CAPSLOCK, KEY_F1, KEY_F2, +/*060*/ KEY_F3, KEY_F4, KEY_F5, KEY_F6, +/*064*/ KEY_F7, KEY_F8, KEY_F9, KEY_F10, +/*068*/ KEY_F11, KEY_F12, KEY_SYSRQ, KEY_SCROLLLOCK, +/*072*/ KEY_PAUSE, KEY_INSERT, KEY_HOME, KEY_PAGEUP, +/*076*/ KEY_DELETE, KEY_END, KEY_PAGEDOWN, KEY_RIGHT, +/*080*/ KEY_LEFT, KEY_DOWN, KEY_UP, KEY_NUMLOCK, +/*084*/ KEY_KPSLASH, KEY_KPASTERISK, KEY_KPMINUS, KEY_KPPLUS, +/*088*/ KEY_KPENTER, KEY_KP1, KEY_KP2, KEY_KP3, +/*092*/ KEY_KP4, KEY_KP5, KEY_KP6, KEY_KP7, +/*096*/ KEY_KP8, KEY_KP9, KEY_KP0, KEY_KPDOT, +/*100*/ KEY_102ND, KEY_COMPOSE, KEY_POWER, KEY_KPEQUAL, +/*104*/ KEY_F13, KEY_F14, KEY_F15, KEY_F16, +/*108*/ KEY_F17, KEY_F18, KEY_F19, KEY_F20, +/*112*/ KEY_F21, KEY_F22, KEY_F23, KEY_F24, +/*116*/ KEY_OPEN, KEY_HELP, KEY_PROPS, KEY_FRONT, +/*120*/ KEY_STOP, KEY_AGAIN, KEY_UNDO, KEY_CUT, +/*124*/ KEY_COPY, KEY_PASTE, KEY_FIND, KEY_MUTE, +/*128*/ KEY_VOLUMEUP, KEY_VOLUMEDOWN, KEY_RESERVED, KEY_RESERVED, +/*132*/ KEY_RESERVED, KEY_KPCOMMA, KEY_RESERVED, KEY_RO, +/*136*/ KEY_KATAKANAHIRAGANA, KEY_YEN, KEY_HENKAN, KEY_MUHENKAN, +/*140*/ KEY_KPJPCOMMA, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/*144*/ KEY_HANGEUL, KEY_HANJA, KEY_KATAKANA, KEY_HIRAGANA, +/*148*/ KEY_ZENKAKUHANKAKU, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/*152*/ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/*156*/ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/*160*/ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/*164*/ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/*168*/ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/*172*/ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/*176*/ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/*180*/ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/*184*/ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/*188*/ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/*192*/ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/*196*/ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/*200*/ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/*204*/ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/*208*/ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/*212*/ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/*216*/ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/*220*/ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +/*224*/ KEY_LEFTCTRL, KEY_LEFTSHIFT, KEY_LEFTALT, KEY_LEFTMETA, +/*228*/ KEY_RIGHTCTRL, KEY_RIGHTSHIFT, KEY_RIGHTALT, KEY_RIGHTMETA, +/*232*/ KEY_PLAYPAUSE, KEY_STOPCD, KEY_PREVIOUSSONG, KEY_NEXTSONG, +/*236*/ KEY_EJECTCD, KEY_VOLUMEUP, KEY_VOLUMEDOWN, KEY_MUTE, +/*240*/ KEY_WWW, KEY_BACK, KEY_FORWARD, KEY_STOP, +/*244*/ KEY_FIND, KEY_SCROLLUP, KEY_SCROLLDOWN, KEY_EDIT, +/*248*/ KEY_SLEEP, KEY_SCROLLLOCK, KEY_REFRESH, KEY_CALC, +/*252*/ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +}; + + +static int stkbd_wait_for_events(struct stkbd_keyboard *kbd); + +static void stkbd_dispatch_report(struct stkbd_keyboard *kbd) +{ + struct stkbd_event *event = kbd->event; + u8 *new = event->raw_report; + u8 *old = kbd->old_raw_report; + int i; + + /* + * The report is a standard USB HID report for a keyboard. + * Modifiers come in byte 0 as variable data and map to + * positions 224-231 of the USB HID keyboard/keypad page. + */ + for (i = 0; i < 8; i++) + input_report_key(kbd->idev, stkbd_keycode[i + 224], + (new[0] >> i) & 1); + + /* + * Byte 1 is reserved and ignored here. + * Bytes 2 to 7 contain the indexes of up to 6 keys pressed. + * A value of 01 for an index means "Keyboard ErrorRollOver" and is + * reported when the keyboard is in error (for example, when too + * many keys are simultaneously pressed). + * Index values less than or equal to 03 can be safely ignored. + */ + for (i = 2; i < 8; i++) { + /* released keys are old indexes not found in new array */ + if (old[i] > 3 && memscan(new + 2, old[i], 6) == new + 8) { + if (stkbd_keycode[old[i]]) + input_report_key(kbd->idev, + stkbd_keycode[old[i]], 0); + else + drv_printk(KERN_WARNING, "unknown key" + " (scancode %#x) released.", old[i]); + } + + /* pressed keys are new indexes not found in old array */ + if (new[i] > 3 && memscan(old + 2, new[i], 6) == old + 8) { + if (stkbd_keycode[new[i]]) + input_report_key(kbd->idev, + stkbd_keycode[new[i]], 1); + else + drv_printk(KERN_WARNING, "unknown key" + " (scancode %#x) pressed.", new[i]); + } + } + + input_sync(kbd->idev); + memcpy(old, new, 8); +} + +static int stkbd_dispatch_ipc_request(struct starlet_ipc_request *req) +{ + struct stkbd_keyboard *kbd; + struct stkbd_event *event; + int error; + + /* retrieve the interesting data before freeing the request */ + kbd = req->done_data; + error = req->result; + starlet_ipc_free_request(req); + + clear_bit(__STKBD_WAITING_REPORT, &kbd->flags); + + if (kbd->fd == -1) + return -ENODEV; + + if (error) { + DBG("%s: error=%d (%x)\n", __func__, error, error); + } else { + event = kbd->event; + + switch (event->type) { + case STKBD_EV_CONNECT: + drv_printk(KERN_INFO, "keyboard connected\n"); + break; + case STKBD_EV_DISCONNECT: + drv_printk(KERN_INFO, "keyboard disconnected\n"); + break; + case STKBD_EV_REPORT: + if (test_bit(__STKBD_RUNNING, &kbd->flags)) + stkbd_dispatch_report(kbd); + break; + } + + error = stkbd_wait_for_events(kbd); + } + return error; +} + +static int stkbd_wait_for_events(struct stkbd_keyboard *kbd) +{ + struct stkbd_event *event = kbd->event; + int error = 0; + + if (!test_and_set_bit(__STKBD_WAITING_REPORT, &kbd->flags)) { + error = starlet_ioctl_nowait(kbd->fd, 0, + NULL, 0, + event, sizeof(*event), + stkbd_dispatch_ipc_request, + kbd); + if (error) + drv_printk(KERN_ERR, "ioctl error %d (%04x)\n", + error, error); + } + return error; +} + +/* + * Input driver hooks. + * + */ + +static DEFINE_MUTEX(open_lock); + +static int stkbd_open(struct input_dev *idev) +{ + struct stkbd_keyboard *kbd = input_get_drvdata(idev); + int error = 0; + + if (!kbd) + return -ENODEV; + + mutex_lock(&open_lock); + kbd->usage++; + set_bit(__STKBD_RUNNING, &kbd->flags); + mutex_unlock(&open_lock); + + return error; +} + +static void stkbd_close(struct input_dev *idev) +{ + struct stkbd_keyboard *kbd = input_get_drvdata(idev); + + if (!kbd) + return; + + mutex_lock(&open_lock); + kbd->usage--; + if (kbd->usage == 0) + clear_bit(__STKBD_RUNNING, &kbd->flags); + mutex_unlock(&open_lock); +} + +static void stkbd_setup_keyboard(struct input_dev *idev) +{ + int i; + + set_bit(EV_KEY, idev->evbit); + set_bit(EV_REP, idev->evbit); + + for (i = 0; i < 255; ++i) + set_bit(stkbd_keycode[i], idev->keybit); + clear_bit(0, idev->keybit); +} + +static int stkbd_init_input_dev(struct stkbd_keyboard *kbd) +{ + struct input_dev *idev; + int error; + + idev = input_allocate_device(); + if (!idev) { + drv_printk(KERN_ERR, "failed to allocate input_dev\n"); + return -ENOMEM; + } + + idev->dev.parent = kbd->dev; + + strcpy(kbd->name, "USB keyboard"); + idev->name = kbd->name; + + input_set_drvdata(idev, kbd); + kbd->idev = idev; + + stkbd_setup_keyboard(idev); + + idev->open = stkbd_open; + idev->close = stkbd_close; + + error = input_register_device(kbd->idev); + if (error) { + input_free_device(kbd->idev); + return error; + } + + return 0; +} + +static void stkbd_exit_input_dev(struct stkbd_keyboard *kbd) +{ + input_free_device(kbd->idev); +} + +/* + * Setup routines. + * + */ + +static int stkbd_init(struct stkbd_keyboard *kbd) +{ + struct stkbd_event *event; + int error; + + event = starlet_kzalloc(sizeof(*event), GFP_KERNEL); + if (!event) { + drv_printk(KERN_ERR, "failed to allocate stkbd_event\n"); + error = -ENOMEM; + goto err; + } + kbd->event = event; + + kbd->fd = starlet_open(stkbd_dev_path, 0); + if (kbd->fd < 0) { + drv_printk(KERN_ERR, "unable to open device %s\n", + stkbd_dev_path); + error = kbd->fd; + goto err_event; + } + + error = stkbd_init_input_dev(kbd); + if (error) + goto err_fd; + + /* start to grab events from the keyboard */ + error = stkbd_wait_for_events(kbd); + if (error) + goto err_input_dev; + + return 0; + +err_input_dev: + stkbd_exit_input_dev(kbd); +err_fd: + starlet_close(kbd->fd); +err_event: + starlet_kfree(event); +err: + return error; +} + +static void stkbd_exit(struct stkbd_keyboard *kbd) +{ + stkbd_exit_input_dev(kbd); + starlet_close(kbd->fd); + starlet_kfree(kbd->event); +} + +/* + * Driver model helper routines. + * + */ + +static int stkbd_do_probe(struct device *dev) +{ + struct stkbd_keyboard *kbd; + int error; + + kbd = kzalloc(sizeof(*kbd), GFP_KERNEL); + if (!kbd) { + drv_printk(KERN_ERR, "failed to allocate stkbd_keyboard\n"); + return -ENOMEM; + } + dev_set_drvdata(dev, kbd); + kbd->dev = dev; + + error = stkbd_init(kbd); + if (error) { + dev_set_drvdata(dev, NULL); + kfree(kbd); + } + return error; +} + +static int stkbd_do_remove(struct device *dev) +{ + struct stkbd_keyboard *kbd = dev_get_drvdata(dev); + + if (kbd) { + stkbd_exit(kbd); + dev_set_drvdata(dev, NULL); + kfree(kbd); + return 0; + } + return -ENODEV; +} + + +/* + * OF platform driver hooks. + * + */ + +static int __init stkbd_of_probe(struct of_device *odev, + const struct of_device_id *match) +{ + return stkbd_do_probe(&odev->dev); +} + +static int __exit stkbd_of_remove(struct of_device *odev) +{ + return stkbd_do_remove(&odev->dev); +} + +static struct of_device_id stkbd_of_match[] = { + { .compatible = "nintendo,starlet-ios-keyboard" }, + { }, +}; + + +MODULE_DEVICE_TABLE(of, stkbd_of_match); + +static struct of_platform_driver stkbd_of_driver = { + .owner = THIS_MODULE, + .name = DRV_MODULE_NAME, + .match_table = stkbd_of_match, + .probe = stkbd_of_probe, + .remove = stkbd_of_remove, +}; + + +/* + * Module interface hooks. + * + */ + +static int __init stkbd_init_module(void) +{ + drv_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION, + stkbd_driver_version); + + return of_register_platform_driver(&stkbd_of_driver); +} + +static void __exit stkbd_exit_module(void) +{ + of_unregister_platform_driver(&stkbd_of_driver); +} + +module_init(stkbd_init_module); +module_exit(stkbd_exit_module); + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_AUTHOR(DRV_AUTHOR); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/si/Kconfig b/drivers/input/si/Kconfig new file mode 100644 index 0000000..51de017 --- /dev/null +++ b/drivers/input/si/Kconfig @@ -0,0 +1,14 @@ +# +# Nintendo GameCube/Wii Serial Interface (SI) configuration +# + +config GAMECUBE_SI + tristate "Nintendo GameCube/Wii Serial Interface (SI) support" + depends on GAMECUBE_COMMON + ---help--- + Say Y here if you want to use the standard pads as joysticks or + want to use a keyboard. Everything is autodetected. + + NOTE: Keyboard detection doesn't work 100%. Building this as a + module is recommended, this way you can unload/load the driver + to re-detect. diff --git a/drivers/input/si/Makefile b/drivers/input/si/Makefile new file mode 100644 index 0000000..5087995 --- /dev/null +++ b/drivers/input/si/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for the Nintendo GameCube/Wii Serial Interface (SI). +# + +obj-$(CONFIG_GAMECUBE_SI) += gcn-si.o diff --git a/drivers/input/si/gcn-keymap.h b/drivers/input/si/gcn-keymap.h new file mode 100644 index 0000000..7a3345a --- /dev/null +++ b/drivers/input/si/gcn-keymap.h @@ -0,0 +1,79 @@ +/* + * keymap for a Datel adapater + US keyboard (not the normal keyboard) + * not mapped: + * printscreen / sysreq + * pause / break + * numlock + * windows key 1 + * windows key 2 + * windows menu key + * + * cursor keys send both 36 and their own code, so 36 must be RESERVED + */ + +static unsigned char gamecube_keymap[] = { + /* 00 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_HOME, KEY_END, + /* 08 */ KEY_PAGEUP, KEY_PAGEDOWN, KEY_SCROLLLOCK, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + /* 10 */ KEY_A, KEY_B, KEY_C, KEY_D, + KEY_E, KEY_F, KEY_G, KEY_H, + /* 18 */ KEY_I, KEY_J, KEY_K, KEY_L, + KEY_M, KEY_N, KEY_O, KEY_P, + /* 20 */ KEY_Q, KEY_R, KEY_S, KEY_T, + KEY_U, KEY_V, KEY_W, KEY_X, + /* 28 */ KEY_Y, KEY_Z, KEY_1, KEY_2, + KEY_3, KEY_4, KEY_5, KEY_6, + /* 30 */ KEY_7, KEY_8, KEY_9, KEY_0, + KEY_MINUS, KEY_EQUAL, KEY_RESERVED, KEY_KPASTERISK, + /* 38 */ KEY_LEFTBRACE, KEY_SEMICOLON, KEY_APOSTROPHE, KEY_RIGHTBRACE, + KEY_COMMA, KEY_DOT, KEY_SLASH, KEY_BACKSLASH, + /* 40 */ KEY_F1, KEY_F2, KEY_F3, KEY_F4, + KEY_F5, KEY_F6, KEY_F7, KEY_F8, + /* 48 */ KEY_F9, KEY_F10, KEY_F11, KEY_F12, + KEY_ESC, KEY_INSERT, KEY_DELETE, KEY_GRAVE, + /* 50 */ KEY_BACKSPACE, KEY_TAB, KEY_RESERVED, KEY_CAPSLOCK, + KEY_LEFTSHIFT, KEY_RIGHTSHIFT, KEY_LEFTCTRL, KEY_LEFTALT, + /* 58 */ KEY_RESERVED, KEY_SPACE, KEY_RESERVED, KEY_RESERVED, + KEY_LEFT, KEY_DOWN, KEY_UP, KEY_RIGHT, + /* 60 */ KEY_RESERVED, KEY_ENTER, KEY_RESERVED, KEY_RESERVED, + KEY_SEMICOLON, KEY_KPPLUS, KEY_RESERVED, KEY_RESERVED, + /* 68 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + /* 70 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + /* 78 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + /* 80 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + /* 88 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + /* 90 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + /* 98 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + /* a0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + /* a8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + /* b0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + /* b8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + /* c0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + /* c8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + /* d0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + /* d8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + /* e0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + /* e8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + /* f0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + /* f8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED +}; diff --git a/drivers/input/si/gcn-si.c b/drivers/input/si/gcn-si.c new file mode 100644 index 0000000..d8b2e8c --- /dev/null +++ b/drivers/input/si/gcn-si.c @@ -0,0 +1,753 @@ +/* + * drivers/input/gcn-si.c + * + * Nintendo GameCube/Wii Serial Interface (SI) driver. + * Copyright (C) 2004-2009 The GameCube Linux Team + * Copyright (C) 2004 Steven Looman + * Copyright (C) 2005,2008,2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +/* #define SI_DEBUG */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * 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 , " \ + "Albert Herranz" + +static char si_driver_version[] = "1.0i"; + +#define drv_printk(level, format, arg...) \ + printk(level DRV_MODULE_NAME ": " format , ## arg) + +#define SI_MAX_PORTS 4 /* the four controller ports */ +#define SI_REFRESH_TIME (HZ/100) /* polling interval */ + +/* + * Hardware registers + */ +#define SI_PORT_SPACING 12 + +#define SICOUTBUF(i) (0x00 + (i)*SI_PORT_SPACING) +#define SICINBUFH(i) (0x04 + (i)*SI_PORT_SPACING) +#define SICINBUFL(i) (0x08 + (i)*SI_PORT_SPACING) +#define SIPOLL 0x30 +#define SICOMCSR 0x34 +#define SISR 0x38 +#define SIEXILK 0x3c + +#define ID_PAD 0x0900 +#define ID_KEYBOARD 0x0820 +#define ID_WIRELESS_BIT (1 << 15) +#define ID_WAVEBIRD_BIT (1 << 8) + +#define PAD_START (1 << 28) +#define PAD_Y (1 << 27) +#define PAD_X (1 << 26) +#define PAD_B (1 << 25) +#define PAD_A (1 << 24) +#define PAD_LT (1 << 22) +#define PAD_RT (1 << 21) +#define PAD_Z (1 << 20) +#define PAD_UP (1 << 19) +#define PAD_DOWN (1 << 18) +#define PAD_RIGHT (1 << 17) +#define PAD_LEFT (1 << 16) + + +struct si_keyboard_status { + unsigned char old[3]; +}; + +enum si_control_type { + CTL_PAD, + CTL_KEYBOARD, + CTL_UNKNOWN +}; + +struct si_drvdata; + +struct si_port { + unsigned int index; + struct si_drvdata *drvdata; + + u32 id; /* SI id */ + + enum si_control_type type; + unsigned int raw[2]; + + struct input_dev *idev; + struct timer_list timer; + char name[32]; + + union { + struct si_keyboard_status keyboard; + }; + +}; + +struct si_drvdata { + unsigned long flags; +#define SI_QUIESCE (1<<0) + + struct si_port ports[SI_MAX_PORTS]; + + void __iomem *io_base; + + struct device *dev; +}; + + +#ifdef HACK_FORCE_KEYBOARD_PORT + +static int si_force_keyboard_port = -1; + +#ifdef MODULE +module_param_named(force_keyboard_port, si_force_keyboard_port, int, 0644); +MODULE_PARM_DESC(force_keyboard_port, "port n becomes a keyboard port if" + " automatic identification fails"); +#else +static int __init si_force_keyboard_port_setup(char *line) +{ + if (sscanf(line, "%d", &si_force_keyboard_port) != 1) + si_force_keyboard_port = -1; + return 1; +} +__setup("force_keyboard_port=", si_force_keyboard_port_setup); +#endif /* MODULE */ + +#endif /* HACK_FORCE_KEYBOARD_PORT */ + + +/* + * Hardware. + * + */ + +static void si_reset_all(void __iomem *io_base) +{ + int i; + + /* clear all SI registers */ + + for (i = 0; i < SI_MAX_PORTS; ++i) + out_be32(io_base + SICOUTBUF(i), 0); + for (i = 0; i < SI_MAX_PORTS; ++i) + out_be32(io_base + SICINBUFH(i), 0); + for (i = 0; i < SI_MAX_PORTS; ++i) + out_be32(io_base + SICINBUFL(i), 0); + out_be32(io_base + SIPOLL, 0); + out_be32(io_base + SICOMCSR, 0); + out_be32(io_base + SISR, 0); + + /* these too... */ + out_be32(io_base + 0x80, 0); + out_be32(io_base + 0x84, 0); + out_be32(io_base + 0x88, 0); + out_be32(io_base + 0x8c, 0); + out_be32(io_base + 0x90, 0); + out_be32(io_base + 0x94, 0); + out_be32(io_base + 0x98, 0); + out_be32(io_base + 0x9c, 0); + out_be32(io_base + 0xa0, 0); + out_be32(io_base + 0xa4, 0); + out_be32(io_base + 0xa8, 0); + out_be32(io_base + 0xac, 0); +} + +static void si_set_rumbling(void __iomem *io_base, unsigned int index, + int rumble) +{ + out_be32(io_base + SICOUTBUF(index), 0x00400000 | (rumble) ? 1 : 0); + out_be32(io_base + SISR, 0x80000000); +} + +static void si_wait_transfer_done(void __iomem *io_base) +{ + unsigned long deadline = jiffies + 2*HZ; + int borked = 0; + + while (!(in_be32(io_base + SICOMCSR) & (1 << 31)) && !borked) { + cpu_relax(); + borked = time_after(jiffies, deadline); + } + + if (borked) { + drv_printk(KERN_ERR, "serial transfer took too long, " + "is your hardware ok?"); + } + + out_be32(io_base + SICOMCSR, + in_be32(io_base + SICOMCSR) | (1 << 31)); /* ack IRQ */ +} + +static u32 si_get_controller_id(void __iomem *io_base, + unsigned int index) +{ + out_be32(io_base + SICOUTBUF(index), 0); + out_be32(io_base + SIPOLL, 0); + + out_be32(io_base + SISR, 0x80000000); + out_be32(io_base + SICOMCSR, 0xd0010001 | index << 1); + si_wait_transfer_done(io_base); + + return in_be32(io_base + 0x80) >> 16; +} + +static void si_setup_polling(struct si_drvdata *drvdata) +{ + void __iomem *io_base = drvdata->io_base; + unsigned long pad_bits = 0; + int i; + + for (i = 0; i < SI_MAX_PORTS; ++i) { + switch (drvdata->ports[i].type) { + case CTL_PAD: + out_be32(io_base + SICOUTBUF(i), 0x00400300); + break; + case CTL_KEYBOARD: + out_be32(io_base + SICOUTBUF(i), 0x00540000); + break; + default: + continue; + } + pad_bits |= 1 << (7 - i); + } + out_be32(io_base + SIPOLL, 0x00f70200 | pad_bits); + + out_be32(io_base + SISR, 0x80000000); + out_be32(io_base + SICOMCSR, 0xc0010801); + si_wait_transfer_done(io_base); +} + +static void si_timer(unsigned long data) +{ + struct si_port *port = (struct si_port *)data; + unsigned int index = port->index; + void __iomem *io_base = port->drvdata->io_base; + unsigned long raw[2]; + unsigned char key[3]; + unsigned char oldkey; + int i; + + raw[0] = in_be32(io_base + SICINBUFH(index)); + raw[1] = in_be32(io_base + SICINBUFL(index)); + + switch (port->type) { + case CTL_PAD: + /* buttons */ + input_report_key(port->idev, BTN_A, raw[0] & PAD_A); + input_report_key(port->idev, BTN_B, raw[0] & PAD_B); + input_report_key(port->idev, BTN_X, raw[0] & PAD_X); + input_report_key(port->idev, BTN_Y, raw[0] & PAD_Y); + input_report_key(port->idev, BTN_Z, raw[0] & PAD_Z); + input_report_key(port->idev, BTN_TL, + raw[0] & PAD_LT); + input_report_key(port->idev, BTN_TR, + raw[0] & PAD_RT); + input_report_key(port->idev, BTN_START, + raw[0] & PAD_START); + input_report_key(port->idev, BTN_0, raw[0] & PAD_UP); + input_report_key(port->idev, BTN_1, raw[0] & PAD_RIGHT); + input_report_key(port->idev, BTN_2, raw[0] & PAD_DOWN); + input_report_key(port->idev, BTN_3, raw[0] & PAD_LEFT); + + /* axis */ + /* a stick */ + input_report_abs(port->idev, ABS_X, + raw[0] >> 8 & 0xFF); + input_report_abs(port->idev, ABS_Y, + 0xFF - (raw[0] >> 0 & 0xFF)); + + /* b pad */ + if (raw[0] & PAD_RIGHT) + input_report_abs(port->idev, ABS_HAT0X, 1); + else if (raw[0] & PAD_LEFT) + input_report_abs(port->idev, ABS_HAT0X, -1); + else + input_report_abs(port->idev, ABS_HAT0X, 0); + + if (raw[0] & PAD_DOWN) + input_report_abs(port->idev, ABS_HAT0Y, 1); + else if (raw[0] & PAD_UP) + input_report_abs(port->idev, ABS_HAT0Y, -1); + else + input_report_abs(port->idev, ABS_HAT0Y, 0); + + /* c stick */ + input_report_abs(port->idev, ABS_RX, + raw[1] >> 24 & 0xFF); + input_report_abs(port->idev, ABS_RY, + raw[1] >> 16 & 0xFF); + + /* triggers */ + input_report_abs(port->idev, ABS_BRAKE, + raw[1] >> 8 & 0xFF); + input_report_abs(port->idev, ABS_GAS, + raw[1] >> 0 & 0xFF); + + break; + + case CTL_KEYBOARD: + /* + raw nibbles: + [4][0][0][0][0][0][0] <1H><1L><2H><2L><3H><3L> + where: + [n] = fixed to n + = high / low nibble of n-th key pressed + (0 if not pressed) + = <1H> xor <2H> xor <3H> + = counter: 0, 0, 1, 1, 2, 2, ..., F, F, 0, 0, ... + */ + key[0] = (raw[1] >> 24) & 0xFF; + key[1] = (raw[1] >> 16) & 0xFF; + key[2] = (raw[1] >> 8) & 0xFF; + + /* check if anything was released */ + for (i = 0; i < 3; ++i) { + oldkey = port->keyboard.old[i]; + if (oldkey != key[0] && + oldkey != key[1] && oldkey != key[2]) + input_report_key(port->idev, + gamecube_keymap[oldkey], 0); + } + + /* report keys */ + for (i = 0; i < 3; ++i) { + if (key[i]) + input_report_key(port->idev, + gamecube_keymap[key[i]], 1); + port->keyboard.old[i] = key[i]; + } + break; + + default: + break; + } + + input_sync(port->idev); + + if (!(port->drvdata->flags & SI_QUIESCE)) + mod_timer(&port->timer, jiffies + SI_REFRESH_TIME); +} + +/* + * Input driver hooks. + * + */ + +static int si_open(struct input_dev *idev) +{ + struct si_port *port = input_get_drvdata(idev); + + init_timer(&port->timer); + port->timer.function = si_timer; + port->timer.data = (unsigned long)port; + port->timer.expires = jiffies + SI_REFRESH_TIME; + add_timer(&port->timer); + + return 0; +} + +static void si_close(struct input_dev *idev) +{ + struct si_port *port = input_get_drvdata(idev); + + del_timer(&port->timer); +} + +static int si_event(struct input_dev *idev, unsigned int type, + unsigned int code, int value) +{ + struct si_port *port = input_get_drvdata(idev); + unsigned int index = port->index; + void __iomem *io_base = port->drvdata->io_base; + + if (type == EV_FF) { + if (code == FF_RUMBLE) + si_set_rumbling(io_base, index, value); + } + + return value; +} + +static int si_setup_pad(struct input_dev *idev) +{ + struct ff_device *ff; + int retval; + + set_bit(EV_KEY, idev->evbit); + set_bit(EV_ABS, idev->evbit); + + set_bit(BTN_A, idev->keybit); + set_bit(BTN_B, idev->keybit); + set_bit(BTN_X, idev->keybit); + set_bit(BTN_Y, idev->keybit); + set_bit(BTN_Z, idev->keybit); + set_bit(BTN_TL, idev->keybit); + set_bit(BTN_TR, idev->keybit); + set_bit(BTN_START, idev->keybit); + set_bit(BTN_0, idev->keybit); + set_bit(BTN_1, idev->keybit); + set_bit(BTN_2, idev->keybit); + set_bit(BTN_3, idev->keybit); + + /* a stick */ + set_bit(ABS_X, idev->absbit); + idev->absmin[ABS_X] = 0; + idev->absmax[ABS_X] = 255; + idev->absfuzz[ABS_X] = 8; + idev->absflat[ABS_X] = 8; + + set_bit(ABS_Y, idev->absbit); + idev->absmin[ABS_Y] = 0; + idev->absmax[ABS_Y] = 255; + idev->absfuzz[ABS_Y] = 8; + idev->absflat[ABS_Y] = 8; + + /* b pad */ + input_set_abs_params(idev, ABS_HAT0X, -1, 1, 0, 0); + input_set_abs_params(idev, ABS_HAT0Y, -1, 1, 0, 0); + + /* c stick */ + set_bit(ABS_RX, idev->absbit); + idev->absmin[ABS_RX] = 0; + idev->absmax[ABS_RX] = 255; + idev->absfuzz[ABS_RX] = 8; + idev->absflat[ABS_RX] = 8; + + set_bit(ABS_RY, idev->absbit); + idev->absmin[ABS_RY] = 0; + idev->absmax[ABS_RY] = 255; + idev->absfuzz[ABS_RY] = 8; + idev->absflat[ABS_RY] = 8; + + /* triggers */ + set_bit(ABS_GAS, idev->absbit); + idev->absmin[ABS_GAS] = -255; + idev->absmax[ABS_GAS] = 255; + idev->absfuzz[ABS_GAS] = 16; + idev->absflat[ABS_GAS] = 16; + + set_bit(ABS_BRAKE, idev->absbit); + idev->absmin[ABS_BRAKE] = -255; + idev->absmax[ABS_BRAKE] = 255; + idev->absfuzz[ABS_BRAKE] = 16; + idev->absflat[ABS_BRAKE] = 16; + + /* rumbling */ + set_bit(EV_FF, idev->evbit); + set_bit(FF_RUMBLE, idev->ffbit); + retval = input_ff_create(idev, 1); + if (retval) + return retval; + ff = idev->ff; + idev->event = si_event; + return 0; +} + +static void si_setup_keyboard(struct input_dev *idev) +{ + int i; + + set_bit(EV_KEY, idev->evbit); + set_bit(EV_REP, idev->evbit); + + for (i = 0; i < 255; ++i) + set_bit(gamecube_keymap[i], idev->keybit); +} + +static int si_port_probe(struct si_port *port) +{ + unsigned int index = port->index; + void __iomem *io_base = port->drvdata->io_base; + struct input_dev *idev; + int retval = 0; + + /* + * Determine input device type from SI id. + */ + port->id = si_get_controller_id(io_base, index); + if (port->id == ID_PAD) { + port->type = CTL_PAD; + strcpy(port->name, "standard pad"); + } else if (port->id & ID_WIRELESS_BIT) { + /* wireless pad */ + port->type = CTL_PAD; + strcpy(port->name, (port->id & ID_WAVEBIRD_BIT) ? + "Nintendo Wavebird" : "wireless pad"); + } else if (port->id == ID_KEYBOARD) { + port->type = CTL_KEYBOARD; + strcpy(port->name, "keyboard"); + } else { + port->type = CTL_UNKNOWN; + if (port->id) { + sprintf(port->name, "unknown (%x)", + port->id); +#ifdef HACK_FORCE_KEYBOARD_PORT + if (index+1 == si_force_keyboard_port) { + drv_printk(KERN_WARNING, + "port %d forced to keyboard mode\n", + index+1); + port->id = ID_KEYBOARD; + port->type = CTL_KEYBOARD; + strcpy(port->name, "keyboard (forced)"); + } +#endif /* HACK_FORCE_KEYBOARD_PORT */ + } else { + strcpy(port->name, "not present"); + } + } + + if (port->type == CTL_UNKNOWN) { + retval = -ENODEV; + goto done; + } + + idev = input_allocate_device(); + if (!idev) { + drv_printk(KERN_ERR, "failed to allocate input_dev\n"); + retval = -ENOMEM; + goto done; + } + + idev->open = si_open; + idev->close = si_close; + idev->name = port->name; + + switch (port->type) { + case CTL_PAD: + retval = si_setup_pad(idev); + break; + case CTL_KEYBOARD: + si_setup_keyboard(idev); + break; + default: + break; + } + + if (retval) { + input_free_device(idev); + goto done; + } + + input_set_drvdata(idev, port); + port->idev = idev; + +done: + return retval; +} + +/* + * Setup routines. + * + */ + +static int si_init(struct si_drvdata *drvdata, struct resource *mem) +{ + struct si_port *port; + int index; + int retval; + int error; + + drvdata->io_base = ioremap(mem->start, mem->end - mem->start + 1); + + si_reset_all(drvdata->io_base); + for (index = 0; index < SI_MAX_PORTS; ++index) { + port = &drvdata->ports[index]; + + memset(port, 0, sizeof(*port)); + port->index = index; + port->drvdata = drvdata; + + retval = si_port_probe(port); + if (!retval) { + error = input_register_device(port->idev); + if (error) { + drv_printk(KERN_ERR, + "input device registration failed" + " (%d) for port %d", error, index+1); + port->idev = NULL; + } else + drv_printk(KERN_INFO, "port %d: %s\n", + index+1, port->name); + } + } + + si_setup_polling(drvdata); + + return 0; +} + +static void si_exit(struct si_drvdata *drvdata) +{ + struct si_port *port; + int index; + + for (index = 0; index < SI_MAX_PORTS; ++index) { + port = &drvdata->ports[index]; + if (port->idev) + input_unregister_device(port->idev); + } + + if (drvdata->io_base) { + iounmap(drvdata->io_base); + drvdata->io_base = NULL; + } +} + +/* + * Driver model helper routines. + * + */ + +static int si_do_probe(struct device *dev, struct resource *mem) +{ + struct si_drvdata *drvdata; + int retval; + + drvdata = kzalloc(sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) { + drv_printk(KERN_ERR, "failed to allocate si_drvdata\n"); + return -ENOMEM; + } + dev_set_drvdata(dev, drvdata); + drvdata->dev = dev; + + retval = si_init(drvdata, mem); + if (retval) { + dev_set_drvdata(dev, NULL); + kfree(drvdata); + } + return retval; +} + +static int si_do_remove(struct device *dev) +{ + struct si_drvdata *drvdata = dev_get_drvdata(dev); + + if (drvdata) { + si_exit(drvdata); + dev_set_drvdata(dev, NULL); + kfree(drvdata); + return 0; + } + return -ENODEV; +} + +static int si_do_shutdown(struct device *dev) +{ + struct si_drvdata *drvdata = dev_get_drvdata(dev); + int i; + + if (drvdata) { + drvdata->flags |= SI_QUIESCE; + for (i = 0; i < SI_MAX_PORTS; ++i) + del_timer_sync(&drvdata->ports[i].timer); + si_reset_all(drvdata->io_base); + } + return 0; +} + + +/* + * OF platform driver hooks. + * + */ + +static int __init si_of_probe(struct of_device *odev, + const struct of_device_id *match) +{ + struct resource mem; + int retval; + + retval = of_address_to_resource(odev->node, 0, &mem); + if (retval) { + drv_printk(KERN_ERR, "no io memory range found\n"); + return -ENODEV; + } + + return si_do_probe(&odev->dev, &mem); +} + +static int __exit si_of_remove(struct of_device *odev) +{ + return si_do_remove(&odev->dev); +} + +static int si_of_shutdown(struct of_device *odev) +{ + return si_do_shutdown(&odev->dev); +} + +static struct of_device_id si_of_match[] = { + { .compatible = "nintendo,flipper-serial" }, + { .compatible = "nintendo,hollywood-serial" }, + { }, +}; + + +MODULE_DEVICE_TABLE(of, si_of_match); + +static struct of_platform_driver si_of_driver = { + .owner = THIS_MODULE, + .name = DRV_MODULE_NAME, + .match_table = si_of_match, + .probe = si_of_probe, + .remove = si_of_remove, + .shutdown = si_of_shutdown, +}; + + +/* + * Module interface hooks. + * + */ + +static int __init si_init_module(void) +{ + drv_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION, + si_driver_version); + + return of_register_platform_driver(&si_of_driver); +} + +static void __exit si_exit_module(void) +{ + of_unregister_platform_driver(&si_of_driver); +} + +module_init(si_init_module); +module_exit(si_exit_module); + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_AUTHOR(DRV_AUTHOR); +MODULE_LICENSE("GPL"); + diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index df1f86b..d6cd229 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -55,6 +55,28 @@ config ATMEL_TCB_CLKSRC_BLOCK TC can be used for other purposes, such as PWM generation and interval timing. +config GAMECUBE_GQR + tristate "Nintendo GameCube/Wii Graphic Quantization Registers (GQR)" + depends on GAMECUBE_COMMON + help + This option enables device driver support for the Gekko/Broadway + processors' Graphic Quantization Registers. + These registers are used with the psql and psqst instructions. + The registers will appear in /proc/sys/gqr. + +config GAMECUBE_MI + tristate "Nintendo GameCube Memory Interface (MI)" + depends on GAMECUBE + help + If you say yes to this option, support will be included for the + Memory Interface (MI) of the Nintendo GameCube. + + The MI allows one to setup up to four protected memory regions, + catching invalid accesses to them. The MI catches out of bounds + memory accesses too. + + If in doubt, say N here. + config IBM_ASM tristate "Device driver for IBM RSA service processor" depends on X86 && PCI && INPUT && EXPERIMENTAL diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index f982d2e..4b74341 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -21,5 +21,7 @@ obj-$(CONFIG_HP_ILO) += hpilo.o obj-$(CONFIG_ISL29003) += isl29003.o obj-$(CONFIG_EP93XX_PWM) += ep93xx_pwm.o obj-$(CONFIG_C2PORT) += c2port/ +obj-$(CONFIG_GAMECUBE_GQR) += gcn-gqr.o +obj-$(CONFIG_GAMECUBE_MI) += gcn-mi.o obj-y += eeprom/ obj-y += cb710/ diff --git a/drivers/misc/gcn-gqr.c b/drivers/misc/gcn-gqr.c new file mode 100644 index 0000000..ff390eb --- /dev/null +++ b/drivers/misc/gcn-gqr.c @@ -0,0 +1,129 @@ +/* + * drivers/misc/gcn-gqr.c + * + * Nintendo GameCube GQR driver + * Copyright (C) 2004-2009 The GameCube Linux Team + * Copyright (C) 2004 Todd Jeffreys + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +static u32 gqr_values[8]; +static struct ctl_table_header *gqr_table_header; + +#define SPR_GQR0 912 +#define SPR_GQR1 913 +#define SPR_GQR2 914 +#define SPR_GQR3 915 +#define SPR_GQR4 916 +#define SPR_GQR5 917 +#define SPR_GQR6 918 +#define SPR_GQR7 919 + +#define MFSPR_CASE(i) case (i): (*((u32 *)table->data) = mfspr(SPR_GQR##i)) +#define MTSPR_CASE(i) case (i): mtspr(SPR_GQR##i, *((u32 *)table->data)) + +static int proc_dogqr(struct ctl_table *table, int write, + void __user *buffer, size_t *lenp, loff_t *ppos) +{ + int r; + + if (!write) { /* if they are reading, update the variable */ + switch (table->data - (void *)gqr_values) { + MFSPR_CASE(0); break; + MFSPR_CASE(1); break; + MFSPR_CASE(2); break; + MFSPR_CASE(3); break; + MFSPR_CASE(4); break; + MFSPR_CASE(5); break; + MFSPR_CASE(6); break; + MFSPR_CASE(7); break; + default: + return -EFAULT; /* shouldn't happen */ + } + } + + r = proc_dointvec(table, write, buffer, lenp, ppos); + + if ((r == 0) && write) { /* if they are writing, update the reg */ + switch (table->data - (void *)gqr_values) { + MTSPR_CASE(0); break; + MTSPR_CASE(1); break; + MTSPR_CASE(2); break; + MTSPR_CASE(3); break; + MTSPR_CASE(4); break; + MTSPR_CASE(5); break; + MTSPR_CASE(6); break; + MTSPR_CASE(7); break; + default: + return -EFAULT; /* shouldn't happen */ + } + } + + return r; +} + +#define DECLARE_GQR(i) { \ + .ctl_name = CTL_UNNUMBERED, \ + .procname = "gqr" #i, \ + .data = gqr_values + i, \ + .maxlen = sizeof(int), \ + .mode = 0644, \ + .proc_handler = &proc_dogqr \ + } + +static struct ctl_table gqr_members[] = { + DECLARE_GQR(0), + DECLARE_GQR(1), + DECLARE_GQR(2), + DECLARE_GQR(3), + DECLARE_GQR(4), + DECLARE_GQR(5), + DECLARE_GQR(6), + DECLARE_GQR(7), + { .ctl_name = 0 } +}; + +static struct ctl_table gqr_table[] = { + { + .ctl_name = CTL_UNNUMBERED, + .procname = "gqr", + .mode = 0555, + .child = gqr_members, + }, + { .ctl_name = 0 } +}; + +int __init gcngqr_init(void) +{ + gqr_table_header = register_sysctl_table(gqr_table); + if (!gqr_table_header) { + printk(KERN_ERR "Unable to register GQR sysctl table\n"); + return -ENOMEM; + } + return 0; +} + +void __exit gcngqr_exit(void) +{ + unregister_sysctl_table(gqr_table_header); +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Todd Jeffreys "); +module_init(gcngqr_init); +module_exit(gcngqr_exit); diff --git a/drivers/misc/gcn-mi.c b/drivers/misc/gcn-mi.c new file mode 100644 index 0000000..3ecc402 --- /dev/null +++ b/drivers/misc/gcn-mi.c @@ -0,0 +1,445 @@ +/* + * drivers/misc/gcn-mi.c + * + * Nintendo GameCube Memory Interface (MI) driver. + * Copyright (C) 2004-2009 The GameCube Linux Team + * Copyright (C) 2004,2005,2007,2008,2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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, "# \n"); + for (region = 0; region < MI_MAX_REGIONS; region++) { + if ((priv->regions_bitmap & (1<faults[region]); + } + } + len += sprintf(page+len, "%s\t%lu\n", + "none", priv->faults[MI_MAX_REGIONS]); + + return len; +} + +#endif /* CONFIG_PROC_FS */ + +/* + * + */ +static int mi_setup_irq(struct mi_private *priv) +{ + int retval; + + retval = request_irq(priv->irq, mi_handler, 0, DRV_MODULE_NAME, priv); + if (retval) + mi_printk(KERN_ERR, "request of irq%d failed\n", priv->irq); + else + out_be16(MI_IMR, (1<<4)); /* do not mask all MI interrupts */ + + return retval; +} + +/* + * + */ +static int mi_probe(struct device *device, struct resource *mem, int irq) +{ + struct mi_private *priv; + int retval; + + priv = kmalloc(sizeof(struct mi_private), GFP_KERNEL); + if (!priv) { + retval = -ENOMEM; + goto err; + } + + memset(priv, 0, sizeof(*priv)); + /* + int region; + priv->nr_regions = 0; + priv->regions_bitmap = 0; + for( region = 0; region < MI_MAX_REGIONS; region++ ) { + priv->faults[region] = 0; + } + priv->last_address_faults = 0; + */ + spin_lock_init(&priv->lock); + + priv->device = device; + dev_set_drvdata(priv->device, priv); + + priv->irq = irq; + retval = mi_setup_irq(priv); + if (retval) + goto err_setup_irq; + +#ifdef CONFIG_PROC_FS + { + struct platform_device *pdev = to_platform_device(device); + priv->proc_file = create_proc_read_entry(dev_name(&pdev->dev), + 0444, NULL, + mi_proc_read, priv); + } +#endif /* CONFIG_PROC_FS */ + + mi_private = priv; + + return 0; + +err_setup_irq: + dev_set_drvdata(priv->device, NULL); + kfree(priv); +err: + return retval; +} + +/* + * + */ +static void mi_shutdown(struct mi_private *priv) +{ + gcn_mi_region_unprotect_all(); +} + +/* + * + */ +static void mi_remove(struct mi_private *priv) +{ +#ifdef CONFIG_PROC_FS + struct platform_device *pdev = to_platform_device(priv->device); + remove_proc_entry(dev_name(&pdev->dev), NULL); +#endif /* CONFIG_PROC_FS */ + + mi_shutdown(priv); + + /* free interrupt handler */ + free_irq(priv->irq, priv); + + kfree(priv); + mi_private = NULL; +} + +/* + * + */ +static int __init mi_drv_probe(struct device *device) +{ + struct platform_device *pdev = to_platform_device(device); + struct resource *mem; + int irq; + + irq = platform_get_irq(pdev, 0); + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) + return -ENODEV; + + mi_printk(KERN_INFO, "%s\n", DRV_DESCRIPTION); + + return mi_probe(device, mem, irq); +} + +/* + * + */ +static int mi_drv_remove(struct device *device) +{ + struct mi_private *priv = dev_get_drvdata(device); + + if (priv) { + mi_remove(priv); + dev_set_drvdata(device, NULL); + } + + return 0; +} + +/* + * + */ +static void mi_drv_shutdown(struct device *device) +{ + struct mi_private *priv = dev_get_drvdata(device); + + if (priv) + mi_shutdown(priv); +} + +static struct device_driver mi_device_driver = { + .name = "mi", + .bus = &platform_bus_type, + .probe = mi_drv_probe, + .remove = mi_drv_remove, + .shutdown = mi_drv_shutdown, +}; + +static struct resource mi_resources[] = { + [0] = { + .start = MI_BASE, + .end = MI_BASE + MI_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MI_IRQ, + .end = MI_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device mi_device = { + .name = "mi", + .id = 0, + .num_resources = ARRAY_SIZE(mi_resources), + .resource = mi_resources, +}; + +/* + * + */ +static int __init mi_init(void) +{ + int retval = 0; + + retval = driver_register(&mi_device_driver); + if (!retval) + retval = platform_device_register(&mi_device); + + return retval; +} + +/* + * + */ +static void __exit mi_exit(void) +{ + platform_device_unregister(&mi_device); + driver_unregister(&mi_device_driver); +} + +module_init(mi_init); +module_exit(mi_exit); + + +/* public interface */ + +/* + * + */ +int gcn_mi_region_protect(unsigned long physlo, unsigned long physhi, int type) +{ + struct mi_private *priv = mi_private; + int region, free_regions; + u16 pagelo, pagehi; + + if (!priv) + return -ENODEV; + + if (type < MI_PROT_NONE || type > MI_PROT_RW) + return -EINVAL; + + if ((physlo & ~MI_PAGE_MASK) != 0 || (physhi & ~MI_PAGE_MASK) != 0) + return -EINVAL; + + free_regions = MI_MAX_REGIONS - priv->nr_regions; + if (free_regions <= 0) + return -ENOMEM; + for (region = 0; region < MI_MAX_REGIONS; region++) { + if ((priv->regions_bitmap & (1<= 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<regions_bitmap &= ~(1 << region); + priv->nr_regions--; + + return 0; +} + +/* + * + */ +void gcn_mi_region_unprotect_all(void) +{ + int region; + + out_be16(MI_IMR, 0); + for (region = 0; region < MI_MAX_REGIONS; region++) + gcn_mi_region_unprotect(region); +} + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_AUTHOR(DRV_AUTHOR); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/gcn-mi.h b/drivers/misc/gcn-mi.h new file mode 100644 index 0000000..201387b --- /dev/null +++ b/drivers/misc/gcn-mi.h @@ -0,0 +1,30 @@ +/* + * drivers/misc/gcn-mi.h + * + * Nintendo GameCube Memory Interface driver + * Copyright (C) 2004-2009 The GameCube Linux Team + * Copyright (C) 2004,2005,2008,2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#ifndef __GCN_MI_H +#define __GCN_MI_H + +#define MI_MAX_REGIONS 4 + +#define MI_PROT_NONE 0x00 +#define MI_PROT_RO 0x01 +#define MI_PROT_WO 0x02 +#define MI_PROT_RW 0x03 + +int gcn_mi_region_protect(unsigned long physlo, unsigned long physhi, int type); +int gcn_mi_region_unprotect(int region); +void gcn_mi_region_unprotect_all(void); + +#endif /* __GCN_MI_H */ + diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 7dab2e5..7afcf2c 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1073,6 +1073,7 @@ void mmc_rescan(struct work_struct *work) mmc_claim_host(host); mmc_power_up(host); + sdio_reset(host); mmc_go_idle(host); mmc_send_if_cond(host, host->ocr_avail); diff --git a/drivers/mmc/core/sdio_cis.c b/drivers/mmc/core/sdio_cis.c index f85dcd5..9538389 100644 --- a/drivers/mmc/core/sdio_cis.c +++ b/drivers/mmc/core/sdio_cis.c @@ -97,26 +97,56 @@ static const unsigned char speed_val[16] = static const unsigned int speed_unit[8] = { 10000, 100000, 1000000, 10000000, 0, 0, 0, 0 }; -/* FUNCE tuples with these types get passed to SDIO drivers */ -static const unsigned char funce_type_whitelist[] = { - 4 /* CISTPL_FUNCE_LAN_NODE_ID used in Broadcom cards */ + +typedef int (tpl_parse_t)(struct mmc_card *, struct sdio_func *, + const unsigned char *, unsigned); + +struct cis_tpl { + unsigned char code; + unsigned char min_size; + tpl_parse_t *parse; }; -static int cistpl_funce_whitelisted(unsigned char type) +static int cis_tpl_parse(struct mmc_card *card, struct sdio_func *func, + const char *tpl_descr, + const struct cis_tpl *tpl, int tpl_count, + unsigned char code, + const unsigned char *buf, unsigned size) { - int i; + int i, ret; - for (i = 0; i < ARRAY_SIZE(funce_type_whitelist); i++) { - if (funce_type_whitelist[i] == type) - return 1; + /* look for a matching code in the table */ + for (i = 0; i < tpl_count; i++, tpl++) { + if (tpl->code == code) + break; } - return 0; + if (i < tpl_count) { + if (size >= tpl->min_size) { + if (tpl->parse) + ret = tpl->parse(card, func, buf, size); + else + ret = -EILSEQ; /* known tuple, not parsed */ + } else { + /* invalid tuple */ + ret = -EINVAL; + } + if (ret && ret != -EILSEQ && ret != -ENOENT) { + printk(KERN_ERR "%s: bad %s tuple 0x%02x (%u bytes)\n", + mmc_hostname(card->host), tpl_descr, code, size); + } + } else { + /* unknown tuple */ + ret = -ENOENT; + } + + return ret; } -static int cistpl_funce_common(struct mmc_card *card, +static int cistpl_funce_common(struct mmc_card *card, struct sdio_func *func, const unsigned char *buf, unsigned size) { - if (size < 0x04 || buf[0] != 0) + /* Only valid for the common CIS (function 0) */ + if (func) return -EINVAL; /* TPLFE_FN0_BLK_SIZE */ @@ -129,20 +159,24 @@ static int cistpl_funce_common(struct mmc_card *card, return 0; } -static int cistpl_funce_func(struct sdio_func *func, +static int cistpl_funce_func(struct mmc_card *card, struct sdio_func *func, const unsigned char *buf, unsigned size) { unsigned vsn; unsigned min_size; - /* let SDIO drivers take care of whitelisted FUNCE tuples */ - if (cistpl_funce_whitelisted(buf[0])) - return -EILSEQ; + /* Only valid for the individual function's CIS (1-7) */ + if (!func) + return -EINVAL; + /* + * This tuple has a different length depending on the SDIO spec + * version. + */ vsn = func->card->cccr.sdio_vsn; min_size = (vsn == SDIO_SDIO_REV_1_00) ? 28 : 42; - if (size < min_size || buf[0] != 1) + if (size < min_size) return -EINVAL; /* TPLFE_MAX_BLK_SIZE */ @@ -157,39 +191,32 @@ static int cistpl_funce_func(struct sdio_func *func, return 0; } +/* + * Known TPLFE_TYPEs table for CISTPL_FUNCE tuples. + * + * Note that, unlike PCMCIA, CISTPL_FUNCE tuples are not parsed depending + * on the TPLFID_FUNCTION value of the previous CISTPL_FUNCID as on SDIO + * TPLFID_FUNCTION is always hardcoded to 0x0C. + */ +static const struct cis_tpl cis_tpl_funce_list[] = { + { 0x00, 4, cistpl_funce_common }, + { 0x01, 0, cistpl_funce_func }, + { 0x04, 1+1+6, /* CISTPL_FUNCE_LAN_NODE_ID */ }, +}; + static int cistpl_funce(struct mmc_card *card, struct sdio_func *func, const unsigned char *buf, unsigned size) { - int ret; - - /* - * There should be two versions of the CISTPL_FUNCE tuple, - * one for the common CIS (function 0) and a version used by - * the individual function's CIS (1-7). Yet, the later has a - * different length depending on the SDIO spec version. - */ - if (func) - ret = cistpl_funce_func(func, buf, size); - else - ret = cistpl_funce_common(card, buf, size); - - if (ret && ret != -EILSEQ) { - printk(KERN_ERR "%s: bad CISTPL_FUNCE size %u " - "type %u\n", mmc_hostname(card->host), size, buf[0]); - } + if (size < 1) + return -EINVAL; - return ret; + return cis_tpl_parse(card, func, "CISTPL_FUNCE", + cis_tpl_funce_list, + ARRAY_SIZE(cis_tpl_funce_list), + buf[0], buf, size); } -typedef int (tpl_parse_t)(struct mmc_card *, struct sdio_func *, - const unsigned char *, unsigned); - -struct cis_tpl { - unsigned char code; - unsigned char min_size; - tpl_parse_t *parse; -}; - +/* Known TPL_CODEs table for CIS tuples */ static const struct cis_tpl cis_tpl_list[] = { { 0x15, 3, cistpl_vers_1 }, { 0x20, 4, cistpl_manfid }, @@ -268,46 +295,38 @@ static int sdio_read_cis(struct mmc_card *card, struct sdio_func *func) break; } - for (i = 0; i < ARRAY_SIZE(cis_tpl_list); i++) - if (cis_tpl_list[i].code == tpl_code) - break; - if (i < ARRAY_SIZE(cis_tpl_list)) { - const struct cis_tpl *tpl = cis_tpl_list + i; - if (tpl_link < tpl->min_size) { - printk(KERN_ERR - "%s: bad CIS tuple 0x%02x" - " (length = %u, expected >= %u)\n", - mmc_hostname(card->host), - tpl_code, tpl_link, tpl->min_size); - ret = -EINVAL; - } else if (tpl->parse) { - ret = tpl->parse(card, func, - this->data, tpl_link); - } + /* Try to parse the CIS tuple */ + ret = cis_tpl_parse(card, func, "CIS", + cis_tpl_list, ARRAY_SIZE(cis_tpl_list), + tpl_code, this->data, tpl_link); + if (ret == -EILSEQ || ret == -ENOENT) { /* - * We don't need the tuple anymore if it was - * successfully parsed by the SDIO core or if it is - * not going to be parsed by SDIO drivers. + * The tuple is unknown or known but not parsed. + * Queue the tuple for the function driver. */ - if (!ret || ret != -EILSEQ) - kfree(this); - } else { - /* unknown tuple */ - ret = -EILSEQ; - } - - if (ret == -EILSEQ) { - /* this tuple is unknown to the core or whitelisted */ this->next = NULL; this->code = tpl_code; this->size = tpl_link; *prev = this; prev = &this->next; - printk(KERN_DEBUG - "%s: queuing CIS tuple 0x%02x length %u\n", - mmc_hostname(card->host), tpl_code, tpl_link); + + if (ret == -ENOENT) { + /* warn about unknown tuples */ + printk(KERN_WARNING "%s: queuing unknown" + " CIS tuple 0x%02x (%u bytes)\n", + mmc_hostname(card->host), + tpl_code, tpl_link); + } + /* keep on analyzing tuples */ ret = 0; + } else { + /* + * We don't need the tuple anymore if it was + * successfully parsed by the SDIO core or if it is + * not going to be queued for a driver. + */ + kfree(this); } ptr += tpl_link; diff --git a/drivers/mmc/core/sdio_ops.c b/drivers/mmc/core/sdio_ops.c index 4eb7825..dea36d9 100644 --- a/drivers/mmc/core/sdio_ops.c +++ b/drivers/mmc/core/sdio_ops.c @@ -67,13 +67,13 @@ int mmc_send_io_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr) return err; } -int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn, - unsigned addr, u8 in, u8* out) +static int mmc_io_rw_direct_host(struct mmc_host *host, int write, unsigned fn, + unsigned addr, u8 in, u8 *out) { struct mmc_command cmd; int err; - BUG_ON(!card); + BUG_ON(!host); BUG_ON(fn > 7); /* sanity check */ @@ -90,11 +90,11 @@ int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn, cmd.arg |= in; cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_AC; - err = mmc_wait_for_cmd(card->host, &cmd, 0); + err = mmc_wait_for_cmd(host, &cmd, 0); if (err) return err; - if (mmc_host_is_spi(card->host)) { + if (mmc_host_is_spi(host)) { /* host driver already reported errors */ } else { if (cmd.resp[0] & R5_ERROR) @@ -106,7 +106,7 @@ int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn, } if (out) { - if (mmc_host_is_spi(card->host)) + if (mmc_host_is_spi(host)) *out = (cmd.resp[0] >> 8) & 0xFF; else *out = cmd.resp[0] & 0xFF; @@ -115,6 +115,13 @@ int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn, return 0; } +int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn, + unsigned addr, u8 in, u8 *out) +{ + BUG_ON(!card); + return mmc_io_rw_direct_host(card->host, write, fn, addr, in, out); +} + int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn, unsigned addr, int incr_addr, u8 *buf, unsigned blocks, unsigned blksz) { @@ -182,3 +189,20 @@ int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn, return 0; } +int sdio_reset(struct mmc_host *host) +{ + int ret; + u8 abort; + + /* SDIO Simplified Specification V2.0, 4.4 Reset for SDIO */ + + ret = mmc_io_rw_direct_host(host, 0, 0, SDIO_CCCR_ABORT, 0, &abort); + if (ret) + abort = 0x08; + else + abort |= 0x08; + + ret = mmc_io_rw_direct_host(host, 1, 0, SDIO_CCCR_ABORT, abort, NULL); + return ret; +} + diff --git a/drivers/mmc/core/sdio_ops.h b/drivers/mmc/core/sdio_ops.h index e2e74b0..12a4d3a 100644 --- a/drivers/mmc/core/sdio_ops.h +++ b/drivers/mmc/core/sdio_ops.h @@ -17,6 +17,7 @@ int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn, unsigned addr, u8 in, u8* out); int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn, unsigned addr, int incr_addr, u8 *buf, unsigned blocks, unsigned blksz); +int sdio_reset(struct mmc_host *host); #endif diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 432ae83..1ef4127 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -119,6 +119,16 @@ config MMC_SDHCI_S3C_DMA YMMV. +config MMC_SDHCI_HLWD + tristate "Nintendo Wii (Hollywood) SDHCI support" + depends on MMC_SDHCI && STARLET_MINI + select MMC_SDHCI_IO_ACCESSORS + help + This selects the Nintendo Wii Secure Digital Host Controller + Interface contained in the Hollywood chipset. + + If unsure, say N. + config MMC_OMAP tristate "TI OMAP Multimedia Card Interface support" depends on ARCH_OMAP diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index abcb040..3012e00 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_MMC_RICOH_MMC) += ricoh_mmc.o obj-$(CONFIG_MMC_SDHCI_OF) += sdhci-of.o obj-$(CONFIG_MMC_SDHCI_PLTFM) += sdhci-pltfm.o obj-$(CONFIG_MMC_SDHCI_S3C) += sdhci-s3c.o +obj-$(CONFIG_MMC_SDHCI_HLWD) += sdhci-hlwd.o obj-$(CONFIG_MMC_WBSD) += wbsd.o obj-$(CONFIG_MMC_AU1X) += au1xmmc.o obj-$(CONFIG_MMC_OMAP) += omap.o diff --git a/drivers/mmc/host/sdhci-hlwd.c b/drivers/mmc/host/sdhci-hlwd.c new file mode 100644 index 0000000..d827ea1 --- /dev/null +++ b/drivers/mmc/host/sdhci-hlwd.c @@ -0,0 +1,264 @@ +/* + * drivers/mmc/host/sdhci-hlwd.c + * + * Nintendo Wii Secure Digital Host Controller Interface. + * Copyright (C) 2009 The GameCube Linux Team + * Copyright (C) 2009 Albert Herranz + * + * Based on sdhci-of.c + * + * Copyright (c) 2007 Freescale Semiconductor, Inc. + * Copyright (c) 2009 MontaVista Software, Inc. + * + * Authors: Xiaobo Xie + * Anton Vorontsov + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include "sdhci.h" + +#define DRV_MODULE_NAME "sdhci-hlwd" +#define DRV_DESCRIPTION "Nintendo Wii Secure Digital Host Controller Interface" +#define DRV_AUTHOR "Albert Herranz" + +static char sdhci_hlwd_driver_version[] = "0.1i"; + +#define drv_printk(level, format, arg...) \ + printk(level DRV_MODULE_NAME ": " format , ## arg) + +#define DBG(fmt, arg...) drv_printk(KERN_DEBUG, "%s: " fmt, \ + __func__, ## arg) + + +struct sdhci_hlwd_data { + unsigned int quirks; + struct sdhci_ops ops; +}; + +struct sdhci_hlwd_host { + u16 xfer_mode_shadow; +}; + +static u32 sdhci_hlwd_readl(struct sdhci_host *host, int reg) +{ + return in_be32(host->ioaddr + reg); +} + +static u16 sdhci_hlwd_readw(struct sdhci_host *host, int reg) +{ + return in_be16(host->ioaddr + (reg ^ 0x2)); +} + +static u8 sdhci_hlwd_readb(struct sdhci_host *host, int reg) +{ + return in_8(host->ioaddr + (reg ^ 0x3)); +} + +static void sdhci_hlwd_writel(struct sdhci_host *host, u32 val, int reg) +{ + out_be32(host->ioaddr + reg, val); + udelay(5); +} + +static void sdhci_hlwd_writew(struct sdhci_host *host, u16 val, int reg) +{ + struct sdhci_hlwd_host *hlwd_host = sdhci_priv(host); + int base = reg & ~0x3; + int shift = (reg & 0x2) * 8; + + switch (reg) { + case SDHCI_TRANSFER_MODE: + /* + * Postpone this write, we must do it together with a + * command write that is down below. + */ + hlwd_host->xfer_mode_shadow = val; + return; + case SDHCI_COMMAND: + sdhci_hlwd_writel(host, val << 16 | hlwd_host->xfer_mode_shadow, + SDHCI_TRANSFER_MODE); + return; + } + clrsetbits_be32(host->ioaddr + base, + 0xffff << shift, val << shift); + udelay(5); +} + +static void sdhci_hlwd_writeb(struct sdhci_host *host, u8 val, int reg) +{ + int base = reg & ~0x3; + int shift = (reg & 0x3) * 8; + + clrsetbits_be32(host->ioaddr + base , 0xff << shift, val << shift); + udelay(5); +} + +static struct sdhci_hlwd_data sdhci_hlwd = { + .quirks = SDHCI_QUIRK_32BIT_DMA_ADDR | + SDHCI_QUIRK_32BIT_DMA_SIZE, + .ops = { + .readl = sdhci_hlwd_readl, + .readw = sdhci_hlwd_readw, + .readb = sdhci_hlwd_readb, + .writel = sdhci_hlwd_writel, + .writew = sdhci_hlwd_writew, + .writeb = sdhci_hlwd_writeb, + }, +}; + + +#ifdef CONFIG_PM + +static int sdhci_hlwd_suspend(struct of_device *ofdev, pm_message_t state) +{ + struct sdhci_host *host = dev_get_drvdata(&ofdev->dev); + + return mmc_suspend_host(host->mmc, state); +} + +static int sdhci_hlwd_resume(struct of_device *ofdev) +{ + struct sdhci_host *host = dev_get_drvdata(&ofdev->dev); + + return mmc_resume_host(host->mmc); +} + +#else + +#define sdhci_hlwd_suspend NULL +#define sdhci_hlwd_resume NULL + +#endif + +static int __devinit sdhci_hlwd_probe(struct of_device *ofdev, + const struct of_device_id *match) +{ + struct device_node *np = ofdev->node; + struct sdhci_hlwd_data *sdhci_hlwd_data = match->data; + struct sdhci_host *host; + struct sdhci_hlwd_host *hlwd_host; + struct resource res; + int error; + + if (starlet_get_ipc_flavour() != STARLET_IPC_MINI) { + error = -ENODEV; + goto out; + } + + if (!of_device_is_available(np)) { + error = -ENODEV; + goto out; + } + + host = sdhci_alloc_host(&ofdev->dev, sizeof(*hlwd_host)); + if (!host) { + DBG("unable to allocate sdhci_host\n"); + error = -ENODEV; + goto out; + } + + dev_set_drvdata(&ofdev->dev, host); + + error = of_address_to_resource(np, 0, &res); + if (error) { + DBG("of_address_to_resource failed (%d)\n", error); + goto err_no_address; + } + + host->ioaddr = ioremap(res.start, resource_size(&res)); + if (!host->ioaddr) { + DBG("ioremap failed\n"); + error = -EINVAL; + goto err_ioremap; + } + + host->irq = irq_of_parse_and_map(np, 0); + if (!host->irq) { + DBG("irq_of_parse_and_map failed\n"); + error = -EINVAL; + goto err_no_irq; + } + + host->hw_name = dev_name(&ofdev->dev); + if (sdhci_hlwd_data) { + host->quirks = sdhci_hlwd_data->quirks; + host->ops = &sdhci_hlwd_data->ops; + } + + error = sdhci_add_host(host); + if (error) { + DBG("sdhci_add_host failed\n"); + goto err_add_host; + } + + return 0; + +err_add_host: + irq_dispose_mapping(host->irq); +err_no_irq: + iounmap(host->ioaddr); +err_ioremap: +err_no_address: + sdhci_free_host(host); +out: + return error; +} + +static int __devexit sdhci_hlwd_remove(struct of_device *ofdev) +{ + struct sdhci_host *host = dev_get_drvdata(&ofdev->dev); + + sdhci_remove_host(host, 0); + irq_dispose_mapping(host->irq); + iounmap(host->ioaddr); + sdhci_free_host(host); + return 0; +} + +static const struct of_device_id sdhci_hlwd_match[] = { + { .compatible = "nintendo,hollywood-sdhci", .data = &sdhci_hlwd, }, + {}, +}; +MODULE_DEVICE_TABLE(of, sdhci_hlwd_match); + +static struct of_platform_driver sdhci_hlwd_driver = { + .driver.name = DRV_MODULE_NAME, + .match_table = sdhci_hlwd_match, + .probe = sdhci_hlwd_probe, + .remove = __devexit_p(sdhci_hlwd_remove), + .suspend = sdhci_hlwd_suspend, + .resume = sdhci_hlwd_resume, +}; + +static int __init sdhci_hlwd_init(void) +{ + drv_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION, + sdhci_hlwd_driver_version); + + return of_register_platform_driver(&sdhci_hlwd_driver); +} +module_init(sdhci_hlwd_init); + +static void __exit sdhci_hlwd_exit(void) +{ + of_unregister_platform_driver(&sdhci_hlwd_driver); +} +module_exit(sdhci_hlwd_exit); + +MODULE_AUTHOR(DRV_AUTHOR); +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_LICENSE("GPL"); + diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index b2f71f7..90e68f8 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -270,6 +270,15 @@ config BMAC To compile this driver as a module, choose M here: the module will be called bmac. +config GAMECUBE_BBA + tristate "Nintendo GameCube ethernet BroadBand Adapter (BBA)" + depends on GAMECUBE_EXI && GAMECUBE + help + Say Y here to add ethernet support for the Broadband Adapter (BBA). + + To compile this driver as a module, choose M here: the module + will be called gcn-bba. + config ARIADNE tristate "Ariadne support" depends on ZORRO diff --git a/drivers/net/Makefile b/drivers/net/Makefile index 246323d..1bc4d98 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -246,6 +246,7 @@ pasemi_mac_driver-objs := pasemi_mac.o pasemi_mac_ethtool.o obj-$(CONFIG_MLX4_CORE) += mlx4/ obj-$(CONFIG_ENC28J60) += enc28j60.o obj-$(CONFIG_ETHOC) += ethoc.o +obj-$(CONFIG_GAMECUBE_BBA) += gcn-bba.o obj-$(CONFIG_XTENSA_XT2000_SONIC) += xtsonic.o diff --git a/drivers/net/gcn-bba.c b/drivers/net/gcn-bba.c new file mode 100644 index 0000000..bc79d2c --- /dev/null +++ b/drivers/net/gcn-bba.c @@ -0,0 +1,1263 @@ +/** + * drivers/net/gcn-bba.c + * + * Nintendo GameCube Broadband Adapter (BBA) driver + * Copyright (C) 2004-2009 The GameCube Linux Team + * Copyright (C) 2005 Todd Jeffreys + * Copyright (C) 2004,2005,2006,2007,2008,2009 Albert Herranz + * + * Based on previous work by Stefan Esser, Franz Lehner, Costis and tmbinc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#define BBA_DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define DRV_MODULE_NAME "gcn-bba" +#define DRV_DESCRIPTION "Nintendo GameCube Broadband Adapter (BBA) driver" +#define DRV_AUTHOR "Albert Herranz, " \ + "Todd Jeffreys" + +static char bba_driver_version[] = "1.4i"; + + +#define bba_printk(level, format, arg...) \ + printk(level DRV_MODULE_NAME ": " format , ## arg) + +#ifdef BBA_DEBUG +# define DBG(fmt, args...) \ + printk(KERN_ERR "%s: " fmt, __func__ , ## args) +#else +# define DBG(fmt, args...) +#endif + +/* + * EXpansion Interface glue for the Broadband Adapter. + * + */ +#define BBA_EXI_ID 0x04020200 + +#define BBA_EXI_IRQ_CHANNEL 2 /* INT line uses EXI2INTB */ +#define BBA_EXI_CHANNEL 0 /* rest of lines use EXI0xxx */ +#define BBA_EXI_DEVICE 2 /* chip select, EXI0CSB2 */ +#define BBA_EXI_FREQ 5 /* 32MHz */ + +#define BBA_CMD_IR_MASKALL 0x00 +#define BBA_CMD_IR_MASKNONE 0xf8 + +static inline void bba_select(void); +static inline void bba_deselect(void); +static inline void bba_write(void *data, size_t len); +static inline void bba_read(void *data, size_t len); + +static void bba_ins(int reg, void *val, int len); +static void bba_outs(int reg, void *val, int len); + +/* + * Command Registers I/O. + */ + +static inline void bba_cmd_ins_nosel(int reg, void *val, int len) +{ + u16 req; + req = reg << 8; + bba_write(&req, sizeof(req)); + bba_read(val, len); +} + +static void bba_cmd_ins(int reg, void *val, int len) +{ + bba_select(); + bba_cmd_ins_nosel(reg, val, len); + bba_deselect(); +} + +static inline void bba_cmd_outs_nosel(int reg, void *val, int len) +{ + u16 req; + req = (reg << 8) | 0x4000; + bba_write(&req, sizeof(req)); + bba_write(val, len); +} + +static void bba_cmd_outs(int reg, void *val, int len) +{ + bba_select(); + bba_cmd_outs_nosel(reg, val, len); + bba_deselect(); +} + +static inline u8 bba_cmd_in8(int reg) +{ + u8 val; + bba_cmd_ins(reg, &val, sizeof(val)); + return val; +} + +static u8 bba_cmd_in8_slow(int reg) +{ + u8 val; + bba_select(); + bba_cmd_ins_nosel(reg, &val, sizeof(val)); + udelay(200); + bba_deselect(); + return val; +} + +static inline void bba_cmd_out8(int reg, u8 val) +{ + bba_cmd_outs(reg, &val, sizeof(val)); +} + + +/* + * Registers I/O. + */ + +static inline u8 bba_in8(int reg) +{ + u8 val; + bba_ins(reg, &val, sizeof(val)); + return val; +} + +static inline void bba_out8(int reg, u8 val) +{ + bba_outs(reg, &val, sizeof(val)); +} + +static inline u16 bba_in16(int reg) +{ + u16 val; + bba_ins(reg, &val, sizeof(val)); + return le16_to_cpup(&val); +} + +static inline void bba_out16(int reg, u16 val) +{ + cpu_to_le16s(&val); + bba_outs(reg, &val, sizeof(val)); +} + +#define bba_in12(reg) (bba_in16(reg) & 0x0fff) +#define bba_out12(reg, val) do { bba_out16(reg, (val)&0x0fff); } while (0) + +static inline void bba_ins_nosel(int reg, void *val, int len) +{ + u32 req; + req = (reg << 8) | 0x80000000; + bba_write(&req, sizeof(req)); + bba_read(val, len); +} + +static void bba_ins(int reg, void *val, int len) +{ + bba_select(); + bba_ins_nosel(reg, val, len); + bba_deselect(); +} + +static inline void bba_outs_nosel(int reg, void *val, int len) +{ + u32 req; + req = (reg << 8) | 0xC0000000; + bba_write(&req, sizeof(req)); + bba_write(val, len); +} + +static inline void bba_outs_nosel_continued(void *val, int len) +{ + bba_write(val, len); +} + +static void bba_outs(int reg, void *val, int len) +{ + bba_select(); + bba_outs_nosel(reg, val, len); + bba_deselect(); +} + + +/* + * Macronix mx98728ec supporting bits. + * + */ + +#define BBA_NCRA 0x00 /* Network Control Register A, RW */ +#define BBA_NCRA_RESET (1<<0) /* RESET */ +#define BBA_NCRA_ST0 (1<<1) /* ST0, Start transmit command/status */ +#define BBA_NCRA_ST1 (1<<2) /* ST1, " */ +#define BBA_NCRA_SR (1<<3) /* SR, Start Receive */ + +#define BBA_NCRB 0x01 /* Network Control Register B, RW */ +#define BBA_NCRB_PR (1<<0) /* PR, Promiscuous Mode */ +#define BBA_NCRB_CA (1<<1) /* CA, Capture Effect Mode */ +#define BBA_NCRB_PM (1<<2) /* PM, Pass Multicast */ +#define BBA_NCRB_PB (1<<3) /* PB, Pass Bad Frame */ +#define BBA_NCRB_AB (1<<4) /* AB, Accept Broadcast */ +#define BBA_NCRB_HBD (1<<5) /* HBD, reserved */ +#define BBA_NCRB_RXINTC0 (1<<6) /* RXINTC, Receive Interrupt Counter */ +#define BBA_NCRB_RXINTC1 (1<<7) /* " */ +#define BBA_NCRB_1_PACKET_PER_INT (0<<6) /* 0 0 */ +#define BBA_NCRB_2_PACKETS_PER_INT (1<<6) /* 0 1 */ +#define BBA_NCRB_4_PACKETS_PER_INT (2<<6) /* 1 0 */ +#define BBA_NCRB_8_PACKETS_PER_INT (3<<6) /* 1 1 */ + +#define BBA_LTPS 0x04 /* Last Transmitted Packet Status, RO */ +#define BBA_LRPS 0x05 /* Last Received Packet Status, RO */ + +#define BBA_IMR 0x08 /* Interrupt Mask Register, RW, 00h */ +#define BBA_IMR_FRAGIM (1<<0) /* FRAGIM, Fragment Counter Int Mask */ +#define BBA_IMR_RIM (1<<1) /* RIM, Receive Interrupt Mask */ +#define BBA_IMR_TIM (1<<2) /* TIM, Transmit Interrupt Mask */ +#define BBA_IMR_REIM (1<<3) /* REIM, Receive Error Interrupt Mask */ +#define BBA_IMR_TEIM (1<<4) /* TEIM, Transmit Error Interrupt Mask */ +#define BBA_IMR_FIFOEIM (1<<5) /* FIFOEIM, FIFO Error Interrupt Mask */ +#define BBA_IMR_BUSEIM (1<<6) /* BUSEIM, BUS Error Interrupt Mask */ +#define BBA_IMR_RBFIM (1<<7) /* RBFIM, RX Buf Full Interrupt Mask */ + +#define BBA_IR 0x09 /* Interrupt Register, RW, 00h */ +#define BBA_IR_FRAGI (1<<0) /* FRAGI, Fragment Counter Interrupt */ +#define BBA_IR_RI (1<<1) /* RI, Receive Interrupt */ +#define BBA_IR_TI (1<<2) /* TI, Transmit Interrupt */ +#define BBA_IR_REI (1<<3) /* REI, Receive Error Interrupt */ +#define BBA_IR_TEI (1<<4) /* TEI, Transmit Error Interrupt */ +#define BBA_IR_FIFOEI (1<<5) /* FIFOEI, FIFO Error Interrupt */ +#define BBA_IR_BUSEI (1<<6) /* BUSEI, BUS Error Interrupt */ +#define BBA_IR_RBFI (1<<7) /* RBFI, RX Buffer Full Interrupt */ + +#define BBA_BP 0x0a/*+0x0b*/ /* Boundary Page Pointer Register */ +#define BBA_TLBP 0x0c/*+0x0d*/ /* TX Low Boundary Page Pointer Register */ +#define BBA_TWP 0x0e/*+0x0f*/ /* Transmit Buf Write Page Pointer Register */ +#define BBA_TRP 0x12/*+0x13*/ /* Transmit Buf Read Page Pointer Register */ +#define BBA_RWP 0x16/*+0x17*/ /* Receive Buffer Write Page Pointer Register */ +#define BBA_RRP 0x18/*+0x19*/ /* Receive Buffer Read Page Pointer Register */ +#define BBA_RHBP 0x1a/*+0x1b*/ /* Receive High Boundary Page Ptr Register */ + +#define BBA_RXINTT 0x14/*+0x15*/ /* Receive Interrupt Timer Register */ + +#define BBA_NAFR_PAR0 0x20 /* Physical Address Register Byte 0 */ +#define BBA_NAFR_PAR1 0x21 /* Physical Address Register Byte 1 */ +#define BBA_NAFR_PAR2 0x22 /* Physical Address Register Byte 2 */ +#define BBA_NAFR_PAR3 0x23 /* Physical Address Register Byte 3 */ +#define BBA_NAFR_PAR4 0x24 /* Physical Address Register Byte 4 */ +#define BBA_NAFR_PAR5 0x25 /* Physical Address Register Byte 5 */ + +#define BBA_NWAYC 0x30 /* NWAY Configuration Register, RW, 84h */ +#define BBA_NWAYC_FD (1<<0) /* FD, Full Duplex Mode */ +#define BBA_NWAYC_PS100 (1<<1) /* PS100/10, Port Select 100/10 */ +#define BBA_NWAYC_ANE (1<<2) /* ANE, Autonegotiation Enable */ +#define BBA_NWAYC_ANS_RA (0x01<<3) /* ANS, Restart Autonegotiation */ +#define BBA_NWAYC_LTE (1<<7) /* LTE, Link Test Enable */ + +#define BBA_GCA 0x32 /* GMAC Configuration A Register, RW, 00h */ +#define BBA_GCA_ARXERRB (1<<3) /* ARXERRB, Accept RX pkt with error */ + +#define BBA_MISC 0x3d /* MISC Control Register 1, RW, 3ch */ +#define BBA_MISC_BURSTDMA (1<<0) +#define BBA_MISC_DISLDMA (1<<1) + +#define BBA_TXFIFOCNT 0x3e/*0x3f*/ /* Transmit FIFO Counter Register */ +#define BBA_WRTXFIFOD 0x48/*-0x4b*/ /* Write TX FIFO Data Port Register */ + +#define BBA_MISC2 0x50 /* MISC Control Register 2, RW, 00h */ +#define BBA_MISC2_HBRLEN0 (1<<0) /* HBRLEN, Host Burst Read Length */ +#define BBA_MISC2_HBRLEN1 (1<<1) /* " */ +#define BBA_MISC2_AUTORCVR (1<<7) /* Auto RX Full Recovery */ + +#define BBA_RX_STATUS_BF (1<<0) +#define BBA_RX_STATUS_CRC (1<<1) +#define BBA_RX_STATUS_FAE (1<<2) +#define BBA_RX_STATUS_FO (1<<3) +#define BBA_RX_STATUS_RW (1<<4) +#define BBA_RX_STATUS_MF (1<<5) +#define BBA_RX_STATUS_RF (1<<6) +#define BBA_RX_STATUS_RERR (1<<7) + +#define BBA_TX_STATUS_CC0 (1<<0) +#define BBA_TX_STATUS_CC1 (1<<1) +#define BBA_TX_STATUS_CC2 (1<<2) +#define BBA_TX_STATUS_CC3 (1<<3) +#define BBA_TX_STATUS_CCMASK (0x0f) +#define BBA_TX_STATUS_CRSLOST (1<<4) +#define BBA_TX_STATUS_UF (1<<5) +#define BBA_TX_STATUS_OWC (1<<6) +#define BBA_TX_STATUS_OWN (1<<7) +#define BBA_TX_STATUS_TERR (1<<7) + +#define BBA_TX_MAX_PACKET_SIZE 1518 /* 14+1500+4 */ +#define BBA_RX_MAX_PACKET_SIZE 1536 /* 6 pages * 256 bytes */ + + +/** + * + * DRIVER NOTES + * + * 1. Packet Memory organization + * + * rx: 15 pages of 256 bytes, 2 full sized packets only (6 pages each) + * tx: through FIFO, not using packet memory + * + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |1|2|3|4|5|6|7|8|9|A|B|C|D|E|F| + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ^ ^ + * | | + * TLBP RHBP + * BP + * + */ + +#define BBA_INIT_TLBP 0x00 +#define BBA_INIT_BP 0x01 +#define BBA_INIT_RHBP 0x0f +#define BBA_INIT_RWP BBA_INIT_BP +#define BBA_INIT_RRP BBA_INIT_BP + +enum { + __BBA_RBFIM_OFF = 0, +}; + +struct bba_descr { +#if defined(__BIG_ENDIAN_BITFIELD) + __u32 status:8, + packet_len:12, + next_packet_ptr:12; +#elif defined(__LITTLE_ENDIAN_BITFIELD) + __u32 next_packet_ptr:12, + packet_len:12, + status:8; +#else +#error "Unsupported byte order." +#endif +} __attribute((packed)); + + +struct bba_private { + spinlock_t lock; + unsigned long flags; +#define BBA_RBFIM_OFF (1<<__BBA_RBFIM_OFF) + + u32 msg_enable; + u8 revid; + u8 __0x04_init[2]; + u8 __0x05_init; + + struct sk_buff *tx_skb; + int rx_work; + + struct task_struct *io_thread; + wait_queue_head_t io_waitq; + + struct net_device *dev; + struct net_device_stats stats; + + struct exi_device *exi_device; +}; + +static int bba_event_handler(struct exi_channel *exi_channel, + unsigned int event, void *dev0); +static int bba_setup_hardware(struct net_device *dev); + +/* + * Opens the network device. + */ +static int bba_open(struct net_device *dev) +{ + struct bba_private *priv = netdev_priv(dev); + int retval; + + /* INTs are triggered on EXI channel 2 */ + retval = exi_event_register(to_exi_channel(BBA_EXI_IRQ_CHANNEL), + EXI_EVENT_IRQ, + priv->exi_device, + bba_event_handler, dev, + (1 << BBA_EXI_CHANNEL)); + if (retval < 0) { + bba_printk(KERN_ERR, "unable to register EXI event %d\n", + EXI_EVENT_IRQ); + goto out; + } + + /* reset the hardware to a known state */ + exi_dev_take(priv->exi_device); + retval = bba_setup_hardware(dev); + exi_dev_give(priv->exi_device); + + /* inform the network layer that we are ready */ + netif_start_queue(dev); +out: + return retval; +} + +/* + * Closes the network device. + */ +static int bba_close(struct net_device *dev) +{ + struct bba_private *priv = netdev_priv(dev); + + /* do not allow more packets to be queued */ + netif_carrier_off(dev); + netif_stop_queue(dev); + + exi_dev_take(priv->exi_device); + + /* stop receiver */ + bba_out8(BBA_NCRA, bba_in8(BBA_NCRA) & ~BBA_NCRA_SR); + + /* mask all interrupts */ + bba_out8(BBA_IMR, 0x00); + + exi_dev_give(priv->exi_device); + + /* unregister exi event */ + exi_event_unregister(to_exi_channel(BBA_EXI_IRQ_CHANNEL), + EXI_EVENT_IRQ); + + return 0; +} + +/* + * Returns the network device statistics. + */ +static struct net_device_stats *bba_get_stats(struct net_device *dev) +{ + struct bba_private *priv = netdev_priv(dev); + + return &priv->stats; +} + +/* + * Starts transmission for a packet. + * We can't do real hardware i/o here. + */ +static int bba_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct bba_private *priv = netdev_priv(dev); + unsigned long flags; + int retval = NETDEV_TX_OK; + + /* we are not able to send packets greater than this */ + if (skb->len > BBA_TX_MAX_PACKET_SIZE) { + dev_kfree_skb(skb); + priv->stats.tx_dropped++; + /* silently drop the package */ + goto out; + } + + spin_lock_irqsave(&priv->lock, flags); + + /* + * If there's no packet pending, store the packet for transmission + * and wake up the io thread. Otherwise, we are busy. + */ + if (!priv->tx_skb) { + priv->tx_skb = skb; + dev->trans_start = jiffies; + wake_up(&priv->io_waitq); + } else { + retval = NETDEV_TX_BUSY; + } + + /* we can only send one packet at a time through the FIFO */ + netif_stop_queue(dev); + + spin_unlock_irqrestore(&priv->lock, flags); + +out: + return retval; +} + +/* + * Updates transmission error statistics. + * Caller holds the device lock. + */ +static int bba_tx_err(u8 status, struct net_device *dev) +{ + struct bba_private *priv = netdev_priv(dev); + int last_tx_errors = priv->stats.tx_errors; + + if (status & BBA_TX_STATUS_TERR) { + if (status & BBA_TX_STATUS_CCMASK) { + priv->stats.collisions += + (status & BBA_TX_STATUS_CCMASK); + priv->stats.tx_errors++; + } + if (status & BBA_TX_STATUS_CRSLOST) { + priv->stats.tx_carrier_errors++; + priv->stats.tx_errors++; + } + if (status & BBA_TX_STATUS_UF) { + priv->stats.tx_fifo_errors++; + priv->stats.tx_errors++; + } + if (status & BBA_TX_STATUS_OWC) { + priv->stats.tx_window_errors++; + priv->stats.tx_errors++; + } + } + + if (last_tx_errors != priv->stats.tx_errors) { + if (netif_msg_tx_err(priv)) { + bba_printk(KERN_DEBUG, "tx errors, status %8.8x.\n", + status); + } + } + return priv->stats.tx_errors; +} + +/* + * Transmits a packet already stored in the driver's internal tx slot. + */ +static int bba_tx(struct net_device *dev) +{ + struct bba_private *priv = netdev_priv(dev); + struct sk_buff *skb; + unsigned long flags; + int retval = NETDEV_TX_OK; + + static u8 pad[ETH_ZLEN] __attribute__ ((aligned(EXI_DMA_ALIGN+1))); + int pad_len; + + exi_dev_take(priv->exi_device); + + /* if the TXFIFO is in use, we'll try it later when free */ + if (bba_in8(BBA_NCRA) & (BBA_NCRA_ST0 | BBA_NCRA_ST1)) { + retval = NETDEV_TX_BUSY; + goto out; + } + + spin_lock_irqsave(&priv->lock, flags); + skb = priv->tx_skb; + priv->tx_skb = NULL; + spin_unlock_irqrestore(&priv->lock, flags); + + /* tell the card about the length of this packet */ + bba_out12(BBA_TXFIFOCNT, skb->len); + + /* + * Store the packet in the TXFIFO, including padding if needed. + * Packet transmission tries to make use of DMA transfers. + */ + + bba_select(); + bba_outs_nosel(BBA_WRTXFIFOD, skb->data, skb->len); + if (skb->len < ETH_ZLEN) { + pad_len = ETH_ZLEN - skb->len; + memset(pad, 0, pad_len); + bba_outs_nosel_continued(pad, pad_len); + } + bba_deselect(); + + /* tell the card to send the packet right now */ + bba_out8(BBA_NCRA, (bba_in8(BBA_NCRA) | BBA_NCRA_ST1) & ~BBA_NCRA_ST0); + + /* update statistics */ + priv->stats.tx_bytes += skb->len; + priv->stats.tx_packets++; + + /* free this packet and remove it from our transmission "queue" */ + dev_kfree_skb(skb); + +out: + exi_dev_give(priv->exi_device); + + return retval; +} + +/* + * Updates reception error statistics. + * Caller has already taken the exi channel. + */ +static int bba_rx_err(u8 status, struct net_device *dev) +{ + struct bba_private *priv = netdev_priv(dev); + int last_rx_errors = priv->stats.rx_errors; + + if (status == 0xff) { + priv->stats.rx_over_errors++; + priv->stats.rx_errors++; + } else { + if (status & BBA_RX_STATUS_RERR) { + if (status & BBA_RX_STATUS_CRC) { + priv->stats.rx_crc_errors++; + priv->stats.rx_errors++; + } + if (status & BBA_RX_STATUS_FO) { + priv->stats.rx_fifo_errors++; + priv->stats.rx_errors++; + } + if (status & BBA_RX_STATUS_RW) { + priv->stats.rx_length_errors++; + priv->stats.rx_errors++; + } + if (status & BBA_RX_STATUS_BF) { + priv->stats.rx_over_errors++; + priv->stats.rx_errors++; + } + if (status & BBA_RX_STATUS_RF) { + priv->stats.rx_length_errors++; + priv->stats.rx_errors++; + } + } + if (status & BBA_RX_STATUS_FAE) { + priv->stats.rx_frame_errors++; + priv->stats.rx_errors++; + } + } + + if (last_rx_errors != priv->stats.rx_errors) { + if (netif_msg_rx_err(priv)) { + bba_printk(KERN_DEBUG, "rx errors, status %8.8x.\n", + status); + } + } + return priv->stats.rx_errors; +} + +/* + * Reception function. Receives up to @budget packets. + */ +static int bba_rx(struct net_device *dev, int budget) +{ + struct bba_private *priv = netdev_priv(dev); + struct sk_buff *skb; + struct bba_descr descr; + int lrps, size; + unsigned long pos, top; + unsigned short rrp, rwp; + int received = 0; + + exi_dev_take(priv->exi_device); + + /* get current receiver pointers */ + rwp = bba_in12(BBA_RWP); + rrp = bba_in12(BBA_RRP); + + while (netif_running(dev) && received < budget && rrp != rwp) { + bba_ins(rrp << 8, &descr, sizeof(descr)); + le32_to_cpus((u32 *) &descr); + + size = descr.packet_len - 4; /* ignore CRC */ + lrps = descr.status; + + /* abort processing in case of errors */ + if (size > BBA_RX_MAX_PACKET_SIZE + 4) { + DBG("packet too big %d", size); + continue; + } + + if ((lrps & (BBA_RX_STATUS_RERR | BBA_RX_STATUS_FAE))) { + DBG("error %x on received packet\n", lrps); + bba_rx_err(lrps, dev); + rwp = bba_in12(BBA_RWP); + rrp = bba_in12(BBA_RRP); + continue; + } + + /* allocate a buffer, omitting the CRC (4 bytes) */ + skb = dev_alloc_skb(size + NET_IP_ALIGN); + if (!skb) { + priv->stats.rx_dropped++; + continue; + } + skb->dev = dev; + skb_reserve(skb, NET_IP_ALIGN); /* align */ + skb_put(skb, size); + + pos = (rrp << 8) + 4; /* skip descriptor */ + top = (BBA_INIT_RHBP + 1) << 8; + + if ((pos + size) < top) { + /* full packet in one chunk */ + bba_ins(pos, skb->data, size); + } else { + /* packet wrapped */ + int chunk_size = top - pos; + + bba_ins(pos, skb->data, chunk_size); + rrp = BBA_INIT_RRP; + bba_ins(rrp << 8, skb->data + chunk_size, + size - chunk_size); + } + + skb->protocol = eth_type_trans(skb, dev); + + dev->last_rx = jiffies; + priv->stats.rx_bytes += size; + priv->stats.rx_packets++; + + netif_rx(skb); + received++; + + /* move read pointer to next packet */ + rrp = descr.next_packet_ptr; + bba_out12(BBA_RRP, rrp); + + /* get write pointer and continue */ + rwp = bba_in12(BBA_RWP); + } + + /* there are no more packets pending if we didn't exhaust our budget */ + if (received < budget) + priv->rx_work = 0; + + /* re-enable RBFI if it was disabled before */ + if (test_and_clear_bit(__BBA_RBFIM_OFF, &priv->flags)) + bba_out8(BBA_IMR, bba_in8(BBA_IMR) | BBA_IMR_RBFIM); + + exi_dev_give(priv->exi_device); + + return received; +} + +/* + * Input/Output thread. Sends and receives packets. + */ +static int bba_io_thread(void *bba_priv) +{ + struct bba_private *priv = bba_priv; +/* struct task_struct *me = current; */ +/* struct sched_param param = { .sched_priority = MAX_RT_PRIO-1 }; */ + +/* sched_setscheduler(me, SCHED_FIFO, ¶m); */ + + set_user_nice(current, -20); + current->flags |= PF_NOFREEZE; + set_current_state(TASK_RUNNING); + + /* + * XXX We currently do not freeze this thread. + * The bba is often used to access the root filesystem. + */ + + while (!kthread_should_stop()) { + /* + * We want to get scheduled at least once every 2 minutes + * to avoid a softlockup spurious message... + * "INFO: task kbbaiod blocked for more than 120 seconds." + */ + wait_event_timeout(priv->io_waitq, + priv->rx_work || priv->tx_skb, 90*HZ); + while (priv->rx_work || priv->tx_skb) { + if (priv->rx_work) + bba_rx(priv->dev, 0x0f); + if (priv->tx_skb) + bba_tx(priv->dev); + } + } + return 0; +} + +/* + * Handles interrupt work from the network device. + * Caller has already taken the exi channel. + */ +static void bba_interrupt(struct net_device *dev) +{ + struct bba_private *priv = netdev_priv(dev); + u8 ir, imr, status, lrps, ltps; + int loops = 0; + + ir = bba_in8(BBA_IR); + imr = bba_in8(BBA_IMR); + status = ir & imr; + + /* close possible races with dev_close */ + if (unlikely(!netif_running(dev))) { + bba_out8(BBA_IR, status); + bba_out8(BBA_IMR, 0x00); + goto out; + } + + while (status) { + bba_out8(BBA_IR, status); + + /* avoid multiple receive buffer full interrupts */ + if (status & BBA_IR_RBFI) { + bba_out8(BBA_IMR, bba_in8(BBA_IMR) & ~BBA_IMR_RBFIM); + set_bit(__BBA_RBFIM_OFF, &priv->flags); + } + + if ((status & (BBA_IR_RI | BBA_IR_RBFI))) { + priv->rx_work = 1; + wake_up(&priv->io_waitq); + } + if ((status & (BBA_IR_TI|BBA_IR_FIFOEI))) { + /* allow more packets to be sent */ + netif_wake_queue(dev); + } + + if ((status & (BBA_IR_RBFI|BBA_IR_REI))) { + lrps = bba_in8(BBA_LRPS); + bba_rx_err(lrps, dev); + } + if (status & BBA_IR_TEI) { + ltps = bba_in8(BBA_LTPS); + bba_tx_err(ltps, dev); + } + + if (status & BBA_IR_FIFOEI) + DBG("FIFOEI\n"); + if (status & BBA_IR_BUSEI) + DBG("BUSEI\n"); + if (status & BBA_IR_FRAGI) + DBG("FRAGI\n"); + + ir = bba_in8(BBA_IR); + imr = bba_in8(BBA_IMR); + status = ir & imr; + + loops++; + } + + if (loops > 3) + DBG("a lot of interrupt work (%d loops)\n", loops); + + /* wake up xmit queue in case transmitter is idle */ + if ((bba_in8(BBA_NCRA) & (BBA_NCRA_ST0 | BBA_NCRA_ST1)) == 0) + netif_wake_queue(dev); + +out: + return; +} + +/* + * Retrieves the MAC address of the adapter. + * Caller has already taken the exi channel. + */ +static void bba_retrieve_ether_addr(struct net_device *dev) +{ + bba_ins(BBA_NAFR_PAR0, dev->dev_addr, ETH_ALEN); + if (!is_valid_ether_addr(dev->dev_addr)) + random_ether_addr(dev->dev_addr); +} + +/* + * Resets the hardware to a known state. + * Caller has already taken the exi channel. + */ +static void bba_reset_hardware(struct net_device *dev) +{ + struct bba_private *priv = netdev_priv(dev); + + /* unknown, mx register 0x60 */ + bba_out8(0x60, 0); + udelay(1000); + + /* unknown, command register 0x0f */ + bba_cmd_in8_slow(0x0f); + udelay(1000); + + /* software reset (write 1 then write 0) */ + bba_out8(BBA_NCRA, BBA_NCRA_RESET); + udelay(100); + bba_out8(BBA_NCRA, 0); + + /* unknown, command register 0x01 */ + /* XXX obtain bits needed for challenge/response calculation later */ + priv->revid = bba_cmd_in8(0x01); + + /* unknown, command registers 0x04, 0x05 */ + bba_cmd_outs(0x04, priv->__0x04_init, 2); + bba_cmd_out8(0x05, priv->__0x05_init); + + /* + * These initializations seem to limit the final port speed to 10Mbps + * half duplex. Bypassing them, allows one to set other port speeds. + * But, remember that the bba spi-like bus clock operates at 32MHz. + * ---Albert Herranz + */ + + /* unknown, mx registers 0x5b, 0x5c, 0x5e */ + bba_out8(0x5b, bba_in8(0x5b) & ~(1 << 7)); + bba_out8(0x5e, 1); /* without this the BBA goes at half the speed */ + bba_out8(0x5c, bba_in8(0x5c) | 4); + udelay(1000); + + /* accept broadcast, assert int for every packet received */ + bba_out8(BBA_NCRB, BBA_NCRB_AB | BBA_NCRB_1_PACKET_PER_INT); + + /* setup receive interrupt time out, in 40ns units */ + bba_out8(BBA_RXINTT, 0x00); + bba_out8(BBA_RXINTT+1, 0x06); /* 0x0600 = 61us */ + + /* auto RX full recovery */ + bba_out8(BBA_MISC2, BBA_MISC2_AUTORCVR); + + /* initialize packet memory layout */ + bba_out12(BBA_TLBP, BBA_INIT_TLBP); + bba_out12(BBA_BP, BBA_INIT_BP); + bba_out12(BBA_RHBP, BBA_INIT_RHBP); + + /* set receive page pointers */ + bba_out12(BBA_RWP, BBA_INIT_RWP); + bba_out12(BBA_RRP, BBA_INIT_RRP); + + /* packet memory won't contain packets with RW, FO, CRC errors */ + bba_out8(BBA_GCA, BBA_GCA_ARXERRB); +} + +/* + * Prepares the hardware for operation. + * Caller has already taken the exi channel. + */ +static int bba_setup_hardware(struct net_device *dev) +{ + /* reset hardware to a sane state */ + bba_reset_hardware(dev); + + /* start receiver */ + bba_out8(BBA_NCRA, BBA_NCRA_SR); + + /* clear all interrupts */ + bba_out8(BBA_IR, 0xFF); + + /* enable all interrupts */ + bba_out8(BBA_IMR, 0xFF & ~(BBA_IMR_FIFOEIM /*| BBA_IMR_REIM*/)); + + /* unknown, short command registers 0x02 */ + /* XXX enable interrupts on the EXI glue logic */ + bba_cmd_out8(0x02, BBA_CMD_IR_MASKNONE); + + /* DO NOT clear interrupts on the EXI glue logic !!! */ + /* we need that initial interrupts for the challenge/response */ + + return 0; /* OK */ +} + +/* + * Calculates a response for a given challenge. + */ +static unsigned long bba_calc_response(unsigned long val, + struct bba_private *priv) +{ + u8 revid_0, revid_eth_0, revid_eth_1; + u8 i0, i1, i2, i3; + u8 c0, c1, c2, c3; + + revid_0 = priv->revid; + revid_eth_0 = priv->__0x04_init[0]; + revid_eth_1 = priv->__0x04_init[1]; + + i0 = val >> 24; + i1 = val >> 16; + i2 = val >> 8; + i3 = val; + + c0 = (i0 + i1 * 0xc1 + 0x18 + revid_0) ^ (i3 * i2 + 0x90); + c1 = (i1 + i2 + 0x90) ^ (c0 + i0 - 0xc1); + c2 = (i2 + 0xc8) ^ (c0 + ((revid_eth_0 + revid_0 * 0x23) ^ 0x19)); + c3 = (i0 + 0xc1) ^ (i3 + ((revid_eth_1 + 0xc8) ^ 0x90)); + + return (c0 << 24) | (c1 << 16) | (c2 << 8) | c3; +} + +/* + * Handles IRQ events from the exi layer. + * + * We are called from softirq context, and with the exi channel kindly taken + * for us. We can also safely do exi transfers of less than 32 bytes, which + * are guaranteed to not sleep by the exi layer. + */ +static int bba_event_handler(struct exi_channel *exi_channel, + unsigned int event, void *dev0) +{ + struct net_device *dev = (struct net_device *)dev0; + struct bba_private *priv = netdev_priv(dev); + register u8 status, mask; + + /* XXX mask all EXI glue interrupts */ + bba_cmd_out8(0x02, BBA_CMD_IR_MASKALL); + + /* get interrupt status from EXI glue */ + status = bba_cmd_in8(0x03); + + /* start with the usual case */ + mask = (1<<7); + + /* normal interrupt from the macronix chip */ + if (status & mask) { + /* call our interrupt handler */ + bba_interrupt(dev); + goto out; + } + + /* "killing" interrupt, try to not get one of these! */ + mask >>= 1; + if (status & mask) { + DBG("bba: killing interrupt!\n"); + /* reset the adapter so that we can continue working */ + bba_setup_hardware(dev); + goto out; + } + + /* command error interrupt, haven't seen one yet */ + mask >>= 1; + if (status & mask) + goto out; + + /* challenge/response interrupt */ + mask >>= 1; + if (status & mask) { + unsigned long response; + unsigned long challenge; + + /* kids, don't do it without an adult present */ + bba_cmd_out8(0x05, priv->__0x05_init); + bba_cmd_ins(0x08, &challenge, sizeof(challenge)); + response = bba_calc_response(challenge, priv); + bba_cmd_outs(0x09, &response, sizeof(response)); + + goto out; + } + + /* challenge/response status interrupt */ + mask >>= 1; + if (status & mask) { + /* better get a "1" here ... */ + u8 result = bba_cmd_in8(0x0b); + if (result != 1) { + bba_printk(KERN_DEBUG, + "challenge failed! (result=%d)\n", result); + } + goto out; + } + + /* should not happen, treat as normal interrupt in any case */ + DBG("bba: unknown interrupt type = %d\n", status); + +out: + /* assert interrupt */ + bba_cmd_out8(0x03, mask); + + /* enable interrupts again */ + bba_cmd_out8(0x02, BBA_CMD_IR_MASKNONE); + + return 1; +} + +static struct net_device *bba_dev; + +static inline void bba_select(void) +{ + struct bba_private *priv = netdev_priv(bba_dev); + exi_dev_select(priv->exi_device); + +} + +static inline void bba_deselect(void) +{ + struct bba_private *priv = netdev_priv(bba_dev); + exi_dev_deselect(priv->exi_device); +} + +static inline void bba_read(void *data, size_t len) +{ + struct bba_private *priv = netdev_priv(bba_dev); + return exi_dev_read(priv->exi_device, data, len); +} + +static inline void bba_write(void *data, size_t len) +{ + struct bba_private *priv = netdev_priv(bba_dev); + return exi_dev_write(priv->exi_device, data, len); +} + +static const struct net_device_ops bba_netdev_ops = { + .ndo_open = bba_open, + .ndo_stop = bba_close, + .ndo_start_xmit = bba_start_xmit, + .ndo_get_stats = bba_get_stats, + .ndo_change_mtu = eth_change_mtu, + .ndo_validate_addr = eth_validate_addr, + .ndo_set_mac_address = eth_mac_addr, +}; + +/* + * Initializes a BroadBand Adapter device. + */ +static int __devinit bba_init_device(struct exi_device *exi_device) +{ + struct net_device *dev; + struct bba_private *priv; + int err; + + /* allocate a network device */ + dev = alloc_etherdev(sizeof(*priv)); + if (!dev) { + bba_printk(KERN_ERR, "unable to allocate net device\n"); + err = -ENOMEM; + goto err_out; + } + SET_NETDEV_DEV(dev, &exi_device->dev); + + /* we use the event system from the EXI driver, so no irq here */ + dev->irq = 0; + + /* network device hooks */ + dev->netdev_ops = &bba_netdev_ops; + + priv = netdev_priv(dev); + priv->dev = dev; + priv->exi_device = exi_device; + + spin_lock_init(&priv->lock); + + /* initialization values */ + priv->revid = 0xf0; + priv->__0x04_init[0] = 0xd1; + priv->__0x04_init[1] = 0x07; + priv->__0x05_init = 0x4e; + + /* i/o artifacts */ + priv->tx_skb = NULL; + priv->rx_work = 0; + init_waitqueue_head(&priv->io_waitq); + priv->io_thread = kthread_run(bba_io_thread, priv, "kbbaiod"); + + /* the hardware can't do multicast */ + dev->flags &= ~IFF_MULTICAST; + + exi_set_drvdata(exi_device, dev); + if (bba_dev) + free_netdev(bba_dev); + bba_dev = dev; + + /* we need to retrieve the MAC address before registration */ + exi_dev_take(priv->exi_device); + bba_reset_hardware(dev); + bba_retrieve_ether_addr(dev); + exi_dev_give(priv->exi_device); + + /* this makes our device available to the kernel */ + err = register_netdev(dev); + if (err) { + bba_printk(KERN_ERR, "cannot register net device, aborting.\n"); + goto err_out_free_dev; + } + + return 0; + +err_out_free_dev: + exi_set_drvdata(exi_device, NULL); + free_netdev(dev); + bba_dev = NULL; + +err_out: + return err; +} + +/* + * Removes a BroadBand Adapter device from the system. + */ +static void __devexit bba_remove(struct exi_device *exi_device) +{ + struct net_device *dev = (struct net_device *) + exi_get_drvdata(exi_device); + struct bba_private *priv; + + if (dev) { + priv = netdev_priv(dev); + + kthread_stop(priv->io_thread); + + unregister_netdev(dev); + free_netdev(dev); + exi_set_drvdata(exi_device, NULL); + bba_dev = NULL; + } + exi_device_put(exi_device); +} + +/* + * Probes for a BroadBand Adapter device. + * Actually, the exi layer has already probed for us. + */ +static int __devinit bba_probe(struct exi_device *exi_device) +{ + int ret = -ENODEV; + + if (exi_device_get(exi_device)) + ret = bba_init_device(exi_device); + + return ret; +} + + +static struct exi_device_id bba_eid_table[] = { + [0] = { + .channel = BBA_EXI_CHANNEL, + .device = BBA_EXI_DEVICE, + .id = BBA_EXI_ID + }, + { .id = 0 } +}; + +static struct exi_driver bba_driver = { + .name = "bba", + .eid_table = bba_eid_table, + .frequency = BBA_EXI_FREQ, + .probe = bba_probe, + .remove = bba_remove, +}; + +/** + * bba_init_module - driver initialization routine + * + * Initializes the BroadBand Adapter driver module. + * + */ +static int __init bba_init_module(void) +{ + bba_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION, + bba_driver_version); + + return exi_driver_register(&bba_driver); +} + +/** + * bba_exit_module - driver exit routine + * + * Removes the BroadBand Adapter driver module. + * + */ +static void __exit bba_exit_module(void) +{ + exi_driver_unregister(&bba_driver); +} + +module_init(bba_init_module); +module_exit(bba_exit_module); + +MODULE_AUTHOR(DRV_AUTHOR); +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_LICENSE("GPL"); + diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c index ca5ca5a..6a11527 100644 --- a/drivers/net/usb/usbnet.c +++ b/drivers/net/usb/usbnet.c @@ -586,7 +586,9 @@ static int unlink_urbs (struct usbnet *dev, struct sk_buff_head *q) // during some PM-driven resume scenarios, // these (async) unlinks complete immediately + spin_unlock(&q->lock); retval = usb_unlink_urb (urb); + spin_lock(&q->lock); if (retval != -EINPROGRESS && retval != 0) devdbg (dev, "unlink urb err, %d", retval); else diff --git a/drivers/net/wireless/b43/Kconfig b/drivers/net/wireless/b43/Kconfig index 9da5373..d5439f5 100644 --- a/drivers/net/wireless/b43/Kconfig +++ b/drivers/net/wireless/b43/Kconfig @@ -146,3 +146,14 @@ config B43_FORCE_PIO Say N! This is only for debugging the PIO engine code. You do _NOT_ want to enable this. + +config B43_DEFAULT_QOS_OFF + bool "Start with QoS disabled" + depends on B43 + ---help--- + This will start the driver with Quality Of Service disabled. + + Say Y if you are using an OpenFWWF firmware without QoS support + and building the b43 driver in-kernel. + Otherwise, say N. + diff --git a/drivers/net/wireless/b43/main.c b/drivers/net/wireless/b43/main.c index d605634..fb25139 100644 --- a/drivers/net/wireless/b43/main.c +++ b/drivers/net/wireless/b43/main.c @@ -68,6 +68,11 @@ MODULE_LICENSE("GPL"); MODULE_FIRMWARE(B43_SUPPORTED_FIRMWARE_ID); +#ifdef CONFIG_B43_DEFAULT_QOS_OFF +#define B43_QOS_DEFAULT 0 +#else +#define B43_QOS_DEFAULT 1 +#endif static int modparam_bad_frames_preempt; module_param_named(bad_frames_preempt, modparam_bad_frames_preempt, int, 0444); @@ -90,7 +95,7 @@ static int modparam_hwtkip; module_param_named(hwtkip, modparam_hwtkip, int, 0444); MODULE_PARM_DESC(hwtkip, "Enable hardware tkip."); -static int modparam_qos = 1; +static int modparam_qos = B43_QOS_DEFAULT; module_param_named(qos, modparam_qos, int, 0444); MODULE_PARM_DESC(qos, "Enable QOS support (default on)"); diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 3c20dae..c8cb835 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -573,6 +573,15 @@ config RTC_DRV_AB3100 Select this to enable the ST-Ericsson AB3100 Mixed Signal IC RTC support. This chip contains a battery- and capacitor-backed RTC. +config RTC_DRV_GCN + bool "Nintendo GameCube/Wii Real Time Clock and SRAM" + depends on GAMECUBE_EXI + default y + help + If you say yes to this option, support will be included for the + Real Time Clock and SRAM of the Nintendo GameCube/Wii. + + If in doubt, say Y here. comment "on-CPU RTC drivers" diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index aa3fbd5..742ea6d 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -43,6 +43,7 @@ obj-$(CONFIG_RTC_DRV_EFI) += rtc-efi.o obj-$(CONFIG_RTC_DRV_EP93XX) += rtc-ep93xx.o obj-$(CONFIG_RTC_DRV_FM3130) += rtc-fm3130.o obj-$(CONFIG_RTC_DRV_GENERIC) += rtc-generic.o +obj-$(CONFIG_RTC_DRV_GCN) += rtc-gcn.o obj-$(CONFIG_RTC_DRV_ISL1208) += rtc-isl1208.o obj-$(CONFIG_RTC_DRV_M41T80) += rtc-m41t80.o obj-$(CONFIG_RTC_DRV_M41T94) += rtc-m41t94.o diff --git a/drivers/rtc/rtc-gcn.c b/drivers/rtc/rtc-gcn.c new file mode 100644 index 0000000..d5bae5c --- /dev/null +++ b/drivers/rtc/rtc-gcn.c @@ -0,0 +1,339 @@ +/* + * drivers/rtc/rtc-gcn.c + * + * Nintendo GameCube/Wii RTC/SRAM driver + * Copyright (C) 2004-2009 The GameCube Linux Team + * Copyright (C) 2005,2008,2009 Albert Herranz + * + * Based on gamecube_time.c from Torben Nielsen. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include + +#define DRV_MODULE_NAME "rtc-gcn" +#define DRV_DESCRIPTION "Nintendo GameCube/Wii RTC/SRAM driver" +#define DRV_AUTHOR "Torben Nielsen, " \ + "Albert Herranz" + +static char gcnrtc_driver_version[] = "1.0i"; + +#define drv_printk(level, format, arg...) \ + printk(level DRV_MODULE_NAME ": " format , ## arg) + + +#define RTC_EXI_GCN_ID 0xffff1698 +#define RTC_EXI_RVL_ID 0xfffff308 + +#define RTC_EXI_CHANNEL 0 +#define RTC_EXI_DEVICE 1 +#define RTC_EXI_FREQ 3 /* 8MHz */ + +#define RTC_OFFSET 946684800L + + +struct gcn_sram { + u16 csum1; + u16 csum2; + u32 ead0; + u32 ead1; + int bias; + s8 horz_display_offset; + u8 ntd; + u8 language; + u8 flags; + u8 reserved[44]; +}; + +struct gcnrtc_drvdata { + spinlock_t lock; + struct exi_device *dev; + + struct rtc_device *rtc_dev; + + struct gcn_sram sram; +}; + +static struct gcnrtc_drvdata gcnrtc_drvdata; + +/* + * Hardware interfaces. + * + */ + +/* + * Loads the SRAM contents. + * Context: user. + */ +static void sram_load(struct exi_device *dev) +{ + struct gcnrtc_drvdata *drvdata = exi_get_drvdata(dev); + struct gcn_sram *sram = &drvdata->sram; + u32 req; + + exi_dev_take(dev); + + /* select the SRAM device */ + exi_dev_select(dev); + + /* send the appropriate command */ + req = 0x20000100; + exi_dev_write(dev, &req, sizeof(req)); + + /* read the SRAM data */ + exi_dev_read(dev, sram, sizeof(*sram)); + + /* deselect the SRAM device */ + exi_dev_deselect(dev); + + exi_dev_give(dev); + + return; +} + +/* + * Gets the hardware clock date and time. + * Context: user. + */ +static unsigned long gcnrtc_read_time(struct exi_device *dev) +{ + unsigned long a = 0; + + exi_dev_take(dev); + + /* select the SRAM device */ + exi_dev_select(dev); + + /* send the appropriate command */ + a = 0x20000000; + exi_dev_write(dev, &a, sizeof(a)); + + /* read the time and date value */ + exi_dev_read(dev, &a, sizeof(a)); + + /* deselect the RTC device */ + exi_dev_deselect(dev); + + exi_dev_give(dev); + + return a; +} + +/* + * Sets the hardware clock date and time to @aval. + * Context: user, interrupt (adjtimex). + */ +static int gcnrtc_write_time(struct exi_device *dev, unsigned long aval) +{ + u32 req; + int retval; + + /* + * We may get called from the timer interrupt. In that case, + * we could fail if the exi channel used to access the RTC + * is busy. If this happens, we just return an error. The timer + * interrupt code is prepared to deal with such case. + */ + + retval = exi_dev_try_take(dev); + if (!retval) { + /* select the RTC device */ + exi_dev_select(dev); + + /* send the appropriate command */ + req = 0xa0000000; + exi_dev_write(dev, &req, sizeof(req)); + + /* set the new time and date value */ + exi_dev_write(dev, &aval, sizeof(aval)); + + /* deselect the RTC device */ + exi_dev_deselect(dev); + + exi_dev_give(dev); + } + return retval; +} + +/* + * Platform time functions. + * + */ + +/* + * Platform specific function to return the current date and time. + */ +static void gcnrtc_plat_rtc_get_time(struct rtc_time *t) +{ + struct gcnrtc_drvdata *drvdata = &gcnrtc_drvdata; + unsigned long nowtime; + + if (!drvdata->dev) + return; + + nowtime = gcnrtc_read_time(drvdata->dev) + + drvdata->sram.bias + RTC_OFFSET; + rtc_time_to_tm(nowtime, t); +} + +/* + * Platform specific function to set the current date and time. + * + */ +static int gcnrtc_plat_rtc_set_time(struct rtc_time *t) +{ + struct gcnrtc_drvdata *drvdata = &gcnrtc_drvdata; + unsigned long nowtime; + + if (!drvdata->dev) + return -ENODEV; + + rtc_tm_to_time(t, &nowtime); + return gcnrtc_write_time(drvdata->dev, + nowtime - RTC_OFFSET - drvdata->sram.bias); +} + +/* + * RTC class driver. + * + */ + +/* + * + */ +static int gcnrtc_rtc_read_time(struct device *dev, struct rtc_time *t) +{ + gcnrtc_plat_rtc_get_time(t); + return 0; +} + +/* + * + */ +static int gcnrtc_rtc_set_time(struct device *dev, struct rtc_time *t) +{ + return gcnrtc_plat_rtc_set_time(t); +} + +static const struct rtc_class_ops gcnrtc_ops = { + .read_time = gcnrtc_rtc_read_time, + .set_time = gcnrtc_rtc_set_time, +}; + + +/* + * EXI driver. + * + */ + +/* + * + */ +static int gcnrtc_probe(struct exi_device *dev) +{ + struct gcnrtc_drvdata *drvdata = &gcnrtc_drvdata; + unsigned long flags; + int retval = -ENODEV; + + if (exi_device_get(dev)) { + spin_lock_init(&drvdata->lock); + + exi_set_drvdata(dev, drvdata); + drvdata->dev = dev; + + memset(&drvdata->sram, 0, sizeof(struct gcn_sram)); + sram_load(dev); + + spin_lock_irqsave(&drvdata->lock, flags); + ppc_md.set_rtc_time = gcnrtc_plat_rtc_set_time; + ppc_md.get_rtc_time = gcnrtc_plat_rtc_get_time; + spin_unlock_irqrestore(&drvdata->lock, flags); + + drvdata->rtc_dev = rtc_device_register(DRV_MODULE_NAME, + &dev->dev, + &gcnrtc_ops, + THIS_MODULE); + retval = 0; + } + + return retval; +} + +/* + * + */ +static void gcnrtc_remove(struct exi_device *dev) +{ + struct gcnrtc_drvdata *drvdata = exi_get_drvdata(dev); + unsigned long flags; + + if (drvdata) { + spin_lock_irqsave(&drvdata->lock, flags); + ppc_md.set_rtc_time = NULL; + ppc_md.get_rtc_time = NULL; + spin_unlock_irqrestore(&drvdata->lock, flags); + + if (!IS_ERR(drvdata->rtc_dev)) + rtc_device_unregister(drvdata->rtc_dev); + } + exi_device_put(dev); +} + + +static struct exi_device_id gcnrtc_eid_table[] = { + { + .channel = RTC_EXI_CHANNEL, + .device = RTC_EXI_DEVICE, + .id = RTC_EXI_GCN_ID + }, + { + .channel = RTC_EXI_CHANNEL, + .device = RTC_EXI_DEVICE, + .id = RTC_EXI_RVL_ID + }, + { }, +}; + +static struct exi_driver gcnrtc_driver = { + .name = DRV_MODULE_NAME, + .eid_table = gcnrtc_eid_table, + .frequency = RTC_EXI_FREQ, + .probe = gcnrtc_probe, + .remove = gcnrtc_remove, +}; + + +/* + * + */ +static int __init gcnrtc_init_module(void) +{ + drv_printk(KERN_INFO, "%s - version %s\n", + DRV_DESCRIPTION, gcnrtc_driver_version); + + return exi_driver_register(&gcnrtc_driver); +} + +/* + * + */ +static void __exit gcnrtc_exit_module(void) +{ + exi_driver_unregister(&gcnrtc_driver); +} + +module_init(gcnrtc_init_module); +module_exit(gcnrtc_exit_module); + +MODULE_AUTHOR(DRV_AUTHOR); +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_LICENSE("GPL"); diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index e522572..c9e405c 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -1477,4 +1477,17 @@ config SERIAL_BCM63XX_CONSOLE If you have enabled the serial port on the bcm63xx CPU you can make it the console by answering Y to this option. +config SERIAL_USBGECKO + bool "USBGecko adapter on the Nintendo GameCube/Wii" + depends on GAMECUBE_EXI + select SERIAL_CORE + help + This is a driver for the USB Gecko adapter for the Nintendo GameCube + and Wii gaming consoles. It provides a console and a tty interface. + + If you have an adapter like this, say Y here, otherwise say N. + + To compile this driver as a module, choose M here: the + module will be called usbgecko. + endmenu diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile index d21d5dd..5a1feab 100644 --- a/drivers/serial/Makefile +++ b/drivers/serial/Makefile @@ -81,3 +81,4 @@ obj-$(CONFIG_SERIAL_KS8695) += serial_ks8695.o obj-$(CONFIG_KGDB_SERIAL_CONSOLE) += kgdboc.o obj-$(CONFIG_SERIAL_QE) += ucc_uart.o obj-$(CONFIG_SERIAL_TIMBERDALE) += timbuart.o +obj-$(CONFIG_SERIAL_USBGECKO) += usbgecko.o diff --git a/drivers/serial/usbgecko.c b/drivers/serial/usbgecko.c new file mode 100644 index 0000000..223890d --- /dev/null +++ b/drivers/serial/usbgecko.c @@ -0,0 +1,597 @@ +/* + * drivers/serial/usbgecko.c + * + * Console and TTY driver for the USB Gecko adapter. + * Copyright (C) 2008-2009 The GameCube Linux Team + * Copyright (C) 2008,2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#define UG_DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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, ¶m); + set_task_state(current, TASK_RUNNING); + + chunk = 0; + while (!kthread_should_stop()) { + count = 0; + adapter = tty->driver_data; + if (adapter) + count = ug_safe_getc(adapter, &ch); + set_task_state(current, TASK_INTERRUPTIBLE); + if (count) { + tty_insert_flip_char(tty, ch, TTY_NORMAL); + if (chunk++ > max_outstanding) { + tty_flip_buffer_push(tty); + chunk = 0; + } + } else { + if (chunk) { + tty_flip_buffer_push(tty); + chunk = 0; + } + schedule_timeout(1); + } + set_task_state(current, TASK_RUNNING); + } + + return 0; +} + +static int ug_tty_open(struct tty_struct *tty, struct file *filp) +{ + struct ug_adapter *adapter; + int index; + int retval = 0; + + index = tty->index; + adapter = &ug_adapters[index]; + + mutex_lock(&adapter->mutex); + + if (!adapter->exi_device) { + mutex_unlock(&adapter->mutex); + return -ENODEV; + } + + if (!adapter->refcnt) { + adapter->poller = kthread_run(ug_tty_poller, tty, "kugtty"); + if (IS_ERR(adapter->poller)) { + drv_printk(KERN_ERR, "error creating poller thread\n"); + mutex_unlock(&adapter->mutex); + return -ENOMEM; + } + } + + adapter->refcnt++; + tty->driver_data = adapter; + + mutex_unlock(&adapter->mutex); + + return retval; +} + +static void ug_tty_close(struct tty_struct *tty, struct file *filp) +{ + struct ug_adapter *adapter; + int index; + + index = tty->index; + adapter = &ug_adapters[index]; + + mutex_lock(&adapter->mutex); + + adapter->refcnt--; + if (!adapter->refcnt) { + if (!IS_ERR(adapter->poller)) + kthread_stop(adapter->poller); + adapter->poller = ERR_PTR(-EINVAL); + tty->driver_data = NULL; + } + + mutex_unlock(&adapter->mutex); +} + +static int ug_tty_write(struct tty_struct *tty, + const unsigned char *buf, int count) +{ + struct ug_adapter *adapter = tty->driver_data; + char *b = (char *)buf; + int index; + int i; + + if (!adapter) + return -ENODEV; + + index = tty->index; + adapter = &ug_adapters[index]; + for (i = 0; i < count; i++) + ug_safe_putc(adapter, *b++); + return count; +} + +static int ug_tty_write_room(struct tty_struct *tty) +{ + return 0x123; /* whatever */ +} + +static int ug_tty_chars_in_buffer(struct tty_struct *tty) +{ + return 0; /* unbuffered */ +} + + +static const struct tty_operations ug_tty_ops = { + .open = ug_tty_open, + .close = ug_tty_close, + .write = ug_tty_write, + .write_room = ug_tty_write_room, + .chars_in_buffer = ug_tty_chars_in_buffer, +}; + + +static int ug_tty_init(void) +{ + struct tty_driver *driver; + int retval; + + driver = alloc_tty_driver(2); + if (!driver) + return -ENOMEM; + driver->name = DRV_MODULE_NAME "con"; + driver->major = TTY_MAJOR; + driver->minor_start = 64; + driver->type = TTY_DRIVER_TYPE_SYSCONS; + driver->init_termios = tty_std_termios; + tty_set_operations(driver, &ug_tty_ops); + retval = tty_register_driver(driver); + if (retval) { + put_tty_driver(driver); + return retval; + } + ug_tty_driver = driver; + return 0; +} + +static void ug_tty_exit(void) +{ + struct tty_driver *driver = ug_tty_driver; + + ug_tty_driver = NULL; + if (driver) { + tty_unregister_driver(driver); + put_tty_driver(driver); + } +} + + + +/* + * + * EXI layer interface. + */ + +/* + * + */ +static int ug_probe(struct exi_device *exi_device) +{ + struct console *console; + struct ug_adapter *adapter; + unsigned int slot; + + /* don't try to drive a device which already has a real identifier */ + if (exi_device->eid.id != EXI_ID_NONE) + return -ENODEV; + + if (!ug_check_adapter(exi_device)) + return -ENODEV; + + slot = to_channel(exi_get_exi_channel(exi_device)); + console = &ug_consoles[slot]; + adapter = console->data; + + drv_printk(KERN_INFO, "USB Gecko detected in memcard slot-%c\n", + 'A'+slot); + + adapter->poller = ERR_PTR(-EINVAL); + mutex_init(&adapter->mutex); + adapter->refcnt = 0; + + adapter->exi_device = exi_device_get(exi_device); + exi_set_drvdata(exi_device, adapter); + register_console(console); + + ug_tty_init(); + + return 0; +} + +/* + * Makes unavailable the USB Gecko adapter identified by the EXI device + * `exi_device'. + */ +static void ug_remove(struct exi_device *exi_device) +{ + struct console *console; + struct ug_adapter *adapter; + unsigned int slot; + + slot = to_channel(exi_get_exi_channel(exi_device)); + console = &ug_consoles[slot]; + adapter = console->data; + + if (adapter->refcnt) + drv_printk(KERN_ERR, "adapter removed while in use!\n"); + + ug_tty_exit(); + + unregister_console(console); + exi_set_drvdata(exi_device, NULL); + adapter->exi_device = NULL; + exi_device_put(exi_device); + + mutex_destroy(&adapter->mutex); + + drv_printk(KERN_INFO, "USB Gecko removed from memcard slot-%c\n", + 'A'+slot); +} + +static struct exi_device_id ug_eid_table[] = { + [0] = { + .channel = UG_SLOTA_CHANNEL, + .device = UG_SLOTA_DEVICE, + .id = EXI_ID_NONE, + }, + [1] = { + .channel = UG_SLOTB_CHANNEL, + .device = UG_SLOTB_DEVICE, + .id = EXI_ID_NONE, + }, + {.id = 0} +}; + +static struct exi_driver ug_exi_driver = { + .name = DRV_MODULE_NAME, + .eid_table = ug_eid_table, + .frequency = UG_SPI_CLK_IDX, + .probe = ug_probe, + .remove = ug_remove, +}; + + +/* + * + * Module interface. + */ + +static int __init ug_init_module(void) +{ + drv_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION, + ug_driver_version); + + return exi_driver_register(&ug_exi_driver); +} + +static void __exit ug_exit_module(void) +{ + exi_driver_unregister(&ug_exi_driver); +} + +module_init(ug_init_module); +module_exit(ug_exit_module); + +MODULE_AUTHOR(DRV_AUTHOR); +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_LICENSE("GPL"); + diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index 2407508..0a2a7f4 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -22,6 +22,8 @@ config USB_ARCH_HAS_HCD default y if PCMCIA && !M32R # sl811_cs default y if ARM # SL-811 default y if SUPERH # r8a66597-hcd + default y if WII # rvl-sthcd + default y if STARLET_IOS # rvl-sthcd default PCI # many non-PCI SOC chips embed OHCI diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index be3c9b8..7c782a7 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_USB_SL811_HCD) += host/ obj-$(CONFIG_USB_ISP1362_HCD) += host/ obj-$(CONFIG_USB_U132_HCD) += host/ obj-$(CONFIG_USB_R8A66597_HCD) += host/ +obj-$(CONFIG_USB_WII_HCD) += host/ obj-$(CONFIG_USB_HWA_HCD) += host/ obj-$(CONFIG_USB_ISP1760_HCD) += host/ diff --git a/drivers/usb/core/buffer.c b/drivers/usb/core/buffer.c index 3ba2fff..db5ee6b 100644 --- a/drivers/usb/core/buffer.c +++ b/drivers/usb/core/buffer.c @@ -115,6 +115,9 @@ void *hcd_buffer_alloc( return kmalloc(size, mem_flags); } + /* make sure that we allocate correctly aligned dma memory */ + size = _ALIGN_UP(size, dma_get_cache_alignment()); + for (i = 0; i < HCD_BUFFER_POOLS; i++) { if (size <= pool_max [i]) return dma_pool_alloc(hcd->pool [i], mem_flags, dma); @@ -141,6 +144,9 @@ void hcd_buffer_free( return; } + /* account for the real size */ + size = _ALIGN_UP(size, dma_get_cache_alignment()); + for (i = 0; i < HCD_BUFFER_POOLS; i++) { if (size <= pool_max [i]) { dma_pool_free(hcd->pool [i], addr, dma); diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 24e6205..fddf958 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -1212,10 +1212,10 @@ EXPORT_SYMBOL_GPL(usb_hcd_unlink_urb_from_ep); * */ -static int hcd_alloc_coherent(struct usb_bus *bus, - gfp_t mem_flags, dma_addr_t *dma_handle, - void **vaddr_handle, size_t size, - enum dma_data_direction dir) +static int hcd_alloc_local_mem(struct usb_bus *bus, + gfp_t mem_flags, dma_addr_t *dma_handle, + void **vaddr_handle, size_t size, + enum dma_data_direction dir) { unsigned char *vaddr; @@ -1242,7 +1242,7 @@ static int hcd_alloc_coherent(struct usb_bus *bus, return 0; } -static void hcd_free_coherent(struct usb_bus *bus, dma_addr_t *dma_handle, +static void hcd_free_local_mem(struct usb_bus *bus, dma_addr_t *dma_handle, void **vaddr_handle, size_t size, enum dma_data_direction dir) { @@ -1259,6 +1259,138 @@ static void hcd_free_coherent(struct usb_bus *bus, dma_addr_t *dma_handle, *dma_handle = 0; } +/* + * HCD_BOUNCE_DMA_MEM exists for platforms that have weird + * constraints accessing DMA coherent memory. + * In this case, buffer allocations are satisfied from normal memory + * and, only at the very last moment, before the actual transfer, + * buffers are copied to/from their corresponding DMA coherent bounce + * buffers. + * + * As an illustrative example, the Nintendo Wii video game console + * is a NOT_COHERENT_CACHE platform that is unable to safely perform + * non-32 bit uncached writes to the top 64MB of RAM (it actually + * _corrupts_ RAM if done). + * Thus, in that platform, DMA buffers cannot be directly used by kernel + * code unless the code guarantees that all write accesses to buffers + * are done in 32 bit chunks (which is not the case in the USB subsystem). + */ + +/* + * XXX + * This is actually generic DMA code that could be moved to a more + * appropiate file. + * + */ + +static void *dma_memcpy_to_coherent(void *dst, const void *src, size_t len) +{ + u32 *q = dst, *p = (void *)src; + u8 *s; + + while (len >= 4) { + *q++ = *p++; + len -= 4; + } + s = (u8 *)p; + switch (len) { + case 3: + *q = s[0] << 24 | s[1] << 16 | s[2] << 8; + break; + case 2: + *q = s[0] << 24 | s[1] << 16; + break; + case 1: + *q = s[0] << 24; + break; + default: + break; + } + return dst; +} + +static void *dma_memcpy_from_coherent(void *dst, const void *src, size_t len) +{ + u32 *q = dst, *p = (void *)src; + u32 v; + u8 *d; + + while (len >= 4) { + *q++ = *p++; + len -= 4; + } + if (len) { + d = (u8 *)q; + v = p[0]; + switch (len) { + case 3: + d[2] = (v >> 8) & 0xff; + /* FALL THROUGH */ + case 2: + d[1] = (v >> 16) & 0xff; + /* FALL THROUGH */ + case 1: + d[0] = (v >> 24) & 0xff; + break; + default: + break; + } + } + return dst; +} + +struct dma_coherent_buffer_ctx { + unsigned char *vaddr; + dma_addr_t dma_handle; +}; + +static int dma_bounce_to_coherent(struct device *dev, + gfp_t mem_flags, dma_addr_t *dma_handle, + void **vaddr_handle, size_t size, + enum dma_data_direction dir) +{ + struct dma_coherent_buffer_ctx ctx; + unsigned char *vaddr; + size_t up_size = _ALIGN_UP(size + sizeof(ctx), + dma_get_cache_alignment()); + + ctx.vaddr = *vaddr_handle; + ctx.dma_handle = *dma_handle; + + vaddr = dma_alloc_coherent(dev, up_size, dma_handle, mem_flags); + if (!vaddr) + return -ENOMEM; + + memcpy(vaddr + up_size - sizeof(ctx), &ctx, sizeof(ctx)); + + if (dir == DMA_TO_DEVICE) + dma_memcpy_to_coherent(vaddr, *vaddr_handle, size); + + *vaddr_handle = vaddr; + return 0; +} + +static void dma_bounce_from_coherent(struct device *dev, dma_addr_t *dma_handle, + void **vaddr_handle, size_t size, + enum dma_data_direction dir) +{ + struct dma_coherent_buffer_ctx ctx; + unsigned char *vaddr = *vaddr_handle; + size_t up_size = _ALIGN_UP(size + sizeof(ctx), + dma_get_cache_alignment()); + + memcpy(&ctx, vaddr + up_size - sizeof(ctx), sizeof(ctx)); + vaddr = ctx.vaddr; + + if (dir == DMA_FROM_DEVICE) + dma_memcpy_from_coherent(vaddr, *vaddr_handle, size); + + dma_free_coherent(dev, up_size, *vaddr_handle, *dma_handle); + + *vaddr_handle = vaddr; + *dma_handle = ctx.dma_handle; +} + static int map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags) { @@ -1273,47 +1405,74 @@ static int map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, if (is_root_hub(urb->dev)) return 0; - if (usb_endpoint_xfer_control(&urb->ep->desc) - && !(urb->transfer_flags & URB_NO_SETUP_DMA_MAP)) { - if (hcd->self.uses_dma) - urb->setup_dma = dma_map_single( - hcd->self.controller, - urb->setup_packet, - sizeof(struct usb_ctrlrequest), - DMA_TO_DEVICE); - else if (hcd->driver->flags & HCD_LOCAL_MEM) - ret = hcd_alloc_coherent( - urb->dev->bus, mem_flags, + if (usb_endpoint_xfer_control(&urb->ep->desc)) { + if (hcd->driver->flags & HCD_BOUNCE_DMA_MEM) { + if (!(urb->transfer_flags & URB_NO_SETUP_DMA_MAP)) + urb->setup_dma = 0; + ret = dma_bounce_to_coherent( + hcd->self.controller, mem_flags, &urb->setup_dma, (void **)&urb->setup_packet, sizeof(struct usb_ctrlrequest), DMA_TO_DEVICE); + } else if (!(urb->transfer_flags & URB_NO_SETUP_DMA_MAP)) { + if (hcd->self.uses_dma) + urb->setup_dma = dma_map_single( + hcd->self.controller, + urb->setup_packet, + sizeof(struct usb_ctrlrequest), + DMA_TO_DEVICE); + else if (hcd->driver->flags & HCD_LOCAL_MEM) + ret = hcd_alloc_local_mem( + urb->dev->bus, mem_flags, + &urb->setup_dma, + (void **)&urb->setup_packet, + sizeof(struct usb_ctrlrequest), + DMA_TO_DEVICE); + } } dir = usb_urb_dir_in(urb) ? DMA_FROM_DEVICE : DMA_TO_DEVICE; - if (ret == 0 && urb->transfer_buffer_length != 0 - && !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP)) { - if (hcd->self.uses_dma) - urb->transfer_dma = dma_map_single ( - hcd->self.controller, - urb->transfer_buffer, - urb->transfer_buffer_length, - dir); - else if (hcd->driver->flags & HCD_LOCAL_MEM) { - ret = hcd_alloc_coherent( - urb->dev->bus, mem_flags, + if (ret == 0 && urb->transfer_buffer_length != 0) { + if (hcd->driver->flags & HCD_BOUNCE_DMA_MEM) { + if (!(urb->transfer_flags & URB_NO_SETUP_DMA_MAP)) + urb->transfer_dma = 0; + ret = dma_bounce_to_coherent( + hcd->self.controller, mem_flags, &urb->transfer_dma, &urb->transfer_buffer, urb->transfer_buffer_length, dir); - if (ret && usb_endpoint_xfer_control(&urb->ep->desc) - && !(urb->transfer_flags & URB_NO_SETUP_DMA_MAP)) - hcd_free_coherent(urb->dev->bus, - &urb->setup_dma, - (void **)&urb->setup_packet, - sizeof(struct usb_ctrlrequest), - DMA_TO_DEVICE); + if (ret && usb_endpoint_xfer_control(&urb->ep->desc)) + dma_bounce_from_coherent(hcd->self.controller, + &urb->setup_dma, + (void **)&urb->setup_packet, + sizeof(struct usb_ctrlrequest), + DMA_TO_DEVICE); + } else if (!(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP)) { + if (hcd->self.uses_dma) + urb->transfer_dma = dma_map_single( + hcd->self.controller, + urb->transfer_buffer, + urb->transfer_buffer_length, + dir); + else if (hcd->driver->flags & HCD_LOCAL_MEM) { + ret = hcd_alloc_local_mem( + urb->dev->bus, mem_flags, + &urb->transfer_dma, + &urb->transfer_buffer, + urb->transfer_buffer_length, + dir); + + if (ret && + usb_endpoint_xfer_control(&urb->ep->desc)) + hcd_free_local_mem(urb->dev->bus, + &urb->setup_dma, + (void **)&urb->setup_packet, + sizeof(struct usb_ctrlrequest), + DMA_TO_DEVICE); + } } } return ret; @@ -1326,32 +1485,49 @@ static void unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) if (is_root_hub(urb->dev)) return; - if (usb_endpoint_xfer_control(&urb->ep->desc) - && !(urb->transfer_flags & URB_NO_SETUP_DMA_MAP)) { - if (hcd->self.uses_dma) - dma_unmap_single(hcd->self.controller, urb->setup_dma, + if (usb_endpoint_xfer_control(&urb->ep->desc)) { + if (hcd->driver->flags & HCD_BOUNCE_DMA_MEM) + dma_bounce_from_coherent(hcd->self.controller, + &urb->setup_dma, + (void **)&urb->setup_packet, sizeof(struct usb_ctrlrequest), DMA_TO_DEVICE); - else if (hcd->driver->flags & HCD_LOCAL_MEM) - hcd_free_coherent(urb->dev->bus, &urb->setup_dma, + else if (!(urb->transfer_flags & URB_NO_SETUP_DMA_MAP)) { + if (hcd->self.uses_dma) + dma_unmap_single(hcd->self.controller, + urb->setup_dma, + sizeof(struct usb_ctrlrequest), + DMA_TO_DEVICE); + else if (hcd->driver->flags & HCD_LOCAL_MEM) + hcd_free_local_mem(urb->dev->bus, + &urb->setup_dma, (void **)&urb->setup_packet, sizeof(struct usb_ctrlrequest), DMA_TO_DEVICE); + } } dir = usb_urb_dir_in(urb) ? DMA_FROM_DEVICE : DMA_TO_DEVICE; - if (urb->transfer_buffer_length != 0 - && !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP)) { - if (hcd->self.uses_dma) - dma_unmap_single(hcd->self.controller, - urb->transfer_dma, - urb->transfer_buffer_length, - dir); - else if (hcd->driver->flags & HCD_LOCAL_MEM) - hcd_free_coherent(urb->dev->bus, &urb->transfer_dma, - &urb->transfer_buffer, - urb->transfer_buffer_length, - dir); + if (urb->transfer_buffer_length != 0) { + if (hcd->driver->flags & HCD_BOUNCE_DMA_MEM) + dma_bounce_from_coherent(hcd->self.controller, + &urb->transfer_dma, + &urb->transfer_buffer, + urb->transfer_buffer_length, + dir); + else if (!(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP)) { + if (hcd->self.uses_dma) + dma_unmap_single(hcd->self.controller, + urb->transfer_dma, + urb->transfer_buffer_length, + dir); + else if (hcd->driver->flags & HCD_LOCAL_MEM) + hcd_free_local_mem(urb->dev->bus, + &urb->transfer_dma, + &urb->transfer_buffer, + urb->transfer_buffer_length, + dir); + } } } @@ -1973,6 +2149,10 @@ struct usb_hcd *usb_create_hcd (const struct hc_driver *driver, dev_set_drvdata(dev, hcd); kref_init(&hcd->kref); + /* pretend we can't do DMA */ + if (driver->flags & HCD_BOUNCE_DMA_MEM) + dev->dma_mask = NULL; + usb_bus_init(&hcd->self); hcd->self.controller = dev; hcd->self.bus_name = bus_name; diff --git a/drivers/usb/core/hcd.h b/drivers/usb/core/hcd.h index bcbe104..673bb66 100644 --- a/drivers/usb/core/hcd.h +++ b/drivers/usb/core/hcd.h @@ -169,12 +169,13 @@ struct hc_driver { irqreturn_t (*irq) (struct usb_hcd *hcd); int flags; -#define HCD_MEMORY 0x0001 /* HC regs use memory (else I/O) */ -#define HCD_LOCAL_MEM 0x0002 /* HC needs local memory */ -#define HCD_USB11 0x0010 /* USB 1.1 */ -#define HCD_USB2 0x0020 /* USB 2.0 */ -#define HCD_USB3 0x0040 /* USB 3.0 */ -#define HCD_MASK 0x0070 +#define HCD_MEMORY 0x0001 /* HC regs use memory (else I/O) */ +#define HCD_LOCAL_MEM 0x0002 /* HC needs local memory */ +#define HCD_BOUNCE_DMA_MEM 0x0004 /* HC needs bounced DMA memory */ +#define HCD_USB11 0x0010 /* USB 1.1 */ +#define HCD_USB2 0x0020 /* USB 2.0 */ +#define HCD_USB3 0x0040 /* USB 3.0 */ +#define HCD_MASK 0x0070 /* called to init HCD and root hub */ int (*reset) (struct usb_hcd *hcd); diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 9b43b22..b1d17ff 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -113,6 +113,14 @@ config USB_EHCI_HCD_PPC_OF Enables support for the USB controller present on the PowerPC OpenFirmware platform bus. +config USB_EHCI_HCD_HLWD + bool "Nintendo Wii (Hollywood) EHCI USB controller support" + depends on USB_EHCI_HCD && STARLET_MINI + default y + ---help--- + Say Y here to support the EHCI USB controller found in the + Hollywood chipset of the Nintendo Wii video game console. + config USB_W90X900_EHCI bool "W90X900(W90P910) EHCI support" depends on USB_EHCI_HCD && ARCH_W90X900 @@ -221,6 +229,15 @@ config USB_OHCI_HCD_PPC_OF depends on USB_OHCI_HCD && PPC_OF default USB_OHCI_HCD_PPC_OF_BE || USB_OHCI_HCD_PPC_OF_LE +config USB_OHCI_HCD_HLWD + bool "Nintendo Wii (Hollywood) OHCI USB controller support" + depends on USB_OHCI_HCD && STARLET_MINI + select USB_OHCI_LITTLE_ENDIAN + default y + ---help--- + Say Y here to support the OHCI USB controller found in the + Hollywood chipset of the Nintendo Wii video game console. + config USB_OHCI_HCD_PCI bool "OHCI support for PCI-bus USB controllers" depends on USB_OHCI_HCD && PCI && (STB03xxx || PPC_MPC52xx || USB_OHCI_HCD_PPC_OF) @@ -354,6 +371,25 @@ config USB_R8A66597_HCD To compile this driver as a module, choose M here: the module will be called r8a66597-hcd. +config USB_WII_HCD + tristate "Nintendo Wii HCD support" + depends on USB && STARLET_IOS && !HIGHMEM && EXPERIMENTAL + help + The Nintendo Wii includes a USB 1.1 host controller that can be + accessed through the API provided by the starlet subsystem. + + Enable this option if you plan to use the internal Nintendo Wii + bluetooth dongle or any USB peripheral connected to the external + ports. + + USB devices using isochronous transfers are not supported. + Use of USB hubs is partially supported. + + Use completely at you own risk. If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called rvl-sthcd. + config USB_WHCI_HCD tristate "Wireless USB Host Controller Interface (WHCI) driver (EXPERIMENTAL)" depends on EXPERIMENTAL diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index f58b249..03e7f87 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -31,4 +31,5 @@ obj-$(CONFIG_USB_SL811_CS) += sl811_cs.o obj-$(CONFIG_USB_U132_HCD) += u132-hcd.o obj-$(CONFIG_USB_R8A66597_HCD) += r8a66597-hcd.o obj-$(CONFIG_USB_ISP1760_HCD) += isp1760.o +obj-$(CONFIG_USB_WII_HCD) += rvl-sthcd.o obj-$(CONFIG_USB_HWA_HCD) += hwa-hc.o diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 8198fc0..0711223 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -1122,6 +1122,11 @@ MODULE_LICENSE ("GPL"); #define OF_PLATFORM_DRIVER ehci_hcd_ppc_of_driver #endif +#ifdef CONFIG_USB_EHCI_HCD_HLWD +#include "ehci-hlwd.c" +#define OF_PLATFORM_DRIVER ehci_hcd_hlwd_driver +#endif + #ifdef CONFIG_PLAT_ORION #include "ehci-orion.c" #define PLATFORM_DRIVER ehci_orion_driver diff --git a/drivers/usb/host/ehci-hlwd.c b/drivers/usb/host/ehci-hlwd.c new file mode 100644 index 0000000..b4dedd1 --- /dev/null +++ b/drivers/usb/host/ehci-hlwd.c @@ -0,0 +1,265 @@ +/* + * drivers/usb/host/ehci-hlwd.c + * + * Nintendo Wii (Hollywood) USB Enhanced Host Controller Interface. + * Copyright (C) 2009 The GameCube Linux Team + * Copyright (C) 2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * Based on ehci-ppc-of.c + * + * EHCI HCD (Host Controller Driver) for USB. + * + * Bus Glue for PPC On-Chip EHCI driver on the of_platform bus + * Tested on AMCC PPC 440EPx + * + * Valentine Barshak + * + * Based on "ehci-ppc-soc.c" by Stefan Roese + * and "ohci-ppc-of.c" by Sylvain Munaut + * + * This file is licenced under the GPL. + */ + +#include + +#include +#include +#include + +#define DRV_MODULE_NAME "ehci-hlwd" +#define DRV_DESCRIPTION "Nintendo Wii EHCI Host Controller" +#define DRV_AUTHOR "Albert Herranz" + +#define HLWD_EHCI_CTL 0x0d0400cc +#define HLWD_EHCI_CTL_INTE (1<<15) + + +/* called during probe() after chip reset completes */ +static int ehci_hlwd_reset(struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + void __iomem *ehci_ctl; + int error; + + dbg_hcs_params(ehci, "reset"); + dbg_hcc_params(ehci, "reset"); + + error = ehci_halt(ehci); + if (error) + goto out; + + error = ehci_init(hcd); + if (error) + goto out; + + ehci_ctl = ioremap(HLWD_EHCI_CTL, 4); + if (!ehci_ctl) { + printk(KERN_ERR __FILE__ ": ioremap failed\n"); + error = -EBUSY; + goto out; + } + + /* enable notification of EHCI interrupts */ + out_be32(ehci_ctl, in_be32(ehci_ctl) | HLWD_EHCI_CTL_INTE); + iounmap(ehci_ctl); + + ehci->sbrn = 0x20; + error = ehci_reset(ehci); + ehci_port_power(ehci, 0); +out: + return error; +} + +static const struct hc_driver ehci_hlwd_hc_driver = { + .description = hcd_name, + .product_desc = "Nintendo Wii EHCI Host Controller", + .hcd_priv_size = sizeof(struct ehci_hcd), + + /* + * generic hardware linkage + */ + .irq = ehci_irq, + .flags = HCD_USB2 | HCD_BOUNCE_DMA_MEM, + + /* + * basic lifecycle operations + */ + .reset = ehci_hlwd_reset, + .start = ehci_run, + .stop = ehci_stop, + .shutdown = ehci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ehci_urb_enqueue, + .urb_dequeue = ehci_urb_dequeue, + .endpoint_disable = ehci_endpoint_disable, + .endpoint_reset = ehci_endpoint_reset, + + /* + * scheduling support + */ + .get_frame_number = ehci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ehci_hub_status_data, + .hub_control = ehci_hub_control, +#ifdef CONFIG_PM + .bus_suspend = ehci_bus_suspend, + .bus_resume = ehci_bus_resume, +#endif + .relinquish_port = ehci_relinquish_port, + .port_handed_over = ehci_port_handed_over, + + .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, +}; + + +static int __devinit +ehci_hcd_hlwd_probe(struct of_device *op, const struct of_device_id *match) +{ + struct device_node *dn = op->node; + struct usb_hcd *hcd; + struct ehci_hcd *ehci = NULL; + struct resource res; + dma_addr_t coherent_mem_addr; + size_t coherent_mem_size; + int irq; + int error = -ENODEV; + + if (usb_disabled()) + goto out; + + if (starlet_get_ipc_flavour() != STARLET_IPC_MINI) + goto out; + + dev_dbg(&op->dev, "initializing " DRV_MODULE_NAME " USB Controller\n"); + + error = of_address_to_resource(dn, 0, &res); + if (error) + goto out; + + hcd = usb_create_hcd(&ehci_hlwd_hc_driver, &op->dev, DRV_MODULE_NAME); + if (!hcd) { + error = -ENOMEM; + goto out; + } + + hcd->rsrc_start = res.start; + hcd->rsrc_len = resource_size(&res); + + error = of_address_to_resource(dn, 1, &res); + if (error) { + /* satisfy coherent memory allocations from mem1 or mem2 */ + dev_warn(&op->dev, "using normal memory\n"); + } else { + coherent_mem_addr = res.start; + coherent_mem_size = res.end - res.start + 1; + if (!dma_declare_coherent_memory(&op->dev, coherent_mem_addr, + coherent_mem_addr, + coherent_mem_size, + DMA_MEMORY_MAP | + DMA_MEMORY_EXCLUSIVE)) { + dev_err(&op->dev, "error declaring %u bytes of" + " coherent memory at 0x%p\n", + coherent_mem_size, (void *)coherent_mem_addr); + error = -EBUSY; + goto err_decl_coherent; + } + } + + irq = irq_of_parse_and_map(dn, 0); + if (irq == NO_IRQ) { + printk(KERN_ERR __FILE__ ": irq_of_parse_and_map failed\n"); + error = -EBUSY; + goto err_irq; + } + + hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); + if (!hcd->regs) { + printk(KERN_ERR __FILE__ ": ioremap failed\n"); + error = -EBUSY; + goto err_ioremap; + } + + ehci = hcd_to_ehci(hcd); + ehci->caps = hcd->regs; + ehci->regs = hcd->regs + + HC_LENGTH(ehci_readl(ehci, &ehci->caps->hc_capbase)); + + /* cache this readonly data; minimize chip reads */ + ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params); + + error = usb_add_hcd(hcd, irq, 0); + if (error == 0) + return 0; + + iounmap(hcd->regs); +err_ioremap: + irq_dispose_mapping(irq); +err_irq: + dma_release_declared_memory(&op->dev); +err_decl_coherent: + usb_put_hcd(hcd); +out: + return error; +} + + +static int ehci_hcd_hlwd_remove(struct of_device *op) +{ + struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + + dev_set_drvdata(&op->dev, NULL); + + dev_dbg(&op->dev, "stopping " DRV_MODULE_NAME " USB Controller\n"); + + usb_remove_hcd(hcd); + iounmap(hcd->regs); + irq_dispose_mapping(hcd->irq); + dma_release_declared_memory(&op->dev); + usb_put_hcd(hcd); + + return 0; +} + + +static int ehci_hcd_hlwd_shutdown(struct of_device *op) +{ + struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + + if (hcd->driver->shutdown) + hcd->driver->shutdown(hcd); + + return 0; +} + + +static struct of_device_id ehci_hcd_hlwd_match[] = { + { + .compatible = "nintendo,hollywood-ehci", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, ehci_hcd_hlwd_match); + +static struct of_platform_driver ehci_hcd_hlwd_driver = { + .name = DRV_MODULE_NAME, + .match_table = ehci_hcd_hlwd_match, + .probe = ehci_hcd_hlwd_probe, + .remove = ehci_hcd_hlwd_remove, + .shutdown = ehci_hcd_hlwd_shutdown, + .driver = { + .name = DRV_MODULE_NAME, + .owner = THIS_MODULE, + }, +}; + diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 556c0b4..207e91c 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -599,6 +599,24 @@ ehci_port_speed(struct ehci_hcd *ehci, unsigned int portsc) #define ehci_big_endian_mmio(e) 0 #endif +#ifdef CONFIG_USB_EHCI_HCD_HLWD + +#include + +static inline unsigned int ehci_readl(const struct ehci_hcd *ehci, + __u32 __iomem *regs) +{ + return in_be32(regs); +} + +static inline void ehci_writel(const struct ehci_hcd *ehci, + const unsigned int val, __u32 __iomem *regs) +{ + out_be32(regs, val); +} + +#else + /* * Big-endian read/write functions are arch-specific. * Other arches can be added if/when they're needed. @@ -632,6 +650,8 @@ static inline void ehci_writel(const struct ehci_hcd *ehci, #endif } +#endif /* CONFIG_USB_EHCI_HCD_HLWD */ + /* * On certain ppc-44x SoC there is a HW issue, that could only worked around with * explicit suspend/operate of OHCI. This function hereby makes sense only on that arch. diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index 24eb747..d0c854e 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -1065,6 +1065,11 @@ MODULE_LICENSE ("GPL"); #define OF_PLATFORM_DRIVER ohci_hcd_ppc_of_driver #endif +#ifdef CONFIG_USB_OHCI_HCD_HLWD +#include "ohci-hlwd.c" +#define OF_PLATFORM_DRIVER ohci_hcd_hlwd_driver +#endif + #ifdef CONFIG_PPC_PS3 #include "ohci-ps3.c" #define PS3_SYSTEM_BUS_DRIVER ps3_ohci_driver diff --git a/drivers/usb/host/ohci-hlwd.c b/drivers/usb/host/ohci-hlwd.c new file mode 100644 index 0000000..513666e --- /dev/null +++ b/drivers/usb/host/ohci-hlwd.c @@ -0,0 +1,356 @@ +/* + * drivers/usb/host/ohci-hlwd.c + * + * Nintendo Wii (Hollywood) USB Open Host Controller Interface. + * Copyright (C) 2009 The GameCube Linux Team + * Copyright (C) 2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * Based on ohci-ppc-of.c + * + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber + * (C) Copyright 2000-2002 David Brownell + * (C) Copyright 2002 Hewlett-Packard Company + * (C) Copyright 2006 Sylvain Munaut + * + * Bus glue for OHCI HC on the of_platform bus + * + * Modified for of_platform bus from ohci-sa1111.c + * + * This file is licenced under the GPL. + */ + +#include +#include + +#include +#include +#include /* for get_tbl() */ + +#define DRV_MODULE_NAME "ohci-hlwd" +#define DRV_DESCRIPTION "Nintendo Wii OHCI Host Controller" +#define DRV_AUTHOR "Albert Herranz" + +#define HLWD_EHCI_CTL 0x0d0400cc /* vendor control register */ +#define HLWD_EHCI_CTL_OH0INTE (1<<11) /* oh0 interrupt enable */ +#define HLWD_EHCI_CTL_OH1INTE (1<<12) /* oh1 interrupt enable */ + +#define __spin_event_timeout(condition, timeout_usecs, result, __end_tbl) \ + for (__end_tbl = get_tbl() + tb_ticks_per_usec * timeout_usecs; \ + !(result = (condition)) && (int)(__end_tbl - get_tbl()) > 0;) + + +static DEFINE_SPINLOCK(control_quirk_lock); + +void ohci_hlwd_control_quirk(struct ohci_hcd *ohci) +{ + static struct ed *ed; /* empty ED */ + struct td *td; /* dummy TD */ + __hc32 head; + __hc32 current; + unsigned long ctx; + int result; + unsigned long flags; + + /* + * One time only. + * Allocate and keep a special empty ED with just a dummy TD. + */ + if (!ed) { + ed = ed_alloc(ohci, GFP_ATOMIC); + if (!ed) + return; + + td = td_alloc(ohci, GFP_ATOMIC); + if (!td) { + ed_free(ohci, ed); + ed = NULL; + return; + } + + ed->hwNextED = 0; + ed->hwTailP = ed->hwHeadP = cpu_to_hc32(ohci, + td->td_dma & ED_MASK); + ed->hwINFO |= cpu_to_hc32(ohci, ED_OUT); + wmb(); + } + + spin_lock_irqsave(&control_quirk_lock, flags); + + /* + * The OHCI USB host controllers on the Nintendo Wii + * video game console stop working when new TDs are + * added to a scheduled control ED after a transfer has + * has taken place on it. + * + * Before scheduling any new control TD, we make the + * controller happy by always loading a special control ED + * with a single dummy TD and letting the controller attempt + * the transfer. + * The controller won't do anything with it, as the special + * ED has no TDs, but it will keep the controller from failing + * on the next transfer. + */ + head = ohci_readl(ohci, &ohci->regs->ed_controlhead); + if (head) { + /* + * Load the special empty ED and tell the controller to + * process the control list. + */ + ohci_writel(ohci, ed->dma, &ohci->regs->ed_controlhead); + ohci_writel (ohci, ohci->hc_control | OHCI_CTRL_CLE, + &ohci->regs->control); + ohci_writel (ohci, OHCI_CLF, &ohci->regs->cmdstatus); + + /* spin until the controller is done with the control list */ + current = ohci_readl(ohci, &ohci->regs->ed_controlcurrent); + __spin_event_timeout(!current, 10 /* usecs */, result, ctx) { + cpu_relax(); + current = ohci_readl(ohci, + &ohci->regs->ed_controlcurrent); + } + + /* restore the old control head and control settings */ + ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); + ohci_writel(ohci, head, &ohci->regs->ed_controlhead); + } + + spin_unlock_irqrestore(&control_quirk_lock, flags); +} + +void ohci_hlwd_bulk_quirk(struct ohci_hcd *ohci) +{ + /* + * There seem to be issues too with the bulk list processing on the + * OHCI controller found in the Nintendo Wii video game console. + * The exact problem remains still unidentified, but adding a small + * delay seems to workaround it. + * + * As an example, without this quirk the wiimote controller stops + * responding after a few seconds because one of its bulk endpoint + * descriptors gets stuck. + */ + udelay(250); +} + +static int __devinit ohci_hlwd_start(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + void __iomem *ehci_ctl; + int error; + + error = ohci_init(ohci); + if (error) + goto out; + + ehci_ctl = ioremap(HLWD_EHCI_CTL, 4); + if (!ehci_ctl) { + printk(KERN_ERR __FILE__ ": ioremap failed\n"); + error = -EBUSY; + ohci_stop(hcd); + goto out; + } + + /* enable notification of OHCI interrupts */ + out_be32(ehci_ctl, in_be32(ehci_ctl) | + 0xe0000 | HLWD_EHCI_CTL_OH0INTE | HLWD_EHCI_CTL_OH1INTE); + iounmap(ehci_ctl); + + error = ohci_run(ohci); + if (error) { + err("can't start %s", ohci_to_hcd(ohci)->self.bus_name); + ohci_stop(hcd); + goto out; + } + +out: + return error; +} + +static const struct hc_driver ohci_hlwd_hc_driver = { + .description = hcd_name, + .product_desc = "Nintendo Wii OHCI Host Controller", + .hcd_priv_size = sizeof(struct ohci_hcd), + + /* + * generic hardware linkage + */ + .irq = ohci_irq, + .flags = HCD_USB11 | HCD_BOUNCE_DMA_MEM, + + /* + * basic lifecycle operations + */ + .start = ohci_hlwd_start, + .stop = ohci_stop, + .shutdown = ohci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ohci_urb_enqueue, + .urb_dequeue = ohci_urb_dequeue, + .endpoint_disable = ohci_endpoint_disable, + + /* + * scheduling support + */ + .get_frame_number = ohci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ohci_hub_status_data, + .hub_control = ohci_hub_control, +#ifdef CONFIG_PM + .bus_suspend = ohci_bus_suspend, + .bus_resume = ohci_bus_resume, +#endif + .start_port_reset = ohci_start_port_reset, +}; + + +static int __devinit +ohci_hcd_hlwd_probe(struct of_device *op, const struct of_device_id *match) +{ + struct device_node *dn = op->node; + struct usb_hcd *hcd; + struct ohci_hcd *ohci = NULL; + struct resource res; + dma_addr_t coherent_mem_addr; + size_t coherent_mem_size; + int irq; + int error = -ENODEV; + + if (usb_disabled()) + goto out; + + if (starlet_get_ipc_flavour() != STARLET_IPC_MINI) + goto out; + + dev_dbg(&op->dev, "initializing " DRV_MODULE_NAME " USB Controller\n"); + + error = of_address_to_resource(dn, 0, &res); + if (error) + goto out; + + hcd = usb_create_hcd(&ohci_hlwd_hc_driver, &op->dev, DRV_MODULE_NAME); + if (!hcd) { + error = -ENOMEM; + goto out; + } + + hcd->rsrc_start = res.start; + hcd->rsrc_len = resource_size(&res); + + error = of_address_to_resource(dn, 1, &res); + if (error) { + /* satisfy coherent memory allocations from mem1 or mem2 */ + dev_warn(&op->dev, "using normal memory\n"); + } else { + coherent_mem_addr = res.start; + coherent_mem_size = res.end - res.start + 1; + if (!dma_declare_coherent_memory(&op->dev, coherent_mem_addr, + coherent_mem_addr, + coherent_mem_size, + DMA_MEMORY_MAP | + DMA_MEMORY_EXCLUSIVE)) { + dev_err(&op->dev, "error declaring %u bytes of" + " coherent memory at 0x%p\n", + coherent_mem_size, (void *)coherent_mem_addr); + error = -EBUSY; + goto err_decl_coherent; + } + } + + irq = irq_of_parse_and_map(dn, 0); + if (irq == NO_IRQ) { + printk(KERN_ERR __FILE__ ": irq_of_parse_and_map failed\n"); + error = -EBUSY; + goto err_irq; + } + + hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); + if (!hcd->regs) { + printk(KERN_ERR __FILE__ ": ioremap failed\n"); + error = -EBUSY; + goto err_ioremap; + } + + ohci = hcd_to_ohci(hcd); + ohci->flags |= OHCI_QUIRK_WII; + + ohci_hcd_init(ohci); + + error = usb_add_hcd(hcd, irq, IRQF_DISABLED); + if (error) + goto err_add_hcd; + + return 0; + +err_add_hcd: + iounmap(hcd->regs); +err_ioremap: + irq_dispose_mapping(irq); +err_irq: + dma_release_declared_memory(&op->dev); +err_decl_coherent: + usb_put_hcd(hcd); +out: + return error; +} + +static int ohci_hcd_hlwd_remove(struct of_device *op) +{ + struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + + dev_set_drvdata(&op->dev, NULL); + + dev_dbg(&op->dev, "stopping " DRV_MODULE_NAME " USB Controller\n"); + + usb_remove_hcd(hcd); + iounmap(hcd->regs); + irq_dispose_mapping(hcd->irq); + dma_release_declared_memory(&op->dev); + usb_put_hcd(hcd); + + return 0; +} + +static int ohci_hcd_hlwd_shutdown(struct of_device *op) +{ + struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + + if (hcd->driver->shutdown) + hcd->driver->shutdown(hcd); + + return 0; +} + + +static struct of_device_id ohci_hcd_hlwd_match[] = { + { + .compatible = "nintendo,hollywood-ohci", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, ohci_hcd_hlwd_match); + +static struct of_platform_driver ohci_hcd_hlwd_driver = { + .name = DRV_MODULE_NAME, + .match_table = ohci_hcd_hlwd_match, + .probe = ohci_hcd_hlwd_probe, + .remove = ohci_hcd_hlwd_remove, + .shutdown = ohci_hcd_hlwd_shutdown, + .driver = { + .name = DRV_MODULE_NAME, + .owner = THIS_MODULE, + }, +}; + diff --git a/drivers/usb/host/ohci-q.c b/drivers/usb/host/ohci-q.c index 35288bc..0e0e267 100644 --- a/drivers/usb/host/ohci-q.c +++ b/drivers/usb/host/ohci-q.c @@ -618,6 +618,9 @@ static void td_submit_urb ( && ohci_to_hcd(ohci)->self.bandwidth_isoc_reqs == 0; /* FALLTHROUGH */ case PIPE_BULK: + if (ohci->flags & OHCI_QUIRK_WII) + ohci_hlwd_bulk_quirk(ohci); + info = is_out ? TD_T_TOGGLE | TD_CC | TD_DP_OUT : TD_T_TOGGLE | TD_CC | TD_DP_IN; @@ -649,6 +652,9 @@ static void td_submit_urb ( * any DATA phase works normally, and the STATUS ack is special. */ case PIPE_CONTROL: + if (ohci->flags & OHCI_QUIRK_WII) + ohci_hlwd_control_quirk(ohci); + info = TD_CC | TD_DP_SETUP | TD_T_DATA0; td_fill (ohci, info, urb->setup_dma, 8, urb, cnt++); if (data_len > 0) { diff --git a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h index 5bf15fe..57e15dc 100644 --- a/drivers/usb/host/ohci.h +++ b/drivers/usb/host/ohci.h @@ -16,6 +16,28 @@ typedef __u32 __bitwise __hc32; typedef __u16 __bitwise __hc16; /* + * Some platforms have weird constraints when accessing memory. + * + * For example, the Nintendo Wii video game console is unable to perform + * non-32 bit writes to non-cached memory for its second block of 64MB of RAM. + * As this platform also requires CONFIG_NOT_COHERENT_CACHE, all memory + * allocated using the dma memory allocation functions can only be written + * using 32-bit accesses. + * + * Because of this constraint, as a workaround, we make sure that all + * fields in struct ed and td (which are allocated from dma pools) are + * always 32 bit fields. + * Note that the remaining structs allocated from dma-able memory are already + * 32 bit fields. + */ +#ifdef CONFIG_USB_OHCI_HCD_HLWD +#define ohci_fld(type) u32 +#else +#define ohci_fld(type) type +#endif + + +/* * OHCI Endpoint Descriptor (ED) ... holds TD queue * See OHCI spec, section 4.2 * @@ -51,21 +73,21 @@ struct ed { /* create --> IDLE --> OPER --> ... --> IDLE --> destroy * usually: OPER --> UNLINK --> (IDLE | OPER) --> ... */ - u8 state; /* ED_{IDLE,UNLINK,OPER} */ + ohci_fld(u8) state; /* ED_{IDLE,UNLINK,OPER} */ #define ED_IDLE 0x00 /* NOT linked to HC */ #define ED_UNLINK 0x01 /* being unlinked from hc */ #define ED_OPER 0x02 /* IS linked to hc */ - u8 type; /* PIPE_{BULK,...} */ + ohci_fld(u8) type; /* PIPE_{BULK,...} */ /* periodic scheduling params (for intr and iso) */ - u8 branch; - u16 interval; - u16 load; - u16 last_iso; /* iso only */ + ohci_fld(u8) branch; + ohci_fld(u16) interval; + ohci_fld(u16) load; + ohci_fld(u16) last_iso; /* iso only */ /* HC may see EDs on rm_list until next frame (frame_no == tick) */ - u16 tick; + ohci_fld(u16) tick; } __attribute__ ((aligned(16))); #define ED_MASK ((u32)~0x0f) /* strip hw status in low addr bits */ @@ -118,7 +140,7 @@ struct td { __hc16 hwPSW [MAXPSW]; /* rest are purely for the driver's use */ - __u8 index; + ohci_fld(__u8) index; struct ed *ed; struct td *td_hash; /* dma-->td hashtable */ struct td *next_dl_td; @@ -403,6 +425,7 @@ struct ohci_hcd { #define OHCI_QUIRK_HUB_POWER 0x100 /* distrust firmware power/oc setup */ #define OHCI_QUIRK_AMD_ISO 0x200 /* ISO transfers*/ #define OHCI_QUIRK_AMD_PREFETCH 0x400 /* pre-fetch for ISO transfer */ +#define OHCI_QUIRK_WII 0x800 /* Hollywood chipset */ // there are also chip quirks/bugs in init logic struct work_struct nec_work; /* Worker for NEC quirk */ @@ -545,6 +568,37 @@ static inline struct usb_hcd *ohci_to_hcd (const struct ohci_hcd *ohci) #define big_endian_mmio(ohci) 0 /* only little endian */ #endif +#ifdef CONFIG_USB_OHCI_HCD_HLWD + +#include + +static inline unsigned int _ohci_readl(const struct ohci_hcd *ohci, + __hc32 __iomem *regs) +{ + return in_be32(regs); +} + +static inline void _ohci_writel(const struct ohci_hcd *ohci, + const unsigned int val, __hc32 __iomem *regs) +{ + out_be32(regs, val); +} + +extern void ohci_hlwd_control_quirk(struct ohci_hcd *ohci); +extern void ohci_hlwd_bulk_quirk(struct ohci_hcd *ohci); + +#else + +static inline void ohci_hlwd_control_quirk(struct ohci_hcd *ohci) +{ + return; +} + +static inline void ohci_hlwd_bulk_quirk(struct ohci_hcd *ohci) +{ + return; +} + /* * Big-endian read/write functions are arch-specific. * Other arches can be added if/when they're needed. @@ -574,6 +628,8 @@ static inline void _ohci_writel (const struct ohci_hcd *ohci, #endif } +#endif /* CONFIG_USB_OHCI_HCD_HLWD */ + #ifdef CONFIG_ARCH_LH7A404 /* Marc Singer: at the time this code was written, the LH7A404 * had a problem reading the USB host registers. This diff --git a/drivers/usb/host/rvl-sthcd.c b/drivers/usb/host/rvl-sthcd.c new file mode 100644 index 0000000..7d53e70 --- /dev/null +++ b/drivers/usb/host/rvl-sthcd.c @@ -0,0 +1,2377 @@ +/* + * drivers/usb/host/rvl-sthcd.c + * + * USB Host Controller driver for the Nintendo Wii + * Copyright (C) 2008-2009 The GameCube Linux Team + * Copyright (C) 2008 Maarten ter Huurne + * Copyright (C) 2008,2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +/* + * + * TODO + * - cleanup debuging mess + * + */ + +#ifdef CONFIG_HIGHMEM +#error Sorry, this driver cannot currently work if HIGHMEM is y +#endif + +#define DBG(fmt, arg...) drv_printk(KERN_DEBUG, fmt, ##arg) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../core/hcd.h" +#include "../core/hub.h" + +#define DRV_MODULE_NAME "rvl-sthcd" +#define DRV_DESCRIPTION "USB Host Controller driver for the Nintendo Wii" +#define DRV_AUTHOR "Maarten ter Huurne, " \ + "Albert Herranz" + +static char sthcd_driver_version[] = "0.5i"; + +#define drv_printk(level, format, arg...) \ + printk(level DRV_MODULE_NAME ": " format , ## arg) + + +/* TODO: Use enum instead? */ +#define STHCD_IOCTLV_CONTROLREQ 0 +#define STHCD_IOCTLV_BULKREQ 1 +#define STHCD_IOCTLV_INTRREQ 2 +#define STHCD_IOCTL_SUSPENDDEVICE 5 +#define STHCD_IOCTL_RESUMEDEVICE 6 +#define STHCD_IOCTLV_GETDEVICELIST 12 +#define STHCD_IOCTL_DEVICEREMOVALNOTIFY 26 +#define STHCD_IOCTLV_DEVICEINSERTNOTIFY 27 + +/* + * The Nintendo Wii has only 2 external USB ports (plus 1 internal USB port), + * but the starlet API provides access to USB devices in a port independent + * way. This is true also for USB devices attached to external hubs. + * + * Our HCD model currently maps one starlet USB device to one HCD port, thus + * we need additional ports here. + */ +#define STHCD_MAX_DEVIDS 15 +#define STHCD_MAX_PORTS STHCD_MAX_DEVIDS + +/* + * We get error -7008 after performing large transfers. + * Using this arbitrary limit makes things work. + */ +#define STHCD_MAX_CHUNK_SIZE (2048) + +#define STHCD_PORT_MAX_RESETS 2 /* maximum number of consecutive + * resets allowed for a port */ +#define STHCD_RESCAN_INTERVAL 5 /* seconds */ + +#define starlet_ioh_sg_entry(sg, ptr) \ + starlet_ioh_sg_set_buf((sg), (ptr), sizeof(*(ptr))) + + +struct sthcd_hcd; +struct sthcd_port; +struct sthcd_oh; + +/* + * starlet USB device abstraction (udev). + * + */ +struct sthcd_udev { + u16 idVendor; + u16 idProduct; + int fd; /* starlet file descriptor */ + + u16 devnum; /* USB address set by kernel */ + + struct list_head node; /* in list of connected devices */ + struct sthcd_oh *oh; /* parent Open Host controller */ + + struct list_head pep_list; /* list of private endpoints */ +}; + +/* + * starlet USB device identifier. + * + */ +struct sthcd_devid { + u32 _unk1; + u16 idVendor; + u16 idProduct; +}; + +enum { + __STHCD_PORT_INUSE = 0, + __STHCD_PORT_DOOMED, +}; + + +/* + * "Virtual" HCD USB port. + * + */ +struct sthcd_port { + unsigned long flags; +#define STHCD_PORT_INUSE (1 << __STHCD_PORT_INUSE) +#define STHCD_PORT_DOOMED (1 << __STHCD_PORT_DOOMED) + + u32 status_change; + unsigned nr_resets; + + struct sthcd_udev udev; /* one udev per port */ +}; + +/* + * starlet Open Host controller abstraction (oh). + * + */ +struct sthcd_oh { + unsigned int index; + int fd; /* starlet file descriptor */ + + unsigned int max_devids; + struct sthcd_devid *new_devids; + struct sthcd_devid *devids; + unsigned int nr_devids; /* actual no of devices */ + + struct sthcd_hcd *hcd; /* parent Host Controller */ +}; + +/* + * Host Controller (hcd). + * + */ +struct sthcd_hcd { + spinlock_t lock; + + struct sthcd_oh oh[2]; + + struct sthcd_port *ports; /* array of ports */ + unsigned int nr_ports; + + struct list_head device_list; /* list of connected devices */ + + wait_queue_head_t rescan_waitq; /* wait queue for the rescan task */ + struct task_struct *rescan_task; +}; + + +/* + * Private endpoint (pep). + * + * A pep takes care of the transfers for an endpoint. + */ + +struct sthcd_ctrl_params_in { + struct usb_ctrlrequest req; + u8 _unk1; /* timeout? */ +}; +struct sthcd_ctrl_xfer_ctx { + struct starlet_ioh_sg in[6]; + struct sthcd_ctrl_params_in *params_in; +}; + +struct sthcd_bulk_intr_params_in { + u8 bEndpointAddress; + u16 wLength; +}; +struct sthcd_bulk_intr_xfer_ctx { + struct starlet_ioh_sg in[2]; + struct sthcd_bulk_intr_params_in *params_in; +}; + +enum { + __STHCD_PEP_DISABLED = 0, + __STHCD_PEP_XFERBUSY, /* pep is actively xferring data */ +}; + +struct sthcd_pep { + unsigned long flags; +#define STHCD_PEP_DISABLED (1 << __STHCD_PEP_DISABLED) +#define STHCD_PEP_XFERBUSY (1 << __STHCD_PEP_XFERBUSY) + + unsigned long outstanding; + + struct usb_host_endpoint *ep; /* associated endpoint */ + struct sthcd_hcd *sthcd; /* associated hcd */ + + /* local copy of endpoint descriptor bmAttributes */ + __u8 bmAttributes; + + /* xfer context data */ + + struct urb *urb; /* urb being transferred */ + + struct sthcd_udev *udev; /* udev for this urb */ + struct list_head node; /* in list of peps for this udev */ + + size_t io_xfer_offset; /* number of bytes transferred */ + void *io_buf; /* data buffer */ + size_t io_buf_len; /* length of io_buf */ + + int request; /* ioctlv request */ + union { + struct sthcd_bulk_intr_xfer_ctx *bulk_intr; + struct sthcd_ctrl_xfer_ctx *ctrl; + } ctx; /* transfer context */ + + unsigned int nents_in; /* number of input sg entries */ + struct starlet_ioh_sg *in; /* input sg list */ + struct starlet_ioh_sg io[1]; /* input/output sg list */ +}; + + +/* + * Debugging facilities. + * + */ + +#if 0 +static inline void print_buffer(void *buf, u32 size) +{ + int i; + for (i = 0; i < (size + 3) / 4; i += 4) { + u32 *data = &((u32 *)buf)[i]; + printk(KERN_INFO " %08X %08X %08X %08X\n", + data[0], data[1], data[2], data[3] + ); + } +} +#endif + +/* + * Type conversion routines. + * + */ + +static inline struct sthcd_hcd *hcd_to_sthcd(struct usb_hcd *hcd) +{ + return (struct sthcd_hcd *)(hcd->hcd_priv); +} + +static inline struct usb_hcd *sthcd_to_hcd(struct sthcd_hcd *sthcd) +{ + return container_of((void *)sthcd, struct usb_hcd, hcd_priv); +} + +static inline struct sthcd_port *udev_to_port(struct sthcd_udev *_udev) +{ + return container_of(_udev, struct sthcd_port, udev); +} + + +/* + * Private End Point abstraction. + * + */ + +static inline struct sthcd_pep *ep_to_pep(struct usb_host_endpoint *ep) +{ + return ep->hcpriv; +} + +static inline int pep_is_enabled(struct sthcd_pep *pep) +{ + return !test_bit(__STHCD_PEP_DISABLED, &pep->flags); +} + +static int sthcd_pep_alloc_ctrl_xfer_ctx(struct sthcd_pep *pep) +{ + struct sthcd_ctrl_xfer_ctx *ctx; + struct sthcd_ctrl_params_in *params_in; + int error; + + ctx = starlet_kzalloc(sizeof(*ctx), GFP_ATOMIC); + if (!ctx) { + error = -ENOMEM; + goto done; + } + + params_in = starlet_ioh_kzalloc(sizeof(*params_in)); + if (!params_in) { + starlet_kfree(ctx); + error = -ENOMEM; + goto done; + } + + ctx->params_in = params_in; + + starlet_ioh_sg_init_table(ctx->in, 6); + starlet_ioh_sg_entry(&ctx->in[0], ¶ms_in->req.bRequestType); + starlet_ioh_sg_entry(&ctx->in[1], ¶ms_in->req.bRequest); + starlet_ioh_sg_entry(&ctx->in[2], ¶ms_in->req.wValue); + starlet_ioh_sg_entry(&ctx->in[3], ¶ms_in->req.wIndex); + starlet_ioh_sg_entry(&ctx->in[4], ¶ms_in->req.wLength); + starlet_ioh_sg_entry(&ctx->in[5], ¶ms_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], ¶ms_in->bEndpointAddress); + starlet_ioh_sg_entry(&ctx->in[1], ¶ms_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(¶ms_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], ¶ms_in->req.bRequestType); + starlet_ioh_sg_entry(&in[1], ¶ms_in->req.bRequest); + starlet_ioh_sg_entry(&in[2], ¶ms_in->req.wValue); + starlet_ioh_sg_entry(&in[3], ¶ms_in->req.wIndex); + starlet_ioh_sg_entry(&in[4], ¶ms_in->req.wLength); + starlet_ioh_sg_entry(&in[5], ¶ms_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], ¶ms_in->devid_count); + starlet_ioh_sg_entry(&in[1], ¶ms_in->_type); + + starlet_ioh_sg_init_table(io, 2); + starlet_ioh_sg_entry(&io[0], ¶ms_io->devid_count); + starlet_ioh_sg_set_buf(&io[1], ¶ms_io->devids, size); + + error = starlet_ioh_ioctlv(fd, STHCD_IOCTLV_GETDEVICELIST, + 2, in, 2, io); + + if (error < 0) { + DBG("%s: error=%d (%x)\n", __func__, error, error); + } else { + memcpy(devids, params_io->devids, size); + error = params_io->devid_count; + } + + starlet_ioh_kfree(params_in); + starlet_ioh_kfree(params_io); + + return error; +} + +static int sthcd_devid_match(struct sthcd_devid *id1, struct sthcd_devid *id2) +{ + return id1->idVendor == id2->idVendor && + id1->idProduct == id2->idProduct; +} + +static int sthcd_devid_find(struct sthcd_devid *haystack, size_t count, + struct sthcd_devid *needle) +{ + unsigned int i; + + for (i = 0; i < count; i++) { + if (sthcd_devid_match(&haystack[i], needle)) + return 1; + } + return 0; +} + +static int sthcd_oh_rescan(struct sthcd_oh *oh) +{ + static unsigned int poll_cycles; + struct usb_hcd *hcd = sthcd_to_hcd(oh->hcd); + struct sthcd_devid *p; + int nr_new_devids, i; + int changes; + int error; + + error = sthcd_get_device_list(oh->hcd, oh->fd, oh->new_devids, + oh->max_devids); + if (error < 0) + return error; + + nr_new_devids = error; + changes = 0; + + for (i = 0; i < oh->nr_devids; i++) { + p = &oh->devids[i]; + if (!sthcd_devid_find(oh->new_devids, nr_new_devids, p)) { + /* removal */ + error = sthcd_oh_remove_udev(oh, p->idVendor, + p->idProduct); + if (!error) + changes++; + } + } + + for (i = 0; i < nr_new_devids; i++) { + p = &oh->new_devids[i]; + if (!sthcd_devid_find(oh->devids, oh->nr_devids, p)) { + /* insertion */ + error = sthcd_oh_check_hub(oh, p->idVendor, + p->idProduct); + if (error == 0) { + /* not a hub, register the usb device */ + error = sthcd_oh_insert_udev(oh, p->idVendor, + p->idProduct); + if (!error) + changes++; + } else { + drv_printk(KERN_INFO, + "ignoring hub %04X.%04X\n", + p->idVendor, p->idProduct); + } + } + } + + memcpy(oh->devids, oh->new_devids, nr_new_devids * sizeof(*p)); + oh->nr_devids = nr_new_devids; + + /* + * FIXME + * We ask here the USB layer to explicitly poll for root hub changes + * until we get at least two complete rescan cycles without changes. + * + * Otherwise, for unknown reasons, we end up missing the detection of + * some devices, even if the insertion/removal of these devices is + * properly signaled in port->status_change. + */ + if (changes) { +#if 1 + if (!poll_cycles) { + hcd->poll_rh = 1; + usb_hcd_poll_rh_status(hcd); + } + poll_cycles = 2; + } else { + if (!poll_cycles) + hcd->poll_rh = 0; + else + poll_cycles--; +#else + usb_hcd_poll_rh_status(hcd); +#endif + } + + return 0; +} + +static int sthcd_oh_init(struct sthcd_oh *oh, unsigned int index, + struct sthcd_hcd *sthcd, size_t max_devids) +{ + char pathname[16]; + int error; + + if (index != 0 && index != 1) + return -EINVAL; + + snprintf(pathname, sizeof(pathname), "/dev/usb/oh%u", index); + error = starlet_open(pathname, 0); + if (error < 0) + return error; + + oh->fd = error; + oh->devids = kzalloc(2 * max_devids * sizeof(struct sthcd_devid), + GFP_KERNEL); + if (!oh->devids) { + starlet_close(oh->fd); + return -ENOMEM; + } + + oh->new_devids = oh->devids + max_devids; + + oh->max_devids = max_devids; + oh->nr_devids = 0; + + oh->index = index; + oh->hcd = sthcd; + + return 0; +} + +static void sthcd_oh_exit(struct sthcd_oh *oh) +{ + starlet_close(oh->fd); + oh->fd = -1; + kfree(oh->devids); + oh->devids = NULL; +} + +static int sthcd_rescan_thread(void *arg) +{ + struct sthcd_hcd *sthcd = arg; + struct sthcd_oh *oh; + + /* + * REVISIT + * We may need to rescan oh1 if bluetooth dongle disconnects. + */ + + /* oh1 has non-removable devices only, so just scan it once */ + sthcd_oh_rescan(&sthcd->oh[1]); + + oh = &sthcd->oh[0]; + + while (!kthread_should_stop()) { + sthcd_oh_rescan(oh); + + /* re-check again after the configured interval */ + sleep_on_timeout(&sthcd->rescan_waitq, + STHCD_RESCAN_INTERVAL*HZ); + } + return 0; +} + + +/* + * + * + */ + +static int sthcd_init(struct usb_hcd *hcd) +{ + return 0; +} + +static int sthcd_start(struct usb_hcd *hcd) +{ + struct sthcd_hcd *sthcd = hcd_to_sthcd(hcd); + int error; + + /* + * This is to prevent a spurious error from the kernel usb stack + * as we do not make use of interrupts. + */ + set_bit(HCD_FLAG_SAW_IRQ, &hcd->flags); + + hcd->uses_new_polling = 1; + + /* oh0 is the external bus */ + error = sthcd_oh_init(&sthcd->oh[0], 0, sthcd, STHCD_MAX_DEVIDS); + if (error < 0) { + DBG("%s: error=%d (%x)\n", __func__, error, error); + return error; + } + + /* oh1 is the internal bus, used only by the bluetooth dongle */ + error = sthcd_oh_init(&sthcd->oh[1], 1, sthcd, 1); + if (error < 0) { + DBG("%s: error=%d (%x)\n", __func__, error, error); + sthcd_oh_exit(&sthcd->oh[0]); + return error; + } + + hcd->state = HC_STATE_RUNNING; + + /* device insertion/removal is managed by the rescan thread */ + sthcd->rescan_task = kthread_run(sthcd_rescan_thread, sthcd, "ksthcd"); + if (IS_ERR(sthcd->rescan_task)) + drv_printk(KERN_ERR, "failed to start rescan thread\n"); + + return 0; +} + +static void sthcd_stop(struct usb_hcd *hcd) +{ + struct sthcd_hcd *sthcd = hcd_to_sthcd(hcd); + + if (!IS_ERR(sthcd->rescan_task)) { + kthread_stop(sthcd->rescan_task); + sthcd->rescan_task = ERR_PTR(-EINVAL); + } + + sthcd_oh_exit(&sthcd->oh[0]); + sthcd_oh_exit(&sthcd->oh[1]); + + hcd->state &= ~HC_STATE_RUNNING; +} + +static int sthcd_get_frame_number(struct usb_hcd *hcd) +{ + DBG("%s: CALLED\n", __func__); + return 0; +} + + +static int sthcd_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + struct sthcd_hcd *sthcd = hcd_to_sthcd(hcd); + struct usb_host_endpoint *ep; + struct sthcd_pep *pep; + unsigned long flags; + int error; + + spin_lock_irqsave(&sthcd->lock, flags); + + /* REVISIT, paranoid */ + if (urb->status != -EINPROGRESS) { + DBG("%s: status != -EINPROGRESS\n", __func__); + error = urb->status; + goto done; + } + + error = usb_hcd_link_urb_to_ep(hcd, urb); + if (error) + goto done; + + ep = urb->ep; + + /* allocate a pep for each endpoint on first use */ + if (!ep->hcpriv) { + pep = sthcd_pep_alloc(sthcd, ep); + if (!pep) { + error = -ENOMEM; + goto err_linked; + } + ep->hcpriv = pep; + } else { + pep = ep->hcpriv; + } + + error = sthcd_pep_send_urb(pep, urb); + if (!error) + goto done; + +err_linked: + usb_hcd_unlink_urb_from_ep(hcd, urb); +done: + spin_unlock_irqrestore(&sthcd->lock, flags); + return error; +} + +static int sthcd_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + struct sthcd_hcd *sthcd = hcd_to_sthcd(hcd); + struct usb_host_endpoint *ep; + struct sthcd_pep *pep; + unsigned long flags; + int error; + + spin_lock_irqsave(&sthcd->lock, flags); + + error = usb_hcd_check_unlink_urb(hcd, urb, status); + if (error) + goto done; + + ep = urb->ep; + pep = ep_to_pep(ep); + if (pep && pep->urb == urb) { + /* + * There is an urb in flight. + * + * We deattach the urb from the pep and leave the pep to the + * callback function, which will free it upon completion, + * without further action. + */ + sthcd_pep_takeout_urb(pep); + ep->hcpriv = NULL; + } + + usb_hcd_unlink_urb_from_ep(hcd, urb); + sthcd_giveback_urb(sthcd, urb, status); + +done: + spin_unlock_irqrestore(&sthcd->lock, flags); + +#if 0 + if (error < 0) + DBG("%s: error=%d (%x)\n", __func__, error, error); +#endif + return error; +} + +static void sthcd_endpoint_disable(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct sthcd_hcd *sthcd = hcd_to_sthcd(hcd); + struct sthcd_pep *pep; + unsigned long flags; + + spin_lock_irqsave(&sthcd->lock, flags); + pep = ep->hcpriv; + + /* do nothing if the pep was already freed */ + if (!pep) + goto done; + + if (pep->urb) { + /* + * There is an urb in flight. + * + * Disable the private endpoint and take the urb out of it. + * The callback function will take care of freeing the pep + * when the starlet call completes. + */ + set_bit(__STHCD_PEP_DISABLED, &pep->flags); + sthcd_pep_takeout_urb(pep); + } else { + /* the pep can be freed immediately when no urb is in flight */ + sthcd_pep_free(pep); + } + ep->hcpriv = NULL; + +done: + spin_unlock_irqrestore(&sthcd->lock, flags); +} + + +static const struct hc_driver starlet_hc_driver = { + .description = DRV_MODULE_NAME, + .product_desc = "Nintendo Wii USB Host Controller", + .hcd_priv_size = sizeof(struct sthcd_hcd), + + .irq = NULL, + .flags = HCD_USB11, + + /* REVISIT, power management calls not yet supported */ + + .reset = sthcd_init, + .start = sthcd_start, + .stop = sthcd_stop, + + .get_frame_number = sthcd_get_frame_number, + + .urb_enqueue = sthcd_urb_enqueue, + .urb_dequeue = sthcd_urb_dequeue, + .endpoint_disable = sthcd_endpoint_disable, + + .hub_status_data = sthcd_hub_status_data, + .hub_control = sthcd_hub_control, +}; + +static int __devinit sthcd_driver_probe(struct device *dev) +{ + struct sthcd_hcd *sthcd; + struct usb_hcd *hcd; + int error = -ENOMEM; + + if (starlet_get_ipc_flavour() != STARLET_IPC_IOS) + return -ENODEV; + + /* + * We can't use normal dma as starlet requires MEM2 buffers + * to work properly in all cases. + */ + dev->dma_mask = NULL; + + hcd = usb_create_hcd(&starlet_hc_driver, dev, DRV_MODULE_NAME); + if (!hcd) + goto err; + + sthcd = hcd_to_sthcd(hcd); + spin_lock_init(&sthcd->lock); + + sthcd->nr_ports = STHCD_MAX_PORTS; + sthcd->ports = kzalloc(sthcd->nr_ports * sizeof(struct sthcd_port), + GFP_KERNEL); + if (!sthcd->ports) + goto err_alloc_ports; + + INIT_LIST_HEAD(&sthcd->device_list); + init_waitqueue_head(&sthcd->rescan_waitq); + + error = usb_add_hcd(hcd, 0, 0); + if (error) { + drv_printk(KERN_INFO, "%s: error %d adding hcd\n", + __func__, error); + goto err_add; + } + + return 0; + +err_add: + kfree(sthcd->ports); +err_alloc_ports: + usb_put_hcd(hcd); +err: + return error; +} + +static int __devexit sthcd_driver_remove(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + usb_remove_hcd(hcd); + usb_put_hcd(hcd); + return 0; +} + + +/* + * Open Firmware platform device routines + * + */ + +static int __init sthcd_of_probe(struct of_device *odev, + const struct of_device_id *match) +{ + return sthcd_driver_probe(&odev->dev); +} + +static int __exit sthcd_of_remove(struct of_device *odev) +{ + return sthcd_driver_remove(&odev->dev); +} + +static struct of_device_id sthcd_of_match[] = { + { .compatible = "nintendo,starlet-ios-hcd" }, + { }, +}; + +MODULE_DEVICE_TABLE(of, sthcd_of_match); + +static struct of_platform_driver sthcd_of_driver = { + .owner = THIS_MODULE, + .name = DRV_MODULE_NAME, + .match_table = sthcd_of_match, + .probe = sthcd_of_probe, + .remove = sthcd_of_remove, +}; + + +/* + * Linux module framework + * + */ + +static int __init sthcd_module_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + drv_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION, + sthcd_driver_version); + + return of_register_platform_driver(&sthcd_of_driver); +} + +static void __exit sthcd_module_exit(void) +{ + of_unregister_platform_driver(&sthcd_of_driver); +} + +module_init(sthcd_module_init); +module_exit(sthcd_module_exit); + +MODULE_AUTHOR(DRV_AUTHOR); +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_LICENSE("GPL"); + diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 188e1ba..8aa2e6b 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -1650,6 +1650,25 @@ config CARMINE_DRAM_CUSTOM Use custom board timings. endchoice +config FB_GAMECUBE + bool "Nintendo GameCube/Wii frame buffer" + depends on FB && GAMECUBE_COMMON + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This is the frame buffer device driver for the Nintendo GameCube. + +config WII_AVE_RVL + bool "Nintendo Wii audio/video encoder support" + depends on FB_GAMECUBE && WII + select I2C_GPIO + select I2C + default y + help + Say Y here to support the audio/video encoder found in the + Nintendo Wii video game console. + config FB_AU1100 bool "Au1100 LCD Driver" depends on (FB = y) && MIPS && SOC_AU1100 diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 80232e1..161c99c 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -128,6 +128,7 @@ obj-$(CONFIG_XEN_FBDEV_FRONTEND) += xen-fbfront.o obj-$(CONFIG_FB_CARMINE) += carminefb.o obj-$(CONFIG_FB_MB862XX) += mb862xx/ obj-$(CONFIG_FB_MSM) += msm/ +obj-$(CONFIG_FB_GAMECUBE) += gcnfb.o # Platform or fallback drivers go here obj-$(CONFIG_FB_UVESA) += uvesafb.o diff --git a/drivers/video/gcnfb.c b/drivers/video/gcnfb.c new file mode 100644 index 0000000..24f8a82 --- /dev/null +++ b/drivers/video/gcnfb.c @@ -0,0 +1,2284 @@ +/* + * drivers/video/gcn-vifb.c + * + * Nintendo GameCube/Wii Video Interface (VI) frame buffer driver + * Copyright (C) 2004-2009 The GameCube Linux Team + * Copyright (C) 2004 Michael Steil + * Copyright (C) 2004,2005 Todd Jeffreys + * Copyright (C) 2006,2007,2008,2009 Albert Herranz + * + * Based on vesafb (c) 1998 Gerd Knorr + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_WII_AVE_RVL +#include +#endif + +#define DRV_MODULE_NAME "gcn-vifb" +#define DRV_DESCRIPTION "Nintendo GameCube/Wii Video Interface (VI) driver" +#define DRV_AUTHOR "Michael Steil , " \ + "Todd Jeffreys , " \ + "Albert Herranz" + +static char vifb_driver_version[] = "2.1i"; + +#define drv_printk(level, format, arg...) \ + printk(level DRV_MODULE_NAME ": " format , ## arg) + + +/* + * Hardware registers. + */ + +#define __declare_vi_reg_set_field(reg_size, reg, field_size, field, \ + mask, shift) \ +static inline reg_size vi_##reg##_set_##field(reg_size reg, field_size field) \ +{ \ + reg &= ~(mask << shift); \ + reg |= (field & mask) << shift; \ + return reg; \ +} + +#define __declare_vi_reg_clear_field(reg_size, reg, field, mask, shift) \ +static inline reg_size vi_##reg##_clear_##field(reg_size reg) \ +{ \ + reg &= ~(mask << shift); \ + return reg; \ +} + +#define __declare_vi_reg_get_field(reg_size, reg, field_size, field, \ + mask, shift) \ +static inline field_size vi_##reg##_get_##field(reg_size reg) \ +{ \ + return (reg>>shift)&mask; \ +} + +#define __declare_vi_reg_field(reg_size, reg, field_size, field, \ + mask, shift) \ +static inline reg_size vi_##reg##_##field(field_size field) \ +{ \ + return (field & mask) << shift; \ +} + +#define __vi_reg_field(reg_size, reg, field_size, field, mask, shift) \ +__declare_vi_reg_set_field(reg_size, reg, field_size, field, mask, shift) \ +__declare_vi_reg_clear_field(reg_size, reg, field, mask, shift) \ +__declare_vi_reg_get_field(reg_size, reg, field_size, field, mask, shift) \ +__declare_vi_reg_field(reg_size, reg, field_size, field, mask, shift) + + +#define VI_VTR 0x00 /* Vertical Timing, 16 bits */ +__vi_reg_field(u16, vtr, u16, acv, 0x3ff, 4); /* ACtive Video */ +__vi_reg_field(u16, vtr, u8, equ, 0xf, 0); /* EQUalization pulse */ + +#define VI_DCR 0x02 /* Display Configuration, 16 bits */ +__vi_reg_field(u16, dcr, u8, fmt, 0x3, 8); /* Format */ +__vi_reg_field(u16, dcr, u8, le1, 0x3, 6); /* Latch Enable 1 */ +__vi_reg_field(u16, dcr, u8, le0, 0x3, 4); /* Latch Enable 0 */ +__vi_reg_field(u16, dcr, u8, dlr, 0x1, 3); /* 3D mode */ +__vi_reg_field(u16, dcr, u8, nin, 0x1, 2); /* Non-Interlaced */ +__vi_reg_field(u16, dcr, u8, rst, 0x1, 1); /* Reset */ +__vi_reg_field(u16, dcr, u8, enb, 0x1, 0); /* Enable */ + +#define VI_HTR0 0x04 /* Horizontal Timing 0, 32 bits */ +__vi_reg_field(u32, htr0, u8, hcs, 0x7f, 24); /* Horz Color Start */ +__vi_reg_field(u32, htr0, u8, hce, 0x7f, 16); /* Horz Color End */ +__vi_reg_field(u32, htr0, u16, hlw, 0x1ff, 0); /* Half Line Width */ + +#define VI_HTR1 0x08 /* Horizontal Timing 1, 32 bits */ +__vi_reg_field(u32, htr1, u16, hbs, 0x3ff, 17); /* Horz Blank Start */ +__vi_reg_field(u32, htr1, u16, hbe, 0x3ff, 7); /* Horz Blank End */ +__vi_reg_field(u32, htr1, u8, hsy, 0x7f, 0); /* Horz Sync Width */ + +#define VI_VTO 0x0c /* Vertical Timing Odd, 32 bits */ +__vi_reg_field(u32, vto, u16, psb, 0x3ff, 16); /* Post Blanking */ +__vi_reg_field(u32, vto, u16, prb, 0x3ff, 0); /* Pre Blanking */ + +#define VI_VTE 0x10 /* Vertical Timing Even, 32 bits */ +__vi_reg_field(u32, vte, u16, psb, 0x3ff, 16); /* Post Blanking */ +__vi_reg_field(u32, vte, u16, prb, 0x3ff, 0); /* Pre Blanking */ + +#define VI_BBOI 0x14 /* Burst Blanking Odd Interval, 32 bits */ +__vi_reg_field(u32, bboi, u16, be3, 0x7ff, 21); +__vi_reg_field(u32, bboi, u8, bs3, 0x1f, 16); +__vi_reg_field(u32, bboi, u16, be1, 0x7ff, 5); +__vi_reg_field(u32, bboi, u8, bs1, 0x1f, 0); + +#define VI_BBEI 0x18 /* Burst Blanking Even Interval, 32 bits */ +__vi_reg_field(u32, bbei, u16, be4, 0x7ff, 21); +__vi_reg_field(u32, bbei, u8, bs4, 0x1f, 16); +__vi_reg_field(u32, bbei, u16, be2, 0x7ff, 5); +__vi_reg_field(u32, bbei, u8, bs2, 0x1f, 0); + +#define VI_TFBL 0x1c /* Top Field Base (L), 32 bits */ +__vi_reg_field(u32, tfbl, u8, pob, 0x1, 28); /* Page Offset Bit */ +__vi_reg_field(u32, tfbl, u8, xof, 0xf, 24); /* X Offset */ +__vi_reg_field(u32, tfbl, u32, fba, 0xffffff, 0); /* Frame Buf Address */ + +#define VI_TFBR 0x20 /* Top Field Base (R), 32 bits */ +__vi_reg_field(u32, tfbr, u8, pob, 0x1, 28); /* Page Offset Bit */ +__vi_reg_field(u32, tfbr, u32, fba, 0xffffff, 0); /* Frame Buf Address */ + +#define VI_BFBL 0x24 /* Bottom Field Base (L), 32 bits */ +__vi_reg_field(u32, bfbl, u8, pob, 0x1, 28); /* Page Offset Bit */ +__vi_reg_field(u32, bfbl, u8, xof, 0xf, 24); /* X Offset */ +__vi_reg_field(u32, bfbl, u32, fba, 0xffffff, 0); /* Frame Buf Address */ + +#define VI_BFBR 0x28 /* Bottom Field Base (R), 32 bits */ +__vi_reg_field(u32, bfbr, u8, pob, 0x1, 28); /* Page Offset Bit */ +__vi_reg_field(u32, bfbr, u32, fba, 0xffffff, 0); /* Frame Buf Address */ + +#define VI_DPV 0x2c /* Display Position Vertical, 16 bits */ +__vi_reg_field(u16, dpv, u16, val, 0x7ff, 0); + +#define VI_DPH 0x2e /* Display Position Horizontal, 16 bits */ +__vi_reg_field(u16, dph, u16, val, 0x7ff, 0); + +#define VI_DI0 0x30 /* Display Interrupt 0, 32 bits */ +#define VI_DI1 0x34 /* Display Interrupt 1, 32 bits */ +#define VI_DI2 0x38 /* Display Interrupt 2, 32 bits */ +#define VI_DI3 0x3C /* Display Interrupt 3, 32 bits */ +__vi_reg_field(u32, dix, u8, irq, 0x1, 31); +__vi_reg_field(u32, dix, u8, enb, 0x1, 28); +__vi_reg_field(u32, dix, u16, vct, 0x3ff, 16); +__vi_reg_field(u32, dix, u16, hct, 0x3ff, 0); + +#define VI_DL0 0x40 /* Display Latch 0, 32 bits */ + +#define VI_DL1 0x44 /* Display Latch 1, 32 bits */ + +#define VI_PCR 0x48 /* Picture Configuration, 16 bits */ +__vi_reg_field(u16, pcr, u8, wpl, 0xff, 8); /* reads per line in words */ +__vi_reg_field(u16, pcr, u8, std, 0xff, 0); /* stride per line in words */ + +#define VI_HSR 0x4a /* Horizontal Scaling, 16 bits */ +__vi_reg_field(u16, hsr, u8, hs_en, 0x1, 12); +__vi_reg_field(u16, hsr, u16, stp, 0x1ff, 0); + +#define VI_FCT0 0x4c /* Filter Coeficient Table 0, 32 bits */ +#define VI_FCT1 0x50 /* Filter Coeficient Table 1, 32 bits */ +#define VI_FCT2 0x54 /* Filter Coeficient Table 2, 32 bits */ +#define VI_FCT3 0x58 /* Filter Coeficient Table 3, 32 bits */ +#define VI_FCT4 0x5c /* Filter Coeficient Table 4, 32 bits */ +#define VI_FCT5 0x60 /* Filter Coeficient Table 5, 32 bits */ +#define VI_FCT6 0x64 /* Filter Coeficient Table 6, 32 bits */ + +#define VI_AA 0x68 /* Anti-aliasing, 32 bits */ + +#define VI_CLK 0x6c /* Video Clock, 16 bits */ +__vi_reg_field(u16, clk, u8, _54mhz, 0x1, 0); + +#define VI_SEL 0x6e /* DTV Status, 16 bits */ +__vi_reg_field(u16, sel, u8, component, 0x1, 0); + +#define VI_HSW 0x70 /* Horizontal Scaling Width, 16 bits */ +__vi_reg_field(u16, hsw, u16, width, 0x3ff, 0); + +#define VI_HBE 0x72 /* Horizontal Border End, 16 bits */ +#define VI_HBS 0x74 /* Horizontal Border Start, 16 bits */ + +#define VI_UNK1 0x76 /* Unknown1, 16 bits */ +#define VI_UNK2 0x78 /* Unknown2, 32 bits */ +#define VI_UNK3 0x7c /* Unknown3, 32 bits */ + + +enum { + VI_SCAN_DONTCARE = 0, + VI_SCAN_INTERLACED, + VI_SCAN_PROGRESSIVE, +}; +enum { + VI_RATE_DONTCARE = 0, + VI_RATE_50Hz, + VI_RATE_60Hz, +}; +enum { + VI_TV_DONTCARE = 0, + VI_TV_NTSC, + VI_TV_PAL, +}; + + +/* + * Video modes and timings. + * + */ + +enum { + VI_VM_NTSC_480i = 0, + VI_VM_NTSC_480p, + VI_VM_PAL_576i50, + VI_VM_PAL_480i60, + VI_VM_PAL_480p, +}; + +enum vi_video_format { + VI_FMT_NTSC = 0, + VI_FMT_PAL, + VI_FMT_MPAL, + VI_FMT_DEBUG, +}; + +enum vi_tv_mode_flags { + __PAL_COLOR, /* vs NTSC_COLOR */ + __PROGRESSIVE, /* vs interlaced */ +}; + +#define VI_VMF_PAL_COLOR (1<<__PAL_COLOR) +#define VI_VMF_PROGRESSIVE (1<<__PROGRESSIVE) + + +#define VI_VERT_ALIGN 0x1 /* in lines-1 */ +#define VI_HORZ_ALIGN 0xf /* in pixels-1 */ +#define VI_HORZ_WORD_SIZE 32 /* bytes */ + + +/* + * Video mode timings. + */ +struct vi_mode_timings { + /* VERTICAL SETTINGS */ + + /* + * NTSC 480i + * 1 field = 262.5 lines (242.5 active, 20 blank) + * 1 frame = 2 fields = 2 x 262.5 = 525 lines (485 active, 40 blank) + * + * PAL 576i + * 1 field = 312.5 lines (287.5 active, 25 blank) + * 1 frame = 2 fields = 2 x 312.5 = 625 lines (575 active, 50 blank) + * + * NOTES: + * - the start of sync is considered the start of a line + * - the width of a half line is the width of a line divided by two + * + */ + + /* + * Vertical position of the first active video line (0=top). + */ + unsigned int ypos; + + /* + * Horizontal position in pixels where the vertical blanking + * interval starts. Used for signaling the start of the vertical + * retrace. + */ + unsigned int htrap; + + /* + * Vertical position in field lines where the vertical blanking + * interval starts. Used for signaling the start of the vertical + * retrace. + */ + unsigned int vtrap; + + /* + * Active Video, specified in number of field lines. + */ + u16 acv; + /* + * Equalization pulse, specified in number of half lines. + */ + u8 equ; + + /* + * Pre-blanking, specified in half lines. + */ + u16 prb_odd; + u16 prb_even; + + /* + * Post-blanking, specified in half lines. + */ + u16 psb_odd; + u16 psb_even; + + /* + * NOTE: + * Irrespective of what patent 6,609,977 says: + * - "bs*" seems to tell where the burst blanking for the current + * field ends + * - "be*" seems to tell where the next burst blanking starts + */ + + /* + * Patent says: "Start to burst blanking start in half lines". + */ + u8 bs1; + u8 bs2; + u8 bs3; + u8 bs4; + + /* + * Patent says: "Start to burst blanking end in half lines". + */ + u16 be1; + u16 be2; + u16 be3; + u16 be4; + + /* HORIZONTAL SETTINGS */ + + /* + * A = Blank Start to Horizontal Sync Start, "Front Porch" + * right_margin + * B = Horizontal Sync Width + * hsync_len + * C = Horizontal Sync End to Blank End, "Back Porch" + * left_margin + * D = Horizontal Line Width + * hsync_len + left_margin + xres + right_margin + * E = Horizontal Visible Width + * xres + * + * :<-----------------D----------------->: + * : : : :<----------E-------->: : + * ::<-B->:<-C->: ::<-B->:<-C->: + * : : : : : : : : + * ___ __________//_________ __ + * | | | | + * | | | | + * Blank |___ _____| |___ _____| + * | | | | + * Sync |_____| |_____| + * + * + * f = Sync Start to Color Burst Start + * g = Color Burst Width + * + * : : : : + * :<--A-->:<-----B----->:<-------C-------->: + * : : : : + * _ Peak white level + * | Color | + * | Burst | + * |_______ ______|||||||||___| _ Blanking level + * | Sync | ||||||||| + * |_____________| _ Sync level + * + * : : : : : + * : :<---------f-------->:<--g-->: : + * :<--------------- A + B + C ------------>: + * : : + * + */ + + /* Half (horizontal) line width, in pixel clocks (D/2) */ + u16 hlw; + + /* Horizontal Sync Width, in pixel clocks (B) */ + u8 hsy; + + /* NOTE + * The color burst interval falls within the back porch, + * i.e. hcs must be greater than B and hce lower than B+C. + */ + + /* Horizontal sync start to color burst start in pixel clocks (f) */ + u8 hcs; + /* Horizontal sync start to color burst end in pixel clocks (f+g) */ + u8 hce; + + /* + * The following two settings depend on the effective horizontal + * line length, as they rely on A or C. + */ + + /* Half line to horizontal blank start (D/2 - A)*/ + u16 hbs; + + /* Horizontal sync start to horizontal blank end (B+C)*/ + u16 hbe; + +}; + +/* + * TV mode. + */ +struct vi_tv_mode { + char *name; + __u32 flags; + int width; /* visible width in pixels */ + int height; /* visible height in lines */ + int lines; /* total lines */ +}; + +/* + * Video control data structure. + */ +struct vi_ctl { + spinlock_t lock; + + void __iomem *io_base; + unsigned int irq; + + int in_vtrace; + wait_queue_head_t vtrace_waitq; + + int visible_page; + unsigned long page_address[2]; + unsigned long flip_pending; + + struct vi_tv_mode *mode; + struct vi_mode_timings timings; + int has_component_cable:1; /* at last detection time */ + + struct fb_info *info; +#ifdef CONFIG_WII_AVE_RVL + struct i2c_client *i2c_client; +#endif +}; + + +/* + * TV Mode Table + */ +static struct vi_tv_mode vi_tv_modes[] = { + [VI_VM_NTSC_480i] = { + .name = "NTSC 480i", + .width = 640, + .height = 480, + .lines = 525, + }, + [VI_VM_NTSC_480p] = { + .name = "NTSC 480p", + .flags = VI_VMF_PROGRESSIVE, + .width = 640, + .height = 480, + .lines = 525, + }, + [VI_VM_PAL_576i50] = { + .name = "PAL 576i", + .flags = VI_VMF_PAL_COLOR, + .width = 640, + .height = 574, + .lines = 625, + }, + [VI_VM_PAL_480i60] = { + .name = "PAL 480i 60Hz", + .flags = VI_VMF_PAL_COLOR, + .width = 640, + .height = 480, + .lines = 525, + }, + [VI_VM_PAL_480p] = { + .name = "PAL 480p", + .flags = VI_VMF_PROGRESSIVE|VI_VMF_PAL_COLOR, + .width = 640, + .height = 480, + .lines = 525, + }, +}; + +/* + * Filter Coeficient Table + */ +static const u32 vi_fct[] = { + 0x1AE771F0, 0x0DB4A574, 0x00C1188E, 0xC4C0CBE2, + 0xFCECDECF, 0x13130F08, 0x00080C0F, +}; + + +/* + * Default fix and var framebuffer data. + */ +static struct fb_fix_screeninfo vifb_fix __devinitdata = { + .id = DRV_MODULE_NAME, + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_TRUECOLOR, /* lies, lies, lies, ... */ + .accel = FB_ACCEL_NONE, +}; + +static struct fb_var_screeninfo vifb_var = { + .activate = FB_ACTIVATE_NOW, + .width = 640, + .height = 480, + .bits_per_pixel = 16, + .vmode = FB_VMODE_INTERLACED, +}; + + +/* + * Setup parameters. + */ +static int want_ypan = 1; /* 0..nothing, 1..ypan */ + +/* use old behaviour for video mode settings */ +static int nostalgic; + +static int force_scan; +static int force_rate; +static int force_tv; + +static u32 pseudo_palette[17]; + + + +/* + * + * + */ + +#ifdef CONFIG_WII_AVE_RVL +static int vi_ave_setup(struct vi_ctl *ctl); +static int vi_ave_get_video_format(struct vi_ctl *ctl, + enum vi_video_format *fmt); +#endif + +/* some glue to the gx side */ +static inline void gcngx_dispatch_vtrace(struct vi_ctl *ctl) +{ +} + + +/* + * + * Color space handling. + */ + +/* + * RGB to YCbYCr conversion support bits. + * We are using here the ITU.BT-601 Y'CbCr standard. + * + * References: + * - "Colour Space Conversions" by Adrian Ford and Alan Roberts, 1998 + * (google for coloureq.pdf) + * + */ + +#define RGB2YUV_SHIFT 16 +#define RGB2YUV_LUMA 16 +#define RGB2YUV_CHROMA 128 + +#define Yr ((int)(0.299 * (1<> 11) & 0x1f); + g1 = ((rgb1 >> 5) & 0x3f); + b1 = ((rgb1 >> 0) & 0x1f); + + /* fast (approximated) scaling to 8 bits, thanks to Masken */ + r1 = (r1 << 3) | (r1 >> 2); + g1 = (g1 << 2) | (g1 >> 4); + b1 = (b1 << 3) | (b1 >> 2); + + Y1 = clamp(((Yr * r1 + Yg * g1 + Yb * b1) >> RGB2YUV_SHIFT) + + RGB2YUV_LUMA, 16, 235); + if (rgb1 == rgb2) { + /* this is just another fast path */ + Y2 = Y1; + r = r1; + g = g1; + b = b1; + } else { + /* same as we did for r1 before */ + r2 = ((rgb2 >> 11) & 0x1f); + g2 = ((rgb2 >> 5) & 0x3f); + b2 = ((rgb2 >> 0) & 0x1f); + r2 = (r2 << 3) | (r2 >> 2); + g2 = (g2 << 2) | (g2 >> 4); + b2 = (b2 << 3) | (b2 >> 2); + + Y2 = clamp(((Yr * r2 + Yg * g2 + Yb * b2) >> RGB2YUV_SHIFT) + + RGB2YUV_LUMA, + 16, 235); + + r = (r1 + r2) / 2; + g = (g1 + g2) / 2; + b = (b1 + b2) / 2; + } + + Cb = clamp(((Ur * r + Ug * g + Ub * b) >> RGB2YUV_SHIFT) + + RGB2YUV_CHROMA, 16, 240); + Cr = clamp(((Vr * r + Vg * g + Vb * b) >> RGB2YUV_SHIFT) + + RGB2YUV_CHROMA, 16, 240); + + return (((uint8_t) Y1) << 24) | (((uint8_t) Cb) << 16) | + (((uint8_t) Y2) << 8) | (((uint8_t) Cr) << 0); +} + + +/* + * Video mode timings calculation. + * + * Please, refer to the definition of "struct vi_mode_timings" for + * a explanation of the different constants involved. + * + * References: + * - http://www.pembers.freeserve.co.uk/World-TV-Standards + */ + +static inline int vi_vmode_is_progressive(__u32 vmode) +{ + return (vmode & FB_VMODE_MASK) == FB_VMODE_NONINTERLACED; +} + +static int vi_calc_horz_timings(struct vi_mode_timings *timings, + struct fb_var_screeninfo *var, + u16 width, u16 max_active_width, + u16 A, u8 B, u16 C, u16 D, + u8 f, u16 g) +{ + u16 extra_blanking, margin; + + if (width > max_active_width) + return -EINVAL; + + /* adjusted horizontal settings */ + extra_blanking = max_active_width - width; + margin = extra_blanking / 2; + A += margin; + C += extra_blanking - margin; + + timings->hlw = D / 2; + timings->hsy = B; + timings->hcs = f; + timings->hce = f + g; + timings->hbs = (D/2) - A; + timings->hbe = B + C; + + /* + * Start of the blanking interval, between the first and second fields, + * begins after the last half line of the field. + */ + timings->htrap = (D / 2) + 1; + + var->left_margin = C; + var->right_margin = A; + var->hsync_len = B; + + return 0; +} + +static int vi_ntsc_525_calc_horz_timings(struct vi_mode_timings *timings, + struct fb_var_screeninfo *var, + u16 width) +{ + u16 max_active_width; + u16 A, C, D, g; + u8 B, f; + + /* standard horizontal settings for 714 pixels */ + D = 858; /* pixel clocks (H=63.556us, 13.5MHz clock) */ + max_active_width = 714; /* (52.9us) 714.15 pixel clocks */ + B = 64; /* ( 4.7us) 63.45 pixel clocks */ + f = 71; /* ( 5.3us) 71.55 pixel clocks */ + g = 34; /* ( 2.5us) 33.75 pixel clocks */ + A = 20; /* ( 1.5us) 20.25 pixel clocks */ + C = 60; /* ( 4.5us) 60.75 pixel clocks */ + + return vi_calc_horz_timings(timings, var, width, max_active_width, + A, B, C, D, f, g); +} + +static int vi_calc_vert_timings(struct vi_mode_timings *timings, + struct fb_var_screeninfo *var, + u16 height, u16 max_active_height, + u16 P, u16 Q, u8 equ) +{ + u16 extra_blanking, margin, prb, psb; + u8 interlace_bias; + u8 shift; + + if (height > max_active_height) + return -EINVAL; + + extra_blanking = max_active_height - height; /* in frame lines */ + margin = extra_blanking / 2; /* centered margins */ + prb = margin; /* in half lines */ + psb = extra_blanking - margin; /* in half lines */ + + /* + * Start of the blanking interval, between the first and second fields, + * begins after the last line of the field. + */ + if (vi_vmode_is_progressive(var->vmode)) { + timings->acv = height; + timings->vtrap = prb + height; + interlace_bias = 0; + shift = 1; + } else { + timings->acv = height / 2; + timings->vtrap = (prb + height) / 2; + interlace_bias = 1; + shift = 0; + } + + timings->equ = equ << shift; + var->vsync_len = (3 * timings->equ) / 2; /* pre-eq + sync + post-eq */ + + /* + * prb_* is specified as the number of half-lines since the end of + * the post-equalizing period. + * psb_* is specified as the number of half-lines from the end of + * the field. + */ + + timings->ypos = margin; + + if (timings->ypos & 0x01) { + /* odd field (1,3,5,...) */ + timings->prb_odd = (P + interlace_bias + prb) << shift; + timings->psb_odd = (Q - interlace_bias + psb) << shift; + timings->prb_even = (P + prb) << shift; + timings->psb_even = (Q + psb) << shift; + } else { + /* even field (2,4,6,...) */ + timings->prb_even = (P + interlace_bias + prb) << shift; + timings->psb_even = (Q - interlace_bias + psb) << shift; + timings->prb_odd = (P + prb) << shift; + timings->psb_odd = (Q + psb) << shift; + } + + var->upper_margin = (Q + prb) / 2; + var->lower_margin = (P + psb) / 2; + + return 0; +} + +static int vi_ntsc_525_calc_vert_timings(struct vi_mode_timings *timings, + struct fb_var_screeninfo *var, + u16 height) +{ + u16 max_active_height; + u16 P, Q; + u8 equ; + + /* standard vertical settings for 484 active lines */ + max_active_height = 484; /* 2 * 242.5 = 485 (*1) */ + + /* blanking interval */ + /* from start of line 10, field 1 to end of line 20, field 1 */ + P = 2 * (20-10 + 1); + Q = 1; /* (*1) field line compensation for 484 vs 485 lines */ + + equ = 2 * 3; /* 3 lines of equalization */ + + return vi_calc_vert_timings(timings, var, height, max_active_height, + P, Q, equ); +} + +static int vi_pal_625_calc_timings(struct vi_mode_timings *timings, + struct fb_var_screeninfo *var, + unsigned int width, unsigned int height) +{ + u16 max_active_height, max_active_width; + u16 A, C, D, g, P, Q; + u8 B, f, equ; + int error; + + /* standard horizontal settings for 702 pixels */ + D = 864; /* pixel clocks (H=64us, 13.5MHz clock) */ + max_active_width = 702; /* (51.95us) 701.32 pixel clocks */ + B = 64; /* ( 4.7us) 63.45 pixel clocks */ + f = 75; /* ( 5.6us) 75.6 pixel clocks */ + g = 30; /* ( 2.25us) 30.38 pixel clocks */ + A = 22; /* ( 1.65us) 22.27 pixel clocks */ + C = 76; /* ( 5.7us) 76.95 pixel clocks */ + + error = vi_calc_horz_timings(timings, var, width, max_active_width, + A, B, C, D, f, g); + if (error) + goto err_out; + + /* standard vertical settings for 574 active lines */ + max_active_height = 574; /* 2 * 287.5 = 575 (*1) */ + + /* blanking interval */ + /* from start of line 6, field 1 to mid of line 23, field 1 */ + P = (2 * (23-6 + 1)) - 1; + Q = 1; /* (*1) field line compensation for 574 vs 575 lines */ + + equ = 2 * 2.5; /* 2.5 lines of equalization */ + + error = vi_calc_vert_timings(timings, var, height, max_active_height, + P, Q, equ); + if (error) + goto err_out; + + /* + * Location of the 9 lines of burst blanking for each field + * (settings expressed in half lines). + */ + + /* from start of line 1, field 1 to end of line 6, field 1 */ + timings->bs1 = 2 * (6-1 + 1); + + /* from start of line 1, field 1 to end of line 309, field 2 */ + timings->be1 = 2 * (309-1 + 1); + + /* from mid of line 313, field 2 to end of line 318, field 2 */ + timings->bs2 = (2 * (318-313 + 1)) - 1; + + /* from mid of line 313, field 2 to end of line 621, field 2 */ + timings->be2 = (2 * (612-617 + 1)) - 1; + + /* from start of line 1, field 3 to end of line 5, field 3 */ + timings->bs3 = 2 * (5-1 + 1); + + /* from start of line 1, field 3 to end of line 310, field 4 */ + timings->be3 = 2 * (310-1 + 1); + + /* from mid of line 313, field 4 to end of line 319, field 4 */ + timings->bs4 = (2 * (319-313 + 1)) - 1; + + /* from mid of line 313, field 4 to end of line 622, field 4 */ + timings->be4 = (2 * (622-313 + 1)) - 1; + +err_out: + return error; +} + +static int vi_ntsc_525_calc_timings(struct vi_mode_timings *timings, + struct fb_var_screeninfo *var, + unsigned int width, unsigned int height) +{ + int error; + + error = vi_ntsc_525_calc_horz_timings(timings, var, width); + if (error) + goto err_out; + + error = vi_ntsc_525_calc_vert_timings(timings, var, height); + if (error) + goto err_out; + + /* + * Location of the 9 lines of burst blanking for each field + * (settings expressed in half lines). + */ + + /* from start of line 4, field 1 to end of line 9, field 1 */ + timings->bs1 = 2 * (9-4 + 1); + + /* from start of line 4, field 1 to end of line 263, field 2 */ + timings->be1 = 2 * (263-4 + 1); + + /* from mid of line 266, field 2 to end of line 272, field 2 */ + timings->bs2 = (2 * (272-266 + 1)) - 1; + + /* from mid of line 266, field 2 to end of line 525, field 2 */ + timings->be2 = (2 * (525-266 + 1)) - 1; + + /* from start of line 4, field 3 to end of line 9, field 3 */ + timings->bs3 = 2 * (9-4 + 1); + + /* from start of line 4, field 3 to end of line 263, field 4 */ + timings->be3 = 2 * (263-4 + 1); + + /* from mid of line 266, field 4 to end of line 272, field 4 */ + timings->bs4 = (2 * (272-266 + 1)) - 1; + + /* from mid of line 266, field 4 to end of line 525, field 4 */ + timings->be4 = (2 * (525-266 + 1)) - 1; + +err_out: + return error; +} + +static int vi_ntsc_525_prog_calc_timings(struct vi_mode_timings *timings, + struct fb_var_screeninfo *var, + unsigned int width, + unsigned int height) +{ + int error; + + error = vi_ntsc_525_calc_horz_timings(timings, var, width); + if (error) + goto err_out; + + error = vi_ntsc_525_calc_vert_timings(timings, var, height); + if (error) + goto err_out; + + /* + * Location of the 18 lines of burst blanking + * (settings expressed in half lines). + */ + + /* + * |0 0 0 0 0 0|0 0 0 1 1 1|1 1 1 1 1 1| + * |1,2,3,4,5,6|7,8,9,0,1,2|3,4,5,6,7,8| + * :pre-equ :sync : post-equ : + */ + + /* from start of line 7 to end of line 18 */ + timings->bs1 = 2 * (18-7 + 1); + timings->bs2 = timings->bs3 = timings->bs4 = timings->bs1; + + /* from start of line 7 to end of line 525 (last) */ + timings->be1 = 2 * (525-7 + 1); + timings->be2 = timings->be3 = timings->be4 = timings->be1; + +err_out: + return error; +} + + + +/* + * Video hardware support. + * + */ + +static inline int vi_has_component_cable(struct vi_ctl *ctl) +{ + return vi_sel_get_component(in_be16(ctl->io_base + VI_SEL)); +} + +/* + * Get video mode reported by hardware. + * 0=NTSC, 1=PAL, 2=MPAL, 3=debug + */ +static inline enum vi_video_format vi_get_video_format(struct vi_ctl *ctl) +{ + return vi_dcr_get_fmt(in_be16(ctl->io_base + VI_DCR)); +} + +static inline int vi_video_format_is_ntsc(struct vi_ctl *ctl) +{ + return vi_get_video_format(ctl) == VI_FMT_NTSC; +} + +static void vi_reset_video(struct vi_ctl *ctl) +{ + void __iomem *io_base = ctl->io_base; + u16 dcr; + + dcr = in_be16(io_base + VI_DCR); + out_be16(io_base + VI_DCR, vi_dcr_set_rst(dcr, 1)); + out_be16(io_base + VI_DCR, vi_dcr_clear_rst(dcr)); +} + +/* + * Try to determine current TV video mode. + */ +static void vi_detect_tv_mode(struct vi_ctl *ctl) +{ + struct vi_tv_mode *modes = vi_tv_modes, *mode; + void __iomem *io_base = ctl->io_base; + char *guess = ""; + enum vi_video_format fmt; + int ntsc_idx, pal_idx; + u16 dcr; + int error; + + dcr = in_be16(io_base + VI_DCR); + + ctl->has_component_cable = vi_has_component_cable(ctl); + + if ((force_scan == VI_SCAN_PROGRESSIVE && + ctl->has_component_cable) || + (force_scan != VI_SCAN_INTERLACED && + ctl->has_component_cable && + vi_dcr_get_nin(dcr))) { + /* progressive modes */ + ntsc_idx = VI_VM_NTSC_480p; + pal_idx = VI_VM_PAL_480p; + } else { + /* interlaced modes */ + ntsc_idx = VI_VM_NTSC_480i; + if (force_rate == VI_RATE_50Hz || + (force_rate != VI_RATE_60Hz && + vi_dcr_get_fmt(dcr) == VI_FMT_PAL)) + pal_idx = VI_VM_PAL_576i50; + else + pal_idx = VI_VM_PAL_480i60; + } + + if (force_tv == VI_TV_PAL || + (force_tv != VI_TV_NTSC && pal_idx == VI_VM_PAL_576i50)) + fmt = VI_FMT_PAL; + else if (force_tv == VI_TV_NTSC) + fmt = VI_FMT_NTSC; + else { +#ifdef CONFIG_WII_AVE_RVL + /* + * Look at the audio/video encoder to detect true PAL vs NTSC. + */ + error = vi_ave_get_video_format(ctl, &fmt); + if (error) { + guess = " (initial guess)"; + if (force_tv == VI_TV_PAL || + pal_idx == VI_VM_PAL_576i50) + fmt = VI_FMT_PAL; + else + fmt = VI_FMT_NTSC; + } +#else + error = 0; + fmt = vi_get_video_format(ctl); +#endif + } + switch (fmt) { + case VI_FMT_PAL: + mode = modes + pal_idx; + break; + case VI_FMT_MPAL: + case VI_FMT_DEBUG: + /* we currently don't support MPAL or DEBUG, sorry */ + /* FALLTHROUGH */ + case VI_FMT_NTSC: + default: + mode = modes + ntsc_idx; + break; + } + + ctl->mode = mode; + + drv_printk(KERN_INFO, "%s%s\n", mode->name, guess); +} + +/* + * Initialize the video hardware for a given TV mode. + */ +static int vi_setup_tv_mode(struct vi_ctl *ctl) +{ + void __iomem *io_base = ctl->io_base; + struct vi_mode_timings *timings = &ctl->timings; + struct fb_var_screeninfo *var = &ctl->info->var; + unsigned int bytes_per_pixel = var->bits_per_pixel / 8; + struct vi_tv_mode *mode; + int has_component_cable; + u16 std, ppl; + + /* we need to re-detect the tv mode if the cable type changes */ + has_component_cable = vi_has_component_cable(ctl); + if ((ctl->has_component_cable && !has_component_cable) || + (!ctl->has_component_cable && has_component_cable)) + vi_detect_tv_mode(ctl); + + mode = ctl->mode; + + out_be16(io_base + VI_DCR, + vi_dcr_fmt((mode->lines == 625) ? VI_FMT_PAL : VI_FMT_NTSC) | + vi_dcr_nin((mode->flags & VI_VMF_PROGRESSIVE) ? 1 : 0) | + vi_dcr_enb(1)); + + out_be16(io_base + VI_VTR, + vi_vtr_equ(timings->equ) | vi_vtr_acv(timings->acv)); + + out_be32(io_base + VI_HTR0, + vi_htr0_hcs(timings->hcs) | vi_htr0_hce(timings->hce) | + vi_htr0_hlw(timings->hlw)); + + out_be32(io_base + VI_HTR1, + vi_htr1_hbs(timings->hbs) | vi_htr1_hbe(timings->hbe) | + vi_htr1_hsy(timings->hsy)); + + out_be32(io_base + VI_VTO, + vi_vto_prb(timings->prb_odd) | vi_vto_psb(timings->psb_odd)); + + out_be32(io_base + VI_VTE, + vi_vte_prb(timings->prb_even) | vi_vte_psb(timings->psb_even)); + + out_be32(io_base + VI_BBOI, + vi_bboi_bs1(timings->bs1) | vi_bboi_be1(timings->be1) | + vi_bboi_bs3(timings->bs3) | vi_bboi_be3(timings->be3)); + + out_be32(io_base + VI_BBEI, + vi_bbei_bs2(timings->bs2) | vi_bbei_be2(timings->be2) | + vi_bbei_bs4(timings->bs4) | vi_bbei_be4(timings->be4)); + + /* used only for 3D stuff */ + out_be32(io_base + VI_TFBR, 0); + out_be32(io_base + VI_BFBR, 0); + + std = (var->xres_virtual * bytes_per_pixel) / VI_HORZ_WORD_SIZE; + if (!(mode->flags & VI_VMF_PROGRESSIVE)) + std *= 2; + ppl = _ALIGN_UP((var->xoffset & VI_HORZ_ALIGN) + var->xres, + VI_HORZ_ALIGN+1); + out_be16(io_base + VI_PCR, + vi_pcr_std(std) | + vi_pcr_wpl((ppl * bytes_per_pixel) / VI_HORZ_WORD_SIZE)); + + /* scaler is disabled */ + out_be16(io_base + VI_HSR, vi_hsr_stp(256) | vi_hsr_hs_en(0)); + + /* filter coeficient table, anti-aliasing */ + out_be32(io_base + VI_FCT0, vi_fct[0]); + out_be32(io_base + VI_FCT1, vi_fct[1]); + out_be32(io_base + VI_FCT2, vi_fct[2]); + out_be32(io_base + VI_FCT3, vi_fct[3]); + out_be32(io_base + VI_FCT4, vi_fct[4]); + out_be32(io_base + VI_FCT5, vi_fct[5]); + out_be32(io_base + VI_FCT6, vi_fct[6]); + out_be32(io_base + VI_AA, 0x00ff0000); + + /* clock */ + out_be16(io_base + VI_CLK, + vi_clk__54mhz((mode->flags & VI_VMF_PROGRESSIVE) ? 1 : 0)); + + /* superfluous, no scaler */ + out_be16(io_base + VI_HSW, vi_hsw_width(var->xres)); + + /* borders for DEBUG mode encoder, not used in retail consoles */ + out_be16(io_base + VI_HBE, 0); + out_be16(io_base + VI_HBS, 0); + + /* whatever */ + out_be16(io_base + VI_UNK1, 0x00ff); + out_be32(io_base + VI_UNK2, 0x00ff00ff); + out_be32(io_base + VI_UNK3, 0x00ff00ff); + +#ifdef CONFIG_WII_AVE_RVL + vi_ave_setup(ctl); +#endif + + return 0; +} + +/* + * Set the address from where the video encoder will display data on screen. + */ +void vi_set_framebuffer(struct vi_ctl *ctl, u32 addr) +{ + struct fb_info *info = ctl->info; + void __iomem *io_base = ctl->io_base; + u32 top, bot; + u8 xof; + + top = bot = addr; + if (!vi_vmode_is_progressive(info->var.vmode)) { + if (ctl->timings.ypos & 0x01) + top += info->fix.line_length; + else + bot += info->fix.line_length; + } + xof = (top / 2) & VI_HORZ_ALIGN; + + out_be32(io_base + VI_TFBL, + vi_tfbl_pob(1) | vi_tfbl_xof(xof) | vi_tfbl_fba(top >> 5)); + out_be32(io_base + VI_BFBL, vi_bfbl_pob(1) | vi_bfbl_fba(bot >> 5)); +} + +/* + * Swap the visible and back pages. + */ +static inline void vi_flip_page(struct vi_ctl *ctl) +{ + ctl->visible_page ^= 1; + vi_set_framebuffer(ctl, ctl->page_address[ctl->visible_page]); + + ctl->flip_pending = 0; +} + +static void vi_enable_interrupts(struct vi_ctl *ctl, int enable) +{ + void __iomem *io_base = ctl->io_base; + + if (enable) { + /* + * We use DI0 and DI1 to signal the retrace interval. + */ + + /* start of the vertical retrace */ + out_be32(io_base + VI_DI1, + vi_dix_irq(1) | vi_dix_enb(1) | + vi_dix_vct(ctl->timings.vtrap) | + vi_dix_hct(ctl->timings.htrap)); + + /* end of the vertical retrace */ + out_be32(io_base + VI_DI0, + vi_dix_irq(1) | vi_dix_enb(1) | + vi_dix_vct(1) | + vi_dix_hct(1)); + } else { + out_be32(io_base + VI_DI0, 0); + out_be32(io_base + VI_DI1, 0); + } + /* these two are currently not used */ + out_be32(io_base + VI_DI2, 0); + out_be32(io_base + VI_DI3, 0); +} + +static void vi_dispatch_vtrace(struct vi_ctl *ctl) +{ + unsigned long flags; + + spin_lock_irqsave(&ctl->lock, flags); + if (ctl->flip_pending) + vi_flip_page(ctl); + spin_unlock_irqrestore(&ctl->lock, flags); + + wake_up_interruptible(&ctl->vtrace_waitq); +} + +static irqreturn_t vi_irq_handler(int irq, void *dev) +{ + struct fb_info *info = dev_get_drvdata((struct device *)dev); + struct vi_ctl *ctl = info->par; + void __iomem *io_base = ctl->io_base; + u32 val; + + /* DI0 and DI1 are used to account for the vertical retrace */ + val = in_be32(io_base + VI_DI0); + if (vi_dix_get_irq(val)) { + ctl->in_vtrace = 0; + gcngx_dispatch_vtrace(ctl); /* backwards compatibility */ + + out_be32(io_base + VI_DI0, vi_dix_clear_irq(val)); + return IRQ_HANDLED; + } + val = in_be32(io_base + VI_DI1); + if (vi_dix_get_irq(val)) { + ctl->in_vtrace = 1; + vi_dispatch_vtrace(ctl); + gcngx_dispatch_vtrace(ctl); /* backwards compatibility */ + + out_be32(io_base + VI_DI1, vi_dix_clear_irq(val)); + return IRQ_HANDLED; + } + + /* currently unused, just in case */ + val = in_be32(io_base + VI_DI2); + if (vi_dix_get_irq(val)) { + out_be32(io_base + VI_DI2, vi_dix_clear_irq(val)); + return IRQ_HANDLED; + } + val = in_be32(io_base + VI_DI3); + if (vi_dix_get_irq(val)) { + out_be32(io_base + VI_DI3, vi_dix_clear_irq(val)); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +#ifdef CONFIG_WII_AVE_RVL + +/* + * Audio/Video Encoder hardware support. + * + */ + +/* + * I/O accessors. + */ + +static int vi_ave_outs(struct i2c_client *client, u8 reg, + void *data, size_t len) +{ + struct i2c_adapter *adap = client->adapter; + struct i2c_msg msg[1]; + u8 buf[34]; + s32 result; + int error = -EINVAL; + + if (len > sizeof(buf)-1) + goto err_out; + + buf[0] = reg; + memcpy(&buf[1], data, len); + + msg[0].addr = client->addr; + msg[0].flags = client->flags & I2C_M_TEN; + msg[0].len = len+1; + msg[0].buf = buf; + + result = i2c_transfer(adap, msg, 1); + if (result < 0) + error = result; + else if (result == 1) + error = 0; + else + error = -EIO; + +err_out: + if (error) + drv_printk(KERN_ERR, "RVL-AVE: " + "error (%d) writing to register %02Xh\n", + error, reg); + return error; +} + +static int vi_ave_out8(struct i2c_client *client, u8 reg, u8 data) +{ + return vi_ave_outs(client, reg, &data, sizeof(data)); +} + +static int vi_ave_out16(struct i2c_client *client, u8 reg, u16 data) +{ + cpu_to_be16s(&data); + return vi_ave_outs(client, reg, &data, sizeof(data)); +} + +static int vi_ave_out32(struct i2c_client *client, u8 reg, u32 data) +{ + cpu_to_be32s(&data); + return vi_ave_outs(client, reg, &data, sizeof(data)); +} + +static int vi_ave_ins(struct i2c_client *client, u8 reg, + void *data, size_t len) +{ + struct i2c_adapter *adap = client->adapter; + struct i2c_msg msg[2]; + s32 result; + int error; + + msg[0].addr = client->addr; + msg[0].flags = client->flags & I2C_M_TEN; + msg[0].len = sizeof(reg); + msg[0].buf = ® + + msg[1].addr = client->addr; + msg[1].flags = (client->flags & I2C_M_TEN) | I2C_M_RD; + msg[1].len = len; + msg[1].buf = data; + + result = i2c_transfer(adap, msg, 2); + if (result < 0) + error = result; + else if (result == 2) + error = 0; + else + error = -EIO; + + if (error) + drv_printk(KERN_ERR, "RVL-AVE: " + "error (%d) reading from register %02Xh\n", + error, reg); + + return error; +} + +static int vi_ave_in8(struct i2c_client *client, u8 reg, u8 *data) +{ + return vi_ave_ins(client, reg, data, sizeof(*data)); +} + + +/* + * Try to detect current video format. + */ +static int vi_ave_get_video_format(struct vi_ctl *ctl, + enum vi_video_format *fmt) +{ + u8 val = 0xff; + int error = -ENODEV; + + if (!ctl->i2c_client) + goto err_out; + + error = vi_ave_in8(ctl->i2c_client, 0x01, &val); + if (error) + goto err_out; + + if ((val & 0x1f) == 2) + *fmt = VI_FMT_PAL; + else + *fmt = VI_FMT_NTSC; +err_out: + return error; +} + + +static u8 vi_ave_gamma[] = { + 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, + 0x10, 0x00, 0x10, 0x00, 0x10, 0x20, 0x40, 0x60, + 0x80, 0xa0, 0xeb, 0x10, 0x00, 0x20, 0x00, 0x40, + 0x00, 0x60, 0x00, 0x80, 0x00, 0xa0, 0x00, 0xeb, + 0x00 +}; + +/* + * Initialize the audio/video encoder. + */ +static int vi_ave_setup(struct vi_ctl *ctl) +{ + struct i2c_client *client; + u8 macrovision[26]; + u8 component, format, pal60; + + if (!ctl->i2c_client) + return -ENODEV; + + client = ctl->i2c_client; + memset(macrovision, 0, sizeof(macrovision)); + + /* + * Magic initialization sequence borrowed from libogc. + */ + + vi_ave_out8(client, 0x6a, 1); + vi_ave_out8(client, 0x65, 1); + + /* + * NOTE + * We _can't_ use the fmt field in DCR to derive "format" here. + * DCR uses fmt=0 (NTSC) also for PAL 525 modes. + */ + + format = 0; /* default to NTSC */ + if ((ctl->mode->flags & VI_VMF_PAL_COLOR) != 0) + format = 2; /* PAL */ + component = (ctl->has_component_cable) ? 1<<5 : 0; + vi_ave_out8(client, 0x01, component | format); + + vi_ave_out8(client, 0x00, 0); + vi_ave_out16(client, 0x71, 0x8e8e); + vi_ave_out8(client, 0x02, 7); + vi_ave_out16(client, 0x05, 0x0000); + vi_ave_out16(client, 0x08, 0x0000); + vi_ave_out32(client, 0x7a, 0x00000000); + vi_ave_outs(client, 0x40, macrovision, sizeof(macrovision)); + vi_ave_out8(client, 0x0a, 0); + vi_ave_out8(client, 0x03, 1); + vi_ave_outs(client, 0x10, vi_ave_gamma, sizeof(vi_ave_gamma)); + vi_ave_out8(client, 0x04, 1); + + vi_ave_out32(client, 0x7a, 0x00000000); + vi_ave_out16(client, 0x08, 0x0000); + + vi_ave_out8(client, 0x03, 1); + + /* clear bit 1 otherwise red and blue get swapped */ + if (ctl->has_component_cable) + vi_ave_out8(client, 0x62, 0); + + /* PAL 480i/60 supposedly needs a "filter" */ + pal60 = !!(format == 2 && ctl->mode->lines == 525); + vi_ave_out8(client, 0x6e, pal60); + + return 0; +} + +static struct vi_ctl *first_vi_ctl; +static struct i2c_client *first_vi_ave; + +static int vi_attach_ave(struct vi_ctl *ctl, struct i2c_client *client) +{ + if (!ctl) + return -ENODEV; + if (!client) + return -EINVAL; + + spin_lock(&ctl->lock); + if (!ctl->i2c_client) { + ctl->i2c_client = i2c_use_client(client); + spin_unlock(&ctl->lock); + drv_printk(KERN_INFO, "AVE-RVL support loaded\n"); + return 0; + } + spin_unlock(&ctl->lock); + return -EBUSY; +} + +static void vi_dettach_ave(struct vi_ctl *ctl) +{ + struct i2c_client *client; + + if (!ctl) + return; + + spin_lock(&ctl->lock); + if (ctl->i2c_client) { + client = ctl->i2c_client; + ctl->i2c_client = NULL; + spin_unlock(&ctl->lock); + i2c_release_client(client); + drv_printk(KERN_INFO, "AVE-RVL support unloaded\n"); + return; + } + spin_unlock(&ctl->lock); +} + +static int vi_ave_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int error; + + /* attach first a/v encoder to first framebuffer */ + if (!first_vi_ave) { + first_vi_ave = client; + error = vi_attach_ave(first_vi_ctl, client); + if (!error) { + /* setup again the video mode using the a/v encoder */ + vi_detect_tv_mode(first_vi_ctl); + vi_setup_tv_mode(first_vi_ctl); + } + } + return 0; +} + +static int vi_ave_remove(struct i2c_client *client) +{ + if (first_vi_ave == client) + first_vi_ave = NULL; + return 0; +} + +static const struct i2c_device_id vi_ave_id[] = { + { "wii-ave-rvl", 0 }, + { } +}; + +static struct i2c_driver vi_ave_driver = { + .driver = { + .name = DRV_MODULE_NAME, + }, + .probe = vi_ave_probe, + .remove = vi_ave_remove, + .id_table = vi_ave_id, +}; + +#endif /* CONFIG_WII_AVE_RVL */ + + +/* + * Linux framebuffer support routines. + * + */ + +/* + * This is just a quick, dirty and cheap way of getting right colors on the + * linux framebuffer console. + */ +unsigned int vifb_writel(unsigned int rgbrgb, void *address) +{ + uint16_t *rgb = (uint16_t *)&rgbrgb; + return fb_writel_real(rgbrgb16toycbycr(rgb[0], rgb[1]), address); +} + +static int vifb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, struct fb_info *info) +{ + /* + * Set a single color register. The values supplied are + * already rounded down to the hardware's capabilities + * (according to the entries in the `var' structure). Return + * != 0 for invalid regno. + */ + + if (regno >= info->cmap.len) + return 1; + + switch (info->var.bits_per_pixel) { + case 16: + if (info->var.red.offset == 10) { + /* 1:5:5:5, not used currently */ + ((u32 *) (info->pseudo_palette))[regno] = + ((red & 0xf800) >> 1) | + ((green & 0xf800) >> 6) | ((blue & 0xf800) >> 11); + } else { + /* 0:5:6:5 */ + ((u32 *) (info->pseudo_palette))[regno] = + ((red & 0xf800)) | + ((green & 0xfc00) >> 5) | ((blue & 0xf800) >> 11); + } + break; + case 8: + case 15: + case 24: + case 32: + break; + } + return 0; +} + +/* + * Pan the display by altering the framebuffer address in hardware. + */ +static int vifb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct vi_ctl *ctl = info->par; + void __iomem *io_base = ctl->io_base; + unsigned int bytes_per_pixel = info->var.bits_per_pixel / 8; + unsigned long flags; + int offset; + u16 ppl; + + ppl = _ALIGN_UP((var->xoffset & VI_HORZ_ALIGN) + var->xres, + VI_HORZ_ALIGN+1); + out_be16(io_base + VI_PCR, + vi_pcr_set_wpl(in_be16(io_base + VI_PCR), + (ppl * bytes_per_pixel) / VI_HORZ_WORD_SIZE)); + + offset = (var->yoffset * info->fix.line_length) + + var->xoffset * bytes_per_pixel; + vi_set_framebuffer(ctl, info->fix.smem_start + offset); + + spin_lock_irqsave(&ctl->lock, flags); + if (info->fix.smem_start + offset >= ctl->page_address[1]) + ctl->visible_page = 1; + else + ctl->visible_page = 0; + spin_unlock_irqrestore(&ctl->lock, flags); + + return 0; +} + +static int vifb_check_var_timings(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct vi_ctl *ctl = info->par; + struct vi_tv_mode *mode = ctl->mode; + struct vi_mode_timings timings; + u32 yres = var->yres; + int error = -EINVAL; + + if (nostalgic && yres == 576) + yres = 574; + + if (vi_vmode_is_progressive(var->vmode)) { + /* 480p */ + error = vi_ntsc_525_prog_calc_timings(&timings, var, + var->xres, var->yres); + } else { + if (mode->lines == 625) + /* 576i */ + error = vi_pal_625_calc_timings(&timings, var, + var->xres, yres); + else + /* 480i */ + error = vi_ntsc_525_calc_timings(&timings, var, + var->xres, var->yres); + } + if (error) + goto err_out; + + ctl->timings = timings; + var->pixclock = KHZ2PICOS(13.5 * 1000); + var->sync = FB_SYNC_BROADCAST; + + error = 0; + +err_out: + return error; +} + +/* + * Check var and eventually tweak it to something supported. + * Do not modify par here. + */ +static int vifb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct vi_ctl *ctl = info->par; + struct vi_tv_mode *mode = ctl->mode; + int error = -EINVAL; + unsigned int bytes_per_pixel; + __u32 xres, yres, xres_virtual, yres_virtual; + + /* we support only 16bpp */ + if (var->bits_per_pixel != 16) { + drv_printk(KERN_ERR, "unsupported depth %u\n", + var->bits_per_pixel); + goto err_out; + } + + yres = var->yres; + if (yres & VI_VERT_ALIGN) + yres = _ALIGN_UP(yres, VI_VERT_ALIGN+1); + if (yres > mode->height) { + if (!nostalgic) { + drv_printk(KERN_ERR, "yres %u out of bounds\n", yres); + goto err_out; + } + if (!(mode->height == 574 && yres == 576)) + yres = mode->height; + } + if (yres < 16) { + /* XXX, fbcon will happily page fault for yres < 13 */ + yres = 16; + } + if (!yres) + yres = mode->height; + + yres_virtual = var->yres_virtual; + if (!yres_virtual || yres_virtual < yres) + yres_virtual = yres; + + xres = var->xres; + if (xres & VI_HORZ_ALIGN) + xres = _ALIGN_UP(xres, VI_HORZ_ALIGN+1); + if (xres > mode->width) { + drv_printk(KERN_ERR, "xres %u out of bounds\n", var->xres); + goto err_out; + } + if (!xres) + xres = mode->width; + + xres_virtual = var->xres_virtual; + if (xres_virtual & VI_HORZ_ALIGN) + xres_virtual = _ALIGN_UP(xres_virtual, VI_HORZ_ALIGN+1); + if (!xres_virtual || xres_virtual < xres) + xres_virtual = xres; + + bytes_per_pixel = var->bits_per_pixel / 8; + if (xres_virtual * yres_virtual * bytes_per_pixel > + info->fix.smem_len) { + drv_printk(KERN_ERR, "not enough memory for virtual resolution" + " (%ux%ux%u)\n", + xres_virtual, yres_virtual, + var->bits_per_pixel); + goto err_out; + } + + var->xres = xres; + var->yres = yres; + var->xres_virtual = xres_virtual; + var->yres_virtual = yres_virtual; + + var->xoffset = 0; + var->yoffset = 0; + + var->grayscale = 0; + + /* we support ony 16 bits per pixel */ + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 0; + var->blue.length = 5; + var->transp.offset = 0; + var->transp.length = 0; + + var->nonstd = 0; /* lies... */ + + /* enable non-interlaced mode if supported */ + if (force_scan != VI_SCAN_INTERLACED && ctl->has_component_cable) { + var->vmode = (mode->flags & VI_VMF_PROGRESSIVE) ? + FB_VMODE_NONINTERLACED : + FB_VMODE_INTERLACED; + } else + var->vmode = FB_VMODE_INTERLACED; + + error = vifb_check_var_timings(var, info); + if (error) + goto err_out; + +err_out: + return error; +} + +/* + * Set the video mode according to info->var. + */ +static int vifb_set_par(struct fb_info *info) +{ + struct vi_ctl *ctl = info->par; + struct fb_var_screeninfo *var = &info->var; + unsigned long flags; + + /* horizontal line in bytes */ + info->fix.line_length = var->xres_virtual * (var->bits_per_pixel / 8); + + ctl->page_address[0] = info->fix.smem_start; + if (var->yres * info->fix.line_length <= info->fix.smem_len / 2) + ctl->page_address[1] = + info->fix.smem_start + var->yres * info->fix.line_length; + else + ctl->page_address[1] = info->fix.smem_start; + + /* set page 0 as the visible page and cancel pending flips */ + spin_lock_irqsave(&ctl->lock, flags); + ctl->visible_page = 1; + vi_flip_page(ctl); + spin_unlock_irqrestore(&ctl->lock, flags); + + info->flags = FBINFO_FLAG_DEFAULT; + if (want_ypan) { + info->fix.xpanstep = 2; + info->fix.ypanstep = 1; + info->flags |= FBINFO_HWACCEL_YPAN; + } else { + info->fix.xpanstep = 0; + info->fix.ypanstep = 0; + } + + vi_setup_tv_mode(ctl); + + /* enable the video retrace handling */ + vi_enable_interrupts(ctl, 1); + + return 0; +} + +static int vifb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + unsigned long off; + unsigned long start; + u32 len; + + off = vma->vm_pgoff << PAGE_SHIFT; + + /* frame buffer memory */ + start = info->fix.smem_start; + len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.smem_len); + start &= PAGE_MASK; + if ((vma->vm_end - vma->vm_start + off) > len) + return -EINVAL; + off += start; + vma->vm_pgoff = off >> PAGE_SHIFT; + + /* this is an IO map, tell maydump to skip this VMA */ + vma->vm_flags |= VM_IO | VM_RESERVED; + + /* we share RAM between the cpu and the video hardware */ + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, + vma->vm_page_prot)) + return -EAGAIN; + return 0; +} + +static int vifb_ioctl(struct fb_info *info, + unsigned int cmd, unsigned long arg) +{ + struct vi_ctl *ctl = info->par; + void __user *argp; + unsigned long flags; + int page; + + switch (cmd) { + case FBIOWAITRETRACE: + interruptible_sleep_on(&ctl->vtrace_waitq); + return signal_pending(current) ? -EINTR : 0; + case FBIOFLIPHACK: + /* + * If arg == NULL then + * Try to flip the video page as soon as possible. + * Returns the current visible video page number. + */ + if (!arg) { + spin_lock_irqsave(&ctl->lock, flags); + if (ctl->in_vtrace) + vi_flip_page(ctl); + else + ctl->flip_pending = 1; + spin_unlock_irqrestore(&ctl->lock, flags); + return ctl->visible_page; + } + + /* + * If arg != NULL then + * Wait until the video page number pointed by arg + * is not visible. + * Returns the current visible video page number. + */ + argp = (void __user *)arg; + if (copy_from_user(&page, argp, sizeof(int))) + return -EFAULT; + + if (page != 0 && page != 1) + return -EINVAL; + + spin_lock_irqsave(&ctl->lock, flags); + ctl->flip_pending = 0; + if (ctl->visible_page == page) { + if (ctl->in_vtrace) { + vi_flip_page(ctl); + } else { + ctl->flip_pending = 1; + spin_unlock_irqrestore(&ctl->lock, flags); + interruptible_sleep_on(&ctl->vtrace_waitq); + return signal_pending(current) ? + -EINTR : ctl->visible_page; + } + } + spin_unlock_irqrestore(&ctl->lock, flags); + return ctl->visible_page; + } + return -EINVAL; +} + + +struct fb_ops vifb_ops = { + .owner = THIS_MODULE, + .fb_setcolreg = vifb_setcolreg, + .fb_pan_display = vifb_pan_display, + .fb_ioctl = vifb_ioctl, + .fb_set_par = vifb_set_par, + .fb_check_var = vifb_check_var, + .fb_mmap = vifb_mmap, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +/* + * Driver model helper routines. + * + */ + +static int __devinit vifb_do_probe(struct device *dev, + struct resource *mem, unsigned int irq, + unsigned long xfb_start, unsigned long xfb_size) +{ + struct fb_info *info; + struct vi_ctl *ctl; + int video_cmap_len; + int error = -EINVAL; + + info = framebuffer_alloc(sizeof(struct vi_ctl), dev); + if (!info) + goto err_framebuffer_alloc; + + info->fbops = &vifb_ops; + info->var = vifb_var; + info->fix = vifb_fix; + + ctl = info->par; + ctl->info = info; + + /* first things first */ + ctl->io_base = ioremap(mem->start, mem->end - mem->start + 1); + ctl->irq = irq; + + /* + * Location and size of the external framebuffer. + */ + info->fix.smem_start = xfb_start; + info->fix.smem_len = xfb_size; + + if (!request_mem_region(info->fix.smem_start, info->fix.smem_len, + DRV_MODULE_NAME)) { + drv_printk(KERN_WARNING, + "failed to request video memory at %p\n", + (void *)info->fix.smem_start); + } + + info->screen_base = ioremap(info->fix.smem_start, info->fix.smem_len); + if (!info->screen_base) { + drv_printk(KERN_ERR, + "failed to ioremap video memory at %p (%dk)\n", + (void *)info->fix.smem_start, + info->fix.smem_len / 1024); + error = -EIO; + goto err_ioremap; + } + + spin_lock_init(&ctl->lock); + init_waitqueue_head(&ctl->vtrace_waitq); + + vi_reset_video(ctl); + vi_detect_tv_mode(ctl); + + if (!nostalgic) { + /* by default, start with overscan compensation */ + info->var.xres = 576; + if (ctl->mode->height == 574) + info->var.yres = 516; + else + info->var.yres = 432; + } else { + info->var.xres = ctl->mode->width; + info->var.yres = ctl->mode->height; + } + + ctl->visible_page = 0; + ctl->flip_pending = 0; + + drv_printk(KERN_INFO, + "framebuffer at 0x%p, mapped to 0x%p, size %dk\n", + (void *)info->fix.smem_start, info->screen_base, + info->fix.smem_len / 1024); + + video_cmap_len = 16; + info->pseudo_palette = pseudo_palette; + if (fb_alloc_cmap(&info->cmap, video_cmap_len, 0)) { + error = -ENOMEM; + goto err_alloc_cmap; + } + + error = vifb_check_var(&info->var, info); + if (error) + goto err_check_var; + + drv_printk(KERN_INFO, "mode is %dx%dx%d\n", info->var.xres, + info->var.yres, info->var.bits_per_pixel); + + dev_set_drvdata(dev, info); + + vi_enable_interrupts(ctl, 0); + + error = request_irq(ctl->irq, vi_irq_handler, 0, DRV_MODULE_NAME, dev); + if (error) { + drv_printk(KERN_ERR, "unable to register IRQ %u\n", ctl->irq); + goto err_request_irq; + } + + /* now register us */ + if (register_framebuffer(info) < 0) { + error = -EINVAL; + goto err_register_framebuffer; + } + +#ifdef CONFIG_WII_AVE_RVL + if (!first_vi_ctl) + first_vi_ctl = ctl; + + /* try to attach the a/v encoder now */ + vi_attach_ave(ctl, first_vi_ave); +#endif + + printk(KERN_INFO "fb%d: %s frame buffer device\n", + info->node, info->fix.id); + + return 0; + +err_register_framebuffer: + free_irq(ctl->irq, 0); +err_check_var: +err_request_irq: + fb_dealloc_cmap(&info->cmap); +err_alloc_cmap: + iounmap(info->screen_base); +err_ioremap: + release_mem_region(info->fix.smem_start, info->fix.smem_len); + + dev_set_drvdata(dev, NULL); + iounmap(ctl->io_base); + framebuffer_release(info); +err_framebuffer_alloc: + return error; +} + +static int __devexit vifb_do_remove(struct device *dev) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct vi_ctl *ctl = info->par; + + if (!info) + return -ENODEV; + + free_irq(ctl->irq, dev); + unregister_framebuffer(info); + fb_dealloc_cmap(&info->cmap); + iounmap(info->screen_base); + release_mem_region(info->fix.smem_start, info->fix.smem_len); + + dev_set_drvdata(dev, NULL); + iounmap(ctl->io_base); + +#ifdef CONFIG_WII_AVE_RVL + vi_dettach_ave(ctl); + if (first_vi_ctl == ctl) + first_vi_ctl = NULL; +#endif + framebuffer_release(info); + return 0; +} + +static int vifb_do_shutdown(struct device *dev) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct vi_ctl *ctl = info->par; + void __iomem *io_base = ctl->io_base; + + vi_enable_interrupts(ctl, 0); + vi_reset_video(ctl); + out_be16(io_base + VI_DCR, vi_dcr_enb(0)); + + return 0; +} + +#ifndef MODULE + +static int __devinit vifb_setup(char *options) +{ + char *this_opt; + + if (!options || !*options) + return 0; + + drv_printk(KERN_INFO, "options: %s\n", options); + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!*this_opt) + continue; + + if (!strcmp(this_opt, "redraw")) + want_ypan = 0; + else if (!strcmp(this_opt, "interlaced")) + force_scan = VI_SCAN_INTERLACED; + else if (!strcmp(this_opt, "progressive")) + force_scan = VI_SCAN_PROGRESSIVE; + else if (!strcmp(this_opt, "50Hz")) + force_rate = VI_RATE_50Hz; + else if (!strcmp(this_opt, "60Hz")) + force_rate = VI_RATE_60Hz; + else if (!strncmp(this_opt, "tv=", 3)) { + if (!strncmp(this_opt + 3, "PAL", 3)) + force_tv = VI_TV_PAL; + else if (!strncmp(this_opt + 3, "NTSC", 4)) + force_tv = VI_TV_NTSC; + } else if (!strcmp(this_opt, "nostalgic")) + nostalgic = 1; + } + + if (force_scan == VI_SCAN_PROGRESSIVE || force_tv == VI_TV_NTSC) { + if (force_rate == VI_RATE_50Hz) { + drv_printk(KERN_INFO, "ignoring forced 50Hz setting\n"); + force_rate = VI_RATE_DONTCARE; + } + } + return 0; +} + +#endif /* MODULE */ + + +/* + * OF platform driver hooks. + * + */ + +static int __init vifb_of_probe(struct of_device *odev, + const struct of_device_id *match) +{ + struct resource res; + const unsigned long *prop; + unsigned long xfb_start, xfb_size; + int retval; + + retval = of_address_to_resource(odev->node, 0, &res); + if (retval) { + drv_printk(KERN_ERR, "no io memory range found\n"); + return -ENODEV; + } + + prop = of_get_property(odev->node, "xfb-start", NULL); + if (!prop) { + drv_printk(KERN_ERR, "no xfb start found\n"); + return -ENODEV; + } + xfb_start = *prop; + + prop = of_get_property(odev->node, "xfb-size", NULL); + if (!prop) { + drv_printk(KERN_ERR, "no xfb size found\n"); + return -ENODEV; + } + xfb_size = *prop; + + return vifb_do_probe(&odev->dev, + &res, irq_of_parse_and_map(odev->node, 0), + xfb_start, xfb_size); +} + +static int __exit vifb_of_remove(struct of_device *odev) +{ + return vifb_do_remove(&odev->dev); +} + +static int vifb_of_shutdown(struct of_device *odev) +{ + return vifb_do_shutdown(&odev->dev); +} + + +static struct of_device_id vifb_of_match[] = { + { .compatible = "nintendo,flipper-video", }, + { .compatible = "nintendo,hollywood-video", }, + { }, +}; + +MODULE_DEVICE_TABLE(of, vifb_of_match); + +static struct of_platform_driver vifb_of_driver = { + .owner = THIS_MODULE, + .name = DRV_MODULE_NAME, + .match_table = vifb_of_match, + .probe = vifb_of_probe, + .remove = vifb_of_remove, + .shutdown = vifb_of_shutdown, +}; + +/* + * Module interface hooks + * + */ + +static int __init vifb_init_module(void) +{ + int error; + char *option = NULL; + + drv_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION, + vifb_driver_version); + +#ifndef MODULE + if (fb_get_options(DRV_MODULE_NAME, &option)) + return -ENODEV; + if (!option) { + /* for backwards compatibility */ + if (fb_get_options("gcnfb", &option)) + return -ENODEV; + } + error = vifb_setup(option); +#endif + +#ifdef CONFIG_WII_AVE_RVL + error = i2c_add_driver(&vi_ave_driver); + if (error) + drv_printk(KERN_ERR, "failed to register AVE (%d)\n", error); +#endif + + return of_register_platform_driver(&vifb_of_driver); +} + +static void __exit vifb_exit_module(void) +{ + of_unregister_platform_driver(&vifb_of_driver); +#ifdef CONFIG_WII_AVE_RVL + i2c_del_driver(&vi_ave_driver); +#endif +} + +module_init(vifb_init_module); +module_exit(vifb_exit_module); + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_AUTHOR(DRV_AUTHOR); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/logo/Kconfig b/drivers/video/logo/Kconfig index 39ac49e..6fa54c6 100644 --- a/drivers/video/logo/Kconfig +++ b/drivers/video/logo/Kconfig @@ -42,6 +42,11 @@ config LOGO_DEC_CLUT224 depends on MACH_DECSTATION || ALPHA default y +config LOGO_GAMECUBE_CLUT224 + bool "224-color GameCube Linux logo" + depends on GAMECUBE + default y + config LOGO_MAC_CLUT224 bool "224-color Macintosh Linux logo" depends on MAC diff --git a/drivers/video/logo/Makefile b/drivers/video/logo/Makefile index 3b43781..8d73a67 100644 --- a/drivers/video/logo/Makefile +++ b/drivers/video/logo/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_LOGO_LINUX_CLUT224) += logo_linux_clut224.o obj-$(CONFIG_LOGO_BLACKFIN_CLUT224) += logo_blackfin_clut224.o obj-$(CONFIG_LOGO_BLACKFIN_VGA16) += logo_blackfin_vga16.o obj-$(CONFIG_LOGO_DEC_CLUT224) += logo_dec_clut224.o +obj-$(CONFIG_LOGO_GAMECUBE_CLUT224) += logo_gcn_clut224.o obj-$(CONFIG_LOGO_MAC_CLUT224) += logo_mac_clut224.o obj-$(CONFIG_LOGO_PARISC_CLUT224) += logo_parisc_clut224.o obj-$(CONFIG_LOGO_SGI_CLUT224) += logo_sgi_clut224.o diff --git a/drivers/video/logo/logo.c b/drivers/video/logo/logo.c index ea7a8cc..e7b485a 100644 --- a/drivers/video/logo/logo.c +++ b/drivers/video/logo/logo.c @@ -75,6 +75,10 @@ const struct linux_logo * __init_refok fb_find_logo(int depth) /* DEC Linux logo on MIPS/MIPS64 or ALPHA */ logo = &logo_dec_clut224; #endif +#ifdef CONFIG_LOGO_GAMECUBE_CLUT224 + /* GameCube Linux logo */ + logo = &logo_gcn_clut224; +#endif #ifdef CONFIG_LOGO_MAC_CLUT224 /* Macintosh Linux logo on m68k */ if (MACH_IS_MAC) diff --git a/drivers/video/logo/logo_gcn_clut224.ppm b/drivers/video/logo/logo_gcn_clut224.ppm new file mode 100644 index 0000000..08e12b3 --- /dev/null +++ b/drivers/video/logo/logo_gcn_clut224.ppm @@ -0,0 +1,1123 @@ +P3 +80 80 +255 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +6 6 6 6 6 6 10 10 10 10 10 10 10 10 10 6 6 6 +6 6 6 6 6 6 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 6 6 6 10 10 10 14 14 14 +22 22 22 26 26 26 30 30 30 34 34 34 30 30 30 30 30 30 +26 26 26 18 18 17 14 14 14 10 10 10 6 6 6 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 6 6 6 14 14 14 26 26 26 42 42 42 +54 54 55 66 66 66 78 78 78 78 78 78 78 78 78 74 74 74 +66 66 66 54 54 55 42 42 42 26 26 26 18 18 17 10 10 10 +6 6 6 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 10 10 10 22 22 22 42 42 42 66 66 66 87 86 85 +66 66 66 38 38 38 38 38 38 22 22 22 26 26 26 34 34 34 +54 54 55 66 66 66 87 86 85 70 70 70 46 46 46 26 26 26 +14 14 14 6 6 6 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +10 10 10 26 26 26 50 50 50 82 82 82 58 58 58 6 6 6 +2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 +2 2 6 2 2 6 6 6 6 54 54 55 87 86 85 66 66 66 +38 38 38 18 18 17 6 6 6 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 6 6 6 +22 22 22 50 50 50 78 78 78 34 34 34 2 2 6 2 2 6 +2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 +2 2 6 2 2 6 2 2 6 2 2 6 6 6 6 70 70 70 +78 78 78 46 46 46 22 22 22 6 6 6 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 6 6 6 18 18 17 +42 42 42 82 82 82 26 26 26 2 2 6 2 2 6 2 2 6 +2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 +2 2 6 14 14 14 46 46 46 34 34 34 6 6 6 2 2 6 +42 42 42 78 78 78 42 42 42 18 18 17 6 6 6 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 10 10 10 30 30 30 +66 66 66 58 58 58 2 2 6 2 2 6 2 2 6 2 2 6 +2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 +2 2 6 26 26 26 87 86 85 102 98 90 46 46 46 10 10 10 +2 2 6 58 58 58 70 70 70 34 34 34 10 10 10 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 14 14 14 42 42 42 +87 86 85 10 10 10 2 2 6 2 2 6 2 2 6 2 2 6 +2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 +2 2 6 30 30 30 94 94 98 94 94 94 58 58 58 26 26 26 +2 2 6 6 6 6 78 78 78 54 54 55 22 22 22 6 6 6 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 6 6 6 22 22 22 62 62 62 +62 62 62 2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 +2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 +2 2 6 26 26 26 54 54 55 38 38 38 18 18 17 10 10 10 +2 2 6 2 2 6 34 34 34 82 82 82 38 38 38 14 14 14 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 6 6 6 30 30 30 78 78 78 +30 30 30 2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 +2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 +2 2 6 10 10 10 10 10 10 2 2 6 2 2 6 2 2 6 +2 2 6 2 2 6 2 2 6 78 78 78 50 50 50 18 18 17 +6 6 6 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 10 10 10 38 38 38 87 86 85 +14 14 14 2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 +2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 +2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 +2 2 6 2 2 6 2 2 2 54 54 55 66 66 66 26 26 26 +6 6 6 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 14 14 14 42 42 42 82 82 82 +2 2 6 2 2 6 2 2 6 6 6 6 10 10 10 2 2 6 +2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 +2 2 6 6 6 6 14 14 14 10 10 10 2 2 6 2 2 6 +2 2 6 2 2 6 2 2 6 18 18 17 82 82 82 34 34 34 +10 10 10 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 14 14 14 46 46 46 87 86 85 +2 2 6 2 2 6 6 6 6 6 6 6 22 22 22 34 34 34 +6 6 6 2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 +18 18 17 34 34 34 10 10 10 50 50 50 22 22 22 2 2 6 +2 2 6 2 2 6 2 2 6 10 10 10 87 86 85 42 42 42 +14 14 14 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 14 14 14 46 46 46 87 86 85 +2 2 6 2 2 6 38 38 38 118 118 118 94 94 94 22 22 22 +22 22 22 2 2 6 2 2 6 2 2 6 14 14 14 87 86 85 +141 139 143 160 158 162 152 152 152 38 38 38 26 26 26 6 6 6 +2 2 6 2 2 6 2 2 6 2 2 6 87 86 85 46 46 46 +14 14 14 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 14 14 14 46 46 46 87 86 85 +2 2 6 14 14 14 134 133 135 199 199 199 194 193 197 118 118 118 +10 10 10 2 2 6 2 2 6 6 6 6 102 98 90 190 190 190 +209 208 212 219 220 223 215 214 215 134 133 135 14 14 14 6 6 6 +2 2 6 2 2 6 2 2 6 2 2 6 87 86 85 50 50 50 +18 18 17 6 6 6 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 14 14 14 46 46 46 87 86 85 +2 2 6 54 54 55 219 220 223 199 199 199 219 220 223 246 246 246 +58 58 58 2 2 6 2 2 6 30 30 30 209 208 212 254 254 254 +173 173 174 121 121 122 219 220 223 236 233 239 74 74 74 2 2 6 +2 2 6 2 2 6 2 2 6 2 2 6 70 70 70 58 58 58 +22 22 22 6 6 6 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 14 14 14 46 46 46 82 82 82 +2 2 6 104 103 100 173 173 174 26 26 26 90 90 90 229 226 233 +121 121 122 10 10 10 14 14 14 46 46 46 229 231 235 190 190 190 +6 6 6 74 74 74 90 90 90 239 238 242 160 158 162 2 2 6 +2 2 6 2 2 6 2 2 6 2 2 6 70 70 70 58 58 58 +22 22 22 6 6 6 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 14 14 14 42 42 42 87 86 85 +6 6 6 118 118 118 104 103 100 6 6 6 70 70 70 152 152 152 +122 122 130 18 18 17 38 38 38 54 54 55 219 220 223 104 103 100 +2 2 2 14 14 14 46 46 46 190 190 190 199 199 199 2 2 2 +2 2 6 2 2 6 2 2 6 2 2 6 74 74 74 62 62 62 +22 22 22 6 6 6 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 14 14 14 42 42 42 94 94 94 +14 14 14 104 103 100 122 122 130 2 2 2 18 18 17 118 118 118 +124 98 46 122 94 10 122 94 10 101 78 10 163 163 162 107 107 108 +2 2 6 2 2 2 2 2 2 194 193 197 194 193 197 6 6 6 +2 2 6 2 2 6 2 2 6 2 2 6 74 74 74 62 62 62 +22 22 22 6 6 6 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 10 10 10 38 38 38 90 90 90 +14 14 14 62 62 62 209 208 212 26 26 26 44 31 8 158 118 10 +226 171 11 238 183 13 214 174 14 188 146 14 214 174 14 174 146 62 +26 26 26 2 2 2 70 70 70 246 246 246 141 139 143 2 2 2 +2 2 6 2 2 6 2 2 6 2 2 6 70 70 70 66 66 66 +26 26 26 6 6 6 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 10 10 10 38 38 38 87 86 85 +14 14 14 6 6 6 199 199 199 190 166 114 186 134 10 226 171 11 +238 178 14 234 193 17 234 193 17 236 202 20 246 206 46 242 210 18 +234 193 17 188 146 14 218 194 134 210 206 186 42 42 42 2 2 6 +2 2 6 2 2 6 2 2 6 2 2 6 50 50 50 74 74 74 +30 30 30 6 6 6 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 10 10 10 34 34 34 87 86 85 +14 14 14 2 2 2 122 86 26 194 134 10 226 166 10 238 183 13 +226 186 14 234 193 17 236 202 20 246 216 61 246 217 39 246 217 39 +246 214 22 242 210 18 242 210 18 226 186 14 122 86 26 2 2 6 +2 2 6 2 2 6 2 2 6 2 2 6 50 50 50 82 82 82 +34 34 34 10 10 10 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 10 10 10 34 34 34 82 82 82 +30 30 30 63 43 6 182 122 6 204 146 10 231 174 11 238 183 13 +234 193 17 236 202 20 242 210 18 246 216 61 246 217 39 246 214 22 +246 214 22 242 210 18 226 186 14 214 174 14 188 146 14 6 6 6 +2 2 6 2 2 6 2 2 6 2 2 6 26 26 26 94 94 94 +42 42 42 14 14 14 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 10 10 10 30 30 30 78 78 78 +50 50 50 104 70 6 186 134 10 218 158 10 238 178 14 242 186 14 +236 202 20 236 202 20 246 216 61 246 217 39 246 217 39 246 214 22 +242 210 18 198 154 10 198 138 10 218 158 10 154 114 10 2 2 6 +2 2 6 2 2 6 2 2 6 2 2 6 6 6 6 90 90 90 +54 54 55 18 18 17 6 6 6 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 10 10 10 30 30 30 78 78 78 +46 46 46 22 22 22 138 93 6 198 154 10 238 183 13 238 183 13 +236 202 20 242 210 18 246 214 22 246 214 22 242 210 18 204 166 16 +186 134 10 210 150 10 216 162 10 210 150 10 101 78 10 2 2 6 +6 6 6 54 54 55 14 14 14 2 2 6 2 2 6 62 62 62 +74 74 74 30 30 30 10 10 10 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 10 10 10 34 34 34 78 78 78 +50 50 50 6 6 6 94 70 30 138 102 14 188 146 14 226 186 14 +236 202 20 234 193 17 214 174 14 188 146 14 173 119 7 186 134 10 +210 150 10 214 154 10 202 150 34 182 158 106 102 98 90 2 2 2 +2 2 6 78 78 78 118 118 118 54 54 55 2 2 6 22 22 22 +90 90 90 46 46 46 18 18 17 6 6 6 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 10 10 10 38 38 38 87 86 85 +50 50 50 6 6 6 132 131 125 174 154 114 160 108 10 173 119 7 +198 154 10 188 146 14 198 138 10 198 138 10 204 146 10 204 146 10 +198 138 10 190 166 114 194 193 197 199 199 199 173 173 174 14 14 14 +2 2 6 18 18 17 118 118 118 118 118 118 22 22 22 2 2 6 +74 74 74 70 70 70 30 30 30 10 10 10 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 6 6 6 18 18 17 50 50 50 104 103 100 +26 26 26 10 10 10 141 139 143 190 190 190 174 154 114 160 108 10 +198 138 10 188 146 14 198 138 10 194 134 10 182 122 6 190 142 34 +186 174 146 185 185 187 199 199 199 219 220 223 215 214 215 66 66 66 +2 2 6 2 2 6 50 50 50 62 62 62 6 6 6 2 2 2 +10 10 10 90 90 90 50 50 50 18 18 17 6 6 6 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 10 10 10 34 34 34 74 74 74 74 74 74 +2 2 2 6 6 6 141 139 143 199 199 199 190 190 190 163 163 162 +154 122 54 160 108 10 160 108 10 164 122 42 174 154 114 185 185 187 +194 193 197 209 208 212 250 250 250 254 254 254 250 250 250 182 182 182 +6 6 6 2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 +2 2 6 62 62 62 74 74 74 34 34 34 14 14 14 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 10 10 10 22 22 22 54 54 55 94 94 94 18 18 17 +2 2 6 50 50 50 229 231 235 219 220 223 194 193 197 190 190 190 +190 190 190 190 190 190 190 190 190 190 190 190 190 190 190 194 193 197 +215 214 215 242 242 242 254 254 254 254 254 254 250 250 250 254 254 254 +82 82 82 2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 +2 2 6 14 14 14 87 86 85 54 54 55 22 22 22 6 6 6 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +6 6 6 18 18 17 46 46 46 90 90 90 46 46 46 18 18 17 +6 6 6 182 182 182 254 254 254 250 250 250 210 206 186 190 190 190 +190 190 190 190 190 190 190 190 190 190 190 190 210 206 186 229 231 235 +254 254 254 250 250 250 250 250 250 254 254 254 254 254 254 254 254 254 +198 200 208 14 14 14 2 2 6 2 2 6 2 2 6 2 2 6 +2 2 6 2 2 6 42 42 42 87 86 85 42 42 42 18 18 17 +6 6 6 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 6 6 6 +14 14 14 38 38 38 74 74 74 66 66 66 2 2 6 6 6 6 +90 90 90 250 250 250 250 250 250 254 254 254 239 238 242 199 199 199 +190 190 190 190 190 190 194 193 197 219 220 223 183 174 199 134 114 171 +207 201 219 254 254 254 254 254 254 254 254 254 250 250 250 254 254 254 +250 250 250 82 82 82 2 2 2 2 2 6 2 2 6 2 2 6 +2 2 6 2 2 6 2 2 6 78 78 78 70 70 70 34 34 34 +14 14 14 6 6 6 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 14 14 14 +34 34 34 66 66 66 78 78 78 6 6 6 2 2 2 18 18 17 +219 220 223 254 254 254 254 254 254 250 250 250 254 254 254 246 246 246 +222 223 236 229 231 235 229 226 233 140 131 164 109 96 159 109 96 159 +120 106 166 170 156 193 236 233 239 254 254 254 254 254 254 254 254 254 +254 254 254 182 182 182 2 2 6 2 2 6 2 2 6 2 2 6 +2 2 6 2 2 6 2 2 6 18 18 17 90 90 90 62 62 62 +30 30 30 10 10 10 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 10 10 10 26 26 26 +58 58 58 90 90 90 18 18 17 2 2 6 2 2 6 107 107 108 +254 254 254 254 254 254 254 254 254 250 250 250 250 250 250 254 254 254 +250 250 250 189 183 204 121 102 164 116 94 159 117 98 162 114 103 163 +114 103 163 120 106 166 136 121 175 146 139 165 107 107 108 118 118 118 +141 139 143 182 182 182 219 220 223 2 2 6 2 2 6 2 2 6 +2 2 6 2 2 6 2 2 6 2 2 6 18 18 17 94 94 94 +54 54 55 26 26 26 10 10 10 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 6 6 6 22 22 22 50 50 50 +90 90 90 26 26 26 2 2 6 2 2 2 14 14 14 199 199 199 +250 250 250 254 254 254 254 254 254 254 254 254 254 254 254 229 226 233 +147 132 180 109 90 154 158 147 190 198 200 208 137 128 178 120 106 166 +121 102 164 120 106 166 130 120 175 130 120 175 127 110 169 76 72 88 +58 58 58 58 58 58 74 74 74 118 118 118 194 193 197 2 2 6 +2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 38 38 38 +87 86 85 50 50 50 22 22 22 6 6 6 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 6 6 6 14 14 14 38 38 38 82 82 82 +34 34 34 2 2 6 2 2 6 2 2 2 42 42 42 194 193 197 +246 246 246 254 254 254 254 254 254 254 254 254 200 193 212 122 98 164 +113 86 148 109 96 159 128 114 172 158 147 190 130 120 175 120 106 166 +127 110 169 127 110 169 130 120 175 130 120 175 142 128 179 140 131 164 +102 98 122 58 50 84 54 54 55 62 62 62 74 74 74 134 133 135 +236 233 239 2 2 2 2 2 6 2 2 6 2 2 6 2 2 6 +38 38 38 82 82 82 42 42 42 14 14 14 6 6 6 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 10 10 10 26 26 26 62 62 62 66 66 66 +2 2 6 2 2 6 2 2 6 6 6 6 70 70 70 169 165 179 +210 206 186 229 231 235 236 233 239 146 139 165 105 86 155 105 86 155 +109 96 159 109 90 154 109 96 159 114 103 163 114 103 163 120 106 166 +120 106 166 128 114 172 130 120 175 137 128 178 137 128 178 142 134 183 +155 141 181 140 131 164 70 70 70 58 50 84 66 66 66 84 82 94 +118 118 118 229 231 235 2 2 6 2 2 6 2 2 6 2 2 6 +2 2 6 62 62 62 66 66 66 30 30 30 10 10 10 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 14 14 14 42 42 42 82 82 82 18 18 17 +2 2 6 2 2 6 2 2 6 10 10 10 94 94 94 182 182 182 +219 220 223 200 193 212 122 98 164 102 82 152 105 86 155 109 90 154 +109 96 159 109 90 154 109 96 159 114 103 163 114 103 163 127 110 169 +128 114 172 128 114 172 130 120 175 137 128 178 142 128 179 148 140 189 +148 140 189 158 147 190 158 147 190 118 110 134 76 72 88 94 90 106 +94 94 98 132 131 125 2 2 6 2 2 6 2 2 6 2 2 6 +2 2 6 6 6 6 87 86 85 46 46 46 18 18 17 6 6 6 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 6 6 6 22 22 22 54 54 55 70 70 70 2 2 6 +2 2 6 10 10 10 2 2 2 22 22 22 173 173 174 236 233 239 +155 141 181 102 82 152 102 82 152 102 82 152 109 90 154 105 86 155 +109 90 154 87 78 126 58 50 84 45 38 66 45 38 66 54 46 76 +82 70 114 114 103 163 130 120 175 136 121 175 137 128 178 142 128 179 +142 134 183 148 140 189 158 147 190 158 147 190 146 139 165 118 106 138 +126 122 142 87 86 85 199 199 199 2 2 6 2 2 6 2 2 6 +2 2 6 2 2 6 62 62 62 66 66 66 26 26 26 10 10 10 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 10 10 10 30 30 30 74 74 74 50 50 50 2 2 6 +26 26 26 26 26 26 2 2 6 107 107 108 207 201 219 120 106 166 +103 83 144 102 82 152 105 86 155 102 82 152 94 82 138 87 78 126 +34 26 54 6 6 6 6 6 6 2 2 6 2 2 6 2 2 2 +2 2 2 10 6 18 64 60 89 130 120 175 137 128 178 137 128 178 +142 128 179 148 140 189 142 134 183 146 140 180 142 134 183 148 140 189 +155 141 181 110 106 126 146 146 147 18 18 17 2 2 6 2 2 6 +2 2 6 2 2 6 18 18 17 87 86 85 42 42 42 14 14 14 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 14 14 14 42 42 42 90 90 90 22 22 22 2 2 6 +42 42 42 2 2 2 78 78 78 166 150 188 102 82 152 99 78 147 +111 77 141 102 82 152 99 78 147 105 86 155 87 78 126 10 10 10 +6 6 6 2 2 6 2 2 6 6 2 2 2 2 2 2 2 2 +2 2 6 6 6 6 2 2 6 45 38 66 130 120 175 136 121 175 +137 128 178 137 128 178 137 128 178 137 128 178 137 128 178 142 128 179 +142 134 183 140 131 164 138 131 154 38 38 38 10 10 10 2 2 6 +2 2 6 2 2 6 2 2 6 78 78 78 58 58 58 22 22 22 +6 6 6 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +6 6 6 18 18 17 54 54 55 82 82 82 2 2 6 26 26 26 +22 22 22 216 211 227 127 110 169 97 74 146 99 78 147 99 78 147 +102 82 152 102 82 152 99 78 147 94 82 138 34 26 54 2 2 6 +2 2 6 6 2 7 50 50 50 30 30 30 6 6 6 6 2 2 +2 2 2 2 2 2 2 2 6 2 2 6 87 78 126 130 120 175 +128 114 172 130 120 175 130 120 175 136 121 175 137 128 178 130 120 175 +130 120 175 137 128 178 142 134 183 213 206 226 38 38 38 2 2 6 +2 2 6 2 2 6 2 2 6 46 46 46 78 78 78 30 30 30 +10 10 10 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +10 10 10 30 30 30 74 74 74 58 58 58 2 2 6 42 42 42 +189 183 204 105 86 155 97 74 146 97 74 146 93 70 140 97 74 146 +105 86 155 99 78 147 102 82 152 94 82 138 10 6 18 6 2 7 +6 6 6 2 2 2 10 6 18 50 50 50 54 54 55 18 18 17 +6 2 2 6 2 7 6 2 7 2 2 6 54 46 76 120 106 166 +127 110 169 127 110 169 127 110 169 128 114 172 127 110 169 128 114 172 +128 114 172 127 110 169 128 114 172 130 120 175 38 38 38 14 14 14 +2 2 6 2 2 6 2 2 6 6 6 6 87 86 85 46 46 46 +14 14 14 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 6 6 6 +14 14 14 42 42 42 90 90 90 18 18 17 18 18 17 26 26 26 +183 174 199 122 98 164 97 74 146 99 78 147 97 74 146 97 74 146 +99 78 147 99 78 147 102 82 152 99 78 147 14 14 14 26 26 26 +26 26 26 6 2 7 6 2 7 6 2 7 22 22 22 66 66 66 +30 30 30 2 2 6 2 2 6 2 2 6 58 50 84 120 106 166 +114 103 163 114 103 163 114 103 163 114 103 163 114 103 163 114 103 163 +114 103 163 120 106 166 109 90 154 134 120 165 10 10 10 34 34 34 +2 2 6 2 2 6 2 2 6 2 2 6 74 74 74 58 58 58 +22 22 22 6 6 6 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 10 10 10 +26 26 26 66 66 66 82 82 82 2 2 6 38 38 38 6 2 7 +196 186 211 154 134 184 127 110 169 99 78 147 97 74 146 97 74 146 +99 78 147 99 78 147 99 78 147 102 82 152 45 38 66 58 50 84 +54 54 55 22 22 22 6 2 7 6 2 7 6 2 7 10 10 10 +14 14 14 6 2 2 2 2 2 10 6 18 94 82 138 109 96 159 +109 96 159 109 96 159 109 96 159 109 96 159 109 96 159 109 96 159 +109 96 159 105 86 155 116 94 159 147 132 180 2 2 6 46 46 46 +2 2 6 2 2 6 2 2 6 2 2 6 42 42 42 74 74 74 +30 30 30 10 10 10 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 6 6 6 14 14 14 +42 42 42 90 90 90 26 26 26 6 6 6 42 42 42 2 2 6 +200 193 212 154 134 184 154 134 184 136 121 175 105 86 155 97 74 146 +97 74 146 99 78 147 99 78 147 97 74 146 95 75 136 40 30 56 +94 90 106 104 103 100 50 50 50 26 26 26 6 2 7 6 2 7 +2 2 6 6 2 2 10 6 18 64 60 89 109 90 154 105 86 155 +105 86 155 105 86 155 105 86 155 105 86 155 105 86 155 102 82 152 +109 90 154 116 94 159 116 94 159 157 140 187 2 2 6 46 46 46 +2 2 6 2 2 6 2 2 6 2 2 6 10 10 10 87 86 85 +38 38 38 10 10 10 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 10 10 10 26 26 26 +66 66 66 82 82 82 2 2 6 22 22 22 18 18 17 6 2 7 +207 201 219 154 134 184 147 132 180 147 132 180 147 126 185 114 90 155 +97 74 146 97 74 146 111 77 141 130 90 130 99 78 147 95 75 136 +58 50 84 84 82 94 87 86 85 38 38 38 6 2 7 6 2 7 +10 6 18 34 26 54 86 66 120 102 82 152 99 78 147 102 82 152 +102 82 152 102 82 152 102 82 152 99 78 147 99 78 147 109 90 154 +114 90 155 116 94 159 116 94 159 170 156 193 2 2 6 38 38 38 +2 2 6 2 2 6 2 2 6 2 2 6 6 6 6 87 86 85 +46 46 46 14 14 14 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 6 6 6 18 18 17 46 46 46 +87 86 85 18 18 17 2 2 6 34 34 34 10 10 10 10 6 18 +213 206 226 162 146 182 183 174 199 147 132 180 142 134 183 147 132 180 +126 102 165 97 74 146 97 74 146 130 90 130 166 110 126 118 78 130 +93 70 140 97 74 146 86 66 120 70 56 108 70 56 108 70 56 108 +87 78 126 94 82 138 99 78 147 99 78 147 99 78 147 99 78 147 +97 74 146 97 74 146 97 74 146 102 82 152 114 90 155 116 94 159 +116 94 159 122 98 164 122 98 164 177 168 195 6 6 6 30 30 30 +2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 82 82 82 +54 54 55 18 18 17 6 6 6 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 10 10 10 26 26 26 66 66 66 +62 62 62 2 2 6 2 2 2 38 38 38 10 10 10 22 22 22 +226 218 234 170 156 193 207 201 219 194 193 197 146 139 165 147 126 185 +147 132 180 134 114 171 99 78 147 99 78 147 118 78 130 113 86 148 +97 74 146 97 74 146 97 74 146 97 74 146 97 74 146 99 78 147 +97 74 146 97 74 146 97 74 146 97 74 146 97 74 146 93 70 140 +97 74 146 93 70 140 105 86 155 109 90 154 93 70 140 116 94 159 +122 98 164 122 98 164 126 102 165 189 183 204 10 10 10 30 30 30 +2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 66 66 66 +58 58 58 22 22 22 6 6 6 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 10 10 10 38 38 38 78 78 78 +6 6 6 2 2 6 2 2 6 46 46 46 14 14 14 42 42 42 +229 226 233 170 156 193 183 174 199 132 131 125 198 200 208 158 147 190 +140 131 164 142 134 183 136 121 175 113 86 148 95 75 136 97 74 146 +97 74 146 95 75 136 93 70 140 93 70 140 97 74 146 102 82 152 +97 74 146 93 70 140 93 70 140 93 70 140 93 70 140 93 70 140 +97 74 146 105 86 155 99 78 147 88 65 130 92 71 128 116 94 159 +122 98 164 126 102 165 127 106 167 196 186 211 22 22 22 14 14 14 +2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 66 66 66 +62 62 62 22 22 22 6 6 6 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 6 6 6 18 18 17 50 50 50 74 74 74 +2 2 6 2 2 6 14 14 14 70 70 70 34 34 34 62 62 62 +236 233 239 170 156 193 194 193 197 46 42 70 200 193 212 199 199 199 +177 162 197 137 128 178 147 132 180 147 126 185 121 102 164 97 74 146 +93 70 140 93 70 140 97 74 146 93 70 140 93 70 140 88 65 130 +88 65 130 93 70 140 93 70 140 93 70 140 93 70 140 97 74 146 +113 86 148 97 74 146 86 66 120 92 71 128 93 70 140 122 98 164 +126 102 165 127 106 167 127 106 167 207 201 219 30 30 30 2 2 6 +2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 66 66 66 +62 62 62 22 22 22 6 6 6 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 6 6 6 18 18 17 54 54 55 62 62 62 +2 2 6 2 2 6 2 2 6 30 30 30 46 46 46 74 74 74 +254 254 254 158 147 190 236 233 239 199 199 199 215 214 215 190 190 190 +163 163 162 183 174 199 138 131 154 142 128 179 147 126 185 127 110 169 +99 78 147 93 70 140 97 74 146 93 70 140 93 70 140 88 65 130 +88 65 130 88 65 130 93 70 140 88 65 130 102 82 152 105 86 155 +92 71 128 86 66 120 86 66 120 92 71 128 95 75 136 126 102 165 +127 106 167 127 106 167 127 106 167 213 206 226 30 30 30 2 2 6 +2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 66 66 66 +58 58 58 22 22 22 6 6 6 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 6 6 6 22 22 22 58 58 58 62 62 62 +2 2 6 2 2 6 2 2 2 6 2 7 30 30 30 78 78 78 +250 250 250 162 146 182 207 201 219 222 223 236 229 231 235 173 173 174 +38 38 38 194 193 197 189 183 204 146 139 165 137 128 178 147 132 180 +136 121 175 105 86 155 88 65 130 93 70 140 93 70 140 88 65 130 +88 65 130 88 65 130 88 65 130 105 86 155 102 82 152 88 65 130 +86 66 120 88 65 130 82 70 114 92 71 128 99 78 147 127 106 167 +126 102 165 126 102 165 127 110 169 226 218 234 14 14 14 22 22 22 +26 26 26 18 18 17 6 6 6 2 2 6 2 2 2 82 82 82 +54 54 55 18 18 17 6 6 6 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 6 6 6 26 26 26 62 62 62 107 107 108 +74 54 14 186 134 10 216 162 10 122 94 10 6 6 6 62 62 62 +239 238 242 170 156 193 155 141 181 200 193 212 222 223 236 229 231 235 +134 133 135 198 200 208 194 193 197 183 174 199 152 152 152 136 121 175 +142 128 179 142 128 179 117 98 162 88 65 130 88 65 130 93 70 140 +88 65 130 99 78 147 109 90 154 114 90 155 95 75 136 86 66 120 +92 71 128 88 65 130 92 71 128 95 75 136 103 83 144 120 106 166 +127 106 167 127 110 169 155 141 181 160 158 162 2 2 2 2 2 2 +6 6 6 18 18 17 66 66 66 38 38 38 6 6 6 94 94 94 +50 50 50 18 18 17 6 6 6 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 6 6 6 +10 10 10 10 10 10 18 18 17 38 38 38 78 78 78 132 131 125 +218 158 10 242 186 14 246 190 14 246 190 14 158 118 10 10 10 10 +90 90 90 226 218 234 166 150 188 157 140 187 189 183 204 216 211 227 +229 231 235 219 220 223 173 173 174 74 74 74 189 183 204 169 165 179 +134 120 165 136 121 175 137 128 178 127 110 169 93 70 140 88 65 130 +102 82 152 113 86 148 114 90 155 114 90 155 92 71 128 86 66 120 +92 71 128 95 75 136 92 71 128 95 75 136 117 98 162 127 106 167 +127 110 169 177 162 197 6 2 7 6 2 7 2 2 6 2 2 6 +2 2 6 2 2 6 38 38 38 46 46 46 22 22 22 107 107 108 +54 54 55 18 18 17 6 6 6 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 6 6 6 14 14 14 22 22 22 +30 30 30 38 38 38 50 50 50 70 70 70 107 107 108 190 142 34 +226 171 11 242 186 14 246 190 14 246 190 14 246 190 14 154 114 10 +6 6 6 74 74 74 236 233 239 136 121 175 157 140 187 177 162 197 +216 211 227 236 233 239 209 208 212 66 66 66 169 165 179 194 193 197 +177 168 195 138 131 154 136 121 175 137 128 178 127 106 167 102 82 152 +109 90 154 109 90 154 109 90 154 116 94 159 99 78 147 86 66 120 +92 71 128 95 75 136 103 83 144 116 94 159 127 106 167 134 120 165 +196 186 211 44 31 8 2 2 2 2 2 6 2 2 6 2 2 6 +2 2 6 6 6 6 30 30 30 26 26 26 204 166 16 154 142 90 +66 66 66 26 26 26 6 6 6 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 6 6 6 18 18 17 38 38 38 58 58 58 +78 78 78 87 86 85 102 98 90 121 121 122 174 146 62 210 150 10 +231 174 11 246 186 14 246 190 14 246 190 14 246 186 14 234 193 17 +101 78 10 2 2 6 46 46 46 215 214 215 177 168 195 148 140 189 +158 147 190 222 223 236 239 238 242 215 214 215 215 214 215 185 185 187 +122 122 130 183 174 199 138 131 154 136 121 175 127 110 169 113 86 148 +109 90 154 114 90 155 116 94 159 122 98 164 99 78 147 95 75 136 +87 78 126 118 78 130 116 94 159 122 98 164 136 121 175 213 206 226 +204 166 16 18 18 17 2 2 6 2 2 6 2 2 6 2 2 6 +2 2 6 2 2 6 6 6 6 122 94 10 236 202 20 234 193 17 +82 82 82 34 34 34 10 10 10 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 14 14 14 38 38 38 70 70 70 154 122 54 +190 142 34 204 146 10 198 138 10 198 138 10 214 154 10 226 171 11 +242 186 14 246 190 14 246 190 14 246 190 14 246 190 14 246 186 14 +226 171 11 44 31 8 6 2 7 22 22 22 160 158 162 200 193 212 +157 140 187 157 140 187 207 201 219 209 208 212 222 223 236 194 193 197 +30 30 30 146 139 165 146 139 165 134 120 165 134 114 171 114 90 155 +116 94 159 122 98 164 122 98 164 122 98 164 111 77 141 92 71 128 +109 90 154 122 98 164 126 102 165 142 128 179 229 226 233 242 186 14 +214 154 10 44 31 8 2 2 2 2 2 6 2 2 6 2 2 6 +2 2 6 2 2 6 63 43 6 231 174 11 234 193 17 238 183 13 +114 102 78 38 38 38 14 14 14 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 6 6 6 22 22 22 54 54 55 154 122 54 214 154 10 +226 171 11 231 174 11 226 171 11 226 171 11 238 178 14 242 186 14 +246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 +242 198 14 188 146 14 10 10 10 6 2 7 6 2 7 118 118 118 +216 211 227 157 140 187 154 134 184 183 174 199 215 214 215 222 223 236 +169 165 179 173 173 174 138 131 154 128 114 172 134 120 165 116 94 159 +122 98 164 122 98 164 121 102 164 126 102 165 103 83 144 116 94 159 +127 106 167 127 106 167 154 134 184 236 233 239 238 178 14 238 178 14 +210 150 10 138 93 6 14 14 14 2 2 6 2 2 6 2 2 6 +6 6 6 63 43 6 198 138 10 238 178 14 238 178 14 238 183 13 +126 114 90 58 58 58 22 22 22 6 6 6 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 10 10 10 30 30 30 70 70 70 179 134 43 226 171 11 +238 183 13 242 186 14 242 186 14 246 186 14 246 190 14 246 190 14 +246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 234 193 17 94 70 30 2 2 2 2 2 6 2 2 6 +66 66 66 229 226 233 170 156 193 147 132 180 170 156 193 207 201 219 +219 220 223 219 220 223 138 131 154 128 114 172 134 114 171 117 98 162 +121 102 164 121 102 164 121 102 164 120 106 166 116 94 159 127 106 167 +128 114 172 169 165 179 160 158 162 214 166 58 231 174 11 231 174 11 +218 158 10 194 134 10 160 108 10 118 82 10 101 78 10 118 82 10 +166 114 6 198 138 10 226 171 11 238 183 13 242 186 14 242 186 14 +162 146 94 78 78 78 34 34 34 14 14 14 6 6 6 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 6 6 6 30 30 30 78 78 78 190 142 34 226 166 10 +238 183 13 246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 242 198 14 204 166 16 18 18 17 2 2 6 2 2 6 +2 2 6 38 38 38 160 158 162 177 168 195 142 134 183 155 141 181 +200 193 212 222 223 236 146 139 165 128 114 172 136 121 175 122 98 164 +122 98 164 122 98 164 126 102 165 114 90 155 122 98 164 128 114 172 +196 186 211 210 206 186 200 193 212 206 162 42 226 171 11 238 178 14 +226 166 10 204 146 10 204 146 10 194 134 10 186 134 10 198 138 10 +210 150 10 231 174 11 242 186 14 246 190 14 246 190 14 242 186 14 +226 171 11 126 114 90 62 62 62 30 30 30 14 14 14 6 6 6 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 10 10 10 30 30 30 78 78 78 179 134 43 226 166 10 +238 183 13 246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 246 190 14 242 198 14 138 102 14 2 2 6 2 2 6 +2 2 6 2 2 6 78 78 78 250 250 250 196 186 211 147 132 180 +154 134 184 207 201 219 169 165 179 136 121 175 136 121 175 122 98 164 +126 102 165 122 98 164 114 90 155 121 102 164 134 120 165 213 206 226 +250 250 250 219 220 223 194 193 197 190 150 46 216 162 10 238 178 14 +231 174 11 226 166 10 218 158 10 214 154 10 214 154 10 218 158 10 +226 171 11 238 183 13 246 190 14 246 190 14 246 190 14 246 190 14 +242 186 14 206 162 42 102 98 90 58 58 58 30 30 30 14 14 14 +6 6 6 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 10 10 10 30 30 30 74 74 74 179 134 43 218 158 10 +238 178 14 246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 246 186 14 242 198 14 226 186 14 63 43 6 2 2 6 +2 2 6 2 2 6 22 22 22 239 238 242 254 254 254 213 206 226 +157 140 187 147 132 180 155 141 181 136 121 175 136 121 175 121 102 164 +117 98 162 116 94 159 127 106 167 147 126 185 226 218 234 250 250 250 +250 250 250 222 223 236 190 190 190 179 134 43 218 158 10 238 178 14 +238 183 13 238 178 14 231 174 11 226 166 10 226 171 11 231 174 11 +238 178 14 242 186 14 246 190 14 246 190 14 246 190 14 246 190 14 +238 183 13 238 183 13 206 162 42 107 107 108 66 66 66 34 34 34 +14 14 14 6 6 6 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 6 6 6 26 26 26 70 70 70 162 134 66 210 150 10 +238 178 14 246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 246 190 14 246 186 14 234 193 17 188 146 14 14 14 14 +2 2 6 2 2 6 46 46 46 246 246 246 254 254 254 254 254 254 +229 226 233 157 140 187 147 132 180 136 121 175 127 110 169 116 94 159 +121 102 164 121 102 164 162 146 182 236 233 239 254 254 254 254 254 254 +254 254 254 219 220 223 87 86 85 160 108 10 218 158 10 238 178 14 +242 186 14 246 186 14 242 186 14 238 183 13 238 183 13 242 186 14 +242 186 14 242 186 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 246 190 14 242 186 14 226 171 11 142 122 74 66 66 66 +30 30 30 10 10 10 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 6 6 6 26 26 26 70 70 70 162 134 66 210 150 10 +238 178 14 246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 246 190 14 246 190 14 246 190 14 234 193 17 122 94 10 +30 30 30 107 107 108 219 220 223 254 254 254 250 250 250 250 250 250 +250 250 250 216 211 227 118 106 138 147 132 180 134 114 171 126 102 165 +127 106 167 177 168 195 250 250 250 254 254 254 254 254 254 254 254 254 +242 242 242 82 82 82 14 14 14 160 108 10 218 158 10 238 178 14 +242 186 14 246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 246 190 14 246 190 14 246 190 14 238 183 13 162 134 66 +46 46 46 18 18 17 6 6 6 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 10 10 10 30 30 30 78 78 78 162 134 66 210 150 10 +238 178 14 242 186 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 246 190 14 246 190 14 246 190 14 242 198 14 214 174 14 +186 174 146 254 254 254 254 254 254 254 254 254 254 254 254 254 254 254 +254 254 254 250 250 250 250 250 250 196 186 211 136 121 175 134 114 171 +200 193 212 254 254 254 254 254 254 254 254 254 250 250 250 219 220 223 +58 58 58 2 2 2 18 18 17 166 114 6 218 158 10 231 174 11 +246 186 14 246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 246 190 14 246 190 14 246 186 14 242 186 14 190 150 46 +54 54 55 22 22 22 6 6 6 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 14 14 14 38 38 38 87 86 85 179 134 43 214 154 10 +238 178 14 246 186 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 234 193 17 +188 146 14 215 214 215 250 250 250 254 254 254 254 254 254 254 254 254 +254 254 254 254 254 254 254 254 254 250 250 250 216 211 227 226 218 234 +250 250 250 254 254 254 254 254 254 250 250 250 173 173 174 26 26 26 +2 2 2 2 2 2 44 31 8 160 108 10 216 162 10 242 186 14 +246 186 14 246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 246 190 14 246 190 14 238 178 14 226 166 10 142 122 74 +46 46 46 18 18 17 6 6 6 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +6 6 6 18 18 17 50 50 50 107 107 108 186 134 10 226 166 10 +242 186 14 246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 226 186 14 +216 162 10 141 110 47 229 226 233 254 254 254 254 254 254 250 250 250 +254 254 254 254 254 254 254 254 254 250 250 250 250 250 250 254 254 254 +254 254 254 250 250 250 199 199 199 62 62 62 2 2 2 2 2 2 +2 2 2 2 2 6 44 31 8 160 108 10 216 162 10 238 183 13 +246 186 14 246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 242 186 14 231 174 11 214 154 10 154 122 54 66 66 66 +30 30 30 10 10 10 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +6 6 6 22 22 22 54 54 55 154 122 54 204 146 10 231 174 11 +242 186 14 246 186 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 246 190 14 246 190 14 246 190 14 242 186 14 238 178 14 +216 162 10 160 108 10 63 43 6 134 133 135 219 220 223 250 250 250 +254 254 254 254 254 254 254 254 254 250 250 250 242 242 242 209 208 212 +141 139 143 66 66 66 6 6 6 6 2 7 2 2 6 2 2 6 +2 2 6 2 2 2 63 43 6 160 108 10 218 158 10 238 178 14 +246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 238 183 13 +231 174 11 218 158 10 190 142 34 126 114 90 70 70 70 38 38 38 +18 18 17 6 6 6 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +6 6 6 22 22 22 62 62 62 164 122 42 204 146 10 226 166 10 +238 178 14 238 183 13 238 183 13 242 186 14 246 186 14 246 190 14 +246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 238 178 14 +216 162 10 173 119 7 82 54 6 2 2 2 6 6 6 30 30 30 +54 54 55 62 62 62 50 50 50 38 38 38 18 18 17 6 6 6 +6 2 2 2 2 2 2 2 2 2 2 6 2 2 6 2 2 6 +2 2 6 6 6 6 82 54 6 166 114 6 214 154 10 238 178 14 +246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 246 190 14 +246 190 14 246 190 14 238 183 13 238 183 13 226 171 11 210 150 10 +179 134 43 126 114 90 78 78 78 50 50 50 34 34 34 18 18 17 +6 6 6 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +6 6 6 18 18 17 46 46 46 164 122 42 194 134 10 198 138 10 +218 158 10 216 162 10 226 166 10 226 171 11 231 174 11 238 178 14 +238 183 13 238 183 13 242 186 14 246 186 14 246 190 14 246 190 14 +246 190 14 246 190 14 246 190 14 246 190 14 242 186 14 231 174 11 +210 150 10 160 108 10 104 70 6 10 10 10 6 2 2 2 2 2 +2 2 2 2 2 2 6 2 2 6 2 2 2 2 2 2 2 6 +2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 +2 2 6 6 6 6 90 62 6 166 114 6 204 146 10 231 174 11 +242 186 14 246 190 14 246 190 14 246 190 14 246 186 14 242 186 14 +238 183 13 231 174 11 226 166 10 214 154 10 179 134 43 126 114 90 +87 86 85 58 58 58 38 38 38 22 22 22 10 10 10 6 6 6 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 14 14 14 34 34 34 66 66 66 141 110 47 158 118 10 +166 114 6 182 122 6 186 134 10 198 138 10 204 146 10 204 146 10 +210 150 10 216 162 10 226 166 10 231 174 11 238 183 13 246 186 14 +246 186 14 246 186 14 246 186 14 246 186 14 238 183 13 218 158 10 +186 134 10 154 98 6 104 70 6 14 14 14 2 2 2 2 2 6 +2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 +2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 2 2 6 +2 2 6 6 6 6 82 54 6 154 98 6 186 134 10 216 162 10 +238 178 14 238 183 13 246 186 14 242 186 14 238 183 13 231 174 11 +226 166 10 204 146 10 186 134 10 154 122 54 90 90 90 62 62 62 +42 42 42 22 22 22 14 14 14 6 6 6 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 6 6 6 18 18 17 34 34 34 62 62 62 78 78 78 +102 98 90 126 114 90 141 110 47 160 108 10 160 108 10 166 114 6 +173 119 7 182 122 6 186 134 10 198 138 10 210 150 10 216 162 10 +226 171 11 238 178 14 238 178 14 231 174 11 216 162 10 198 138 10 +160 108 10 127 82 6 90 62 6 10 10 10 2 2 6 2 2 6 +18 18 17 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 +38 38 38 38 38 38 38 38 38 38 38 38 26 26 26 2 2 6 +2 2 2 6 6 6 63 43 6 138 93 6 173 119 7 204 146 10 +216 162 10 231 174 11 231 174 11 231 174 11 216 162 10 210 150 10 +186 134 10 160 108 10 126 114 90 82 82 82 50 50 50 30 30 30 +14 14 14 6 6 6 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 6 6 6 14 14 14 22 22 22 34 34 34 +42 42 42 58 58 58 74 74 74 87 86 85 102 98 90 122 102 70 +124 98 46 122 86 26 138 93 6 154 98 6 160 108 10 182 122 6 +186 134 10 194 134 10 204 146 10 204 146 10 182 122 6 160 108 10 +134 86 6 104 70 6 44 31 8 54 54 55 107 107 108 102 98 90 +87 86 85 82 82 82 78 78 78 78 78 78 78 78 78 78 78 78 +78 78 78 78 78 78 78 78 78 82 82 82 87 86 85 94 94 94 +107 107 108 104 103 100 85 65 33 127 82 6 160 108 10 182 122 6 +186 134 10 204 146 10 204 146 10 204 146 10 194 134 10 173 119 7 +154 98 6 107 107 108 66 66 66 42 42 42 22 22 22 10 10 10 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 6 6 6 10 10 10 +14 14 14 22 22 22 30 30 30 38 38 38 50 50 50 62 62 62 +74 74 74 90 90 90 102 98 90 114 102 78 122 86 26 127 82 6 +138 93 6 154 98 6 154 98 6 154 98 6 134 86 6 118 82 10 +104 70 6 85 65 33 102 98 90 82 82 82 58 58 58 46 46 46 +38 38 38 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 +34 34 34 34 34 34 34 34 34 34 34 34 38 38 38 42 42 42 +54 54 55 82 82 82 91 83 66 90 62 6 127 82 6 160 108 10 +166 114 6 166 114 6 173 119 7 166 114 6 154 98 6 122 86 26 +102 98 90 62 62 62 34 34 34 18 18 17 6 6 6 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 6 6 6 6 6 6 10 10 10 18 18 17 22 22 22 +30 30 30 42 42 42 50 50 50 70 70 70 87 86 85 102 98 90 +104 84 56 104 70 6 104 70 6 104 70 6 104 70 6 90 62 6 +82 54 6 94 94 94 62 62 62 38 38 38 22 22 22 14 14 14 +10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 +6 6 6 10 10 10 10 10 10 10 10 10 10 10 10 14 14 14 +22 22 22 42 42 42 70 70 70 91 83 66 82 54 6 104 70 6 +127 82 6 138 93 6 134 86 6 118 82 10 104 84 56 87 86 85 +58 58 58 30 30 30 14 14 14 6 6 6 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 6 6 6 +10 10 10 14 14 14 18 18 17 26 26 26 38 38 38 54 54 55 +74 74 74 87 86 85 91 83 66 91 83 66 91 83 66 87 86 85 +78 78 78 50 50 50 30 30 30 14 14 14 6 6 6 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +6 6 6 18 18 17 34 34 34 58 58 58 78 78 78 78 78 78 +91 83 66 91 83 66 91 83 66 91 83 66 74 74 74 50 50 50 +26 26 26 14 14 14 10 6 18 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 6 6 6 6 6 6 14 14 14 18 18 17 +30 30 30 38 38 38 46 46 46 50 50 50 46 46 46 42 42 42 +30 30 30 18 18 17 10 10 10 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 6 6 6 14 14 14 26 26 26 38 38 38 50 50 50 +54 54 55 58 58 58 54 54 55 42 42 42 30 30 30 18 18 17 +10 10 10 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 6 6 6 +6 6 6 10 10 10 14 14 14 18 18 17 18 18 17 14 14 14 +10 10 10 6 6 6 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 6 6 6 14 14 14 18 18 17 +22 22 22 22 22 22 18 18 17 14 14 14 10 10 10 6 6 6 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +2 2 2 2 2 2 diff --git a/include/asm-generic/gpio.h b/include/asm-generic/gpio.h index 66d6106..3e74f68 100644 --- a/include/asm-generic/gpio.h +++ b/include/asm-generic/gpio.h @@ -119,6 +119,7 @@ extern void gpio_free(unsigned gpio); extern int gpio_direction_input(unsigned gpio); extern int gpio_direction_output(unsigned gpio, int value); +extern int gpio_direction_is_output(unsigned gpio); extern int gpio_get_value_cansleep(unsigned gpio); extern void gpio_set_value_cansleep(unsigned gpio, int value); diff --git a/include/linux/exi.h b/include/linux/exi.h new file mode 100644 index 0000000..737dcd8 --- /dev/null +++ b/include/linux/exi.h @@ -0,0 +1,343 @@ +/* + * include/linux/exi.h + * + * Nintendo GameCube EXpansion Interface definitions + * Copyright (C) 2004-2009 The GameCube Linux Team + * Copyright (C) 2004 Arthur Othieno + * Copyright (C) 2004,2005 Todd Jeffreys + * Copyright (C) 2005,2007,2008,2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#ifndef __EXI_H +#define __EXI_H + +#ifdef CONFIG_GAMECUBE_EXI + +#include +#include + + +struct exi_channel; + +/* + * + */ +struct exi_device_id { + unsigned int channel; +#define EXI_CHANNEL_ANY (~0) + + unsigned int device; +#define EXI_DEVICE_ANY (~0) + + u32 id; +#define EXI_ID_INVALID (~0) +#define EXI_ID_NONE (EXI_ID_INVALID-1) +}; + +/* + * + */ +struct exi_device { + struct exi_channel *exi_channel; + + struct exi_device_id eid; + int frequency; + + unsigned flags; +#define EXI_DEV_DYING (1<<0) + + struct device dev; +}; + +#define to_exi_device(n) container_of(n, struct exi_device, dev) + +struct exi_device *exi_get_exi_device(struct exi_channel *exi_channel, + int device); + +/* + * + */ +struct exi_driver { + char *name; + struct exi_device_id *eid_table; + int frequency; + + int (*probe) (struct exi_device *dev); + void (*remove) (struct exi_device *dev); + + struct device_driver driver; +}; + +#define to_exi_driver(n) container_of(n, struct exi_driver, driver) + + +/* + * EXpansion Interface devices and drivers. + * + */ +extern struct exi_device *exi_device_get(struct exi_device *exi_device); +extern void exi_device_put(struct exi_device *exi_device); + +extern int exi_driver_register(struct exi_driver *exi_driver); +extern void exi_driver_unregister(struct exi_driver *exi_driver); + +static inline void *exi_get_drvdata(struct exi_device *exi_dev) +{ + return dev_get_drvdata(&exi_dev->dev); +} + +static inline void exi_set_drvdata(struct exi_device *exi_dev, void *data) +{ + dev_set_drvdata(&exi_dev->dev, data); +} + +static inline int exi_is_dying(struct exi_device *exi_device) +{ + return exi_device->flags & EXI_DEV_DYING; +} + +static inline int exi_set_dying(struct exi_device *exi_device, int status) +{ + if (status) + exi_device->flags |= EXI_DEV_DYING; + else + exi_device->flags &= ~EXI_DEV_DYING; + + return exi_is_dying(exi_device); +} + +extern u32 exi_get_id(struct exi_device *exi_device); + +extern void exi_quiesce(void); + +/* + * EXpansion Interface channels. + * + */ + +extern void exi_channel_init(struct exi_channel *exi_channel, + unsigned int channel); + +extern struct exi_channel *to_exi_channel(unsigned int channel); +extern unsigned int to_channel(struct exi_channel *exi_channel); + +static inline struct exi_channel *exi_get_exi_channel(struct exi_device *dev) +{ + return dev->exi_channel; +} + +#define EXI_EVENT_IRQ 0 +#define EXI_EVENT_INSERT 1 +#define EXI_EVENT_TC 2 + +typedef int (*exi_event_handler_t)(struct exi_channel *exi_channel, + unsigned int event_id, void *data); + +extern int exi_event_register(struct exi_channel *exi_channel, + unsigned int event_id, + struct exi_device *exi_device, + exi_event_handler_t handler, void *data, + unsigned int channel_mask); +extern int exi_event_unregister(struct exi_channel *exi_channel, + unsigned int event_id); + + +/* + * Commands. + * + * + */ +struct exi_command { + int opcode; +#define EXI_OP_READ (0x00<<2) /* same as in EXIxCR */ +#define EXI_OP_WRITE (0x01<<2) /* same as in EXIxCR */ +#define EXI_OP_READWRITE (0x02<<2) /* same as in EXIxCR */ + +#define EXI_OP_TAKE 0x0100 +#define EXI_OP_GIVE 0x0200 +#define EXI_OP_SELECT 0x0400 +#define EXI_OP_DESELECT 0x0800 + +#define EXI_OP_NOP -1 + + unsigned long flags; +#define EXI_CMD_NOWAIT (1<<0) +#define EXI_CMD_NODMA (1<<1) +#define EXI_CMD_IDI (1<<2) + + void *data; + size_t len; + size_t bytes_left; + + dma_addr_t dma_addr; + size_t dma_len; + + void *done_data; + void (*done)(struct exi_command *cmd); + + struct exi_channel *exi_channel; + struct exi_device *exi_device; +}; + +#include "../drivers/exi/exi-hw.h" + +static inline void exi_op_basic(struct exi_command *cmd, + struct exi_channel *exi_channel) +{ + memset(cmd, 0, sizeof(*cmd)); + cmd->exi_channel = exi_channel; + cmd->exi_device = exi_channel_owner(exi_channel); +} + +static inline void exi_op_nop(struct exi_command *cmd, + struct exi_channel *exi_channel) +{ + exi_op_basic(cmd, exi_channel); + cmd->opcode = EXI_OP_NOP; +} + +static inline void exi_op_take(struct exi_command *cmd, + struct exi_device *exi_device) +{ + exi_op_basic(cmd, exi_device->exi_channel); + cmd->opcode = EXI_OP_TAKE; + cmd->exi_device = exi_device; +} + +static inline void exi_op_give(struct exi_command *cmd, + struct exi_channel *exi_channel) +{ + exi_op_basic(cmd, exi_channel); + cmd->opcode = EXI_OP_GIVE; +} + +static inline void exi_op_select(struct exi_command *cmd, + struct exi_device *exi_device) +{ + exi_op_basic(cmd, exi_device->exi_channel); + cmd->opcode = EXI_OP_SELECT; + cmd->exi_device = exi_device; +} + +static inline void exi_op_deselect(struct exi_command *cmd, + struct exi_channel *exi_channel) +{ + exi_op_basic(cmd, exi_channel); + cmd->opcode = EXI_OP_DESELECT; +} + +static inline void exi_op_transfer(struct exi_command *cmd, + struct exi_channel *exi_channel, + void *data, size_t len, int opcode) +{ + exi_op_basic(cmd, exi_channel); + cmd->opcode = opcode; + cmd->data = data; + cmd->len = len; +} + + +/* + * EXpansion Interface interfaces. + * + */ + +/* + * Raw. + */ +extern void exi_select_raw(struct exi_channel *exi_channel, + unsigned int device, unsigned int freq); +extern void exi_deselect_raw(struct exi_channel *exi_channel); + +#define exi_transfer_u8_raw __exi_transfer_raw_u8 +#define exi_transfer_u16_raw __exi_transfer_raw_u16 +#define exi_transfer_u32_raw __exi_transfer_raw_u32 + +extern void exi_transfer_raw(struct exi_channel *exi_channel, + void *data, size_t len, int mode); +extern void exi_dma_transfer_raw(struct exi_channel *channel, + dma_addr_t data, size_t len, int mode); + +/* + * Standard. + */ + +int exi_take(struct exi_device *exi_device, int wait); +int exi_give(struct exi_device *exi_device); +void exi_select(struct exi_device *exi_device); +void exi_deselect(struct exi_channel *exi_channel); +void exi_transfer(struct exi_channel *exi_channel, + void *data, size_t len, int opcode, unsigned long flags); + +static inline int exi_dev_take(struct exi_device *exi_device) +{ + return exi_take(exi_device, 1); +} + +static inline int exi_dev_try_take(struct exi_device *exi_device) +{ + return exi_take(exi_device, 0); +} + +static inline int exi_dev_give(struct exi_device *exi_device) +{ + return exi_give(exi_device); +} + +static inline void exi_dev_select(struct exi_device *exi_device) +{ + exi_select(exi_device); +} + +static inline void exi_dev_deselect(struct exi_device *exi_device) +{ + exi_deselect(exi_device->exi_channel); +} + +static inline void exi_dev_transfer(struct exi_device *exi_device, + void *data, size_t len, int opcode, unsigned long flags) +{ + exi_transfer(exi_device->exi_channel, data, len, opcode, flags); +} + +static inline void exi_dev_read(struct exi_device *dev, void *data, size_t len) +{ + exi_dev_transfer(dev, data, len, EXI_OP_READ, 0); +} + +static inline void exi_dev_write(struct exi_device *dev, void *data, size_t len) +{ + exi_dev_transfer(dev, data, len, EXI_OP_WRITE, 0); +} + +static inline void exi_dev_readwrite(struct exi_device *dev, void *data, + size_t len) +{ + exi_dev_transfer(dev, data, len, EXI_OP_READWRITE, 0); +} + +static inline int exi_dev_set_freq(struct exi_device *dev, unsigned int freq) +{ + BUG_ON(freq > EXI_MAX_FREQ); + + dev->frequency = freq; + + return freq; +} + +#else + +static inline void exi_quiesce(void) +{ +} + +#endif /* CONFIG_GAMECUBE_EXI */ + +#endif /* __EXI_H */ + diff --git a/include/linux/fb.h b/include/linux/fb.h index 862e7d4..70e3d0f 100644 --- a/include/linux/fb.h +++ b/include/linux/fb.h @@ -38,6 +38,11 @@ struct dentry; #define FBIOPUT_MODEINFO 0x4617 #define FBIOGET_DISPINFO 0x4618 +#define FBIOWAITRETRACE 0x4619 +#define FBIOWAITPEFINISH 0x4620 +#define FBIOVIRTTOPHYS 0x4621 +#define FBIOFLIP 0x4622 +#define FBIOFLIPHACK 0x4623 /* libsdl */ #define FB_TYPE_PACKED_PIXELS 0 /* Packed Pixels */ #define FB_TYPE_PLANES 1 /* Non interleaved planes */ @@ -907,7 +912,13 @@ struct fb_info { #define fb_readq __raw_readq #define fb_writeb __raw_writeb #define fb_writew __raw_writew -#define fb_writel __raw_writel +#ifndef CONFIG_FB_GAMECUBE /* XXX Why? O' why? */ +# define fb_writel __raw_writel +#else + extern unsigned int vifb_writel(unsigned int, void *); +# define fb_writel(b, addr) vifb_writel(b, addr) +# define fb_writel_real(b, addr) (*(/*volatile*/ u32 __iomem *)(addr) = (b)) +#endif #define fb_writeq __raw_writeq #define fb_memset memset_io diff --git a/include/linux/gpio.h b/include/linux/gpio.h index 059bd18..8a1ec97 100644 --- a/include/linux/gpio.h +++ b/include/linux/gpio.h @@ -51,6 +51,11 @@ static inline int gpio_direction_output(unsigned gpio, int value) return -ENOSYS; } +static inline int gpio_direction_is_output(unsigned gpio) +{ + return -EINVAL; +} + static inline int gpio_get_value(unsigned gpio) { /* GPIO can never have been requested or set as {in,out}put */ diff --git a/include/linux/i2c-gpio.h b/include/linux/i2c-gpio.h index c1bcb1f..bc4f01c 100644 --- a/include/linux/i2c-gpio.h +++ b/include/linux/i2c-gpio.h @@ -33,6 +33,7 @@ struct i2c_gpio_platform_data { unsigned int sda_is_open_drain:1; unsigned int scl_is_open_drain:1; unsigned int scl_is_output_only:1; + unsigned int sda_enforce_dir:1; }; #endif /* _LINUX_I2C_GPIO_H */ diff --git a/include/linux/kernel.h b/include/linux/kernel.h index f4e3184..e4aa214 100644 --- a/include/linux/kernel.h +++ b/include/linux/kernel.h @@ -381,7 +381,7 @@ static inline char *pack_hex_byte(char *buf, u8 byte) #define pr_info(fmt, ...) \ printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__) #define pr_cont(fmt, ...) \ - printk(KERN_CONT fmt, ##__VA_ARGS__) + printk(KERN_CONT pr_fmt(fmt), ##__VA_ARGS__) /* pr_devel() should produce zero code unless DEBUG is defined */ #ifdef DEBUG diff --git a/include/linux/kexec.h b/include/linux/kexec.h index adc34f2..01f9095 100644 --- a/include/linux/kexec.h +++ b/include/linux/kexec.h @@ -52,10 +52,11 @@ */ typedef unsigned long kimage_entry_t; -#define IND_DESTINATION 0x1 -#define IND_INDIRECTION 0x2 -#define IND_DONE 0x4 -#define IND_SOURCE 0x8 +#define IND_DESTINATION 0x01 +#define IND_INDIRECTION 0x02 +#define IND_DONE 0x04 +#define IND_SOURCE 0x08 +#define IND_NOALLOC 0x10 /* special case for memory preserving code */ #define KEXEC_SEGMENT_MAX 16 struct kexec_segment { @@ -125,6 +126,8 @@ extern asmlinkage long compat_sys_kexec_load(unsigned long entry, #endif extern struct page *kimage_alloc_control_pages(struct kimage *image, unsigned int order); +extern int kimage_add_preserved_region(struct kimage *image, unsigned long to, + unsigned long from, int length); extern void crash_kexec(struct pt_regs *); int kexec_should_crash(struct task_struct *); void crash_save_cpu(struct pt_regs *regs, int cpu); diff --git a/include/linux/linux_logo.h b/include/linux/linux_logo.h index ca5bd91..c1e8586 100644 --- a/include/linux/linux_logo.h +++ b/include/linux/linux_logo.h @@ -38,6 +38,7 @@ extern const struct linux_logo logo_linux_clut224; extern const struct linux_logo logo_blackfin_vga16; extern const struct linux_logo logo_blackfin_clut224; extern const struct linux_logo logo_dec_clut224; +extern const struct linux_logo logo_gcn_clut224; extern const struct linux_logo logo_mac_clut224; extern const struct linux_logo logo_parisc_clut224; extern const struct linux_logo logo_sgi_clut224; diff --git a/kernel/kexec.c b/kernel/kexec.c index f336e21..3e4f721 100644 --- a/kernel/kexec.c +++ b/kernel/kexec.c @@ -580,18 +580,29 @@ static int kimage_set_destination(struct kimage *image, } -static int kimage_add_page(struct kimage *image, unsigned long page) +static inline int kimage_add_page_with_flags(struct kimage *image, + unsigned long page, int flags) { int result; page &= PAGE_MASK; - result = kimage_add_entry(image, page | IND_SOURCE); + result = kimage_add_entry(image, page | (flags & ~PAGE_MASK)); if (result == 0) image->destination += PAGE_SIZE; return result; } +static int kimage_add_page(struct kimage *image, unsigned long page) +{ + return kimage_add_page_with_flags(image, page, IND_SOURCE); +} + +static int kimage_add_page_noalloc(struct kimage *image, unsigned long page) +{ + return kimage_add_page_with_flags(image, page, IND_SOURCE|IND_NOALLOC); +} + static void kimage_free_extra_pages(struct kimage *image) { @@ -610,6 +621,27 @@ static void kimage_terminate(struct kimage *image) *image->entry = IND_DONE; } +int kimage_add_preserved_region(struct kimage *image, unsigned long to, + unsigned long from, int length) +{ + int result = 0; + + if (length > 0) { + result = kimage_set_destination(image, to); + if (result < 0) + goto out; + while (length > 0) { + result = kimage_add_page_noalloc(image, from); + if (result < 0) + goto out; + from += PAGE_SIZE; + length -= PAGE_SIZE; + } + } +out: + return result; +} + #define for_each_kimage_entry(image, ptr, entry) \ for (ptr = &image->head; (entry = *ptr) && !(entry & IND_DONE); \ ptr = (entry & IND_INDIRECTION)? \ @@ -641,9 +673,11 @@ static void kimage_free(struct kimage *image) * done with it. */ ind = entry; + } else if (entry & IND_SOURCE) { + /* free only entries that we really allocated */ + if (!(entry & IND_NOALLOC)) + kimage_free_entry(entry); } - else if (entry & IND_SOURCE) - kimage_free_entry(entry); } /* Free the final indirection page */ if (ind & IND_INDIRECTION) diff --git a/sound/ppc/Kconfig b/sound/ppc/Kconfig index 0519c60..e2faff8 100644 --- a/sound/ppc/Kconfig +++ b/sound/ppc/Kconfig @@ -49,4 +49,22 @@ config SND_PS3_DEFAULT_START_DELAY depends on SND_PS3 default "2000" +config SND_GAMECUBE + tristate "Nintendo GameCube/Wii" + depends on SND && GAMECUBE_COMMON + help + Say Y here to include support for audio on the Nintendo GameCube/Wii. + + To compile this driver as a module, choose M here: the module + will be called snd-gcn. + +config SND_GAMECUBE_MIC + tristate "Nintendo GameCube Microphone (DOL-022)" + depends on SND && GAMECUBE_EXI && EXPERIMENTAL + help + If you say yes to this option, support will be included for the + Nintendo GameCube Microphone (DOL-022). + + If in doubt, say N here. + endif # SND_PPC diff --git a/sound/ppc/Makefile b/sound/ppc/Makefile index 679c45a..fa77f53 100644 --- a/sound/ppc/Makefile +++ b/sound/ppc/Makefile @@ -4,7 +4,10 @@ # snd-powermac-objs := powermac.o pmac.o awacs.o burgundy.o daca.o tumbler.o keywest.o beep.o +snd-gcn-objs := gcn-ai.o # Toplevel Module Dependency obj-$(CONFIG_SND_POWERMAC) += snd-powermac.o obj-$(CONFIG_SND_PS3) += snd_ps3.o +obj-$(CONFIG_SND_GAMECUBE) += snd-gcn.o +obj-$(CONFIG_SND_GAMECUBE_MIC) += gcn-mic.o diff --git a/sound/ppc/gcn-ai.c b/sound/ppc/gcn-ai.c new file mode 100644 index 0000000..0269137 --- /dev/null +++ b/sound/ppc/gcn-ai.c @@ -0,0 +1,598 @@ +/* + * sound/ppc/gcn-ai.c + * + * Nintendo GameCube/Wii Audio Interface (AI) driver + * Copyright (C) 2004-2009 The GameCube Linux Team + * Copyright (C) 2007,2008,2009 Albert Herranz + * + * Based on work from mist, kirin, groepaz, Steve_-, isobel and others. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define SNDRV_GET_ID +#include + + +#define DRV_MODULE_NAME "gcn-ai" +#define DRV_DESCRIPTION "Nintendo GameCube/Wii Audio Interface (AI) driver" +#define DRV_AUTHOR "Michael Steil, " \ + "(kirin), " \ + "(groepaz), " \ + "Steven Looman, " \ + "Albert Herranz" + +static char ai_driver_version[] = "1.0i"; + +#define drv_printk(level, format, arg...) \ + printk(level DRV_MODULE_NAME ": " format , ## arg) + + +/* + * Hardware. + * + */ + +/* + * DSP registers. + */ +#define AI_DSP_CSR 0x0a /* 16 bits */ +#define AI_CSR_RES (1<<0) +#define AI_CSR_PIINT (1<<1) +#define AI_CSR_HALT (1<<2) +#define AI_CSR_AIDINT (1<<3) +#define AI_CSR_AIDINTMASK (1<<4) +#define AI_CSR_ARINT (1<<5) +#define AI_CSR_ARINTMASK (1<<6) +#define AI_CSR_DSPINT (1<<7) +#define AI_CSR_DSPINTMASK (1<<8) +#define AI_CSR_DSPDMA (1<<9) +#define AI_CSR_RESETXXX (1<<11) + +#define AI_DSP_DMA_ADDRH 0x30 /* 16 bits */ + +#define AI_DSP_DMA_ADDRL 0x32 /* 16 bits */ + +#define AI_DSP_DMA_CTLLEN 0x36 /* 16 bits */ +#define AI_CTLLEN_PLAY (1<<15) + +#define AI_DSP_DMA_LEFT 0x3a /* 16 bits */ + +/* + * AI registers. + */ +#define AI_AICR 0x00 /* 32 bits */ +#define AI_AICR_RATE (1<<6) + + +/* + * Sound chip. + */ +struct snd_gcn { + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + + int start_play; + int stop_play; + + dma_addr_t dma_addr; + size_t period_size; + int nperiods; + int cur_period; + + void __iomem *dsp_base; + void __iomem *ai_base; + unsigned int irq; + + struct device *dev; +}; + + +/* + * Hardware functions. + * + */ + +static void ai_dsp_load_sample(void __iomem *dsp_base, + void *addr, size_t size) +{ + u32 daddr = (unsigned long)addr; + + out_be16(dsp_base + AI_DSP_DMA_ADDRH, daddr >> 16); + out_be16(dsp_base + AI_DSP_DMA_ADDRL, daddr & 0xffff); + out_be16(dsp_base + AI_DSP_DMA_CTLLEN, + (in_be16(dsp_base + AI_DSP_DMA_CTLLEN) & AI_CTLLEN_PLAY) | + size >> 5); +} + +static void ai_dsp_start_sample(void __iomem *dsp_base) +{ + out_be16(dsp_base + AI_DSP_DMA_CTLLEN, + in_be16(dsp_base + AI_DSP_DMA_CTLLEN) | AI_CTLLEN_PLAY); +} + +static void ai_dsp_stop_sample(void __iomem *dsp_base) +{ + out_be16(dsp_base + AI_DSP_DMA_CTLLEN, + in_be16(dsp_base + AI_DSP_DMA_CTLLEN) & ~AI_CTLLEN_PLAY); +} + +static int ai_dsp_get_remaining_byte_count(void __iomem *dsp_base) +{ + return in_be16(dsp_base + AI_DSP_DMA_LEFT) << 5; +} + +static void ai_enable_interrupts(void __iomem *dsp_base) +{ + unsigned long flags; + + /* enable AI DMA and DSP interrupts */ + local_irq_save(flags); + out_be16(dsp_base + AI_DSP_CSR, + in_be16(dsp_base + AI_DSP_CSR) | + AI_CSR_AIDINTMASK | AI_CSR_PIINT); + local_irq_restore(flags); +} + +static void ai_disable_interrupts(void __iomem *dsp_base) +{ + unsigned long flags; + + /* disable AI interrupts */ + local_irq_save(flags); + out_be16(dsp_base + AI_DSP_CSR, + in_be16(dsp_base + AI_DSP_CSR) & ~AI_CSR_AIDINTMASK); + local_irq_restore(flags); +} + +static void ai_set_rate(void __iomem *ai_base, int fortyeight) +{ + /* set rate to 48KHz or 32KHz */ + if (fortyeight) + out_be32(ai_base + AI_AICR, + in_be32(ai_base + AI_AICR) & ~AI_AICR_RATE); + else + out_be32(ai_base + AI_AICR, + in_be32(ai_base + AI_AICR) | AI_AICR_RATE); +} + + +static int index = SNDRV_DEFAULT_IDX1; /* index 0-MAX */ +static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */ + +static struct snd_gcn *gcn_audio; + +static struct snd_pcm_hardware snd_gcn_playback = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16_BE, + .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000, + .rate_min = 32000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 32768, + .period_bytes_min = 32, + .period_bytes_max = 32768, + .periods_min = 1, + .periods_max = 1024, +}; + +static int snd_gcn_open(struct snd_pcm_substream *substream) +{ + struct snd_gcn *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + chip->playback_substream = substream; + runtime->hw = snd_gcn_playback; + + /* align to 32 bytes */ + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + 32); + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + 32); + + return 0; +} + +static int snd_gcn_close(struct snd_pcm_substream *substream) +{ + struct snd_gcn *chip = snd_pcm_substream_chip(substream); + + chip->playback_substream = NULL; + return 0; +} + +static int snd_gcn_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static int snd_gcn_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int snd_gcn_prepare(struct snd_pcm_substream *substream) +{ + struct snd_gcn *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + /* set requested sample rate */ + switch (runtime->rate) { + case 32000: + ai_set_rate(chip->ai_base, 0); + break; + case 48000: + ai_set_rate(chip->ai_base, 1); + break; + default: + drv_printk(KERN_ERR, "unsupported rate %i\n", runtime->rate); + return -EINVAL; + } + + return 0; +} + +static int snd_gcn_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_gcn *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* do something to start the PCM engine */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + chip->period_size = snd_pcm_lib_period_bytes(substream); + chip->nperiods = snd_pcm_lib_buffer_bytes(substream) / + chip->period_size; + chip->cur_period = 0; + chip->stop_play = 0; + chip->start_play = 1; + + chip->dma_addr = dma_map_single(chip->dev, + runtime->dma_area, + chip->period_size, + DMA_TO_DEVICE); + ai_dsp_load_sample(chip->dsp_base, runtime->dma_area, + chip->period_size); + ai_dsp_start_sample(chip->dsp_base); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + chip->stop_play = 1; + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t snd_gcn_pointer(struct snd_pcm_substream *substream) +{ + struct snd_gcn *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int left, bytes; + + left = ai_dsp_get_remaining_byte_count(chip->dsp_base); + bytes = chip->period_size * (chip->cur_period + 1); + + return bytes_to_frames(runtime, bytes - left); +} + +static irqreturn_t snd_gcn_interrupt(int irq, void *dev) +{ + struct snd_gcn *chip = dev; + void *addr; + unsigned long flags; + u16 csr; + + /* + * This is a shared interrupt. Do nothing if it ain't ours. + */ + csr = in_be16(chip->dsp_base + AI_DSP_CSR); + if (!(csr & AI_CSR_AIDINT)) + return IRQ_NONE; + + if (chip->start_play) { + chip->start_play = 0; + } else { + /* stop current sample */ + ai_dsp_stop_sample(chip->dsp_base); + dma_unmap_single(chip->dev, chip->dma_addr, chip->period_size, + DMA_TO_DEVICE); + + /* load next sample if we are not stopping */ + if (!chip->stop_play) { + if (chip->cur_period < (chip->nperiods - 1)) + chip->cur_period++; + else + chip->cur_period = 0; + + addr = chip->playback_substream->runtime->dma_area + + (chip->cur_period * chip->period_size); + chip->dma_addr = dma_map_single(chip->dev, + addr, + chip->period_size, + DMA_TO_DEVICE); + ai_dsp_load_sample(chip->dsp_base, addr, + chip->period_size); + ai_dsp_start_sample(chip->dsp_base); + + snd_pcm_period_elapsed(chip->playback_substream); + } + } + /* + * Ack the AI DMA interrupt, going through lengths to only ack + * the audio part. + */ + local_irq_save(flags); + csr = in_be16(chip->dsp_base + AI_DSP_CSR); + csr &= ~(AI_CSR_PIINT | AI_CSR_ARINT | AI_CSR_DSPINT); + out_be16(chip->dsp_base + AI_DSP_CSR, csr); + local_irq_restore(flags); + + return IRQ_HANDLED; +} + + +static struct snd_pcm_ops snd_gcn_playback_ops = { + .open = snd_gcn_open, + .close = snd_gcn_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_gcn_hw_params, + .hw_free = snd_gcn_hw_free, + .prepare = snd_gcn_prepare, + .trigger = snd_gcn_trigger, + .pointer = snd_gcn_pointer, +}; + +static int __devinit snd_gcn_new_pcm(struct snd_gcn *chip) +{ + struct snd_pcm *pcm; + int retval; + + retval = snd_pcm_new(chip->card, chip->card->shortname, 0, 1, 0, &pcm); + if (retval < 0) + return retval; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_gcn_playback_ops); + + /* preallocate 64k buffer */ + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data + (GFP_KERNEL), 64 * 1024, + 64 * 1024); + + pcm->info_flags = 0; + pcm->private_data = chip; + strcpy(pcm->name, chip->card->shortname); + + chip->pcm = pcm; + + return 0; +} + +static void ai_shutdown(struct snd_gcn *chip) +{ + ai_dsp_stop_sample(chip->dsp_base); + ai_disable_interrupts(chip->dsp_base); +} + +static int __devinit ai_init(struct snd_gcn *chip, + struct resource *dsp, struct resource *ai, + unsigned int irq) +{ + struct snd_card *card; + int retval; + + chip->dsp_base = ioremap(dsp->start, dsp->end - dsp->start + 1); + chip->ai_base = ioremap(ai->start, ai->end - ai->start + 1); + chip->irq = irq; + + chip->stop_play = 1; + card = chip->card; + + strcpy(card->driver, DRV_MODULE_NAME); + strcpy(card->shortname, card->driver); + sprintf(card->longname, "Nintendo GameCube Audio Interface"); + + /* PCM */ + retval = snd_gcn_new_pcm(chip); + if (retval < 0) + goto err_new_pcm; + + retval = request_irq(chip->irq, snd_gcn_interrupt, + IRQF_DISABLED | IRQF_SHARED, + card->shortname, chip); + if (retval) { + drv_printk(KERN_ERR, "unable to request IRQ %d\n", chip->irq); + goto err_request_irq; + } + ai_enable_interrupts(chip->dsp_base); + + gcn_audio = chip; + retval = snd_card_register(card); + if (retval) { + drv_printk(KERN_ERR, "failed to register card\n"); + goto err_card_register; + } + + return 0; + +err_card_register: + ai_disable_interrupts(chip->dsp_base); + free_irq(chip->irq, chip); +err_request_irq: +err_new_pcm: + iounmap(chip->dsp_base); + iounmap(chip->ai_base); + return retval; +} + +static void ai_exit(struct snd_gcn *chip) +{ + ai_dsp_stop_sample(chip->dsp_base); + ai_disable_interrupts(chip->dsp_base); + + free_irq(chip->irq, chip); + iounmap(chip->dsp_base); + iounmap(chip->ai_base); +} + + +/* + * Device interfaces. + * + */ + +static int ai_do_shutdown(struct device *dev) +{ + struct snd_gcn *chip; + + chip = dev_get_drvdata(dev); + if (chip) { + ai_shutdown(chip); + return 0; + } + return -ENODEV; +} + +static int __devinit ai_do_probe(struct device *dev, + struct resource *dsp, struct resource *ai, + unsigned int irq) +{ + struct snd_card *card; + struct snd_gcn *chip; + int retval; + + retval = snd_card_create(index, id, THIS_MODULE, sizeof(struct snd_gcn), &card); + if (retval < 0) { + drv_printk(KERN_ERR, "failed to allocate card\n"); + return -ENOMEM; + } + chip = (struct snd_gcn *)card->private_data; + memset(chip, 0, sizeof(*chip)); + chip->card = card; + dev_set_drvdata(dev, chip); + chip->dev = dev; + + retval = ai_init(chip, dsp, ai, irq); + if (retval) + snd_card_free(card); + + return retval; +} + +static int ai_do_remove(struct device *dev) +{ + struct snd_gcn *chip; + + chip = dev_get_drvdata(dev); + if (chip) { + ai_exit(chip); + dev_set_drvdata(dev, NULL); + snd_card_free(chip->card); + return 0; + } + return -ENODEV; +} + +/* + * OF Platform device interfaces. + * + */ + +static int __init ai_of_probe(struct of_device *odev, + const struct of_device_id *match) +{ + struct resource dsp, ai; + int retval; + + retval = of_address_to_resource(odev->node, 0, &dsp); + if (retval) { + drv_printk(KERN_ERR, "no dsp io memory range found\n"); + return -ENODEV; + } + retval = of_address_to_resource(odev->node, 1, &ai); + if (retval) { + drv_printk(KERN_ERR, "no ai io memory range found\n"); + return -ENODEV; + } + + return ai_do_probe(&odev->dev, + &dsp, &ai, irq_of_parse_and_map(odev->node, 0)); +} + +static int __exit ai_of_remove(struct of_device *odev) +{ + return ai_do_remove(&odev->dev); +} + +static int ai_of_shutdown(struct of_device *odev) +{ + return ai_do_shutdown(&odev->dev); +} + + +static struct of_device_id ai_of_match[] = { + { .compatible = "nintendo,flipper-audio" }, + { .compatible = "nintendo,hollywood-audio" }, + { }, +}; + +MODULE_DEVICE_TABLE(of, ai_of_match); + +static struct of_platform_driver ai_of_driver = { + .owner = THIS_MODULE, + .name = DRV_MODULE_NAME, + .match_table = ai_of_match, + .probe = ai_of_probe, + .remove = ai_of_remove, + .shutdown = ai_of_shutdown, +}; + +/* + * Module interfaces. + * + */ + +static int __init ai_init_module(void) +{ + drv_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION, + ai_driver_version); + + return of_register_platform_driver(&ai_of_driver); +} + +static void __exit ai_exit_module(void) +{ + of_unregister_platform_driver(&ai_of_driver); +} + +module_init(ai_init_module); +module_exit(ai_exit_module); + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_AUTHOR(DRV_AUTHOR); +MODULE_LICENSE("GPL"); + diff --git a/sound/ppc/gcn-mic.c b/sound/ppc/gcn-mic.c new file mode 100644 index 0000000..47a163f --- /dev/null +++ b/sound/ppc/gcn-mic.c @@ -0,0 +1,832 @@ +/* + * sound/ppc/gcn-mic.c + * + * Nintendo Microphone (DOL-022) driver + * Copyright (C) 2006-2009 The GameCube Linux Team + * Copyright (C) 2006,2007,2008,2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#define MIC_DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#define SNDRV_GET_ID +#include + +#define DRV_MODULE_NAME "gcn-mic" +#define DRV_DESCRIPTION "Nintendo Microphone (DOL-022) driver" +#define DRV_AUTHOR "Albert Herranz" + +MODULE_AUTHOR(DRV_AUTHOR); +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_LICENSE("GPL"); + +static char mic_driver_version[] = "0.1i"; + +#define mic_printk(level, format, arg...) \ + printk(level DRV_MODULE_NAME ": " format , ## arg) + +#ifdef MIC_DEBUG +# define DBG(fmt, args...) \ + printk(KERN_ERR "%s: " fmt, __func__ , ## args) +#else +# define DBG(fmt, args...) +#endif + + +#define MIC_EXI_ID 0x0a000000 + +#define MIC_SLOTA_CHANNEL 0 /* EXI0xxx */ +#define MIC_SLOTA_DEVICE 0 /* chip select, EXI0CSB0 */ + +#define MIC_SLOTB_CHANNEL 1 /* EXI1xxx */ +#define MIC_SLOTB_DEVICE 0 /* chip select, EXI1CSB0 */ + +#define MIC_SPI_CLK_IDX EXI_CLK_16MHZ + + +struct mic_device { + spinlock_t lock; + unsigned long flags; + + u16 status; + u16 control; +#define MIC_CTL_RATE_MASK (0x3<<11) +#define MIC_CTL_RATE_11025 (0x0<<11) +#define MIC_CTL_RATE_22050 (0x1<<11) +#define MIC_CTL_RATE_44100 (0x2<<11) +#define MIC_CTL_PERIOD_MASK (0x3<<13) +#define MIC_CTL_PERIOD_32 (0x0<<13) +#define MIC_CTL_PERIOD_64 (0x1<<13) +#define MIC_CTL_PERIOD_128 (0x2<<13) +#define MIC_CTL_START_SAMPLING (1<<15) + + struct task_struct *io_thread; + wait_queue_head_t io_waitq; + atomic_t io_pending; + + struct snd_card *card; + struct snd_pcm *pcm; + + struct snd_pcm_substream *c_substream; + u8 *c_orig, *c_cur; + int c_left; + + int running; + +#ifdef CONFIG_PROC_FS + struct proc_dir_entry *proc; +#endif /* CONFIG_PROC_FS */ + + int refcnt; + struct exi_device *exi_device; +}; + + +/* + * + */ +static void mic_hey(struct mic_device *dev) +{ + struct exi_device *exi_device = dev->exi_device; + u8 cmd = 0xff; + + exi_dev_select(exi_device); + exi_dev_write(exi_device, &cmd, sizeof(cmd)); + exi_dev_deselect(exi_device); +} + +/* + * + */ +static int mic_get_status(struct mic_device *dev) +{ + struct exi_device *exi_device = dev->exi_device; + u8 cmd = 0x40; + + exi_dev_select(exi_device); + exi_dev_write(exi_device, &cmd, sizeof(cmd)); + exi_dev_read(exi_device, &dev->status, sizeof(dev->status)); + exi_dev_deselect(exi_device); + + return dev->status; +} + +/* + * + */ +static void mic_control(struct mic_device *dev) +{ + struct exi_device *exi_device = dev->exi_device; + u8 cmd[3]; + + cmd[0] = 0x80; + cmd[1] = dev->control >> 8; + cmd[2] = dev->control & 0xff; + + DBG("control 0x80%02x%02x\n", cmd[1], cmd[2]); + + exi_dev_select(exi_device); + exi_dev_write(exi_device, cmd, sizeof(cmd)); + exi_dev_deselect(exi_device); + +} + +/* + * + */ +static void mic_read_period(struct mic_device *dev, void *buf, size_t len) +{ + struct exi_device *exi_device = dev->exi_device; + u8 cmd = 0x20; + + exi_dev_select(exi_device); + exi_dev_write(exi_device, &cmd, sizeof(cmd)); + exi_dev_read(exi_device, buf, len); + exi_dev_deselect(exi_device); + +/* DBG("mic cmd 0x20\n"); */ +} + +/* + * + */ +static void mic_enable_sampling(struct mic_device *dev, int enable) +{ + if (enable) + dev->control |= MIC_CTL_START_SAMPLING; + else + dev->control &= ~MIC_CTL_START_SAMPLING; +} + +/* + * + */ +static int mic_set_sample_rate(struct mic_device *dev, int rate) +{ + u16 control; + + switch (rate) { + case 11025: + control = MIC_CTL_RATE_11025; + break; + case 22050: + control = MIC_CTL_RATE_22050; + break; + case 44100: + control = MIC_CTL_RATE_44100; + break; + default: + mic_printk(KERN_ERR, "unsupported rate: %d\n", rate); + return -EINVAL; + } + dev->control &= ~MIC_CTL_RATE_MASK; + dev->control |= control; + return 0; +} + +/* + * + */ +static int mic_set_period(struct mic_device *dev, int period_bytes) +{ + u16 control; + + switch (period_bytes) { + case 32: + control = MIC_CTL_PERIOD_32; + break; + case 64: + control = MIC_CTL_PERIOD_64; + break; + case 128: + control = MIC_CTL_PERIOD_128; + break; + default: + mic_printk(KERN_ERR, "unsupported period: %d bytes\n", + period_bytes); + return -EINVAL; + } + dev->control &= ~MIC_CTL_PERIOD_MASK; + dev->control |= control; + return 0; +} + +/* + * /proc support + * + */ + +/* + * + */ +static int mic_init_proc(struct mic_device *dev) +{ + return 0; +} + +/* + * + */ +static void mic_exit_proc(struct mic_device *dev) +{ +} + + + +/* + * Driver + * + */ + +static int index = SNDRV_DEFAULT_IDX1; +static char *id = SNDRV_DEFAULT_STR1; + +static struct snd_pcm_hardware mic_snd_capture = { +#if 0 + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_NONINTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID), +#endif + .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_NONINTERLEAVED), + .formats = SNDRV_PCM_FMTBIT_S16_BE, + .rates = SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_22050 | + SNDRV_PCM_RATE_44100, + .rate_min = 11025, + .rate_max = 44100, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = 32768, + .period_bytes_min = 32, + .period_bytes_max = 128, + .periods_min = 1, + .periods_max = 1024, +}; + +#if 0 +static unsigned int period_bytes[] = { 32, 64, 128 }; +static struct snd_pcm_hw_constraint_list constraints_period_bytes = { + .count = ARRAY_SIZE(period_bytes), + .list = period_bytes, + .mask = 0, +}; +#endif + +/* + * + */ +static void mic_wakeup_io_thread(struct mic_device *dev) +{ + if (!IS_ERR(dev->io_thread)) { + atomic_inc(&dev->io_pending); + wake_up(&dev->io_waitq); + } +} + +/* + * + */ +static void mic_stop_io_thread(struct mic_device *dev) +{ + if (!IS_ERR(dev->io_thread)) { + atomic_inc(&dev->io_pending); + kthread_stop(dev->io_thread); + } +} + +/* + * Input/Output thread. Receives audio samples from the microphone. + */ +static int mic_io_thread(void *param) +{ + struct mic_device *dev = param; + struct snd_pcm_substream *substream; + int period_bytes; + u16 status; + + set_user_nice(current, -20); + set_current_state(TASK_RUNNING); + + for (;;) { + wait_event(dev->io_waitq, atomic_read(&dev->io_pending) > 0); + atomic_dec(&dev->io_pending); + + if (kthread_should_stop()) + break; + + if (try_to_freeze()) + continue; + + exi_dev_take(dev->exi_device); + status = mic_get_status(dev); + if (dev->running) { + substream = dev->c_substream; + + if (!dev->c_left) { + dev->c_cur = dev->c_orig; + dev->c_left = + snd_pcm_lib_buffer_bytes(substream); + } + + period_bytes = snd_pcm_lib_period_bytes(substream); + if (period_bytes > dev->c_left) + period_bytes = dev->c_left; + mic_read_period(dev, dev->c_cur, period_bytes); + dev->c_cur += period_bytes; + dev->c_left -= period_bytes; + + exi_dev_give(dev->exi_device); + snd_pcm_period_elapsed(substream); + exi_dev_take(dev->exi_device); + + if (status & 0x0200) { + DBG("0x0200\n"); + mic_hey(dev); + mic_enable_sampling(dev, 1); + mic_control(dev); + } + } else { + /* mic_enable_sampling(dev, 0); */ + dev->control = 0; + mic_control(dev); + } + exi_dev_give(dev->exi_device); + } + return 0; +} + +/* + * + */ +static int mic_event_handler(struct exi_channel *exi_channel, + unsigned int event, void *dev0) +{ + struct mic_device *dev = (struct mic_device *)dev0; + + /* exi channel is not taken, no exi operations here please */ + mic_wakeup_io_thread(dev); + + return 0; +} + +static int hw_rule_period_bytes_by_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval *period_bytes = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_BYTES); + struct snd_interval *rate = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + + DBG("rate: min %d, max %d\n", rate->min, rate->max); + + if (rate->min == rate->max) { + if (rate->min >= 44100) { + struct snd_interval t = { + .min = 128, + .max = 128, + .integer = 1, + }; + return snd_interval_refine(period_bytes, &t); + } else if (rate->min >= 22050) { + struct snd_interval t = { + .min = 32, + .max = 32, + .integer = 1, + }; + return snd_interval_refine(period_bytes, &t); + } else { + struct snd_interval t = { + .min = 32, + .max = 32, + .integer = 1, + }; + return snd_interval_refine(period_bytes, &t); + } + } + return 0; +} + +static int mic_snd_pcm_capture_open(struct snd_pcm_substream *substream) +{ + struct mic_device *dev = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned long flags; + int retval; + + DBG("enter\n"); + + spin_lock_irqsave(&dev->lock, flags); + dev->running = 0; + dev->c_substream = substream; + spin_unlock_irqrestore(&dev->lock, flags); + + runtime->hw = mic_snd_capture; + +#if 0 + /* only 32, 64 and 128 */ + retval = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + &constraints_period_bytes); + if (retval < 0) + return retval; +#endif + snd_pcm_hw_rule_add(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + hw_rule_period_bytes_by_rate, 0, + SNDRV_PCM_HW_PARAM_RATE, -1); + + /* align to 32 bytes */ + retval = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + 32); + return retval; + +} + +static int mic_snd_pcm_capture_close(struct snd_pcm_substream *substream) +{ + struct mic_device *dev = snd_pcm_substream_chip(substream); + unsigned long flags; + +DBG("enter\n"); + + spin_lock_irqsave(&dev->lock, flags); + dev->running = 0; + dev->c_substream = NULL; + spin_unlock_irqrestore(&dev->lock, flags); + + mic_wakeup_io_thread(dev); + + return 0; +} + +static int mic_snd_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ +DBG("enter\n"); + + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static int mic_snd_pcm_hw_free(struct snd_pcm_substream *substream) +{ +DBG("enter\n"); + + snd_pcm_lib_free_pages(substream); + return 0; +} + +static int mic_snd_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct mic_device *dev = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned long flags; + int retval; + +DBG("enter\n"); + + mic_printk(KERN_INFO, "rate=%d, channels=%d, sample_bits=%d\n", + runtime->rate, runtime->channels, + runtime->sample_bits); + mic_printk(KERN_INFO, "format=%d, access=%d\n", + runtime->format, runtime->access); + mic_printk(KERN_INFO, "buffer_bytes=%d, period_bytes=%d\n", + snd_pcm_lib_buffer_bytes(substream), + snd_pcm_lib_period_bytes(substream)); + + spin_lock_irqsave(&dev->lock, flags); + dev->c_orig = runtime->dma_area; + dev->c_left = 0; + spin_unlock_irqrestore(&dev->lock, flags); + + retval = mic_set_sample_rate(dev, runtime->rate); + if (retval < 0) + return retval; + + retval = mic_set_period(dev, snd_pcm_lib_period_bytes(substream)); + + return retval; +} + +static int mic_snd_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct mic_device *dev = snd_pcm_substream_chip(substream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (!dev->running) { + DBG("trigger start\n"); + dev->running = 1; + exi_dev_take(dev->exi_device); + mic_hey(dev); + mic_enable_sampling(dev, 1); + mic_control(dev); + exi_dev_give(dev->exi_device); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + DBG("trigger stop\n"); + dev->running = 0; + break; + } + return 0; +} + +static snd_pcm_uframes_t +mic_snd_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct mic_device *dev = snd_pcm_substream_chip(substream); + size_t ptr; + + if (!dev->running || !dev->c_left) + return 0; + + ptr = dev->c_cur - dev->c_orig; + return bytes_to_frames(substream->runtime, ptr); +} + + +static struct snd_pcm_ops mic_snd_pcm_capture_ops = { + .open = mic_snd_pcm_capture_open, + .close = mic_snd_pcm_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = mic_snd_pcm_hw_params, + .hw_free = mic_snd_pcm_hw_free, + .prepare = mic_snd_pcm_prepare, + .trigger = mic_snd_pcm_trigger, + .pointer = mic_snd_pcm_pointer, +}; + +/* + * + */ +static int mic_snd_new_pcm(struct mic_device *dev) +{ + struct snd_pcm *pcm; + int retval; + +DBG("enter\n"); + + retval = snd_pcm_new(dev->card, dev->card->shortname, 0, 0, 1, &pcm); + if (retval < 0) + return retval; + + pcm->private_data = dev; + strcpy(pcm->name, dev->card->shortname); + dev->pcm = pcm; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &mic_snd_pcm_capture_ops); + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data + (GFP_KERNEL), + 32*1024, 32*1024); + return 0; +} + +/* + * + */ +static int mic_init_snd(struct mic_device *dev) +{ + struct snd_card *card; + int retval = -ENOMEM; + +DBG("enter\n"); + + retval = snd_card_create(index, id, THIS_MODULE, 0, &card); + if (retval < 0) { + mic_printk(KERN_ERR, "unable to create sound card\n"); + goto err_card; + } + + strcpy(card->driver, DRV_MODULE_NAME); + strcpy(card->shortname, DRV_MODULE_NAME); + strcpy(card->longname, "Nintendo GameCube Microphone"); + + dev->card = card; + + retval = mic_snd_new_pcm(dev); + if (retval < 0) + goto err_new_pcm; + + retval = snd_card_register(card); + if (retval) { + mic_printk(KERN_ERR, "unable to register sound card\n"); + goto err_card_register; + } + + return 0; + +err_card_register: +err_new_pcm: + snd_card_free(card); + dev->card = NULL; +err_card: + return retval; +} + +/* + * + */ +static void mic_exit_snd(struct mic_device *dev) +{ +DBG("enter\n"); + + if (dev->card) { + snd_card_disconnect(dev->card); + snd_card_free_when_closed(dev->card); + + dev->card = NULL; + dev->pcm = NULL; + dev->c_substream = NULL; + } +} + +/* + * + */ +static int mic_init(struct mic_device *dev) +{ + struct exi_device *exi_device = dev->exi_device; + struct exi_channel *exi_channel = exi_get_exi_channel(exi_device); + int channel; + int retval = -ENOMEM; + +DBG("enter\n"); + + spin_lock_init(&dev->lock); + + dev->running = 0; + + retval = mic_init_snd(dev); + if (retval) + goto err_init_snd; + + init_waitqueue_head(&dev->io_waitq); + channel = to_channel(exi_get_exi_channel(dev->exi_device)); + dev->io_thread = kthread_run(mic_io_thread, dev, "kmicd/%d", channel); + if (IS_ERR(dev->io_thread)) { + mic_printk(KERN_ERR, "error creating io thread\n"); + goto err_io_thread; + } + + retval = exi_event_register(exi_channel, EXI_EVENT_IRQ, + exi_device, + mic_event_handler, dev, + 0 /*(1 << to_channel(exi_channel))*/); + if (retval) { + mic_printk(KERN_ERR, "error registering exi event\n"); + goto err_event_register; + } + + retval = mic_init_proc(dev); + if (retval) + goto err_init_proc; + + return 0; + +err_init_proc: + exi_event_unregister(exi_channel, EXI_EVENT_IRQ); +err_event_register: + mic_stop_io_thread(dev); +err_io_thread: + mic_exit_snd(dev); +err_init_snd: + return retval; + +} + +/* + * + */ +static void mic_exit(struct mic_device *dev) +{ + struct exi_device *exi_device = dev->exi_device; + struct exi_channel *exi_channel = exi_get_exi_channel(exi_device); + +DBG("enter\n"); + + dev->running = 0; + + mic_exit_proc(dev); + + exi_event_unregister(exi_channel, EXI_EVENT_IRQ); + + if (!IS_ERR(dev->io_thread)) + mic_stop_io_thread(dev); + + mic_exit_snd(dev); +} + +/* + * + */ +static int mic_probe(struct exi_device *exi_device) +{ + struct mic_device *dev; + int retval; + + /* we only care about the microphone */ + if (exi_device->eid.id != MIC_EXI_ID) + return -ENODEV; + + DBG("Microphone inserted\n"); + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->exi_device = exi_device_get(exi_device); + exi_set_drvdata(exi_device, dev); + + retval = mic_init(dev); + if (retval) { + exi_set_drvdata(exi_device, NULL); + exi_device_put(exi_device); + dev->exi_device = NULL; + kfree(dev); + } + + return retval; +} + +/* + * + */ +static void mic_remove(struct exi_device *exi_device) +{ + struct mic_device *dev = exi_get_drvdata(exi_device); + + DBG("Microphone removed\n"); + + if (dev) { + mic_exit(dev); + if (dev->exi_device) + exi_device_put(dev->exi_device); + dev->exi_device = NULL; + kfree(dev); + } + exi_set_drvdata(exi_device, NULL); +} + +static struct exi_device_id mic_eid_table[] = { + [0] = { + .channel = MIC_SLOTA_CHANNEL, + .device = MIC_SLOTA_DEVICE, + .id = MIC_EXI_ID, + }, + [1] = { + .channel = MIC_SLOTB_CHANNEL, + .device = MIC_SLOTB_DEVICE, + .id = MIC_EXI_ID, + }, + {.id = 0} +}; + +static struct exi_driver mic_driver = { + .name = DRV_MODULE_NAME, + .eid_table = mic_eid_table, + .frequency = MIC_SPI_CLK_IDX, + .probe = mic_probe, + .remove = mic_remove, +}; + +static int __init mic_init_module(void) +{ + int retval = 0; + + mic_printk(KERN_INFO, "%s - version %s\n", DRV_DESCRIPTION, + mic_driver_version); + + retval = exi_driver_register(&mic_driver); + + return retval; +} + +static void __exit mic_exit_module(void) +{ + exi_driver_unregister(&mic_driver); +} + +module_init(mic_init_module); +module_exit(mic_exit_module); +