From: Tony Lindgren Date: Tue, 7 Apr 2009 23:22:17 +0000 (-0700) Subject: Extra omap code in linux-omap tree X-Git-Url: http://www.pilppa.org/gitweb/gitweb.cgi?a=commitdiff_plain;h=b934c987ee1764eb09b8843a3ee00eabc24bd52c;hp=14fc69723d3442ef46f8f82b3f481e82f06a346d;p=linux-2.6-omap-h63xx.git Extra omap code in linux-omap tree This patch contains all the uncategorized patches not in mainline. These patches will be reworked for mainline or removed. Signed-off-by: Tony Lindgren --- diff --git a/Documentation/arm/OMAP/README b/Documentation/arm/OMAP/README new file mode 100644 index 00000000000..038d86370e6 --- /dev/null +++ b/Documentation/arm/OMAP/README @@ -0,0 +1,376 @@ + + README for ARM based OMAP processor from TI + =========================================== + +This is the README for Linux 2.6 on ARM based TI OMAP processors. + +In the first section it gives some general hints how to start with OMAP Linux. + +When successfully build a OMAP Linux kernel with help of first section and no +bootloader is already on the board, section 2 gives some tips how to use +commercial JTAG tools. + +In March 2004 the Linux Kernel 2.6 for ARM based TI OMAP processors was cleaned. +The goal was to send clean patches to RMK's official ARM tree and to make it +easier to add new OMAP processors or boards to the kernel tree. To keep the +kernel tree clean now, this document describes also some steps how +to add code for a new OMAP processor or OMAP based board to the OMAP Linux 2.6 +kernel tree. This is what the third section of this document is about. + +Section 4 of this README reports some rules to be followed to write +clean code to make it ready for easy inclusion into public OMAP Linux kernel. + +For more information also see TI's 'Linux Community for Texas Instruments OMAP +Processors' web page: + +http://linux.omap.com + +There, various downloads and resources can be found (e.g. documentation how +to build the kernel, how to use u-boot with OMAP Linux, pre-built tool chain +etc.). + +The mailing list for OMAP Linux is hosted there, too: + +http://linux.omap.com/mailman/listinfo + + +1. General hints how to start with OMAP Linux +-------------------------------------------------------------- + +The minimal setup is a arm-linux-gcc cross compiler, make, and some editor. +You will also most likely need a JTAG to flash the bootloader for the first +time. + +The first step is to get a bootloader for your board, u-boot is the +recommended one: + +http://www.denx.de/en/Software/GIT + +Then you need to compile it with the same cross compiler as you would use +for the Linux kernel. Then you need to flash it to the board either via the +serial port, or by using a JTAG. + +Once you have the bootloader running, you can compile the kernel. + +You can get the OMAP sources either from the OMAP GIT tree, or by +applying patches. The OMAP GIT tree has the most up to date sources +and is the recommended one. + +- Using GIT and cloning OMAP GIT tree please follow the README at: + +http://www.muru.com/linux/omap/README_OMAP_GIT + +Hint: If you are sitting behind a firewall and have to use a proxy for +internet access, you can access GIT by http by setting the +http_proxy envirionment variable: + +http_proxy=http://proxy_username:proxy_password@proxy_name:proxy_port/ + +If you use bash shell, then this might look like: + +export http_proxy=http://foo:123@abc.host.com:8080/ + +with: + +foo: Your user name for the proxy +123: Your password for the proxy +abc.host.com: The name of your proxy you use for internet access +8080: The port used on to access the proxy + + +- Using Patches: + +If you don't want to use GIT, then you can do the same thing with patch. + +Download the latest OMAP Linux patch from: + +http://www.muru.com/linux/omap/ + +Get a matching Linux kernel from: + +ftp://ftp.kernel.org/pub/linux/kernel/v2.6/ + +For example, if you download Linux-2.6.4-omap1 from muru.com, then you need +linux-2.6.4 kernel from kernel.org: + +$ wget ftp://ftp.kernel.org/pub/linux/kernel/v2.6/linux-2.6.4.tar.bz2 +$ tar xjf linux-2.6.4.tar.bz2 +$ cd linux-2.6.4 +$ cat ../Linux-2.6.4-omap1 | patch -p1 + +Note: If OMAP patch from muru.com is against a kernel release candidate, +marked by -rcX, then kernel can be found on kernel.org under v2.6/testing/ + +Now, if you have a local kernel tree, either by GIT or by patch, you +should look into arch/arm/configs/ to see which of the various omap_xxx +configurations there you want to use. For example, if you have a OMAP1510 +based Innovator board, you select omap_innovator_1510_defconfig by + +$ make omap_innovator_1510_defconfig + +at top level directory (linux-2.6.4 in the example above). + +Then you can compile the kernel with + +$ make vmlinux + +Or make Image or make zImage or make uImage. + +Once you have the kernel compiled, you can upload it to the board via serial +port or JTAG (see below). + +Then you need a root file system either as initrd or on the flash. + +Once you have the system booting to Linux, you can use pretty much any Linux +applications cross compiled for ARM. + + +2. JTAG usage +-------------------------------------------------------------- + +If the flash of your board is really 'empty' and no bootloader is on the board +(e.g. u-boot) then you need a JTAG connection. With JTAG you can write +a bootloader to board's flash or download OMAP Linux kernel. For OMAP +commercial JTAG tools are available, so you have to pay for it. + +Examples are TI's Code Composer Studio (CCS) or Lauterbach's TRACE32 JTAG. + +- Linux kernel download with CCS + +You can use CCS to directly load an ELF file to your board. For example, use +arch/arm/boot/compressed/vmlinux. zImage isn't suited because it is not an ELF +file. CCS looks for .out files, so copy arch/arm/boot/compressed/vmlinux +to vmlinux.out and load it using CCS. Or use the filter *.* to select +vmlinux directly. Remember to run arm-linux-strip on ELF file first as CCS +get stroppy about unstripped ELF files. + +If you want vmlinux to be linked to run at a specific address, you can use +the CONFIG_ZBOOT options in the kernel build. But first try without +CONFIG_ZBOOT as the compressed image should be able to run from address +zero (if your CCS .gel files map address zero.) + +Otherwise, use something like this: + +CONFIG_ZBOOT_ROM=y +CONFIG_ZBOOT_ROM_TEXT=10408000 +CONFIG_ZBOOT_ROM_BSS=10800000 + +Also note that CCS is pretty useless for debugging Linux as it doesn't +properly handle virtual memory. In other words, once the MMU is +turned on and Linux is using virtual memory, CCS can no longer +properly disassemble, set breakpoints or read memory. + + +- Linux kernel download with Lauterbach TRACE32 + +To be done. + + +3. How to add new processor or board to OMAP Linux kernel tree +-------------------------------------------------------------- + +It is assumed that the OMAP processor to be added is based on an already +supported ARM core (e.g. ARM925 or ARM926). How to add support for new ARM +processor core that is not supported by ARM Linux is not scope of this document. + +1. If a new OMAP processor should be added, identify the ARM core of this +processor. E.g. at time of writing this document in March 2004 OMAP730 (ARM926 +core), OMAP1510 (ARM925 core) and OMAP1610 (ARM926 core) are supported. + +For a new board or device, identify the OMAP processor on the board. E.g. at +time of writing this document in March 2004 four boards are supported: +Innovator1510 (OMAP1510 processor), Innovator1610 (OMAP1610 processor), +Perseus2 (OMAP730 processor) and H2 (OMAP1610 processor). + +Please refer http://www.muru.com/linux/omap/ to get latest information on the +list of boards supported. + +/* Discussion needed: How to handle the tons of compatible processors? +E.g. what to do if OMAP16xx is mainly identical with OMAP16yy? */ + +2. Start with arch/arm/mach-omap[1/2]/Kconfig and add a new processor or board +option. + +To add a new processor add a new config option to the "OMAP Core Type" choice. +See examples for the syntax. The config option has to be called "ARCH_OMAPxxxx" +where xxxx is the number of OMAP processor. Don't forget to select a existing +clock frequency or to add a new one in "OMAP Feature Selections" section for +your new processor. + +To add a new board or device, add a new config option to the "OMAP Board Type" +choice. See examples for the syntax. The config option for boards has to be +called "MACH_OMAP_yyyy" where yyyy is the board name. Don't forget to add a +short help. + +Note: Kernel 2.6 Kconfig system will automatically expand the configuration +names with a leading "CONFIG_". So "ARCH_OMAPxxxx" will be expanded to +"CONFIG_ARCH_OMAPxxxx" and "MACH_OMAP_yyy" will expand to +"CONFIG_MACH_OMAP_yyyy". In code this can then be used by macros like +"#ifdef CONFIG_ARCH_OMAPxxxx" and "#ifdef CONFIG_MACH_OMAP_yyyy". + +Note: How to handle boards which are compatible or extensions of other boards? +See MACH_OMAP_H2 for example. The H2 depends on MACH_OMAP_INNOVATOR and expands +it. This is done by an additional select MACH_OMAP_INNOVATOR in MACH_OMAP_H2 +configuration option. With this the whole MACH_OMAP_INNOVATOR configuration is +selected and an additional symbol CONFIG_MACH_OMAP_H2 is available to +distinguish between INNOVATOR and H2 where necessary. + +3a. Only for new processors: Add the ARCH_OMAPxxxx to the correct ARM core in +arch/arm/mm/Kconfig. E.g. ARCH_OMAP730 in CPU_ARM926T configuration. + +3b. Only for new boards: Register the board within ARM Linux machine +registration system from RMK. For the CONFIG_ section use the same name like +in arch/arm/mach-omap[1/2]/Kconfig. E.g. MACH_OMAP_yyyy. For MACH_TYPE_ section use +OMAP_yyyy where yyyy is the board name like above. + +Note: The elements of RMKs machine registration are used in +arch/arm/tools/mach-types. While kernel compilation +include/asm-arm/mach-types.h is generated automagically from this file. The +content of mach-types.h then is used for machine identification by kernel +bootcode and can be used for board identification. + +Note: The ARM Linux machine registration system from RMK can be found under: + +www.arm.linux.org.uk/developer/machines/ + +Note: Only OMAP based boards should be registered to RMKs registration +system. Not processors. + +4. Add a processor or board specific header file in include/asm-arm/arch-omap/. +Use board-yyyy.h with yyyy board name or omapxxxx.h with xxxx processor number. + +5. Add a processor or board specific section into include/asm-arm/arch-omap/ +hardware.h. Use examples for syntax and use CONFIG_ names as defined in +arch/arm/mach-omap[1/2]/Kconfig. + +6. Add processor or board specific macros to board-yyyy.h or omapxxxx.h. The +macros to these specific files have to be named OMAPxxxx_ with xxxx processor +number to make them unique. + +7a. Only for new boards: Add a file board-yyyy.c with yyyy board name to +arch/arm/mach-omap[1/2]/. Put board specific initialization code and resource +description into this file. The first element of MACHINE_START must be equal to +MACH_TYPE_ section of machine registration (see arch/arm/tools/mach-types after +machine registration at RMKs registration system). + +Put only code into this file that is board specific and not common. See other +board files for examples. + +7b. Only for new processors: Add processor specific IO description and +iotable_init() to arch/arm/mach-omap[1/2]/io.c. See examples for the syntax. + +If you have introduced new clock definition in 2., add support for this new +clock in include/asm-arm/arch-omap/clocks.h and arch/arm/mach-omap[1/2]/clocks.c. + +8. Only for new boards: Add "obj-$(CONFIG_MACH_OMAP_yyyy) += board-yyyy.o" with +yyyy board name to arch/arm/mach-omap[1/2]/Makefile. This is used to compile your new +board specific initialization code from 7a. + +9. Check if other of the existing files have to be adjusted for the new +processor or board. Things to check: + +- Pin multiplexing +- GPIO configuration +- Power Management +- Clocking +- Interrupt controller and interrupt configuration +- Additional board specific things (e.g. FPGAs) + +If other existing files or device drivers have to be changed, use the following +mechanism for processor specific things: + +#ifdef CONFIG_ARCH_OMAPxxxx + if (cpu_is_omapxxxx()) { + /* Do the OMAPxxxx processor specific magic */ + } +#endif + +Note: cpu_is_omapxxxx() macro is defined in include/asm-arm/arch-omap/hardware.h +and uses OMAP_ID_REG for runtime processor identifcation. + +For board differentiation use board macro from include/asm-arm/mach-types.h: + +#ifdef CONFIG_MACH_OMAP_yyyy + if (machine_is_omap_yyyy()) { + /* Do the board specific magic */ + } +#endif + +Note: If technically possible and already implemented the OMAP Linux kernel +has support for a "one binary fits all" machanism. That is, the goal is to be +able to enable support for multiple OMAP processors and/or boards in Kconfig +system. Then it is decided by bootparameters and at runtime on which processor +and/or board the kernel is actually running on. With this machanism it is +possible to use the same kernel binary on different OMAP processors or boards +without recompiling. This is achived by the cpu_is_omapxxxx() and +machine_is_omap_yyyy() macros. + +On the other hand, for memory limited embedded systems it should be possible +to compile the kernel with support for only one processor/board combination. +For this a kernel binary is necessary which isn't bloated with code for all +other (unused) processors and boards. This is achived by using the preprocessor +CONFIG_ARCH_OMAPxxxx and CONFIG_MACH_OMAP_yyyy macros around the runtime +cpu_is_omapxxxx() and machine_is_omap_yyyy() selection. + +At the moment, the price for this flexibility is a increased number of #ifdef's +throughout the code. + +10. Configure the kernel by make menuconfig or make xconfig and select the new +processor or board. + +11. Compile the kernel by an appropriate cross compilation toolchain. Make this +until the code compiles error and warning free. The kernel should also be +compiled with the various debug checking thingies enabled (e.g. +CONFIG_DEBUG_SPINLOCK, CONFIG_DEBUG_PAGEALLOC etc.). + +/* ToDo: Anything to say about toolchain? */ + +12. Download the kernel image to the board and test it until it works ;-) + +It's not in the scope of this document how to do this (use a appropriate +bootloader or JTAG download). + +Note: The kernel initialization code expects some special values in the +registers R0, R1 and R2 of the ARM processor. These registers have to be +written by bootloader or debugger before starting the kernel. R0 has to be +zero, R1 has to contain the machine number from machine registration in +arch/arm/tools/mach-types. R2 points to the physical address of tagged list +in system RAM. For more information see Documentation/arm/Booting. + +While testing a new processor or board configuration, it is recommended to +enable low level debugging. This uses low level output functions to print kernel +messages on serial line before console is working. Enable it by + +Kernel hacking -> Kernel debugging -> Kernel low-level debugging functions + +in kernel configuration system. + +13. Check that no other processors or boards are broken by the new code. A first +test is to successful compile the other omap_xxx configurations from +arch/arm/configs/. Do this by e.g. + +cd linux +make omap_innovator_1510_defconfig +Compile the kernel + +Even better: Enable support for several processors and boards in Kconfig +system and compile kernel successfully. + +14. Only for new boards: Add a new default board configuration to +arch/arm/configs. Use omap_yyyy_xxxx_defconfig with yyyy boardname and xxxx +processornumber as filename. + +15. If the new code works, compiles without warnings and seems to break no other +configurations, post a patch to linux-omap-open-source@list.ti.com. + +With sending a patch to the community, it is reviewed, can be used and tested by +other users. It then can be included into the public OMAP kernel tree. + +16. Then adapt device drivers or write additional drivers for non-existing +processor peripherals or board devices. Improve and maintain the code for your +new processor or board. + +------------------------------------------------------------------ +Last modified 15. March 2006 +The OMAP Linux Kernel Team +Dirk Behme diff --git a/Documentation/arm/OMAP/gpio b/Documentation/arm/OMAP/gpio new file mode 100644 index 00000000000..8a011ad41c3 --- /dev/null +++ b/Documentation/arm/OMAP/gpio @@ -0,0 +1,285 @@ + + OMAP GPIO API's HowTo + ===================== + +This document is a short summary how to use OMAP Linux GPIO API. It is +mainly focussed on OMAP5912 OSK, but should fit with extensions (more +or less GPIOs) to other OMAP processors as well. + +If anything is missing, is wrong, needs extension or update, please send +update to Linux-omap-open-source@linux.omap.com. + + ************************************************************* + + NOTICE: these OMAP-specific interfaces are deprecated/obsolete. + + See Documentation/gpio.txt for information on the standard + cross-platform GPIO interface. All new code should use those + calls instead of the ones described here. + + The only exception to that policy is the omap_cfg_reg() call, + which isn't a GPIO-specific interface; it configures how chip + functions are multiplexed to pins, with GPIO being only one + of those functions. + + ************************************************************* + +I. GPIO Modules/Banks +--------------------- + +OMAP5912 OSK has 64 GPIOs (general purpose IO pins). These are organized +in four modules (banks) with 16 pins each. OMAP GPIO API doesn't distinguish +between modules and numbers the pins from 0 - 63: + +A) GPIO MODULE/BANK 0 - PIN 0-15 +B) GPIO MODULE/BANK 1 - PIN 16-31 +C) GPIO MODULE/BANK 2 - PIN 32-47 +D) GPIO MODULE/BANK 3 - PIN 48-63 + +See + +http://www-s.ti.com/sc/psheets/spru767a/spru767a.pdf + +for more details. + +II. GPIO API's +-------------- + +A) Include + +#include + +B) omap_cfg_reg(xxxx); + +Description: Configure pin mux. + +Parameter: Pin to be configured for GPIO. + +Note: This function may only be necessary for some GPIO pins. Because OMAP + chip itself has less real hardware pins than necessary to use all + its functionality at the same time, some pins share different + functions (called pin multiplexing, short pin mux). E.g. one pin may + be used for serial interface *or* GPIO. Check if this is the case for + the GPIO you want to use and if you have to configure the pin mux. + +C) omap_request_gpio(int gpio) + +Description: Request GPIO to be used. + +Parameter: int gpio - GPIO PIN (Pin 0-63) + +Note: Using this function, you dont have to worry about banks/modules where + the gpio pin is. + +D) omap_set_gpio_direction(int gpio, int is_input) + +Description: This function is responsible for setting the gpio pin direction + (input or output). + +Parameter: int gpio - GPIO PIN (Pin 0-63) + int is_input - pin direction (0 = output, 1 = input) + +E) omap_set_gpio_dataout(int gpio, int enable) + +Description: This function is responsible for writing to a pin. + +Parameter: int gpio - GPIO PIN (Pin 0-63) + int enable - pin value (0 or 1) + +F) omap_get_gpio_datain(int gpio) + +Description: This function is responsible for reading pin values. + +Parameter: int gpio - GPIO PIN (Pin 0-63) + +G) omap_free_gpio(int gpio) + +Description: This function is responsible for freeing the pin used. + +Parameter: int gpio - GPIO PIN (Pin 0-63) + +H) OMAP_GPIO_IRQ(int gpio) + +Description: Returns the Interrupt number for the specified gpio pin. + +Parameter: int gpio - GPIO PIN (Pin 0-63) + +I) set_irq_type(unsigned int irq, unsigned int type) + +Description: This function is responsible for setting the type of interrupt + (RISING or FALLING). + +Parameter: unsigned int irq - The interrupt number for the gpio pin. + unsigned int type - (IRQT_RISING = rising, IRQT_FALLING= falling) + + +III. Example +------------ + +1) Writing to gpio pin#3 a value 1 and reading the value of gpio pin#3. + +#include + +int ret; /* Return value */ + +omap_request_gpio(3); /* Request for gpio pin */ +omap_set_gpio_direction(3,0); +omap_set_set_dataout(3,1); /* Writing a 1 to gpio pin # 3: */ +ret = omap_get_datain(3); /* Reading the value of pin # 3 */ +printk("value of pin # 3 = %d\n",ret); +omap_free_gpio(3); /* Freeing gpio pin # 3 */ + +2) Interrupt input by gpio pin#3 + +#include + +omap_request_gpio(3); /* Request for gpio pin */ +omap_set_gpio_direction(3,0); +set_irq_type(OMAP_GPIO_IRQ(3),IRQT_RISING); /* Setting up pin for interrupt */ +request_irq(OMAP_GPIO_IRQ(3), (void *)&my_int_handler, SA_SHIRQ,....); + +... /* Do stuff, handle interrupts in my_int_handler */ + +free_irq(OMAP_GPIO_IRQ(3),&id); /* Freeing interrupt and gpio pin */ +omap_free_gpio(3); + +------------------------------------------------------------------ +Last modified 14. August 2006 +The OMAP Linux Kernel Team +Arnold +Dirk Behme + + OMAP GPIO API's HowTo + ===================== + +This document is a short summary how to use OMAP Linux GPIO API. It is +mainly focussed on OMAP5912 OSK, but should fit with extensions (more +or less GPIOs) to other OMAP processors as well. + +If anything is missing, is wrong, needs extension or update, please send +update to Linux-omap-open-source@linux.omap.com. + +I. GPIO Modules/Banks +--------------------- + +OMAP5912 OSK has 64 GPIOs (general purpose IO pins). These are organized +in four modules (banks) with 16 pins each. OMAP GPIO API doesn't distinguish +between modules and numbers the pins from 0 - 63: + +A) GPIO MODULE/BANK 0 - PIN 0-15 +B) GPIO MODULE/BANK 1 - PIN 16-31 +C) GPIO MODULE/BANK 2 - PIN 32-47 +D) GPIO MODULE/BANK 3 - PIN 48-63 + +See + +http://www-s.ti.com/sc/psheets/spru767a/spru767a.pdf + +for more details. + +II. GPIO API's +-------------- + +A) Include + +#include + +B) omap_cfg_reg(xxxx); + +Description: Configure pin mux. + +Parameter: Pin to be configured for GPIO. + +Note: This function may only be necessary for some GPIO pins. Because OMAP + chip itself has less real hardware pins than necessary to use all + its functionality at the same time, some pins share different + functions (called pin multiplexing, short pin mux). E.g. one pin may + be used for serial interface *or* GPIO. Check if this is the case for + the GPIO you want to use and if you have to configure the pin mux. + +C) omap_request_gpio(int gpio) + +Description: Request GPIO to be used. + +Parameter: int gpio - GPIO PIN (Pin 0-63) + +Note: Using this function, you dont have to worry about banks/modules where + the gpio pin is. + +D) omap_set_gpio_direction(int gpio, int is_input) + +Description: This function is responsible for setting the gpio pin direction + (input or output). + +Parameter: int gpio - GPIO PIN (Pin 0-63) + int is_input - pin direction (0 = output, 1 = input) + +E) omap_set_gpio_dataout(int gpio, int enable) + +Description: This function is responsible for writing to a pin. + +Parameter: int gpio - GPIO PIN (Pin 0-63) + int enable - pin value (0 or 1) + +F) omap_get_gpio_datain(int gpio) + +Description: This function is responsible for reading pin values. + +Parameter: int gpio - GPIO PIN (Pin 0-63) + +G) omap_free_gpio(int gpio) + +Description: This function is responsible for freeing the pin used. + +Parameter: int gpio - GPIO PIN (Pin 0-63) + +H) OMAP_GPIO_IRQ(int gpio) + +Description: Returns the Interrupt number for the specified gpio pin. + +Parameter: int gpio - GPIO PIN (Pin 0-63) + +I) set_irq_type(unsigned int irq, unsigned int type) + +Description: This function is responsible for setting the type of interrupt + (RISING or FALLING). + +Parameter: unsigned int irq - The interrupt number for the gpio pin. + unsigned int type - (IRQT_RISING = rising, IRQT_FALLING= falling) + + +III. Example +------------ + +1) Writing to gpio pin#3 a value 1 and reading the value of gpio pin#3. + +#include + +int ret; /* Return value */ + +omap_request_gpio(3); /* Request for gpio pin */ +omap_set_gpio_direction(3,0); +omap_set_set_dataout(3,1); /* Writing a 1 to gpio pin # 3: */ +ret = omap_get_datain(3); /* Reading the value of pin # 3 */ +printk("value of pin # 3 = %d\n",ret); +omap_free_gpio(3); /* Freeing gpio pin # 3 */ + +2) Interrupt input by gpio pin#3 + +#include + +omap_request_gpio(3); /* Request for gpio pin */ +omap_set_gpio_direction(3,0); +set_irq_type(OMAP_GPIO_IRQ(3),IRQT_RISING); /* Setting up pin for interrupt */ +request_irq(OMAP_GPIO_IRQ(3), (void *)&my_int_handler, SA_SHIRQ,....); + +... /* Do stuff, handle interrupts in my_int_handler */ + +free_irq(OMAP_GPIO_IRQ(3),&id); /* Freeing interrupt and gpio pin */ +omap_free_gpio(3); + +------------------------------------------------------------------ +Last modified 14. August 2006 +The OMAP Linux Kernel Team +Arnold +Dirk Behme diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index e02b893fb90..fd5663bf4b3 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -1378,6 +1378,10 @@ source "drivers/uio/Kconfig" source "drivers/staging/Kconfig" +if ARCH_OMAP +source "drivers/cbus/Kconfig" +endif + endmenu source "fs/Kconfig" diff --git a/arch/arm/boot/compressed/Makefile b/arch/arm/boot/compressed/Makefile index fbe5eef1f6c..98d589a59b5 100644 --- a/arch/arm/boot/compressed/Makefile +++ b/arch/arm/boot/compressed/Makefile @@ -40,6 +40,10 @@ ifeq ($(CONFIG_PXA_SHARPSL),y) OBJS += head-sharpsl.o endif +ifeq ($(CONFIG_MACH_OMAP_PERSEUS2),y) +OBJS += head-omap.o +endif + ifeq ($(CONFIG_CPU_BIG_ENDIAN),y) ifeq ($(CONFIG_CPU_CP15),y) OBJS += big-endian.o diff --git a/arch/arm/boot/compressed/head-omap.S b/arch/arm/boot/compressed/head-omap.S new file mode 100644 index 00000000000..ba3ecca4d61 --- /dev/null +++ b/arch/arm/boot/compressed/head-omap.S @@ -0,0 +1,18 @@ +/* + * linux/arch/arm/boot/compressed/head-omap.S + * + * OMAP specific tweaks. This is merged into head.S by the linker. + * + */ + +#include +#include + + .section ".start", "ax" + +__OMAP_start: +#ifdef CONFIG_MACH_OMAP_PERSEUS2 + /* support for booting without u-boot */ + mov r7, #(MACH_TYPE_OMAP_PERSEUS2 & ~0xf) + orr r7, r7, #(MACH_TYPE_OMAP_PERSEUS2 & 0xf) +#endif diff --git a/arch/arm/include/asm/setup.h b/arch/arm/include/asm/setup.h index ee1304f22f9..2e0b9e04f12 100644 --- a/arch/arm/include/asm/setup.h +++ b/arch/arm/include/asm/setup.h @@ -136,6 +136,13 @@ struct tag_acorn { __u8 adfsdrives; }; +/* TI OMAP specific information */ +#define ATAG_BOARD 0x414f4d50 + +struct tag_omap { + u8 data[0]; +}; + /* footbridge memory clock, see arch/arm/mach-footbridge/arch.c */ #define ATAG_MEMCLK 0x41000402 @@ -161,6 +168,11 @@ struct tag { */ struct tag_acorn acorn; + /* + * OMAP specific + */ + struct tag_omap omap; + /* * DC21285 specific */ diff --git a/arch/arm/kernel/head.S b/arch/arm/kernel/head.S index 21e17dc94cb..1bc918cbdb1 100644 --- a/arch/arm/kernel/head.S +++ b/arch/arm/kernel/head.S @@ -282,7 +282,7 @@ __create_page_tables: .endif str r6, [r0] -#ifdef CONFIG_DEBUG_LL +#if defined(CONFIG_DEBUG_LL) || defined(CONFIG_DEBUG_SPINLOCK) ldr r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags /* * Map in IO space for serial debugging. diff --git a/arch/arm/mach-omap1/Kconfig b/arch/arm/mach-omap1/Kconfig index cd8de89c5fa..55ecc01ea20 100644 --- a/arch/arm/mach-omap1/Kconfig +++ b/arch/arm/mach-omap1/Kconfig @@ -46,7 +46,6 @@ config MACH_OMAP_H2 config MACH_OMAP_H3 bool "TI H3 Support" depends on ARCH_OMAP1 && ARCH_OMAP16XX -# select GPIOEXPANDER_OMAP help TI OMAP 1710 H3 board support. Say Y here if you have such a board. diff --git a/arch/arm/mach-omap1/Makefile b/arch/arm/mach-omap1/Makefile index 1bda8f5d754..6867cd3ad0b 100644 --- a/arch/arm/mach-omap1/Makefile +++ b/arch/arm/mach-omap1/Makefile @@ -13,6 +13,10 @@ obj-$(CONFIG_OMAP_32K_TIMER) += timer32k.o # Power Management obj-$(CONFIG_PM) += pm.o sleep.o +# DSP +obj-$(CONFIG_OMAP_MBOX_FWK) += mailbox_mach.o +mailbox_mach-objs := mailbox.o + led-y := leds.o # Specific board support diff --git a/arch/arm/mach-omap1/board-h2-mmc.c b/arch/arm/mach-omap1/board-h2-mmc.c index 44d4a966bed..46098f54682 100644 --- a/arch/arm/mach-omap1/board-h2-mmc.c +++ b/arch/arm/mach-omap1/board-h2-mmc.c @@ -26,19 +26,13 @@ static int mmc_set_power(struct device *dev, int slot, int power_on, int vdd) { - if (power_on) - gpio_direction_output(H2_TPS_GPIO_MMC_PWR_EN, 1); - else - gpio_direction_output(H2_TPS_GPIO_MMC_PWR_EN, 0); - + gpio_set_value(H2_TPS_GPIO_MMC_PWR_EN, power_on); return 0; } static int mmc_late_init(struct device *dev) { - int ret; - - ret = gpio_request(H2_TPS_GPIO_MMC_PWR_EN, "MMC power"); + int ret = gpio_request(H2_TPS_GPIO_MMC_PWR_EN, "MMC power"); if (ret < 0) return ret; @@ -47,7 +41,7 @@ static int mmc_late_init(struct device *dev) return ret; } -static void mmc_shutdown(struct device *dev) +static void mmc_cleanup(struct device *dev) { gpio_free(H2_TPS_GPIO_MMC_PWR_EN); } @@ -60,7 +54,7 @@ static void mmc_shutdown(struct device *dev) static struct omap_mmc_platform_data mmc1_data = { .nr_slots = 1, .init = mmc_late_init, - .shutdown = mmc_shutdown, + .cleanup = mmc_cleanup, .dma_mask = 0xffffffff, .slots[0] = { .set_power = mmc_set_power, diff --git a/arch/arm/mach-omap1/board-h3-mmc.c b/arch/arm/mach-omap1/board-h3-mmc.c index 0d8a3c195e2..5e8877ce35e 100644 --- a/arch/arm/mach-omap1/board-h3-mmc.c +++ b/arch/arm/mach-omap1/board-h3-mmc.c @@ -26,11 +26,7 @@ static int mmc_set_power(struct device *dev, int slot, int power_on, int vdd) { - if (power_on) - gpio_direction_output(H3_TPS_GPIO_MMC_PWR_EN, 1); - else - gpio_direction_output(H3_TPS_GPIO_MMC_PWR_EN, 0); - + gpio_set_value(H3_TPS_GPIO_MMC_PWR_EN, power_on); return 0; } diff --git a/arch/arm/mach-omap1/board-h3.c b/arch/arm/mach-omap1/board-h3.c index 4695965114c..f597968733b 100644 --- a/arch/arm/mach-omap1/board-h3.c +++ b/arch/arm/mach-omap1/board-h3.c @@ -39,12 +39,10 @@ #include #include -#include #include #include #include #include -#include #include #include #include @@ -276,104 +274,6 @@ static struct platform_device h3_kp_device = { .resource = h3_kp_resources, }; - -/* Select between the IrDA and aGPS module - */ -static int h3_select_irda(struct device *dev, int state) -{ - unsigned char expa; - int err = 0; - - if ((err = read_gpio_expa(&expa, 0x26))) { - printk(KERN_ERR "Error reading from I/O EXPANDER \n"); - return err; - } - - /* 'P6' enable/disable IRDA_TX and IRDA_RX */ - if (state & IR_SEL) { /* IrDA */ - if ((err = write_gpio_expa(expa | 0x40, 0x26))) { - printk(KERN_ERR "Error writing to I/O EXPANDER \n"); - return err; - } - } else { - if ((err = write_gpio_expa(expa & ~0x40, 0x26))) { - printk(KERN_ERR "Error writing to I/O EXPANDER \n"); - return err; - } - } - return err; -} - -static void set_trans_mode(struct work_struct *work) -{ - struct omap_irda_config *irda_config = - container_of(work, struct omap_irda_config, gpio_expa.work); - int mode = irda_config->mode; - unsigned char expa; - int err = 0; - - if ((err = read_gpio_expa(&expa, 0x27)) != 0) { - printk(KERN_ERR "Error reading from I/O expander\n"); - } - - expa &= ~0x03; - - if (mode & IR_SIRMODE) { - expa |= 0x01; - } else { /* MIR/FIR */ - expa |= 0x03; - } - - if ((err = write_gpio_expa(expa, 0x27)) != 0) { - printk(KERN_ERR "Error writing to I/O expander\n"); - } -} - -static int h3_transceiver_mode(struct device *dev, int mode) -{ - struct omap_irda_config *irda_config = dev->platform_data; - - irda_config->mode = mode; - cancel_delayed_work(&irda_config->gpio_expa); - PREPARE_DELAYED_WORK(&irda_config->gpio_expa, set_trans_mode); - schedule_delayed_work(&irda_config->gpio_expa, 0); - - return 0; -} - -static struct omap_irda_config h3_irda_data = { - .transceiver_cap = IR_SIRMODE | IR_MIRMODE | IR_FIRMODE, - .transceiver_mode = h3_transceiver_mode, - .select_irda = h3_select_irda, - .rx_channel = OMAP_DMA_UART3_RX, - .tx_channel = OMAP_DMA_UART3_TX, - .dest_start = UART3_THR, - .src_start = UART3_RHR, - .tx_trigger = 0, - .rx_trigger = 0, -}; - -static struct resource h3_irda_resources[] = { - [0] = { - .start = INT_UART3, - .end = INT_UART3, - .flags = IORESOURCE_IRQ, - }, -}; - -static u64 irda_dmamask = 0xffffffff; - -static struct platform_device h3_irda_device = { - .name = "omapirda", - .id = 0, - .dev = { - .platform_data = &h3_irda_data, - .dma_mask = &irda_dmamask, - }, - .num_resources = ARRAY_SIZE(h3_irda_resources), - .resource = h3_irda_resources, -}; - static struct platform_device h3_lcd_device = { .name = "lcd_h3", .id = -1, @@ -395,7 +295,6 @@ static struct platform_device *devices[] __initdata = { &nand_device, &smc91x_device, &intlat_device, - &h3_irda_device, &h3_kp_device, &h3_lcd_device, }; diff --git a/arch/arm/mach-omap1/board-nokia770.c b/arch/arm/mach-omap1/board-nokia770.c index 7bc7a3cb9c5..8780ca66639 100644 --- a/arch/arm/mach-omap1/board-nokia770.c +++ b/arch/arm/mach-omap1/board-nokia770.c @@ -35,6 +35,7 @@ #include #include #include +#include #define ADS7846_PENDOWN_GPIO 15 @@ -181,11 +182,7 @@ static struct omap_usb_config nokia770_usb_config __initdata = { static int nokia770_mmc_set_power(struct device *dev, int slot, int power_on, int vdd) { - if (power_on) - gpio_set_value(NOKIA770_GPIO_MMC_POWER, 1); - else - gpio_set_value(NOKIA770_GPIO_MMC_POWER, 0); - + gpio_set_value(NOKIA770_GPIO_MMC_POWER, power_on); return 0; } diff --git a/arch/arm/mach-omap2/Kconfig b/arch/arm/mach-omap2/Kconfig index 64ab386a65c..3a2214cb37a 100644 --- a/arch/arm/mach-omap2/Kconfig +++ b/arch/arm/mach-omap2/Kconfig @@ -10,10 +10,13 @@ config ARCH_OMAP2420 depends on ARCH_OMAP24XX select OMAP_DM_TIMER select ARCH_OMAP_OTG + select CPU_V6 config ARCH_OMAP2430 bool "OMAP2430 support" depends on ARCH_OMAP24XX + select ARCH_OMAP_OTG + select CPU_V6 config ARCH_OMAP34XX bool "OMAP34xx Based System" @@ -23,6 +26,7 @@ config ARCH_OMAP3430 bool "OMAP3430 support" depends on ARCH_OMAP3 && ARCH_OMAP34XX select ARCH_OMAP_OTG + select CPU_V7 comment "OMAP Board Type" depends on ARCH_OMAP2 || ARCH_OMAP3 @@ -31,25 +35,98 @@ config MACH_OMAP_GENERIC bool "Generic OMAP board" depends on ARCH_OMAP2 && ARCH_OMAP24XX +config MACH_NOKIA_N800 + bool "Nokia N800" + depends on ARCH_OMAP2420 + select VIDEO_TCM825X if VIDEO_OMAP2 && VIDEO_HELPER_CHIPS_AUTO + select CBUS if VIDEO_TCM825X + select CBUS_RETU if VIDEO_TCM825X + select MENELAUS if VIDEO_TCM825X + select OMAP_GPIO_SWITCH + +config MACH_NOKIA_N810 + bool "Nokia N810" + depends on MACH_NOKIA_N800 + +config MACH_NOKIA_N810_WIMAX + bool "Nokia N810 WiMAX" + depends on MACH_NOKIA_N800 + select MACH_NOKIA_N810 + +config MACH_NOKIA_RX51 + bool "Nokia RX-51 board" + depends on ARCH_OMAP3 && ARCH_OMAP34XX + +config MACH_OMAP2_TUSB6010 + bool + depends on ARCH_OMAP2 && ARCH_OMAP2420 + default y if MACH_NOKIA_N800 + config MACH_OMAP_H4 bool "OMAP 2420 H4 board" - depends on ARCH_OMAP2 && ARCH_OMAP24XX + depends on ARCH_OMAP2 && ARCH_OMAP2420 select OMAP_DEBUG_DEVICES +config MACH_OMAP_H4_TUSB + bool "TUSB 6010 EVM board" + depends on MACH_OMAP_H4 + select MACH_OMAP2_TUSB6010 + help + Set this if you've got a TUSB6010 high speed USB board. + You may need to consult the schematics for your revisions + of the Menelaus and TUSB boards, and make changes to be + sure this is set up properly for your board stack. + + Be sure to select OTG mode operation, not host-only or + peripheral-only. + +config MACH_OMAP_H4_OTG + bool "Use USB OTG connector, not device connector (S1.10)" + depends on MACH_OMAP_H4 + help + Set this if you've set S1.10 (on the mainboard) to use the + Mini-AB (OTG) connector and OTG transceiver with the USB0 + port, instead of the Mini-B ("download") connector with its + non-OTG transceiver. + + Note that the "download" connector can be used to bootstrap + the system from the OMAP mask ROM. Also, since this is a + development platform, you can also force the OTG port into + a non-OTG operational mode. + +config MACH_OMAP2_H4_USB1 + bool "Use USB1 port, not UART2 (S3.3)" + depends on MACH_OMAP_H4 + help + Set this if you've set SW3.3 (on the CPU card) so that the + expansion connectors receive USB1 signals instead of UART2. + config MACH_OMAP_APOLLON bool "OMAP 2420 Apollon board" - depends on ARCH_OMAP2 && ARCH_OMAP24XX + depends on ARCH_OMAP2 && ARCH_OMAP2420 config MACH_OMAP_2430SDP bool "OMAP 2430 SDP board" + depends on ARCH_OMAP2 && ARCH_OMAP2430 + +config MACH_OMAP_LDP + bool "OMAP3 LDP board" + depends on ARCH_OMAP3 && ARCH_OMAP34XX + +config MACH_OMAP2EVM + bool "OMAP 2530 EVM board" depends on ARCH_OMAP2 && ARCH_OMAP24XX -config MACH_OMAP3_BEAGLE - bool "OMAP3 BEAGLE board" +config MACH_OMAP_3430SDP + bool "OMAP 3430 SDP board" depends on ARCH_OMAP3 && ARCH_OMAP34XX -config MACH_OMAP_LDP - bool "OMAP3 LDP board" +config MACH_OMAP3EVM + bool "OMAP 3530 EVM board" + depends on ARCH_OMAP3 && ARCH_OMAP34XX + +config MACH_OMAP3_BEAGLE + bool "OMAP3 BEAGLE board" depends on ARCH_OMAP3 && ARCH_OMAP34XX config MACH_OVERO diff --git a/arch/arm/mach-omap2/Makefile b/arch/arm/mach-omap2/Makefile index c49d9bfa3ab..76acefa0c76 100644 --- a/arch/arm/mach-omap2/Makefile +++ b/arch/arm/mach-omap2/Makefile @@ -28,28 +28,59 @@ endif obj-$(CONFIG_ARCH_OMAP2) += clock24xx.o obj-$(CONFIG_ARCH_OMAP3) += clock34xx.o +# DSP +obj-$(CONFIG_OMAP_MBOX_FWK) += mailbox_mach.o +mailbox_mach-objs := mailbox.o + # Specific board support obj-$(CONFIG_MACH_OMAP_GENERIC) += board-generic.o obj-$(CONFIG_MACH_OMAP_H4) += board-h4.o obj-$(CONFIG_MACH_OMAP_2430SDP) += board-2430sdp.o \ + board-2430sdp-flash.o \ mmc-twl4030.o -obj-$(CONFIG_MACH_OMAP_APOLLON) += board-apollon.o -obj-$(CONFIG_MACH_OMAP3_BEAGLE) += board-omap3beagle.o \ - mmc-twl4030.o -obj-$(CONFIG_MACH_OMAP_LDP) += board-ldp.o \ - mmc-twl4030.o -obj-$(CONFIG_MACH_OVERO) += board-overo.o \ - mmc-twl4030.o -obj-$(CONFIG_MACH_OMAP3_PANDORA) += board-omap3pandora.o \ +obj-$(CONFIG_MACH_OMAP2EVM) += board-omap2evm.o \ mmc-twl4030.o obj-$(CONFIG_MACH_OMAP_3430SDP) += board-3430sdp.o \ + mmc-twl4030.o \ + board-3430sdp-flash.o +obj-$(CONFIG_MACH_OMAP3EVM) += board-omap3evm.o \ + mmc-twl4030.o \ + board-omap3evm-flash.o \ + twl4030-generic-scripts.o +obj-$(CONFIG_MACH_OMAP3_BEAGLE) += board-omap3beagle.o \ + mmc-twl4030.o \ + twl4030-generic-scripts.o +obj-$(CONFIG_MACH_OMAP_LDP) += board-ldp.o \ mmc-twl4030.o - +obj-$(CONFIG_MACH_OMAP_APOLLON) += board-apollon.o \ + board-apollon-mmc.o \ + board-apollon-keys.o +obj-$(CONFIG_MACH_NOKIA_N800) += board-n800.o board-n800-flash.o \ + board-n800-mmc.o board-n800-bt.o \ + board-n800-usb.o \ + board-n800-dsp.o \ + board-n800-camera.o +obj-$(CONFIG_MACH_NOKIA_N810) += board-n810.o obj-$(CONFIG_MACH_NOKIA_RX51) += board-rx51.o \ + board-n800-flash.o \ + board-rx51-flash.o \ + board-rx51-sdram.o \ + board-rx51-video.o \ board-rx51-peripherals.o \ mmc-twl4030.o +obj-$(CONFIG_MACH_OVERO) += board-overo.o \ + mmc-twl4030.o \ + twl4030-generic-scripts.o +obj-$(CONFIG_MACH_OMAP3_PANDORA) += board-omap3pandora.o \ + mmc-twl4030.o + # Platform specific device init code -ifeq ($(CONFIG_USB_MUSB_SOC),y) -obj-y += usb-musb.o +obj-$(CONFIG_USB_MUSB_SOC) += usb-musb.o +obj-$(CONFIG_MACH_OMAP2_TUSB6010) += usb-tusb6010.o + +ifneq ($(CONFIG_USB_EHCI_HCD),) + obj-y += usb-ehci.o endif + + diff --git a/arch/arm/mach-omap2/board-2430sdp-flash.c b/arch/arm/mach-omap2/board-2430sdp-flash.c new file mode 100644 index 00000000000..57d3b553715 --- /dev/null +++ b/arch/arm/mach-omap2/board-2430sdp-flash.c @@ -0,0 +1,185 @@ +/* + * linux/arch/arm/mach-omap2/board-2430sdp-flash.c + * + * Copyright (C) 2007 MontaVista Software, Inc. + * Author: Kevin Hilman + * + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define ONENAND_MAP 0x20000000 +#define GPMC_OFF_CONFIG1_0 0x60 + +enum fstype { + NAND = 0, + NOR, + ONENAND, + UNKNOWN = -1 +}; + +static enum fstype flash_type = NAND; + +static struct mtd_partition nand_partitions[] = { + { + .name = "X-Loader", + .offset = 0, + .size = 4*(64*2048), /* 0-3 blks reserved. + Mandated by ROM code */ + .mask_flags = MTD_WRITEABLE /* force read-only */ + }, + { + .name = "U-Boot", + .offset = MTDPART_OFS_APPEND, + .size = 4*(64*2048), + .mask_flags = MTD_WRITEABLE /* force read-only */ + }, + { + .name = "U-Boot Environment", + .offset = MTDPART_OFS_APPEND, + .size = 2*(64*2048), + }, + { + .name = "Kernel", + .offset = MTDPART_OFS_APPEND, + .size = 32*(64*2048), /* 4*1M */ + }, + { + .name = "File System", + .offset = MTDPART_OFS_APPEND, + .size = MTDPART_SIZ_FULL, + }, +}; +static struct omap_nand_platform_data sdp_nand_data = { + .parts = nand_partitions, + .nr_parts = ARRAY_SIZE(nand_partitions), + .dma_channel = -1, /* disable DMA in OMAP OneNAND driver */ +}; + +static struct platform_device sdp_nand_device = { + .name = "omap2-nand", + .id = -1, + .dev = { + .platform_data = &sdp_nand_data, + }, +}; + +static struct mtd_partition onenand_partitions[] = { + { + .name = "(OneNAND)X-Loader", + .offset = 0, + .size = 4*(64*2048), /* 0-3 blks reserved. + Mandated by ROM code */ + .mask_flags = MTD_WRITEABLE /* force read-only */ + }, + { + .name = "(OneNAND)U-Boot", + .offset = MTDPART_OFS_APPEND, + .size = 2*(64*2048), + .mask_flags = MTD_WRITEABLE /* force read-only */ + }, + { + .name = "(OneNAND)U-Boot Environment", + .offset = MTDPART_OFS_APPEND, + .size = 1*(64*2048), + }, + { + .name = "(OneNAND)Kernel", + .offset = MTDPART_OFS_APPEND, + .size = 4*(64*2048), + }, + { + .name = "(OneNAND)File System", + .offset = MTDPART_OFS_APPEND, + .size = MTDPART_SIZ_FULL, + }, +}; + +static struct omap_onenand_platform_data sdp_onenand_data = { + .parts = onenand_partitions, + .nr_parts = ARRAY_SIZE(onenand_partitions), + .dma_channel = -1, /* disable DMA in OMAP OneNAND driver */ +}; + +static struct platform_device sdp_onenand_device = { + .name = "omap2-onenand", + .id = -1, + .dev = { + .platform_data = &sdp_onenand_data, + }, +}; + +void __init sdp2430_flash_init(void) +{ + void __iomem *gpmc_base_add, *gpmc_cs_base_add; + unsigned char cs = 0; + + gpmc_base_add = (__force void __iomem *)OMAP243X_GPMC_VIRT; + while (cs < GPMC_CS_NUM) { + int ret = 0; + + /* Each GPMC set for a single CS is at offset 0x30 */ + gpmc_cs_base_add = (gpmc_base_add + GPMC_OFF_CONFIG1_0 + + (cs*0x30)); + + /* xloader/Uboot would have programmed the NAND/oneNAND + * base address for us This is a ugly hack. The proper + * way of doing this is to pass the setup of u-boot up + * to kernel using kernel params - something on the + * lines of machineID. Check if Nand/oneNAND is + * configured */ + ret = __raw_readl(gpmc_cs_base_add + GPMC_CS_CONFIG1); + if ((ret & 0xC00) == (0x800)) { + /* Found it!! */ + printk(KERN_INFO "NAND: Found NAND on CS %d \n", cs); + flash_type = NAND; + break; + } + ret = __raw_readl(gpmc_cs_base_add + GPMC_CS_CONFIG7); + if ((ret & 0x3F) == (ONENAND_MAP >> 24)) { + /* Found it!! */ + flash_type = ONENAND; + break; + } + cs++; + } + if (cs >= GPMC_CS_NUM) { + printk(KERN_INFO "MTD: Unable to find MTD configuration in " + "GPMC - not registering.\n"); + return; + } + + if (flash_type == NAND) { + sdp_nand_data.cs = cs; + sdp_nand_data.gpmc_cs_baseaddr = gpmc_cs_base_add; + sdp_nand_data.gpmc_baseaddr = gpmc_base_add; + + if (platform_device_register(&sdp_nand_device) < 0) { + printk(KERN_ERR "Unable to register NAND device\n"); + return; + } + } + + if (flash_type == ONENAND) { + sdp_onenand_data.cs = cs; + + if (platform_device_register(&sdp_onenand_device) < 0) { + printk(KERN_ERR "Unable to register OneNAND device\n"); + return; + } + } +} diff --git a/arch/arm/mach-omap2/board-2430sdp.c b/arch/arm/mach-omap2/board-2430sdp.c index 22143651037..899e6e3402d 100644 --- a/arch/arm/mach-omap2/board-2430sdp.c +++ b/arch/arm/mach-omap2/board-2430sdp.c @@ -19,10 +19,13 @@ #include #include #include +#include #include #include #include #include +#include +#include #include #include @@ -33,18 +36,30 @@ #include #include #include +#include #include #include -#include +#include + #include "mmc-twl4030.h" #define SDP2430_CS0_BASE 0x04000000 #define SDP2430_FLASH_CS 0 #define SDP2430_SMC91X_CS 5 - #define SDP2430_ETHR_GPIO_IRQ 149 +/* GPIO used for TSC2046 (touchscreen) + * + * Also note that the tsc2046 is the same silicon as the ads7846, so + * that driver is used for the touchscreen. */ +#define TS_GPIO 24 + +#define TWL4030_MSECURE_GPIO 118 +#define SECONDARY_LCD_GPIO 147 + +extern void sdp2430_flash_init(void); + static struct mtd_partition sdp2430_partitions[] = { /* bootloader (U-Boot, etc) in first sector */ { @@ -112,6 +127,11 @@ static struct resource sdp2430_smc91x_resources[] = { }, }; +static struct platform_device sdp2430_lcd_device = { + .name = "sdp2430_lcd", + .id = -1, +}; + static struct platform_device sdp2430_smc91x_device = { .name = "smc91x", .id = -1, @@ -119,9 +139,125 @@ static struct platform_device sdp2430_smc91x_device = { .resource = sdp2430_smc91x_resources, }; +/* + * Key mapping for 2430 SDP board + */ + +static int sdp2430_keymap[] = { + KEY(0, 0, KEY_LEFT), + KEY(0, 1, KEY_RIGHT), + KEY(0, 2, KEY_A), + KEY(0, 3, KEY_B), + KEY(0, 4, KEY_C), + KEY(1, 0, KEY_DOWN), + KEY(1, 1, KEY_UP), + KEY(1, 2, KEY_E), + KEY(1, 3, KEY_F), + KEY(1, 4, KEY_G), + KEY(2, 0, KEY_ENTER), + KEY(2, 1, KEY_I), + KEY(2, 2, KEY_J), + KEY(2, 3, KEY_K), + KEY(2, 4, KEY_3), + KEY(3, 0, KEY_M), + KEY(3, 1, KEY_N), + KEY(3, 2, KEY_O), + KEY(3, 3, KEY_P), + KEY(3, 4, KEY_Q), + KEY(4, 0, KEY_R), + KEY(4, 1, KEY_4), + KEY(4, 2, KEY_T), + KEY(4, 3, KEY_U), + KEY(4, 4, KEY_D), + KEY(5, 0, KEY_V), + KEY(5, 1, KEY_W), + KEY(5, 2, KEY_L), + KEY(5, 3, KEY_S), + KEY(5, 4, KEY_H), + 0 +}; + +static struct twl4030_keypad_data sdp2430_kp_data = { + .rows = 5, + .cols = 6, + .keymap = sdp2430_keymap, + .keymapsize = ARRAY_SIZE(sdp2430_keymap), + .rep = 1, +}; + +static int __init msecure_init(void) +{ + int ret = 0; + +#ifdef CONFIG_RTC_DRV_TWL4030 + ret = gpio_request(TWL4030_MSECURE_GPIO, "msecure"); + if (ret < 0) { + printk(KERN_ERR "msecure_init: can't reserve GPIO:%d !\n", + TWL4030_MSECURE_GPIO); + goto out; + } + /* + * TWL4030 will be in secure mode if msecure line from OMAP is low. + * Make msecure line high in order to change the TWL4030 RTC time + * and calender registers. + */ + gpio_direction_output(TWL4030_MSECURE_GPIO, 1); +out: +#endif + + return ret; +} + static struct platform_device *sdp2430_devices[] __initdata = { &sdp2430_smc91x_device, &sdp2430_flash_device, + &sdp2430_lcd_device, +}; + +static void ads7846_dev_init(void) +{ + if (gpio_request(TS_GPIO, "ads7846 irq") < 0) + printk(KERN_ERR "can't get ads746 pen down GPIO\n"); + + gpio_direction_input(TS_GPIO); + + omap_set_gpio_debounce(TS_GPIO, 1); + omap_set_gpio_debounce_time(TS_GPIO, 0xa); +} + +static int ads7846_get_pendown_state(void) +{ + return !gpio_get_value(TS_GPIO); +} + +static struct ads7846_platform_data tsc2046_config __initdata = { + .get_pendown_state = ads7846_get_pendown_state, + .keep_vref_on = 1, +}; + +static struct omap2_mcspi_device_config tsc2046_mcspi_config = { + .turbo_mode = 0, + .single_channel = 0, /* 0: slave, 1: master */ +}; + +static struct omap_lcd_config sdp2430_lcd_config __initdata = { + .ctrl_name = "internal", +}; + +static struct spi_board_info sdp2430_spi_board_info[] __initdata = { + [0] = { + /* + * TSC2046 operates at a max freqency of 2MHz, so + * operate slightly below at 1.5MHz + */ + .modalias = "ads7846", + .bus_num = 1, + .chip_select = 0, + .max_speed_hz = 1500000, + .controller_data = &tsc2046_mcspi_config, + .irq = OMAP_GPIO_IRQ(TS_GPIO), + .platform_data = &tsc2046_config, + }, }; static inline void __init sdp2430_init_smc91x(void) @@ -199,8 +335,16 @@ static struct omap_uart_config sdp2430_uart_config __initdata = { .enabled_uarts = ((1 << 0) | (1 << 1) | (1 << 2)), }; -static struct omap_board_config_kernel sdp2430_config[] = { +static +struct omap_serial_console_config sdp2430_serial_console_config __initdata = { + .console_uart = 1, + .console_speed = 115200, +}; + +static struct omap_board_config_kernel sdp2430_config[] __initdata = { {OMAP_TAG_UART, &sdp2430_uart_config}, + {OMAP_TAG_LCD, &sdp2430_lcd_config}, + {OMAP_TAG_SERIAL_CONSOLE, &sdp2430_serial_console_config}, }; @@ -210,12 +354,23 @@ static struct twl4030_gpio_platform_data sdp2430_gpio_data = { .irq_end = TWL4030_GPIO_IRQ_END, }; +static struct twl4030_usb_data sdp2430_usb_data = { + .usb_mode = T2_USB_MODE_ULPI, +}; + +static struct twl4030_madc_platform_data sdp2430_madc_data = { + .irq_line = 1, +}; + static struct twl4030_platform_data sdp2430_twldata = { .irq_base = TWL4030_IRQ_BASE, .irq_end = TWL4030_IRQ_END, /* platform_data for children goes here */ .gpio = &sdp2430_gpio_data, + .madc = &sdp2430_madc_data, + .keypad = &sdp2430_kp_data, + .usb = &sdp2430_usb_data, }; static struct i2c_board_info __initdata sdp2430_i2c_boardinfo[] = { @@ -254,8 +409,19 @@ static void __init omap_2430sdp_init(void) omap_board_config = sdp2430_config; omap_board_config_size = ARRAY_SIZE(sdp2430_config); omap_serial_init(); - twl4030_mmc_init(mmc); + + msecure_init(); + + sdp2430_flash_init(); usb_musb_init(); + + spi_register_board_info(sdp2430_spi_board_info, + ARRAY_SIZE(sdp2430_spi_board_info)); + ads7846_dev_init(); + twl4030_mmc_init(mmc); + + /* turn off secondary LCD backlight */ + gpio_direction_output(SECONDARY_LCD_GPIO, 0); } static void __init omap_2430sdp_map_io(void) diff --git a/arch/arm/mach-omap2/board-3430sdp-flash.c b/arch/arm/mach-omap2/board-3430sdp-flash.c new file mode 100644 index 00000000000..f0e25a46417 --- /dev/null +++ b/arch/arm/mach-omap2/board-3430sdp-flash.c @@ -0,0 +1,290 @@ +/* + * linux/arch/arm/mach-omap2/board-3430sdp-flash.c + * + * Copyright (c) 2007 Texas Instruments + * + * Modified from mach-omap2/board-2430sdp-flash.c + * Author: Rohit Choraria + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define NAND_BLOCK_SIZE SZ_128K + +/* NAND */ +/* IMPORTANT NOTE ON MAPPING + * 3430SDP - 34XX + * ---------- + * NOR always on 0x04000000 for SDPV1 + * NOR always on 0x10000000 for SDPV2 + * MPDB always on 0x08000000 + * NAND always on 0x0C000000 + * OneNand Mapped to 0x20000000 + * Boot Mode(NAND/NOR). The other on CS1 + */ +#define FLASH_BASE_SDPV1 0x04000000 /* NOR flash (64 Meg aligned) */ +#define FLASH_BASE_SDPV2 0x10000000 /* NOR flash (256 Meg aligned) */ +#define DEBUG_BASE 0x08000000 /* debug board */ +#define NAND_BASE 0x0C000000 /* NAND flash */ +#define ONENAND_MAP 0x20000000 /* OneNand flash */ + +/* various memory sizes */ +#define FLASH_SIZE_SDPV1 SZ_64M +#define FLASH_SIZE_SDPV2 SZ_128M + +static struct mtd_partition sdp_nor_partitions[] = { + /* bootloader (U-Boot, etc) in first sector */ + { + .name = "Bootloader-NOR", + .offset = 0, + .size = SZ_256K, + .mask_flags = MTD_WRITEABLE, /* force read-only */ + }, + /* bootloader params in the next sector */ + { + .name = "Params-NOR", + .offset = MTDPART_OFS_APPEND, + .size = SZ_256K, + .mask_flags = 0, + }, + /* kernel */ + { + .name = "Kernel-NOR", + .offset = MTDPART_OFS_APPEND, + .size = SZ_2M, + .mask_flags = 0 + }, + /* file system */ + { + .name = "Filesystem-NOR", + .offset = MTDPART_OFS_APPEND, + .size = MTDPART_SIZ_FULL, + .mask_flags = 0 + } +}; + +static struct flash_platform_data sdp_nor_data = { + .map_name = "cfi_probe", + .width = 2, + .parts = sdp_nor_partitions, + .nr_parts = ARRAY_SIZE(sdp_nor_partitions), +}; + +static struct resource sdp_nor_resource = { + .start = 0, + .end = 0, + .flags = IORESOURCE_MEM, +}; + +static struct platform_device sdp_nor_device = { + .name = "omapflash", + .id = 0, + .dev = { + .platform_data = &sdp_nor_data, + }, + .num_resources = 1, + .resource = &sdp_nor_resource, +}; + +static int sdp_onenand_setup(void __iomem *, int freq); + +static struct mtd_partition sdp_onenand_partitions[] = { + { + .name = "X-Loader-OneNAND", + .offset = 0, + .size = 4 * (64 * 2048), + .mask_flags = MTD_WRITEABLE /* force read-only */ + }, + { + .name = "U-Boot-OneNAND", + .offset = MTDPART_OFS_APPEND, + .size = 2 * (64 * 2048), + .mask_flags = MTD_WRITEABLE /* force read-only */ + }, + { + .name = "U-Boot Environment-OneNAND", + .offset = MTDPART_OFS_APPEND, + .size = 1 * (64 * 2048), + }, + { + .name = "Kernel-OneNAND", + .offset = MTDPART_OFS_APPEND, + .size = 16 * (64 * 2048), + }, + { + .name = "File System-OneNAND", + .offset = MTDPART_OFS_APPEND, + .size = MTDPART_SIZ_FULL, + }, +}; + +static struct omap_onenand_platform_data sdp_onenand_data = { + .parts = sdp_onenand_partitions, + .nr_parts = ARRAY_SIZE(sdp_onenand_partitions), + .onenand_setup = sdp_onenand_setup, + .dma_channel = -1, /* disable DMA in OMAP OneNAND driver */ +}; + +static struct platform_device sdp_onenand_device = { + .name = "omap2-onenand", + .id = -1, + .dev = { + .platform_data = &sdp_onenand_data, + }, +}; + +/* + * sdp_onenand_setup - The function configures the onenand flash. + * @onenand_base: Onenand base address + * + * @return int: Currently always returning zero. + */ +static int sdp_onenand_setup(void __iomem *onenand_base, int freq) +{ + /* Onenand setup does nothing at present */ + return 0; +} + +static struct mtd_partition sdp_nand_partitions[] = { + /* All the partition sizes are listed in terms of NAND block size */ + { + .name = "X-Loader-NAND", + .offset = 0, + .size = 4 * NAND_BLOCK_SIZE, + .mask_flags = MTD_WRITEABLE, /* force read-only */ + }, + { + .name = "U-Boot-NAND", + .offset = MTDPART_OFS_APPEND, /* Offset = 0x80000 */ + .size = 4 * NAND_BLOCK_SIZE, + .mask_flags = MTD_WRITEABLE, /* force read-only */ + }, + { + .name = "Boot Env-NAND", + .offset = MTDPART_OFS_APPEND, /* Offset = 0x100000 */ + .size = 2 * NAND_BLOCK_SIZE, + }, + { + .name = "Kernel-NAND", + .offset = MTDPART_OFS_APPEND, /* Offset = 0x140000 */ + .size = 32 * NAND_BLOCK_SIZE, + }, + { + .name = "File System - NAND", + .size = MTDPART_SIZ_FULL, + .offset = MTDPART_OFS_APPEND, /* Offset = 0x540000 */ + }, +}; + +static struct omap_nand_platform_data sdp_nand_data = { + .parts = sdp_nand_partitions, + .nr_parts = ARRAY_SIZE(sdp_nand_partitions), + .nand_setup = NULL, + .dma_channel = -1, /* disable DMA in OMAP NAND driver */ + .dev_ready = NULL, +}; + +static struct resource sdp_nand_resource = { + .flags = IORESOURCE_MEM, +}; + +static struct platform_device sdp_nand_device = { + .name = "omap2-nand", + .id = 0, + .dev = { + .platform_data = &sdp_nand_data, + }, + .num_resources = 1, + .resource = &sdp_nand_resource, +}; + + +/** + * sdp3430_flash_init - Identify devices connected to GPMC and register. + * + * @return - void. + */ +void __init sdp3430_flash_init(void) +{ + u8 cs = 0; + u8 nandcs = GPMC_CS_NUM + 1; + u8 onenandcs = GPMC_CS_NUM + 1; + unsigned long gpmc_base_add; + + gpmc_base_add = OMAP34XX_GPMC_VIRT; + + /* Configure start address and size of NOR device */ + if (omap_rev() > OMAP3430_REV_ES1_0) { + sdp_nor_resource.start = FLASH_BASE_SDPV2; + sdp_nor_resource.end = FLASH_BASE_SDPV2 + + FLASH_SIZE_SDPV2 - 1; + } else { + sdp_nor_resource.start = FLASH_BASE_SDPV1; + sdp_nor_resource.end = FLASH_BASE_SDPV1 + + FLASH_SIZE_SDPV1 - 1; + } + + if (platform_device_register(&sdp_nor_device) < 0) + printk(KERN_ERR "Unable to register NOR device\n"); + + while (cs < GPMC_CS_NUM) { + u32 ret = 0; + ret = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG1); + + /* + * xloader/Uboot would have programmed the NAND/oneNAND + * base address for us This is a ugly hack. The proper + * way of doing this is to pass the setup of u-boot up + * to kernel using kernel params - something on the + * lines of machineID. Check if oneNAND is configured + */ + if ((ret & 0xC00) == 0x800) { + /* Found it!! */ + if (nandcs > GPMC_CS_NUM) + nandcs = cs; + } else { + ret = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG7); + if ((ret & 0x3F) == (ONENAND_MAP >> 24)) + onenandcs = cs; + } + cs++; + } + if ((nandcs > GPMC_CS_NUM) && (onenandcs > GPMC_CS_NUM)) { + printk(KERN_INFO "NAND/OneNAND: Unable to find configuration " + " in GPMC\n "); + return; + } + + if (nandcs < GPMC_CS_NUM) { + sdp_nand_data.cs = nandcs; + sdp_nand_data.gpmc_cs_baseaddr = (void *)(gpmc_base_add + + GPMC_CS0_BASE + nandcs*GPMC_CS_SIZE); + sdp_nand_data.gpmc_baseaddr = (void *) (gpmc_base_add); + + if (platform_device_register(&sdp_nand_device) < 0) + printk(KERN_ERR "Unable to register NAND device\n"); + } + + if (onenandcs < GPMC_CS_NUM) { + sdp_onenand_data.cs = onenandcs; + if (platform_device_register(&sdp_onenand_device) < 0) + printk(KERN_ERR "Unable to register OneNAND device\n"); + } +} diff --git a/arch/arm/mach-omap2/board-3430sdp.c b/arch/arm/mach-omap2/board-3430sdp.c index ed927497212..03acac79adf 100644 --- a/arch/arm/mach-omap2/board-3430sdp.c +++ b/arch/arm/mach-omap2/board-3430sdp.c @@ -17,12 +17,12 @@ #include #include #include +#include #include #include #include #include #include -#include #include #include @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -38,12 +39,13 @@ #include #include -#include +#include "sdram-qimonda-hyb18m512160af-6.h" #include "mmc-twl4030.h" #define CONFIG_DISABLE_HFCLK 1 +#define SDP3430_ETHR_START DEBUG_BASE #define SDP3430_ETHR_GPIO_IRQ_SDPV1 29 #define SDP3430_ETHR_GPIO_IRQ_SDPV2 6 #define SDP3430_SMC91X_CS 3 @@ -54,8 +56,13 @@ #define ENABLE_VAUX3_DEDICATED 0x03 #define ENABLE_VAUX3_DEV_GRP 0x20 +#define ENABLE_VAUX3_DEDICATED 0x03 +#define ENABLE_VAUX3_DEV_GRP 0x20 + #define TWL4030_MSECURE_GPIO 22 +extern void sdp3430_flash_init(void); + static struct resource sdp3430_smc91x_resources[] = { [0] = { .flags = IORESOURCE_MEM, @@ -118,6 +125,42 @@ static struct twl4030_keypad_data sdp3430_kp_data = { static int ts_gpio; /* Needed for ads7846_get_pendown_state */ +static int __init msecure_init(void) +{ + int ret = 0; + +#ifdef CONFIG_RTC_DRV_TWL4030 + /* 3430ES2.0 doesn't have msecure/gpio-22 line connected to T2 */ + if (omap_type() == OMAP2_DEVICE_TYPE_GP && + omap_rev() < OMAP3430_REV_ES2_0) { + void __iomem *msecure_pad_config_reg = omap_ctrl_base_get() + + 0xA3C; + int mux_mask = 0x04; + u16 tmp; + + ret = gpio_request(TWL4030_MSECURE_GPIO, "msecure"); + if (ret < 0) { + printk(KERN_ERR "msecure_init: can't" + "reserve GPIO:%d !\n", TWL4030_MSECURE_GPIO); + goto out; + } + /* + * TWL4030 will be in secure mode if msecure line from OMAP + * is low. Make msecure line high in order to change the + * TWL4030 RTC time and calender registers. + */ + tmp = __raw_readw(msecure_pad_config_reg); + tmp &= 0xF8; /* To enable mux mode 03/04 = GPIO_RTC */ + tmp |= mux_mask;/* To enable mux mode 03/04 = GPIO_RTC */ + __raw_writew(tmp, msecure_pad_config_reg); + + gpio_direction_output(TWL4030_MSECURE_GPIO, 1); + } +out: +#endif + return ret; +} + /** * @brief ads7846_dev_init : Requests & sets GPIO line for pen-irq * @@ -141,9 +184,42 @@ static int ads7846_get_pendown_state(void) return !gpio_get_value(ts_gpio); } +/* + * This enable(1)/disable(0) the voltage for TS: uses twl4030 calls + */ +static int ads7846_vaux_control(int vaux_cntrl) +{ + int ret = 0; + + /* FIXME use regulator calls */ + +#ifdef CONFIG_TWL4030_CORE + /* check for return value of ldo_use: if success it returns 0 */ + if (vaux_cntrl == VAUX_ENABLE) { + if (ret != twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, + ENABLE_VAUX3_DEDICATED, TWL4030_VAUX3_DEDICATED)) + return -EIO; + if (ret != twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, + ENABLE_VAUX3_DEV_GRP, TWL4030_VAUX3_DEV_GRP)) + return -EIO; + } else if (vaux_cntrl == VAUX_DISABLE) { + if (ret != twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, + 0x00, TWL4030_VAUX3_DEDICATED)) + return -EIO; + if (ret != twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, + 0x00, TWL4030_VAUX3_DEV_GRP)) + return -EIO; + } +#else + ret = -EIO; +#endif + return ret; +} + static struct ads7846_platform_data tsc2046_config __initdata = { .get_pendown_state = ads7846_get_pendown_state, .keep_vref_on = 1, + .vaux_control = ads7846_vaux_control, }; @@ -222,7 +298,7 @@ static inline void __init sdp3430_init_smc91x(void) static void __init omap_3430sdp_init_irq(void) { - omap2_init_common_hw(NULL); + omap2_init_common_hw(hyb18m512160af6_sdrc_params); omap_init_irq(); omap_gpio_init(); sdp3430_init_smc91x(); @@ -331,6 +407,87 @@ static struct twl4030_madc_platform_data sdp3430_madc_data = { .irq_line = 1, }; + +static struct twl4030_ins __initdata sleep_on_seq[] = { +/* + * Turn off VDD1 and VDD2. + */ + {MSG_SINGULAR(DEV_GRP_P1, 0xf, RES_STATE_OFF), 4}, + {MSG_SINGULAR(DEV_GRP_P1, 0x10, RES_STATE_OFF), 2}, +#ifdef CONFIG_DISABLE_HFCLK +/* + * And also turn off the OMAP3 PLLs and the sysclk output. + */ + {MSG_SINGULAR(DEV_GRP_P1, 0x7, RES_STATE_OFF), 3}, + {MSG_SINGULAR(DEV_GRP_P1, 0x19, RES_STATE_OFF), 3}, +#endif +}; + +static struct twl4030_script sleep_on_script __initdata = { + .script = sleep_on_seq, + .size = ARRAY_SIZE(sleep_on_seq), + .flags = TRITON_SLEEP_SCRIPT, +}; + +static struct twl4030_ins wakeup_seq[] __initdata = { +#ifndef CONFIG_DISABLE_HFCLK +/* + * Wakeup VDD1 and VDD2. + */ + {MSG_SINGULAR(DEV_GRP_P1, 0xf, RES_STATE_ACTIVE), 4}, + {MSG_SINGULAR(DEV_GRP_P1, 0x10, RES_STATE_ACTIVE), 2}, +#else +/* + * Reenable the OMAP3 PLLs. + * Wakeup VDD1 and VDD2. + * Reenable sysclk output. + */ + {MSG_SINGULAR(DEV_GRP_P1, 0x7, RES_STATE_ACTIVE), 0x30}, + {MSG_SINGULAR(DEV_GRP_P1, 0xf, RES_STATE_ACTIVE), 0x30}, + {MSG_SINGULAR(DEV_GRP_P1, 0x10, RES_STATE_ACTIVE), 0x37}, + {MSG_SINGULAR(DEV_GRP_P1, 0x19, RES_STATE_ACTIVE), 3}, +#endif /* #ifndef CONFIG_DISABLE_HFCLK */ +}; + +static struct twl4030_script wakeup_script __initdata = { + .script = wakeup_seq, + .size = ARRAY_SIZE(wakeup_seq), + .flags = TRITON_WAKEUP12_SCRIPT | TRITON_WAKEUP3_SCRIPT, +}; + +static struct twl4030_ins wrst_seq[] __initdata = { +/* + * Reset twl4030. + * Reset VDD1 regulator. + * Reset VDD2 regulator. + * Reset VPLL1 regulator. + * Enable sysclk output. + * Reenable twl4030. + */ + {MSG_SINGULAR(DEV_GRP_NULL, 0x1b, RES_STATE_OFF), 2}, + {MSG_SINGULAR(DEV_GRP_P1, 0xf, RES_STATE_WRST), 15}, + {MSG_SINGULAR(DEV_GRP_P1, 0x10, RES_STATE_WRST), 15}, + {MSG_SINGULAR(DEV_GRP_P1, 0x7, RES_STATE_WRST), 0x60}, + {MSG_SINGULAR(DEV_GRP_P1, 0x19, RES_STATE_ACTIVE), 2}, + {MSG_SINGULAR(DEV_GRP_NULL, 0x1b, RES_STATE_ACTIVE), 2}, +}; +static struct twl4030_script wrst_script __initdata = { + .script = wrst_seq, + .size = ARRAY_SIZE(wakeup_seq), + .flags = TRITON_WRST_SCRIPT, +}; + +static struct twl4030_script *twl4030_scripts[] __initdata = { + &sleep_on_script, + &wakeup_script, + &wrst_script, +}; + +static struct twl4030_power_data sdp3430_t2scripts_data __initdata = { + .scripts = twl4030_scripts, + .size = ARRAY_SIZE(twl4030_scripts), +}; + /* * Apply all the fixed voltages since most versions of U-Boot * don't bother with that initialization. @@ -472,6 +629,7 @@ static struct twl4030_platform_data sdp3430_twldata = { .gpio = &sdp3430_gpio_data, .madc = &sdp3430_madc_data, .keypad = &sdp3430_kp_data, + .power = &sdp3430_t2scripts_data, .usb = &sdp3430_usb_data, .vaux1 = &sdp3430_vaux1, @@ -520,8 +678,11 @@ static void __init omap_3430sdp_init(void) spi_register_board_info(sdp3430_spi_board_info, ARRAY_SIZE(sdp3430_spi_board_info)); ads7846_dev_init(); + sdp3430_flash_init(); + msecure_init(); omap_serial_init(); usb_musb_init(); + usb_ehci_init(); } static void __init omap_3430sdp_map_io(void) diff --git a/arch/arm/mach-omap2/board-apollon-keys.c b/arch/arm/mach-omap2/board-apollon-keys.c new file mode 100644 index 00000000000..10329c0ae6a --- /dev/null +++ b/arch/arm/mach-omap2/board-apollon-keys.c @@ -0,0 +1,103 @@ +/* + * linux/arch/arm/mach-omap2/board-apollon-keys.c + * + * Copyright (C) 2007 Samsung Electronics + * Author: Kyungmin Park + * + * 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 +#include +#include +#include +#include + +#include +#include + +#define SW_ENTER_GPIO16 16 +#define SW_UP_GPIO17 17 +#define SW_DOWN_GPIO58 58 +#define SW_LEFT_GPIO95 95 +#define SW_RIGHT_GPIO96 96 +#define SW_ESC_GPIO97 97 + +extern int apollon_plus(void); + +static struct gpio_keys_button apollon_gpio_keys_buttons[] = { + [0] = { + .code = KEY_ENTER, + .gpio = SW_ENTER_GPIO16, + .desc = "enter sw", + }, + [1] = { + .code = KEY_UP, + .gpio = SW_UP_GPIO17, + .desc = "up sw", + }, + [2] = { + .code = KEY_DOWN, + .gpio = SW_DOWN_GPIO58, + .desc = "down sw", + }, + [3] = { + .code = KEY_LEFT, + .gpio = SW_LEFT_GPIO95, + .desc = "left sw", + }, + [4] = { + .code = KEY_RIGHT, + .gpio = SW_RIGHT_GPIO96, + .desc = "right sw", + }, + [5] = { + .code = KEY_ESC, + .gpio = SW_ESC_GPIO97, + .desc = "esc sw", + }, +}; + +static struct gpio_keys_platform_data apollon_gpio_keys = { + .buttons = apollon_gpio_keys_buttons, + .nbuttons = ARRAY_SIZE(apollon_gpio_keys_buttons), +}; + +static struct platform_device apollon_gpio_keys_device = { + .name = "gpio-keys", + .id = -1, + .dev = { + .platform_data = &apollon_gpio_keys, + }, +}; + +static void __init apollon_sw_init(void) +{ + /* Enter SW - Y11 */ + omap_cfg_reg(Y11_242X_GPIO16); + /* Up SW - AA12 */ + omap_cfg_reg(AA12_242X_GPIO17); + /* Down SW - AA8 */ + omap_cfg_reg(AA8_242X_GPIO58); + + if (apollon_plus()) { + /* Left SW - P18 */ + omap_cfg_reg(P18_24XX_GPIO95); + /* Right SW - M18 */ + omap_cfg_reg(M18_24XX_GPIO96); + /* Esc SW - L14 */ + omap_cfg_reg(L14_24XX_GPIO97); + } else + apollon_gpio_keys.nbuttons = 3; +} + +static int __init omap_apollon_keys_init(void) +{ + apollon_sw_init(); + + return platform_device_register(&apollon_gpio_keys_device); +} + +arch_initcall(omap_apollon_keys_init); diff --git a/arch/arm/mach-omap2/board-apollon-mmc.c b/arch/arm/mach-omap2/board-apollon-mmc.c new file mode 100644 index 00000000000..3197741ec47 --- /dev/null +++ b/arch/arm/mach-omap2/board-apollon-mmc.c @@ -0,0 +1,101 @@ +/* + * linux/arch/arm/mach-omap2/board-apollon-mmc.c + * + * Copyright (C) 2005-2007 Samsung Electronics + * Author: Kyungmin Park + * + * 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 +#include + +#include +#include + +#ifdef CONFIG_MMC_OMAP + +static struct device *mmc_device; + +static int apollon_mmc_set_power(struct device *dev, int slot, int power_on, + int vdd) +{ +#ifdef CONFIG_MMC_DEBUG + dev_dbg(dev, "Set slot %d power: %s (vdd %d)\n", slot + 1, + power_on ? "on" : "off", vdd); +#endif + if (slot != 0) { + dev_err(dev, "No such slot %d\n", slot + 1); + return -ENODEV; + } + + return 0; +} + +static int apollon_mmc_set_bus_mode(struct device *dev, int slot, int bus_mode) +{ +#ifdef CONFIG_MMC_DEBUG + dev_dbg(dev, "Set slot %d bus_mode %s\n", slot + 1, + bus_mode == MMC_BUSMODE_OPENDRAIN ? "open-drain" : "push-pull"); +#endif + if (slot != 0) { + dev_err(dev, "No such slot %d\n", slot + 1); + return -ENODEV; + } + + return 0; +} + +static int apollon_mmc_late_init(struct device *dev) +{ + mmc_device = dev; + + return 0; +} + +static void apollon_mmc_cleanup(struct device *dev) +{ +} + +/* + * Note: If you want to detect card feature, please assign GPIO 37 + */ +static struct omap_mmc_platform_data mmc1_data = { + .nr_slots = 1, + .init = apollon_mmc_late_init, + .cleanup = apollon_mmc_cleanup, + .dma_mask = 0xffffffff, + .slots[0] = { + .wires = 4, + + /* + * Use internal loop-back in MMC/SDIO Module Input Clock + * selection + */ + .internal_clock = 1, + + .set_power = apollon_mmc_set_power, + .set_bus_mode = apollon_mmc_set_bus_mode, + .ocr_mask = MMC_VDD_30_31 | MMC_VDD_31_32 | + MMC_VDD_32_33 | MMC_VDD_33_34, + .name = "mmcblk", + }, +}; + +static struct omap_mmc_platform_data *mmc_data[OMAP24XX_NR_MMC]; + +void __init apollon_mmc_init(void) +{ + mmc_data[0] = &mmc1_data; + omap2_init_mmc(mmc_data, OMAP24XX_NR_MMC); +} + +#else /* !CONFIG_MMC_OMAP */ + +void __init apollon_mmc_init(void) +{ +} + +#endif /* CONFIG_MMC_OMAP */ diff --git a/arch/arm/mach-omap2/board-apollon.c b/arch/arm/mach-omap2/board-apollon.c index 06dfba888b0..936cb49bdd0 100644 --- a/arch/arm/mach-omap2/board-apollon.c +++ b/arch/arm/mach-omap2/board-apollon.c @@ -1,10 +1,10 @@ /* * linux/arch/arm/mach-omap2/board-apollon.c * - * Copyright (C) 2005,2006 Samsung Electronics + * Copyright (C) 2005-2008 Samsung Electronics * Author: Kyungmin Park * - * Modified from mach-omap/omap2/board-h4.c + * Modified from mach-omap2/board-h4.c * * Code for apollon OMAP2 board. Should work on many OMAP2 systems where * the bootloader passes the board-specific data to the kernel. @@ -22,11 +22,16 @@ #include #include #include +#include +#include #include #include #include #include +#include +#include +#include #include #include #include @@ -39,20 +44,30 @@ #include #include #include -#include /* LED & Switch macros */ #define LED0_GPIO13 13 #define LED1_GPIO14 14 #define LED2_GPIO15 15 -#define SW_ENTER_GPIO16 16 -#define SW_UP_GPIO17 17 -#define SW_DOWN_GPIO58 58 +#define LED3_GPIO92 92 +#define LED4_GPIO93 93 #define APOLLON_FLASH_CS 0 #define APOLLON_ETH_CS 1 +#define APOLLON_NOR_CS 3 #define APOLLON_ETHR_GPIO_IRQ 74 +#define APOLLON_ONENAND_CS2_ADDRESS (0x00000e40 | (0x10000000 >> 24)) +#define APOLLON_EXT_CS3_ADDRESS (0x00000c40 | (0x18000000 >> 24)) + +extern apollon_mmc_init(void); + +int apollon_plus(void) +{ + /* The apollon plus has IDCODE revision 5 */ + return omap_rev() & 0xc0; +} + static struct mtd_partition apollon_partitions[] = { { .name = "X-Loader + U-Boot", @@ -108,6 +123,63 @@ static struct platform_device apollon_onenand_device = { .resource = apollon_flash_resource, }; +static struct mtd_partition apollon_nor_partitions[] = { + { + .name = "U-Boot", + .offset = 0, + .size = SZ_128K, + .mask_flags = MTD_WRITEABLE, + }, + { + .name = "params", + .offset = MTDPART_OFS_APPEND, + .size = SZ_128K, + }, + { + .name = "kernel", + .offset = MTDPART_OFS_APPEND, + .size = SZ_2M, + }, + { + .name = "rootfs", + .offset = MTDPART_OFS_APPEND, + .size = SZ_4M - SZ_256K, + }, + { + .name = "application", + .offset = MTDPART_OFS_APPEND, + .size = SZ_8M + SZ_2M, + }, + { + .name = "reserved", + .offset = MTDPART_OFS_APPEND, + .size = MTDPART_SIZ_FULL, + }, +}; + +static struct flash_platform_data apollon_nor_data = { + .map_name = "cfi_probe", + .width = 2, + .parts = apollon_nor_partitions, + .nr_parts = ARRAY_SIZE(apollon_nor_partitions), +}; + +static struct resource apollon_nor_resource[] = { + [0] = { + .flags = IORESOURCE_MEM, + } +}; + +static struct platform_device apollon_nor_device = { + .name = "omapflash", + .id = -1, + .dev = { + .platform_data = &apollon_nor_data, + }, + .num_resources = ARRAY_SIZE(apollon_nor_resource), + .resource = apollon_nor_resource, +}; + static void __init apollon_flash_init(void) { unsigned long base; @@ -118,6 +190,13 @@ static void __init apollon_flash_init(void) } apollon_flash_resource[0].start = base; apollon_flash_resource[0].end = base + SZ_128K - 1; + + if (gpmc_cs_request(APOLLON_NOR_CS, SZ_32M, &base) < 0) { + printk(KERN_ERR "Cannot request NOR GPMC CS\n"); + return; + } + apollon_nor_resource[0].start = base; + apollon_nor_resource[0].end = base + SZ_32M - 1; } static struct resource apollon_smc91x_resources[] = { @@ -143,34 +222,37 @@ static struct platform_device apollon_lcd_device = { .id = -1, }; -static struct omap_led_config apollon_led_config[] = { +static struct gpio_led apollon_led_config[] = { { - .cdev = { - .name = "apollon:led0", - }, - .gpio = LED0_GPIO13, + .name = "d2", + .gpio = LED0_GPIO13, + .default_trigger = "heartbeat", }, { - .cdev = { - .name = "apollon:led1", - }, - .gpio = LED1_GPIO14, + .name = "d3", + .gpio = LED1_GPIO14, }, { - .cdev = { - .name = "apollon:led2", - }, - .gpio = LED2_GPIO15, + .name = "d4", + .gpio = LED2_GPIO15, + }, + { + .name = "d5", + .gpio = LED3_GPIO92, + }, + { + .name = "d6", + .gpio = LED4_GPIO93, }, }; -static struct omap_led_platform_data apollon_led_data = { - .nr_leds = ARRAY_SIZE(apollon_led_config), +static struct gpio_led_platform_data apollon_led_data = { + .num_leds = ARRAY_SIZE(apollon_led_config), .leds = apollon_led_config, }; static struct platform_device apollon_led_device = { - .name = "omap-led", + .name = "leds-gpio", .id = -1, .dev = { .platform_data = &apollon_led_data, @@ -179,6 +261,7 @@ static struct platform_device apollon_led_device = { static struct platform_device *apollon_devices[] __initdata = { &apollon_onenand_device, + &apollon_nor_device, &apollon_smc91x_device, &apollon_lcd_device, &apollon_led_device, @@ -187,7 +270,6 @@ static struct platform_device *apollon_devices[] __initdata = { static inline void __init apollon_init_smc91x(void) { unsigned long base; - unsigned int rate; struct clk *gpmc_fck; int eth_cs; @@ -226,7 +308,7 @@ static inline void __init apollon_init_smc91x(void) gpmc_cs_write_reg(eth_cs, GPMC_CS_CONFIG6, 0x000003C2); } - if (gpmc_cs_request(APOLLON_ETH_CS, SZ_16M, &base) < 0) { + if (gpmc_cs_request(eth_cs, SZ_16M, &base) < 0) { printk(KERN_ERR "Failed to request GPMC CS for smc91x\n"); goto out; } @@ -238,7 +320,7 @@ static inline void __init apollon_init_smc91x(void) if (gpio_request(APOLLON_ETHR_GPIO_IRQ, "SMC91x irq") < 0) { printk(KERN_ERR "Failed to request GPIO%d for smc91x IRQ\n", APOLLON_ETHR_GPIO_IRQ); - gpmc_cs_free(APOLLON_ETH_CS); + gpmc_cs_free(eth_cs); goto out; } gpio_direction_input(APOLLON_ETHR_GPIO_IRQ); @@ -256,6 +338,24 @@ static void __init omap_apollon_init_irq(void) apollon_init_smc91x(); } +static struct tsc210x_config tsc_platform_data = { + .use_internal = 1, + .monitor = TSC_TEMP, + .mclk = "sys_clkout", +}; + +static struct spi_board_info apollon_spi_board_info[] = { + [0] = { + .modalias = "tsc2101", + .irq = OMAP_GPIO_IRQ(85), + .bus_num = 1, + .chip_select = 0, + .mode = SPI_MODE_1, + .max_speed_hz = 6000000, + .platform_data = &tsc_platform_data, + }, +}; + static struct omap_uart_config apollon_uart_config __initdata = { .enabled_uarts = (1 << 0) | (0 << 1) | (0 << 2), }; @@ -271,7 +371,7 @@ static struct omap_lcd_config apollon_lcd_config __initdata = { .ctrl_name = "internal", }; -static struct omap_board_config_kernel apollon_config[] = { +static struct omap_board_config_kernel apollon_config[] __initdata = { { OMAP_TAG_UART, &apollon_uart_config }, { OMAP_TAG_LCD, &apollon_lcd_config }, }; @@ -280,16 +380,18 @@ static void __init apollon_led_init(void) { /* LED0 - AA10 */ omap_cfg_reg(AA10_242X_GPIO13); - gpio_request(LED0_GPIO13, "LED0"); - gpio_direction_output(LED0_GPIO13, 0); /* LED1 - AA6 */ omap_cfg_reg(AA6_242X_GPIO14); - gpio_request(LED1_GPIO14, "LED1"); - gpio_direction_output(LED1_GPIO14, 0); /* LED2 - AA4 */ omap_cfg_reg(AA4_242X_GPIO15); - gpio_request(LED2_GPIO15, "LED2"); - gpio_direction_output(LED2_GPIO15, 0); + + if (apollon_plus()) { + /* LED3 - M15 */ + omap_cfg_reg(M15_24XX_GPIO92); + /* LED4 - P20 */ + omap_cfg_reg(P20_24XX_GPIO93); + } else + apollon_led_data.num_leds = 3; } static void __init apollon_usb_init(void) @@ -302,21 +404,101 @@ static void __init apollon_usb_init(void) omap_usb_init(&apollon_usb_config); } -static void __init omap_apollon_init(void) +static void __init apollon_tsc_init(void) +{ + /* TSC2101 */ + omap_cfg_reg(N15_24XX_GPIO85); + gpio_request(85, "tsc2101 irq"); + gpio_direction_input(85); + + omap_cfg_reg(W14_24XX_SYS_CLKOUT); /* mclk */ +} + +static void __init apollon_cs_init(void) { - u32 v; + unsigned long base; + unsigned int rate; + struct clk *l3ck; + u32 value; + int cs, sync = 0; + + l3ck = clk_get(NULL, "core_l3_ck"); + if (IS_ERR(l3ck)) + rate = 100000000; + else + rate = clk_get_rate(l3ck); + + /* CS2: OneNAND */ + cs = 2; + value = gpmc_cs_read_reg(0, GPMC_CS_CONFIG1); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG1, value); + value = gpmc_cs_read_reg(0, GPMC_CS_CONFIG2); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG2, value); + value = gpmc_cs_read_reg(0, GPMC_CS_CONFIG3); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG3, value); + value = gpmc_cs_read_reg(0, GPMC_CS_CONFIG4); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG4, value); + value = gpmc_cs_read_reg(0, GPMC_CS_CONFIG5); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG5, value); + value = gpmc_cs_read_reg(0, GPMC_CS_CONFIG6); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG6, value); + + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG7, APOLLON_ONENAND_CS2_ADDRESS); + + /* CS3: External NOR */ + cs = APOLLON_NOR_CS; + if (rate >= 160000000) { + sync = 1; + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG1, 0xe5011211); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG2, 0x00090b01); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG3, 0x00020201); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG4, 0x09030b03); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG5, 0x010a0a0c); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG6, 0x00000000); + } else if (rate >= 130000000) { + /* Not yet know ... Use the async values */ + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG1, 0x00021201); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG2, 0x00121601); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG3, 0x00040401); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG4, 0x12061605); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG5, 0x01151317); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG6, 0x00000000); + } else {/* rate = 100000000 */ + sync = 1; + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG1, 0xe1001202); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG2, 0x00151501); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG3, 0x00050501); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG4, 0x0e070e07); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG5, 0x01131F1F); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG6, 0x00000000); + } + + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG7, APOLLON_EXT_CS3_ADDRESS); + + if (gpmc_cs_request(cs, SZ_32M, &base) < 0) { + printk(KERN_ERR "Failed to request GPMC CS for external\n"); + return; + } + /* Synchronous mode */ + if (sync) { + void __iomem *addr = ioremap(base, SZ_32M); + writew(0xaa, addr + 0xaaa); + writew(0x55, addr + 0x554); + writew(0xc0, addr + 0x24aaa); + iounmap(addr); + } + + gpmc_cs_free(cs); +} + +static void __init omap_apollon_init(void) +{ + apollon_cs_init(); apollon_led_init(); apollon_flash_init(); apollon_usb_init(); - - /* REVISIT: where's the correct place */ - omap_cfg_reg(W19_24XX_SYS_NIRQ); - - /* Use Interal loop-back in MMC/SDIO Module Input Clock selection */ - v = omap_ctrl_readl(OMAP2_CONTROL_DEVCONF0); - v |= (1 << 24); - omap_ctrl_writel(v, OMAP2_CONTROL_DEVCONF0); + apollon_tsc_init(); /* * Make sure the serial ports are muxed on at this point. @@ -327,6 +509,13 @@ static void __init omap_apollon_init(void) omap_board_config = apollon_config; omap_board_config_size = ARRAY_SIZE(apollon_config); omap_serial_init(); + omap_register_i2c_bus(1, 100, NULL, 0); + omap_register_i2c_bus(2, 100, NULL, 0); + + spi_register_board_info(apollon_spi_board_info, + ARRAY_SIZE(apollon_spi_board_info)); + + apollon_mmc_init(); } static void __init omap_apollon_map_io(void) diff --git a/arch/arm/mach-omap2/board-generic.c b/arch/arm/mach-omap2/board-generic.c index 3492162a65c..0e353b3611f 100644 --- a/arch/arm/mach-omap2/board-generic.c +++ b/arch/arm/mach-omap2/board-generic.c @@ -41,7 +41,7 @@ static struct omap_uart_config generic_uart_config __initdata = { .enabled_uarts = ((1 << 0) | (1 << 1) | (1 << 2)), }; -static struct omap_board_config_kernel generic_config[] = { +static struct omap_board_config_kernel generic_config[] __initdata = { { OMAP_TAG_UART, &generic_uart_config }, }; @@ -50,6 +50,8 @@ static void __init omap_generic_init(void) omap_board_config = generic_config; omap_board_config_size = ARRAY_SIZE(generic_config); omap_serial_init(); + omap_register_i2c_bus(1, 100, NULL, 0); + omap_register_i2c_bus(2, 100, NULL, 0); } static void __init omap_generic_map_io(void) diff --git a/arch/arm/mach-omap2/board-h4.c b/arch/arm/mach-omap2/board-h4.c index a0267a9ab46..cbca22a0199 100644 --- a/arch/arm/mach-omap2/board-h4.c +++ b/arch/arm/mach-omap2/board-h4.c @@ -18,12 +18,22 @@ #include #include #include -#include -#include #include #include #include #include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include #include #include @@ -32,15 +42,12 @@ #include #include -#include -#include #include #include #include #include #include #include -#include #include #include @@ -49,6 +56,47 @@ #define H4_ETHR_GPIO_IRQ 92 +/* FPGA on debug board has 32 GPIOs: 16 dedicated to leds, + * 8 outputs on a header, and 6 inputs from a DIP switch. + */ +#define H4_DEBUG_GPIO_BASE OMAP_MAX_GPIO_LINES +# define H4_DEBUG_GPIO_SW3_1 (H4_DEBUG_GPIO_BASE + 24) +# define H4_DEBUG_GPIO_SW3_2 (H4_DEBUG_GPIO_BASE + 25) +# define H4_DEBUG_GPIO_SW3_3 (H4_DEBUG_GPIO_BASE + 26) +# define H4_DEBUG_GPIO_SW3_4 (H4_DEBUG_GPIO_BASE + 27) +# define H4_DEBUG_GPIO_SW3_5 (H4_DEBUG_GPIO_BASE + 28) +# define H4_DEBUG_GPIO_SW3_8 (H4_DEBUG_GPIO_BASE + 29) + +/* H4 baseboard has 3 PCF8574 (8 bit) I2C GPIO expanders */ +#define H4_U191_GPIO_BASE (H4_DEBUG_GPIO_BASE + 32) +# define H4_GPIO_IRDA_FIRSEL (H4_U191_GPIO_BASE + 0) +# define H4_GPIO_MODEM_MOD_EN (H4_U191_GPIO_BASE + 1) +# define H4_GPIO_WLAN_MOD_EN (H4_U191_GPIO_BASE + 2) +# define H4_GPIO_CAM_MODULE_EN (H4_U191_GPIO_BASE + 3) +# define H4_GPIO_HANDSET_EN (H4_U191_GPIO_BASE + 4) +# define H4_GPIO_LCD_ENBKL (H4_U191_GPIO_BASE + 5) +# define H4_GPIO_AUDIO_ENVDD (H4_U191_GPIO_BASE + 6) +# define H4_GPIO_LCD_ENVDD (H4_U191_GPIO_BASE + 7) + +#define H4_U192_GPIO_BASE (H4_U191_GPIO_BASE + 8) +# define H4_GPIO_IRDA_AGPSn (H4_U192_GPIO_BASE + 0) +# define H4_GPIO_AGPS_PWREN (H4_U192_GPIO_BASE + 1) +# define H4_GPIO_AGPS_RSTn (H4_U192_GPIO_BASE + 2) +# define H4_GPIO_AGPS_SLEEP (H4_U192_GPIO_BASE + 3) +# define H4_GPIO_AGPS_PA_XMT (H4_U192_GPIO_BASE + 4) +# define H4_GPIO_MODEM_SPR2 (H4_U192_GPIO_BASE + 5) +# define H4_GPIO_MODEM_SPR1 (H4_U192_GPIO_BASE + 6) +# define H4_GPIO_BT_ACLK_ENn (H4_U192_GPIO_BASE + 7) + +#define H4_U193_GPIO_BASE (H4_U192_GPIO_BASE + 8) +# define H4_GPIO_SPR0 (H4_U193_GPIO_BASE + 0) +# define H4_GPIO_SPR1 (H4_U193_GPIO_BASE + 1) +# define H4_GPIO_WLAN_SHUTDOWN (H4_U193_GPIO_BASE + 2) +# define H4_GPIO_WLAN_RESET (H4_U193_GPIO_BASE + 3) +# define H4_GPIO_WLAN_CLK_ENn (H4_U193_GPIO_BASE + 4) + /* 5, 6 not connected */ +# define H4_GPIO_CAM_RST (H4_U193_GPIO_BASE + 7) + static unsigned int row_gpios[6] = { 88, 89, 124, 11, 6, 96 }; static unsigned int col_gpios[7] = { 90, 91, 100, 36, 12, 97, 98 }; @@ -138,31 +186,16 @@ static struct platform_device h4_flash_device = { .resource = &h4_flash_resource, }; -/* Select between the IrDA and aGPS module - */ +#if defined(CONFIG_OMAP_IR) || defined(CONFIG_OMAP_IR_MODULE) + +/* Select between the IrDA and aGPS module */ static int h4_select_irda(struct device *dev, int state) { - unsigned char expa; - int err = 0; - - if ((err = read_gpio_expa(&expa, 0x21))) { - printk(KERN_ERR "Error reading from I/O expander\n"); - return err; - } + /* U192.P0 = high for IRDA; else AGPS */ + gpio_set_value_cansleep(H4_GPIO_IRDA_AGPSn, state & IR_SEL); - /* 'P6' enable/disable IRDA_TX and IRDA_RX */ - if (state & IR_SEL) { /* IrDa */ - if ((err = write_gpio_expa(expa | 0x01, 0x21))) { - printk(KERN_ERR "Error writing to I/O expander\n"); - return err; - } - } else { - if ((err = write_gpio_expa(expa & ~0x01, 0x21))) { - printk(KERN_ERR "Error writing to I/O expander\n"); - return err; - } - } - return err; + /* NOTE: UART3 can also hook up to a DB9 or to GSM ... */ + return 0; } static void set_trans_mode(struct work_struct *work) @@ -170,22 +203,9 @@ static void set_trans_mode(struct work_struct *work) struct omap_irda_config *irda_config = container_of(work, struct omap_irda_config, gpio_expa.work); int mode = irda_config->mode; - unsigned char expa; - int err = 0; - - if ((err = read_gpio_expa(&expa, 0x20)) != 0) { - printk(KERN_ERR "Error reading from I/O expander\n"); - } - expa &= ~0x01; - - if (!(mode & IR_SIRMODE)) { /* MIR/FIR */ - expa |= 0x01; - } - - if ((err = write_gpio_expa(expa, 0x20)) != 0) { - printk(KERN_ERR "Error writing to I/O expander\n"); - } + /* U191.P0 = low for SIR; else MIR/FIR */ + gpio_set_value_cansleep(H4_GPIO_IRDA_FIRSEL, !(mode & IR_SIRMODE)); } static int h4_transceiver_mode(struct device *dev, int mode) @@ -199,6 +219,10 @@ static int h4_transceiver_mode(struct device *dev, int mode) return 0; } +#else +static int h4_select_irda(struct device *dev, int state) { return 0; } +static int h4_transceiver_mode(struct device *dev, int mode) { return 0; } +#endif static struct omap_irda_config h4_irda_data = { .transceiver_cap = IR_SIRMODE | IR_MIRMODE | IR_FIRMODE, @@ -269,6 +293,8 @@ static u32 get_sysboot_value(void) OMAP2_SYSBOOT_1_MASK | OMAP2_SYSBOOT_0_MASK)); } +/* FIXME: This function should be moved to some other file, gpmc.c? */ + /* H4-2420's always used muxed mode, H4-2422's always use non-muxed * * Note: OMAP-GIT doesn't correctly do is_cpu_omap2422 and is_cpu_omap2423 @@ -372,7 +398,11 @@ static void __init omap_h4_init_irq(void) } static struct omap_uart_config h4_uart_config __initdata = { +#ifdef CONFIG_MACH_OMAP2_H4_USB1 + .enabled_uarts = ((1 << 0) | (1 << 1)), +#else .enabled_uarts = ((1 << 0) | (1 << 1) | (1 << 2)), +#endif }; static struct omap_lcd_config h4_lcd_config __initdata = { @@ -412,21 +442,309 @@ static struct omap_usb_config h4_usb_config __initdata = { #endif }; -static struct omap_board_config_kernel h4_config[] = { +/* ----------------------------------------------------------------------- */ + +static struct tsc210x_config tsc_platform_data = { + .use_internal = 1, + .monitor = TSC_VBAT | TSC_TEMP, + /* REVISIT temp calibration data -- board-specific; from EEPROM? */ + .mclk = "sys_clkout", +}; + +static struct spi_board_info h4_spi_board_info[] __initdata = { + { + .modalias = "tsc2101", + .bus_num = 1, + .chip_select = 0, + .mode = SPI_MODE_1, + .irq = OMAP_GPIO_IRQ(93), + .max_speed_hz = 16000000, + .platform_data = &tsc_platform_data, + }, + + /* nCS1 -- to lcd board, but unused + * nCS2 -- to WLAN/miniPCI + */ +}; + +/* ----------------------------------------------------------------------- */ + +static struct omap_board_config_kernel h4_config[] __initdata = { { OMAP_TAG_UART, &h4_uart_config }, { OMAP_TAG_LCD, &h4_lcd_config }, }; +#ifdef CONFIG_MACH_OMAP_H4_TUSB + +#include + +static struct musb_hdrc_platform_data tusb_data = { + .mode = MUSB_OTG, + .min_power = 25, /* x2 = 50 mA drawn from VBUS as peripheral */ + + /* 1.8V supplied by Menelaus, other voltages supplied by VBAT; + * so no switching. + */ +}; + +static void __init tusb_evm_setup(void) +{ + static char announce[] __initdata = + KERN_INFO "TUSB 6010 EVM\n"; + int irq; + unsigned dmachan = 0; + + /* There are at least 32 different combinations of boards that + * are loosely called "H4", with a 2420 ... different OMAP chip + * revisions (with pin mux changes for DMAREQ, GPMC errata, etc), + * modifications of the CPU board, mainboard, EVM, TUSB etc. + * Plus omap2422, omap2423, etc. + * + * So you might need to tweak this setup to make the TUSB EVM + * behave on your particular setup ... + */ + + /* Already set up: GPMC AD[0..15], CLK, nOE, nWE, nADV_ALE */ + omap_cfg_reg(E2_GPMC_NCS2); + omap_cfg_reg(L2_GPMC_NCS7); + omap_cfg_reg(M1_GPMC_WAIT2); + + switch ((omap_rev() >> 8) & 0x0f) { + case 0: /* ES 1.0 */ + case 1: /* ES 2.0 */ + /* Assume early board revision without optional ES2.0 + * rework to swap J15 & AA10 so DMAREQ0 works + */ + omap_cfg_reg(AA10_242X_GPIO13); + irq = 13; + /* omap_cfg_reg(J15_24XX_DMAREQ0); */ + break; + default: + /* Later Menelaus boards can support all 6 DMA request + * lines, at the price of boot flash A23-A26. + */ + omap_cfg_reg(J15_24XX_GPIO99); + irq = 99; + dmachan = (1 << 1) | (1 << 0); +#if !(defined(CONFIG_MTD_OMAP_NOR) || defined(CONFIG_MTD_OMAP_NOR_MODULE)) + dmachan |= (1 << 5) | (1 << 4) (1 << 3) | (1 << 2); +#endif + break; + } + + if (tusb6010_setup_interface(&tusb_data, + TUSB6010_REFCLK_24, /* waitpin */ 2, + /* async cs */ 2, /* sync cs */ 7, + irq, dmachan) == 0) + printk(announce); +} + +#endif + +#if defined(CONFIG_VIDEO_OV9640) || defined(CONFIG_VIDEO_OV9640_MODULE) +/* + * Common OV9640 register initialization for all image sizes, pixel formats, + * and frame rates + */ +const static struct ov9640_reg ov9640_common[] = { + { 0x12, 0x80 }, { 0x11, 0x80 }, { 0x13, 0x8F }, /* COM7, CLKRC, COM8 */ + { 0x01, 0x80 }, { 0x02, 0x80 }, { 0x04, 0x00 }, /* BLUE, RED, COM1 */ + { 0x0E, 0x81 }, { 0x0F, 0x4F }, { 0x14, 0x4A }, /* COM5, COM6, COM9 */ + { 0x16, 0x02 }, { 0x1B, 0x01 }, { 0x24, 0x70 }, /* ?, PSHFT, AEW */ + { 0x25, 0x68 }, { 0x26, 0xD3 }, { 0x27, 0x90 }, /* AEB, VPT, BBIAS */ + { 0x2A, 0x00 }, { 0x2B, 0x00 }, { 0x32, 0x24 }, /* EXHCH, EXHCL, HREF */ + { 0x33, 0x02 }, { 0x37, 0x02 }, { 0x38, 0x13 }, /* CHLF, ADC, ACOM */ + { 0x39, 0xF0 }, { 0x3A, 0x00 }, { 0x3B, 0x01 }, /* OFON, TSLB, COM11 */ + { 0x3D, 0x90 }, { 0x3E, 0x02 }, { 0x3F, 0xF2 }, /* COM13, COM14, EDGE */ + { 0x41, 0x02 }, { 0x42, 0xC8 }, /* COM16, COM17 */ + { 0x43, 0xF0 }, { 0x44, 0x10 }, { 0x45, 0x6C }, /* ?, ?, ? */ + { 0x46, 0x6C }, { 0x47, 0x44 }, { 0x48, 0x44 }, /* ?, ?, ? */ + { 0x49, 0x03 }, { 0x59, 0x49 }, { 0x5A, 0x94 }, /* ?, ?, ? */ + { 0x5B, 0x46 }, { 0x5C, 0x84 }, { 0x5D, 0x5C }, /* ?, ?, ? */ + { 0x5E, 0x08 }, { 0x5F, 0x00 }, { 0x60, 0x14 }, /* ?, ?, ? */ + { 0x61, 0xCE }, /* ? */ + { 0x62, 0x70 }, { 0x63, 0x00 }, { 0x64, 0x04 }, /* LCC1, LCC2, LCC3 */ + { 0x65, 0x00 }, { 0x66, 0x00 }, /* LCC4, LCC5 */ + { 0x69, 0x00 }, { 0x6A, 0x3E }, { 0x6B, 0x3F }, /* HV, MBD, DBLV */ + { 0x6C, 0x40 }, { 0x6D, 0x30 }, { 0x6E, 0x4B }, /* GSP1, GSP2, GSP3 */ + { 0x6F, 0x60 }, { 0x70, 0x70 }, { 0x71, 0x70 }, /* GSP4, GSP5, GSP6 */ + { 0x72, 0x70 }, { 0x73, 0x70 }, { 0x74, 0x60 }, /* GSP7, GSP8, GSP9 */ + { 0x75, 0x60 }, { 0x76, 0x50 }, { 0x77, 0x48 }, /* GSP10,GSP11,GSP12 */ + { 0x78, 0x3A }, { 0x79, 0x2E }, { 0x7A, 0x28 }, /* GSP13,GSP14,GSP15 */ + { 0x7B, 0x22 }, { 0x7C, 0x04 }, { 0x7D, 0x07 }, /* GSP16,GST1, GST2 */ + { 0x7E, 0x10 }, { 0x7F, 0x28 }, { 0x80, 0x36 }, /* GST3, GST4, GST5 */ + { 0x81, 0x44 }, { 0x82, 0x52 }, { 0x83, 0x60 }, /* GST6, GST7, GST8 */ + { 0x84, 0x6C }, { 0x85, 0x78 }, { 0x86, 0x8C }, /* GST9, GST10,GST11 */ + { 0x87, 0x9E }, { 0x88, 0xBB }, { 0x89, 0xD2 }, /* GST12,GST13,GST14 */ + { 0x8A, 0xE6 }, { 0x13, 0x8F }, { 0x00, 0x7F }, /* GST15, COM8 */ + { OV9640_REG_TERM, OV9640_VAL_TERM } +}; + +static int ov9640_sensor_power_set(int power) +{ + /* power up the sensor? */ + gpio_set_value_cansleep(H4_GPIO_CAM_MODULE_EN, power); + + /* take it out of reset if it's not powered */ + gpio_direction_output(H4_GPIO_CAM_RST, !power); + + return 0; +} + +static struct v4l2_ifparm ifparm = { + .if_type = V4L2_IF_TYPE_BT656, + .u = { + .bt656 = { + .frame_start_on_rising_vs = 1, + .nobt_vs_inv = 1, + .mode = V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT, + .clock_min = OV9640_XCLK_MIN, + .clock_max = OV9640_XCLK_MAX, + }, + }, +}; + +static int ov9640_ifparm(struct v4l2_ifparm *p) +{ + *p = ifparm; + + return 0; +} + +static struct ov9640_platform_data h4_ov9640_platform_data = { + .power_set = ov9640_sensor_power_set, + .default_regs = ov9640_common, + .ifparm = ov9640_ifparm, +}; + +#endif + +/* leave LCD powered off unless it will be used */ +#if defined(CONFIG_FB_OMAP) || defined(CONFIG_FB_OMAP_MODULE) +#define LCD_ENABLED true +#else +#define LCD_ENABLED false +#endif + +static struct gpio_led backlight_leds[] = { + { + .name = "lcd_h4", + .default_trigger = "backlight", + .gpio = H4_GPIO_LCD_ENBKL, + }, + { }, +}; + +static struct gpio_led_platform_data backlight_led_data = { + .num_leds = 1, + .leds = backlight_leds, +}; + +static struct platform_device h4_backlight_device = { + .name = "leds-gpio", + .id = 0, + .dev.platform_data = &backlight_led_data, +}; + +static int +u191_setup(struct i2c_client *client, int gpio, unsigned ngpio, void *context) +{ + /* P0 = IRDA control, FIR/MIR vs SIR */ + gpio_request(H4_GPIO_IRDA_FIRSEL, "irda_firsel"); + gpio_direction_output(H4_GPIO_IRDA_FIRSEL, false); + + /* P3 = camera sensor module PWDN */ + gpio_request(H4_GPIO_CAM_MODULE_EN, "camera_en"); + gpio_direction_output(H4_GPIO_CAM_MODULE_EN, false); + + /* P7 = LCD_ENVDD ... controls power to LCD (including backlight) + * P5 = LCD_ENBKL ... switches backlight + */ + gpio_request(H4_GPIO_LCD_ENVDD, "lcd_power"); + gpio_direction_output(H4_GPIO_LCD_ENVDD, LCD_ENABLED); + if (LCD_ENABLED) { + h4_backlight_device.dev.parent = &client->dev; + platform_device_register(&h4_backlight_device); + } + + /* P6 = AUDIO_ENVDD ... switch power to microphone */ + gpio_request(H4_GPIO_AUDIO_ENVDD, "audio_power"); + gpio_direction_output(H4_GPIO_AUDIO_ENVDD, true); + + return 0; +} + + +static struct pcf857x_platform_data u191_platform_data = { + .gpio_base = H4_U191_GPIO_BASE, + .setup = u191_setup, +}; + +static int +u192_setup(struct i2c_client *client, int gpio, unsigned ngpio, void *context) +{ + gpio_request(H4_GPIO_IRDA_AGPSn, "irda/agps"); + gpio_direction_output(H4_GPIO_IRDA_AGPSn, false); + + return 0; +} + +static struct pcf857x_platform_data u192_platform_data = { + .gpio_base = H4_U192_GPIO_BASE, + .setup = u192_setup, +}; + +static int +u193_setup(struct i2c_client *client, int gpio, unsigned ngpio, void *context) +{ + /* reset sensor */ + gpio_request(H4_GPIO_CAM_RST, "camera_rst"); + gpio_direction_output(H4_GPIO_CAM_RST, true); + + return 0; +} + +static struct pcf857x_platform_data u193_platform_data = { + .gpio_base = H4_U193_GPIO_BASE, + .setup = u193_setup, +}; + static struct at24_platform_data m24c01 = { .byte_len = SZ_1K / 8, .page_size = 16, }; static struct i2c_board_info __initdata h4_i2c_board_info[] = { + { /* U191 gpios */ + I2C_BOARD_INFO("pcf8574", 0x20), + .platform_data = &u191_platform_data, + }, + { /* U192 gpios */ + I2C_BOARD_INFO("pcf8574", 0x21), + .platform_data = &u192_platform_data, + }, + { /* U193 gpios */ + I2C_BOARD_INFO("pcf8574", 0x22), + .platform_data = &u193_platform_data, + }, + { + I2C_BOARD_INFO("rv5c387a", 0x32), + /* no IRQ wired to OMAP; nINTB goes to AGPS */ + }, + { + I2C_BOARD_INFO("menelaus", 0x72), + .irq = INT_24XX_SYS_NIRQ, + }, { I2C_BOARD_INFO("isp1301_omap", 0x2d), .irq = OMAP_GPIO_IRQ(125), }, +#if defined(CONFIG_VIDEO_OV9640) || defined(CONFIG_VIDEO_OV9640_MODULE) + { + I2C_BOARD_INFO("ov9640", 0x30), + .platform_data = &h4_ov9640_platform_data, + }, +#endif { /* EEPROM on mainboard */ I2C_BOARD_INFO("24c01", 0x52), .platform_data = &m24c01, @@ -457,14 +775,54 @@ static void __init omap_h4_init(void) } #endif - i2c_register_board_info(1, h4_i2c_board_info, - ARRAY_SIZE(h4_i2c_board_info)); +#ifdef CONFIG_MACH_OMAP2_H4_USB1 + /* S3.3 controls whether these pins are for UART2 or USB1 */ + omap_cfg_reg(N14_24XX_USB1_SE0); + omap_cfg_reg(P15_24XX_USB1_DAT); + omap_cfg_reg(W20_24XX_USB1_TXEN); + omap_cfg_reg(V19_24XX_USB1_RCV); +#endif + + /* Menelaus interrupt */ + omap_cfg_reg(W19_24XX_SYS_NIRQ); platform_add_devices(h4_devices, ARRAY_SIZE(h4_devices)); omap_board_config = h4_config; omap_board_config_size = ARRAY_SIZE(h4_config); - omap_usb_init(&h4_usb_config); omap_serial_init(); + omap_usb_init(&h4_usb_config); + omap_register_i2c_bus(1, 100, h4_i2c_board_info, + ARRAY_SIZE(h4_i2c_board_info)); + + /* smc91x, debug leds, ps/2, extra uarts */ + h4_init_debug(); + +#ifdef CONFIG_MACH_OMAP_H4_TUSB + tusb_evm_setup(); +#endif + + /* defaults seem ok for: + * omap_cfg_reg(U18_24XX_SPI1_SCK); + * omap_cfg_reg(V20_24XX_SPI1_MOSI); + * omap_cfg_reg(T18_24XX_SPI1_MISO); + * omap_cfg_reg(U19_24XX_SPI1_NCS0); + */ + + /* TSC2101 */ + omap_cfg_reg(P20_24XX_GPIO93); + gpio_request(93, "tsc_irq"); + gpio_direction_input(93); + + omap_cfg_reg(W14_24XX_SYS_CLKOUT); /* mclk */ + /* defaults seem ok for: + * omap_cfg_reg(Y15_EAC_AC_SCLK); // bclk + * omap_cfg_reg(R14_EAC_AC_FS); + * omap_cfg_reg(V15_EAC_AC_DOUT); + * omap_cfg_reg(W15_EAC_AC_DIN); + */ + + spi_register_board_info(h4_spi_board_info, + ARRAY_SIZE(h4_spi_board_info)); } static void __init omap_h4_map_io(void) diff --git a/arch/arm/mach-omap2/board-ldp.c b/arch/arm/mach-omap2/board-ldp.c index da57b0fcda1..804fd8e7e97 100644 --- a/arch/arm/mach-omap2/board-ldp.c +++ b/arch/arm/mach-omap2/board-ldp.c @@ -16,13 +16,13 @@ #include #include #include +#include #include #include #include #include #include #include -#include #include #include @@ -35,10 +35,11 @@ #include #include #include +#include +#include #include #include -#include #include "mmc-twl4030.h" @@ -46,6 +47,10 @@ #define LDP_SMSC911X_GPIO 152 #define DEBUG_BASE 0x08000000 #define LDP_ETHR_START DEBUG_BASE +#define ENABLE_VAUX1_DEDICATED 0x03 +#define ENABLE_VAUX1_DEV_GRP 0x20 + +#define TWL4030_MSECURE_GPIO 22 static struct resource ldp_smsc911x_resources[] = { [0] = { @@ -77,8 +82,244 @@ static struct platform_device ldp_smsc911x_device = { }, }; +static int ldp_twl4030_keymap[] = { + KEY(0, 0, KEY_1), + KEY(1, 0, KEY_2), + KEY(2, 0, KEY_3), + KEY(0, 1, KEY_4), + KEY(1, 1, KEY_5), + KEY(2, 1, KEY_6), + KEY(3, 1, KEY_F5), + KEY(0, 2, KEY_7), + KEY(1, 2, KEY_8), + KEY(2, 2, KEY_9), + KEY(3, 2, KEY_F6), + KEY(0, 3, KEY_F7), + KEY(1, 3, KEY_0), + KEY(2, 3, KEY_F8), + PERSISTENT_KEY(4, 5), + KEY(4, 4, KEY_VOLUMEUP), + KEY(5, 5, KEY_VOLUMEDOWN), + 0 +}; + +static struct twl4030_keypad_data ldp_kp_twl4030_data = { + .rows = 6, + .cols = 6, + .keymap = ldp_twl4030_keymap, + .keymapsize = ARRAY_SIZE(ldp_twl4030_keymap), + .rep = 1, +}; + +static struct gpio_keys_button ldp_gpio_keys_buttons[] = { + [0] = { + .code = KEY_ENTER, + .gpio = 101, + .desc = "enter sw", + .active_low = 1, + .debounce_interval = 30, + }, + [1] = { + .code = KEY_F1, + .gpio = 102, + .desc = "func 1", + .active_low = 1, + .debounce_interval = 30, + }, + [2] = { + .code = KEY_F2, + .gpio = 103, + .desc = "func 2", + .active_low = 1, + .debounce_interval = 30, + }, + [3] = { + .code = KEY_F3, + .gpio = 104, + .desc = "func 3", + .active_low = 1, + .debounce_interval = 30, + }, + [4] = { + .code = KEY_F4, + .gpio = 105, + .desc = "func 4", + .active_low = 1, + .debounce_interval = 30, + }, + [5] = { + .code = KEY_LEFT, + .gpio = 106, + .desc = "left sw", + .active_low = 1, + .debounce_interval = 30, + }, + [6] = { + .code = KEY_RIGHT, + .gpio = 107, + .desc = "right sw", + .active_low = 1, + .debounce_interval = 30, + }, + [7] = { + .code = KEY_UP, + .gpio = 108, + .desc = "up sw", + .active_low = 1, + .debounce_interval = 30, + }, + [8] = { + .code = KEY_DOWN, + .gpio = 109, + .desc = "down sw", + .active_low = 1, + .debounce_interval = 30, + }, +}; + +static struct gpio_keys_platform_data ldp_gpio_keys = { + .buttons = ldp_gpio_keys_buttons, + .nbuttons = ARRAY_SIZE(ldp_gpio_keys_buttons), + .rep = 1, +}; + +static struct platform_device ldp_gpio_keys_device = { + .name = "gpio-keys", + .id = -1, + .dev = { + .platform_data = &ldp_gpio_keys, + }, +}; + +static int ts_gpio; + +static int __init msecure_init(void) +{ + int ret = 0; + +#ifdef CONFIG_RTC_DRV_TWL4030 + /* 3430ES2.0 doesn't have msecure/gpio-22 line connected to T2 */ + if (omap_type() == OMAP2_DEVICE_TYPE_GP && + omap_rev() < OMAP3430_REV_ES2_0) { + void __iomem *msecure_pad_config_reg = + omap_ctrl_base_get() + 0xA3C; + int mux_mask = 0x04; + u16 tmp; + + ret = gpio_request(TWL4030_MSECURE_GPIO, "msecure"); + if (ret < 0) { + printk(KERN_ERR "msecure_init: can't" + "reserve GPIO:%d !\n", TWL4030_MSECURE_GPIO); + goto out; + } + /* + * TWL4030 will be in secure mode if msecure line from OMAP + * is low. Make msecure line high in order to change the + * TWL4030 RTC time and calender registers. + */ + + tmp = __raw_readw(msecure_pad_config_reg); + tmp &= 0xF8; /* To enable mux mode 03/04 = GPIO_RTC */ + tmp |= mux_mask;/* To enable mux mode 03/04 = GPIO_RTC */ + __raw_writew(tmp, msecure_pad_config_reg); + + gpio_direction_output(TWL4030_MSECURE_GPIO, 1); + } +out: +#endif + return ret; +} + +/** + * @brief ads7846_dev_init : Requests & sets GPIO line for pen-irq + * + * @return - void. If request gpio fails then Flag KERN_ERR. + */ +static void ads7846_dev_init(void) +{ + if (gpio_request(ts_gpio, "ads7846 irq") < 0) { + printk(KERN_ERR "can't get ads746 pen down GPIO\n"); + return; + } + + gpio_direction_input(ts_gpio); + + omap_set_gpio_debounce(ts_gpio, 1); + omap_set_gpio_debounce_time(ts_gpio, 0xa); +} + +static int ads7846_get_pendown_state(void) +{ + return !gpio_get_value(ts_gpio); +} + +/* + * This enable(1)/disable(0) the voltage for TS: uses twl4030 calls + */ +static int ads7846_vaux_control(int vaux_cntrl) +{ + int ret = 0; + +#ifdef CONFIG_TWL4030_CORE + /* check for return value of ldo_use: if success it returns 0 */ + if (vaux_cntrl == VAUX_ENABLE) { + if (ret != twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, + ENABLE_VAUX1_DEDICATED, TWL4030_VAUX1_DEDICATED)) + return -EIO; + if (ret != twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, + ENABLE_VAUX1_DEV_GRP, TWL4030_VAUX1_DEV_GRP)) + return -EIO; + } else if (vaux_cntrl == VAUX_DISABLE) { + if (ret != twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, + 0x00, TWL4030_VAUX1_DEDICATED)) + return -EIO; + if (ret != twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, + 0x00, TWL4030_VAUX1_DEV_GRP)) + return -EIO; + } +#else + ret = -EIO; +#endif + return ret; +} + +static struct ads7846_platform_data tsc2046_config __initdata = { + .get_pendown_state = ads7846_get_pendown_state, + .keep_vref_on = 1, + .vaux_control = ads7846_vaux_control, +}; + + +static struct omap2_mcspi_device_config tsc2046_mcspi_config = { + .turbo_mode = 0, + .single_channel = 1, /* 0: slave, 1: master */ +}; + +static struct spi_board_info ldp_spi_board_info[] __initdata = { + [0] = { + /* + * TSC2046 operates at a max freqency of 2MHz, so + * operate slightly below at 1.5MHz + */ + .modalias = "ads7846", + .bus_num = 1, + .chip_select = 0, + .max_speed_hz = 1500000, + .controller_data = &tsc2046_mcspi_config, + .irq = 0, + .platform_data = &tsc2046_config, + }, +}; + +static struct platform_device ldp_lcd_device = { + .name = "ldp_lcd", + .id = -1, +}; + static struct platform_device *ldp_devices[] __initdata = { &ldp_smsc911x_device, + &ldp_lcd_device, + &ldp_gpio_keys_device, }; static inline void __init ldp_init_smsc911x(void) @@ -110,6 +351,7 @@ static inline void __init ldp_init_smsc911x(void) gpio_direction_input(eth_gpio); } + static void __init omap_ldp_init_irq(void) { omap2_init_common_hw(NULL); @@ -122,8 +364,114 @@ static struct omap_uart_config ldp_uart_config __initdata = { .enabled_uarts = ((1 << 0) | (1 << 1) | (1 << 2)), }; +static struct omap_lcd_config ldp_lcd_config __initdata = { + .ctrl_name = "internal", +}; + static struct omap_board_config_kernel ldp_config[] __initdata = { { OMAP_TAG_UART, &ldp_uart_config }, + { OMAP_TAG_LCD, &ldp_lcd_config }, +}; + +static int ldp_batt_table[] = { +/* 0 C*/ +30800, 29500, 28300, 27100, +26000, 24900, 23900, 22900, 22000, 21100, 20300, 19400, 18700, 17900, +17200, 16500, 15900, 15300, 14700, 14100, 13600, 13100, 12600, 12100, +11600, 11200, 10800, 10400, 10000, 9630, 9280, 8950, 8620, 8310, +8020, 7730, 7460, 7200, 6950, 6710, 6470, 6250, 6040, 5830, +5640, 5450, 5260, 5090, 4920, 4760, 4600, 4450, 4310, 4170, +4040, 3910, 3790, 3670, 3550 +}; + +static struct twl4030_ins __initdata sleep_on_seq[] = { +/* + * Turn off VDD1 and VDD2. + */ + {MSG_SINGULAR(DEV_GRP_P1, 0xf, RES_STATE_OFF), 4}, + {MSG_SINGULAR(DEV_GRP_P1, 0x10, RES_STATE_OFF), 2}, +#ifdef CONFIG_DISABLE_HFCLK +/* + * And also turn off the OMAP3 PLLs and the sysclk output. + */ + {MSG_SINGULAR(DEV_GRP_P1, 0x7, RES_STATE_OFF), 3}, + {MSG_SINGULAR(DEV_GRP_P1, 0x19, RES_STATE_OFF), 3}, +#endif +}; + +static struct twl4030_script sleep_on_script __initdata = { + .script = sleep_on_seq, + .size = ARRAY_SIZE(sleep_on_seq), + .flags = TRITON_SLEEP_SCRIPT, +}; + +static struct twl4030_ins wakeup_seq[] __initdata = { +#ifndef CONFIG_DISABLE_HFCLK +/* + * Wakeup VDD1 and VDD2. + */ + {MSG_SINGULAR(DEV_GRP_P1, 0xf, RES_STATE_ACTIVE), 4}, + {MSG_SINGULAR(DEV_GRP_P1, 0x10, RES_STATE_ACTIVE), 2}, +#else +/* + * Reenable the OMAP3 PLLs. + * Wakeup VDD1 and VDD2. + * Reenable sysclk output. + */ + {MSG_SINGULAR(DEV_GRP_P1, 0x7, RES_STATE_ACTIVE), 0x30}, + {MSG_SINGULAR(DEV_GRP_P1, 0xf, RES_STATE_ACTIVE), 0x30}, + {MSG_SINGULAR(DEV_GRP_P1, 0x10, RES_STATE_ACTIVE), 0x37}, + {MSG_SINGULAR(DEV_GRP_P1, 0x19, RES_STATE_ACTIVE), 3}, +#endif /* #ifndef CONFIG_DISABLE_HFCLK */ +}; + +static struct twl4030_script wakeup_script __initdata = { + .script = wakeup_seq, + .size = ARRAY_SIZE(wakeup_seq), + .flags = TRITON_WAKEUP12_SCRIPT | TRITON_WAKEUP3_SCRIPT, +}; + +static struct twl4030_ins wrst_seq[] __initdata = { +/* + * Reset twl4030. + * Reset VDD1 regulator. + * Reset VDD2 regulator. + * Reset VPLL1 regulator. + * Enable sysclk output. + * Reenable twl4030. + */ + {MSG_SINGULAR(DEV_GRP_NULL, 0x1b, RES_STATE_OFF), 2}, + {MSG_SINGULAR(DEV_GRP_P1, 0xf, RES_STATE_WRST), 15}, + {MSG_SINGULAR(DEV_GRP_P1, 0x10, RES_STATE_WRST), 15}, + {MSG_SINGULAR(DEV_GRP_P1, 0x7, RES_STATE_WRST), 0x60}, + {MSG_SINGULAR(DEV_GRP_P1, 0x19, RES_STATE_ACTIVE), 2}, + {MSG_SINGULAR(DEV_GRP_NULL, 0x1b, RES_STATE_ACTIVE), 2}, +}; + +static struct twl4030_script wrst_script __initdata = { + .script = wrst_seq, + .size = ARRAY_SIZE(wakeup_seq), + .flags = TRITON_WRST_SCRIPT, +}; + +static struct twl4030_script *twl4030_scripts[] __initdata = { + &sleep_on_script, + &wakeup_script, + &wrst_script, +}; + +static struct twl4030_power_data sdp3430_t2scripts_data __initdata = { + .scripts = twl4030_scripts, + .size = ARRAY_SIZE(twl4030_scripts), +}; + +static struct twl4030_bci_platform_data ldp_bci_data = { + .battery_tmp_tbl = ldp_batt_table, + .tblsize = ARRAY_SIZE(ldp_batt_table), +}; + +static struct twl4030_usb_data ldp_usb_data = { + .usb_mode = T2_USB_MODE_ULPI, }; static struct twl4030_gpio_platform_data ldp_gpio_data = { @@ -132,12 +480,21 @@ static struct twl4030_gpio_platform_data ldp_gpio_data = { .irq_end = TWL4030_GPIO_IRQ_END, }; +static struct twl4030_madc_platform_data ldp_madc_data = { + .irq_line = 1, +}; + static struct twl4030_platform_data ldp_twldata = { .irq_base = TWL4030_IRQ_BASE, .irq_end = TWL4030_IRQ_END, /* platform_data for children goes here */ + .bci = &ldp_bci_data, + .madc = &ldp_madc_data, + .usb = &ldp_usb_data, + .power = &sdp3430_t2scripts_data, .gpio = &ldp_gpio_data, + .keypad = &ldp_kp_twl4030_data, }; static struct i2c_board_info __initdata ldp_i2c_boardinfo[] = { @@ -174,9 +531,15 @@ static void __init omap_ldp_init(void) platform_add_devices(ldp_devices, ARRAY_SIZE(ldp_devices)); omap_board_config = ldp_config; omap_board_config_size = ARRAY_SIZE(ldp_config); + ts_gpio = 54; + ldp_spi_board_info[0].irq = gpio_to_irq(ts_gpio); + spi_register_board_info(ldp_spi_board_info, + ARRAY_SIZE(ldp_spi_board_info)); + msecure_init(); + ads7846_dev_init(); omap_serial_init(); - twl4030_mmc_init(mmc); usb_musb_init(); + twl4030_mmc_init(mmc); } static void __init omap_ldp_map_io(void) diff --git a/arch/arm/mach-omap2/board-n800-bt.c b/arch/arm/mach-omap2/board-n800-bt.c new file mode 100644 index 00000000000..da3a7bb6d89 --- /dev/null +++ b/arch/arm/mach-omap2/board-n800-bt.c @@ -0,0 +1,43 @@ +/* + * Nokia N800 platform-specific data for Bluetooth + * + * Copyright (C) 2005, 2006 Nokia Corporation + * Contact: Ville Tervo + * + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include +#include +#include +#include + +static struct platform_device n800_bt_device = { + .name = "hci_h4p", + .id = -1, + .num_resources = 0, +}; + +void __init n800_bt_init(void) +{ + const struct omap_bluetooth_config *bt_config; + + bt_config = (void *) omap_get_config(OMAP_TAG_NOKIA_BT, + struct omap_bluetooth_config); + n800_bt_device.dev.platform_data = (void *) bt_config; + if (platform_device_register(&n800_bt_device) < 0) + BUG(); +} + diff --git a/arch/arm/mach-omap2/board-n800-camera.c b/arch/arm/mach-omap2/board-n800-camera.c new file mode 100644 index 00000000000..395912818ae --- /dev/null +++ b/arch/arm/mach-omap2/board-n800-camera.c @@ -0,0 +1,376 @@ +/* + * arch/arm/mach-omap2/board-n800-camera.c + * + * Copyright (C) 2007 Nokia Corporation + * + * Contact: Sakari Ailus + * + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include <../drivers/cbus/retu.h> +#include <../drivers/media/video/tcm825x.h> + +#include "board-n800.h" + +#if defined (CONFIG_VIDEO_TCM825X) || defined (CONFIG_VIDEO_TCM825X_MODULE) + +#define OMAP24XX_CAMERA_JAM_HACK + +#ifdef OMAP24XX_CAMERA_JAM_HACK +/* + * We don't need to check every pixel to assume that the frame is + * corrupt and the sensor is jammed. CHECK_X and CHECK_Y are the + * number of u32s to check per line / row, plus there are two lines in + * the bottom of the frame. + */ +#define CHECK_X 8 +#define CHECK_Y 6 +/* + * Start checking after this many frames since resetting the sensor. + * Sometimes the first frame(s) is(/are) black which could trigger + * unwanted reset(s). + */ +#define JAM_CHECK_AFTER 3 +/* + * If the sensor is quickly brought into bright conditions from dark, + * it may temporarily be saturated, leaving out the normal background + * noise. This many saturated frames may go through before the sensor + * is considered jammed. + */ +#define SATURATED_MAX 30 +#endif + +#define N800_CAM_SENSOR_RESET_GPIO 53 + +static int sensor_okay; +#ifdef OMAP24XX_CAMERA_JAM_HACK +static int frames_after_reset; +static int saturated_count; +#endif + +const static struct tcm825x_reg tcm825x_regs_n800[] = { + /* initial settings for 2.5 V */ + {0x00, 0x03}, {0x03, 0x29}, {0xaa, 0x2a}, {0xc0, 0x2b}, + {0x10, 0x2c}, {0x4c, 0x2d}, {0x9c, 0x3f}, + + /* main settings */ + {0x00, 0x00}, {0x30, 0x01}, {0x0e, 0x02}, /* initial */ + {0x0f, 0x04}, {0x02, 0x05}, {0x0d, 0x06}, {0xc0, 0x07}, + {0x38, 0x08}, {0x50, 0x09}, {0x80, 0x0a}, {0x40, 0x0b}, + {0x40, 0x0c}, {0x00, 0x0d}, {0x04, 0x0e}, {0x04, 0x0f}, + {0x22, 0x10}, {0x96, 0x11}, {0xf0, 0x12}, {0x08, 0x13}, + {0x08, 0x14}, {0x30, 0x15}, {0x30, 0x16}, {0x01, 0x17}, + {0x40, 0x18}, {0x87, 0x19}, {0x2b, 0x1a}, {0x84, 0x1b}, + {0x52, 0x1c}, {0x44, 0x1d}, {0x68, 0x1e}, {0x00, 0x1f}, + {0x00, 0x20}, {0x01, 0x21}, {0x27, 0x22}, {0x40, 0x23}, + {0x27, 0x24}, {0x5f, 0x25}, {0x00, 0x26}, {0x16, 0x27}, + {0x23, 0x28}, /* initial */ /* initial */ /* initial */ + /* initial */ /* initial */ {0x00, 0x2e}, {0x00, 0x2f}, + {0x00, 0x30}, {0x00, 0x31}, {0x00, 0x32}, {0x00, 0x33}, + {0x00, 0x34}, {0x00, 0x35}, {0x00, 0x36}, {0x00, 0x37}, + {0x00, 0x38}, {0x8c, 0x39}, {0xc8, 0x3A}, {0x80, 0x3b}, + {0x00, 0x3c}, {0x17, 0x3d}, {0x85, 0x3e}, /* initial */ + {0xa0, 0x40}, {0x00, 0x41}, {0x00, 0x42}, {0x00, 0x43}, + {0x08, 0x44}, {0x12, 0x45}, {0x00, 0x46}, {0x20, 0x47}, + {0x30, 0x48}, {0x18, 0x49}, {0x20, 0x4a}, {0x4d, 0x4b}, + {0x0c, 0x4c}, {0xe0, 0x4d}, {0x20, 0x4e}, {0x89, 0x4f}, + {0x21, 0x50}, {0x80, 0x51}, {0x02, 0x52}, {0x00, 0x53}, + {0x30, 0x54}, {0x90, 0x55}, {0x40, 0x56}, {0x06, 0x57}, + {0x0f, 0x58}, {0x23, 0x59}, {0x08, 0x5A}, {0x04, 0x5b}, + {0x08, 0x5c}, {0x08, 0x5d}, {0x08, 0x5e}, {0x08, 0x5f}, + {TCM825X_VAL_TERM, TCM825X_REG_TERM} +}; + +const static struct tcm825x_reg tcm825x_regs_n810[] = { + /* initial settings for 2.5 V */ + {0x00, 0x03}, {0x03, 0x29}, {0xaa, 0x2a}, {0xc0, 0x2b}, + {0x10, 0x2c}, {0x4c, 0x2d}, {0x9c, 0x3f}, + + /* main settings */ + {0x00, 0x00}, {0x30, 0x01}, {0x0e, 0x02}, /* initial */ + {0xcf, 0x04}, {0x02, 0x05}, {0x0d, 0x06}, {0xc0, 0x07}, + {0x38, 0x08}, {0x50, 0x09}, {0x80, 0x0a}, {0x40, 0x0b}, + {0x40, 0x0c}, {0x00, 0x0d}, {0x04, 0x0e}, {0x04, 0x0f}, + {0x22, 0x10}, {0x96, 0x11}, {0xf0, 0x12}, {0x08, 0x13}, + {0x08, 0x14}, {0x30, 0x15}, {0x30, 0x16}, {0x01, 0x17}, + {0x40, 0x18}, {0x87, 0x19}, {0x2b, 0x1a}, {0x84, 0x1b}, + {0x52, 0x1c}, {0x44, 0x1d}, {0x68, 0x1e}, {0x00, 0x1f}, + {0x00, 0x20}, {0x01, 0x21}, {0x27, 0x22}, {0x40, 0x23}, + {0x27, 0x24}, {0x5f, 0x25}, {0x00, 0x26}, {0x16, 0x27}, + {0x23, 0x28}, /* initial */ /* initial */ /* initial */ + /* initial */ /* initial */ {0x00, 0x2e}, {0x00, 0x2f}, + {0x00, 0x30}, {0x00, 0x31}, {0x00, 0x32}, {0x00, 0x33}, + {0x00, 0x34}, {0x00, 0x35}, {0x00, 0x36}, {0x00, 0x37}, + {0x00, 0x38}, {0x8c, 0x39}, {0xc8, 0x3A}, {0x80, 0x3b}, + {0x00, 0x3c}, {0x17, 0x3d}, {0x85, 0x3e}, /* initial */ + {0xa0, 0x40}, {0x00, 0x41}, {0x00, 0x42}, {0x00, 0x43}, + {0x08, 0x44}, {0x12, 0x45}, {0x00, 0x46}, {0x20, 0x47}, + {0x30, 0x48}, {0x18, 0x49}, {0x20, 0x4a}, {0x4d, 0x4b}, + {0x0c, 0x4c}, {0xe0, 0x4d}, {0x20, 0x4e}, {0x89, 0x4f}, + {0x21, 0x50}, {0x80, 0x51}, {0x02, 0x52}, {0x00, 0x53}, + {0x30, 0x54}, {0x90, 0x55}, {0x40, 0x56}, {0x06, 0x57}, + {0x0f, 0x58}, {0x23, 0x59}, {0x08, 0x5A}, {0x04, 0x5b}, + {0x08, 0x5c}, {0x08, 0x5d}, {0x08, 0x5e}, {0x08, 0x5f}, + {TCM825X_VAL_TERM, TCM825X_REG_TERM} +}; + +static int tcm825x_is_okay(void) +{ + return sensor_okay; +} + +/* + * VSIM1 --> CAM_IOVDD --> IOVDD (1.8 V) + */ +static int tcm825x_power_on(void) +{ + int ret; + + /* Set VMEM to 1.5V and VIO to 2.5V */ + ret = menelaus_set_vmem(1500); + if (ret < 0) { + /* Try once more, it seems the sensor power up causes + * some problems on the I2C bus. */ + ret = menelaus_set_vmem(1500); + if (ret < 0) + return ret; + } + msleep(1); + + ret = menelaus_set_vio(2500); + if (ret < 0) + return ret; + + /* Set VSim1 on */ + retu_write_reg(RETU_REG_CTRL_SET, 0x0080); + msleep(1); + + gpio_set_value(N800_CAM_SENSOR_RESET_GPIO, 1); + msleep(1); + + saturated_count = 0; + frames_after_reset = 0; + + return 0; +} + +static int tcm825x_power_off(void) +{ + int ret; + + gpio_set_value(N800_CAM_SENSOR_RESET_GPIO, 0); + msleep(1); + + /* Set VSim1 off */ + retu_write_reg(RETU_REG_CTRL_CLR, 0x0080); + msleep(1); + + /* Set VIO_MODE to off */ + ret = menelaus_set_vio(0); + if (ret < 0) + return ret; + msleep(1); + + /* Set VMEM_MODE to off */ + ret = menelaus_set_vmem(0); + if (ret < 0) + return ret; + msleep(1); + + return 0; +} + +static int tcm825x_power_set(int power) +{ + BUG_ON(!sensor_okay); + + if (power) + return tcm825x_power_on(); + else + return tcm825x_power_off(); +} + +static const struct tcm825x_reg *tcm825x_default_regs(void) +{ + if (machine_is_nokia_n810()) + return tcm825x_regs_n810; + + return tcm825x_regs_n800; +} + +#ifdef OMAP24XX_CAMERA_JAM_HACK +/* + * Check for jammed sensor, in which case all horizontal lines are + * equal. Handle also case where sensor could be saturated awhile in + * case of rapid increase of brightness. + */ +static int tcm825x_needs_reset(struct v4l2_int_device *s, void *buf, + struct v4l2_pix_format *pix) +{ + int i, j; + uint32_t xor, xor2; + uint32_t offset; + uint32_t dx_offset; + uint32_t saturated_pattern; + int is_saturated = 1; + + switch (pix->pixelformat) { + default: + case V4L2_PIX_FMT_RGB565: + saturated_pattern = 0xffffffff; /* guess */ + break; + case V4L2_PIX_FMT_UYVY: + saturated_pattern = 0xe080e080; + break; + } + + /* This won't work for height under 2 at all. */ + if (pix->height < 2) + return 0; + /* Check that there is enough image data. */ + if (pix->width * TCM825X_BYTES_PER_PIXEL < sizeof(uint32_t)) + return 0; + /* + * Don't check for jamming immediately. Sometimes frames + * immediately after reset are black. + */ + if (frames_after_reset < JAM_CHECK_AFTER) { + frames_after_reset++; + return 0; + } + + dx_offset = ((pix->width - sizeof(uint32_t) / TCM825X_BYTES_PER_PIXEL) + * TCM825X_BYTES_PER_PIXEL) / (CHECK_X - 1); + dx_offset = dx_offset - dx_offset % TCM825X_BYTES_PER_PIXEL; + /* + * Check two lines in the bottom first. They're unlikely to be + * saturated and quick to check. + */ + offset = (pix->height - 2) * pix->bytesperline; + xor = xor2 = 0; + for (j = 0; j < CHECK_X; j++) { + uint32_t *val = buf + offset; + uint32_t *val2 = buf + offset + pix->bytesperline; + xor ^= *val; + if (*val != saturated_pattern) + is_saturated = 0; + xor2 ^= *val2; + if (xor2 != xor) { + saturated_count = 0; + return 0; + } + offset += dx_offset; + } + /* Check the rest of the picture. */ + offset = 0; + for (i = 0; i < CHECK_Y; i++) { + uint32_t offset2 = offset; + xor2 = 0; + for (j = 0; j < CHECK_X; j++) { + uint32_t *val = buf + offset2; + xor2 ^= *val; + offset2 += dx_offset; + } + if (xor2 != xor) { + saturated_count = 0; + return 0; + } + offset += pix->bytesperline * ((pix->height - 2) / CHECK_Y); + } + + if (is_saturated && saturated_count++ < SATURATED_MAX) + return 0; + + return -EIO; +} +#else +static int tcm825x_needs_reset(struct v4l2_int_device *s, void *buf, + struct v4l2_pix_format *pix) +{ + return 0; +} +#endif + +static const struct v4l2_ifparm ifparm = { + .if_type = V4L2_IF_TYPE_BT656, + .u = { + .bt656 = { + .frame_start_on_rising_vs = 1, + .latch_clk_inv = 1, + .mode = V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT, + .clock_min = TCM825X_XCLK_MIN, + .clock_max = TCM825X_XCLK_MAX, + }, + }, +}; + +static int tcm825x_ifparm(struct v4l2_ifparm *p) +{ + *p = ifparm; + + return 0; +} + +static int tcm825x_is_upside_down(void) +{ + return machine_is_nokia_n810(); +} + +const struct tcm825x_platform_data n800_tcm825x_platform_data = { + .is_okay = tcm825x_is_okay, + .power_set = tcm825x_power_set, + .default_regs = tcm825x_default_regs, + .needs_reset = tcm825x_needs_reset, + .ifparm = tcm825x_ifparm, + .is_upside_down = tcm825x_is_upside_down, +}; + +void __init n800_cam_init(void) +{ + int r; + + r = gpio_request(N800_CAM_SENSOR_RESET_GPIO, "TCM825x reset"); + if (r < 0) { + printk(KERN_WARNING "%s: failed to request gpio\n", + __func__); + return; + } + + gpio_direction_output(N800_CAM_SENSOR_RESET_GPIO, 0); + + sensor_okay = 1; +} + +#else +void __init n800_cam_init(void) +{ +} + +#endif diff --git a/arch/arm/mach-omap2/board-n800-dsp.c b/arch/arm/mach-omap2/board-n800-dsp.c new file mode 100644 index 00000000000..5f3f0d6e954 --- /dev/null +++ b/arch/arm/mach-omap2/board-n800-dsp.c @@ -0,0 +1,155 @@ +/* + * linux/arch/arm/mach-omap2/board-n800-dsp.c + * + * Copyright (C) 2006 Nokia Corporation. + * + * Contact: Hiroshi DOYU + * + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#if defined(CONFIG_OMAP_DSP) + +/* + * dsp peripheral device: AUDIO + */ +static struct dsp_kfunc_device n800_audio_device = { + .name = "audio", + .type = DSP_KFUNC_DEV_TYPE_AUDIO, + .enable = n800_audio_enable, + .disable = n800_audio_disable, +}; + +/* + * dsp peripheral device: TIMER + */ +static int dsp_timer_probe(struct dsp_kfunc_device *kdev, int stage) +{ + char clockname[20]; + + strcpy(clockname, kdev->name); + strcat(clockname, "_fck"); + + kdev->fck = clk_get(NULL, clockname); + if (IS_ERR(kdev->fck)) { + printk(KERN_ERR "couldn't acquire %s\n", clockname); + return PTR_ERR(kdev->fck); + } + pr_debug("%s probed successfully\n", clockname); + + strcpy(clockname, kdev->name); + strcat(clockname, "_ick"); + kdev->ick = clk_get(NULL, clockname); + if (IS_ERR(kdev->ick)) { + printk(KERN_ERR "couldn't acquire %s\n", clockname); + goto fail; + } + pr_debug("%s probed successfully\n", clockname); + + return 0; + fail: + clk_put(kdev->fck); + + return PTR_ERR(kdev->ick); +} + +static int dsp_timer_remove(struct dsp_kfunc_device *kdev, int stage) +{ + clk_put(kdev->ick); + clk_put(kdev->fck); + pr_debug("%s removed successfully\n", kdev->name); + return 0; +} + +static int dsp_timer_enable(struct dsp_kfunc_device *kdev, int stage) +{ + pr_debug("%s enabled(%d)\n", kdev->name, stage); + + spin_lock(&kdev->lock); + + if (kdev->enabled) + goto out; + kdev->enabled = 1; + + clk_enable(kdev->fck); + clk_enable(kdev->ick); + out: + spin_unlock(&kdev->lock); + + return 0; +} + +static int dsp_timer_disable(struct dsp_kfunc_device *kdev, int stage) +{ + pr_debug("%s disabled(%d)\n", kdev->name, stage); + + spin_lock(&kdev->lock); + + if (kdev->enabled == 0) + goto out; + kdev->enabled = 0; + + clk_disable(kdev->ick); + clk_disable(kdev->fck); + out: + spin_unlock(&kdev->lock); + + return 0; +} + +static struct dsp_kfunc_device n800_timer_device = { + .name = "gpt5", + .type = DSP_KFUNC_DEV_TYPE_COMMON, + .probe = dsp_timer_probe, + .remove = dsp_timer_remove, + .enable = dsp_timer_enable, + .disable = dsp_timer_disable, +}; + +static struct dsp_kfunc_device *n800_kfunc_dev[] = { + &n800_audio_device, + &n800_timer_device, +}; + +void __init n800_dsp_init(void) +{ + int i, ret; + struct dsp_kfunc_device **p = n800_kfunc_dev; + + for (i = 0; i < ARRAY_SIZE(n800_kfunc_dev); i++) { + ret = dsp_kfunc_device_register(p[i]); + if (ret) { + printk(KERN_ERR + "KFUNC device registration failed: %s\n", + p[i]->name); + } + } +} + +#else +void __init n800_dsp_init(void) { } +#endif /* CONFIG_OMAP_DSP */ diff --git a/arch/arm/mach-omap2/board-n800-flash.c b/arch/arm/mach-omap2/board-n800-flash.c new file mode 100644 index 00000000000..52aaf76417e --- /dev/null +++ b/arch/arm/mach-omap2/board-n800-flash.c @@ -0,0 +1,349 @@ +/* + * linux/arch/arm/mach-omap2/board-n800-flash.c + * + * Copyright (C) 2006 Nokia Corporation + * Author: Juha Yrjola + * + * 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 +#include +#include +#include + +#include +#include +#include +#include + +struct mtd_partition n800_partitions[ONENAND_MAX_PARTITIONS]; + +int n800_onenand_setup(void __iomem *, int freq); + +static struct omap_onenand_platform_data n800_onenand_data = { + .cs = 0, + .parts = n800_partitions, + .nr_parts = 0, /* filled later */ + .onenand_setup = n800_onenand_setup, +}; + +static struct platform_device n800_onenand_device = { + .name = "omap2-onenand", + .id = -1, + .dev = { + .platform_data = &n800_onenand_data, + }, +}; + +static int omap2_onenand_set_async_mode(int cs, void __iomem *onenand_base) +{ + struct gpmc_timings t; + + const int t_cer = 15; + const int t_avdp = 12; + const int t_aavdh = 7; + const int t_ce = 76; + const int t_aa = 76; + const int t_oe = 20; + const int t_cez = 20; /* max of t_cez, t_oez */ + const int t_ds = 30; + const int t_wpl = 40; + const int t_wph = 30; + + memset(&t, 0, sizeof(t)); + t.sync_clk = 0; + t.cs_on = 0; + t.adv_on = 0; + + /* Read */ + t.adv_rd_off = gpmc_round_ns_to_ticks(max_t(int, t_avdp, t_cer)); + t.oe_on = t.adv_rd_off + gpmc_round_ns_to_ticks(t_aavdh); + t.access = t.adv_on + gpmc_round_ns_to_ticks(t_aa); + t.access = max_t(int, t.access, t.cs_on + gpmc_round_ns_to_ticks(t_ce)); + t.access = max_t(int, t.access, t.oe_on + gpmc_round_ns_to_ticks(t_oe)); + t.oe_off = t.access + gpmc_round_ns_to_ticks(1); + t.cs_rd_off = t.oe_off; + t.rd_cycle = t.cs_rd_off + gpmc_round_ns_to_ticks(t_cez); + + /* Write */ + t.adv_wr_off = t.adv_rd_off; + t.we_on = t.oe_on; + if (cpu_is_omap34xx()) { + t.wr_data_mux_bus = t.we_on; + t.wr_access = t.we_on + gpmc_round_ns_to_ticks(t_ds); + } + t.we_off = t.we_on + gpmc_round_ns_to_ticks(t_wpl); + t.cs_wr_off = t.we_off + gpmc_round_ns_to_ticks(t_wph); + t.wr_cycle = t.cs_wr_off + gpmc_round_ns_to_ticks(t_cez); + + /* Configure GPMC for asynchronous read */ + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG1, + GPMC_CONFIG1_DEVICESIZE_16 | + GPMC_CONFIG1_MUXADDDATA); + + return gpmc_cs_set_timings(cs, &t); +} + +static unsigned short omap2_onenand_readw(void __iomem *addr) +{ + return readw(addr); +} + +static void omap2_onenand_writew(unsigned short value, void __iomem *addr) +{ + writew(value, addr); +} + +static void set_onenand_cfg(void __iomem *onenand_base, int latency, + int sync_write, int hf) +{ + u32 reg; + + reg = omap2_onenand_readw(onenand_base + ONENAND_REG_SYS_CFG1); + reg &= ~((0x7 << ONENAND_SYS_CFG1_BRL_SHIFT) | (0x7 << 9)); + reg |= (latency << ONENAND_SYS_CFG1_BRL_SHIFT) | + ONENAND_SYS_CFG1_SYNC_READ | + ONENAND_SYS_CFG1_BL_16; + if (sync_write) + reg |= ONENAND_SYS_CFG1_SYNC_WRITE; + else + reg &= ~ONENAND_SYS_CFG1_SYNC_WRITE; + if (hf) + reg |= ONENAND_SYS_CFG1_HF; + else + reg &= ~ONENAND_SYS_CFG1_HF; + omap2_onenand_writew(reg, onenand_base + ONENAND_REG_SYS_CFG1); +} + +static int omap2_onenand_set_sync_mode(int cs, void __iomem *onenand_base, + int freq) +{ + struct gpmc_timings t; + const int t_cer = 15; + const int t_avdp = 12; + const int t_cez = 20; /* max of t_cez, t_oez */ + const int t_ds = 30; + const int t_wpl = 40; + const int t_wph = 30; + int min_gpmc_clk_period, t_ces, t_avds, t_avdh, t_ach, t_aavdh, t_rdyo; + int tick_ns, div, fclk_offset_ns, fclk_offset, gpmc_clk_ns, latency; + int err, ticks_cez, sync_write = 0, first_time = 0, hf = 0; + u32 reg; + + if (!freq) { + /* Very first call freq is not known */ + err = omap2_onenand_set_async_mode(cs, onenand_base); + if (err) + return err; + reg = omap2_onenand_readw(onenand_base + + ONENAND_REG_VERSION_ID); + switch ((reg >> 4) & 0xf) { + case 0: + freq = 40; + break; + case 1: + freq = 54; + break; + case 2: + freq = 66; + break; + case 3: + freq = 83; + break; + case 4: + freq = 104; + break; + default: + freq = 54; + break; + } + first_time = 1; + } + + switch (freq) { + case 83: + min_gpmc_clk_period = 12; /* 83 MHz */ + t_ces = 5; + t_avds = 4; + t_avdh = 2; + t_ach = 6; + t_aavdh = 6; + t_rdyo = 9; + if (cpu_is_omap34xx()) + sync_write = 1; + break; + case 66: + min_gpmc_clk_period = 15; /* 66 MHz */ + t_ces = 6; + t_avds = 5; + t_avdh = 2; + t_ach = 6; + t_aavdh = 6; + t_rdyo = 11; + if (cpu_is_omap34xx()) + sync_write = 1; + break; + default: + min_gpmc_clk_period = 18; /* 54 MHz */ + t_ces = 7; + t_avds = 7; + t_avdh = 7; + t_ach = 9; + t_aavdh = 7; + t_rdyo = 15; + break; + } + + tick_ns = gpmc_ticks_to_ns(1); + div = gpmc_cs_calc_divider(cs, min_gpmc_clk_period); + gpmc_clk_ns = gpmc_ticks_to_ns(div); + if (gpmc_clk_ns < 15) /* >66Mhz */ + hf = 1; + if (hf) + latency = 6; + else if (gpmc_clk_ns >= 25) /* 40 MHz*/ + latency = 3; + else + latency = 4; + + if (first_time) + set_onenand_cfg(onenand_base, latency, sync_write, hf); + + if (div == 1) { + reg = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG2); + reg |= (1 << 7); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG2, reg); + reg = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG3); + reg |= (1 << 7); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG3, reg); + reg = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG4); + reg |= (1 << 7); + reg |= (1 << 23); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG4, reg); + } else { + reg = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG2); + reg &= ~(1 << 7); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG2, reg); + reg = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG3); + reg &= ~(1 << 7); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG3, reg); + reg = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG4); + reg &= ~(1 << 7); + reg &= ~(1 << 23); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG4, reg); + } + + /* Set synchronous read timings */ + memset(&t, 0, sizeof(t)); + t.sync_clk = min_gpmc_clk_period; + t.cs_on = 0; + t.adv_on = 0; + fclk_offset_ns = gpmc_round_ns_to_ticks(max_t(int, t_ces, t_avds)); + fclk_offset = gpmc_ns_to_ticks(fclk_offset_ns); + t.page_burst_access = gpmc_clk_ns; + + /* Read */ + t.adv_rd_off = gpmc_ticks_to_ns(fclk_offset + gpmc_ns_to_ticks(t_avdh)); + t.oe_on = gpmc_ticks_to_ns(fclk_offset + gpmc_ns_to_ticks(t_ach)); + t.access = gpmc_ticks_to_ns(fclk_offset + (latency + 1) * div); + t.oe_off = t.access + gpmc_round_ns_to_ticks(1); + t.cs_rd_off = t.oe_off; + ticks_cez = ((gpmc_ns_to_ticks(t_cez) + div - 1) / div) * div; + t.rd_cycle = gpmc_ticks_to_ns(fclk_offset + (latency + 1) * div + + ticks_cez); + + /* Write */ + if (sync_write) { + t.adv_wr_off = t.adv_rd_off; + t.we_on = 0; + t.we_off = t.cs_rd_off; + t.cs_wr_off = t.cs_rd_off; + t.wr_cycle = t.rd_cycle; + if (cpu_is_omap34xx()) { + t.wr_data_mux_bus = gpmc_ticks_to_ns(fclk_offset + + gpmc_ns_to_ticks(min_gpmc_clk_period + + t_rdyo)); + t.wr_access = t.access; + } + } else { + t.adv_wr_off = gpmc_round_ns_to_ticks(max_t(int, t_avdp, t_cer)); + t.we_on = t.adv_wr_off + gpmc_round_ns_to_ticks(t_aavdh); + t.we_off = t.we_on + gpmc_round_ns_to_ticks(t_wpl); + t.cs_wr_off = t.we_off + gpmc_round_ns_to_ticks(t_wph); + t.wr_cycle = t.cs_wr_off + gpmc_round_ns_to_ticks(t_cez); + if (cpu_is_omap34xx()) { + t.wr_data_mux_bus = t.we_on; + t.wr_access = t.we_on + gpmc_round_ns_to_ticks(t_ds); + } + } + + /* Configure GPMC for synchronous read */ + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG1, + GPMC_CONFIG1_WRAPBURST_SUPP | + GPMC_CONFIG1_READMULTIPLE_SUPP | + GPMC_CONFIG1_READTYPE_SYNC | + (sync_write ? GPMC_CONFIG1_WRITEMULTIPLE_SUPP : 0) | + (sync_write ? GPMC_CONFIG1_WRITETYPE_SYNC : 0) | + GPMC_CONFIG1_CLKACTIVATIONTIME(fclk_offset) | + GPMC_CONFIG1_PAGE_LEN(2) | + (cpu_is_omap34xx() ? 0 : + (GPMC_CONFIG1_WAIT_READ_MON | + GPMC_CONFIG1_WAIT_PIN_SEL(0))) | + GPMC_CONFIG1_DEVICESIZE_16 | + GPMC_CONFIG1_DEVICETYPE_NOR | + GPMC_CONFIG1_MUXADDDATA); + + err = gpmc_cs_set_timings(cs, &t); + if (err) + return err; + + set_onenand_cfg(onenand_base, latency, sync_write, hf); + + return 0; +} + +int n800_onenand_setup(void __iomem *onenand_base, int freq) +{ + struct omap_onenand_platform_data *datap = &n800_onenand_data; + struct device *dev = &n800_onenand_device.dev; + + /* Set sync timings in GPMC */ + if (omap2_onenand_set_sync_mode(datap->cs, onenand_base, freq) < 0) { + dev_err(dev, "Unable to set synchronous mode\n"); + return -EINVAL; + } + + return 0; +} + +void __init n800_flash_init(void) +{ + const struct omap_partition_config *part; + int i = 0; + + n800_onenand_data.gpio_irq = cpu_is_omap34xx() ? 65 : 26; + + while ((part = omap_get_nr_config(OMAP_TAG_PARTITION, + struct omap_partition_config, i)) != NULL) { + struct mtd_partition *mpart; + + mpart = n800_partitions + i; + mpart->name = (char *) part->name; + mpart->size = part->size; + mpart->offset = part->offset; + mpart->mask_flags = part->mask_flags; + i++; + if (i == ARRAY_SIZE(n800_partitions)) { + printk(KERN_ERR "Too many partitions supplied\n"); + return; + } + } + n800_onenand_data.nr_parts = i; + if (platform_device_register(&n800_onenand_device) < 0) { + printk(KERN_ERR "Unable to register OneNAND device\n"); + return; + } +} diff --git a/arch/arm/mach-omap2/board-n800-mmc.c b/arch/arm/mach-omap2/board-n800-mmc.c new file mode 100644 index 00000000000..b2376a96fbf --- /dev/null +++ b/arch/arm/mach-omap2/board-n800-mmc.c @@ -0,0 +1,374 @@ +/* + * linux/arch/arm/mach-omap2/board-n800-mmc.c + * + * Copyright (C) 2006 Nokia Corporation + * Author: Juha Yrjola + * + * 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 +#include +#include +#include + +#include + +#include + +#if defined(CONFIG_MMC_OMAP) || defined(CONFIG_MMC_OMAP_MODULE) + +static const int slot_switch_gpio = 96; + +static const int n810_slot2_pw_vddf = 23; +static const int n810_slot2_pw_vdd = 9; + +static int slot1_cover_open; +static int slot2_cover_open; +static struct device *mmc_device; + +/* + * VMMC --> slot 1 (N800 & N810) + * VDCDC3_APE, VMCS2_APE --> slot 2 on N800 + * GPIO96 --> Menelaus GPIO2 + * GPIO23 --> controls slot2 VSD (N810 only) + * GPIO9 --> controls slot2 VIO_SD (N810 only) + */ + +static int n800_mmc_switch_slot(struct device *dev, int slot) +{ +#ifdef CONFIG_MMC_DEBUG + dev_dbg(dev, "Choose slot %d\n", slot + 1); +#endif + gpio_set_value(slot_switch_gpio, slot); + return 0; +} + +static int n800_mmc_set_power_menelaus(struct device *dev, int slot, + int power_on, int vdd) +{ + int mV; + +#ifdef CONFIG_MMC_DEBUG + dev_dbg(dev, "Set slot %d power: %s (vdd %d)\n", slot + 1, + power_on ? "on" : "off", vdd); +#endif + if (slot == 0) { + if (!power_on) + return menelaus_set_vmmc(0); + switch (1 << vdd) { + case MMC_VDD_33_34: + case MMC_VDD_32_33: + case MMC_VDD_31_32: + mV = 3100; + break; + case MMC_VDD_30_31: + mV = 3000; + break; + case MMC_VDD_28_29: + mV = 2800; + break; + case MMC_VDD_165_195: + mV = 1850; + break; + default: + BUG(); + } + return menelaus_set_vmmc(mV); + } else { + if (!power_on) + return menelaus_set_vdcdc(3, 0); + switch (1 << vdd) { + case MMC_VDD_33_34: + case MMC_VDD_32_33: + mV = 3300; + break; + case MMC_VDD_30_31: + case MMC_VDD_29_30: + mV = 3000; + break; + case MMC_VDD_28_29: + case MMC_VDD_27_28: + mV = 2800; + break; + case MMC_VDD_24_25: + case MMC_VDD_23_24: + mV = 2400; + break; + case MMC_VDD_22_23: + case MMC_VDD_21_22: + mV = 2200; + break; + case MMC_VDD_20_21: + mV = 2000; + break; + case MMC_VDD_165_195: + mV = 1800; + break; + default: + BUG(); + } + return menelaus_set_vdcdc(3, mV); + } + return 0; +} + +static void nokia_mmc_set_power_internal(struct device *dev, + int power_on) +{ + dev_dbg(dev, "Set internal slot power %s\n", + power_on ? "on" : "off"); + + if (power_on) { + gpio_set_value(n810_slot2_pw_vddf, 1); + udelay(30); + gpio_set_value(n810_slot2_pw_vdd, 1); + udelay(100); + } else { + gpio_set_value(n810_slot2_pw_vdd, 0); + msleep(50); + gpio_set_value(n810_slot2_pw_vddf, 0); + msleep(50); + } +} + +static int n800_mmc_set_power(struct device *dev, int slot, int power_on, + int vdd) +{ + if (machine_is_nokia_n800() || slot == 0) + return n800_mmc_set_power_menelaus(dev, slot, power_on, vdd); + + nokia_mmc_set_power_internal(dev, power_on); + + return 0; +} + +static int n800_mmc_set_bus_mode(struct device *dev, int slot, int bus_mode) +{ + int r; + + dev_dbg(dev, "Set slot %d bus mode %s\n", slot + 1, + bus_mode == MMC_BUSMODE_OPENDRAIN ? "open-drain" : "push-pull"); + BUG_ON(slot != 0 && slot != 1); + slot++; + switch (bus_mode) { + case MMC_BUSMODE_OPENDRAIN: + r = menelaus_set_mmc_opendrain(slot, 1); + break; + case MMC_BUSMODE_PUSHPULL: + r = menelaus_set_mmc_opendrain(slot, 0); + break; + default: + BUG(); + } + if (r != 0 && printk_ratelimit()) + dev_err(dev, "MMC: unable to set bus mode for slot %d\n", + slot); + return r; +} + +static int n800_mmc_get_cover_state(struct device *dev, int slot) +{ + slot++; + BUG_ON(slot != 1 && slot != 2); + if (slot == 1) + return slot1_cover_open; + else + return slot2_cover_open; +} + +static void n800_mmc_callback(void *data, u8 card_mask) +{ + int bit, *openp, index; + + if (machine_is_nokia_n800()) { + bit = 1 << 1; + openp = &slot2_cover_open; + index = 1; + } else { + bit = 1; + openp = &slot1_cover_open; + index = 0; + } + + if (card_mask & bit) + *openp = 1; + else + *openp = 0; + + omap_mmc_notify_cover_event(mmc_device, index, *openp); +} + +void n800_mmc_slot1_cover_handler(void *arg, int closed_state) +{ + if (mmc_device == NULL) + return; + + slot1_cover_open = !closed_state; + omap_mmc_notify_cover_event(mmc_device, 0, closed_state); +} + +static int n800_mmc_late_init(struct device *dev) +{ + int r, bit, *openp; + int vs2sel; + + mmc_device = dev; + + r = menelaus_set_slot_sel(1); + if (r < 0) + return r; + + if (machine_is_nokia_n800()) + vs2sel = 0; + else + vs2sel = 2; + + r = menelaus_set_mmc_slot(2, 0, vs2sel, 1); + if (r < 0) + return r; + + n800_mmc_set_power(dev, 0, MMC_POWER_ON, 16); /* MMC_VDD_28_29 */ + n800_mmc_set_power(dev, 1, MMC_POWER_ON, 16); + + r = menelaus_set_mmc_slot(1, 1, 0, 1); + if (r < 0) + return r; + r = menelaus_set_mmc_slot(2, 1, vs2sel, 1); + if (r < 0) + return r; + + r = menelaus_get_slot_pin_states(); + if (r < 0) + return r; + + if (machine_is_nokia_n800()) { + bit = 1 << 1; + openp = &slot2_cover_open; + } else { + bit = 1; + openp = &slot1_cover_open; + slot2_cover_open = 0; + } + + /* All slot pin bits seem to be inversed until first swith change */ + if (r == 0xf || r == (0xf & ~bit)) + r = ~r; + + if (r & bit) + *openp = 1; + else + *openp = 0; + + r = menelaus_register_mmc_callback(n800_mmc_callback, NULL); + + return r; +} + +static void n800_mmc_shutdown(struct device *dev) +{ + int vs2sel; + + if (machine_is_nokia_n800()) + vs2sel = 0; + else + vs2sel = 2; + + menelaus_set_mmc_slot(1, 0, 0, 0); + menelaus_set_mmc_slot(2, 0, vs2sel, 0); +} + +static void n800_mmc_cleanup(struct device *dev) +{ + menelaus_unregister_mmc_callback(); + + gpio_free(slot_switch_gpio); + + if (machine_is_nokia_n810()) { + gpio_free(n810_slot2_pw_vddf); + gpio_free(n810_slot2_pw_vdd); + } +} + +/* + * MMC controller1 has two slots that are multiplexed via I2C. + * MMC controller2 is not in use. + */ +static struct omap_mmc_platform_data mmc1_data = { + .nr_slots = 2, + .switch_slot = n800_mmc_switch_slot, + .init = n800_mmc_late_init, + .cleanup = n800_mmc_cleanup, + .shutdown = n800_mmc_shutdown, + .max_freq = 24000000, + .dma_mask = 0xffffffff, + .slots[0] = { + .wires = 4, + .set_power = n800_mmc_set_power, + .set_bus_mode = n800_mmc_set_bus_mode, + .get_cover_state= n800_mmc_get_cover_state, + .ocr_mask = MMC_VDD_165_195 | MMC_VDD_30_31 | + MMC_VDD_32_33 | MMC_VDD_33_34, + .name = "internal", + }, + .slots[1] = { + .set_power = n800_mmc_set_power, + .set_bus_mode = n800_mmc_set_bus_mode, + .get_cover_state= n800_mmc_get_cover_state, + .ocr_mask = MMC_VDD_165_195 | MMC_VDD_20_21 | + MMC_VDD_21_22 | MMC_VDD_22_23 | MMC_VDD_23_24 | + MMC_VDD_24_25 | MMC_VDD_27_28 | MMC_VDD_28_29 | + MMC_VDD_29_30 | MMC_VDD_30_31 | MMC_VDD_32_33 | + MMC_VDD_33_34, + .name = "external", + }, +}; + +static struct omap_mmc_platform_data *mmc_data[OMAP24XX_NR_MMC]; + +void __init n800_mmc_init(void) + +{ + if (machine_is_nokia_n810()) { + mmc1_data.slots[0].name = "external"; + + /* + * Some Samsung Movinand chips do not like open-ended + * multi-block reads and fall to braind-dead state + * while doing so. Reducing the number of blocks in + * the transfer or delays in clock disable do not help + */ + mmc1_data.slots[1].name = "internal"; + mmc1_data.slots[1].ban_openended = 1; + } + + if (gpio_request(slot_switch_gpio, "MMC slot switch") < 0) + BUG(); + gpio_direction_output(slot_switch_gpio, 0); + + if (machine_is_nokia_n810()) { + if (gpio_request(n810_slot2_pw_vddf, "MMC slot 2 Vddf") < 0) + BUG(); + gpio_direction_output(n810_slot2_pw_vddf, 0); + + if (gpio_request(n810_slot2_pw_vdd, "MMC slot 2 Vdd") < 0) + BUG(); + gpio_direction_output(n810_slot2_pw_vdd, 0); + } + + mmc_data[0] = &mmc1_data; + omap2_init_mmc(mmc_data, OMAP24XX_NR_MMC); +} +#else + +void __init n800_mmc_init(void) +{ +} + +void n800_mmc_slot1_cover_handler(void *arg, int state) +{ +} + +#endif diff --git a/arch/arm/mach-omap2/board-n800-usb.c b/arch/arm/mach-omap2/board-n800-usb.c new file mode 100644 index 00000000000..8250014bee6 --- /dev/null +++ b/arch/arm/mach-omap2/board-n800-usb.c @@ -0,0 +1,175 @@ +/* + * linux/arch/arm/mach-omap2/board-n800-usb.c + * + * Copyright (C) 2006 Nokia Corporation + * Author: Juha Yrjola + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#define TUSB_ASYNC_CS 1 +#define TUSB_SYNC_CS 4 +#define GPIO_TUSB_INT 58 +#define GPIO_TUSB_ENABLE 0 + +static int tusb_set_power(int state); +static int tusb_set_clock(struct clk *osc_ck, int state); + +#if defined(CONFIG_USB_MUSB_OTG) +# define BOARD_MODE MUSB_OTG +#elif defined(CONFIG_USB_MUSB_PERIPHERAL) +# define BOARD_MODE MUSB_PERIPHERAL +#else /* defined(CONFIG_USB_MUSB_HOST) */ +# define BOARD_MODE MUSB_HOST +#endif + +static struct musb_hdrc_eps_bits musb_eps[] = { + { "ep1_tx", 5, }, + { "ep1_rx", 5, }, + { "ep2_tx", 5, }, + { "ep2_rx", 5, }, + { "ep3_tx", 3, }, + { "ep3_rx", 3, }, + { "ep4_tx", 3, }, + { "ep4_rx", 3, }, + { "ep5_tx", 2, }, + { "ep5_rx", 2, }, + { "ep6_tx", 2, }, + { "ep6_rx", 2, }, + { "ep7_tx", 2, }, + { "ep7_rx", 2, }, + { "ep8_tx", 2, }, + { "ep8_rx", 2, }, + { "ep9_tx", 2, }, + { "ep9_rx", 2, }, + { "ep10_tx", 2, }, + { "ep10_rx", 2, }, + { "ep11_tx", 2, }, + { "ep11_rx", 2, }, + { "ep12_tx", 2, }, + { "ep12_rx", 2, }, + { "ep13_tx", 2, }, + { "ep13_rx", 2, }, + { "ep14_tx", 2, }, + { "ep14_rx", 2, }, + { "ep15_tx", 2, }, + { "ep15_rx", 2, }, +}; + +static struct musb_hdrc_config musb_config = { + .multipoint = 1, + .dyn_fifo = 1, + .soft_con = 1, + .dma = 1, + .num_eps = 16, + .dma_channels = 7, + .ram_bits = 12, + .eps_bits = musb_eps, +}; + +static struct musb_hdrc_platform_data tusb_data = { + .mode = BOARD_MODE, + .set_power = tusb_set_power, + .set_clock = tusb_set_clock, + .min_power = 25, /* x2 = 50 mA drawn from VBUS as peripheral */ + .power = 100, /* Max 100 mA VBUS for host mode */ + .clock = "osc_ck", + .config = &musb_config, +}; + +/* + * Enable or disable power to TUSB6010. When enabling, turn on 3.3 V and + * 1.5 V voltage regulators of PM companion chip. Companion chip will then + * provide then PGOOD signal to TUSB6010 which will release it from reset. + */ +static int tusb_set_power(int state) +{ + int i, retval = 0; + + if (state) { + gpio_set_value(GPIO_TUSB_ENABLE, 1); + msleep(1); + + /* Wait until TUSB6010 pulls INT pin down */ + i = 100; + while (i && gpio_get_value(GPIO_TUSB_INT)) { + msleep(1); + i--; + } + + if (!i) { + printk(KERN_ERR "tusb: powerup failed\n"); + retval = -ENODEV; + } + } else { + gpio_set_value(GPIO_TUSB_ENABLE, 0); + msleep(10); + } + + return retval; +} + +static int osc_ck_on; + +static int tusb_set_clock(struct clk *osc_ck, int state) +{ + if (state) { + if (osc_ck_on > 0) + return -ENODEV; + + //omap2_block_sleep(); + clk_enable(osc_ck); + osc_ck_on = 1; + } else { + if (osc_ck_on == 0) + return -ENODEV; + + clk_disable(osc_ck); + osc_ck_on = 0; + //omap2_allow_sleep(); + } + + return 0; +} + +void __init n800_usb_init(void) +{ + int ret = 0; + static char announce[] __initdata = KERN_INFO "TUSB 6010\n"; + + /* PM companion chip power control pin */ + ret = gpio_request(GPIO_TUSB_ENABLE, "TUSB6010 enable"); + if (ret != 0) { + printk(KERN_ERR "Could not get TUSB power GPIO%i\n", + GPIO_TUSB_ENABLE); + return; + } + gpio_direction_output(GPIO_TUSB_ENABLE, 0); + + tusb_set_power(0); + + ret = tusb6010_setup_interface(&tusb_data, TUSB6010_REFCLK_19, 2, + TUSB_ASYNC_CS, TUSB_SYNC_CS, + GPIO_TUSB_INT, 0x3f); + if (ret != 0) + goto err; + + printk(announce); + + return; + +err: + gpio_free(GPIO_TUSB_ENABLE); +} diff --git a/arch/arm/mach-omap2/board-n800.c b/arch/arm/mach-omap2/board-n800.c new file mode 100644 index 00000000000..f1552f0d7b6 --- /dev/null +++ b/arch/arm/mach-omap2/board-n800.c @@ -0,0 +1,750 @@ +/* + * linux/arch/arm/mach-omap2/board-n800.c + * + * Copyright (C) 2005-2007 Nokia Corporation + * Author: Juha Yrjola + * + * Modified from mach-omap2/board-generic.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include <../drivers/cbus/tahvo.h> +#include <../drivers/media/video/tcm825x.h> + +#define N800_BLIZZARD_POWERDOWN_GPIO 15 +#define N800_STI_GPIO 62 +#define N800_KEYB_IRQ_GPIO 109 +#define N800_DAV_IRQ_GPIO 103 +#define N800_TSC2301_RESET_GPIO 118 + +#ifdef CONFIG_MACH_NOKIA_N810 +static s16 rx44_keymap[LM8323_KEYMAP_SIZE] = { + [0x01] = KEY_Q, + [0x02] = KEY_K, + [0x03] = KEY_O, + [0x04] = KEY_P, + [0x05] = KEY_BACKSPACE, + [0x06] = KEY_A, + [0x07] = KEY_S, + [0x08] = KEY_D, + [0x09] = KEY_F, + [0x0a] = KEY_G, + [0x0b] = KEY_H, + [0x0c] = KEY_J, + + [0x11] = KEY_W, + [0x12] = KEY_F4, + [0x13] = KEY_L, + [0x14] = KEY_APOSTROPHE, + [0x16] = KEY_Z, + [0x17] = KEY_X, + [0x18] = KEY_C, + [0x19] = KEY_V, + [0x1a] = KEY_B, + [0x1b] = KEY_N, + [0x1c] = KEY_LEFTSHIFT, /* Actually, this is both shift keys */ + [0x1f] = KEY_F7, + + [0x21] = KEY_E, + [0x22] = KEY_SEMICOLON, + [0x23] = KEY_MINUS, + [0x24] = KEY_EQUAL, + [0x2b] = KEY_FN, + [0x2c] = KEY_M, + [0x2f] = KEY_F8, + + [0x31] = KEY_R, + [0x32] = KEY_RIGHTCTRL, + [0x34] = KEY_SPACE, + [0x35] = KEY_COMMA, + [0x37] = KEY_UP, + [0x3c] = KEY_COMPOSE, + [0x3f] = KEY_F6, + + [0x41] = KEY_T, + [0x44] = KEY_DOT, + [0x46] = KEY_RIGHT, + [0x4f] = KEY_F5, + [0x51] = KEY_Y, + [0x53] = KEY_DOWN, + [0x55] = KEY_ENTER, + [0x5f] = KEY_ESC, + + [0x61] = KEY_U, + [0x64] = KEY_LEFT, + + [0x71] = KEY_I, + [0x75] = KEY_KPENTER, +}; + +static struct lm8323_platform_data lm8323_pdata = { + .repeat = 0, /* Repeat is handled in userspace for now. */ + .keymap = rx44_keymap, + .size_x = 8, + .size_y = 8, + .debounce_time = 12, + .active_time = 500, + + .name = "Internal keyboard", + .pwm1_name = "n810::keyboard", + .pwm2_name = "n810::cover", +}; +#endif + +void __init nokia_n800_init_irq(void) +{ + omap2_init_common_hw(NULL); + omap_init_irq(); + omap_gpio_init(); + +#ifdef CONFIG_OMAP_STI + if (gpio_request(N800_STI_GPIO, "STI") < 0) { + printk(KERN_ERR "Failed to request GPIO %d for STI\n", + N800_STI_GPIO); + return; + } + + gpio_direction_output(N800_STI_GPIO, 0); +#endif +} + +#if defined(CONFIG_MENELAUS) && defined(CONFIG_SENSORS_TMP105) + +static int n800_tmp105_set_power(int enable) +{ + return menelaus_set_vaux(enable ? 2800 : 0); +} + +#else + +#define n800_tmp105_set_power NULL + +#endif + +static struct omap_uart_config n800_uart_config __initdata = { + .enabled_uarts = (1 << 0) | (1 << 2), +}; + +#include "../../../drivers/cbus/retu.h" + +static struct omap_fbmem_config n800_fbmem0_config __initdata = { + .size = 752 * 1024, +}; + +static struct omap_fbmem_config n800_fbmem1_config __initdata = { + .size = 752 * 1024, +}; + +static struct omap_fbmem_config n800_fbmem2_config __initdata = { + .size = 752 * 1024, +}; + +static struct omap_tmp105_config n800_tmp105_config __initdata = { + .tmp105_irq_pin = 125, + .set_power = n800_tmp105_set_power, +}; + +static void mipid_shutdown(struct mipid_platform_data *pdata) +{ + if (pdata->nreset_gpio != -1) { + pr_info("shutdown LCD\n"); + gpio_set_value(pdata->nreset_gpio, 0); + msleep(120); + } +} + +static struct mipid_platform_data n800_mipid_platform_data = { + .shutdown = mipid_shutdown, +}; + +static void __init mipid_dev_init(void) +{ + const struct omap_lcd_config *conf; + + conf = omap_get_config(OMAP_TAG_LCD, struct omap_lcd_config); + if (conf != NULL) { + n800_mipid_platform_data.nreset_gpio = conf->nreset_gpio; + n800_mipid_platform_data.data_lines = conf->data_lines; + } +} + +static struct { + struct clk *sys_ck; +} blizzard; + +static int blizzard_get_clocks(void) +{ + blizzard.sys_ck = clk_get(0, "osc_ck"); + if (IS_ERR(blizzard.sys_ck)) { + printk(KERN_ERR "can't get Blizzard clock\n"); + return PTR_ERR(blizzard.sys_ck); + } + return 0; +} + +static unsigned long blizzard_get_clock_rate(struct device *dev) +{ + return clk_get_rate(blizzard.sys_ck); +} + +static void blizzard_enable_clocks(int enable) +{ + if (enable) + clk_enable(blizzard.sys_ck); + else + clk_disable(blizzard.sys_ck); +} + +static void blizzard_power_up(struct device *dev) +{ + /* Vcore to 1.475V */ + tahvo_set_clear_reg_bits(0x07, 0, 0xf); + msleep(10); + + blizzard_enable_clocks(1); + gpio_set_value(N800_BLIZZARD_POWERDOWN_GPIO, 1); +} + +static void blizzard_power_down(struct device *dev) +{ + gpio_set_value(N800_BLIZZARD_POWERDOWN_GPIO, 0); + blizzard_enable_clocks(0); + + /* Vcore to 1.005V */ + tahvo_set_clear_reg_bits(0x07, 0xf, 0); +} + +static struct blizzard_platform_data n800_blizzard_data = { + .power_up = blizzard_power_up, + .power_down = blizzard_power_down, + .get_clock_rate = blizzard_get_clock_rate, + .te_connected = 1, +}; + +static void __init blizzard_dev_init(void) +{ + int r; + + r = gpio_request(N800_BLIZZARD_POWERDOWN_GPIO, "Blizzard pd"); + if (r < 0) + return; + gpio_direction_output(N800_BLIZZARD_POWERDOWN_GPIO, 1); + + blizzard_get_clocks(); + omapfb_set_ctrl_platform_data(&n800_blizzard_data); +} + +static struct omap_board_config_kernel n800_config[] __initdata = { + { OMAP_TAG_UART, &n800_uart_config }, + { OMAP_TAG_FBMEM, &n800_fbmem0_config }, + { OMAP_TAG_FBMEM, &n800_fbmem1_config }, + { OMAP_TAG_FBMEM, &n800_fbmem2_config }, + { OMAP_TAG_TMP105, &n800_tmp105_config }, +}; + +static struct tsc2301_platform_data tsc2301_config = { + .reset_gpio = N800_TSC2301_RESET_GPIO, + .keymap = { + -1, /* Event for bit 0 */ + KEY_UP, /* Event for bit 1 (up) */ + KEY_F5, /* Event for bit 2 (home) */ + -1, /* Event for bit 3 */ + KEY_LEFT, /* Event for bit 4 (left) */ + KEY_ENTER, /* Event for bit 5 (enter) */ + KEY_RIGHT, /* Event for bit 6 (right) */ + -1, /* Event for bit 7 */ + KEY_ESC, /* Event for bit 8 (cycle) */ + KEY_DOWN, /* Event for bit 9 (down) */ + KEY_F4, /* Event for bit 10 (menu) */ + -1, /* Event for bit 11 */ + KEY_F8, /* Event for bit 12 (Zoom-) */ + KEY_F6, /* Event for bit 13 (FS) */ + KEY_F7, /* Event for bit 14 (Zoom+) */ + -1, /* Event for bit 15 */ + }, + .kp_rep = 0, + .keyb_name = "Internal keypad", +}; + +static void tsc2301_dev_init(void) +{ + int r; + int gpio = N800_KEYB_IRQ_GPIO; + + r = gpio_request(gpio, "tsc2301 KBD IRQ"); + if (r >= 0) { + gpio_direction_input(gpio); + tsc2301_config.keyb_int = gpio_to_irq(gpio); + } else { + printk(KERN_ERR "unable to get KBD GPIO"); + } + + gpio = N800_DAV_IRQ_GPIO; + r = gpio_request(gpio, "tsc2301 DAV IRQ"); + if (r >= 0) { + gpio_direction_input(gpio); + tsc2301_config.dav_int = gpio_to_irq(gpio); + } else { + printk(KERN_ERR "unable to get DAV GPIO"); + } +} + +static int __init tea5761_dev_init(void) +{ + const struct omap_tea5761_config *info; + int enable_gpio = 0; + + info = omap_get_config(OMAP_TAG_TEA5761, struct omap_tea5761_config); + if (info) + enable_gpio = info->enable_gpio; + + if (enable_gpio) { + pr_debug("Enabling tea5761 at GPIO %d\n", + enable_gpio); + + if (gpio_request(enable_gpio, "TEA5761 enable") < 0) { + printk(KERN_ERR "Can't request GPIO %d\n", + enable_gpio); + return -ENODEV; + } + + gpio_direction_output(enable_gpio, 0); + udelay(50); + gpio_set_value(enable_gpio, 1); + } + + return 0; +} + +static struct omap2_mcspi_device_config tsc2301_mcspi_config = { + .turbo_mode = 0, + .single_channel = 1, +}; + +static struct omap2_mcspi_device_config mipid_mcspi_config = { + .turbo_mode = 0, + .single_channel = 1, +}; + +static struct omap2_mcspi_device_config cx3110x_mcspi_config = { + .turbo_mode = 0, + .single_channel = 1, +}; + +#ifdef CONFIG_TOUCHSCREEN_TSC2005 +static struct tsc2005_platform_data tsc2005_config = { + .reset_gpio = 94, + .dav_gpio = 106 +}; + +static struct omap2_mcspi_device_config tsc2005_mcspi_config = { + .turbo_mode = 0, + .single_channel = 1, +}; +#endif + +static struct spi_board_info n800_spi_board_info[] __initdata = { + { + .modalias = "lcd_mipid", + .bus_num = 1, + .chip_select = 1, + .max_speed_hz = 4000000, + .controller_data= &mipid_mcspi_config, + .platform_data = &n800_mipid_platform_data, + }, { + .modalias = "cx3110x", + .bus_num = 2, + .chip_select = 0, + .max_speed_hz = 48000000, + .controller_data= &cx3110x_mcspi_config, + }, + { + .modalias = "tsc2301", + .bus_num = 1, + .chip_select = 0, + .max_speed_hz = 6000000, + .controller_data= &tsc2301_mcspi_config, + .platform_data = &tsc2301_config, + }, +}; + +static struct spi_board_info n810_spi_board_info[] __initdata = { + { + .modalias = "lcd_mipid", + .bus_num = 1, + .chip_select = 1, + .max_speed_hz = 4000000, + .controller_data = &mipid_mcspi_config, + .platform_data = &n800_mipid_platform_data, + }, + { + .modalias = "cx3110x", + .bus_num = 2, + .chip_select = 0, + .max_speed_hz = 48000000, + .controller_data = &cx3110x_mcspi_config, + }, + { + .modalias = "tsc2005", + .bus_num = 1, + .chip_select = 0, + .max_speed_hz = 6000000, + .controller_data = &tsc2005_mcspi_config, + .platform_data = &tsc2005_config, + }, +}; + +static void __init tsc2005_set_config(void) +{ + const struct omap_lcd_config *conf; + + conf = omap_get_config(OMAP_TAG_LCD, struct omap_lcd_config); + if (conf != NULL) { +#ifdef CONFIG_TOUCHSCREEN_TSC2005 + if (strcmp(conf->panel_name, "lph8923") == 0) { + tsc2005_config.ts_x_plate_ohm = 180; + tsc2005_config.ts_hw_avg = 0; + tsc2005_config.ts_ignore_last = 0; + tsc2005_config.ts_touch_pressure = 1500; + tsc2005_config.ts_stab_time = 100; + tsc2005_config.ts_pressure_max = 2048; + tsc2005_config.ts_pressure_fudge = 2; + tsc2005_config.ts_x_max = 4096; + tsc2005_config.ts_x_fudge = 4; + tsc2005_config.ts_y_max = 4096; + tsc2005_config.ts_y_fudge = 7; + } else if (strcmp(conf->panel_name, "ls041y3") == 0) { + tsc2005_config.ts_x_plate_ohm = 280; + tsc2005_config.ts_hw_avg = 0; + tsc2005_config.ts_ignore_last = 0; + tsc2005_config.ts_touch_pressure = 1500; + tsc2005_config.ts_stab_time = 1000; + tsc2005_config.ts_pressure_max = 2048; + tsc2005_config.ts_pressure_fudge = 2; + tsc2005_config.ts_x_max = 4096; + tsc2005_config.ts_x_fudge = 4; + tsc2005_config.ts_y_max = 4096; + tsc2005_config.ts_y_fudge = 7; + } else { + printk(KERN_ERR "Unknown panel type, set default " + "touchscreen configuration\n"); + tsc2005_config.ts_x_plate_ohm = 200; + tsc2005_config.ts_stab_time = 100; + } +#endif + } +} + +#if defined(CONFIG_CBUS_RETU) && defined(CONFIG_LEDS_OMAP_PWM) + +void retu_keypad_led_set_power(struct omap_pwm_led_platform_data *self, + int on_off) +{ + if (on_off) { + retu_write_reg(RETU_REG_CTRL_SET, 1 << 6); + msleep(2); + retu_write_reg(RETU_REG_CTRL_SET, 1 << 3); + } else { + retu_write_reg(RETU_REG_CTRL_CLR, (1 << 6) | (1 << 3)); + } +} + +static struct omap_pwm_led_platform_data n800_keypad_led_data = { + .name = "keypad", + .intensity_timer = 10, + .blink_timer = 9, + .set_power = retu_keypad_led_set_power, +}; + +static struct platform_device n800_keypad_led_device = { + .name = "omap_pwm_led", + .id = -1, + .dev = { + .platform_data = &n800_keypad_led_data, + }, +}; +#endif + +#if defined(CONFIG_TOUCHSCREEN_TSC2301) +static void __init n800_ts_set_config(void) +{ + const struct omap_lcd_config *conf; + + conf = omap_get_config(OMAP_TAG_LCD, struct omap_lcd_config); + if (conf != NULL) { + if (strcmp(conf->panel_name, "lph8923") == 0) { + tsc2301_config.ts_x_plate_ohm = 180; + tsc2301_config.ts_hw_avg = 8; + tsc2301_config.ts_max_pressure = 2048; + tsc2301_config.ts_touch_pressure = 400; + tsc2301_config.ts_stab_time = 100; + tsc2301_config.ts_pressure_fudge = 2; + tsc2301_config.ts_x_max = 4096; + tsc2301_config.ts_x_fudge = 4; + tsc2301_config.ts_y_max = 4096; + tsc2301_config.ts_y_fudge = 7; + } else if (strcmp(conf->panel_name, "ls041y3") == 0) { + tsc2301_config.ts_x_plate_ohm = 280; + tsc2301_config.ts_hw_avg = 8; + tsc2301_config.ts_touch_pressure = 400; + tsc2301_config.ts_max_pressure = 2048; + tsc2301_config.ts_stab_time = 1000; + tsc2301_config.ts_pressure_fudge = 2; + tsc2301_config.ts_x_max = 4096; + tsc2301_config.ts_x_fudge = 4; + tsc2301_config.ts_y_max = 4096; + tsc2301_config.ts_y_fudge = 7; + } else { + printk(KERN_ERR "Unknown panel type, set default " + "touchscreen configuration\n"); + tsc2301_config.ts_x_plate_ohm = 200; + tsc2301_config.ts_stab_time = 100; + } + } +} +#else +static inline void n800_ts_set_config(void) +{ +} +#endif + +static struct omap_gpio_switch n800_gpio_switches[] __initdata = { + { + .name = "bat_cover", + .gpio = -1, + .debounce_rising = 100, + .debounce_falling = 0, + .notify = n800_mmc_slot1_cover_handler, + .notify_data = NULL, + }, { + .name = "headphone", + .gpio = -1, + .debounce_rising = 200, + .debounce_falling = 200, + }, { + .name = "cam_act", + .gpio = -1, + .debounce_rising = 200, + .debounce_falling = 200, + }, { + .name = "cam_turn", + .gpio = -1, + .debounce_rising = 100, + .debounce_falling = 100, + }, +}; + +#if defined(CONFIG_CBUS_RETU_HEADSET) +static struct platform_device retu_headset_device = { + .name = "retu-headset", + .id = -1, +}; +#endif + +static struct platform_device *n800_devices[] __initdata = { +#if defined(CONFIG_CBUS_RETU) && defined(CONFIG_LEDS_OMAP_PWM) + &n800_keypad_led_device, +#endif +#if defined(CONFIG_CBUS_RETU_HEADSET) + &retu_headset_device, +#endif +}; + +#ifdef CONFIG_MENELAUS +static int n800_auto_sleep_regulators(void) +{ + u32 val; + int ret; + + val = EN_VPLL_SLEEP | EN_VMMC_SLEEP \ + | EN_VAUX_SLEEP | EN_VIO_SLEEP \ + | EN_VMEM_SLEEP | EN_DC3_SLEEP \ + | EN_VC_SLEEP | EN_DC2_SLEEP; + + ret = menelaus_set_regulator_sleep(1, val); + if (ret < 0) { + printk(KERN_ERR "Could not set regulators to sleep on " + "menelaus: %u\n", ret); + return ret; + } + return 0; +} + +static int n800_auto_voltage_scale(void) +{ + int ret; + + ret = menelaus_set_vcore_hw(1400, 1050); + if (ret < 0) { + printk(KERN_ERR "Could not set VCORE voltage on " + "menelaus: %u\n", ret); + return ret; + } + return 0; +} + +static int n800_menelaus_init(struct device *dev) +{ + int ret; + + ret = n800_auto_voltage_scale(); + if (ret < 0) + return ret; + ret = n800_auto_sleep_regulators(); + if (ret < 0) + return ret; + return 0; +} + +static struct menelaus_platform_data n800_menelaus_platform_data = { + .late_init = n800_menelaus_init, +}; +#endif + +static struct i2c_board_info __initdata n800_i2c_board_info_1[] = { + { + I2C_BOARD_INFO("menelaus", 0x72), + .irq = INT_24XX_SYS_NIRQ, + .platform_data = &n800_menelaus_platform_data, + }, +}; + +static struct lp5521_platform_data n810_lp5521_platform_data = { + .mode = LP5521_MODE_DIRECT_CONTROL, + .label = "n810", + .red_present = true, + .green_present = true, + .blue_present = true, +}; + +extern struct tcm825x_platform_data n800_tcm825x_platform_data; + +static struct i2c_board_info __initdata_or_module n8x0_i2c_board_info_2[] = { + { + I2C_BOARD_INFO(TCM825X_NAME, TCM825X_I2C_ADDR), +#if defined (CONFIG_VIDEO_TCM825X) || defined (CONFIG_VIDEO_TCM825X_MODULE) + .platform_data = &n800_tcm825x_platform_data, +#endif + }, +}; + + +static struct i2c_board_info __initdata_or_module n800_i2c_board_info_2[] = { + { + I2C_BOARD_INFO("tea5761", 0x10), + }, +}; + +static struct i2c_board_info __initdata_or_module n810_i2c_board_info_2[] = { + { + I2C_BOARD_INFO("lm8323", 0x45), + .irq = OMAP_GPIO_IRQ(109), + .platform_data = &lm8323_pdata, + }, + { + I2C_BOARD_INFO("tsl2563", 0x29), + }, + { + I2C_BOARD_INFO("lp5521", 0x32), + .platform_data = &n810_lp5521_platform_data, + }, +}; + +void __init nokia_n800_common_init(void) +{ + platform_add_devices(n800_devices, ARRAY_SIZE(n800_devices)); + + n800_flash_init(); + n800_mmc_init(); + n800_bt_init(); + n800_dsp_init(); + n800_usb_init(); + n800_cam_init(); + if (machine_is_nokia_n800()) + spi_register_board_info(n800_spi_board_info, + ARRAY_SIZE(n800_spi_board_info)); + if (machine_is_nokia_n810()) { + tsc2005_set_config(); + spi_register_board_info(n810_spi_board_info, + ARRAY_SIZE(n810_spi_board_info)); + } + omap_serial_init(); + omap_register_i2c_bus(1, 400, n800_i2c_board_info_1, + ARRAY_SIZE(n800_i2c_board_info_1)); + omap_register_i2c_bus(2, 400, n8x0_i2c_board_info_2, + ARRAY_SIZE(n8x0_i2c_board_info_2)); + if (machine_is_nokia_n800()) + i2c_register_board_info(2, n800_i2c_board_info_2, + ARRAY_SIZE(n800_i2c_board_info_2)); + if (machine_is_nokia_n810()) + i2c_register_board_info(2, n810_i2c_board_info_2, + ARRAY_SIZE(n810_i2c_board_info_2)); + + mipid_dev_init(); + blizzard_dev_init(); +} + +static void __init nokia_n800_init(void) +{ + nokia_n800_common_init(); + + n800_ts_set_config(); + tsc2301_dev_init(); + tea5761_dev_init(); + omap_register_gpio_switches(n800_gpio_switches, + ARRAY_SIZE(n800_gpio_switches)); +} + +void __init nokia_n800_map_io(void) +{ + omap_board_config = n800_config; + omap_board_config_size = ARRAY_SIZE(n800_config); + + omap2_set_globals_242x(); + omap2_map_common_io(); +} + +MACHINE_START(NOKIA_N800, "Nokia N800") + .phys_io = 0x48000000, + .io_pg_offst = ((0xd8000000) >> 18) & 0xfffc, + .boot_params = 0x80000100, + .map_io = nokia_n800_map_io, + .init_irq = nokia_n800_init_irq, + .init_machine = nokia_n800_init, + .timer = &omap_timer, +MACHINE_END diff --git a/arch/arm/mach-omap2/board-n800.h b/arch/arm/mach-omap2/board-n800.h new file mode 100644 index 00000000000..e71dae4722a --- /dev/null +++ b/arch/arm/mach-omap2/board-n800.h @@ -0,0 +1,23 @@ +/* + * linux/arch/arm/mach-omap2/board-n800.h + * + * Copyright (C) 2005-2007 Nokia Corporation + * Author: Lauri Leukkunen + * + * Modified from mach-omap2/board-n800.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __ARCH_ARM_MACH_OMAP2_BOARD_N800_H +#define __ARCH_ARM_MACH_OMAP2_BOARD_N800_H + +void __init nokia_n800_common_init(void); +void __init nokia_n800_map_io(void); +void __init nokia_n800_init_irq(void); + +extern const struct tcm825x_platform_data n800_tcm825x_platform_data; + +#endif diff --git a/arch/arm/mach-omap2/board-n810.c b/arch/arm/mach-omap2/board-n810.c new file mode 100644 index 00000000000..7984d43aeec --- /dev/null +++ b/arch/arm/mach-omap2/board-n810.c @@ -0,0 +1,47 @@ +/* + * linux/arch/arm/mach-omap2/board-n810.c + * + * Copyright (C) 2007 Nokia + * Author: Lauri Leukkunen + * + * 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 +#include +#include + +#include +#include +#include +#include +#include + +#include "board-n800.h" + +static void __init nokia_n810_init(void) +{ + nokia_n800_common_init(); +} + +MACHINE_START(NOKIA_N810, "Nokia N810") + .phys_io = 0x48000000, + .io_pg_offst = ((0xd8000000) >> 18) & 0xfffc, + .boot_params = 0x80000100, + .map_io = nokia_n800_map_io, + .init_irq = nokia_n800_init_irq, + .init_machine = nokia_n810_init, + .timer = &omap_timer, +MACHINE_END + +MACHINE_START(NOKIA_N810_WIMAX, "Nokia N810 WiMAX") + .phys_io = 0x48000000, + .io_pg_offst = ((0xd8000000) >> 18) & 0xfffc, + .boot_params = 0x80000100, + .map_io = nokia_n800_map_io, + .init_irq = nokia_n800_init_irq, + .init_machine = nokia_n810_init, + .timer = &omap_timer, +MACHINE_END diff --git a/arch/arm/mach-omap2/board-omap2evm.c b/arch/arm/mach-omap2/board-omap2evm.c new file mode 100644 index 00000000000..86477ccc996 --- /dev/null +++ b/arch/arm/mach-omap2/board-omap2evm.c @@ -0,0 +1,385 @@ +/* + * linux/arch/arm/mach-omap2/board-omap2evm.c + * + * Copyright (C) 2008 Mistral Solutions Pvt Ltd + * + * Modified from mach-omap2/board-generic.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mmc-twl4030.h" + +#define OMAP2EVM_ETHR_START 0x2c000000 +#define OMAP2EVM_ETHR_SIZE 1024 +#define OMAP2EVM_ETHR_GPIO_IRQ 149 +#define OMAP2_EVM_TS_GPIO 85 + +#define GPMC_OFF_CONFIG1_0 0x60 + +static struct mtd_partition omap2evm_nand_partitions[] = { + { + .name = "X-Loader", + .offset = 0, + .size = 1 * (64 * 2048), + .mask_flags = MTD_WRITEABLE, /* force read-only */ + }, + { + .name = "U-Boot", + .offset = MTDPART_OFS_APPEND, + .size = 3 * (64 * 2048), + .mask_flags = MTD_WRITEABLE, /* force read-only */ + }, + { + .name = "U-Boot Environment", + .offset = MTDPART_OFS_APPEND, + .size = 1 * (64 * 2048), + }, + { + .name = "Kernel", + .offset = MTDPART_OFS_APPEND, + .size = 16 * (64 * 2048), /* 2MB */ + }, + { + .name = "Ramdisk", + .offset = MTDPART_OFS_APPEND, + .size = 32 * (64 * 2048), /* 4MB */ + }, + { + .name = "Filesystem", + .offset = MTDPART_OFS_APPEND, + .size = MTDPART_SIZ_FULL, + } +}; + +static struct omap_nand_platform_data omap2evm_nand_data = { + .parts = omap2evm_nand_partitions, + .nr_parts = ARRAY_SIZE(omap2evm_nand_partitions), + .dma_channel = -1, /* disable DMA in OMAP NAND driver */ +}; + +static struct resource omap2evm_nand_resource = { + .flags = IORESOURCE_MEM, +}; + +static struct platform_device omap2evm_nand_device = { + .name = "omap2-nand", + .id = -1, + .dev = { + .platform_data = &omap2evm_nand_data, + }, + .num_resources = 1, + .resource = &omap2evm_nand_resource, +}; + +void __init omap2evm_flash_init(void) +{ + void __iomem *gpmc_base_add, *gpmc_cs_base_add; + unsigned char cs = 0; + + gpmc_base_add = (__force void __iomem *)OMAP243X_GPMC_VIRT; + while (cs < GPMC_CS_NUM) { + int ret = 0; + + /* Each GPMC set for a single CS is at offset 0x30 */ + gpmc_cs_base_add = (gpmc_base_add + GPMC_OFF_CONFIG1_0 + + (cs * 0x30)); + + /* xloader/Uboot would have programmed the NAND + * base address for us This is a ugly hack. The proper + * way of doing this is to pass the setup of u-boot up + * to kernel using kernel params - something on the + * lines of machineID. Check if Nand is + * configured */ + ret = __raw_readl(gpmc_cs_base_add + GPMC_CS_CONFIG1); + if ((ret & 0xC00) == (0x800)) { + /* Found it!! */ + printk(KERN_INFO "NAND: Found NAND on CS %d \n", cs); + break; + } + cs++; + } + if (cs >= GPMC_CS_NUM) { + printk(KERN_INFO "MTD: Unable to find MTD configuration in " + "GPMC - not registering.\n"); + return; + } + + omap2evm_nand_data.cs = cs; + omap2evm_nand_data.gpmc_cs_baseaddr = gpmc_cs_base_add; + omap2evm_nand_data.gpmc_baseaddr = gpmc_base_add; + + if (platform_device_register(&omap2evm_nand_device) < 0) { + printk(KERN_ERR "Unable to register NAND device\n"); + return; + } +} + +static struct resource omap2evm_smc911x_resources[] = { + [0] = { + .start = OMAP2EVM_ETHR_START, + .end = (OMAP2EVM_ETHR_START + OMAP2EVM_ETHR_SIZE - 1), + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = OMAP_GPIO_IRQ(OMAP2EVM_ETHR_GPIO_IRQ), + .end = OMAP_GPIO_IRQ(OMAP2EVM_ETHR_GPIO_IRQ), + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device omap2evm_smc911x_device = { + .name = "smc911x", + .id = -1, + .num_resources = ARRAY_SIZE(omap2evm_smc911x_resources), + .resource = &omap2evm_smc911x_resources [0], +}; + +static inline void __init omap2evm_init_smc911x(void) +{ + int gpio = OMAP2EVM_ETHR_GPIO_IRQ; + int ret; + + ret = gpio_request(gpio, "smc911x IRQ"); + if (ret < 0) { + printk(KERN_ERR "Failed to request GPIO %d for smc911x IRQ\n", + gpio); + return; + } + gpio_direction_input(gpio); + +} + +static struct platform_device omap2_evm_lcd_device = { + .name = "omap2evm_lcd", + .id = -1, +}; + +static struct omap_lcd_config omap2_evm_lcd_config __initdata = { + .ctrl_name = "internal", +}; + +static void ads7846_dev_init(void) +{ + int gpio = OMAP2_EVM_TS_GPIO; + int ret; + + ret = gpio_request(gpio, "ads7846_pen_down"); + if (ret < 0) { + printk(KERN_ERR "Failed to request GPIO %d for ads7846 pen down IRQ\n", + gpio); + return; + } + + gpio_direction_input(gpio); + + /*Setting the MUX */ + omap_cfg_reg(Y18_2430_MCSPI1_CLK); + omap_cfg_reg(AD15_2430_MCSPI1_SIMO); + omap_cfg_reg(AE17_2430_MCSPI1_SOMI); + omap_cfg_reg(U1_2430_MCSPI1_CS0); + + omap_cfg_reg(AF19_2430_GPIO_85); + +} + +static int ads7846_get_pendown_state(void) +{ + return !gpio_get_value(OMAP2_EVM_TS_GPIO); +} + +struct ads7846_platform_data ads7846_config = { + .x_max = 0x0fff, + .y_max = 0x0fff, + .x_plate_ohms = 180, + .pressure_max = 255, + .debounce_max = 10, + .debounce_tol = 3, + .debounce_rep = 1, + .get_pendown_state = ads7846_get_pendown_state, + .keep_vref_on = 1, + .settle_delay_usecs = 150, +}; + +static struct omap2_mcspi_device_config ads7846_mcspi_config = { + .turbo_mode = 0, + .single_channel = 1, /* 0: slave, 1: master */ +}; + +struct spi_board_info omap2evm_spi_board_info[] = { + [0] = { + .modalias = "ads7846", + .bus_num = 1, + .chip_select = 0, + .max_speed_hz = 1500000, + .controller_data = &ads7846_mcspi_config, + .irq = OMAP_GPIO_IRQ(OMAP2_EVM_TS_GPIO), + .platform_data = &ads7846_config, + }, +}; + + +static int omap2evm_keymap[] = { + KEY(0, 0, KEY_LEFT), + KEY(0, 1, KEY_RIGHT), + KEY(0, 2, KEY_A), + KEY(0, 3, KEY_B), + KEY(1, 0, KEY_DOWN), + KEY(1, 1, KEY_UP), + KEY(1, 2, KEY_E), + KEY(1, 3, KEY_F), + KEY(2, 0, KEY_ENTER), + KEY(2, 1, KEY_I), + KEY(2, 2, KEY_J), + KEY(2, 3, KEY_K), + KEY(3, 0, KEY_M), + KEY(3, 1, KEY_N), + KEY(3, 2, KEY_O), + KEY(3, 3, KEY_P) +}; + +static struct twl4030_keypad_data omap2evm_kp_data = { + .rows = 4, + .cols = 4, + .keymap = omap2evm_keymap, + .keymapsize = ARRAY_SIZE(omap2evm_keymap), + .rep = 1, +}; + +static void __init omap2_evm_init_irq(void) +{ + omap2_init_common_hw(NULL); + omap_init_irq(); + omap_gpio_init(); + omap2evm_init_smc911x(); +} + +static struct omap_uart_config omap2_evm_uart_config __initdata = { + .enabled_uarts = ((1 << 0) | (1 << 1) | (1 << 2)), +}; + +static struct omap_board_config_kernel omap2_evm_config[] __initdata = { + { OMAP_TAG_UART, &omap2_evm_uart_config }, + { OMAP_TAG_LCD, &omap2_evm_lcd_config }, +}; + +static struct twl4030_gpio_platform_data omap2evm_gpio_data = { + .gpio_base = OMAP_MAX_GPIO_LINES, + .irq_base = TWL4030_GPIO_IRQ_BASE, + .irq_end = TWL4030_GPIO_IRQ_END, +}; + +static struct twl4030_usb_data omap2evm_usb_data = { + .usb_mode = T2_USB_MODE_ULPI, +}; + +static struct twl4030_madc_platform_data omap2evm_madc_data = { + .irq_line = 1, +}; + +static struct twl4030_platform_data omap2evm_twldata = { + .irq_base = TWL4030_IRQ_BASE, + .irq_end = TWL4030_IRQ_END, + + /* platform_data for children goes here */ + .keypad = &omap2evm_kp_data, + .madc = &omap2evm_madc_data, + .usb = &omap2evm_usb_data, + .gpio = &omap2evm_gpio_data, +}; + +static struct i2c_board_info __initdata omap2evm_i2c_boardinfo[] = { + { + I2C_BOARD_INFO("twl4030", 0x48), + .flags = I2C_CLIENT_WAKE, + .irq = INT_24XX_SYS_NIRQ, + .platform_data = &omap2evm_twldata, + }, +}; + +static int __init omap2_evm_i2c_init(void) +{ + omap_register_i2c_bus(1, 400, NULL, 0); + omap_register_i2c_bus(2, 2600, omap2evm_i2c_boardinfo, + ARRAY_SIZE(omap2evm_i2c_boardinfo)); + return 0; +} + +static struct platform_device *omap2_evm_devices[] __initdata = { + &omap2_evm_lcd_device, + &omap2evm_smc911x_device, +}; + +static struct twl4030_hsmmc_info mmc[] __initdata = { + { + .mmc = 1, + .wires = 4, + .gpio_cd = -EINVAL, + .gpio_wp = -EINVAL, + }, + {} /* Terminator */ +}; + +static void __init omap2_evm_init(void) +{ + omap2_evm_i2c_init(); + + platform_add_devices(omap2_evm_devices, ARRAY_SIZE(omap2_evm_devices)); + omap_board_config = omap2_evm_config; + omap_board_config_size = ARRAY_SIZE(omap2_evm_config); + spi_register_board_info(omap2evm_spi_board_info, + ARRAY_SIZE(omap2evm_spi_board_info)); + omap_serial_init(); + twl4030_mmc_init(mmc); + omap2evm_flash_init(); + ads7846_dev_init(); +} + +static void __init omap2_evm_map_io(void) +{ + omap2_set_globals_243x(); + omap2_map_common_io(); +} + +MACHINE_START(OMAP2EVM, "OMAP2EVM Board") + /* Maintainer: Arun KS */ + .phys_io = 0x48000000, + .io_pg_offst = ((0xd8000000) >> 18) & 0xfffc, + .boot_params = 0x80000100, + .map_io = omap2_evm_map_io, + .init_irq = omap2_evm_init_irq, + .init_machine = omap2_evm_init, + .timer = &omap_timer, +MACHINE_END diff --git a/arch/arm/mach-omap2/board-omap3beagle.c b/arch/arm/mach-omap2/board-omap3beagle.c index 744740ae1b9..346351e0fe8 100644 --- a/arch/arm/mach-omap2/board-omap3beagle.c +++ b/arch/arm/mach-omap2/board-omap3beagle.c @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -37,14 +38,16 @@ #include #include +#include #include #include #include #include -#include +#include "twl4030-generic-scripts.h" #include "mmc-twl4030.h" + #define GPMC_CS0_BASE 0x60 #define GPMC_CS_SIZE 0x30 @@ -104,10 +107,16 @@ static struct platform_device omap3beagle_nand_device = { .resource = &omap3beagle_nand_resource, }; +#include "sdram-micron-mt46h32m32lf-6.h" + static struct omap_uart_config omap3_beagle_uart_config __initdata = { .enabled_uarts = ((1 << 0) | (1 << 1) | (1 << 2)), }; +static struct twl4030_usb_data beagle_usb_data = { + .usb_mode = T2_USB_MODE_ULPI, +}; + static struct twl4030_hsmmc_info mmc[] = { { .mmc = 1, @@ -117,6 +126,14 @@ static struct twl4030_hsmmc_info mmc[] = { {} /* Terminator */ }; +static struct regulator_consumer_supply beagle_vmmc1_supply = { + .supply = "vmmc", +}; + +static struct regulator_consumer_supply beagle_vsim_supply = { + .supply = "vmmc_aux", +}; + static struct gpio_led gpio_leds[]; static int beagle_twl_gpio_setup(struct device *dev, @@ -127,6 +144,10 @@ static int beagle_twl_gpio_setup(struct device *dev, mmc[0].gpio_cd = gpio + 0; twl4030_mmc_init(mmc); + /* link regulators to MMC adapters */ + beagle_vmmc1_supply.dev = mmc[0].dev; + beagle_vsim_supply.dev = mmc[0].dev; + /* REVISIT: need ehci-omap hooks for external VBUS * power switch and overcurrent detect */ @@ -155,12 +176,114 @@ static struct twl4030_gpio_platform_data beagle_gpio_data = { .setup = beagle_twl_gpio_setup, }; +static struct platform_device omap3_beagle_lcd_device = { + .name = "omap3beagle_lcd", + .id = -1, +}; + +static struct regulator_consumer_supply beagle_vdac_supply = { + .supply = "vdac", + .dev = &omap3_beagle_lcd_device.dev, +}; + +static struct regulator_consumer_supply beagle_vdvi_supply = { + .supply = "vdvi", + .dev = &omap3_beagle_lcd_device.dev, +}; + +/* VMMC1 for MMC1 pins CMD, CLK, DAT0..DAT3 (20 mA, plus card == max 220 mA) */ +static struct regulator_init_data beagle_vmmc1 = { + .constraints = { + .min_uV = 1850000, + .max_uV = 3150000, + .valid_modes_mask = REGULATOR_MODE_NORMAL + | REGULATOR_MODE_STANDBY, + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE + | REGULATOR_CHANGE_MODE + | REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = 1, + .consumer_supplies = &beagle_vmmc1_supply, +}; + +/* VSIM for MMC1 pins DAT4..DAT7 (2 mA, plus card == max 50 mA) */ +static struct regulator_init_data beagle_vsim = { + .constraints = { + .min_uV = 1800000, + .max_uV = 3000000, + .valid_modes_mask = REGULATOR_MODE_NORMAL + | REGULATOR_MODE_STANDBY, + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE + | REGULATOR_CHANGE_MODE + | REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = 1, + .consumer_supplies = &beagle_vsim_supply, +}; + +/* VDAC for DSS driving S-Video (8 mA unloaded, max 65 mA) */ +static struct regulator_init_data beagle_vdac = { + .constraints = { + .min_uV = 1800000, + .max_uV = 1800000, + .valid_modes_mask = REGULATOR_MODE_NORMAL + | REGULATOR_MODE_STANDBY, + .valid_ops_mask = REGULATOR_CHANGE_MODE + | REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = 1, + .consumer_supplies = &beagle_vdac_supply, +}; + +/* VPLL2 for digital video outputs */ +static struct regulator_init_data beagle_vpll2 = { + .constraints = { + .name = "VDVI", + .min_uV = 1800000, + .max_uV = 1800000, + .valid_modes_mask = REGULATOR_MODE_NORMAL + | REGULATOR_MODE_STANDBY, + .valid_ops_mask = REGULATOR_CHANGE_MODE + | REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = 1, + .consumer_supplies = &beagle_vdvi_supply, +}; + +static const struct twl4030_resconfig beagle_resconfig[] = { + /* disable regulators that u-boot left enabled; the + * devices' drivers should be managing these. + */ + { .resource = RES_VAUX3, }, /* not even connected! */ + { .resource = RES_VMMC1, }, + { .resource = RES_VSIM, }, + { .resource = RES_VPLL2, }, + { .resource = RES_VDAC, }, + { .resource = RES_VUSB_1V5, }, + { .resource = RES_VUSB_1V8, }, + { .resource = RES_VUSB_3V1, }, + { 0, }, +}; + +static struct twl4030_power_data beagle_power_data = { + .resource_config = beagle_resconfig, + /* REVISIT can't use GENERIC3430_T2SCRIPTS_DATA; + * among other things, it makes reboot fail. + */ +}; + static struct twl4030_platform_data beagle_twldata = { .irq_base = TWL4030_IRQ_BASE, .irq_end = TWL4030_IRQ_END, /* platform_data for children goes here */ + .usb = &beagle_usb_data, .gpio = &beagle_gpio_data, + .power = &beagle_power_data, + .vmmc1 = &beagle_vmmc1, + .vsim = &beagle_vsim, + .vdac = &beagle_vdac, + .vpll2 = &beagle_vpll2, }; static struct i2c_board_info __initdata beagle_i2c_boardinfo[] = { @@ -184,16 +307,11 @@ static int __init omap3_beagle_i2c_init(void) static void __init omap3_beagle_init_irq(void) { - omap2_init_common_hw(NULL); + omap2_init_common_hw(mt46h32m32lf6_sdrc_params); omap_init_irq(); omap_gpio_init(); } -static struct platform_device omap3_beagle_lcd_device = { - .name = "omap3beagle_lcd", - .id = -1, -}; - static struct omap_lcd_config omap3_beagle_lcd_config __initdata = { .ctrl_name = "internal", }; @@ -315,6 +433,7 @@ static void __init omap3_beagle_init(void) gpio_direction_output(170, true); usb_musb_init(); + usb_ehci_init(); omap3beagle_flash_init(); } diff --git a/arch/arm/mach-omap2/board-omap3evm-flash.c b/arch/arm/mach-omap2/board-omap3evm-flash.c new file mode 100644 index 00000000000..b58b7c6b1af --- /dev/null +++ b/arch/arm/mach-omap2/board-omap3evm-flash.c @@ -0,0 +1,192 @@ +/* + * board-omap3evm-flash.c + * + * Copyright (c) 2008 Texas Instruments, + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define ONENAND_MAP 0x20000000 + +static int omap3evm_onenand_setup(void __iomem *, int freq); + +static struct mtd_partition omap3evm_onenand_partitions[] = { + { + .name = "xloader-onenand", + .offset = 0, + .size = 4*(64*2048), + .mask_flags = MTD_WRITEABLE + }, + { + .name = "uboot-onenand", + .offset = MTDPART_OFS_APPEND, + .size = 15*(64*2048), + .mask_flags = MTD_WRITEABLE + }, + { + .name = "params-onenand", + .offset = MTDPART_OFS_APPEND, + .size = 1*(64*2048), + }, + { + .name = "linux-onenand", + .offset = MTDPART_OFS_APPEND, + .size = 40*(64*2048), + }, + { + .name = "jffs2-onenand", + .offset = MTDPART_OFS_APPEND, + .size = MTDPART_SIZ_FULL, + }, +}; + +static struct omap_onenand_platform_data omap3evm_onenand_data = { + .parts = omap3evm_onenand_partitions, + .nr_parts = ARRAY_SIZE(omap3evm_onenand_partitions), + .onenand_setup = omap3evm_onenand_setup, + .dma_channel = -1, /* disable DMA in OMAP OneNAND driver */ +}; + +static struct platform_device omap3evm_onenand_device = { + .name = "omap2-onenand", + .id = -1, + .dev = { + .platform_data = &omap3evm_onenand_data, + }, +}; + +static struct mtd_partition omap3evm_nand_partitions[] = { + /* All the partition sizes are listed in terms of NAND block size */ + { + .name = "xloader-nand", + .offset = 0, + .size = 4*(128 * 1024), + .mask_flags = MTD_WRITEABLE + }, + { + .name = "uboot-nand", + .offset = MTDPART_OFS_APPEND, + .size = 14*(128 * 1024), + .mask_flags = MTD_WRITEABLE + }, + { + .name = "params-nand", + + .offset = MTDPART_OFS_APPEND, + .size = 2*(128 * 1024) + }, + { + .name = "linux-nand", + .offset = MTDPART_OFS_APPEND, + .size = 40*(128 * 1024) + }, + { + .name = "jffs2-nand", + .size = MTDPART_SIZ_FULL, + .offset = MTDPART_OFS_APPEND, + }, +}; + +static struct omap_nand_platform_data omap3evm_nand_data = { + .parts = omap3evm_nand_partitions, + .nr_parts = ARRAY_SIZE(omap3evm_nand_partitions), + .nand_setup = NULL, + .dma_channel = -1, /* disable DMA in OMAP NAND driver */ + .dev_ready = NULL, +}; + +static struct resource omap3evm_nand_resource = { + .flags = IORESOURCE_MEM, +}; + +static struct platform_device omap3evm_nand_device = { + .name = "omap2-nand", + .id = 0, + .dev = { + .platform_data = &omap3evm_nand_data, + }, + .num_resources = 1, + .resource = &omap3evm_nand_resource, +}; + +/* + * omap3evm_onenand_setup - Set the onenand sync mode + * @onenand_base: The onenand base address in GPMC memory map + * + */ + +static int omap3evm_onenand_setup(void __iomem *onenand_base, int freq) +{ + /* nothing is required to be setup for onenand as of now */ + return 0; +} + +void __init omap3evm_flash_init(void) +{ + u8 cs = 0; + u8 onenandcs = GPMC_CS_NUM + 1, nandcs = GPMC_CS_NUM + 1; + u32 gpmc_base_add = OMAP34XX_GPMC_VIRT; + + while (cs < GPMC_CS_NUM) { + u32 ret = 0; + ret = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG1); + + /* + * xloader/Uboot would have programmed the NAND/oneNAND + * base address for us This is a ugly hack. The proper + * way of doing this is to pass the setup of u-boot up + * to kernel using kernel params - something on the + * lines of machineID. Check if NAND/oneNAND is configured + */ + if ((ret & 0xC00) == 0x800) { + /* Found it!! */ + if (nandcs > GPMC_CS_NUM) + nandcs = cs; + } else { + ret = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG7); + if ((ret & 0x3F) == (ONENAND_MAP >> 24)) + onenandcs = cs; + } + cs++; + } + if ((nandcs > GPMC_CS_NUM) && (onenandcs > GPMC_CS_NUM)) { + printk(KERN_INFO "NAND/OneNAND: Unable to find configuration " + " in GPMC\n "); + return; + } + + if (nandcs < GPMC_CS_NUM) { + omap3evm_nand_data.cs = nandcs; + omap3evm_nand_data.gpmc_cs_baseaddr = (void *)(gpmc_base_add + + GPMC_CS0_BASE + nandcs*GPMC_CS_SIZE); + omap3evm_nand_data.gpmc_baseaddr = (void *) (gpmc_base_add); + + if (platform_device_register(&omap3evm_nand_device) < 0) { + printk(KERN_ERR "Unable to register NAND device\n"); + } + } + + if (onenandcs < GPMC_CS_NUM) { + omap3evm_onenand_data.cs = onenandcs; + if (platform_device_register(&omap3evm_onenand_device) < 0) + printk(KERN_ERR "Unable to register OneNAND device\n"); + } +} + diff --git a/arch/arm/mach-omap2/board-omap3evm.c b/arch/arm/mach-omap2/board-omap3evm.c new file mode 100644 index 00000000000..6eb3c523091 --- /dev/null +++ b/arch/arm/mach-omap2/board-omap3evm.c @@ -0,0 +1,333 @@ +/* + * linux/arch/arm/mach-omap2/board-omap3evm.c + * + * Copyright (C) 2008 Texas Instruments + * + * Modified from mach-omap2/board-3430sdp.c + * + * Initial code: Syed Mohammed Khasim + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "sdram-micron-mt46h32m32lf-6.h" +#include "twl4030-generic-scripts.h" +#include "mmc-twl4030.h" + +#define OMAP3_EVM_TS_GPIO 175 + +#define OMAP3EVM_ETHR_START 0x2c000000 +#define OMAP3EVM_ETHR_SIZE 1024 +#define OMAP3EVM_ETHR_GPIO_IRQ 176 +#define OMAP3EVM_SMC911X_CS 5 + +extern void omap3evm_flash_init(void); + +static struct resource omap3evm_smc911x_resources[] = { + [0] = { + .start = OMAP3EVM_ETHR_START, + .end = (OMAP3EVM_ETHR_START + OMAP3EVM_ETHR_SIZE - 1), + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = OMAP_GPIO_IRQ(OMAP3EVM_ETHR_GPIO_IRQ), + .end = OMAP_GPIO_IRQ(OMAP3EVM_ETHR_GPIO_IRQ), + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device omap3evm_smc911x_device = { + .name = "smc911x", + .id = -1, + .num_resources = ARRAY_SIZE(omap3evm_smc911x_resources), + .resource = &omap3evm_smc911x_resources [0], +}; + +static inline void __init omap3evm_init_smc911x(void) +{ + int eth_cs; + struct clk *l3ck; + unsigned int rate; + + eth_cs = OMAP3EVM_SMC911X_CS; + + l3ck = clk_get(NULL, "l3_ck"); + if (IS_ERR(l3ck)) + rate = 100000000; + else + rate = clk_get_rate(l3ck); + + if (gpio_request(OMAP3EVM_ETHR_GPIO_IRQ, "SMC911x irq") < 0) { + printk(KERN_ERR "Failed to request GPIO%d for smc911x IRQ\n", + OMAP3EVM_ETHR_GPIO_IRQ); + return; + } + + gpio_direction_input(OMAP3EVM_ETHR_GPIO_IRQ); +} + +static struct omap_uart_config omap3_evm_uart_config __initdata = { + .enabled_uarts = ((1 << 0) | (1 << 1) | (1 << 2)), +}; + +static struct twl4030_hsmmc_info mmc[] = { + { + .mmc = 1, + .wires = 4, + .gpio_cd = -EINVAL, + .gpio_wp = 63, + }, + {} /* Terminator */ +}; + +static struct gpio_led gpio_leds[] = { + { + .name = "omap3evm::ledb", + /* normally not visible (board underside) */ + .default_trigger = "default-on", + .gpio = -EINVAL, /* gets replaced */ + .active_low = true, + }, +}; + +static struct gpio_led_platform_data gpio_led_info = { + .leds = gpio_leds, + .num_leds = ARRAY_SIZE(gpio_leds), +}; + +static struct platform_device leds_gpio = { + .name = "leds-gpio", + .id = -1, + .dev = { + .platform_data = &gpio_led_info, + }, +}; + + +static int omap3evm_twl_gpio_setup(struct device *dev, + unsigned gpio, unsigned ngpio) +{ + /* gpio + 0 is "mmc0_cd" (input/IRQ) */ + omap_cfg_reg(L8_34XX_GPIO63); + mmc[0].gpio_cd = gpio + 0; + twl4030_mmc_init(mmc); + + /* Most GPIOs are for USB OTG. Some are mostly sent to + * the P2 connector; notably LEDA for the LCD backlight. + */ + + /* TWL4030_GPIO_MAX + 1 == ledB (out, active low LED) */ + gpio_leds[2].gpio = gpio + TWL4030_GPIO_MAX + 1; + + platform_device_register(&leds_gpio); + + return 0; +} + +static struct twl4030_gpio_platform_data omap3evm_gpio_data = { + .gpio_base = OMAP_MAX_GPIO_LINES, + .irq_base = TWL4030_GPIO_IRQ_BASE, + .irq_end = TWL4030_GPIO_IRQ_END, + .use_leds = true, + .setup = omap3evm_twl_gpio_setup, +}; + +static struct twl4030_usb_data omap3evm_usb_data = { + .usb_mode = T2_USB_MODE_ULPI, +}; + +static int omap3evm_keymap[] = { + KEY(0, 0, KEY_LEFT), + KEY(0, 1, KEY_RIGHT), + KEY(0, 2, KEY_A), + KEY(0, 3, KEY_B), + KEY(1, 0, KEY_DOWN), + KEY(1, 1, KEY_UP), + KEY(1, 2, KEY_E), + KEY(1, 3, KEY_F), + KEY(2, 0, KEY_ENTER), + KEY(2, 1, KEY_I), + KEY(2, 2, KEY_J), + KEY(2, 3, KEY_K), + KEY(3, 0, KEY_M), + KEY(3, 1, KEY_N), + KEY(3, 2, KEY_O), + KEY(3, 3, KEY_P) +}; + +static struct twl4030_keypad_data omap3evm_kp_data = { + .rows = 4, + .cols = 4, + .keymap = omap3evm_keymap, + .keymapsize = ARRAY_SIZE(omap3evm_keymap), + .rep = 1, +}; + +static struct twl4030_madc_platform_data omap3evm_madc_data = { + .irq_line = 1, +}; + +static struct twl4030_platform_data omap3evm_twldata = { + .irq_base = TWL4030_IRQ_BASE, + .irq_end = TWL4030_IRQ_END, + + /* platform_data for children goes here */ + .keypad = &omap3evm_kp_data, + .madc = &omap3evm_madc_data, + .usb = &omap3evm_usb_data, + .power = GENERIC3430_T2SCRIPTS_DATA, + .gpio = &omap3evm_gpio_data, +}; + +static struct i2c_board_info __initdata omap3evm_i2c_boardinfo[] = { + { + I2C_BOARD_INFO("twl4030", 0x48), + .flags = I2C_CLIENT_WAKE, + .irq = INT_34XX_SYS_NIRQ, + .platform_data = &omap3evm_twldata, + }, +}; + +static int __init omap3_evm_i2c_init(void) +{ + omap_register_i2c_bus(1, 2600, omap3evm_i2c_boardinfo, + ARRAY_SIZE(omap3evm_i2c_boardinfo)); + omap_register_i2c_bus(2, 400, NULL, 0); + omap_register_i2c_bus(3, 400, NULL, 0); + return 0; +} + +static struct platform_device omap3_evm_lcd_device = { + .name = "omap3evm_lcd", + .id = -1, +}; + +static struct omap_lcd_config omap3_evm_lcd_config __initdata = { + .ctrl_name = "internal", +}; + +static void ads7846_dev_init(void) +{ + if (gpio_request(OMAP3_EVM_TS_GPIO, "ADS7846 pendown") < 0) + printk(KERN_ERR "can't get ads7846 pen down GPIO\n"); + + gpio_direction_input(OMAP3_EVM_TS_GPIO); + + omap_set_gpio_debounce(OMAP3_EVM_TS_GPIO, 1); + omap_set_gpio_debounce_time(OMAP3_EVM_TS_GPIO, 0xa); +} + +static int ads7846_get_pendown_state(void) +{ + return !gpio_get_value(OMAP3_EVM_TS_GPIO); +} + +struct ads7846_platform_data ads7846_config = { + .x_max = 0x0fff, + .y_max = 0x0fff, + .x_plate_ohms = 180, + .pressure_max = 255, + .debounce_max = 10, + .debounce_tol = 3, + .debounce_rep = 1, + .get_pendown_state = ads7846_get_pendown_state, + .keep_vref_on = 1, + .settle_delay_usecs = 150, +}; + +static struct omap2_mcspi_device_config ads7846_mcspi_config = { + .turbo_mode = 0, + .single_channel = 1, /* 0: slave, 1: master */ +}; + +struct spi_board_info omap3evm_spi_board_info[] = { + [0] = { + .modalias = "ads7846", + .bus_num = 1, + .chip_select = 0, + .max_speed_hz = 1500000, + .controller_data = &ads7846_mcspi_config, + .irq = OMAP_GPIO_IRQ(OMAP3_EVM_TS_GPIO), + .platform_data = &ads7846_config, + }, +}; + +static void __init omap3_evm_init_irq(void) +{ + omap2_init_common_hw(mt46h32m32lf6_sdrc_params); + omap_init_irq(); + omap_gpio_init(); + omap3evm_init_smc911x(); +} + +static struct omap_board_config_kernel omap3_evm_config[] __initdata = { + { OMAP_TAG_UART, &omap3_evm_uart_config }, + { OMAP_TAG_LCD, &omap3_evm_lcd_config }, +}; + +static struct platform_device *omap3_evm_devices[] __initdata = { + &omap3_evm_lcd_device, + &omap3evm_smc911x_device, +}; + +static void __init omap3_evm_init(void) +{ + omap3_evm_i2c_init(); + + platform_add_devices(omap3_evm_devices, ARRAY_SIZE(omap3_evm_devices)); + omap_board_config = omap3_evm_config; + omap_board_config_size = ARRAY_SIZE(omap3_evm_config); + + spi_register_board_info(omap3evm_spi_board_info, + ARRAY_SIZE(omap3evm_spi_board_info)); + + omap_serial_init(); + usb_musb_init(); + usb_ehci_init(); + omap3evm_flash_init(); + ads7846_dev_init(); +} + +static void __init omap3_evm_map_io(void) +{ + omap2_set_globals_343x(); + omap2_map_common_io(); +} + +MACHINE_START(OMAP3EVM, "OMAP3 EVM") + /* Maintainer: Syed Mohammed Khasim - Texas Instruments */ + .phys_io = 0x48000000, + .io_pg_offst = ((0xd8000000) >> 18) & 0xfffc, + .boot_params = 0x80000100, + .map_io = omap3_evm_map_io, + .init_irq = omap3_evm_init_irq, + .init_machine = omap3_evm_init, + .timer = &omap_timer, +MACHINE_END diff --git a/arch/arm/mach-omap2/board-omap3pandora.c b/arch/arm/mach-omap2/board-omap3pandora.c index 402f09c6cf1..c67f62ff885 100644 --- a/arch/arm/mach-omap2/board-omap3pandora.c +++ b/arch/arm/mach-omap2/board-omap3pandora.c @@ -17,7 +17,11 @@ * */ +#include +#include +#include #include +#include #include #include @@ -25,21 +29,119 @@ #include #include +#include +#include +#include + #include #include +#include #include #include #include #include +#include #include -#include +#include #include +#include +#include "sdram-micron-mt46h32m32lf-6.h" #include "mmc-twl4030.h" + +#define NAND_BLOCK_SIZE SZ_128K +#define GPMC_CS0_BASE 0x60 +#define GPMC_CS_SIZE 0x30 + #define OMAP3_PANDORA_TS_GPIO 94 +static struct mtd_partition omap3pandora_nand_partitions[] = { + { + .name = "xloader", + .offset = 0, /* Offset = 0x00000 */ + .size = 4 * NAND_BLOCK_SIZE, + .mask_flags = MTD_WRITEABLE + }, { + .name = "uboot", + .offset = MTDPART_OFS_APPEND, /* Offset = 0x80000 */ + .size = 14 * NAND_BLOCK_SIZE, + }, { + .name = "uboot environment", + .offset = MTDPART_OFS_APPEND, /* Offset = 0x240000 */ + .size = 2 * NAND_BLOCK_SIZE, + }, { + .name = "linux", + .offset = MTDPART_OFS_APPEND, /* Offset = 0x280000 */ + .size = 32 * NAND_BLOCK_SIZE, + }, { + .name = "rootfs", + .offset = MTDPART_OFS_APPEND, /* Offset = 0x680000 */ + .size = MTDPART_SIZ_FULL, + }, +}; + +static struct omap_nand_platform_data omap3pandora_nand_data = { + .parts = omap3pandora_nand_partitions, + .nr_parts = ARRAY_SIZE(omap3pandora_nand_partitions), + .dma_channel = -1, /* disable DMA in OMAP NAND driver */ +}; + +static struct resource omap3pandora_nand_resource[] = { + { + .flags = IORESOURCE_MEM, + }, +}; + +static struct platform_device omap3pandora_nand_device = { + .name = "omap2-nand", + .id = -1, + .dev = { + .platform_data = &omap3pandora_nand_data, + }, + .num_resources = ARRAY_SIZE(omap3pandora_nand_resource), + .resource = omap3pandora_nand_resource, +}; + +static void __init omap3pandora_flash_init(void) +{ + u8 cs = 0; + u8 nandcs = GPMC_CS_NUM + 1; + + u32 gpmc_base_add = OMAP34XX_GPMC_VIRT; + + /* find out the chip-select on which NAND exists */ + while (cs < GPMC_CS_NUM) { + u32 ret = 0; + ret = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG1); + + if ((ret & 0xC00) == 0x800) { + printk(KERN_INFO "Found NAND on CS%d\n", cs); + if (nandcs > GPMC_CS_NUM) + nandcs = cs; + } + cs++; + } + + if (nandcs > GPMC_CS_NUM) { + printk(KERN_INFO "NAND: Unable to find configuration " + "in GPMC\n "); + return; + } + + if (nandcs < GPMC_CS_NUM) { + omap3pandora_nand_data.cs = nandcs; + omap3pandora_nand_data.gpmc_cs_baseaddr = (void *) + (gpmc_base_add + GPMC_CS0_BASE + nandcs * GPMC_CS_SIZE); + omap3pandora_nand_data.gpmc_baseaddr = (void *) (gpmc_base_add); + + printk(KERN_INFO "Registering NAND on CS%d\n", nandcs); + if (platform_device_register(&omap3pandora_nand_device) < 0) + printk(KERN_ERR "Unable to register NAND device\n"); + } +} + static struct twl4030_hsmmc_info omap3pandora_mmc[] = { { .mmc = 1, @@ -118,7 +220,7 @@ static int __init omap3pandora_i2c_init(void) static void __init omap3pandora_init_irq(void) { - omap2_init_common_hw(NULL); + omap2_init_common_hw(mt46h32m32lf6_sdrc_params); omap_init_irq(); omap_gpio_init(); } @@ -200,8 +302,10 @@ static void __init omap3pandora_init(void) omap_serial_init(); spi_register_board_info(omap3pandora_spi_board_info, ARRAY_SIZE(omap3pandora_spi_board_info)); - omap3pandora_ads7846_init(); usb_musb_init(); + usb_ehci_init(); + omap3pandora_flash_init(); + omap3pandora_ads7846_init(); } static void __init omap3pandora_map_io(void) diff --git a/arch/arm/mach-omap2/board-overo.c b/arch/arm/mach-omap2/board-overo.c index b1f23bea863..f357de9681c 100644 --- a/arch/arm/mach-omap2/board-overo.c +++ b/arch/arm/mach-omap2/board-overo.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -45,6 +46,8 @@ #include #include +#include "sdram-micron-mt46h32m32lf-6.h" +#include "twl4030-generic-scripts.h" #include "mmc-twl4030.h" #define OVERO_GPIO_BT_XGATE 15 @@ -57,8 +60,8 @@ #define GPMC_CS0_BASE 0x60 #define GPMC_CS_SIZE 0x30 -#define OVERO_SMSC911X_CS 5 -#define OVERO_SMSC911X_GPIO 176 +#define OVERO_SMSC911X_CS 5 +#define OVERO_SMSC911X_GPIO 176 #if defined(CONFIG_TOUCHSCREEN_ADS7846) || \ defined(CONFIG_TOUCHSCREEN_ADS7846_MODULE) @@ -180,6 +183,8 @@ static inline void __init overo_init_smsc911x(void) static inline void __init overo_init_smsc911x(void) { return; } #endif + + static struct mtd_partition overo_nand_partitions[] = { { .name = "xloader", @@ -271,21 +276,77 @@ static struct omap_uart_config overo_uart_config __initdata = { .enabled_uarts = ((1 << 0) | (1 << 1) | (1 << 2)), }; +static struct twl4030_hsmmc_info mmc[] = { + { + .mmc = 1, + .wires = 4, + .gpio_cd = -EINVAL, + .gpio_wp = -EINVAL, + }, + { + .mmc = 2, + .wires = 4, + .gpio_cd = -EINVAL, + .gpio_wp = -EINVAL, + .transceiver = true, + .ocr_mask = 0x00100000, /* 3.3V */ + }, + {} /* Terminator */ +}; + +static struct regulator_consumer_supply overo_vmmc1_supply = { + .supply = "vmmc", +}; + +static int overo_twl_gpio_setup(struct device *dev, + unsigned gpio, unsigned ngpio) +{ + twl4030_mmc_init(mmc); + + overo_vmmc1_supply.dev = mmc[0].dev; + + return 0; +} + static struct twl4030_gpio_platform_data overo_gpio_data = { .gpio_base = OMAP_MAX_GPIO_LINES, .irq_base = TWL4030_GPIO_IRQ_BASE, .irq_end = TWL4030_GPIO_IRQ_END, + .setup = overo_twl_gpio_setup, }; +static struct twl4030_usb_data overo_usb_data = { + .usb_mode = T2_USB_MODE_ULPI, +}; + +static struct regulator_init_data overo_vmmc1 = { + .constraints = { + .min_uV = 1850000, + .max_uV = 3150000, + .valid_modes_mask = REGULATOR_MODE_NORMAL + | REGULATOR_MODE_STANDBY, + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE + | REGULATOR_CHANGE_MODE + | REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = 1, + .consumer_supplies = &overo_vmmc1_supply, +}; + +/* mmc2 (WLAN) and Bluetooth don't use twl4030 regulators */ + static struct twl4030_platform_data overo_twldata = { .irq_base = TWL4030_IRQ_BASE, .irq_end = TWL4030_IRQ_END, .gpio = &overo_gpio_data, + .usb = &overo_usb_data, + .power = GENERIC3430_T2SCRIPTS_DATA, + .vmmc1 = &overo_vmmc1, }; static struct i2c_board_info __initdata overo_i2c_boardinfo[] = { { - I2C_BOARD_INFO("twl4030", 0x48), + I2C_BOARD_INFO("tps65950", 0x48), .flags = I2C_CLIENT_WAKE, .irq = INT_34XX_SYS_NIRQ, .platform_data = &overo_twldata, @@ -303,7 +364,7 @@ static int __init overo_i2c_init(void) static void __init overo_init_irq(void) { - omap2_init_common_hw(NULL); + omap2_init_common_hw(mt46h32m32lf6_sdrc_params); omap_init_irq(); omap_gpio_init(); } @@ -326,23 +387,6 @@ static struct platform_device *overo_devices[] __initdata = { &overo_lcd_device, }; -static struct twl4030_hsmmc_info mmc[] __initdata = { - { - .mmc = 1, - .wires = 4, - .gpio_cd = -EINVAL, - .gpio_wp = -EINVAL, - }, - { - .mmc = 2, - .wires = 4, - .gpio_cd = -EINVAL, - .gpio_wp = -EINVAL, - .transceiver = true, - }, - {} /* Terminator */ -}; - static void __init overo_init(void) { overo_i2c_init(); @@ -350,9 +394,10 @@ static void __init overo_init(void) omap_board_config = overo_config; omap_board_config_size = ARRAY_SIZE(overo_config); omap_serial_init(); - twl4030_mmc_init(mmc); - overo_flash_init(); usb_musb_init(); + usb_ehci_init(); + overo_flash_init(); + overo_init_smsc911x(); overo_ads7846_init(); overo_init_smsc911x(); diff --git a/arch/arm/mach-omap2/board-rx51-flash.c b/arch/arm/mach-omap2/board-rx51-flash.c new file mode 100644 index 00000000000..f3b7eaf5d8d --- /dev/null +++ b/arch/arm/mach-omap2/board-rx51-flash.c @@ -0,0 +1,21 @@ +/* + * linux/arch/arm/mach-omap2/board-rx51-flash.c + * + * Copyright (C) 2008 Nokia + * + * 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 +#include +#include + +extern void __init n800_flash_init(void); + +void __init rx51_flash_init(void) +{ + n800_flash_init(); +} + diff --git a/arch/arm/mach-omap2/board-rx51-peripherals.c b/arch/arm/mach-omap2/board-rx51-peripherals.c index a7381729645..22183aff166 100644 --- a/arch/arm/mach-omap2/board-rx51-peripherals.c +++ b/arch/arm/mach-omap2/board-rx51-peripherals.c @@ -13,29 +13,34 @@ #include #include #include +#include #include #include #include #include #include -#include #include +#include #include #include #include #include #include -#include #include "mmc-twl4030.h" +#define SYSTEM_REV_B_USES_VAUX3 0x1699 +#define SYSTEM_REV_S_USES_VAUX3 0x8 #define SMC91X_CS 1 #define SMC91X_GPIO_IRQ 54 #define SMC91X_GPIO_RESET 164 #define SMC91X_GPIO_PWRDWN 86 +#define RX51_TSC2005_RESET_GPIO 104 +#define RX51_TSC2005_IRQ_GPIO 100 + static struct resource rx51_smc91x_resources[] = { [0] = { .flags = IORESOURCE_MEM, @@ -52,6 +57,38 @@ static struct platform_device rx51_smc91x_device = { .resource = rx51_smc91x_resources, }; +static struct tsc2005_platform_data tsc2005_config = { + .reset_gpio = RX51_TSC2005_RESET_GPIO, /* not used */ + + .ts_x_plate_ohm = 280, + .ts_hw_avg = 0, + .ts_touch_pressure = 1500, + .ts_stab_time = 1000, + .ts_pressure_max = 2048, + .ts_pressure_fudge = 2, + .ts_x_max = 4096, + .ts_x_fudge = 4, + .ts_y_max = 4096, + .ts_y_fudge = 7, +}; + +static struct omap2_mcspi_device_config tsc2005_mcspi_config = { + .turbo_mode = 0, + .single_channel = 1, +}; + +static struct spi_board_info rx51_peripherals_spi_board_info[] = { + [0] = { + .modalias = "tsc2005", + .bus_num = 1, + .chip_select = 0, + .irq = OMAP_GPIO_IRQ(RX51_TSC2005_IRQ_GPIO), + .max_speed_hz = 6000000, + .controller_data = &tsc2005_mcspi_config, + .platform_data = &tsc2005_config, + }, +}; + static int rx51_keymap[] = { KEY(0, 0, KEY_Q), KEY(0, 1, KEY_W), @@ -199,6 +236,18 @@ free1: printk(KERN_ERR "Could not initialize smc91x\n"); } +static void __init rx51_init_tsc2005(void) +{ + int r; + + r = gpio_request(RX51_TSC2005_IRQ_GPIO, "tsc2005 DAV IRQ"); + if (r >= 0) { + gpio_direction_input(RX51_TSC2005_IRQ_GPIO); + } else { + printk(KERN_ERR "unable to get DAV GPIO"); + } +} + static struct twl4030_madc_platform_data rx51_madc_data = { .irq_line = 1, }; @@ -259,7 +308,7 @@ static struct regulator_init_data rx51_vaux2 = { }; /* VAUX3 - adds more power to VIO_18 rail */ -static struct regulator_init_data rx51_vaux3 = { +static struct regulator_init_data rx51_vaux3_cam = { .constraints = { .name = "VCAM_DIG_18", .min_uV = 1800000, @@ -272,6 +321,22 @@ static struct regulator_init_data rx51_vaux3 = { }, }; +static struct regulator_init_data rx51_vaux3_mmc = { + .constraints = { + .name = "VMMC2_30", + .min_uV = 2800000, + .max_uV = 3000000, + .apply_uV = true, + .valid_modes_mask = REGULATOR_MODE_NORMAL + | REGULATOR_MODE_STANDBY, + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE + | REGULATOR_CHANGE_MODE + | REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = 1, + .consumer_supplies = &rx51_vmmc2_supply, +}; + static struct regulator_init_data rx51_vaux4 = { .constraints = { .name = "VCAM_ANA_28", @@ -382,10 +447,8 @@ static struct twl4030_platform_data rx51_twldata = { .vaux1 = &rx51_vaux1, .vaux2 = &rx51_vaux2, - .vaux3 = &rx51_vaux3, .vaux4 = &rx51_vaux4, .vmmc1 = &rx51_vmmc1, - .vmmc2 = &rx51_vmmc2, .vsim = &rx51_vsim, .vdac = &rx51_vdac, }; @@ -401,6 +464,13 @@ static struct i2c_board_info __initdata rx51_peripherals_i2c_board_info_1[] = { static int __init rx51_i2c_init(void) { + if ((system_rev >= SYSTEM_REV_S_USES_VAUX3 && system_rev < 0x100) || + system_rev >= SYSTEM_REV_B_USES_VAUX3) + rx51_twldata.vaux3 = &rx51_vaux3_mmc; + else { + rx51_twldata.vaux3 = &rx51_vaux3_cam; + rx51_twldata.vmmc2 = &rx51_vmmc2; + } omap_register_i2c_bus(1, 2600, rx51_peripherals_i2c_board_info_1, ARRAY_SIZE(rx51_peripherals_i2c_board_info_1)); omap_register_i2c_bus(2, 100, NULL, 0); @@ -413,7 +483,10 @@ void __init rx51_peripherals_init(void) { platform_add_devices(rx51_peripherals_devices, ARRAY_SIZE(rx51_peripherals_devices)); + spi_register_board_info(rx51_peripherals_spi_board_info, + ARRAY_SIZE(rx51_peripherals_spi_board_info)); rx51_i2c_init(); rx51_init_smc91x(); + rx51_init_tsc2005(); } diff --git a/arch/arm/mach-omap2/board-rx51-sdram.c b/arch/arm/mach-omap2/board-rx51-sdram.c new file mode 100644 index 00000000000..32b5da412af --- /dev/null +++ b/arch/arm/mach-omap2/board-rx51-sdram.c @@ -0,0 +1,219 @@ +/* + * SDRC register values for the Samsung K4X1G323PC + * + * Copyright (C) 2008 Nokia Corporation + * + * Lauri Leukkunen + * + * Original code by Juha Yrjölä + * + * 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 +#include +#include +#include + +#include +#include +#include +#include + + +/* In picoseconds, except for tREF */ +struct sdram_timings { + u32 casl; + u32 tDAL; + u32 tDPL; + u32 tRRD; + u32 tRCD; + u32 tRP; + u32 tRAS; + u32 tRC; + u32 tRFC; + u32 tXSR; + + u32 tREF; /* in ms */ +}; + +struct sdram_info { + u8 row_lines; +}; + + +struct omap_sdrc_params rx51_sdrc_params[2]; + +static const struct sdram_timings rx51_timings[] = { + { + .casl = 3, + .tDAL = 15000 + 18000, + .tDPL = 15000, + .tRRD = 12000, + .tRCD = 18000, + .tRP = 18000, + .tRAS = 42000, + .tRC = 66000, + .tRFC = 97500, + .tXSR = 120000, + + .tREF = 64, + }, +}; + +static const struct sdram_info rx51_info = { + .row_lines = 13, +}; + +#define CM_BASE 0x48004000 + +#define CM_CLKSEL_CORE 0x0a40 +#define CM_CLKSEL1_PLL 0x0d40 + +#define PRM_CLKSEL 0x48306d40 +#define PRM_CLKSRC_CTRL 0x48307270 + +static u32 cm_base = CM_BASE; + +static inline u32 cm_read_reg(int idx) +{ + return *(u32 *)OMAP2_IO_ADDRESS(cm_base + idx); +} + +static const unsigned long sys_clk_rate_table[] = { + 12000, 13000, 19200, 26000, 38400, 16800 +}; + +static unsigned long get_sys_clk_rate(void) +{ + unsigned long rate; + + rate = sys_clk_rate_table[*(u32 *)OMAP2_IO_ADDRESS(PRM_CLKSEL) & 0x07]; + if (((*(u32 *)OMAP2_IO_ADDRESS(PRM_CLKSRC_CTRL) >> 6) & 0x03) == 0x02) + rate /= 2; + return rate; +} + +static unsigned long get_core_rate(void) +{ + unsigned long rate; + u32 l; + + l = cm_read_reg(CM_CLKSEL1_PLL); + rate = get_sys_clk_rate(); + rate *= ((l >> 16) & 0x7ff); + rate /= ((l >> 8) & 0x7f) + 1; + rate /= (l >> 27) & 0x1f; + + return rate; +} + +static unsigned long get_l3_rate(void) +{ + u32 l; + + l = cm_read_reg(CM_CLKSEL_CORE); + return get_core_rate() / (l & 0x03); +} + + + +static unsigned long sdrc_get_fclk_period(void) +{ + /* In picoseconds */ + return 1000000000 / get_l3_rate(); +} + +static unsigned int sdrc_ps_to_ticks(unsigned int time_ps) +{ + unsigned long tick_ps; + + /* Calculate in picosecs to yield more exact results */ + tick_ps = sdrc_get_fclk_period(); + + return (time_ps + tick_ps - 1) / tick_ps; +} +#undef DEBUG +#ifdef DEBUG +static int set_sdrc_timing_regval(u32 *regval, int st_bit, int end_bit, + int time, const char *name) +#else +static int set_sdrc_timing_regval(u32 *regval, int st_bit, int end_bit, + int time) +#endif +{ + int ticks, mask, nr_bits; + + if (time == 0) + ticks = 0; + else + ticks = sdrc_ps_to_ticks(time); + nr_bits = end_bit - st_bit + 1; + if (ticks >= 1 << nr_bits) + return -1; + mask = (1 << nr_bits) - 1; + *regval &= ~(mask << st_bit); + *regval |= ticks << st_bit; +#ifdef DEBUG + printk("SDRC %s: %i ticks %i ns\n", name, ticks, + (unsigned int)sdrc_get_fclk_period() * ticks / 1000); +#endif + + return 0; +} + +#ifdef DEBUG +#define SDRC_SET_ONE(reg, st, end, field) \ + if (set_sdrc_timing_regval((reg), (st), (end), rx51_timings->field, #field) < 0) \ + err = -1 +#else +#define SDRC_SET_ONE(reg, st, end, field) \ + if (set_sdrc_timing_regval((reg), (st), (end), rx51_timings->field) < 0) \ + err = -1 +#endif + +struct omap_sdrc_params *rx51_get_sdram_timings(void) +{ + u32 ticks_per_ms; + u32 rfr, l; + u32 actim_ctrla, actim_ctrlb; + u32 rfr_ctrl; + int err = 0; + + SDRC_SET_ONE(&actim_ctrla, 0, 4, tDAL); + SDRC_SET_ONE(&actim_ctrla, 6, 8, tDPL); + SDRC_SET_ONE(&actim_ctrla, 9, 11, tRRD); + SDRC_SET_ONE(&actim_ctrla, 12, 14, tRCD); + SDRC_SET_ONE(&actim_ctrla, 15, 17, tRP); + SDRC_SET_ONE(&actim_ctrla, 18, 21, tRAS); + SDRC_SET_ONE(&actim_ctrla, 22, 26, tRC); + SDRC_SET_ONE(&actim_ctrla, 27, 31, tRFC); + + SDRC_SET_ONE(&actim_ctrlb, 0, 7, tXSR); + + ticks_per_ms = sdrc_ps_to_ticks(1000000000); + rfr = rx51_timings[0].tREF * ticks_per_ms / (1 << rx51_info.row_lines); + if (rfr > 65535 + 50) + rfr = 65535; + else + rfr -= 50; + + l = rfr << 8; + rfr_ctrl = l | 0x3; /* autorefresh, reload counter with 8xARCV */ + + rx51_sdrc_params[0].rate = 133333333; + rx51_sdrc_params[0].actim_ctrla = actim_ctrla; + rx51_sdrc_params[0].actim_ctrlb = actim_ctrlb; + rx51_sdrc_params[0].rfr_ctrl = rfr_ctrl; + rx51_sdrc_params[0].mr = 0x32; + + rx51_sdrc_params[1].rate = 0; + + if (err < 0) + return NULL; + + return &rx51_sdrc_params[0]; +} + diff --git a/arch/arm/mach-omap2/board-rx51-video.c b/arch/arm/mach-omap2/board-rx51-video.c new file mode 100644 index 00000000000..6ebdc8279e0 --- /dev/null +++ b/arch/arm/mach-omap2/board-rx51-video.c @@ -0,0 +1,79 @@ +/* + * linux/arch/arm/mach-omap2/board-rx51-video.c + * + * Copyright (C) 2008 Nokia + * + * 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 +#include +#include +#include +#include + +#include +#include +#include +#include + + +static struct omap2_mcspi_device_config mipid_mcspi_config = { + .turbo_mode = 0, + .single_channel = 1, +}; + +static struct platform_device rx51_lcd_device = { + .name = "lcd_mipid", + .id = -1, +}; + +static void mipid_shutdown(struct mipid_platform_data *pdata) +{ + if (pdata->nreset_gpio != -1) { + pr_info("shutdown LCD\n"); + gpio_direction_output(pdata->nreset_gpio, 0); + msleep(120); + } +} + +static struct mipid_platform_data rx51_mipid_platform_data = { + .shutdown = mipid_shutdown, +}; + +static void __init mipid_dev_init(void) +{ + const struct omap_lcd_config *conf; + + conf = omap_get_config(OMAP_TAG_LCD, struct omap_lcd_config); + if (conf != NULL) { + rx51_mipid_platform_data.nreset_gpio = conf->nreset_gpio; + rx51_mipid_platform_data.data_lines = conf->data_lines; + } +} + +static struct spi_board_info rx51_video_spi_board_info[] = { + [0] = { + .modalias = "lcd_mipid", + .bus_num = 1, + .chip_select = 2, + .max_speed_hz = 6000000, + .controller_data = &mipid_mcspi_config, + .platform_data = &rx51_mipid_platform_data, + }, +}; + +static struct platform_device *rx51_video_devices[] = { + &rx51_lcd_device, +}; + +void __init rx51_video_init(void) +{ + platform_add_devices(rx51_video_devices, ARRAY_SIZE(rx51_video_devices)); + spi_register_board_info(rx51_video_spi_board_info, + ARRAY_SIZE(rx51_video_spi_board_info)); + mipid_dev_init(); +} + diff --git a/arch/arm/mach-omap2/board-rx51.c b/arch/arm/mach-omap2/board-rx51.c index 3a0daac6c83..768f507a569 100644 --- a/arch/arm/mach-omap2/board-rx51.c +++ b/arch/arm/mach-omap2/board-rx51.c @@ -16,7 +16,6 @@ #include #include #include -#include #include #include @@ -24,6 +23,7 @@ #include #include +#include #include #include #include @@ -31,6 +31,7 @@ #include #include #include +#include static struct omap_uart_config rx51_uart_config = { .enabled_uarts = ((1 << 0) | (1 << 1) | (1 << 2)), @@ -62,12 +63,14 @@ static struct omap_board_config_kernel rx51_config[] = { static void __init rx51_init_irq(void) { - omap2_init_common_hw(NULL); + omap2_init_common_hw(rx51_get_sdram_timings()); omap_init_irq(); omap_gpio_init(); } +extern void __init rx51_flash_init(void); extern void __init rx51_peripherals_init(void); +extern void __init rx51_video_init(void); static void __init rx51_init(void) { @@ -75,7 +78,9 @@ static void __init rx51_init(void) omap_board_config_size = ARRAY_SIZE(rx51_config); omap_serial_init(); usb_musb_init(); + rx51_flash_init(); rx51_peripherals_init(); + rx51_video_init(); } static void __init rx51_map_io(void) diff --git a/arch/arm/mach-omap2/devices.c b/arch/arm/mach-omap2/devices.c index d6b4b2f8722..894cc355818 100644 --- a/arch/arm/mach-omap2/devices.c +++ b/arch/arm/mach-omap2/devices.c @@ -25,7 +25,6 @@ #include #include #include -#include #include #if defined(CONFIG_VIDEO_OMAP2) || defined(CONFIG_VIDEO_OMAP2_MODULE) @@ -355,10 +354,12 @@ static void omap_init_mcspi(void) platform_device_register(&omap2_mcspi1); platform_device_register(&omap2_mcspi2); #if defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP3) - platform_device_register(&omap2_mcspi3); + if (cpu_is_omap2430() || cpu_is_omap343x()) + platform_device_register(&omap2_mcspi3); #endif #ifdef CONFIG_ARCH_OMAP3 - platform_device_register(&omap2_mcspi4); + if (cpu_is_omap343x()) + platform_device_register(&omap2_mcspi4); #endif } @@ -366,38 +367,6 @@ static void omap_init_mcspi(void) static inline void omap_init_mcspi(void) {} #endif -#ifdef CONFIG_SND_OMAP24XX_EAC - -#define OMAP2_EAC_BASE 0x48090000 - -static struct resource omap2_eac_resources[] = { - { - .start = OMAP2_EAC_BASE, - .end = OMAP2_EAC_BASE + 0x109, - .flags = IORESOURCE_MEM, - }, -}; - -static struct platform_device omap2_eac_device = { - .name = "omap24xx-eac", - .id = -1, - .num_resources = ARRAY_SIZE(omap2_eac_resources), - .resource = omap2_eac_resources, - .dev = { - .platform_data = NULL, - }, -}; - -void omap_init_eac(struct eac_platform_data *pdata) -{ - omap2_eac_device.dev.platform_data = pdata; - platform_device_register(&omap2_eac_device); -} - -#else -void omap_init_eac(struct eac_platform_data *pdata) {} -#endif - #ifdef CONFIG_OMAP_SHA1_MD5 static struct resource sha1_md5_resources[] = { { diff --git a/arch/arm/mach-omap2/id.c b/arch/arm/mach-omap2/id.c index 34b5914e0f8..32ceaa65824 100644 --- a/arch/arm/mach-omap2/id.c +++ b/arch/arm/mach-omap2/id.c @@ -45,6 +45,28 @@ int omap_chip_is(struct omap_chip_id oci) } EXPORT_SYMBOL(omap_chip_is); +int omap_type(void) +{ + u32 val = 0; + + if (cpu_is_omap24xx()) { + val = omap_ctrl_readl(OMAP24XX_CONTROL_STATUS); + } else if (cpu_is_omap34xx()) { + val = omap_ctrl_readl(OMAP343X_CONTROL_STATUS); + } else { + pr_err("Cannot detect omap type!\n"); + goto out; + } + + val &= OMAP2_DEVICETYPE_MASK; + val >>= 8; + +out: + return val; +} +EXPORT_SYMBOL(omap_type); + + /*----------------------------------------------------------------------------*/ #define OMAP_TAP_IDCODE 0x0204 diff --git a/arch/arm/mach-omap2/irq.c b/arch/arm/mach-omap2/irq.c index 9ba20d985dd..998c5c45587 100644 --- a/arch/arm/mach-omap2/irq.c +++ b/arch/arm/mach-omap2/irq.c @@ -73,9 +73,9 @@ static int omap_check_spurious(unsigned int irq) u32 sir, spurious; sir = intc_bank_read_reg(&irq_banks[0], INTC_SIR); - spurious = sir >> 6; + spurious = sir >> 7; - if (spurious > 1) { + if (spurious) { printk(KERN_WARNING "Spurious irq %i: 0x%08x, please flush " "posted write for irq %i\n", irq, sir, previous_irq); diff --git a/arch/arm/mach-omap2/mmc-twl4030.c b/arch/arm/mach-omap2/mmc-twl4030.c index dc40b3e7220..46d6e9bcadf 100644 --- a/arch/arm/mach-omap2/mmc-twl4030.c +++ b/arch/arm/mach-omap2/mmc-twl4030.c @@ -16,8 +16,8 @@ #include #include #include -#include -#include +#include +#include #include #include @@ -26,31 +26,9 @@ #include "mmc-twl4030.h" -#if defined(CONFIG_TWL4030_CORE) && \ - (defined(CONFIG_MMC_OMAP_HS) || defined(CONFIG_MMC_OMAP_HS_MODULE)) -#define LDO_CLR 0x00 -#define VSEL_S2_CLR 0x40 - -#define VMMC1_DEV_GRP 0x27 -#define VMMC1_CLR 0x00 -#define VMMC1_315V 0x03 -#define VMMC1_300V 0x02 -#define VMMC1_285V 0x01 -#define VMMC1_185V 0x00 -#define VMMC1_DEDICATED 0x2A - -#define VMMC2_DEV_GRP 0x2B -#define VMMC2_CLR 0x40 -#define VMMC2_315V 0x0c -#define VMMC2_300V 0x0b -#define VMMC2_285V 0x0a -#define VMMC2_280V 0x09 -#define VMMC2_260V 0x08 -#define VMMC2_185V 0x06 -#define VMMC2_DEDICATED 0x2E - -#define VMMC_DEV_GRP_P1 0x20 +#if defined(CONFIG_REGULATOR) && \ + (defined(CONFIG_MMC_OMAP_HS) || defined(CONFIG_MMC_OMAP_HS_MODULE)) static u16 control_pbias_offset; static u16 control_devconf1_offset; @@ -59,19 +37,16 @@ static u16 control_devconf1_offset; static struct twl_mmc_controller { struct omap_mmc_platform_data *mmc; - u8 twl_vmmc_dev_grp; - u8 twl_mmc_dedicated; - char name[HSMMC_NAME_LEN + 1]; -} hsmmc[OMAP34XX_NR_MMC] = { - { - .twl_vmmc_dev_grp = VMMC1_DEV_GRP, - .twl_mmc_dedicated = VMMC1_DEDICATED, - }, - { - .twl_vmmc_dev_grp = VMMC2_DEV_GRP, - .twl_mmc_dedicated = VMMC2_DEDICATED, - }, -}; + /* Vcc == configured supply + * Vcc_alt == optional + * - MMC1, supply for DAT4..DAT7 + * - MMC2/MMC2, external level shifter voltage supply, for + * chip (SDIO, eMMC, etc) or transceiver (MMC2 only) + */ + struct regulator *vcc; + struct regulator *vcc_aux; + char name[HSMMC_NAME_LEN + 1]; +} hsmmc[OMAP34XX_NR_MMC]; static int twl_mmc_card_detect(int irq) { @@ -117,16 +92,60 @@ static int twl_mmc_late_init(struct device *dev) int ret = 0; int i; - ret = gpio_request(mmc->slots[0].switch_pin, "mmc_cd"); - if (ret) - goto done; - ret = gpio_direction_input(mmc->slots[0].switch_pin); - if (ret) - goto err; + /* MMC/SD/SDIO doesn't require a card detect switch */ + if (gpio_is_valid(mmc->slots[0].switch_pin)) { + ret = gpio_request(mmc->slots[0].switch_pin, "mmc_cd"); + if (ret) + goto done; + ret = gpio_direction_input(mmc->slots[0].switch_pin); + if (ret) + goto err; + } + /* require at least main regulator */ for (i = 0; i < ARRAY_SIZE(hsmmc); i++) { if (hsmmc[i].name == mmc->slots[0].name) { + struct regulator *reg; + hsmmc[i].mmc = mmc; + + reg = regulator_get(dev, "vmmc"); + if (IS_ERR(reg)) { + dev_dbg(dev, "vmmc regulator missing\n"); + /* HACK: until fixed.c regulator is usable, + * we don't require a main regulator + * for MMC2 or MMC3 + */ + if (i != 0) + break; + ret = PTR_ERR(reg); + goto err; + } + hsmmc[i].vcc = reg; + mmc->slots[0].ocr_mask = mmc_regulator_get_ocrmask(reg); + + /* allow an aux regulator */ + reg = regulator_get(dev, "vmmc_aux"); + hsmmc[i].vcc_aux = IS_ERR(reg) ? NULL : reg; + + /* UGLY HACK: workaround regulator framework bugs. + * When the bootloader leaves a supply active, it's + * initialized with zero usecount ... and we can't + * disable it without first disabling it. Until the + * framework is fixed, we need a workaround like this + * (which is safe for MMC, but not in general). + */ + if (regulator_is_enabled(hsmmc[i].vcc) > 0) { + regulator_enable(hsmmc[i].vcc); + regulator_disable(hsmmc[i].vcc); + } + if (hsmmc[i].vcc_aux) { + if (regulator_is_enabled(reg) > 0) { + regulator_enable(reg); + regulator_disable(reg); + } + } + break; } } @@ -173,96 +192,6 @@ static int twl_mmc_resume(struct device *dev, int slot) #define twl_mmc_resume NULL #endif -/* - * Sets the MMC voltage in twl4030 - */ - -#define MMC1_OCR (MMC_VDD_165_195 \ - |MMC_VDD_28_29|MMC_VDD_29_30|MMC_VDD_30_31|MMC_VDD_31_32) -#define MMC2_OCR (MMC_VDD_165_195 \ - |MMC_VDD_25_26|MMC_VDD_26_27|MMC_VDD_27_28 \ - |MMC_VDD_28_29|MMC_VDD_29_30|MMC_VDD_30_31|MMC_VDD_31_32) - -static int twl_mmc_set_voltage(struct twl_mmc_controller *c, int vdd) -{ - int ret; - u8 vmmc = 0, dev_grp_val; - - if (!vdd) - goto doit; - - if (c->twl_vmmc_dev_grp == VMMC1_DEV_GRP) { - /* VMMC1: max 220 mA. And for 8-bit mode, - * VSIM: max 50 mA - */ - switch (1 << vdd) { - case MMC_VDD_165_195: - vmmc = VMMC1_185V; - /* and VSIM_180V */ - break; - case MMC_VDD_28_29: - vmmc = VMMC1_285V; - /* and VSIM_280V */ - break; - case MMC_VDD_29_30: - case MMC_VDD_30_31: - vmmc = VMMC1_300V; - /* and VSIM_300V */ - break; - case MMC_VDD_31_32: - vmmc = VMMC1_315V; - /* error if VSIM needed */ - break; - default: - return -EINVAL; - } - } else if (c->twl_vmmc_dev_grp == VMMC2_DEV_GRP) { - /* VMMC2: max 100 mA */ - switch (1 << vdd) { - case MMC_VDD_165_195: - vmmc = VMMC2_185V; - break; - case MMC_VDD_25_26: - case MMC_VDD_26_27: - vmmc = VMMC2_260V; - break; - case MMC_VDD_27_28: - vmmc = VMMC2_280V; - break; - case MMC_VDD_28_29: - vmmc = VMMC2_285V; - break; - case MMC_VDD_29_30: - case MMC_VDD_30_31: - vmmc = VMMC2_300V; - break; - case MMC_VDD_31_32: - vmmc = VMMC2_315V; - break; - default: - return -EINVAL; - } - } else { - return -EINVAL; - } - -doit: - if (vdd) - dev_grp_val = VMMC_DEV_GRP_P1; /* Power up */ - else - dev_grp_val = LDO_CLR; /* Power down */ - - ret = twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, - dev_grp_val, c->twl_vmmc_dev_grp); - if (ret || !vdd) - return ret; - - ret = twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, - vmmc, c->twl_mmc_dedicated); - - return ret; -} - static int twl_mmc1_set_power(struct device *dev, int slot, int power_on, int vdd) { @@ -273,11 +202,13 @@ static int twl_mmc1_set_power(struct device *dev, int slot, int power_on, /* * Assume we power both OMAP VMMC1 (for CMD, CLK, DAT0..3) and the - * card using the same TWL VMMC1 supply (hsmmc[0]); OMAP has both + * card with Vcc regulator (from twl4030 or whatever). OMAP has both * 1.8V and 3.0V modes, controlled by the PBIAS register. * * In 8-bit modes, OMAP VMMC1A (for DAT4..7) needs a supply, which * is most naturally TWL VSIM; those pins also use PBIAS. + * + * FIXME handle VMMC1A as needed ... */ if (power_on) { if (cpu_is_omap2430()) { @@ -300,7 +231,7 @@ static int twl_mmc1_set_power(struct device *dev, int slot, int power_on, reg &= ~OMAP2_PBIASLITEPWRDNZ0; omap_ctrl_writel(reg, control_pbias_offset); - ret = twl_mmc_set_voltage(c, vdd); + ret = mmc_regulator_set_ocr(c->vcc, vdd); /* 100ms delay required for PBIAS configuration */ msleep(100); @@ -316,7 +247,7 @@ static int twl_mmc1_set_power(struct device *dev, int slot, int power_on, reg &= ~OMAP2_PBIASLITEPWRDNZ0; omap_ctrl_writel(reg, control_pbias_offset); - ret = twl_mmc_set_voltage(c, 0); + ret = mmc_regulator_set_ocr(c->vcc, 0); /* 100ms delay required for PBIAS configuration */ msleep(100); @@ -329,19 +260,33 @@ static int twl_mmc1_set_power(struct device *dev, int slot, int power_on, return ret; } -static int twl_mmc2_set_power(struct device *dev, int slot, int power_on, int vdd) +static int twl_mmc23_set_power(struct device *dev, int slot, int power_on, int vdd) { - int ret; + int ret = 0; struct twl_mmc_controller *c = &hsmmc[1]; struct omap_mmc_platform_data *mmc = dev->platform_data; + /* If we don't see a Vcc regulator, assume it's a fixed + * voltage always-on regulator. + */ + if (!c->vcc) + return 0; + /* - * Assume TWL VMMC2 (hsmmc[1]) is used only to power the card ... OMAP + * Assume Vcc regulator is used only to power the card ... OMAP * VDDS is used to power the pins, optionally with a transceiver to * support cards using voltages other than VDDS (1.8V nominal). When a * transceiver is used, DAT3..7 are muxed as transceiver control pins. + * + * In some cases this regulator won't support enable/disable; + * e.g. it's a fixed rail for a WLAN chip. + * + * In other cases vcc_aux switches interface power. Example, for + * eMMC cards it represents VccQ. Sometimes transceivers or SDIO + * chips/cards need an interface voltage rail too. */ if (power_on) { + /* only MMC2 supports a CLKIN */ if (mmc->slots[0].internal_clock) { u32 reg; @@ -349,24 +294,23 @@ static int twl_mmc2_set_power(struct device *dev, int slot, int power_on, int vd reg |= OMAP2_MMCSDIO2ADPCLKISEL; omap_ctrl_writel(reg, control_devconf1_offset); } - ret = twl_mmc_set_voltage(c, vdd); + ret = mmc_regulator_set_ocr(c->vcc, vdd); + /* enable interface voltage rail, if needed */ + if (ret == 0 && c->vcc_aux) { + ret = regulator_enable(c->vcc_aux); + if (ret < 0) + ret = mmc_regulator_set_ocr(c->vcc, 0); + } } else { - ret = twl_mmc_set_voltage(c, 0); + if (c->vcc_aux && (ret = regulator_is_enabled(c->vcc_aux)) > 0) + ret = regulator_disable(c->vcc_aux); + if (ret == 0) + ret = mmc_regulator_set_ocr(c->vcc, 0); } return ret; } -static int twl_mmc3_set_power(struct device *dev, int slot, int power_on, - int vdd) -{ - /* - * Assume MMC3 has self-powered device connected, for example on-board - * chip with external power source. - */ - return 0; -} - static struct omap_mmc_platform_data *hsmmc_data[OMAP34XX_NR_MMC] __initdata; void __init twl4030_mmc_init(struct twl4030_hsmmc_info *controllers) @@ -405,17 +349,16 @@ void __init twl4030_mmc_init(struct twl4030_hsmmc_info *controllers) if (c->name) strncpy(twl->name, c->name, HSMMC_NAME_LEN); else - snprintf(twl->name, ARRAY_SIZE(twl->name), - "mmc%islot%i", c->mmc, 1); + sprintf(twl->name, "mmc%islot%i", c->mmc, 1); mmc->slots[0].name = twl->name; mmc->nr_slots = 1; mmc->slots[0].wires = c->wires; mmc->slots[0].internal_clock = !c->ext_clock; mmc->dma_mask = 0xffffffff; + mmc->init = twl_mmc_late_init; - /* note: twl4030 card detect GPIOs normally switch VMMCx ... */ + /* note: twl4030 card detect GPIOs can disable VMMCx ... */ if (gpio_is_valid(c->gpio_cd)) { - mmc->init = twl_mmc_late_init; mmc->cleanup = twl_mmc_cleanup; mmc->suspend = twl_mmc_suspend; mmc->resume = twl_mmc_resume; @@ -439,26 +382,28 @@ void __init twl4030_mmc_init(struct twl4030_hsmmc_info *controllers) } else mmc->slots[0].gpio_wp = -EINVAL; - /* NOTE: we assume OMAP's MMC1 and MMC2 use - * the TWL4030's VMMC1 and VMMC2, respectively; - * and that MMC3 device has it's own power source. + /* NOTE: MMC slots should have a Vcc regulator set up. + * This may be from a TWL4030-family chip, another + * controllable regulator, or a fixed supply. + * + * temporary HACK: ocr_mask instead of fixed supply */ + mmc->slots[0].ocr_mask = c->ocr_mask; switch (c->mmc) { case 1: + /* on-chip level shifting via PBIAS0/PBIAS1 */ mmc->slots[0].set_power = twl_mmc1_set_power; - mmc->slots[0].ocr_mask = MMC1_OCR; break; case 2: - mmc->slots[0].set_power = twl_mmc2_set_power; - if (c->transceiver) - mmc->slots[0].ocr_mask = MMC2_OCR; - else - mmc->slots[0].ocr_mask = MMC_VDD_165_195; - break; + if (c->ext_clock) + c->transceiver = 1; + if (c->transceiver && c->wires > 4) + c->wires = 4; + /* FALLTHROUGH */ case 3: - mmc->slots[0].set_power = twl_mmc3_set_power; - mmc->slots[0].ocr_mask = MMC_VDD_165_195; + /* off-chip level shifting, or none */ + mmc->slots[0].set_power = twl_mmc23_set_power; break; default: pr_err("MMC%d configuration not supported!\n", c->mmc); diff --git a/arch/arm/mach-omap2/mmc-twl4030.h b/arch/arm/mach-omap2/mmc-twl4030.h index ea59e862429..3807c45c9a6 100644 --- a/arch/arm/mach-omap2/mmc-twl4030.h +++ b/arch/arm/mach-omap2/mmc-twl4030.h @@ -16,9 +16,10 @@ struct twl4030_hsmmc_info { int gpio_wp; /* or -EINVAL */ char *name; /* or NULL for default */ struct device *dev; /* returned: pointer to mmc adapter */ + int ocr_mask; /* temporary HACK */ }; -#if defined(CONFIG_TWL4030_CORE) && \ +#if defined(CONFIG_REGULATOR) && \ (defined(CONFIG_MMC_OMAP) || defined(CONFIG_MMC_OMAP_MODULE) || \ defined(CONFIG_MMC_OMAP_HS) || defined(CONFIG_MMC_OMAP_HS_MODULE)) diff --git a/arch/arm/mach-omap2/sdram-micron-mt46h32m32lf-6.h b/arch/arm/mach-omap2/sdram-micron-mt46h32m32lf-6.h new file mode 100644 index 00000000000..ef35415ca89 --- /dev/null +++ b/arch/arm/mach-omap2/sdram-micron-mt46h32m32lf-6.h @@ -0,0 +1,55 @@ +/* + * SDRC register values for the Micron MT46H32M32LF-6 + * + * Copyright (C) 2008 Texas Instruments, Inc. + * Copyright (C) 2008 Nokia Corporation + * + * Paul Walmsley + * + * 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 ARCH_ARM_MACH_OMAP2_SDRAM_MICRON_MT46H32M32LF +#define ARCH_ARM_MACH_OMAP2_SDRAM_MICRON_MT46H32M32LF + +#include + +/* Micron MT46H32M32LF-6 */ +/* XXX Using ARE = 0x1 (no autorefresh burst) -- can this be changed? */ +static struct omap_sdrc_params mt46h32m32lf6_sdrc_params[] = { + [0] = { + .rate = 165941176, + .actim_ctrla = 0x9a9db4c6, + .actim_ctrlb = 0x00011217, + .rfr_ctrl = 0x0004dc01, + .mr = 0x00000032, + }, + [1] = { + .rate = 133333333, + .actim_ctrla = 0x7a19b485, + .actim_ctrlb = 0x00011213, + .rfr_ctrl = 0x0003de01, + .mr = 0x00000032, + }, + [2] = { + .rate = 82970588, + .actim_ctrla = 0x51512283, + .actim_ctrlb = 0x0001120c, + .rfr_ctrl = 0x00025501, + .mr = 0x00000032, + }, + [3] = { + .rate = 66666666, + .actim_ctrla = 0x410d2243, + .actim_ctrlb = 0x0001120a, + .rfr_ctrl = 0x0001d601, + .mr = 0x00000032, + }, + [4] = { + .rate = 0 + }, +}; + +#endif diff --git a/arch/arm/mach-omap2/sdram-qimonda-hyb18m512160af-6.h b/arch/arm/mach-omap2/sdram-qimonda-hyb18m512160af-6.h new file mode 100644 index 00000000000..74a92c862e6 --- /dev/null +++ b/arch/arm/mach-omap2/sdram-qimonda-hyb18m512160af-6.h @@ -0,0 +1,55 @@ +/* + * SDRC register values for the Qimonda HYB18M512160AF-6 + * + * Copyright (C) 2008 Texas Instruments, Inc. + * Copyright (C) 2008 Nokia Corporation + * + * Paul Walmsley + * + * 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 ARCH_ARM_MACH_OMAP2_SDRAM_QIMONDA_HYB18M512160AF6 +#define ARCH_ARM_MACH_OMAP2_SDRAM_QIMONDA_HYB18M512160AF6 + +#include + +/* Qimonda HYB18M512160AF-6 */ +/* XXX Using ARE = 0x1 (no autorefresh burst) -- can this be changed? */ +static struct omap_sdrc_params hyb18m512160af6_sdrc_params[] = { + [0] = { + .rate = 165941176, + .actim_ctrla = 0x629db4c6, + .actim_ctrlb = 0x00012214, + .rfr_ctrl = 0x0004dc01, + .mr = 0x00000032, + }, + [1] = { + .rate = 133333333, + .actim_ctrla = 0x5219b485, + .actim_ctrlb = 0x00012210, + .rfr_ctrl = 0x0003de01, + .mr = 0x00000032, + }, + [2] = { + .rate = 82970588, + .actim_ctrla = 0x31512283, + .actim_ctrlb = 0x0001220a, + .rfr_ctrl = 0x00025501, + .mr = 0x00000022, + }, + [3] = { + .rate = 66666666, + .actim_ctrla = 0x290d2243, + .actim_ctrlb = 0x00012208, + .rfr_ctrl = 0x0001d601, + .mr = 0x00000022, + }, + [4] = { + .rate = 0 + }, +}; + +#endif diff --git a/arch/arm/mach-omap2/twl4030-generic-scripts.c b/arch/arm/mach-omap2/twl4030-generic-scripts.c new file mode 100644 index 00000000000..4293752732d --- /dev/null +++ b/arch/arm/mach-omap2/twl4030-generic-scripts.c @@ -0,0 +1,81 @@ +/* + * arch/arm/mach-omap2/twl4030-generic-scripts.c + * + * Generic power control scripts for TWL4030 + * + * Copyright (C) 2008 Nokia Corporation + * Copyright (C) 2006 Texas Instruments, Inc + * + * Written by Kalle Jokiniemi + * Peter De Schrijver + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef CONFIG_TWL4030_POWER + +#include +#include +#include +#include + +/* + * This script instructs twl4030 to first put the Reset and Control (RC) + * resources to sleep and then all the other resources. + */ + +static struct twl4030_ins sleep_on_seq[] __initdata = { + {MSG_BROADCAST(DEV_GRP_NULL, RES_GRP_RC, RES_TYPE_ALL, RES_TYPE2_R0, + RES_STATE_SLEEP), 4}, + {MSG_BROADCAST(DEV_GRP_NULL, RES_GRP_ALL, RES_TYPE_ALL, RES_TYPE2_R0, + RES_STATE_SLEEP), 4}, +}; + +static struct twl4030_script sleep_on_script __initdata = { + .script = sleep_on_seq, + .size = ARRAY_SIZE(sleep_on_seq), + .flags = TRITON_SLEEP_SCRIPT, +}; + +/* + * This script instructs twl4030 to first enable CLKEN, then wakeup the + * regulators and then all other resources. + */ + +static struct twl4030_ins wakeup_seq[] __initdata = { + {MSG_SINGULAR(DEV_GRP_NULL, 0x17, RES_STATE_ACTIVE), 0x30}, + {MSG_BROADCAST(DEV_GRP_NULL, RES_GRP_PP_PR, RES_TYPE_ALL, RES_TYPE2_R0, + RES_STATE_ACTIVE), 0x37}, + {MSG_BROADCAST(DEV_GRP_NULL, RES_GRP_ALL, RES_TYPE_ALL, RES_TYPE2_R0, + RES_STATE_ACTIVE), 0x2}, +}; + +static struct twl4030_script wakeup_script __initdata = { + .script = wakeup_seq, + .size = ARRAY_SIZE(wakeup_seq), + .flags = TRITON_WAKEUP12_SCRIPT | TRITON_WAKEUP3_SCRIPT, +}; + +static struct twl4030_script *twl4030_scripts[] __initdata = { + &sleep_on_script, + &wakeup_script, +}; + +struct twl4030_power_data generic3430_t2scripts_data __initdata = { + .scripts = twl4030_scripts, + .size = ARRAY_SIZE(twl4030_scripts), +}; + + +#endif /* CONFIG_TWL4030_POWER */ diff --git a/arch/arm/mach-omap2/twl4030-generic-scripts.h b/arch/arm/mach-omap2/twl4030-generic-scripts.h new file mode 100644 index 00000000000..fb69c2e45bf --- /dev/null +++ b/arch/arm/mach-omap2/twl4030-generic-scripts.h @@ -0,0 +1,13 @@ +#ifndef __TWL4030_GENERIC_SCRIPTS_H +#define __TWL4030_GENERIC_SCRIPTS_H + +#include + +#ifdef CONFIG_TWL4030_POWER +extern struct twl4030_power_data generic3430_t2scripts_data; +#define GENERIC3430_T2SCRIPTS_DATA &generic3430_t2scripts_data +#else +#define GENERIC3430_T2SCRIPTS_DATA NULL +#endif /* CONFIG_TWL4030_POWER */ + +#endif diff --git a/arch/arm/mach-omap2/usb-ehci.c b/arch/arm/mach-omap2/usb-ehci.c new file mode 100644 index 00000000000..23fe8578d2a --- /dev/null +++ b/arch/arm/mach-omap2/usb-ehci.c @@ -0,0 +1,160 @@ +/* + * linux/arch/arm/mach-omap2/usb-ehci.c + * + * This file will contain the board specific details for the + * Synopsys EHCI host controller on OMAP3430 + * + * Copyright (C) 2007 Texas Instruments + * Author: Vikram Pandita + * + * Generalization by: + * Felipe Balbi + * + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +static struct resource ehci_resources[] = { + [0] = { + .start = OMAP34XX_HSUSB_HOST_BASE + 0x800, + .end = OMAP34XX_HSUSB_HOST_BASE + 0x800 + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { /* general IRQ */ + .start = INT_34XX_EHCI_IRQ, + .flags = IORESOURCE_IRQ, + } +}; + +static u64 ehci_dmamask = ~(u32)0; +static struct platform_device ehci_device = { + .name = "ehci-omap", + .id = 0, + .dev = { + .dma_mask = &ehci_dmamask, + .coherent_dma_mask = 0xffffffff, + .platform_data = NULL, + }, + .num_resources = ARRAY_SIZE(ehci_resources), + .resource = ehci_resources, +}; + +/* MUX settings for EHCI pins */ +/* + * setup_ehci_io_mux - initialize IO pad mux for USBHOST + */ +static void setup_ehci_io_mux(void) +{ +#ifdef CONFIG_OMAP_EHCI_PHY_MODE + /* PHY mode of operation for board: 750-2083-001 + * ISP1504 connected to Port1 and Port2 + * Do Func Mux setting for 12-pin ULPI PHY mode + */ + + /* Port1 */ + omap_cfg_reg(Y9_3430_USB1HS_PHY_STP); + omap_cfg_reg(Y8_3430_USB1HS_PHY_CLK); + omap_cfg_reg(AA14_3430_USB1HS_PHY_DIR); + omap_cfg_reg(AA11_3430_USB1HS_PHY_NXT); + omap_cfg_reg(W13_3430_USB1HS_PHY_DATA0); + omap_cfg_reg(W12_3430_USB1HS_PHY_DATA1); + omap_cfg_reg(W11_3430_USB1HS_PHY_DATA2); + omap_cfg_reg(Y11_3430_USB1HS_PHY_DATA3); + omap_cfg_reg(W9_3430_USB1HS_PHY_DATA4); + omap_cfg_reg(Y12_3430_USB1HS_PHY_DATA5); + omap_cfg_reg(W8_3430_USB1HS_PHY_DATA6); + omap_cfg_reg(Y13_3430_USB1HS_PHY_DATA7); + + /* Port2 */ + omap_cfg_reg(AA10_3430_USB2HS_PHY_STP); + omap_cfg_reg(AA8_3430_USB2HS_PHY_CLK); + omap_cfg_reg(AA9_3430_USB2HS_PHY_DIR); + omap_cfg_reg(AB11_3430_USB2HS_PHY_NXT); + omap_cfg_reg(AB10_3430_USB2HS_PHY_DATA0); + omap_cfg_reg(AB9_3430_USB2HS_PHY_DATA1); + omap_cfg_reg(W3_3430_USB2HS_PHY_DATA2); + omap_cfg_reg(T4_3430_USB2HS_PHY_DATA3); + omap_cfg_reg(T3_3430_USB2HS_PHY_DATA4); + omap_cfg_reg(R3_3430_USB2HS_PHY_DATA5); + omap_cfg_reg(R4_3430_USB2HS_PHY_DATA6); + omap_cfg_reg(T2_3430_USB2HS_PHY_DATA7); + +#else + /* Set Func mux for : + * TLL mode of operation + * 12-pin ULPI SDR TLL mode for Port1/2/3 + */ + + /* Port1 */ + omap_cfg_reg(Y9_3430_USB1HS_TLL_STP); + omap_cfg_reg(Y8_3430_USB1HS_TLL_CLK); + omap_cfg_reg(AA14_3430_USB1HS_TLL_DIR); + omap_cfg_reg(AA11_3430_USB1HS_TLL_NXT); + omap_cfg_reg(W13_3430_USB1HS_TLL_DATA0); + omap_cfg_reg(W12_3430_USB1HS_TLL_DATA1); + omap_cfg_reg(W11_3430_USB1HS_TLL_DATA2); + omap_cfg_reg(Y11_3430_USB1HS_TLL_DATA3); + omap_cfg_reg(W9_3430_USB1HS_TLL_DATA4); + omap_cfg_reg(Y12_3430_USB1HS_TLL_DATA5); + omap_cfg_reg(W8_3430_USB1HS_TLL_DATA6); + omap_cfg_reg(Y13_3430_USB1HS_TLL_DATA7); + + /* Port2 */ + omap_cfg_reg(AA10_3430_USB2HS_TLL_STP); + omap_cfg_reg(AA8_3430_USB2HS_TLL_CLK); + omap_cfg_reg(AA9_3430_USB2HS_TLL_DIR); + omap_cfg_reg(AB11_3430_USB2HS_TLL_NXT); + omap_cfg_reg(AB10_3430_USB2HS_TLL_DATA0); + omap_cfg_reg(AB9_3430_USB2HS_TLL_DATA1); + omap_cfg_reg(W3_3430_USB2HS_TLL_DATA2); + omap_cfg_reg(T4_3430_USB2HS_TLL_DATA3); + omap_cfg_reg(T3_3430_USB2HS_TLL_DATA4); + omap_cfg_reg(R3_3430_USB2HS_TLL_DATA5); + omap_cfg_reg(R4_3430_USB2HS_TLL_DATA6); + omap_cfg_reg(T2_3430_USB2HS_TLL_DATA7); + + /* Port3 */ + omap_cfg_reg(AB3_3430_USB3HS_TLL_STP); + omap_cfg_reg(AA6_3430_USB3HS_TLL_CLK); + omap_cfg_reg(AA3_3430_USB3HS_TLL_DIR); + omap_cfg_reg(Y3_3430_USB3HS_TLL_NXT); + omap_cfg_reg(AA5_3430_USB3HS_TLL_DATA0); + omap_cfg_reg(Y4_3430_USB3HS_TLL_DATA1); + omap_cfg_reg(Y5_3430_USB3HS_TLL_DATA2); + omap_cfg_reg(W5_3430_USB3HS_TLL_DATA3); + omap_cfg_reg(AB12_3430_USB3HS_TLL_DATA4); + omap_cfg_reg(AB13_3430_USB3HS_TLL_DATA5); + omap_cfg_reg(AA13_3430_USB3HS_TLL_DATA6); + omap_cfg_reg(AA12_3430_USB3HS_TLL_DATA7); +#endif /* CONFIG_OMAP_EHCI_PHY_MODE */ + + return; +} + +void __init usb_ehci_init(void) +{ + /* Setup Pin IO MUX for EHCI */ + if (cpu_is_omap34xx()) + setup_ehci_io_mux(); + + if (platform_device_register(&ehci_device) < 0) { + printk(KERN_ERR "Unable to register HS-USB (EHCI) device\n"); + return; + } +} + + diff --git a/arch/arm/mach-omap2/usb-musb.c b/arch/arm/mach-omap2/usb-musb.c index fc74e913c41..927c2d91a03 100644 --- a/arch/arm/mach-omap2/usb-musb.c +++ b/arch/arm/mach-omap2/usb-musb.c @@ -161,17 +161,15 @@ static struct platform_device nop_xceiv_device = { void __init usb_musb_init(void) { - if (cpu_is_omap243x()) + if (cpu_is_omap243x()) { musb_resources[0].start = OMAP243X_HS_BASE; - else + musb_plat.clock = "usbhs_ick"; + } else { musb_resources[0].start = OMAP34XX_HSUSB_OTG_BASE; - musb_resources[0].end = musb_resources[0].start + SZ_8K - 1; + musb_plat.clock = "hsotgusb_ick"; + } - /* - * REVISIT: This line can be removed once all the platforms using - * musb_core.c have been converted to use use clkdev. - */ - musb_plat.clock = "ick"; + musb_resources[0].end = musb_resources[0].start + SZ_8K - 1; #ifdef CONFIG_NOP_USB_XCEIV if (platform_device_register(&nop_xceiv_device) < 0) { diff --git a/arch/arm/mach-omap2/usb-tusb6010.c b/arch/arm/mach-omap2/usb-tusb6010.c index 15e509013de..59c1d57e507 100644 --- a/arch/arm/mach-omap2/usb-tusb6010.c +++ b/arch/arm/mach-omap2/usb-tusb6010.c @@ -175,8 +175,6 @@ static int tusb_set_sync_mode(unsigned sysclk_ps, unsigned fclk_ps) return gpmc_cs_set_timings(sync_cs, &t); } -extern unsigned long gpmc_get_fclk_period(void); - /* tusb driver calls this when it changes the chip's clocking */ int tusb6010_platform_retime(unsigned is_refclk) { @@ -187,7 +185,7 @@ int tusb6010_platform_retime(unsigned is_refclk) unsigned sysclk_ps; int status; - if (!refclk_psec) + if (!refclk_psec || sysclk_ps == 0) return -ENODEV; sysclk_ps = is_refclk ? refclk_psec : TUSB6010_OSCCLK_60; diff --git a/arch/arm/plat-omap/Kconfig b/arch/arm/plat-omap/Kconfig index 9dd68fafb37..b37fc101e9f 100644 --- a/arch/arm/plat-omap/Kconfig +++ b/arch/arm/plat-omap/Kconfig @@ -73,6 +73,39 @@ config OMAP_RESET_CLOCKS probably do not want this option enabled until your device drivers work properly. +config OMAP_BOOT_TAG + bool "OMAP bootloader information passing" + depends on ARCH_OMAP + default n + help + Say Y, if you have a bootloader which passes information + about your board and its peripheral configuration. + +config OMAP_BOOT_REASON + bool "Support for boot reason" + depends on OMAP_BOOT_TAG + default n + help + Say Y, if you want to have a procfs entry for reading the boot + reason in user-space. + +config OMAP_COMPONENT_VERSION + bool "Support for component version display" + depends on OMAP_BOOT_TAG && PROC_FS + default n + help + Say Y, if you want to have a procfs entry for reading component + versions (supplied by the bootloader) in user-space. + +config OMAP_GPIO_SWITCH + bool "GPIO switch support" + default n + help + Say Y, if you want to have support for reporting of GPIO + switches (e.g. cover switches) via sysfs. Your bootloader has + to provide information about the switches to the kernel via the + ATAG_BOARD mechanism if they're not defined by the board config. + config OMAP_MUX bool "OMAP multiplexing support" depends on ARCH_OMAP diff --git a/arch/arm/plat-omap/Makefile b/arch/arm/plat-omap/Makefile index 04a100cfb8e..3ebc09ed0f6 100644 --- a/arch/arm/plat-omap/Makefile +++ b/arch/arm/plat-omap/Makefile @@ -16,6 +16,9 @@ obj-$(CONFIG_OMAP_MCBSP) += mcbsp.o obj-$(CONFIG_CPU_FREQ) += cpu-omap.o obj-$(CONFIG_OMAP_DM_TIMER) += dmtimer.o +obj-$(CONFIG_OMAP_BOOT_REASON) += bootreason.o +obj-$(CONFIG_OMAP_COMPONENT_VERSION) += component-version.o +obj-$(CONFIG_OMAP_GPIO_SWITCH) += gpio-switch.o obj-$(CONFIG_OMAP_DEBUG_DEVICES) += debug-devices.o obj-$(CONFIG_OMAP_DEBUG_LEDS) += debug-leds.o i2c-omap-$(CONFIG_I2C_OMAP) := i2c.o diff --git a/arch/arm/plat-omap/bootreason.c b/arch/arm/plat-omap/bootreason.c new file mode 100644 index 00000000000..d527b1bb06f --- /dev/null +++ b/arch/arm/plat-omap/bootreason.c @@ -0,0 +1,79 @@ +/* + * linux/arch/arm/plat-omap/bootreason.c + * + * OMAP Bootreason passing + * + * Copyright (c) 2004 Nokia + * + * Written by David Weinehall + * + * 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include +#include +#include + +static char boot_reason[16]; + +static int omap_bootreason_read_proc(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int len = 0; + + len += sprintf(page + len, "%s\n", boot_reason); + + *start = page + off; + + if (len > off) + len -= off; + else + len = 0; + + return len < count ? len : count; +} + +static int __init bootreason_init(void) +{ + const struct omap_boot_reason_config *cfg; + int reason_valid = 0; + + cfg = omap_get_config(OMAP_TAG_BOOT_REASON, struct omap_boot_reason_config); + if (cfg != NULL) { + strncpy(boot_reason, cfg->reason_str, sizeof(cfg->reason_str)); + boot_reason[sizeof(cfg->reason_str)] = 0; + reason_valid = 1; + } else { + /* Read the boot reason from the OMAP registers */ + } + + if (!reason_valid) + return -ENOENT; + + printk(KERN_INFO "Bootup reason: %s\n", boot_reason); + + if (!create_proc_read_entry("bootreason", S_IRUGO, NULL, + omap_bootreason_read_proc, NULL)) + return -ENOMEM; + + return 0; +} + +late_initcall(bootreason_init); diff --git a/arch/arm/plat-omap/common.c b/arch/arm/plat-omap/common.c index d1797147732..28666126fd3 100644 --- a/arch/arm/plat-omap/common.c +++ b/arch/arm/plat-omap/common.c @@ -40,12 +40,32 @@ #define NO_LENGTH_CHECK 0xffffffff -unsigned char omap_bootloader_tag[512]; +unsigned char omap_bootloader_tag[1024]; int omap_bootloader_tag_len; struct omap_board_config_kernel *omap_board_config; int omap_board_config_size; +#ifdef CONFIG_OMAP_BOOT_TAG + +static int __init parse_tag_omap(const struct tag *tag) +{ + u32 size = tag->hdr.size - (sizeof(tag->hdr) >> 2); + + size <<= 2; + if (size > sizeof(omap_bootloader_tag)) + return -1; + + memcpy(omap_bootloader_tag, tag->u.omap.data, size); + omap_bootloader_tag_len = size; + + return 0; +} + +__tagtable(ATAG_BOARD, parse_tag_omap); + +#endif + static const void *get_config(u16 tag, size_t len, int skip, size_t *len_out) { struct omap_board_config_kernel *kinfo = NULL; diff --git a/arch/arm/plat-omap/component-version.c b/arch/arm/plat-omap/component-version.c new file mode 100644 index 00000000000..3c9d5230cd8 --- /dev/null +++ b/arch/arm/plat-omap/component-version.c @@ -0,0 +1,65 @@ +/* + * linux/arch/arm/plat-omap/component-version.c + * + * Copyright (C) 2005 Nokia Corporation + * Written by Juha Yrjölä + * + * 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 +#include +#include +#include +#include +#include + +static int component_version_read_proc(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int len, i; + const struct omap_version_config *ver; + char *p; + + i = 0; + p = page; + while ((ver = omap_get_nr_config(OMAP_TAG_VERSION_STR, + struct omap_version_config, i)) != NULL) { + p += sprintf(p, "%-12s%s\n", ver->component, ver->version); + i++; + } + + len = (p - page) - off; + if (len < 0) + len = 0; + + *eof = (len <= count) ? 1 : 0; + *start = page + off; + + return len; +} + +static int __init component_version_init(void) +{ + if (omap_get_config(OMAP_TAG_VERSION_STR, struct omap_version_config) == NULL) + return -ENODEV; + if (!create_proc_read_entry("component_version", S_IRUGO, NULL, + component_version_read_proc, NULL)) + return -ENOMEM; + + return 0; +} + +static void __exit component_version_exit(void) +{ + remove_proc_entry("component_version", NULL); +} + +late_initcall(component_version_init); +module_exit(component_version_exit); + +MODULE_AUTHOR("Juha Yrjölä "); +MODULE_DESCRIPTION("Component version driver"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/plat-omap/dma.c b/arch/arm/plat-omap/dma.c index 21cc0142b97..609301860c5 100644 --- a/arch/arm/plat-omap/dma.c +++ b/arch/arm/plat-omap/dma.c @@ -738,7 +738,7 @@ int omap_request_dma(int dev_id, const char *dev_name, * id. */ dma_write(dev_id | (1 << 10), CCR(free_ch)); - } else if (cpu_is_omap7xx() || cpu_is_omap15xx()) { + } else if (cpu_is_omap730() || cpu_is_omap15xx()) { dma_write(dev_id, CCR(free_ch)); } @@ -760,19 +760,12 @@ void omap_free_dma(int lch) { unsigned long flags; - spin_lock_irqsave(&dma_chan_lock, flags); if (dma_chan[lch].dev_id == -1) { pr_err("omap_dma: trying to free unallocated DMA channel %d\n", lch); - spin_unlock_irqrestore(&dma_chan_lock, flags); return; } - dma_chan[lch].dev_id = -1; - dma_chan[lch].next_lch = -1; - dma_chan[lch].callback = NULL; - spin_unlock_irqrestore(&dma_chan_lock, flags); - if (cpu_class_is_omap1()) { /* Disable all DMA interrupts for the channel. */ dma_write(0, CICR(lch)); @@ -798,6 +791,12 @@ void omap_free_dma(int lch) dma_write(0, CCR(lch)); omap_clear_dma(lch); } + + spin_lock_irqsave(&dma_chan_lock, flags); + dma_chan[lch].dev_id = -1; + dma_chan[lch].next_lch = -1; + dma_chan[lch].callback = NULL; + spin_unlock_irqrestore(&dma_chan_lock, flags); } EXPORT_SYMBOL(omap_free_dma); @@ -2346,7 +2345,7 @@ static int __init omap_init_dma(void) printk(KERN_INFO "DMA support for OMAP15xx initialized\n"); dma_chan_count = 9; enable_1510_mode = 1; - } else if (cpu_is_omap16xx() || cpu_is_omap7xx()) { + } else if (cpu_is_omap16xx() || cpu_is_omap730()) { printk(KERN_INFO "OMAP DMA hardware version %d\n", dma_read(HW_ID)); printk(KERN_INFO "DMA capabilities: %08x:%08x:%04x:%04x:%04x\n", @@ -2424,6 +2423,19 @@ static int __init omap_init_dma(void) if (cpu_class_is_omap2()) setup_irq(INT_24XX_SDMA_IRQ0, &omap24xx_dma_irq); + /* Enable smartidle idlemodes and autoidle */ + if (cpu_is_omap34xx()) { + u32 v = dma_read(OCP_SYSCONFIG); + v &= ~(DMA_SYSCONFIG_MIDLEMODE_MASK | + DMA_SYSCONFIG_SIDLEMODE_MASK | + DMA_SYSCONFIG_AUTOIDLE); + v |= (DMA_SYSCONFIG_MIDLEMODE(DMA_IDLEMODE_SMARTIDLE) | + DMA_SYSCONFIG_SIDLEMODE(DMA_IDLEMODE_SMARTIDLE) | + DMA_SYSCONFIG_AUTOIDLE); + dma_write(v , OCP_SYSCONFIG); + } + + /* FIXME: Update LCD DMA to work on 24xx */ if (cpu_class_is_omap1()) { r = request_irq(INT_DMA_LCD, lcd_dma_irq_handler, 0, diff --git a/arch/arm/plat-omap/dmtimer.c b/arch/arm/plat-omap/dmtimer.c index bfd47570cc9..a05205c12f7 100644 --- a/arch/arm/plat-omap/dmtimer.c +++ b/arch/arm/plat-omap/dmtimer.c @@ -238,7 +238,7 @@ static struct omap_dm_timer omap3_dm_timers[] = { { .phys_base = 0x49040000, .irq = INT_24XX_GPTIMER9 }, { .phys_base = 0x48086000, .irq = INT_24XX_GPTIMER10 }, { .phys_base = 0x48088000, .irq = INT_24XX_GPTIMER11 }, - { .phys_base = 0x48304000, .irq = INT_24XX_GPTIMER12 }, + { .phys_base = 0x48304000, .irq = INT_34XX_GPT12_IRQ }, }; static const char *omap3_dm_source_names[] __initdata = { @@ -321,11 +321,9 @@ static void omap_dm_timer_reset(struct omap_dm_timer *timer) l |= 0x2 << 8; /* Set clock activity to perserve f-clock on idle */ /* - * Enable wake-up only for GPT1 on OMAP2 CPUs. - * FIXME: All timers should have wake-up enabled and clear - * PRCM status. + * Enable wake-up on OMAP2 CPUs. */ - if (cpu_class_is_omap2() && (timer == &dm_timers[0])) + if (cpu_class_is_omap2()) l |= 1 << 2; omap_dm_timer_write_reg(timer, OMAP_TIMER_OCP_CFG_REG, l); diff --git a/arch/arm/plat-omap/fb.c b/arch/arm/plat-omap/fb.c index ce6b4baeede..3746222bed1 100644 --- a/arch/arm/plat-omap/fb.c +++ b/arch/arm/plat-omap/fb.c @@ -206,9 +206,10 @@ void __init omapfb_reserve_sdram(void) config_invalid = 1; return; } - if (rg.paddr) + if (rg.paddr) { reserve_bootmem(rg.paddr, rg.size, BOOTMEM_DEFAULT); - reserved += rg.size; + reserved += rg.size; + } omapfb_config.mem_desc.region[i] = rg; configured_regions++; } diff --git a/arch/arm/plat-omap/gpio-switch.c b/arch/arm/plat-omap/gpio-switch.c new file mode 100644 index 00000000000..9053ea08696 --- /dev/null +++ b/arch/arm/plat-omap/gpio-switch.c @@ -0,0 +1,558 @@ +/* + * linux/arch/arm/plat-omap/gpio-switch.c + * + * Copyright (C) 2004-2006 Nokia Corporation + * Written by Juha Yrjölä + * and Paul Mundt + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct gpio_switch { + char name[14]; + u16 gpio; + unsigned flags:4; + unsigned type:4; + unsigned state:1; + unsigned both_edges:1; + + u16 debounce_rising; + u16 debounce_falling; + + void (* notify)(void *data, int state); + void *notify_data; + + struct work_struct work; + struct timer_list timer; + struct platform_device pdev; + + struct list_head node; +}; + +static LIST_HEAD(gpio_switches); +static struct platform_device *gpio_sw_platform_dev; +static struct platform_driver gpio_sw_driver; + +static const struct omap_gpio_switch *board_gpio_sw_table; +static int board_gpio_sw_count; + +static const char *cover_str[2] = { "open", "closed" }; +static const char *connection_str[2] = { "disconnected", "connected" }; +static const char *activity_str[2] = { "inactive", "active" }; + +/* + * GPIO switch state default debounce delay in ms + */ +#define OMAP_GPIO_SW_DEFAULT_DEBOUNCE 10 + +static const char **get_sw_str(struct gpio_switch *sw) +{ + switch (sw->type) { + case OMAP_GPIO_SWITCH_TYPE_COVER: + return cover_str; + case OMAP_GPIO_SWITCH_TYPE_CONNECTION: + return connection_str; + case OMAP_GPIO_SWITCH_TYPE_ACTIVITY: + return activity_str; + default: + BUG(); + return NULL; + } +} + +static const char *get_sw_type(struct gpio_switch *sw) +{ + switch (sw->type) { + case OMAP_GPIO_SWITCH_TYPE_COVER: + return "cover"; + case OMAP_GPIO_SWITCH_TYPE_CONNECTION: + return "connection"; + case OMAP_GPIO_SWITCH_TYPE_ACTIVITY: + return "activity"; + default: + BUG(); + return NULL; + } +} + +static void print_sw_state(struct gpio_switch *sw, int state) +{ + const char **str; + + str = get_sw_str(sw); + if (str != NULL) + printk(KERN_INFO "%s (GPIO %d) is now %s\n", sw->name, sw->gpio, str[state]); +} + +static int gpio_sw_get_state(struct gpio_switch *sw) +{ + int state; + + state = gpio_get_value(sw->gpio); + if (sw->flags & OMAP_GPIO_SWITCH_FLAG_INVERTED) + state = !state; + + return state; +} + +static ssize_t gpio_sw_state_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct gpio_switch *sw = dev_get_drvdata(dev); + const char **str; + char state[16]; + int enable; + + if (!(sw->flags & OMAP_GPIO_SWITCH_FLAG_OUTPUT)) + return -EPERM; + + if (sscanf(buf, "%15s", state) != 1) + return -EINVAL; + + str = get_sw_str(sw); + if (strcmp(state, str[0]) == 0) + sw->state = enable = 0; + else if (strcmp(state, str[1]) == 0) + sw->state = enable = 1; + else + return -EINVAL; + + if (sw->flags & OMAP_GPIO_SWITCH_FLAG_INVERTED) + enable = !enable; + gpio_set_value(sw->gpio, enable); + + return count; +} + +static ssize_t gpio_sw_state_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct gpio_switch *sw = dev_get_drvdata(dev); + const char **str; + + str = get_sw_str(sw); + return sprintf(buf, "%s\n", str[sw->state]); +} + +static DEVICE_ATTR(state, S_IRUGO | S_IWUSR, gpio_sw_state_show, + gpio_sw_state_store); + +static ssize_t gpio_sw_type_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct gpio_switch *sw = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", get_sw_type(sw)); +} + +static DEVICE_ATTR(type, S_IRUGO, gpio_sw_type_show, NULL); + +static ssize_t gpio_sw_direction_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct gpio_switch *sw = dev_get_drvdata(dev); + int is_output; + + is_output = sw->flags & OMAP_GPIO_SWITCH_FLAG_OUTPUT; + return sprintf(buf, "%s\n", is_output ? "output" : "input"); +} + +static DEVICE_ATTR(direction, S_IRUGO, gpio_sw_direction_show, NULL); + + +static irqreturn_t gpio_sw_irq_handler(int irq, void *arg) +{ + struct gpio_switch *sw = arg; + unsigned long timeout; + int state; + + if (!sw->both_edges) { + if (gpio_get_value(sw->gpio)) + set_irq_type(gpio_to_irq(sw->gpio), IRQ_TYPE_EDGE_FALLING); + else + set_irq_type(gpio_to_irq(sw->gpio), IRQ_TYPE_EDGE_RISING); + } + + state = gpio_sw_get_state(sw); + if (sw->state == state) + return IRQ_HANDLED; + + if (state) + timeout = sw->debounce_rising; + else + timeout = sw->debounce_falling; + if (!timeout) + schedule_work(&sw->work); + else + mod_timer(&sw->timer, jiffies + msecs_to_jiffies(timeout)); + + return IRQ_HANDLED; +} + +static void gpio_sw_timer(unsigned long arg) +{ + struct gpio_switch *sw = (struct gpio_switch *) arg; + + schedule_work(&sw->work); +} + +static void gpio_sw_handler(struct work_struct *work) +{ + struct gpio_switch *sw = container_of(work, struct gpio_switch, work); + int state; + + state = gpio_sw_get_state(sw); + if (sw->state == state) + return; + + sw->state = state; + if (sw->notify != NULL) + sw->notify(sw->notify_data, state); + sysfs_notify(&sw->pdev.dev.kobj, NULL, "state"); + print_sw_state(sw, state); +} + +static int __init can_do_both_edges(struct gpio_switch *sw) +{ + if (!cpu_class_is_omap1()) + return 1; + if (OMAP_GPIO_IS_MPUIO(sw->gpio)) + return 0; + else + return 1; +} + +static void gpio_sw_release(struct device *dev) +{ +} + +static int __init new_switch(struct gpio_switch *sw) +{ + int r, direction, trigger; + + switch (sw->type) { + case OMAP_GPIO_SWITCH_TYPE_COVER: + case OMAP_GPIO_SWITCH_TYPE_CONNECTION: + case OMAP_GPIO_SWITCH_TYPE_ACTIVITY: + break; + default: + printk(KERN_ERR "invalid GPIO switch type: %d\n", sw->type); + return -EINVAL; + } + + sw->pdev.name = sw->name; + sw->pdev.id = -1; + + sw->pdev.dev.parent = &gpio_sw_platform_dev->dev; + sw->pdev.dev.driver = &gpio_sw_driver.driver; + sw->pdev.dev.release = gpio_sw_release; + + r = platform_device_register(&sw->pdev); + if (r) { + printk(KERN_ERR "gpio-switch: platform device registration " + "failed for %s", sw->name); + return r; + } + dev_set_drvdata(&sw->pdev.dev, sw); + + r = gpio_request(sw->gpio, sw->name); + if (r < 0) { + platform_device_unregister(&sw->pdev); + return r; + } + + /* input: 1, output: 0 */ + direction = !(sw->flags & OMAP_GPIO_SWITCH_FLAG_OUTPUT); + if (direction) { + gpio_direction_input(sw->gpio); + sw->state = gpio_sw_get_state(sw); + } else { + int state = sw->state = !!(sw->flags & + OMAP_GPIO_SWITCH_FLAG_OUTPUT_INIT_ACTIVE); + + if (sw->flags & OMAP_GPIO_SWITCH_FLAG_INVERTED) + state = !state; + gpio_direction_output(sw->gpio, state); + } + + r = 0; + r |= device_create_file(&sw->pdev.dev, &dev_attr_state); + r |= device_create_file(&sw->pdev.dev, &dev_attr_type); + r |= device_create_file(&sw->pdev.dev, &dev_attr_direction); + if (r) + printk(KERN_ERR "gpio-switch: attribute file creation " + "failed for %s\n", sw->name); + + if (!direction) + return 0; + + if (can_do_both_edges(sw)) { + trigger = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING; + sw->both_edges = 1; + } else { + if (gpio_get_value(sw->gpio)) + trigger = IRQF_TRIGGER_FALLING; + else + trigger = IRQF_TRIGGER_RISING; + } + r = request_irq(gpio_to_irq(sw->gpio), gpio_sw_irq_handler, + IRQF_SHARED | trigger, sw->name, sw); + if (r < 0) { + printk(KERN_ERR "gpio-switch: request_irq() failed " + "for GPIO %d\n", sw->gpio); + platform_device_unregister(&sw->pdev); + gpio_free(sw->gpio); + return r; + } + + INIT_WORK(&sw->work, gpio_sw_handler); + init_timer(&sw->timer); + + sw->timer.function = gpio_sw_timer; + sw->timer.data = (unsigned long)sw; + + list_add(&sw->node, &gpio_switches); + + return 0; +} + +static int __init add_atag_switches(void) +{ + const struct omap_gpio_switch_config *cfg; + struct gpio_switch *sw; + int i, r; + + for (i = 0; ; i++) { + cfg = omap_get_nr_config(OMAP_TAG_GPIO_SWITCH, + struct omap_gpio_switch_config, i); + if (cfg == NULL) + break; + sw = kzalloc(sizeof(*sw), GFP_KERNEL); + if (sw == NULL) { + printk(KERN_ERR "gpio-switch: kmalloc failed\n"); + return -ENOMEM; + } + strncpy(sw->name, cfg->name, sizeof(cfg->name)); + sw->gpio = cfg->gpio; + sw->flags = cfg->flags; + sw->type = cfg->type; + sw->debounce_rising = OMAP_GPIO_SW_DEFAULT_DEBOUNCE; + sw->debounce_falling = OMAP_GPIO_SW_DEFAULT_DEBOUNCE; + if ((r = new_switch(sw)) < 0) { + kfree(sw); + return r; + } + } + return 0; +} + +static struct gpio_switch * __init find_switch(int gpio, const char *name) +{ + struct gpio_switch *sw; + + list_for_each_entry(sw, &gpio_switches, node) { + if ((gpio < 0 || sw->gpio != gpio) && + (name == NULL || strcmp(sw->name, name) != 0)) + continue; + + if (gpio < 0 || name == NULL) + goto no_check; + + if (strcmp(sw->name, name) != 0) + printk("gpio-switch: name mismatch for %d (%s, %s)\n", + gpio, name, sw->name); + else if (sw->gpio != gpio) + printk("gpio-switch: GPIO mismatch for %s (%d, %d)\n", + name, gpio, sw->gpio); +no_check: + return sw; + } + return NULL; +} + +static int __init add_board_switches(void) +{ + int i; + + for (i = 0; i < board_gpio_sw_count; i++) { + const struct omap_gpio_switch *cfg; + struct gpio_switch *sw; + int r; + + cfg = board_gpio_sw_table + i; + if (strlen(cfg->name) > sizeof(sw->name) - 1) + return -EINVAL; + /* Check whether we only update an existing switch + * or add a new switch. */ + sw = find_switch(cfg->gpio, cfg->name); + if (sw != NULL) { + sw->debounce_rising = cfg->debounce_rising; + sw->debounce_falling = cfg->debounce_falling; + sw->notify = cfg->notify; + sw->notify_data = cfg->notify_data; + continue; + } else { + if (cfg->gpio < 0 || cfg->name == NULL) { + printk("gpio-switch: required switch not " + "found (%d, %s)\n", cfg->gpio, + cfg->name); + continue; + } + } + sw = kzalloc(sizeof(*sw), GFP_KERNEL); + if (sw == NULL) { + printk(KERN_ERR "gpio-switch: kmalloc failed\n"); + return -ENOMEM; + } + strlcpy(sw->name, cfg->name, sizeof(sw->name)); + sw->gpio = cfg->gpio; + sw->flags = cfg->flags; + sw->type = cfg->type; + sw->debounce_rising = cfg->debounce_rising; + sw->debounce_falling = cfg->debounce_falling; + sw->notify = cfg->notify; + sw->notify_data = cfg->notify_data; + if ((r = new_switch(sw)) < 0) { + kfree(sw); + return r; + } + } + return 0; +} + +static void gpio_sw_cleanup(void) +{ + struct gpio_switch *sw = NULL, *old = NULL; + + list_for_each_entry(sw, &gpio_switches, node) { + if (old != NULL) + kfree(old); + flush_scheduled_work(); + del_timer_sync(&sw->timer); + + free_irq(gpio_to_irq(sw->gpio), sw); + + device_remove_file(&sw->pdev.dev, &dev_attr_state); + device_remove_file(&sw->pdev.dev, &dev_attr_type); + device_remove_file(&sw->pdev.dev, &dev_attr_direction); + + platform_device_unregister(&sw->pdev); + gpio_free(sw->gpio); + old = sw; + } + kfree(old); +} + +static void __init report_initial_state(void) +{ + struct gpio_switch *sw; + + list_for_each_entry(sw, &gpio_switches, node) { + int state; + + state = gpio_get_value(sw->gpio); + if (sw->flags & OMAP_GPIO_SWITCH_FLAG_INVERTED) + state = !state; + if (sw->notify != NULL) + sw->notify(sw->notify_data, state); + print_sw_state(sw, state); + } +} + +static int gpio_sw_remove(struct platform_device *dev) +{ + return 0; +} + +static struct platform_driver gpio_sw_driver = { + .remove = gpio_sw_remove, + .driver = { + .name = "gpio-switch", + }, +}; + +void __init omap_register_gpio_switches(const struct omap_gpio_switch *tbl, + int count) +{ + BUG_ON(board_gpio_sw_table != NULL); + + board_gpio_sw_table = tbl; + board_gpio_sw_count = count; +} + +static int __init gpio_sw_init(void) +{ + int r; + + printk(KERN_INFO "OMAP GPIO switch handler initializing\n"); + + r = platform_driver_register(&gpio_sw_driver); + if (r) + return r; + + gpio_sw_platform_dev = platform_device_register_simple("gpio-switch", + -1, NULL, 0); + if (IS_ERR(gpio_sw_platform_dev)) { + r = PTR_ERR(gpio_sw_platform_dev); + goto err1; + } + + r = add_atag_switches(); + if (r < 0) + goto err2; + + r = add_board_switches(); + if (r < 0) + goto err2; + + report_initial_state(); + + return 0; +err2: + gpio_sw_cleanup(); + platform_device_unregister(gpio_sw_platform_dev); +err1: + platform_driver_unregister(&gpio_sw_driver); + return r; +} + +static void __exit gpio_sw_exit(void) +{ + gpio_sw_cleanup(); + platform_device_unregister(gpio_sw_platform_dev); + platform_driver_unregister(&gpio_sw_driver); +} + +#ifndef MODULE +late_initcall(gpio_sw_init); +#else +module_init(gpio_sw_init); +#endif +module_exit(gpio_sw_exit); + +MODULE_AUTHOR("Juha Yrjölä , Paul Mundt base + OMAP24XX_GPIO_IRQSTATUS2; if (cpu_is_omap24xx() || cpu_is_omap34xx()) - __raw_writel(gpio_mask, bank->base + OMAP24XX_GPIO_IRQSTATUS2); + __raw_writel(gpio_mask, reg); + + /* Flush posted write for the irq status to avoid spurious interrupts */ + __raw_readl(reg); #endif } @@ -921,13 +925,10 @@ static int _set_gpio_wakeup(struct gpio_bank *bank, int gpio, int enable) case METHOD_MPUIO: case METHOD_GPIO_1610: spin_lock_irqsave(&bank->lock, flags); - if (enable) { + if (enable) bank->suspend_wakeup |= (1 << gpio); - enable_irq_wake(bank->irq); - } else { - disable_irq_wake(bank->irq); + else bank->suspend_wakeup &= ~(1 << gpio); - } spin_unlock_irqrestore(&bank->lock, flags); return 0; #endif @@ -940,13 +941,10 @@ static int _set_gpio_wakeup(struct gpio_bank *bank, int gpio, int enable) return -EINVAL; } spin_lock_irqsave(&bank->lock, flags); - if (enable) { + if (enable) bank->suspend_wakeup |= (1 << gpio); - enable_irq_wake(bank->irq); - } else { - disable_irq_wake(bank->irq); + else bank->suspend_wakeup &= ~(1 << gpio); - } spin_unlock_irqrestore(&bank->lock, flags); return 0; #endif diff --git a/arch/arm/plat-omap/include/mach/board-nokia.h b/arch/arm/plat-omap/include/mach/board-nokia.h new file mode 100644 index 00000000000..198d761136b --- /dev/null +++ b/arch/arm/plat-omap/include/mach/board-nokia.h @@ -0,0 +1,67 @@ +/* + * arch/arm/plat-omap/include/mach/board-nokia.h + * + * Information structures for Nokia-specific board config data + * + * Copyright (C) 2005 Nokia Corporation + */ + +#ifndef __ASM_ARCH_OMAP_NOKIA_H +#define __ASM_ARCH_OMAP_NOKIA_H + +#include + +struct tsc2301_platform_data; +struct dsp_kfunc_device; +extern void n800_bt_init(void); +extern void n800_dsp_init(void); +extern void n800_flash_init(void); +extern void n800_mmc_init(void); +extern void n800_pm_init(void); +extern void n800_usb_init(void); +extern void n800_cam_init(void); +extern void n800_audio_init(struct tsc2301_platform_data *); +extern int n800_audio_enable(struct dsp_kfunc_device *kdev, int stage); +extern int n800_audio_disable(struct dsp_kfunc_device *kdev, int stage); +extern void n800_mmc_slot1_cover_handler(void *arg, int state); + +#define OMAP_TAG_NOKIA_BT 0x4e01 +#define OMAP_TAG_WLAN_CX3110X 0x4e02 +#define OMAP_TAG_CBUS 0x4e03 +#define OMAP_TAG_EM_ASIC_BB5 0x4e04 + +#define BT_CHIP_CSR 1 +#define BT_CHIP_TI 2 + +#define BT_SYSCLK_12 1 +#define BT_SYSCLK_38_4 2 + +struct omap_bluetooth_config { + u8 chip_type; + u8 bt_wakeup_gpio; + u8 host_wakeup_gpio; + u8 reset_gpio; + u8 bt_uart; + u8 bd_addr[6]; + u8 bt_sysclk; +}; + +struct omap_wlan_cx3110x_config { + u8 chip_type; + s16 power_gpio; + s16 irq_gpio; + s16 spi_cs_gpio; +}; + +struct omap_cbus_config { + s16 clk_gpio; + s16 dat_gpio; + s16 sel_gpio; +}; + +struct omap_em_asic_bb5_config { + s16 retu_irq_gpio; + s16 tahvo_irq_gpio; +}; + +#endif diff --git a/arch/arm/plat-omap/include/mach/board-rx51.h b/arch/arm/plat-omap/include/mach/board-rx51.h new file mode 100644 index 00000000000..7b3287202f1 --- /dev/null +++ b/arch/arm/plat-omap/include/mach/board-rx51.h @@ -0,0 +1,47 @@ +/* + * linux/include/asm-arm/arch-omap/board-rx51.h + * + * Copyright (C) 2007 Nokia + * + * Hardware definitions for Nokia RX-51 + * based on board-3430sdp.h + * + * + * 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __ASM_ARCH_OMAP_BOARD_RX51_H +#define __ASM_ARCH_OMAP_BOARD_RX51_H + +#include + +#ifdef CONFIG_USB_MUSB_SOC +extern void rx51_usb_init(void); +#else +static inline void rx51_usb_init(void) { } +#endif + +extern void n800_bt_init(void); + +struct omap_sdrc_params *rx51_get_sdram_timings(void); + +#endif /* __ASM_ARCH_OMAP_BOARD_RX51_H */ + diff --git a/arch/arm/plat-omap/include/mach/board.h b/arch/arm/plat-omap/include/mach/board.h index 50ea79a0efa..ae8c2db22eb 100644 --- a/arch/arm/plat-omap/include/mach/board.h +++ b/arch/arm/plat-omap/include/mach/board.h @@ -23,9 +23,12 @@ #define OMAP_TAG_FBMEM 0x4f08 #define OMAP_TAG_STI_CONSOLE 0x4f09 #define OMAP_TAG_CAMERA_SENSOR 0x4f0a +#define OMAP_TAG_PARTITION 0x4f0b +#define OMAP_TAG_TEA5761 0x4f10 +#define OMAP_TAG_TMP105 0x4f11 #define OMAP_TAG_BOOT_REASON 0x4f80 -#define OMAP_TAG_FLASH_PART 0x4f81 +#define OMAP_TAG_FLASH_PART_STR 0x4f81 #define OMAP_TAG_VERSION_STR 0x4f82 struct omap_clock_config { @@ -43,12 +46,6 @@ struct omap_sti_console_config { u8 channel; }; -struct omap_camera_sensor_config { - u16 reset_gpio; - int (*power_on)(void * data); - int (*power_off)(void * data); -}; - struct omap_usb_config { /* Configure drivers according to the connectors on your board: * - "A" connector (rectagular) @@ -108,9 +105,9 @@ struct omap_pwm_led_platform_data { struct omap_gpio_switch_config { char name[12]; u16 gpio; - int flags:4; - int type:4; - int key_code:24; /* Linux key code */ + u8 flags:4; + u8 type:4; + unsigned int key_code:24; /* Linux key code */ }; struct omap_uart_config { @@ -118,8 +115,25 @@ struct omap_uart_config { unsigned int enabled_uarts; }; +struct omap_tea5761_config { + u16 enable_gpio; +}; + +/* This cannot be passed from the bootloader */ +struct omap_tmp105_config { + u16 tmp105_irq_pin; + int (* set_power)(int enable); +}; + +struct omap_partition_config { + char name[16]; + unsigned int size; + unsigned int offset; + /* same as in include/linux/mtd/partitions.h */ + unsigned int mask_flags; +}; -struct omap_flash_part_config { +struct omap_flash_part_str_config { char part_table[0]; }; diff --git a/arch/arm/plat-omap/include/mach/dma.h b/arch/arm/plat-omap/include/mach/dma.h index 54fe9665b18..224b0770ec2 100644 --- a/arch/arm/plat-omap/include/mach/dma.h +++ b/arch/arm/plat-omap/include/mach/dma.h @@ -387,6 +387,21 @@ #define DMA_THREAD_FIFO_25 (0x02 << 14) #define DMA_THREAD_FIFO_50 (0x03 << 14) +/* DMA4_OCP_SYSCONFIG bits */ +#define DMA_SYSCONFIG_MIDLEMODE_MASK (3 << 12) +#define DMA_SYSCONFIG_CLOCKACTIVITY_MASK (3 << 8) +#define DMA_SYSCONFIG_EMUFREE (1 << 5) +#define DMA_SYSCONFIG_SIDLEMODE_MASK (3 << 3) +#define DMA_SYSCONFIG_SOFTRESET (1 << 2) +#define DMA_SYSCONFIG_AUTOIDLE (1 << 0) + +#define DMA_SYSCONFIG_MIDLEMODE(n) ((n) << 12) +#define DMA_SYSCONFIG_SIDLEMODE(n) ((n) << 3) + +#define DMA_IDLEMODE_SMARTIDLE 0x2 +#define DMA_IDLEMODE_NO_IDLE 0x1 +#define DMA_IDLEMODE_FORCE_IDLE 0x0 + /* Chaining modes*/ #ifndef CONFIG_ARCH_OMAP1 #define OMAP_DMA_STATIC_CHAIN 0x1 diff --git a/arch/arm/plat-omap/include/mach/eac.h b/arch/arm/plat-omap/include/mach/eac.h deleted file mode 100644 index 9e62cf03027..00000000000 --- a/arch/arm/plat-omap/include/mach/eac.h +++ /dev/null @@ -1,100 +0,0 @@ -/* - * arch/arm/plat-omap/include/mach2/eac.h - * - * Defines for Enhanced Audio Controller - * - * Contact: Jarkko Nikula - * - * Copyright (C) 2006 Nokia Corporation - * Copyright (C) 2004 Texas Instruments, Inc. - * - * 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. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA - * - */ - -#ifndef __ASM_ARM_ARCH_OMAP2_EAC_H -#define __ASM_ARM_ARCH_OMAP2_EAC_H - -#include -#include -#include - -#include - -/* master codec clock source */ -#define EAC_MCLK_EXT_MASK 0x100 -enum eac_mclk_src { - EAC_MCLK_INT_11290000, /* internal 96 MHz / 8.5 = 11.29 Mhz */ - EAC_MCLK_EXT_11289600 = EAC_MCLK_EXT_MASK, - EAC_MCLK_EXT_12288000, - EAC_MCLK_EXT_2x11289600, - EAC_MCLK_EXT_2x12288000, -}; - -/* codec port interface mode */ -enum eac_codec_mode { - EAC_CODEC_PCM, - EAC_CODEC_AC97, - EAC_CODEC_I2S_MASTER, /* codec port, I.e. EAC is the master */ - EAC_CODEC_I2S_SLAVE, -}; - -/* configuration structure for I2S mode */ -struct eac_i2s_conf { - /* if enabled, then first data slot (left channel) is signaled as - * positive level of frame sync EAC.AC_FS */ - unsigned polarity_changed_mode:1; - /* if enabled, then serial data starts one clock cycle after the - * of EAC.AC_FS for first audio slot */ - unsigned sync_delay_enable:1; -}; - -/* configuration structure for EAC codec port */ -struct eac_codec { - enum eac_mclk_src mclk_src; - - enum eac_codec_mode codec_mode; - union { - struct eac_i2s_conf i2s; - } codec_conf; - - int default_rate; /* audio sampling rate */ - - int (* set_power)(void *private_data, int dac, int adc); - int (* register_controls)(void *private_data, - struct snd_card *card); - const char *short_name; - - void *private_data; -}; - -/* structure for passing platform dependent data to the EAC driver */ -struct eac_platform_data { - int (* init)(struct device *eac_dev); - void (* cleanup)(struct device *eac_dev); - /* these callbacks are used to configure & control external MCLK - * source. NULL if not used */ - int (* enable_ext_clocks)(struct device *eac_dev); - void (* disable_ext_clocks)(struct device *eac_dev); -}; - -extern void omap_init_eac(struct eac_platform_data *pdata); - -extern int eac_register_codec(struct device *eac_dev, struct eac_codec *codec); -extern void eac_unregister_codec(struct device *eac_dev); - -extern int eac_set_mode(struct device *eac_dev, int play, int rec); - -#endif /* __ASM_ARM_ARCH_OMAP2_EAC_H */ diff --git a/arch/arm/plat-omap/include/mach/gpio-switch.h b/arch/arm/plat-omap/include/mach/gpio-switch.h index 10da0e07c0c..20967806609 100644 --- a/arch/arm/plat-omap/include/mach/gpio-switch.h +++ b/arch/arm/plat-omap/include/mach/gpio-switch.h @@ -24,11 +24,12 @@ * low -> inactive * */ -#define OMAP_GPIO_SWITCH_TYPE_COVER 0x0000 -#define OMAP_GPIO_SWITCH_TYPE_CONNECTION 0x0001 -#define OMAP_GPIO_SWITCH_TYPE_ACTIVITY 0x0002 -#define OMAP_GPIO_SWITCH_FLAG_INVERTED 0x0001 -#define OMAP_GPIO_SWITCH_FLAG_OUTPUT 0x0002 +#define OMAP_GPIO_SWITCH_TYPE_COVER 0x0000 +#define OMAP_GPIO_SWITCH_TYPE_CONNECTION 0x0001 +#define OMAP_GPIO_SWITCH_TYPE_ACTIVITY 0x0002 +#define OMAP_GPIO_SWITCH_FLAG_INVERTED 0x0001 +#define OMAP_GPIO_SWITCH_FLAG_OUTPUT 0x0002 +#define OMAP_GPIO_SWITCH_FLAG_OUTPUT_INIT_ACTIVE 0x0004 struct omap_gpio_switch { const char *name; @@ -48,7 +49,11 @@ struct omap_gpio_switch { }; /* Call at init time only */ +#ifdef CONFIG_OMAP_GPIO_SWITCH extern void omap_register_gpio_switches(const struct omap_gpio_switch *tbl, int count); +#else +#define omap_register_gpio_switches(tbl, count) do { } while (0) +#endif #endif diff --git a/arch/arm/plat-omap/include/mach/gpioexpander.h b/arch/arm/plat-omap/include/mach/gpioexpander.h deleted file mode 100644 index 90444a0d6b1..00000000000 --- a/arch/arm/plat-omap/include/mach/gpioexpander.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * arch/arm/plat-omap/include/mach/gpioexpander.h - * - * - * Copyright (C) 2004 Texas Instruments, Inc. - * - * This package 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. - * - * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED - * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. - */ - -#ifndef __ASM_ARCH_OMAP_GPIOEXPANDER_H -#define __ASM_ARCH_OMAP_GPIOEXPANDER_H - -/* Function Prototypes for GPIO Expander functions */ - -#ifdef CONFIG_GPIOEXPANDER_OMAP -int read_gpio_expa(u8 *, int); -int write_gpio_expa(u8 , int); -#else -static inline int read_gpio_expa(u8 *val, int addr) -{ - return 0; -} -static inline int write_gpio_expa(u8 val, int addr) -{ - return 0; -} -#endif - -#endif /* __ASM_ARCH_OMAP_GPIOEXPANDER_H */ diff --git a/arch/arm/plat-omap/include/mach/gpmc.h b/arch/arm/plat-omap/include/mach/gpmc.h index 921b16532ff..b0b2edf8c55 100644 --- a/arch/arm/plat-omap/include/mach/gpmc.h +++ b/arch/arm/plat-omap/include/mach/gpmc.h @@ -25,8 +25,18 @@ #define GPMC_CS_NAND_ADDRESS 0x20 #define GPMC_CS_NAND_DATA 0x24 +/* + * The following gpmc registers are being used by + * nand driver and hence is defined here. + * TBD: Move them to gpmc.c by providing appropriate + * methods to read and write into these registers + */ +#define GPMC_IRQSTATUS 0x18 #define GPMC_CONFIG 0x50 #define GPMC_STATUS 0x54 +#define GPMC_CS0_BASE 0x60 +#define GPMC_CS_SIZE 0x30 + #define GPMC_CONFIG1_WRAPBURST_SUPP (1 << 31) #define GPMC_CONFIG1_READMULTIPLE_SUPP (1 << 30) diff --git a/arch/arm/plat-omap/include/mach/irqs.h b/arch/arm/plat-omap/include/mach/irqs.h index 7f57ee66f36..9499a0520e0 100644 --- a/arch/arm/plat-omap/include/mach/irqs.h +++ b/arch/arm/plat-omap/include/mach/irqs.h @@ -420,8 +420,6 @@ #define INT_34XX_MMC3_IRQ 94 #define INT_34XX_GPT12_IRQ 95 -#define INT_34XX_BENCH_MPU_EMUL 3 - /* Max. 128 level 2 IRQs (OMAP1610), 192 GPIOs (OMAP730/850) and * 16 MPUIO lines */ #define OMAP_MAX_GPIO_LINES 192 @@ -467,6 +465,7 @@ #ifndef __ASSEMBLY__ extern void omap_init_irq(void); +extern int omap_irq_pending(void); #endif #include diff --git a/arch/arm/plat-omap/include/mach/keypad.h b/arch/arm/plat-omap/include/mach/keypad.h index 232923aaf61..b7f270a6782 100644 --- a/arch/arm/plat-omap/include/mach/keypad.h +++ b/arch/arm/plat-omap/include/mach/keypad.h @@ -14,6 +14,7 @@ struct omap_kp_platform_data { int rows; int cols; int *keymap; + int irq; unsigned int keymapsize; unsigned int rep:1; unsigned long delay; @@ -33,7 +34,12 @@ struct omap_kp_platform_data { #define GROUP_3 (3 << 16) #define GROUP_MASK GROUP_3 +#define ROWCOL_MASK 0xFF000000 +#define KEY_PERSISTENT 0x00800000 +#define KEYNUM_MASK 0x00EFFFFF #define KEY(col, row, val) (((col) << 28) | ((row) << 24) | (val)) +#define PERSISTENT_KEY(col, row) (((col) << 28) | ((row) << 24) | \ + KEY_PERSISTENT) #endif diff --git a/arch/arm/plat-omap/include/mach/mmc.h b/arch/arm/plat-omap/include/mach/mmc.h index 4435bd434e1..81d5b36534b 100644 --- a/arch/arm/plat-omap/include/mach/mmc.h +++ b/arch/arm/plat-omap/include/mach/mmc.h @@ -79,7 +79,6 @@ struct omap_mmc_platform_data { /* use the internal clock */ unsigned internal_clock:1; - s16 power_pin; int switch_pin; /* gpio (card detect) */ int gpio_wp; /* gpio (write protect) */ diff --git a/arch/arm/plat-omap/include/mach/sti.h b/arch/arm/plat-omap/include/mach/sti.h new file mode 100644 index 00000000000..af439170eba --- /dev/null +++ b/arch/arm/plat-omap/include/mach/sti.h @@ -0,0 +1,172 @@ +#ifndef __ASM_ARCH_OMAP_STI_H +#define __ASM_ARCH_OMAP_STI_H + +#include + +/* + * STI/SDTI + */ +#define STI_REVISION 0x00 +#define STI_SYSCONFIG 0x10 +#define STI_SYSSTATUS 0x14 +#define STI_IRQSTATUS 0x18 +#define STI_IRQSETEN 0x1c + +#if defined(CONFIG_ARCH_OMAP1) +#define STI_IRQCLREN 0x20 +#define STI_ER 0x24 +#define STI_DR 0x28 +#define STI_RX_DR 0x2c +#define STI_RX_STATUS 0x30 +#define STI_CLK_CTRL 0x34 +#define STI_IOBOTT0 0x4c +#define STI_IOTOP0 0x50 +#define STI_IOBOTT1 0x54 +#define STI_IOTOP1 0x58 +#define STI_SERIAL_CFG 0x60 + +#define STI_OCPT2_MATCH_INT 0 +#define STI_OCPT1_MATCH_INT 1 +#define STI_EMIFS_MATCH_INT 2 +#define STI_EMIFF_MATCH_INT 3 +#define STI_IO_MATCH_INT 4 +#define STI_RX_INT 5 +#define STI_DUMP_REQUEST_INT 6 +#define STI_DUMP_UNDERRUN_INT 7 +#define STI_WAKEUP_INT 9 + +#define STI_NR_IRQS 10 + +#define STI_IRQSTATUS_MASK 0x2ff + +#define STI_RXFIFO_EMPTY (1 << 0) + +/* + * We use the following enums to retain consistency with the STI "functional" + * specification. + */ + +/* STI_ER */ +enum { + UnlockStatMatch = (1 << 2), /* Unlock status match event regs */ + IOMPUStr1En1 = (1 << 3), /* MPU IO match, strobe 1, window 1 */ + IOMPUStr0En1 = (1 << 4), /* MPU IO match, strobe 0, window 1 */ + IOMPUStr1En0 = (1 << 5), /* MPU IO match, strobe 1, window 0 */ + IOMPUStr0En0 = (1 << 6), /* MPU IO match, strobe 0, window 0 */ + IODSPStr1En1 = (1 << 7), /* DSP IO match, strobe 1, window 1 */ + IODSPStr0En1 = (1 << 8), /* DSP IO match, strobe 0, window 1 */ + IODSPStr1En0 = (1 << 9), /* DSP IO match, strobe 1, window 0 */ + IODSPStr0En0 = (1 << 10), /* DSP IO match, strobe 0, window 0 */ + MemMatchEn = (1 << 11), /* Memory matched event */ + DSPCmdEn = (1 << 12), /* DSP command write */ + MPUCmdEn = (1 << 13), /* MPU command write */ + MemDumpEn = (1 << 14), /* System memory dump */ + STIEn = (1 << 15), /* Global trace enable */ +}; + +#define STI_PERCHANNEL_SIZE 4 + +#define to_channel_address(channel) \ + (sti_channel_base + STI_PERCHANNEL_SIZE * (channel)) + +#elif defined(CONFIG_ARCH_OMAP2) + +/* XTI interrupt bits */ +enum { + STI_WAKEUP_INT = 0, + STI_ETB_THRESHOLD_INT, + STI_RX_INT, + STI_DUMP_REQUEST_INT, + STI_NR_IRQS, +}; + +/* XTI_TRACESELECT */ +enum { + CmdTimeStampEn = (1 << 0), /* Command write timestamps */ + WinTimeStampEn = (1 << 1), /* Window match timestamps */ + WinMatchEn = (1 << 2), /* Window match trace */ + DSPCmdEn = (1 << 3), /* DSP command write */ + MPUCmdEn = (1 << 4), /* MPU command write */ + MemDumpEn0 = (1 << 5), /* System memory dump */ + MemDumpEn1 = (1 << 6), + MemDumpEn2 = (1 << 7), + ExtTriggerEn = (1 << 8), /* External trace trigger */ + STIEn = (1 << 9), /* System trace enable */ +}; + +#define STI_IRQSTATUS_MASK 0x0f +#define STI_PERCHANNEL_SIZE 64 + +/* XTI registers */ +#define XTI_SYSSTATUS 0x14 +#define XTI_TRACESELECT 0x24 +#define XTI_RXDATA 0x28 +#define XTI_SCLKCRTL 0x2c +#define XTI_SCONFIG 0x30 + +/* STI Compatability */ +#define STI_RX_STATUS XTI_SYSSTATUS +#define STI_IRQCLREN STI_IRQSETEN +#define STI_ER XTI_TRACESELECT +#define STI_DR XTI_TRACESELECT +#define STI_RX_DR XTI_RXDATA +#define STI_CLK_CTRL XTI_SCLKCRTL +#define STI_SERIAL_CFG XTI_SCONFIG + +#define STI_RXFIFO_EMPTY (1 << 8) + +#define to_channel_address(channel) \ + (sti_channel_base + STI_PERCHANNEL_SIZE * (channel)) + +#elif defined(CONFIG_ARCH_OMAP3) + +#define STI_PERCHANNEL_SIZE 0x1000 +#define to_channel_address(channel) \ + (sti_channel_base + STI_PERCHANNEL_SIZE * (channel) + 0x800) + +#endif + +/* arch/arm/plat-omap/sti/sti.c */ +extern void __iomem *sti_base, *sti_channel_base; + +int sti_request_irq(unsigned int irq, void *handler, unsigned long arg); +void sti_free_irq(unsigned int irq); +void sti_enable_irq(unsigned int irq); +void sti_disable_irq(unsigned int irq); +void sti_ack_irq(unsigned int irq); + +int sti_trace_enable(int event); +void sti_trace_disable(int event); + +void sti_channel_write_trace(int len, int id, void *data, unsigned int channel); + +/* arch/arm/plat-omap/sti/sti-fifo.c */ +int sti_read_packet(unsigned char *buf, int maxsize); + +static inline unsigned long sti_readl(unsigned long reg) +{ + return __raw_readl(sti_base + reg); +} + +static inline void sti_writel(unsigned long data, unsigned long reg) +{ + __raw_writel(data, sti_base + reg); +} + +static inline void sti_channel_writeb(unsigned char data, unsigned int channel) +{ + __raw_writeb(data, to_channel_address(channel)); +} + +static inline void sti_channel_writel(unsigned long data, unsigned int channel) +{ + __raw_writel(data, to_channel_address(channel)); +} + +#define STI_TRACE_CONTROL_CHANNEL 253 + +static inline void sti_channel_flush(unsigned int channel) +{ + sti_channel_writeb(channel, STI_TRACE_CONTROL_CHANNEL); +} +#endif /* __ASM_ARCH_OMAP_STI_H */ diff --git a/arch/arm/plat-omap/include/mach/usb.h b/arch/arm/plat-omap/include/mach/usb.h index 69f0ceed500..f6d334ffaae 100644 --- a/arch/arm/plat-omap/include/mach/usb.h +++ b/arch/arm/plat-omap/include/mach/usb.h @@ -33,9 +33,17 @@ extern void usb_musb_init(void); static inline void usb_musb_init(void) { } -#endif +#endif /* !OMAP1 && !MUSB */ -#endif +#if defined(CONFIG_USB_EHCI_HCD) || defined(CONFIG_USB_EHCI_HCD_MODULE) +extern void usb_ehci_init(void); +#else +static inline void usb_ehci_init(void) +{ +} +#endif /* !OMAP1 && !EHCI */ + +#endif /* !OMAP1 */ void omap_usb_init(struct omap_usb_config *pdata); diff --git a/arch/arm/plat-omap/include/mach/vmalloc.h b/arch/arm/plat-omap/include/mach/vmalloc.h index dc104cd9619..b97dfafeebd 100644 --- a/arch/arm/plat-omap/include/mach/vmalloc.h +++ b/arch/arm/plat-omap/include/mach/vmalloc.h @@ -17,5 +17,5 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#define VMALLOC_END (PAGE_OFFSET + 0x10000000) +#define VMALLOC_END (PAGE_OFFSET + 0x18000000) diff --git a/arch/arm/plat-omap/sram.c b/arch/arm/plat-omap/sram.c index fa5297d643d..e1e637f222c 100644 --- a/arch/arm/plat-omap/sram.c +++ b/arch/arm/plat-omap/sram.c @@ -38,8 +38,8 @@ #define OMAP1_SRAM_VA VMALLOC_END #define OMAP2_SRAM_PA 0x40200000 #define OMAP2_SRAM_PUB_PA 0x4020f800 -#define OMAP2_SRAM_VA VMALLOC_END -#define OMAP2_SRAM_PUB_VA (VMALLOC_END + 0x800) +#define OMAP2_SRAM_VA 0xe3000000 +#define OMAP2_SRAM_PUB_VA (OMAP2_SRAM_VA + 0x800) #define OMAP3_SRAM_PA 0x40200000 #define OMAP3_SRAM_VA 0xd7000000 #define OMAP3_SRAM_PUB_PA 0x40208000 @@ -359,14 +359,14 @@ static u32 (*_omap3_sram_configure_core_dpll)(u32 sdrc_rfr_ctrl, u32 m2); u32 omap3_configure_core_dpll(u32 sdrc_rfr_ctrl, u32 sdrc_actim_ctrla, u32 sdrc_actim_ctrlb, u32 m2) -{ + { if (!_omap3_sram_configure_core_dpll) omap_sram_error(); return _omap3_sram_configure_core_dpll(sdrc_rfr_ctrl, sdrc_actim_ctrla, sdrc_actim_ctrlb, m2); -} + } /* REVISIT: Should this be same as omap34xx_sram_init() after off-idle? */ void restore_sram_functions(void) @@ -378,7 +378,7 @@ void restore_sram_functions(void) omap3_sram_configure_core_dpll_sz); } -int __init omap34xx_sram_init(void) +int __init omap3_sram_init(void) { _omap3_sram_configure_core_dpll = omap_sram_push(omap3_sram_configure_core_dpll, @@ -387,7 +387,7 @@ int __init omap34xx_sram_init(void) return 0; } #else -static inline int omap34xx_sram_init(void) +static inline int omap3_sram_init(void) { return 0; } @@ -405,7 +405,7 @@ int __init omap_sram_init(void) else if (cpu_is_omap2430()) omap243x_sram_init(); else if (cpu_is_omap34xx()) - omap34xx_sram_init(); + omap3_sram_init(); return 0; } diff --git a/drivers/Makefile b/drivers/Makefile index 1ba9e471ca4..625f69d8ecd 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -36,9 +36,13 @@ obj-$(CONFIG_FB_INTEL) += video/intelfb/ obj-y += cbus/ +# we also need input/serio early so serio bus is initialized by the time +# serial drivers start registering their serio ports +obj-$(CONFIG_SERIO) += input/serio/ obj-y += serial/ obj-$(CONFIG_PARPORT) += parport/ obj-y += base/ block/ misc/ mfd/ media/ +obj-y += i2c/ obj-$(CONFIG_NUBUS) += nubus/ obj-y += macintosh/ obj-$(CONFIG_IDE) += ide/ @@ -68,12 +72,10 @@ obj-$(CONFIG_USB) += usb/ obj-$(CONFIG_USB_MUSB_HDRC) += usb/musb/ obj-$(CONFIG_PCI) += usb/ obj-$(CONFIG_USB_GADGET) += usb/gadget/ -obj-$(CONFIG_SERIO) += input/serio/ obj-$(CONFIG_GAMEPORT) += input/gameport/ obj-$(CONFIG_INPUT) += input/ obj-$(CONFIG_I2O) += message/ obj-$(CONFIG_RTC_LIB) += rtc/ -obj-y += i2c/ obj-$(CONFIG_W1) += w1/ obj-$(CONFIG_POWER_SUPPLY) += power/ obj-$(CONFIG_HWMON) += hwmon/ diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig index 1164837bb78..68eff964e53 100644 --- a/drivers/bluetooth/Kconfig +++ b/drivers/bluetooth/Kconfig @@ -161,6 +161,27 @@ config BT_HCIBTUART Say Y here to compile support for HCI UART devices into the kernel or say M to compile it as module (btuart_cs). +config BT_HCIBRF6150 + tristate "HCI TI BRF6150 driver with H4 extensions" + depends on BT && ARCH_OMAP + help + Bluetooth HCI driver for TI BRF6150 with H4 extensions. + This driver provides support for BRF6150 Bluetooth chip + with vendor-specific H4 extensions. + + Say Y here to compile support for TI BRF6150 devices into the + kernel or say M to compile it as module (brf6150). + +config BT_HCIH4P + tristate "HCI driver with H4 Nokia extensions" + depends on BT && ARCH_OMAP + help + Bluetooth HCI driver with H4 extensions. This driver provides + support for H4+ Bluetooth chip with vendor-specific H4 extensions. + + Say Y here to compile support for h4 extended devices into the kernel + or say M to compile it as module (hci_h4p). + config BT_HCIVHCI tristate "HCI VHCI (Virtual HCI device) driver" help diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile index 16930f93d1c..9aebb460a34 100644 --- a/drivers/bluetooth/Makefile +++ b/drivers/bluetooth/Makefile @@ -11,6 +11,8 @@ obj-$(CONFIG_BT_HCIDTL1) += dtl1_cs.o obj-$(CONFIG_BT_HCIBT3C) += bt3c_cs.o obj-$(CONFIG_BT_HCIBLUECARD) += bluecard_cs.o obj-$(CONFIG_BT_HCIBTUART) += btuart_cs.o +obj-$(CONFIG_BT_HCIBRF6150) += brf6150.o +obj-$(CONFIG_BT_HCIH4P) += hci_h4p/ obj-$(CONFIG_BT_HCIBTUSB) += btusb.o obj-$(CONFIG_BT_HCIBTSDIO) += btsdio.o diff --git a/drivers/bluetooth/brf6150.c b/drivers/bluetooth/brf6150.c new file mode 100644 index 00000000000..211fa5e0c64 --- /dev/null +++ b/drivers/bluetooth/brf6150.c @@ -0,0 +1,1051 @@ +/* + * linux/drivers/bluetooth/brf6150/brf6150.c + * + * Copyright (C) 2005 Nokia Corporation + * Written by Ville Tervo + * + * 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 program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "brf6150.h" + +#if 0 +#define NBT_DBG(fmt, arg...) printk("%s: " fmt "" , __FUNCTION__ , ## arg) +#else +#define NBT_DBG(...) +#endif + +#if 0 +#define NBT_DBG_FW(fmt, arg...) printk("%s: " fmt "" , __FUNCTION__ , ## arg) +#else +#define NBT_DBG_FW(...) +#endif + +#if 0 +#define NBT_DBG_POWER(fmt, arg...) printk("%s: " fmt "" , __FUNCTION__ , ## arg) +#else +#define NBT_DBG_POWER(...) +#endif + +#if 0 +#define NBT_DBG_TRANSFER(fmt, arg...) printk("%s: " fmt "" , __FUNCTION__ , ## arg) +#else +#define NBT_DBG_TRANSFER(...) +#endif + +#if 0 +#define NBT_DBG_TRANSFER_NF(fmt, arg...) printk(fmt "" , ## arg) +#else +#define NBT_DBG_TRANSFER_NF(...) +#endif + +#define PM_TIMEOUT (2000) + +static void brf6150_device_release(struct device *dev); +static struct brf6150_info *exit_info; + +static struct platform_device brf6150_device = { + .name = BT_DEVICE, + .id = -1, + .num_resources = 0, + .dev = { + .release = brf6150_device_release, + } +}; + +static struct device_driver brf6150_driver = { + .name = BT_DRIVER, + .bus = &platform_bus_type, +}; + +static inline void brf6150_outb(struct brf6150_info *info, unsigned int offset, u8 val) +{ + outb(val, info->uart_base + (offset << 2)); +} + +static inline u8 brf6150_inb(struct brf6150_info *info, unsigned int offset) +{ + return inb(info->uart_base + (offset << 2)); +} + +static void brf6150_set_rts(struct brf6150_info *info, int active) +{ + u8 b; + + b = brf6150_inb(info, UART_MCR); + if (active) + b |= UART_MCR_RTS; + else + b &= ~UART_MCR_RTS; + brf6150_outb(info, UART_MCR, b); +} + +static void brf6150_wait_for_cts(struct brf6150_info *info, int active, + int timeout_ms) +{ + int okay; + unsigned long timeout; + + okay = 0; + timeout = jiffies + msecs_to_jiffies(timeout_ms); + for (;;) { + int state; + + state = brf6150_inb(info, UART_MSR) & UART_MSR_CTS; + if (active) { + if (state) + break; + } else { + if (!state) + break; + } + if (jiffies > timeout) + break; + } +} + +static inline void brf6150_set_auto_ctsrts(struct brf6150_info *info, int on) +{ + u8 lcr, b; + + lcr = brf6150_inb(info, UART_LCR); + brf6150_outb(info, UART_LCR, 0xbf); + b = brf6150_inb(info, UART_EFR); + if (on) + b |= UART_EFR_CTS | UART_EFR_RTS; + else + b &= ~(UART_EFR_CTS | UART_EFR_RTS); + brf6150_outb(info, UART_EFR, b); + brf6150_outb(info, UART_LCR, lcr); +} + +static inline void brf6150_enable_pm_rx(struct brf6150_info *info) +{ + if (info->pm_enabled) { + info->rx_pm_enabled = 1; + } +} + +static inline void brf6150_disable_pm_rx(struct brf6150_info *info) +{ + if (info->pm_enabled) { + info->rx_pm_enabled = 0; + } +} + +static void brf6150_enable_pm_tx(struct brf6150_info *info) +{ + if (info->pm_enabled) { + mod_timer(&info->pm_timer, jiffies + msecs_to_jiffies(PM_TIMEOUT)); + info->tx_pm_enabled = 1; + } +} + +static void brf6150_disable_pm_tx(struct brf6150_info *info) +{ + if (info->pm_enabled) { + info->tx_pm_enabled = 0; + gpio_set_value(info->btinfo->bt_wakeup_gpio, 1); + } + if (gpio_get_value(info->btinfo->host_wakeup_gpio)) + tasklet_schedule(&info->tx_task); +} + +static void brf6150_pm_timer(unsigned long data) +{ + struct brf6150_info *info; + + info = (struct brf6150_info *)data; + if (info->tx_pm_enabled && info->rx_pm_enabled && !test_bit(HCI_INQUIRY, &info->hdev->flags)) + gpio_set_value(info->btinfo->bt_wakeup_gpio, 0); + else + mod_timer(&info->pm_timer, jiffies + msecs_to_jiffies(PM_TIMEOUT)); +} + +static int brf6150_change_speed(struct brf6150_info *info, unsigned long speed) +{ + unsigned int divisor; + u8 lcr, mdr1; + + NBT_DBG("Setting speed %lu\n", speed); + + if (speed >= 460800) { + divisor = UART_CLOCK / 13 / speed; + mdr1 = 3; + } else { + divisor = UART_CLOCK / 16 / speed; + mdr1 = 0; + } + + brf6150_outb(info, UART_OMAP_MDR1, 7); /* Make sure UART mode is disabled */ + lcr = brf6150_inb(info, UART_LCR); + brf6150_outb(info, UART_LCR, UART_LCR_DLAB); /* Set DLAB */ + brf6150_outb(info, UART_DLL, divisor & 0xff); /* Set speed */ + brf6150_outb(info, UART_DLM, divisor >> 8); + brf6150_outb(info, UART_LCR, lcr); + brf6150_outb(info, UART_OMAP_MDR1, mdr1); /* Make sure UART mode is enabled */ + + return 0; +} + +/* Firmware handling */ +static int brf6150_open_firmware(struct brf6150_info *info) +{ + int err; + + info->fw_pos = 0; + err = request_firmware(&info->fw_entry, "brf6150fw.bin", &brf6150_device.dev); + + return err; +} + +static struct sk_buff *brf6150_read_fw_cmd(struct brf6150_info *info, int how) +{ + struct sk_buff *skb; + unsigned int cmd_len; + + if (info->fw_pos >= info->fw_entry->size) { + return NULL; + } + + cmd_len = info->fw_entry->data[info->fw_pos++]; + if (!cmd_len) + return NULL; + + if (info->fw_pos + cmd_len > info->fw_entry->size) { + printk(KERN_WARNING "Corrupted firmware image\n"); + return NULL; + } + + skb = bt_skb_alloc(cmd_len, how); + if (!skb) { + printk(KERN_WARNING "Cannot reserve memory for buffer\n"); + return NULL; + } + memcpy(skb_put(skb, cmd_len), &info->fw_entry->data[info->fw_pos], cmd_len); + + info->fw_pos += cmd_len; + + return skb; +} + +static int brf6150_close_firmware(struct brf6150_info *info) +{ + release_firmware(info->fw_entry); + return 0; +} + +static int brf6150_send_alive_packet(struct brf6150_info *info) +{ + struct sk_buff *skb; + + NBT_DBG("Sending alive packet\n"); + skb = brf6150_read_fw_cmd(info, GFP_ATOMIC); + if (!skb) { + printk(KERN_WARNING "Cannot read alive command"); + return -1; + } + + clk_enable(info->uart_ck); + skb_queue_tail(&info->txq, skb); + tasklet_schedule(&info->tx_task); + + NBT_DBG("Alive packet sent\n"); + return 0; +} + +static void brf6150_alive_packet(struct brf6150_info *info, struct sk_buff *skb) +{ + NBT_DBG("Received alive packet\n"); + if (skb->data[1] == 0xCC) { + complete(&info->init_completion); + } + + kfree_skb(skb); +} + +static int brf6150_send_negotiation(struct brf6150_info *info) +{ + struct sk_buff *skb; + NBT_DBG("Sending negotiation..\n"); + + brf6150_change_speed(info, INIT_SPEED); + + skb = brf6150_read_fw_cmd(info, GFP_KERNEL); + + if (!skb) { + printk(KERN_WARNING "Cannot read negoatiation message"); + return -1; + } + + clk_enable(info->uart_ck); + skb_queue_tail(&info->txq, skb); + tasklet_schedule(&info->tx_task); + + + NBT_DBG("Negotiation sent\n"); + return 0; +} + +static void brf6150_negotiation_packet(struct brf6150_info *info, + struct sk_buff *skb) +{ + if (skb->data[1] == 0x20) { + /* Change to operational settings */ + brf6150_set_rts(info, 0); + brf6150_wait_for_cts(info, 0, 100); + brf6150_change_speed(info, MAX_BAUD_RATE); + brf6150_set_rts(info, 1); + brf6150_wait_for_cts(info, 1, 100); + brf6150_set_auto_ctsrts(info, 1); + brf6150_send_alive_packet(info); + } else { + printk(KERN_WARNING "Could not negotiate brf6150 settings\n"); + } + kfree_skb(skb); +} + +static int brf6150_get_hdr_len(u8 pkt_type) +{ + long retval; + + switch (pkt_type) { + case H4_EVT_PKT: + retval = HCI_EVENT_HDR_SIZE; + break; + case H4_ACL_PKT: + retval = HCI_ACL_HDR_SIZE; + break; + case H4_SCO_PKT: + retval = HCI_SCO_HDR_SIZE; + break; + case H4_NEG_PKT: + retval = 9; + break; + case H4_ALIVE_PKT: + retval = 3; + break; + default: + printk(KERN_ERR "brf6150: Unknown H4 packet"); + retval = -1; + break; + } + + return retval; +} + +static unsigned int brf6150_get_data_len(struct brf6150_info *info, + struct sk_buff *skb) +{ + long retval = -1; + struct hci_event_hdr *evt_hdr; + struct hci_acl_hdr *acl_hdr; + struct hci_sco_hdr *sco_hdr; + + switch (bt_cb(skb)->pkt_type) { + case H4_EVT_PKT: + evt_hdr = (struct hci_event_hdr *)skb->data; + retval = evt_hdr->plen; + break; + case H4_ACL_PKT: + acl_hdr = (struct hci_acl_hdr *)skb->data; + retval = le16_to_cpu(acl_hdr->dlen); + break; + case H4_SCO_PKT: + sco_hdr = (struct hci_sco_hdr *)skb->data; + retval = sco_hdr->dlen; + break; + case H4_NEG_PKT: + retval = 0; + break; + case H4_ALIVE_PKT: + retval = 0; + break; + } + + return retval; +} + +static void brf6150_parse_fw_event(struct brf6150_info *info) +{ + struct hci_fw_event *ev; + + if (bt_cb(info->rx_skb)->pkt_type != H4_EVT_PKT) { + printk(KERN_WARNING "Got non event fw packet.\n"); + info->fw_error = 1; + return; + } + + ev = (struct hci_fw_event *)info->rx_skb->data; + if (ev->hev.evt != HCI_EV_CMD_COMPLETE) { + printk(KERN_WARNING "Got non cmd complete fw event\n"); + info->fw_error = 1; + return; + } + + if (ev->status != 0) { + printk(KERN_WARNING "Got error status from fw command\n"); + info->fw_error = 1; + return; + } + + complete(&info->fw_completion); +} + +static inline void brf6150_recv_frame(struct brf6150_info *info, + struct sk_buff *skb) +{ + if (unlikely(!test_bit(HCI_RUNNING, &info->hdev->flags))) { + NBT_DBG("fw_event\n"); + brf6150_parse_fw_event(info); + kfree_skb(skb); + } else { + hci_recv_frame(skb); + if (!(brf6150_inb(info, UART_LSR) & UART_LSR_DR)) + brf6150_enable_pm_rx(info); + NBT_DBG("Frame sent to upper layer\n"); + } + +} + +static inline void brf6150_rx(struct brf6150_info *info) +{ + u8 byte; + + NBT_DBG_TRANSFER("rx_tasklet woke up\ndata "); + + while (brf6150_inb(info, UART_LSR) & UART_LSR_DR) { + if (info->rx_skb == NULL) { + info->rx_skb = bt_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC); + if (!info->rx_skb) { + printk(KERN_WARNING "brf6150: Can't allocate memory for new packet\n"); + return; + } + info->rx_state = WAIT_FOR_PKT_TYPE; + info->rx_skb->dev = (void *)info->hdev; + brf6150_disable_pm_rx(info); + clk_enable(info->uart_ck); + } + + byte = brf6150_inb(info, UART_RX); + if (info->garbage_bytes) { + info->garbage_bytes--; + info->hdev->stat.err_rx++; + continue; + } + info->hdev->stat.byte_rx++; + NBT_DBG_TRANSFER_NF("0x%.2x ", byte); + switch (info->rx_state) { + case WAIT_FOR_PKT_TYPE: + bt_cb(info->rx_skb)->pkt_type = byte; + info->rx_count = brf6150_get_hdr_len(byte); + if (info->rx_count >= 0) { + info->rx_state = WAIT_FOR_HEADER; + } else { + info->hdev->stat.err_rx++; + kfree_skb(info->rx_skb); + info->rx_skb = NULL; + clk_disable(info->uart_ck); + } + break; + case WAIT_FOR_HEADER: + info->rx_count--; + *skb_put(info->rx_skb, 1) = byte; + if (info->rx_count == 0) { + info->rx_count = brf6150_get_data_len(info, info->rx_skb); + if (info->rx_count > skb_tailroom(info->rx_skb)) { + printk(KERN_WARNING "brf6150: Frame is %ld bytes too long.\n", + info->rx_count - skb_tailroom(info->rx_skb)); + info->rx_skb = NULL; + info->garbage_bytes = info->rx_count - skb_tailroom(info->rx_skb); + clk_disable(info->uart_ck); + break; + } + info->rx_state = WAIT_FOR_DATA; + if (bt_cb(info->rx_skb)->pkt_type == H4_NEG_PKT) { + brf6150_negotiation_packet(info, info->rx_skb); + info->rx_skb = NULL; + clk_disable(info->uart_ck); + return; + } + if (bt_cb(info->rx_skb)->pkt_type == H4_ALIVE_PKT) { + brf6150_alive_packet(info, info->rx_skb); + info->rx_skb = NULL; + clk_disable(info->uart_ck); + return; + } + } + break; + case WAIT_FOR_DATA: + info->rx_count--; + *skb_put(info->rx_skb, 1) = byte; + if (info->rx_count == 0) { + brf6150_recv_frame(info, info->rx_skb); + info->rx_skb = NULL; + clk_disable(info->uart_ck); + } + break; + default: + WARN_ON(1); + break; + } + } + + NBT_DBG_TRANSFER_NF("\n"); +} + +static void brf6150_tx_tasklet(unsigned long data) +{ + unsigned int sent = 0; + unsigned long flags; + struct sk_buff *skb; + struct brf6150_info *info = (struct brf6150_info *)data; + + NBT_DBG_TRANSFER("tx_tasklet woke up\n data "); + + skb = skb_dequeue(&info->txq); + if (!skb) { + /* No data in buffer */ + brf6150_enable_pm_tx(info); + return; + } + + /* Copy data to tx fifo */ + while (!(brf6150_inb(info, UART_OMAP_SSR) & UART_OMAP_SSR_TXFULL) && + (sent < skb->len)) { + NBT_DBG_TRANSFER_NF("0x%.2x ", skb->data[sent]); + brf6150_outb(info, UART_TX, skb->data[sent]); + sent++; + } + + info->hdev->stat.byte_tx += sent; + NBT_DBG_TRANSFER_NF("\n"); + if (skb->len == sent) { + kfree_skb(skb); + clk_disable(info->uart_ck); + } else { + skb_pull(skb, sent); + skb_queue_head(&info->txq, skb); + } + + spin_lock_irqsave(&info->lock, flags); + brf6150_outb(info, UART_IER, brf6150_inb(info, UART_IER) | UART_IER_THRI); + spin_unlock_irqrestore(&info->lock, flags); +} + +static irqreturn_t brf6150_interrupt(int irq, void *data) +{ + struct brf6150_info *info = (struct brf6150_info *)data; + u8 iir, msr; + int ret; + unsigned long flags; + + ret = IRQ_NONE; + + clk_enable(info->uart_ck); + iir = brf6150_inb(info, UART_IIR); + if (iir & UART_IIR_NO_INT) { + printk("Interrupt but no reason irq 0x%.2x\n", iir); + clk_disable(info->uart_ck); + return IRQ_HANDLED; + } + + NBT_DBG("In interrupt handler iir 0x%.2x\n", iir); + + iir &= UART_IIR_ID; + + if (iir == UART_IIR_MSI) { + msr = brf6150_inb(info, UART_MSR); + ret = IRQ_HANDLED; + } + if (iir == UART_IIR_RLSI) { + brf6150_inb(info, UART_RX); + brf6150_inb(info, UART_LSR); + ret = IRQ_HANDLED; + } + + if (iir == UART_IIR_RDI) { + brf6150_rx(info); + ret = IRQ_HANDLED; + } + + if (iir == UART_IIR_THRI) { + spin_lock_irqsave(&info->lock, flags); + brf6150_outb(info, UART_IER, brf6150_inb(info, UART_IER) & ~UART_IER_THRI); + spin_unlock_irqrestore(&info->lock, flags); + tasklet_schedule(&info->tx_task); + ret = IRQ_HANDLED; + } + + clk_disable(info->uart_ck); + return ret; +} + +static irqreturn_t brf6150_wakeup_interrupt(int irq, void *dev_inst) +{ + struct brf6150_info *info = dev_inst; + int should_wakeup; + unsigned long flags; + + spin_lock_irqsave(&info->lock, flags); + should_wakeup = gpio_get_value(info->btinfo->host_wakeup_gpio); + NBT_DBG_POWER("gpio interrupt %d\n", should_wakeup); + if (should_wakeup) { + clk_enable(info->uart_ck); + brf6150_set_auto_ctsrts(info, 1); + brf6150_rx(info); + tasklet_schedule(&info->tx_task); + } else { + brf6150_set_auto_ctsrts(info, 0); + brf6150_set_rts(info, 0); + clk_disable(info->uart_ck); + } + + spin_unlock_irqrestore(&info->lock, flags); + return IRQ_HANDLED; +} + +static int brf6150_init_uart(struct brf6150_info *info) +{ + int count = 0; + + /* Reset the UART */ + brf6150_outb(info, UART_OMAP_SYSC, UART_SYSC_OMAP_RESET); + while (!(brf6150_inb(info, UART_OMAP_SYSS) & UART_SYSS_RESETDONE)) { + if (count++ > 100) { + printk(KERN_ERR "brf6150: UART reset timeout\n"); + return -1; + } + udelay(1); + } + + /* Enable and setup FIFO */ + brf6150_outb(info, UART_LCR, UART_LCR_WLEN8); + brf6150_outb(info, UART_OMAP_MDR1, 0x00); /* Make sure UART mode is enabled */ + brf6150_outb(info, UART_OMAP_SCR, 0x00); + brf6150_outb(info, UART_EFR, brf6150_inb(info, UART_EFR) | UART_EFR_ECB); + brf6150_outb(info, UART_MCR, brf6150_inb(info, UART_MCR) | UART_MCR_TCRTLR); + brf6150_outb(info, UART_TI752_TLR, 0xff); + brf6150_outb(info, UART_TI752_TCR, 0x1f); + brf6150_outb(info, UART_FCR, UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT); + brf6150_outb(info, UART_IER, UART_IER_RDI); + + return 0; +} + +static int brf6150_reset(struct brf6150_info *info) +{ + gpio_set_value(info->btinfo->bt_wakeup_gpio, 0); + gpio_set_value(info->btinfo->reset_gpio, 0); + current->state = TASK_UNINTERRUPTIBLE; + schedule_timeout(msecs_to_jiffies(10)); + gpio_set_value(info->btinfo->bt_wakeup_gpio, 1); + current->state = TASK_UNINTERRUPTIBLE; + schedule_timeout(msecs_to_jiffies(100)); + gpio_set_value(info->btinfo->reset_gpio, 1); + current->state = TASK_UNINTERRUPTIBLE; + schedule_timeout(msecs_to_jiffies(100)); + + return 0; +} + +static int brf6150_send_firmware(struct brf6150_info *info) +{ + struct sk_buff *skb; + + init_completion(&info->fw_completion); + info->fw_error = 0; + + while ((skb = brf6150_read_fw_cmd(info, GFP_KERNEL)) != NULL) { + clk_enable(info->uart_ck); + skb_queue_tail(&info->txq, skb); + tasklet_schedule(&info->tx_task); + + if (!wait_for_completion_timeout(&info->fw_completion, HZ)) { + return -1; + } + + if (info->fw_error) { + return -1; + } + } + NBT_DBG_FW("Firmware sent\n"); + + return 0; + +} + +/* hci callback functions */ +static int brf6150_hci_flush(struct hci_dev *hdev) +{ + struct brf6150_info *info; + info = hdev->driver_data; + + skb_queue_purge(&info->txq); + + return 0; +} + +static int brf6150_hci_open(struct hci_dev *hdev) +{ + struct brf6150_info *info; + int err; + + info = hdev->driver_data; + + if (test_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + if (brf6150_open_firmware(info) < 0) { + printk("Cannot open firmware\n"); + return -1; + } + + info->rx_state = WAIT_FOR_PKT_TYPE; + info->rx_count = 0; + info->garbage_bytes = 0; + info->rx_skb = NULL; + info->pm_enabled = 0; + set_irq_type(gpio_to_irq(info->btinfo->host_wakeup_gpio), IRQ_TYPE_NONE); + init_completion(&info->fw_completion); + + clk_enable(info->uart_ck); + + brf6150_init_uart(info); + brf6150_set_auto_ctsrts(info, 0); + brf6150_set_rts(info, 0); + brf6150_reset(info); + brf6150_wait_for_cts(info, 1, 10); + brf6150_set_rts(info, 1); + if (brf6150_send_negotiation(info)) { + brf6150_close_firmware(info); + return -1; + } + + if (!wait_for_completion_interruptible_timeout(&info->init_completion, HZ)) { + brf6150_close_firmware(info); + clk_disable(info->uart_ck); + clear_bit(HCI_RUNNING, &hdev->flags); + return -1; + } + brf6150_set_auto_ctsrts(info, 1); + + err = brf6150_send_firmware(info); + brf6150_close_firmware(info); + if (err < 0) + printk(KERN_ERR "brf6150: Sending firmware failed. Bluetooth won't work properly\n"); + + set_irq_type(gpio_to_irq(info->btinfo->host_wakeup_gpio), IRQ_TYPE_EDGE_BOTH); + info->pm_enabled = 1; + set_bit(HCI_RUNNING, &hdev->flags); + return 0; +} + +static int brf6150_hci_close(struct hci_dev *hdev) +{ + struct brf6150_info *info = hdev->driver_data; + if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + brf6150_hci_flush(hdev); + clk_disable(info->uart_ck); + del_timer_sync(&info->pm_timer); + gpio_set_value(info->btinfo->bt_wakeup_gpio, 0); + set_irq_type(gpio_to_irq(info->btinfo->host_wakeup_gpio), IRQ_TYPE_NONE); + + return 0; +} + +static void brf6150_hci_destruct(struct hci_dev *hdev) +{ +} + +static int brf6150_hci_send_frame(struct sk_buff *skb) +{ + struct brf6150_info *info; + struct hci_dev *hdev = (struct hci_dev *)skb->dev; + + if (!hdev) { + printk(KERN_WARNING "brf6150: Frame for unknown device\n"); + return -ENODEV; + } + + if (!test_bit(HCI_RUNNING, &hdev->flags)) { + printk(KERN_WARNING "brf6150: Frame for non-running device\n"); + return -EIO; + } + + info = hdev->driver_data; + + switch (bt_cb(skb)->pkt_type) { + case HCI_COMMAND_PKT: + hdev->stat.cmd_tx++; + break; + case HCI_ACLDATA_PKT: + hdev->stat.acl_tx++; + break; + case HCI_SCODATA_PKT: + hdev->stat.sco_tx++; + break; + }; + + /* Push frame type to skb */ + clk_enable(info->uart_ck); + *skb_push(skb, 1) = bt_cb(skb)->pkt_type; + skb_queue_tail(&info->txq, skb); + + brf6150_disable_pm_tx(info); + + return 0; +} + +static int brf6150_hci_ioctl(struct hci_dev *hdev, unsigned int cmd, unsigned long arg) +{ + return -ENOIOCTLCMD; +} + +static void brf6150_device_release(struct device *dev) +{ +} + +static int brf6150_register_hdev(struct brf6150_info *info) +{ + struct hci_dev *hdev; + + /* Initialize and register HCI device */ + + hdev = hci_alloc_dev(); + if (!hdev) { + printk(KERN_WARNING "brf6150: Can't allocate memory for device\n"); + return -ENOMEM; + } + info->hdev = hdev; + + hdev->type = HCI_UART; + hdev->driver_data = info; + + hdev->open = brf6150_hci_open; + hdev->close = brf6150_hci_close; + hdev->destruct = brf6150_hci_destruct; + hdev->flush = brf6150_hci_flush; + hdev->send = brf6150_hci_send_frame; + hdev->destruct = brf6150_hci_destruct; + hdev->ioctl = brf6150_hci_ioctl; + + hdev->owner = THIS_MODULE; + + if (hci_register_dev(hdev) < 0) { + printk(KERN_WARNING "brf6150: Can't register HCI device %s.\n", hdev->name); + return -ENODEV; + } + + return 0; +} + +static int __init brf6150_init(void) +{ + struct brf6150_info *info; + int irq, err; + + info = kmalloc(sizeof(struct brf6150_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + memset(info, 0, sizeof(struct brf6150_info)); + + brf6150_device.dev.driver_data = info; + init_completion(&info->init_completion); + init_completion(&info->fw_completion); + info->pm_enabled = 0; + info->rx_pm_enabled = 0; + info->tx_pm_enabled = 0; + info->garbage_bytes = 0; + tasklet_init(&info->tx_task, brf6150_tx_tasklet, (unsigned long)info); + spin_lock_init(&info->lock); + skb_queue_head_init(&info->txq); + init_timer(&info->pm_timer); + info->pm_timer.function = brf6150_pm_timer; + info->pm_timer.data = (unsigned long)info; + exit_info = NULL; + + info->btinfo = omap_get_config(OMAP_TAG_NOKIA_BT, struct omap_bluetooth_config); + if (info->btinfo == NULL) + return -1; + + NBT_DBG("RESET gpio: %d\n", info->btinfo->reset_gpio); + NBT_DBG("BTWU gpio: %d\n", info->btinfo->bt_wakeup_gpio); + NBT_DBG("HOSTWU gpio: %d\n", info->btinfo->host_wakeup_gpio); + NBT_DBG("Uart: %d\n", info->btinfo->bt_uart); + NBT_DBG("sysclk: %d\n", info->btinfo->bt_sysclk); + + err = gpio_request(info->btinfo->reset_gpio, "BT reset"); + if (err < 0) + { + printk(KERN_WARNING "Cannot get GPIO line %d", + info->btinfo->reset_gpio); + kfree(info); + return err; + } + + err = gpio_request(info->btinfo->bt_wakeup_gpio, "BT wakeup"); + if (err < 0) + { + printk(KERN_WARNING "Cannot get GPIO line 0x%d", + info->btinfo->bt_wakeup_gpio); + gpio_free(info->btinfo->reset_gpio); + kfree(info); + return err; + } + + err = gpio_request(info->btinfo->host_wakeup_gpio, "BT host wakeup"); + if (err < 0) + { + printk(KERN_WARNING "Cannot get GPIO line %d", + info->btinfo->host_wakeup_gpio); + gpio_free(info->btinfo->reset_gpio); + gpio_free(info->btinfo->bt_wakeup_gpio); + kfree(info); + return err; + } + + gpio_direction_output(info->btinfo->reset_gpio, 0); + gpio_direction_output(info->btinfo->bt_wakeup_gpio, 0); + gpio_direction_input(info->btinfo->host_wakeup_gpio); + set_irq_type(gpio_to_irq(info->btinfo->host_wakeup_gpio), IRQ_TYPE_NONE); + + switch (info->btinfo->bt_uart) { + case 1: + irq = INT_UART1; + info->uart_ck = clk_get(NULL, "uart1_ck"); + /* FIXME: Use platform_get_resource for the port */ + info->uart_base = ioremap(OMAP_UART1_BASE, 0x16); + if (!info->uart_base) + goto cleanup; + break; + case 2: + irq = INT_UART2; + info->uart_ck = clk_get(NULL, "uart2_ck"); + /* FIXME: Use platform_get_resource for the port */ + info->uart_base = ioremap(OMAP_UART2_BASE, 0x16); + if (!info->uart_base) + goto cleanup; + break; + case 3: + irq = INT_UART3; + info->uart_ck = clk_get(NULL, "uart3_ck"); + /* FIXME: Use platform_get_resource for the port */ + info->uart_base = ioremap(OMAP_UART3_BASE, 0x16); + if (!info->uart_base) + goto cleanup; + break; + default: + printk(KERN_ERR "No uart defined\n"); + goto cleanup; + } + + info->irq = irq; + err = request_irq(irq, brf6150_interrupt, 0, "brf6150", (void *)info); + if (err < 0) { + printk(KERN_ERR "brf6150: unable to get IRQ %d\n", irq); + goto cleanup; + } + + err = request_irq(gpio_to_irq(info->btinfo->host_wakeup_gpio), + brf6150_wakeup_interrupt, 0, "brf6150_wkup", (void *)info); + if (err < 0) { + printk(KERN_ERR "brf6150: unable to get wakeup IRQ %d\n", + gpio_to_irq(info->btinfo->host_wakeup_gpio)); + free_irq(irq, (void *)info); + goto cleanup; + } + + /* Register with LDM */ + if (platform_device_register(&brf6150_device)) { + printk(KERN_ERR "failed to register brf6150 device\n"); + err = -ENODEV; + goto cleanup_irq; + } + /* Register the driver with LDM */ + if (driver_register(&brf6150_driver)) { + printk(KERN_WARNING "failed to register brf6150 driver\n"); + platform_device_unregister(&brf6150_device); + err = -ENODEV; + goto cleanup_irq; + } + + if (brf6150_register_hdev(info) < 0) { + printk(KERN_WARNING "failed to register brf6150 hci device\n"); + platform_device_unregister(&brf6150_device); + driver_unregister(&brf6150_driver); + goto cleanup_irq; + } + + exit_info = info; + return 0; + +cleanup_irq: + free_irq(irq, (void *)info); + free_irq(gpio_to_irq(info->btinfo->host_wakeup_gpio), (void *)info); +cleanup: + gpio_free(info->btinfo->reset_gpio); + gpio_free(info->btinfo->bt_wakeup_gpio); + gpio_free(info->btinfo->host_wakeup_gpio); + kfree(info); + + return err; +} + +static void __exit brf6150_exit(void) +{ + brf6150_hci_close(exit_info->hdev); + hci_free_dev(exit_info->hdev); + gpio_free(exit_info->btinfo->reset_gpio); + gpio_free(exit_info->btinfo->bt_wakeup_gpio); + gpio_free(exit_info->btinfo->host_wakeup_gpio); + free_irq(exit_info->irq, (void *)exit_info); + free_irq(gpio_to_irq(exit_info->btinfo->host_wakeup_gpio), (void *)exit_info); + kfree(exit_info); +} + +module_init(brf6150_init); +module_exit(brf6150_exit); + +MODULE_DESCRIPTION("brf6150 hci driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ville Tervo "); diff --git a/drivers/bluetooth/brf6150.h b/drivers/bluetooth/brf6150.h new file mode 100644 index 00000000000..e8b29880e9f --- /dev/null +++ b/drivers/bluetooth/brf6150.h @@ -0,0 +1,91 @@ +/* + * linux/drivers/bluetooth/brf6150/brf6150.h + * + * Copyright (C) 2005 Nokia Corporation + * Written by Ville Tervo + * + * 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 program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + +#ifndef __DRIVERS_BLUETOOTH_BRF6150_H +#define __DRIVERS_BLUETOOTH_BRF6150_H + +#define UART_SYSC_OMAP_RESET 0x02 +#define UART_SYSS_RESETDONE 0x01 +#define UART_OMAP_SCR_EMPTY_THR 0x08 +#define UART_OMAP_SCR_WAKEUP 0x10 +#define UART_OMAP_SSR_WAKEUP 0x02 +#define UART_OMAP_SSR_TXFULL 0x01 + +struct brf6150_info { + struct hci_dev *hdev; + spinlock_t lock; + + struct clk *uart_ck; + unsigned long uart_base; + unsigned int irq; + + struct sk_buff_head txq; + struct sk_buff *rx_skb; + const struct omap_bluetooth_config *btinfo; + const struct firmware *fw_entry; + int fw_pos; + int fw_error; + struct completion fw_completion; + struct completion init_completion; + struct tasklet_struct tx_task; + long rx_count; + unsigned long garbage_bytes; + unsigned long rx_state; + int pm_enabled; + int rx_pm_enabled; + int tx_pm_enabled; + struct timer_list pm_timer; +}; + +#define BT_DEVICE "nokia_btuart" +#define BT_DRIVER "nokia_btuart" + +#define MAX_BAUD_RATE 921600 +#define UART_CLOCK 48000000 +#define BT_INIT_DIVIDER 320 +#define BT_BAUDRATE_DIVIDER 384000000 +#define BT_SYSCLK_DIV 1000 +#define INIT_SPEED 120000 + +#define H4_TYPE_SIZE 1 + +/* H4+ packet types */ +#define H4_CMD_PKT 0x01 +#define H4_ACL_PKT 0x02 +#define H4_SCO_PKT 0x03 +#define H4_EVT_PKT 0x04 +#define H4_NEG_PKT 0x06 +#define H4_ALIVE_PKT 0x07 + +/* TX states */ +#define WAIT_FOR_PKT_TYPE 1 +#define WAIT_FOR_HEADER 2 +#define WAIT_FOR_DATA 3 + +struct hci_fw_event { + struct hci_event_hdr hev; + struct hci_ev_cmd_complete cmd; + __u8 status; +} __attribute__ ((packed)); + +#endif /* __DRIVERS_BLUETOOTH_BRF6150_H */ diff --git a/drivers/bluetooth/hci_h4p/Makefile b/drivers/bluetooth/hci_h4p/Makefile new file mode 100644 index 00000000000..07608a4b777 --- /dev/null +++ b/drivers/bluetooth/hci_h4p/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the Linux Bluetooth HCI device drivers. +# + +obj-$(CONFIG_BT_HCIH4P) += hci_h4p.o + +hci_h4p-objs := core.o fw.o uart.o sysfs.o fw-ti.o fw-csr.o diff --git a/drivers/bluetooth/hci_h4p/core.c b/drivers/bluetooth/hci_h4p/core.c new file mode 100644 index 00000000000..a5b76ad9bdb --- /dev/null +++ b/drivers/bluetooth/hci_h4p/core.c @@ -0,0 +1,1013 @@ +/* + * This file is part of hci_h4p bluetooth driver + * + * Copyright (C) 2005, 2006 Nokia Corporation. + * + * Contact: Ville Tervo + * + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "hci_h4p.h" + +#define PM_TIMEOUT 200 + +/* This should be used in function that cannot release clocks */ +static void hci_h4p_set_clk(struct hci_h4p_info *info, int *clock, int enable) +{ + unsigned long flags; + + spin_lock_irqsave(&info->clocks_lock, flags); + if (enable && !*clock) { + NBT_DBG_POWER("Enabling %p\n", clock); + clk_enable(info->uart_fclk); +#ifdef CONFIG_ARCH_OMAP2 + if (cpu_is_omap24xx()) { + clk_enable(info->uart_iclk); + omap2_block_sleep(); + } +#endif + } + if (!enable && *clock) { + NBT_DBG_POWER("Disabling %p\n", clock); + clk_disable(info->uart_fclk); +#ifdef CONFIG_ARCH_OMAP2 + if (cpu_is_omap24xx()) { + clk_disable(info->uart_iclk); + omap2_allow_sleep(); + } +#endif + } + + *clock = enable; + spin_unlock_irqrestore(&info->clocks_lock, flags); +} + +/* Power management functions */ +static void hci_h4p_disable_tx(struct hci_h4p_info *info) +{ + NBT_DBG_POWER("\n"); + + if (!info->pm_enabled) + return; + + mod_timer(&info->tx_pm_timer, jiffies + msecs_to_jiffies(PM_TIMEOUT)); +} + +static void hci_h4p_enable_tx(struct hci_h4p_info *info) +{ + NBT_DBG_POWER("\n"); + + if (!info->pm_enabled) + return; + + del_timer_sync(&info->tx_pm_timer); + if (info->tx_pm_enabled) { + info->tx_pm_enabled = 0; + hci_h4p_set_clk(info, &info->tx_clocks_en, 1); + gpio_set_value(info->bt_wakeup_gpio, 1); + } +} + +static void hci_h4p_tx_pm_timer(unsigned long data) +{ + struct hci_h4p_info *info; + + NBT_DBG_POWER("\n"); + + info = (struct hci_h4p_info *)data; + + if (hci_h4p_inb(info, UART_LSR) & UART_LSR_TEMT) { + gpio_set_value(info->bt_wakeup_gpio, 0); + hci_h4p_set_clk(info, &info->tx_clocks_en, 0); + info->tx_pm_enabled = 1; + } + else { + mod_timer(&info->tx_pm_timer, jiffies + msecs_to_jiffies(PM_TIMEOUT)); + } +} + +static void hci_h4p_disable_rx(struct hci_h4p_info *info) +{ + if (!info->pm_enabled) + return; + + mod_timer(&info->rx_pm_timer, jiffies + msecs_to_jiffies(PM_TIMEOUT)); +} + +static void hci_h4p_enable_rx(struct hci_h4p_info *info) +{ + unsigned long flags; + + if (!info->pm_enabled) + return; + + del_timer_sync(&info->rx_pm_timer); + spin_lock_irqsave(&info->lock, flags); + if (info->rx_pm_enabled) { + hci_h4p_set_clk(info, &info->rx_clocks_en, 1); + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) | UART_IER_RDI); + __hci_h4p_set_auto_ctsrts(info, 1, UART_EFR_RTS); + info->rx_pm_enabled = 0; + } + spin_unlock_irqrestore(&info->lock, flags); +} + +static void hci_h4p_rx_pm_timer(unsigned long data) +{ + unsigned long flags; + struct hci_h4p_info *info = (struct hci_h4p_info *)data; + + spin_lock_irqsave(&info->lock, flags); + if (!(hci_h4p_inb(info, UART_LSR) & UART_LSR_DR)) { + __hci_h4p_set_auto_ctsrts(info, 0, UART_EFR_RTS); + hci_h4p_set_rts(info, 0); + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) & ~UART_IER_RDI); + hci_h4p_set_clk(info, &info->rx_clocks_en, 0); + info->rx_pm_enabled = 1; + } + else { + mod_timer(&info->rx_pm_timer, jiffies + msecs_to_jiffies(PM_TIMEOUT)); + } + spin_unlock_irqrestore(&info->lock, flags); +} + +/* Negotiation functions */ +int hci_h4p_send_alive_packet(struct hci_h4p_info *info) +{ + NBT_DBG("Sending alive packet\n"); + + if (!info->alive_cmd_skb) + return -EINVAL; + + /* Keep reference to buffer so we can reuse it */ + info->alive_cmd_skb = skb_get(info->alive_cmd_skb); + + skb_queue_tail(&info->txq, info->alive_cmd_skb); + tasklet_schedule(&info->tx_task); + + NBT_DBG("Alive packet sent\n"); + + return 0; +} + +static void hci_h4p_alive_packet(struct hci_h4p_info *info, struct sk_buff *skb) +{ + NBT_DBG("Received alive packet\n"); + if (skb->data[1] == 0xCC) { + complete(&info->init_completion); + } + + kfree_skb(skb); +} + +static int hci_h4p_send_negotiation(struct hci_h4p_info *info, struct sk_buff *skb) +{ + NBT_DBG("Sending negotiation..\n"); + + hci_h4p_change_speed(info, INIT_SPEED); + + info->init_error = 0; + init_completion(&info->init_completion); + skb_queue_tail(&info->txq, skb); + tasklet_schedule(&info->tx_task); + + if (!wait_for_completion_interruptible_timeout(&info->init_completion, + msecs_to_jiffies(1000))) + return -ETIMEDOUT; + + NBT_DBG("Negotiation sent\n"); + return info->init_error; +} + +static void hci_h4p_negotiation_packet(struct hci_h4p_info *info, + struct sk_buff *skb) +{ + int err = 0; + + if (skb->data[1] == 0x20) { + /* Change to operational settings */ + hci_h4p_set_rts(info, 0); + + err = hci_h4p_wait_for_cts(info, 0, 100); + if (err < 0) + goto neg_ret; + + hci_h4p_change_speed(info, MAX_BAUD_RATE); + + err = hci_h4p_wait_for_cts(info, 1, 100); + if (err < 0) + goto neg_ret; + + hci_h4p_set_auto_ctsrts(info, 1, UART_EFR_CTS | UART_EFR_RTS); + + err = hci_h4p_send_alive_packet(info); + if (err < 0) + goto neg_ret; + } else { + dev_err(info->dev, "Could not negotiate hci_h4p settings\n"); + err = -EINVAL; + goto neg_ret; + } + + kfree_skb(skb); + return; + +neg_ret: + info->init_error = err; + complete(&info->init_completion); + kfree_skb(skb); +} + +/* H4 packet handling functions */ +static int hci_h4p_get_hdr_len(struct hci_h4p_info *info, u8 pkt_type) +{ + long retval; + + switch (pkt_type) { + case H4_EVT_PKT: + retval = HCI_EVENT_HDR_SIZE; + break; + case H4_ACL_PKT: + retval = HCI_ACL_HDR_SIZE; + break; + case H4_SCO_PKT: + retval = HCI_SCO_HDR_SIZE; + break; + case H4_NEG_PKT: + retval = 11; + break; + case H4_ALIVE_PKT: + retval = 3; + break; + default: + dev_err(info->dev, "Unknown H4 packet type 0x%.2x\n", pkt_type); + retval = -1; + break; + } + + return retval; +} + +static unsigned int hci_h4p_get_data_len(struct hci_h4p_info *info, + struct sk_buff *skb) +{ + long retval = -1; + struct hci_event_hdr *evt_hdr; + struct hci_acl_hdr *acl_hdr; + struct hci_sco_hdr *sco_hdr; + + switch (bt_cb(skb)->pkt_type) { + case H4_EVT_PKT: + evt_hdr = (struct hci_event_hdr *)skb->data; + retval = evt_hdr->plen; + break; + case H4_ACL_PKT: + acl_hdr = (struct hci_acl_hdr *)skb->data; + retval = le16_to_cpu(acl_hdr->dlen); + break; + case H4_SCO_PKT: + sco_hdr = (struct hci_sco_hdr *)skb->data; + retval = sco_hdr->dlen; + break; + case H4_NEG_PKT: + retval = 0; + break; + case H4_ALIVE_PKT: + retval = 0; + break; + } + + return retval; +} + +static inline void hci_h4p_recv_frame(struct hci_h4p_info *info, + struct sk_buff *skb) +{ + + if (unlikely(!test_bit(HCI_RUNNING, &info->hdev->flags))) { + NBT_DBG("fw_event\n"); + hci_h4p_parse_fw_event(info, skb); + } else { + hci_recv_frame(skb); + NBT_DBG("Frame sent to upper layer\n"); + } +} + +static void hci_h4p_rx_tasklet(unsigned long data) +{ + u8 byte; + unsigned long flags; + struct hci_h4p_info *info = (struct hci_h4p_info *)data; + + NBT_DBG("tasklet woke up\n"); + NBT_DBG_TRANSFER("rx_tasklet woke up\ndata "); + + while (hci_h4p_inb(info, UART_LSR) & UART_LSR_DR) { + byte = hci_h4p_inb(info, UART_RX); + if (info->garbage_bytes) { + info->garbage_bytes--; + continue; + } + if (info->rx_skb == NULL) { + info->rx_skb = bt_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC | GFP_DMA); + if (!info->rx_skb) { + dev_err(info->dev, "Can't allocate memory for new packet\n"); + goto finish_task; + } + info->rx_state = WAIT_FOR_PKT_TYPE; + info->rx_skb->dev = (void *)info->hdev; + } + info->hdev->stat.byte_rx++; + NBT_DBG_TRANSFER_NF("0x%.2x ", byte); + switch (info->rx_state) { + case WAIT_FOR_PKT_TYPE: + bt_cb(info->rx_skb)->pkt_type = byte; + info->rx_count = hci_h4p_get_hdr_len(info, byte); + if (info->rx_count < 0) { + info->hdev->stat.err_rx++; + kfree_skb(info->rx_skb); + info->rx_skb = NULL; + } else { + info->rx_state = WAIT_FOR_HEADER; + } + break; + case WAIT_FOR_HEADER: + info->rx_count--; + *skb_put(info->rx_skb, 1) = byte; + if (info->rx_count == 0) { + info->rx_count = hci_h4p_get_data_len(info, info->rx_skb); + if (info->rx_count > skb_tailroom(info->rx_skb)) { + dev_err(info->dev, "Frame is %ld bytes too long.\n", + info->rx_count - skb_tailroom(info->rx_skb)); + kfree_skb(info->rx_skb); + info->rx_skb = NULL; + info->garbage_bytes = info->rx_count - skb_tailroom(info->rx_skb); + break; + } + info->rx_state = WAIT_FOR_DATA; + + if (bt_cb(info->rx_skb)->pkt_type == H4_NEG_PKT) { + hci_h4p_negotiation_packet(info, info->rx_skb); + info->rx_skb = NULL; + info->rx_state = WAIT_FOR_PKT_TYPE; + goto finish_task; + } + if (bt_cb(info->rx_skb)->pkt_type == H4_ALIVE_PKT) { + hci_h4p_alive_packet(info, info->rx_skb); + info->rx_skb = NULL; + info->rx_state = WAIT_FOR_PKT_TYPE; + goto finish_task; + } + } + break; + case WAIT_FOR_DATA: + info->rx_count--; + *skb_put(info->rx_skb, 1) = byte; + if (info->rx_count == 0) { + /* H4+ devices should allways send word aligned packets */ + if (!(info->rx_skb->len % 2)) { + info->garbage_bytes++; + } + hci_h4p_recv_frame(info, info->rx_skb); + info->rx_skb = NULL; + } + break; + default: + WARN_ON(1); + break; + } + } + +finish_task: + spin_lock_irqsave(&info->lock, flags); + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) | UART_IER_RDI); + spin_unlock_irqrestore(&info->lock, flags); + + NBT_DBG_TRANSFER_NF("\n"); + NBT_DBG("rx_ended\n"); +} + +static void hci_h4p_tx_tasklet(unsigned long data) +{ + unsigned int sent = 0; + unsigned long flags; + struct sk_buff *skb; + struct hci_h4p_info *info = (struct hci_h4p_info *)data; + + NBT_DBG("tasklet woke up\n"); + NBT_DBG_TRANSFER("tx_tasklet woke up\n data "); + + skb = skb_dequeue(&info->txq); + if (!skb) { + /* No data in buffer */ + NBT_DBG("skb ready\n"); + hci_h4p_disable_tx(info); + return; + } + + /* Copy data to tx fifo */ + while (!(hci_h4p_inb(info, UART_OMAP_SSR) & UART_OMAP_SSR_TXFULL) && + (sent < skb->len)) { + NBT_DBG_TRANSFER_NF("0x%.2x ", skb->data[sent]); + hci_h4p_outb(info, UART_TX, skb->data[sent]); + sent++; + } + + info->hdev->stat.byte_tx += sent; + NBT_DBG_TRANSFER_NF("\n"); + if (skb->len == sent) { + kfree_skb(skb); + } else { + skb_pull(skb, sent); + skb_queue_head(&info->txq, skb); + } + + spin_lock_irqsave(&info->lock, flags); + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) | UART_IER_THRI); + spin_unlock_irqrestore(&info->lock, flags); +} + +static irqreturn_t hci_h4p_interrupt(int irq, void *data) +{ + struct hci_h4p_info *info = (struct hci_h4p_info *)data; + u8 iir, msr; + int ret; + unsigned long flags; + + ret = IRQ_NONE; + + iir = hci_h4p_inb(info, UART_IIR); + if (iir & UART_IIR_NO_INT) { + dev_err(info->dev, "Interrupt but no reason irq 0x%.2x\n", iir); + return IRQ_HANDLED; + } + + NBT_DBG("In interrupt handler iir 0x%.2x\n", iir); + + iir &= UART_IIR_ID; + + if (iir == UART_IIR_MSI) { + msr = hci_h4p_inb(info, UART_MSR); + ret = IRQ_HANDLED; + } + if (iir == UART_IIR_RLSI) { + hci_h4p_inb(info, UART_RX); + hci_h4p_inb(info, UART_LSR); + ret = IRQ_HANDLED; + } + + if (iir == UART_IIR_RDI) { + spin_lock_irqsave(&info->lock, flags); + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) & ~UART_IER_RDI); + spin_unlock_irqrestore(&info->lock, flags); + tasklet_schedule(&info->rx_task); + ret = IRQ_HANDLED; + } + + if (iir == UART_IIR_THRI) { + spin_lock_irqsave(&info->lock, flags); + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) & ~UART_IER_THRI); + spin_unlock_irqrestore(&info->lock, flags); + tasklet_schedule(&info->tx_task); + ret = IRQ_HANDLED; + } + + return ret; +} + +static irqreturn_t hci_h4p_wakeup_interrupt(int irq, void *dev_inst) +{ + struct hci_h4p_info *info = dev_inst; + int should_wakeup; + struct hci_dev *hdev; + + if (!info->hdev) + return IRQ_HANDLED; + + hdev = info->hdev; + + if (!test_bit(HCI_RUNNING, &hdev->flags)) + return IRQ_HANDLED; + + should_wakeup = gpio_get_value(info->host_wakeup_gpio); + NBT_DBG_POWER("gpio interrupt %d\n", should_wakeup); + if (should_wakeup) { + hci_h4p_enable_rx(info); + } else { + hci_h4p_disable_rx(info); + } + + return IRQ_HANDLED; +} + +static int hci_h4p_reset(struct hci_h4p_info *info) +{ + int err; + + hci_h4p_init_uart(info); + hci_h4p_set_rts(info, 0); + + gpio_set_value(info->reset_gpio, 0); + msleep(100); + gpio_set_value(info->bt_wakeup_gpio, 1); + gpio_set_value(info->reset_gpio, 1); + msleep(100); + + err = hci_h4p_wait_for_cts(info, 1, 10); + if (err < 0) { + dev_err(info->dev, "No cts from bt chip\n"); + return err; + } + + hci_h4p_set_rts(info, 1); + + return 0; +} + +/* hci callback functions */ +static int hci_h4p_hci_flush(struct hci_dev *hdev) +{ + struct hci_h4p_info *info; + info = hdev->driver_data; + + skb_queue_purge(&info->txq); + + return 0; +} + +static int hci_h4p_hci_open(struct hci_dev *hdev) +{ + struct hci_h4p_info *info; + int err; + struct sk_buff *neg_cmd_skb; + struct sk_buff_head fw_queue; + + info = hdev->driver_data; + + if (test_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + skb_queue_head_init(&fw_queue); + err = hci_h4p_read_fw(info, &fw_queue); + if (err < 0) { + dev_err(info->dev, "Cannot read firmware\n"); + return err; + } + neg_cmd_skb = skb_dequeue(&fw_queue); + if (!neg_cmd_skb) { + err = -EPROTO; + goto err_clean; + } + info->alive_cmd_skb = skb_dequeue(&fw_queue); + if (!info->alive_cmd_skb) { + err = -EPROTO; + goto err_clean; + } + + hci_h4p_set_clk(info, &info->tx_clocks_en, 1); + hci_h4p_set_clk(info, &info->rx_clocks_en, 1); + + tasklet_enable(&info->tx_task); + tasklet_enable(&info->rx_task); + info->rx_state = WAIT_FOR_PKT_TYPE; + info->rx_count = 0; + info->garbage_bytes = 0; + info->rx_skb = NULL; + info->pm_enabled = 0; + init_completion(&info->fw_completion); + + err = hci_h4p_reset(info); + if (err < 0) + goto err_clean; + + err = hci_h4p_send_negotiation(info, neg_cmd_skb); + neg_cmd_skb = NULL; + if (err < 0) + goto err_clean; + + err = hci_h4p_send_fw(info, &fw_queue); + if (err < 0) { + dev_err(info->dev, "Sending firmware failed.\n"); + goto err_clean; + } + + kfree_skb(info->alive_cmd_skb); + info->alive_cmd_skb = NULL; + info->pm_enabled = 1; + info->tx_pm_enabled = 1; + info->rx_pm_enabled = 0; + set_bit(HCI_RUNNING, &hdev->flags); + + NBT_DBG("hci up and running\n"); + return 0; + +err_clean: + hci_h4p_hci_flush(hdev); + tasklet_disable(&info->tx_task); + tasklet_disable(&info->rx_task); + hci_h4p_reset_uart(info); + hci_h4p_set_clk(info, &info->tx_clocks_en, 0); + hci_h4p_set_clk(info, &info->rx_clocks_en, 0); + gpio_set_value(info->reset_gpio, 0); + gpio_set_value(info->bt_wakeup_gpio, 0); + skb_queue_purge(&fw_queue); + kfree_skb(neg_cmd_skb); + neg_cmd_skb = NULL; + kfree_skb(info->alive_cmd_skb); + info->alive_cmd_skb = NULL; + kfree_skb(info->rx_skb); + + return err; +} + +static int hci_h4p_hci_close(struct hci_dev *hdev) +{ + struct hci_h4p_info *info = hdev->driver_data; + + if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + hci_h4p_hci_flush(hdev); + del_timer_sync(&info->tx_pm_timer); + del_timer_sync(&info->rx_pm_timer); + tasklet_disable(&info->tx_task); + tasklet_disable(&info->rx_task); + hci_h4p_set_clk(info, &info->tx_clocks_en, 1); + hci_h4p_set_clk(info, &info->rx_clocks_en, 1); + hci_h4p_reset_uart(info); + hci_h4p_set_clk(info, &info->tx_clocks_en, 0); + hci_h4p_set_clk(info, &info->rx_clocks_en, 0); + gpio_set_value(info->reset_gpio, 0); + gpio_set_value(info->bt_wakeup_gpio, 0); + kfree_skb(info->rx_skb); + + return 0; +} + +static void hci_h4p_hci_destruct(struct hci_dev *hdev) +{ +} + +static int hci_h4p_hci_send_frame(struct sk_buff *skb) +{ + struct hci_h4p_info *info; + struct hci_dev *hdev = (struct hci_dev *)skb->dev; + int err = 0; + + if (!hdev) { + printk(KERN_WARNING "hci_h4p: Frame for unknown device\n"); + return -ENODEV; + } + + NBT_DBG("dev %p, skb %p\n", hdev, skb); + + info = hdev->driver_data; + + if (!test_bit(HCI_RUNNING, &hdev->flags)) { + dev_warn(info->dev, "Frame for non-running device\n"); + return -EIO; + } + + switch (bt_cb(skb)->pkt_type) { + case HCI_COMMAND_PKT: + hdev->stat.cmd_tx++; + break; + case HCI_ACLDATA_PKT: + hdev->stat.acl_tx++; + break; + case HCI_SCODATA_PKT: + hdev->stat.sco_tx++; + break; + } + + /* Push frame type to skb */ + *skb_push(skb, 1) = (bt_cb(skb)->pkt_type); + /* We should allways send word aligned data to h4+ devices */ + if (skb->len % 2) { + err = skb_pad(skb, 1); + } + if (err) + return err; + + hci_h4p_enable_tx(info); + skb_queue_tail(&info->txq, skb); + tasklet_schedule(&info->tx_task); + + return 0; +} + +static int hci_h4p_hci_ioctl(struct hci_dev *hdev, unsigned int cmd, unsigned long arg) +{ + return -ENOIOCTLCMD; +} + +static int hci_h4p_register_hdev(struct hci_h4p_info *info) +{ + struct hci_dev *hdev; + + /* Initialize and register HCI device */ + + hdev = hci_alloc_dev(); + if (!hdev) { + dev_err(info->dev, "Can't allocate memory for device\n"); + return -ENOMEM; + } + info->hdev = hdev; + + hdev->type = HCI_UART; + hdev->driver_data = info; + + hdev->open = hci_h4p_hci_open; + hdev->close = hci_h4p_hci_close; + hdev->flush = hci_h4p_hci_flush; + hdev->send = hci_h4p_hci_send_frame; + hdev->destruct = hci_h4p_hci_destruct; + hdev->ioctl = hci_h4p_hci_ioctl; + + hdev->owner = THIS_MODULE; + + if (hci_register_dev(hdev) < 0) { + dev_err(info->dev, "hci_h4p: Can't register HCI device %s.\n", hdev->name); + return -ENODEV; + } + + return 0; +} + +static int hci_h4p_probe(struct platform_device *pdev) +{ + struct omap_bluetooth_config *bt_config; + struct hci_h4p_info *info; + int irq, err; + + dev_info(&pdev->dev, "Registering HCI H4P device\n"); + info = kzalloc(sizeof(struct hci_h4p_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->dev = &pdev->dev; + info->pm_enabled = 0; + info->tx_pm_enabled = 0; + info->rx_pm_enabled = 0; + info->garbage_bytes = 0; + info->tx_clocks_en = 0; + info->rx_clocks_en = 0; + tasklet_init(&info->tx_task, hci_h4p_tx_tasklet, (unsigned long)info); + tasklet_init(&info->rx_task, hci_h4p_rx_tasklet, (unsigned long)info); + /* hci_h4p_hci_open assumes that tasklet is disabled in startup */ + tasklet_disable(&info->tx_task); + tasklet_disable(&info->rx_task); + spin_lock_init(&info->lock); + spin_lock_init(&info->clocks_lock); + skb_queue_head_init(&info->txq); + init_timer(&info->tx_pm_timer); + info->tx_pm_timer.function = hci_h4p_tx_pm_timer; + info->tx_pm_timer.data = (unsigned long)info; + init_timer(&info->rx_pm_timer); + info->rx_pm_timer.function = hci_h4p_rx_pm_timer; + info->rx_pm_timer.data = (unsigned long)info; + + if (pdev->dev.platform_data == NULL) { + dev_err(&pdev->dev, "Could not get Bluetooth config data\n"); + return -ENODATA; + } + + bt_config = pdev->dev.platform_data; + info->chip_type = bt_config->chip_type; + info->bt_wakeup_gpio = bt_config->bt_wakeup_gpio; + info->host_wakeup_gpio = bt_config->host_wakeup_gpio; + info->reset_gpio = bt_config->reset_gpio; + info->bt_sysclk = bt_config->bt_sysclk; + + NBT_DBG("RESET gpio: %d\n", info->reset_gpio); + NBT_DBG("BTWU gpio: %d\n", info->bt_wakeup_gpio); + NBT_DBG("HOSTWU gpio: %d\n", info->host_wakeup_gpio); + NBT_DBG("Uart: %d\n", bt_config->bt_uart); + NBT_DBG("sysclk: %d\n", info->bt_sysclk); + + err = gpio_request(info->reset_gpio, "BT reset"); + if (err < 0) { + dev_err(&pdev->dev, "Cannot get GPIO line %d\n", + info->reset_gpio); + kfree(info); + goto cleanup; + } + + err = gpio_request(info->bt_wakeup_gpio, "BT wakeup"); + if (err < 0) + { + dev_err(info->dev, "Cannot get GPIO line 0x%d", + info->bt_wakeup_gpio); + gpio_free(info->reset_gpio); + kfree(info); + goto cleanup; + } + + err = gpio_request(info->host_wakeup_gpio, "BT host wakeup"); + if (err < 0) + { + dev_err(info->dev, "Cannot get GPIO line %d", + info->host_wakeup_gpio); + gpio_free(info->reset_gpio); + gpio_free(info->bt_wakeup_gpio); + kfree(info); + goto cleanup; + } + + gpio_direction_output(info->reset_gpio, 0); + gpio_direction_output(info->bt_wakeup_gpio, 0); + gpio_direction_input(info->host_wakeup_gpio); + + switch (bt_config->bt_uart) { + case 1: + if (cpu_is_omap16xx()) { + irq = INT_UART1; + info->uart_fclk = clk_get(NULL, "uart1_ck"); + } else if (cpu_is_omap24xx()) { + irq = INT_24XX_UART1_IRQ; + info->uart_iclk = clk_get(NULL, "uart1_ick"); + info->uart_fclk = clk_get(NULL, "uart1_fck"); + } + /* FIXME: Use platform_get_resource for the port */ + info->uart_base = ioremap(OMAP_UART1_BASE, 0x16); + if (!info->uart_base) + goto cleanup; + break; + case 2: + if (cpu_is_omap16xx()) { + irq = INT_UART2; + info->uart_fclk = clk_get(NULL, "uart2_ck"); + } else { + irq = INT_24XX_UART2_IRQ; + info->uart_iclk = clk_get(NULL, "uart2_ick"); + info->uart_fclk = clk_get(NULL, "uart2_fck"); + } + /* FIXME: Use platform_get_resource for the port */ + info->uart_base = ioremap(OMAP_UART2_BASE, 0x16); + if (!info->uart_base) + goto cleanup; + break; + case 3: + if (cpu_is_omap16xx()) { + irq = INT_UART3; + info->uart_fclk = clk_get(NULL, "uart3_ck"); + } else { + irq = INT_24XX_UART3_IRQ; + info->uart_iclk = clk_get(NULL, "uart3_ick"); + info->uart_fclk = clk_get(NULL, "uart3_fck"); + } + /* FIXME: Use platform_get_resource for the port */ + info->uart_base = ioremap(OMAP_UART3_BASE, 0x16); + if (!info->uart_base) + goto cleanup; + break; + default: + dev_err(info->dev, "No uart defined\n"); + goto cleanup; + } + + info->irq = irq; + err = request_irq(irq, hci_h4p_interrupt, 0, "hci_h4p", (void *)info); + if (err < 0) { + dev_err(info->dev, "hci_h4p: unable to get IRQ %d\n", irq); + goto cleanup; + } + + err = request_irq(gpio_to_irq(info->host_wakeup_gpio), + hci_h4p_wakeup_interrupt, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "hci_h4p_wkup", (void *)info); + if (err < 0) { + dev_err(info->dev, "hci_h4p: unable to get wakeup IRQ %d\n", + gpio_to_irq(info->host_wakeup_gpio)); + free_irq(irq, (void *)info); + goto cleanup; + } + + hci_h4p_set_clk(info, &info->tx_clocks_en, 1); + hci_h4p_set_auto_ctsrts(info, 0, UART_EFR_CTS | UART_EFR_RTS); + err = hci_h4p_init_uart(info); + if (err < 0) + goto cleanup_irq; + err = hci_h4p_reset(info); + if (err < 0) + goto cleanup_irq; + err = hci_h4p_wait_for_cts(info, 1, 10); + if (err < 0) + goto cleanup_irq; + hci_h4p_set_clk(info, &info->tx_clocks_en, 0); + + platform_set_drvdata(pdev, info); + err = hci_h4p_sysfs_create_files(info->dev); + if (err < 0) + goto cleanup_irq; + + if (hci_h4p_register_hdev(info) < 0) { + dev_err(info->dev, "failed to register hci_h4p hci device\n"); + goto cleanup_irq; + } + gpio_set_value(info->reset_gpio, 0); + + return 0; + +cleanup_irq: + free_irq(irq, (void *)info); + free_irq(gpio_to_irq(info->host_wakeup_gpio), (void *)info); +cleanup: + gpio_set_value(info->reset_gpio, 0); + gpio_free(info->reset_gpio); + gpio_free(info->bt_wakeup_gpio); + gpio_free(info->host_wakeup_gpio); + kfree(info); + + return err; + +} + +static int hci_h4p_remove(struct platform_device *dev) +{ + struct hci_h4p_info *info; + + info = platform_get_drvdata(dev); + + hci_h4p_hci_close(info->hdev); + free_irq(gpio_to_irq(info->host_wakeup_gpio), (void *) info); + hci_free_dev(info->hdev); + gpio_free(info->reset_gpio); + gpio_free(info->bt_wakeup_gpio); + gpio_free(info->host_wakeup_gpio); + free_irq(info->irq, (void *) info); + kfree(info); + + return 0; +} + +static struct platform_driver hci_h4p_driver = { + .probe = hci_h4p_probe, + .remove = hci_h4p_remove, + .driver = { + .name = "hci_h4p", + }, +}; + +static int __init hci_h4p_init(void) +{ + int err = 0; + + /* Register the driver with LDM */ + err = platform_driver_register(&hci_h4p_driver); + if (err < 0) + printk(KERN_WARNING "failed to register hci_h4p driver\n"); + + return err; +} + +static void __exit hci_h4p_exit(void) +{ + platform_driver_unregister(&hci_h4p_driver); +} + +module_init(hci_h4p_init); +module_exit(hci_h4p_exit); + +MODULE_DESCRIPTION("h4 driver with nokia extensions"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ville Tervo"); diff --git a/drivers/bluetooth/hci_h4p/fw-csr.c b/drivers/bluetooth/hci_h4p/fw-csr.c new file mode 100644 index 00000000000..209aab26573 --- /dev/null +++ b/drivers/bluetooth/hci_h4p/fw-csr.c @@ -0,0 +1,141 @@ +/* + * This file is part of hci_h4p bluetooth driver + * + * Copyright (C) 2005, 2006 Nokia Corporation. + * + * Contact: Ville Tervo + * + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include + +#include "hci_h4p.h" + +void hci_h4p_bc4_parse_fw_event(struct hci_h4p_info *info, struct sk_buff *skb) +{ + /* Check if this is fw packet */ + if (skb->data[0] != 0xff) { + hci_recv_frame(skb); + return; + } + + if (skb->data[11] || skb->data[12]) { + dev_err(info->dev, "Firmware sending command failed\n"); + info->fw_error = -EPROTO; + } + + kfree_skb(skb); + complete(&info->fw_completion); +} + +int hci_h4p_bc4_send_fw(struct hci_h4p_info *info, + struct sk_buff_head *fw_queue) +{ + struct sk_buff *skb; + unsigned int offset; + int retries, count, i; + + info->fw_error = 0; + + NBT_DBG_FW("Sending firmware\n"); + skb = skb_dequeue(fw_queue); + + if (!skb) + return -ENOMSG; + + /* Check if this is bd_address packet */ + if (skb->data[15] == 0x01 && skb->data[16] == 0x00) { + offset = 21; + skb->data[offset + 1] = 0x00; + skb->data[offset + 5] = 0x00; + skb->data[offset + 7] = info->bdaddr[0]; + skb->data[offset + 6] = info->bdaddr[1]; + skb->data[offset + 4] = info->bdaddr[2]; + skb->data[offset + 0] = info->bdaddr[3]; + skb->data[offset + 3] = info->bdaddr[4]; + skb->data[offset + 2] = info->bdaddr[5]; + } + + for (i = 0; i < 6; i++) { + if (info->bdaddr[i] != 0x00) + break; + } + + if (i > 5) { + dev_info(info->dev, "Valid bluetooth address not found.\n"); + kfree_skb(skb); + return -ENODEV; + } + + for (count = 1; ; count++) { + NBT_DBG_FW("Sending firmware command %d\n", count); + init_completion(&info->fw_completion); + skb_queue_tail(&info->txq, skb); + tasklet_schedule(&info->tx_task); + + skb = skb_dequeue(fw_queue); + if (!skb) + break; + + if (!wait_for_completion_timeout(&info->fw_completion, + msecs_to_jiffies(1000))) { + dev_err(info->dev, "No reply to fw command\n"); + return -ETIMEDOUT; + } + + if (info->fw_error) { + dev_err(info->dev, "FW error\n"); + return -EPROTO; + } + }; + + /* Wait for chip warm reset */ + retries = 100; + while ((!skb_queue_empty(&info->txq) || + !(hci_h4p_inb(info, UART_LSR) & UART_LSR_TEMT)) && + retries--) { + msleep(10); + } + if (!retries) { + dev_err(info->dev, "Transmitter not empty\n"); + return -ETIMEDOUT; + } + + hci_h4p_change_speed(info, BC4_MAX_BAUD_RATE); + + if (hci_h4p_wait_for_cts(info, 1, 100)) { + dev_err(info->dev, "cts didn't go down after final speed change\n"); + return -ETIMEDOUT; + } + + retries = 100; + do { + init_completion(&info->init_completion); + hci_h4p_send_alive_packet(info); + retries--; + } while (!wait_for_completion_timeout(&info->init_completion, 100) && + retries > 0); + + if (!retries) { + dev_err(info->dev, "No alive reply after speed change\n"); + return -ETIMEDOUT; + } + + return 0; +} diff --git a/drivers/bluetooth/hci_h4p/fw-ti.c b/drivers/bluetooth/hci_h4p/fw-ti.c new file mode 100644 index 00000000000..b98b579bff5 --- /dev/null +++ b/drivers/bluetooth/hci_h4p/fw-ti.c @@ -0,0 +1,90 @@ +/* + * This file is part of hci_h4p bluetooth driver + * + * Copyright (C) 2005, 2006 Nokia Corporation. + * + * Contact: Ville Tervo + * + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include + +#include "hci_h4p.h" + +void hci_h4p_brf6150_parse_fw_event(struct hci_h4p_info *info, + struct sk_buff *skb) +{ + struct hci_fw_event *ev; + int err = 0; + + if (bt_cb(skb)->pkt_type != H4_EVT_PKT) { + dev_err(info->dev, "Got non event fw packet.\n"); + err = -EPROTO; + goto ret; + } + + ev = (struct hci_fw_event *)skb->data; + if (ev->hev.evt != HCI_EV_CMD_COMPLETE) { + dev_err(info->dev, "Got non cmd complete fw event\n"); + err = -EPROTO; + goto ret; + } + + if (ev->status != 0) { + dev_err(info->dev, "Got error status from fw command\n"); + err = -EPROTO; + goto ret; + } + +ret: + info->fw_error = err; + complete(&info->fw_completion); +} + +int hci_h4p_brf6150_send_fw(struct hci_h4p_info *info, struct sk_buff_head *fw_queue) +{ + struct sk_buff *skb; + int err = 0; + + info->fw_error = 0; + + while ((skb = skb_dequeue(fw_queue)) != NULL) { + /* We should allways send word aligned data to h4+ devices */ + if (skb->len % 2) { + err = skb_pad(skb, 1); + } + if (err) + return err; + + init_completion(&info->fw_completion); + skb_queue_tail(&info->txq, skb); + tasklet_schedule(&info->tx_task); + + if (!wait_for_completion_timeout(&info->fw_completion, HZ)) { + dev_err(info->dev, "Timeout while sending brf6150 fw\n"); + return -ETIMEDOUT; + } + + if (info->fw_error) { + dev_err(info->dev, "There was fw_error while sending bfr6150 fw\n"); + return -EPROTO; + } + } + NBT_DBG_FW("Firmware sent\n"); + + return 0; +} diff --git a/drivers/bluetooth/hci_h4p/fw.c b/drivers/bluetooth/hci_h4p/fw.c new file mode 100644 index 00000000000..792b6b70fc9 --- /dev/null +++ b/drivers/bluetooth/hci_h4p/fw.c @@ -0,0 +1,152 @@ +/* + * This file is part of hci_h4p bluetooth driver + * + * Copyright (C) 2005, 2006 Nokia Corporation. + * + * Contact: Ville Tervo + * + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include + +#include + +#include "hci_h4p.h" + +static int fw_pos; + +/* Firmware handling */ +static int hci_h4p_open_firmware(struct hci_h4p_info *info, + const struct firmware **fw_entry) +{ + int err; + + fw_pos = 0; + NBT_DBG_FW("Opening %d firmware\n", info->chip_type); + switch (info->chip_type) { + case BT_CHIP_TI: + err = request_firmware(fw_entry, "brf6150fw.bin", info->dev); + break; + case BT_CHIP_CSR: + err = request_firmware(fw_entry, "bc4fw.bin", info->dev); + break; + default: + dev_err(info->dev, "Invalid chip type\n"); + *fw_entry = NULL; + err = -EINVAL; + } + + return err; +} + +static void hci_h4p_close_firmware(const struct firmware *fw_entry) +{ + release_firmware(fw_entry); +} + +/* Read fw. Return length of the command. If no more commands in + * fw 0 is returned. In error case return value is negative. + */ +static int hci_h4p_read_fw_cmd(struct hci_h4p_info *info, struct sk_buff **skb, + const struct firmware *fw_entry, int how) +{ + unsigned int cmd_len; + + if (fw_pos >= fw_entry->size) { + return 0; + } + + cmd_len = fw_entry->data[fw_pos++]; + if (!cmd_len) + return 0; + + if (fw_pos + cmd_len > fw_entry->size) { + dev_err(info->dev, "Corrupted firmware image\n"); + return -EMSGSIZE; + } + + *skb = bt_skb_alloc(cmd_len, how); + if (!*skb) { + dev_err(info->dev, "Cannot reserve memory for buffer\n"); + return -ENOMEM; + } + memcpy(skb_put(*skb, cmd_len), &fw_entry->data[fw_pos], cmd_len); + + fw_pos += cmd_len; + + return (*skb)->len; +} + +int hci_h4p_read_fw(struct hci_h4p_info *info, struct sk_buff_head *fw_queue) +{ + const struct firmware *fw_entry = NULL; + struct sk_buff *skb = NULL; + int err; + + err = hci_h4p_open_firmware(info, &fw_entry); + if (err < 0 || !fw_entry) + goto err_clean; + + while ((err = hci_h4p_read_fw_cmd(info, &skb, fw_entry, GFP_KERNEL))) { + if (err < 0 || !skb) + goto err_clean; + + skb_queue_tail(fw_queue, skb); + } + +err_clean: + hci_h4p_close_firmware(fw_entry); + return err; +} + +int hci_h4p_send_fw(struct hci_h4p_info *info, struct sk_buff_head *fw_queue) +{ + int err; + + switch(info->chip_type) { + case BT_CHIP_CSR: + err = hci_h4p_bc4_send_fw(info, fw_queue); + break; + case BT_CHIP_TI: + err = hci_h4p_brf6150_send_fw(info, fw_queue); + break; + default: + dev_err(info->dev, "Don't know how to send firmware\n"); + err = -EINVAL; + } + + return err; +} + +void hci_h4p_parse_fw_event(struct hci_h4p_info *info, struct sk_buff *skb) +{ + switch (info->chip_type) { + case BT_CHIP_CSR: + hci_h4p_bc4_parse_fw_event(info, skb); + break; + case BT_CHIP_TI: + hci_h4p_brf6150_parse_fw_event(info, skb); + break; + default: + dev_err(info->dev, "Don't know how to parse fw event\n"); + info->fw_error = -EINVAL; + } + + return; +} diff --git a/drivers/bluetooth/hci_h4p/hci_h4p.h b/drivers/bluetooth/hci_h4p/hci_h4p.h new file mode 100644 index 00000000000..85e9116436c --- /dev/null +++ b/drivers/bluetooth/hci_h4p/hci_h4p.h @@ -0,0 +1,183 @@ +/* + * This file is part of hci_h4p bluetooth driver + * + * Copyright (C) 2005, 2006 Nokia Corporation. + * + * Contact: Ville Tervo + * + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include + +#include +#include +#include + +#ifndef __DRIVERS_BLUETOOTH_HCI_H4P_H +#define __DRIVERS_BLUETOOTH_HCI_H4P_H + +#define UART_SYSC_OMAP_RESET 0x03 +#define UART_SYSS_RESETDONE 0x01 +#define UART_OMAP_SCR_EMPTY_THR 0x08 +#define UART_OMAP_SCR_WAKEUP 0x10 +#define UART_OMAP_SSR_WAKEUP 0x02 +#define UART_OMAP_SSR_TXFULL 0x01 + +#if 0 +#define NBT_DBG(fmt, arg...) printk("%s: " fmt "" , __FUNCTION__ , ## arg) +#else +#define NBT_DBG(...) +#endif + +#if 0 +#define NBT_DBG_FW(fmt, arg...) printk("%s: " fmt "" , __FUNCTION__ , ## arg) +#else +#define NBT_DBG_FW(...) +#endif + +#if 0 +#define NBT_DBG_POWER(fmt, arg...) printk("%s: " fmt "" , __FUNCTION__ , ## arg) +#else +#define NBT_DBG_POWER(...) +#endif + +#if 0 +#define NBT_DBG_TRANSFER(fmt, arg...) printk("%s: " fmt "" , __FUNCTION__ , ## arg) +#else +#define NBT_DBG_TRANSFER(...) +#endif + +#if 0 +#define NBT_DBG_TRANSFER_NF(fmt, arg...) printk(fmt "" , ## arg) +#else +#define NBT_DBG_TRANSFER_NF(...) +#endif + +#if 0 +#define NBT_DBG_DMA(fmt, arg...) printk("%s: " fmt "" , __FUNCTION__ , ## arg) +#else +#define NBT_DBG_DMA(...) +#endif + +struct hci_h4p_info { + struct hci_dev *hdev; + spinlock_t lock; + + void __iomem *uart_base; + unsigned long uart_phys_base; + int irq; + struct device *dev; + u8 bdaddr[6]; + u8 chip_type; + u8 bt_wakeup_gpio; + u8 host_wakeup_gpio; + u8 reset_gpio; + u8 bt_sysclk; + + + struct sk_buff_head fw_queue; + struct sk_buff *alive_cmd_skb; + struct completion init_completion; + struct completion fw_completion; + int fw_error; + int init_error; + + struct sk_buff_head txq; + struct tasklet_struct tx_task; + + struct sk_buff *rx_skb; + long rx_count; + unsigned long rx_state; + unsigned long garbage_bytes; + struct tasklet_struct rx_task; + + int pm_enabled; + int tx_pm_enabled; + int rx_pm_enabled; + struct timer_list tx_pm_timer; + struct timer_list rx_pm_timer; + + int tx_clocks_en; + int rx_clocks_en; + spinlock_t clocks_lock; + struct clk *uart_iclk; + struct clk *uart_fclk; +}; + +#define MAX_BAUD_RATE 921600 +#define BC4_MAX_BAUD_RATE 3692300 +#define UART_CLOCK 48000000 +#define BT_INIT_DIVIDER 320 +#define BT_BAUDRATE_DIVIDER 384000000 +#define BT_SYSCLK_DIV 1000 +#define INIT_SPEED 120000 + +#define H4_TYPE_SIZE 1 + +/* H4+ packet types */ +#define H4_CMD_PKT 0x01 +#define H4_ACL_PKT 0x02 +#define H4_SCO_PKT 0x03 +#define H4_EVT_PKT 0x04 +#define H4_NEG_PKT 0x06 +#define H4_ALIVE_PKT 0x07 + +/* TX states */ +#define WAIT_FOR_PKT_TYPE 1 +#define WAIT_FOR_HEADER 2 +#define WAIT_FOR_DATA 3 + +struct hci_fw_event { + struct hci_event_hdr hev; + struct hci_ev_cmd_complete cmd; + u8 status; +} __attribute__ ((packed)); + +struct hci_bc4_set_bdaddr { + u8 type; + struct hci_command_hdr cmd_hdr; +} __attribute__ ((packed)); + +int hci_h4p_send_alive_packet(struct hci_h4p_info *info); + +void hci_h4p_bc4_parse_fw_event(struct hci_h4p_info *info, + struct sk_buff *skb); +int hci_h4p_bc4_send_fw(struct hci_h4p_info *info, + struct sk_buff_head *fw_queue); + +void hci_h4p_brf6150_parse_fw_event(struct hci_h4p_info *info, + struct sk_buff *skb); +int hci_h4p_brf6150_send_fw(struct hci_h4p_info *info, + struct sk_buff_head *fw_queue); + +int hci_h4p_read_fw(struct hci_h4p_info *info, struct sk_buff_head *fw_queue); +int hci_h4p_send_fw(struct hci_h4p_info *info, struct sk_buff_head *fw_queue); +void hci_h4p_parse_fw_event(struct hci_h4p_info *info, struct sk_buff *skb); + +int hci_h4p_sysfs_create_files(struct device *dev); + +void hci_h4p_outb(struct hci_h4p_info *info, unsigned int offset, u8 val); +u8 hci_h4p_inb(struct hci_h4p_info *info, unsigned int offset); +void hci_h4p_set_rts(struct hci_h4p_info *info, int active); +int hci_h4p_wait_for_cts(struct hci_h4p_info *info, int active, int timeout_ms); +void __hci_h4p_set_auto_ctsrts(struct hci_h4p_info *info, int on, u8 which); +void hci_h4p_set_auto_ctsrts(struct hci_h4p_info *info, int on, u8 which); +void hci_h4p_change_speed(struct hci_h4p_info *info, unsigned long speed); +int hci_h4p_reset_uart(struct hci_h4p_info *info); +int hci_h4p_init_uart(struct hci_h4p_info *info); + +#endif /* __DRIVERS_BLUETOOTH_HCI_H4P_H */ diff --git a/drivers/bluetooth/hci_h4p/sysfs.c b/drivers/bluetooth/hci_h4p/sysfs.c new file mode 100644 index 00000000000..687df69324b --- /dev/null +++ b/drivers/bluetooth/hci_h4p/sysfs.c @@ -0,0 +1,74 @@ +/* + * This file is part of hci_h4p bluetooth driver + * + * Copyright (C) 2005, 2006 Nokia Corporation. + * + * Contact: Ville Tervo + * + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include + +#include "hci_h4p.h" + +#ifdef CONFIG_SYSFS + +static ssize_t hci_h4p_store_bdaddr(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hci_h4p_info *info = (struct hci_h4p_info*)dev_get_drvdata(dev); + unsigned int bdaddr[6]; + int ret, i; + + ret = sscanf(buf, "%2x:%2x:%2x:%2x:%2x:%2x\n", + &bdaddr[0], &bdaddr[1], &bdaddr[2], + &bdaddr[3], &bdaddr[4], &bdaddr[5]); + + if (ret != 6) { + return -EINVAL; + } + + for (i = 0; i < 6; i++) + info->bdaddr[i] = bdaddr[i] & 0xff; + + return count; +} + +static ssize_t hci_h4p_show_bdaddr(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hci_h4p_info *info = (struct hci_h4p_info*)dev_get_drvdata(dev); + + return sprintf(buf, "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n", + info->bdaddr[0], + info->bdaddr[1], + info->bdaddr[2], + info->bdaddr[3], + info->bdaddr[4], + info->bdaddr[5]); +} + +static DEVICE_ATTR(bdaddr, S_IRUGO | S_IWUSR, hci_h4p_show_bdaddr, hci_h4p_store_bdaddr); +int hci_h4p_sysfs_create_files(struct device *dev) +{ + return device_create_file(dev, &dev_attr_bdaddr); +} + +#endif diff --git a/drivers/bluetooth/hci_h4p/uart.c b/drivers/bluetooth/hci_h4p/uart.c new file mode 100644 index 00000000000..1b75a4204dd --- /dev/null +++ b/drivers/bluetooth/hci_h4p/uart.c @@ -0,0 +1,164 @@ +/* + * This file is part of hci_h4p bluetooth driver + * + * Copyright (C) 2005, 2006 Nokia Corporation. + * + * Contact: Ville Tervo + * + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include + +#include + +#include "hci_h4p.h" + +inline void hci_h4p_outb(struct hci_h4p_info *info, unsigned int offset, u8 val) +{ + outb(val, info->uart_base + (offset << 2)); +} + +inline u8 hci_h4p_inb(struct hci_h4p_info *info, unsigned int offset) +{ + return inb(info->uart_base + (offset << 2)); +} + +void hci_h4p_set_rts(struct hci_h4p_info *info, int active) +{ + u8 b; + + b = hci_h4p_inb(info, UART_MCR); + if (active) + b |= UART_MCR_RTS; + else + b &= ~UART_MCR_RTS; + hci_h4p_outb(info, UART_MCR, b); +} + +int hci_h4p_wait_for_cts(struct hci_h4p_info *info, int active, + int timeout_ms) +{ + int okay; + unsigned long timeout; + + okay = 0; + timeout = jiffies + msecs_to_jiffies(timeout_ms); + for (;;) { + int state; + + state = hci_h4p_inb(info, UART_MSR) & UART_MSR_CTS; + if (active) { + if (state) + return 0; + } else { + if (!state) + return 0; + } + if (time_after(jiffies, timeout)) + return -ETIMEDOUT; + } +} + +void __hci_h4p_set_auto_ctsrts(struct hci_h4p_info *info, int on, u8 which) +{ + u8 lcr, b; + + lcr = hci_h4p_inb(info, UART_LCR); + hci_h4p_outb(info, UART_LCR, 0xbf); + b = hci_h4p_inb(info, UART_EFR); + if (on) + b |= which; + else + b &= ~which; + hci_h4p_outb(info, UART_EFR, b); + hci_h4p_outb(info, UART_LCR, lcr); +} + +void hci_h4p_set_auto_ctsrts(struct hci_h4p_info *info, int on, u8 which) +{ + unsigned long flags; + + spin_lock_irqsave(&info->lock, flags); + __hci_h4p_set_auto_ctsrts(info, on, which); + spin_unlock_irqrestore(&info->lock, flags); +} + +void hci_h4p_change_speed(struct hci_h4p_info *info, unsigned long speed) +{ + unsigned int divisor; + u8 lcr, mdr1; + + NBT_DBG("Setting speed %lu\n", speed); + + if (speed >= 460800) { + divisor = UART_CLOCK / 13 / speed; + mdr1 = 3; + } else { + divisor = UART_CLOCK / 16 / speed; + mdr1 = 0; + } + + hci_h4p_outb(info, UART_OMAP_MDR1, 7); /* Make sure UART mode is disabled */ + lcr = hci_h4p_inb(info, UART_LCR); + hci_h4p_outb(info, UART_LCR, UART_LCR_DLAB); /* Set DLAB */ + hci_h4p_outb(info, UART_DLL, divisor & 0xff); /* Set speed */ + hci_h4p_outb(info, UART_DLM, divisor >> 8); + hci_h4p_outb(info, UART_LCR, lcr); + hci_h4p_outb(info, UART_OMAP_MDR1, mdr1); /* Make sure UART mode is enabled */ +} + +int hci_h4p_reset_uart(struct hci_h4p_info *info) +{ + int count = 0; + + /* Reset the UART */ + hci_h4p_outb(info, UART_OMAP_SYSC, UART_SYSC_OMAP_RESET); + while (!(hci_h4p_inb(info, UART_OMAP_SYSS) & UART_SYSS_RESETDONE)) { + if (count++ > 100) { + dev_err(info->dev, "hci_h4p: UART reset timeout\n"); + return -ENODEV; + } + udelay(1); + } + + return 0; +} + +int hci_h4p_init_uart(struct hci_h4p_info *info) +{ + int err; + + err = hci_h4p_reset_uart(info); + if (err < 0) + return err; + + /* Enable and setup FIFO */ + hci_h4p_outb(info, UART_LCR, UART_LCR_WLEN8); + hci_h4p_outb(info, UART_OMAP_MDR1, 0x00); /* Make sure UART mode is enabled */ + hci_h4p_outb(info, UART_OMAP_SCR, 0x80); + hci_h4p_outb(info, UART_EFR, UART_EFR_ECB); + hci_h4p_outb(info, UART_MCR, UART_MCR_TCRTLR); + hci_h4p_outb(info, UART_TI752_TLR, 0x1f); + hci_h4p_outb(info, UART_TI752_TCR, 0xef); + hci_h4p_outb(info, UART_FCR, UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR | + UART_FCR_CLEAR_XMIT | UART_FCR_R_TRIG_00); + hci_h4p_outb(info, UART_IER, UART_IER_RDI); + + return 0; +} diff --git a/drivers/char/hw_random/omap-rng.c b/drivers/char/hw_random/omap-rng.c index 538313f9e7a..de201faa9df 100644 --- a/drivers/char/hw_random/omap-rng.c +++ b/drivers/char/hw_random/omap-rng.c @@ -205,7 +205,6 @@ static struct platform_driver omap_rng_driver = { .name = "omap_rng", .owner = THIS_MODULE, }, - .probe = omap_rng_probe, .remove = __exit_p(omap_rng_remove), .suspend = omap_rng_suspend, .resume = omap_rng_resume @@ -216,7 +215,7 @@ static int __init omap_rng_init(void) if (!cpu_is_omap16xx() && !cpu_is_omap24xx()) return -ENODEV; - return platform_driver_register(&omap_rng_driver); + return platform_driver_probe(&omap_rng_driver, omap_rng_probe); } static void __exit omap_rng_exit(void) diff --git a/drivers/crypto/Kconfig b/drivers/crypto/Kconfig index 01afd758072..f43cdd66425 100644 --- a/drivers/crypto/Kconfig +++ b/drivers/crypto/Kconfig @@ -83,6 +83,13 @@ config ZCRYPT_MONOLITHIC that contains all parts of the crypto device driver (ap bus, request router and all the card drivers). +config OMAP_SHA1_MD5 + tristate "Support for OMAP SHA1/MD5 hw engine" + depends on ARCH_OMAP24XX && CRYPTO_SHA1 && CRYPTO_MD5 + help + OMAP processors have SHA1/MD5 module accelerator. Select this if you + want to use the OMAP module for SHA1/MD5 algorithms. + config CRYPTO_SHA1_S390 tristate "SHA1 digest algorithm" depends on S390 diff --git a/drivers/crypto/Makefile b/drivers/crypto/Makefile index 9bf4a2bc884..e6095f0f177 100644 --- a/drivers/crypto/Makefile +++ b/drivers/crypto/Makefile @@ -1,6 +1,7 @@ obj-$(CONFIG_CRYPTO_DEV_PADLOCK_AES) += padlock-aes.o obj-$(CONFIG_CRYPTO_DEV_PADLOCK_SHA) += padlock-sha.o obj-$(CONFIG_CRYPTO_DEV_GEODE) += geode-aes.o +obj-$(CONFIG_OMAP_SHA1_MD5) += omap-sha1-md5.o obj-$(CONFIG_CRYPTO_DEV_HIFN_795X) += hifn_795x.o obj-$(CONFIG_CRYPTO_DEV_TALITOS) += talitos.o obj-$(CONFIG_CRYPTO_DEV_IXP4XX) += ixp4xx_crypto.o diff --git a/drivers/crypto/omap-sha1-md5.c b/drivers/crypto/omap-sha1-md5.c new file mode 100644 index 00000000000..6d7770325f7 --- /dev/null +++ b/drivers/crypto/omap-sha1-md5.c @@ -0,0 +1,575 @@ +/* + * Cryptographic API. + * + * Support for OMAP SHA1/MD5 HW acceleration. + * + * Copyright (c) 2007 Instituto Nokia de Tecnologia - INdT + * Author: David Cohen + * + * 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. + * + * This driver is based on padlock-sha.c driver. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SHA_REG_DIGEST(x) (0x00 + ((x) * 0x04)) +#define SHA_REG_DIN(x) (0x1C + ((x) * 0x04)) + +#define SHA1_MD5_BLOCK_SIZE SHA1_BLOCK_SIZE +#define MD5_DIGEST_SIZE 16 + +#define SHA_REG_DIGCNT 0x14 + +#define SHA_REG_CTRL 0x18 +#define SHA_REG_CTRL_LENGTH (0xFFFFFFFF << 5) +#define SHA_REG_CTRL_CLOSE_HASH (1 << 4) +#define SHA_REG_CTRL_ALGO_CONST (1 << 3) +#define SHA_REG_CTRL_ALGO (1 << 2) +#define SHA_REG_CTRL_INPUT_READY (1 << 1) +#define SHA_REG_CTRL_OUTPUT_READY (1 << 0) + +#define SHA_REG_REV 0x5C +#define SHA_REG_REV_MAJOR 0xF0 +#define SHA_REG_REV_MINOR 0x0F + +#define SHA_REG_MASK 0x60 +#define SHA_REG_MASK_DMA_EN (1 << 3) +#define SHA_REG_MASK_IT_EN (1 << 2) +#define SHA_REG_MASK_SOFTRESET (1 << 1) +#define SHA_REG_AUTOIDLE (1 << 0) + +#define SHA_REG_SYSSTATUS 0x64 +#define SHA_REG_SYSSTATUS_RESETDONE (1 << 0) + +#define DRIVER_NAME "OMAP SHA1/MD5" + +struct omap_sha1_md5_ctx { + unsigned int type_algo; + unsigned int bufcnt; + unsigned int digcnt; + int algo_const; + int bypass; + int digsize; + u8 hash[SHA1_DIGEST_SIZE]; + u8 buffer[SHA1_BLOCK_SIZE]; + struct hash_desc fallback; +}; + +struct omap_sha1_md5_dev { + unsigned long base_address; + int irq; + int digready; + struct clk *sha1_ick; + struct omap_sha1_md5_ctx + *hw_ctx; + struct device *dev; + wait_queue_head_t wq; +}; + +static struct omap_sha1_md5_dev *sha1_md5_data; + +#define SHA_REG_IOADDR(d, x) (void *)IO_ADDRESS((d)->base_address + (x)) + +static u32 omap_sha1_md5_read(struct omap_sha1_md5_dev *data, u32 offset) +{ + return __raw_readl(SHA_REG_IOADDR(data, offset)); +} + +static void omap_sha1_md5_write(struct omap_sha1_md5_dev *data, + u32 value, u32 offset) +{ + __raw_writel(value, SHA_REG_IOADDR(data, offset)); +} + +static void omap_sha1_md5_write_mask(struct omap_sha1_md5_dev *data, + u32 value, u32 mask, u32 address) +{ + u32 val; + + val = omap_sha1_md5_read(data, address); + val &= ~mask; + val |= value; + omap_sha1_md5_write(data, val, address); +} + +static inline void omap_sha1_md5_enable_clk(struct crypto_tfm *tfm) +{ + struct omap_sha1_md5_dev *data = sha1_md5_data; + + clk_enable(data->sha1_ick); +} + +static inline void omap_sha1_md5_disable_clk(struct crypto_tfm *tfm) +{ + struct omap_sha1_md5_dev *data = sha1_md5_data; + + clk_disable(data->sha1_ick); +} + +static void omap_sha1_md5_copy_hash(struct crypto_tfm *tfm) +{ + struct omap_sha1_md5_ctx *ctx = crypto_tfm_ctx(tfm); + struct omap_sha1_md5_dev *data = sha1_md5_data; + + u32 *hash = (u32 *)ctx->hash; + + if (ctx->type_algo) { + /* SHA1 results are in big endian */ + hash[0] = be32_to_cpu( + omap_sha1_md5_read(data, SHA_REG_DIGEST(0))); + hash[1] = be32_to_cpu( + omap_sha1_md5_read(data, SHA_REG_DIGEST(1))); + hash[2] = be32_to_cpu( + omap_sha1_md5_read(data, SHA_REG_DIGEST(2))); + hash[3] = be32_to_cpu( + omap_sha1_md5_read(data, SHA_REG_DIGEST(3))); + hash[4] = be32_to_cpu( + omap_sha1_md5_read(data, SHA_REG_DIGEST(4))); + } else { + /* MD5 results are in little endian */ + hash[0] = le32_to_cpu( + omap_sha1_md5_read(data, SHA_REG_DIGEST(0))); + hash[1] = le32_to_cpu( + omap_sha1_md5_read(data, SHA_REG_DIGEST(1))); + hash[2] = le32_to_cpu( + omap_sha1_md5_read(data, SHA_REG_DIGEST(2))); + hash[3] = le32_to_cpu( + omap_sha1_md5_read(data, SHA_REG_DIGEST(3))); + } +} + +static void omap_sha1_md5_bypass(struct crypto_tfm *tfm, + u8 *data, unsigned int length) +{ + struct omap_sha1_md5_ctx *ctx = crypto_tfm_ctx(tfm); + + if (unlikely(!ctx->bypass)) + return; + + if (ctx->bypass == 1) { + crypto_hash_init(&ctx->fallback); + ctx->bypass++; + } + + if (length) { + struct scatterlist sg; + + sg_set_buf(&sg, data, length); + crypto_hash_update(&ctx->fallback, &sg, sg.length); + } +} + +static void omap_sha1_md5_digest_buffer(struct crypto_tfm *tfm, + u8 *buf, unsigned int len, int close_hash) +{ + struct omap_sha1_md5_ctx *ctx = crypto_tfm_ctx(tfm); + struct omap_sha1_md5_dev *data = sha1_md5_data; + unsigned int algo_const = 0; + int c; + u32 *buffer = (u32 *)buf; + + if (unlikely(ctx->bypass)) { + omap_sha1_md5_bypass(tfm, buf, len); + return; + } + + if (unlikely(ctx->algo_const)) { + algo_const = SHA_REG_CTRL_ALGO_CONST; + ctx->algo_const = 0; + } else + omap_sha1_md5_write(data, ctx->digcnt, SHA_REG_DIGCNT); + + if (unlikely(close_hash)) + close_hash = SHA_REG_CTRL_CLOSE_HASH; + + /* Setting ALGO_CONST only for the first iteration + * and CLOSE_HASH only for the last one. */ + omap_sha1_md5_write_mask(data, + ctx->type_algo | algo_const | close_hash | (len << 5), + SHA_REG_CTRL_ALGO_CONST | SHA_REG_CTRL_CLOSE_HASH | + SHA_REG_CTRL_ALGO | SHA_REG_CTRL_LENGTH, + SHA_REG_CTRL); + + ctx->digcnt += len; + while (!(omap_sha1_md5_read(data, SHA_REG_CTRL) + & SHA_REG_CTRL_INPUT_READY)); + + if (len % 4) + len = (len/4) + 1; + else + len /= 4; + for (c = 0; c < len; c++) + omap_sha1_md5_write(data, buffer[c], SHA_REG_DIN(c)); +} + +static void omap_sha1_md5_append_buffer(struct crypto_tfm *tfm, + const uint8_t *data, unsigned int length) +{ + struct omap_sha1_md5_ctx *ctx = crypto_tfm_ctx(tfm); + + BUG_ON((ctx->bufcnt + length) > SHA1_MD5_BLOCK_SIZE); + + memcpy(&ctx->buffer[ctx->bufcnt], data, length); + ctx->bufcnt += length; +} + +static void omap_sha1_md5_dia_update(struct crypto_tfm *tfm, + const uint8_t *data, unsigned int length) +{ + struct omap_sha1_md5_ctx *ctx = crypto_tfm_ctx(tfm); + + /* We need to save the last buffer <= 64 to digest it with + * CLOSE_HASH = 1 */ + if (ctx->bufcnt && ((ctx->bufcnt + length) > SHA1_MD5_BLOCK_SIZE)) { + unsigned int c = SHA1_MD5_BLOCK_SIZE - ctx->bufcnt; + + omap_sha1_md5_append_buffer(tfm, data, c); + data += c; + length -= c; + if (length) { + ctx->bufcnt = 0; + omap_sha1_md5_digest_buffer(tfm, ctx->buffer, + SHA1_MD5_BLOCK_SIZE, 0); + } + } + + while (length > SHA1_MD5_BLOCK_SIZE) { + /* Revisit: use DMA here */ + omap_sha1_md5_digest_buffer(tfm, (u8 *)data, + SHA1_MD5_BLOCK_SIZE, 0); + length -= SHA1_MD5_BLOCK_SIZE; + data += SHA1_MD5_BLOCK_SIZE; + } + + if (length) + omap_sha1_md5_append_buffer(tfm, data, length); +} + +static void omap_sha1_md5_start_reset(struct crypto_tfm *tfm) +{ + struct omap_sha1_md5_dev *data = sha1_md5_data; + + omap_sha1_md5_write_mask(data, SHA_REG_MASK_SOFTRESET, + SHA_REG_MASK_SOFTRESET, SHA_REG_MASK); +} + +static void omap_sha1_md5_wait_reset(struct crypto_tfm *tfm) +{ + struct omap_sha1_md5_dev *data = sha1_md5_data; + + while (!(omap_sha1_md5_read(data, SHA_REG_SYSSTATUS) + & SHA_REG_SYSSTATUS_RESETDONE)); +} + +static void omap_sha1_md5_dia_init(struct crypto_tfm *tfm) +{ + struct omap_sha1_md5_dev *data = sha1_md5_data; + struct omap_sha1_md5_ctx *ctx = crypto_tfm_ctx(tfm); + + if (unlikely(data->hw_ctx)) + ctx->bypass = 1; + else { + data->hw_ctx = ctx; + ctx->bypass = 0; + omap_sha1_md5_enable_clk(tfm); + omap_sha1_md5_start_reset(tfm); + data->digready = 0; + } + + if (ctx->bypass) { + omap_sha1_md5_bypass(tfm, NULL, 0); + return; + } + + ctx->algo_const = 1; + ctx->bufcnt = 0; + ctx->digcnt = 0; + + omap_sha1_md5_wait_reset(tfm); + omap_sha1_md5_write_mask(data, SHA_REG_MASK_IT_EN, + SHA_REG_MASK_DMA_EN | SHA_REG_MASK_IT_EN, SHA_REG_MASK); +} + +static void omap_sha1_md5_dia_final(struct crypto_tfm *tfm, uint8_t *out) +{ + struct omap_sha1_md5_ctx *ctx = crypto_tfm_ctx(tfm); + struct omap_sha1_md5_dev *data = sha1_md5_data; + int digsize = ctx->digsize; + + /* The buffer should be >= 9 */ + if (((ctx->digcnt + ctx->bufcnt) < 9) && !ctx->bypass) + ctx->bypass = 1; + + omap_sha1_md5_digest_buffer(tfm, ctx->buffer, ctx->bufcnt, 1); + + if (unlikely(ctx->bypass)) { + crypto_hash_final(&ctx->fallback, out); + ctx->bypass = 0; + goto bypass; + } else + data->digready = 1; + + wait_event_interruptible(data->wq, (data->digready == 2)); + omap_sha1_md5_copy_hash(tfm); + + memcpy(out, ctx->hash, digsize); + +bypass: + if (data->hw_ctx == ctx) { + omap_sha1_md5_disable_clk(tfm); + data->hw_ctx = NULL; + } +} + +static irqreturn_t omap_sha1_md5_irq(int irq, void *dev_id) +{ + struct omap_sha1_md5_dev *data = dev_id; + + omap_sha1_md5_write_mask(data, SHA_REG_CTRL_OUTPUT_READY, + SHA_REG_CTRL_OUTPUT_READY, SHA_REG_CTRL); + + if (likely(!data->digready)) + return IRQ_HANDLED; + + if (data->hw_ctx == NULL) { + dev_err(data->dev, "unknown interrupt.\n"); + return IRQ_HANDLED; + } + + data->digready = 2; + wake_up_interruptible(&data->wq); + + return IRQ_HANDLED; +} + +static int omap_sha1_md5_cra_init(struct crypto_tfm *tfm) +{ + struct omap_sha1_md5_ctx *ctx = crypto_tfm_ctx(tfm); + struct omap_sha1_md5_dev *data = sha1_md5_data; + const char *fallback_driver_name = tfm->__crt_alg->cra_name; + struct crypto_hash *fallback_tfm; + + /* Allocate a fallback and abort if it failed. */ + fallback_tfm = crypto_alloc_hash(fallback_driver_name, 0, + CRYPTO_ALG_ASYNC | + CRYPTO_ALG_NEED_FALLBACK); + if (IS_ERR(fallback_tfm)) { + dev_err(data->dev, "fallback driver '%s' could not be" + "loaded.\n", fallback_driver_name); + return PTR_ERR(fallback_tfm); + } + + ctx->fallback.tfm = fallback_tfm; + + return 0; +} + +static int omap_sha1_cra_init(struct crypto_tfm *tfm) +{ + struct omap_sha1_md5_ctx *ctx = crypto_tfm_ctx(tfm); + + ctx->type_algo = SHA_REG_CTRL_ALGO; + ctx->digsize = SHA1_DIGEST_SIZE; + + return omap_sha1_md5_cra_init(tfm); +} + +static int omap_md5_cra_init(struct crypto_tfm *tfm) +{ + struct omap_sha1_md5_ctx *ctx = crypto_tfm_ctx(tfm); + + ctx->type_algo = 0; + ctx->digsize = MD5_DIGEST_SIZE; + + return omap_sha1_md5_cra_init(tfm); +} + +static void omap_sha1_md5_cra_exit(struct crypto_tfm *tfm) +{ + struct omap_sha1_md5_ctx *ctx = crypto_tfm_ctx(tfm); + + crypto_free_hash(ctx->fallback.tfm); + ctx->fallback.tfm = NULL; +} + +static struct crypto_alg omap_sha1_alg = { + .cra_name = "sha1", + .cra_driver_name = "omap-sha1", + .cra_flags = CRYPTO_ALG_TYPE_DIGEST | + CRYPTO_ALG_NEED_FALLBACK, + .cra_blocksize = SHA1_MD5_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct omap_sha1_md5_ctx), + .cra_module = THIS_MODULE, + .cra_list = LIST_HEAD_INIT(omap_sha1_alg.cra_list), + .cra_init = omap_sha1_cra_init, + .cra_exit = omap_sha1_md5_cra_exit, + .cra_u = { + .digest = { + .dia_digestsize = SHA1_DIGEST_SIZE, + .dia_init = omap_sha1_md5_dia_init, + .dia_update = omap_sha1_md5_dia_update, + .dia_final = omap_sha1_md5_dia_final, + } + } +}; + +static struct crypto_alg omap_md5_alg = { + .cra_name = "md5", + .cra_driver_name = "omap-md5", + .cra_flags = CRYPTO_ALG_TYPE_DIGEST | + CRYPTO_ALG_NEED_FALLBACK, + .cra_blocksize = SHA1_MD5_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct omap_sha1_md5_ctx), + .cra_module = THIS_MODULE, + .cra_list = LIST_HEAD_INIT(omap_md5_alg.cra_list), + .cra_init = omap_md5_cra_init, + .cra_exit = omap_sha1_md5_cra_exit, + .cra_u = { + .digest = { + .dia_digestsize = MD5_DIGEST_SIZE, + .dia_init = omap_sha1_md5_dia_init, + .dia_update = omap_sha1_md5_dia_update, + .dia_final = omap_sha1_md5_dia_final, + } + } +}; + +static int omap_sha1_md5_probe(struct platform_device *pdev) +{ + struct omap_sha1_md5_dev *data; + struct device *dev = &pdev->dev; + struct resource *res; + int rc; + + rc = crypto_register_alg(&omap_sha1_alg); + if (rc) + goto sha1_err; + rc = crypto_register_alg(&omap_md5_alg); + if (rc) + goto md5_err; + + data = kzalloc(sizeof(struct omap_sha1_md5_dev), GFP_KERNEL); + if (data == NULL) { + dev_err(dev, "unable to alloc data struct.\n"); + goto data_err; + } + platform_set_drvdata(pdev, data); + data->dev = dev; + + /* Get the base address */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "invalid resource type\n"); + rc = -ENODEV; + goto res_err; + } + data->base_address = res->start; + + /* Set the private data */ + sha1_md5_data = data; + + /* Get the IRQ */ + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(dev, "invalid resource type\n"); + rc = -ENODEV; + goto res_err; + } + data->irq = res->start; + + rc = request_irq(res->start, omap_sha1_md5_irq, + IRQF_TRIGGER_LOW, DRIVER_NAME, data); + if (rc) { + dev_err(dev, "unable to request irq.\n"); + goto res_err; + } + + /* Initializing the clock */ + data->sha1_ick = clk_get(0, "sha_ick"); + if (!data->sha1_ick) { + dev_err(dev, "clock intialization failed.\n"); + rc = -ENODEV; + goto clk_err; + } + + init_waitqueue_head(&data->wq); + + dev_info(dev, "hw accel on OMAP rev %u.%u\n", + (omap_sha1_md5_read(data, SHA_REG_REV) & SHA_REG_REV_MAJOR)>>4, + omap_sha1_md5_read(data, SHA_REG_REV) & SHA_REG_REV_MINOR); + + return 0; + +clk_err: + free_irq(data->irq, data); +res_err: + kfree(data); +data_err: + crypto_unregister_alg(&omap_md5_alg); +md5_err: + crypto_unregister_alg(&omap_sha1_alg); +sha1_err: + dev_err(dev, "initialization failed.\n"); + return rc; +} + +static int omap_sha1_md5_remove(struct platform_device *pdev) +{ + struct omap_sha1_md5_dev *data = platform_get_drvdata(pdev); + + free_irq(data->irq, data); + kfree(data); + crypto_unregister_alg(&omap_sha1_alg); + crypto_unregister_alg(&omap_md5_alg); + + return 0; +} + +static struct platform_driver omap_sha1_md5_driver = { + .probe = omap_sha1_md5_probe, + .remove = omap_sha1_md5_remove, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init omap_sha1_md5_init(void) +{ + int ret; + + ret = platform_driver_register(&omap_sha1_md5_driver); + if (ret) + return ret; + + return 0; +} + +static void __exit omap_sha1_md5_exit(void) +{ + platform_driver_unregister(&omap_sha1_md5_driver); +} + +module_init(omap_sha1_md5_init); +module_exit(omap_sha1_md5_exit); + +MODULE_DESCRIPTION("OMAP SHA1/MD5 hw acceleration support."); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Cohen"); diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 0e8a9185f67..1d3374ae7eb 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -995,6 +995,22 @@ config SENSORS_APPLESMC Say Y here if you have an applicable laptop and want to experience the awesome power of applesmc. +config SENSORS_TSC210X + tristate "TI TSC210x battery & temperature sensors" + depends on HWMON && SPI_MASTER + select SPI_TSC210X + help + Say Y if your board has a TSC210x chip and you want to + have its battery state, auxiliary input and/or temperature + sensors exported through hwmon. + + This driver can also be built as a module. In this case + the module will be called tsc210x_sensors. + +config SENSORS_OMAP34XX + tristate "TI OMAP34xx internal temperature sensor" + depends on ARCH_OMAP3 && HIGH_RES_TIMERS + config HWMON_DEBUG_CHIP bool "Hardware Monitoring Chip debugging messages" default n diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 1d3757837b4..023f5719abd 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -86,7 +86,9 @@ obj-$(CONFIG_SENSORS_VT1211) += vt1211.o obj-$(CONFIG_SENSORS_VT8231) += vt8231.o obj-$(CONFIG_SENSORS_W83627EHF) += w83627ehf.o obj-$(CONFIG_SENSORS_W83L785TS) += w83l785ts.o +obj-$(CONFIG_SENSORS_TSC210X) += tsc210x_sensors.o obj-$(CONFIG_SENSORS_W83L786NG) += w83l786ng.o +obj-$(CONFIG_SENSORS_OMAP34XX) += omap34xx_temp.o ifeq ($(CONFIG_HWMON_DEBUG_CHIP),y) EXTRA_CFLAGS += -DDEBUG diff --git a/drivers/hwmon/omap34xx_temp.c b/drivers/hwmon/omap34xx_temp.c new file mode 100644 index 00000000000..18e07643165 --- /dev/null +++ b/drivers/hwmon/omap34xx_temp.c @@ -0,0 +1,268 @@ +/* + * omap34xx_temp.c - Linux kernel module for OMAP34xx hardware monitoring + * + * Copyright (C) 2008 Nokia Corporation + * + * Written by Peter De Schrijver + * + * Inspired by k8temp.c + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TEMP_SENSOR_SOC BIT(8) +#define TEMP_SENSOR_EOCZ BIT(7) + +/* minimum delay for EOCZ rise after SOC rise is + * 11 cycles of the 32.768Khz clock */ +#define EOCZ_MIN_RISING_DELAY (11 * 30518) + +/* maximum delay for EOCZ rise after SOC rise is + * 14 cycles of the 32.768Khz clock */ +#define EOCZ_MAX_RISING_DELAY (14 * 30518) + +/* minimum delay for EOCZ falling is + * 36 cycles of the 32.768Khz clock */ +#define EOCZ_MIN_FALLING_DELAY (36 * 30518) + +/* maximum delay for EOCZ falling is + * 40 cycles of the 32.768Khz clock */ +#define EOCZ_MAX_FALLING_DELAY (40 * 30518) + +struct omap34xx_data { + struct device *hwmon_dev; + struct clk *clk_32k; + struct mutex update_lock; + const char *name; + char valid; + unsigned long last_updated; + u32 temp; +}; + +static struct platform_device omap34xx_temp_device = { + .name = "omap34xx_temp", + .id = -1, +}; + +static int adc_to_temp[] = { + -40, -40, -40, -40, -40, -39, -38, -36, -34, -32, -31, -29, -28, -26, + -25, -24, -22, -21, -19, -18, -17, -15, -14, -12, -11, -9, -8, -7, -5, + -4, -2, -1, 0, 1, 3, 4, 5, 7, 8, 10, 11, 13, 14, 15, 17, 18, 20, 21, + 22, 24, 25, 27, 28, 30, 31, 32, 34, 35, 37, 38, 39, 41, 42, 44, 45, + 47, 48, 49, 51, 52, 53, 55, 56, 58, 59, 60, 62, 63, 65, 66, 67, 69, + 70, 72, 73, 74, 76, 77, 79, 80, 81, 83, 84, 85, 87, 88, 89, 91, 92, + 94, 95, 96, 98, 99, 100, 102, 103, 105, 106, 107, 109, 110, 111, 113, + 114, 116, 117, 118, 120, 121, 122, 124, 124, 125, 125, 125, 125, 125}; + +static inline u32 wait_for_eocz(int min_delay, int max_delay, u32 level) +{ + struct timespec timeout; + ktime_t expire; + u32 temp_sensor_reg; + + level &= 1; + level *= TEMP_SENSOR_EOCZ; + + expire = ktime_add_ns(ktime_get(), max_delay); + timeout = ns_to_timespec(min_delay); + hrtimer_nanosleep(&timeout, NULL, HRTIMER_MODE_REL, CLOCK_MONOTONIC); + do { + temp_sensor_reg = omap_ctrl_readl(OMAP343X_CONTROL_TEMP_SENSOR); + if ((temp_sensor_reg & TEMP_SENSOR_EOCZ) == level) + break; + } while (ktime_us_delta(expire, ktime_get()) > 0); + + return (temp_sensor_reg & TEMP_SENSOR_EOCZ) == level; +} + +static void omap34xx_update(struct omap34xx_data *data) +{ + u32 temp_sensor_reg; + + mutex_lock(&data->update_lock); + + if (!data->valid + || time_after(jiffies, data->last_updated + HZ)) { + + clk_enable(data->clk_32k); + + temp_sensor_reg = omap_ctrl_readl(OMAP343X_CONTROL_TEMP_SENSOR); + temp_sensor_reg |= TEMP_SENSOR_SOC; + omap_ctrl_writel(temp_sensor_reg, OMAP343X_CONTROL_TEMP_SENSOR); + + if (!wait_for_eocz(EOCZ_MIN_RISING_DELAY, + EOCZ_MAX_RISING_DELAY, 1)) + goto err; + + temp_sensor_reg = omap_ctrl_readl(OMAP343X_CONTROL_TEMP_SENSOR); + temp_sensor_reg &= ~TEMP_SENSOR_SOC; + omap_ctrl_writel(temp_sensor_reg, OMAP343X_CONTROL_TEMP_SENSOR); + + if (!wait_for_eocz(EOCZ_MIN_FALLING_DELAY, + EOCZ_MAX_FALLING_DELAY, 0)) + goto err; + + data->temp = omap_ctrl_readl(OMAP343X_CONTROL_TEMP_SENSOR) & + ((1<<7) - 1); + data->last_updated = jiffies; + data->valid = 1; + +err: + clk_disable(data->clk_32k); + } + + mutex_unlock(&data->update_lock); +} + +static ssize_t show_name(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct omap34xx_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", data->name); +} + +static ssize_t show_temp_raw(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct omap34xx_data *data = dev_get_drvdata(dev); + + omap34xx_update(data); + + return sprintf(buf, "%d\n", data->temp); +} + +static ssize_t show_temp(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct omap34xx_data *data = dev_get_drvdata(dev); + + omap34xx_update(data); + + return sprintf(buf, "%d\n", adc_to_temp[data->temp]); +} + +static SENSOR_DEVICE_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, 0); +static SENSOR_DEVICE_ATTR_2(temp1_input_raw, S_IRUGO, show_temp_raw, + NULL, 0, 0); +static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); + +static int __devinit omap34xx_temp_probe(void) +{ + int err; + struct omap34xx_data *data; + + err = platform_device_register(&omap34xx_temp_device); + if (err) { + printk(KERN_ERR + "Unable to register omap34xx temperature device\n"); + goto exit; + } + + data = kzalloc(sizeof(struct omap34xx_data), GFP_KERNEL); + if (!data) { + err = -ENOMEM; + goto exit_platform; + } + + dev_set_drvdata(&omap34xx_temp_device.dev, data); + mutex_init(&data->update_lock); + data->name = "omap34xx_temp"; + + data->clk_32k = clk_get(&omap34xx_temp_device.dev, "ts_fck"); + if (IS_ERR(data->clk_32k)) { + err = PTR_ERR(data->clk_32k); + goto exit_free; + } + + err = device_create_file(&omap34xx_temp_device.dev, + &sensor_dev_attr_temp1_input.dev_attr); + if (err) + goto clock_free; + + err = device_create_file(&omap34xx_temp_device.dev, + &sensor_dev_attr_temp1_input_raw.dev_attr); + if (err) + goto exit_remove; + + err = device_create_file(&omap34xx_temp_device.dev, &dev_attr_name); + if (err) + goto exit_remove_raw; + + data->hwmon_dev = hwmon_device_register(&omap34xx_temp_device.dev); + + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto exit_remove_all; + } + + return 0; + +exit_remove_all: + device_remove_file(&omap34xx_temp_device.dev, + &dev_attr_name); +exit_remove_raw: + device_remove_file(&omap34xx_temp_device.dev, + &sensor_dev_attr_temp1_input_raw.dev_attr); +exit_remove: + device_remove_file(&omap34xx_temp_device.dev, + &sensor_dev_attr_temp1_input.dev_attr); +clock_free: + clk_put(data->clk_32k); + +exit_free: + kfree(data); +exit_platform: + platform_device_unregister(&omap34xx_temp_device); +exit: + return err; +} + +static int __init omap34xx_temp_init(void) +{ + return omap34xx_temp_probe(); +} + +static void __exit omap34xx_temp_exit(void) +{ + struct omap34xx_data *data = + dev_get_drvdata(&omap34xx_temp_device.dev); + + clk_put(data->clk_32k); + hwmon_device_unregister(data->hwmon_dev); + device_remove_file(&omap34xx_temp_device.dev, + &sensor_dev_attr_temp1_input.dev_attr); + device_remove_file(&omap34xx_temp_device.dev, &dev_attr_name); + kfree(data); + platform_device_unregister(&omap34xx_temp_device); +} + +MODULE_AUTHOR("Peter De Schrijver"); +MODULE_DESCRIPTION("Omap34xx temperature sensor"); +MODULE_LICENSE("GPL"); + +module_init(omap34xx_temp_init) +module_exit(omap34xx_temp_exit) + diff --git a/drivers/hwmon/tsc210x_sensors.c b/drivers/hwmon/tsc210x_sensors.c new file mode 100644 index 00000000000..daac126209c --- /dev/null +++ b/drivers/hwmon/tsc210x_sensors.c @@ -0,0 +1,296 @@ +/* + * tsc210x_sensors.c - hwmon interface to TI TSC210x sensors + * + * Copyright (c) 2005-2007 Andrzej Zaborowski + * + * This package 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 package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_APM +# include +#endif + +#include + + +/* + * TI TSC210x chips include an ADC that's shared between various + * sensors (temperature, battery, vAUX, etc) and the touchscreen. + * This driver packages access to the non-touchscreen sensors + * available on a given board. + */ + +struct tsc210x_hwmon { + int bat[2], aux[2], temp[2]; + + struct device *dev; + struct tsc210x_config *pdata; +#ifdef CONFIG_APM + /* prevent APM from colliding with normal hwmon accessors */ + spinlock_t apm_lock; +#endif +}; + +#ifdef CONFIG_APM +# define apm_lock(h) spin_lock(&(h)->apm_lock) +# define apm_unlock(h) spin_unlock(&(h)->apm_lock) +#else +# define apm_lock(h) do { } while (0) +# define apm_unlock(h) do { } while (0) +#endif + +static void tsc210x_ports(void *context, int bat[], int aux[]) +{ + struct tsc210x_hwmon *hwmon = context; + + apm_lock(hwmon); + + /* FIXME for tsc2101 and tsc2111, battery voltage is: + * VBAT = (5 * VREF * (bat[x])) / (2 ^ bits) + * For tsc2100 and tsc2102, use "6" not "5"; that formula ignores + * an external 100-300 Ohm resistor making the right value be just + * a bit over 5 (or 6). + * + * FIXME the vAUX measurements need scaling too, but in that case + * there's no *internal* voltage divider so just scale to VREF. + * + * --> This code needs to know VREF, the VBAT multiplier, and + * the precision. For now, assume VREF 1.25V and 12 bits. + * When an external reference is used, it normally won't + * match the 1.25V (or 2.5V) values supported internally... + * + * --> Output units should become milliVolts; currently they are + * dimensionless... + */ + hwmon->bat[0] = bat[0]; + hwmon->bat[1] = bat[1]; + + hwmon->aux[0] = aux[0]; + hwmon->aux[1] = aux[1]; + + apm_unlock(hwmon); +} + +/* FIXME temp sensors also need scaling so values are milliVolts... + * temperature (given calibration data) should be millidegrees C. + */ + +static void tsc210x_temp1(void *context, int temp) +{ + struct tsc210x_hwmon *hwmon = context; + + apm_lock(hwmon); + hwmon->temp[0] = temp; + apm_unlock(hwmon); +} + +static void tsc210x_temp2(void *context, int temp) +{ + struct tsc210x_hwmon *hwmon = context; + + apm_lock(hwmon); + hwmon->temp[1] = temp; + apm_unlock(hwmon); +} + +#define TSC210X_INPUT(devname, field) \ +static ssize_t tsc_show_ ## devname(struct device *dev, \ + struct device_attribute *devattr, char *buf) \ +{ \ + struct tsc210x_hwmon *hwmon = dev_get_drvdata(dev); \ + return sprintf(buf, "%i\n", hwmon->field); \ +} \ +static DEVICE_ATTR(devname ## _input, S_IRUGO, tsc_show_ ## devname, NULL); + +TSC210X_INPUT(in0, bat[0]) +TSC210X_INPUT(in1, bat[1]) +TSC210X_INPUT(in2, aux[0]) +TSC210X_INPUT(in3, aux[1]) +TSC210X_INPUT(in4, temp[0]) +TSC210X_INPUT(in5, temp[1]) + +static ssize_t tsc_show_temp1(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct tsc210x_hwmon *hwmon = dev_get_drvdata(dev); + int t1 = hwmon->temp[0]; + int t2 = hwmon->temp[1]; + int diff; + int value; + + /* + * Use method #2 (differential) to calculate current temperature. + * The difference between TEMP2 and TEMP1 input values is + * multiplied by a constant to obtain current temperature. + * To find this constant we use the values measured at 25 C as + * thermometer calibration data. + * + * 298150 is 25 degrees Celcius represented in Kelvins and + * multiplied by 1000 for fixed point precision (273.15 + 25). + * 273150 is zero degrees Celcius. + */ + diff = hwmon->pdata->temp_at25c[1] - hwmon->pdata->temp_at25c[0]; + value = (t2 - t1) * 298150 / diff; /* This is in Kelvins now */ + + value -= 273150; /* Celcius millidegree */ + return sprintf(buf, "%i\n", value); +} +static DEVICE_ATTR(temp1_input, S_IRUGO, tsc_show_temp1, NULL); + +#ifdef CONFIG_APM +static struct tsc210x_hwmon *apm_hwmon; + +static void tsc210x_get_power_status(struct apm_power_info *info) +{ + struct tsc210x_hwmon *hwmon = apm_hwmon; + + apm_lock(hwmon); + hwmon->pdata->apm_report(info, hwmon->bat); + apm_unlock(hwmon); +} +#endif + +static int tsc210x_hwmon_probe(struct platform_device *pdev) +{ + struct tsc210x_hwmon *hwmon; + struct tsc210x_config *pdata = pdev->dev.platform_data; + int status = 0; + + hwmon = (struct tsc210x_hwmon *) + kzalloc(sizeof(struct tsc210x_hwmon), GFP_KERNEL); + if (!hwmon) { + dev_dbg(&pdev->dev, "allocation failed\n"); + return -ENOMEM; + } + + hwmon->dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(hwmon->dev)) { + kfree(hwmon); + dev_dbg(&pdev->dev, "registration failed\n"); + return PTR_ERR(hwmon->dev); + } + + hwmon->pdata = pdata; + +#ifdef CONFIG_APM + spin_lock_init(&hwmon->apm_lock); + + if (pdata->apm_report) { + apm_hwmon = hwmon; + apm_get_power_status = tsc210x_get_power_status; + } +#endif + + platform_set_drvdata(pdev, hwmon); + + if (pdata->monitor & (TSC_BAT1 | TSC_BAT2 | TSC_AUX1 | TSC_AUX2)) + status |= tsc210x_ports_cb(pdev->dev.parent, + tsc210x_ports, hwmon); + if (pdata->monitor & TSC_TEMP) { + status |= tsc210x_temp1_cb(pdev->dev.parent, + tsc210x_temp1, hwmon); + status |= tsc210x_temp2_cb(pdev->dev.parent, + tsc210x_temp2, hwmon); + } + + if (status) { + tsc210x_ports_cb(pdev->dev.parent, NULL, NULL); + tsc210x_temp1_cb(pdev->dev.parent, NULL, NULL); + tsc210x_temp2_cb(pdev->dev.parent, NULL, NULL); + platform_set_drvdata(pdev, NULL); +#ifdef CONFIG_APM + if (pdata->apm_report) + apm_get_power_status = 0; +#endif + hwmon_device_unregister(hwmon->dev); + kfree(hwmon); + return status; + } + + if (pdata->monitor & TSC_BAT1) + status |= device_create_file(&pdev->dev, &dev_attr_in0_input); + if (pdata->monitor & TSC_BAT2) + status |= device_create_file(&pdev->dev, &dev_attr_in1_input); + if (pdata->monitor & TSC_AUX1) + status |= device_create_file(&pdev->dev, &dev_attr_in2_input); + if (pdata->monitor & TSC_AUX2) + status |= device_create_file(&pdev->dev, &dev_attr_in3_input); + if (pdata->monitor & TSC_TEMP) { + status |= device_create_file(&pdev->dev, &dev_attr_in4_input); + status |= device_create_file(&pdev->dev, &dev_attr_in5_input); + + if ((pdata->temp_at25c[1] - pdata->temp_at25c[0]) == 0) + dev_warn(&pdev->dev, "No temp calibration data.\n"); + else + status |= device_create_file(&pdev->dev, + &dev_attr_temp1_input); + } + if (status) /* Not fatal */ + dev_dbg(&pdev->dev, "Creating one or more " + "attribute files failed\n"); + + return 0; +} + +static int __exit tsc210x_hwmon_remove(struct platform_device *pdev) +{ + struct tsc210x_hwmon *dev = platform_get_drvdata(pdev); + + tsc210x_ports_cb(pdev->dev.parent, NULL, NULL); + tsc210x_temp1_cb(pdev->dev.parent, NULL, NULL); + tsc210x_temp2_cb(pdev->dev.parent, NULL, NULL); + platform_set_drvdata(pdev, NULL); +#ifdef CONFIG_APM + if (dev->pdata->apm_report) + apm_get_power_status = 0; +#endif + hwmon_device_unregister(dev->dev); + kfree(dev); + return 0; +} + +static struct platform_driver tsc210x_hwmon_driver = { + .probe = tsc210x_hwmon_probe, + .remove = __exit_p(tsc210x_hwmon_remove), + /* Nothing to do on suspend/resume */ + .driver = { + .name = "tsc210x-hwmon", + }, +}; + +static int __init tsc210x_hwmon_init(void) +{ + /* can't use driver_probe() here since the parent device + * gets registered "late" + */ + return platform_driver_register(&tsc210x_hwmon_driver); +} +module_init(tsc210x_hwmon_init); + +static void __exit tsc210x_hwmon_exit(void) +{ + platform_driver_unregister(&tsc210x_hwmon_driver); +} +module_exit(tsc210x_hwmon_exit); + +MODULE_AUTHOR("Andrzej Zaborowski"); +MODULE_DESCRIPTION("hwmon driver for TI TSC210x-connected sensors."); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index 711ca08ab77..f1e6aa7b93b 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -5,6 +5,7 @@ menuconfig I2C tristate "I2C support" depends on HAS_IOMEM + default y if MACH_OMAP_H3 || MACH_OMAP_OSK ---help--- I2C (pronounce: I-square-C) is a slow serial bus protocol used in many micro controller applications and developed by Philips. SMBus, diff --git a/drivers/i2c/busses/i2c-omap.c b/drivers/i2c/busses/i2c-omap.c index ece0125a1ee..9919c086ff5 100644 --- a/drivers/i2c/busses/i2c-omap.c +++ b/drivers/i2c/busses/i2c-omap.c @@ -672,8 +672,9 @@ omap_i2c_isr(int this_irq, void *dev_id) if (stat & OMAP_I2C_STAT_RRDY) num_bytes = dev->fifo_size; else - num_bytes = omap_i2c_read_reg(dev, - OMAP_I2C_BUFSTAT_REG); + num_bytes = (omap_i2c_read_reg(dev, + OMAP_I2C_BUFSTAT_REG) + >> 8) & 0x3F; } while (num_bytes) { num_bytes--; @@ -711,8 +712,9 @@ omap_i2c_isr(int this_irq, void *dev_id) if (stat & OMAP_I2C_STAT_XRDY) num_bytes = dev->fifo_size; else - num_bytes = omap_i2c_read_reg(dev, - OMAP_I2C_BUFSTAT_REG); + num_bytes = (omap_i2c_read_reg(dev, + OMAP_I2C_BUFSTAT_REG)) + & 0x3F; } while (num_bytes) { num_bytes--; diff --git a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig index 8f8c81eb0ae..a01f40778ae 100644 --- a/drivers/i2c/chips/Kconfig +++ b/drivers/i2c/chips/Kconfig @@ -64,6 +64,31 @@ config SENSORS_PCA9539 This driver is deprecated and will be dropped soon. Use drivers/gpio/pca953x.c instead. +config TWL4030_MADC + tristate "TWL4030 MADC Driver" + depends on TWL4030_CORE + help + The TWL4030 Monitoring ADC driver enables the host + processor to monitor analog signals using analog-to-digital + conversions on the input source. TWL4030 MADC provides the + following features: + - Single 10-bit ADC with successive approximation register (SAR) conversion; + - Analog multiplexer for 16 inputs; + - Seven (of the 16) inputs are freely available; + - Battery voltage monitoring; + - Concurrent conversion request management; + - Interrupt signal to Primary Interrupt Handler; + - Averaging feature; + - Selective enable/disable of the averaging feature. + + Say 'y' here to statically link this module into the kernel or 'm' + to build it as a dinamically loadable module. The module will be + called twl4030-madc.ko + +config TWL4030_POWEROFF + tristate "TWL4030 device poweroff" + depends on TWL4030_CORE + config SENSORS_MAX6875 tristate "Maxim MAX6875 Power supply supervisor" depends on EXPERIMENTAL @@ -89,4 +114,24 @@ config SENSORS_TSL2550 This driver can also be built as a module. If so, the module will be called tsl2550. +config SENSORS_TSL2563 + tristate "Taos TSL2563 ambient light sensor" + depends on I2C && HWMON + help + If you say yes here you get support for the Taos TSL2563 + ambient light sensor. + + This driver can also be built as a module. If so, the module + will be called tsl2563. + +config MENELAUS + bool "TWL92330/Menelaus PM chip" + depends on I2C=y && ARCH_OMAP24XX + help + If you say yes here you get support for the Texas Instruments + TWL92330/Menelaus Power Management chip. This include voltage + regulators, Dual slot memory card tranceivers, real-time clock + and other features that are often used in portable devices like + cell phones and PDAs. + endmenu diff --git a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile index 55a37603718..0f8a24539a9 100644 --- a/drivers/i2c/chips/Makefile +++ b/drivers/i2c/chips/Makefile @@ -16,8 +16,10 @@ obj-$(CONFIG_SENSORS_PCA9539) += pca9539.o obj-$(CONFIG_SENSORS_PCF8574) += pcf8574.o obj-$(CONFIG_PCF8575) += pcf8575.o obj-$(CONFIG_SENSORS_TSL2550) += tsl2550.o +obj-$(CONFIG_TWL4030_POWEROFF) += twl4030-poweroff.o +obj-$(CONFIG_TWL4030_MADC) += twl4030-madc.o +obj-$(CONFIG_RTC_X1205_I2C) += x1205.o ifeq ($(CONFIG_I2C_DEBUG_CHIP),y) EXTRA_CFLAGS += -DDEBUG endif - diff --git a/drivers/i2c/chips/tsl2563.c b/drivers/i2c/chips/tsl2563.c new file mode 100644 index 00000000000..e05b88007b1 --- /dev/null +++ b/drivers/i2c/chips/tsl2563.c @@ -0,0 +1,739 @@ +/* + * drivers/i2c/chips/tsl2563.c + * + * Copyright (C) 2008 Nokia Corporation + * + * Written by Timo O. Karjalainen + * Contact: Mathias Nyman + * + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "tsl2563" + +/* Use this many bits for fraction part. */ +#define ADC_FRAC_BITS (14) + +/* Given number of 1/10000's in ADC_FRAC_BITS precision. */ +#define FRAC10K(f) (((f) * (1L << (ADC_FRAC_BITS))) / (10000)) + +/* Bits used for fraction in calibration coefficients.*/ +#define CALIB_FRAC_BITS (10) +/* 0.5 in CALIB_FRAC_BITS precision */ +#define CALIB_FRAC_HALF (1 << (CALIB_FRAC_BITS - 1)) +/* Make a fraction from a number n that was multiplied with b. */ +#define CALIB_FRAC(n, b) (((n) << CALIB_FRAC_BITS) / (b)) +/* Decimal 10^(digits in sysfs presentation) */ +#define CALIB_BASE_SYSFS (1000) + +#define TSL2563_CMD (0x80) +#define TSL2563_CLEARINT (0x40) + +#define TSL2563_REG_CTRL (0x00) +#define TSL2563_REG_TIMING (0x01) +#define TSL2563_REG_LOWLOW (0x02) /* data0 low threshold, 2 bytes */ +#define TSL2563_REG_LOWHIGH (0x03) +#define TSL2563_REG_HIGHLOW (0x04) /* data0 high threshold, 2 bytes */ +#define TSL2563_REG_HIGHHIGH (0x05) +#define TSL2563_REG_INT (0x06) +#define TSL2563_REG_ID (0x0a) +#define TSL2563_REG_DATA0LOW (0x0c) /* broadband sensor value, 2 bytes */ +#define TSL2563_REG_DATA0HIGH (0x0d) +#define TSL2563_REG_DATA1LOW (0x0e) /* infrared sensor value, 2 bytes */ +#define TSL2563_REG_DATA1HIGH (0x0f) + +#define TSL2563_CMD_POWER_ON (0x03) +#define TSL2563_CMD_POWER_OFF (0x00) +#define TSL2563_CTRL_POWER_MASK (0x03) + +#define TSL2563_TIMING_13MS (0x00) +#define TSL2563_TIMING_100MS (0x01) +#define TSL2563_TIMING_400MS (0x02) +#define TSL2563_TIMING_MASK (0x03) +#define TSL2563_TIMING_GAIN16 (0x10) +#define TSL2563_TIMING_GAIN1 (0x00) + +#define TSL2563_INT_DISBLED (0x00) +#define TSL2563_INT_LEVEL (0x10) +#define TSL2563_INT_PERSIST(n) ((n) & 0x0F) + +struct tsl2563_gainlevel_coeff { + u8 gaintime; + u16 min; + u16 max; +}; + +static struct tsl2563_gainlevel_coeff tsl2563_gainlevel_table[] = { + { + .gaintime = TSL2563_TIMING_400MS | TSL2563_TIMING_GAIN16, + .min = 0, + .max = 65534, + }, { + .gaintime = TSL2563_TIMING_400MS | TSL2563_TIMING_GAIN1, + .min = 2048, + .max = 65534, + }, { + .gaintime = TSL2563_TIMING_100MS | TSL2563_TIMING_GAIN1, + .min = 4095, + .max = 37177, + }, { + .gaintime = TSL2563_TIMING_13MS | TSL2563_TIMING_GAIN1, + .min = 3000, + .max = 65535, + }, +}; + +struct tsl2563_chip { + struct mutex lock; + struct i2c_client *client; + struct device *hwmon_dev; + + /* Remember state for suspend and resume functions */ + pm_message_t state; + + struct tsl2563_gainlevel_coeff *gainlevel; + + /* Thresholds are in lux */ + u16 low_thres; + u16 high_thres; + u8 intr; + + /* Calibration coefficients */ + u32 calib0; + u32 calib1; + + /* Cache current values, to be returned while suspended */ + u32 data0; + u32 data1; +}; + +static int tsl2563_write(struct i2c_client *client, u8 reg, u8 value) +{ + int ret; + u8 buf[2]; + + buf[0] = TSL2563_CMD | reg; + buf[1] = value; + + ret = i2c_master_send(client, buf, sizeof(buf)); + return (ret == sizeof(buf)) ? 0 : ret; +} + +static int tsl2563_read(struct i2c_client *client, u8 reg, void *buf, int len) +{ + int ret; + u8 cmd = TSL2563_CMD | reg; + + ret = i2c_master_send(client, &cmd, sizeof(cmd)); + if (ret != sizeof(cmd)) + return ret; + + return i2c_master_recv(client, buf, len); +} + +static int tsl2563_set_power(struct tsl2563_chip *chip, int on) +{ + struct i2c_client *client = chip->client; + u8 cmd; + + cmd = on ? TSL2563_CMD_POWER_ON : TSL2563_CMD_POWER_OFF; + return tsl2563_write(client, TSL2563_REG_CTRL, cmd); +} + +/* + * Return value is 0 for off, 1 for on, or a negative error + * code if reading failed. + */ +static int tsl2563_get_power(struct tsl2563_chip *chip) +{ + struct i2c_client *client = chip->client; + int ret; + u8 val; + + ret = tsl2563_read(client, TSL2563_REG_CTRL, &val, sizeof(val)); + if (ret != sizeof(val)) + return ret; + + return (val & TSL2563_CTRL_POWER_MASK) == TSL2563_CMD_POWER_ON; +} + +static int tsl2563_configure(struct tsl2563_chip *chip) +{ + struct i2c_client *client = chip->client; + int ret; + + ret = tsl2563_write(client, TSL2563_REG_TIMING, + chip->gainlevel->gaintime); + if (ret) + goto out; + + ret = tsl2563_write(client, TSL2563_REG_INT, chip->intr); + +out: + return ret; +} + +static int tsl2563_detect(struct tsl2563_chip *chip) +{ + int ret; + + ret = tsl2563_set_power(chip, 1); + if (ret) + return ret; + + ret = tsl2563_get_power(chip); + if (ret < 0) + return ret; + + return ret ? 0 : -ENODEV; +} + +static int tsl2563_read_id(struct tsl2563_chip *chip, u8 *id) +{ + struct i2c_client *client = chip->client; + int ret; + + ret = tsl2563_read(client, TSL2563_REG_ID, id, sizeof(*id)); + if (ret != sizeof(*id)) + return ret; + + return 0; +} + +/* + * "Normalized" ADC value is one obtained with 400ms of integration time and + * 16x gain. This function returns the number of bits of shift needed to + * convert between normalized values and HW values obtained using given + * timing and gain settings. + */ +static int adc_shiftbits(u8 timing) +{ + int shift = 0; + + switch (timing & TSL2563_TIMING_MASK) { + case TSL2563_TIMING_13MS: + shift += 5; + break; + case TSL2563_TIMING_100MS: + shift += 2; + break; + case TSL2563_TIMING_400MS: + /* no-op */ + break; + } + + if (!(timing & TSL2563_TIMING_GAIN16)) + shift += 4; + + return shift; +} + +/* Convert a HW ADC value to normalized scale. */ +static u32 normalize_adc(u16 adc, u8 timing) +{ + return adc << adc_shiftbits(timing); +} + +static void tsl2563_wait_adc(struct tsl2563_chip *chip) +{ + unsigned int delay; + + switch (chip->gainlevel->gaintime & TSL2563_TIMING_MASK) { + case TSL2563_TIMING_13MS: + delay = 14; + break; + case TSL2563_TIMING_100MS: + delay = 101; + break; + default: + delay = 402; + } + /* + * TODO: Make sure that we wait at least required delay but why we + * have to extend it one tick more? + */ + schedule_timeout_interruptible(msecs_to_jiffies(delay) + 2); +} + +static int tsl2563_adjust_gainlevel(struct tsl2563_chip *chip, u16 adc) +{ + struct i2c_client *client = chip->client; + + if (adc > chip->gainlevel->max || adc < chip->gainlevel->min) { + + (adc > chip->gainlevel->max) ? + chip->gainlevel++ : chip->gainlevel--; + + tsl2563_write(client, TSL2563_REG_TIMING, + chip->gainlevel->gaintime); + + tsl2563_wait_adc(chip); + tsl2563_wait_adc(chip); + + return 1; + } else + return 0; +} + +static int tsl2563_get_adc(struct tsl2563_chip *chip) +{ + struct i2c_client *client = chip->client; + u8 buf0[2], buf1[2]; + u16 adc0, adc1; + int retry = 1; + int ret = 0; + + if (chip->state.event != PM_EVENT_ON) + goto out; + + while (retry) { + ret = tsl2563_read(client, + TSL2563_REG_DATA0LOW | TSL2563_CLEARINT, + buf0, sizeof(buf0)); + if (ret != sizeof(buf0)) + goto out; + + ret = tsl2563_read(client, TSL2563_REG_DATA1LOW, + buf1, sizeof(buf1)); + if (ret != sizeof(buf1)) + goto out; + + adc0 = (buf0[1] << 8) + buf0[0]; + adc1 = (buf1[1] << 8) + buf1[0]; + + retry = tsl2563_adjust_gainlevel(chip, adc0); + } + + chip->data0 = normalize_adc(adc0, chip->gainlevel->gaintime); + chip->data1 = normalize_adc(adc1, chip->gainlevel->gaintime); + + ret = 0; +out: + return ret; +} + +static inline int calib_to_sysfs(u32 calib) +{ + return (int) (((calib * CALIB_BASE_SYSFS) + + CALIB_FRAC_HALF) >> CALIB_FRAC_BITS); +} + +static inline u32 calib_from_sysfs(int value) +{ + return (((u32) value) << CALIB_FRAC_BITS) / CALIB_BASE_SYSFS; +} + +/* + * Conversions between lux and ADC values. + * + * The basic formula is lux = c0 * adc0 - c1 * adc1, where c0 and c1 are + * appropriate constants. Different constants are needed for different + * kinds of light, determined by the ratio adc1/adc0 (basically the ratio + * of the intensities in infrared and visible wavelengths). lux_table below + * lists the upper threshold of the adc1/adc0 ratio and the corresponding + * constants. + */ + +struct tsl2563_lux_coeff { + unsigned long ch_ratio; + unsigned long ch0_coeff; + unsigned long ch1_coeff; +}; + +static const struct tsl2563_lux_coeff lux_table[] = { + { + .ch_ratio = FRAC10K(1300), + .ch0_coeff = FRAC10K(315), + .ch1_coeff = FRAC10K(262), + }, { + .ch_ratio = FRAC10K(2600), + .ch0_coeff = FRAC10K(337), + .ch1_coeff = FRAC10K(430), + }, { + .ch_ratio = FRAC10K(3900), + .ch0_coeff = FRAC10K(363), + .ch1_coeff = FRAC10K(529), + }, { + .ch_ratio = FRAC10K(5200), + .ch0_coeff = FRAC10K(392), + .ch1_coeff = FRAC10K(605), + }, { + .ch_ratio = FRAC10K(6500), + .ch0_coeff = FRAC10K(229), + .ch1_coeff = FRAC10K(291), + }, { + .ch_ratio = FRAC10K(8000), + .ch0_coeff = FRAC10K(157), + .ch1_coeff = FRAC10K(180), + }, { + .ch_ratio = FRAC10K(13000), + .ch0_coeff = FRAC10K(34), + .ch1_coeff = FRAC10K(26), + }, { + .ch_ratio = ULONG_MAX, + .ch0_coeff = 0, + .ch1_coeff = 0, + }, +}; + +/* + * Convert normalized, scaled ADC values to lux. + */ +static unsigned int adc_to_lux(u32 adc0, u32 adc1) +{ + const struct tsl2563_lux_coeff *lp = lux_table; + unsigned long ratio, lux, ch0 = adc0, ch1 = adc1; + + ratio = ch0 ? ((ch1 << ADC_FRAC_BITS) / ch0) : ULONG_MAX; + + while (lp->ch_ratio < ratio) + lp++; + + lux = ch0 * lp->ch0_coeff - ch1 * lp->ch1_coeff; + + return (unsigned int) (lux >> ADC_FRAC_BITS); +} + +/*--------------------------------------------------------------*/ +/* Sysfs interface */ +/*--------------------------------------------------------------*/ + +static ssize_t tsl2563_adc0_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tsl2563_chip *chip = dev_get_drvdata(dev); + int ret; + + mutex_lock(&chip->lock); + + ret = tsl2563_get_adc(chip); + if (ret) + return ret; + + ret = snprintf(buf, PAGE_SIZE, "%d\n", chip->data0); + mutex_unlock(&chip->lock); + + return ret; +} + +static ssize_t tsl2563_adc1_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tsl2563_chip *chip = dev_get_drvdata(dev); + int ret; + + mutex_lock(&chip->lock); + + ret = tsl2563_get_adc(chip); + if (ret) + return ret; + + ret = snprintf(buf, PAGE_SIZE, "%d\n", chip->data1); + mutex_unlock(&chip->lock); + + return ret; +} + +/* Apply calibration coefficient to ADC count. */ +static u32 calib_adc(u32 adc, u32 calib) +{ + unsigned long scaled = adc; + + scaled *= calib; + scaled >>= CALIB_FRAC_BITS; + + return (u32) scaled; +} + +static ssize_t tsl2563_lux_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tsl2563_chip *chip = dev_get_drvdata(dev); + u32 calib0, calib1; + int ret; + + mutex_lock(&chip->lock); + + ret = tsl2563_get_adc(chip); + if (ret) + goto out; + + calib0 = calib_adc(chip->data0, chip->calib0); + calib1 = calib_adc(chip->data1, chip->calib1); + + ret = snprintf(buf, PAGE_SIZE, "%d\n", adc_to_lux(calib0, calib1)); + +out: + mutex_unlock(&chip->lock); + return ret; +} + +static ssize_t format_calib(char *buf, int len, u32 calib) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", calib_to_sysfs(calib)); +} + +static ssize_t tsl2563_calib0_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tsl2563_chip *chip = dev_get_drvdata(dev); + int ret; + + mutex_lock(&chip->lock); + ret = format_calib(buf, PAGE_SIZE, chip->calib0); + mutex_unlock(&chip->lock); + return ret; +} + +static ssize_t tsl2563_calib1_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tsl2563_chip *chip = dev_get_drvdata(dev); + int ret; + + mutex_lock(&chip->lock); + ret = format_calib(buf, PAGE_SIZE, chip->calib1); + mutex_unlock(&chip->lock); + return ret; +} + +static int do_calib_store(struct device *dev, const char *buf, size_t len, + int ch) +{ + struct tsl2563_chip *chip = dev_get_drvdata(dev); + int value; + u32 calib; + + if (1 != sscanf(buf, "%d", &value)) + return -EINVAL; + + calib = calib_from_sysfs(value); + + if (ch) + chip->calib1 = calib; + else + chip->calib0 = calib; + + return len; +} + +static ssize_t tsl2563_calib0_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + return do_calib_store(dev, buf, len, 0); +} + +static ssize_t tsl2563_calib1_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + return do_calib_store(dev, buf, len, 1); +} + +static DEVICE_ATTR(adc0, S_IRUGO, tsl2563_adc0_show, NULL); +static DEVICE_ATTR(adc1, S_IRUGO, tsl2563_adc1_show, NULL); +static DEVICE_ATTR(lux, S_IRUGO, tsl2563_lux_show, NULL); +static DEVICE_ATTR(calib0, S_IRUGO | S_IWUSR, + tsl2563_calib0_show, tsl2563_calib0_store); +static DEVICE_ATTR(calib1, S_IRUGO | S_IWUSR, + tsl2563_calib1_show, tsl2563_calib1_store); + +static struct attribute *tsl2563_attributes[] = { + &dev_attr_adc0.attr, + &dev_attr_adc1.attr, + &dev_attr_lux.attr, + &dev_attr_calib0.attr, + &dev_attr_calib1.attr, + NULL +}; + +static const struct attribute_group tsl2563_group = { + .attrs = tsl2563_attributes, +}; + +static int tsl2563_register_sysfs(struct i2c_client *client) +{ + struct device *dev = &client->dev; + + return sysfs_create_group(&dev->kobj, &tsl2563_group); +} + +static void tsl2563_unregister_sysfs(struct i2c_client *client) +{ + struct device *dev = &client->dev; + + sysfs_remove_group(&dev->kobj, &tsl2563_group); +} + +/*--------------------------------------------------------------*/ +/* Probe, Attach, Remove */ +/*--------------------------------------------------------------*/ +static struct i2c_driver tsl2563_i2c_driver; + +static int tsl2563_probe(struct i2c_client *client, + const struct i2c_device_id *device_id) +{ + struct tsl2563_chip *chip; + int err = 0; + u8 id; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + i2c_set_clientdata(client, chip); + chip->client = client; + + err = tsl2563_detect(chip); + if (err) { + dev_err(&client->dev, "device not found, error %d \n", -err); + goto fail1; + } + + err = tsl2563_read_id(chip, &id); + if (err) + goto fail1; + + mutex_init(&chip->lock); + + /* Default values used until userspace says otherwise */ + chip->low_thres = 0x0; + chip->high_thres = 0xffff; + chip->gainlevel = tsl2563_gainlevel_table; + chip->intr = TSL2563_INT_PERSIST(4); + chip->calib0 = calib_from_sysfs(CALIB_BASE_SYSFS); + chip->calib1 = calib_from_sysfs(CALIB_BASE_SYSFS); + + dev_info(&client->dev, "model %d, rev. %d\n", id >> 4, id & 0x0f); + + err = tsl2563_configure(chip); + if (err) + goto fail1; + + chip->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(chip->hwmon_dev)) + goto fail1; + + err = tsl2563_register_sysfs(client); + if (err) { + dev_err(&client->dev, "sysfs registration failed, %d\n", err); + goto fail2; + } + + return 0; +fail2: + hwmon_device_unregister(chip->hwmon_dev); +fail1: + kfree(chip); + return err; +} + +static int tsl2563_remove(struct i2c_client *client) +{ + struct tsl2563_chip *chip = i2c_get_clientdata(client); + + tsl2563_unregister_sysfs(client); + hwmon_device_unregister(chip->hwmon_dev); + + kfree(chip); + return 0; +} + +static int tsl2563_suspend(struct i2c_client *client, pm_message_t state) +{ + struct tsl2563_chip *chip = i2c_get_clientdata(client); + int ret; + + mutex_lock(&chip->lock); + + ret = tsl2563_set_power(chip, 0); + if (ret) + goto out; + + chip->state = state; + +out: + mutex_unlock(&chip->lock); + return ret; +} + +static int tsl2563_resume(struct i2c_client *client) +{ + struct tsl2563_chip *chip = i2c_get_clientdata(client); + int ret; + + mutex_lock(&chip->lock); + + ret = tsl2563_set_power(chip, 1); + if (ret) + goto out; + + ret = tsl2563_configure(chip); + if (ret) + goto out; + + chip->state.event = PM_EVENT_ON; + +out: + mutex_unlock(&chip->lock); + return ret; +} + +static const struct i2c_device_id tsl2563_id[] = { + { DRIVER_NAME, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, tsl2563_id); + +static struct i2c_driver tsl2563_i2c_driver = { + .driver = { + .name = DRIVER_NAME, + }, + .suspend = tsl2563_suspend, + .resume = tsl2563_resume, + .probe = tsl2563_probe, + .remove = __devexit_p(tsl2563_remove), + .id_table = tsl2563_id, +}; + +static int __init tsl2563_init(void) +{ + return i2c_add_driver(&tsl2563_i2c_driver); +} + +static void __exit tsl2563_exit(void) +{ + i2c_del_driver(&tsl2563_i2c_driver); +} + +MODULE_AUTHOR("Nokia Corporation"); +MODULE_DESCRIPTION("tsl2563 light sensor driver"); +MODULE_LICENSE("GPL"); + +module_init(tsl2563_init); +module_exit(tsl2563_exit); diff --git a/drivers/i2c/chips/twl4030-madc.c b/drivers/i2c/chips/twl4030-madc.c new file mode 100644 index 00000000000..d3e0a7fd7ef --- /dev/null +++ b/drivers/i2c/chips/twl4030-madc.c @@ -0,0 +1,528 @@ +/* + * drivers/i2c/chips/twl4030-madc.c + * + * TWL4030 MADC module driver + * + * Copyright (C) 2008 Nokia Corporation + * Mikko Ylinen + * + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define TWL4030_MADC_PFX "twl4030-madc: " + +struct twl4030_madc_data { + struct device *dev; + struct mutex lock; + struct work_struct ws; + struct twl4030_madc_request requests[TWL4030_MADC_NUM_METHODS]; + int imr; + int isr; +}; + +static struct twl4030_madc_data *the_madc; + +static +const struct twl4030_madc_conversion_method twl4030_conversion_methods[] = { + [TWL4030_MADC_RT] = { + .sel = TWL4030_MADC_RTSELECT_LSB, + .avg = TWL4030_MADC_RTAVERAGE_LSB, + .rbase = TWL4030_MADC_RTCH0_LSB, + }, + [TWL4030_MADC_SW1] = { + .sel = TWL4030_MADC_SW1SELECT_LSB, + .avg = TWL4030_MADC_SW1AVERAGE_LSB, + .rbase = TWL4030_MADC_GPCH0_LSB, + .ctrl = TWL4030_MADC_CTRL_SW1, + }, + [TWL4030_MADC_SW2] = { + .sel = TWL4030_MADC_SW2SELECT_LSB, + .avg = TWL4030_MADC_SW2AVERAGE_LSB, + .rbase = TWL4030_MADC_GPCH0_LSB, + .ctrl = TWL4030_MADC_CTRL_SW2, + }, +}; + +static int twl4030_madc_read(struct twl4030_madc_data *madc, u8 reg) +{ + int ret; + u8 val; + + ret = twl4030_i2c_read_u8(TWL4030_MODULE_MADC, &val, reg); + if (ret) { + dev_dbg(madc->dev, "unable to read register 0x%X\n", reg); + return ret; + } + + return val; +} + +static void twl4030_madc_write(struct twl4030_madc_data *madc, u8 reg, u8 val) +{ + int ret; + + ret = twl4030_i2c_write_u8(TWL4030_MODULE_MADC, val, reg); + if (ret) + dev_err(madc->dev, "unable to write register 0x%X\n", reg); +} + +static int twl4030_madc_channel_raw_read(struct twl4030_madc_data *madc, u8 reg) +{ + u8 msb, lsb; + + /* For each ADC channel, we have MSB and LSB register pair. MSB address + * is always LSB address+1. reg parameter is the addr of LSB register */ + msb = twl4030_madc_read(madc, reg + 1); + lsb = twl4030_madc_read(madc, reg); + + return (int)(((msb << 8) | lsb) >> 6); +} + +static int twl4030_madc_read_channels(struct twl4030_madc_data *madc, + u8 reg_base, u16 channels, int *buf) +{ + int count = 0; + u8 reg, i; + + if (unlikely(!buf)) + return 0; + + for (i = 0; i < TWL4030_MADC_MAX_CHANNELS; i++) { + if (channels & (1<imr); + val &= ~(1 << id); + twl4030_madc_write(madc, madc->imr, val); +} + +static void twl4030_madc_disable_irq(struct twl4030_madc_data *madc, int id) +{ + u8 val; + + val = twl4030_madc_read(madc, madc->imr); + val |= (1 << id); + twl4030_madc_write(madc, madc->imr, val); +} + +static irqreturn_t twl4030_madc_irq_handler(int irq, void *_madc) +{ + struct twl4030_madc_data *madc = _madc; + u8 isr_val, imr_val; + int i; + +#ifdef CONFIG_LOCKDEP + /* WORKAROUND for lockdep forcing IRQF_DISABLED on us, which + * we don't want and can't tolerate. Although it might be + * friendlier not to borrow this thread context... + */ + local_irq_enable(); +#endif + + /* Use COR to ack interrupts since we have no shared IRQs in ISRx */ + isr_val = twl4030_madc_read(madc, madc->isr); + imr_val = twl4030_madc_read(madc, madc->imr); + + isr_val &= ~imr_val; + + for (i = 0; i < TWL4030_MADC_NUM_METHODS; i++) { + + if (!(isr_val & (1<requests[i].result_pending = 1; + } + + schedule_work(&madc->ws); + + return IRQ_HANDLED; +} + +static void twl4030_madc_work(struct work_struct *ws) +{ + const struct twl4030_madc_conversion_method *method; + struct twl4030_madc_data *madc; + struct twl4030_madc_request *r; + int len, i; + + madc = container_of(ws, struct twl4030_madc_data, ws); + mutex_lock(&madc->lock); + + for (i = 0; i < TWL4030_MADC_NUM_METHODS; i++) { + + r = &madc->requests[i]; + + /* No pending results for this method, move to next one */ + if (!r->result_pending) + continue; + + method = &twl4030_conversion_methods[r->method]; + + /* Read results */ + len = twl4030_madc_read_channels(madc, method->rbase, + r->channels, r->rbuf); + + /* Return results to caller */ + if (r->func_cb != NULL) { + r->func_cb(len, r->channels, r->rbuf); + r->func_cb = NULL; + } + + /* Free request */ + r->result_pending = 0; + r->active = 0; + } + + mutex_unlock(&madc->lock); +} + +static int twl4030_madc_set_irq(struct twl4030_madc_data *madc, + struct twl4030_madc_request *req) +{ + struct twl4030_madc_request *p; + + p = &madc->requests[req->method]; + + memcpy(p, req, sizeof *req); + + twl4030_madc_enable_irq(madc, req->method); + + return 0; +} + +static inline void twl4030_madc_start_conversion(struct twl4030_madc_data *madc, + int conv_method) +{ + const struct twl4030_madc_conversion_method *method; + + method = &twl4030_conversion_methods[conv_method]; + + switch (conv_method) { + case TWL4030_MADC_SW1: + case TWL4030_MADC_SW2: + twl4030_madc_write(madc, method->ctrl, TWL4030_MADC_SW_START); + break; + case TWL4030_MADC_RT: + default: + break; + } +} + +static int twl4030_madc_wait_conversion_ready( + struct twl4030_madc_data *madc, + unsigned int timeout_ms, u8 status_reg) +{ + unsigned long timeout; + + timeout = jiffies + msecs_to_jiffies(timeout_ms); + do { + u8 reg; + + reg = twl4030_madc_read(madc, status_reg); + if (!(reg & TWL4030_MADC_BUSY) && (reg & TWL4030_MADC_EOC_SW)) + return 0; + } while (!time_after(jiffies, timeout)); + + return -EAGAIN; +} + +int twl4030_madc_conversion(struct twl4030_madc_request *req) +{ + const struct twl4030_madc_conversion_method *method; + u8 ch_msb, ch_lsb; + int ret; + + if (unlikely(!req)) + return -EINVAL; + + mutex_lock(&the_madc->lock); + + /* Do we have a conversion request ongoing */ + if (the_madc->requests[req->method].active) { + ret = -EBUSY; + goto out; + } + + ch_msb = (req->channels >> 8) & 0xff; + ch_lsb = req->channels & 0xff; + + method = &twl4030_conversion_methods[req->method]; + + /* Select channels to be converted */ + twl4030_madc_write(the_madc, method->sel + 1, ch_msb); + twl4030_madc_write(the_madc, method->sel, ch_lsb); + + /* Select averaging for all channels if do_avg is set */ + if (req->do_avg) { + twl4030_madc_write(the_madc, method->avg + 1, ch_msb); + twl4030_madc_write(the_madc, method->avg, ch_lsb); + } + + if ((req->type == TWL4030_MADC_IRQ_ONESHOT) && (req->func_cb != NULL)) { + twl4030_madc_set_irq(the_madc, req); + twl4030_madc_start_conversion(the_madc, req->method); + the_madc->requests[req->method].active = 1; + ret = 0; + goto out; + } + + /* With RT method we should not be here anymore */ + if (req->method == TWL4030_MADC_RT) { + ret = -EINVAL; + goto out; + } + + twl4030_madc_start_conversion(the_madc, req->method); + the_madc->requests[req->method].active = 1; + + /* Wait until conversion is ready (ctrl register returns EOC) */ + ret = twl4030_madc_wait_conversion_ready(the_madc, 5, method->ctrl); + if (ret) { + dev_dbg(the_madc->dev, "conversion timeout!\n"); + the_madc->requests[req->method].active = 0; + goto out; + } + + ret = twl4030_madc_read_channels(the_madc, method->rbase, req->channels, + req->rbuf); + + the_madc->requests[req->method].active = 0; + +out: + mutex_unlock(&the_madc->lock); + + return ret; +} +EXPORT_SYMBOL(twl4030_madc_conversion); + +static int twl4030_madc_set_current_generator(struct twl4030_madc_data *madc, + int chan, int on) +{ + int ret; + u8 regval; + + /* Current generator is only available for ADCIN0 and ADCIN1. NB: + * ADCIN1 current generator only works when AC or VBUS is present */ + if (chan > 1) + return EINVAL; + + ret = twl4030_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, + ®val, TWL4030_BCI_BCICTL1); + if (on) + regval |= (chan) ? TWL4030_BCI_ITHEN : TWL4030_BCI_TYPEN; + else + regval &= (chan) ? ~TWL4030_BCI_ITHEN : ~TWL4030_BCI_TYPEN; + ret = twl4030_i2c_write_u8(TWL4030_MODULE_MAIN_CHARGE, + regval, TWL4030_BCI_BCICTL1); + + return ret; +} + +static int twl4030_madc_set_power(struct twl4030_madc_data *madc, int on) +{ + u8 regval; + + regval = twl4030_madc_read(madc, TWL4030_MADC_CTRL1); + if (on) + regval |= TWL4030_MADC_MADCON; + else + regval &= ~TWL4030_MADC_MADCON; + twl4030_madc_write(madc, TWL4030_MADC_CTRL1, regval); + + return 0; +} + +static long twl4030_madc_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + struct twl4030_madc_user_parms par; + int val, ret; + + ret = copy_from_user(&par, (void __user *) arg, sizeof(par)); + if (ret) { + dev_dbg(the_madc->dev, "copy_from_user: %d\n", ret); + return -EACCES; + } + + switch (cmd) { + case TWL4030_MADC_IOCX_ADC_RAW_READ: { + struct twl4030_madc_request req; + if (par.channel >= TWL4030_MADC_MAX_CHANNELS) + return -EINVAL; + + req.channels = (1 << par.channel); + req.do_avg = par.average; + req.method = TWL4030_MADC_SW1; + req.func_cb = NULL; + + val = twl4030_madc_conversion(&req); + if (val <= 0) { + par.status = -1; + } else { + par.status = 0; + par.result = (u16)req.rbuf[par.channel]; + } + break; + } + default: + return -EINVAL; + } + + ret = copy_to_user((void __user *) arg, &par, sizeof(par)); + if (ret) { + dev_dbg(the_madc->dev, "copy_to_user: %d\n", ret); + return -EACCES; + } + + return 0; +} + +static struct file_operations twl4030_madc_fileops = { + .owner = THIS_MODULE, + .unlocked_ioctl = twl4030_madc_ioctl +}; + +static struct miscdevice twl4030_madc_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "twl4030-madc", + .fops = &twl4030_madc_fileops +}; + +static int __init twl4030_madc_probe(struct platform_device *pdev) +{ + struct twl4030_madc_data *madc; + struct twl4030_madc_platform_data *pdata = pdev->dev.platform_data; + int ret; + u8 regval; + + madc = kzalloc(sizeof *madc, GFP_KERNEL); + if (!madc) + return -ENOMEM; + + if (!pdata) { + dev_dbg(&pdev->dev, "platform_data not available\n"); + ret = -EINVAL; + goto err_pdata; + } + + madc->imr = (pdata->irq_line == 1) ? TWL4030_MADC_IMR1 : TWL4030_MADC_IMR2; + madc->isr = (pdata->irq_line == 1) ? TWL4030_MADC_ISR1 : TWL4030_MADC_ISR2; + + ret = misc_register(&twl4030_madc_device); + if (ret) { + dev_dbg(&pdev->dev, "could not register misc_device\n"); + goto err_misc; + } + twl4030_madc_set_power(madc, 1); + twl4030_madc_set_current_generator(madc, 0, 1); + + ret = twl4030_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, + ®val, TWL4030_BCI_BCICTL1); + + regval |= TWL4030_BCI_MESBAT; + + ret = twl4030_i2c_write_u8(TWL4030_MODULE_MAIN_CHARGE, + regval, TWL4030_BCI_BCICTL1); + + ret = request_irq(platform_get_irq(pdev, 0), twl4030_madc_irq_handler, + 0, "twl4030_madc", madc); + if (ret) { + dev_dbg(&pdev->dev, "could not request irq\n"); + goto err_irq; + } + + platform_set_drvdata(pdev, madc); + mutex_init(&madc->lock); + INIT_WORK(&madc->ws, twl4030_madc_work); + + the_madc = madc; + + return 0; + +err_irq: + misc_deregister(&twl4030_madc_device); + +err_misc: +err_pdata: + kfree(madc); + + return ret; +} + +static int __exit twl4030_madc_remove(struct platform_device *pdev) +{ + struct twl4030_madc_data *madc = platform_get_drvdata(pdev); + + twl4030_madc_set_power(madc, 0); + twl4030_madc_set_current_generator(madc, 0, 0); + free_irq(platform_get_irq(pdev, 0), madc); + cancel_work_sync(&madc->ws); + misc_deregister(&twl4030_madc_device); + + return 0; +} + +static struct platform_driver twl4030_madc_driver = { + .probe = twl4030_madc_probe, + .remove = __exit_p(twl4030_madc_remove), + .driver = { + .name = "twl4030_madc", + .owner = THIS_MODULE, + }, +}; + +static int __init twl4030_madc_init(void) +{ + return platform_driver_register(&twl4030_madc_driver); +} +module_init(twl4030_madc_init); + +static void __exit twl4030_madc_exit(void) +{ + platform_driver_unregister(&twl4030_madc_driver); +} +module_exit(twl4030_madc_exit); + +MODULE_ALIAS("platform:twl4030-madc"); +MODULE_AUTHOR("Nokia Corporation"); +MODULE_DESCRIPTION("twl4030 ADC driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/i2c/chips/twl4030-poweroff.c b/drivers/i2c/chips/twl4030-poweroff.c new file mode 100644 index 00000000000..0ebab0ba4e9 --- /dev/null +++ b/drivers/i2c/chips/twl4030-poweroff.c @@ -0,0 +1,76 @@ +/* + * linux/drivers/i2c/chips/twl4030_poweroff.c + * + * Power off device + * + * Copyright (C) 2008 Nokia Corporation + * + * Written by Peter De Schrijver + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include + +#define PWR_P1_SW_EVENTS 0x10 +#define PWR_DEVOFF (1<<0) + +static void twl4030_poweroff(void) +{ + u8 val; + int err; + + err = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &val, + PWR_P1_SW_EVENTS); + if (err) { + printk(KERN_WARNING "I2C error %d while reading TWL4030" + "PM_MASTER P1_SW_EVENTS\n", err); + return ; + } + + val |= PWR_DEVOFF; + + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, val, + PWR_P1_SW_EVENTS); + + if (err) { + printk(KERN_WARNING "I2C error %d while writing TWL4030" + "PM_MASTER P1_SW_EVENTS\n", err); + return ; + } + + return; +} + +static int __init twl4030_poweroff_init(void) +{ + pm_power_off = twl4030_poweroff; + + return 0; +} + +static void __exit twl4030_poweroff_exit(void) +{ + pm_power_off = NULL; +} + +module_init(twl4030_poweroff_init); +module_exit(twl4030_poweroff_exit); + +MODULE_ALIAS("i2c:twl4030-poweroff"); +MODULE_DESCRIPTION("Triton2 device power off"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Peter De Schrijver"); diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index ea2638b4198..0c20376a2ed 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -259,6 +259,41 @@ config KEYBOARD_OMAP To compile this driver as a module, choose M here: the module will be called omap-keypad. +config KEYBOARD_TWL4030 + tristate "TI TWL4030/TWL5030/TPS659x0 keypad support" + depends on TWL4030_CORE + help + Say Y here if your board use the keypad controller on + TWL4030 family chips. It's safe to say enable this + even on boards that don't use the keypad controller. + + To compile this driver as a module, choose M here: the + module will be called twl4030_keypad. + +config OMAP_PS2 + tristate "TI OMAP Innovator 1510 PS/2 keyboard & mouse support" + depends on ARCH_OMAP15XX && MACH_OMAP_INNOVATOR + help + Say Y here if you want to use the OMAP Innovator 1510 PS/2 + keyboard and mouse. + + To compile this driver as a module, choose M here: the + module will be called innovator_ps2. + +config KEYBOARD_TSC2301 + tristate "TSC2301 keypad support" + depends on SPI_TSC2301 + help + Say Y here for if you are using the keypad features of TSC2301. + +config KEYBOARD_LM8323 + tristate "LM8323 keypad chip" + depends on I2C + depends on LEDS + help + If you say yes here you get support for the National Semiconductor + LM8323 keypad controller. + config KEYBOARD_PXA27x tristate "PXA27x/PXA3xx keypad support" depends on PXA27x || PXA3xx diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index 36351e1190f..9f68f208895 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -19,6 +19,10 @@ obj-$(CONFIG_KEYBOARD_TOSA) += tosakbd.o obj-$(CONFIG_KEYBOARD_HIL) += hil_kbd.o obj-$(CONFIG_KEYBOARD_HIL_OLD) += hilkbd.o obj-$(CONFIG_KEYBOARD_OMAP) += omap-keypad.o +obj-$(CONFIG_OMAP_PS2) += innovator_ps2.o +obj-$(CONFIG_KEYBOARD_TSC2301) += tsc2301_kp.o +obj-$(CONFIG_KEYBOARD_LM8323) += lm8323.o +obj-$(CONFIG_KEYBOARD_TWL4030) += twl4030_keypad.o obj-$(CONFIG_KEYBOARD_PXA27x) += pxa27x_keypad.o obj-$(CONFIG_KEYBOARD_PXA930_ROTARY) += pxa930_rotary.o obj-$(CONFIG_KEYBOARD_AAED2000) += aaed2000_kbd.o diff --git a/drivers/input/keyboard/innovator_ps2.c b/drivers/input/keyboard/innovator_ps2.c new file mode 100644 index 00000000000..5f888393eec --- /dev/null +++ b/drivers/input/keyboard/innovator_ps2.c @@ -0,0 +1,1279 @@ +/* + * drivers/char/innovator_ps2.c + * + * Basic PS/2 keyboard/mouse driver for the Juno® USAR HID controller + * present on the TI Innovator/OMAP1510 Break-out-board. + * + * + * Author: MontaVista Software, Inc. + * or + * + * + * 2003 (c) MontaVista Software, Inc. This file is licensed under + * the terms of the GNU General Public License version 2. This program + * is licensed "as is" without any warranty of any kind, whether express + * or implied. + * + * + * REFERENCES: + * + * 1. Technical Reference Manual + * Juno® 01 + * Multi-function ICs family + * UR8HC007-001 HID & Power management controller + * Document Number: DOC8-007-001-TR-075 + * Date: February 2002 + * Copyright ©1998-2002 Semtech Corporation + * http://www.semtech.com/pdf/doc8-007-001-tr.pdf + * + * 2. Juno® 01 UR8HC007-001 Data Sheet + * Extremely Low-power Input Device and Power Management IC + * Copyright ©1998-2002 Semtech Corporation + * DOC8-007-001-DS-112 + * http://www.semtech.com/pdf/doc8-007-001-ds.pdf + * + * + * HISTORY: + * + * 20030626: George G. Davis + * Initially based on the following RidgeRun DSPlinux Version 1.6 files: + * linux-2.4.15-rmk1-dsplinux/arch/arm/dsplinux/hid/omap1510_hid.c + * linux-2.4.15-rmk1-dsplinux/arch/arm/dsplinux/hid/omap1510_hid.h + * linux-2.4.15-rmk1-dsplinux/arch/arm/dsplinux/hid/omap1510_ps2.c + * linux-2.4.15-rmk1-dsplinux/arch/arm/dsplinux/hid/omap1510_spi.c + * All original files above are + * Copyright (C) 2001 RidgeRun, Inc. + * Author: Alex McMains + * + * 20040812: Thiago Radicchi + * Cleanup of old code from 2.4 driver and some debug code. + * Minor changes in interrupt handling code. + * + * NOTES: + * + * 1. This driver does not provide support for setting keyboard/mouse + * configuration parameters. Both devices are managed directly by + * the Juno UR8HC007-001 on behalf of the host. This minimises the + * amount of host processing required to manage HID events and state + * changes, e.g. both keyboard and mouse devices are hot pluggable + * with no host intervention required. However, we cannot customise + * keyboard/mouse settings in this case. So we live with the defaults + * as setup by the Juno UR8HC007-001 whatever they may be. + * 2. Keyboard auto repeat does not work. See 1 above. : ) + * + * + * TODO: + * + * 1. Complete DPM/LDM stubs and test. + * 2. Add SPI error handling support, i.e. resend, etc.,. + * 3. Determine why innovator_hid_interrupt() is called for every + * invocation of Innovator FPGA IRQ demux. It appears that the + * missed Innovator ethernet workaround may be to blame. However, + * it does not adversely affect operation of this driver since we + * check for assertion of ATN prior to servicing the interrupt. If + * ATN is negated, we bug out right away. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#undef INNOVATOR_KEYB_DEBUG +#ifdef INNOVATOR_KEYB_DEBUG +#define dbg(format, arg...) printk(KERN_DEBUG "%s:%d: " format , \ + __FUNCTION__ , __LINE__ , ## arg) +#define entry() printk(KERN_DEBUG "%s:%d: Entry\n" , __FUNCTION__ , __LINE__) +#define exit() printk(KERN_DEBUG "%s:%d: Exit\n" , __FUNCTION__ , __LINE__) +#define dump_packet(p, n) \ + { \ + int i; \ + printk(KERN_DEBUG "%s:%d: %08x:" , \ + __FUNCTION__ , __LINE__ , (int) p); \ + for (i = 0; i < n; i += 1) { \ + printk(" %02x", (int) p[i]); \ + } \ + printk("\n"); \ + } +#else +#define dbg(format, arg...) do {} while (0) +#define entry() do {} while (0) +#define exit() do {} while (0) +#define dump_packet(p, n) do {} while (0) +#endif + + +#define PFX "innovator_ps2" +#define err(format, arg...) printk(KERN_ERR PFX ": " format , ## arg) +#define info(format, arg...) printk(KERN_INFO PFX ": " format , ## arg) +#define warn(format, arg...) printk(KERN_WARNING PFX ": " format , ## arg) + + +/****************************************************************************/ + +/* + * Synchronous communications timing parameters (Reference [1] pg 7-7) + */ + +#define tMSA 5000 /* -/5ms _SS to _ATN (master transfer) */ +#define tMAC 100 /* 100us/5ms _ATN to first clock pulse (master + transfer) */ +#define tMIB 150 /* 150us/5ms Beginning of byte transfer to beginning + of next byte transfer */ +#define tSIB 150 /* 150us/5ms Beginning of byte transfer to beginning + of next byte transfer */ +#define tMSP 100 /* -/100us Last clock pulse of packet to _SS + de-assertion */ +#define tMNSA 100 /* -/100us _SS de-assertion to _ATN de-assertion */ +#define tMNEXT 120 /* 120uS/- _ATN release to _SS re-assertion + (master transfer) */ +#define tSAS 5000 /* -/5ms _ATN to _SS (slave transfer) */ +#define tSSC 100 /* 100us/5ms _SS to first clock pulse (slave + transfer) */ +#define tSNA 100 /* -/100us Last clock pulse of packet to _ATN + de-assertion */ +#define tSNAS 100 /* -/100us _ATN release to _SS de-assertion */ +#define tSNEXT 120 /* 120us/- _SS release to _ATN re-assertion + (slave transfer) */ +#define tSCK 4 /* 4us/- Clock period */ +#define tSLOW 2 /* 2us/- Clock LOW period */ +#define tHOLD 200 /* 200ns/- Master data hold time */ +#define tSETUP 100 /* 100ns/- Master data setup Time */ +#define tSSETUP 500 /* -/500ns Slave data setup time from clock + falling edge */ + + +/* + * Protocol Headers (Reference [1], pg. 5-1): + */ + + +/* Protocols used in commands issued by the host: */ +#define SIMPLE 0x80 /* Simple commands + * Common for both host and controller + * protocol headers. + */ +#define WRITE_REGISTER_BIT 0x81 /* Write register bit */ +#define READ_REGISTER_BIT 0x82 /* Read register bit */ +#define WRITE_REGISTER 0x83 /* Write register */ +#define READ_REGISTER 0x84 /* Read register */ +#define WRITE_BLOCK 0x85 /* Write block */ +#define READ_BLOCK 0x86 /* Read block */ + + +/* Protocols used in responses, reports and alerts issued by the controller: */ +#define REPORT_REGISTER_BIT 0x81 /* Report register bit & event alerts */ +#define REPORT_REGISTER 0x83 /* Report register */ +#define REPORT_BLOCK 0x85 /* Report block */ +#define POINTING_REPORT 0x87 /* Pointing device data report */ +#define KEYBOARD_REPORT 0x88 /* Keyboard device data report */ + + +/* Simple Commands (Reference [1], pg 5-3): */ +#define INITIALIZE 0x00 /* Forces the recipient to enter the + * known default power-on state. + */ +#define INITIALIZATION_COMPLETE 0x01 /* Issued as a hand-shake response only + * to the "Initialize" command. + */ +#define RESEND_REQUEST 0x05 /* Issued upon error in the reception + * of a package. The recipient resends + * the last transmitted packet. + */ + +/* Register offsets (Reference [1], pg 6-1 thru 6-9): */ + +#define REG_PM_COMM 0 +#define REG_PM_STATUS 1 +#define REG_PAGENO 255 + +/* Power management bits ((Reference [1], pg 6-10): */ + +#define SUS_STATE 0x2 /* in REG_PM_COMM */ + +/* Miscellaneous constants: */ + +#define X_MSB_SHIFT (8-4) +#define X_MSB_MASK (3<<4) +#define Y_MSB_SHIFT (8-6) +#define Y_MSB_MASK (3<<6) + + +#define JUNO_BLOCK_SIZE 32 +#define JUNO_BUFFER_SIZE 256 + + +/* + * Errors: + */ + +#define E_BAD_HEADER 1 +#define E_BAD_LRC 2 +#define E_ZERO_BYTES 3 +#define E_BAD_VALUE 4 +#define E_BAD_MODE 5 +#define E_REPORT_MODE 6 +#define E_BAD_ACK 7 +#define E_BAD_DEVICE_ID 8 +#define E_PKT_SZ 9 + + +/* + * Host/Controller Command/Response Formats: + */ + +typedef struct _simple_t { + u8 header; + u8 cmd_code; + u8 LRC; +} __attribute__ ((packed)) simple_t; + +typedef struct _write_bit_t { + u8 header; + u8 offset; + u8 value_bit; + u8 LRC; +} __attribute__ ((packed)) write_bit_t; + +typedef struct _read_bit_t { + u8 header; + u8 offset; + u8 bit; + u8 LRC; +} __attribute__ ((packed)) read_bit_t; + +typedef struct _write_reg_t { + u8 header; + u8 offset; + u8 value; + u8 LRC; +} __attribute__ ((packed)) write_reg_t; + +typedef struct _read_reg_t { + u8 header; + u8 offset; + u8 LRC; +} __attribute__ ((packed)) read_reg_t; + +typedef struct _write_block_t { + u8 header; + u8 offset; + u8 length; + u8 block[JUNO_BLOCK_SIZE + 1]; /* Hack: LRC is last element of block[] */ +} __attribute__ ((packed)) write_block_t; + +typedef struct _read_block_t { + u8 header; + u8 offset; + u8 length; + u8 LRC; +} __attribute__ ((packed)) read_block_t; + +typedef struct _report_bit_t { + u8 header; + u8 offset; + u8 value_bit; + u8 LRC; +} __attribute__ ((packed)) report_bit_t; + +typedef struct _report_reg_t { + u8 header; + u8 offset; + u8 value; + u8 LRC; +} __attribute__ ((packed)) report_reg_t; + +typedef struct _report_block_t { + u8 header; + u8 offset; + u8 length; + u8 block[32]; + u8 LRC; +} __attribute__ ((packed)) report_block_t; + +typedef struct _mse_report_t { + u8 header; + u8 buttons; + u8 Xdisplacement; + u8 Ydisplacement; + u8 Zdisplacement; + u8 LRC; +} __attribute__ ((packed)) mse_report_t; + +typedef struct _kdb_report_t { + u8 header; + u8 keynum; /* up > 0x80, down < 0x7E, all keys up 0x00 */ + u8 LRC; +} __attribute__ ((packed)) kdb_report_t; + + +static u8 buffer[JUNO_BUFFER_SIZE]; + +static void do_hid_tasklet(unsigned long); +DECLARE_TASKLET(hid_tasklet, do_hid_tasklet, 0); +static struct innovator_hid_dev *hid; + +struct innovator_hid_dev { + struct input_dev *mouse, *keyboard; + int open; + int irq_enabled; +}; + +/****************************************************************************/ + +/* + * Low-level TI Innovator/OMAP1510 FPGA HID SPI interface helper functions: + */ + +static u8 +innovator_fpga_hid_rd(void) +{ + u8 val = inb(INNOVATOR_FPGA_HID_SPI); + return val; +} + +static void +innovator_fpga_hid_wr(u8 val) +{ + outb(val, INNOVATOR_FPGA_HID_SPI); +} + +static void +innovator_fpga_hid_frob(u8 mask, u8 val) +{ + unsigned long flags; + local_irq_save(flags); + innovator_fpga_hid_wr((innovator_fpga_hid_rd() & ~mask) | val); + local_irq_restore(flags); +} + +static void +innovator_fpga_hid_set_bits(u8 x) +{ + innovator_fpga_hid_frob(x, x); +} + +static void +SS(int value) +{ + innovator_fpga_hid_frob(OMAP1510_FPGA_HID_nSS, value ? OMAP1510_FPGA_HID_nSS : 0); +} + +static void +SCLK(int value) +{ + innovator_fpga_hid_frob(OMAP1510_FPGA_HID_SCLK, value ? OMAP1510_FPGA_HID_SCLK : 0); +} + +static void +MOSI(int value) +{ + innovator_fpga_hid_frob(OMAP1510_FPGA_HID_MOSI, value ? OMAP1510_FPGA_HID_MOSI : 0); +} + +static u8 +MISO(void) +{ + return ((innovator_fpga_hid_rd() & OMAP1510_FPGA_HID_MISO) ? 1 : 0); +} + +static u8 +ATN(void) +{ + return ((innovator_fpga_hid_rd() & OMAP1510_FPGA_HID_ATN) ? 1 : 0); +} + +static int +wait_for_ATN(int assert, int timeout) +{ + do { + if (ATN() == assert) + return 0; + udelay(1); + } while (timeout -= 1); + return -1; +} + +static u8 +innovator_fpga_hid_xfer_byte(u8 xbyte) +{ + int i; + u8 rbyte; + + for (rbyte = 0, i = 7; i >= 0; i -= 1) { + SCLK(0); + MOSI((xbyte >> i) & 1); + udelay(tSLOW); + SCLK(1); + rbyte = (rbyte << 1) | MISO(); + udelay(tSLOW); + } + + return rbyte; +} + +static void +innovator_fpga_hid_reset(void) +{ + innovator_fpga_hid_wr(OMAP1510_FPGA_HID_SCLK | OMAP1510_FPGA_HID_MOSI); + mdelay(1); + innovator_fpga_hid_set_bits(OMAP1510_FPGA_HID_RESETn); +} + + +/***************************************************************************** + + Refer to Reference [1], Chapter 7 / Low-level communications, Serial + Peripheral Interface (SPI) implementation Host (master) packet + transmission timing, pg. 7-3, for timing and implementation details + for spi_xmt(). + + *****************************************************************************/ + +int +spi_xmt(u8 * p, u8 n) +{ + unsigned long flags; + + dump_packet(p, n); + local_irq_save(flags); + disable_irq(OMAP1510_INT_FPGA_ATN); + + if (ATN()) { + /* Oops, we have a collision. */ + enable_irq(OMAP1510_INT_FPGA_ATN); + local_irq_restore(flags); + dbg("Protocol error: ATN is asserted\n"); + return -EAGAIN; + } + + SS(1); + + if (wait_for_ATN(1, tMSA) < 0) { + SS(0); + enable_irq(OMAP1510_INT_FPGA_ATN); + local_irq_restore(flags); + dbg("timeout waiting for ATN assertion\n"); + return -EREMOTEIO; + } + + udelay(tMAC); + + while (n--) { + innovator_fpga_hid_xfer_byte(*p++); + if (n) { + udelay(tMIB - 8 * tSCK); + } + } + + MOSI(1); /* Set MOSI to idle high. */ + + /* NOTE: The data sheet does not specify a minimum delay + * here. But innovator_fpga_hid_xfer_byte() gives us a half-clock + * delay (tSLOW) after the last bit is sent. So I'm happy with + * that. + */ + + SS(0); + + if (wait_for_ATN(0, tMNSA) < 0) { + enable_irq(OMAP1510_INT_FPGA_ATN); + local_irq_restore(flags); + dbg("timeout waiting for ATN negation\n"); + return -EREMOTEIO; + } + + udelay(tMNEXT); + enable_irq(OMAP1510_INT_FPGA_ATN); + local_irq_restore(flags); + return 0; +} + + +/***************************************************************************** + + Refer to Reference [1], Chapter 7 / Low-level communications, Serial + Peripheral Interface (SPI) implementation, Slave packet transmission + timing, pg. 7-5, for timing and implementation details for spi_rcv(). + + *****************************************************************************/ + +int +spi_rcv(u8 * p, int len) +{ + unsigned long flags; + int ret = 0; + + if (len > 256) { + /* Limit packet size to something reasonable */ + return -1; + } + + local_irq_save(flags); + + if (wait_for_ATN(1, tMSA) < 0) { + local_irq_restore(flags); + dbg("Protocol error: ATN is not asserted\n"); + return -EREMOTEIO; + } + + SS(1); + + udelay(tSSC); + + while (ATN()) { + if (ret >= len) { + err("over run error\n"); + ret = -1; + break; + } + p[ret++] = innovator_fpga_hid_xfer_byte(0xff); + udelay(tSNA); /* Wait long enough to detect negation of ATN + * after last clock pulse of packet. + * + * NOTE: Normally, we need a minimum delay of + * tSIB between the start of one byte + * and the start of the next. However, + * we also need to wait long enough + * for the USAR to negate ATN before + * starting the next byte. So we use + * max(tSIB - 8 * tSCK, tSNA) here to + * satisfy both constraints. + */ + } + + SS(0); /* NOTE: The data sheet does not specify a minimum delay + * here. But innovator_fpga_hid_xfer_byte() gives us a + * half-clock delay (tSLOW) after the last bit is sent. So + * I'm happy with that (rather than no delay at all : ). + */ + + + udelay(tSNEXT); /* This isn't quite right. Assertion of ATN after + * negation of SS is an USAR timing constraint. + * What we need here is a spec for the minimum + * delay from SS negation to SS assertion. But + * for now, just use this brain dead delay. + */ + + local_irq_restore(flags); + + if (ret > 0) { + dump_packet(p, ret); + } + + return ret; +} + + +/***************************************************************************** + Calculate Host/Controller Command/Response Longitudinal Redundancy Check (LRC) + + The algorithm implemented in calculate_LRC() below is taken directly from + the reference [1], Chapter 7 / Low-level communications, LRC (Longitudinal + Redundancy Check), pg 5-10. + + *****************************************************************************/ + +static u8 +calculate_LRC(u8 * p, int n) +{ + u8 LRC; + int i; + + /* + * Init the LRC using the first two message bytes. + */ + LRC = p[0] ^ p[1]; + + /* + * Update the LRC using the remainder of the p. + */ + for (i = 2; i < n; i++) + LRC ^= p[i]; + + /* + * If the MSB is set then clear the MSB and change the next + * most significant bit + */ + if (LRC & 0x80) + LRC ^= 0xC0; + + return LRC; +} + + +/* + * Controller response helper functions: + */ + +static inline int +report_mouse(mse_report_t * p, int n) +{ + if (p->header != POINTING_REPORT) + return -E_BAD_HEADER; + + if (n != sizeof(mse_report_t)) + return -E_PKT_SZ; + + return (p->LRC != calculate_LRC((u8 *) p, sizeof(mse_report_t) - 1)) ? + -E_BAD_LRC : POINTING_REPORT; +} + +static inline int +report_keyboard(kdb_report_t * p, int n) +{ + if (p->header != KEYBOARD_REPORT) + return -E_BAD_HEADER; + + if (n != sizeof(kdb_report_t)) + return -E_PKT_SZ; + + return (p->LRC != calculate_LRC((u8 *) p, sizeof(kdb_report_t) - 1)) ? + -E_BAD_LRC : KEYBOARD_REPORT; +} + + +/* + * Miscellaneous helper functions: + */ + +static inline int +report_type(u8 * type) +{ + /* check the header to find out what kind of report it is */ + if ((*type) == KEYBOARD_REPORT) + return KEYBOARD_REPORT; + else if ((*type) == POINTING_REPORT) + return POINTING_REPORT; + else + return -E_BAD_HEADER; +} + +static inline int +report_async(void * p, int n) +{ + int ret; + + if ((ret = spi_rcv((u8 *) p, n)) < 0) + return ret; + + if (report_type((u8 *) p) == POINTING_REPORT) + ret = report_mouse((mse_report_t *) p, ret); + else if (report_type((u8 *) p) == KEYBOARD_REPORT) + ret = report_keyboard((kdb_report_t *) p, ret); + + return ret; +} + +/* + * Host command helper functions: + */ + +#if 0 +/* REVISIT/TODO: Wrapper for command/response with resend handing. */ +static int +spi_xfer(u8 * optr, u8 osz, u8 * iptr, u8 isz) +{ + static u8 buf[256]; + int ret; + int xretries = 3; + + do { + if (optr != NULL && osz) { + do { + ret = spi_xmt((u8 *) optr, osz); + } while (ret < 0); + } + + ret = spi_rcv((u8 *) buf, 256); + + if (ret == -EREMOTEIO) { + if (iptr == NULL) { + break; + } + } + } while (xretries--); + + return ret; +} +#endif + +/* REVISIT: Enable these when/if additional Juno features are required. */ +static inline int +simple(u8 cmd) +{ + static simple_t p; + int ret; + + p.header = SIMPLE; + p.cmd_code = cmd; + p.LRC = calculate_LRC((u8 *) & p, sizeof(p) - 1); + + if ((ret = spi_xmt((u8 *) & p, sizeof(p))) < 0) + return ret; + + if ((ret = spi_rcv((u8 *) & p, sizeof(p))) < 0) + return ret; + + if (ret == 0) + return -E_ZERO_BYTES; + + if (ret != sizeof(p)) + return -E_PKT_SZ; + + if (p.header != SIMPLE) + return -E_BAD_HEADER; + + if (p.LRC != calculate_LRC((u8 *) & p, sizeof(p) - 1)) + return -E_BAD_LRC; + + /* REVISIT: Need to check or return response code here? */ +} + +static inline int +write_bit(u8 offset, u8 bit, u8 value) +{ + static write_bit_t p; + + p.header = WRITE_REGISTER_BIT; + p.offset = offset; + p.value_bit = (bit << 1) | (value & 1); + p.LRC = calculate_LRC((u8 *) & p, sizeof(p) - 1); + + return spi_xmt((u8 *) & p, sizeof(p)); +} + +static inline int +read_bit(u8 offset, u8 bit, u8 * data) +{ + static read_bit_t p; + static report_bit_t q; + int ret; + + p.header = READ_REGISTER_BIT; + p.offset = offset; + p.bit = bit; + p.LRC = calculate_LRC((u8 *) & p, sizeof(p) - 1); + + if ((ret = spi_xmt((u8 *) & p, sizeof(p))) < 0) + return ret; + + if ((ret = spi_rcv((u8 *) & q, sizeof(q))) < 0) + return ret; + + if (ret == 0) + return -E_ZERO_BYTES; + + if (ret != sizeof(q)) + return -E_PKT_SZ; + + if (q.header != REPORT_REGISTER_BIT) + return -E_BAD_HEADER; + + if (q.LRC != calculate_LRC((u8 *) & q, sizeof(q) - 1)) + return -E_BAD_LRC; + + *data = q.value_bit; + + return 0; +} + +static inline int +write_reg(u8 offset, u8 value) +{ + static write_reg_t p; + + p.header = WRITE_REGISTER; + p.offset = offset; + p.value = value; + p.LRC = calculate_LRC((u8 *) & p, sizeof(p) - 1); + + return spi_xmt((u8 *) & p, sizeof(p)); +} + +static inline int +read_reg(u8 offset, u8 * data) +{ + static read_reg_t p; + static report_reg_t q; + int ret; + + p.header = READ_REGISTER; + p.offset = offset; + p.LRC = calculate_LRC((u8 *) & p, sizeof(p) - 1); + + if ((ret = spi_xmt((u8 *) & p, sizeof(p))) < 0) + return ret; + + if ((ret = spi_rcv((u8 *) & q, sizeof(q))) < 0) + return ret; + + if (ret == 0) + return -E_ZERO_BYTES; + + if (ret != sizeof(q)) + return -E_PKT_SZ; + + if (q.header != REPORT_REGISTER) + return -E_BAD_HEADER; + + if (q.LRC != calculate_LRC((u8 *) & q, sizeof(q) - 1)) + return -E_BAD_LRC; + + *data = q.value; + + return 0; +} + +static inline int +write_block(u8 offset, u8 length, u8 * block) +{ + static write_block_t p; + + p.header = WRITE_BLOCK; + p.offset = offset; + p.length = length; + memcpy(&p.block, block, length); + p.block[length] = calculate_LRC((u8 *) & p, 3 + length); + + return spi_xmt((u8 *) & p, 4 + length); +} + +static inline int +read_block(u8 offset, u8 length, u8 * buf) +{ + static read_block_t p; + static report_block_t q; + int ret; + + p.header = READ_BLOCK; + p.offset = offset; + p.length = length; + p.LRC = calculate_LRC((u8 *) & p, sizeof(p) - 1); + + if ((ret = spi_xmt((u8 *) & p, sizeof(p))) < 0) + return ret; + + if ((ret = spi_rcv((u8 *) & q, sizeof(q))) < 0) + return ret; + + if (ret == 0) + return -E_ZERO_BYTES; + + if (ret != sizeof(4 + q.length)) + return -E_PKT_SZ; + + if (q.header != REPORT_BLOCK) + return -E_BAD_HEADER; + + if (q.block[q.length] != calculate_LRC((u8 *) & q, 3 + q.length)) + return -E_BAD_LRC; + + if (length != q.length) + return -E_PKT_SZ; + + memcpy(buf, &q.block, length); + + return 0; +} + +#ifdef INNOVATOR_KEYB_DEBUG +static void +ctrl_dump_regs(void) +{ + int i; + int n; + + for (i = 0; i < 256; i += 8) { + read_block(i, 16, buffer); + mdelay(1); + } +} +#endif + +/*****************************************************************************/ + +static void +process_pointing_report(struct innovator_hid_dev *hid, u8 * buffer) +{ + static int prev_x, prev_y, prev_btn; + int x, y, btn; + hid->keyboard = input_allocate_device(); + hid->mouse = input_allocate_device(); + + if (buffer[1] & (1 << 3)) { + /* relative pointing device report */ + x = buffer[2]; + y = buffer[3]; + + /* check the sign and convert from 2's complement if negative */ + if (buffer[1] & (1<<4)) + x = ~(-x) - 255; + + /* input driver wants -y */ + if (buffer[1] & (1<<5)) + y = -(~(-y) - 255); + else + y = -y; + + input_report_key(hid->mouse, + BTN_LEFT, buffer[1] & (1<<0)); + input_report_key(hid->mouse, + BTN_RIGHT, buffer[1] & (1<<1)); + input_report_key(hid->mouse, + BTN_MIDDLE, buffer[1] & (1<<2)); + input_report_rel(hid->mouse, REL_X, x); + input_report_rel(hid->mouse, REL_Y, y); + } else { + /* REVISIT: Does this work? */ + /* absolute pointing device report */ + x = buffer[2] + ((buffer[1] & X_MSB_MASK) << X_MSB_SHIFT); + y = buffer[3] + ((buffer[1] & Y_MSB_MASK) << Y_MSB_SHIFT); + btn = buffer[1] & (1<<0); + + if ((prev_x == x) && (prev_y == y) + && (prev_btn == btn)) + return; + + input_report_key(hid->mouse, BTN_LEFT, btn); + input_report_abs(hid->mouse, ABS_X, x); + input_report_abs(hid->mouse, ABS_Y, y); + prev_x = x; + prev_y = y; + prev_btn = btn; + } + input_sync(hid->mouse); + dbg("HID X: %d Y: %d Functions: %x\n", x, y, buffer[1]); +} + +/* + * Reference [1], Appendix A, Semtech standard PS/2 key number definitions, + * pgs. A-1 through A-3. The following table lists standard PS/2 key numbers + * used by the Juno® 01 keyboard manager. + * + * NOTES: + * 1. The following table indices are E0 codes which require special handling: + * 53..62, 77..78, 94, 96, 100, 102..104, 108..110 + * 2. The following table indices are E1 codes which require special handling: + * 101 + */ + +static unsigned char usar2scancode[128] = { + 0x00, 0x29, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x2b, 0x1e, 0x1f, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x1c, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, + 0x33, 0x34, 0x35, 0x39, 0x01, 0x52, 0x53, 0x4b, + 0x47, 0x4f, 0x48, 0x50, 0x49, 0x51, 0x4d, 0x37, + 0x4e, 0x4f, 0x50, 0x51, 0x4b, 0x4c, 0x4d, 0x47, + 0x48, 0x49, 0x52, 0x53, 0x4a, 0x1c, 0x35, 0x3b, + 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, + 0x44, 0x57, 0x58, 0x2a, 0x36, 0x38, 0x38, 0x1d, + 0x1d, 0x3a, 0x45, 0x46, 0x2a, 0x1d, 0x5b, 0x5c, + 0x5d, 0xff, 0x00, 0x00, 0x5e, 0x5f, 0x63, 0x70, + 0x7b, 0x79, 0x7d, 0x73, 0x5b, 0x5c, 0x5d, 0x63, + 0x65, 0x66, 0x68, 0x69, 0x6b, 0x56, 0x54, 0x00 +}; + +/* + * The following are bit masks used to encode E0 scan codes which + * require special handling. However, scan codes 100 and 101 are + * excludable here since they each require unique multi-byte scan + * code translations and are therefore dealt with individually via + * handle_print_scr() and handle_pause() respectively below. + */ + +static unsigned long int e0_codes1 = 0x030003ff; /* scan codes 53..84 */ +static unsigned long int e0_codes2 = 0x038e0a00; /* scan codes 85..116 */ + +static void +handle_print_scr(int up) +{ + if (up) { + input_report_key(hid->keyboard, 0xe0, 1); + input_report_key(hid->keyboard, 0xb7, 1); + input_report_key(hid->keyboard, 0xe0, 1); + input_report_key(hid->keyboard, 0xaa, 1); + } else { + input_report_key(hid->keyboard, 0xe0, 0); + input_report_key(hid->keyboard, 0x2a, 0); + input_report_key(hid->keyboard, 0xe0, 0); + input_report_key(hid->keyboard, 0x37, 0); + } +} + +static void +handle_pause(void) +{ + input_report_key(hid->keyboard, 0xe1, 0); + input_report_key(hid->keyboard, 0x1d, 0); + input_report_key(hid->keyboard, 0x45, 0); + input_report_key(hid->keyboard, 0xe1, 0); + input_report_key(hid->keyboard, 0x9d, 0); + input_report_key(hid->keyboard, 0xc5, 0); +} + +static void +process_keyboard_report(struct innovator_hid_dev *hid, u8 * buffer) +{ + unsigned char ch = buffer[1] & 0x7f; + int up = buffer[1] & 0x80 ? 1 : 0; + int is_e0 = 0; + hid->keyboard = input_allocate_device(); + hid->mouse = input_allocate_device(); + + if ((ch == 106) || (ch == 107)) + return; /* no code */ + + if (ch == 100) { + handle_print_scr(up); + return; + } + + if (ch == 101) { + handle_pause(); + return; + } + + if ((ch >= 53) && (ch <= 84)) { + /* first block of e0 codes */ + is_e0 = e0_codes1 & (1 << (ch - 53)); + } else if ((ch >= 85) && (ch <= 116)) { + /* second block of e0 codes */ + is_e0 = e0_codes2 & (1 << (ch - 85)); + } + + if (is_e0) { + input_report_key(hid->keyboard, 0xe0, !up); + } + input_report_key(hid->keyboard, usar2scancode[ch], !up); + input_sync(hid->keyboard); +} + +static irqreturn_t +innovator_hid_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + if (ATN()) { + disable_irq(OMAP1510_INT_FPGA_ATN); + tasklet_schedule(&hid_tasklet); + } + return IRQ_HANDLED; +} + +static void +do_hid_tasklet(unsigned long unused) +{ + int ret; + if ((ret = report_async(buffer, 256)) == -1) { + dbg("Error: Bad Juno return value: %d\n", ret); + } else if (ret == KEYBOARD_REPORT) { + process_keyboard_report(hid, buffer); + } else if (ret == POINTING_REPORT) { + process_pointing_report(hid, buffer); + } else { + dbg("ERROR: bad report\n"); + } + enable_irq(OMAP1510_INT_FPGA_ATN); +} + +static int +innovator_hid_open(struct input_dev *dev) +{ + if (hid->open++) + return 0; + + if (request_irq(OMAP1510_INT_FPGA_ATN, (void *) innovator_hid_interrupt, + IRQF_DISABLED, PFX, hid) < 0) + return -EINVAL; + + return 0; +} + +static void +innovator_hid_close(struct input_dev *dev) +{ + if (!--hid->open) + return; + + if (hid == NULL) + return; + + kfree(hid); +} + +static int innovator_ps2_remove(struct device *dev) +{ + return 0; +} + +static void innovator_ps2_device_release(struct device *dev) +{ + /* Nothing */ +} + +static int innovator_ps2_suspend(struct device *dev, pm_message_t state) +{ + u8 pmcomm = 0; + + /* + * Set SUS_STATE in REG_PM_COMM (Page 0 R0). This will cause + * PM_MOD bits of REG_PM_STATUS to show suspended state, + * but the SUS_STAT bit of REG_PM_STATUS will continue to + * reflect the state of the _HSUS pin. + */ + + if (write_reg(REG_PAGENO, 0) < 0) + printk("ps2 suspend: write_reg REG_PAGENO error\n"); + + if (read_reg(REG_PM_COMM, &pmcomm) < 0) + printk("ps2 suspend: read_reg REG_PM_COMM error\n"); + + if (write_reg(REG_PM_COMM, pmcomm | SUS_STATE) < 0) + printk("ps2 suspend: write_reg REG_PM_COMM error\n"); + + return 0; +} + +static int innovator_ps2_resume(struct device *dev) +{ + u8 pmcomm = 0; + + /* + * Clear SUS_STATE from REG_PM_COMM (Page 0 R0). + */ + + if (write_reg(REG_PAGENO, 0) < 0) + printk("ps2 resume: write_reg REG_PAGENO error\n"); + + if (read_reg(REG_PM_COMM, &pmcomm) < 0) + printk("ps2 resume: read_reg REG_PM_COMM error\n"); + + if (write_reg(REG_PM_COMM, pmcomm & ~SUS_STATE) < 0) + printk("ps2 resume: write_reg REG_PM_COMM error\n"); + + return 0; +} + +static struct device_driver innovator_ps2_driver = { + .name = "innovator_ps2", + .bus = &platform_bus_type, + .remove = innovator_ps2_remove, + .suspend = innovator_ps2_suspend, + .resume = innovator_ps2_resume, +}; + +static struct platform_device innovator_ps2_device = { + .name = "ps2", + .id = -1, + .dev = { + .driver = &innovator_ps2_driver, + .release = innovator_ps2_device_release, + }, +}; + +static int __init +innovator_kbd_init(void) +{ + int i; + info("Innovator PS/2 keyboard/mouse driver v1.0\n"); + + innovator_fpga_hid_reset(); + + if ((hid = kmalloc(sizeof(struct innovator_hid_dev), + GFP_KERNEL)) == NULL) { + warn("unable to allocate space for HID device\n"); + return -ENOMEM; + } + + /* setup the mouse */ + memset(hid, 0, sizeof(struct innovator_hid_dev)); + hid->mouse = input_allocate_device(); + hid->mouse->evbit[0] = BIT(EV_KEY) | BIT(EV_REL); + hid->mouse->keybit[BIT_WORD(BTN_MOUSE)] = + BIT(BTN_LEFT) | BIT(BTN_RIGHT) | + BIT(BTN_MIDDLE) | BIT(BTN_TOUCH); + hid->mouse->relbit[0] = BIT(REL_X) | BIT(REL_Y); + hid->mouse->private = hid; + hid->mouse->open = innovator_hid_open; + hid->mouse->close = innovator_hid_close; + hid->mouse->name = "innovator_mouse"; + hid->mouse->id.bustype = 0; + hid->mouse->id.vendor = 0; + hid->mouse->id.product = 0; + hid->mouse->id.version = 0; + hid->keyboard = input_allocate_device(); + hid->keyboard->evbit[0] = BIT(EV_KEY) | BIT(EV_REP); + hid->keyboard->keycodesize = sizeof(unsigned char); + hid->keyboard->keycodemax = ARRAY_SIZE(usar2scancode); + for(i = 0; i < 128; i++) + set_bit(usar2scancode[i], hid->keyboard->keybit); + hid->keyboard->private = hid; + hid->keyboard->open = innovator_hid_open; + hid->keyboard->close = innovator_hid_close; + hid->keyboard->name = "innovator_keyboard"; + hid->keyboard->id.bustype = 0; + hid->keyboard->id.vendor = 0; + hid->keyboard->id.product = 0; + hid->keyboard->id.version = 0; + input_register_device(hid->mouse); + input_register_device(hid->keyboard); + innovator_hid_open(hid->mouse); + innovator_hid_open(hid->keyboard); + + if (driver_register(&innovator_ps2_driver) != 0) + printk(KERN_ERR "Driver register failed for innovator_ps2\n"); + + if (platform_device_register(&innovator_ps2_device) != 0) { + printk(KERN_ERR "Device register failed for ps2\n"); + driver_unregister(&innovator_ps2_driver); + } + +#ifdef INNOVATOR_KEYB_DEBUG + ctrl_dump_regs(); +#endif + return 0; +} + +static void __exit +innovator_kbd_exit(void) +{ + input_unregister_device(hid->mouse); + input_unregister_device(hid->keyboard); + free_irq(OMAP1510_INT_FPGA_ATN, hid); + if (hid != NULL) + kfree(hid); + driver_unregister(&innovator_ps2_driver); + platform_device_unregister(&innovator_ps2_device); + return; +} + +module_init(innovator_kbd_init); +module_exit(innovator_kbd_exit); + +MODULE_AUTHOR("George G. Davis "); +MODULE_DESCRIPTION("Innovator PS/2 Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/keyboard/lm8323.c b/drivers/input/keyboard/lm8323.c new file mode 100644 index 00000000000..a95ea033300 --- /dev/null +++ b/drivers/input/keyboard/lm8323.c @@ -0,0 +1,928 @@ +/* + * drivers/i2c/chips/lm8323.c + * + * Copyright (C) 2007-2009 Nokia Corporation + * + * Written by Daniel Stone + * Timo O. Karjalainen + * + * Updated by Felipe Balbi + * + * 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 (version 2 of the License only). + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Commands to send to the chip. */ +#define LM8323_CMD_READ_ID 0x80 /* Read chip ID. */ +#define LM8323_CMD_WRITE_CFG 0x81 /* Set configuration item. */ +#define LM8323_CMD_READ_INT 0x82 /* Get interrupt status. */ +#define LM8323_CMD_RESET 0x83 /* Reset, same as external one */ +#define LM8323_CMD_WRITE_PORT_SEL 0x85 /* Set GPIO in/out. */ +#define LM8323_CMD_WRITE_PORT_STATE 0x86 /* Set GPIO pullup. */ +#define LM8323_CMD_READ_PORT_SEL 0x87 /* Get GPIO in/out. */ +#define LM8323_CMD_READ_PORT_STATE 0x88 /* Get GPIO pullup. */ +#define LM8323_CMD_READ_FIFO 0x89 /* Read byte from FIFO. */ +#define LM8323_CMD_RPT_READ_FIFO 0x8a /* Read FIFO (no increment). */ +#define LM8323_CMD_SET_ACTIVE 0x8b /* Set active time. */ +#define LM8323_CMD_READ_ERR 0x8c /* Get error status. */ +#define LM8323_CMD_READ_ROTATOR 0x8e /* Read rotator status. */ +#define LM8323_CMD_SET_DEBOUNCE 0x8f /* Set debouncing time. */ +#define LM8323_CMD_SET_KEY_SIZE 0x90 /* Set keypad size. */ +#define LM8323_CMD_READ_KEY_SIZE 0x91 /* Get keypad size. */ +#define LM8323_CMD_READ_CFG 0x92 /* Get configuration item. */ +#define LM8323_CMD_WRITE_CLOCK 0x93 /* Set clock config. */ +#define LM8323_CMD_READ_CLOCK 0x94 /* Get clock config. */ +#define LM8323_CMD_PWM_WRITE 0x95 /* Write PWM script. */ +#define LM8323_CMD_START_PWM 0x96 /* Start PWM engine. */ +#define LM8323_CMD_STOP_PWM 0x97 /* Stop PWM engine. */ + +/* Interrupt status. */ +#define INT_KEYPAD 0x01 /* Key event. */ +#define INT_ROTATOR 0x02 /* Rotator event. */ +#define INT_ERROR 0x08 /* Error: use CMD_READ_ERR. */ +#define INT_NOINIT 0x10 /* Lost configuration. */ +#define INT_PWM1 0x20 /* PWM1 stopped. */ +#define INT_PWM2 0x40 /* PWM2 stopped. */ +#define INT_PWM3 0x80 /* PWM3 stopped. */ + +/* Errors (signalled by INT_ERROR, read with CMD_READ_ERR). */ +#define ERR_BADPAR 0x01 /* Bad parameter. */ +#define ERR_CMDUNK 0x02 /* Unknown command. */ +#define ERR_KEYOVR 0x04 /* Too many keys pressed. */ +#define ERR_FIFOOVER 0x40 /* FIFO overflow. */ + +/* Configuration keys (CMD_{WRITE,READ}_CFG). */ +#define CFG_MUX1SEL 0x01 /* Select MUX1_OUT input. */ +#define CFG_MUX1EN 0x02 /* Enable MUX1_OUT. */ +#define CFG_MUX2SEL 0x04 /* Select MUX2_OUT input. */ +#define CFG_MUX2EN 0x08 /* Enable MUX2_OUT. */ +#define CFG_PSIZE 0x20 /* Package size (must be 0). */ +#define CFG_ROTEN 0x40 /* Enable rotator. */ + +/* Clock settings (CMD_{WRITE,READ}_CLOCK). */ +#define CLK_RCPWM_INTERNAL 0x00 +#define CLK_RCPWM_EXTERNAL 0x03 +#define CLK_SLOWCLKEN 0x08 /* Enable 32.768kHz clock. */ +#define CLK_SLOWCLKOUT 0x40 /* Enable slow pulse output. */ + +/* The possible addresses corresponding to CONFIG1 and CONFIG2 pin wirings. */ +#define LM8323_I2C_ADDR00 (0x84 >> 1) /* 1000 010x */ +#define LM8323_I2C_ADDR01 (0x86 >> 1) /* 1000 011x */ +#define LM8323_I2C_ADDR10 (0x88 >> 1) /* 1000 100x */ +#define LM8323_I2C_ADDR11 (0x8A >> 1) /* 1000 101x */ + +/* Key event fifo length */ +#define LM8323_FIFO_LEN 15 + +/* Commands for PWM engine; feed in with PWM_WRITE. */ +/* Load ramp counter from duty cycle field (range 0 - 0xff). */ +#define PWM_SET(v) (0x4000 | ((v) & 0xff)) +/* Go to start of script. */ +#define PWM_GOTOSTART 0x0000 +/* + * Stop engine (generates interrupt). If reset is 1, clear the program + * counter, else leave it. + */ +#define PWM_END(reset) (0xc000 | (!!(reset) << 11)) +/* + * Ramp. If s is 1, divide clock by 512, else divide clock by 16. + * Take t clock scales (up to 63) per step, for n steps (up to 126). + * If u is set, ramp up, else ramp down. + */ +#define PWM_RAMP(s, t, n, u) ((!!(s) << 14) | ((t) & 0x3f) << 8 | \ + ((n) & 0x7f) | ((u) ? 0 : 0x80)) +/* + * Loop (i.e. jump back to pos) for a given number of iterations (up to 63). + * If cnt is zero, execute until PWM_END is encountered. + */ +#define PWM_LOOP(cnt, pos) (0xa000 | (((cnt) & 0x3f) << 7) | \ + ((pos) & 0x3f)) +/* + * Wait for trigger. Argument is a mask of channels, shifted by the channel + * number, e.g. 0xa for channels 3 and 1. Note that channels are numbered + * from 1, not 0. + */ +#define PWM_WAIT_TRIG(chans) (0xe000 | (((chans) & 0x7) << 6)) +/* Send trigger. Argument is same as PWM_WAIT_TRIG. */ +#define PWM_SEND_TRIG(chans) (0xe000 | ((chans) & 0x7)) + +struct lm8323_pwm { + int id; + int enabled; + int fade_time; + int brightness; + int desired_brightness; + int running; + /* pwm lock */ + struct mutex lock; + struct work_struct work; + struct led_classdev cdev; +}; + +struct lm8323_chip { + /* device lock */ + struct mutex lock; + struct i2c_client *client; + struct work_struct work; + struct input_dev *idev; + unsigned kp_enabled:1; + unsigned pm_suspend:1; + unsigned keys_down; + char phys[32]; + s16 keymap[LM8323_KEYMAP_SIZE]; + int size_x; + int size_y; + int debounce_time; + int active_time; + struct lm8323_pwm pwm1; + struct lm8323_pwm pwm2; + struct lm8323_pwm pwm3; +}; + +#define client_to_lm8323(c) container_of(c, struct lm8323_chip, client) +#define dev_to_lm8323(d) container_of(d, struct lm8323_chip, client->dev) +#define work_to_lm8323(w) container_of(w, struct lm8323_chip, work) +#define cdev_to_pwm(c) container_of(c, struct lm8323_pwm, cdev) +#define work_to_pwm(w) container_of(w, struct lm8323_pwm, work) + +static struct lm8323_chip *pwm_to_lm8323(struct lm8323_pwm *pwm) +{ + switch (pwm->id) { + case 1: + return container_of(pwm, struct lm8323_chip, pwm1); + case 2: + return container_of(pwm, struct lm8323_chip, pwm2); + case 3: + return container_of(pwm, struct lm8323_chip, pwm3); + default: + return NULL; + } +} + +#define LM8323_MAX_DATA 8 + +/* + * To write, we just access the chip's address in write mode, and dump the + * command and data out on the bus. The command byte and data are taken as + * sequential u8s out of varargs, to a maximum of LM8323_MAX_DATA. + */ +static int lm8323_write(struct lm8323_chip *lm, int len, ...) +{ + int ret, i; + va_list ap; + u8 data[LM8323_MAX_DATA]; + + va_start(ap, len); + + if (unlikely(len > LM8323_MAX_DATA)) { + dev_err(&lm->client->dev, "tried to send %d bytes\n", len); + va_end(ap); + return 0; + } + + for (i = 0; i < len; i++) + data[i] = va_arg(ap, int); + + va_end(ap); + + /* + * If the host is asleep while we send the data, we can get a NACK + * back while it wakes up, so try again, once. + */ + ret = i2c_master_send(lm->client, data, len); + if (unlikely(ret == -EREMOTEIO)) + ret = i2c_master_send(lm->client, data, len); + if (unlikely(ret != len)) + dev_err(&lm->client->dev, "sent %d bytes of %d total\n", + len, ret); + + return ret; +} + +/* + * To read, we first send the command byte to the chip and end the transaction, + * then access the chip in read mode, at which point it will send the data. + */ +static int lm8323_read(struct lm8323_chip *lm, u8 cmd, u8 *buf, int len) +{ + int ret; + + /* + * If the host is asleep while we send the byte, we can get a NACK + * back while it wakes up, so try again, once. + */ + ret = i2c_master_send(lm->client, &cmd, 1); + if (unlikely(ret == -EREMOTEIO)) + ret = i2c_master_send(lm->client, &cmd, 1); + if (unlikely(ret != 1)) { + dev_err(&lm->client->dev, "sending read cmd 0x%02x failed\n", + cmd); + return 0; + } + + ret = i2c_master_recv(lm->client, buf, len); + if (unlikely(ret != len)) + dev_err(&lm->client->dev, "wanted %d bytes, got %d\n", + len, ret); + + return ret; +} + +/* + * Set the chip active time (idle time before it enters halt). + */ +static void lm8323_set_active_time(struct lm8323_chip *lm, int time) +{ + lm8323_write(lm, 2, LM8323_CMD_SET_ACTIVE, time >> 2); +} + +/* + * The signals are AT-style: the low 7 bits are the keycode, and the top + * bit indicates the state (1 for down, 0 for up). + */ +static inline u8 lm8323_whichkey(u8 event) +{ + return event & 0x7f; +} + +static inline int lm8323_ispress(u8 event) +{ + return (event & 0x80) ? 1 : 0; +} + +static void process_keys(struct lm8323_chip *lm) +{ + u8 event; + u8 key_fifo[LM8323_FIFO_LEN + 1]; + int old_keys_down = lm->keys_down; + int ret; + int i = 0; + + /* + * Read all key events from the FIFO at once. Next READ_FIFO clears the + * FIFO even if we didn't read all events previously. + */ + ret = lm8323_read(lm, LM8323_CMD_READ_FIFO, key_fifo, LM8323_FIFO_LEN); + + if (ret < 0) { + dev_err(&lm->client->dev, "Failed reading fifo \n"); + return; + } + key_fifo[ret] = 0; + + while ((event = key_fifo[i])) { + u8 key = lm8323_whichkey(event); + int isdown = lm8323_ispress(event); + s16 keycode = lm->keymap[key]; + + if (likely(keycode > 0)) { + dev_vdbg(&lm->client->dev, "key 0x%02x %s\n", key, + isdown ? "down" : "up"); + if (likely(lm->kp_enabled)) { + input_report_key(lm->idev, keycode, isdown); + input_sync(lm->idev); + } + if (isdown) + lm->keys_down++; + else + lm->keys_down--; + } else { + dev_err(&lm->client->dev, "keycode 0x%02x not mapped " + "to any key\n", key); + } + i++; + } + + /* + * Errata: We need to ensure that the chip never enters halt mode + * during a keypress, so set active time to 0. When it's released, + * we can enter halt again, so set the active time back to normal. + */ + if (!old_keys_down && lm->keys_down) + lm8323_set_active_time(lm, 0); + if (old_keys_down && !lm->keys_down) + lm8323_set_active_time(lm, lm->active_time); +} + +static void lm8323_process_error(struct lm8323_chip *lm) +{ + u8 error; + + if (lm8323_read(lm, LM8323_CMD_READ_ERR, &error, 1) == 1) { + if (error & ERR_FIFOOVER) + dev_vdbg(&lm->client->dev, "fifo overflow!\n"); + if (error & ERR_KEYOVR) + dev_vdbg(&lm->client->dev, + "more than two keys pressed\n"); + if (error & ERR_CMDUNK) + dev_vdbg(&lm->client->dev, + "unknown command submitted\n"); + if (error & ERR_BADPAR) + dev_vdbg(&lm->client->dev, "bad command parameter\n"); + } +} + +static void lm8323_reset(struct lm8323_chip *lm) +{ + /* The docs say we must pass 0xAA as the data byte. */ + lm8323_write(lm, 2, LM8323_CMD_RESET, 0xAA); +} + +static int lm8323_configure(struct lm8323_chip *lm) +{ + int keysize = (lm->size_x << 4) | lm->size_y; + int clock = (CLK_SLOWCLKEN | CLK_RCPWM_EXTERNAL); + int debounce = lm->debounce_time >> 2; + int active = lm->active_time >> 2; + + /* + * Active time must be greater than the debounce time: if it's + * a close-run thing, give ourselves a 12ms buffer. + */ + if (debounce >= active) + active = debounce + 3; + + lm8323_write(lm, 2, LM8323_CMD_WRITE_CFG, 0); + lm8323_write(lm, 2, LM8323_CMD_WRITE_CLOCK, clock); + lm8323_write(lm, 2, LM8323_CMD_SET_KEY_SIZE, keysize); + lm8323_set_active_time(lm, lm->active_time); + lm8323_write(lm, 2, LM8323_CMD_SET_DEBOUNCE, debounce); + lm8323_write(lm, 3, LM8323_CMD_WRITE_PORT_STATE, 0xff, 0xff); + lm8323_write(lm, 3, LM8323_CMD_WRITE_PORT_SEL, 0, 0); + + /* + * Not much we can do about errors at this point, so just hope + * for the best. + */ + + return 0; +} + +static void pwm_done(struct lm8323_pwm *pwm) +{ + mutex_lock(&pwm->lock); + pwm->running = 0; + if (pwm->desired_brightness != pwm->brightness) + schedule_work(&pwm->work); + mutex_unlock(&pwm->lock); +} + +/* + * Bottom half: handle the interrupt by posting key events, or dealing with + * errors appropriately. + */ +static void lm8323_work(struct work_struct *work) +{ + struct lm8323_chip *lm = work_to_lm8323(work); + u8 ints; + + mutex_lock(&lm->lock); + + while ((lm8323_read(lm, LM8323_CMD_READ_INT, &ints, 1) == 1) && ints) { + if (likely(ints & INT_KEYPAD)) + process_keys(lm); + if (ints & INT_ROTATOR) { + /* We don't currently support the rotator. */ + dev_vdbg(&lm->client->dev, "rotator fired\n"); + } + if (ints & INT_ERROR) { + dev_vdbg(&lm->client->dev, "error!\n"); + lm8323_process_error(lm); + } + if (ints & INT_NOINIT) { + dev_err(&lm->client->dev, "chip lost config; " + "reinitialising\n"); + lm8323_configure(lm); + } + if (ints & INT_PWM1) { + dev_vdbg(&lm->client->dev, "pwm1 engine completed\n"); + pwm_done(&lm->pwm1); + } + if (ints & INT_PWM2) { + dev_vdbg(&lm->client->dev, "pwm2 engine completed\n"); + pwm_done(&lm->pwm2); + } + if (ints & INT_PWM3) { + dev_vdbg(&lm->client->dev, "pwm3 engine completed\n"); + pwm_done(&lm->pwm3); + } + } + + mutex_unlock(&lm->lock); +} + +/* + * We cannot use I2C in interrupt context, so we just schedule work. + */ +static irqreturn_t lm8323_irq(int irq, void *data) +{ + struct lm8323_chip *lm = data; + + schedule_work(&lm->work); + + return IRQ_HANDLED; +} + +/* + * Read the chip ID. + */ +static int lm8323_read_id(struct lm8323_chip *lm, u8 *buf) +{ + int bytes; + + bytes = lm8323_read(lm, LM8323_CMD_READ_ID, buf, 2); + if (unlikely(bytes != 2)) + return -EIO; + + return 0; +} + +static void lm8323_write_pwm_one(struct lm8323_pwm *pwm, int pos, u16 cmd) +{ + struct lm8323_chip *lm = pwm_to_lm8323(pwm); + + lm8323_write(lm, 4, LM8323_CMD_PWM_WRITE, (pos << 2) | pwm->id, + (cmd & 0xff00) >> 8, cmd & 0x00ff); +} + +/* + * Write a script into a given PWM engine, concluding with PWM_END. + * If 'kill' is nonzero, the engine will be shut down at the end + * of the script, producing a zero output. Otherwise the engine + * will be kept running at the final PWM level indefinitely. + */ +static void lm8323_write_pwm(struct lm8323_pwm *pwm, int kill, + int len, const u16 *cmds) +{ + struct lm8323_chip *lm = pwm_to_lm8323(pwm); + int i; + + for (i = 0; i < len; i++) + lm8323_write_pwm_one(pwm, i, cmds[i]); + + lm8323_write_pwm_one(pwm, i++, PWM_END(kill)); + lm8323_write(lm, 2, LM8323_CMD_START_PWM, pwm->id); + pwm->running = 1; +} + +static void lm8323_pwm_work(struct work_struct *work) +{ + struct lm8323_pwm *pwm = work_to_pwm(work); + int div512, perstep, steps, hz, up, kill; + u16 pwm_cmds[3]; + int num_cmds = 0; + + mutex_lock(&pwm->lock); + + /* + * Do nothing if we're already at the requested level, + * or previous setting is not yet complete. In the latter + * case we will be called again when the previous PWM script + * finishes. + */ + if (pwm->running || pwm->desired_brightness == pwm->brightness) { + mutex_unlock(&pwm->lock); + return; + } + + kill = (pwm->desired_brightness == 0); + up = (pwm->desired_brightness > pwm->brightness); + steps = abs(pwm->desired_brightness - pwm->brightness); + + /* + * Convert time (in ms) into a divisor (512 or 16 on a refclk of + * 32768Hz), and number of ticks per step. + */ + if ((pwm->fade_time / steps) > (32768 / 512)) { + div512 = 1; + hz = 32768 / 512; + } else { + div512 = 0; + hz = 32768 / 16; + } + + perstep = (hz * pwm->fade_time) / (steps * 1000); + + if (perstep == 0) + perstep = 1; + else if (perstep > 63) + perstep = 63; + + while (steps) { + int s; + + s = min(126, steps); + pwm_cmds[num_cmds++] = PWM_RAMP(div512, perstep, s, up); + steps -= s; + } + + lm8323_write_pwm(pwm, kill, num_cmds, pwm_cmds); + + pwm->brightness = pwm->desired_brightness; + mutex_unlock(&pwm->lock); +} + +static void lm8323_pwm_set_brightness(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct lm8323_pwm *pwm = cdev_to_pwm(led_cdev); + struct lm8323_chip *lm = pwm_to_lm8323(pwm); + + mutex_lock(&pwm->lock); + pwm->desired_brightness = brightness; + mutex_unlock(&pwm->lock); + + if (in_interrupt()) { + schedule_work(&pwm->work); + } else { + /* + * Schedule PWM work as usual unless we are going into suspend + */ + mutex_lock(&lm->lock); + if (likely(!lm->pm_suspend)) + schedule_work(&pwm->work); + else + lm8323_pwm_work(&pwm->work); + mutex_unlock(&lm->lock); + } +} + +static ssize_t lm8323_pwm_show_time(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm8323_pwm *pwm = cdev_to_pwm(led_cdev); + + return sprintf(buf, "%d\n", pwm->fade_time); +} + +static ssize_t lm8323_pwm_store_time(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm8323_pwm *pwm = cdev_to_pwm(led_cdev); + int ret; + int time; + + ret = strict_strtoul(buf, 10, &time); + /* Numbers only, please. */ + if (ret) + return -EINVAL; + + pwm->fade_time = time; + + return strlen(buf); +} +static DEVICE_ATTR(time, 0644, lm8323_pwm_show_time, lm8323_pwm_store_time); + +static int init_pwm(struct lm8323_chip *lm, int id, struct device *dev, + const char *name) +{ + struct lm8323_pwm *pwm = NULL; + + BUG_ON(id > 3); + + switch (id) { + case 1: + pwm = &lm->pwm1; + break; + case 2: + pwm = &lm->pwm2; + break; + case 3: + pwm = &lm->pwm3; + break; + } + + pwm->id = id; + pwm->fade_time = 0; + pwm->brightness = 0; + pwm->desired_brightness = 0; + pwm->running = 0; + mutex_init(&pwm->lock); + if (name) { + pwm->cdev.name = name; + pwm->cdev.brightness_set = lm8323_pwm_set_brightness; + if (led_classdev_register(dev, &pwm->cdev) < 0) { + dev_err(dev, "couldn't register PWM %d\n", id); + return -1; + } + if (device_create_file(pwm->cdev.dev, + &dev_attr_time) < 0) { + dev_err(dev, "couldn't register time attribute\n"); + led_classdev_unregister(&pwm->cdev); + return -1; + } + INIT_WORK(&pwm->work, lm8323_pwm_work); + pwm->enabled = 1; + } else { + pwm->enabled = 0; + } + + return 0; +} + +static struct i2c_driver lm8323_i2c_driver; + +static ssize_t lm8323_show_disable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lm8323_chip *lm = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", !lm->kp_enabled); +} + +static ssize_t lm8323_set_disable(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct lm8323_chip *lm = dev_get_drvdata(dev); + int ret; + int i; + + ret = strict_strtoul(buf, 10, &i); + + mutex_lock(&lm->lock); + lm->kp_enabled = !i; + mutex_unlock(&lm->lock); + + return count; +} +static DEVICE_ATTR(disable_kp, 0644, lm8323_show_disable, lm8323_set_disable); + +static int lm8323_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct lm8323_platform_data *pdata; + struct input_dev *idev; + struct lm8323_chip *lm; + int i, err = 0; + unsigned long tmo; + u8 data[2]; + + lm = kzalloc(sizeof *lm, GFP_KERNEL); + if (!lm) + return -ENOMEM; + + i2c_set_clientdata(client, lm); + lm->client = client; + pdata = client->dev.platform_data; + if (!pdata || !pdata->size_x || !pdata->size_y) { + dev_err(&client->dev, "missing platform_data\n"); + err = -EINVAL; + goto fail2; + } + + lm->size_x = pdata->size_x; + if (lm->size_x > 8) { + dev_err(&client->dev, "invalid x size %d specified\n", + lm->size_x); + err = -EINVAL; + goto fail2; + } + + lm->size_y = pdata->size_y; + if (lm->size_y > 12) { + dev_err(&client->dev, "invalid y size %d specified\n", + lm->size_y); + err = -EINVAL; + goto fail2; + } + + dev_vdbg(&client->dev, "Keypad size: %d x %d\n", + lm->size_x, lm->size_y); + + lm->debounce_time = pdata->debounce_time; + lm->active_time = pdata->active_time; + + lm8323_reset(lm); + + /* Nothing's set up to service the IRQ yet, so just spin for max. + * 100ms until we can configure. */ + tmo = jiffies + msecs_to_jiffies(100); + while (lm8323_read(lm, LM8323_CMD_READ_INT, data, 1) == 1) { + if (data[0] & INT_NOINIT) + break; + + if (time_after(jiffies, tmo)) { + dev_err(&client->dev, + "timeout waiting for initialisation\n"); + break; + } + + msleep(1); + } + lm8323_configure(lm); + + /* If a true probe check the device */ + if (lm8323_read_id(lm, data) != 0) { + dev_err(&client->dev, "device not found\n"); + err = -ENODEV; + goto fail2; + } + + if (init_pwm(lm, 1, &client->dev, pdata->pwm1_name) < 0) + goto fail3; + if (init_pwm(lm, 2, &client->dev, pdata->pwm2_name) < 0) + goto fail4; + if (init_pwm(lm, 3, &client->dev, pdata->pwm3_name) < 0) + goto fail5; + + mutex_init(&lm->lock); + INIT_WORK(&lm->work, lm8323_work); + + err = request_irq(client->irq, lm8323_irq, + IRQF_TRIGGER_FALLING | IRQF_DISABLED, + "lm8323", lm); + if (err) { + dev_err(&client->dev, "could not get IRQ %d\n", client->irq); + goto fail6; + } + + device_init_wakeup(&client->dev, 1); + enable_irq_wake(client->irq); + + lm->kp_enabled = 1; + err = device_create_file(&client->dev, &dev_attr_disable_kp); + if (err < 0) + goto fail7; + + idev = input_allocate_device(); + if (!idev) { + err = -ENOMEM; + goto fail8; + } + + if (pdata->name) + idev->name = pdata->name; + else + idev->name = "LM8323 keypad"; + snprintf(lm->phys, sizeof(lm->phys), "%s/input-kp", dev_name(&client->dev)); + idev->phys = lm->phys; + + lm->keys_down = 0; + idev->evbit[0] = BIT(EV_KEY); + for (i = 0; i < LM8323_KEYMAP_SIZE; i++) { + if (pdata->keymap[i] > 0) + __set_bit(pdata->keymap[i], idev->keybit); + + lm->keymap[i] = pdata->keymap[i]; + } + + if (pdata->repeat) + __set_bit(EV_REP, idev->evbit); + + lm->idev = idev; + err = input_register_device(idev); + if (err) { + dev_dbg(&client->dev, "error registering input device\n"); + goto fail8; + } + + return 0; + +fail8: + device_remove_file(&client->dev, &dev_attr_disable_kp); +fail7: + free_irq(client->irq, lm); +fail6: + if (lm->pwm3.enabled) + led_classdev_unregister(&lm->pwm3.cdev); +fail5: + if (lm->pwm2.enabled) + led_classdev_unregister(&lm->pwm2.cdev); +fail4: + if (lm->pwm1.enabled) + led_classdev_unregister(&lm->pwm1.cdev); +fail3: +fail2: + kfree(lm); + return err; +} + +static int lm8323_remove(struct i2c_client *client) +{ + struct lm8323_chip *lm = i2c_get_clientdata(client); + + disable_irq_wake(client->irq); + free_irq(client->irq, lm); + cancel_work_sync(&lm->work); + input_unregister_device(lm->idev); + device_remove_file(&lm->client->dev, &dev_attr_disable_kp); + if (lm->pwm3.enabled) + led_classdev_unregister(&lm->pwm3.cdev); + if (lm->pwm2.enabled) + led_classdev_unregister(&lm->pwm2.cdev); + if (lm->pwm1.enabled) + led_classdev_unregister(&lm->pwm1.cdev); + kfree(lm); + + return 0; +} + +#ifdef CONFIG_PM +/* + * We don't need to explicitly suspend the chip, as it already switches off + * when there's no activity. + */ +static int lm8323_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct lm8323_chip *lm = i2c_get_clientdata(client); + + set_irq_wake(client->irq, 0); + disable_irq(client->irq); + + mutex_lock(&lm->lock); + lm->pm_suspend = 1; + mutex_unlock(&lm->lock); + + if (lm->pwm1.enabled) + led_classdev_suspend(&lm->pwm1.cdev); + if (lm->pwm2.enabled) + led_classdev_suspend(&lm->pwm2.cdev); + if (lm->pwm3.enabled) + led_classdev_suspend(&lm->pwm3.cdev); + + return 0; +} + +static int lm8323_resume(struct i2c_client *client) +{ + struct lm8323_chip *lm = i2c_get_clientdata(client); + + mutex_lock(&lm->lock); + lm->pm_suspend = 0; + mutex_unlock(&lm->lock); + + if (lm->pwm1.enabled) + led_classdev_resume(&lm->pwm1.cdev); + if (lm->pwm2.enabled) + led_classdev_resume(&lm->pwm2.cdev); + if (lm->pwm3.enabled) + led_classdev_resume(&lm->pwm3.cdev); + + enable_irq(client->irq); + set_irq_wake(client->irq, 1); + + return 0; +} +#else +#define lm8323_suspend NULL +#define lm8323_resume NULL +#endif + +static const struct i2c_device_id lm8323_id[] = { + { "lm8323", 0 }, + { } +}; + +static struct i2c_driver lm8323_i2c_driver = { + .driver = { + .name = "lm8323", + }, + .probe = lm8323_probe, + .remove = lm8323_remove, + .suspend = lm8323_suspend, + .resume = lm8323_resume, + .id_table = lm8323_id, +}; +MODULE_DEVICE_TABLE(i2c, lm8323_id); + +static int __init lm8323_init(void) +{ + return i2c_add_driver(&lm8323_i2c_driver); +} +module_init(lm8323_init); + +static void __exit lm8323_exit(void) +{ + i2c_del_driver(&lm8323_i2c_driver); +} +module_exit(lm8323_exit); + +MODULE_AUTHOR("Timo O. Karjalainen "); +MODULE_AUTHOR("Daniel Stone"); +MODULE_AUTHOR("Felipe Balbi "); +MODULE_DESCRIPTION("LM8323 keypad driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/input/keyboard/omap-keypad.c b/drivers/input/keyboard/omap-keypad.c index 058fa8b02c2..98da278a1cc 100644 --- a/drivers/input/keyboard/omap-keypad.c +++ b/drivers/input/keyboard/omap-keypad.c @@ -33,13 +33,15 @@ #include #include #include +#include #include +#include #include #include -#include #include #include #include +#include #include #undef NEW_BOARD_LEARNING_MODE @@ -60,6 +62,8 @@ struct omap_kp { unsigned int cols; unsigned long delay; unsigned int debounce; + int suspended; + spinlock_t suspend_lock; }; static DECLARE_TASKLET_DISABLED(kp_tasklet, omap_kp_tasklet, 0); @@ -96,6 +100,14 @@ static u8 get_row_gpio_val(struct omap_kp *omap_kp) static irqreturn_t omap_kp_interrupt(int irq, void *dev_id) { struct omap_kp *omap_kp = dev_id; + unsigned long flags; + + spin_lock_irqsave(&omap_kp->suspend_lock, flags); + if (omap_kp->suspended) { + spin_unlock_irqrestore(&omap_kp->suspend_lock, flags); + return IRQ_HANDLED; + } + spin_unlock_irqrestore(&omap_kp->suspend_lock, flags); /* disable keyboard interrupt and schedule for handling */ if (cpu_is_omap24xx()) { @@ -263,15 +275,29 @@ static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR, omap_kp_enable_show, omap_kp_enabl #ifdef CONFIG_PM static int omap_kp_suspend(struct platform_device *dev, pm_message_t state) { - /* Nothing yet */ + struct omap_kp *omap_kp = platform_get_drvdata(dev); + unsigned long flags; + spin_lock_irqsave(&omap_kp->suspend_lock, flags); + + /* + * Re-enable the interrupt in case it has been masked by the + * handler and a key is still pressed. We need the interrupt + * to wake us up from suspended. + */ + if (cpu_class_is_omap1()) + omap_writew(0, OMAP_MPUIO_BASE + OMAP_MPUIO_KBD_MASKIT); + + omap_kp->suspended = 1; + spin_unlock_irqrestore(&omap_kp->suspend_lock, flags); return 0; } static int omap_kp_resume(struct platform_device *dev) { - /* Nothing yet */ + struct omap_kp *omap_kp = platform_get_drvdata(dev); + omap_kp->suspended = 0; return 0; } #else @@ -284,7 +310,7 @@ static int __devinit omap_kp_probe(struct platform_device *pdev) struct omap_kp *omap_kp; struct input_dev *input_dev; struct omap_kp_platform_data *pdata = pdev->dev.platform_data; - int i, col_idx, row_idx, irq_idx, ret; + int i, col_idx = 0, row_idx = 0, irq_idx, ret; if (!pdata->rows || !pdata->cols || !pdata->keymap) { printk(KERN_ERR "No rows, cols or keymap from pdata\n"); @@ -301,7 +327,9 @@ static int __devinit omap_kp_probe(struct platform_device *pdev) platform_set_drvdata(pdev, omap_kp); + spin_lock_init(&omap_kp->suspend_lock); omap_kp->input = input_dev; + omap_kp->suspended = 0; /* Disable the interrupt for the MPUIO keyboard */ if (!cpu_is_omap24xx()) diff --git a/drivers/input/keyboard/tsc2301_kp.c b/drivers/input/keyboard/tsc2301_kp.c new file mode 100644 index 00000000000..0f2cb7f5a10 --- /dev/null +++ b/drivers/input/keyboard/tsc2301_kp.c @@ -0,0 +1,475 @@ +/* + * TSC2301 keypad driver + * + * Copyright (C) 2005-2006 Nokia Corporation + * + * Written by Jarkko Oikarinen + * Rewritten by Juha Yrjola + * + * 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 program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define TSC2301_KEYBOARD_PRODUCT_ID 0x0051 +#define TSC2301_KEYBOARD_PRODUCT_VERSION 0x0001 +#define TSC2301_DEBOUNCE_TIME_2MS 0x0000 +#define TSC2301_DEBOUNCE_TIME_10MS 0x0800 +#define TSC2301_DEBOUNCE_TIME_20MS 0x1000 +#define TSC2301_DEBOUNCE_TIME_50MS 0x1800 +#define TSC2301_DEBOUNCE_TIME_60MS 0x2000 +#define TSC2301_DEBOUNCE_TIME_80MS 0x2800 +#define TSC2301_DEBOUNCE_TIME_100MS 0x3000 +#define TSC2301_DEBOUNCE_TIME_120MS 0x3800 + +#define TSC2301_DEBOUNCE_TIME TSC2301_DEBOUNCE_TIME_20MS + +#define TSC2301_RELEASE_TIMEOUT 50 + +struct tsc2301_kp { + struct input_dev *idev; + char phys[32]; + spinlock_t lock; + struct mutex mutex; + struct timer_list timer; + u16 keys_pressed; + unsigned pending:1; + unsigned user_disabled:1; + unsigned disable_depth; + + struct spi_transfer read_xfer[4]; + struct spi_message read_msg; + + u16 data; + u16 mask; + + int irq; + s16 keymap[16]; +}; + +static inline int tsc2301_kp_disabled(struct tsc2301 *tsc) +{ + return tsc->kp->disable_depth != 0; +} + +static void tsc2301_kp_send_key_events(struct tsc2301 *tsc, + u16 prev_state, + u16 new_state) +{ + struct tsc2301_kp *kp = tsc->kp; + u16 common, released, pressed; + int i; + + common = prev_state & new_state; + released = common ^ prev_state; + pressed = common ^ new_state; + if (!released && !pressed) + return; + for (i = 0; i < 16 && (released || pressed); i++) { + if (released & 1) { + dev_dbg(&tsc->spi->dev, "key %d released\n", i); + input_report_key(kp->idev, kp->keymap[i], 0); + } + released >>= 1; + if (pressed & 1) { + dev_dbg(&tsc->spi->dev, "key %d pressed\n", i); + input_report_key(kp->idev, kp->keymap[i], 1); + } + pressed >>= 1; + } + input_sync(kp->idev); +} + +static inline void _filter_out(struct tsc2301 *tsc, u16 prev_state, + u16 *new_state, int row1, int row2, u8 rect_pat) +{ + u16 mask; + + mask = (rect_pat << (row1 * 4)) | (rect_pat << (row2 * 4)); + mask &= ~prev_state; + *new_state &= ~mask; + dev_dbg(&tsc->spi->dev, "filtering ghost keys %02x\n", mask); +} + +static void tsc2301_filter_ghost_keys(struct tsc2301 *tsc, u16 prev_state, + u16 *new_state) +{ + int row1, row2; + u16 key_map; + u16 row1_map; + static const u8 rect_pat[] = { + 0x3, 0x5, 0x9, 0x6, 0xa, 0xc, 0, + }; + + key_map = *new_state; + for (row1 = 0; row1 < 4; row1++) { + row1_map = (key_map >> (row1 * 4)) & 0xf; + if (!row1_map) + continue; + for (row2 = row1 + 1; row2 < 4; row2++) { + u16 rect_map = (key_map >> (row2 * 4)) & 0xf; + const u8 *rp; + + rect_map &= row1_map; + if (!rect_map) + continue; + for (rp = rect_pat; *rp; rp++) + if ((rect_map & *rp) == *rp) + _filter_out(tsc, prev_state, new_state, + row1, row2, *rp); + } + } +} + +static void tsc2301_kp_timer(unsigned long arg) +{ + struct tsc2301 *tsc = (void *) arg; + struct tsc2301_kp *kp = tsc->kp; + unsigned long flags; + + tsc2301_kp_send_key_events(tsc, kp->keys_pressed, 0); + spin_lock_irqsave(&kp->lock, flags); + kp->keys_pressed = 0; + spin_unlock_irqrestore(&kp->lock, flags); +} + +static void tsc2301_kp_rx(void *arg) +{ + struct tsc2301 *tsc = arg; + struct tsc2301_kp *kp = tsc->kp; + unsigned long flags; + u16 kp_data; + + kp_data = kp->data; + dev_dbg(&tsc->spi->dev, "KP data %04x\n", kp_data); + + tsc2301_filter_ghost_keys(tsc, kp->keys_pressed, &kp_data); + tsc2301_kp_send_key_events(tsc, kp->keys_pressed, kp_data); + spin_lock_irqsave(&kp->lock, flags); + kp->keys_pressed = kp_data; + kp->pending = 0; + spin_unlock_irqrestore(&kp->lock, flags); +} + +static irqreturn_t tsc2301_kp_irq_handler(int irq, void *dev_id) +{ + struct tsc2301 *tsc = dev_id; + struct tsc2301_kp *kp = tsc->kp; + unsigned long flags; + int r; + + spin_lock_irqsave(&kp->lock, flags); + if (tsc2301_kp_disabled(tsc)) { + spin_unlock_irqrestore(&kp->lock, flags); + return IRQ_HANDLED; + } + kp->pending = 1; + spin_unlock_irqrestore(&kp->lock, flags); + mod_timer(&kp->timer, + jiffies + msecs_to_jiffies(TSC2301_RELEASE_TIMEOUT)); + r = spi_async(tsc->spi, &tsc->kp->read_msg); + if (r) + dev_err(&tsc->spi->dev, "kp: spi_async() failed"); + return IRQ_HANDLED; +} + +static void tsc2301_kp_start_scan(struct tsc2301 *tsc) +{ + tsc2301_write_reg(tsc, TSC2301_REG_KPMASK, tsc->kp->mask); + tsc2301_write_reg(tsc, TSC2301_REG_KEY, TSC2301_DEBOUNCE_TIME); +} + +static void tsc2301_kp_stop_scan(struct tsc2301 *tsc) +{ + tsc2301_write_reg(tsc, TSC2301_REG_KEY, 1 << 14); +} + +/* Must be called with the mutex held */ +static void tsc2301_kp_enable(struct tsc2301 *tsc) +{ + struct tsc2301_kp *kp = tsc->kp; + unsigned long flags; + + spin_lock_irqsave(&kp->lock, flags); + BUG_ON(!tsc2301_kp_disabled(tsc)); + if (--kp->disable_depth != 0) { + spin_unlock_irqrestore(&kp->lock, flags); + return; + } + spin_unlock_irqrestore(&kp->lock, flags); + + set_irq_type(kp->irq, IRQ_TYPE_EDGE_FALLING); + tsc2301_kp_start_scan(tsc); + enable_irq(kp->irq); +} + +/* Must be called with the mutex held */ +static int tsc2301_kp_disable(struct tsc2301 *tsc, int release_keys) +{ + struct tsc2301_kp *kp = tsc->kp; + unsigned long flags; + + spin_lock_irqsave(&kp->lock, flags); + if (kp->disable_depth++ != 0) { + spin_unlock_irqrestore(&kp->lock, flags); + goto out; + } + disable_irq_nosync(kp->irq); + set_irq_type(kp->irq, IRQ_TYPE_NONE); + spin_unlock_irqrestore(&kp->lock, flags); + + while (kp->pending) { + msleep(1); + } + + tsc2301_kp_stop_scan(tsc); +out: + if (!release_keys) + del_timer(&kp->timer); /* let timeout release keys */ + + return 0; +} + +/* The following workaround is needed for a HW bug triggered by the + * following: + * 1. keep any key pressed + * 2. disable keypad + * 3. release all keys + * 4. reenable keypad + * 5. disable touch screen controller + * + * After this the keypad scanner will get stuck in busy state and won't + * report any interrupts for further keypresses. One way to recover is to + * restart the keypad scanner whenever we enable / disable the + * touchscreen controller. + */ +void tsc2301_kp_restart(struct tsc2301 *tsc) +{ + if (!tsc2301_kp_disabled(tsc)) { + tsc2301_kp_start_scan(tsc); + } +} + +static ssize_t tsc2301_kp_disable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tsc2301 *tsc = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", tsc2301_kp_disabled(tsc) ? 1 : 0); +} + +static ssize_t tsc2301_kp_disable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct tsc2301 *tsc = dev_get_drvdata(dev); + struct tsc2301_kp *kp = tsc->kp; + char *endp; + int i; + + i = simple_strtoul(buf, &endp, 10); + i = i ? 1 : 0; + + mutex_lock(&kp->mutex); + if (i == kp->user_disabled) { + mutex_unlock(&kp->mutex); + return count; + } + kp->user_disabled = i; + + if (i) + tsc2301_kp_disable(tsc, 1); + else + tsc2301_kp_enable(tsc); + mutex_unlock(&kp->mutex); + + return count; +} + +static DEVICE_ATTR(disable_kp, 0664, tsc2301_kp_disable_show, + tsc2301_kp_disable_store); + +static const u16 tsc2301_kp_read_data = 0x8000 | TSC2301_REG_KPDATA; + +static void tsc2301_kp_setup_spi_xfer(struct tsc2301 *tsc) +{ + struct tsc2301_kp *kp = tsc->kp; + struct spi_message *m = &kp->read_msg; + struct spi_transfer *x = &kp->read_xfer[0]; + + spi_message_init(&kp->read_msg); + + x->tx_buf = &tsc2301_kp_read_data; + x->len = 2; + spi_message_add_tail(x, m); + x++; + + x->rx_buf = &kp->data; + x->len = 2; + spi_message_add_tail(x, m); + + m->complete = tsc2301_kp_rx; + m->context = tsc; +} + +#ifdef CONFIG_PM +int tsc2301_kp_suspend(struct tsc2301 *tsc) +{ + struct tsc2301_kp *kp = tsc->kp; + + mutex_lock(&kp->mutex); + tsc2301_kp_disable(tsc, 1); + mutex_unlock(&kp->mutex); + return 0; +} + +void tsc2301_kp_resume(struct tsc2301 *tsc) +{ + struct tsc2301_kp *kp = tsc->kp; + + mutex_lock(&kp->mutex); + tsc2301_kp_enable(tsc); + mutex_unlock(&kp->mutex); +} +#endif + +int __devinit tsc2301_kp_init(struct tsc2301 *tsc, + struct tsc2301_platform_data *pdata) +{ + struct input_dev *idev; + struct tsc2301_kp *kp; + int r, i; + u16 mask; + + if (pdata->keyb_int < 0) { + dev_err(&tsc->spi->dev, "need kbirq"); + return -EINVAL; + } + + kp = kzalloc(sizeof(*kp), GFP_KERNEL); + if (kp == NULL) + return -ENOMEM; + tsc->kp = kp; + + kp->irq = pdata->keyb_int; + spin_lock_init(&kp->lock); + mutex_init(&kp->mutex); + + init_timer(&kp->timer); + kp->timer.data = (unsigned long) tsc; + kp->timer.function = tsc2301_kp_timer; + + idev = input_allocate_device(); + if (idev == NULL) { + r = -ENOMEM; + goto err1; + } + if (pdata->keyb_name) + idev->name = pdata->keyb_name; + else + idev->name = "TSC2301 keypad"; + snprintf(kp->phys, sizeof(kp->phys), "%s/input-kp", dev_name(&tsc->spi->dev)); + idev->phys = kp->phys; + + mask = 0; + idev->evbit[0] = BIT(EV_KEY); + for (i = 0; i < 16; i++) { + if (pdata->keymap[i] > 0) { + set_bit(pdata->keymap[i], idev->keybit); + kp->keymap[i] = pdata->keymap[i]; + } else { + kp->keymap[i] = -1; + mask |= 1 << i; + } + } + + if (pdata->kp_rep) + set_bit(EV_REP, idev->evbit); + + kp->idev = idev; + + tsc2301_kp_setup_spi_xfer(tsc); + + r = device_create_file(&tsc->spi->dev, &dev_attr_disable_kp); + if (r < 0) + goto err2; + + tsc2301_kp_start_scan(tsc); + + /* IRQ mode 0 is faulty, it can cause the KBIRQ to get stuck. + * Mode 2 deasserts the IRQ at: + * - HW or SW reset + * - Setting SCS flag in REG_KEY register + * - Releasing all keys + * - Reading the REG_KPDATA + */ + tsc2301_write_kbc(tsc, 2); + + tsc2301_write_reg(tsc, TSC2301_REG_KPMASK, mask); + kp->mask = mask; + + set_irq_type(kp->irq, IRQ_TYPE_EDGE_FALLING); + + r = request_irq(kp->irq, tsc2301_kp_irq_handler, IRQF_SAMPLE_RANDOM, + "tsc2301-kp", tsc); + if (r < 0) { + dev_err(&tsc->spi->dev, "unable to get kbirq IRQ"); + goto err3; + } + set_irq_wake(kp->irq, 1); + + /* We need to read the register once..? */ + tsc2301_read_reg(tsc, TSC2301_REG_KPDATA); + + r = input_register_device(idev); + if (r < 0) { + dev_err(&tsc->spi->dev, "can't register keypad device\n"); + goto err4; + } + + return 0; + +err4: + free_irq(kp->irq, tsc); +err3: + tsc2301_kp_stop_scan(tsc); + device_remove_file(&tsc->spi->dev, &dev_attr_disable_kp); +err2: + input_free_device(kp->idev); +err1: + kfree(kp); + return r; +} + +void __devexit tsc2301_kp_exit(struct tsc2301 *tsc) +{ + struct tsc2301_kp *kp = tsc->kp; + + tsc2301_kp_disable(tsc, 1); + input_unregister_device(kp->idev); + free_irq(kp->irq, tsc); + device_remove_file(&tsc->spi->dev, &dev_attr_disable_kp); + + kfree(kp); +} diff --git a/drivers/input/keyboard/twl4030_keypad.c b/drivers/input/keyboard/twl4030_keypad.c new file mode 100644 index 00000000000..8bfd65015ac --- /dev/null +++ b/drivers/input/keyboard/twl4030_keypad.c @@ -0,0 +1,493 @@ +/* + * twl4030_keypad.c - driver for 8x8 keypad controller in twl4030 chips + * + * Copyright (C) 2007 Texas Instruments, Inc. + * Copyright (C) 2008 Nokia Corporation + * + * Code re-written for 2430SDP by: + * Syed Mohammed Khasim + * + * Initial Code: + * Manjunatha G K + * + * 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 program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include + + +/* + * The TWL4030 family chips include a keypad controller that supports + * up to an 8x8 switch matrix. The controller can issue system wakeup + * events, since it uses only the always-on 32KiHz oscillator, and has + * an internal state machine that decodes pressed keys, including + * multi-key combinations. + * + * This driver lets boards define what keycodes they wish to report for + * which scancodes, as part of the "struct twl4030_keypad_data" used in + * the probe() routine. + * + * See the TPS65950 documentation; that's the general availability + * version of the TWL5030 second generation part. + */ +#define MAX_ROWS 8 /* TWL4030 hard limit */ + +struct twl4030_keypad { + unsigned *keymap; + unsigned int keymapsize; + u16 kp_state[MAX_ROWS]; + unsigned n_rows; + unsigned n_cols; + unsigned irq; + + struct device *dbg_dev; + struct input_dev *input; +}; + +#define ROWCOL_MASK KEY(0xf, 0xf, 0) +#define KEYNUM_MASK ~PERSISTENT_KEY(0xf, 0xf) + +/*----------------------------------------------------------------------*/ + +/* arbitrary prescaler value 0..7 */ +#define PTV_PRESCALER 4 + +/* Register Offsets */ +#define KEYP_CTRL 0x00 +#define KEYP_DEB 0x01 +#define KEYP_LONG_KEY 0x02 +#define KEYP_LK_PTV 0x03 +#define KEYP_TIMEOUT_L 0x04 +#define KEYP_TIMEOUT_H 0x05 +#define KEYP_KBC 0x06 +#define KEYP_KBR 0x07 +#define KEYP_SMS 0x08 +#define KEYP_FULL_CODE_7_0 0x09 /* row 0 column status */ +#define KEYP_FULL_CODE_15_8 0x0a /* ... row 1 ... */ +#define KEYP_FULL_CODE_23_16 0x0b +#define KEYP_FULL_CODE_31_24 0x0c +#define KEYP_FULL_CODE_39_32 0x0d +#define KEYP_FULL_CODE_47_40 0x0e +#define KEYP_FULL_CODE_55_48 0x0f +#define KEYP_FULL_CODE_63_56 0x10 +#define KEYP_ISR1 0x11 +#define KEYP_IMR1 0x12 +#define KEYP_ISR2 0x13 +#define KEYP_IMR2 0x14 +#define KEYP_SIR 0x15 +#define KEYP_EDR 0x16 /* edge triggers */ +#define KEYP_SIH_CTRL 0x17 + +/* KEYP_CTRL_REG Fields */ +#define KEYP_CTRL_SOFT_NRST BIT(0) +#define KEYP_CTRL_SOFTMODEN BIT(1) +#define KEYP_CTRL_LK_EN BIT(2) +#define KEYP_CTRL_TOE_EN BIT(3) +#define KEYP_CTRL_TOLE_EN BIT(4) +#define KEYP_CTRL_RP_EN BIT(5) +#define KEYP_CTRL_KBD_ON BIT(6) + +/* KEYP_DEB, KEYP_LONG_KEY, KEYP_TIMEOUT_x*/ +#define KEYP_PERIOD_US(t, prescale) ((t) / (31 << (prescale + 1)) - 1) + +/* KEYP_LK_PTV_REG Fields */ +#define KEYP_LK_PTV_PTV_SHIFT 5 + +/* KEYP_{IMR,ISR,SIR} Fields */ +#define KEYP_IMR1_MIS BIT(3) +#define KEYP_IMR1_TO BIT(2) +#define KEYP_IMR1_LK BIT(1) +#define KEYP_IMR1_KP BIT(0) + +/* KEYP_EDR Fields */ +#define KEYP_EDR_KP_FALLING 0x01 +#define KEYP_EDR_KP_RISING 0x02 +#define KEYP_EDR_KP_BOTH 0x03 +#define KEYP_EDR_LK_FALLING 0x04 +#define KEYP_EDR_LK_RISING 0x08 +#define KEYP_EDR_TO_FALLING 0x10 +#define KEYP_EDR_TO_RISING 0x20 +#define KEYP_EDR_MIS_FALLING 0x40 +#define KEYP_EDR_MIS_RISING 0x80 + + +/*----------------------------------------------------------------------*/ + +static int twl4030_kpread(struct twl4030_keypad *kp, + u8 *data, u32 reg, u8 num_bytes) +{ + int ret; + + ret = twl4030_i2c_read(TWL4030_MODULE_KEYPAD, data, reg, num_bytes); + if (ret < 0) { + dev_warn(kp->dbg_dev, + "Couldn't read TWL4030: %X - ret %d[%x]\n", + reg, ret, ret); + return ret; + } + return ret; +} + +static int twl4030_kpwrite_u8(struct twl4030_keypad *kp, u8 data, u32 reg) +{ + int ret; + + ret = twl4030_i2c_write_u8(TWL4030_MODULE_KEYPAD, data, reg); + if (ret < 0) { + dev_warn(kp->dbg_dev, + "Could not write TWL4030: %X - ret %d[%x]\n", + reg, ret, ret); + return ret; + } + return ret; +} + +static int twl4030_find_key(struct twl4030_keypad *kp, int col, int row) +{ + int i, rc; + + rc = KEY(col, row, 0); + for (i = 0; i < kp->keymapsize; i++) + if ((kp->keymap[i] & ROWCOL_MASK) == rc) + return kp->keymap[i] & (KEYNUM_MASK | KEY_PERSISTENT); + + return -EINVAL; +} + +static inline u16 twl4030_col_xlate(struct twl4030_keypad *kp, u8 col) +{ + /* If all bits in a row are active for all coloumns then + * we have that row line connected to gnd. Mark this + * key on as if it was on matrix position n_cols (ie + * one higher than the size of the matrix). + */ + if (col == 0xFF) + return 1 << kp->n_cols; + else + return col & ((1 << kp->n_cols) - 1); +} + +static int twl4030_read_kp_matrix_state(struct twl4030_keypad *kp, u16 *state) +{ + u8 new_state[MAX_ROWS]; + int row; + int ret = twl4030_kpread(kp, + new_state, KEYP_FULL_CODE_7_0, kp->n_rows); + if (ret >= 0) { + for (row = 0; row < kp->n_rows; row++) + state[row] = twl4030_col_xlate(kp, new_state[row]); + } + return ret; +} + +static int twl4030_is_in_ghost_state(struct twl4030_keypad *kp, u16 *key_state) +{ + int i; + u16 check = 0; + + for (i = 0; i < kp->n_rows; i++) { + u16 col = key_state[i]; + + if ((col & check) && hweight16(col) > 1) + return 1; + check |= col; + } + + return 0; +} + +static void twl4030_kp_scan(struct twl4030_keypad *kp, int release_all) +{ + u16 new_state[MAX_ROWS]; + int col, row; + + if (release_all) + memset(new_state, 0, sizeof(new_state)); + else { + /* check for any changes */ + int ret = twl4030_read_kp_matrix_state(kp, new_state); + + if (ret < 0) /* panic ... */ + return; + if (twl4030_is_in_ghost_state(kp, new_state)) + return; + } + + /* check for changes and print those */ + for (row = 0; row < kp->n_rows; row++) { + int changed = new_state[row] ^ kp->kp_state[row]; + + if (!changed) + continue; + + for (col = 0; col < kp->n_cols; col++) { + int key; + + if (!(changed & (1 << col))) + continue; + + dev_dbg(kp->dbg_dev, "key [%d:%d] %s\n", row, col, + (new_state[row] & (1 << col)) ? + "press" : "release"); + + key = twl4030_find_key(kp, col, row); + if (key < 0) + dev_warn(kp->dbg_dev, + "Spurious key event %d-%d\n", + col, row); + else if (key & KEY_PERSISTENT) + continue; + else + input_report_key(kp->input, key, + new_state[row] & (1 << col)); + } + kp->kp_state[row] = new_state[row]; + } + input_sync(kp->input); +} + +/* + * Keypad interrupt handler + */ +static irqreturn_t do_kp_irq(int irq, void *_kp) +{ + struct twl4030_keypad *kp = _kp; + u8 reg; + int ret; + +#ifdef CONFIG_LOCKDEP + /* WORKAROUND for lockdep forcing IRQF_DISABLED on us, which + * we don't want and can't tolerate. Although it might be + * friendlier not to borrow this thread context... + */ + local_irq_enable(); +#endif + + /* Read & Clear TWL4030 pending interrupt */ + ret = twl4030_kpread(kp, ®, KEYP_ISR1, 1); + + /* Release all keys if I2C has gone bad or + * the KEYP has gone to idle state */ + if ((ret >= 0) && (reg & KEYP_IMR1_KP)) + twl4030_kp_scan(kp, 0); + else + twl4030_kp_scan(kp, 1); + + return IRQ_HANDLED; +} + +/* + * Registers keypad device with input subsystem + * and configures TWL4030 keypad registers + */ +static int __devinit twl4030_kp_probe(struct platform_device *pdev) +{ + u8 reg; + int i; + int ret = 0; + struct twl4030_keypad *kp; + struct twl4030_keypad_data *pdata = pdev->dev.platform_data; + + if (!pdata || !pdata->rows || !pdata->cols || !pdata->keymap + || pdata->rows > 8 || pdata->cols > 8) { + dev_err(&pdev->dev, "Invalid platform_data\n"); + return -EINVAL; + } + + kp = kzalloc(sizeof(*kp), GFP_KERNEL); + if (!kp) + return -ENOMEM; + + platform_set_drvdata(pdev, kp); + + /* Get the debug Device */ + kp->dbg_dev = &pdev->dev; + + kp->input = input_allocate_device(); + if (!kp->input) { + kfree(kp); + return -ENOMEM; + } + + kp->keymap = pdata->keymap; + kp->keymapsize = pdata->keymapsize; + kp->n_rows = pdata->rows; + kp->n_cols = pdata->cols; + kp->irq = platform_get_irq(pdev, 0); + + /* setup input device */ + __set_bit(EV_KEY, kp->input->evbit); + + /* Enable auto repeat feature of Linux input subsystem */ + if (pdata->rep) + __set_bit(EV_REP, kp->input->evbit); + + for (i = 0; i < kp->keymapsize; i++) + __set_bit(kp->keymap[i] & KEYNUM_MASK, + kp->input->keybit); + + kp->input->name = "TWL4030 Keypad"; + kp->input->phys = "twl4030_keypad/input0"; + kp->input->dev.parent = &pdev->dev; + + kp->input->id.bustype = BUS_HOST; + kp->input->id.vendor = 0x0001; + kp->input->id.product = 0x0001; + kp->input->id.version = 0x0003; + + kp->input->keycode = kp->keymap; + kp->input->keycodesize = sizeof(unsigned int); + kp->input->keycodemax = kp->keymapsize; + + ret = input_register_device(kp->input); + if (ret < 0) { + dev_err(kp->dbg_dev, + "Unable to register twl4030 keypad device\n"); + goto err2; + } + + /* Enable controller, with hardware decoding but not autorepeat */ + reg = KEYP_CTRL_SOFT_NRST | KEYP_CTRL_SOFTMODEN + | KEYP_CTRL_TOE_EN | KEYP_CTRL_KBD_ON; + ret = twl4030_kpwrite_u8(kp, reg, KEYP_CTRL); + if (ret < 0) + goto err3; + + /* NOTE: we could use sih_setup() here to package keypad + * event sources as four different IRQs ... but we don't. + */ + + /* Enable TO rising and KP rising and falling edge detection */ + reg = KEYP_EDR_KP_BOTH | KEYP_EDR_TO_RISING; + ret = twl4030_kpwrite_u8(kp, reg, KEYP_EDR); + if (ret < 0) + goto err3; + + /* Set PTV prescaler Field */ + reg = (PTV_PRESCALER << KEYP_LK_PTV_PTV_SHIFT); + ret = twl4030_kpwrite_u8(kp, reg, KEYP_LK_PTV); + if (ret < 0) + goto err3; + + /* Set key debounce time to 20 ms */ + i = KEYP_PERIOD_US(20000, PTV_PRESCALER); + ret = twl4030_kpwrite_u8(kp, i, KEYP_DEB); + if (ret < 0) + goto err3; + + /* Set timeout period to 100 ms */ + i = KEYP_PERIOD_US(200000, PTV_PRESCALER); + ret = twl4030_kpwrite_u8(kp, (i & 0xFF), KEYP_TIMEOUT_L); + if (ret < 0) + goto err3; + ret = twl4030_kpwrite_u8(kp, (i >> 8), KEYP_TIMEOUT_H); + if (ret < 0) + goto err3; + + /* Enable Clear-on-Read; disable remembering events that fire + * after the IRQ but before our handler acks (reads) them, + */ + reg = TWL4030_SIH_CTRL_COR_MASK | TWL4030_SIH_CTRL_PENDDIS_MASK; + ret = twl4030_kpwrite_u8(kp, reg, KEYP_SIH_CTRL); + if (ret < 0) + goto err3; + + /* initialize key state; irqs update it from here on */ + ret = twl4030_read_kp_matrix_state(kp, kp->kp_state); + if (ret < 0) + goto err3; + + /* + * This ISR will always execute in kernel thread context because of + * the need to access the TWL4030 over the I2C bus. + * + * NOTE: we assume this host is wired to TWL4040 INT1, not INT2 ... + */ + ret = request_irq(kp->irq, do_kp_irq, 0, pdev->name, kp); + if (ret < 0) { + dev_info(kp->dbg_dev, "request_irq failed for irq no=%d\n", + kp->irq); + goto err3; + } else { + /* Enable KP and TO interrupts now. */ + reg = (u8) ~(KEYP_IMR1_KP | KEYP_IMR1_TO); + ret = twl4030_kpwrite_u8(kp, reg, KEYP_IMR1); + if (ret < 0) + goto err5; + } + + return ret; +err5: + /* mask all events - we don't care about the result */ + (void) twl4030_kpwrite_u8(kp, 0xff, KEYP_IMR1); + free_irq(kp->irq, NULL); +err3: + input_unregister_device(kp->input); + kp->input = NULL; +err2: + input_free_device(kp->input); + kfree(kp); + return -ENODEV; +} + +static int __devexit twl4030_kp_remove(struct platform_device *pdev) +{ + struct twl4030_keypad *kp = platform_get_drvdata(pdev); + + free_irq(kp->irq, kp); + input_unregister_device(kp->input); + kfree(kp); + + return 0; +} + +/* + * NOTE: twl4030 are multi-function devices connected via I2C. + * So this device is a child of an I2C parent, thus it needs to + * support unplug/replug (which most platform devices don't). + */ + +MODULE_ALIAS("platform:twl4030_keypad"); + +static struct platform_driver twl4030_kp_driver = { + .probe = twl4030_kp_probe, + .remove = __devexit_p(twl4030_kp_remove), + .driver = { + .name = "twl4030_keypad", + .owner = THIS_MODULE, + }, +}; + +static int __init twl4030_kp_init(void) +{ + return platform_driver_register(&twl4030_kp_driver); +} +module_init(twl4030_kp_init); + +static void __exit twl4030_kp_exit(void) +{ + platform_driver_unregister(&twl4030_kp_driver); +} +module_exit(twl4030_kp_exit); + +MODULE_AUTHOR("Texas Instruments"); +MODULE_DESCRIPTION("TWL4030 Keypad Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 67e5553f699..6fa9e3847c3 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -193,6 +193,16 @@ config INPUT_CM109 To compile this driver as a module, choose M here: the module will be called cm109. +config INPUT_TWL4030_PWRBUTTON + tristate "TWL4030 Power button Driver" + depends on TWL4030_CORE + help + Say Y here if you want to enable power key reporting via the + TWL4030 family of chips. + + To compile this driver as a module, choose M here. The module will + be called twl4030_pwrbutton. + config INPUT_UINPUT tristate "User level driver support" help diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index bb62e6efacf..2fabcdba827 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_INPUT_YEALINK) += yealink.o obj-$(CONFIG_INPUT_CM109) += cm109.o obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o obj-$(CONFIG_INPUT_UINPUT) += uinput.o +obj-$(CONFIG_INPUT_TWL4030_PWRBUTTON) += twl4030-pwrbutton.o obj-$(CONFIG_INPUT_APANEL) += apanel.o obj-$(CONFIG_INPUT_SGI_BTNS) += sgi_btns.o obj-$(CONFIG_INPUT_PCF50633_PMU) += pcf50633-input.o diff --git a/drivers/input/misc/twl4030-pwrbutton.c b/drivers/input/misc/twl4030-pwrbutton.c new file mode 100644 index 00000000000..71508306684 --- /dev/null +++ b/drivers/input/misc/twl4030-pwrbutton.c @@ -0,0 +1,146 @@ +/** + * twl4030-pwrbutton.c - TWL4030 Power Button Input Driver + * + * Copyright (C) 2008-2009 Nokia Corporation + * + * Written by Peter De Schrijver + * Several fixes by Felipe Balbi + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define PWR_PWRON_IRQ (1 << 0) + +#define STS_HW_CONDITIONS 0xf + +static irqreturn_t powerbutton_irq(int irq, void *_pwr) +{ + struct input_dev *pwr = _pwr; + int err; + u8 value; + +#ifdef CONFIG_LOCKDEP + /* WORKAROUND for lockdep forcing IRQF_DISABLED on us, which + * we don't want and can't tolerate since this is a threaded + * IRQ and can sleep due to the i2c reads it has to issue. + * Although it might be friendlier not to borrow this thread + * context... + */ + local_irq_enable(); +#endif + + err = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &value, + STS_HW_CONDITIONS); + if (!err) { + input_report_key(pwr, KEY_POWER, value & PWR_PWRON_IRQ); + input_sync(pwr); + } else { + dev_err(pwr->dev.parent, "twl4030: i2c error %d while reading" + " TWL4030 PM_MASTER STS_HW_CONDITIONS register\n", err); + } + + return IRQ_HANDLED; +} + +static int __devinit twl4030_pwrbutton_probe(struct platform_device *pdev) +{ + struct input_dev *pwr; + int irq = platform_get_irq(pdev, 0); + int err; + + pwr = input_allocate_device(); + if (!pwr) { + dev_dbg(&pdev->dev, "Can't allocate power button\n"); + err = -ENOMEM; + goto out; + } + + err = request_irq(irq, powerbutton_irq, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "twl4030_pwrbutton", pwr); + if (err < 0) { + dev_dbg(&pdev->dev, "Can't get IRQ for pwrbutton: %d\n", err); + goto free_input_dev; + } + + pwr->evbit[0] = BIT_MASK(EV_KEY); + pwr->keybit[BIT_WORD(KEY_POWER)] = BIT_MASK(KEY_POWER); + pwr->name = "twl4030_pwrbutton"; + pwr->phys = "twl4030_pwrbutton/input0"; + pwr->dev.parent = &pdev->dev; + platform_set_drvdata(pdev, pwr); + + err = input_register_device(pwr); + if (err) { + dev_dbg(&pdev->dev, "Can't register power button: %d\n", err); + goto free_irq_and_out; + } + + return 0; + +free_irq_and_out: + free_irq(irq, NULL); +free_input_dev: + input_free_device(pwr); +out: + return err; +} + +static int __devexit twl4030_pwrbutton_remove(struct platform_device *pdev) +{ + struct input_dev *pwr = platform_get_drvdata(pdev); + int irq = platform_get_irq(pdev, 0); + + free_irq(irq, pwr); + input_unregister_device(pwr); + + return 0; +} + +struct platform_driver twl4030_pwrbutton_driver = { + .probe = twl4030_pwrbutton_probe, + .remove = __devexit_p(twl4030_pwrbutton_remove), + .driver = { + .name = "twl4030_pwrbutton", + .owner = THIS_MODULE, + }, +}; + +static int __init twl4030_pwrbutton_init(void) +{ + return platform_driver_register(&twl4030_pwrbutton_driver); +} +module_init(twl4030_pwrbutton_init); + +static void __exit twl4030_pwrbutton_exit(void) +{ + platform_driver_unregister(&twl4030_pwrbutton_driver); +} +module_exit(twl4030_pwrbutton_exit); + +MODULE_ALIAS("platform:twl4030_pwrbutton"); +MODULE_DESCRIPTION("Triton2 Power Button"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Peter De Schrijver "); +MODULE_AUTHOR("Felipe Balbi "); + diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index bb6486a8c07..5bd3bfac9c0 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -242,6 +242,32 @@ config TOUCHSCREEN_ATMEL_TSADCC To compile this driver as a module, choose M here: the module will be called atmel_tsadcc. +config TOUCHSCREEN_TSC2005 + tristate "TSC2005 touchscreen support" + depends on SPI_MASTER + help + Say Y here for if you are using the touchscreen features of TSC2005. + +config TOUCHSCREEN_TSC210X + tristate "TI TSC210x based touchscreens" + depends on SPI_MASTER + select SPI_TSC210X + help + Say Y here if you have a touchscreen interface using a + TI TSC210x controller, and your board-specific initialisation + code includes that in its table of SPI devices. + + If unsure, say N (but it's safe to say "Y"). + + To compile this driver as a module, choose M here: the + module will be called tsc210x_ts. + +config TOUCHSCREEN_TSC2301 + tristate "TSC2301 touchscreen support" + depends on SPI_TSC2301 + help + Say Y here for if you are using the touchscreen features of TSC2301. + config TOUCHSCREEN_UCB1400 tristate "Philips UCB1400 touchscreen" depends on AC97_BUS diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index d3375aff46f..a2530b83ff7 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -25,11 +25,11 @@ obj-$(CONFIG_TOUCHSCREEN_PENMOUNT) += penmount.o obj-$(CONFIG_TOUCHSCREEN_TOUCHIT213) += touchit213.o obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT) += touchright.o obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN) += touchwin.o -obj-$(CONFIG_TOUCHSCREEN_TSC2007) += tsc2007.o obj-$(CONFIG_TOUCHSCREEN_UCB1400) += ucb1400_ts.o -obj-$(CONFIG_TOUCHSCREEN_WACOM_W8001) += wacom_w8001.o +obj-$(CONFIG_TOUCHSCREEN_TSC2005) += tsc2005.o +obj-$(CONFIG_TOUCHSCREEN_TSC210X) += tsc210x_ts.o +obj-$(CONFIG_TOUCHSCREEN_TSC2301) += tsc2301_ts.o obj-$(CONFIG_TOUCHSCREEN_WM97XX) += wm97xx-ts.o -obj-$(CONFIG_TOUCHSCREEN_DA9034) += da9034-ts.o wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9705) += wm9705.o wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9712) += wm9712.o wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9713) += wm9713.o diff --git a/drivers/input/touchscreen/ads7846.c b/drivers/input/touchscreen/ads7846.c index 056ac77e2cf..c5ecce1842f 100644 --- a/drivers/input/touchscreen/ads7846.c +++ b/drivers/input/touchscreen/ads7846.c @@ -878,6 +878,15 @@ static int __devinit ads7846_probe(struct spi_device *spi) return -ENODEV; } + /* enable voltage */ + if (pdata->vaux_control != NULL) { + err = pdata->vaux_control(VAUX_ENABLE); + if (err != 0) { + dev_dbg(&spi->dev, "TS vaux enable failed\n"); + return err; + } + } + /* don't exceed max specified sample rate */ if (spi->max_speed_hz > (125000 * SAMPLE_BITS)) { dev_dbg(&spi->dev, "f(sample) %d KHz?\n", diff --git a/drivers/input/touchscreen/tsc2005.c b/drivers/input/touchscreen/tsc2005.c new file mode 100644 index 00000000000..ec4c9e7a736 --- /dev/null +++ b/drivers/input/touchscreen/tsc2005.c @@ -0,0 +1,728 @@ +/* + * TSC2005 touchscreen driver + * + * Copyright (C) 2006-2008 Nokia Corporation + * + * Author: Lauri Leukkunen + * based on TSC2301 driver by Klaus K. Pedersen + * + * 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 program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +/** + * The touchscreen interface operates as follows: + * + * Initialize: + * Request access to GPIO103 (DAV) + * tsc2005_dav_irq_handler will trigger when DAV line goes down + * + * 1) Pen is pressed against touchscreeen + * 2) TSC2005 performs AD conversion + * 3) After the conversion is done TSC2005 drives DAV line down + * 4) GPIO IRQ is received and tsc2005_dav_irq_handler is called + * 5) tsc2005_ts_irq_handler queues up an spi transfer to fetch + * the x, y, z1, z2 values + * 6) tsc2005_ts_rx() reports coordinates to input layer and + * sets up tsc2005_ts_timer() to be called after TSC2005_TS_SCAN_TIME + * 7) When the penup_timer expires, there have not been DAV interrupts + * during the last 20ms which means the pen has been lifted. + */ + +#define TSC2005_VDD_LOWER_27 + +#ifdef TSC2005_VDD_LOWER_27 +#define TSC2005_HZ (10000000) +#else +#define TSC2005_HZ (25000000) +#endif + +#define TSC2005_CMD (0x80) +#define TSC2005_REG (0x00) + +#define TSC2005_CMD_STOP (1) +#define TSC2005_CMD_10BIT (0 << 2) +#define TSC2005_CMD_12BIT (1 << 2) + +#define TSC2005_CMD_SCAN_XYZZ (0 << 3) +#define TSC2005_CMD_SCAN_XY (1 << 3) +#define TSC2005_CMD_SCAN_X (2 << 3) +#define TSC2005_CMD_SCAN_Y (3 << 3) +#define TSC2005_CMD_SCAN_ZZ (4 << 3) +#define TSC2005_CMD_AUX_SINGLE (5 << 3) +#define TSC2005_CMD_TEMP1 (6 << 3) +#define TSC2005_CMD_TEMP2 (7 << 3) +#define TSC2005_CMD_AUX_CONT (8 << 3) +#define TSC2005_CMD_TEST_X_CONN (9 << 3) +#define TSC2005_CMD_TEST_Y_CONN (10 << 3) +/* command 11 reserved */ +#define TSC2005_CMD_TEST_SHORT (12 << 3) +#define TSC2005_CMD_DRIVE_XX (13 << 3) +#define TSC2005_CMD_DRIVE_YY (14 << 3) +#define TSC2005_CMD_DRIVE_YX (15 << 3) + +#define TSC2005_REG_X (0 << 3) +#define TSC2005_REG_Y (1 << 3) +#define TSC2005_REG_Z1 (2 << 3) +#define TSC2005_REG_Z2 (3 << 3) +#define TSC2005_REG_AUX (4 << 3) +#define TSC2005_REG_TEMP1 (5 << 3) +#define TSC2005_REG_TEMP2 (6 << 3) +#define TSC2005_REG_STATUS (7 << 3) +#define TSC2005_REG_AUX_HIGH (8 << 3) +#define TSC2005_REG_AUX_LOW (9 << 3) +#define TSC2005_REG_TEMP_HIGH (10 << 3) +#define TSC2005_REG_TEMP_LOW (11 << 3) +#define TSC2005_REG_CFR0 (12 << 3) +#define TSC2005_REG_CFR1 (13 << 3) +#define TSC2005_REG_CFR2 (14 << 3) +#define TSC2005_REG_FUNCTION (15 << 3) + +#define TSC2005_REG_PND0 (1 << 1) +#define TSC2005_REG_READ (0x01) +#define TSC2005_REG_WRITE (0x00) + + +#define TSC2005_CFR0_LONGSAMPLING (1) +#define TSC2005_CFR0_DETECTINWAIT (1 << 1) +#define TSC2005_CFR0_SENSETIME_32US (0) +#define TSC2005_CFR0_SENSETIME_96US (1 << 2) +#define TSC2005_CFR0_SENSETIME_544US (1 << 3) +#define TSC2005_CFR0_SENSETIME_2080US (1 << 4) +#define TSC2005_CFR0_SENSETIME_2656US (0x001C) +#define TSC2005_CFR0_PRECHARGE_20US (0x0000) +#define TSC2005_CFR0_PRECHARGE_84US (0x0020) +#define TSC2005_CFR0_PRECHARGE_276US (0x0040) +#define TSC2005_CFR0_PRECHARGE_1044US (0x0080) +#define TSC2005_CFR0_PRECHARGE_1364US (0x00E0) +#define TSC2005_CFR0_STABTIME_0US (0x0000) +#define TSC2005_CFR0_STABTIME_100US (0x0100) +#define TSC2005_CFR0_STABTIME_500US (0x0200) +#define TSC2005_CFR0_STABTIME_1MS (0x0300) +#define TSC2005_CFR0_STABTIME_5MS (0x0400) +#define TSC2005_CFR0_STABTIME_100MS (0x0700) +#define TSC2005_CFR0_CLOCK_4MHZ (0x0000) +#define TSC2005_CFR0_CLOCK_2MHZ (0x0800) +#define TSC2005_CFR0_CLOCK_1MHZ (0x1000) +#define TSC2005_CFR0_RESOLUTION12 (0x2000) +#define TSC2005_CFR0_STATUS (0x4000) +#define TSC2005_CFR0_PENMODE (0x8000) + +#define TSC2005_CFR0_INITVALUE (TSC2005_CFR0_STABTIME_1MS | \ + TSC2005_CFR0_CLOCK_1MHZ | \ + TSC2005_CFR0_RESOLUTION12 | \ + TSC2005_CFR0_PRECHARGE_276US | \ + TSC2005_CFR0_PENMODE) + +#define TSC2005_CFR1_BATCHDELAY_0MS (0x0000) +#define TSC2005_CFR1_BATCHDELAY_1MS (0x0001) +#define TSC2005_CFR1_BATCHDELAY_2MS (0x0002) +#define TSC2005_CFR1_BATCHDELAY_4MS (0x0003) +#define TSC2005_CFR1_BATCHDELAY_10MS (0x0004) +#define TSC2005_CFR1_BATCHDELAY_20MS (0x0005) +#define TSC2005_CFR1_BATCHDELAY_40MS (0x0006) +#define TSC2005_CFR1_BATCHDELAY_100MS (0x0007) + +#define TSC2005_CFR1_INITVALUE (TSC2005_CFR1_BATCHDELAY_2MS) + +#define TSC2005_CFR2_MAVE_TEMP (0x0001) +#define TSC2005_CFR2_MAVE_AUX (0x0002) +#define TSC2005_CFR2_MAVE_Z (0x0004) +#define TSC2005_CFR2_MAVE_Y (0x0008) +#define TSC2005_CFR2_MAVE_X (0x0010) +#define TSC2005_CFR2_AVG_1 (0x0000) +#define TSC2005_CFR2_AVG_3 (0x0400) +#define TSC2005_CFR2_AVG_7 (0x0800) +#define TSC2005_CFR2_MEDIUM_1 (0x0000) +#define TSC2005_CFR2_MEDIUM_3 (0x1000) +#define TSC2005_CFR2_MEDIUM_7 (0x2000) +#define TSC2005_CFR2_MEDIUM_15 (0x3000) + +#define TSC2005_CFR2_IRQ_DAV (0x4000) +#define TSC2005_CFR2_IRQ_PEN (0x8000) +#define TSC2005_CFR2_IRQ_PENDAV (0x0000) + +#define TSC2005_CFR2_INITVALUE (TSC2005_CFR2_IRQ_DAV | \ + TSC2005_CFR2_MAVE_X | \ + TSC2005_CFR2_MAVE_Y | \ + TSC2005_CFR2_MAVE_Z | \ + TSC2005_CFR2_MEDIUM_15 | \ + TSC2005_CFR2_AVG_7) + +#define MAX_12BIT ((1 << 12) - 1) +#define TS_SAMPLES 4 +#define TS_RECT_SIZE 8 +#define TSC2005_TS_PENUP_TIME 20 + +static const u32 tsc2005_read_reg[] = { + (TSC2005_REG | TSC2005_REG_X | TSC2005_REG_READ) << 16, + (TSC2005_REG | TSC2005_REG_Y | TSC2005_REG_READ) << 16, + (TSC2005_REG | TSC2005_REG_Z1 | TSC2005_REG_READ) << 16, + (TSC2005_REG | TSC2005_REG_Z2 | TSC2005_REG_READ) << 16, +}; +#define NUM_READ_REGS (sizeof(tsc2005_read_reg)/sizeof(tsc2005_read_reg[0])) + +struct tsc2005 { + struct spi_device *spi; + + struct input_dev *idev; + char phys[32]; + struct timer_list penup_timer; + spinlock_t lock; + struct mutex mutex; + + struct spi_message read_msg; + struct spi_transfer read_xfer[NUM_READ_REGS]; + u32 data[NUM_READ_REGS]; + + /* previous x,y,z */ + int x; + int y; + int p; + /* average accumulators for each component */ + int sample_cnt; + int avg_x; + int avg_y; + int avg_z1; + int avg_z2; + /* configuration */ + int x_plate_ohm; + int hw_avg_max; + int stab_time; + int p_max; + int touch_pressure; + int irq; + s16 dav_gpio; + /* status */ + u8 sample_sent; + u8 pen_down; + u8 disabled; + u8 disable_depth; + u8 spi_active; +}; + +static void tsc2005_cmd(struct tsc2005 *ts, u8 cmd) +{ + u16 data = TSC2005_CMD | TSC2005_CMD_12BIT | cmd; + struct spi_message msg; + struct spi_transfer xfer = { 0 }; + + xfer.tx_buf = &data; + xfer.rx_buf = NULL; + xfer.len = 1; + xfer.bits_per_word = 8; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + spi_sync(ts->spi, &msg); +} + +static void tsc2005_write(struct tsc2005 *ts, u8 reg, u16 value) +{ + u32 tx; + struct spi_message msg; + struct spi_transfer xfer = { 0 }; + + tx = (TSC2005_REG | reg | TSC2005_REG_PND0 | + TSC2005_REG_WRITE) << 16; + tx |= value; + + xfer.tx_buf = &tx; + xfer.rx_buf = NULL; + xfer.len = 4; + xfer.bits_per_word = 24; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + spi_sync(ts->spi, &msg); +} + +static void tsc2005_ts_update_pen_state(struct tsc2005 *ts, + int x, int y, int pressure) +{ + if (pressure) { + input_report_abs(ts->idev, ABS_X, x); + input_report_abs(ts->idev, ABS_Y, y); + input_report_abs(ts->idev, ABS_PRESSURE, pressure); + if (!ts->pen_down) { + input_report_key(ts->idev, BTN_TOUCH, 1); + ts->pen_down = 1; + } + } else { + input_report_abs(ts->idev, ABS_PRESSURE, 0); + if (ts->pen_down) { + input_report_key(ts->idev, BTN_TOUCH, 0); + ts->pen_down = 0; + } + } + + input_sync(ts->idev); +} + +/* + * This function is called by the SPI framework after the coordinates + * have been read from TSC2005 + */ +static void tsc2005_ts_rx(void *arg) +{ + struct tsc2005 *ts = arg; + unsigned long flags; + int inside_rect, pressure_limit; + int x, y, z1, z2, pressure; + + spin_lock_irqsave(&ts->lock, flags); + + x = ts->data[0]; + y = ts->data[1]; + z1 = ts->data[2]; + z2 = ts->data[3]; + + /* validate pressure and position */ + if (x > MAX_12BIT || y > MAX_12BIT) + goto out; + + /* skip coords if the pressure-components are out of range */ + if (z1 < 100 || z2 > 4000) + goto out; + + /* don't run average on the "pen down" event */ + if (ts->sample_sent) { + ts->avg_x += x; + ts->avg_y += y; + ts->avg_z1 += z1; + ts->avg_z2 += z2; + + if (++ts->sample_cnt < TS_SAMPLES) + goto out; + + x = ts->avg_x / TS_SAMPLES; + y = ts->avg_y / TS_SAMPLES; + z1 = ts->avg_z1 / TS_SAMPLES; + z2 = ts->avg_z2 / TS_SAMPLES; + } + + ts->sample_cnt = 0; + ts->avg_x = 0; + ts->avg_y = 0; + ts->avg_z1 = 0; + ts->avg_z2 = 0; + + if (z1) { + pressure = x * (z2 - z1) / z1; + pressure = pressure * ts->x_plate_ohm / 4096; + } else + goto out; + + pressure_limit = ts->sample_sent? ts->p_max: ts->touch_pressure; + if (pressure > pressure_limit) + goto out; + + /* discard the event if it still is within the previous rect - unless + * if the pressure is harder, but then use previous x,y position */ + inside_rect = (ts->sample_sent && + x > (int)ts->x - TS_RECT_SIZE && + x < (int)ts->x + TS_RECT_SIZE && + y > (int)ts->y - TS_RECT_SIZE && + y < (int)ts->y + TS_RECT_SIZE); + if (inside_rect) + x = ts->x, y = ts->y; + + if (!inside_rect || pressure < ts->p) { + tsc2005_ts_update_pen_state(ts, x, y, pressure); + ts->sample_sent = 1; + ts->x = x; + ts->y = y; + ts->p = pressure; + } +out: + ts->spi_active = 0; + spin_unlock_irqrestore(&ts->lock, flags); + + /* kick pen up timer - to make sure it expires again(!) */ + if (ts->sample_sent) + mod_timer(&ts->penup_timer, + jiffies + msecs_to_jiffies(TSC2005_TS_PENUP_TIME)); +} + +static void tsc2005_ts_penup_timer_handler(unsigned long data) +{ + struct tsc2005 *ts = (struct tsc2005 *)data; + + if (ts->sample_sent) { + tsc2005_ts_update_pen_state(ts, 0, 0, 0); + ts->sample_sent = 0; + } +} + +/* + * This interrupt is called when pen is down and coordinates are + * available. That is indicated by a falling edge on DAV line. + */ +static irqreturn_t tsc2005_ts_irq_handler(int irq, void *dev_id) +{ + struct tsc2005 *ts = dev_id; + int r; + + if (ts->spi_active) + return IRQ_HANDLED; + + ts->spi_active = 1; + r = spi_async(ts->spi, &ts->read_msg); + if (r) + dev_err(&ts->spi->dev, "ts: spi_async() failed"); + + /* kick pen up timer */ + mod_timer(&ts->penup_timer, + jiffies + msecs_to_jiffies(TSC2005_TS_PENUP_TIME)); + + return IRQ_HANDLED; +} + +static void tsc2005_ts_setup_spi_xfer(struct tsc2005 *ts) +{ + struct spi_message *m = &ts->read_msg; + struct spi_transfer *x = &ts->read_xfer[0]; + int i; + + spi_message_init(m); + + for (i = 0; i < NUM_READ_REGS; i++, x++) { + x->tx_buf = &tsc2005_read_reg[i]; + x->rx_buf = &ts->data[i]; + x->len = 4; + x->bits_per_word = 24; + x->cs_change = i < (NUM_READ_REGS - 1); + spi_message_add_tail(x, m); + } + + m->complete = tsc2005_ts_rx; + m->context = ts; +} + +static ssize_t tsc2005_ts_pen_down_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct tsc2005 *tsc = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", tsc->pen_down); +} + +static DEVICE_ATTR(pen_down, S_IRUGO, tsc2005_ts_pen_down_show, NULL); + +static int tsc2005_configure(struct tsc2005 *tsc, int flags) +{ + tsc2005_write(tsc, TSC2005_REG_CFR0, TSC2005_CFR0_INITVALUE); + tsc2005_write(tsc, TSC2005_REG_CFR1, TSC2005_CFR1_INITVALUE); + tsc2005_write(tsc, TSC2005_REG_CFR2, TSC2005_CFR2_INITVALUE); + tsc2005_cmd(tsc, flags); + + return 0; +} + +static void tsc2005_start_scan(struct tsc2005 *tsc) +{ + tsc2005_configure(tsc, TSC2005_CMD_SCAN_XYZZ); +} + +static void tsc2005_stop_scan(struct tsc2005 *tsc) +{ + tsc2005_cmd(tsc, TSC2005_CMD_STOP); +} + +/* Must be called with mutex held */ +static void tsc2005_disable(struct tsc2005 *ts) +{ + if (ts->disable_depth++ != 0) + return; + + disable_irq(ts->irq); + + /* wait until penup timer expire normally */ + do { + msleep(4); + } while (ts->sample_sent); + + tsc2005_stop_scan(ts); +} + +static void tsc2005_enable(struct tsc2005 *ts) +{ + if (--ts->disable_depth != 0) + return; + + enable_irq(ts->irq); + + tsc2005_start_scan(ts); +} + +static ssize_t tsc2005_disable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tsc2005 *ts = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", ts->disabled); +} + +static ssize_t tsc2005_disable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct tsc2005 *tsc = dev_get_drvdata(dev); + unsigned long res; + int i; + + i = strict_strtoul(buf, 10, &res); + i = i ? 1 : 0; + + mutex_lock(&tsc->mutex); + if (i == tsc->disabled) + goto out; + tsc->disabled = i; + + if (i) + tsc2005_disable(tsc); + else + tsc2005_enable(tsc); +out: + mutex_unlock(&tsc->mutex); + return count; +} + +static DEVICE_ATTR(disable_ts, 0664, tsc2005_disable_show, + tsc2005_disable_store); + + +static int __devinit tsc2005_ts_init(struct tsc2005 *ts, + struct tsc2005_platform_data *pdata) +{ + struct input_dev *idev; + int dav_gpio, r; + int x_max, y_max; + int x_fudge, y_fudge, p_fudge; + + if (pdata->dav_gpio < 0) { + dev_err(&ts->spi->dev, "need DAV GPIO"); + return -EINVAL; + } + dav_gpio = pdata->dav_gpio; + ts->dav_gpio = dav_gpio; + dev_dbg(&ts->spi->dev, "TSC2005: DAV GPIO = %d\n", dav_gpio); + + r = gpio_request(dav_gpio, "TSC2005 dav"); + if (r < 0) { + dev_err(&ts->spi->dev, "unable to get DAV GPIO"); + goto err1; + } + gpio_direction_input(dav_gpio); + ts->irq = gpio_to_irq(dav_gpio); + dev_dbg(&ts->spi->dev, "TSC2005: DAV IRQ = %d\n", ts->irq); + + init_timer(&ts->penup_timer); + setup_timer(&ts->penup_timer, tsc2005_ts_penup_timer_handler, + (unsigned long)ts); + + spin_lock_init(&ts->lock); + mutex_init(&ts->mutex); + + ts->x_plate_ohm = pdata->ts_x_plate_ohm ? : 280; + ts->hw_avg_max = pdata->ts_hw_avg; + ts->stab_time = pdata->ts_stab_time; + x_max = pdata->ts_x_max ? : 4096; + x_fudge = pdata->ts_x_fudge ? : 4; + y_max = pdata->ts_y_max ? : 4096; + y_fudge = pdata->ts_y_fudge ? : 8; + ts->p_max = pdata->ts_pressure_max ? : MAX_12BIT; + ts->touch_pressure = pdata->ts_touch_pressure ? : ts->p_max; + p_fudge = pdata->ts_pressure_fudge ? : 2; + + idev = input_allocate_device(); + if (idev == NULL) { + r = -ENOMEM; + goto err2; + } + + idev->name = "TSC2005 touchscreen"; + snprintf(ts->phys, sizeof(ts->phys), "%s/input-ts", + dev_name(&ts->spi->dev)); + idev->phys = ts->phys; + + idev->evbit[0] = BIT(EV_ABS) | BIT(EV_KEY); + idev->absbit[0] = BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_PRESSURE); + ts->idev = idev; + + tsc2005_ts_setup_spi_xfer(ts); + + input_set_abs_params(idev, ABS_X, 0, x_max, x_fudge, 0); + input_set_abs_params(idev, ABS_Y, 0, y_max, y_fudge, 0); + input_set_abs_params(idev, ABS_PRESSURE, 0, ts->p_max, p_fudge, 0); + + tsc2005_start_scan(ts); + + r = request_irq(ts->irq, tsc2005_ts_irq_handler, + IRQF_TRIGGER_FALLING | IRQF_DISABLED | + IRQF_SAMPLE_RANDOM, "tsc2005", ts); + if (r < 0) { + dev_err(&ts->spi->dev, "unable to get DAV IRQ"); + goto err3; + } + + set_irq_wake(ts->irq, 1); + + r = input_register_device(idev); + if (r < 0) { + dev_err(&ts->spi->dev, "can't register touchscreen device\n"); + goto err4; + } + + /* We can tolerate these failing */ + if (device_create_file(&ts->spi->dev, &dev_attr_pen_down)); + if (device_create_file(&ts->spi->dev, &dev_attr_disable_ts)); + + return 0; +err4: + free_irq(ts->irq, ts); +err3: + tsc2005_stop_scan(ts); + input_free_device(idev); +err2: + gpio_free(dav_gpio); +err1: + return r; +} + +static int __devinit tsc2005_probe(struct spi_device *spi) +{ + struct tsc2005 *tsc; + struct tsc2005_platform_data *pdata = spi->dev.platform_data; + int r; + + if (!pdata) { + dev_dbg(&spi->dev, "no platform data?\n"); + return -ENODEV; + } + + tsc = kzalloc(sizeof(*tsc), GFP_KERNEL); + if (tsc == NULL) + return -ENOMEM; + + dev_set_drvdata(&spi->dev, tsc); + tsc->spi = spi; + spi->dev.power.power_state = PMSG_ON; + + spi->mode = SPI_MODE_0; + spi->bits_per_word = 8; + /* The max speed might've been defined by the board-specific + * struct */ + if (!spi->max_speed_hz) + spi->max_speed_hz = TSC2005_HZ; + + spi_setup(spi); + + r = tsc2005_ts_init(tsc, pdata); + if (r) + goto err1; + + return 0; + +err1: + kfree(tsc); + return r; +} + +static int __devexit tsc2005_remove(struct spi_device *spi) +{ + struct tsc2005 *ts = dev_get_drvdata(&spi->dev); + + mutex_lock(&ts->mutex); + tsc2005_disable(ts); + mutex_unlock(&ts->mutex); + + device_remove_file(&ts->spi->dev, &dev_attr_disable_ts); + device_remove_file(&ts->spi->dev, &dev_attr_pen_down); + + free_irq(ts->irq, ts); + input_unregister_device(ts->idev); + + gpio_free(ts->dav_gpio); + kfree(ts); + + return 0; +} + +#ifdef CONFIG_PM +static int tsc2005_suspend(struct spi_device *spi, pm_message_t mesg) +{ + struct tsc2005 *ts = dev_get_drvdata(&spi->dev); + + mutex_lock(&ts->mutex); + tsc2005_disable(ts); + mutex_unlock(&ts->mutex); + + return 0; +} + +static int tsc2005_resume(struct spi_device *spi) +{ + struct tsc2005 *ts = dev_get_drvdata(&spi->dev); + + mutex_lock(&ts->mutex); + tsc2005_enable(ts); + mutex_unlock(&ts->mutex); + + return 0; +} +#endif + +static struct spi_driver tsc2005_driver = { + .driver = { + .name = "tsc2005", + .owner = THIS_MODULE, + }, +#ifdef CONFIG_PM + .suspend = tsc2005_suspend, + .resume = tsc2005_resume, +#endif + .probe = tsc2005_probe, + .remove = __devexit_p(tsc2005_remove), +}; + +static int __init tsc2005_init(void) +{ + printk(KERN_INFO "TSC2005 driver initializing\n"); + + return spi_register_driver(&tsc2005_driver); +} +module_init(tsc2005_init); + +static void __exit tsc2005_exit(void) +{ + spi_unregister_driver(&tsc2005_driver); +} +module_exit(tsc2005_exit); + +MODULE_AUTHOR("Lauri Leukkunen "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:tsc2005"); diff --git a/drivers/input/touchscreen/tsc210x_ts.c b/drivers/input/touchscreen/tsc210x_ts.c new file mode 100644 index 00000000000..5828b6d5514 --- /dev/null +++ b/drivers/input/touchscreen/tsc210x_ts.c @@ -0,0 +1,160 @@ +/* + * tsc210x_ts.c - touchscreen input device for TI TSC210x chips + * + * Copyright (c) 2006-2007 Andrzej Zaborowski + * + * This package 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 package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include + +#include + + +/* + * The sensor ADC on tsc210x chips is most often used with the smart + * touchscreen controller. Those controllers can be made to improve + * sample quality directly by multi-sampling and by taking the mean or + * median of various numbers of samples. They also take X, Y, and + * pressure measurements automatically, so this driver has relatively + * little to do. + * + * There are a few chips in this family that don't have quite the same + * touchscreen interface, e.g. no "median" mode. + */ + +static void tsc210x_touch(void *context, int touching) +{ + struct input_dev *dev = context; + + if (!touching) { + input_report_abs(dev, ABS_X, 0); + input_report_abs(dev, ABS_Y, 0); + input_report_abs(dev, ABS_PRESSURE, 0); + input_sync(dev); + } + + input_report_key(dev, BTN_TOUCH, touching); +} + +static void tsc210x_coords(void *context, int x, int y, int z1, int z2) +{ + struct input_dev *dev = context; + int p; + + /* Calculate the touch resistance a la equation #1 */ + if (z1 != 0) + p = x * (z2 - z1) / (z1 << 4); + else + p = 1; + + input_report_abs(dev, ABS_X, x); + input_report_abs(dev, ABS_Y, y); + input_report_abs(dev, ABS_PRESSURE, p); + input_sync(dev); +} + +static int tsc210x_ts_probe(struct platform_device *pdev) +{ + int status; + struct input_dev *dev; + + dev = input_allocate_device(); + if (!dev) + return -ENOMEM; + + status = tsc210x_touch_cb(pdev->dev.parent, tsc210x_touch, dev); + if (status) { + input_free_device(dev); + return status; + } + + status = tsc210x_coords_cb(pdev->dev.parent, tsc210x_coords, dev); + if (status) { + tsc210x_touch_cb(pdev->dev.parent, NULL, NULL); + input_free_device(dev); + return status; + } + + dev->name = "TSC210x Touchscreen"; + dev->dev.parent = &pdev->dev; + dev->evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + dev->keybit[BIT_WORD(BTN_TOUCH)] |= BIT_MASK(BTN_TOUCH); + dev->absbit[0] = BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_PRESSURE); + dev->phys = "tsc210x/input0"; + dev->id.bustype = BUS_HOST; + dev->id.vendor = 0x0001; + dev->id.product = 0x2100; + dev->id.version = 0x0001; + + status = input_register_device(dev); + if (status) { + tsc210x_coords_cb(pdev->dev.parent, NULL, NULL); + tsc210x_touch_cb(pdev->dev.parent, NULL, NULL); + input_free_device(dev); + return status; + } + + platform_set_drvdata(pdev, dev); + printk(KERN_INFO "TSC210x touchscreen initialised\n"); + return 0; +} + +static int __exit tsc210x_ts_remove(struct platform_device *pdev) +{ + struct input_dev *dev = platform_get_drvdata(pdev); + + tsc210x_touch_cb(pdev->dev.parent, NULL, NULL); + tsc210x_coords_cb(pdev->dev.parent, NULL, NULL); + platform_set_drvdata(pdev, NULL); + input_unregister_device(dev); + input_free_device(dev); + + return 0; +} + +static struct platform_driver tsc210x_ts_driver = { + .probe = tsc210x_ts_probe, + .remove = __exit_p(tsc210x_ts_remove), + /* Nothing to do on suspend/resume */ + .driver = { + .name = "tsc210x-ts", + .owner = THIS_MODULE, + }, +}; + +static int __init tsc210x_ts_init(void) +{ + /* can't use driver_probe() here since the parent device + * gets registered "late" + */ + return platform_driver_register(&tsc210x_ts_driver); +} +module_init(tsc210x_ts_init); + +static void __exit tsc210x_ts_exit(void) +{ + platform_driver_unregister(&tsc210x_ts_driver); +} +module_exit(tsc210x_ts_exit); + +MODULE_AUTHOR("Andrzej Zaborowski"); +MODULE_DESCRIPTION("Touchscreen input driver for TI TSC2101/2102."); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/tsc2301_ts.c b/drivers/input/touchscreen/tsc2301_ts.c new file mode 100644 index 00000000000..a157b48dd0b --- /dev/null +++ b/drivers/input/touchscreen/tsc2301_ts.c @@ -0,0 +1,676 @@ +/* + * TSC2301 touchscreen driver + * + * Copyright (C) 2005-2008 Nokia Corporation + * + * Written by Jarkko Oikarinen, Imre Deak and Juha Yrjola + * + * 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 program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include + +#include + +/** + * The touchscreen interface operates as follows: + * + * Initialize: + * Request access to GPIO103 (DAV) + * tsc2301_ts_irq_handler will trigger when DAV line goes down + * + * 1) Pen is pressed against touchscreeen + * 2) TSC2301 performs AD conversion + * 3) After the conversion is done TSC2301 drives DAV line down + * 4) GPIO IRQ is received and tsc2301_ts_irq_handler is called + * 5) tsc2301_ts_irq_handler queues up an spi transfer to fetch + * the x, y, z1, z2 values + * 6) SPI framework calls tsc2301_ts_rx after the coordinates are read + * 7) When the penup_timer expires, there have not been DAV interrupts + * during the last 20ms which means the pen has been lifted. + */ + + +#define TSC2301_TOUCHSCREEN_PRODUCT_ID 0x0052 +#define TSC2301_TOUCHSCREEN_PRODUCT_VERSION 0x0001 + +#define TSC2301_TS_PENUP_TIME 20 + +#define TSC2301_ADCREG_CONVERSION_CTRL_BY_TSC2301 0x8000 +#define TSC2301_ADCREG_CONVERSION_CTRL_BY_HOST 0x0000 + +#define TSC2301_ADCREG_FUNCTION_NONE 0x0000 +#define TSC2301_ADCREG_FUNCTION_XY 0x0400 +#define TSC2301_ADCREG_FUNCTION_XYZ 0x0800 +#define TSC2301_ADCREG_FUNCTION_X 0x0C00 +#define TSC2301_ADCREG_FUNCTION_Y 0x1000 +#define TSC2301_ADCREG_FUNCTION_Z 0x1400 +#define TSC2301_ADCREG_FUNCTION_DAT1 0x1800 +#define TSC2301_ADCREG_FUNCTION_DAT2 0x1C00 +#define TSC2301_ADCREG_FUNCTION_AUX1 0x2000 +#define TSC2301_ADCREG_FUNCTION_AUX2 0x2400 +#define TSC2301_ADCREG_FUNCTION_TEMP 0x2800 + +#define TSC2301_ADCREG_RESOLUTION_8BIT 0x0100 +#define TSC2301_ADCREG_RESOLUTION_10BIT 0x0200 +#define TSC2301_ADCREG_RESOLUTION_12BIT 0x0300 + +#define TSC2301_ADCREG_AVERAGING_NONE 0x0000 +#define TSC2301_ADCREG_AVERAGING_4AVG 0x0040 +#define TSC2301_ADCREG_AVERAGING_8AVG 0x0080 +#define TSC2301_ADCREG_AVERAGING_16AVG 0x00C0 + +#define TSC2301_ADCREG_CLOCK_8MHZ 0x0000 +#define TSC2301_ADCREG_CLOCK_4MHZ 0x0010 +#define TSC2301_ADCREG_CLOCK_2MHZ 0x0020 +#define TSC2301_ADCREG_CLOCK_1MHZ 0x0030 + +#define TSC2301_ADCREG_VOLTAGE_STAB_0US 0x0000 +#define TSC2301_ADCREG_VOLTAGE_STAB_100US 0x0002 +#define TSC2301_ADCREG_VOLTAGE_STAB_500US 0x0004 +#define TSC2301_ADCREG_VOLTAGE_STAB_1MS 0x0006 +#define TSC2301_ADCREG_VOLTAGE_STAB_5MS 0x0008 +#define TSC2301_ADCREG_VOLTAGE_STAB_10MS 0x000A +#define TSC2301_ADCREG_VOLTAGE_STAB_50MS 0x000C +#define TSC2301_ADCREG_VOLTAGE_STAB_100MS 0x000E + +#define TSC2301_ADCREG_STOP_CONVERSION 0x4000 + +#define MAX_12BIT ((1 << 12) - 1) + +#define TS_RECT_SIZE 8 +#define TSF_MIN_Z1 100 +#define TSF_MAX_Z2 4000 + +#define TSF_SAMPLES 4 + +struct ts_filter { + int sample_cnt; + + int avg_x; + int avg_y; + int avg_z1; + int avg_z2; +}; + +struct ts_coords { + u16 x; + u16 y; + u16 z1; + u16 z2; +}; + +struct tsc2301_ts { + struct input_dev *idev; + char phys[32]; + struct timer_list penup_timer; + struct mutex mutex; + + struct spi_transfer read_xfer[2]; + struct spi_message read_msg; + struct ts_coords *coords; + + struct ts_filter filter; + + int hw_avg_max; + u16 x; + u16 y; + u16 p; + + u16 x_plate_ohm; + int stab_time; + int max_pressure; + int touch_pressure; + + u8 event_sent; + u8 pen_down; + u8 disabled; + u8 disable_depth; + + int hw_flags; + int irq; +}; + + +static const u16 tsc2301_ts_read_data = 0x8000 | TSC2301_REG_X; + +static int tsc2301_ts_check_config(struct tsc2301_ts *ts, int *hw_flags) +{ + int flags; + + flags = 0; + switch (ts->hw_avg_max) { + case 0: + flags |= TSC2301_ADCREG_AVERAGING_NONE; + break; + case 4: + flags |= TSC2301_ADCREG_AVERAGING_4AVG; + break; + case 8: + flags |= TSC2301_ADCREG_AVERAGING_8AVG; + break; + case 16: + flags |= TSC2301_ADCREG_AVERAGING_16AVG; + break; + default: + return -EINVAL; + } + + switch (ts->stab_time) { + case 0: + flags |= TSC2301_ADCREG_VOLTAGE_STAB_0US; + break; + case 100: + flags |= TSC2301_ADCREG_VOLTAGE_STAB_100US; + break; + case 500: + flags |= TSC2301_ADCREG_VOLTAGE_STAB_500US; + break; + case 1000: + flags |= TSC2301_ADCREG_VOLTAGE_STAB_1MS; + break; + case 5000: + flags |= TSC2301_ADCREG_VOLTAGE_STAB_5MS; + break; + case 10000: + flags |= TSC2301_ADCREG_VOLTAGE_STAB_10MS; + break; + case 50000: + flags |= TSC2301_ADCREG_VOLTAGE_STAB_50MS; + break; + case 100000: + flags |= TSC2301_ADCREG_VOLTAGE_STAB_100MS; + break; + default: + return -EINVAL; + } + + *hw_flags = flags; + return 0; +} + +/* + * This odd three-time initialization is to work around a bug in TSC2301. + * See TSC2301 errata for details. + */ +static int tsc2301_ts_configure(struct tsc2301 *tsc, int flags) +{ + struct spi_transfer xfer[5]; + struct spi_transfer *x; + struct spi_message m; + int i; + u16 val1, val2, val3; + u16 data[10]; + + val1 = TSC2301_ADCREG_CONVERSION_CTRL_BY_HOST | + TSC2301_ADCREG_STOP_CONVERSION | + TSC2301_ADCREG_FUNCTION_NONE | + TSC2301_ADCREG_RESOLUTION_12BIT | + TSC2301_ADCREG_AVERAGING_NONE | + TSC2301_ADCREG_CLOCK_2MHZ | + TSC2301_ADCREG_VOLTAGE_STAB_100MS; + + val2 = TSC2301_ADCREG_CONVERSION_CTRL_BY_HOST | + TSC2301_ADCREG_FUNCTION_XYZ | + TSC2301_ADCREG_RESOLUTION_12BIT | + TSC2301_ADCREG_AVERAGING_16AVG | + TSC2301_ADCREG_CLOCK_1MHZ | + TSC2301_ADCREG_VOLTAGE_STAB_100MS; + + /* Averaging and voltage stabilization settings in flags */ + val3 = TSC2301_ADCREG_CONVERSION_CTRL_BY_TSC2301 | + TSC2301_ADCREG_FUNCTION_XYZ | + TSC2301_ADCREG_RESOLUTION_12BIT | + TSC2301_ADCREG_CLOCK_2MHZ | + flags; + + /* Now we prepare the command for transferring */ + data[0] = TSC2301_REG_ADC; + data[1] = val1; + data[2] = TSC2301_REG_ADC; + data[3] = val2; + data[4] = TSC2301_REG_ADC; + data[5] = val3; + data[6] = TSC2301_REG_REF; + data[7] = 1 << 4 | 1 << 2 | 1; /* intref, 100uS settl, 2.5V ref */ + data[8] = TSC2301_REG_CONFIG; + data[9] = 3 << 3 | 2 << 0; /* 340uS pre-chrg, 544us delay */ + + spi_message_init(&m); + m.spi = tsc->spi; + + memset(xfer, 0, sizeof(xfer)); + x = &xfer[0]; + + for (i = 0; i < 10; i += 2) { + x->tx_buf = &data[i]; + x->len = 4; + if (i != 8) + x->cs_change = 1; + spi_message_add_tail(x, &m); + x++; + } + spi_sync(m.spi, &m); + + return 0; +} + +static void tsc2301_ts_start_scan(struct tsc2301 *tsc) +{ + tsc2301_ts_configure(tsc, tsc->ts->hw_flags); + tsc2301_kp_restart(tsc); +} + +static void tsc2301_ts_stop_scan(struct tsc2301 *tsc) +{ + tsc2301_write_reg(tsc, TSC2301_REG_ADC, TSC2301_ADCREG_STOP_CONVERSION); + tsc2301_kp_restart(tsc); +} + +static void update_pen_state(struct tsc2301_ts *ts, int x, int y, int pressure) +{ + if (pressure) { + input_report_abs(ts->idev, ABS_X, x); + input_report_abs(ts->idev, ABS_Y, y); + input_report_abs(ts->idev, ABS_PRESSURE, pressure); + if (!ts->pen_down) + input_report_key(ts->idev, BTN_TOUCH, 1); + ts->pen_down = 1; + } else { + input_report_abs(ts->idev, ABS_PRESSURE, 0); + if (ts->pen_down) + input_report_key(ts->idev, BTN_TOUCH, 0); + ts->pen_down = 0; + } + + input_sync(ts->idev); + +#ifdef VERBOSE + dev_dbg(&tsc->spi->dev, "x %4d y %4d p %4d\n", x, y, pressure); +#endif +} + +static int filter(struct tsc2301_ts *ts, int x, int y, int z1, int z2) +{ + int inside_rect, pressure_limit, Rt; + struct ts_filter *tsf = &ts->filter; + + /* validate pressure and position */ + if (x > MAX_12BIT || y > MAX_12BIT) + return 0; + + /* skip coords if the pressure-components are out of range */ + if (z1 < TSF_MIN_Z1 || z2 > TSF_MAX_Z2) + return 0; + + /* Use the x,y,z1,z2 directly on the first "pen down" event */ + if (ts->event_sent) { + tsf->avg_x += x; + tsf->avg_y += y; + tsf->avg_z1 += z1; + tsf->avg_z2 += z2; + + if (++tsf->sample_cnt < TSF_SAMPLES) + return 0; + x = tsf->avg_x / TSF_SAMPLES; + y = tsf->avg_y / TSF_SAMPLES; + z1 = tsf->avg_z1 / TSF_SAMPLES; + z2 = tsf->avg_z2 / TSF_SAMPLES; + } + tsf->sample_cnt = 0; + tsf->avg_x = 0; + tsf->avg_y = 0; + tsf->avg_z1 = 0; + tsf->avg_z2 = 0; + + pressure_limit = ts->event_sent? ts->max_pressure: ts->touch_pressure; + + /* z1 is always at least 100: */ + Rt = x * (z2 - z1) / z1; + Rt = Rt * ts->x_plate_ohm / 4096; + if (Rt > pressure_limit) + return 0; + + /* discard the event if it still is within the previous rect - unless + * if the pressure is harder, but then use previous x,y position */ + inside_rect = ( + x > (int)ts->x - TS_RECT_SIZE && x < (int)ts->x + TS_RECT_SIZE && + y > (int)ts->y - TS_RECT_SIZE && y < (int)ts->y + TS_RECT_SIZE); + + if (!ts->event_sent || !inside_rect) { + ts->x = x; + ts->y = y; + ts->p = Rt; + return 1; + } else if (Rt < ts->p) { + ts->p = Rt; + return 1; + } + return 0; +} + +/* + * This procedure is called by the SPI framework after the coordinates + * have been read from TSC2301 + */ +static void tsc2301_ts_rx(void *arg) +{ + struct tsc2301 *tsc = arg; + struct tsc2301_ts *ts = tsc->ts; + int send_event; + int x, y, z1, z2; + + x = ts->coords->x; + y = ts->coords->y; + z1 = ts->coords->z1; + z2 = ts->coords->z2; + + send_event = filter(ts, x, y, z1, z2); + if (send_event) { + update_pen_state(ts, ts->x, ts->y, ts->p); + ts->event_sent = 1; + } + + mod_timer(&ts->penup_timer, + jiffies + msecs_to_jiffies(TSC2301_TS_PENUP_TIME)); +} + +/* + * Timer is called TSC2301_TS_PENUP_TIME after pen is up + */ +static void tsc2301_ts_timer_handler(unsigned long data) +{ + struct tsc2301 *tsc = (struct tsc2301 *)data; + struct tsc2301_ts *ts = tsc->ts; + + if (ts->event_sent) { + ts->event_sent = 0; + update_pen_state(ts, 0, 0, 0); + } +} + +/* + * This interrupt is called when pen is down and coordinates are + * available. That is indicated by a falling edge on DEV line. + */ +static irqreturn_t tsc2301_ts_irq_handler(int irq, void *dev_id) +{ + struct tsc2301 *tsc = dev_id; + struct tsc2301_ts *ts = tsc->ts; + int r; + + r = spi_async(tsc->spi, &ts->read_msg); + if (r) + dev_err(&tsc->spi->dev, "ts: spi_async() failed"); + + mod_timer(&ts->penup_timer, + jiffies + msecs_to_jiffies(TSC2301_TS_PENUP_TIME)); + + return IRQ_HANDLED; +} + +static void tsc2301_ts_disable(struct tsc2301 *tsc) +{ + struct tsc2301_ts *ts = tsc->ts; + + if (ts->disable_depth++ != 0) + return; + + disable_irq(ts->irq); + + /* wait until penup timer expire normally */ + do { + msleep(1); + } while (ts->event_sent); + + tsc2301_ts_stop_scan(tsc); +} + +static void tsc2301_ts_enable(struct tsc2301 *tsc) +{ + struct tsc2301_ts *ts = tsc->ts; + + if (--ts->disable_depth != 0) + return; + + enable_irq(ts->irq); + + tsc2301_ts_start_scan(tsc); +} + +#ifdef CONFIG_PM +int tsc2301_ts_suspend(struct tsc2301 *tsc) +{ + struct tsc2301_ts *ts = tsc->ts; + + mutex_lock(&ts->mutex); + tsc2301_ts_disable(tsc); + mutex_unlock(&ts->mutex); + + return 0; +} + +void tsc2301_ts_resume(struct tsc2301 *tsc) +{ + struct tsc2301_ts *ts = tsc->ts; + + mutex_lock(&ts->mutex); + tsc2301_ts_enable(tsc); + mutex_unlock(&ts->mutex); +} +#endif + +static void tsc2301_ts_setup_spi_xfer(struct tsc2301 *tsc) +{ + struct tsc2301_ts *ts = tsc->ts; + struct spi_message *m = &ts->read_msg; + struct spi_transfer *x = &ts->read_xfer[0]; + + spi_message_init(m); + + x->tx_buf = &tsc2301_ts_read_data; + x->len = 2; + spi_message_add_tail(x, m); + + x++; + x->rx_buf = ts->coords; + x->len = 8; + spi_message_add_tail(x, m); + + m->complete = tsc2301_ts_rx; + m->context = tsc; +} + +static ssize_t tsc2301_ts_pen_down_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct tsc2301 *tsc = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", tsc->ts->pen_down); +} + +static DEVICE_ATTR(pen_down, S_IRUGO, tsc2301_ts_pen_down_show, NULL); + +static ssize_t tsc2301_ts_disable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tsc2301 *tsc = dev_get_drvdata(dev); + struct tsc2301_ts *ts = tsc->ts; + + return sprintf(buf, "%u\n", ts->disabled); +} + +static ssize_t tsc2301_ts_disable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct tsc2301 *tsc = dev_get_drvdata(dev); + struct tsc2301_ts *ts = tsc->ts; + char *endp; + int i; + + i = simple_strtoul(buf, &endp, 10); + i = i ? 1 : 0; + mutex_lock(&ts->mutex); + if (i == ts->disabled) goto out; + ts->disabled = i; + + if (i) + tsc2301_ts_disable(tsc); + else + tsc2301_ts_enable(tsc); +out: + mutex_unlock(&ts->mutex); + return count; +} + +static DEVICE_ATTR(disable_ts, 0664, tsc2301_ts_disable_show, + tsc2301_ts_disable_store); + +int __devinit tsc2301_ts_init(struct tsc2301 *tsc, + struct tsc2301_platform_data *pdata) +{ + struct tsc2301_ts *ts; + struct input_dev *idev; + int r; + int x_max, y_max; + int x_fudge, y_fudge, p_fudge; + + if (pdata->dav_int <= 0) { + dev_err(&tsc->spi->dev, "need DAV IRQ"); + return -EINVAL; + } + + ts = kzalloc(sizeof(*ts), GFP_KERNEL); + if (ts == NULL) + return -ENOMEM; + tsc->ts = ts; + + ts->coords = kzalloc(sizeof(*ts->coords), GFP_KERNEL); + if (ts->coords == NULL) { + kfree(ts); + return -ENOMEM; + } + + ts->irq = pdata->dav_int; + + init_timer(&ts->penup_timer); + setup_timer(&ts->penup_timer, tsc2301_ts_timer_handler, + (unsigned long)tsc); + + mutex_init(&ts->mutex); + + ts->x_plate_ohm = pdata->ts_x_plate_ohm ? : 280; + ts->hw_avg_max = pdata->ts_hw_avg; + ts->max_pressure = pdata->ts_max_pressure ? : MAX_12BIT; + ts->touch_pressure = pdata->ts_touch_pressure ? : ts->max_pressure; + ts->stab_time = pdata->ts_stab_time; + + x_max = pdata->ts_x_max ? : 4096; + y_max = pdata->ts_y_max ? : 4096; + x_fudge = pdata->ts_x_fudge ? : 4; + y_fudge = pdata->ts_y_fudge ? : 8; + p_fudge = pdata->ts_pressure_fudge ? : 2; + + if ((r = tsc2301_ts_check_config(ts, &ts->hw_flags))) { + dev_err(&tsc->spi->dev, "invalid configuration\n"); + goto err2; + } + + idev = input_allocate_device(); + if (idev == NULL) { + r = -ENOMEM; + goto err2; + } + idev->name = "TSC2301 touchscreen"; + snprintf(ts->phys, sizeof(ts->phys), + "%s/input-ts", dev_name(&tsc->spi->dev)); + idev->phys = ts->phys; + idev->dev.parent = &tsc->spi->dev; + + idev->evbit[0] = BIT(EV_ABS) | BIT(EV_KEY); + idev->absbit[0] = BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_PRESSURE); + ts->idev = idev; + + tsc2301_ts_setup_spi_xfer(tsc); + + /* These parameters should perhaps be configurable? */ + input_set_abs_params(idev, ABS_X, 0, x_max, x_fudge, 0); + input_set_abs_params(idev, ABS_Y, 0, y_max, y_fudge, 0); + input_set_abs_params(idev, ABS_PRESSURE, 0, ts->max_pressure, + p_fudge, 0); + + tsc2301_ts_start_scan(tsc); + + r = request_irq(ts->irq, tsc2301_ts_irq_handler, + IRQF_SAMPLE_RANDOM | IRQF_TRIGGER_FALLING, + "tsc2301-ts", tsc); + if (r < 0) { + dev_err(&tsc->spi->dev, "unable to get DAV IRQ"); + goto err3; + } + set_irq_wake(ts->irq, 1); + + if (device_create_file(&tsc->spi->dev, &dev_attr_pen_down) < 0) + goto err4; + if (device_create_file(&tsc->spi->dev, &dev_attr_disable_ts) < 0) + goto err5; + + r = input_register_device(idev); + if (r < 0) { + dev_err(&tsc->spi->dev, "can't register touchscreen device\n"); + goto err6; + } + + return 0; +err6: + device_remove_file(&tsc->spi->dev, &dev_attr_disable_ts); +err5: + device_remove_file(&tsc->spi->dev, &dev_attr_pen_down); +err4: + free_irq(ts->irq, tsc); +err3: + tsc2301_ts_stop_scan(tsc); + input_free_device(idev); +err2: + kfree(ts->coords); + kfree(ts); + return r; +} + +void __devexit tsc2301_ts_exit(struct tsc2301 *tsc) +{ + struct tsc2301_ts *ts = tsc->ts; + + tsc2301_ts_disable(tsc); + + device_remove_file(&tsc->spi->dev, &dev_attr_disable_ts); + device_remove_file(&tsc->spi->dev, &dev_attr_pen_down); + + free_irq(ts->irq, tsc); + input_unregister_device(ts->idev); + + kfree(ts->coords); + kfree(ts); +} +MODULE_AUTHOR("Jarkko Oikarinen "); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 9b60b6b684d..788f48cf921 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -70,11 +70,26 @@ config LEDS_WRAP help This option enables support for the PCEngines WRAP programmable LEDs. -config LEDS_ALIX2 - tristate "LED Support for ALIX.2 and ALIX.3 series" - depends on LEDS_CLASS && X86 && EXPERIMENTAL +config LEDS_OMAP_DEBUG + boolean "LED Support for OMAP debug board LEDs" + depends on LEDS_CLASS=y && ARCH_OMAP help - This option enables support for the PCEngines ALIX.2 and ALIX.3 LEDs. + Enables support for the LEDs on the debug board used with OMAP + reference boards like H2/H3/H4 and Perseus2. Up to six of these + may be claimed by the original ARM debug LED API. + +config LEDS_OMAP + tristate "LED Support for OMAP GPIO LEDs" + depends on LEDS_CLASS && ARCH_OMAP + help + This option enables support for the LEDs on OMAP processors. + +config LEDS_OMAP_PWM + tristate "LED Support for OMAP PWM-controlled LEDs" + depends on LEDS_CLASS && ARCH_OMAP && OMAP_DM_TIMER + help + This options enables support for LEDs connected to GPIO lines + controlled by a PWM timer on OMAP CPUs. config LEDS_H1940 tristate "LED Support for iPAQ H1940 device" @@ -90,7 +105,7 @@ config LEDS_COBALT_QUBE config LEDS_COBALT_RAQ bool "LED Support for the Cobalt Raq series" - depends on LEDS_CLASS=y && MIPS_COBALT + depends on LEDS_CLASS && MIPS_COBALT select LEDS_TRIGGERS help This option enables support for the Cobalt Raq series LEDs. @@ -155,6 +170,16 @@ config LEDS_LP5521 This driver can be built as a module by choosing 'M'. The module will be called leds-lp5521. +config LEDS_LP5521 + tristate "LED Support for the LP5521 LEDs" + depends on LEDS_CLASS && I2C + help + If you say 'Y' here you get support for the National Semiconductor + LP5521 LED driver used in n8x0 boards. + + This driver can be built as a module by choosing 'M'. The module + will be called leds-lp5521. + config LEDS_CLEVO_MAIL tristate "Mail LED on Clevo notebook" depends on LEDS_CLASS && X86 && SERIO_I8042 && DMI @@ -193,13 +218,6 @@ config LEDS_PCA955X LED driver chips accessed via the I2C bus. Supported devices include PCA9550, PCA9551, PCA9552, and PCA9553. -config LEDS_WM8350 - tristate "LED Support for WM8350 AudioPlus PMIC" - depends on LEDS_CLASS && MFD_WM8350 - help - This option enables support for LEDs driven by the Wolfson - Microelectronics WM8350 AudioPlus PMIC. - config LEDS_DA903X tristate "LED Support for DA9030/DA9034 PMIC" depends on LEDS_CLASS && PMIC_DA903X diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 2d41c4dcf92..ee79340b3fa 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -13,13 +13,15 @@ obj-$(CONFIG_LEDS_S3C24XX) += leds-s3c24xx.o obj-$(CONFIG_LEDS_AMS_DELTA) += leds-ams-delta.o obj-$(CONFIG_LEDS_NET48XX) += leds-net48xx.o obj-$(CONFIG_LEDS_WRAP) += leds-wrap.o -obj-$(CONFIG_LEDS_ALIX2) += leds-alix2.o +obj-$(CONFIG_LEDS_OMAP) += leds-omap.o +obj-$(CONFIG_LEDS_OMAP_PWM) += leds-omap-pwm.o obj-$(CONFIG_LEDS_H1940) += leds-h1940.o obj-$(CONFIG_LEDS_COBALT_QUBE) += leds-cobalt-qube.o obj-$(CONFIG_LEDS_COBALT_RAQ) += leds-cobalt-raq.o obj-$(CONFIG_LEDS_SUNFIRE) += leds-sunfire.o obj-$(CONFIG_LEDS_PCA9532) += leds-pca9532.o obj-$(CONFIG_LEDS_GPIO) += leds-gpio.o +obj-$(CONFIG_LEDS_LP5521) += leds-lp5521.o obj-$(CONFIG_LEDS_CLEVO_MAIL) += leds-clevo-mail.o obj-$(CONFIG_LEDS_HP6XX) += leds-hp6xx.o obj-$(CONFIG_LEDS_FSG) += leds-fsg.o diff --git a/drivers/leds/leds-lp5521.c b/drivers/leds/leds-lp5521.c new file mode 100644 index 00000000000..3e1ca3129e3 --- /dev/null +++ b/drivers/leds/leds-lp5521.c @@ -0,0 +1,724 @@ +/* + * lp5521.c - LP5521 LED Driver + * + * Copyright (C) 2007 Nokia Corporation + * + * Written by Mathias Nyman + * Updated by Felipe Balbi + * + * 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 program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include + +#define LP5521_DRIVER_NAME "lp5521" + +#define LP5521_REG_R_PWM 0x02 +#define LP5521_REG_B_PWM 0x04 +#define LP5521_REG_ENABLE 0x00 +#define LP5521_REG_OP_MODE 0x01 +#define LP5521_REG_G_PWM 0x03 +#define LP5521_REG_R_CNTRL 0x05 +#define LP5521_REG_G_CNTRL 0x06 +#define LP5521_REG_B_CNTRL 0x07 +#define LP5521_REG_MISC 0x08 +#define LP5521_REG_R_CHANNEL_PC 0x09 +#define LP5521_REG_G_CHANNEL_PC 0x0a +#define LP5521_REG_B_CHANNEL_PC 0x0b +#define LP5521_REG_STATUS 0x0c +#define LP5521_REG_RESET 0x0d +#define LP5521_REG_GPO 0x0e +#define LP5521_REG_R_PROG_MEM 0x10 +#define LP5521_REG_G_PROG_MEM 0x30 +#define LP5521_REG_B_PROG_MEM 0x50 + +#define LP5521_CURRENT_1m5 0x0f +#define LP5521_CURRENT_3m1 0x1f +#define LP5521_CURRENT_4m7 0x2f +#define LP5521_CURRENT_6m3 0x3f +#define LP5521_CURRENT_7m9 0x4f +#define LP5521_CURRENT_9m5 0x5f +#define LP5521_CURRENT_11m1 0x6f +#define LP5521_CURRENT_12m7 0x7f +#define LP5521_CURRENT_14m3 0x8f +#define LP5521_CURRENT_15m9 0x9f +#define LP5521_CURRENT_17m5 0xaf +#define LP5521_CURRENT_19m1 0xbf +#define LP5521_CURRENT_20m7 0xcf +#define LP5521_CURRENT_22m3 0xdf +#define LP5521_CURRENT_23m9 0xef +#define LP5521_CURRENT_25m5 0xff + +#define LP5521_PROGRAM_LENGTH 32 /* in bytes */ + +struct lp5521_chip { + /* device lock */ + struct mutex lock; + struct i2c_client *client; + + struct work_struct red_work; + struct work_struct green_work; + struct work_struct blue_work; + + struct led_classdev ledr; + struct led_classdev ledg; + struct led_classdev ledb; + + enum lp5521_mode mode; + + int red; + int green; + int blue; +}; + +static int lp5521_set_mode(struct lp5521_chip *chip, enum lp5521_mode mode); + +static inline int lp5521_write(struct i2c_client *client, u8 reg, u8 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +static inline int lp5521_read(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +static int lp5521_configure(struct i2c_client *client) +{ + int ret = 0; + + /* Enable chip and set light to logarithmic mode*/ + ret |= lp5521_write(client, LP5521_REG_ENABLE, 0xc0); + + /* setting all color pwms to direct control mode */ + ret |= lp5521_write(client, LP5521_REG_OP_MODE, 0x3f); + + /* setting current to 4.7 mA for all channels */ + ret |= lp5521_write(client, LP5521_REG_R_CNTRL, LP5521_CURRENT_4m7); + ret |= lp5521_write(client, LP5521_REG_G_CNTRL, LP5521_CURRENT_4m7); + ret |= lp5521_write(client, LP5521_REG_B_CNTRL, LP5521_CURRENT_4m7); + + /* Enable auto-powersave, set charge pump to auto, red to battery */ + ret |= lp5521_write(client, LP5521_REG_MISC, 0x3c); + + /* initialize all channels pwm to zero */ + ret |= lp5521_write(client, LP5521_REG_R_PWM, 0); + ret |= lp5521_write(client, LP5521_REG_G_PWM, 0); + ret |= lp5521_write(client, LP5521_REG_B_PWM, 0); + + /* Not much can be done about errors at this point */ + return ret; +} + +static int lp5521_load_program(struct lp5521_chip *chip, u8 *pattern) +{ + struct i2c_client *client = chip->client; + int ret = 0; + + /* Enter load program mode for all led channels */ + ret |= lp5521_write(client, LP5521_REG_OP_MODE, 0x15); /* 0001 0101 */ + if (ret) + return ret; + + if (chip->red) + ret |= i2c_smbus_write_i2c_block_data(client, + LP5521_REG_R_PROG_MEM, + LP5521_PROGRAM_LENGTH, + pattern); + if (chip->green) + ret |= i2c_smbus_write_i2c_block_data(client, + LP5521_REG_G_PROG_MEM, + LP5521_PROGRAM_LENGTH, + pattern); + if (chip->blue) + ret |= i2c_smbus_write_i2c_block_data(client, + LP5521_REG_B_PROG_MEM, + LP5521_PROGRAM_LENGTH, + pattern); + + return ret; +} + +static int lp5521_run_program(struct lp5521_chip *chip) +{ + struct i2c_client *client = chip->client; + int reg; + u8 mask = 0xc0; + u8 exec_state = 0; + + reg = lp5521_read(client, LP5521_REG_ENABLE); + if (reg < 0) + return reg; + + reg &= mask; + + /* set all active channels exec state to countinous run*/ + exec_state |= (chip->red << 5); + exec_state |= (chip->green << 3); + exec_state |= (chip->blue << 1); + + reg |= exec_state; + + if (lp5521_write(client, LP5521_REG_ENABLE, reg)) + dev_dbg(&client->dev, "failed writing to register %02x\n", + LP5521_REG_ENABLE); + + /* set op-mode to run for active channels, disabled for others */ + if (lp5521_write(client, LP5521_REG_OP_MODE, exec_state)) + dev_dbg(&client->dev, "failed writing to register %02x\n", + LP5521_REG_OP_MODE); + + return 0; +} + +/*--------------------------------------------------------------*/ +/* Sysfs interface */ +/*--------------------------------------------------------------*/ + +static ssize_t show_active_channels(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct lp5521_chip *chip = dev_get_drvdata(dev); + char channels[4]; + int pos = 0; + + if (chip->red) + pos += sprintf(channels + pos, "r"); + if (chip->green) + pos += sprintf(channels + pos, "g"); + if (chip->blue) + pos += sprintf(channels + pos, "b"); + + channels[pos] = '\0'; + + return sprintf(buf, "%s\n", channels); +} + +static ssize_t store_active_channels(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct lp5521_chip *chip = dev_get_drvdata(dev); + + chip->red = 0; + chip->green = 0; + chip->blue = 0; + + if (strchr(buf, 'r') != NULL) + chip->red = 1; + if (strchr(buf, 'b') != NULL) + chip->blue = 1; + if (strchr(buf, 'g') != NULL) + chip->green = 1; + + return len; +} + +static ssize_t show_color(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + int r, g, b; + + r = lp5521_read(client, LP5521_REG_R_PWM); + g = lp5521_read(client, LP5521_REG_G_PWM); + b = lp5521_read(client, LP5521_REG_B_PWM); + + if (r < 0 || g < 0 || b < 0) + return -EINVAL; + + return sprintf(buf, "%.2x:%.2x:%.2x\n", r, g, b); +} + +static ssize_t store_color(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lp5521_chip *chip = i2c_get_clientdata(client); + int ret; + unsigned r, g, b; + + + ret = sscanf(buf, "%2x:%2x:%2x", &r, &g, &b); + if (ret != 3) + return -EINVAL; + + mutex_lock(&chip->lock); + + ret = lp5521_write(client, LP5521_REG_R_PWM, (u8)r); + ret = lp5521_write(client, LP5521_REG_G_PWM, (u8)g); + ret = lp5521_write(client, LP5521_REG_B_PWM, (u8)b); + + mutex_unlock(&chip->lock); + + return len; +} + +static ssize_t store_load(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct lp5521_chip *chip = dev_get_drvdata(dev); + int ret, nrchars, offset = 0, i = 0; + char c[3]; + unsigned cmd; + u8 pattern[LP5521_PROGRAM_LENGTH] = {0}; + + while ((offset < len - 1) && (i < LP5521_PROGRAM_LENGTH)) { + + /* separate sscanfs because length is working only for %s */ + ret = sscanf(buf + offset, "%2s%n ", c, &nrchars); + ret = sscanf(c, "%2x", &cmd); + if (ret != 1) + goto fail; + pattern[i] = (u8)cmd; + + offset += nrchars; + i++; + } + + /* pattern commands are always two bytes long */ + if (i % 2) + goto fail; + + mutex_lock(&chip->lock); + + ret = lp5521_load_program(chip, pattern); + mutex_unlock(&chip->lock); + + if (ret) { + dev_err(dev, "lp5521 failed loading pattern\n"); + return ret; + } + + return len; +fail: + dev_err(dev, "lp5521 wrong pattern format\n"); + return -EINVAL; +} + +static ssize_t show_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct lp5521_chip *chip = dev_get_drvdata(dev); + char *mode; + + mutex_lock(&chip->lock); + switch (chip->mode) { + case LP5521_MODE_RUN: + mode = "run"; + break; + case LP5521_MODE_LOAD: + mode = "load"; + break; + case LP5521_MODE_DIRECT_CONTROL: + mode = "direct"; + break; + default: + mode = "undefined"; + } + mutex_unlock(&chip->lock); + + return sprintf(buf, "%s\n", mode); +} + +static ssize_t store_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct lp5521_chip *chip = dev_get_drvdata(dev); + + mutex_lock(&chip->lock); + + if (sysfs_streq(buf, "run")) + lp5521_set_mode(chip, LP5521_MODE_RUN); + else if (sysfs_streq(buf, "load")) + lp5521_set_mode(chip, LP5521_MODE_LOAD); + else if (sysfs_streq(buf, "direct")) + lp5521_set_mode(chip, LP5521_MODE_DIRECT_CONTROL); + else + len = -EINVAL; + + mutex_unlock(&chip->lock); + + return len; +} + +static ssize_t show_current(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + int r, g, b; + + r = lp5521_read(client, LP5521_REG_R_CNTRL); + g = lp5521_read(client, LP5521_REG_G_CNTRL); + b = lp5521_read(client, LP5521_REG_B_CNTRL); + + if (r < 0 || g < 0 || b < 0) + return -EINVAL; + + r >>= 4; + g >>= 4; + b >>= 4; + + return sprintf(buf, "%x %x %x\n", r, g, b); +} + +static ssize_t store_current(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct lp5521_chip *chip = dev_get_drvdata(dev); + struct i2c_client *client = chip->client; + int ret; + unsigned curr; + + ret = sscanf(buf, "%1x", &curr); + if (ret != 1) + return -EINVAL; + + /* current level is determined by the 4 upper bits, rest is ones */ + curr = (curr << 4) | 0x0f; + + mutex_lock(&chip->lock); + + ret |= lp5521_write(client, LP5521_REG_R_CNTRL, (u8)curr); + ret |= lp5521_write(client, LP5521_REG_G_CNTRL, (u8)curr); + ret |= lp5521_write(client, LP5521_REG_B_CNTRL, (u8)curr); + + mutex_unlock(&chip->lock); + + return len; +} + +static DEVICE_ATTR(color, S_IRUGO | S_IWUGO, show_color, store_color); +static DEVICE_ATTR(load, S_IWUGO, NULL, store_load); +static DEVICE_ATTR(mode, S_IRUGO | S_IWUGO, show_mode, store_mode); +static DEVICE_ATTR(active_channels, S_IRUGO | S_IWUGO, + show_active_channels, store_active_channels); +static DEVICE_ATTR(led_current, S_IRUGO | S_IWUGO, show_current, store_current); + +static int lp5521_register_sysfs(struct i2c_client *client) +{ + struct device *dev = &client->dev; + int ret; + + ret = device_create_file(dev, &dev_attr_color); + if (ret) + goto fail1; + ret = device_create_file(dev, &dev_attr_load); + if (ret) + goto fail2; + ret = device_create_file(dev, &dev_attr_active_channels); + if (ret) + goto fail3; + ret = device_create_file(dev, &dev_attr_mode); + if (ret) + goto fail4; + ret = device_create_file(dev, &dev_attr_led_current); + if (ret) + goto fail5; + + return 0; + +fail5: + device_remove_file(dev, &dev_attr_mode); +fail4: + device_remove_file(dev, &dev_attr_active_channels); +fail3: + device_remove_file(dev, &dev_attr_load); +fail2: + device_remove_file(dev, &dev_attr_color); +fail1: + return ret; +} + +static void lp5521_unregister_sysfs(struct i2c_client *client) +{ + struct device *dev = &client->dev; + + device_remove_file(dev, &dev_attr_led_current); + device_remove_file(dev, &dev_attr_mode); + device_remove_file(dev, &dev_attr_active_channels); + device_remove_file(dev, &dev_attr_color); + device_remove_file(dev, &dev_attr_load); +} + +/*--------------------------------------------------------------*/ +/* Set chip operating mode */ +/*--------------------------------------------------------------*/ + +static int lp5521_set_mode(struct lp5521_chip *chip, enum lp5521_mode mode) +{ + struct i2c_client *client = chip->client ; + int ret = 0; + + /* if in that mode already do nothing, except for run */ + if (chip->mode == mode && mode != LP5521_MODE_RUN) + return 0; + + switch (mode) { + case LP5521_MODE_RUN: + ret = lp5521_run_program(chip); + break; + case LP5521_MODE_LOAD: + ret |= lp5521_write(client, LP5521_REG_OP_MODE, 0x15); + break; + case LP5521_MODE_DIRECT_CONTROL: + ret |= lp5521_write(client, LP5521_REG_OP_MODE, 0x3F); + break; + default: + dev_dbg(&client->dev, "unsupported mode %d\n", mode); + } + + chip->mode = mode; + + return ret; +} + +static void lp5521_red_work(struct work_struct *work) +{ + struct lp5521_chip *chip = container_of(work, struct lp5521_chip, red_work); + int ret; + + ret = lp5521_configure(chip->client); + if (ret) { + dev_dbg(&chip->client->dev, "could not configure lp5521, %d\n", + ret); + return; + } + + ret = lp5521_write(chip->client, LP5521_REG_R_PWM, chip->red); + if (ret) + dev_dbg(&chip->client->dev, "could not set brightness, %d\n", + ret); +} + +static void lp5521_red_set(struct led_classdev *led, + enum led_brightness value) +{ + struct lp5521_chip *chip = container_of(led, struct lp5521_chip, ledr); + + chip->red = value; + schedule_work(&chip->red_work); +} + +static void lp5521_green_work(struct work_struct *work) +{ + struct lp5521_chip *chip = container_of(work, struct lp5521_chip, green_work); + int ret; + + ret = lp5521_configure(chip->client); + if (ret) { + dev_dbg(&chip->client->dev, "could not configure lp5521, %d\n", + ret); + return; + } + + ret = lp5521_write(chip->client, LP5521_REG_G_PWM, chip->green); + if (ret) + dev_dbg(&chip->client->dev, "could not set brightness, %d\n", + ret); +} + +static void lp5521_green_set(struct led_classdev *led, + enum led_brightness value) +{ + struct lp5521_chip *chip = container_of(led, struct lp5521_chip, ledg); + + chip->green = value; + schedule_work(&chip->green_work); +} + +static void lp5521_blue_work(struct work_struct *work) +{ + struct lp5521_chip *chip = container_of(work, struct lp5521_chip, blue_work); + int ret; + + ret = lp5521_configure(chip->client); + if (ret) { + dev_dbg(&chip->client->dev, "could not configure lp5521, %d\n", + ret); + return; + } + + ret = lp5521_write(chip->client, LP5521_REG_B_PWM, chip->blue); + if (ret) + dev_dbg(&chip->client->dev, "could not set brightness, %d\n", + ret); +} + +static void lp5521_blue_set(struct led_classdev *led, + enum led_brightness value) +{ + struct lp5521_chip *chip = container_of(led, struct lp5521_chip, ledb); + + chip->blue = value; + schedule_work(&chip->blue_work); +} + +/*--------------------------------------------------------------*/ +/* Probe, Attach, Remove */ +/*--------------------------------------------------------------*/ + +static int __init lp5521_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct lp5521_platform_data *pdata = client->dev.platform_data; + struct lp5521_chip *chip; + char name[16]; + int ret = 0; + + if (!pdata) { + dev_err(&client->dev, "platform_data is missing\n"); + return -EINVAL; + } + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->client = client; + strncpy(client->name, LP5521_DRIVER_NAME, I2C_NAME_SIZE); + i2c_set_clientdata(client, chip); + + mutex_init(&chip->lock); + + INIT_WORK(&chip->red_work, lp5521_red_work); + INIT_WORK(&chip->green_work, lp5521_green_work); + INIT_WORK(&chip->blue_work, lp5521_blue_work); + + ret = lp5521_configure(client); + if (ret < 0) { + dev_err(&client->dev, "lp5521 error configuring chip \n"); + goto fail1; + } + + /* Set default values */ + chip->mode = pdata->mode; + chip->red = pdata->red_present; + chip->green = pdata->green_present; + chip->blue = pdata->blue_present; + + chip->ledr.brightness_set = lp5521_red_set; + chip->ledr.default_trigger = NULL; + snprintf(name, sizeof(name), "%s::red", pdata->label); + chip->ledr.name = name; + ret = led_classdev_register(&client->dev, &chip->ledr); + if (ret < 0) { + dev_dbg(&client->dev, "failed to register led %s, %d\n", + chip->ledb.name, ret); + goto fail1; + } + + chip->ledg.brightness_set = lp5521_green_set; + chip->ledg.default_trigger = NULL; + snprintf(name, sizeof(name), "%s::green", pdata->label); + chip->ledg.name = name; + ret = led_classdev_register(&client->dev, &chip->ledg); + if (ret < 0) { + dev_dbg(&client->dev, "failed to register led %s, %d\n", + chip->ledb.name, ret); + goto fail2; + } + + chip->ledb.brightness_set = lp5521_blue_set; + chip->ledb.default_trigger = NULL; + snprintf(name, sizeof(name), "%s::blue", pdata->label); + chip->ledb.name = name; + ret = led_classdev_register(&client->dev, &chip->ledb); + if (ret < 0) { + dev_dbg(&client->dev, "failed to register led %s, %d\n", chip->ledb.name, ret); + goto fail3; + } + + ret = lp5521_register_sysfs(client); + if (ret) { + dev_err(&client->dev, "lp5521 registering sysfs failed \n"); + goto fail4; + } + + return 0; + +fail4: + led_classdev_unregister(&chip->ledb); +fail3: + led_classdev_unregister(&chip->ledg); +fail2: + led_classdev_unregister(&chip->ledr); +fail1: + i2c_set_clientdata(client, NULL); + kfree(chip); + + return ret; +} + +static int __exit lp5521_remove(struct i2c_client *client) +{ + struct lp5521_chip *chip = i2c_get_clientdata(client); + + lp5521_unregister_sysfs(client); + i2c_set_clientdata(client, NULL); + + led_classdev_unregister(&chip->ledb); + led_classdev_unregister(&chip->ledg); + led_classdev_unregister(&chip->ledr); + + kfree(chip); + + return 0; +} + +static const struct i2c_device_id lp5521_id[] = { + { LP5521_DRIVER_NAME, 0}, + { }, +}; +MODULE_DEVICE_TABLE(i2c, lp5521_id); + +static struct i2c_driver lp5521_driver = { + .driver = { + .name = LP5521_DRIVER_NAME, + }, + .probe = lp5521_probe, + .remove = __exit_p(lp5521_remove), + .id_table = lp5521_id, +}; + +static int __init lp5521_init(void) +{ + return i2c_add_driver(&lp5521_driver); +} +module_init(lp5521_init); + +static void __exit lp5521_exit(void) +{ + i2c_del_driver(&lp5521_driver); +} +module_exit(lp5521_exit); + +MODULE_AUTHOR("Mathias Nyman "); +MODULE_DESCRIPTION("lp5521 LED driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-omap-pwm.c b/drivers/leds/leds-omap-pwm.c new file mode 100644 index 00000000000..57eb38359f3 --- /dev/null +++ b/drivers/leds/leds-omap-pwm.c @@ -0,0 +1,376 @@ +/* drivers/leds/leds-omap_pwm.c + * + * Driver to blink LEDs using OMAP PWM timers + * + * Copyright (C) 2006 Nokia Corporation + * Author: Timo Teras + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct omap_pwm_led { + struct led_classdev cdev; + struct work_struct work; + struct omap_pwm_led_platform_data *pdata; + struct omap_dm_timer *intensity_timer; + struct omap_dm_timer *blink_timer; + int powered; + unsigned int on_period, off_period; + enum led_brightness brightness; +}; + +static inline struct omap_pwm_led *pdev_to_omap_pwm_led(struct platform_device *pdev) +{ + return platform_get_drvdata(pdev); +} + +static inline struct omap_pwm_led *cdev_to_omap_pwm_led(struct led_classdev *led_cdev) +{ + return container_of(led_cdev, struct omap_pwm_led, cdev); +} + +static inline struct omap_pwm_led *work_to_omap_pwm_led(struct work_struct *work) +{ + return container_of(work, struct omap_pwm_led, work); +} + +static void omap_pwm_led_set_blink(struct omap_pwm_led *led) +{ + if (!led->powered) + return; + + if (led->on_period != 0 && led->off_period != 0) { + unsigned long load_reg, cmp_reg; + + load_reg = 32768 * (led->on_period + led->off_period) / 1000; + cmp_reg = 32768 * led->on_period / 1000; + + omap_dm_timer_stop(led->blink_timer); + omap_dm_timer_set_load(led->blink_timer, 1, -load_reg); + omap_dm_timer_set_match(led->blink_timer, 1, -cmp_reg); + omap_dm_timer_set_pwm(led->blink_timer, 1, 1, + OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE); + omap_dm_timer_write_counter(led->blink_timer, -2); + omap_dm_timer_start(led->blink_timer); + } else { + omap_dm_timer_set_pwm(led->blink_timer, 1, 1, + OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE); + omap_dm_timer_stop(led->blink_timer); + } +} + +static void omap_pwm_led_power_on(struct omap_pwm_led *led) +{ + if (led->powered) + return; + led->powered = 1; + + /* Select clock */ + omap_dm_timer_enable(led->intensity_timer); + omap_dm_timer_set_source(led->intensity_timer, OMAP_TIMER_SRC_32_KHZ); + + /* Turn voltage on */ + if (led->pdata->set_power != NULL) + led->pdata->set_power(led->pdata, 1); + + /* Enable PWM timers */ + if (led->blink_timer != NULL) { + omap_dm_timer_enable(led->blink_timer); + omap_dm_timer_set_source(led->blink_timer, + OMAP_TIMER_SRC_32_KHZ); + omap_pwm_led_set_blink(led); + } + + omap_dm_timer_set_load(led->intensity_timer, 1, 0xffffff00); +} + +static void omap_pwm_led_power_off(struct omap_pwm_led *led) +{ + if (!led->powered) + return; + led->powered = 0; + + /* Everything off */ + omap_dm_timer_stop(led->intensity_timer); + omap_dm_timer_disable(led->intensity_timer); + + if (led->blink_timer != NULL) { + omap_dm_timer_stop(led->blink_timer); + omap_dm_timer_disable(led->blink_timer); + } + + if (led->pdata->set_power != NULL) + led->pdata->set_power(led->pdata, 0); +} + +static void omap_pwm_led_set_pwm_cycle(struct omap_pwm_led *led, int cycle) +{ + int n; + + if (cycle == 0) + n = 0xff; + else n = cycle - 1; + + if (cycle == LED_FULL) { + omap_dm_timer_set_pwm(led->intensity_timer, 1, 1, + OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE); + omap_dm_timer_stop(led->intensity_timer); + } else { + omap_dm_timer_set_pwm(led->intensity_timer, 0, 1, + OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE); + omap_dm_timer_set_match(led->intensity_timer, 1, + (0xffffff00) | cycle); + omap_dm_timer_start(led->intensity_timer); + } +} + +static void omap_pwm_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct omap_pwm_led *led = cdev_to_omap_pwm_led(led_cdev); + + led->brightness = value; + schedule_work(&led->work); +} + +static void omap_pwm_led_work(struct work_struct *work) +{ + struct omap_pwm_led *led = work_to_omap_pwm_led(work); + + if (led->brightness != LED_OFF) { + omap_pwm_led_power_on(led); + omap_pwm_led_set_pwm_cycle(led, led->brightness); + } else { + omap_pwm_led_power_off(led); + } +} + +static ssize_t omap_pwm_led_on_period_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct omap_pwm_led *led = cdev_to_omap_pwm_led(led_cdev); + + return sprintf(buf, "%u\n", led->on_period) + 1; +} + +static ssize_t omap_pwm_led_on_period_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct omap_pwm_led *led = cdev_to_omap_pwm_led(led_cdev); + int ret = -EINVAL; + unsigned long val; + char *after; + size_t count; + + val = simple_strtoul(buf, &after, 10); + count = after - buf; + if (*after && isspace(*after)) + count++; + + if (count == size) { + led->on_period = val; + omap_pwm_led_set_blink(led); + ret = count; + } + + return ret; +} + +static ssize_t omap_pwm_led_off_period_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct omap_pwm_led *led = cdev_to_omap_pwm_led(led_cdev); + + return sprintf(buf, "%u\n", led->off_period) + 1; +} + +static ssize_t omap_pwm_led_off_period_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct omap_pwm_led *led = cdev_to_omap_pwm_led(led_cdev); + int ret = -EINVAL; + unsigned long val; + char *after; + size_t count; + + val = simple_strtoul(buf, &after, 10); + count = after - buf; + if (*after && isspace(*after)) + count++; + + if (count == size) { + led->off_period = val; + omap_pwm_led_set_blink(led); + ret = count; + } + + return ret; +} + +static DEVICE_ATTR(on_period, 0644, omap_pwm_led_on_period_show, + omap_pwm_led_on_period_store); +static DEVICE_ATTR(off_period, 0644, omap_pwm_led_off_period_show, + omap_pwm_led_off_period_store); + +static int omap_pwm_led_probe(struct platform_device *pdev) +{ + struct omap_pwm_led_platform_data *pdata = pdev->dev.platform_data; + struct omap_pwm_led *led; + int ret; + + led = kzalloc(sizeof(struct omap_pwm_led), GFP_KERNEL); + if (led == NULL) { + dev_err(&pdev->dev, "No memory for device\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, led); + led->cdev.brightness_set = omap_pwm_led_set; + led->cdev.default_trigger = NULL; + led->cdev.name = pdata->name; + led->pdata = pdata; + led->brightness = LED_OFF; + INIT_WORK(&led->work, omap_pwm_led_work); + + dev_info(&pdev->dev, "OMAP PWM LED (%s) at GP timer %d/%d\n", + pdata->name, pdata->intensity_timer, pdata->blink_timer); + + /* register our new led device */ + ret = led_classdev_register(&pdev->dev, &led->cdev); + if (ret < 0) { + dev_err(&pdev->dev, "led_classdev_register failed\n"); + goto error_classdev; + } + + /* get related dm timers */ + led->intensity_timer = omap_dm_timer_request_specific(pdata->intensity_timer); + if (led->intensity_timer == NULL) { + dev_err(&pdev->dev, "failed to request intensity pwm timer\n"); + ret = -ENODEV; + goto error_intensity; + } + omap_dm_timer_disable(led->intensity_timer); + + if (pdata->blink_timer != 0) { + led->blink_timer = omap_dm_timer_request_specific(pdata->blink_timer); + if (led->blink_timer == NULL) { + dev_err(&pdev->dev, "failed to request blinking pwm timer\n"); + ret = -ENODEV; + goto error_blink1; + } + omap_dm_timer_disable(led->blink_timer); + + ret = device_create_file(led->cdev.dev, + &dev_attr_on_period); + if(ret) + goto error_blink2; + + ret = device_create_file(led->cdev.dev, + &dev_attr_off_period); + if(ret) + goto error_blink3; + + } + + return 0; + +error_blink3: + device_remove_file(led->cdev.dev, + &dev_attr_on_period); +error_blink2: + dev_err(&pdev->dev, "failed to create device file(s)\n"); +error_blink1: + omap_dm_timer_free(led->intensity_timer); +error_intensity: + led_classdev_unregister(&led->cdev); +error_classdev: + kfree(led); + return ret; +} + +static int omap_pwm_led_remove(struct platform_device *pdev) +{ + struct omap_pwm_led *led = pdev_to_omap_pwm_led(pdev); + + device_remove_file(led->cdev.dev, + &dev_attr_on_period); + device_remove_file(led->cdev.dev, + &dev_attr_off_period); + led_classdev_unregister(&led->cdev); + + omap_pwm_led_set(&led->cdev, LED_OFF); + if (led->blink_timer != NULL) + omap_dm_timer_free(led->blink_timer); + omap_dm_timer_free(led->intensity_timer); + kfree(led); + + return 0; +} + +#ifdef CONFIG_PM +static int omap_pwm_led_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct omap_pwm_led *led = pdev_to_omap_pwm_led(pdev); + + led_classdev_suspend(&led->cdev); + return 0; +} + +static int omap_pwm_led_resume(struct platform_device *pdev) +{ + struct omap_pwm_led *led = pdev_to_omap_pwm_led(pdev); + + led_classdev_resume(&led->cdev); + return 0; +} +#else +#define omap_pwm_led_suspend NULL +#define omap_pwm_led_resume NULL +#endif + +static struct platform_driver omap_pwm_led_driver = { + .probe = omap_pwm_led_probe, + .remove = omap_pwm_led_remove, + .suspend = omap_pwm_led_suspend, + .resume = omap_pwm_led_resume, + .driver = { + .name = "omap_pwm_led", + .owner = THIS_MODULE, + }, +}; + +static int __init omap_pwm_led_init(void) +{ + return platform_driver_register(&omap_pwm_led_driver); +} + +static void __exit omap_pwm_led_exit(void) +{ + platform_driver_unregister(&omap_pwm_led_driver); +} + +module_init(omap_pwm_led_init); +module_exit(omap_pwm_led_exit); + +MODULE_AUTHOR("Timo Teras"); +MODULE_DESCRIPTION("OMAP PWM LED driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-omap.c b/drivers/leds/leds-omap.c new file mode 100644 index 00000000000..e936403f9d7 --- /dev/null +++ b/drivers/leds/leds-omap.c @@ -0,0 +1,129 @@ +/* drivers/leds/leds-omap.c + * + * (C) 2006 Samsung Electronics + * Kyungmin Park + * + * OMAP - LEDs GPIO driver + * + * 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 +#include +#include +#include +#include + +#include +#include + +/* our context */ + +static void omap_set_led_gpio(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct omap_led_config *led_dev; + + led_dev = container_of(led_cdev, struct omap_led_config, cdev); + gpio_set_value(led_dev->gpio, value); +} + +static int omap_led_probe(struct platform_device *dev) +{ + struct omap_led_platform_data *pdata = dev->dev.platform_data; + struct omap_led_config *leds = pdata->leds; + int i, ret = 0; + + for (i = 0; ret >= 0 && i < pdata->nr_leds; i++) { + ret = gpio_request(leds[i].gpio, leds[i].cdev.name); + if (ret < 0) + break; + gpio_direction_output(leds[i].gpio, 0); + if (!leds[i].cdev.brightness_set) + leds[i].cdev.brightness_set = omap_set_led_gpio; + + ret = led_classdev_register(&dev->dev, &leds[i].cdev); + } + + if (ret < 0 && i > 1) { + for (i = i - 2; i >= 0; i--) { + led_classdev_unregister(&leds[i].cdev); + gpio_free(leds[i].gpio); + } + } + + return ret; +} + +static int omap_led_remove(struct platform_device *dev) +{ + struct omap_led_platform_data *pdata = dev->dev.platform_data; + struct omap_led_config *leds = pdata->leds; + int i; + + for (i = 0; i < pdata->nr_leds; i++) { + led_classdev_unregister(&leds[i].cdev); + gpio_free(leds[i].gpio); + } + + return 0; +} + +#ifdef CONFIG_PM +static int omap_led_suspend(struct platform_device *dev, pm_message_t state) +{ + struct omap_led_platform_data *pdata = dev->dev.platform_data; + struct omap_led_config *leds = pdata->leds; + int i; + + for (i = 0; i < pdata->nr_leds; i++) + led_classdev_suspend(&leds[i].cdev); + + return 0; +} + +static int omap_led_resume(struct platform_device *dev) +{ + struct omap_led_platform_data *pdata = dev->dev.platform_data; + struct omap_led_config *leds = pdata->leds; + int i; + + for (i = 0; i < pdata->nr_leds; i++) + led_classdev_resume(&leds[i].cdev); + + return 0; +} +#else +#define omap_led_suspend NULL +#define omap_led_resume NULL +#endif + +static struct platform_driver omap_led_driver = { + .probe = omap_led_probe, + .remove = omap_led_remove, + .suspend = omap_led_suspend, + .resume = omap_led_resume, + .driver = { + .name = "omap-led", + .owner = THIS_MODULE, + }, +}; + +static int __init omap_led_init(void) +{ + return platform_driver_register(&omap_led_driver); +} + +static void __exit omap_led_exit(void) +{ + platform_driver_unregister(&omap_led_driver); +} + +module_init(omap_led_init); +module_exit(omap_led_exit); + +MODULE_AUTHOR("Kyungmin Park"); +MODULE_DESCRIPTION("OMAP LED driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index ee3927ab11e..502c1165058 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -107,6 +107,19 @@ config TWL4030_CORE high speed USB OTG transceiver, an audio codec (on most versions) and many other features. +config TWL4030_POWER + bool "Support power resources on TWL4030 family chips" + depends on TWL4030_CORE + help + Say yes here if you want to use the power resources on the + TWL4030 family chips. Most of these resources are regulators, + which have a separate driver; some are control signals, such + as clock request handshaking. + + This driver uses board-specific data to initialize the resources + and load scripts controling which resources are switched off/on + or reset when a sleep, wakeup or warm reset event occurs. + config MFD_TMIO bool default n diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 3afb5192e4d..44a0053d610 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -23,6 +23,7 @@ obj-$(CONFIG_TPS65010) += tps65010.o obj-$(CONFIG_MENELAUS) += menelaus.o obj-$(CONFIG_TWL4030_CORE) += twl4030-core.o twl4030-irq.o +obj-$(CONFIG_TWL4030_POWER) += twl4030-power.o obj-$(CONFIG_MFD_CORE) += mfd-core.o diff --git a/drivers/mfd/twl4030-core.c b/drivers/mfd/twl4030-core.c index ec90e953adc..769b34bd48e 100644 --- a/drivers/mfd/twl4030-core.c +++ b/drivers/mfd/twl4030-core.c @@ -89,6 +89,12 @@ #define twl_has_madc() false #endif +#ifdef CONFIG_TWL4030_POWER +#define twl_has_power() true +#else +#define twl_has_power() false +#endif + #if defined(CONFIG_RTC_DRV_TWL4030) || defined(CONFIG_RTC_DRV_TWL4030_MODULE) #define twl_has_rtc() true #else @@ -101,6 +107,12 @@ #define twl_has_usb() false #endif +#if defined(CONFIG_INPUT_TWL4030_PWRBUTTON) \ + || defined(CONFIG_INPUT_TWL4030_PWBUTTON_MODULE) +#define twl_has_pwrbutton() true +#else +#define twl_has_pwrbutton() false +#endif /* Triton Core internal information (BEGIN) */ @@ -225,6 +237,8 @@ static struct twl4030mapping twl4030_map[TWL4030_MODULE_LAST + 1] = { { 3, TWL4030_BASEADD_SECURED_REG }, }; +extern void twl4030_power_init(struct twl4030_power_data *triton2_scripts); + /*----------------------------------------------------------------------*/ /* Exported Functions */ @@ -526,6 +540,13 @@ add_children(struct twl4030_platform_data *pdata, unsigned long features) usb_transceiver = child; } + if (twl_has_pwrbutton()) { + child = add_child(1, "twl4030_pwrbutton", + NULL, 0, true, pdata->irq_base + 8 + 0, 0); + if (IS_ERR(child)) + return PTR_ERR(child); + } + if (twl_has_regulator()) { /* child = add_regulator(TWL4030_REG_VPLL1, pdata->vpll1); @@ -776,6 +797,10 @@ twl4030_probe(struct i2c_client *client, const struct i2c_device_id *id) /* setup clock framework */ clocks_init(&client->dev); + /* load power event scripts */ + if (twl_has_power() && pdata->power) + twl4030_power_init(pdata->power); + /* Maybe init the T2 Interrupt subsystem */ if (client->irq && pdata->irq_base diff --git a/drivers/mfd/twl4030-power.c b/drivers/mfd/twl4030-power.c new file mode 100644 index 00000000000..9dc493b8cd9 --- /dev/null +++ b/drivers/mfd/twl4030-power.c @@ -0,0 +1,364 @@ +/* + * linux/drivers/i2c/chips/twl4030-power.c + * + * Handle TWL4030 Power initialization + * + * Copyright (C) 2008 Nokia Corporation + * Copyright (C) 2006 Texas Instruments, Inc + * + * Written by Kalle Jokiniemi + * Peter De Schrijver + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include + +#include + +static u8 triton_next_free_address = 0x2b; + +#define PWR_P1_SW_EVENTS 0x10 +#define PWR_DEVOFF (1<<0) + +#define PHY_TO_OFF_PM_MASTER(p) (p - 0x36) +#define PHY_TO_OFF_PM_RECEIVER(p) (p - 0x5b) + +#define NUM_OF_RESOURCES 28 + +/* resource - hfclk */ +#define R_HFCLKOUT_DEV_GRP PHY_TO_OFF_PM_RECEIVER(0xe6) + +/* PM events */ +#define R_P1_SW_EVENTS PHY_TO_OFF_PM_MASTER(0x46) +#define R_P2_SW_EVENTS PHY_TO_OFF_PM_MASTER(0x47) +#define R_P3_SW_EVENTS PHY_TO_OFF_PM_MASTER(0x48) +#define R_CFG_P1_TRANSITION PHY_TO_OFF_PM_MASTER(0x36) +#define R_CFG_P2_TRANSITION PHY_TO_OFF_PM_MASTER(0x37) +#define R_CFG_P3_TRANSITION PHY_TO_OFF_PM_MASTER(0x38) + +#define LVL_WAKEUP 0x08 + +#define ENABLE_WARMRESET (1<<4) + +#define END_OF_SCRIPT 0x3f + +#define R_SEQ_ADD_A2S PHY_TO_OFF_PM_MASTER(0x55) +#define R_SEQ_ADD_SA12 PHY_TO_OFF_PM_MASTER(0x56) +#define R_SEQ_ADD_S2A3 PHY_TO_OFF_PM_MASTER(0x57) +#define R_SEQ_ADD_WARM PHY_TO_OFF_PM_MASTER(0x58) +#define R_MEMORY_ADDRESS PHY_TO_OFF_PM_MASTER(0x59) +#define R_MEMORY_DATA PHY_TO_OFF_PM_MASTER(0x5a) + +#define R_PROTECT_KEY 0x0E +#define KEY_1 0xC0 +#define KEY_2 0x0C + +/* resource configuration registers */ + +#define DEVGROUP_OFFSET 0 +#define TYPE_OFFSET 1 + +static u8 res_config_addrs[] = { + [RES_VAUX1] = 0x17, + [RES_VAUX2] = 0x1b, + [RES_VAUX3] = 0x1f, + [RES_VAUX4] = 0x23, + [RES_VMMC1] = 0x27, + [RES_VMMC2] = 0x2b, + [RES_VPLL1] = 0x2f, + [RES_VPLL2] = 0x33, + [RES_VSIM] = 0x37, + [RES_VDAC] = 0x3b, + [RES_VINTANA1] = 0x3f, + [RES_VINTANA2] = 0x43, + [RES_VINTDIG] = 0x47, + [RES_VIO] = 0x4b, + [RES_VDD1] = 0x55, + [RES_VDD2] = 0x63, + [RES_VUSB_1V5] = 0x71, + [RES_VUSB_1V8] = 0x74, + [RES_VUSB_3V1] = 0x77, + [RES_VUSBCP] = 0x7a, + [RES_REGEN] = 0x7f, + [RES_NRES_PWRON] = 0x82, + [RES_CLKEN] = 0x85, + [RES_SYSEN] = 0x88, + [RES_HFCLKOUT] = 0x8b, + [RES_32KCLKOUT] = 0x8e, + [RES_RESET] = 0x91, + [RES_Main_Ref] = 0x94, +}; + +static int __init twl4030_write_script_byte(u8 address, u8 byte) +{ + int err; + + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, address, + R_MEMORY_ADDRESS); + err |= twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, byte, + R_MEMORY_DATA); + + return err; +} + +static int __init twl4030_write_script_ins(u8 address, u16 pmb_message, + u8 delay, u8 next) +{ + int err = 0; + + address *= 4; + err |= twl4030_write_script_byte(address++, pmb_message >> 8); + err |= twl4030_write_script_byte(address++, pmb_message & 0xff); + err |= twl4030_write_script_byte(address++, delay); + err |= twl4030_write_script_byte(address++, next); + + return err; +} + +static int __init twl4030_write_script(u8 address, struct twl4030_ins *script, + int len) +{ + int err = 0; + + for (; len; len--, address++, script++) { + if (len == 1) + err |= twl4030_write_script_ins(address, + script->pmb_message, + script->delay, + END_OF_SCRIPT); + else + err |= twl4030_write_script_ins(address, + script->pmb_message, + script->delay, + address + 1); + } + + return err; +} + +static int __init config_wakeup3_sequence(u8 address) +{ + + int err = 0; + + /* Set SLEEP to ACTIVE SEQ address for P3 */ + err |= twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, address, + R_SEQ_ADD_S2A3); + + err |= twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, LVL_WAKEUP, + R_P3_SW_EVENTS); + if (err) + printk(KERN_ERR "TWL4030 wakeup sequence for P3" \ + "config error\n"); + + return err; +} + +static int __init config_wakeup12_sequence(u8 address) +{ + int err = 0; + + /* Set SLEEP to ACTIVE SEQ address for P1 and P2 */ + err |= twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, address, + R_SEQ_ADD_SA12); + + /* P1/P2/P3 LVL_WAKEUP should be on LEVEL */ + err |= twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, LVL_WAKEUP, + R_P1_SW_EVENTS); + err |= twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, LVL_WAKEUP, + R_P2_SW_EVENTS); + + if (machine_is_omap_3430sdp() || machine_is_omap_ldp()) { + u8 data; + /* Disabling AC charger effect on sleep-active transitions */ + err |= twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &data, + R_CFG_P1_TRANSITION); + data &= ~(1<<1); + err |= twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, data , + R_CFG_P1_TRANSITION); + } + + if (err) + printk(KERN_ERR "TWL4030 wakeup sequence for P1 and P2" \ + "config error\n"); + + return err; +} + +static int __init config_sleep_sequence(u8 address) +{ + int err = 0; + + /* + * CLKREQ is pulled high on the 2430SDP, therefore, we need to take + * it out of the HFCLKOUT DEV_GRP for P1 else HFCLKOUT can't be stopped. + */ + + err |= twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, + 0x20, R_HFCLKOUT_DEV_GRP); + + /* Set ACTIVE to SLEEP SEQ address in T2 memory*/ + err |= twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, address, + R_SEQ_ADD_A2S); + + if (err) + printk(KERN_ERR "TWL4030 sleep sequence config error\n"); + + return err; +} + +static int __init config_warmreset_sequence(u8 address) +{ + + int err = 0; + u8 rd_data; + + /* Set WARM RESET SEQ address for P1 */ + err |= twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, address, + R_SEQ_ADD_WARM); + + /* P1/P2/P3 enable WARMRESET */ + err |= twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &rd_data, + R_P1_SW_EVENTS); + rd_data |= ENABLE_WARMRESET; + err |= twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, rd_data, + R_P1_SW_EVENTS); + + err |= twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &rd_data, + R_P2_SW_EVENTS); + rd_data |= ENABLE_WARMRESET; + err |= twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, rd_data, + R_P2_SW_EVENTS); + + err |= twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &rd_data, + R_P3_SW_EVENTS); + rd_data |= ENABLE_WARMRESET; + err |= twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, rd_data, + R_P3_SW_EVENTS); + + if (err) + printk(KERN_ERR + "TWL4030 warmreset seq config error\n"); + return err; +} + +void twl4030_configure_resource(struct twl4030_resconfig *rconfig) +{ + int rconfig_addr; + u8 type; + + if (rconfig->resource > NUM_OF_RESOURCES) { + printk(KERN_ERR + "TWL4030 Resource %d does not exist\n", + rconfig->resource); + return; + } + + rconfig_addr = res_config_addrs[rconfig->resource]; + + /* Set resource group */ + + if (rconfig->devgroup >= 0) + twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, + rconfig->devgroup << 5, + rconfig_addr + DEVGROUP_OFFSET); + + /* Set resource types */ + + if (twl4030_i2c_read_u8(TWL4030_MODULE_PM_RECEIVER, + &type, + rconfig_addr + TYPE_OFFSET) < 0) { + printk(KERN_ERR + "TWL4030 Resource %d type could not read\n", + rconfig->resource); + return; + } + + if (rconfig->type >= 0) { + type &= ~7; + type |= rconfig->type; + } + + if (rconfig->type2 >= 0) { + type &= ~(3 << 3); + type |= rconfig->type2 << 3; + } + + twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, + type, rconfig_addr + TYPE_OFFSET); + +} + +static int __init load_triton_script(struct twl4030_script *tscript) +{ + u8 address = triton_next_free_address; + int err; + + err = twl4030_write_script(address, tscript->script, tscript->size); + if (err) + return err; + + triton_next_free_address += tscript->size; + + if (tscript->flags & TRITON_WRST_SCRIPT) + err |= config_warmreset_sequence(address); + + if (tscript->flags & TRITON_WAKEUP12_SCRIPT) + err |= config_wakeup12_sequence(address); + + if (tscript->flags & TRITON_WAKEUP3_SCRIPT) + err |= config_wakeup3_sequence(address); + + if (tscript->flags & TRITON_SLEEP_SCRIPT) + err |= config_sleep_sequence(address); + + return err; +} + +void __init twl4030_power_init(struct twl4030_power_data *triton2_scripts) +{ + int err = 0; + int i; + struct twl4030_resconfig *resconfig; + + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, KEY_1, + R_PROTECT_KEY); + err |= twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, KEY_2, + R_PROTECT_KEY); + if (err) + printk(KERN_ERR + "TWL4030 Unable to unlock registers\n"); + + for (i = 0; i < triton2_scripts->size; i++) { + err = load_triton_script(triton2_scripts->scripts[i]); + if (err) + break; + } + + resconfig = triton2_scripts->resource_config; + if (resconfig) { + while (resconfig->resource) { + twl4030_configure_resource(resconfig); + resconfig++; + } + } + + if (twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, 0, R_PROTECT_KEY)) + printk(KERN_ERR + "TWL4030 Unable to relock registers\n"); +} diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 6d1ac180f6e..b1b2696c9c2 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -150,6 +150,20 @@ config ATMEL_SSC If unsure, say N. +config OMAP_STI + bool "Serial Trace Interface support" + depends on ARCH_OMAP16XX || ARCH_OMAP24XX || ARCH_OMAP34XX + default n + help + Serial Trace Interface. The protocols suported for OMAP1/2/3 are + STI/CSTI/XTIv2 correspondingly. + +config OMAP_STI_CONSOLE + bool "STI console support" + depends on OMAP_STI + help + This enables a console driver by way of STI/XTI. + config ENCLOSURE_SERVICES tristate "Enclosure Services" default n diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 7871f05dcb9..8143c4d157a 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_TIFM_CORE) += tifm_core.o obj-$(CONFIG_TIFM_7XX1) += tifm_7xx1.o obj-$(CONFIG_PHANTOM) += phantom.o obj-$(CONFIG_SGI_IOC4) += ioc4.o +obj-$(CONFIG_OMAP_STI) += sti/ obj-$(CONFIG_ENCLOSURE_SERVICES) += enclosure.o obj-$(CONFIG_KGDB_TESTS) += kgdbts.o obj-$(CONFIG_SGI_XP) += sgi-xp/ diff --git a/drivers/misc/sti/Makefile b/drivers/misc/sti/Makefile new file mode 100644 index 00000000000..43574d15e92 --- /dev/null +++ b/drivers/misc/sti/Makefile @@ -0,0 +1,8 @@ +ifeq ($(CONFIG_ARCH_OMAP3),y) +obj-$(CONFIG_OMAP_STI) += sdti.o +else +obj-$(CONFIG_OMAP_STI) += sti.o sti-fifo.o +endif + +obj-$(CONFIG_NET) += sti-netlink.o +obj-$(CONFIG_OMAP_STI_CONSOLE) += sti-console.o diff --git a/drivers/misc/sti/sdti.c b/drivers/misc/sti/sdti.c new file mode 100644 index 00000000000..31780de3e7f --- /dev/null +++ b/drivers/misc/sti/sdti.c @@ -0,0 +1,227 @@ +/* + * Support functions for OMAP3 SDTI (Serial Debug Tracing Interface) + * + * Copyright (C) 2008 Nokia Corporation + * Written by: Roman Tereshonkov + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SDTI_REVISION 0x000 +#define SDTI_SYSCONFIG 0x010 +#define SDTI_SYSSTATUS 0x014 +#define SDTI_WINCTRL 0x024 +#define SDTI_SCONFIG 0x028 +#define SDTI_TESTCTRL 0x02C +#define SDTI_LOCK_ACCESS 0xFB0 + +#define CPU1_TRACE_EN 0x01 +#define CPU2_TRACE_EN 0x02 + +#define SDTI_SYSCONFIG_SOFTRESET (1 << 1) +#define SDTI_SYSCONFIG_AUTOIDLE (1 << 0) + +static struct clk *sdti_fck, *sdti_ick; +void __iomem *sti_base, *sti_channel_base; +static DEFINE_SPINLOCK(sdti_lock); +static int sdti_initialized; + +void sti_channel_write_trace(int len, int id, void *data, + unsigned int channel) +{ + const u8 *tpntr = data; + unsigned long flags; + + spin_lock_irqsave(&sdti_lock, flags); + + if (unlikely(!sdti_initialized)) + goto skip; + + sti_channel_writeb(id, channel); + while (len--) + sti_channel_writeb(*tpntr++, channel); + sti_channel_flush(channel); + skip: + spin_unlock_irqrestore(&sdti_lock, flags); +} +EXPORT_SYMBOL(sti_channel_write_trace); + +static void omap_sdti_reset(void) +{ + int i; + + sti_writel(SDTI_SYSCONFIG_SOFTRESET, SDTI_SYSCONFIG); + + for (i = 0; i < 10000; i++) + if (sti_readl(SDTI_SYSSTATUS) & 1) + break; + if (i == 10000) + printk(KERN_WARNING "XTI: no real reset\n"); +} + +static int __init omap_sdti_init(void) +{ + char buf[64]; + int i, ret = 0; + + sdti_fck = clk_get(NULL, "pclk_fck"); + if (IS_ERR(sdti_fck)) { + printk(KERN_ERR "Cannot get clk pclk_fck\n"); + ret = PTR_ERR(sdti_fck); + goto err0; + } + sdti_ick = clk_get(NULL, "pclkx2_fck"); + if (IS_ERR(sdti_ick)) { + printk(KERN_ERR "Cannot get clk pclkx2_fck\n"); + ret = PTR_ERR(sdti_ick); + goto err1; + } + ret = clk_enable(sdti_fck); + if (ret) { + printk(KERN_ERR "Cannot enable sdti_fck\n"); + goto err2; + } + ret = clk_enable(sdti_ick); + if (ret) { + printk(KERN_ERR "Cannot enable sdti_ick\n"); + goto err3; + } + + omap_sdti_reset(); + sti_writel(0xC5ACCE55, SDTI_LOCK_ACCESS); + + /* Autoidle */ + sti_writel(SDTI_SYSCONFIG_AUTOIDLE, SDTI_SYSCONFIG); + + /* Claim SDTI */ + sti_writel(1 << 30, SDTI_WINCTRL); + i = sti_readl(SDTI_WINCTRL); + if (!(i & (1 << 30))) + printk(KERN_WARNING "SDTI: cannot claim SDTI\n"); + + /* 4 bits dual, fclk/3 */ + sti_writel(0x43, SDTI_SCONFIG); + + /* CPU2 trace enable */ + sti_writel(i | CPU2_TRACE_EN, SDTI_WINCTRL); + i = sti_readl(SDTI_WINCTRL); + + /* Enable SDTI */ + sti_writel((1 << 31) | (i & 0x3FFFFFFF), SDTI_WINCTRL); + + spin_lock_irq(&sdti_lock); + sdti_initialized = 1; + spin_unlock_irq(&sdti_lock); + + i = sti_readl(SDTI_REVISION); + snprintf(buf, sizeof(buf), "OMAP SDTI support loaded (HW v%u.%u)\n", + (i >> 4) & 0x0f, i & 0x0f); + printk(KERN_INFO "%s", buf); + sti_channel_write_trace(strlen(buf), 0xc3, buf, 239); + + return ret; + +err3: + clk_disable(sdti_fck); +err2: + clk_put(sdti_ick); +err1: + clk_put(sdti_fck); +err0: + return ret; +} + +static void omap_sdti_exit(void) +{ + sti_writel(0, SDTI_WINCTRL); + clk_disable(sdti_fck); + clk_disable(sdti_ick); + clk_put(sdti_fck); + clk_put(sdti_ick); +} + +static int __devinit omap_sdti_probe(struct platform_device *pdev) +{ + struct resource *res, *cres; + unsigned int size; + + if (pdev->num_resources != 2) { + dev_err(&pdev->dev, "invalid number of resources: %d\n", + pdev->num_resources); + return -ENODEV; + } + + /* SDTI base */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (unlikely(!res)) { + dev_err(&pdev->dev, "invalid mem resource\n"); + return -ENODEV; + } + + /* Channel base */ + cres = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (unlikely(!cres)) { + dev_err(&pdev->dev, "invalid channel mem resource\n"); + return -ENODEV; + } + + size = res->end - res->start; + sti_base = ioremap(res->start, size); + if (unlikely(!sti_base)) + return -ENODEV; + + size = cres->end - cres->start; + sti_channel_base = ioremap(cres->start, size); + if (unlikely(!sti_channel_base)) { + iounmap(sti_base); + return -ENODEV; + } + + return omap_sdti_init(); +} + +static int __devexit omap_sdti_remove(struct platform_device *pdev) +{ + iounmap(sti_channel_base); + iounmap(sti_base); + omap_sdti_exit(); + + return 0; +} + +static struct platform_driver omap_sdti_driver = { + .probe = omap_sdti_probe, + .remove = __devexit_p(omap_sdti_remove), + .driver = { + .name = "sti", + .owner = THIS_MODULE, + }, +}; + +static int __init omap_sdti_module_init(void) +{ + return platform_driver_register(&omap_sdti_driver); +} + +static void __exit omap_sdti_module_exit(void) +{ + platform_driver_unregister(&omap_sdti_driver); +} +subsys_initcall(omap_sdti_module_init); +module_exit(omap_sdti_module_exit); + +MODULE_AUTHOR("Roman Tereshonkov"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/sti/sti-console.c b/drivers/misc/sti/sti-console.c new file mode 100644 index 00000000000..2062d23dca0 --- /dev/null +++ b/drivers/misc/sti/sti-console.c @@ -0,0 +1,189 @@ +/* + * Console support for OMAP STI/XTI + * + * Copyright (C) 2004, 2005, 2006 Nokia Corporation + * Written by: Paul Mundt + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "sticon" + +static struct tty_driver *tty_driver; +static DEFINE_SPINLOCK(sti_console_lock); +static unsigned int sti_console_channel = -1; +static int sti_line_done = -1; + +/* + * Write a string to any channel (including terminating NULL) + * Returns number of characters written. + */ +static int sti_channel_puts(const char *string, unsigned int channel, int len) +{ + int count = 0; + + /* + * sti_line_done is needed to determine when we have reached the + * end of the line. write() has a tendency to hand us small + * strings which otherwise end up creating newlines.. we need to + * keep the channel open and in append mode until the line has + * been terminated. + */ + if (sti_line_done != 0) { +#ifdef __LITTLE_ENDIAN + sti_channel_writeb(0xc3, channel); +#else + sti_channel_writeb(0xc0, channel); +#endif + xchg(&sti_line_done, 0); + } + + while (*string && count != len) { + char c = *string++; + + count++; + + if (c == '\n') { + xchg(&sti_line_done, 1); + sti_channel_writeb(0, channel); + break; + } else + sti_channel_writeb(c, channel); + } + + if (sti_line_done) + sti_channel_flush(channel); + + return count; +} + +static int sti_tty_open(struct tty_struct *tty, struct file *filp) +{ + return 0; +} + +static int sti_tty_write(struct tty_struct *tty, + const unsigned char *buf, int len) +{ + unsigned long flags; + int bytes; + + spin_lock_irqsave(&sti_console_lock, flags); + bytes = sti_channel_puts(buf, sti_console_channel, len); + spin_unlock_irqrestore(&sti_console_lock, flags); + + return bytes; +} + +static int sti_tty_write_room(struct tty_struct *tty) +{ + return 0x100000; +} + +static int sti_tty_chars_in_buffer(struct tty_struct *tty) +{ + return 0; +} + +static struct tty_operations sti_tty_ops = { + .open = sti_tty_open, + .write = sti_tty_write, + .write_room = sti_tty_write_room, + .chars_in_buffer = sti_tty_chars_in_buffer, +}; + +static void sti_console_write(struct console *c, const char *s, unsigned n) +{ + unsigned long flags; + + spin_lock_irqsave(&sti_console_lock, flags); + sti_channel_puts(s, sti_console_channel, n); + spin_unlock_irqrestore(&sti_console_lock, flags); +} + +static struct tty_driver *sti_console_device(struct console *c, int *index) +{ + *index = c->index; + return tty_driver; +} + +static int sti_console_setup(struct console *c, char *opts) +{ + return 0; +} + +static struct console sti_console = { + .name = DRV_NAME, + .write = sti_console_write, + .device = sti_console_device, + .setup = sti_console_setup, + .flags = CON_PRINTBUFFER | CON_ENABLED, + .index = -1, +}; + +static int __init sti_console_init(void) +{ + const struct omap_sti_console_config *info; + + info = omap_get_config(OMAP_TAG_STI_CONSOLE, + struct omap_sti_console_config); + if (info && info->enable) { + add_preferred_console(DRV_NAME, 0, NULL); + + sti_console_channel = info->channel; + } + + if (unlikely(sti_console_channel == -1)) + return -EINVAL; + + register_console(&sti_console); + + return 0; +} +__initcall(sti_console_init); + +static int __init sti_tty_init(void) +{ + struct tty_driver *tty; + int ret; + + tty = alloc_tty_driver(1); + if (!tty) + return -ENOMEM; + + tty->name = DRV_NAME; + tty->driver_name = DRV_NAME; + tty->major = 0; /* dynamic major */ + tty->minor_start = 0; + tty->type = TTY_DRIVER_TYPE_SYSTEM; + tty->subtype = SYSTEM_TYPE_SYSCONS; + tty->init_termios = tty_std_termios; + + tty_set_operations(tty, &sti_tty_ops); + + ret = tty_register_driver(tty); + if (ret) { + put_tty_driver(tty); + return ret; + } + + tty_driver = tty; + return 0; +} +late_initcall(sti_tty_init); + +module_param(sti_console_channel, uint, 0); +MODULE_PARM_DESC(sti_console_channel, "STI console channel"); +MODULE_AUTHOR("Paul Mundt"); +MODULE_DESCRIPTION("OMAP STI console support"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/sti/sti-fifo.c b/drivers/misc/sti/sti-fifo.c new file mode 100644 index 00000000000..1ea5b1896fb --- /dev/null +++ b/drivers/misc/sti/sti-fifo.c @@ -0,0 +1,117 @@ +/* + * STI RX FIFO Support + * + * Copyright (C) 2005, 2006 Nokia Corporation + * Written by: Paul Mundt and + * Roman Tereshonkov + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#include +#include +#include +#include +#include + +#define STI_READ_BUFFER_SIZE 1024 +#define sti_buf_pos(pos) ((sti_crb->bufpos + (pos)) % \ + STI_READ_BUFFER_SIZE) + +static struct sti_cycle_buffer { + int bufpos; + int datalen; + unsigned char *buf; +} *sti_crb; + +/** + * sti_read_packet - STI read packet (read an entire STI packet) + * @buf: Buffer to store the packet. + * @maxsize: Maximum size requested. + * + * This reads in a single completed STI packet from the RX FIFOs and + * places it in @buf for further processing. + * + * The return value is < 0 on error, and >= 0 for the number of bytes + * actually read. As per the STI specification, we require a 0xC1 to + * indicate the end of the packet, and we don't return the packet until + * we've read the entire thing in. + * + * Due to the size of the FIFOs, it's unrealistic to constantly drain + * this for 1 or 2 bytes at a time, so we assemble it here and return + * the whole thing. + */ +int sti_read_packet(unsigned char *buf, int maxsize) +{ + unsigned int pos; + + if (unlikely(!buf)) + return -EINVAL; + if (!sti_crb->datalen) + return 0; + + pos = sti_buf_pos(sti_crb->datalen - 1); + /* End of packet */ + if (sti_crb->buf[pos] == 0xC1) { + int i; + + for (i = 0; i < sti_crb->datalen && i < maxsize; i++) { + pos = sti_buf_pos(i); + buf[i] = sti_crb->buf[pos]; + } + + sti_crb->bufpos = sti_buf_pos(i); + sti_crb->datalen -= i; + + return i; + } + + return 0; +} +EXPORT_SYMBOL(sti_read_packet); + +static void sti_fifo_irq(unsigned long arg) +{ + /* If there is data read it */ + while (!(sti_readl(STI_RX_STATUS) & STI_RXFIFO_EMPTY)) { + unsigned int pos = sti_buf_pos(sti_crb->datalen); + + sti_crb->buf[pos] = sti_readl(STI_RX_DR); + sti_crb->datalen++; + } + + sti_ack_irq(STI_RX_INT); +} + +static int __init sti_fifo_init(void) +{ + unsigned int size; + int ret; + + size = sizeof(struct sti_cycle_buffer) + STI_READ_BUFFER_SIZE; + sti_crb = kmalloc(size, GFP_KERNEL); + if (!sti_crb) + return -ENOMEM; + + sti_crb->bufpos = sti_crb->datalen = 0; + sti_crb->buf = (unsigned char *)(sti_crb + sizeof(*sti_crb)); + + ret = sti_request_irq(STI_RX_INT, sti_fifo_irq, 0); + if (ret != 0) + kfree(sti_crb); + + return ret; +} + +static void __exit sti_fifo_exit(void) +{ + sti_free_irq(STI_RX_INT); + kfree(sti_crb); +} + +module_init(sti_fifo_init); +module_exit(sti_fifo_exit); + +MODULE_AUTHOR("Paul Mundt, Roman Tereshonkov"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/sti/sti-netlink.c b/drivers/misc/sti/sti-netlink.c new file mode 100644 index 00000000000..dbd6a03ec20 --- /dev/null +++ b/drivers/misc/sti/sti-netlink.c @@ -0,0 +1,157 @@ +/* + * OMAP STI/XTI communications interface via netlink socket. + * + * Copyright (C) 2004, 2005, 2006 Nokia Corporation + * Written by: Paul Mundt + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +static struct sock *sti_sock; +static DEFINE_MUTEX(sti_netlink_mutex); + +enum { + STI_READ, + STI_WRITE, +}; + +#if defined(CONFIG_ARCH_OMAP1) || defined(CONFIG_ARCH_OMAP2) +static int sti_netlink_read(int pid, int seq, void *payload, int size) +{ + struct sk_buff *skb; + struct nlmsghdr *nlh; + int ret, len = NLMSG_SPACE(size); + unsigned char *tail; + + skb = alloc_skb(len, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + tail = skb->tail; + nlh = NLMSG_PUT(skb, pid, seq, STI_READ, + len - (sizeof(struct nlmsghdr))); + nlh->nlmsg_flags = 0; + memcpy(NLMSG_DATA(nlh), payload, size); + nlh->nlmsg_len = skb->tail - tail; + + ret = netlink_unicast(sti_sock, skb, pid, MSG_DONTWAIT); + if (ret > 0) + ret = 0; + + return ret; + +nlmsg_failure: + if (skb) + kfree_skb(skb); + + return -EINVAL; +} +#endif + +/* + * We abuse nlmsg_type and nlmsg_flags for our purposes. + * + * The ID is encoded into the upper 8 bits of the nlmsg_type, while the + * channel number is encoded into the upper 8 bits of the nlmsg_flags. + */ +static int sti_netlink_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh) +{ + void *data; + u8 chan, id; + int size; + int ret = 0, len = 0; + + data = NLMSG_DATA(nlh); + chan = (nlh->nlmsg_flags >> 8) & 0xff; + id = (nlh->nlmsg_type >> 8) & 0xff; + size = (int)(nlh->nlmsg_len - ((char *)data - (char *)nlh)); + + switch (nlh->nlmsg_type & 0xff) { + case STI_WRITE: + sti_channel_write_trace(size, id, data, chan); + break; +#if defined(CONFIG_ARCH_OMAP1) || defined(CONFIG_ARCH_OMAP2) + case STI_READ: + data = kmalloc(size, GFP_KERNEL); + if (!data) + return -ENOMEM; + memset(data, 0, size); + + len = sti_read_packet(data, size); + ret = sti_netlink_read(NETLINK_CB(skb).pid, nlh->nlmsg_seq, + data, len); + kfree(data); + break; +#endif + default: + return -ENOTTY; + } + + return ret; +} + +static int sti_netlink_receive_skb(struct sk_buff *skb) +{ + while (skb->len >= NLMSG_SPACE(0)) { + struct nlmsghdr *nlh; + u32 rlen; + int ret; + + nlh = (struct nlmsghdr *)skb->data; + if (nlh->nlmsg_len < sizeof(struct nlmsghdr) || + skb->len < nlh->nlmsg_len) + break; + + rlen = NLMSG_ALIGN(nlh->nlmsg_len); + if (rlen > skb->len) + rlen = skb->len; + + ret = sti_netlink_receive_msg(skb, nlh); + if (ret) + netlink_ack(skb, nlh, -ret); + else if (nlh->nlmsg_flags & NLM_F_ACK) + netlink_ack(skb, nlh, 0); + + skb_pull(skb, rlen); + } + + return 0; +} + +static void sti_netlink_receive(struct sk_buff *skb) +{ + if (!mutex_trylock(&sti_netlink_mutex)) + return; + + sti_netlink_receive_skb(skb); + mutex_unlock(&sti_netlink_mutex); +} + +static int __init sti_netlink_init(void) +{ + sti_sock = netlink_kernel_create(&init_net, NETLINK_USERSOCK, 0, + sti_netlink_receive, NULL, + THIS_MODULE); + if (!sti_sock) { + printk(KERN_ERR "STI: Failed to create netlink socket\n"); + return -ENODEV; + } + + return 0; +} + +module_init(sti_netlink_init); + +MODULE_AUTHOR("Paul Mundt"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("STI netlink-driven communications interface"); diff --git a/drivers/misc/sti/sti.c b/drivers/misc/sti/sti.c new file mode 100644 index 00000000000..518298bb3b5 --- /dev/null +++ b/drivers/misc/sti/sti.c @@ -0,0 +1,430 @@ +/* + * Support functions for OMAP STI/XTI (Serial Tracing Interface) + * + * Copyright (C) 2004, 2005, 2006 Nokia Corporation + * Written by: Paul Mundt + * + * STI initialization code and channel handling + * from Juha Yrjölä . + * + * XTI initialization + * from Roman Tereshonkov . + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct clk *sti_ck; +void __iomem *sti_base, *sti_channel_base; +static unsigned long sti_kern_mask = STIEn; +static unsigned long sti_irq_mask = STI_IRQSTATUS_MASK; +static DEFINE_SPINLOCK(sti_lock); + +static struct sti_irqdesc { + irqreturn_t (*func)(unsigned long); + unsigned long data; +} ____cacheline_aligned sti_irq_desc[STI_NR_IRQS]; + +void sti_channel_write_trace(int len, int id, void *data, unsigned int channel) +{ + const u8 *tpntr = data; + + sti_channel_writeb(id, channel); + + if (cpu_is_omap16xx()) + /* Check u32 boundary */ + if (!((u32)data & (STI_PERCHANNEL_SIZE - 1)) && + (len >= STI_PERCHANNEL_SIZE)) { + const u32 *asrc = data; + + do { + sti_channel_writel(cpu_to_be32(*asrc++), + channel); + len -= STI_PERCHANNEL_SIZE; + } while (len >= STI_PERCHANNEL_SIZE); + + tpntr = (const u8 *)asrc; + } + + while (len--) + sti_channel_writeb(*tpntr++, channel); + + sti_channel_flush(channel); +} +EXPORT_SYMBOL(sti_channel_write_trace); + +void sti_enable_irq(unsigned int id) +{ + spin_lock_irq(&sti_lock); + sti_writel(1 << id, STI_IRQSETEN); + spin_unlock_irq(&sti_lock); +} +EXPORT_SYMBOL(sti_enable_irq); + +void sti_disable_irq(unsigned int id) +{ + spin_lock_irq(&sti_lock); + + if (cpu_is_omap16xx()) + sti_writel(1 << id, STI_IRQCLREN); + else if (cpu_is_omap24xx()) + sti_writel(sti_readl(STI_IRQSETEN) & ~(1 << id), STI_IRQSETEN); + else + BUG(); + + spin_unlock_irq(&sti_lock); +} +EXPORT_SYMBOL(sti_disable_irq); + +void sti_ack_irq(unsigned int id) +{ + /* Even though the clear state is 0, we have to write 1 to clear */ + sti_writel(1 << id, STI_IRQSTATUS); +} +EXPORT_SYMBOL(sti_ack_irq); + +int sti_request_irq(unsigned int irq, void *handler, unsigned long arg) +{ + struct sti_irqdesc *desc; + + if (unlikely(!handler || irq > STI_NR_IRQS)) + return -EINVAL; + + desc = sti_irq_desc + irq; + if (unlikely(desc->func)) { + printk(KERN_WARNING "STI: Attempting to request in-use IRQ " + "%d, consider fixing your code..\n", irq); + return -EBUSY; + } + + desc->func = handler; + desc->data = arg; + + sti_enable_irq(irq); + return 0; +} +EXPORT_SYMBOL(sti_request_irq); + +void sti_free_irq(unsigned int irq) +{ + struct sti_irqdesc *desc = sti_irq_desc + irq; + + if (unlikely(irq > STI_NR_IRQS)) + return; + + sti_disable_irq(irq); + + desc->func = NULL; + desc->data = 0; +} +EXPORT_SYMBOL(sti_free_irq); + +/* + * This is a bit heavy, so normally we would defer this to a tasklet. + * Unfortunately tasklets are too slow for the RX FIFO interrupt (and + * possibly some others), so we just do the irqdesc walking here. + */ +static irqreturn_t sti_interrupt(int irq, void *dev_id) +{ + int ret = IRQ_NONE; + u16 status; + int i; + + status = sti_readl(STI_IRQSTATUS) & sti_irq_mask; + + for (i = 0; status; i++) { + struct sti_irqdesc *desc = sti_irq_desc + i; + u16 id = 1 << i; + + if (!(status & id)) + continue; + + if (likely(desc && desc->func)) + ret |= desc->func(desc->data); + if (unlikely(ret == IRQ_NONE)) { + printk("STI: spurious interrupt (id %d)\n", id); + sti_disable_irq(i); + sti_ack_irq(i); + ret = IRQ_HANDLED; + } + + status &= ~id; + } + + return IRQ_RETVAL(ret); +} + +static void omap_sti_reset(void) +{ + int i; + + /* Reset STI module */ + sti_writel(0x02, STI_SYSCONFIG); + + /* Wait a while for the STI module to complete its reset */ + for (i = 0; i < 10000; i++) + if (sti_readl(STI_SYSSTATUS) & 1) + break; +} + +static int __init sti_init(void) +{ + char buf[64]; + int i; + + if (cpu_is_omap16xx()) { + /* Release ARM Rhea buses peripherals enable */ + sti_writel(sti_readl(ARM_RSTCT2) | 0x0001, ARM_RSTCT2); + + /* Enable TC1_CK (functional clock) */ + sti_ck = clk_get(NULL, "tc1_ck"); + } else if (cpu_is_omap24xx()) + /* Enable emulation tools clock */ + sti_ck = clk_get(NULL, "emul_ck"); + + if (IS_ERR(sti_ck)) + return PTR_ERR(sti_ck); + + clk_enable(sti_ck); + + /* Reset STI module */ + omap_sti_reset(); + + /* Enable STI */ + sti_trace_enable(MPUCmdEn); + + /* Change to custom serial protocol */ + sti_writel(0x01, STI_SERIAL_CFG); + + /* Set STI clock control register to normal mode */ + sti_writel(0x00, STI_CLK_CTRL); + + i = sti_readl(STI_REVISION); + snprintf(buf, sizeof(buf), "OMAP STI support loaded (HW v%u.%u)\n", + (i >> 4) & 0x0f, i & 0x0f); + printk(KERN_INFO "%s", buf); + + sti_channel_write_trace(strlen(buf), 0xc3, buf, 239); + + return 0; +} + +static void sti_exit(void) +{ + u32 tmp; + + /* + * This should have already been done by reset, but we switch off + * STI entirely just for added sanity.. + */ + tmp = sti_readl(STI_ER); + tmp &= ~STIEn; + sti_writel(tmp, STI_ER); + + clk_disable(sti_ck); + clk_put(sti_ck); +} + +static void __sti_trace_enable(int event) +{ + u32 tmp; + + tmp = sti_readl(STI_ER); + tmp |= sti_kern_mask | event; + sti_writel(tmp, STI_ER); +} + +int sti_trace_enable(int event) +{ + spin_lock_irq(&sti_lock); + sti_kern_mask |= event; + __sti_trace_enable(event); + spin_unlock_irq(&sti_lock); + + return 0; +} +EXPORT_SYMBOL(sti_trace_enable); + +static void __sti_trace_disable(int event) +{ + u32 tmp; + + tmp = sti_readl(STI_DR); + + if (cpu_is_omap16xx()) { + tmp |= event; + tmp &= ~sti_kern_mask; + } else if (cpu_is_omap24xx()) { + tmp &= ~event; + tmp |= sti_kern_mask; + } else + BUG(); + + sti_writel(tmp, STI_DR); +} + +void sti_trace_disable(int event) +{ + spin_lock_irq(&sti_lock); + sti_kern_mask &= ~event; + __sti_trace_disable(event); + spin_unlock_irq(&sti_lock); +} +EXPORT_SYMBOL(sti_trace_disable); + +static ssize_t +sti_trace_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "0x%08lx\n", sti_readl(STI_ER)); +} + +static ssize_t +sti_trace_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int evt = simple_strtoul(buf, NULL, 0); + int mask = ~evt; + + spin_lock_irq(&sti_lock); + __sti_trace_disable(mask); + __sti_trace_enable(evt); + spin_unlock_irq(&sti_lock); + + return count; +} +static DEVICE_ATTR(trace, S_IRUGO | S_IWUSR, sti_trace_show, sti_trace_store); + +static ssize_t +sti_imask_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "0x%04lx\n", sti_irq_mask); +} + +static ssize_t +sti_imask_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + spin_lock_irq(&sti_lock); + sti_irq_mask = simple_strtoul(buf, NULL, 0); + spin_unlock_irq(&sti_lock); + + return count; +} +static DEVICE_ATTR(imask, S_IRUGO | S_IWUSR, sti_imask_show, sti_imask_store); + +static int __devinit sti_probe(struct platform_device *pdev) +{ + struct resource *res, *cres; + unsigned int size; + int ret; + + if (pdev->num_resources != 3) { + dev_err(&pdev->dev, "invalid number of resources: %d\n", + pdev->num_resources); + return -ENODEV; + } + + /* STI base */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (unlikely(!res)) { + dev_err(&pdev->dev, "invalid mem resource\n"); + return -ENODEV; + } + + /* Channel base */ + cres = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (unlikely(!cres)) { + dev_err(&pdev->dev, "invalid channel mem resource\n"); + return -ENODEV; + } + + ret = device_create_file(&pdev->dev, &dev_attr_trace); + if (unlikely(ret != 0)) + return ret; + + ret = device_create_file(&pdev->dev, &dev_attr_imask); + if (unlikely(ret != 0)) + goto err; + + size = res->end - res->start + 1; + sti_base = ioremap(res->start, size); + if (!sti_base) { + ret = -ENOMEM; + goto err_badremap; + } + + size = cres->end - cres->start + 1; + sti_channel_base = ioremap(cres->start, size); + if (!sti_channel_base) { + iounmap(sti_base); + ret = -ENOMEM; + goto err_badremap; + } + + ret = request_irq(platform_get_irq(pdev, 0), sti_interrupt, + IRQF_DISABLED, "sti", NULL); + if (unlikely(ret != 0)) + goto err_badirq; + + return sti_init(); + +err_badirq: + iounmap(sti_channel_base); + iounmap(sti_base); +err_badremap: + device_remove_file(&pdev->dev, &dev_attr_imask); +err: + device_remove_file(&pdev->dev, &dev_attr_trace); + + return ret; +} + +static int __devexit sti_remove(struct platform_device *pdev) +{ + unsigned int irq = platform_get_irq(pdev, 0); + + iounmap(sti_channel_base); + iounmap(sti_base); + + device_remove_file(&pdev->dev, &dev_attr_trace); + device_remove_file(&pdev->dev, &dev_attr_imask); + free_irq(irq, NULL); + sti_exit(); + + return 0; +} + +static struct platform_driver sti_driver = { + .probe = sti_probe, + .remove = __devexit_p(sti_remove), + .driver = { + .name = "sti", + .owner = THIS_MODULE, + }, +}; + +static int __init sti_module_init(void) +{ + return platform_driver_register(&sti_driver); +} + +static void __exit sti_module_exit(void) +{ + platform_driver_unregister(&sti_driver); +} +subsys_initcall(sti_module_init); +module_exit(sti_module_exit); + +MODULE_AUTHOR("Paul Mundt, Juha Yrjölä, Roman Tereshonkov"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index b4cf691f3f6..5e7ef789c82 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -85,8 +85,9 @@ config MMC_SDHCI_OF config MMC_OMAP tristate "TI OMAP Multimedia Card Interface support" - depends on ARCH_OMAP + depends on ARCH_OMAP1 || (ARCH_OMAP2 && ARCH_OMAP2420) select TPS65010 if MACH_OMAP_H2 + select OMAP_GPIO_SWITCH if MACH_NOKIA_N800 help This selects the TI OMAP Multimedia card Interface. If you have an OMAP board with a Multimedia Card slot, diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index 5570849188c..bfa25c01c87 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -157,8 +157,6 @@ struct mmc_omap_host { struct timer_list dma_timer; unsigned dma_len; - short power_pin; - struct mmc_omap_slot *slots[OMAP_MMC_MAX_SLOTS]; struct mmc_omap_slot *current_slot; spinlock_t slot_lock; diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index d183be6f2a5..63a06266828 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -489,6 +489,7 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) } OMAP_HSMMC_WRITE(host->base, STAT, status); + OMAP_HSMMC_READ(host->base, STAT); /* flush posted write */ if (end_cmd || (status & CC)) mmc_omap_cmd_done(host, host->cmd); @@ -1070,7 +1071,6 @@ static int __init omap_mmc_probe(struct platform_device *pdev) mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count; mmc->max_seg_size = mmc->max_req_size; - mmc->ocr_avail = mmc_slot(host).ocr_mask; mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED; if (pdata->slots[host->slot_id].wires >= 8) @@ -1107,13 +1107,14 @@ static int __init omap_mmc_probe(struct platform_device *pdev) goto err_irq; } + /* initialize power supplies, gpios, etc */ if (pdata->init != NULL) { if (pdata->init(&pdev->dev) != 0) { - dev_dbg(mmc_dev(host->mmc), - "Unable to configure MMC IRQs\n"); + dev_dbg(mmc_dev(host->mmc), "late init error\n"); goto err_irq_cd_init; } } + mmc->ocr_avail = mmc_slot(host).ocr_mask; /* Request IRQ for card detect */ if ((mmc_slot(host).card_detect_irq)) { diff --git a/drivers/mtd/cmdlinepart.c b/drivers/mtd/cmdlinepart.c index 5011fa73f91..6b172a4f886 100644 --- a/drivers/mtd/cmdlinepart.c +++ b/drivers/mtd/cmdlinepart.c @@ -354,7 +354,7 @@ static int parse_cmdline_partitions(struct mtd_info *master, * * This function needs to be visible for bootloaders. */ -static int mtdpart_setup(char *s) +int mtdpart_setup(char *s) { cmdline = s; return 1; diff --git a/drivers/mtd/maps/Kconfig b/drivers/mtd/maps/Kconfig index 82923bd2d9c..5d590fe58d7 100644 --- a/drivers/mtd/maps/Kconfig +++ b/drivers/mtd/maps/Kconfig @@ -445,6 +445,13 @@ config MTD_CEIVA PhotoMax Digital Picture Frame. If you have such a device, say 'Y'. +config MTD_NOR_TOTO + tristate "NOR Flash device on TOTO board" + depends on ARCH_OMAP && OMAP_TOTO + help + This enables access to the NOR flash on the Texas Instruments + TOTO board. + config MTD_H720X tristate "Hynix evaluation board mappings" depends on MTD_CFI && ( ARCH_H7201 || ARCH_H7202 ) diff --git a/drivers/mtd/maps/Makefile b/drivers/mtd/maps/Makefile index 2dbc1bec848..a39401735ba 100644 --- a/drivers/mtd/maps/Makefile +++ b/drivers/mtd/maps/Makefile @@ -52,6 +52,7 @@ obj-$(CONFIG_MTD_NETtel) += nettel.o obj-$(CONFIG_MTD_SCB2_FLASH) += scb2_flash.o obj-$(CONFIG_MTD_H720X) += h720x-flash.o obj-$(CONFIG_MTD_SBC8240) += sbc8240.o +obj-$(CONFIG_MTD_NOR_TOTO) += omap-toto-flash.o obj-$(CONFIG_MTD_IXP4XX) += ixp4xx.o obj-$(CONFIG_MTD_IXP2000) += ixp2000.o obj-$(CONFIG_MTD_WRSBC8260) += wr_sbc82xx_flash.o diff --git a/drivers/mtd/maps/omap_nor.c b/drivers/mtd/maps/omap_nor.c index a24478102b1..8cc92958e5e 100644 --- a/drivers/mtd/maps/omap_nor.c +++ b/drivers/mtd/maps/omap_nor.c @@ -144,11 +144,12 @@ out_free_info: static int __exit omapflash_remove(struct platform_device *pdev) { struct omapflash_info *info = platform_get_drvdata(pdev); + struct flash_platform_data *pdata = pdev->dev.platform_data; platform_set_drvdata(pdev, NULL); if (info) { - if (info->parts) { + if (info->parts || (pdata && pdata->parts)) { del_mtd_partitions(info->mtd); kfree(info->parts); } else diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index 890936d0275..80b691b67b5 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -74,6 +74,31 @@ config MTD_NAND_AMS_DELTA help Support for NAND flash on Amstrad E3 (Delta). +config MTD_NAND_OMAP2 + tristate "NAND Flash device on OMAP2 and OMAP3" + depends on ARM && MTD_NAND && (ARCH_OMAP2 || ARCH_OMAP3) + help + Support for NAND flash on Texas Instruments OMAP2 and OMAP3 platforms. + +config MTD_NAND_OMAP + tristate "NAND Flash device on OMAP H3/H2/P2 boards" + depends on ARM && ARCH_OMAP1 && MTD_NAND && (MACH_OMAP_H2 || MACH_OMAP_H3 || MACH_OMAP_PERSEUS2) + help + Support for NAND flash on Texas Instruments H3/H2/P2 platforms. + +config MTD_NAND_OMAP_HW + bool "OMAP HW NAND Flash controller support" + depends on ARM && ARCH_OMAP16XX && MTD_NAND + + help + Driver for TI OMAP16xx hardware NAND flash controller. + +config MTD_NAND_TOTO + tristate "NAND Flash device on TOTO board" + depends on ARCH_OMAP && BROKEN + help + Support for NAND flash on Texas Instruments Toto platform. + config MTD_NAND_TS7250 tristate "NAND Flash device on TS-7250 board" depends on MACH_TS72XX diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index d33860ac42c..9bf5b1da2a4 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_MTD_NAND_IDS) += nand_ids.o obj-$(CONFIG_MTD_NAND_CAFE) += cafe_nand.o obj-$(CONFIG_MTD_NAND_SPIA) += spia.o obj-$(CONFIG_MTD_NAND_AMS_DELTA) += ams-delta.o +obj-$(CONFIG_MTD_NAND_TOTO) += toto.o obj-$(CONFIG_MTD_NAND_AUTCPU12) += autcpu12.o obj-$(CONFIG_MTD_NAND_EDB7312) += edb7312.o obj-$(CONFIG_MTD_NAND_AU1550) += au1550nd.o @@ -25,6 +26,9 @@ obj-$(CONFIG_MTD_NAND_CS553X) += cs553x_nand.o obj-$(CONFIG_MTD_NAND_NDFC) += ndfc.o obj-$(CONFIG_MTD_NAND_ATMEL) += atmel_nand.o obj-$(CONFIG_MTD_NAND_GPIO) += gpio.o +obj-$(CONFIG_MTD_NAND_OMAP) += omap-nand-flash.o +obj-$(CONFIG_MTD_NAND_OMAP2) += omap2.o +obj-$(CONFIG_MTD_NAND_OMAP_HW) += omap-hw.o obj-$(CONFIG_MTD_NAND_CM_X270) += cmx270_nand.o obj-$(CONFIG_MTD_NAND_BASLER_EXCITE) += excite_nandflash.o obj-$(CONFIG_MTD_NAND_PXA3xx) += pxa3xx_nand.o diff --git a/drivers/mtd/nand/omap-hw.c b/drivers/mtd/nand/omap-hw.c new file mode 100644 index 00000000000..7eb9894e029 --- /dev/null +++ b/drivers/mtd/nand/omap-hw.c @@ -0,0 +1,859 @@ +/* + * drivers/mtd/nand/omap-hw.c + * + * This is the MTD driver for OMAP1710 internal HW NAND controller. + * + * Copyright (C) 2004-2006 Nokia Corporation + * + * Author: Jarkko Lavinen and + * Juha Yrjölä + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; see the file COPYING. If not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#define NAND_BASE 0xfffbcc00 +#define NND_REVISION 0x00 +#define NND_ACCESS 0x04 +#define NND_ADDR_SRC 0x08 +#define NND_CTRL 0x10 +#define NND_MASK 0x14 +#define NND_STATUS 0x18 +#define NND_READY 0x1c +#define NND_COMMAND 0x20 +#define NND_COMMAND_SEC 0x24 +#define NND_ECC_SELECT 0x28 +#define NND_ECC_START 0x2c +#define NND_ECC_9 0x4c +#define NND_RESET 0x50 +#define NND_FIFO 0x54 +#define NND_FIFOCTRL 0x58 +#define NND_PSC_CLK 0x5c +#define NND_SYSTEST 0x60 +#define NND_SYSCFG 0x64 +#define NND_SYSSTATUS 0x68 +#define NND_FIFOTEST1 0x6c +#define NND_FIFOTEST2 0x70 +#define NND_FIFOTEST3 0x74 +#define NND_FIFOTEST4 0x78 +#define NND_PSC1_CLK 0x8c +#define NND_PSC2_CLK 0x90 + + +#define NND_CMD_READ1_LOWER 0x00 +#define NND_CMD_WRITE1_LOWER 0x00 +#define NND_CMD_READ1_UPPER 0x01 +#define NND_CMD_WRITE1_UPPER 0x01 +#define NND_CMD_PROGRAM_END 0x10 +#define NND_CMD_READ2_SPARE 0x50 +#define NND_CMD_WRITE2_SPARE 0x50 +#define NND_CMD_ERASE 0x60 +#define NND_CMD_STATUS 0x70 +#define NND_CMD_PROGRAM 0x80 +#define NND_CMD_READ_ID 0x90 +#define NND_CMD_ERASE_END 0xD0 +#define NND_CMD_RESET 0xFF + + +#define NAND_Ecc_P1e (1 << 0) +#define NAND_Ecc_P2e (1 << 1) +#define NAND_Ecc_P4e (1 << 2) +#define NAND_Ecc_P8e (1 << 3) +#define NAND_Ecc_P16e (1 << 4) +#define NAND_Ecc_P32e (1 << 5) +#define NAND_Ecc_P64e (1 << 6) +#define NAND_Ecc_P128e (1 << 7) +#define NAND_Ecc_P256e (1 << 8) +#define NAND_Ecc_P512e (1 << 9) +#define NAND_Ecc_P1024e (1 << 10) +#define NAND_Ecc_P2048e (1 << 11) + +#define NAND_Ecc_P1o (1 << 16) +#define NAND_Ecc_P2o (1 << 17) +#define NAND_Ecc_P4o (1 << 18) +#define NAND_Ecc_P8o (1 << 19) +#define NAND_Ecc_P16o (1 << 20) +#define NAND_Ecc_P32o (1 << 21) +#define NAND_Ecc_P64o (1 << 22) +#define NAND_Ecc_P128o (1 << 23) +#define NAND_Ecc_P256o (1 << 24) +#define NAND_Ecc_P512o (1 << 25) +#define NAND_Ecc_P1024o (1 << 26) +#define NAND_Ecc_P2048o (1 << 27) + +#define TF(value) (value ? 1 : 0) + +#define P2048e(a) (TF(a & NAND_Ecc_P2048e) << 0 ) +#define P2048o(a) (TF(a & NAND_Ecc_P2048o) << 1 ) +#define P1e(a) (TF(a & NAND_Ecc_P1e) << 2 ) +#define P1o(a) (TF(a & NAND_Ecc_P1o) << 3 ) +#define P2e(a) (TF(a & NAND_Ecc_P2e) << 4 ) +#define P2o(a) (TF(a & NAND_Ecc_P2o) << 5 ) +#define P4e(a) (TF(a & NAND_Ecc_P4e) << 6 ) +#define P4o(a) (TF(a & NAND_Ecc_P4o) << 7 ) + +#define P8e(a) (TF(a & NAND_Ecc_P8e) << 0 ) +#define P8o(a) (TF(a & NAND_Ecc_P8o) << 1 ) +#define P16e(a) (TF(a & NAND_Ecc_P16e) << 2 ) +#define P16o(a) (TF(a & NAND_Ecc_P16o) << 3 ) +#define P32e(a) (TF(a & NAND_Ecc_P32e) << 4 ) +#define P32o(a) (TF(a & NAND_Ecc_P32o) << 5 ) +#define P64e(a) (TF(a & NAND_Ecc_P64e) << 6 ) +#define P64o(a) (TF(a & NAND_Ecc_P64o) << 7 ) + +#define P128e(a) (TF(a & NAND_Ecc_P128e) << 0 ) +#define P128o(a) (TF(a & NAND_Ecc_P128o) << 1 ) +#define P256e(a) (TF(a & NAND_Ecc_P256e) << 2 ) +#define P256o(a) (TF(a & NAND_Ecc_P256o) << 3 ) +#define P512e(a) (TF(a & NAND_Ecc_P512e) << 4 ) +#define P512o(a) (TF(a & NAND_Ecc_P512o) << 5 ) +#define P1024e(a) (TF(a & NAND_Ecc_P1024e) << 6 ) +#define P1024o(a) (TF(a & NAND_Ecc_P1024o) << 7 ) + +#define P8e_s(a) (TF(a & NAND_Ecc_P8e) << 0 ) +#define P8o_s(a) (TF(a & NAND_Ecc_P8o) << 1 ) +#define P16e_s(a) (TF(a & NAND_Ecc_P16e) << 2 ) +#define P16o_s(a) (TF(a & NAND_Ecc_P16o) << 3 ) +#define P1e_s(a) (TF(a & NAND_Ecc_P1e) << 4 ) +#define P1o_s(a) (TF(a & NAND_Ecc_P1o) << 5 ) +#define P2e_s(a) (TF(a & NAND_Ecc_P2e) << 6 ) +#define P2o_s(a) (TF(a & NAND_Ecc_P2o) << 7 ) + +#define P4e_s(a) (TF(a & NAND_Ecc_P4e) << 0 ) +#define P4o_s(a) (TF(a & NAND_Ecc_P4o) << 1 ) + +extern struct nand_oobinfo jffs2_oobinfo; + +/* + * MTD structure for OMAP board + */ +static struct mtd_info *omap_mtd; +static struct clk *omap_nand_clk; +static int omap_nand_dma_ch; +static struct completion omap_nand_dma_comp; +static unsigned long omap_nand_base = OMAP1_IO_ADDRESS(NAND_BASE); + +static inline u32 nand_read_reg(int idx) +{ + return __raw_readl(omap_nand_base + idx); +} + +static inline void nand_write_reg(int idx, u32 val) +{ + __raw_writel(val, omap_nand_base + idx); +} + +static inline u8 nand_read_reg8(int idx) +{ + return __raw_readb(omap_nand_base + idx); +} + +static inline void nand_write_reg8(int idx, u8 val) +{ + __raw_writeb(val, omap_nand_base + idx); +} + +static void omap_nand_select_chip(struct mtd_info *mtd, int chip) +{ + u32 l; + + switch(chip) { + case -1: + l = nand_read_reg(NND_CTRL); + l |= (1 << 8) | (1 << 10) | (1 << 12) | (1 << 14); + nand_write_reg(NND_CTRL, l); + break; + case 0: + /* Also CS1, CS2, CS4 would be available */ + l = nand_read_reg(NND_CTRL); + l &= ~(1 << 8); + nand_write_reg(NND_CTRL, l); + break; + default: + BUG(); + } +} + +static void nand_dma_cb(int lch, u16 ch_status, void *data) +{ + complete((struct completion *) data); +} + +static void omap_nand_dma_transfer(struct mtd_info *mtd, void *addr, + unsigned int u32_count, int is_write) +{ + const int block_size = 16; + unsigned int block_count, len; + int dma_ch; + unsigned long fifo_reg, timeout, jiffies_before, jiffies_spent; + static unsigned long max_jiffies = 0; + + dma_ch = omap_nand_dma_ch; + block_count = u32_count * 4 / block_size; + nand_write_reg(NND_STATUS, 0x0f); + nand_write_reg(NND_FIFOCTRL, (block_size << 24) | block_count); + fifo_reg = NAND_BASE + NND_FIFO; + if (is_write) { + omap_set_dma_dest_params(dma_ch, OMAP_DMA_PORT_TIPB, + OMAP_DMA_AMODE_CONSTANT, fifo_reg, + 0, 0); + omap_set_dma_src_params(dma_ch, OMAP_DMA_PORT_EMIFF, + OMAP_DMA_AMODE_POST_INC, + virt_to_phys(addr), + 0, 0); +// omap_set_dma_src_burst_mode(dma_ch, OMAP_DMA_DATA_BURST_4); + /* Set POSTWRITE bit */ + nand_write_reg(NND_CTRL, nand_read_reg(NND_CTRL) | (1 << 16)); + } else { + omap_set_dma_src_params(dma_ch, OMAP_DMA_PORT_TIPB, + OMAP_DMA_AMODE_CONSTANT, fifo_reg, + 0, 0); + omap_set_dma_dest_params(dma_ch, OMAP_DMA_PORT_EMIFF, + OMAP_DMA_AMODE_POST_INC, + virt_to_phys(addr), + 0, 0); +// omap_set_dma_dest_burst_mode(dma_ch, OMAP_DMA_DATA_BURST_8); + /* Set PREFETCH bit */ + nand_write_reg(NND_CTRL, nand_read_reg(NND_CTRL) | (1 << 17)); + } + omap_set_dma_transfer_params(dma_ch, OMAP_DMA_DATA_TYPE_S32, block_size / 4, + block_count, OMAP_DMA_SYNC_FRAME, + 0, 0); + init_completion(&omap_nand_dma_comp); + + len = u32_count << 2; + dma_cache_maint(addr, len, DMA_TO_DEVICE); + omap_start_dma(dma_ch); + jiffies_before = jiffies; + timeout = wait_for_completion_timeout(&omap_nand_dma_comp, + msecs_to_jiffies(1000)); + jiffies_spent = (unsigned long)((long)jiffies - (long)jiffies_before); + if (jiffies_spent > max_jiffies) + max_jiffies = jiffies_spent; + + if (timeout == 0) { + printk(KERN_WARNING "omap-hw-nand: DMA timeout after %u ms, max. seen latency %u ms\n", + jiffies_to_msecs(jiffies_spent), + jiffies_to_msecs(max_jiffies)); + } + if (!is_write) + dma_cache_maint(addr, len, DMA_FROM_DEVICE); + + nand_write_reg(NND_CTRL, nand_read_reg(NND_CTRL) & ~((1 << 16) | (1 << 17))); +} + +static void fifo_read(u32 *out, unsigned int len) +{ + const int block_size = 16; + unsigned long status_reg, fifo_reg; + int c; + + status_reg = omap_nand_base + NND_STATUS; + fifo_reg = omap_nand_base + NND_FIFO; + len = len * 4 / block_size; + nand_write_reg(NND_FIFOCTRL, (block_size << 24) | len); + nand_write_reg(NND_STATUS, 0x0f); + nand_write_reg(NND_CTRL, nand_read_reg(NND_CTRL) | (1 << 17)); + c = block_size / 4; + while (len--) { + int i; + + while ((__raw_readl(status_reg) & (1 << 2)) == 0); + __raw_writel(0x0f, status_reg); + for (i = 0; i < c; i++) { + u32 l = __raw_readl(fifo_reg); + *out++ = l; + } + } + nand_write_reg(NND_CTRL, nand_read_reg(NND_CTRL) & ~(1 << 17)); + nand_write_reg(NND_STATUS, 0x0f); +} + +static void omap_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len) +{ + unsigned long access_reg; + + if (likely(((unsigned long) buf & 3) == 0 && (len & 3) == 0)) { + int u32_count = len >> 2; + u32 *dest = (u32 *) buf; + /* If the transfer is big enough and the length divisible by + * 16, we try to use DMA transfer, or FIFO copy in case of + * DMA failure (e.g. all channels busy) */ + if (u32_count > 64 && (u32_count & 3) == 0) { + if (omap_nand_dma_ch >= 0) { + omap_nand_dma_transfer(mtd, buf, u32_count, 0); + return; + } + /* In case of an error, fallback to FIFO copy */ + fifo_read((u32 *) buf, u32_count); + return; + } + access_reg = omap_nand_base + NND_ACCESS; + /* Small buffers we just read directly */ + while (u32_count--) + *dest++ = __raw_readl(access_reg); + } else { + /* If we're not word-aligned, we use byte copy */ + access_reg = omap_nand_base + NND_ACCESS; + while (len--) + *buf++ = __raw_readb(access_reg); + } +} + +static void omap_nand_write_buf(struct mtd_info *mtd, const u_char *buf, int len) +{ + if (likely(((unsigned long) buf & 3) == 0 && (len & 3) == 0)) { + const u32 *src = (const u32 *) buf; + + len >>= 2; +#if 0 + /* If the transfer is big enough and length divisible by 16, + * we try to use DMA transfer. */ + if (len > 256 / 4 && (len & 3) == 0) { + if (omap_nand_dma_transfer(mtd, (void *) buf, len, 1) == 0) + return; + /* In case of an error, fallback to CPU copy */ + } +#endif + while (len--) + nand_write_reg(NND_ACCESS, *src++); + } else { + while (len--) + nand_write_reg8(NND_ACCESS, *buf++); + } +} + +static int omap_nand_verify_buf(struct mtd_info *mtd, const u_char *buf, int len) +{ + if (likely(((unsigned long) buf & 3) == 0 && (len & 3) == 0)) { + const u32 *dest = (const u32 *) buf; + len >>= 2; + while (len--) + if (*dest++ != nand_read_reg(NND_ACCESS)) + return -EFAULT; + } else { + while (len--) + if (*buf++ != nand_read_reg8(NND_ACCESS)) + return -EFAULT; + } + return 0; +} + +static u_char omap_nand_read_byte(struct mtd_info *mtd) +{ + return nand_read_reg8(NND_ACCESS); +} + +static int omap_nand_dev_ready(struct mtd_info *mtd) +{ + u32 l; + + l = nand_read_reg(NND_READY); + return l & 0x01; +} + +static int nand_write_command(u8 cmd, u32 addr, int addr_valid) +{ + if (addr_valid) { + nand_write_reg(NND_ADDR_SRC, addr); + nand_write_reg8(NND_COMMAND, cmd); + } else { + nand_write_reg(NND_ADDR_SRC, 0); + nand_write_reg8(NND_COMMAND_SEC, cmd); + } + while (!omap_nand_dev_ready(NULL)); + return 0; +} + +/* + * Send command to NAND device + */ +static void omap_nand_command(struct mtd_info *mtd, unsigned command, int column, int page_addr) +{ + struct nand_chip *this = mtd->priv; + + /* + * Write out the command to the device. + */ + if (command == NAND_CMD_SEQIN) { + int readcmd; + + if (column >= mtd->writesize) { + /* OOB area */ + column -= mtd->writesize; + readcmd = NAND_CMD_READOOB; + } else if (column < 256) { + /* First 256 bytes --> READ0 */ + readcmd = NAND_CMD_READ0; + } else { + column -= 256; + readcmd = NAND_CMD_READ1; + } + nand_write_command(readcmd, 0, 0); + } + switch (command) { + case NAND_CMD_RESET: + case NAND_CMD_PAGEPROG: + case NAND_CMD_STATUS: + case NAND_CMD_ERASE2: + nand_write_command(command, 0, 0); + break; + case NAND_CMD_ERASE1: + nand_write_command(command, ((page_addr & 0xFFFFFF00) << 1) | (page_addr & 0XFF), 1); + break; + default: + nand_write_command(command, (page_addr << this->page_shift) | column, 1); + } +} + +static void omap_nand_command_lp(struct mtd_info *mtd, unsigned command, int column, int page_addr) +{ + struct nand_chip *this = mtd->priv; + + if (command == NAND_CMD_READOOB) { + column += mtd->writesize; + command = NAND_CMD_READ0; + } + switch (command) { + case NAND_CMD_RESET: + case NAND_CMD_PAGEPROG: + case NAND_CMD_STATUS: + case NAND_CMD_ERASE2: + nand_write_command(command, 0, 0); + break; + case NAND_CMD_ERASE1: + nand_write_command(command, page_addr << this->page_shift >> 11, 1); + break; + default: + nand_write_command(command, (page_addr << 16) | column, 1); + } + if (command == NAND_CMD_READ0) + nand_write_command(NAND_CMD_READSTART, 0, 0); +} + +/* + * Generate non-inverted ECC bytes. + * + * Using noninverted ECC can be considered ugly since writing a blank + * page ie. padding will clear the ECC bytes. This is no problem as long + * nobody is trying to write data on the seemingly unused page. + * + * Reading an erased page will produce an ECC mismatch between + * generated and read ECC bytes that has to be dealt with separately. + */ +static int omap_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code) +{ + u32 l; + int reg; + int n; + struct nand_chip *this = mtd->priv; + + /* Ex NAND_ECC_HW12_2048 */ + if ((this->ecc.mode == NAND_ECC_HW) && (this->ecc.size == 2048)) + n = 4; + else + n = 1; + reg = NND_ECC_START; + while (n--) { + l = nand_read_reg(reg); + *ecc_code++ = l; // P128e, ..., P1e + *ecc_code++ = l >> 16; // P128o, ..., P1o + // P2048o, P1024o, P512o, P256o, P2048e, P1024e, P512e, P256e + *ecc_code++ = ((l >> 8) & 0x0f) | ((l >> 20) & 0xf0); + reg += 4; + } + return 0; +} + +/* + * This function will generate true ECC value, which can be used + * when correcting data read from NAND flash memory core + */ +static void gen_true_ecc(u8 *ecc_buf) +{ + u32 tmp = ecc_buf[0] | (ecc_buf[1] << 16) | ((ecc_buf[2] & 0xF0) << 20) | ((ecc_buf[2] & 0x0F) << 8); + + ecc_buf[0] = ~(P64o(tmp) | P64e(tmp) | P32o(tmp) | P32e(tmp) | P16o(tmp) | P16e(tmp) | P8o(tmp) | P8e(tmp) ); + ecc_buf[1] = ~(P1024o(tmp) | P1024e(tmp) | P512o(tmp) | P512e(tmp) | P256o(tmp) | P256e(tmp) | P128o(tmp) | P128e(tmp)); + ecc_buf[2] = ~( P4o(tmp) | P4e(tmp) | P2o(tmp) | P2e(tmp) | P1o(tmp) | P1e(tmp) | P2048o(tmp) | P2048e(tmp)); +} + +/* + * This function compares two ECC's and indicates if there is an error. + * If the error can be corrected it will be corrected to the buffer + */ +static int omap_nand_compare_ecc(u8 *ecc_data1, /* read from NAND memory */ + u8 *ecc_data2, /* read from register */ + u8 *page_data) +{ + uint i; + u8 tmp0_bit[8], tmp1_bit[8], tmp2_bit[8]; + u8 comp0_bit[8], comp1_bit[8], comp2_bit[8]; + u8 ecc_bit[24]; + u8 ecc_sum = 0; + u8 find_bit = 0; + uint find_byte = 0; + int isEccFF; + + isEccFF = ((*(u32 *)ecc_data1 & 0xFFFFFF) == 0xFFFFFF); + + gen_true_ecc(ecc_data1); + gen_true_ecc(ecc_data2); + + for (i = 0; i <= 2; i++) { + *(ecc_data1 + i) = ~(*(ecc_data1 + i)); + *(ecc_data2 + i) = ~(*(ecc_data2 + i)); + } + + for (i = 0; i < 8; i++) { + tmp0_bit[i] = *ecc_data1 % 2; + *ecc_data1 = *ecc_data1 / 2; + } + + for (i = 0; i < 8; i++) { + tmp1_bit[i] = *(ecc_data1 + 1) % 2; + *(ecc_data1 + 1) = *(ecc_data1 + 1) / 2; + } + + for (i = 0; i < 8; i++) { + tmp2_bit[i] = *(ecc_data1 + 2) % 2; + *(ecc_data1 + 2) = *(ecc_data1 + 2) / 2; + } + + for (i = 0; i < 8; i++) { + comp0_bit[i] = *ecc_data2 % 2; + *ecc_data2 = *ecc_data2 / 2; + } + + for (i = 0; i < 8; i++) { + comp1_bit[i] = *(ecc_data2 + 1) % 2; + *(ecc_data2 + 1) = *(ecc_data2 + 1) / 2; + } + + for (i = 0; i < 8; i++) { + comp2_bit[i] = *(ecc_data2 + 2) % 2; + *(ecc_data2 + 2) = *(ecc_data2 + 2) / 2; + } + + for (i = 0; i< 6; i++ ) + ecc_bit[i] = tmp2_bit[i + 2] ^ comp2_bit[i + 2]; + + for (i = 0; i < 8; i++) + ecc_bit[i + 6] = tmp0_bit[i] ^ comp0_bit[i]; + + for (i = 0; i < 8; i++) + ecc_bit[i + 14] = tmp1_bit[i] ^ comp1_bit[i]; + + ecc_bit[22] = tmp2_bit[0] ^ comp2_bit[0]; + ecc_bit[23] = tmp2_bit[1] ^ comp2_bit[1]; + + for (i = 0; i < 24; i++) + ecc_sum += ecc_bit[i]; + + switch (ecc_sum) { + case 0: + /* Not reached because this function is not called if + ECC values are equal */ + return 0; + + case 1: + /* Uncorrectable error */ + DEBUG (MTD_DEBUG_LEVEL0, "ECC UNCORRECTED_ERROR 1\n"); + return -1; + + case 12: + /* Correctable error */ + find_byte = (ecc_bit[23] << 8) + + (ecc_bit[21] << 7) + + (ecc_bit[19] << 6) + + (ecc_bit[17] << 5) + + (ecc_bit[15] << 4) + + (ecc_bit[13] << 3) + + (ecc_bit[11] << 2) + + (ecc_bit[9] << 1) + + ecc_bit[7]; + + find_bit = (ecc_bit[5] << 2) + (ecc_bit[3] << 1) + ecc_bit[1]; + + DEBUG (MTD_DEBUG_LEVEL0, "Correcting single bit ECC error at offset: %d, bit: %d\n", find_byte, find_bit); + + page_data[find_byte] ^= (1 << find_bit); + + return 0; + default: + if (isEccFF) { + if (ecc_data2[0] == 0 && ecc_data2[1] == 0 && ecc_data2[2] == 0) + return 0; + } + DEBUG (MTD_DEBUG_LEVEL0, "UNCORRECTED_ERROR default\n"); + return -1; + } +} + +static int omap_nand_correct_data(struct mtd_info *mtd, u_char *dat, u_char *read_ecc, u_char *calc_ecc) +{ + struct nand_chip *this; + int block_count = 0, i, r; + + this = mtd->priv; + /* Ex NAND_ECC_HW12_2048 */ + if ((this->ecc.mode == NAND_ECC_HW) && (this->ecc.size == 2048)) + block_count = 4; + else + block_count = 1; + for (i = 0; i < block_count; i++) { + if (memcmp(read_ecc, calc_ecc, 3) != 0) { + r = omap_nand_compare_ecc(read_ecc, calc_ecc, dat); + if (r < 0) + return r; + } + read_ecc += 3; + calc_ecc += 3; + dat += 512; + } + return 0; +} + +static void omap_nand_enable_hwecc(struct mtd_info *mtd, int mode) +{ + nand_write_reg(NND_RESET, 0x01); +} + +#ifdef CONFIG_MTD_CMDLINE_PARTS + +extern int mtdpart_setup(char *); + +static int __init add_dynamic_parts(struct mtd_info *mtd) +{ + static const char *part_parsers[] = { "cmdlinepart", NULL }; + struct mtd_partition *parts; + const struct omap_flash_part_str_config *cfg; + char *part_str = NULL; + size_t part_str_len; + int c; + + cfg = omap_get_var_config(OMAP_TAG_FLASH_PART_STR, &part_str_len); + if (cfg != NULL) { + part_str = kmalloc(part_str_len + 1, GFP_KERNEL); + if (part_str == NULL) + return -ENOMEM; + memcpy(part_str, cfg->part_table, part_str_len); + part_str[part_str_len] = '\0'; + mtdpart_setup(part_str); + } + c = parse_mtd_partitions(omap_mtd, part_parsers, &parts, 0); + if (part_str != NULL) { + mtdpart_setup(NULL); + kfree(part_str); + } + if (c <= 0) + return -1; + + add_mtd_partitions(mtd, parts, c); + + return 0; +} + +#else + +static inline int add_dynamic_parts(struct mtd_info *mtd) +{ + return -1; +} + +#endif + +static inline int calc_psc(int ns, int cycle_ps) +{ + return (ns * 1000 + (cycle_ps - 1)) / cycle_ps; +} + +static void set_psc_regs(int psc_ns, int psc1_ns, int psc2_ns) +{ + int psc[3], i; + unsigned long rate, ps; + + rate = clk_get_rate(omap_nand_clk); + ps = 1000000000 / (rate / 1000); + psc[0] = calc_psc(psc_ns, ps); + psc[1] = calc_psc(psc1_ns, ps); + psc[2] = calc_psc(psc2_ns, ps); + for (i = 0; i < 3; i++) { + if (psc[i] < 2) + psc[i] = 2; + else if (psc[i] > 256) + psc[i] = 256; + } + nand_write_reg(NND_PSC_CLK, psc[0] - 1); + nand_write_reg(NND_PSC1_CLK, psc[1] - 1); + nand_write_reg(NND_PSC2_CLK, psc[2] - 1); + printk(KERN_INFO "omap-hw-nand: using PSC values %d, %d, %d\n", psc[0], psc[1], psc[2]); +} + +/* + * Main initialization routine + */ +static int __init omap_nand_init(void) +{ + struct nand_chip *this; + int err = 0; + u32 l; + + omap_nand_clk = clk_get(NULL, "armper_ck"); + BUG_ON(omap_nand_clk == NULL); + clk_enable(omap_nand_clk); + + l = nand_read_reg(NND_REVISION); + printk(KERN_INFO "omap-hw-nand: OMAP NAND Controller rev. %d.%d\n", l>>4, l & 0xf); + + /* Reset the NAND Controller */ + nand_write_reg(NND_SYSCFG, 0x02); + while ((nand_read_reg(NND_SYSSTATUS) & 0x01) == 0); + + /* No Prefetch, no postwrite, write prot & enable pairs disabled, + addres counter set to send 4 byte addresses to flash, + A8 is set not to be sent to flash (erase addre needs formatting), + choose little endian, enable 512 byte ECC logic, + */ + nand_write_reg(NND_CTRL, 0xFF01); + + /* Allocate memory for MTD device structure and private data */ + omap_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip), GFP_KERNEL); + if (!omap_mtd) { + printk(KERN_WARNING "omap-hw-nand: Unable to allocate OMAP NAND MTD device structure.\n"); + err = -ENOMEM; + goto free_clock; + } +#if 1 + err = omap_request_dma(OMAP_DMA_NAND, "NAND", nand_dma_cb, + &omap_nand_dma_comp, &omap_nand_dma_ch); + if (err < 0) { + printk(KERN_WARNING "omap-hw-nand: Unable to reserve DMA channel\n"); + omap_nand_dma_ch = -1; + } +#else + omap_nand_dma_ch = -1; +#endif + /* Get pointer to private data */ + this = (struct nand_chip *) (&omap_mtd[1]); + + /* Initialize structures */ + memset((char *) omap_mtd, 0, sizeof(struct mtd_info)); + memset((char *) this, 0, sizeof(struct nand_chip)); + + /* Link the private data with the MTD structure */ + omap_mtd->priv = this; + omap_mtd->name = "omap-nand"; + + this->options = NAND_SKIP_BBTSCAN; + + /* Used from chip select and nand_command() */ + this->read_byte = omap_nand_read_byte; + + this->select_chip = omap_nand_select_chip; + this->dev_ready = omap_nand_dev_ready; + this->chip_delay = 0; + this->ecc.mode = NAND_ECC_HW; + this->ecc.bytes = 3; + this->ecc.size = 512; + this->cmdfunc = omap_nand_command; + this->write_buf = omap_nand_write_buf; + this->read_buf = omap_nand_read_buf; + this->verify_buf = omap_nand_verify_buf; + this->ecc.calculate = omap_nand_calculate_ecc; + this->ecc.correct = omap_nand_correct_data; + this->ecc.hwctl = omap_nand_enable_hwecc; + + nand_write_reg(NND_SYSCFG, 0x1); /* Enable auto idle */ + nand_write_reg(NND_PSC_CLK, 10); + /* Scan to find existance of the device */ + if (nand_scan(omap_mtd, 1)) { + err = -ENXIO; + goto out_mtd; + } + + set_psc_regs(25, 15, 35); + if (this->page_shift == 11) { + this->cmdfunc = omap_nand_command_lp; + l = nand_read_reg(NND_CTRL); + l |= 1 << 4; /* Set the A8 bit in CTRL reg */ + nand_write_reg(NND_CTRL, l); + this->ecc.mode = NAND_ECC_HW; + this->ecc.steps = 1; + this->ecc.size = 2048; + this->ecc.bytes = 12; + nand_write_reg(NND_ECC_SELECT, 6); + } + + /* We have to do bbt scanning ourselves */ + if (this->scan_bbt (omap_mtd)) { + err = -ENXIO; + goto out_mtd; + } + + err = add_dynamic_parts(omap_mtd); + if (err < 0) { + printk(KERN_ERR "omap-hw-nand: no partitions defined\n"); + err = -ENODEV; + nand_release(omap_mtd); + goto out_mtd; + } + /* init completed */ + return 0; +out_mtd: + if (omap_nand_dma_ch >= 0) + omap_free_dma(omap_nand_dma_ch); + kfree(omap_mtd); +free_clock: + clk_put(omap_nand_clk); + return err; +} + +module_init(omap_nand_init); + +/* + * Clean up routine + */ +static void __exit omap_nand_cleanup (void) +{ + clk_disable(omap_nand_clk); + clk_put(omap_nand_clk); + nand_release(omap_mtd); + kfree(omap_mtd); +} + +module_exit(omap_nand_cleanup); + diff --git a/drivers/mtd/nand/omap-nand-flash.c b/drivers/mtd/nand/omap-nand-flash.c new file mode 100644 index 00000000000..5263b19c817 --- /dev/null +++ b/drivers/mtd/nand/omap-nand-flash.c @@ -0,0 +1,184 @@ +/* + * drivers/mtd/nand/omap-nand-flash.c + * + * Copyright (c) 2004 Texas Instruments, Jian Zhang + * Copyright (c) 2004 David Brownell + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#define DRIVER_NAME "omapnand" + +#ifdef CONFIG_MTD_PARTITIONS +static const char *part_probes[] = { "cmdlinepart", NULL }; +#endif + +struct omap_nand_info { + struct omap_nand_platform_data *pdata; + struct mtd_partition *parts; + struct mtd_info mtd; + struct nand_chip nand; +}; + +/* + * hardware specific access to control-lines + * NOTE: boards may use different bits for these!! + * + * ctrl: + * NAND_NCE: bit 0 - don't care + * NAND_CLE: bit 1 -> bit 1 (0x0002) + * NAND_ALE: bit 2 -> bit 2 (0x0004) + */ + +static void omap_nand_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl) +{ + struct nand_chip *chip = mtd->priv; + unsigned long mask; + + if (cmd == NAND_CMD_NONE) + return; + + mask = (ctrl & NAND_CLE) ? 0x02 : 0; + if (ctrl & NAND_ALE) + mask |= 0x04; + writeb(cmd, (unsigned long)chip->IO_ADDR_W | mask); +} + +static int omap_nand_dev_ready(struct mtd_info *mtd) +{ + struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, mtd); + + return info->pdata->dev_ready(info->pdata); +} + +static int __devinit omap_nand_probe(struct platform_device *pdev) +{ + struct omap_nand_info *info; + struct omap_nand_platform_data *pdata = pdev->dev.platform_data; + struct resource *res = pdev->resource; + unsigned long size = res->end - res->start + 1; + int err; + + info = kzalloc(sizeof(struct omap_nand_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + if (!request_mem_region(res->start, size, pdev->dev.driver->name)) { + err = -EBUSY; + goto out_free_info; + } + + info->nand.IO_ADDR_R = ioremap(res->start, size); + if (!info->nand.IO_ADDR_R) { + err = -ENOMEM; + goto out_release_mem_region; + } + info->nand.IO_ADDR_W = info->nand.IO_ADDR_R; + info->nand.cmd_ctrl = omap_nand_hwcontrol; + info->nand.ecc.mode = NAND_ECC_SOFT; + info->nand.options = pdata->options; + if (pdata->dev_ready) + info->nand.dev_ready = omap_nand_dev_ready; + else + info->nand.chip_delay = 20; + + info->mtd.name = dev_name(&pdev->dev); + info->mtd.priv = &info->nand; + + info->pdata = pdata; + + /* DIP switches on H2 and some other boards change between 8 and 16 bit + * bus widths for flash. Try the other width if the first try fails. + */ + if (nand_scan(&info->mtd, 1)) { + info->nand.options ^= NAND_BUSWIDTH_16; + if (nand_scan(&info->mtd, 1)) { + err = -ENXIO; + goto out_iounmap; + } + } + info->mtd.owner = THIS_MODULE; + +#ifdef CONFIG_MTD_PARTITIONS + err = parse_mtd_partitions(&info->mtd, part_probes, &info->parts, 0); + if (err > 0) + add_mtd_partitions(&info->mtd, info->parts, err); + else if (err < 0 && pdata->parts) + add_mtd_partitions(&info->mtd, pdata->parts, pdata->nr_parts); + else +#endif + add_mtd_device(&info->mtd); + + platform_set_drvdata(pdev, info); + + return 0; + +out_iounmap: + iounmap(info->nand.IO_ADDR_R); +out_release_mem_region: + release_mem_region(res->start, size); +out_free_info: + kfree(info); + + return err; +} + +static int omap_nand_remove(struct platform_device *pdev) +{ + struct omap_nand_info *info = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + /* Release NAND device, its internal structures and partitions */ + nand_release(&info->mtd); + iounmap(info->nand.IO_ADDR_R); + kfree(info); + return 0; +} + +static struct platform_driver omap_nand_driver = { + .probe = omap_nand_probe, + .remove = omap_nand_remove, + .driver = { + .name = DRIVER_NAME, + }, +}; +MODULE_ALIAS(DRIVER_NAME); + +static int __init omap_nand_init(void) +{ + return platform_driver_register(&omap_nand_driver); +} + +static void __exit omap_nand_exit(void) +{ + platform_driver_unregister(&omap_nand_driver); +} + +module_init(omap_nand_init); +module_exit(omap_nand_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jian Zhang (and others)"); +MODULE_DESCRIPTION("Glue layer for NAND flash on TI OMAP boards"); + diff --git a/drivers/mtd/nand/omap2.c b/drivers/mtd/nand/omap2.c new file mode 100644 index 00000000000..516da8f755a --- /dev/null +++ b/drivers/mtd/nand/omap2.c @@ -0,0 +1,755 @@ +/* + * drivers/mtd/nand/omap2.c + * + * Copyright (c) 2004 Texas Instruments, Jian Zhang + * Copyright (c) 2004 Micron Technology Inc. + * Copyright (c) 2004 David Brownell + * + * 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 +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#define GPMC_IRQ_STATUS 0x18 +#define GPMC_ECC_CONFIG 0x1F4 +#define GPMC_ECC_CONTROL 0x1F8 +#define GPMC_ECC_SIZE_CONFIG 0x1FC +#define GPMC_ECC1_RESULT 0x200 + +#define DRIVER_NAME "omap2-nand" +#define NAND_IO_SIZE SZ_4K + +#define NAND_WP_ON 1 +#define NAND_WP_OFF 0 +#define NAND_WP_BIT 0x00000010 +#define WR_RD_PIN_MONITORING 0x00600000 + +#define GPMC_BUF_FULL 0x00000001 +#define GPMC_BUF_EMPTY 0x00000000 + +#define NAND_Ecc_P1e (1 << 0) +#define NAND_Ecc_P2e (1 << 1) +#define NAND_Ecc_P4e (1 << 2) +#define NAND_Ecc_P8e (1 << 3) +#define NAND_Ecc_P16e (1 << 4) +#define NAND_Ecc_P32e (1 << 5) +#define NAND_Ecc_P64e (1 << 6) +#define NAND_Ecc_P128e (1 << 7) +#define NAND_Ecc_P256e (1 << 8) +#define NAND_Ecc_P512e (1 << 9) +#define NAND_Ecc_P1024e (1 << 10) +#define NAND_Ecc_P2048e (1 << 11) + +#define NAND_Ecc_P1o (1 << 16) +#define NAND_Ecc_P2o (1 << 17) +#define NAND_Ecc_P4o (1 << 18) +#define NAND_Ecc_P8o (1 << 19) +#define NAND_Ecc_P16o (1 << 20) +#define NAND_Ecc_P32o (1 << 21) +#define NAND_Ecc_P64o (1 << 22) +#define NAND_Ecc_P128o (1 << 23) +#define NAND_Ecc_P256o (1 << 24) +#define NAND_Ecc_P512o (1 << 25) +#define NAND_Ecc_P1024o (1 << 26) +#define NAND_Ecc_P2048o (1 << 27) + +#define TF(value) (value ? 1 : 0) + +#define P2048e(a) (TF(a & NAND_Ecc_P2048e) << 0) +#define P2048o(a) (TF(a & NAND_Ecc_P2048o) << 1) +#define P1e(a) (TF(a & NAND_Ecc_P1e) << 2) +#define P1o(a) (TF(a & NAND_Ecc_P1o) << 3) +#define P2e(a) (TF(a & NAND_Ecc_P2e) << 4) +#define P2o(a) (TF(a & NAND_Ecc_P2o) << 5) +#define P4e(a) (TF(a & NAND_Ecc_P4e) << 6) +#define P4o(a) (TF(a & NAND_Ecc_P4o) << 7) + +#define P8e(a) (TF(a & NAND_Ecc_P8e) << 0) +#define P8o(a) (TF(a & NAND_Ecc_P8o) << 1) +#define P16e(a) (TF(a & NAND_Ecc_P16e) << 2) +#define P16o(a) (TF(a & NAND_Ecc_P16o) << 3) +#define P32e(a) (TF(a & NAND_Ecc_P32e) << 4) +#define P32o(a) (TF(a & NAND_Ecc_P32o) << 5) +#define P64e(a) (TF(a & NAND_Ecc_P64e) << 6) +#define P64o(a) (TF(a & NAND_Ecc_P64o) << 7) + +#define P128e(a) (TF(a & NAND_Ecc_P128e) << 0) +#define P128o(a) (TF(a & NAND_Ecc_P128o) << 1) +#define P256e(a) (TF(a & NAND_Ecc_P256e) << 2) +#define P256o(a) (TF(a & NAND_Ecc_P256o) << 3) +#define P512e(a) (TF(a & NAND_Ecc_P512e) << 4) +#define P512o(a) (TF(a & NAND_Ecc_P512o) << 5) +#define P1024e(a) (TF(a & NAND_Ecc_P1024e) << 6) +#define P1024o(a) (TF(a & NAND_Ecc_P1024o) << 7) + +#define P8e_s(a) (TF(a & NAND_Ecc_P8e) << 0) +#define P8o_s(a) (TF(a & NAND_Ecc_P8o) << 1) +#define P16e_s(a) (TF(a & NAND_Ecc_P16e) << 2) +#define P16o_s(a) (TF(a & NAND_Ecc_P16o) << 3) +#define P1e_s(a) (TF(a & NAND_Ecc_P1e) << 4) +#define P1o_s(a) (TF(a & NAND_Ecc_P1o) << 5) +#define P2e_s(a) (TF(a & NAND_Ecc_P2e) << 6) +#define P2o_s(a) (TF(a & NAND_Ecc_P2o) << 7) + +#define P4e_s(a) (TF(a & NAND_Ecc_P4e) << 0) +#define P4o_s(a) (TF(a & NAND_Ecc_P4o) << 1) + +#ifdef CONFIG_MTD_PARTITIONS +static const char *part_probes[] = { "cmdlinepart", NULL }; +#endif + +struct omap_nand_info { + struct nand_hw_control controller; + struct omap_nand_platform_data *pdata; + struct mtd_info mtd; + struct mtd_partition *parts; + struct nand_chip nand; + struct platform_device *pdev; + + int gpmc_cs; + unsigned long phys_base; + void __iomem *gpmc_cs_baseaddr; + void __iomem *gpmc_baseaddr; +}; + +/* + * omap_nand_wp - This function enable or disable the Write Protect feature on + * NAND device + * @mtd: MTD device structure + * @mode: WP ON/OFF + */ +static void omap_nand_wp(struct mtd_info *mtd, int mode) +{ + struct omap_nand_info *info = container_of(mtd, + struct omap_nand_info, mtd); + + unsigned long config = __raw_readl(info->gpmc_baseaddr + GPMC_CONFIG); + + if (mode) + config &= ~(NAND_WP_BIT); /* WP is ON */ + else + config |= (NAND_WP_BIT); /* WP is OFF */ + + __raw_writel(config, (info->gpmc_baseaddr + GPMC_CONFIG)); +} + +/* + * hardware specific access to control-lines + * NOTE: boards may use different bits for these!! + * + * ctrl: + * NAND_NCE: bit 0 - don't care + * NAND_CLE: bit 1 -> Command Latch + * NAND_ALE: bit 2 -> Address Latch + */ +static void omap_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl) +{ + struct omap_nand_info *info = container_of(mtd, + struct omap_nand_info, mtd); + switch (ctrl) { + case NAND_CTRL_CHANGE | NAND_CTRL_CLE: + info->nand.IO_ADDR_W = info->gpmc_cs_baseaddr + + GPMC_CS_NAND_COMMAND; + info->nand.IO_ADDR_R = info->gpmc_cs_baseaddr + + GPMC_CS_NAND_DATA; + break; + + case NAND_CTRL_CHANGE | NAND_CTRL_ALE: + info->nand.IO_ADDR_W = info->gpmc_cs_baseaddr + + GPMC_CS_NAND_ADDRESS; + info->nand.IO_ADDR_R = info->gpmc_cs_baseaddr + + GPMC_CS_NAND_DATA; + break; + + case NAND_CTRL_CHANGE | NAND_NCE: + info->nand.IO_ADDR_W = info->gpmc_cs_baseaddr + + GPMC_CS_NAND_DATA; + info->nand.IO_ADDR_R = info->gpmc_cs_baseaddr + + GPMC_CS_NAND_DATA; + break; + } + + if (cmd != NAND_CMD_NONE) + __raw_writeb(cmd, info->nand.IO_ADDR_W); +} + +/* + * omap_read_buf16 - read data from NAND controller into buffer + * @mtd: MTD device structure + * @buf: buffer to store date + * @len: number of bytes to read + */ +static void omap_read_buf16(struct mtd_info *mtd, u_char *buf, int len) +{ + struct nand_chip *nand = mtd->priv; + + __raw_readsw(nand->IO_ADDR_R, buf, len / 2); +} + +/* + * omap_write_buf16 - write buffer to NAND controller + * @mtd: MTD device structure + * @buf: data buffer + * @len: number of bytes to write + */ +static void omap_write_buf16(struct mtd_info *mtd, const u_char * buf, int len) +{ + struct omap_nand_info *info = container_of(mtd, + struct omap_nand_info, mtd); + u16 *p = (u16 *) buf; + + /* FIXME try bursts of writesw() or DMA ... */ + len >>= 1; + + while (len--) { + writew(*p++, info->nand.IO_ADDR_W); + + while (GPMC_BUF_EMPTY == (readl(info->gpmc_baseaddr + + GPMC_STATUS) & GPMC_BUF_FULL)); + } +} +/* + * omap_verify_buf - Verify chip data against buffer + * @mtd: MTD device structure + * @buf: buffer containing the data to compare + * @len: number of bytes to compare + */ +static int omap_verify_buf(struct mtd_info *mtd, const u_char * buf, int len) +{ + struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, + mtd); + u16 *p = (u16 *) buf; + + len >>= 1; + + while (len--) { + + if (*p++ != cpu_to_le16(readw(info->nand.IO_ADDR_R))) + return -EFAULT; + } + + return 0; +} + +#ifdef CONFIG_MTD_NAND_OMAP_HWECC +/* + * omap_hwecc_init-Initialize the Hardware ECC for NAND flash in GPMC controller + * @mtd: MTD device structure + */ +static void omap_hwecc_init(struct mtd_info *mtd) +{ + struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, + mtd); + register struct nand_chip *chip = mtd->priv; + unsigned long val = 0x0; + + /* Read from ECC Control Register */ + val = __raw_readl(info->gpmc_baseaddr + GPMC_ECC_CONTROL); + /* Clear all ECC | Enable Reg1 */ + val = ((0x00000001<<8) | 0x00000001); + __raw_writel(val, info->gpmc_baseaddr + GPMC_ECC_CONTROL); + + /* Read from ECC Size Config Register */ + val = __raw_readl(info->gpmc_baseaddr + GPMC_ECC_SIZE_CONFIG); + /* ECCSIZE1=512 | Select eccResultsize[0-3] */ + val = ((((chip->ecc.size >> 1) - 1) << 22) | (0x0000000F)); + __raw_writel(val, info->gpmc_baseaddr + GPMC_ECC_SIZE_CONFIG); +} + +/* + * gen_true_ecc - This function will generate true ECC value, which can be used + * when correcting data read from NAND flash memory core + * @ecc_buf: buffer to store ecc code + */ +static void gen_true_ecc(u8 *ecc_buf) +{ + u32 tmp = ecc_buf[0] | (ecc_buf[1] << 16) | + ((ecc_buf[2] & 0xF0) << 20) | ((ecc_buf[2] & 0x0F) << 8); + + ecc_buf[0] = ~(P64o(tmp) | P64e(tmp) | P32o(tmp) | P32e(tmp) | + P16o(tmp) | P16e(tmp) | P8o(tmp) | P8e(tmp)); + ecc_buf[1] = ~(P1024o(tmp) | P1024e(tmp) | P512o(tmp) | P512e(tmp) | + P256o(tmp) | P256e(tmp) | P128o(tmp) | P128e(tmp)); + ecc_buf[2] = ~(P4o(tmp) | P4e(tmp) | P2o(tmp) | P2e(tmp) | P1o(tmp) | + P1e(tmp) | P2048o(tmp) | P2048e(tmp)); +} + +/* + * omap_compare_ecc - This function compares two ECC's and indicates if there + * is an error. If the error can be corrected it will be corrected to the + * buffer + * @ecc_data1: ecc code from nand spare area + * @ecc_data2: ecc code from hardware register obtained from hardware ecc + * @page_data: page data + */ +static int omap_compare_ecc(u8 *ecc_data1, /* read from NAND memory */ + u8 *ecc_data2, /* read from register */ + u8 *page_data) +{ + uint i; + u8 tmp0_bit[8], tmp1_bit[8], tmp2_bit[8]; + u8 comp0_bit[8], comp1_bit[8], comp2_bit[8]; + u8 ecc_bit[24]; + u8 ecc_sum = 0; + u8 find_bit = 0; + uint find_byte = 0; + int isEccFF; + + isEccFF = ((*(u32 *)ecc_data1 & 0xFFFFFF) == 0xFFFFFF); + + gen_true_ecc(ecc_data1); + gen_true_ecc(ecc_data2); + + for (i = 0; i <= 2; i++) { + *(ecc_data1 + i) = ~(*(ecc_data1 + i)); + *(ecc_data2 + i) = ~(*(ecc_data2 + i)); + } + + for (i = 0; i < 8; i++) { + tmp0_bit[i] = *ecc_data1 % 2; + *ecc_data1 = *ecc_data1 / 2; + } + + for (i = 0; i < 8; i++) { + tmp1_bit[i] = *(ecc_data1 + 1) % 2; + *(ecc_data1 + 1) = *(ecc_data1 + 1) / 2; + } + + for (i = 0; i < 8; i++) { + tmp2_bit[i] = *(ecc_data1 + 2) % 2; + *(ecc_data1 + 2) = *(ecc_data1 + 2) / 2; + } + + for (i = 0; i < 8; i++) { + comp0_bit[i] = *ecc_data2 % 2; + *ecc_data2 = *ecc_data2 / 2; + } + + for (i = 0; i < 8; i++) { + comp1_bit[i] = *(ecc_data2 + 1) % 2; + *(ecc_data2 + 1) = *(ecc_data2 + 1) / 2; + } + + for (i = 0; i < 8; i++) { + comp2_bit[i] = *(ecc_data2 + 2) % 2; + *(ecc_data2 + 2) = *(ecc_data2 + 2) / 2; + } + + for (i = 0; i < 6; i++) + ecc_bit[i] = tmp2_bit[i + 2] ^ comp2_bit[i + 2]; + + for (i = 0; i < 8; i++) + ecc_bit[i + 6] = tmp0_bit[i] ^ comp0_bit[i]; + + for (i = 0; i < 8; i++) + ecc_bit[i + 14] = tmp1_bit[i] ^ comp1_bit[i]; + + ecc_bit[22] = tmp2_bit[0] ^ comp2_bit[0]; + ecc_bit[23] = tmp2_bit[1] ^ comp2_bit[1]; + + for (i = 0; i < 24; i++) + ecc_sum += ecc_bit[i]; + + switch (ecc_sum) { + case 0: + /* Not reached because this function is not called if + * ECC values are equal + */ + return 0; + + case 1: + /* Uncorrectable error */ + DEBUG(MTD_DEBUG_LEVEL0, "ECC UNCORRECTED_ERROR 1\n"); + return -1; + + case 11: + /* UN-Correctable error */ + DEBUG(MTD_DEBUG_LEVEL0, "ECC UNCORRECTED_ERROR B\n"); + return -1; + + case 12: + /* Correctable error */ + find_byte = (ecc_bit[23] << 8) + + (ecc_bit[21] << 7) + + (ecc_bit[19] << 6) + + (ecc_bit[17] << 5) + + (ecc_bit[15] << 4) + + (ecc_bit[13] << 3) + + (ecc_bit[11] << 2) + + (ecc_bit[9] << 1) + + ecc_bit[7]; + + find_bit = (ecc_bit[5] << 2) + (ecc_bit[3] << 1) + ecc_bit[1]; + + DEBUG(MTD_DEBUG_LEVEL0, "Correcting single bit ECC error at " + "offset: %d, bit: %d\n", find_byte, find_bit); + + page_data[find_byte] ^= (1 << find_bit); + + return 0; + default: + if (isEccFF) { + if (ecc_data2[0] == 0 && + ecc_data2[1] == 0 && + ecc_data2[2] == 0) + return 0; + } + DEBUG(MTD_DEBUG_LEVEL0, "UNCORRECTED_ERROR default\n"); + return -1; + } +} + +/* + * omap_correct_data - Compares the ecc read from nand spare area with ECC + * registers values and corrects one bit error if it has occured + * @mtd: MTD device structure + * @dat: page data + * @read_ecc: ecc read from nand flash + * @calc_ecc: ecc read from ECC registers + */ +static int omap_correct_data(struct mtd_info *mtd, u_char * dat, + u_char * read_ecc, u_char * calc_ecc) +{ + struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, + mtd); + int blockCnt = 0, i = 0, ret = 0; + + /* Ex NAND_ECC_HW12_2048 */ + if ((info->nand.ecc.mode == NAND_ECC_HW) && + (info->nand.ecc.size == 2048)) + blockCnt = 4; + else + blockCnt = 1; + + for (i = 0; i < blockCnt; i++) { + if (memcmp(read_ecc, calc_ecc, 3) != 0) { + ret = omap_compare_ecc(read_ecc, calc_ecc, dat); + if (ret < 0) return ret; + } + read_ecc += 3; + calc_ecc += 3; + dat += 512; + } + return 0; +} + +/* + * omap_calcuate_ecc - Generate non-inverted ECC bytes. + * Using noninverted ECC can be considered ugly since writing a blank + * page ie. padding will clear the ECC bytes. This is no problem as long + * nobody is trying to write data on the seemingly unused page. Reading + * an erased page will produce an ECC mismatch between generated and read + * ECC bytes that has to be dealt with separately. + * @mtd: MTD device structure + * @dat: The pointer to data on which ecc is computed + * @ecc_code: The ecc_code buffer + */ +static int omap_calculate_ecc(struct mtd_info *mtd, const u_char *dat, + u_char *ecc_code) +{ + struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, + mtd); + unsigned long val = 0x0; + unsigned long reg; + + /* Start Reading from HW ECC1_Result = 0x200 */ + reg = (unsigned long)(info->gpmc_baseaddr + GPMC_ECC1_RESULT); + val = __raw_readl(reg); + *ecc_code++ = val; /* P128e, ..., P1e */ + *ecc_code++ = val >> 16; /* P128o, ..., P1o */ + /* P2048o, P1024o, P512o, P256o, P2048e, P1024e, P512e, P256e */ + *ecc_code++ = ((val >> 8) & 0x0f) | ((val >> 20) & 0xf0); + reg += 4; + + return 0; +} + +/* + * omap_enable_hwecc - This function enables the hardware ecc functionality + * @mtd: MTD device structure + * @mode: Read/Write mode + */ +static void omap_enable_hwecc(struct mtd_info *mtd, int mode) +{ + struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, + mtd); + register struct nand_chip *chip = mtd->priv; + unsigned int dev_width = (chip->options & NAND_BUSWIDTH_16) ? 1 : 0; + unsigned long val = __raw_readl(info->gpmc_baseaddr + GPMC_ECC_CONFIG); + + switch (mode) { + case NAND_ECC_READ : + __raw_writel(0x101, info->gpmc_baseaddr + GPMC_ECC_CONTROL); + /* (ECC 16 or 8 bit col) | ( CS ) | ECC Enable */ + val = (dev_width << 7) | (info->gpmc_cs << 1) | (0x1); + break; + case NAND_ECC_READSYN : + __raw_writel(0x100, info->gpmc_baseaddr + GPMC_ECC_CONTROL); + /* (ECC 16 or 8 bit col) | ( CS ) | ECC Enable */ + val = (dev_width << 7) | (info->gpmc_cs << 1) | (0x1); + break; + case NAND_ECC_WRITE : + __raw_writel(0x101, info->gpmc_baseaddr + GPMC_ECC_CONTROL); + /* (ECC 16 or 8 bit col) | ( CS ) | ECC Enable */ + val = (dev_width << 7) | (info->gpmc_cs << 1) | (0x1); + break; + default: + DEBUG(MTD_DEBUG_LEVEL0, "Error: Unrecognized Mode[%d]!\n", + mode); + break; + } + + __raw_writel(val, info->gpmc_baseaddr + GPMC_ECC_CONFIG); +} +#endif + +/* + * omap_wait - Wait function is called during Program and erase + * operations and the way it is called from MTD layer, we should wait + * till the NAND chip is ready after the programming/erase operation + * has completed. + * @mtd: MTD device structure + * @chip: NAND Chip structure + */ +static int omap_wait(struct mtd_info *mtd, struct nand_chip *chip) +{ + register struct nand_chip *this = mtd->priv; + struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, + mtd); + int status = 0; + + this->IO_ADDR_W = (void *) info->gpmc_cs_baseaddr + + GPMC_CS_NAND_COMMAND; + this->IO_ADDR_R = (void *) info->gpmc_cs_baseaddr + GPMC_CS_NAND_DATA; + + while (!(status & 0x40)) { + __raw_writeb(NAND_CMD_STATUS & 0xFF, this->IO_ADDR_W); + status = __raw_readb(this->IO_ADDR_R); + } + return status; +} + +/* + * omap_dev_ready - calls the platform specific dev_ready function + * @mtd: MTD device structure + */ +static int omap_dev_ready(struct mtd_info *mtd) +{ + struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, + mtd); + unsigned int val = __raw_readl(info->gpmc_baseaddr + GPMC_IRQ_STATUS); + + if ((val & 0x100) == 0x100) { + /* Clear IRQ Interrupt */ + val |= 0x100; + val &= ~(0x0); + __raw_writel(val, info->gpmc_baseaddr + GPMC_IRQ_STATUS); + } else { + unsigned int cnt = 0; + while (cnt++ < 0x1FF) { + if ((val & 0x100) == 0x100) + return 0; + val = __raw_readl(info->gpmc_baseaddr + + GPMC_IRQ_STATUS); + } + } + + return 1; +} + +static int __devinit omap_nand_probe(struct platform_device *pdev) +{ + struct omap_nand_info *info; + struct omap_nand_platform_data *pdata; + int err; + unsigned long val; + + + pdata = pdev->dev.platform_data; + if (pdata == NULL) { + dev_err(&pdev->dev, "platform data missing\n"); + return -ENODEV; + } + + info = kzalloc(sizeof(struct omap_nand_info), GFP_KERNEL); + if (!info) return -ENOMEM; + + platform_set_drvdata(pdev, info); + + spin_lock_init(&info->controller.lock); + init_waitqueue_head(&info->controller.wq); + + info->pdev = pdev; + + info->gpmc_cs = pdata->cs; + info->gpmc_baseaddr = pdata->gpmc_baseaddr; + info->gpmc_cs_baseaddr = pdata->gpmc_cs_baseaddr; + + info->mtd.priv = &info->nand; + info->mtd.name = dev_name(&pdev->dev); + info->mtd.owner = THIS_MODULE; + + err = gpmc_cs_request(info->gpmc_cs, NAND_IO_SIZE, &info->phys_base); + if (err < 0) { + dev_err(&pdev->dev, "Cannot request GPMC CS\n"); + goto out_free_info; + } + + /* Enable RD PIN Monitoring Reg */ + if (pdata->dev_ready) { + val = gpmc_cs_read_reg(info->gpmc_cs, GPMC_CS_CONFIG1); + val |= WR_RD_PIN_MONITORING; + gpmc_cs_write_reg(info->gpmc_cs, GPMC_CS_CONFIG1, val); + } + + val = gpmc_cs_read_reg(info->gpmc_cs, GPMC_CS_CONFIG7); + val &= ~(0xf << 8); + val |= (0xc & 0xf) << 8; + gpmc_cs_write_reg(info->gpmc_cs, GPMC_CS_CONFIG7, val); + + /* NAND write protect off */ + omap_nand_wp(&info->mtd, NAND_WP_OFF); + + if (!request_mem_region(info->phys_base, NAND_IO_SIZE, + pdev->dev.driver->name)) { + err = -EBUSY; + goto out_free_cs; + } + + info->nand.IO_ADDR_R = ioremap(info->phys_base, NAND_IO_SIZE); + if (!info->nand.IO_ADDR_R) { + err = -ENOMEM; + goto out_release_mem_region; + } + info->nand.controller = &info->controller; + + info->nand.IO_ADDR_W = info->nand.IO_ADDR_R; + info->nand.cmd_ctrl = omap_hwcontrol; + + /* REVISIT: only supports 16-bit NAND flash */ + + info->nand.read_buf = omap_read_buf16; + info->nand.write_buf = omap_write_buf16; + info->nand.verify_buf = omap_verify_buf; + + /* + * If RDY/BSY line is connected to OMAP then use the omap ready funcrtion + * and the generic nand_wait function which reads the status register + * after monitoring the RDY/BSY line.Otherwise use a standard chip delay + * which is slightly more than tR (AC Timing) of the NAND device and read + * status register until you get a failure or success + */ + if (pdata->dev_ready) { + info->nand.dev_ready = omap_dev_ready; + info->nand.chip_delay = 0; + } else { + info->nand.waitfunc = omap_wait; + info->nand.chip_delay = 50; + } + + info->nand.options |= NAND_SKIP_BBTSCAN; + if ((gpmc_cs_read_reg(info->gpmc_cs, GPMC_CS_CONFIG1) & 0x3000) + == 0x1000) + info->nand.options |= NAND_BUSWIDTH_16; + +#ifdef CONFIG_MTD_NAND_OMAP_HWECC + info->nand.ecc.bytes = 3; + info->nand.ecc.size = 512; + info->nand.ecc.calculate = omap_calculate_ecc; + info->nand.ecc.hwctl = omap_enable_hwecc; + info->nand.ecc.correct = omap_correct_data; + info->nand.ecc.mode = NAND_ECC_HW; + + /* init HW ECC */ + omap_hwecc_init(&info->mtd); +#else + info->nand.ecc.mode = NAND_ECC_SOFT; +#endif + + /* DIP switches on some boards change between 8 and 16 bit + * bus widths for flash. Try the other width if the first try fails. + */ + if (nand_scan(&info->mtd, 1)) { + info->nand.options ^= NAND_BUSWIDTH_16; + if (nand_scan(&info->mtd, 1)) { + err = -ENXIO; + goto out_release_mem_region; + } + } + +#ifdef CONFIG_MTD_PARTITIONS + err = parse_mtd_partitions(&info->mtd, part_probes, &info->parts, 0); + if (err > 0) + add_mtd_partitions(&info->mtd, info->parts, err); + else if (pdata->parts) + add_mtd_partitions(&info->mtd, pdata->parts, pdata->nr_parts); + else +#endif + add_mtd_device(&info->mtd); + + platform_set_drvdata(pdev, &info->mtd); + + return 0; + +out_release_mem_region: + release_mem_region(info->phys_base, NAND_IO_SIZE); +out_free_cs: + gpmc_cs_free(info->gpmc_cs); +out_free_info: + kfree(info); + + return err; +} + +static int omap_nand_remove(struct platform_device *pdev) +{ + struct mtd_info *mtd = platform_get_drvdata(pdev); + struct omap_nand_info *info = mtd->priv; + + platform_set_drvdata(pdev, NULL); + /* Release NAND device, its internal structures and partitions */ + nand_release(&info->mtd); + iounmap(info->nand.IO_ADDR_R); + kfree(&info->mtd); + return 0; +} + +static struct platform_driver omap_nand_driver = { + .probe = omap_nand_probe, + .remove = omap_nand_remove, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; +MODULE_ALIAS(DRIVER_NAME); + +static int __init omap_nand_init(void) +{ + printk(KERN_INFO "%s driver initializing\n", DRIVER_NAME); + return platform_driver_register(&omap_nand_driver); +} + +static void __exit omap_nand_exit(void) +{ + platform_driver_unregister(&omap_nand_driver); +} + +module_init(omap_nand_init); +module_exit(omap_nand_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Glue layer for NAND flash on TI OMAP boards"); diff --git a/drivers/net/irda/Kconfig b/drivers/net/irda/Kconfig index e6317557a53..58d0c811afd 100644 --- a/drivers/net/irda/Kconfig +++ b/drivers/net/irda/Kconfig @@ -342,5 +342,15 @@ config MCS_FIR To compile it as a module, choose M here: the module will be called mcs7780. +config OMAP_IR + tristate "OMAP IrDA(SIR/MIR/FIR)" + depends on IRDA && ARCH_OMAP + select GPIOEXPANDER_OMAP if MACH_OMAP_H3 + help + Say Y here if you want to build support for the Texas Instruments + OMAP IrDA device driver, which supports SIR/MIR/FIR. This driver + relies on platform specific helper routines so available capabilities + may vary from one OMAP target to another. + endmenu diff --git a/drivers/net/irda/Makefile b/drivers/net/irda/Makefile index 5d20fde32a2..49e6f062e05 100644 --- a/drivers/net/irda/Makefile +++ b/drivers/net/irda/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_VLSI_FIR) += vlsi_ir.o obj-$(CONFIG_VIA_FIR) += via-ircc.o obj-$(CONFIG_PXA_FICP) += pxaficp_ir.o obj-$(CONFIG_MCS_FIR) += mcs7780.o +obj-$(CONFIG_OMAP_IR) += omap-ir.o obj-$(CONFIG_AU1000_FIR) += au1k_ir.o # SIR drivers obj-$(CONFIG_IRTTY_SIR) += irtty-sir.o sir-dev.o diff --git a/drivers/net/irda/omap-ir.c b/drivers/net/irda/omap-ir.c new file mode 100644 index 00000000000..59401b7dadc --- /dev/null +++ b/drivers/net/irda/omap-ir.c @@ -0,0 +1,901 @@ +/* + * BRIEF MODULE DESCRIPTION + * + * Infra-red driver for the OMAP1610-H2 and OMAP1710-H3 and H4 Platforms + * (SIR/MIR/FIR modes) + * (based on omap-sir.c) + * + * Copyright 2003 MontaVista Software Inc. + * Author: MontaVista Software, Inc. + * source@mvista.com + * + * Copyright 2004 Texas Instruments. + * + * 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + Modifications: + Feb 2004, Texas Instruments + - Ported to 2.6 kernel (Feb 2004). + * + Apr 2004, Texas Instruments + - Added support for H3 (Apr 2004). + Nov 2004, Texas Instruments + - Added support for Power Management. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define UART3_EFR_EN (1 << 4) +#define UART3_MCR_EN_TCR_TLR (1 << 6) + +#define UART3_LCR_WL_8 (3 << 0) +#define UART3_LCR_SP2 (1 << 2) +#define UART3_LCR_DIVEN (1 << 7) + +#define UART3_FCR_FIFO_EN (1 << 0) +#define UART3_FCR_FIFO_RX (1 << 1) +#define UART3_FCR_FIFO_TX (1 << 2) +#define UART3_FCR_FIFO_DMA1 (1 << 3) +#define UART3_FCR_FIFO_TX_TRIG16 (1 << 4) +#define UART3_FCR_FIFO_RX_TRIG16 (1 << 6) +#define UART3_FCR_CONFIG (\ + UART3_FCR_FIFO_EN | UART3_FCR_FIFO_RX |\ + UART3_FCR_FIFO_TX | UART3_FCR_FIFO_DMA1 |\ + UART3_FCR_FIFO_TX_TRIG16 |\ + UART3_FCR_FIFO_RX_TRIG16) + +#define UART3_SCR_TX_TRIG1 (1 << 6) +#define UART3_SCR_RX_TRIG1 (1 << 7) + +#define UART3_MDR1_RESET (0x07) +#define UART3_MDR1_SIR (1 << 0) +#define UART3_MDR1_MIR (4 << 0) +#define UART3_MDR1_FIR (5 << 0) +#define UART3_MDR1_SIP_AUTO (1 << 6) + +#define UART3_MDR2_TRIG1 (0 << 1) +#define UART3_MDR2_IRTX_UNDERRUN (1 << 0) + +#define UART3_ACERG_TX_UNDERRUN_DIS (1 << 4) +#define UART3_ACERG_SD_MODE_LOW (1 << 6) +#define UART3_ACERG_DIS_IR_RX (1 << 5) + +#define UART3_IER_EOF (1 << 5) +#define UART3_IER_CTS (1 << 7) + +#define UART3_IIR_TX_STATUS (1 << 5) +#define UART3_IIR_EOF (0x80) + +#define IS_FIR(omap_ir) ((omap_ir)->speed >= 4000000) + +struct omap_irda { + unsigned char open; + int speed; /* Current IrDA speed */ + int newspeed; + + struct net_device_stats stats; + struct irlap_cb *irlap; + struct qos_info qos; + + int rx_dma_channel; + int tx_dma_channel; + + dma_addr_t rx_buf_dma_phys; /* Physical address of RX DMA buffer */ + dma_addr_t tx_buf_dma_phys; /* Physical address of TX DMA buffer */ + + void *rx_buf_dma_virt; /* Virtual address of RX DMA buffer */ + void *tx_buf_dma_virt; /* Virtual address of TX DMA buffer */ + + struct device *dev; + struct omap_irda_config *pdata; +}; + +static void inline uart_reg_out(int idx, u8 val) +{ + omap_writeb(val, idx); +} + +static u8 inline uart_reg_in(int idx) +{ + u8 b = omap_readb(idx); + return b; +} + +/* forward declarations */ +extern void omap_stop_dma(int lch); +static int omap_irda_set_speed(struct net_device *dev, int speed); + +static void omap_irda_start_rx_dma(struct omap_irda *omap_ir) +{ + /* Configure DMA */ + omap_set_dma_src_params(omap_ir->rx_dma_channel, 0x3, 0x0, + omap_ir->pdata->src_start, + 0, 0); + + omap_enable_dma_irq(omap_ir->rx_dma_channel, 0x01); + + omap_set_dma_dest_params(omap_ir->rx_dma_channel, 0x0, 0x1, + omap_ir->rx_buf_dma_phys, + 0, 0); + + omap_set_dma_transfer_params(omap_ir->rx_dma_channel, 0x0, + IRDA_SKB_MAX_MTU, 0x1, + 0x0, omap_ir->pdata->rx_trigger, 0); + + omap_start_dma(omap_ir->rx_dma_channel); +} + +static void omap_start_tx_dma(struct omap_irda *omap_ir, int size) +{ + /* Configure DMA */ + omap_set_dma_dest_params(omap_ir->tx_dma_channel, 0x03, 0x0, + omap_ir->pdata->dest_start, 0, 0); + + omap_enable_dma_irq(omap_ir->tx_dma_channel, 0x01); + + omap_set_dma_src_params(omap_ir->tx_dma_channel, 0x0, 0x1, + omap_ir->tx_buf_dma_phys, + 0, 0); + + omap_set_dma_transfer_params(omap_ir->tx_dma_channel, 0x0, size, 0x1, + 0x0, omap_ir->pdata->tx_trigger, 0); + + /* Start DMA */ + omap_start_dma(omap_ir->tx_dma_channel); +} + +/* DMA RX callback - normally, we should not go here, + * it calls only if something is going wrong + */ +static void omap_irda_rx_dma_callback(int lch, u16 ch_status, void *data) +{ + struct net_device *dev = data; + struct omap_irda *omap_ir = netdev_priv(dev); + + printk(KERN_ERR "RX Transfer error or very big frame\n"); + + /* Clear interrupts */ + uart_reg_in(UART3_IIR); + + omap_ir->stats.rx_frame_errors++; + + uart_reg_in(UART3_RESUME); + + /* Re-init RX DMA */ + omap_irda_start_rx_dma(omap_ir); +} + +/* DMA TX callback - calling when frame transfer has been finished */ +static void omap_irda_tx_dma_callback(int lch, u16 ch_status, void *data) +{ + struct net_device *dev = data; + struct omap_irda *omap_ir = netdev_priv(dev); + + /*Stop DMA controller */ + omap_stop_dma(omap_ir->tx_dma_channel); +} + +/* + * Set the IrDA communications speed. + * Interrupt have to be disabled here. + */ +static int omap_irda_startup(struct net_device *dev) +{ + struct omap_irda *omap_ir = netdev_priv(dev); + + /* FIXME: use clk_* apis for UART3 clock*/ + /* Enable UART3 clock and set UART3 to IrDA mode */ + if (machine_is_omap_h2() || machine_is_omap_h3()) + omap_writel(omap_readl(MOD_CONF_CTRL_0) | (1 << 31) | (1 << 15), + MOD_CONF_CTRL_0); + + /* Only for H2? + */ + if (omap_ir->pdata->transceiver_mode && machine_is_omap_h2()) { + /* Is it select_irda on H2 ? */ + omap_writel(omap_readl(FUNC_MUX_CTRL_A) | 7, + FUNC_MUX_CTRL_A); + omap_ir->pdata->transceiver_mode(omap_ir->dev, IR_SIRMODE); + } + + uart_reg_out(UART3_MDR1, UART3_MDR1_RESET); /* Reset mode */ + + /* Clear DLH and DLL */ + uart_reg_out(UART3_LCR, UART3_LCR_DIVEN); + + uart_reg_out(UART3_DLL, 0); + uart_reg_out(UART3_DLH, 0); + uart_reg_out(UART3_LCR, 0xbf); /* FIXME: Add #define */ + + uart_reg_out(UART3_EFR, UART3_EFR_EN); + uart_reg_out(UART3_LCR, UART3_LCR_DIVEN); + + /* Enable access to UART3_TLR and UART3_TCR registers */ + uart_reg_out(UART3_MCR, UART3_MCR_EN_TCR_TLR); + + uart_reg_out(UART3_SCR, 0); + /* Set Rx trigger to 1 and Tx trigger to 1 */ + uart_reg_out(UART3_TLR, 0); + + /* Set LCR to 8 bits and 1 stop bit */ + uart_reg_out(UART3_LCR, 0x03); + + /* Clear RX and TX FIFO and enable FIFO */ + /* Use DMA Req for transfers */ + uart_reg_out(UART3_FCR, UART3_FCR_CONFIG); + + uart_reg_out(UART3_MCR, 0); + + uart_reg_out(UART3_SCR, UART3_SCR_TX_TRIG1 | + UART3_SCR_RX_TRIG1); + + /* Enable UART3 SIR Mode,(Frame-length method to end frames) */ + uart_reg_out(UART3_MDR1, UART3_MDR1_SIR); + + /* Set Status FIFO trig to 1 */ + uart_reg_out(UART3_MDR2, 0); + + /* Enables RXIR input */ + /* and disable TX underrun */ + /* SEND_SIP pulse */ + uart_reg_out(UART3_ACREG, UART3_ACERG_SD_MODE_LOW | + UART3_ACERG_TX_UNDERRUN_DIS); + + /* Enable EOF Interrupt only */ + uart_reg_out(UART3_IER, UART3_IER_CTS | UART3_IER_EOF); + + /* Set Maximum Received Frame size to 2048 bytes */ + uart_reg_out(UART3_RXFLL, 0x00); + uart_reg_out(UART3_RXFLH, 0x08); + + uart_reg_in(UART3_RESUME); + + return 0; +} + +static int omap_irda_shutdown(struct omap_irda *omap_ir) +{ + unsigned long flags; + + local_irq_save(flags); + + /* Disable all UART3 Interrupts */ + uart_reg_out(UART3_IER, 0); + + /* Disable UART3 and disable baud rate generator */ + uart_reg_out(UART3_MDR1, UART3_MDR1_RESET); + + /* set SD_MODE pin to high and Disable RX IR */ + uart_reg_out(UART3_ACREG, (UART3_ACERG_DIS_IR_RX | + ~(UART3_ACERG_SD_MODE_LOW))); + + /* Clear DLH and DLL */ + uart_reg_out(UART3_LCR, UART3_LCR_DIVEN); + uart_reg_out(UART3_DLL, 0); + uart_reg_out(UART3_DLH, 0); + + local_irq_restore(flags); + + return 0; +} + +static irqreturn_t +omap_irda_irq(int irq, void *dev_id) +{ + struct net_device *dev = dev_id; + struct omap_irda *omap_ir = netdev_priv(dev); + struct sk_buff *skb; + + u8 status; + int w = 0; + + /* Clear EOF interrupt */ + status = uart_reg_in(UART3_IIR); + + if (status & UART3_IIR_TX_STATUS) { + u8 mdr2 = uart_reg_in(UART3_MDR2); + if (mdr2 & UART3_MDR2_IRTX_UNDERRUN) + printk(KERN_ERR "IrDA Buffer underrun error\n"); + + omap_ir->stats.tx_packets++; + + if (omap_ir->newspeed) { + omap_irda_set_speed(dev, omap_ir->newspeed); + omap_ir->newspeed = 0; + } + + netif_wake_queue(dev); + if (!(status & UART3_IIR_EOF)) + return IRQ_HANDLED; + } + + /* Stop DMA and if there are no errors, send frame to upper layer */ + omap_stop_dma(omap_ir->rx_dma_channel); + + status = uart_reg_in(UART3_SFLSR); /* Take a frame status */ + + if (status != 0) { /* Bad frame? */ + omap_ir->stats.rx_frame_errors++; + uart_reg_in(UART3_RESUME); + } else { + /* We got a frame! */ + skb = dev_alloc_skb(IRDA_SKB_MAX_MTU); + + if (!skb) { + printk(KERN_ERR "omap_sir: out of memory for RX SKB\n"); + return IRQ_HANDLED; + } + /* + * Align any IP headers that may be contained + * within the frame. + */ + + skb_reserve(skb, 1); + + w = omap_get_dma_dst_pos(omap_ir->rx_dma_channel) - + omap_ir->rx_buf_dma_phys; + + if (!IS_FIR(omap_ir)) + /* Copy DMA buffer to skb */ + memcpy(skb_put(skb, w - 2), omap_ir->rx_buf_dma_virt, + w - 2); + else + /* Copy DMA buffer to skb */ + memcpy(skb_put(skb, w - 4), omap_ir->rx_buf_dma_virt, + w - 4); + + skb->dev = dev; + skb_reset_mac_header(skb); + skb->protocol = htons(ETH_P_IRDA); + omap_ir->stats.rx_packets++; + omap_ir->stats.rx_bytes += skb->len; + netif_receive_skb(skb); /* Send data to upper level */ + } + + /* Re-init RX DMA */ + omap_irda_start_rx_dma(omap_ir); + + dev->last_rx = jiffies; + + return IRQ_HANDLED; +} + +static int omap_irda_hard_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct omap_irda *omap_ir = netdev_priv(dev); + int speed = irda_get_next_speed(skb); + int mtt = irda_get_mtt(skb); + int xbofs = irda_get_next_xbofs(skb); + + + /* + * Does this packet contain a request to change the interface + * speed? If so, remember it until we complete the transmission + * of this frame. + */ + if (speed != omap_ir->speed && speed != -1) + omap_ir->newspeed = speed; + + if (xbofs) /* Set number of addtional BOFS */ + uart_reg_out(UART3_EBLR, xbofs + 1); + + /* + * If this is an empty frame, we can bypass a lot. + */ + if (skb->len == 0) { + if (omap_ir->newspeed) { + omap_ir->newspeed = 0; + omap_irda_set_speed(dev, speed); + } + dev_kfree_skb(skb); + return 0; + } + + netif_stop_queue(dev); + + /* Copy skb data to DMA buffer */ + skb_copy_from_linear_data(skb, omap_ir->tx_buf_dma_virt, skb->len); + + /* Copy skb data to DMA buffer */ + omap_ir->stats.tx_bytes += skb->len; + + /* Set frame length */ + uart_reg_out(UART3_TXFLL, (skb->len & 0xff)); + uart_reg_out(UART3_TXFLH, (skb->len >> 8)); + + if (mtt > 1000) + mdelay(mtt / 1000); + else + udelay(mtt); + + /* Start TX DMA transfer */ + omap_start_tx_dma(omap_ir, skb->len); + + /* We can free skb now because it's already in DMA buffer */ + dev_kfree_skb(skb); + + dev->trans_start = jiffies; + + return 0; +} + +static int +omap_irda_ioctl(struct net_device *dev, struct ifreq *ifreq, int cmd) +{ + struct if_irda_req *rq = (struct if_irda_req *)ifreq; + struct omap_irda *omap_ir = netdev_priv(dev); + int ret = -EOPNOTSUPP; + + + switch (cmd) { + case SIOCSBANDWIDTH: + if (capable(CAP_NET_ADMIN)) { + /* + * We are unable to set the speed if the + * device is not running. + */ + if (omap_ir->open) + ret = omap_irda_set_speed(dev, + rq->ifr_baudrate); + else { + printk(KERN_ERR "omap_ir: SIOCSBANDWIDTH:" + " !netif_running\n"); + ret = 0; + } + } + break; + + case SIOCSMEDIABUSY: + ret = -EPERM; + if (capable(CAP_NET_ADMIN)) { + irda_device_set_media_busy(dev, TRUE); + ret = 0; + } + break; + + case SIOCGRECEIVING: + rq->ifr_receiving = 0; + break; + + default: + break; + } + + return ret; +} + +static struct net_device_stats *omap_irda_stats(struct net_device *dev) +{ + struct omap_irda *omap_ir = netdev_priv(dev); + return &omap_ir->stats; +} + +static int omap_irda_start(struct net_device *dev) +{ + struct omap_irda *omap_ir = netdev_priv(dev); + int err; + + omap_ir->speed = 9600; + + err = request_irq(dev->irq, omap_irda_irq, 0, dev->name, dev); + if (err) + goto err_irq; + + /* + * The interrupt must remain disabled for now. + */ + disable_irq(dev->irq); + + /* Request DMA channels for IrDA hardware */ + if (omap_request_dma(omap_ir->pdata->rx_channel, "IrDA Rx DMA", + (void *)omap_irda_rx_dma_callback, + dev, &(omap_ir->rx_dma_channel))) { + printk(KERN_ERR "Failed to request IrDA Rx DMA\n"); + goto err_irq; + } + + if (omap_request_dma(omap_ir->pdata->tx_channel, "IrDA Tx DMA", + (void *)omap_irda_tx_dma_callback, + dev, &(omap_ir->tx_dma_channel))) { + printk(KERN_ERR "Failed to request IrDA Tx DMA\n"); + goto err_irq; + } + + /* Allocate TX and RX buffers for DMA channels */ + omap_ir->rx_buf_dma_virt = + dma_alloc_coherent(NULL, IRDA_SKB_MAX_MTU, + &(omap_ir->rx_buf_dma_phys), + GFP_KERNEL); + + if (!omap_ir->rx_buf_dma_virt) { + printk(KERN_ERR "Unable to allocate memory for rx_buf_dma\n"); + goto err_irq; + } + + omap_ir->tx_buf_dma_virt = + dma_alloc_coherent(NULL, IRDA_SIR_MAX_FRAME, + &(omap_ir->tx_buf_dma_phys), + GFP_KERNEL); + + if (!omap_ir->tx_buf_dma_virt) { + printk(KERN_ERR "Unable to allocate memory for tx_buf_dma\n"); + goto err_mem1; + } + + /* + * Setup the serial port for the specified config. + */ + if (omap_ir->pdata->select_irda) + omap_ir->pdata->select_irda(omap_ir->dev, IR_SEL); + + err = omap_irda_startup(dev); + + if (err) + goto err_startup; + + omap_irda_set_speed(dev, omap_ir->speed = 9600); + + /* + * Open a new IrLAP layer instance. + */ + omap_ir->irlap = irlap_open(dev, &omap_ir->qos, "omap_sir"); + + err = -ENOMEM; + if (!omap_ir->irlap) + goto err_irlap; + + /* Now enable the interrupt and start the queue */ + omap_ir->open = 1; + + /* Start RX DMA */ + omap_irda_start_rx_dma(omap_ir); + + enable_irq(dev->irq); + netif_start_queue(dev); + + return 0; + +err_irlap: + omap_ir->open = 0; + omap_irda_shutdown(omap_ir); +err_startup: + dma_free_coherent(NULL, IRDA_SIR_MAX_FRAME, + omap_ir->tx_buf_dma_virt, omap_ir->tx_buf_dma_phys); +err_mem1: + dma_free_coherent(NULL, IRDA_SKB_MAX_MTU, + omap_ir->rx_buf_dma_virt, omap_ir->rx_buf_dma_phys); +err_irq: + free_irq(dev->irq, dev); + return err; +} + +static int omap_irda_stop(struct net_device *dev) +{ + struct omap_irda *omap_ir = netdev_priv(dev); + + disable_irq(dev->irq); + + netif_stop_queue(dev); + + omap_free_dma(omap_ir->rx_dma_channel); + omap_free_dma(omap_ir->tx_dma_channel); + + if (omap_ir->rx_buf_dma_virt) + dma_free_coherent(NULL, IRDA_SKB_MAX_MTU, + omap_ir->rx_buf_dma_virt, + omap_ir->rx_buf_dma_phys); + if (omap_ir->tx_buf_dma_virt) + dma_free_coherent(NULL, IRDA_SIR_MAX_FRAME, + omap_ir->tx_buf_dma_virt, + omap_ir->tx_buf_dma_phys); + + omap_irda_shutdown(omap_ir); + + /* Stop IrLAP */ + if (omap_ir->irlap) { + irlap_close(omap_ir->irlap); + omap_ir->irlap = NULL; + } + + omap_ir->open = 0; + + /* + * Free resources + */ + free_irq(dev->irq, dev); + + return 0; +} + +static int omap_irda_set_speed(struct net_device *dev, int speed) +{ + struct omap_irda *omap_ir = netdev_priv(dev); + int divisor; + unsigned long flags; + + /* Set IrDA speed */ + if (speed <= 115200) { + + local_irq_save(flags); + + /* SIR mode */ + if (omap_ir->pdata->transceiver_mode) + omap_ir->pdata->transceiver_mode(omap_ir->dev, + IR_SIRMODE); + + /* Set SIR mode */ + uart_reg_out(UART3_MDR1, 1); + uart_reg_out(UART3_EBLR, 1); + + divisor = 48000000 / (16 * speed); /* Base clock 48 MHz */ + + uart_reg_out(UART3_LCR, UART3_LCR_DIVEN); + uart_reg_out(UART3_DLL, (divisor & 0xff)); + uart_reg_out(UART3_DLH, (divisor >> 8)); + uart_reg_out(UART3_LCR, 0x03); + + uart_reg_out(UART3_MCR, 0); + + local_irq_restore(flags); + } else if (speed <= 1152000) { + + local_irq_save(flags); + + /* Set MIR mode, auto SIP */ + uart_reg_out(UART3_MDR1, UART3_MDR1_MIR | + UART3_MDR1_SIP_AUTO); + + uart_reg_out(UART3_EBLR, 2); + + divisor = 48000000 / (41 * speed); /* Base clock 48 MHz */ + + uart_reg_out(UART3_LCR, UART3_LCR_DIVEN); + uart_reg_out(UART3_DLL, (divisor & 0xff)); + uart_reg_out(UART3_DLH, (divisor >> 8)); + uart_reg_out(UART3_LCR, 0x03); + + if (omap_ir->pdata->transceiver_mode) + omap_ir->pdata->transceiver_mode(omap_ir->dev, + IR_MIRMODE); + + local_irq_restore(flags); + } else { + local_irq_save(flags); + + /* FIR mode */ + uart_reg_out(UART3_MDR1, UART3_MDR1_FIR | + UART3_MDR1_SIP_AUTO); + + if (omap_ir->pdata->transceiver_mode) + omap_ir->pdata->transceiver_mode(omap_ir->dev, + IR_FIRMODE); + + local_irq_restore(flags); + } + + omap_ir->speed = speed; + + return 0; +} + +#ifdef CONFIG_PM +/* + * Suspend the IrDA interface. + */ +static int omap_irda_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct net_device *dev = platform_get_drvdata(pdev); + struct omap_irda *omap_ir = netdev_priv(dev); + + if (!dev) + return 0; + + if (omap_ir->open) { + /* + * Stop the transmit queue + */ + netif_device_detach(dev); + disable_irq(dev->irq); + omap_irda_shutdown(omap_ir); + } + return 0; +} + +/* + * Resume the IrDA interface. + */ +static int omap_irda_resume(struct platform_device *pdev) +{ + struct net_device *dev = platform_get_drvdata(pdev); + struct omap_irda *omap_ir= netdev_priv(dev); + + if (!dev) + return 0; + + if (omap_ir->open) { + /* + * If we missed a speed change, initialise at the new speed + * directly. It is debatable whether this is actually + * required, but in the interests of continuing from where + * we left off it is desireable. The converse argument is + * that we should re-negotiate at 9600 baud again. + */ + if (omap_ir->newspeed) { + omap_ir->speed = omap_ir->newspeed; + omap_ir->newspeed = 0; + } + + omap_irda_startup(dev); + omap_irda_set_speed(dev, omap_ir->speed); + enable_irq(dev->irq); + + /* + * This automatically wakes up the queue + */ + netif_device_attach(dev); + } + + return 0; +} +#else +#define omap_irda_suspend NULL +#define omap_irda_resume NULL +#endif + +static int omap_irda_probe(struct platform_device *pdev) +{ + struct net_device *dev; + struct omap_irda *omap_ir; + struct omap_irda_config *pdata = pdev->dev.platform_data; + unsigned int baudrate_mask; + int err = 0; + int irq = NO_IRQ; + + if (!pdata) { + printk(KERN_ERR "IrDA Platform data not supplied\n"); + return -ENOENT; + } + + if (!pdata->rx_channel || !pdata->tx_channel) { + printk(KERN_ERR "IrDA invalid rx/tx channel value\n"); + return -ENOENT; + } + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) { + printk(KERN_WARNING "no irq for IrDA\n"); + return -ENOENT; + } + + dev = alloc_irdadev(sizeof(struct omap_irda)); + if (!dev) + goto err_mem_1; + + + omap_ir = netdev_priv(dev); + omap_ir->dev = &pdev->dev; + omap_ir->pdata = pdata; + + dev->hard_start_xmit = omap_irda_hard_xmit; + dev->open = omap_irda_start; + dev->stop = omap_irda_stop; + dev->do_ioctl = omap_irda_ioctl; + dev->get_stats = omap_irda_stats; + dev->irq = irq; + + irda_init_max_qos_capabilies(&omap_ir->qos); + + baudrate_mask = 0; + if (omap_ir->pdata->transceiver_cap & IR_SIRMODE) + baudrate_mask |= IR_9600|IR_19200|IR_38400|IR_57600|IR_115200; + if (omap_ir->pdata->transceiver_cap & IR_MIRMODE) + baudrate_mask |= IR_57600 | IR_1152000; + if (omap_ir->pdata->transceiver_cap & IR_FIRMODE) + baudrate_mask |= IR_4000000 << 8; + + omap_ir->qos.baud_rate.bits &= baudrate_mask; + omap_ir->qos.min_turn_time.bits = 7; + + irda_qos_bits_to_value(&omap_ir->qos); + + /* Any better way to avoid this? No. */ + if (machine_is_omap_h3() || machine_is_omap_h4()) + INIT_DELAYED_WORK(&omap_ir->pdata->gpio_expa, NULL); + + err = register_netdev(dev); + if (!err) + platform_set_drvdata(pdev, dev); + else + free_netdev(dev); + +err_mem_1: + return err; +} + +static int omap_irda_remove(struct platform_device *pdev) +{ + struct net_device *dev = platform_get_drvdata(pdev); + + if (pdev) { + unregister_netdev(dev); + free_netdev(dev); + } + return 0; +} + +static struct platform_driver omapir_driver = { + .probe = omap_irda_probe, + .remove = omap_irda_remove, + .suspend = omap_irda_suspend, + .resume = omap_irda_resume, + .driver = { + .name = "omapirda", + }, +}; + +static char __initdata banner[] = KERN_INFO "OMAP IrDA driver initializing\n"; + +static int __init omap_irda_init(void) +{ + printk(banner); + return platform_driver_register(&omapir_driver); +} + +static void __exit omap_irda_exit(void) +{ + platform_driver_unregister(&omapir_driver); +} + +module_init(omap_irda_init); +module_exit(omap_irda_exit); + +MODULE_AUTHOR("MontaVista"); +MODULE_DESCRIPTION("OMAP IrDA Driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/net/smc91x.c b/drivers/net/smc91x.c index fdcbaf8dfa7..734bf1e82c4 100644 --- a/drivers/net/smc91x.c +++ b/drivers/net/smc91x.c @@ -421,6 +421,11 @@ static inline void smc_rcv(struct net_device *dev) dev->name, packet_number, status, packet_len, packet_len); + if (unlikely(packet_len == 0 && !(status & RS_ERRORS))) { + printk(KERN_ERR "%s: bad memory timings: rxlen %u status %x\n", + dev->name, packet_len, status); + status |= RS_TOOSHORT; + } back: if (unlikely(packet_len < 6 || status & RS_ERRORS)) { if (status & RS_TOOLONG && packet_len <= (1514 + 4 + 6)) { diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 33da1127992..aad68dbd486 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -63,6 +63,13 @@ config BATTERY_TOSA Say Y to enable support for the battery on the Sharp Zaurus SL-6000 (tosa) models. +config TWL4030_BCI_BATTERY + tristate "OMAP TWL4030 BCI Battery driver" + depends on TWL4030_CORE && TWL4030_MADC + help + Support for OMAP TWL4030 BCI Battery driver. + This driver can give support for TWL4030 Battery Charge Interface. + config BATTERY_WM97XX bool "WM97xx generic battery driver" depends on TOUCHSCREEN_WM97XX=y diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 2fcf41d13e5..7e688e752be 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -21,6 +21,8 @@ obj-$(CONFIG_WM8350_POWER) += wm8350_power.o obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o +obj-$(CONFIG_BATTERY_BQ27x00) += bq27x00_battery.o +obj-$(CONFIG_TWL4030_BCI_BATTERY) += twl4030_bci_battery.o obj-$(CONFIG_BATTERY_TOSA) += tosa_battery.o obj-$(CONFIG_BATTERY_WM97XX) += wm97xx_battery.o obj-$(CONFIG_BATTERY_BQ27x00) += bq27x00_battery.o diff --git a/drivers/power/twl4030_bci_battery.c b/drivers/power/twl4030_bci_battery.c new file mode 100644 index 00000000000..ddba62b1a55 --- /dev/null +++ b/drivers/power/twl4030_bci_battery.c @@ -0,0 +1,1097 @@ +/* + * linux/drivers/power/twl4030_bci_battery.c + * + * OMAP2430/3430 BCI battery driver for Linux + * + * Copyright (C) 2008 Texas Instruments, Inc. + * Author: Texas Instruments, Inc. + * + * This package 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. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define T2_BATTERY_VOLT 0x04 +#define T2_BATTERY_TEMP 0x06 +#define T2_BATTERY_CUR 0x08 + +/* charger constants */ +#define NO_PW_CONN 0 +#define AC_PW_CONN 0x01 +#define USB_PW_CONN 0x02 + +/* TWL4030_MODULE_USB */ +#define REG_POWER_CTRL 0x0AC +#define OTG_EN 0x020 +#define REG_PHY_CLK_CTRL 0x0FE +#define REG_PHY_CLK_CTRL_STS 0x0FF +#define PHY_DPLL_CLK 0x01 + +#define REG_BCICTL1 0x023 +#define REG_BCICTL2 0x024 +#define CGAIN 0x020 +#define ITHEN 0x010 +#define ITHSENS 0x007 + +/* Boot BCI flag bits */ +#define BCIAUTOWEN 0x020 +#define CONFIG_DONE 0x010 +#define BCIAUTOUSB 0x002 +#define BCIAUTOAC 0x001 +#define BCIMSTAT_MASK 0x03F + +/* Boot BCI register */ +#define REG_BOOT_BCI 0x007 +#define REG_CTRL1 0x00 +#define REG_SW1SELECT_MSB 0x07 +#define SW1_CH9_SEL 0x02 +#define REG_CTRL_SW1 0x012 +#define SW1_TRIGGER 0x020 +#define EOC_SW1 0x002 +#define REG_GPCH9 0x049 +#define REG_STS_HW_CONDITIONS 0x0F +#define STS_VBUS 0x080 +#define STS_CHG 0x02 +#define REG_BCIMSTATEC 0x02 +#define REG_BCIMFSTS4 0x010 +#define REG_BCIMFSTS2 0x00E +#define REG_BCIMFSTS3 0x00F +#define REG_BCIMFSTS1 0x001 +#define USBFASTMCHG 0x004 +#define BATSTSPCHG 0x004 +#define BATSTSMCHG 0x040 +#define VBATOV4 0x020 +#define VBATOV3 0x010 +#define VBATOV2 0x008 +#define VBATOV1 0x004 +#define REG_BB_CFG 0x012 +#define BBCHEN 0x010 + +/* Power supply charge interrupt */ +#define REG_PWR_ISR1 0x00 +#define REG_PWR_IMR1 0x01 +#define REG_PWR_EDR1 0x05 +#define REG_PWR_SIH_CTRL 0x007 + +#define USB_PRES 0x004 +#define CHG_PRES 0x002 + +#define USB_PRES_RISING 0x020 +#define USB_PRES_FALLING 0x010 +#define CHG_PRES_RISING 0x008 +#define CHG_PRES_FALLING 0x004 +#define AC_STATEC 0x20 +#define COR 0x004 + +/* interrupt status registers */ +#define REG_BCIISR1A 0x0 +#define REG_BCIISR2A 0x01 + +/* Interrupt flags bits BCIISR1 */ +#define BATSTS_ISR1 0x080 +#define VBATLVL_ISR1 0x001 + +/* Interrupt mask registers for int1*/ +#define REG_BCIIMR1A 0x002 +#define REG_BCIIMR2A 0x003 + + /* Interrupt masks for BCIIMR1 */ +#define BATSTS_IMR1 0x080 +#define VBATLVL_IMR1 0x001 + +/* Interrupt edge detection register */ +#define REG_BCIEDR1 0x00A +#define REG_BCIEDR2 0x00B +#define REG_BCIEDR3 0x00C + +/* BCIEDR2 */ +#define BATSTS_EDRRISIN 0x080 +#define BATSTS_EDRFALLING 0x040 + +/* BCIEDR3 */ +#define VBATLVL_EDRRISIN 0x02 + +/* Step size and prescaler ratio */ +#define TEMP_STEP_SIZE 147 +#define TEMP_PSR_R 100 + +#define VOLT_STEP_SIZE 588 +#define VOLT_PSR_R 100 + +#define CURR_STEP_SIZE 147 +#define CURR_PSR_R1 44 +#define CURR_PSR_R2 80 + +#define BK_VOLT_STEP_SIZE 441 +#define BK_VOLT_PSR_R 100 + +#define ENABLE 1 +#define DISABLE 1 + +/* Ptr to thermistor table */ +int *therm_tbl; + +struct twl4030_bci_device_info { + struct device *dev; + + unsigned long update_time; + int voltage_uV; + int bk_voltage_uV; + int current_uA; + int temp_C; + int charge_rsoc; + int charge_status; + + struct power_supply bat; + struct power_supply bk_bat; + struct delayed_work twl4030_bci_monitor_work; + struct delayed_work twl4030_bk_bci_monitor_work; +}; + +static int usb_charger_flag; +static int LVL_1, LVL_2, LVL_3, LVL_4; + +static int read_bci_val(u8 reg_1); +static inline int clear_n_set(u8 mod_no, u8 clear, u8 set, u8 reg); +static int twl4030charger_presence(void); + +/* + * Report and clear the charger presence event. + */ +static inline int twl4030charger_presence_evt(void) +{ + int ret; + u8 chg_sts, set = 0, clear = 0; + + /* read charger power supply status */ + ret = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &chg_sts, + REG_STS_HW_CONDITIONS); + if (ret) + return IRQ_NONE; + + if (chg_sts & STS_CHG) { /* If the AC charger have been connected */ + /* configuring falling edge detection for CHG_PRES */ + set = CHG_PRES_FALLING; + clear = CHG_PRES_RISING; + } else { /* If the AC charger have been disconnected */ + /* configuring rising edge detection for CHG_PRES */ + set = CHG_PRES_RISING; + clear = CHG_PRES_FALLING; + } + + /* Update the interrupt edge detection register */ + clear_n_set(TWL4030_MODULE_INT, clear, set, REG_PWR_EDR1); + + return 0; +} + +/* + * Interrupt service routine + * + * Attends to TWL 4030 power module interruptions events, specifically + * USB_PRES (USB charger presence) CHG_PRES (AC charger presence) events + * + */ +static irqreturn_t twl4030charger_interrupt(int irq, void *_di) +{ + struct twl4030_bci_device_info *di = _di; + +#ifdef CONFIG_LOCKDEP + /* WORKAROUND for lockdep forcing IRQF_DISABLED on us, which + * we don't want and can't tolerate. Although it might be + * friendlier not to borrow this thread context... + */ + local_irq_enable(); +#endif + + twl4030charger_presence_evt(); + power_supply_changed(&di->bat); + + return IRQ_HANDLED; +} + +/* + * This function handles the twl4030 battery presence interrupt + */ +static int twl4030battery_presence_evt(void) +{ + int ret; + u8 batstsmchg, batstspchg; + + /* check for the battery presence in main charge*/ + ret = twl4030_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, + &batstsmchg, REG_BCIMFSTS3); + if (ret) + return ret; + + /* check for the battery presence in precharge */ + ret = twl4030_i2c_read_u8(TWL4030_MODULE_PRECHARGE, + &batstspchg, REG_BCIMFSTS1); + if (ret) + return ret; + + /* + * REVISIT: Physically inserting/removing the batt + * does not seem to generate an int on 3430ES2 SDP. + */ + if ((batstspchg & BATSTSPCHG) || (batstsmchg & BATSTSMCHG)) { + /* In case of the battery insertion event */ + ret = clear_n_set(TWL4030_MODULE_INTERRUPTS, BATSTS_EDRRISIN, + BATSTS_EDRFALLING, REG_BCIEDR2); + if (ret) + return ret; + } else { + /* In case of the battery removal event */ + ret = clear_n_set(TWL4030_MODULE_INTERRUPTS, BATSTS_EDRFALLING, + BATSTS_EDRRISIN, REG_BCIEDR2); + if (ret) + return ret; + } + + return 0; +} + +/* + * This function handles the twl4030 battery voltage level interrupt. + */ +static int twl4030battery_level_evt(void) +{ + int ret; + u8 mfst; + + /* checking for threshold event */ + ret = twl4030_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, + &mfst, REG_BCIMFSTS2); + if (ret) + return ret; + + /* REVISIT could use a bitmap */ + if (mfst & VBATOV4) { + LVL_4 = 1; + LVL_3 = 0; + LVL_2 = 0; + LVL_1 = 0; + } else if (mfst & VBATOV3) { + LVL_4 = 0; + LVL_3 = 1; + LVL_2 = 0; + LVL_1 = 0; + } else if (mfst & VBATOV2) { + LVL_4 = 0; + LVL_3 = 0; + LVL_2 = 1; + LVL_1 = 0; + } else { + LVL_4 = 0; + LVL_3 = 0; + LVL_2 = 0; + LVL_1 = 1; + } + + return 0; +} + +/* + * Interrupt service routine + * + * Attends to BCI interruptions events, + * specifically BATSTS (battery connection and removal) + * VBATOV (main battery voltage threshold) events + * + */ +static irqreturn_t twl4030battery_interrupt(int irq, void *_di) +{ + u8 isr1a_val, isr2a_val, clear_2a, clear_1a; + int ret; + +#ifdef CONFIG_LOCKDEP + /* WORKAROUND for lockdep forcing IRQF_DISABLED on us, which + * we don't want and can't tolerate. Although it might be + * friendlier not to borrow this thread context... + */ + local_irq_enable(); +#endif + + ret = twl4030_i2c_read_u8(TWL4030_MODULE_INTERRUPTS, &isr1a_val, + REG_BCIISR1A); + if (ret) + return IRQ_NONE; + + ret = twl4030_i2c_read_u8(TWL4030_MODULE_INTERRUPTS, &isr2a_val, + REG_BCIISR2A); + if (ret) + return IRQ_NONE; + + clear_2a = (isr2a_val & VBATLVL_ISR1) ? (VBATLVL_ISR1) : 0; + clear_1a = (isr1a_val & BATSTS_ISR1) ? (BATSTS_ISR1) : 0; + + /* cleaning BCI interrupt status flags */ + ret = twl4030_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, + clear_1a , REG_BCIISR1A); + if (ret) + return IRQ_NONE; + + ret = twl4030_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, + clear_2a , REG_BCIISR2A); + if (ret) + return IRQ_NONE; + + /* battery connetion or removal event */ + if (isr1a_val & BATSTS_ISR1) + twl4030battery_presence_evt(); + /* battery voltage threshold event*/ + else if (isr2a_val & VBATLVL_ISR1) + twl4030battery_level_evt(); + else + return IRQ_NONE; + + return IRQ_HANDLED; +} + +/* + * Enable/Disable hardware battery level event notifications. + */ +static int twl4030battery_hw_level_en(int enable) +{ + int ret; + + if (enable) { + /* unmask VBATOV interrupt for INT1 */ + ret = clear_n_set(TWL4030_MODULE_INTERRUPTS, VBATLVL_IMR1, + 0, REG_BCIIMR2A); + if (ret) + return ret; + + /* configuring interrupt edge detection for VBATOv */ + ret = clear_n_set(TWL4030_MODULE_INTERRUPTS, 0, + VBATLVL_EDRRISIN, REG_BCIEDR3); + if (ret) + return ret; + } else { + /* mask VBATOV interrupt for INT1 */ + ret = clear_n_set(TWL4030_MODULE_INTERRUPTS, 0, + VBATLVL_IMR1, REG_BCIIMR2A); + if (ret) + return ret; + } + + return 0; +} + +/* + * Enable/disable hardware battery presence event notifications. + */ +static int twl4030battery_hw_presence_en(int enable) +{ + int ret; + + if (enable) { + /* unmask BATSTS interrupt for INT1 */ + ret = clear_n_set(TWL4030_MODULE_INTERRUPTS, BATSTS_IMR1, + 0, REG_BCIIMR1A); + if (ret) + return ret; + + /* configuring interrupt edge for BATSTS */ + ret = clear_n_set(TWL4030_MODULE_INTERRUPTS, 0, + BATSTS_EDRRISIN | BATSTS_EDRFALLING, REG_BCIEDR2); + if (ret) + return ret; + } else { + /* mask BATSTS interrupt for INT1 */ + ret = clear_n_set(TWL4030_MODULE_INTERRUPTS, 0, + BATSTS_IMR1, REG_BCIIMR1A); + if (ret) + return ret; + } + + return 0; +} + +/* + * Enable/Disable AC Charge funtionality. + */ +static int twl4030charger_ac_en(int enable) +{ + int ret; + + if (enable) { + /* forcing the field BCIAUTOAC (BOOT_BCI[0) to 1 */ + ret = clear_n_set(TWL4030_MODULE_PM_MASTER, 0, + (CONFIG_DONE | BCIAUTOWEN | BCIAUTOAC), + REG_BOOT_BCI); + if (ret) + return ret; + } else { + /* forcing the field BCIAUTOAC (BOOT_BCI[0) to 0*/ + ret = clear_n_set(TWL4030_MODULE_PM_MASTER, BCIAUTOAC, + (CONFIG_DONE | BCIAUTOWEN), + REG_BOOT_BCI); + if (ret) + return ret; + } + + return 0; +} + +/* + * Enable/Disable USB Charge funtionality. + */ +int twl4030charger_usb_en(int enable) +{ + u8 value; + int ret; + unsigned long timeout; + + if (enable) { + /* Check for USB charger conneted */ + ret = twl4030charger_presence(); + if (ret < 0) + return ret; + + if (!(ret & USB_PW_CONN)) + return -ENXIO; + + /* forcing the field BCIAUTOUSB (BOOT_BCI[1]) to 1 */ + ret = clear_n_set(TWL4030_MODULE_PM_MASTER, 0, + (CONFIG_DONE | BCIAUTOWEN | BCIAUTOUSB), + REG_BOOT_BCI); + if (ret) + return ret; + + ret = clear_n_set(TWL4030_MODULE_USB, 0, PHY_DPLL_CLK, + REG_PHY_CLK_CTRL); + if (ret) + return ret; + + value = 0; + timeout = jiffies + msecs_to_jiffies(50); + + while ((!(value & PHY_DPLL_CLK)) && + time_before(jiffies, timeout)) { + udelay(10); + ret = twl4030_i2c_read_u8(TWL4030_MODULE_USB, &value, + REG_PHY_CLK_CTRL_STS); + if (ret) + return ret; + } + + /* OTG_EN (POWER_CTRL[5]) to 1 */ + ret = clear_n_set(TWL4030_MODULE_USB, 0, OTG_EN, + REG_POWER_CTRL); + if (ret) + return ret; + + mdelay(50); + + /* forcing USBFASTMCHG(BCIMFSTS4[2]) to 1 */ + ret = clear_n_set(TWL4030_MODULE_MAIN_CHARGE, 0, + USBFASTMCHG, REG_BCIMFSTS4); + if (ret) + return ret; + } else { + twl4030charger_presence(); + ret = clear_n_set(TWL4030_MODULE_PM_MASTER, BCIAUTOUSB, + (CONFIG_DONE | BCIAUTOWEN), REG_BOOT_BCI); + if (ret) + return ret; + } + + return 0; +} + +/* + * Return battery temperature + * Or < 0 on failure. + */ +static int twl4030battery_temperature(void) +{ + u8 val; + int temp, curr, volt, res, ret; + + /* Getting and calculating the thermistor voltage */ + ret = read_bci_val(T2_BATTERY_TEMP); + if (ret < 0) + return ret; + + volt = (ret * TEMP_STEP_SIZE) / TEMP_PSR_R; + + /* Getting and calculating the supply current in micro ampers */ + ret = twl4030_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, &val, + REG_BCICTL2); + if (ret) + return 0; + + curr = ((val & ITHSENS) + 1) * 10; + + /* Getting and calculating the thermistor resistance in ohms*/ + res = volt * 1000 / curr; + + /*calculating temperature*/ + for (temp = 58; temp >= 0; temp--) { + int actual = therm_tbl[temp]; + if ((actual - res) >= 0) + break; + } + + /* Negative temperature */ + if (temp < 3) { + if (temp == 2) + temp = -1; + else if (temp == 1) + temp = -2; + else + temp = -3; + } + + return temp + 1; +} + +/* + * Return battery voltage + * Or < 0 on failure. + */ +static int twl4030battery_voltage(void) +{ + int volt = read_bci_val(T2_BATTERY_VOLT); + + return (volt * VOLT_STEP_SIZE) / VOLT_PSR_R; +} + +/* + * Return the battery current + * Or < 0 on failure. + */ +static int twl4030battery_current(void) +{ + int ret, curr = read_bci_val(T2_BATTERY_CUR); + u8 val; + + ret = twl4030_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, &val, + REG_BCICTL1); + if (ret) + return ret; + + if (val & CGAIN) /* slope of 0.44 mV/mA */ + return (curr * CURR_STEP_SIZE) / CURR_PSR_R1; + else /* slope of 0.88 mV/mA */ + return (curr * CURR_STEP_SIZE) / CURR_PSR_R2; +} + +/* + * Return the battery backup voltage + * Or < 0 on failure. + */ +static int twl4030backupbatt_voltage(void) +{ + struct twl4030_madc_request req; + int temp; + + req.channels = (1 << 9); + req.do_avg = 0; + req.method = TWL4030_MADC_SW1; + req.active = 0; + req.func_cb = NULL; + twl4030_madc_conversion(&req); + temp = (u16)req.rbuf[9]; + + return (temp * BK_VOLT_STEP_SIZE) / BK_VOLT_PSR_R; +} + +/* + * Returns an integer value, that means, + * NO_PW_CONN no power supply is connected + * AC_PW_CONN if the AC power supply is connected + * USB_PW_CONN if the USB power supply is connected + * AC_PW_CONN + USB_PW_CONN if USB and AC power supplies are both connected + * + * Or < 0 on failure. + */ +static int twl4030charger_presence(void) +{ + int ret; + u8 hwsts; + + ret = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &hwsts, + REG_STS_HW_CONDITIONS); + if (ret) { + pr_err("twl4030_bci: error reading STS_HW_CONDITIONS\n"); + return ret; + } + + ret = (hwsts & STS_CHG) ? AC_PW_CONN : NO_PW_CONN; + ret += (hwsts & STS_VBUS) ? USB_PW_CONN : NO_PW_CONN; + + if (ret & USB_PW_CONN) + usb_charger_flag = 1; + else + usb_charger_flag = 0; + + return ret; + +} + +/* + * Returns the main charge FSM status + * Or < 0 on failure. + */ +static int twl4030bci_status(void) +{ + int ret; + u8 status; + + ret = twl4030_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, + &status, REG_BCIMSTATEC); + if (ret) { + pr_err("twl4030_bci: error reading BCIMSTATEC\n"); + return ret; + } + + return (int) (status & BCIMSTAT_MASK); +} + +static int read_bci_val(u8 reg) +{ + int ret, temp; + u8 val; + + /* reading MSB */ + ret = twl4030_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, &val, + reg + 1); + if (ret) + return ret; + + temp = ((int)(val & 0x03)) << 8; + + /* reading LSB */ + ret = twl4030_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, &val, + reg); + if (ret) + return ret; + + return temp | val; +} + +/* + * Settup the twl4030 BCI module to enable backup + * battery charging. + */ +static int twl4030backupbatt_voltage_setup(void) +{ + int ret; + + /* Starting backup batery charge */ + ret = clear_n_set(TWL4030_MODULE_PM_RECEIVER, 0, BBCHEN, + REG_BB_CFG); + if (ret) + return ret; + + return 0; +} + +/* + * Settup the twl4030 BCI module to measure battery + * temperature + */ +static int twl4030battery_temp_setup(void) +{ + int ret; + + /* Enabling thermistor current */ + ret = clear_n_set(TWL4030_MODULE_MAIN_CHARGE, 0, ITHEN, + REG_BCICTL1); + if (ret) + return ret; + + return 0; +} + +/* + * Sets and clears bits on an given register on a given module + */ +static inline int clear_n_set(u8 mod_no, u8 clear, u8 set, u8 reg) +{ + int ret; + u8 val = 0; + + /* Gets the initial register value */ + ret = twl4030_i2c_read_u8(mod_no, &val, reg); + if (ret) + return ret; + + /* Clearing all those bits to clear */ + val &= ~(clear); + + /* Setting all those bits to set */ + val |= set; + + /* Update the register */ + ret = twl4030_i2c_write_u8(mod_no, val, reg); + if (ret) + return ret; + + return 0; +} + +static enum power_supply_property twl4030_bci_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, +}; + +static enum power_supply_property twl4030_bk_bci_battery_props[] = { + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +static void +twl4030_bk_bci_battery_read_status(struct twl4030_bci_device_info *di) +{ + di->bk_voltage_uV = twl4030backupbatt_voltage(); +} + +static void twl4030_bk_bci_battery_work(struct work_struct *work) +{ + struct twl4030_bci_device_info *di = container_of(work, + struct twl4030_bci_device_info, + twl4030_bk_bci_monitor_work.work); + + twl4030_bk_bci_battery_read_status(di); + schedule_delayed_work(&di->twl4030_bk_bci_monitor_work, 500); +} + +static void twl4030_bci_battery_read_status(struct twl4030_bci_device_info *di) +{ + di->temp_C = twl4030battery_temperature(); + di->voltage_uV = twl4030battery_voltage(); + di->current_uA = twl4030battery_current(); +} + +static void +twl4030_bci_battery_update_status(struct twl4030_bci_device_info *di) +{ + twl4030_bci_battery_read_status(di); + di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN; + + if (power_supply_am_i_supplied(&di->bat)) + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + else + di->charge_status = POWER_SUPPLY_STATUS_DISCHARGING; +} + +static void twl4030_bci_battery_work(struct work_struct *work) +{ + struct twl4030_bci_device_info *di = container_of(work, + struct twl4030_bci_device_info, twl4030_bci_monitor_work.work); + + twl4030_bci_battery_update_status(di); + schedule_delayed_work(&di->twl4030_bci_monitor_work, 100); +} + + +#define to_twl4030_bci_device_info(x) container_of((x), \ + struct twl4030_bci_device_info, bat); + +static void twl4030_bci_battery_external_power_changed(struct power_supply *psy) +{ + struct twl4030_bci_device_info *di = to_twl4030_bci_device_info(psy); + + cancel_delayed_work(&di->twl4030_bci_monitor_work); + schedule_delayed_work(&di->twl4030_bci_monitor_work, 0); +} + +#define to_twl4030_bk_bci_device_info(x) container_of((x), \ + struct twl4030_bci_device_info, bk_bat); + +static int twl4030_bk_bci_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct twl4030_bci_device_info *di = to_twl4030_bk_bci_device_info(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = di->bk_voltage_uV; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int twl4030_bci_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct twl4030_bci_device_info *di; + int status = 0; + + di = to_twl4030_bci_device_info(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = di->charge_status; + return 0; + default: + break; + } + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = di->voltage_uV; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = di->current_uA; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = di->temp_C; + break; + case POWER_SUPPLY_PROP_ONLINE: + status = twl4030bci_status(); + if ((status & AC_STATEC) == AC_STATEC) + val->intval = POWER_SUPPLY_TYPE_MAINS; + else if (usb_charger_flag) + val->intval = POWER_SUPPLY_TYPE_USB; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_CAPACITY: + /* + * need to get the correct percentage value per the + * battery characteristics. Approx values for now. + */ + if (di->voltage_uV < 2894 || LVL_1) { + val->intval = 5; + LVL_1 = 0; + } else if ((di->voltage_uV < 3451 && di->voltage_uV > 2894) + || LVL_2) { + val->intval = 20; + LVL_2 = 0; + } else if ((di->voltage_uV < 3902 && di->voltage_uV > 3451) + || LVL_3) { + val->intval = 50; + LVL_3 = 0; + } else if ((di->voltage_uV < 3949 && di->voltage_uV > 3902) + || LVL_4) { + val->intval = 75; + LVL_4 = 0; + } else if (di->voltage_uV > 3949) + val->intval = 90; + break; + default: + return -EINVAL; + } + return 0; +} + +static char *twl4030_bci_supplied_to[] = { + "twl4030_bci_battery", +}; + +static int __init twl4030_bci_battery_probe(struct platform_device *pdev) +{ + struct twl4030_bci_platform_data *pdata = pdev->dev.platform_data; + struct twl4030_bci_device_info *di; + int irq; + int ret; + + therm_tbl = pdata->battery_tmp_tbl; + + di = kzalloc(sizeof(*di), GFP_KERNEL); + if (!di) + return -ENOMEM; + + di->dev = &pdev->dev; + di->bat.name = "twl4030_bci_battery"; + di->bat.supplied_to = twl4030_bci_supplied_to; + di->bat.num_supplicants = ARRAY_SIZE(twl4030_bci_supplied_to); + di->bat.type = POWER_SUPPLY_TYPE_BATTERY; + di->bat.properties = twl4030_bci_battery_props; + di->bat.num_properties = ARRAY_SIZE(twl4030_bci_battery_props); + di->bat.get_property = twl4030_bci_battery_get_property; + di->bat.external_power_changed = + twl4030_bci_battery_external_power_changed; + + di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN; + + di->bk_bat.name = "twl4030_bci_bk_battery"; + di->bk_bat.type = POWER_SUPPLY_TYPE_BATTERY; + di->bk_bat.properties = twl4030_bk_bci_battery_props; + di->bk_bat.num_properties = ARRAY_SIZE(twl4030_bk_bci_battery_props); + di->bk_bat.get_property = twl4030_bk_bci_battery_get_property; + di->bk_bat.external_power_changed = NULL; + + twl4030charger_ac_en(ENABLE); + twl4030charger_usb_en(ENABLE); + twl4030battery_hw_level_en(ENABLE); + twl4030battery_hw_presence_en(ENABLE); + + platform_set_drvdata(pdev, di); + + /* settings for temperature sensing */ + ret = twl4030battery_temp_setup(); + if (ret) + goto temp_setup_fail; + + /* enabling GPCH09 for read back battery voltage */ + ret = twl4030backupbatt_voltage_setup(); + if (ret) + goto voltage_setup_fail; + + /* REVISIT do we need to request both IRQs ?? */ + + /* request BCI interruption */ + irq = platform_get_irq(pdev, 1); + ret = request_irq(irq, twl4030battery_interrupt, + 0, pdev->name, NULL); + if (ret) { + dev_dbg(&pdev->dev, "could not request irq %d, status %d\n", + irq, ret); + goto batt_irq_fail; + } + + /* request Power interruption */ + irq = platform_get_irq(pdev, 0); + ret = request_irq(irq, twl4030charger_interrupt, + 0, pdev->name, di); + + if (ret) { + dev_dbg(&pdev->dev, "could not request irq %d, status %d\n", + irq, ret); + goto chg_irq_fail; + } + + ret = power_supply_register(&pdev->dev, &di->bat); + if (ret) { + dev_dbg(&pdev->dev, "failed to register main battery\n"); + goto batt_failed; + } + + INIT_DELAYED_WORK_DEFERRABLE(&di->twl4030_bci_monitor_work, + twl4030_bci_battery_work); + schedule_delayed_work(&di->twl4030_bci_monitor_work, 0); + + ret = power_supply_register(&pdev->dev, &di->bk_bat); + if (ret) { + dev_dbg(&pdev->dev, "failed to register backup battery\n"); + goto bk_batt_failed; + } + + INIT_DELAYED_WORK_DEFERRABLE(&di->twl4030_bk_bci_monitor_work, + twl4030_bk_bci_battery_work); + schedule_delayed_work(&di->twl4030_bk_bci_monitor_work, 500); + + return 0; + +bk_batt_failed: + power_supply_unregister(&di->bat); +batt_failed: + free_irq(irq, di); +chg_irq_fail: + irq = platform_get_irq(pdev, 1); + free_irq(irq, NULL); +batt_irq_fail: +voltage_setup_fail: +temp_setup_fail: + twl4030charger_ac_en(DISABLE); + twl4030charger_usb_en(DISABLE); + twl4030battery_hw_level_en(DISABLE); + twl4030battery_hw_presence_en(DISABLE); + kfree(di); + + return ret; +} + +static int __exit twl4030_bci_battery_remove(struct platform_device *pdev) +{ + struct twl4030_bci_device_info *di = platform_get_drvdata(pdev); + int irq; + + twl4030charger_ac_en(DISABLE); + twl4030charger_usb_en(DISABLE); + twl4030battery_hw_level_en(DISABLE); + twl4030battery_hw_presence_en(DISABLE); + + irq = platform_get_irq(pdev, 0); + free_irq(irq, di); + + irq = platform_get_irq(pdev, 1); + free_irq(irq, NULL); + + flush_scheduled_work(); + power_supply_unregister(&di->bat); + power_supply_unregister(&di->bk_bat); + platform_set_drvdata(pdev, NULL); + kfree(di); + + return 0; +} + +#ifdef CONFIG_PM +static int twl4030_bci_battery_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct twl4030_bci_device_info *di = platform_get_drvdata(pdev); + + di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN; + cancel_delayed_work(&di->twl4030_bci_monitor_work); + cancel_delayed_work(&di->twl4030_bk_bci_monitor_work); + return 0; +} + +static int twl4030_bci_battery_resume(struct platform_device *pdev) +{ + struct twl4030_bci_device_info *di = platform_get_drvdata(pdev); + + schedule_delayed_work(&di->twl4030_bci_monitor_work, 0); + schedule_delayed_work(&di->twl4030_bk_bci_monitor_work, 50); + return 0; +} +#else +#define twl4030_bci_battery_suspend NULL +#define twl4030_bci_battery_resume NULL +#endif /* CONFIG_PM */ + +static struct platform_driver twl4030_bci_battery_driver = { + .probe = twl4030_bci_battery_probe, + .remove = __exit_p(twl4030_bci_battery_remove), + .suspend = twl4030_bci_battery_suspend, + .resume = twl4030_bci_battery_resume, + .driver = { + .name = "twl4030_bci", + }, +}; + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:twl4030_bci"); +MODULE_AUTHOR("Texas Instruments Inc"); + +static int __init twl4030_battery_init(void) +{ + return platform_driver_register(&twl4030_bci_battery_driver); +} +module_init(twl4030_battery_init); + +static void __exit twl4030_battery_exit(void) +{ + platform_driver_unregister(&twl4030_bci_battery_driver); +} +module_exit(twl4030_battery_exit); + diff --git a/drivers/serial/8250.c b/drivers/serial/8250.c index b4b39811b44..67877b1be0d 100644 --- a/drivers/serial/8250.c +++ b/drivers/serial/8250.c @@ -1596,7 +1596,11 @@ static irqreturn_t serial8250_interrupt(int irq, void *dev_id) DEBUG_INTR("end.\n"); +#ifdef CONFIG_ARCH_OMAP15XX + return IRQ_HANDLED; /* FIXME: iir status not ready on 1510 */ +#else return IRQ_RETVAL(handled); +#endif } /* @@ -2387,6 +2391,19 @@ serial8250_set_termios(struct uart_port *port, struct ktermios *termios, /* emulated UARTs (Lucent Venus 167x) need two steps */ serial_outp(up, UART_FCR, UART_FCR_ENABLE_FIFO); } + + /* Note that we need to set ECB to access write water mark + * bits. First allow FCR tx fifo write, then set fcr with + * possible TX fifo settings. */ + if (uart_config[up->port.type].flags & UART_CAP_EFR) { + serial_outp(up, UART_LCR, 0xbf); /* Access EFR */ + serial_outp(up, UART_EFR, UART_EFR_ECB); + serial_outp(up, UART_LCR, 0x0); /* Access FCR */ + serial_outp(up, UART_FCR, fcr); + serial_outp(up, UART_LCR, 0xbf); /* Access EFR */ + serial_outp(up, UART_EFR, 0); + serial_outp(up, UART_LCR, cval); /* Access FCR */ + } else serial_outp(up, UART_FCR, fcr); /* set fcr */ } serial8250_set_mctrl(&up->port, up->port.mctrl); diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 83a185d5296..8bc6d114721 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -230,6 +230,47 @@ config SPI_XILINX # comment "SPI Protocol Masters" +config SPI_TSC210X + depends on SPI_MASTER && EXPERIMENTAL + tristate "TI TSC210x (TSC2101/TSC2102) support" + help + Say Y here if you want support for the TSC210x chips. Some + boards use these for touchscreen and audio support. + + These are members of a family of highly integrated PDA analog + interface circuit. They include a 12-bit ADC used for battery, + temperature, touchscreen, and other sensors. They also have + an audio DAC and amplifier, and in some models an audio ADC. + The audio support is highly chip-specific, but most of the + sensor support works the same. + + Note that the device has to be present in the board's SPI + devices table for this driver to load. This driver doesn't + automatically enable touchscreen, sensors or audio + functionality - enable these in their respective menus. + +config SPI_TSC2301 + tristate "TSC2301 driver" + depends on SPI_MASTER + help + Say Y here if you have a TSC2301 chip connected to an SPI + bus on your board. + + The TSC2301 is a highly integrated PDA analog interface circuit. + It contains a complete 12-bit A/D resistive touch screen + converter (ADC) including drivers, touch pressure measurement + capability, keypad controller, and 8-bit D/A converter (DAC) output + for LCD contrast control. + + To compile this driver as a module, choose M here: the + module will be called tsc2301. + +config SPI_TSC2301_AUDIO + boolean "TSC2301 audio support" + depends on SPI_TSC2301 && SND + help + Say Y here for if you are using the audio features of TSC2301. + config SPI_SPIDEV tristate "User mode SPI device driver support" depends on EXPERIMENTAL diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 5d0451936d8..2b8af987873 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -35,6 +35,9 @@ obj-$(CONFIG_SPI_SH_SCI) += spi_sh_sci.o # SPI protocol drivers (device/link on bus) obj-$(CONFIG_SPI_SPIDEV) += spidev.o obj-$(CONFIG_SPI_TLE62X0) += tle62x0.o +obj-$(CONFIG_SPI_TSC210X) += tsc210x.o +obj-$(CONFIG_SPI_TSC2301) += tsc2301.o +tsc2301-objs := tsc2301-core.o # ... add above this line ... # SPI slave controller drivers (upstream link) diff --git a/drivers/spi/omap2_mcspi.c b/drivers/spi/omap2_mcspi.c index d6d0c5d241c..f2b4d329e1a 100644 --- a/drivers/spi/omap2_mcspi.c +++ b/drivers/spi/omap2_mcspi.c @@ -59,37 +59,40 @@ /* per-register bitmasks: */ -#define OMAP2_MCSPI_SYSCONFIG_AUTOIDLE (1 << 0) -#define OMAP2_MCSPI_SYSCONFIG_SOFTRESET (1 << 1) +#define OMAP2_MCSPI_SYSCONFIG_SMARTIDLE BIT(4) +#define OMAP2_MCSPI_SYSCONFIG_ENAWAKEUP BIT(2) +#define OMAP2_MCSPI_SYSCONFIG_AUTOIDLE BIT(0) +#define OMAP2_MCSPI_SYSCONFIG_SOFTRESET BIT(1) -#define OMAP2_MCSPI_SYSSTATUS_RESETDONE (1 << 0) +#define OMAP2_MCSPI_SYSSTATUS_RESETDONE BIT(0) -#define OMAP2_MCSPI_MODULCTRL_SINGLE (1 << 0) -#define OMAP2_MCSPI_MODULCTRL_MS (1 << 2) -#define OMAP2_MCSPI_MODULCTRL_STEST (1 << 3) +#define OMAP2_MCSPI_MODULCTRL_SINGLE BIT(0) +#define OMAP2_MCSPI_MODULCTRL_MS BIT(2) +#define OMAP2_MCSPI_MODULCTRL_STEST BIT(3) -#define OMAP2_MCSPI_CHCONF_PHA (1 << 0) -#define OMAP2_MCSPI_CHCONF_POL (1 << 1) +#define OMAP2_MCSPI_CHCONF_PHA BIT(0) +#define OMAP2_MCSPI_CHCONF_POL BIT(1) #define OMAP2_MCSPI_CHCONF_CLKD_MASK (0x0f << 2) -#define OMAP2_MCSPI_CHCONF_EPOL (1 << 6) +#define OMAP2_MCSPI_CHCONF_EPOL BIT(6) #define OMAP2_MCSPI_CHCONF_WL_MASK (0x1f << 7) -#define OMAP2_MCSPI_CHCONF_TRM_RX_ONLY (0x01 << 12) -#define OMAP2_MCSPI_CHCONF_TRM_TX_ONLY (0x02 << 12) +#define OMAP2_MCSPI_CHCONF_TRM_RX_ONLY BIT(12) +#define OMAP2_MCSPI_CHCONF_TRM_TX_ONLY BIT(13) #define OMAP2_MCSPI_CHCONF_TRM_MASK (0x03 << 12) -#define OMAP2_MCSPI_CHCONF_DMAW (1 << 14) -#define OMAP2_MCSPI_CHCONF_DMAR (1 << 15) -#define OMAP2_MCSPI_CHCONF_DPE0 (1 << 16) -#define OMAP2_MCSPI_CHCONF_DPE1 (1 << 17) -#define OMAP2_MCSPI_CHCONF_IS (1 << 18) -#define OMAP2_MCSPI_CHCONF_TURBO (1 << 19) -#define OMAP2_MCSPI_CHCONF_FORCE (1 << 20) +#define OMAP2_MCSPI_CHCONF_DMAW BIT(14) +#define OMAP2_MCSPI_CHCONF_DMAR BIT(15) +#define OMAP2_MCSPI_CHCONF_DPE0 BIT(16) +#define OMAP2_MCSPI_CHCONF_DPE1 BIT(17) +#define OMAP2_MCSPI_CHCONF_IS BIT(18) +#define OMAP2_MCSPI_CHCONF_TURBO BIT(19) +#define OMAP2_MCSPI_CHCONF_FORCE BIT(20) -#define OMAP2_MCSPI_CHSTAT_RXS (1 << 0) -#define OMAP2_MCSPI_CHSTAT_TXS (1 << 1) -#define OMAP2_MCSPI_CHSTAT_EOT (1 << 2) +#define OMAP2_MCSPI_CHSTAT_RXS BIT(0) +#define OMAP2_MCSPI_CHSTAT_TXS BIT(1) +#define OMAP2_MCSPI_CHSTAT_EOT BIT(2) -#define OMAP2_MCSPI_CHCTRL_EN (1 << 0) +#define OMAP2_MCSPI_CHCTRL_EN BIT(0) +#define OMAP2_MCSPI_WAKEUPENABLE_WKEN BIT(0) /* We have 2 DMA channels per CS, one for RX and one for TX */ struct omap2_mcspi_dma { @@ -884,8 +887,12 @@ static int __init omap2_mcspi_reset(struct omap2_mcspi *mcspi) } while (!(tmp & OMAP2_MCSPI_SYSSTATUS_RESETDONE)); mcspi_write_reg(master, OMAP2_MCSPI_SYSCONFIG, - /* (3 << 8) | (2 << 3) | */ - OMAP2_MCSPI_SYSCONFIG_AUTOIDLE); + OMAP2_MCSPI_SYSCONFIG_AUTOIDLE | + OMAP2_MCSPI_SYSCONFIG_ENAWAKEUP | + OMAP2_MCSPI_SYSCONFIG_SMARTIDLE); + + mcspi_write_reg(master, OMAP2_MCSPI_WAKEUPENABLE, + OMAP2_MCSPI_WAKEUPENABLE_WKEN); omap2_mcspi_set_master_mode(master); diff --git a/drivers/spi/tsc210x.c b/drivers/spi/tsc210x.c new file mode 100644 index 00000000000..1d2ac94084c --- /dev/null +++ b/drivers/spi/tsc210x.c @@ -0,0 +1,1262 @@ +/* + * tsc210x.c - TSC2101/2102/... driver core + * + * Copyright (c) 2005-2007 Andrzej Zaborowski + * + * This package 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 package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* NOTE: It should be straightforward to make this driver framework handle + * tsc2100 and tsc2111 chips, and maybe others too. The main differences + * are in the audio codec capabilities, but there are also some differences + * in how the various sensors (including touchscreen) are handled. + */ + +/* Bit field definitions for chip registers */ + +/* Scan X, Y, Z1, Z2, chip controlled, 12-bit, 16 samples, 500 usec */ +#define TSC210X_ADC_TS_CONTROL 0x8bf4 +/* Scan BAT1, BAT2, AUX1, AUX2, 12-bit, 16 samples, 500 usec */ +#define TSC210X_ADC_SCAN_CONTROL 0x2ff4 +/* Scan TEMP1, 12-bit, 16 samples, 500 usec */ +#define TSC210X_ADC_T1_CONTROL 0x2bf4 +/* Scan TEMP2, 12-bit, 16 samples, 500 usec */ +#define TSC210X_ADC_T2_CONTROL 0x33f4 +/* PINT/DAV acts as DAV */ +#define TSC210X_ADC_DAV 0x4000 +/* Internal reference, 100 usec delay, 1.25 V reference */ +#define TSC210X_ADC_INT_REF 0x0016 +/* External reference, 100 usec delay, 1.25 V reference */ +#define TSC210X_ADC_EXT_REF 0x0002 +/* 84 usec precharge time, 32 usec sense time */ +#define TSC210X_CONFIG_TIMES 0x0008 +/* The reset sequence */ +#define TSC210X_RESET 0xbb00 +/* Pen Status bit */ +#define TSC210X_ADC_PSTCM (1 << 15) +/* A/D Status bit */ +#define TSC210X_ADC_ADST (1 << 14) +/* (At least) One of X, Y, Z1, Z2 contains data */ +#define TSC210X_TS_DAV 0x0780 +/* (At least) One of BAT1, BAT2, AUX1, AUX2 contains data */ +#define TSC210X_PS_DAV 0x0078 +/* TEMP1 contains data */ +#define TSC210X_T1_DAV 0x0004 +/* TEMP2 contains data */ +#define TSC210X_T2_DAV 0x0002 +#define TSC2101_DAC_ON 0x0000 +#define TSC2101_DAC_OFF 0xe7fc +#define TSC2102_DAC_ON 0x3ba0 +#define TSC2102_DAC_OFF 0xafa0 +#define TSC210X_FS44K (1 << 13) +#define TSC210X_PLL1_OFF 0x0000 +#define TSC210X_PLL1_44K 0x811c +#define TSC210X_PLL1_48K 0x8120 +#define TSC210X_PLL2_44K (5462 << 2) +#define TSC210X_PLL2_48K (1920 << 2) +#define TSC210X_SLVMS (1 << 11) +#define TSC210X_DEEMPF (1 << 0) +#define TSC2102_BASSBC (1 << 1) +#define TSC210X_KEYCLICK_OFF 0x0000 + +#define CS_CHANGE(val) 0 + +struct tsc210x_spi_req { + struct spi_device *dev; + u16 command; + u16 data; + struct spi_message message; +}; + +struct tsc210x_dev { + enum tsc_type { + tsc2101, + tsc2102, + } kind; + struct tsc210x_config *pdata; + struct clk *bclk_ck; + + struct workqueue_struct *queue; + struct delayed_work ts_worker; /* Poll-wait for PEN UP */ + struct delayed_work sensor_worker; /* Scan the ADC inputs */ + struct mutex queue_lock; + struct completion data_avail; + + tsc210x_touch_t touch_cb; + void *touch_cb_ctx; + + tsc210x_coords_t coords_cb; + void *coords_cb_ctx; + + tsc210x_ports_t ports_cb; + void *ports_cb_ctx; + + tsc210x_temp_t temp1_cb; + void *temp2_cb_ctx; + + tsc210x_temp_t temp2_cb; + void *temp1_cb_ctx; + + struct spi_device *spi; + struct spi_transfer *transfers; + struct tsc210x_spi_req req_adc; + struct tsc210x_spi_req req_status; + struct tsc210x_spi_req req_mode; + struct tsc210x_spi_req req_stop; + + int pendown; + int flushing; /* Queue flush in progress */ + u16 status; + u16 adc_data[4]; + int bat[2], aux[2], temp[2]; +}; + +static struct { + unsigned int ts_msecs; /* Interval for .ts_timer */ + unsigned int mode_msecs; /* Interval for .mode_timer */ +} settings; + +module_param_named(touch_check_msecs, settings.ts_msecs, uint, 0); +MODULE_PARM_DESC(touch_check_msecs, "Pen-up polling interval in msecs"); + +module_param_named(sensor_scan_msecs, settings.mode_msecs, uint, 0); +MODULE_PARM_DESC(sensor_scan_msecs, "Temperature & battery scan interval"); + +int tsc210x_write_sync(struct tsc210x_dev *dev, + int page, u8 address, u16 data) +{ + static struct tsc210x_spi_req req; + static struct spi_transfer transfer[2]; + int ret; + + spi_message_init(&req.message); + + /* Address */ + req.command = (page << 11) | (address << 5); + transfer[0].tx_buf = &req.command; + transfer[0].rx_buf = NULL; + transfer[0].len = 2; + spi_message_add_tail(&transfer[0], &req.message); + + /* Data */ + transfer[1].tx_buf = &data; + transfer[1].rx_buf = NULL; + transfer[1].len = 2; + transfer[1].cs_change = CS_CHANGE(1); + spi_message_add_tail(&transfer[1], &req.message); + + ret = spi_sync(dev->spi, &req.message); + if (!ret && req.message.status) + ret = req.message.status; + if (ret) + dev_dbg(&dev->spi->dev, "write_sync --> %d\n", ret); + + return ret; +} +EXPORT_SYMBOL(tsc210x_write_sync); + +int tsc210x_reads_sync(struct tsc210x_dev *dev, + int page, u8 startaddress, u16 *data, int numregs) +{ + static struct tsc210x_spi_req req; + static struct spi_transfer transfer[6]; + int ret, i, j; + + if (numregs + 1 > ARRAY_SIZE(transfer)) + return -EINVAL; + + spi_message_init(&req.message); + i = 0; + j = 0; + + /* Address */ + req.command = 0x8000 | (page << 11) | (startaddress << 5); + transfer[i].tx_buf = &req.command; + transfer[i].rx_buf = NULL; + transfer[i].len = 2; + spi_message_add_tail(&transfer[i ++], &req.message); + + /* Data */ + while (j < numregs) { + transfer[i].tx_buf = NULL; + transfer[i].rx_buf = &data[j ++]; + transfer[i].len = 2; + transfer[i].cs_change = CS_CHANGE(j == numregs); + spi_message_add_tail(&transfer[i ++], &req.message); + } + + ret = spi_sync(dev->spi, &req.message); + if (!ret && req.message.status) + ret = req.message.status; + if (ret) + dev_dbg(&dev->spi->dev, "reads_sync --> %d\n", ret); + + return ret; +} +EXPORT_SYMBOL(tsc210x_reads_sync); + +int tsc210x_read_sync(struct tsc210x_dev *dev, int page, u8 address) +{ + u16 ret; + int status; + + status = tsc210x_reads_sync(dev, page, address, &ret, 1); + return status ? : ret; +} +EXPORT_SYMBOL(tsc210x_read_sync); + + +static void tsc210x_submit_async(struct tsc210x_spi_req *spi) +{ + int ret; + + ret = spi_async(spi->dev, &spi->message); + if (ret) + dev_dbg(&spi->dev->dev, "%s: error %i in SPI request\n", + __FUNCTION__, ret); +} + +static void tsc210x_request_alloc(struct tsc210x_dev *dev, + struct tsc210x_spi_req *spi, int direction, + int page, u8 startaddress, int numregs, u16 *data, + void (*complete)(struct tsc210x_dev *context), + struct spi_transfer **transfer) +{ + spi->dev = dev->spi; + + if (direction == 1) /* Write */ + numregs = 2; + else /* Read */ + numregs += 1; + + spi_message_init(&spi->message); + spi->message.complete = (void (*)(void *)) complete; + spi->message.context = dev; + + /* Address */ + spi->command = (page << 11) | (startaddress << 5); + if (direction != 1) + spi->command |= 1 << 15; + + (*transfer)->tx_buf = &spi->command; + (*transfer)->rx_buf = NULL; + (*transfer)->len = 2; + spi_message_add_tail((*transfer) ++, &spi->message); + + /* Data */ + while (-- numregs) { + if (direction == 1) + (*transfer)->tx_buf = &spi->data; + else + (*transfer)->rx_buf = data++; + (*transfer)->len = 2; + (*transfer)->cs_change = CS_CHANGE(numregs != 1); + spi_message_add_tail((*transfer) ++, &spi->message); + } +} + +#define tsc210x_cb_register_func(cb, cb_t) \ +int tsc210x_ ## cb(struct device *dev, cb_t handler, void *context) \ +{ \ + struct tsc210x_dev *tsc = dev_get_drvdata(dev); \ + \ + /* Lock the module */ \ + if (handler && !tsc->cb) \ + if (!try_module_get(THIS_MODULE)) { \ + dev_err(dev, "Failed to get TSC module\n"); \ + } \ + if (!handler && tsc->cb) \ + module_put(THIS_MODULE); \ + \ + tsc->cb = handler; \ + tsc->cb ## _ctx = context; \ + return 0; \ +} \ +EXPORT_SYMBOL(tsc210x_ ## cb); + +tsc210x_cb_register_func(touch_cb, tsc210x_touch_t) +tsc210x_cb_register_func(coords_cb, tsc210x_coords_t) +tsc210x_cb_register_func(ports_cb, tsc210x_ports_t) +tsc210x_cb_register_func(temp1_cb, tsc210x_temp_t) +tsc210x_cb_register_func(temp2_cb, tsc210x_temp_t) + +#ifdef DEBUG +static void tsc210x_print_dav(struct tsc210x_dev *dev) +{ + int status = tsc210x_read_sync(dev, TSC210X_TS_STATUS_CTRL); + + if (status < 0) { + dev_dbg(&dev->spi->dev, "status %d\n", status); + return; + } + + if (!(status & 0x0fff)) + return; + + dev_dbg(&dev->spi->dev, "data in %s%s%s%s%s%s%s%s%s%s%s\n", + (status & 0x0400) ? " X" : "", + (status & 0x0200) ? " Y" : "", + (status & 0x0100) ? " Z1" : "", + (status & 0x0080) ? " Z2" : "", + (status & 0x0040) ? " BAT1" : "", + (status & 0x0020) ? " BAT2" : "", + (status & 0x0010) ? " AUX1" : "", + (status & 0x0008) ? " AUX2" : "", + (status & 0x0004) ? " TEMP1" : "", + (status & 0x0002) ? " TEMP2" : "", + (status & 0x0001) ? " KP" : ""); +} +#endif + +static void tsc210x_complete_dummy(struct tsc210x_dev *dev) +{ +} + +static inline void tsc210x_touchscreen_mode(struct tsc210x_dev *dev) +{ + /* Scan X, Y, Z1, Z2, chip controlled, 12-bit, 16 samples, 500 usec */ + dev->req_mode.data = TSC210X_ADC_TS_CONTROL; + tsc210x_submit_async(&dev->req_mode); +} + +static inline void tsc210x_portscan_mode(struct tsc210x_dev *dev) +{ + /* Scan BAT1, BAT2, AUX1, AUX2, 12-bit, 16 samples, 500 usec */ + dev->req_mode.data = TSC210X_ADC_SCAN_CONTROL; + tsc210x_submit_async(&dev->req_mode); +} + +static inline void tsc210x_temp1_mode(struct tsc210x_dev *dev) +{ + /* Scan TEMP1, 12-bit, 16 samples, 500 usec */ + dev->req_mode.data = TSC210X_ADC_T1_CONTROL; + tsc210x_submit_async(&dev->req_mode); +} + +static inline void tsc210x_temp2_mode(struct tsc210x_dev *dev) +{ + /* Scan TEMP2, 12-bit, 16 samples, 500 usec */ + dev->req_mode.data = TSC210X_ADC_T2_CONTROL; + tsc210x_submit_async(&dev->req_mode); +} + +/* Abort current conversion if any */ +static void tsc210x_new_mode(struct tsc210x_dev *dev) +{ + dev->req_stop.data = TSC210X_ADC_ADST; + tsc210x_submit_async(&dev->req_stop); +} + +static void tsc210x_queue_scan(struct tsc210x_dev *dev) +{ + if (dev->pdata->monitor) + if (!queue_delayed_work(dev->queue, + &dev->sensor_worker, + msecs_to_jiffies(settings.mode_msecs))) + dev_err(&dev->spi->dev, + "%s: can't queue measurements\n", + __FUNCTION__); +} + +static void tsc210x_queue_penup(struct tsc210x_dev *dev) +{ + if (!queue_delayed_work(dev->queue, + &dev->ts_worker, + msecs_to_jiffies(settings.ts_msecs))) + dev_err(&dev->spi->dev, + "%s: can't queue pen-up poll\n", + __FUNCTION__); +} + +static void tsc210x_status_report(struct tsc210x_dev *dev) +{ + /* + * Read all converted data from corresponding registers + * so that the ADC can move on to a new conversion. + */ + if (dev->status & TSC210X_TS_DAV) { + if (!dev->pendown && !dev->flushing) { + dev->pendown = 1; + if (dev->touch_cb) + dev->touch_cb(dev->touch_cb_ctx, 1); + + tsc210x_queue_penup(dev); + } + + tsc210x_submit_async(&dev->req_adc); + } + + if (dev->status & (TSC210X_PS_DAV | TSC210X_T1_DAV | TSC210X_T2_DAV)) + complete(&dev->data_avail); +} + +static void tsc210x_data_report(struct tsc210x_dev *dev) +{ + u16 adc_data[4]; + + if (dev->status & TSC210X_PS_DAV) { + tsc210x_reads_sync(dev, TSC210X_TS_BAT1, adc_data, 4); + /* NOTE: reads_sync() could fail */ + + dev->bat[0] = adc_data[0]; + dev->bat[1] = adc_data[1]; + dev->aux[0] = adc_data[2]; + dev->aux[1] = adc_data[3]; + if (dev->ports_cb) + dev->ports_cb(dev->ports_cb_ctx, dev->bat, dev->aux); + } + + if (dev->status & TSC210X_T1_DAV) { + dev->temp[0] = tsc210x_read_sync(dev, TSC210X_TS_TEMP1); + + if (dev->temp[0] >= 0 && dev->temp1_cb) + dev->temp1_cb(dev->temp1_cb_ctx, dev->temp[0]); + } + + if (dev->status & TSC210X_T2_DAV) { + dev->temp[1] = tsc210x_read_sync(dev, TSC210X_TS_TEMP2); + + if (dev->temp[1] >= 0 && dev->temp2_cb) + dev->temp2_cb(dev->temp2_cb_ctx, dev->temp[1]); + } +} + +static void tsc210x_coords_report(struct tsc210x_dev *dev) +{ + if (dev->coords_cb) + dev->coords_cb(dev->coords_cb_ctx, + dev->adc_data[0], dev->adc_data[1], + dev->adc_data[2], dev->adc_data[3]); +} + +/* + * There are at least three ways to check for pen-up: + * - the PINT/DAV pin state, + * - reading PSTCM bit in ADC Control register (D15, offset 0x00), + * - reading ADST bit in ADC Control register (D14, offset 0x00), + * ADC idle would indicate no screen touch. + * Unfortunately none of them seems to be 100% accurate and you will + * find they are totally inconsistent, i.e. you get to see any arbitrary + * combination of values in these three bits. So we will busy-wait + * for a moment when the latter two indicate a pen-up, using a queue, + * before we report a pen-up. + */ +static void tsc210x_pressure(struct work_struct *work) +{ + struct tsc210x_dev *dev = + container_of(work, struct tsc210x_dev, ts_worker.work); + int adc_status; + + WARN_ON(!dev->pendown); + + adc_status = tsc210x_read_sync(dev, TSC210X_TS_ADC_CTRL); + if (adc_status < 0) { + dev_dbg(&dev->spi->dev, "pressure, err %d\n", adc_status); + return; + } + + if ((adc_status & TSC210X_ADC_PSTCM) != 0 + || !(adc_status & TSC210X_ADC_ADST)) + tsc210x_queue_penup(dev); + else { + dev->pendown = 0; + if (dev->touch_cb) + dev->touch_cb(dev->touch_cb_ctx, 0); + } +} + +static void tsc210x_wait_data(struct tsc210x_dev *dev) +{ + wait_for_completion(&dev->data_avail); + + tsc210x_data_report(dev); +} + +static void tsc210x_input_scan(struct work_struct *work) +{ + struct tsc210x_dev *dev = (struct tsc210x_dev *) + container_of(work, struct tsc210x_dev, sensor_worker.work); + + tsc210x_new_mode(dev); + + if (dev->pdata->monitor & + (TSC_BAT1 | TSC_BAT2 | TSC_AUX1 | TSC_AUX2)) { + tsc210x_portscan_mode(dev); + tsc210x_wait_data(dev); + } + + if (dev->pdata->monitor & TSC_TEMP) { + tsc210x_temp1_mode(dev); + tsc210x_wait_data(dev); + + tsc210x_temp2_mode(dev); + tsc210x_wait_data(dev); + } + + tsc210x_touchscreen_mode(dev); + + mutex_lock(&dev->queue_lock); + if (!dev->flushing) + tsc210x_queue_scan(dev); + mutex_unlock(&dev->queue_lock); +} + +/* ADC has finished a new conversion for us. */ +static irqreturn_t tsc210x_handler(int irq, void *dev_id) +{ + struct tsc210x_dev *dev = (struct tsc210x_dev *) dev_id; + + /* See what data became available. */ + tsc210x_submit_async(&dev->req_status); + + return IRQ_HANDLED; +} + +#if defined(CONFIG_SOUND) || defined(CONFIG_SOUND_MODULE) + +/* + * FIXME the audio support shouldn't be included in upstream patches + * until it's ready. They might be better as utility functions linked + * with a chip-specific tsc21xx audio module ... e.g. chips with input + * channels need more, as will ones with multiple output channels and + * so on. Each of these functions should probably return a fault code, + * and will need to be exported so the sound drier can be modular. + */ + +/* + * Volume level values should be in the range [0, 127]. + * Higher values mean lower volume. + */ +void tsc210x_set_dac_volume(struct device *dev, u8 left_ch, u8 right_ch) +{ + struct tsc210x_dev *tsc = dev_get_drvdata(dev); + int val; + + if (tsc->kind == tsc2102) { + /* All 0's or all 1's */ + if (left_ch == 0x00 || left_ch == 0x7f) + left_ch ^= 0x7f; + if (right_ch == 0x00 || right_ch == 0x7f) + right_ch ^= 0x7f; + } + + val = tsc210x_read_sync(tsc, TSC210X_DAC_GAIN_CTRL); + if (val < 0) { + dev_dbg(dev, "%s, err %d\n", __FUNCTION__, val); + return; + } + + val &= 0x8080; /* Preserve mute-bits */ + val |= (left_ch << 8) | right_ch; + + tsc210x_write_sync(tsc, TSC210X_DAC_GAIN_CTRL, val); + /* NOTE: write_sync() could fail */ +} + +void tsc210x_set_dac_mute(struct device *dev, int left_ch, int right_ch) +{ + struct tsc210x_dev *tsc = dev_get_drvdata(dev); + int val; + + val = tsc210x_read_sync(tsc, TSC210X_DAC_GAIN_CTRL); + if (val < 0) { + dev_dbg(dev, "%s, err %d\n", __FUNCTION__, val); + return; + } + + val &= 0x7f7f; /* Preserve volume settings */ + val |= (left_ch << 15) | (right_ch << 7); + + tsc210x_write_sync(tsc, TSC210X_DAC_GAIN_CTRL, val); + /* NOTE: write_sync() could fail */ +} + +void tsc210x_get_dac_mute(struct device *dev, int *left_ch, int *right_ch) +{ + struct tsc210x_dev *tsc = dev_get_drvdata(dev); + int val; + + val = tsc210x_read_sync(tsc, TSC210X_DAC_GAIN_CTRL); + if (val < 0) { + dev_dbg(dev, "%s, err %d\n", __FUNCTION__, val); + return; + } + + *left_ch = !!(val & (1 << 15)); + *right_ch = !!(val & (1 << 7)); +} + +void tsc210x_set_deemphasis(struct device *dev, int enable) +{ + struct tsc210x_dev *tsc = dev_get_drvdata(dev); + int val; + + val = tsc210x_read_sync(tsc, TSC210X_POWER_CTRL); + if (val < 0) { + dev_dbg(dev, "%s, err %d\n", __FUNCTION__, val); + return; + } + + if (enable) + val &= ~TSC210X_DEEMPF; + else + val |= TSC210X_DEEMPF; + + tsc210x_write_sync(tsc, TSC210X_POWER_CTRL, val); + /* NOTE: write_sync() could fail */ +} + +void tsc2102_set_bassboost(struct device *dev, int enable) +{ + struct tsc210x_dev *tsc = dev_get_drvdata(dev); + int val; + + val = tsc210x_read_sync(tsc, TSC210X_POWER_CTRL); + if (val < 0) { + dev_dbg(dev, "%s, err %d\n", __FUNCTION__, val); + return; + } + + if (enable) + val &= ~TSC2102_BASSBC; + else + val |= TSC2102_BASSBC; + + tsc210x_write_sync(tsc, TSC210X_POWER_CTRL, val); + /* NOTE: write_sync() could fail */ +} + +/* {rate, dsor, fsref} */ +static const struct tsc210x_rate_info_s tsc2101_rates[] = { + /* Fsref / 6.0 */ + {7350, 7, 1}, + {8000, 7, 0}, + /* Fsref / 5.5 */ + {8018, 6, 1}, + {8727, 6, 0}, + /* Fsref / 5.0 */ + {8820, 5, 1}, + {9600, 5, 0}, + /* Fsref / 4.0 */ + {11025, 4, 1}, + {12000, 4, 0}, + /* Fsref / 3.0 */ + {14700, 3, 1}, + {16000, 3, 0}, + /* Fsref / 2.0 */ + {22050, 2, 1}, + {24000, 2, 0}, + /* Fsref / 1.5 */ + {29400, 1, 1}, + {32000, 1, 0}, + /* Fsref */ + {44100, 0, 1}, + {48000, 0, 0}, + + {0, 0, 0}, +}; + +/* {rate, dsor, fsref} */ +static const struct tsc210x_rate_info_s tsc2102_rates[] = { + /* Fsref / 6.0 */ + {7350, 63, 1}, + {8000, 63, 0}, + /* Fsref / 6.0 */ + {7350, 54, 1}, + {8000, 54, 0}, + /* Fsref / 5.0 */ + {8820, 45, 1}, + {9600, 45, 0}, + /* Fsref / 4.0 */ + {11025, 36, 1}, + {12000, 36, 0}, + /* Fsref / 3.0 */ + {14700, 27, 1}, + {16000, 27, 0}, + /* Fsref / 2.0 */ + {22050, 18, 1}, + {24000, 18, 0}, + /* Fsref / 1.5 */ + {29400, 9, 1}, + {32000, 9, 0}, + /* Fsref */ + {44100, 0, 1}, + {48000, 0, 0}, + + {0, 0, 0}, +}; + +int tsc210x_set_rate(struct device *dev, int rate) +{ + struct tsc210x_dev *tsc = dev_get_drvdata(dev); + int i; + int val; + const struct tsc210x_rate_info_s *rates; + + if (tsc->kind == tsc2101) + rates = tsc2101_rates; + else + rates = tsc2102_rates; + + for (i = 0; rates[i].sample_rate; i ++) + if (rates[i].sample_rate == rate) + break; + if (rates[i].sample_rate == 0) { + dev_err(dev, "Unknown sampling rate %i.0 Hz\n", rate); + return -EINVAL; + } + + if (tsc->kind == tsc2101) { + val = tsc210x_read_sync(tsc, TSC210X_AUDIO1_CTRL); + if (val < 0) { + dev_dbg(dev, "%s, err %d\n", __FUNCTION__, val); + return val; + } + val &= ~((7 << 3) | (7 << 0)); + val |= rates[i].divisor << 3; + val |= rates[i].divisor << 0; + } else + val = rates[i].divisor; + + tsc210x_write_sync(tsc, TSC210X_AUDIO1_CTRL, val); + /* NOTE: write_sync() could fail */ + + val = tsc210x_read_sync(tsc, TSC210X_AUDIO3_CTRL); + if (val < 0) { + dev_dbg(dev, "%s, err %d\n", __FUNCTION__, val); + return val; + } + + if (tsc2102_rates[i].fs_44k) { + tsc210x_write_sync(tsc, + TSC210X_AUDIO3_CTRL, val | TSC210X_FS44K); + /* Enable Phase-locked-loop, set up clock dividers */ + tsc210x_write_sync(tsc, TSC210X_PLL1_CTRL, TSC210X_PLL1_44K); + tsc210x_write_sync(tsc, TSC210X_PLL2_CTRL, TSC210X_PLL2_44K); + } else { + tsc210x_write_sync(tsc, + TSC210X_AUDIO3_CTRL, val & ~TSC210X_FS44K); + /* Enable Phase-locked-loop, set up clock dividers */ + tsc210x_write_sync(tsc, TSC210X_PLL1_CTRL, TSC210X_PLL1_48K); + tsc210x_write_sync(tsc, TSC210X_PLL2_CTRL, TSC210X_PLL2_48K); + } + + return 0; +} + +/* + * Perform basic set-up with default values and power the DAC/ADC on. + */ +void tsc210x_dac_power(struct device *dev, int on) +{ + struct tsc210x_dev *tsc = dev_get_drvdata(dev); + + /* NOTE: write_sync() could fail */ + if (on) { + /* 16-bit words, DSP mode, sample at Fsref */ + tsc210x_write_sync(tsc, + TSC210X_AUDIO1_CTRL, 0x0100); + /* Keyclicks off, soft-stepping at normal rate */ + tsc210x_write_sync(tsc, + TSC210X_AUDIO2_CTRL, TSC210X_KEYCLICK_OFF); + /* 44.1 kHz Fsref, continuous transfer mode, master DAC */ + tsc210x_write_sync(tsc, + TSC210X_AUDIO3_CTRL, 0x2000); + /* Soft-stepping enabled, 1 dB MIX AGC hysteresis */ + tsc210x_write_sync(tsc, + TSC210X_AUDIO4_CTRL, 0x0000); + + /* PLL generates 44.1 kHz */ + tsc210x_write_sync(tsc, + TSC210X_PLL1_CTRL, TSC210X_PLL1_44K); + tsc210x_write_sync(tsc, + TSC210X_PLL2_CTRL, TSC210X_PLL2_44K); + + /* Codec & DAC power up, virtual ground disabled */ + tsc210x_write_sync(tsc, + TSC210X_POWER_CTRL, (tsc->kind == tsc2101) ? + TSC2101_DAC_ON : TSC2102_DAC_ON); + } else { + /* All off */ + tsc210x_write_sync(tsc, + TSC210X_AUDIO2_CTRL, TSC210X_KEYCLICK_OFF); + tsc210x_write_sync(tsc, + TSC210X_PLL1_CTRL, TSC210X_PLL1_OFF); +#if 0 + tsc210x_write_sync(tsc, + TSC210X_POWER_CTRL, (tsc->kind == tsc2101) ? + TSC2102_DAC_OFF : TSC2102_DAC_OFF); +#endif + } +} + +void tsc210x_set_i2s_master(struct device *dev, int state) +{ + struct tsc210x_dev *tsc = dev_get_drvdata(dev); + int val; + + val = tsc210x_read_sync(tsc, TSC210X_AUDIO3_CTRL); + if (val < 0) { + dev_dbg(dev, "%s, err %d\n", __FUNCTION__, val); + return; + } + + /* NOTE: write_sync() could fail */ + if (state) + tsc210x_write_sync(tsc, TSC210X_AUDIO3_CTRL, + val | TSC210X_SLVMS); + else + tsc210x_write_sync(tsc, TSC210X_AUDIO3_CTRL, + val & ~TSC210X_SLVMS); +} +#endif /* CONFIG_SOUND */ + +static int tsc210x_configure(struct tsc210x_dev *dev) +{ + /* NOTE: write_sync() could fail */ + + /* Reset the chip */ + tsc210x_write_sync(dev, TSC210X_TS_RESET_CTRL, TSC210X_RESET); + + /* Reference mode */ + if (dev->pdata->use_internal) + tsc210x_write_sync(dev, + TSC210X_TS_REF_CTRL, TSC210X_ADC_INT_REF); + else + tsc210x_write_sync(dev, + TSC210X_TS_REF_CTRL, TSC210X_ADC_EXT_REF); + + /* Precharge and sense delays, pen touch detection on */ + tsc210x_write_sync(dev, TSC210X_TS_CONFIG_CTRL, TSC210X_CONFIG_TIMES); + + /* PINT/DAV acts as DAV */ + tsc210x_write_sync(dev, TSC210X_TS_STATUS_CTRL, TSC210X_ADC_DAV); + + tsc210x_queue_scan(dev); + return 0; +} + +void tsc210x_keyclick(struct tsc210x_dev *dev, + int amplitude, int freq, int length) +{ + int val; + + val = tsc210x_read_sync(dev, TSC210X_AUDIO2_CTRL); + if (val < 0) { + dev_dbg(&dev->spi->dev, "%s, err %d\n", + __FUNCTION__, val); + return; + } + val &= 0x800f; + + /* Set amplitude */ + switch (amplitude) { + case 1: + val |= 4 << 12; + break; + case 2: + val |= 7 << 12; + break; + default: + break; + } + + /* Frequency */ + val |= (freq & 0x7) << 8; + + /* Round to nearest supported length */ + if (dev->kind == tsc2101) + val = (min(length - 1, 31) >> 1) << 4; + else { + if (length > 20) + val |= 4 << 4; + else if (length > 6) + val |= 3 << 4; + else if (length > 4) + val |= 2 << 4; + else if (length > 2) + val |= 1 << 4; + } + + /* Enable keyclick */ + val |= 0x8000; + + /* NOTE: write_sync() could fail */ + tsc210x_write_sync(dev, TSC210X_AUDIO2_CTRL, val); +} +EXPORT_SYMBOL(tsc210x_keyclick); + +#ifdef CONFIG_PM +/* + * Suspend the chip. + */ +static int +tsc210x_suspend(struct spi_device *spi, pm_message_t state) +{ + struct tsc210x_dev *dev = dev_get_drvdata(&spi->dev); + + if (!dev) + return -ENODEV; + + /* Stop the inputs scan loop */ + mutex_lock(&dev->queue_lock); + dev->flushing = 1; + cancel_delayed_work(&dev->sensor_worker); + mutex_unlock(&dev->queue_lock); + flush_workqueue(dev->queue); + + /* Wait until pen-up happens */ + while (dev->pendown) + flush_workqueue(dev->queue); + + /* Abort current conversion and power down the ADC */ + tsc210x_write_sync(dev, TSC210X_TS_ADC_CTRL, TSC210X_ADC_ADST); + /* NOTE: write_sync() could fail */ + + return 0; +} + +/* + * Resume chip operation. + */ +static int tsc210x_resume(struct spi_device *spi) +{ + struct tsc210x_dev *dev = dev_get_drvdata(&spi->dev); + int err; + + if (!dev) + return 0; + + mutex_lock(&dev->queue_lock); + err = tsc210x_configure(dev); + + dev->flushing = 0; + mutex_unlock(&dev->queue_lock); + + return err; +} +#else +#define tsc210x_suspend NULL +#define tsc210x_resume NULL +#endif + +/* REVISIT don't make these static */ +static struct platform_device tsc210x_ts_device = { + .name = "tsc210x-ts", + .id = -1, +}; + +static struct platform_device tsc210x_hwmon_device = { + .name = "tsc210x-hwmon", + .id = -1, +}; + +static struct platform_device tsc210x_alsa_device = { + .name = "tsc210x-alsa", + .id = -1, +}; + +static int tsc210x_probe(struct spi_device *spi, enum tsc_type type) +{ + struct tsc210x_config *pdata = spi->dev.platform_data; + struct spi_transfer *spi_buffer; + struct tsc210x_dev *dev; + int reg; + int err = 0; + + if (!pdata) { + dev_dbg(&spi->dev, "Platform data not supplied\n"); + return -ENOENT; + } + + if (!spi->irq) { + dev_dbg(&spi->dev, "Invalid irq value\n"); + return -EINVAL; + } + + dev = (struct tsc210x_dev *) + kzalloc(sizeof(struct tsc210x_dev), GFP_KERNEL); + if (!dev) { + dev_dbg(&spi->dev, "No memory\n"); + return -ENOMEM; + } + + dev->pdata = pdata; + dev->pendown = 0; + dev->spi = spi; + dev->kind = type; + dev->queue = create_singlethread_workqueue(spi->dev.driver->name); + if (!dev->queue) { + dev_dbg(&spi->dev, "Can't make a workqueue\n"); + err = -ENOMEM; + goto err_queue; + } + + mutex_init(&dev->queue_lock); + init_completion(&dev->data_avail); + + /* Allocate enough struct spi_transfer's for all requests */ + spi_buffer = kzalloc(sizeof(struct spi_transfer) * 16, GFP_KERNEL); + if (!spi_buffer) { + dev_dbg(&spi->dev, "No memory for SPI buffers\n"); + err = -ENOMEM; + goto err_buffers; + } + + dev->transfers = spi_buffer; + tsc210x_request_alloc(dev, &dev->req_adc, 0, + TSC210X_TS_X, 4, dev->adc_data, + tsc210x_coords_report, &spi_buffer); + tsc210x_request_alloc(dev, &dev->req_status, 0, + TSC210X_TS_STATUS_CTRL, 1, &dev->status, + tsc210x_status_report, &spi_buffer); + tsc210x_request_alloc(dev, &dev->req_mode, 1, + TSC210X_TS_ADC_CTRL, 1, NULL, + tsc210x_complete_dummy, &spi_buffer); + tsc210x_request_alloc(dev, &dev->req_stop, 1, + TSC210X_TS_ADC_CTRL, 1, NULL, + tsc210x_complete_dummy, &spi_buffer); + + if (pdata->bclk) { + /* Get the BCLK */ + dev->bclk_ck = clk_get(&spi->dev, pdata->bclk); + if (IS_ERR(dev->bclk_ck)) { + err = PTR_ERR(dev->bclk_ck); + dev_dbg(&spi->dev, "Unable to get '%s': %i\n", + pdata->bclk, err); + goto err_clk; + } + + clk_enable(dev->bclk_ck); + } + + INIT_DELAYED_WORK(&dev->ts_worker, tsc210x_pressure); + INIT_DELAYED_WORK(&dev->sensor_worker, tsc210x_input_scan); + + /* Setup the communication bus */ + dev_set_drvdata(&spi->dev, dev); + spi->mode = SPI_MODE_1; + spi->bits_per_word = 16; + err = spi_setup(spi); + if (err) + goto err_spi; + + /* Now try to detect the chip, make first contact. These chips + * don't self-identify, but we can expect that the status register + * reports the ADC is idle and use that as a sanity check. (It'd + * be even better if we did a soft reset first...) + */ + reg = tsc210x_read_sync(dev, TSC210X_TS_ADC_CTRL); + if (reg < 0) { + err = reg; + dev_dbg(&dev->spi->dev, "adc_ctrl, err %d\n", err); + goto err_spi; + } + if (!(reg & (1 << 14))) { + err = -EIO; + dev_dbg(&dev->spi->dev, "adc_ctrl, busy? - %04x\n", reg); + goto err_spi; + } + + reg = tsc210x_read_sync(dev, TSC210X_AUDIO3_CTRL); + if (reg < 0) { + err = reg; + dev_dbg(&dev->spi->dev, "revision, err %d\n", err); + goto err_spi; + } + if (reg == 0xffff) { + err = -ENODEV; + dev_dbg(&dev->spi->dev, "no device, err %d\n", err); + goto err_spi; + } + dev_info(&spi->dev, "rev %d, irq %d\n", reg & 0x0007, spi->irq); + + err = tsc210x_configure(dev); + if (err) + goto err_spi; + + /* We want no interrupts before configuration succeeds. */ + mutex_lock(&dev->queue_lock); + dev->flushing = 1; + + if (request_irq(spi->irq, tsc210x_handler, IRQF_SAMPLE_RANDOM | + IRQF_TRIGGER_FALLING, spi->dev.driver->name, + dev)) { + dev_dbg(&spi->dev, "Could not allocate touchscreen IRQ!\n"); + err = -EINVAL; + goto err_irq; + } + + /* Register subdevices controlled by the TSC 2101/2102 */ + tsc210x_ts_device.dev.platform_data = dev; + tsc210x_ts_device.dev.parent = &spi->dev; + err = platform_device_register(&tsc210x_ts_device); + if (err) + goto err_irq; + + tsc210x_hwmon_device.dev.platform_data = pdata; + tsc210x_hwmon_device.dev.parent = &spi->dev; + err = platform_device_register(&tsc210x_hwmon_device); + if (err) + goto err_hwmon; + + tsc210x_alsa_device.dev.platform_data = pdata->alsa_config; + tsc210x_alsa_device.dev.parent = &spi->dev; + err = platform_device_register(&tsc210x_alsa_device); + if (err) + goto err_alsa; + + dev->flushing = 0; + mutex_unlock(&dev->queue_lock); + return 0; + +err_alsa: + platform_device_unregister(&tsc210x_hwmon_device); +err_hwmon: + platform_device_unregister(&tsc210x_ts_device); +err_irq: + mutex_unlock(&dev->queue_lock); +err_spi: + dev_set_drvdata(&spi->dev, NULL); + clk_disable(dev->bclk_ck); + clk_put(dev->bclk_ck); +err_clk: + kfree(dev->transfers); +err_buffers: + destroy_workqueue(dev->queue); +err_queue: + kfree(dev); + return err; +} + +static int tsc2101_probe(struct spi_device *spi) +{ + return tsc210x_probe(spi, tsc2101); +} + +static int tsc2102_probe(struct spi_device *spi) +{ + return tsc210x_probe(spi, tsc2102); +} + +static int tsc210x_remove(struct spi_device *spi) +{ + struct tsc210x_dev *dev = dev_get_drvdata(&spi->dev); + + /* Stop the inputs scan loop */ + mutex_lock(&dev->queue_lock); + dev->flushing = 1; + cancel_delayed_work(&dev->sensor_worker); + mutex_unlock(&dev->queue_lock); + flush_workqueue(dev->queue); + + /* Wait for pen-up */ + while (dev->pendown) + flush_workqueue(dev->queue); + + /* Abort current conversion and power down the ADC */ + tsc210x_write_sync(dev, TSC210X_TS_ADC_CTRL, TSC210X_ADC_ADST); + /* NOTE: write_sync() could fail */ + + destroy_workqueue(dev->queue); + + platform_device_unregister(&tsc210x_ts_device); + platform_device_unregister(&tsc210x_hwmon_device); + platform_device_unregister(&tsc210x_alsa_device); + + dev_set_drvdata(&spi->dev, NULL); + + /* Release the BCLK */ + clk_disable(dev->bclk_ck); + clk_put(dev->bclk_ck); + + kfree(dev->transfers); + kfree(dev); + + return 0; +} + +static struct spi_driver tsc2101_driver = { + .probe = tsc2101_probe, + .remove = tsc210x_remove, + .suspend = tsc210x_suspend, + .resume = tsc210x_resume, + .driver = { + .name = "tsc2101", + .owner = THIS_MODULE, + .bus = &spi_bus_type, + }, +}; + +static struct spi_driver tsc2102_driver = { + .probe = tsc2102_probe, + .remove = tsc210x_remove, + .suspend = tsc210x_suspend, + .resume = tsc210x_resume, + .driver = { + .name = "tsc2102", + .owner = THIS_MODULE, + .bus = &spi_bus_type, + }, +}; + +static char __initdata banner[] = KERN_INFO "TI TSC210x driver initializing\n"; + +static int __init tsc210x_init(void) +{ + int err; + printk(banner); + + settings.ts_msecs = 20; + settings.mode_msecs = 1000; + + err = spi_register_driver(&tsc2101_driver); + if (err != 0) + return err; + + err = spi_register_driver(&tsc2102_driver); + if (err != 0) + spi_unregister_driver(&tsc2101_driver); + + return err; +} +module_init(tsc210x_init); + +static void __exit tsc210x_exit(void) +{ + spi_unregister_driver(&tsc2101_driver); + spi_unregister_driver(&tsc2102_driver); +} +module_exit(tsc210x_exit); + +MODULE_AUTHOR("Andrzej Zaborowski"); +MODULE_DESCRIPTION("Interface driver for TI TSC210x chips."); +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/tsc2301-core.c b/drivers/spi/tsc2301-core.c new file mode 100644 index 00000000000..d64a274e246 --- /dev/null +++ b/drivers/spi/tsc2301-core.c @@ -0,0 +1,287 @@ +/* + * TSC2301 driver + * + * Copyright (C) 2005, 2006 Nokia Corporation + * + * 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 program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include + +u16 tsc2301_read_reg(struct tsc2301 *tsc, int reg) +{ + struct spi_transfer t[2]; + struct spi_message m; + u16 data = 0, cmd; + + cmd = reg; + cmd |= 0x8000; + + memset(t, 0, sizeof(t)); + spi_message_init(&m); + m.spi = tsc->spi; + + t[0].tx_buf = &cmd; + t[0].rx_buf = NULL; + t[0].len = 2; + spi_message_add_tail(&t[0], &m); + + t[1].tx_buf = NULL; + t[1].rx_buf = &data; + t[1].len = 2; + spi_message_add_tail(&t[1], &m); + + spi_sync(m.spi, &m); + + return data; +} + +void tsc2301_write_reg(struct tsc2301 *tsc, int reg, u16 val) +{ + struct spi_transfer t; + struct spi_message m; + u16 data[2]; + + /* Now we prepare the command for transferring */ + data[0] = reg; + data[1] = val; + + spi_message_init(&m); + m.spi = tsc->spi; + + memset(&t, 0, sizeof(t)); + t.tx_buf = data; + t.rx_buf = NULL; + t.len = 4; + spi_message_add_tail(&t, &m); + + spi_sync(m.spi, &m); +} + +void tsc2301_write_kbc(struct tsc2301 *tsc, int val) +{ + u16 w; + + w = tsc->config2_shadow; + w &= ~(0x03 << 14); + w |= (val & 0x03) << 14; + tsc2301_write_reg(tsc, TSC2301_REG_CONFIG2, w); + tsc->config2_shadow = w; +} + +void tsc2301_write_pll(struct tsc2301 *tsc, + int pll_n, int pll_a, int pll_pdc, int pct_e, int pll_o) +{ + u16 w; + + w = tsc->config2_shadow; + w &= ~0x3fff; + w |= (pll_n & 0x0f) | ((pll_a & 0x0f) << 4) | ((pll_pdc & 0x0f) << 8); + w |= pct_e ? (1 << 12) : 0; + w |= pll_o ? (1 << 13) : 0; + tsc2301_write_reg(tsc, TSC2301_REG_CONFIG2, w); + tsc->config2_shadow = w; +} + +void tsc2301_read_buf(struct tsc2301 *tsc, int reg, u16 *rx_buf, int len) +{ + struct spi_transfer t[2]; + struct spi_message m; + u16 cmd, i; + + cmd = reg; + cmd |= 0x8000; + + spi_message_init(&m); + m.spi = tsc->spi; + + memset(t, 0, sizeof(t)); + t[0].tx_buf = &cmd; + t[0].rx_buf = NULL; + t[0].len = 2; + spi_message_add_tail(&t[0], &m); + + t[1].tx_buf = NULL; + t[1].rx_buf = rx_buf; + t[1].len = 2 * len; + spi_message_add_tail(&t[1], &m); + + spi_sync(m.spi, &m); + + for (i = 0; i < len; i++) + printk(KERN_DEBUG "rx_buf[%d]: %04x\n", i, rx_buf[i]); +} + +static int __devinit tsc2301_probe(struct spi_device *spi) +{ + struct tsc2301 *tsc; + struct tsc2301_platform_data *pdata = spi->dev.platform_data; + int r; + u16 w; + + if (!pdata) { + dev_dbg(&spi->dev, "no platform data?\n"); + return -ENODEV; + } + + tsc = kzalloc(sizeof(*tsc), GFP_KERNEL); + if (tsc == NULL) + return -ENOMEM; + + dev_set_drvdata(&spi->dev, tsc); + tsc->spi = spi; + + tsc->enable_clock = pdata->enable_clock; + tsc->disable_clock = pdata->disable_clock; + + if (pdata->reset_gpio >= 0) { + tsc->reset_gpio = pdata->reset_gpio; + r = gpio_request(tsc->reset_gpio, "TSC2301 reset"); + if (r < 0) + goto err1; + gpio_direction_output(tsc->reset_gpio, 1); + mdelay(1); + gpio_set_value(tsc->reset_gpio, 0); + } else + tsc->reset_gpio = -1; + + spi->mode = SPI_MODE_1; + spi->bits_per_word = 16; + /* The max speed might've been defined by the board-specific + * struct */ + if (!spi->max_speed_hz) + spi->max_speed_hz = TSC2301_HZ; + spi_setup(spi); + + /* Soft reset */ + tsc2301_write_reg(tsc, TSC2301_REG_RESET, 0xbb00); + msleep(1); + + w = tsc2301_read_reg(tsc, TSC2301_REG_ADC); + if (!(w & (1 << 14))) { + dev_err(&spi->dev, "invalid ADC reg value: %04x\n", w); + r = -ENODEV; + goto err1; + } + + w = tsc2301_read_reg(tsc, TSC2301_REG_DAC); + if (!(w & (1 << 15))) { + dev_err(&spi->dev, "invalid DAC reg value: %04x\n", w); + r = -ENODEV; + goto err1; + } + + /* Stop keypad scanning */ + tsc2301_write_reg(tsc, TSC2301_REG_KEY, 0x4000); + + /* We have to cache this for read-modify-write, since we can't + * read back BIT15 */ + w = tsc2301_read_reg(tsc, TSC2301_REG_CONFIG2); + /* By default BIT15 is set */ + w |= 1 << 15; + tsc->config2_shadow = w; + + r = tsc2301_kp_init(tsc, pdata); + if (r) + goto err1; + r = tsc2301_ts_init(tsc, pdata); + if (r) + goto err2; + return 0; + + tsc2301_ts_exit(tsc); +err2: + tsc2301_kp_exit(tsc); +err1: + kfree(tsc); + return r; +} + +static int __devexit tsc2301_remove(struct spi_device *spi) +{ + struct tsc2301 *tsc = dev_get_drvdata(&spi->dev); + + tsc2301_ts_exit(tsc); + tsc2301_kp_exit(tsc); + if (tsc->reset_gpio >= 0) + gpio_free(tsc->reset_gpio); + kfree(tsc); + + return 0; +} + +#ifdef CONFIG_PM +static int tsc2301_suspend(struct spi_device *spi, pm_message_t mesg) +{ + struct tsc2301 *tsc = dev_get_drvdata(&spi->dev); + int r; + + if ((r = tsc2301_kp_suspend(tsc)) < 0) + return r; + if ((r = tsc2301_ts_suspend(tsc)) < 0) { + tsc2301_kp_resume(tsc); + return r; + } + + return 0; +} + +static int tsc2301_resume(struct spi_device *spi) +{ + struct tsc2301 *tsc = dev_get_drvdata(&spi->dev); + + tsc2301_ts_resume(tsc); + tsc2301_kp_resume(tsc); + + return 0; +} +#endif + +static struct spi_driver tsc2301_driver = { + .driver = { + .name = "tsc2301", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, +#ifdef CONFIG_PM + .suspend = tsc2301_suspend, + .resume = tsc2301_resume, +#endif + .probe = tsc2301_probe, + .remove = __devexit_p(tsc2301_remove), +}; + +static int __init tsc2301_init(void) +{ + printk("TSC2301 driver initializing\n"); + + return spi_register_driver(&tsc2301_driver); +} +module_init(tsc2301_init); + +static void __exit tsc2301_exit(void) +{ + spi_unregister_driver(&tsc2301_driver); +} +module_exit(tsc2301_exit); + +MODULE_AUTHOR("Juha Yrjölä "); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/omap/Kconfig b/drivers/video/omap/Kconfig index 44408850e2e..c355b59d2d8 100644 --- a/drivers/video/omap/Kconfig +++ b/drivers/video/omap/Kconfig @@ -7,6 +7,69 @@ config FB_OMAP help Frame buffer driver for OMAP based boards. +config FB_OMAP_LCD_VGA + bool "Use LCD in VGA mode" + depends on MACH_OMAP_3430SDP || MACH_OMAP_LDP + +choice + depends on FB_OMAP && MACH_OVERO + prompt "Screen resolution" + default FB_OMAP_079M3R + help + Selected desired screen resolution + +config FB_OMAP_031M3R + boolean "640 x 480 @ 60 Hz Reduced blanking" + +config FB_OMAP_048M3R + boolean "800 x 600 @ 60 Hz Reduced blanking" + +config FB_OMAP_079M3R + boolean "1024 x 768 @ 60 Hz Reduced blanking" + +config FB_OMAP_092M9R + boolean "1280 x 720 @ 60 Hz Reduced blanking" + +endchoice + +config FB_OMAP_LCDC_EXTERNAL + bool "External LCD controller support" + depends on FB_OMAP + help + Say Y here, if you want to have support for boards with an + external LCD controller connected to the SoSSI/RFBI interface. + +config FB_OMAP_LCDC_HWA742 + bool "Epson HWA742 LCD controller support" + depends on FB_OMAP && FB_OMAP_LCDC_EXTERNAL + help + Say Y here if you want to have support for the external + Epson HWA742 LCD controller. + +config FB_OMAP_LCDC_BLIZZARD + bool "Epson Blizzard LCD controller support" + depends on FB_OMAP && FB_OMAP_LCDC_EXTERNAL + help + Say Y here if you want to have support for the external + Epson Blizzard LCD controller. + +config FB_OMAP_MANUAL_UPDATE + bool "Default to manual update mode" + depends on FB_OMAP && FB_OMAP_LCDC_EXTERNAL + help + Say Y here, if your user-space applications are capable of + notifying the frame buffer driver when a change has occured in + the frame buffer content and thus a reload of the image data to + the external frame buffer is required. If unsure, say N. + +config FB_OMAP_LCD_MIPID + bool "MIPI DBI-C/DCS compatible LCD support" + depends on FB_OMAP && SPI_MASTER && CBUS_TAHVO + help + Say Y here if you want to have support for LCDs compatible with + the Mobile Industry Processor Interface DBI-C/DCS + specification. (Supported LCDs: Philips LPH8923, Sharp LS041Y3) + config FB_OMAP_BOOTLOADER_INIT bool "Check bootloader initialization" depends on FB_OMAP @@ -36,23 +99,4 @@ config FB_OMAP_DMA_TUNE answer yes. Answer no if you have a dedicated video memory, or don't use any of the accelerated features. -config FB_OMAP_LCDC_EXTERNAL - bool "External LCD controller support" - depends on FB_OMAP - help - Say Y here, if you want to have support for boards with an - external LCD controller connected to the SoSSI/RFBI interface. - -config FB_OMAP_LCDC_HWA742 - bool "Epson HWA742 LCD controller support" - depends on FB_OMAP && FB_OMAP_LCDC_EXTERNAL - help - Say Y here if you want to have support for the external - Epson HWA742 LCD controller. -config FB_OMAP_LCDC_BLIZZARD - bool "Epson Blizzard LCD controller support" - depends on FB_OMAP && FB_OMAP_LCDC_EXTERNAL - help - Say Y here if you want to have support for the external - Epson Blizzard LCD controller. diff --git a/drivers/video/omap/Makefile b/drivers/video/omap/Makefile index ed13889c116..b63b198d1f0 100644 --- a/drivers/video/omap/Makefile +++ b/drivers/video/omap/Makefile @@ -8,6 +8,7 @@ objs-yy := omapfb_main.o objs-y$(CONFIG_ARCH_OMAP1) += lcdc.o objs-y$(CONFIG_ARCH_OMAP2) += dispc.o +objs-y$(CONFIG_ARCH_OMAP3) += dispc.o objs-$(CONFIG_ARCH_OMAP1)$(CONFIG_FB_OMAP_LCDC_EXTERNAL) += sossi.o objs-$(CONFIG_ARCH_OMAP2)$(CONFIG_FB_OMAP_LCDC_EXTERNAL) += rfbi.o @@ -15,6 +16,7 @@ objs-$(CONFIG_ARCH_OMAP2)$(CONFIG_FB_OMAP_LCDC_EXTERNAL) += rfbi.o objs-y$(CONFIG_FB_OMAP_LCDC_HWA742) += hwa742.o objs-y$(CONFIG_FB_OMAP_LCDC_BLIZZARD) += blizzard.o +objs-y$(CONFIG_MACH_AMS_DELTA) += lcd_ams_delta.o objs-y$(CONFIG_MACH_OMAP_H4) += lcd_h4.o objs-y$(CONFIG_MACH_OMAP_H3) += lcd_h3.o objs-y$(CONFIG_MACH_OMAP_PALMTE) += lcd_palmte.o @@ -24,5 +26,15 @@ objs-$(CONFIG_ARCH_OMAP16XX)$(CONFIG_MACH_OMAP_INNOVATOR) += lcd_inn1610.o objs-$(CONFIG_ARCH_OMAP15XX)$(CONFIG_MACH_OMAP_INNOVATOR) += lcd_inn1510.o objs-y$(CONFIG_MACH_OMAP_OSK) += lcd_osk.o +objs-y$(CONFIG_MACH_OMAP_APOLLON) += lcd_apollon.o +objs-y$(CONFIG_MACH_OMAP_2430SDP) += lcd_2430sdp.o +objs-y$(CONFIG_MACH_OMAP_3430SDP) += lcd_2430sdp.o +objs-y$(CONFIG_MACH_OMAP_LDP) += lcd_ldp.o +objs-y$(CONFIG_MACH_OMAP2EVM) += lcd_omap2evm.o +objs-y$(CONFIG_MACH_OMAP3EVM) += lcd_omap3evm.o +objs-y$(CONFIG_MACH_OMAP3_BEAGLE) += lcd_omap3beagle.o +objs-y$(CONFIG_FB_OMAP_LCD_MIPID) += lcd_mipid.o +objs-y$(CONFIG_MACH_OVERO) += lcd_overo.o + omapfb-objs := $(objs-yy) diff --git a/drivers/video/omap/blizzard.c b/drivers/video/omap/blizzard.c index 9dfcf39d336..f60a2338ef9 100644 --- a/drivers/video/omap/blizzard.c +++ b/drivers/video/omap/blizzard.c @@ -44,6 +44,7 @@ #define BLIZZARD_CLK_SRC 0x0e #define BLIZZARD_MEM_BANK0_ACTIVATE 0x10 #define BLIZZARD_MEM_BANK0_STATUS 0x14 +#define BLIZZARD_PANEL_CONFIGURATION 0x28 #define BLIZZARD_HDISP 0x2a #define BLIZZARD_HNDP 0x2c #define BLIZZARD_VDISP0 0x2e @@ -162,6 +163,10 @@ struct blizzard_struct { int vid_scaled; int last_color_mode; int zoom_on; + int zoom_area_gx1; + int zoom_area_gx2; + int zoom_area_gy1; + int zoom_area_gy2; int screen_width; int screen_height; unsigned te_connected:1; @@ -513,6 +518,12 @@ static int do_full_screen_update(struct blizzard_request *req) return REQ_PENDING; } +static int check_1d_intersect(int a1, int a2, int b1, int b2) +{ + if (a2 <= b1 || b2 <= a1) return 0; + return 1; +} + /* Setup all planes with an overlapping area with the update window. */ static int do_partial_update(struct blizzard_request *req, int plane, int x, int y, int w, int h, @@ -525,6 +536,7 @@ static int do_partial_update(struct blizzard_request *req, int plane, int color_mode; int flags; int zoom_off; + int have_zoom_for_this_update = 0; /* Global coordinates, relative to pixel 0,0 of the LCD */ gx1 = x + blizzard.plane[plane].pos_x; @@ -544,10 +556,6 @@ static int do_partial_update(struct blizzard_request *req, int plane, gx2_out = gx1_out + w_out; gy2_out = gy1_out + h_out; } - zoom_off = blizzard.zoom_on && gx1 == 0 && gy1 == 0 && - w == blizzard.screen_width && h == blizzard.screen_height; - blizzard.zoom_on = (!zoom_off && blizzard.zoom_on) || - (w < w_out || h < h_out); for (i = 0; i < OMAPFB_PLANE_NUM; i++) { struct plane_info *p = &blizzard.plane[i]; @@ -653,8 +661,49 @@ static int do_partial_update(struct blizzard_request *req, int plane, else disable_tearsync(); + if ((gx2_out - gx1_out) != (gx2 - gx1) || + (gy2_out - gy1_out) != (gy2 - gy1)) + have_zoom_for_this_update = 1; + + /* 'background' type of screen update (as opposed to 'destructive') + can be used to disable scaling if scaling is active */ + zoom_off = blizzard.zoom_on && !have_zoom_for_this_update && + (gx1_out == 0) && (gx2_out == blizzard.screen_width) && + (gy1_out == 0) && (gy2_out == blizzard.screen_height) && + (gx1 == 0) && (gy1 == 0); + + if (blizzard.zoom_on && !have_zoom_for_this_update && !zoom_off && + check_1d_intersect(blizzard.zoom_area_gx1, blizzard.zoom_area_gx2, + gx1_out, gx2_out) && + check_1d_intersect(blizzard.zoom_area_gy1, blizzard.zoom_area_gy2, + gy1_out, gy2_out)) { + /* Previous screen update was using scaling, current update + * is not using it. Additionally, current screen update is + * going to overlap with the scaled area. Scaling needs to be + * disabled in order to avoid 'magnifying glass' effect. + * Dummy setup of background window can be used for this. + */ + set_window_regs(0, 0, blizzard.screen_width, + blizzard.screen_height, + 0, 0, blizzard.screen_width, + blizzard.screen_height, + BLIZZARD_COLOR_RGB565, 1, flags); + blizzard.zoom_on = 0; + } + + /* remember scaling settings if we have scaled update */ + if (have_zoom_for_this_update) { + blizzard.zoom_on = 1; + blizzard.zoom_area_gx1 = gx1_out; + blizzard.zoom_area_gx2 = gx2_out; + blizzard.zoom_area_gy1 = gy1_out; + blizzard.zoom_area_gy2 = gy2_out; + } + set_window_regs(gx1, gy1, gx2, gy2, gx1_out, gy1_out, gx2_out, gy2_out, color_mode, zoom_off, flags); + if (zoom_off) + blizzard.zoom_on = 0; blizzard.extif->set_bits_per_cycle(16); /* set_window_regs has left the register index at the right @@ -908,6 +957,35 @@ static int blizzard_set_scale(int plane, int orig_w, int orig_h, return 0; } +static int blizzard_set_rotate(int angle) +{ + u32 l; + + l = blizzard_read_reg(BLIZZARD_PANEL_CONFIGURATION); + l &= ~0x03; + + switch (angle) { + case 0: + l = l | 0x00; + break; + case 90: + l = l | 0x03; + break; + case 180: + l = l | 0x02; + break; + case 270: + l = l | 0x01; + break; + default: + return -EINVAL; + } + + blizzard_write_reg(BLIZZARD_PANEL_CONFIGURATION, l); + + return 0; +} + static int blizzard_enable_plane(int plane, int enable) { if (enable) @@ -1285,7 +1363,8 @@ static void blizzard_get_caps(int plane, struct omapfb_caps *caps) caps->ctrl |= OMAPFB_CAPS_MANUAL_UPDATE | OMAPFB_CAPS_WINDOW_PIXEL_DOUBLE | OMAPFB_CAPS_WINDOW_SCALE | - OMAPFB_CAPS_WINDOW_OVERLAY; + OMAPFB_CAPS_WINDOW_OVERLAY | + OMAPFB_CAPS_WINDOW_ROTATE; if (blizzard.te_connected) caps->ctrl |= OMAPFB_CAPS_TEARSYNC; caps->wnd_color |= (1 << OMAPFB_COLOR_RGB565) | @@ -1560,6 +1639,7 @@ struct lcd_ctrl blizzard_ctrl = { .setup_plane = blizzard_setup_plane, .set_scale = blizzard_set_scale, .enable_plane = blizzard_enable_plane, + .set_rotate = blizzard_set_rotate, .update_window = blizzard_update_window_async, .sync = blizzard_sync, .suspend = blizzard_suspend, diff --git a/drivers/video/omap/dispc.c b/drivers/video/omap/dispc.c index dfb72f5e4c9..c140c214223 100644 --- a/drivers/video/omap/dispc.c +++ b/drivers/video/omap/dispc.c @@ -155,6 +155,8 @@ struct resmap { unsigned long *map; }; +#define MAX_IRQ_HANDLERS 4 + static struct { void __iomem *base; @@ -167,9 +169,11 @@ static struct { int ext_mode; - unsigned long enabled_irqs; - void (*irq_callback)(void *); - void *irq_callback_data; + struct { + u32 irq_mask; + void (*callback)(void *); + void *data; + } irq_handlers[MAX_IRQ_HANDLERS]; struct completion frame_done; int fir_hinc[OMAPFB_PLANE_NUM]; @@ -286,7 +290,7 @@ static void setup_plane_fifo(int plane, int ext_mode) BUG_ON(plane > 2); l = dispc_read_reg(fsz_reg[plane]); - l &= FLD_MASK(0, 9); + l &= FLD_MASK(0, 11); if (ext_mode) { low = l * 3 / 4; high = l; @@ -294,7 +298,7 @@ static void setup_plane_fifo(int plane, int ext_mode) low = l / 4; high = l * 3 / 4; } - MOD_REG_FLD(ftrs_reg[plane], FLD_MASK(16, 9) | FLD_MASK(0, 9), + MOD_REG_FLD(ftrs_reg[plane], FLD_MASK(16, 12) | FLD_MASK(0, 12), (high << 16) | low); } @@ -519,8 +523,7 @@ static int omap_dispc_set_scale(int plane, if ((unsigned)plane > OMAPFB_PLANE_NUM) return -ENODEV; - if (plane == OMAPFB_PLANE_GFX && - (out_width != orig_width || out_height != orig_height)) + if (out_width != orig_width || out_height != orig_height) return -EINVAL; enable_lcd_clocks(1); @@ -809,57 +812,74 @@ static void set_lcd_timings(void) panel->pixel_clock = fck / lck_div / pck_div / 1000; } -int omap_dispc_request_irq(void (*callback)(void *data), void *data) +static void recalc_irq_mask(void) { - int r = 0; + int i; + unsigned long irq_mask = DISPC_IRQ_MASK_ERROR; - BUG_ON(callback == NULL); + for (i = 0; i < MAX_IRQ_HANDLERS; i++) { + if (!dispc.irq_handlers[i].callback) + continue; - if (dispc.irq_callback) - r = -EBUSY; - else { - dispc.irq_callback = callback; - dispc.irq_callback_data = data; + irq_mask |= dispc.irq_handlers[i].irq_mask; } - return r; -} -EXPORT_SYMBOL(omap_dispc_request_irq); - -void omap_dispc_enable_irqs(int irq_mask) -{ enable_lcd_clocks(1); - dispc.enabled_irqs = irq_mask; - irq_mask |= DISPC_IRQ_MASK_ERROR; MOD_REG_FLD(DISPC_IRQENABLE, 0x7fff, irq_mask); enable_lcd_clocks(0); } -EXPORT_SYMBOL(omap_dispc_enable_irqs); -void omap_dispc_disable_irqs(int irq_mask) +int omap_dispc_request_irq(unsigned long irq_mask, void (*callback)(void *data), + void *data) { - enable_lcd_clocks(1); - dispc.enabled_irqs &= ~irq_mask; - irq_mask &= ~DISPC_IRQ_MASK_ERROR; - MOD_REG_FLD(DISPC_IRQENABLE, 0x7fff, irq_mask); - enable_lcd_clocks(0); + int i; + + BUG_ON(callback == NULL); + + for (i = 0; i < MAX_IRQ_HANDLERS; i++) { + if (dispc.irq_handlers[i].callback) + continue; + + dispc.irq_handlers[i].irq_mask = irq_mask; + dispc.irq_handlers[i].callback = callback; + dispc.irq_handlers[i].data = data; + recalc_irq_mask(); + + return 0; + } + + return -EBUSY; } -EXPORT_SYMBOL(omap_dispc_disable_irqs); +EXPORT_SYMBOL(omap_dispc_request_irq); -void omap_dispc_free_irq(void) +void omap_dispc_free_irq(unsigned long irq_mask, void (*callback)(void *data), + void *data) { - enable_lcd_clocks(1); - omap_dispc_disable_irqs(DISPC_IRQ_MASK_ALL); - dispc.irq_callback = NULL; - dispc.irq_callback_data = NULL; - enable_lcd_clocks(0); + int i; + + for (i = 0; i < MAX_IRQ_HANDLERS; i++) { + if (dispc.irq_handlers[i].callback == callback && + dispc.irq_handlers[i].data == data) { + dispc.irq_handlers[i].irq_mask = 0; + dispc.irq_handlers[i].callback = NULL; + dispc.irq_handlers[i].data = NULL; + recalc_irq_mask(); + return; + } + } + + BUG(); } EXPORT_SYMBOL(omap_dispc_free_irq); static irqreturn_t omap_dispc_irq_handler(int irq, void *dev) { - u32 stat = dispc_read_reg(DISPC_IRQSTATUS); + u32 stat; + int i = 0; + enable_lcd_clocks(1); + + stat = dispc_read_reg(DISPC_IRQSTATUS); if (stat & DISPC_IRQ_FRAMEMASK) complete(&dispc.frame_done); @@ -870,30 +890,39 @@ static irqreturn_t omap_dispc_irq_handler(int irq, void *dev) } } - if ((stat & dispc.enabled_irqs) && dispc.irq_callback) - dispc.irq_callback(dispc.irq_callback_data); + for (i = 0; i < MAX_IRQ_HANDLERS; i++) { + if (unlikely(dispc.irq_handlers[i].callback && + (stat & dispc.irq_handlers[i].irq_mask))) + dispc.irq_handlers[i].callback(dispc.irq_handlers[i].data); + } dispc_write_reg(DISPC_IRQSTATUS, stat); + enable_lcd_clocks(0); + return IRQ_HANDLED; } static int get_dss_clocks(void) { - if (IS_ERR((dispc.dss_ick = clk_get(dispc.fbdev->dev, "dss_ick")))) { - dev_err(dispc.fbdev->dev, "can't get dss_ick\n"); + char *dss_ick = "dss_ick"; + char *dss1_fck = cpu_is_omap34xx() ? "dss1_alwon_fck" : "dss1_fck"; + char *tv_fck = cpu_is_omap34xx() ? "dss_tv_fck" : "dss_54m_fck"; + + if (IS_ERR((dispc.dss_ick = clk_get(dispc.fbdev->dev, dss_ick)))) { + dev_err(dispc.fbdev->dev, "can't get %s", dss_ick); return PTR_ERR(dispc.dss_ick); } - if (IS_ERR((dispc.dss1_fck = clk_get(dispc.fbdev->dev, "dss1_fck")))) { - dev_err(dispc.fbdev->dev, "can't get dss1_fck\n"); + if (IS_ERR((dispc.dss1_fck = clk_get(dispc.fbdev->dev, dss1_fck)))) { + dev_err(dispc.fbdev->dev, "can't get %s", dss1_fck); clk_put(dispc.dss_ick); return PTR_ERR(dispc.dss1_fck); } if (IS_ERR((dispc.dss_54m_fck = - clk_get(dispc.fbdev->dev, "dss_54m_fck")))) { - dev_err(dispc.fbdev->dev, "can't get dss_54m_fck\n"); + clk_get(dispc.fbdev->dev, tv_fck)))) { + dev_err(dispc.fbdev->dev, "can't get %s", tv_fck); clk_put(dispc.dss_ick); clk_put(dispc.dss1_fck); return PTR_ERR(dispc.dss_54m_fck); @@ -911,18 +940,13 @@ static void put_dss_clocks(void) static void enable_lcd_clocks(int enable) { - if (enable) + if (enable) { + clk_enable(dispc.dss_ick); clk_enable(dispc.dss1_fck); - else + } else { clk_disable(dispc.dss1_fck); -} - -static void enable_interface_clocks(int enable) -{ - if (enable) - clk_enable(dispc.dss_ick); - else clk_disable(dispc.dss_ick); + } } static void enable_digit_clocks(int enable) @@ -1363,7 +1387,6 @@ static int omap_dispc_init(struct omapfb_device *fbdev, int ext_mode, if ((r = get_dss_clocks()) < 0) goto fail0; - enable_interface_clocks(1); enable_lcd_clocks(1); #ifdef CONFIG_FB_OMAP_BOOTLOADER_INIT @@ -1394,10 +1417,10 @@ static int omap_dispc_init(struct omapfb_device *fbdev, int ext_mode, enable_digit_clocks(0); } - /* Enable smart idle and autoidle */ - l = dispc_read_reg(DISPC_CONTROL); + /* Enable smart standby/idle, autoidle and wakeup */ + l = dispc_read_reg(DISPC_SYSCONFIG); l &= ~((3 << 12) | (3 << 3)); - l |= (2 << 12) | (2 << 3) | (1 << 0); + l |= (2 << 12) | (2 << 3) | (1 << 2) | (1 << 0); dispc_write_reg(DISPC_SYSCONFIG, l); omap_writel(1 << 0, DSS_BASE + DSS_SYSCONFIG); @@ -1407,10 +1430,9 @@ static int omap_dispc_init(struct omapfb_device *fbdev, int ext_mode, dispc_write_reg(DISPC_CONFIG, l); l = dispc_read_reg(DISPC_IRQSTATUS); - dispc_write_reg(l, DISPC_IRQSTATUS); + dispc_write_reg(DISPC_IRQSTATUS, l); - /* Enable those that we handle always */ - omap_dispc_enable_irqs(DISPC_IRQ_FRAMEMASK); + recalc_irq_mask(); if ((r = request_irq(INT_24XX_DSS_IRQ, omap_dispc_irq_handler, 0, MODULE_NAME, fbdev)) < 0) { @@ -1467,7 +1489,6 @@ fail2: free_irq(INT_24XX_DSS_IRQ, fbdev); fail1: enable_lcd_clocks(0); - enable_interface_clocks(0); put_dss_clocks(); fail0: iounmap(dispc.base); @@ -1485,7 +1506,6 @@ static void omap_dispc_cleanup(void) cleanup_fbmem(); free_palette_ram(); free_irq(INT_24XX_DSS_IRQ, dispc.fbdev); - enable_interface_clocks(0); put_dss_clocks(); iounmap(dispc.base); } diff --git a/drivers/video/omap/dispc.h b/drivers/video/omap/dispc.h index ef720a78f6d..0a370cf07f7 100644 --- a/drivers/video/omap/dispc.h +++ b/drivers/video/omap/dispc.h @@ -37,8 +37,10 @@ extern void omap_dispc_set_lcd_size(int width, int height); extern void omap_dispc_enable_lcd_out(int enable); extern void omap_dispc_enable_digit_out(int enable); -extern int omap_dispc_request_irq(void (*callback)(void *data), void *data); -extern void omap_dispc_free_irq(void); +extern int omap_dispc_request_irq(unsigned long irq_mask, + void (*callback)(void *data), void *data); +extern void omap_dispc_free_irq(unsigned long irq_mask, + void (*callback)(void *data), void *data); extern const struct lcd_ctrl omap2_int_ctrl; diff --git a/drivers/video/omap/lcd_2430sdp.c b/drivers/video/omap/lcd_2430sdp.c new file mode 100644 index 00000000000..a22b452c4d5 --- /dev/null +++ b/drivers/video/omap/lcd_2430sdp.c @@ -0,0 +1,199 @@ +/* + * LCD panel support for the TI 2430SDP board + * + * Copyright (C) 2007 MontaVista + * Author: Hunyue Yau + * + * Derived from drivers/video/omap/lcd-apollon.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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#define SDP2430_LCD_PANEL_BACKLIGHT_GPIO 91 +#define SDP2430_LCD_PANEL_ENABLE_GPIO 154 +#define SDP3430_LCD_PANEL_BACKLIGHT_GPIO 24 +#define SDP3430_LCD_PANEL_ENABLE_GPIO 28 + +static unsigned backlight_gpio; +static unsigned enable_gpio; + +#define LCD_PIXCLOCK_MAX 5400 /* freq 5.4 MHz */ +#define PM_RECEIVER TWL4030_MODULE_PM_RECEIVER +#define ENABLE_VAUX2_DEDICATED 0x09 +#define ENABLE_VAUX2_DEV_GRP 0x20 +#define ENABLE_VAUX3_DEDICATED 0x03 +#define ENABLE_VAUX3_DEV_GRP 0x20 + +#define ENABLE_VPLL2_DEDICATED 0x05 +#define ENABLE_VPLL2_DEV_GRP 0xE0 +#define TWL4030_VPLL2_DEV_GRP 0x33 +#define TWL4030_VPLL2_DEDICATED 0x36 + +#define t2_out(c, r, v) twl4030_i2c_write_u8(c, r, v) + + +static int sdp2430_panel_init(struct lcd_panel *panel, + struct omapfb_device *fbdev) +{ + if (machine_is_omap_3430sdp()) { + enable_gpio = SDP3430_LCD_PANEL_ENABLE_GPIO; + backlight_gpio = SDP3430_LCD_PANEL_BACKLIGHT_GPIO; + } else { + enable_gpio = SDP2430_LCD_PANEL_ENABLE_GPIO; + backlight_gpio = SDP2430_LCD_PANEL_BACKLIGHT_GPIO; + } + + gpio_request(enable_gpio, "LCD enable"); /* LCD panel */ + gpio_request(backlight_gpio, "LCD bl"); /* LCD backlight */ + gpio_direction_output(enable_gpio, 0); + gpio_direction_output(backlight_gpio, 0); + + return 0; +} + +static void sdp2430_panel_cleanup(struct lcd_panel *panel) +{ +} + +static int sdp2430_panel_enable(struct lcd_panel *panel) +{ + u8 ded_val, ded_reg; + u8 grp_val, grp_reg; + + if (machine_is_omap_3430sdp()) { + ded_reg = TWL4030_VAUX3_DEDICATED; + ded_val = ENABLE_VAUX3_DEDICATED; + grp_reg = TWL4030_VAUX3_DEV_GRP; + grp_val = ENABLE_VAUX3_DEV_GRP; + + if (omap_rev() > OMAP3430_REV_ES1_0) { + t2_out(PM_RECEIVER, ENABLE_VPLL2_DEDICATED, + TWL4030_VPLL2_DEDICATED); + t2_out(PM_RECEIVER, ENABLE_VPLL2_DEV_GRP, + TWL4030_VPLL2_DEV_GRP); + } + } else { + ded_reg = TWL4030_VAUX2_DEDICATED; + ded_val = ENABLE_VAUX2_DEDICATED; + grp_reg = TWL4030_VAUX2_DEV_GRP; + grp_val = ENABLE_VAUX2_DEV_GRP; + } + + gpio_set_value(enable_gpio, 1); + gpio_set_value(backlight_gpio, 1); + + if (0 != t2_out(PM_RECEIVER, ded_val, ded_reg)) + return -EIO; + if (0 != t2_out(PM_RECEIVER, grp_val, grp_reg)) + return -EIO; + + return 0; +} + +static void sdp2430_panel_disable(struct lcd_panel *panel) +{ + gpio_set_value(enable_gpio, 0); + gpio_set_value(backlight_gpio, 0); + if (omap_rev() > OMAP3430_REV_ES1_0) { + t2_out(PM_RECEIVER, 0x0, TWL4030_VPLL2_DEDICATED); + t2_out(PM_RECEIVER, 0x0, TWL4030_VPLL2_DEV_GRP); + mdelay(4); + } +} + +static unsigned long sdp2430_panel_get_caps(struct lcd_panel *panel) +{ + return 0; +} + +struct lcd_panel sdp2430_panel = { + .name = "sdp2430", + .config = OMAP_LCDC_PANEL_TFT | OMAP_LCDC_INV_VSYNC | + OMAP_LCDC_INV_HSYNC, + + .bpp = 16, + .data_lines = 16, + .x_res = 240, + .y_res = 320, + .hsw = 3, /* hsync_len (4) - 1 */ + .hfp = 3, /* right_margin (4) - 1 */ + .hbp = 39, /* left_margin (40) - 1 */ + .vsw = 1, /* vsync_len (2) - 1 */ + .vfp = 2, /* lower_margin */ + .vbp = 7, /* upper_margin (8) - 1 */ + + .pixel_clock = LCD_PIXCLOCK_MAX, + + .init = sdp2430_panel_init, + .cleanup = sdp2430_panel_cleanup, + .enable = sdp2430_panel_enable, + .disable = sdp2430_panel_disable, + .get_caps = sdp2430_panel_get_caps, +}; + +static int sdp2430_panel_probe(struct platform_device *pdev) +{ + omapfb_register_panel(&sdp2430_panel); + return 0; +} + +static int sdp2430_panel_remove(struct platform_device *pdev) +{ + return 0; +} + +static int sdp2430_panel_suspend(struct platform_device *pdev, pm_message_t mesg) +{ + return 0; +} + +static int sdp2430_panel_resume(struct platform_device *pdev) +{ + return 0; +} + +struct platform_driver sdp2430_panel_driver = { + .probe = sdp2430_panel_probe, + .remove = sdp2430_panel_remove, + .suspend = sdp2430_panel_suspend, + .resume = sdp2430_panel_resume, + .driver = { + .name = "sdp2430_lcd", + .owner = THIS_MODULE, + }, +}; + +static int __init sdp2430_panel_drv_init(void) +{ + return platform_driver_register(&sdp2430_panel_driver); +} + +static void __exit sdp2430_panel_drv_exit(void) +{ + platform_driver_unregister(&sdp2430_panel_driver); +} + +module_init(sdp2430_panel_drv_init); +module_exit(sdp2430_panel_drv_exit); diff --git a/drivers/video/omap/lcd_ams_delta.c b/drivers/video/omap/lcd_ams_delta.c new file mode 100644 index 00000000000..3fd5342d645 --- /dev/null +++ b/drivers/video/omap/lcd_ams_delta.c @@ -0,0 +1,140 @@ +/* + * File: drivers/video/omap/lcd_ams_delta.c + * + * Based on drivers/video/omap/lcd_inn1510.c + * + * LCD panel support for the Amstrad E3 (Delta) videophone. + * + * Copyright (C) 2006 Jonathan McDowell + * + * 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 program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include + +#include +#include + +#include +#include +#include + +#define AMS_DELTA_DEFAULT_CONTRAST 112 + +static int ams_delta_panel_init(struct lcd_panel *panel, + struct omapfb_device *fbdev) +{ + return 0; +} + +static void ams_delta_panel_cleanup(struct lcd_panel *panel) +{ +} + +static int ams_delta_panel_enable(struct lcd_panel *panel) +{ + ams_delta_latch2_write(AMS_DELTA_LATCH2_LCD_NDISP, + AMS_DELTA_LATCH2_LCD_NDISP); + ams_delta_latch2_write(AMS_DELTA_LATCH2_LCD_VBLEN, + AMS_DELTA_LATCH2_LCD_VBLEN); + + omap_writeb(1, OMAP_PWL_CLK_ENABLE); + omap_writeb(AMS_DELTA_DEFAULT_CONTRAST, OMAP_PWL_ENABLE); + + return 0; +} + +static void ams_delta_panel_disable(struct lcd_panel *panel) +{ + ams_delta_latch2_write(AMS_DELTA_LATCH2_LCD_VBLEN, 0); + ams_delta_latch2_write(AMS_DELTA_LATCH2_LCD_NDISP, 0); +} + +static unsigned long ams_delta_panel_get_caps(struct lcd_panel *panel) +{ + return 0; +} + +static struct lcd_panel ams_delta_panel = { + .name = "ams-delta", + .config = 0, + + .bpp = 12, + .data_lines = 16, + .x_res = 480, + .y_res = 320, + .pixel_clock = 4687, + .hsw = 3, + .hfp = 1, + .hbp = 1, + .vsw = 1, + .vfp = 0, + .vbp = 0, + .pcd = 0, + .acb = 37, + + .init = ams_delta_panel_init, + .cleanup = ams_delta_panel_cleanup, + .enable = ams_delta_panel_enable, + .disable = ams_delta_panel_disable, + .get_caps = ams_delta_panel_get_caps, +}; + +static int ams_delta_panel_probe(struct platform_device *pdev) +{ + omapfb_register_panel(&ams_delta_panel); + return 0; +} + +static int ams_delta_panel_remove(struct platform_device *pdev) +{ + return 0; +} + +static int ams_delta_panel_suspend(struct platform_device *pdev, + pm_message_t mesg) +{ + return 0; +} + +static int ams_delta_panel_resume(struct platform_device *pdev) +{ + return 0; +} + +struct platform_driver ams_delta_panel_driver = { + .probe = ams_delta_panel_probe, + .remove = ams_delta_panel_remove, + .suspend = ams_delta_panel_suspend, + .resume = ams_delta_panel_resume, + .driver = { + .name = "lcd_ams_delta", + .owner = THIS_MODULE, + }, +}; + +static int ams_delta_panel_drv_init(void) +{ + return platform_driver_register(&ams_delta_panel_driver); +} + +static void ams_delta_panel_drv_cleanup(void) +{ + platform_driver_unregister(&ams_delta_panel_driver); +} + +module_init(ams_delta_panel_drv_init); +module_exit(ams_delta_panel_drv_cleanup); diff --git a/drivers/video/omap/lcd_apollon.c b/drivers/video/omap/lcd_apollon.c new file mode 100644 index 00000000000..beae5d9329a --- /dev/null +++ b/drivers/video/omap/lcd_apollon.c @@ -0,0 +1,137 @@ +/* + * LCD panel support for the Samsung OMAP2 Apollon board + * + * Copyright (C) 2005,2006 Samsung Electronics + * Author: Kyungmin Park + * + * Derived from drivers/video/omap/lcd-h4.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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include + +#include +#include +#include + +/* #define USE_35INCH_LCD 1 */ + +static int apollon_panel_init(struct lcd_panel *panel, + struct omapfb_device *fbdev) +{ + /* configure LCD PWR_EN */ + omap_cfg_reg(M21_242X_GPIO11); + return 0; +} + +static void apollon_panel_cleanup(struct lcd_panel *panel) +{ +} + +static int apollon_panel_enable(struct lcd_panel *panel) +{ + return 0; +} + +static void apollon_panel_disable(struct lcd_panel *panel) +{ +} + +static unsigned long apollon_panel_get_caps(struct lcd_panel *panel) +{ + return 0; +} + +struct lcd_panel apollon_panel = { + .name = "apollon", + .config = OMAP_LCDC_PANEL_TFT | OMAP_LCDC_INV_VSYNC | + OMAP_LCDC_INV_HSYNC, + + .bpp = 16, + .data_lines = 18, +#ifdef USE_35INCH_LCD + .x_res = 240, + .y_res = 320, + .hsw = 2, + .hfp = 3, + .hbp = 9, + .vsw = 4, + .vfp = 3, + .vbp = 5, +#else + .x_res = 480, + .y_res = 272, + .hsw = 41, + .hfp = 2, + .hbp = 2, + .vsw = 10, + .vfp = 2, + .vbp = 2, +#endif + .pixel_clock = 6250, + + .init = apollon_panel_init, + .cleanup = apollon_panel_cleanup, + .enable = apollon_panel_enable, + .disable = apollon_panel_disable, + .get_caps = apollon_panel_get_caps, +}; + +static int apollon_panel_probe(struct platform_device *pdev) +{ + omapfb_register_panel(&apollon_panel); + return 0; +} + +static int apollon_panel_remove(struct platform_device *pdev) +{ + return 0; +} + +static int apollon_panel_suspend(struct platform_device *pdev, pm_message_t mesg) +{ + return 0; +} + +static int apollon_panel_resume(struct platform_device *pdev) +{ + return 0; +} + +struct platform_driver apollon_panel_driver = { + .probe = apollon_panel_probe, + .remove = apollon_panel_remove, + .suspend = apollon_panel_suspend, + .resume = apollon_panel_resume, + .driver = { + .name = "apollon_lcd", + .owner = THIS_MODULE, + }, +}; + +static int __init apollon_panel_drv_init(void) +{ + return platform_driver_register(&apollon_panel_driver); +} + +static void __exit apollon_panel_drv_exit(void) +{ + platform_driver_unregister(&apollon_panel_driver); +} + +module_init(apollon_panel_drv_init); +module_exit(apollon_panel_drv_exit); diff --git a/drivers/video/omap/lcd_ldp.c b/drivers/video/omap/lcd_ldp.c new file mode 100644 index 00000000000..89252301271 --- /dev/null +++ b/drivers/video/omap/lcd_ldp.c @@ -0,0 +1,200 @@ +/* + * LCD panel support for the TI LDP board + * + * Copyright (C) 2007 WindRiver + * Author: Stanley Miao + * + * Derived from drivers/video/omap/lcd-2430sdp.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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#define LCD_PANEL_BACKLIGHT_GPIO (15 + OMAP_MAX_GPIO_LINES) +#define LCD_PANEL_ENABLE_GPIO (7 + OMAP_MAX_GPIO_LINES) + +#define LCD_PANEL_RESET_GPIO 55 +#define LCD_PANEL_QVGA_GPIO 56 + +#ifdef CONFIG_FB_OMAP_LCD_VGA +#define LCD_XRES 480 +#define LCD_YRES 640 +#define LCD_PIXCLOCK_MAX 41700 +#else +#define LCD_XRES 240 +#define LCD_YRES 320 +#define LCD_PIXCLOCK_MAX 185186 +#endif + +#define PM_RECEIVER TWL4030_MODULE_PM_RECEIVER +#define ENABLE_VAUX2_DEDICATED 0x09 +#define ENABLE_VAUX2_DEV_GRP 0x20 +#define ENABLE_VAUX3_DEDICATED 0x03 +#define ENABLE_VAUX3_DEV_GRP 0x20 + +#define ENABLE_VPLL2_DEDICATED 0x05 +#define ENABLE_VPLL2_DEV_GRP 0xE0 +#define TWL4030_VPLL2_DEV_GRP 0x33 +#define TWL4030_VPLL2_DEDICATED 0x36 + +#define t2_out(c, r, v) twl4030_i2c_write_u8(c, r, v) + + +static int ldp_panel_init(struct lcd_panel *panel, + struct omapfb_device *fbdev) +{ + gpio_request(LCD_PANEL_RESET_GPIO, "lcd reset"); + gpio_request(LCD_PANEL_QVGA_GPIO, "lcd qvga"); + gpio_request(LCD_PANEL_ENABLE_GPIO, "lcd panel"); + gpio_request(LCD_PANEL_BACKLIGHT_GPIO, "lcd backlight"); + + gpio_direction_output(LCD_PANEL_QVGA_GPIO, 0); + gpio_direction_output(LCD_PANEL_RESET_GPIO, 0); + gpio_direction_output(LCD_PANEL_ENABLE_GPIO, 0); + gpio_direction_output(LCD_PANEL_BACKLIGHT_GPIO, 0); + +#ifdef CONFIG_FB_OMAP_LCD_VGA + gpio_set_value(LCD_PANEL_QVGA_GPIO, 0); +#else + gpio_set_value(LCD_PANEL_QVGA_GPIO, 1); +#endif + gpio_set_value(LCD_PANEL_RESET_GPIO, 1); + + return 0; +} + +static void ldp_panel_cleanup(struct lcd_panel *panel) +{ + gpio_free(LCD_PANEL_RESET_GPIO); + gpio_free(LCD_PANEL_QVGA_GPIO); + gpio_free(LCD_PANEL_ENABLE_GPIO); + gpio_free(LCD_PANEL_BACKLIGHT_GPIO); +} + +static int ldp_panel_enable(struct lcd_panel *panel) +{ + if (0 != t2_out(PM_RECEIVER, ENABLE_VPLL2_DEDICATED, + TWL4030_VPLL2_DEDICATED)) + return -EIO; + if (0 != t2_out(PM_RECEIVER, ENABLE_VPLL2_DEV_GRP, + TWL4030_VPLL2_DEV_GRP)) + return -EIO; + + gpio_direction_output(LCD_PANEL_ENABLE_GPIO, 1); + gpio_direction_output(LCD_PANEL_BACKLIGHT_GPIO, 1); + + if (0 != t2_out(PM_RECEIVER, ENABLE_VAUX3_DEDICATED, + TWL4030_VAUX3_DEDICATED)) + return -EIO; + if (0 != t2_out(PM_RECEIVER, ENABLE_VAUX3_DEV_GRP, + TWL4030_VAUX3_DEV_GRP)) + return -EIO; + + return 0; +} + +static void ldp_panel_disable(struct lcd_panel *panel) +{ + gpio_direction_output(LCD_PANEL_ENABLE_GPIO, 0); + gpio_direction_output(LCD_PANEL_BACKLIGHT_GPIO, 0); + + t2_out(PM_RECEIVER, 0x0, TWL4030_VPLL2_DEDICATED); + t2_out(PM_RECEIVER, 0x0, TWL4030_VPLL2_DEV_GRP); + mdelay(4); +} + +static unsigned long ldp_panel_get_caps(struct lcd_panel *panel) +{ + return 0; +} + +struct lcd_panel ldp_panel = { + .name = "ldp", + .config = OMAP_LCDC_PANEL_TFT | OMAP_LCDC_INV_VSYNC | + OMAP_LCDC_INV_HSYNC, + + .bpp = 16, + .data_lines = 18, + .x_res = LCD_XRES, + .y_res = LCD_YRES, + .hsw = 3, /* hsync_len (4) - 1 */ + .hfp = 3, /* right_margin (4) - 1 */ + .hbp = 39, /* left_margin (40) - 1 */ + .vsw = 1, /* vsync_len (2) - 1 */ + .vfp = 2, /* lower_margin */ + .vbp = 7, /* upper_margin (8) - 1 */ + + .pixel_clock = LCD_PIXCLOCK_MAX, + + .init = ldp_panel_init, + .cleanup = ldp_panel_cleanup, + .enable = ldp_panel_enable, + .disable = ldp_panel_disable, + .get_caps = ldp_panel_get_caps, +}; + +static int ldp_panel_probe(struct platform_device *pdev) +{ + omapfb_register_panel(&ldp_panel); + return 0; +} + +static int ldp_panel_remove(struct platform_device *pdev) +{ + return 0; +} + +static int ldp_panel_suspend(struct platform_device *pdev, pm_message_t mesg) +{ + return 0; +} + +static int ldp_panel_resume(struct platform_device *pdev) +{ + return 0; +} + +struct platform_driver ldp_panel_driver = { + .probe = ldp_panel_probe, + .remove = ldp_panel_remove, + .suspend = ldp_panel_suspend, + .resume = ldp_panel_resume, + .driver = { + .name = "ldp_lcd", + .owner = THIS_MODULE, + }, +}; + +static int __init ldp_panel_drv_init(void) +{ + return platform_driver_register(&ldp_panel_driver); +} + +static void __exit ldp_panel_drv_exit(void) +{ + platform_driver_unregister(&ldp_panel_driver); +} + +module_init(ldp_panel_drv_init); +module_exit(ldp_panel_drv_exit); diff --git a/drivers/video/omap/lcd_mipid.c b/drivers/video/omap/lcd_mipid.c new file mode 100644 index 00000000000..1895997d8d8 --- /dev/null +++ b/drivers/video/omap/lcd_mipid.c @@ -0,0 +1,617 @@ +/* + * LCD driver for MIPI DBI-C / DCS compatible LCDs + * + * Copyright (C) 2006 Nokia Corporation + * Author: Imre Deak + * + * 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 program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include +#include +#include +#include + +#include +#include + +#include "../../cbus/tahvo.h" + +#define MIPID_MODULE_NAME "lcd_mipid" + +#define MIPID_CMD_READ_DISP_ID 0x04 +#define MIPID_CMD_READ_RED 0x06 +#define MIPID_CMD_READ_GREEN 0x07 +#define MIPID_CMD_READ_BLUE 0x08 +#define MIPID_CMD_READ_DISP_STATUS 0x09 +#define MIPID_CMD_RDDSDR 0x0F +#define MIPID_CMD_SLEEP_IN 0x10 +#define MIPID_CMD_SLEEP_OUT 0x11 +#define MIPID_CMD_DISP_OFF 0x28 +#define MIPID_CMD_DISP_ON 0x29 + +#define MIPID_VER_LPH8923 3 +#define MIPID_VER_LS041Y3 4 + +#define MIPID_ESD_CHECK_PERIOD msecs_to_jiffies(5000) + +#define to_mipid_device(p) container_of(p, struct mipid_device, \ + panel) +struct mipid_device { + int enabled; + int model; + int revision; + u8 display_id[3]; + unsigned int saved_bklight_level; + unsigned long hw_guard_end; /* next value of jiffies + when we can issue the + next sleep in/out command */ + unsigned long hw_guard_wait; /* max guard time in jiffies */ + + struct omapfb_device *fbdev; + struct spi_device *spi; + struct mutex mutex; + struct lcd_panel panel; + + struct workqueue_struct *esd_wq; + struct delayed_work esd_work; + void (*esd_check)(struct mipid_device *m); +}; + +static void mipid_transfer(struct mipid_device *md, int cmd, const u8 *wbuf, + int wlen, u8 *rbuf, int rlen) +{ + struct spi_message m; + struct spi_transfer *x, xfer[4]; + u16 w; + int r; + + BUG_ON(md->spi == NULL); + + spi_message_init(&m); + + memset(xfer, 0, sizeof(xfer)); + x = &xfer[0]; + + cmd &= 0xff; + x->tx_buf = &cmd; + x->bits_per_word= 9; + x->len = 2; + spi_message_add_tail(x, &m); + + if (wlen) { + x++; + x->tx_buf = wbuf; + x->len = wlen; + x->bits_per_word= 9; + spi_message_add_tail(x, &m); + } + + if (rlen) { + x++; + x->rx_buf = &w; + x->len = 1; + spi_message_add_tail(x, &m); + + if (rlen > 1) { + /* Arrange for the extra clock before the first + * data bit. + */ + x->bits_per_word = 9; + x->len = 2; + + x++; + x->rx_buf = &rbuf[1]; + x->len = rlen - 1; + spi_message_add_tail(x, &m); + } + } + + r = spi_sync(md->spi, &m); + if (r < 0) + dev_dbg(&md->spi->dev, "spi_sync %d\n", r); + + if (rlen) + rbuf[0] = w & 0xff; +} + +static inline void mipid_cmd(struct mipid_device *md, int cmd) +{ + mipid_transfer(md, cmd, NULL, 0, NULL, 0); +} + +static inline void mipid_write(struct mipid_device *md, + int reg, const u8 *buf, int len) +{ + mipid_transfer(md, reg, buf, len, NULL, 0); +} + +static inline void mipid_read(struct mipid_device *md, + int reg, u8 *buf, int len) +{ + mipid_transfer(md, reg, NULL, 0, buf, len); +} + +static void set_data_lines(struct mipid_device *md, int data_lines) +{ + u16 par; + + switch (data_lines) { + case 16: + par = 0x150; + break; + case 18: + par = 0x160; + break; + case 24: + par = 0x170; + break; + } + mipid_write(md, 0x3a, (u8 *)&par, 2); +} + +static void send_init_string(struct mipid_device *md) +{ + u16 initpar[] = { 0x0102, 0x0100, 0x0100 }; + + mipid_write(md, 0xc2, (u8 *)initpar, sizeof(initpar)); + set_data_lines(md, md->panel.data_lines); +} + +static void hw_guard_start(struct mipid_device *md, int guard_msec) +{ + md->hw_guard_wait = msecs_to_jiffies(guard_msec); + md->hw_guard_end = jiffies + md->hw_guard_wait; +} + +static void hw_guard_wait(struct mipid_device *md) +{ + unsigned long wait = md->hw_guard_end - jiffies; + + if ((long)wait > 0 && wait <= md->hw_guard_wait) { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(wait); + } +} + +static void set_sleep_mode(struct mipid_device *md, int on) +{ + int cmd, sleep_time = 50; + + if (on) + cmd = MIPID_CMD_SLEEP_IN; + else + cmd = MIPID_CMD_SLEEP_OUT; + hw_guard_wait(md); + mipid_cmd(md, cmd); + hw_guard_start(md, 120); + /* + * When we enable the panel, it seems we _have_ to sleep + * 120 ms before sending the init string. When disabling the + * panel we'll sleep for the duration of 2 frames, so that the + * controller can still provide the PCLK,HS,VS signals. */ + if (!on) + sleep_time = 120; + msleep(sleep_time); +} + +static void set_display_state(struct mipid_device *md, int enabled) +{ + int cmd = enabled ? MIPID_CMD_DISP_ON : MIPID_CMD_DISP_OFF; + + mipid_cmd(md, cmd); +} + +static int mipid_set_bklight_level(struct lcd_panel *panel, unsigned int level) +{ + struct mipid_device *md = to_mipid_device(panel); + + if (level > tahvo_get_max_backlight_level()) + return -EINVAL; + if (!md->enabled) { + md->saved_bklight_level = level; + return 0; + } + tahvo_set_backlight_level(level); + + return 0; +} + +static unsigned int mipid_get_bklight_level(struct lcd_panel *panel) +{ + return tahvo_get_backlight_level(); +} + +static unsigned int mipid_get_bklight_max(struct lcd_panel *panel) +{ + return tahvo_get_max_backlight_level(); +} + + +static unsigned long mipid_get_caps(struct lcd_panel *panel) +{ + return OMAPFB_CAPS_SET_BACKLIGHT; +} + +static u16 read_first_pixel(struct mipid_device *md) +{ + u16 pixel; + u8 red, green, blue; + + mutex_lock(&md->mutex); + mipid_read(md, MIPID_CMD_READ_RED, &red, 1); + mipid_read(md, MIPID_CMD_READ_GREEN, &green, 1); + mipid_read(md, MIPID_CMD_READ_BLUE, &blue, 1); + mutex_unlock(&md->mutex); + + switch (md->panel.data_lines) { + case 16: + pixel = ((red >> 1) << 11) | (green << 5) | (blue >> 1); + break; + case 24: + /* 24 bit -> 16 bit */ + pixel = ((red >> 3) << 11) | ((green >> 2) << 5) | + (blue >> 3); + break; + default: + BUG(); + } + + return pixel; +} + +static int mipid_run_test(struct lcd_panel *panel, int test_num) +{ + struct mipid_device *md = to_mipid_device(panel); + static const u16 test_values[4] = { + 0x0000, 0xffff, 0xaaaa, 0x5555, + }; + int i; + + if (test_num != MIPID_TEST_RGB_LINES) + return MIPID_TEST_INVALID; + + for (i = 0; i < ARRAY_SIZE(test_values); i++) { + int delay; + unsigned long tmo; + + omapfb_write_first_pixel(md->fbdev, test_values[i]); + tmo = jiffies + msecs_to_jiffies(100); + delay = 25; + while (1) { + u16 pixel; + + msleep(delay); + pixel = read_first_pixel(md); + if (pixel == test_values[i]) + break; + if (time_after(jiffies, tmo)) { + dev_err(&md->spi->dev, + "MIPI LCD RGB I/F test failed: " + "expecting %04x, got %04x\n", + test_values[i], pixel); + return MIPID_TEST_FAILED; + } + delay = 10; + } + } + + return 0; +} + +static void ls041y3_esd_recover(struct mipid_device *md) +{ + dev_err(&md->spi->dev, "performing LCD ESD recovery\n"); + set_sleep_mode(md, 1); + set_sleep_mode(md, 0); +} + +static void ls041y3_esd_check_mode1(struct mipid_device *md) +{ + u8 state1, state2; + + mipid_read(md, MIPID_CMD_RDDSDR, &state1, 1); + set_sleep_mode(md, 0); + mipid_read(md, MIPID_CMD_RDDSDR, &state2, 1); + dev_dbg(&md->spi->dev, "ESD mode 1 state1 %02x state2 %02x\n", + state1, state2); + /* Each sleep out command will trigger a self diagnostic and flip + * Bit6 if the test passes. + */ + if (!((state1 ^ state2) & (1 << 6))) + ls041y3_esd_recover(md); +} + +static void ls041y3_esd_check_mode2(struct mipid_device *md) +{ + int i; + u8 rbuf[2]; + static const struct { + int cmd; + int wlen; + u16 wbuf[3]; + } *rd, rd_ctrl[7] = { + { 0xb0, 4, { 0x0101, 0x01fe, } }, + { 0xb1, 4, { 0x01de, 0x0121, } }, + { 0xc2, 4, { 0x0100, 0x0100, } }, + { 0xbd, 2, { 0x0100, } }, + { 0xc2, 4, { 0x01fc, 0x0103, } }, + { 0xb4, 0, }, + { 0x00, 0, }, + }; + + rd = rd_ctrl; + for (i = 0; i < 3; i++, rd++) + mipid_write(md, rd->cmd, (u8 *)rd->wbuf, rd->wlen); + + udelay(10); + mipid_read(md, rd->cmd, rbuf, 2); + rd++; + + for (i = 0; i < 3; i++, rd++) { + udelay(10); + mipid_write(md, rd->cmd, (u8 *)rd->wbuf, rd->wlen); + } + + dev_dbg(&md->spi->dev, "ESD mode 2 state %02x\n", rbuf[1]); + if (rbuf[1] == 0x00) + ls041y3_esd_recover(md); +} + +static void ls041y3_esd_check(struct mipid_device *md) +{ + ls041y3_esd_check_mode1(md); + if (md->revision >= 0x88) + ls041y3_esd_check_mode2(md); +} + +static void mipid_esd_start_check(struct mipid_device *md) +{ + if (md->esd_check != NULL) + queue_delayed_work(md->esd_wq, &md->esd_work, + MIPID_ESD_CHECK_PERIOD); +} + +static void mipid_esd_stop_check(struct mipid_device *md) +{ + if (md->esd_check != NULL) + cancel_rearming_delayed_workqueue(md->esd_wq, &md->esd_work); +} + +static void mipid_esd_work(struct work_struct *work) +{ + struct mipid_device *md = container_of(work, struct mipid_device, esd_work.work); + + mutex_lock(&md->mutex); + md->esd_check(md); + mutex_unlock(&md->mutex); + mipid_esd_start_check(md); +} + +static int mipid_enable(struct lcd_panel *panel) +{ + struct mipid_device *md = to_mipid_device(panel); + + mutex_lock(&md->mutex); + + if (md->enabled) { + mutex_unlock(&md->mutex); + return 0; + } + set_sleep_mode(md, 0); + md->enabled = 1; + send_init_string(md); + set_display_state(md, 1); + mipid_set_bklight_level(panel, md->saved_bklight_level); + mipid_esd_start_check(md); + + mutex_unlock(&md->mutex); + return 0; +} + +static void mipid_disable(struct lcd_panel *panel) +{ + struct mipid_device *md = to_mipid_device(panel); + + /* + * A final ESD work might be called before returning, + * so do this without holding the lock. + */ + mipid_esd_stop_check(md); + mutex_lock(&md->mutex); + + if (!md->enabled) { + mutex_unlock(&md->mutex); + return; + } + md->saved_bklight_level = mipid_get_bklight_level(panel); + mipid_set_bklight_level(panel, 0); + set_display_state(md, 0); + set_sleep_mode(md, 1); + md->enabled = 0; + + mutex_unlock(&md->mutex); +} + +static int panel_enabled(struct mipid_device *md) +{ + u32 disp_status; + int enabled; + + mipid_read(md, MIPID_CMD_READ_DISP_STATUS, (u8 *)&disp_status, 4); + disp_status = __be32_to_cpu(disp_status); + enabled = (disp_status & (1 << 17)) && (disp_status & (1 << 10)); + dev_dbg(&md->spi->dev, + "LCD panel %senabled by bootloader (status 0x%04x)\n", + enabled ? "" : "not ", disp_status); + return enabled; +} + +static int mipid_init(struct lcd_panel *panel, + struct omapfb_device *fbdev) +{ + struct mipid_device *md = to_mipid_device(panel); + + md->fbdev = fbdev; + md->esd_wq = create_singlethread_workqueue("mipid_esd"); + if (md->esd_wq == NULL) { + dev_err(&md->spi->dev, "can't create ESD workqueue\n"); + return -ENOMEM; + } + INIT_DELAYED_WORK(&md->esd_work, mipid_esd_work); + mutex_init(&md->mutex); + + md->enabled = panel_enabled(md); + + if (md->enabled) + mipid_esd_start_check(md); + else + md->saved_bklight_level = mipid_get_bklight_level(panel); + + return 0; +} + +static void mipid_cleanup(struct lcd_panel *panel) +{ + struct mipid_device *md = to_mipid_device(panel); + + if (md->enabled) + mipid_esd_stop_check(md); + destroy_workqueue(md->esd_wq); +} + +static struct lcd_panel mipid_panel = { + .config = OMAP_LCDC_PANEL_TFT, + + .bpp = 16, + .x_res = 800, + .y_res = 480, + .pixel_clock = 21940, + .hsw = 50, + .hfp = 20, + .hbp = 15, + .vsw = 2, + .vfp = 1, + .vbp = 3, + + .init = mipid_init, + .cleanup = mipid_cleanup, + .enable = mipid_enable, + .disable = mipid_disable, + .get_caps = mipid_get_caps, + .set_bklight_level= mipid_set_bklight_level, + .get_bklight_level= mipid_get_bklight_level, + .get_bklight_max= mipid_get_bklight_max, + .run_test = mipid_run_test, +}; + +static int mipid_detect(struct mipid_device *md) +{ + struct mipid_platform_data *pdata; + + pdata = md->spi->dev.platform_data; + if (pdata == NULL) { + dev_err(&md->spi->dev, "missing platform data\n"); + return -ENOENT; + } + + mipid_read(md, MIPID_CMD_READ_DISP_ID, md->display_id, 3); + dev_dbg(&md->spi->dev, "MIPI display ID: %02x%02x%02x\n", + md->display_id[0], md->display_id[1], md->display_id[2]); + + switch (md->display_id[0]) { + case 0x45: + md->model = MIPID_VER_LPH8923; + md->panel.name = "lph8923"; + break; + case 0x83: + md->model = MIPID_VER_LS041Y3; + md->panel.name = "ls041y3"; + md->esd_check = ls041y3_esd_check; + break; + default: + md->panel.name = "unknown"; + dev_err(&md->spi->dev, "invalid display ID\n"); + return -ENODEV; + } + + md->revision = md->display_id[1]; + md->panel.data_lines = pdata->data_lines; + pr_info("omapfb: %s rev %02x LCD detected\n", + md->panel.name, md->revision); + + return 0; +} + +static int mipid_spi_probe(struct spi_device *spi) +{ + struct mipid_device *md; + int r; + + md = kzalloc(sizeof(*md), GFP_KERNEL); + if (md == NULL) { + dev_err(&spi->dev, "out of memory\n"); + return -ENOMEM; + } + + spi->mode = SPI_MODE_0; + md->spi = spi; + dev_set_drvdata(&spi->dev, md); + md->panel = mipid_panel; + + r = mipid_detect(md); + if (r < 0) + return r; + + omapfb_register_panel(&md->panel); + + return 0; +} + +static int mipid_spi_remove(struct spi_device *spi) +{ + struct mipid_device *md = dev_get_drvdata(&spi->dev); + + mipid_disable(&md->panel); + kfree(md); + + return 0; +} + +static struct spi_driver mipid_spi_driver = { + .driver = { + .name = MIPID_MODULE_NAME, + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = mipid_spi_probe, + .remove = __devexit_p(mipid_spi_remove), +}; + +static int mipid_drv_init(void) +{ + spi_register_driver(&mipid_spi_driver); + + return 0; +} +module_init(mipid_drv_init); + +static void mipid_drv_cleanup(void) +{ + spi_unregister_driver(&mipid_spi_driver); +} +module_exit(mipid_drv_cleanup); + +MODULE_DESCRIPTION("MIPI display driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/omap/lcd_omap2evm.c b/drivers/video/omap/lcd_omap2evm.c new file mode 100644 index 00000000000..2fc46c2bd77 --- /dev/null +++ b/drivers/video/omap/lcd_omap2evm.c @@ -0,0 +1,189 @@ +/* + * LCD panel support for the MISTRAL OMAP2EVM board + * + * Author: Arun C + * + * Derived from drivers/video/omap/lcd_omap3evm.c + * Derived from drivers/video/omap/lcd-apollon.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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include + +#include +#include +#include + +#define LCD_PANEL_ENABLE_GPIO 154 +#define LCD_PANEL_LR 128 +#define LCD_PANEL_UD 129 +#define LCD_PANEL_INI 152 +#define LCD_PANEL_QVGA 148 +#define LCD_PANEL_RESB 153 + +#define LCD_XRES 480 +#define LCD_YRES 640 +#define LCD_PIXCLOCK_MAX 20000 /* in kHz */ + +#define TWL_LED_LEDEN 0x00 +#define TWL_PWMA_PWMAON 0x00 +#define TWL_PWMA_PWMAOFF 0x01 + +static unsigned int bklight_level; + +static int omap2evm_panel_init(struct lcd_panel *panel, + struct omapfb_device *fbdev) +{ + gpio_request(LCD_PANEL_ENABLE_GPIO, "LCD enable"); + gpio_request(LCD_PANEL_LR, "LCD lr"); + gpio_request(LCD_PANEL_UD, "LCD ud"); + gpio_request(LCD_PANEL_INI, "LCD ini"); + gpio_request(LCD_PANEL_QVGA, "LCD qvga"); + gpio_request(LCD_PANEL_RESB, "LCD resb"); + + gpio_direction_output(LCD_PANEL_ENABLE_GPIO, 1); + gpio_direction_output(LCD_PANEL_RESB, 1); + gpio_direction_output(LCD_PANEL_INI, 1); + gpio_direction_output(LCD_PANEL_QVGA, 0); + gpio_direction_output(LCD_PANEL_LR, 1); + gpio_direction_output(LCD_PANEL_UD, 1); + + twl4030_i2c_write_u8(TWL4030_MODULE_LED, 0x11, TWL_LED_LEDEN); + twl4030_i2c_write_u8(TWL4030_MODULE_PWMA, 0x01, TWL_PWMA_PWMAON); + twl4030_i2c_write_u8(TWL4030_MODULE_PWMA, 0x02, TWL_PWMA_PWMAOFF); + bklight_level = 100; + + return 0; +} + +static void omap2evm_panel_cleanup(struct lcd_panel *panel) +{ +} + +static int omap2evm_panel_enable(struct lcd_panel *panel) +{ + gpio_set_value(LCD_PANEL_ENABLE_GPIO, 0); + return 0; +} + +static void omap2evm_panel_disable(struct lcd_panel *panel) +{ + gpio_set_value(LCD_PANEL_ENABLE_GPIO, 1); +} + +static unsigned long omap2evm_panel_get_caps(struct lcd_panel *panel) +{ + return 0; +} + +static int omap2evm_bklight_setlevel(struct lcd_panel *panel, + unsigned int level) +{ + u8 c; + if ((level >= 0) && (level <= 100)) { + c = (125 * (100 - level)) / 100 + 2; + twl4030_i2c_write_u8(TWL4030_MODULE_PWMA, c, TWL_PWMA_PWMAOFF); + bklight_level = level; + } + return 0; +} + +static unsigned int omap2evm_bklight_getlevel(struct lcd_panel *panel) +{ + return bklight_level; +} + +static unsigned int omap2evm_bklight_getmaxlevel(struct lcd_panel *panel) +{ + return 100; +} + +struct lcd_panel omap2evm_panel = { + .name = "omap2evm", + .config = OMAP_LCDC_PANEL_TFT | OMAP_LCDC_INV_VSYNC | + OMAP_LCDC_INV_HSYNC, + + .bpp = 16, + .data_lines = 18, + .x_res = LCD_XRES, + .y_res = LCD_YRES, + .hsw = 3, + .hfp = 0, + .hbp = 28, + .vsw = 2, + .vfp = 1, + .vbp = 0, + + .pixel_clock = LCD_PIXCLOCK_MAX, + + .init = omap2evm_panel_init, + .cleanup = omap2evm_panel_cleanup, + .enable = omap2evm_panel_enable, + .disable = omap2evm_panel_disable, + .get_caps = omap2evm_panel_get_caps, + .set_bklight_level = omap2evm_bklight_setlevel, + .get_bklight_level = omap2evm_bklight_getlevel, + .get_bklight_max = omap2evm_bklight_getmaxlevel, +}; + +static int omap2evm_panel_probe(struct platform_device *pdev) +{ + omapfb_register_panel(&omap2evm_panel); + return 0; +} + +static int omap2evm_panel_remove(struct platform_device *pdev) +{ + return 0; +} + +static int omap2evm_panel_suspend(struct platform_device *pdev, + pm_message_t mesg) +{ + return 0; +} + +static int omap2evm_panel_resume(struct platform_device *pdev) +{ + return 0; +} + +struct platform_driver omap2evm_panel_driver = { + .probe = omap2evm_panel_probe, + .remove = omap2evm_panel_remove, + .suspend = omap2evm_panel_suspend, + .resume = omap2evm_panel_resume, + .driver = { + .name = "omap2evm_lcd", + .owner = THIS_MODULE, + }, +}; + +static int __init omap2evm_panel_drv_init(void) +{ + return platform_driver_register(&omap2evm_panel_driver); +} + +static void __exit omap2evm_panel_drv_exit(void) +{ + platform_driver_unregister(&omap2evm_panel_driver); +} + +module_init(omap2evm_panel_drv_init); +module_exit(omap2evm_panel_drv_exit); diff --git a/drivers/video/omap/lcd_omap3beagle.c b/drivers/video/omap/lcd_omap3beagle.c new file mode 100644 index 00000000000..eae43e4c265 --- /dev/null +++ b/drivers/video/omap/lcd_omap3beagle.c @@ -0,0 +1,133 @@ +/* + * LCD panel support for the TI OMAP3 Beagle board + * + * Author: Koen Kooi + * + * Derived from drivers/video/omap/lcd-omap3evm.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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include + +#include +#include +#include + +#define LCD_PANEL_ENABLE_GPIO 170 + +#define LCD_XRES 1024 +#define LCD_YRES 768 +#define LCD_PIXCLOCK 64000 /* in kHz */ + +static int omap3beagle_panel_init(struct lcd_panel *panel, + struct omapfb_device *fbdev) +{ + gpio_request(LCD_PANEL_ENABLE_GPIO, "LCD enable"); + return 0; +} + +static void omap3beagle_panel_cleanup(struct lcd_panel *panel) +{ +} + +static int omap3beagle_panel_enable(struct lcd_panel *panel) +{ + gpio_set_value(LCD_PANEL_ENABLE_GPIO, 1); + return 0; +} + +static void omap3beagle_panel_disable(struct lcd_panel *panel) +{ + gpio_set_value(LCD_PANEL_ENABLE_GPIO, 0); +} + +static unsigned long omap3beagle_panel_get_caps(struct lcd_panel *panel) +{ + return 0; +} + +struct lcd_panel omap3beagle_panel = { + .name = "omap3beagle", + .config = OMAP_LCDC_PANEL_TFT, + + .bpp = 16, + .data_lines = 24, + .x_res = LCD_XRES, + .y_res = LCD_YRES, + .hsw = 3, /* hsync_len (4) - 1 */ + .hfp = 3, /* right_margin (4) - 1 */ + .hbp = 39, /* left_margin (40) - 1 */ + .vsw = 1, /* vsync_len (2) - 1 */ + .vfp = 2, /* lower_margin */ + .vbp = 7, /* upper_margin (8) - 1 */ + + .pixel_clock = LCD_PIXCLOCK, + + .init = omap3beagle_panel_init, + .cleanup = omap3beagle_panel_cleanup, + .enable = omap3beagle_panel_enable, + .disable = omap3beagle_panel_disable, + .get_caps = omap3beagle_panel_get_caps, +}; + +static int omap3beagle_panel_probe(struct platform_device *pdev) +{ + omapfb_register_panel(&omap3beagle_panel); + return 0; +} + +static int omap3beagle_panel_remove(struct platform_device *pdev) +{ + return 0; +} + +static int omap3beagle_panel_suspend(struct platform_device *pdev, + pm_message_t mesg) +{ + return 0; +} + +static int omap3beagle_panel_resume(struct platform_device *pdev) +{ + return 0; +} + +struct platform_driver omap3beagle_panel_driver = { + .probe = omap3beagle_panel_probe, + .remove = omap3beagle_panel_remove, + .suspend = omap3beagle_panel_suspend, + .resume = omap3beagle_panel_resume, + .driver = { + .name = "omap3beagle_lcd", + .owner = THIS_MODULE, + }, +}; + +static int __init omap3beagle_panel_drv_init(void) +{ + return platform_driver_register(&omap3beagle_panel_driver); +} + +static void __exit omap3beagle_panel_drv_exit(void) +{ + platform_driver_unregister(&omap3beagle_panel_driver); +} + +module_init(omap3beagle_panel_drv_init); +module_exit(omap3beagle_panel_drv_exit); diff --git a/drivers/video/omap/lcd_omap3evm.c b/drivers/video/omap/lcd_omap3evm.c new file mode 100644 index 00000000000..1c3d814f9ff --- /dev/null +++ b/drivers/video/omap/lcd_omap3evm.c @@ -0,0 +1,191 @@ +/* + * LCD panel support for the TI OMAP3 EVM board + * + * Author: Steve Sakoman + * + * Derived from drivers/video/omap/lcd-apollon.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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include + +#include +#include +#include + +#define LCD_PANEL_ENABLE_GPIO 153 +#define LCD_PANEL_LR 2 +#define LCD_PANEL_UD 3 +#define LCD_PANEL_INI 152 +#define LCD_PANEL_QVGA 154 +#define LCD_PANEL_RESB 155 + +#define LCD_XRES 480 +#define LCD_YRES 640 +#define LCD_PIXCLOCK 26000 /* in kHz */ + +#define ENABLE_VDAC_DEDICATED 0x03 +#define ENABLE_VDAC_DEV_GRP 0x20 +#define ENABLE_VPLL2_DEDICATED 0x05 +#define ENABLE_VPLL2_DEV_GRP 0xE0 + +#define TWL_LED_LEDEN 0x00 +#define TWL_PWMA_PWMAON 0x00 +#define TWL_PWMA_PWMAOFF 0x01 + +static unsigned int bklight_level; + +static int omap3evm_panel_init(struct lcd_panel *panel, + struct omapfb_device *fbdev) +{ + gpio_request(LCD_PANEL_LR, "LCD lr"); + gpio_request(LCD_PANEL_UD, "LCD ud"); + gpio_request(LCD_PANEL_INI, "LCD ini"); + gpio_request(LCD_PANEL_RESB, "LCD resb"); + gpio_request(LCD_PANEL_QVGA, "LCD qvga"); + + gpio_direction_output(LCD_PANEL_RESB, 1); + gpio_direction_output(LCD_PANEL_INI, 1); + gpio_direction_output(LCD_PANEL_QVGA, 0); + gpio_direction_output(LCD_PANEL_LR, 1); + gpio_direction_output(LCD_PANEL_UD, 1); + + twl4030_i2c_write_u8(TWL4030_MODULE_LED, 0x11, TWL_LED_LEDEN); + twl4030_i2c_write_u8(TWL4030_MODULE_PWMA, 0x01, TWL_PWMA_PWMAON); + twl4030_i2c_write_u8(TWL4030_MODULE_PWMA, 0x02, TWL_PWMA_PWMAOFF); + bklight_level = 100; + + return 0; +} + +static void omap3evm_panel_cleanup(struct lcd_panel *panel) +{ +} + +static int omap3evm_panel_enable(struct lcd_panel *panel) +{ + gpio_set_value(LCD_PANEL_ENABLE_GPIO, 0); + return 0; +} + +static void omap3evm_panel_disable(struct lcd_panel *panel) +{ + gpio_set_value(LCD_PANEL_ENABLE_GPIO, 1); +} + +static unsigned long omap3evm_panel_get_caps(struct lcd_panel *panel) +{ + return 0; +} + +static int omap3evm_bklight_setlevel(struct lcd_panel *panel, + unsigned int level) +{ + u8 c; + if ((level >= 0) && (level <= 100)) { + c = (125 * (100 - level)) / 100 + 2; + twl4030_i2c_write_u8(TWL4030_MODULE_PWMA, c, TWL_PWMA_PWMAOFF); + bklight_level = level; + } + return 0; +} + +static unsigned int omap3evm_bklight_getlevel(struct lcd_panel *panel) +{ + return bklight_level; +} + +static unsigned int omap3evm_bklight_getmaxlevel(struct lcd_panel *panel) +{ + return 100; +} + +struct lcd_panel omap3evm_panel = { + .name = "omap3evm", + .config = OMAP_LCDC_PANEL_TFT | OMAP_LCDC_INV_VSYNC | + OMAP_LCDC_INV_HSYNC, + + .bpp = 16, + .data_lines = 18, + .x_res = LCD_XRES, + .y_res = LCD_YRES, + .hsw = 3, /* hsync_len (4) - 1 */ + .hfp = 3, /* right_margin (4) - 1 */ + .hbp = 39, /* left_margin (40) - 1 */ + .vsw = 1, /* vsync_len (2) - 1 */ + .vfp = 2, /* lower_margin */ + .vbp = 7, /* upper_margin (8) - 1 */ + + .pixel_clock = LCD_PIXCLOCK, + + .init = omap3evm_panel_init, + .cleanup = omap3evm_panel_cleanup, + .enable = omap3evm_panel_enable, + .disable = omap3evm_panel_disable, + .get_caps = omap3evm_panel_get_caps, + .set_bklight_level = omap3evm_bklight_setlevel, + .get_bklight_level = omap3evm_bklight_getlevel, + .get_bklight_max = omap3evm_bklight_getmaxlevel, +}; + +static int omap3evm_panel_probe(struct platform_device *pdev) +{ + omapfb_register_panel(&omap3evm_panel); + return 0; +} + +static int omap3evm_panel_remove(struct platform_device *pdev) +{ + return 0; +} + +static int omap3evm_panel_suspend(struct platform_device *pdev, + pm_message_t mesg) +{ + return 0; +} + +static int omap3evm_panel_resume(struct platform_device *pdev) +{ + return 0; +} + +struct platform_driver omap3evm_panel_driver = { + .probe = omap3evm_panel_probe, + .remove = omap3evm_panel_remove, + .suspend = omap3evm_panel_suspend, + .resume = omap3evm_panel_resume, + .driver = { + .name = "omap3evm_lcd", + .owner = THIS_MODULE, + }, +}; + +static int __init omap3evm_panel_drv_init(void) +{ + return platform_driver_register(&omap3evm_panel_driver); +} + +static void __exit omap3evm_panel_drv_exit(void) +{ + platform_driver_unregister(&omap3evm_panel_driver); +} + +module_init(omap3evm_panel_drv_init); +module_exit(omap3evm_panel_drv_exit); diff --git a/drivers/video/omap/lcd_overo.c b/drivers/video/omap/lcd_overo.c new file mode 100644 index 00000000000..2bc5c9268e5 --- /dev/null +++ b/drivers/video/omap/lcd_overo.c @@ -0,0 +1,179 @@ +/* + * LCD panel support for the Gumstix Overo + * + * Author: Steve Sakoman + * + * 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 program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include +#include +#include + +#include +#include +#include +#include + +#define LCD_ENABLE 144 + +static int overo_panel_init(struct lcd_panel *panel, + struct omapfb_device *fbdev) +{ + if ((gpio_request(LCD_ENABLE, "LCD_ENABLE") == 0) && + (gpio_direction_output(LCD_ENABLE, 1) == 0)) + gpio_export(LCD_ENABLE, 0); + else + printk(KERN_ERR "could not obtain gpio for LCD_ENABLE\n"); + + return 0; +} + +static void overo_panel_cleanup(struct lcd_panel *panel) +{ + gpio_free(LCD_ENABLE); +} + +static int overo_panel_enable(struct lcd_panel *panel) +{ + gpio_set_value(LCD_ENABLE, 1); + return 0; +} + +static void overo_panel_disable(struct lcd_panel *panel) +{ + gpio_set_value(LCD_ENABLE, 0); +} + +static unsigned long overo_panel_get_caps(struct lcd_panel *panel) +{ + return 0; +} + +struct lcd_panel overo_panel = { + .name = "overo", + .config = OMAP_LCDC_PANEL_TFT, + .bpp = 16, + .data_lines = 24, + +#if defined CONFIG_FB_OMAP_031M3R + + /* 640 x 480 @ 60 Hz Reduced blanking VESA CVT 0.31M3-R */ + .x_res = 640, + .y_res = 480, + .hfp = 48, + .hsw = 32, + .hbp = 80, + .vfp = 3, + .vsw = 4, + .vbp = 7, + .pixel_clock = 23500, + +#elif defined CONFIG_FB_OMAP_048M3R + + /* 800 x 600 @ 60 Hz Reduced blanking VESA CVT 0.48M3-R */ + .x_res = 800, + .y_res = 600, + .hfp = 48, + .hsw = 32, + .hbp = 80, + .vfp = 3, + .vsw = 4, + .vbp = 11, + .pixel_clock = 35500, + +#elif defined CONFIG_FB_OMAP_079M3R + + /* 1024 x 768 @ 60 Hz Reduced blanking VESA CVT 0.79M3-R */ + .x_res = 1024, + .y_res = 768, + .hfp = 48, + .hsw = 32, + .hbp = 80, + .vfp = 3, + .vsw = 4, + .vbp = 15, + .pixel_clock = 56000, + +#elif defined CONFIG_FB_OMAP_092M9R + + /* 1280 x 720 @ 60 Hz Reduced blanking VESA CVT 0.92M9-R */ + .x_res = 1280, + .y_res = 720, + .hfp = 48, + .hsw = 32, + .hbp = 80, + .vfp = 3, + .vsw = 5, + .vbp = 13, + .pixel_clock = 64000, + +#else + + /* use 640 x 480 if no config option */ + /* 640 x 480 @ 60 Hz Reduced blanking VESA CVT 0.31M3-R */ + .x_res = 640, + .y_res = 480, + .hfp = 48, + .hsw = 32, + .hbp = 80, + .vfp = 3, + .vsw = 4, + .vbp = 7, + .pixel_clock = 23500, + +#endif + + .init = overo_panel_init, + .cleanup = overo_panel_cleanup, + .enable = overo_panel_enable, + .disable = overo_panel_disable, + .get_caps = overo_panel_get_caps, +}; + +static int overo_panel_probe(struct platform_device *pdev) +{ + omapfb_register_panel(&overo_panel); + return 0; +} + +static int overo_panel_remove(struct platform_device *pdev) +{ + /* omapfb does not have unregister_panel */ + return 0; +} + +static struct platform_driver overo_panel_driver = { + .probe = overo_panel_probe, + .remove = overo_panel_remove, + .driver = { + .name = "overo_lcd", + .owner = THIS_MODULE, + }, +}; + +static int __init overo_panel_drv_init(void) +{ + return platform_driver_register(&overo_panel_driver); +} + +static void __exit overo_panel_drv_exit(void) +{ + platform_driver_unregister(&overo_panel_driver); +} + +module_init(overo_panel_drv_init); +module_exit(overo_panel_drv_exit); diff --git a/drivers/video/omap/lcd_p2.c b/drivers/video/omap/lcd_p2.c new file mode 100644 index 00000000000..dd40fd7e6ba --- /dev/null +++ b/drivers/video/omap/lcd_p2.c @@ -0,0 +1,340 @@ +/* + * LCD panel support for the TI OMAP P2 board + * + * Authors: + * jekyll + * B Jp + * Brian Swetland + * + * 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 program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include + +#include +#include +#include + +/* + * File: epson-md-tft.h + * + * This file contains definitions for Epsons MD-TF LCD Module + * + * Copyright (C) 2004 MPC-Data Limited (http://www.mpc-data.co.uk) + * Author: Dave Peverley + * + * 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Please report all bugs and problems to the author. + * + */ + +/* LCD uWire commands & params + * All values from Epson + */ +#define LCD_DISON 0xAF +#define LCD_DISOFF 0xAE +#define LCD_DISNOR 0xA6 +#define LCD_DISINV 0xA7 +#define LCD_DISCTL 0xCA +#define LCD_GCP64 0xCB +#define LCD_GCP16 0xCC +#define LCD_GSSET 0xCD +#define LCD_SLPIN 0x95 +#define LCD_SLPOUT 0x94 +#define LCD_SD_PSET 0x75 +#define LCD_MD_PSET 0x76 +#define LCD_SD_CSET 0x15 +#define LCD_MD_CSET 0x16 +#define LCD_DATCTL 0xBC +#define LCD_RAMWR 0x5C +#define LCD_RAMRD 0x5D +#define LCD_PTLIN 0xA8 +#define LCD_PTLOUT 0xA9 +#define LCD_ASCSET 0xAA +#define LCD_SCSTART 0xAB +#define LCD_VOLCTL 0xC6 +#define LCD_NOP 0x25 +#define LCD_OSCISEL 0x7 +#define LCD_3500KSET 0xD1 +#define LCD_3500KEND 0xD2 +#define LCD_14MSET 0xD3 +#define LCD_14MEND 0xD4 + +#define INIT_3500KSET 0x45 +#define INIT_14MSET 0x4B +#define INIT_DATCTL 0x08 /* 6.6.6 bits for D-Sample */ + +#define INIT_OSCISEL 0x05 + +#define INIT_VOLCTL 0x77 /* Nominel "volume" */ + +#define INIT_VOLCTL_Ton 0x98 /* Activate power-IC timer */ +#define INIT_GSSET 0x00 + +const unsigned short INIT_DISCTL[11] = +{ + 0xDE, 0x01, 0x64, 0x00, 0x1B, 0xF4, 0x00, 0xDC, 0x00, 0x02, 0x00 +}; + +const unsigned short INIT_GCP64[126] = +{ + 0x3B,0x00,0x42,0x00,0x4A,0x00,0x51,0x00, + 0x58,0x00,0x5F,0x00,0x66,0x00,0x6E,0x00, + 0x75,0x00,0x7C,0x00,0x83,0x00,0x8A,0x00, + 0x92,0x00,0x99,0x00,0xA0,0x00,0xA7,0x00, + 0xAE,0x00,0xB6,0x00,0xBD,0x00,0xC4,0x00, + 0xCB,0x00,0xD2,0x00,0xDA,0x00,0xE1,0x00, + 0xE8,0x00,0xEF,0x00,0xF6,0x00,0xFE,0x00, + 0x05,0x01,0x0C,0x01,0x13,0x01,0x1A,0x01, + 0x22,0x01,0x29,0x01,0x30,0x01,0x37,0x01, + 0x3E,0x01,0x46,0x01,0x4D,0x01,0x54,0x01, + 0x5B,0x01,0x62,0x01,0x6A,0x01,0x71,0x01, + 0x78,0x01,0x7F,0x01,0x86,0x01,0x8E,0x01, + 0x95,0x01,0x9C,0x01,0xA3,0x01,0xAA,0x01, + 0xB2,0x01,0xB9,0x01,0xC0,0x01,0xC7,0x01, + 0xCE,0x01,0xD6,0x01,0xDD,0x01,0xE4,0x01, + 0xEB,0x01,0xF2,0x01,0xFA,0x01 +}; + +const unsigned short INIT_GCP16[15] = +{ + 0x1A,0x31,0x48,0x54,0x5F,0x67,0x70,0x76,0x7C,0x80,0x83,0x84,0x85,0x87,0x96 +}; + +const unsigned short INIT_MD_PSET[4] = { 0, 0, 219, 0 }; +const unsigned short INIT_MD_CSET[4] = { 2, 0, 177, 0 }; + +const unsigned short INIT_SD_PSET[4] = { 0x00, 0x01, 0x00, 0x01 }; +const unsigned short INIT_SD_CSET[4] = { 0x00, 0x02, 0x00, 0x02 }; + +const unsigned short INIT_ASCSET[7] = { 0x00, 0x00, 0xDB, 0x00, 0xDC, 0x00, 0x01 }; +const unsigned short INIT_SCSTART[2] = { 0x00, 0x00 }; + +/* ----- end of epson_md_tft.h ----- */ + + +#include "../drivers/ssi/omap-uwire.h" + +#define LCD_UWIRE_CS 0 + +static int p2_panel_init(struct lcd_panel *panel, struct omapfb_device *fbdev) +{ + return 0; +} + +static void p2_panel_cleanup(struct lcd_panel *panel) +{ +} + +static int p2_panel_enable(struct lcd_panel *panel) +{ + int i; + unsigned long value; + + /* thwack the reset line */ + gpio_direction_output(19, 0); + mdelay(2); + gpio_set_value(19, 1); + + /* bits 31:28 -> 0 LCD_PXL_15 .. 12 */ + value = omap_readl(OMAP730_IO_CONF_3) & 0x0FFFFFFF; + omap_writel(value, OMAP730_IO_CONF_3); + + /* bits 19:0 -> 0 LCD_VSYNC, AC, PXL_0, PCLK, HSYNC, + ** PXL_9..1, PXL_10, PXL_11 + */ + value = omap_readl(OMAP730_IO_CONF_4) & 0xFFF00000; + omap_writel(value, OMAP730_IO_CONF_4); + + omap_uwire_configure_mode(0,16); + + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_DISOFF, 9, 0,NULL,1); + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_SLPIN, 9, 0,NULL,1); + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_DISNOR, 9, 0,NULL,1); + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_GSSET, 9, 0,NULL,1); + omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_GSSET | 0x100), 9, 0,NULL,1); + + /* DISCTL */ + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_DISCTL, 9, 0,NULL,1); + for (i = 0; i < (sizeof(INIT_DISCTL)/sizeof(unsigned short)); i++) + omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_DISCTL[i] | 0x100), 9, 0,NULL,1); + + /* GCP64 */ + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_GCP64, 9, 0,NULL,1); + for (i = 0; i < (sizeof(INIT_GCP64)/sizeof(unsigned short)); i++) + omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_GCP64[i] | 0x100), 9, 0,NULL,1); + + /* GCP16 */ + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_GCP16, 9, 0,NULL,1); + for (i = 0; i < (sizeof(INIT_GCP16)/sizeof(unsigned short)); i++) + omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_GCP16[i] | 0x100), 9, 0,NULL,1); + + /* MD_CSET */ + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_MD_CSET, 9, 0,NULL,1); + for (i = 0; i < (sizeof(INIT_MD_CSET)/sizeof(unsigned short)); i++) + omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_MD_CSET[i] | 0x100), 9, 0,NULL,1); + + /* MD_PSET */ + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_MD_PSET, 9, 0,NULL,1); + for (i = 0; i < (sizeof(INIT_MD_PSET)/sizeof(unsigned short)); i++) + omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_MD_PSET[i] | 0x100), 9, 0,NULL,1); + + /* SD_CSET */ + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_SD_CSET, 9, 0,NULL,1); + for (i = 0; i < (sizeof(INIT_SD_CSET)/sizeof(unsigned short)); i++) + omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_SD_CSET[i] | 0x100), 9, 0,NULL,1); + + /* SD_PSET */ + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_SD_PSET, 9, 0,NULL,1); + for (i = 0; i < (sizeof(INIT_SD_PSET)/sizeof(unsigned short)); i++) + omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_SD_PSET[i] | 0x100), 9, 0,NULL,1); + + /* DATCTL */ + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_DATCTL, 9, 0,NULL,1); + omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_DATCTL | 0x100), 9, 0,NULL,1); + + /* OSSISEL = d'5 */ + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_OSCISEL, 9, 0,NULL,1); + omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_OSCISEL | 0x100), 9, 0,NULL,1); + + /* 14MSET = d'74 */ + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_14MSET, 9, 0,NULL,1); + omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_14MSET | 0x100), 9, 0,NULL,1); + + /* 14MEND */ + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_14MEND, 9, 0,NULL,1); + + /* 3500KSET = d'69 */ + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_3500KSET, 9, 0,NULL,1); + omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_3500KSET | 0x100), 9, 0,NULL,1); + + /* 3500KEND */ + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_3500KEND, 9, 0,NULL,1); + + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_SLPOUT, 9, 0,NULL,1); + + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_VOLCTL, 9, 0,NULL,1); + omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_VOLCTL_Ton | 0x100), 9, 0,NULL,1); + + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_VOLCTL, 9, 0,NULL,1); + + omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_VOLCTL | 0x100), 9, 0,NULL,1); + + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_DISON, 9, 0,NULL,1); + + /* enable backlight */ + gpio_direction_output(134, 1); + + return 0; +} + +static void p2_panel_disable(struct lcd_panel *panel) +{ +} + +static unsigned long p2_panel_get_caps(struct lcd_panel *panel) +{ + return 0; +} + +struct lcd_panel p2_panel = { + .name = "p2", + .config = OMAP_LCDC_PANEL_TFT | OMAP_LCDC_INV_PIX_CLOCK, + + .bpp = 16, + .data_lines = 16, + .x_res = 176, + .y_res = 220, + .pixel_clock = 12500, + .hsw = 5, + .hfp = 1, + .hbp = 1, + .vsw = 2, + .vfp = 12, + .vbp = 1, + + .init = p2_panel_init, + .cleanup = p2_panel_cleanup, + .enable = p2_panel_enable, + .disable = p2_panel_disable, + .get_caps = p2_panel_get_caps, +}; + +static int p2_panel_probe(struct platform_device *pdev) +{ + omapfb_register_panel(&p2_panel); + return 0; +} + +static int p2_panel_remove(struct platform_device *pdev) +{ + return 0; +} + +static int p2_panel_suspend(struct platform_device *pdev, pm_message_t mesg) +{ + return 0; +} + +static int p2_panel_resume(struct platform_device *pdev) +{ + return 0; +} + +struct platform_driver p2_panel_driver = { + .probe = p2_panel_probe, + .remove = p2_panel_remove, + .suspend = p2_panel_suspend, + .resume = p2_panel_resume, + .driver = { + .name = "lcd_p2", + .owner = THIS_MODULE, + }, +}; + +static int p2_panel_drv_init(void) +{ + return platform_driver_register(&p2_panel_driver); +} + +static void p2_panel_drv_cleanup(void) +{ + platform_driver_unregister(&p2_panel_driver); +} + +module_init(p2_panel_drv_init); +module_exit(p2_panel_drv_cleanup); + diff --git a/drivers/video/omap/omapfb_main.c b/drivers/video/omap/omapfb_main.c index 060d72fe57c..7617dd406a3 100644 --- a/drivers/video/omap/omapfb_main.c +++ b/drivers/video/omap/omapfb_main.c @@ -67,6 +67,7 @@ static struct caps_table_struct ctrl_caps[] = { { OMAPFB_CAPS_WINDOW_PIXEL_DOUBLE, "pixel double window" }, { OMAPFB_CAPS_WINDOW_SCALE, "scale window" }, { OMAPFB_CAPS_WINDOW_OVERLAY, "overlay window" }, + { OMAPFB_CAPS_WINDOW_ROTATE, "rotate window" }, { OMAPFB_CAPS_SET_BACKLIGHT, "backlight setting" }, }; @@ -215,13 +216,22 @@ static int ctrl_change_mode(struct fb_info *fbi) offset, var->xres_virtual, plane->info.pos_x, plane->info.pos_y, var->xres, var->yres, plane->color_mode); - if (fbdev->ctrl->set_scale != NULL) + if (r < 0) + return r; + + if (fbdev->ctrl->set_rotate != NULL) + if((r = fbdev->ctrl->set_rotate(var->rotate)) < 0) + return r; + + if ((fbdev->ctrl->set_scale != NULL) && (plane->idx > 0)) r = fbdev->ctrl->set_scale(plane->idx, var->xres, var->yres, plane->info.out_width, plane->info.out_height); + if (r < 0) + return r; - return r; + return 0; } /* @@ -552,7 +562,6 @@ static int set_fb_var(struct fb_info *fbi, var->xoffset = var->xres_virtual - var->xres; if (var->yres + var->yoffset > var->yres_virtual) var->yoffset = var->yres_virtual - var->yres; - line_size = var->xres * bpp / 8; if (plane->color_mode == OMAPFB_COLOR_RGB444) { var->red.offset = 8; var->red.length = 4; @@ -598,7 +607,7 @@ static void omapfb_rotate(struct fb_info *fbi, int rotate) struct omapfb_device *fbdev = plane->fbdev; omapfb_rqueue_lock(fbdev); - if (cpu_is_omap15xx() && rotate != fbi->var.rotate) { + if (rotate != fbi->var.rotate) { struct fb_var_screeninfo *new_var = &fbdev->new_var; memcpy(new_var, &fbi->var, sizeof(*new_var)); @@ -705,28 +714,42 @@ int omapfb_update_window_async(struct fb_info *fbi, void (*callback)(void *), void *callback_data) { + int xres, yres; struct omapfb_plane_struct *plane = fbi->par; struct omapfb_device *fbdev = plane->fbdev; - struct fb_var_screeninfo *var; + struct fb_var_screeninfo *var = &fbi->var; + + switch (var->rotate) { + case 0: + case 180: + xres = fbdev->panel->x_res; + yres = fbdev->panel->y_res; + break; + case 90: + case 270: + xres = fbdev->panel->y_res; + yres = fbdev->panel->x_res; + break; + default: + return -EINVAL; + } - var = &fbi->var; - if (win->x >= var->xres || win->y >= var->yres || - win->out_x > var->xres || win->out_y >= var->yres) + if (win->x >= xres || win->y >= yres || + win->out_x > xres || win->out_y > yres) return -EINVAL; if (!fbdev->ctrl->update_window || fbdev->ctrl->get_update_mode() != OMAPFB_MANUAL_UPDATE) return -ENODEV; - if (win->x + win->width >= var->xres) - win->width = var->xres - win->x; - if (win->y + win->height >= var->yres) - win->height = var->yres - win->y; - /* The out sizes should be cropped to the LCD size */ - if (win->out_x + win->out_width > fbdev->panel->x_res) - win->out_width = fbdev->panel->x_res - win->out_x; - if (win->out_y + win->out_height > fbdev->panel->y_res) - win->out_height = fbdev->panel->y_res - win->out_y; + if (win->x + win->width > xres) + win->width = xres - win->x; + if (win->y + win->height > yres) + win->height = yres - win->y; + if (win->out_x + win->out_width > xres) + win->out_width = xres - win->out_x; + if (win->out_y + win->out_height > yres) + win->out_height = yres - win->out_y; if (!win->width || !win->height || !win->out_width || !win->out_height) return 0; @@ -1695,8 +1718,8 @@ static int omapfb_do_probe(struct platform_device *pdev, pr_info("omapfb: configured for panel %s\n", fbdev->panel->name); - def_vxres = def_vxres ? : fbdev->panel->x_res; - def_vyres = def_vyres ? : fbdev->panel->y_res; + def_vxres = def_vxres ? def_vxres : fbdev->panel->x_res; + def_vyres = def_vyres ? def_vyres : fbdev->panel->y_res; init_state++; @@ -1818,8 +1841,8 @@ static int omapfb_suspend(struct platform_device *pdev, pm_message_t mesg) { struct omapfb_device *fbdev = platform_get_drvdata(pdev); - omapfb_blank(FB_BLANK_POWERDOWN, fbdev->fb_info[0]); - + if (fbdev != NULL) + omapfb_blank(VESA_POWERDOWN, fbdev->fb_info[0]); return 0; } @@ -1828,7 +1851,8 @@ static int omapfb_resume(struct platform_device *pdev) { struct omapfb_device *fbdev = platform_get_drvdata(pdev); - omapfb_blank(FB_BLANK_UNBLANK, fbdev->fb_info[0]); + if (fbdev != NULL) + omapfb_blank(VESA_NO_BLANKING, fbdev->fb_info[0]); return 0; } diff --git a/drivers/video/omap/rfbi.c b/drivers/video/omap/rfbi.c index a13c8dcad2a..29fa368f522 100644 --- a/drivers/video/omap/rfbi.c +++ b/drivers/video/omap/rfbi.c @@ -2,7 +2,7 @@ * OMAP2 Remote Frame Buffer Interface support * * Copyright (C) 2005 Nokia Corporation - * Author: Juha Yrjölä + * Author: Juha Yrj�l� * Imre Deak * * This program is free software; you can redistribute it and/or modify it @@ -57,6 +57,7 @@ #define DISPC_BASE 0x48050400 #define DISPC_CONTROL 0x0040 +#define DISPC_IRQ_FRAMEMASK 0x0001 static struct { void __iomem *base; @@ -551,7 +552,8 @@ static int rfbi_init(struct omapfb_device *fbdev) l = (0x01 << 2); rfbi_write_reg(RFBI_CONTROL, l); - if ((r = omap_dispc_request_irq(rfbi_dma_callback, NULL)) < 0) { + if ((r = omap_dispc_request_irq(DISPC_IRQ_FRAMEMASK, rfbi_dma_callback, + NULL)) < 0) { dev_err(fbdev->dev, "can't get DISPC irq\n"); rfbi_enable_clocks(0); return r; @@ -568,7 +570,7 @@ static int rfbi_init(struct omapfb_device *fbdev) static void rfbi_cleanup(void) { - omap_dispc_free_irq(); + omap_dispc_free_irq(DISPC_IRQ_FRAMEMASK, rfbi_dma_callback, NULL); rfbi_put_clocks(); iounmap(rfbi.base); } diff --git a/drivers/video/omap/sossi.c b/drivers/video/omap/sossi.c index a7694622024..cc697ccd829 100644 --- a/drivers/video/omap/sossi.c +++ b/drivers/video/omap/sossi.c @@ -2,7 +2,7 @@ * OMAP1 Special OptimiSed Screen Interface support * * Copyright (C) 2004-2005 Nokia Corporation - * Author: Juha Yrjölä + * Author: Juha Yrj�l� * * 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 diff --git a/include/asm-arm/.gitignore b/include/asm-arm/.gitignore index e02c15d158f..e1bf4707aa7 100644 --- a/include/asm-arm/.gitignore +++ b/include/asm-arm/.gitignore @@ -1,2 +1,3 @@ arch +asm-offsets.h mach-types.h diff --git a/include/asm-arm/hardware/tsc2101.h b/include/asm-arm/hardware/tsc2101.h new file mode 100644 index 00000000000..449045841f6 --- /dev/null +++ b/include/asm-arm/hardware/tsc2101.h @@ -0,0 +1,300 @@ +/* + * + * TI TSC2101 Audio CODEC and TS control registers definition + * + * + * Copyright 2003 MontaVista Software Inc. + * Author: MontaVista Software, Inc. + * source@mvista.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __ASM_HARDWARE_TSC2101_H +#define __ASM_HARDWARE_TSC2101_H + +/* Page 0 Touch Screen Data Registers */ +#define TSC2101_TS_X (0x00) +#define TSC2101_TS_Y (0x01) +#define TSC2101_TS_Z1 (0x02) +#define TSC2101_TS_Z2 (0x03) +#define TSC2101_TS_BAT (0x05) +#define TSC2101_TS_AUX1 (0x07) +#define TSC2101_TS_AUX2 (0x08) +#define TSC2101_TS_TEMP1 (0x09) +#define TSC2101_TS_TEMP2 (0x0A) + +/* Page 1 Touch Screen Control registers */ +#define TSC2101_TS_ADC_CTRL (0x00) +#define TSC2101_TS_STATUS (0x01) +#define TSC2101_TS_BUFFER_CTRL (0x02) +#define TSC2101_TS_REF_CTRL (0x03) +#define TSC2101_TS_RESET_CTRL (0x04) +#define TSC2101_TS_CONFIG_CTRL (0x05) +#define TSC2101_TS_TEMP_MAX_THRESHOLD (0x06) +#define TSC2101_TS_TEMP_MIN_THRESHOLD (0x07) +#define TSC2101_TS_AUX1_MAX_THRESHOLD (0x08) +#define TSC2101_TS_AUX1_MIN_THRESHOLD (0x09) +#define TSC2101_TS_AUX2_MAX_THRESHOLD (0x0A) +#define TSC2101_TS_AUX2_MIN_THRESHOLD (0x0B) +#define TSC2101_TS_MEASURE_CONFIG (0x0C) +#define TSC2101_TS_PROG_DELAY (0x0D) + +/* Page 2 Audio codec Control registers */ +#define TSC2101_AUDIO_CTRL_1 (0x00) +#define TSC2101_HEADSET_GAIN_CTRL (0x01) +#define TSC2101_DAC_GAIN_CTRL (0x02) +#define TSC2101_MIXER_PGA_CTRL (0x03) +#define TSC2101_AUDIO_CTRL_2 (0x04) +#define TSC2101_CODEC_POWER_CTRL (0x05) +#define TSC2101_AUDIO_CTRL_3 (0x06) +#define TSC2101_LCH_BASS_BOOST_N0 (0x07) +#define TSC2101_LCH_BASS_BOOST_N1 (0x08) +#define TSC2101_LCH_BASS_BOOST_N2 (0x09) +#define TSC2101_LCH_BASS_BOOST_N3 (0x0A) +#define TSC2101_LCH_BASS_BOOST_N4 (0x0B) +#define TSC2101_LCH_BASS_BOOST_N5 (0x0C) +#define TSC2101_LCH_BASS_BOOST_D1 (0x0D) +#define TSC2101_LCH_BASS_BOOST_D2 (0x0E) +#define TSC2101_LCH_BASS_BOOST_D4 (0x0F) +#define TSC2101_LCH_BASS_BOOST_D5 (0x10) +#define TSC2101_RCH_BASS_BOOST_N0 (0x11) +#define TSC2101_RCH_BASS_BOOST_N1 (0x12) +#define TSC2101_RCH_BASS_BOOST_N2 (0x13) +#define TSC2101_RCH_BASS_BOOST_N3 (0x14) +#define TSC2101_RCH_BASS_BOOST_N4 (0x15) +#define TSC2101_RCH_BASS_BOOST_N5 (0x16) +#define TSC2101_RCH_BASS_BOOST_D1 (0x17) +#define TSC2101_RCH_BASS_BOOST_D2 (0x18) +#define TSC2101_RCH_BASS_BOOST_D4 (0x19) +#define TSC2101_RCH_BASS_BOOST_D5 (0x1A) +#define TSC2101_PLL_PROG_1 (0x1B) +#define TSC2101_PLL_PROG_2 (0x1C) +#define TSC2101_AUDIO_CTRL_4 (0x1D) +#define TSC2101_HANDSET_GAIN_CTRL (0x1E) +#define TSC2101_BUZZER_GAIN_CTRL (0x1F) +#define TSC2101_AUDIO_CTRL_5 (0x20) +#define TSC2101_AUDIO_CTRL_6 (0x21) +#define TSC2101_AUDIO_CTRL_7 (0x22) +#define TSC2101_GPIO_CTRL (0x23) +#define TSC2101_AGC_CTRL (0x24) +#define TSC2101_POWERDOWN_STS (0x25) +#define TSC2101_MIC_AGC_CONTROL (0x26) +#define TSC2101_CELL_AGC_CONTROL (0x27) + +/* Bit field definitions for TS Control */ +#define TSC2101_DATA_AVAILABLE 0x4000 +#define TSC2101_BUFFERMODE_DISABLE 0x0 +#define TSC2101_REF_POWERUP 0x16 +#define TSC2101_ENABLE_TOUCHDETECT 0x08 +#define TSC2101_PRG_DELAY 0x0900 +#define TSC2101_ADC_CONTROL 0x8874 +#define TSC2101_ADC_POWERDOWN 0x4000 + +/* Bit position */ +#define TSC2101_BIT(ARG) ((0x01)<<(ARG)) + +/* Field masks for Audio Control 1 */ +#define AC1_ADCHPF(ARG) (((ARG) & 0x03) << 14) +#define AC1_WLEN(ARG) (((ARG) & 0x03) << 10) +#define AC1_DATFM(ARG) (((ARG) & 0x03) << 8) +#define AC1_DACFS(ARG) (((ARG) & 0x07) << 3) +#define AC1_ADCFS(ARG) (((ARG) & 0x07)) + +/* Field masks for TSC2101_HEADSET_GAIN_CTRL */ +#define HGC_ADMUT_HED TSC2101_BIT(15) +#define HGC_ADPGA_HED(ARG) (((ARG) & 0x7F) << 8) +#define HGC_AGCTG_HED(ARG) (((ARG) & 0x07) << 5) +#define HGC_AGCTC_HED(ARG) (((ARG) & 0x0F) << 1) +#define HGC_AGCEN_HED (0x01) + +/* Field masks for TSC2101_DAC_GAIN_CTRL */ +#define DGC_DALMU TSC2101_BIT(15) +#define DGC_DALVL(ARG) (((ARG) & 0x7F) << 8) +#define DGC_DARMU TSC2101_BIT(7) +#define DGC_DARVL(ARG) (((ARG) & 0x7F)) + +/* Field masks for TSC2101_MIXER_PGA_CTRL */ +#define MPC_ASTMU TSC2101_BIT(15) +#define MPC_ASTG(ARG) (((ARG) & 0x7F) << 8) +#define MPC_MICSEL(ARG) (((ARG) & 0x07) << 5) +#define MPC_MICADC TSC2101_BIT(4) +#define MPC_CPADC TSC2101_BIT(3) +#define MPC_ASTGF (0x01) + +/* Field formats for TSC2101_AUDIO_CTRL_2 */ +#define AC2_KCLEN TSC2101_BIT(15) +#define AC2_KCLAC(ARG) (((ARG) & 0x07) << 12) +#define AC2_APGASS TSC2101_BIT(11) +#define AC2_KCLFRQ(ARG) (((ARG) & 0x07) << 8) +#define AC2_KCLLN(ARG) (((ARG) & 0x0F) << 4) +#define AC2_DLGAF TSC2101_BIT(3) +#define AC2_DRGAF TSC2101_BIT(2) +#define AC2_DASTC TSC2101_BIT(1) +#define AC2_ADGAF (0x01) + +/* Field masks for TSC2101_CODEC_POWER_CTRL */ +#define CPC_MBIAS_HND TSC2101_BIT(15) +#define CPC_MBIAS_HED TSC2101_BIT(14) +#define CPC_ASTPWD TSC2101_BIT(13) +#define CPC_SP1PWDN TSC2101_BIT(12) +#define CPC_SP2PWDN TSC2101_BIT(11) +#define CPC_DAPWDN TSC2101_BIT(10) +#define CPC_ADPWDN TSC2101_BIT(9) +#define CPC_VGPWDN TSC2101_BIT(8) +#define CPC_COPWDN TSC2101_BIT(7) +#define CPC_LSPWDN TSC2101_BIT(6) +#define CPC_ADPWDF TSC2101_BIT(5) +#define CPC_LDAPWDF TSC2101_BIT(4) +#define CPC_RDAPWDF TSC2101_BIT(3) +#define CPC_ASTPWF TSC2101_BIT(2) +#define CPC_BASSBC TSC2101_BIT(1) +#define CPC_DEEMPF (0x01) + +/* Field masks for TSC2101_AUDIO_CTRL_3 */ +#define AC3_DMSVOL(ARG) (((ARG) & 0x03) << 14) +#define AC3_REFFS TSC2101_BIT(13) +#define AC3_DAXFM TSC2101_BIT(12) +#define AC3_SLVMS TSC2101_BIT(11) +#define AC3_ADCOVF TSC2101_BIT(8) +#define AC3_DALOVF TSC2101_BIT(7) +#define AC3_DAROVF TSC2101_BIT(6) +#define AC3_CLPST TSC2101_BIT(3) +#define AC3_REVID(ARG) (((ARG) & 0x07)) + +/* Field masks for TSC2101_PLL_PROG_1 */ +#define PLL1_PLLSEL TSC2101_BIT(15) +#define PLL1_QVAL(ARG) (((ARG) & 0x0F) << 11) +#define PLL1_PVAL(ARG) (((ARG) & 0x07) << 8) +#define PLL1_I_VAL(ARG) (((ARG) & 0x3F) << 2) + +/* Field masks of TSC2101_PLL_PROG_2 */ +#define PLL2_D_VAL(ARG) (((ARG) & 0x3FFF) << 2) + +/* Field masks for TSC2101_AUDIO_CTRL_4 */ +#define AC4_ADSTPD TSC2101_BIT(15) +#define AC4_DASTPD TSC2101_BIT(14) +#define AC4_ASSTPD TSC2101_BIT(13) +#define AC4_CISTPD TSC2101_BIT(12) +#define AC4_BISTPD TSC2101_BIT(11) +#define AC4_AGCHYS(ARG) (((ARG) & 0x03) << 9) +#define AC4_MB_HED(ARG) (((ARG) & 0x03) << 7) +#define AC4_MB_HND TSC2101_BIT(6) +#define AC4_SCPFL TSC2101_BIT(1) + +/* Field masks settings for TSC2101_HANDSET_GAIN_CTRL */ +#define HNGC_ADMUT_HND TSC2101_BIT(15) +#define HNGC_ADPGA_HND(ARG) (((ARG) & 0x7F) << 8) +#define HNGC_AGCTG_HND(ARG) (((ARG) & 0x07) << 5) +#define HNGC_AGCTC_HND(ARG) (((ARG) & 0x0F) << 1) +#define HNGC_AGCEN_HND (0x01) + +/* Field masks settings for TSC2101_BUZZER_GAIN_CTRL */ +#define BGC_MUT_CP TSC2101_BIT(15) +#define BGC_CPGA(ARG) (((ARG) & 0x7F) << 8) +#define BGC_CPGF TSC2101_BIT(7) +#define BGC_MUT_BU TSC2101_BIT(6) +#define BGC_BPGA(ARG) (((ARG) & 0x0F) << 2) +#define BGC_BUGF TSC2101_BIT(1) + +/* Field masks settings for TSC2101_AUDIO_CTRL_5 */ +#define AC5_DIFFIN TSC2101_BIT(15) +#define AC5_DAC2SPK1(ARG) (((ARG) & 0x03) << 13) +#define AC5_AST2SPK1 TSC2101_BIT(12) +#define AC5_BUZ2SPK1 TSC2101_BIT(11) +#define AC5_KCL2SPK1 TSC2101_BIT(10) +#define AC5_CPI2SPK1 TSC2101_BIT(9) +#define AC5_DAC2SPK2(ARG) (((ARG) & 0x03) << 7) +#define AC5_AST2SPK2 TSC2101_BIT(6) +#define AC5_BUZ2SPK2 TSC2101_BIT(5) +#define AC5_KCL2SPK2 TSC2101_BIT(4) +#define AC5_CPI2SPK2 TSC2101_BIT(3) +#define AC5_MUTSPK1 TSC2101_BIT(2) +#define AC5_MUTSPK2 TSC2101_BIT(1) +#define AC5_HDSCPTC (0x01) + +/* Field masks settings for TSC2101_AUDIO_CTRL_6 */ +#define AC6_SPL2LSK TSC2101_BIT(15) +#define AC6_AST2LSK TSC2101_BIT(14) +#define AC6_BUZ2LSK TSC2101_BIT(13) +#define AC6_KCL2LSK TSC2101_BIT(12) +#define AC6_CPI2LSK TSC2101_BIT(11) +#define AC6_MIC2CPO TSC2101_BIT(10) +#define AC6_SPL2CPO TSC2101_BIT(9) +#define AC6_SPR2CPO TSC2101_BIT(8) +#define AC6_MUTLSPK TSC2101_BIT(7) +#define AC6_MUTSPK2 TSC2101_BIT(6) +#define AC6_LDSCPTC TSC2101_BIT(5) +#define AC6_VGNDSCPTC TSC2101_BIT(4) +#define AC6_CAPINTF TSC2101_BIT(3) + +/* Field masks settings for TSC2101_AUDIO_CTRL_7 */ +#define AC7_DETECT TSC2101_BIT(15) +#define AC7_HESTYPE(ARG) (((ARG) & 0x03) << 13) +#define AC7_HDDETFL TSC2101_BIT(12) +#define AC7_BDETFL TSC2101_BIT(11) +#define AC7_HDDEBNPG(ARG) (((ARG) & 0x03) << 9) +#define AC7_BDEBNPG(ARG) (((ARG) & 0x03) << 6) +#define AC7_DGPIO2 TSC2101_BIT(4) +#define AC7_DGPIO1 TSC2101_BIT(3) +#define AC7_CLKGPIO2 TSC2101_BIT(2) +#define AC7_ADWSF(ARG) (((ARG) & 0x03)) + +/* Field masks settings for TSC2101_GPIO_CTRL */ +#define GC_GPO2EN TSC2101_BIT(15) +#define GC_GPO2SG TSC2101_BIT(14) +#define GC_GPI2EN TSC2101_BIT(13) +#define GC_GPI2SGF TSC2101_BIT(12) +#define GC_GPO1EN TSC2101_BIT(11) +#define GC_GPO1SG TSC2101_BIT(10) +#define GC_GPI1EN TSC2101_BIT(9) +#define GC_GPI1SGF TSC2101_BIT(8) + +/* Field masks for TSC2101_AGC_CTRL */ +#define AC_AGCNF_CELL TSC2101_BIT(14) +#define AC_AGCNL(ARG) (((ARG) & 0x07) << 11) +#define AC_AGCHYS_CELL(ARG) (((ARG) & 0x03) << 9) +#define AC_CLPST_CELL TSC2101_BIT(8) +#define AC_AGCTG_CELL(ARG) (((ARG) & 0x07) << 5) +#define AC_AGCTC_CELL(ARG) (((ARG) & 0x0F) << 1) +#define AC_AGCEN_CELL (0x01) + +/* Field masks for TSC2101_POWERDOWN_STS */ +#define PS_SPK1FL TSC2101_BIT(15) +#define PS_SPK2FL TSC2101_BIT(14) +#define PS_HNDFL TSC2101_BIT(13) +#define PS_VGNDFL TSC2101_BIT(12) +#define PS_LSPKFL TSC2101_BIT(11) +#define PS_CELLFL TSC2101_BIT(10) +#define PS_PSEQ TSC2101_BIT(5) +#define PS_PSTIME TSC2101_BIT(4) + +/* Field masks for Register Mic AGC Control */ +#define MAC_MMPGA(ARG) (((ARG) & 0x7F) << 9) +#define MAC_MDEBNS(ARG) (((ARG) & 0x07) << 6) +#define MAC_MDEBSN(ARG) (((ARG) & 0x07) << 3) + +/* Field masks for Register Cellphone AGC Control */ +#define CAC_CMPGA(ARG) (((ARG) & 0x7F) << 9) +#define CAC_CDEBNS(ARG) (((ARG) & 0x07) << 6) +#define CAC_CDEBSN(ARG) (((ARG) & 0x07) << 3) + +#endif /* __ASM_HARDWARE_TSC2101_H */ diff --git a/include/linux/connector.h b/include/linux/connector.h index b9966e64604..94d742bdf25 100644 --- a/include/linux/connector.h +++ b/include/linux/connector.h @@ -36,6 +36,8 @@ #define CN_VAL_CIFS 0x1 #define CN_W1_IDX 0x3 /* w1 communication */ #define CN_W1_VAL 0x1 +#define CN_IDX_SX1SND 0x4 +#define CN_VAL_SX1SND 0x1 #define CN_IDX_V86D 0x4 #define CN_VAL_V86D_UVESAFB 0x1 #define CN_IDX_BB 0x5 /* BlackBoard, from the TSP GPL sampling framework */ diff --git a/include/linux/i2c/lm8323.h b/include/linux/i2c/lm8323.h new file mode 100644 index 00000000000..67e82bc8693 --- /dev/null +++ b/include/linux/i2c/lm8323.h @@ -0,0 +1,46 @@ +/* + * lm8323.h - Configuration for LM8323 keypad driver. + * + * 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 (version 2 of the License only). + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __LINUX_LM8323_H +#define __LINUX_LM8323_H + +#include + +/* + * Largest keycode that the chip can send, plus one, + * so keys can be mapped directly at the index of the + * LM8323 keycode instead of subtracting one. + */ +#define LM8323_KEYMAP_SIZE (0x7f + 1) + +struct lm8323_platform_data { + int debounce_time; /* Time to watch for key bouncing, in ms. */ + int active_time; /* Idle time until sleep, in ms. */ + + int size_x; + int size_y; + int repeat:1; + const s16 *keymap; + + char *pwm1_name; /* Device name for PWM1. */ + char *pwm2_name; /* Device name for PWM2. */ + char *pwm3_name; /* Device name for PWM3. */ + + char *name; /* Device name. */ +}; + +#endif /* __LINUX_LM8323_H */ diff --git a/include/linux/i2c/lp5521.h b/include/linux/i2c/lp5521.h new file mode 100644 index 00000000000..070e8be3d6d --- /dev/null +++ b/include/linux/i2c/lp5521.h @@ -0,0 +1,43 @@ +/* + * lp5521.h - header for LP5521 LED driver + * + * Copyright (C) 2009 Nokia Corporation + * + * Contact: Felipe Balbi + * + * 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 program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __LP5521_H_ +#define __LP5521_H_ + +enum lp5521_mode { + LP5521_MODE_LOAD, + LP5521_MODE_RUN, + LP5521_MODE_DIRECT_CONTROL, +}; + +struct lp5521_platform_data { + enum lp5521_mode mode; + + unsigned red_present:1; + unsigned green_present:1; + unsigned blue_present:1; + + const char *label; +}; + +#endif /* End of __LP5521_H */ diff --git a/include/linux/i2c/menelaus.h b/include/linux/i2c/menelaus.h new file mode 100644 index 00000000000..9587398dba2 --- /dev/null +++ b/include/linux/i2c/menelaus.h @@ -0,0 +1,163 @@ +/* + * include/linux/i2c/menelaus.h + * + * Functions to access Menelaus power management chip + */ + +#ifndef __ASM_ARCH_MENELAUS_H +#define __ASM_ARCH_MENELAUS_H + +#define MENELAUS_I2C_ADDRESS 0x72 + +#define MENELAUS_REV 0x01 +#define MENELAUS_VCORE_CTRL1 0x02 +#define MENELAUS_VCORE_CTRL2 0x03 +#define MENELAUS_VCORE_CTRL3 0x04 +#define MENELAUS_VCORE_CTRL4 0x05 +#define MENELAUS_VCORE_CTRL5 0x06 +#define MENELAUS_DCDC_CTRL1 0x07 +#define MENELAUS_DCDC_CTRL2 0x08 +#define MENELAUS_DCDC_CTRL3 0x09 +#define MENELAUS_LDO_CTRL1 0x0A +#define MENELAUS_LDO_CTRL2 0x0B +#define MENELAUS_LDO_CTRL3 0x0C +#define MENELAUS_LDO_CTRL4 0x0D +#define MENELAUS_LDO_CTRL5 0x0E +#define MENELAUS_LDO_CTRL6 0x0F +#define MENELAUS_LDO_CTRL7 0x10 +#define MENELAUS_LDO_CTRL8 0x11 +#define MENELAUS_SLEEP_CTRL1 0x12 +#define MENELAUS_SLEEP_CTRL2 0x13 +#define MENELAUS_DEVICE_OFF 0x14 +#define MENELAUS_OSC_CTRL 0x15 +#define MENELAUS_DETECT_CTRL 0x16 +#define MENELAUS_INT_MASK1 0x17 +#define MENELAUS_INT_MASK2 0x18 +#define MENELAUS_INT_STATUS1 0x19 +#define MENELAUS_INT_STATUS2 0x1A +#define MENELAUS_INT_ACK1 0x1B +#define MENELAUS_INT_ACK2 0x1C +#define MENELAUS_GPIO_CTRL 0x1D +#define MENELAUS_GPIO_IN 0x1E +#define MENELAUS_GPIO_OUT 0x1F +#define MENELAUS_BBSMS 0x20 +#define MENELAUS_RTC_CTRL 0x21 +#define MENELAUS_RTC_UPDATE 0x22 +#define MENELAUS_RTC_SEC 0x23 +#define MENELAUS_RTC_MIN 0x24 +#define MENELAUS_RTC_HR 0x25 +#define MENELAUS_RTC_DAY 0x26 +#define MENELAUS_RTC_MON 0x27 +#define MENELAUS_RTC_YR 0x28 +#define MENELAUS_RTC_WKDAY 0x29 +#define MENELAUS_RTC_AL_SEC 0x2A +#define MENELAUS_RTC_AL_MIN 0x2B +#define MENELAUS_RTC_AL_HR 0x2C +#define MENELAUS_RTC_AL_DAY 0x2D +#define MENELAUS_RTC_AL_MON 0x2E +#define MENELAUS_RTC_AL_YR 0x2F +#define MENELAUS_RTC_COMP_MSB 0x30 +#define MENELAUS_RTC_COMP_LSB 0x31 +#define MENELAUS_S1_PULL_EN 0x32 +#define MENELAUS_S1_PULL_DIR 0x33 +#define MENELAUS_S2_PULL_EN 0x34 +#define MENELAUS_S2_PULL_DIR 0x35 +#define MENELAUS_MCT_CTRL1 0x36 +#define MENELAUS_MCT_CTRL2 0x37 +#define MENELAUS_MCT_CTRL3 0x38 +#define MENELAUS_MCT_PIN_ST 0x39 +#define MENELAUS_DEBOUNCE1 0x3A + +#define IH_MENELAUS_IRQS 12 +#define MENELAUS_MMC_S1CD_IRQ 0 /* MMC slot 1 card change */ +#define MENELAUS_MMC_S2CD_IRQ 1 /* MMC slot 2 card change */ +#define MENELAUS_MMC_S1D1_IRQ 2 /* MMC DAT1 low in slot 1 */ +#define MENELAUS_MMC_S2D1_IRQ 3 /* MMC DAT1 low in slot 2 */ +#define MENELAUS_LOWBAT_IRQ 4 /* Low battery */ +#define MENELAUS_HOTDIE_IRQ 5 /* Hot die detect */ +#define MENELAUS_UVLO_IRQ 6 /* UVLO detect */ +#define MENELAUS_TSHUT_IRQ 7 /* Thermal shutdown */ +#define MENELAUS_RTCTMR_IRQ 8 /* RTC timer */ +#define MENELAUS_RTCALM_IRQ 9 /* RTC alarm */ +#define MENELAUS_RTCERR_IRQ 10 /* RTC error */ +#define MENELAUS_PSHBTN_IRQ 11 /* Push button */ +#define MENELAUS_RESERVED12_IRQ 12 /* Reserved */ +#define MENELAUS_RESERVED13_IRQ 13 /* Reserved */ +#define MENELAUS_RESERVED14_IRQ 14 /* Reserved */ +#define MENELAUS_RESERVED15_IRQ 15 /* Reserved */ + +/* VCORE_CTRL1 register */ +#define VCORE_CTRL1_BYP_COMP (1 << 5) +#define VCORE_CTRL1_HW_NSW (1 << 7) + +/* GPIO_CTRL register */ +#define GPIO_CTRL_SLOTSELEN (1 << 5) +#define GPIO_CTRL_SLPCTLEN (1 << 6) +#define GPIO1_DIR_INPUT (1 << 0) +#define GPIO2_DIR_INPUT (1 << 1) +#define GPIO3_DIR_INPUT (1 << 2) + +/* MCT_CTRL1 register */ +#define MCT_CTRL1_S1_CMD_OD (1 << 2) +#define MCT_CTRL1_S2_CMD_OD (1 << 3) + +/* MCT_CTRL2 register */ +#define MCT_CTRL2_VS2_SEL_D0 (1 << 0) +#define MCT_CTRL2_VS2_SEL_D1 (1 << 1) +#define MCT_CTRL2_S1CD_BUFEN (1 << 4) +#define MCT_CTRL2_S2CD_BUFEN (1 << 5) +#define MCT_CTRL2_S1CD_DBEN (1 << 6) +#define MCT_CTRL2_S2CD_BEN (1 << 7) + +/* MCT_CTRL3 register */ +#define MCT_CTRL3_SLOT1_EN (1 << 0) +#define MCT_CTRL3_SLOT2_EN (1 << 1) +#define MCT_CTRL3_S1_AUTO_EN (1 << 2) +#define MCT_CTRL3_S2_AUTO_EN (1 << 3) + +/* MCT_PIN_ST register */ +#define MCT_PIN_ST_S1_CD_ST (1 << 0) +#define MCT_PIN_ST_S2_CD_ST (1 << 1) + +struct device; + +struct menelaus_platform_data { + int (*late_init)(struct device *dev); +}; + +extern int menelaus_register_mmc_callback(void (*callback)(void *data, + u8 card_mask), + void *data); +extern void menelaus_unregister_mmc_callback(void); +extern int menelaus_set_mmc_opendrain(int slot, int enable); +extern int menelaus_set_mmc_slot(int slot, int enable, int power, int cd_on); +extern int menelaus_enable_slot(int slot, int enable); + +extern int menelaus_set_vmem(unsigned int mV); +extern int menelaus_set_vio(unsigned int mV); +extern int menelaus_set_vmmc(unsigned int mV); +extern int menelaus_set_vaux(unsigned int mV); +extern int menelaus_set_vdcdc(int dcdc, unsigned int mV); +extern int menelaus_set_slot_sel(int enable); +extern int menelaus_get_slot_pin_states(void); +extern int menelaus_set_vcore_sw(unsigned int mV); +extern int menelaus_set_vcore_hw(unsigned int roof_mV, unsigned int floor_mV); + +#define EN_VPLL_SLEEP (1 << 7) +#define EN_VMMC_SLEEP (1 << 6) +#define EN_VAUX_SLEEP (1 << 5) +#define EN_VIO_SLEEP (1 << 4) +#define EN_VMEM_SLEEP (1 << 3) +#define EN_DC3_SLEEP (1 << 2) +#define EN_DC2_SLEEP (1 << 1) +#define EN_VC_SLEEP (1 << 0) + +extern int menelaus_set_regulator_sleep(int enable, u32 val); + +#if defined(CONFIG_ARCH_OMAP24XX) && defined(CONFIG_MENELAUS) +#define omap_has_menelaus() 1 +#else +#define omap_has_menelaus() 0 +#endif + +#endif diff --git a/include/linux/i2c/twl4030-madc.h b/include/linux/i2c/twl4030-madc.h new file mode 100644 index 00000000000..24523b507e5 --- /dev/null +++ b/include/linux/i2c/twl4030-madc.h @@ -0,0 +1,126 @@ +/* + * include/linux/i2c/twl4030-madc.h + * + * TWL4030 MADC module driver header + * + * Copyright (C) 2008 Nokia Corporation + * Mikko Ylinen + * + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef _TWL4030_MADC_H +#define _TWL4030_MADC_H + +struct twl4030_madc_conversion_method { + u8 sel; + u8 avg; + u8 rbase; + u8 ctrl; +}; + +#define TWL4030_MADC_MAX_CHANNELS 16 + +struct twl4030_madc_request { + u16 channels; + u16 do_avg; + u16 method; + u16 type; + int active; + int result_pending; + int rbuf[TWL4030_MADC_MAX_CHANNELS]; + void (*func_cb)(int len, int channels, int *buf); +}; + +enum conversion_methods { + TWL4030_MADC_RT, + TWL4030_MADC_SW1, + TWL4030_MADC_SW2, + TWL4030_MADC_NUM_METHODS +}; + +enum sample_type { + TWL4030_MADC_WAIT, + TWL4030_MADC_IRQ_ONESHOT, + TWL4030_MADC_IRQ_REARM +}; + +#define TWL4030_MADC_CTRL1 0x00 +#define TWL4030_MADC_CTRL2 0x01 + +#define TWL4030_MADC_RTSELECT_LSB 0x02 +#define TWL4030_MADC_SW1SELECT_LSB 0x06 +#define TWL4030_MADC_SW2SELECT_LSB 0x0A + +#define TWL4030_MADC_RTAVERAGE_LSB 0x04 +#define TWL4030_MADC_SW1AVERAGE_LSB 0x08 +#define TWL4030_MADC_SW2AVERAGE_LSB 0x0C + +#define TWL4030_MADC_CTRL_SW1 0x12 +#define TWL4030_MADC_CTRL_SW2 0x13 + +#define TWL4030_MADC_RTCH0_LSB 0x17 +#define TWL4030_MADC_GPCH0_LSB 0x37 + +#define TWL4030_MADC_MADCON (1<<0) /* MADC power on */ +#define TWL4030_MADC_BUSY (1<<0) /* MADC busy */ +#define TWL4030_MADC_EOC_SW (1<<1) /* MADC conversion completion */ +#define TWL4030_MADC_SW_START (1<<5) /* MADC SWx start conversion */ + +#define TWL4030_MADC_ADCIN0 (1<<0) +#define TWL4030_MADC_ADCIN1 (1<<1) +#define TWL4030_MADC_ADCIN2 (1<<2) +#define TWL4030_MADC_ADCIN3 (1<<3) +#define TWL4030_MADC_ADCIN4 (1<<4) +#define TWL4030_MADC_ADCIN5 (1<<5) +#define TWL4030_MADC_ADCIN6 (1<<6) +#define TWL4030_MADC_ADCIN7 (1<<7) +#define TWL4030_MADC_ADCIN8 (1<<8) +#define TWL4030_MADC_ADCIN9 (1<<9) +#define TWL4030_MADC_ADCIN10 (1<<10) +#define TWL4030_MADC_ADCIN11 (1<<11) +#define TWL4030_MADC_ADCIN12 (1<<12) +#define TWL4030_MADC_ADCIN13 (1<<13) +#define TWL4030_MADC_ADCIN14 (1<<14) +#define TWL4030_MADC_ADCIN15 (1<<15) + +/* Fixed channels */ +#define TWL4030_MADC_BTEMP TWL4030_MADC_ADCIN1 +#define TWL4030_MADC_VBUS TWL4030_MADC_ADCIN8 +#define TWL4030_MADC_VBKB TWL4030_MADC_ADCIN9 +#define TWL4030_MADC_ICHG TWL4030_MADC_ADCIN10 +#define TWL4030_MADC_VCHG TWL4030_MADC_ADCIN11 +#define TWL4030_MADC_VBAT TWL4030_MADC_ADCIN12 + +/* BCI related - XXX To be moved elsewhere */ +#define TWL4030_BCI_BCICTL1 0x23 +#define TWL4030_BCI_MESBAT (1<<1) +#define TWL4030_BCI_TYPEN (1<<4) +#define TWL4030_BCI_ITHEN (1<<3) + +#define TWL4030_MADC_IOC_MAGIC '`' +#define TWL4030_MADC_IOCX_ADC_RAW_READ _IO(TWL4030_MADC_IOC_MAGIC, 0) + +struct twl4030_madc_user_parms { + int channel; + int average; + int status; + u16 result; +}; + +int twl4030_madc_conversion(struct twl4030_madc_request *conv); + +#endif diff --git a/include/linux/i2c/twl4030.h b/include/linux/i2c/twl4030.h index 0dc80ef2497..87accda0e40 100644 --- a/include/linux/i2c/twl4030.h +++ b/include/linux/i2c/twl4030.h @@ -243,6 +243,37 @@ int twl4030_i2c_read(u8 mod_no, u8 *value, u8 reg, unsigned num_bytes); #define RES_STATE_SLEEP 0x8 #define RES_STATE_OFF 0x0 +/* Power resources */ + +#define RES_VAUX1 1 +#define RES_VAUX2 2 +#define RES_VAUX3 3 +#define RES_VAUX4 4 +#define RES_VMMC1 5 +#define RES_VMMC2 6 +#define RES_VPLL1 7 +#define RES_VPLL2 8 +#define RES_VSIM 9 +#define RES_VDAC 10 +#define RES_VINTANA1 11 +#define RES_VINTANA2 12 +#define RES_VINTDIG 13 +#define RES_VIO 14 +#define RES_VDD1 15 +#define RES_VDD2 16 +#define RES_VUSB_1V5 17 +#define RES_VUSB_1V8 18 +#define RES_VUSB_3V1 19 +#define RES_VUSBCP 20 +#define RES_REGEN 21 +#define RES_NRES_PWRON 22 +#define RES_CLKEN 23 +#define RES_SYSEN 24 +#define RES_HFCLKOUT 25 +#define RES_32KCLKOUT 26 +#define RES_RESET 27 +#define RES_Main_Ref 28 + /* * Power Bus Message Format ... these can be sent individually by Linux, * but are usually part of downloaded scripts that are run when various @@ -302,12 +333,19 @@ struct twl4030_madc_platform_data { int irq_line; }; +/* Boards have uniqe mappings of {col, row} --> keycode. + * Column and row are 4 bits, but range only from 0..7; + * a PERSISTENT_KEY is "always on" and never reported. + */ +#define KEY_PERSISTENT 0x00800000 +#define KEY(col, row, keycode) (((col) << 28) | ((row) << 24) | (keycode)) +#define PERSISTENT_KEY(c, r) KEY(c, r, KEY_PERSISTENT) + struct twl4030_keypad_data { - int rows; - int cols; - int *keymap; - int irq; - unsigned int keymapsize; + unsigned rows; + unsigned cols; + unsigned *keymap; + unsigned short keymapsize; unsigned int rep:1; }; @@ -320,6 +358,34 @@ struct twl4030_usb_data { enum twl4030_usb_mode usb_mode; }; +struct twl4030_ins { + u16 pmb_message; + u8 delay; +}; + +struct twl4030_script { + struct twl4030_ins *script; + unsigned size; + u8 flags; +#define TRITON_WRST_SCRIPT (1<<0) +#define TRITON_WAKEUP12_SCRIPT (1<<1) +#define TRITON_WAKEUP3_SCRIPT (1<<2) +#define TRITON_SLEEP_SCRIPT (1<<3) +}; + +struct twl4030_resconfig { + u8 resource; + u8 devgroup; + u8 type; + u8 type2; +}; + +struct twl4030_power_data { + struct twl4030_script **scripts; + unsigned size; + const struct twl4030_resconfig *resource_config; +}; + struct twl4030_platform_data { unsigned irq_base, irq_end; struct twl4030_bci_platform_data *bci; @@ -327,6 +393,7 @@ struct twl4030_platform_data { struct twl4030_madc_platform_data *madc; struct twl4030_keypad_data *keypad; struct twl4030_usb_data *usb; + struct twl4030_power_data *power; /* LDO regulators */ struct regulator_init_data *vdac; @@ -357,7 +424,6 @@ int twl4030_sih_setup(int module); #define TWL4030_VAUX3_DEV_GRP 0x1F #define TWL4030_VAUX3_DEDICATED 0x22 - #if defined(CONFIG_TWL4030_BCI_BATTERY) || \ defined(CONFIG_TWL4030_BCI_BATTERY_MODULE) extern int twl4030charger_usb_en(int enable); diff --git a/include/linux/netfilter_ipv4/ipt_IDLETIMER.h b/include/linux/netfilter_ipv4/ipt_IDLETIMER.h new file mode 100644 index 00000000000..89993e27448 --- /dev/null +++ b/include/linux/netfilter_ipv4/ipt_IDLETIMER.h @@ -0,0 +1,22 @@ +/* + * linux/include/linux/netfilter_ipv4/ipt_IDLETIMER.h + * + * Header file for IP tables timer target module. + * + * Copyright (C) 2004 Nokia Corporation + * Written by Timo Teräs + * + * 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 _IPT_TIMER_H +#define _IPT_TIMER_H + +struct ipt_idletimer_info { + unsigned int timeout; +}; + +#endif diff --git a/include/linux/serial_reg.h b/include/linux/serial_reg.h index 96c0d93fc2c..850db2e8051 100644 --- a/include/linux/serial_reg.h +++ b/include/linux/serial_reg.h @@ -323,6 +323,7 @@ #define UART_OMAP_MVER 0x14 /* Module version register */ #define UART_OMAP_SYSC 0x15 /* System configuration register */ #define UART_OMAP_SYSS 0x16 /* System status register */ +#define UART_OMAP_WER 0x17 /* Wake-up enable register */ #endif /* _LINUX_SERIAL_REG_H */ diff --git a/include/linux/spi/ads7846.h b/include/linux/spi/ads7846.h index 05eab2f11e6..e284a1f1b5a 100644 --- a/include/linux/spi/ads7846.h +++ b/include/linux/spi/ads7846.h @@ -51,5 +51,10 @@ struct ads7846_platform_data { void **filter_data); int (*filter) (void *filter_data, int data_idx, int *val); void (*filter_cleanup)(void *filter_data); + + /* controls enabling/disabling*/ + int (*vaux_control)(int vaux_cntrl); +#define VAUX_ENABLE 1 +#define VAUX_DISABLE 0 }; diff --git a/include/linux/spi/tsc2005.h b/include/linux/spi/tsc2005.h new file mode 100644 index 00000000000..dbc01f70e98 --- /dev/null +++ b/include/linux/spi/tsc2005.h @@ -0,0 +1,29 @@ +#ifndef _LINUX_SPI_TSC2005_H +#define _LINUX_SPI_TSC2005_H + +#include + +struct tsc2005_platform_data { + s16 reset_gpio; + s16 dav_gpio; + s16 pen_int_gpio; + u16 ts_x_plate_ohm; + u32 ts_stab_time; /* voltage settling time */ + u8 ts_hw_avg; /* HW assiseted averaging. Can be + 0, 4, 8, 16 samples per reading */ + u32 ts_touch_pressure; /* Pressure limit until we report a + touch event. After that we switch + to ts_max_pressure. */ + u32 ts_pressure_max;/* Samples with bigger pressure value will + be ignored, since the corresponding X, Y + values are unreliable */ + u32 ts_pressure_fudge; + u32 ts_x_max; + u32 ts_x_fudge; + u32 ts_y_max; + u32 ts_y_fudge; + + unsigned ts_ignore_last : 1; +}; + +#endif diff --git a/include/linux/spi/tsc210x.h b/include/linux/spi/tsc210x.h new file mode 100644 index 00000000000..820bc9c537f --- /dev/null +++ b/include/linux/spi/tsc210x.h @@ -0,0 +1,231 @@ +/* + * include/linux/spi/tsc210x.h + * + * TI TSC2101/2102 control register definitions. + * + * Copyright (c) 2005-2007 Andrzej Zaborowski + * + * This package 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 package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __LINUX_SPI_TSC210X_H +#define __LINUX_SPI_TSC210X_H + +struct apm_power_info; + +struct tsc210x_config { + int use_internal; /* Use internal reference voltage */ + u32 monitor; /* What inputs are wired on this board */ + int temp_at25c[2]; /* Thermometer calibration data */ + void (*apm_report)(struct apm_power_info *info, int battery[]); + /* Report status to APM based on battery[] */ + void *alsa_config; /* .platform_data for the ALSA device */ + const char *mclk; /* Optional: mclk name */ + const char *bclk; /* Optional: bclk name */ +}; + +#define TSC_BAT1 (1 << 0) +#define TSC_BAT2 (1 << 1) +#define TSC_AUX1 (1 << 2) +#define TSC_AUX2 (1 << 3) +#define TSC_TEMP (1 << 4) + +#define TSC_AUX TSC_AUX1 +#define TSC_VBAT TSC_BAT1 + +struct tsc210x_dev; + +/* Drivers for tsc210x components like touchscreen, sensor, and audio + * are packaged as platform drivers which can issue synchronous register + * acceses, and may also register a callback to process their particular + * type of data when that data is automatically sampled. The platform + * device is a child of the TSC spi device. + */ + +extern int tsc210x_read_sync(struct tsc210x_dev *dev, int page, u8 address); +extern int tsc210x_reads_sync(struct tsc210x_dev *dev, int page, + u8 startaddress, u16 *data, int numregs); +extern int tsc210x_write_sync(struct tsc210x_dev *dev, int page, + u8 address, u16 data); + +typedef void (*tsc210x_touch_t)(void *context, int touching); +typedef void (*tsc210x_coords_t)(void *context, int x, int y, int z1, int z2); +typedef void (*tsc210x_ports_t)(void *context, int bat[], int aux[]); +typedef void (*tsc210x_temp_t)(void *context, int temp); + +extern int tsc210x_touch_cb(struct device *dev, + tsc210x_touch_t handler, void *context); +extern int tsc210x_coords_cb(struct device *dev, + tsc210x_coords_t handler, void *context); +extern int tsc210x_ports_cb(struct device *dev, + tsc210x_ports_t handler, void *context); +extern int tsc210x_temp1_cb(struct device *dev, + tsc210x_temp_t handler, void *context); +extern int tsc210x_temp2_cb(struct device *dev, + tsc210x_temp_t handler, void *context); + +#if defined(CONFIG_SOUND) || defined(CONFIG_SOUND_MODULE) +extern void tsc210x_set_dac_volume(struct device *dev, u8 left, u8 right); +extern void tsc210x_set_dac_mute(struct device *dev, int left, int right); +extern void tsc210x_get_dac_mute(struct device *dev, int *left, int *right); +extern void tsc210x_dac_power(struct device *dev, int on); +extern int tsc210x_set_rate(struct device *dev, int rate); +extern void tsc210x_set_i2s_master(struct device *dev, int state); +extern void tsc210x_set_deemphasis(struct device *dev, int enable); +extern void tsc2102_set_bassboost(struct device *dev, int enable); +#endif + +/* + * Emit a short keyclick typically in order to give feedback to + * user on specific events. + * + * amplitude must be between 0 (lowest) and 2 (highest). + * freq must be between 0 (corresponds to 62.5 Hz) and 7 (8 kHz). + * length should be between 2 and 32 periods. + * + * This function sleeps but for a period unrelated to the length of + * the sound, i.e. returning doesn't indicate that the sound has + * finished. + */ +extern void tsc210x_keyclick(struct tsc210x_dev *dev, + int amplitude, int freq, int length); + +/* Page 0, Touch Screen & Keypad Data registers */ +#define TSC210X_TS_X 0, 0x00 +#define TSC210X_TS_Y 0, 0x01 +#define TSC210X_TS_Z1 0, 0x02 +#define TSC210X_TS_Z2 0, 0x03 +#define TSC210X_TS_BAT1 0, 0x05 +#define TSC2102_TS_BAT2 0, 0x06 +#define TSC210X_TS_AUX1 0, 0x07 +#define TSC2101_TS_AUX2 0, 0x08 +#define TSC210X_TS_TEMP1 0, 0x09 +#define TSC210X_TS_TEMP2 0, 0x0a + +/* Page 1, Touch Screen & Keypad Control registers */ +#define TSC210X_TS_ADC_CTRL 1, 0x00 +#define TSC210X_TS_STATUS_CTRL 1, 0x01 +#define TSC2101_TS_BUFFER_CTRL 1, 0x02 +#define TSC210X_TS_REF_CTRL 1, 0x03 +#define TSC210X_TS_RESET_CTRL 1, 0x04 +#define TSC210X_TS_CONFIG_CTRL 1, 0x05 +#define TSC2101_TS_TEMPMAX_CTRL 1, 0x06 +#define TSC2101_TS_TEMPMIN_CTRL 1, 0x07 +#define TSC2101_TS_AUX1MAX_CTRL 1, 0x08 +#define TSC2101_TS_AUX1MIN_CTRL 1, 0x09 +#define TSC2101_TS_AUX2MAX_CTRL 1, 0x0a +#define TSC2101_TS_AUX2MIN_CTRL 1, 0x0b +#define TSC2101_TS_MCONFIG_CTRL 1, 0x0c +#define TSC2101_TS_DELAY_CTRL 1, 0x0d + +/* Page 2, Audio Control registers */ +#define TSC210X_AUDIO1_CTRL 2, 0x00 +#define TSC2101_HEADSET_GAIN_CTRL 2, 0x01 +#define TSC210X_DAC_GAIN_CTRL 2, 0x02 +#define TSC2101_MIXER_GAIN_CTRL 2, 0x03 +#define TSC210X_AUDIO2_CTRL 2, 0x04 +#define TSC210X_POWER_CTRL 2, 0x05 +#define TSC210X_AUDIO3_CTRL 2, 0x06 +#define TSC210X_LCH_BASS_BOOST_N0 2, 0x07 +#define TSC210X_LCH_BASS_BOOST_N1 2, 0x08 +#define TSC210X_LCH_BASS_BOOST_N2 2, 0x09 +#define TSC210X_LCH_BASS_BOOST_N3 2, 0x0a +#define TSC210X_LCH_BASS_BOOST_N4 2, 0x0b +#define TSC210X_LCH_BASS_BOOST_N5 2, 0x0c +#define TSC210X_LCH_BASS_BOOST_D1 2, 0x0d +#define TSC210X_LCH_BASS_BOOST_D2 2, 0x0e +#define TSC210X_LCH_BASS_BOOST_D4 2, 0x0f +#define TSC210X_LCH_BASS_BOOST_D5 2, 0x10 +#define TSC210X_RCH_BASS_BOOST_N0 2, 0x11 +#define TSC210X_RCH_BASS_BOOST_N1 2, 0x12 +#define TSC210X_RCH_BASS_BOOST_N2 2, 0x13 +#define TSC210X_RCH_BASS_BOOST_N3 2, 0x14 +#define TSC210X_RCH_BASS_BOOST_N4 2, 0x15 +#define TSC210X_RCH_BASS_BOOST_N5 2, 0x16 +#define TSC210X_RCH_BASS_BOOST_D1 2, 0x17 +#define TSC210X_RCH_BASS_BOOST_D2 2, 0x18 +#define TSC210X_RCH_BASS_BOOST_D4 2, 0x19 +#define TSC210X_RCH_BASS_BOOST_D5 2, 0x1a +#define TSC210X_PLL1_CTRL 2, 0x1b +#define TSC210X_PLL2_CTRL 2, 0x1c +#define TSC210X_AUDIO4_CTRL 2, 0x1d +#define TSC2101_HANDSET_GAIN_CTRL 2, 0x1e +#define TSC2101_CELL_GAIN_CTRL 2, 0x1f +#define TSC2101_AUIDO5_CTRL 2, 0x20 +#define TSC2101_AUDIO6_CTRL 2, 0x21 +#define TSC2101_AUDIO7_CTRL 2, 0x22 +#define TSC2101_GPIO_CTRL 2, 0x23 +#define TSC2101_IN_AGC_CTRL 2, 0x24 +#define TSC2101_POWER_STATUS 2, 0x25 +#define TSC2101_MIX_AGC_CTRL 2, 0x26 +#define TSC2101_CELL_AGC_CTRL 2, 0x27 + +/* Field masks for Audio Control 1 */ +#define AC1_WLEN(ARG) (((ARG) & 0x03) << 10) +#define AC1_DATFM(ARG) (((ARG) & 0x03) << 8) +#define AC1_DACFS(ARG) ((ARG) & 0x3f) + +/* Field masks for TSC2102_DAC_GAIN_CTRL */ +#define DGC_DALMU (1 << 15) +#define DGC_DALVL(ARG) (((ARG) & 0x7f) << 8) +#define DGC_DARMU (1 << 7) +#define DGC_DARVL(ARG) (((ARG) & 0x7f)) + +/* Field formats for TSC210X_AUDIO2_CTRL */ +#define AC2_KCLEN (1 << 15) +#define AC2_KCLAC(ARG) (((ARG) & 0x07) << 12) +#define AC2_KCLFRQ(ARG) (((ARG) & 0x07) << 8) +#define AC2_KCLLN(ARG) (((ARG) & 0x0f) << 4) +#define AC2_DLGAF (1 << 3) +#define AC2_DRGAF (1 << 2) +#define AC2_DASTC (1 << 1) + +/* Field masks for TSC210X_DAC_POWER_CTRL */ +#define CPC_PWDNC (1 << 15) +#define CPC_DAODRC (1 << 12) +#define CPC_DAPWDN (1 << 10) +#define CPC_VGPWDN (1 << 8) +#define CPC_DAPWDF (1 << 6) +#define CPC_BASSBC (1 << 1) +#define CPC_DEEMPF (0x01) + +/* Field masks for TSC210X_AUDIO3_CTRL */ +#define AC3_DMSVOL(ARG) (((ARG) & 0x03) << 14) +#define AC3_REFFS (1 << 13) +#define AC3_DAXFM (1 << 12) +#define AC3_SLVMS (1 << 11) +#define AC3_DALOVF (1 << 7) +#define AC3_DAROVF (1 << 6) +#define AC3_REVID(ARG) (((ARG) & 0x07)) + +/* Field masks for TSC210X_PLL1_CTRL */ +#define PLL1_PLLEN (1 << 15) +#define PLL1_Q_VAL(ARG) (((ARG) & 0x0f) << 11) +#define PLL1_P_VAL(ARG) (((ARG) & 0x07) << 8) +#define PLL1_I_VAL(ARG) (((ARG) & 0x3f) << 2) + +/* Field masks for TSC210X_PLL2_CTRL */ +#define PLL2_D_VAL(ARG) (((ARG) & 0x3fff) << 2) + +/* Field masks for TSC210X_AUDIO4_CTRL */ +#define AC4_DASTPD (1 << 14) + +struct tsc210x_rate_info_s { + u16 sample_rate; + u8 divisor; + u8 fs_44k; /* 44.1 kHz Fsref if 1, 48 kHz if 0 */ +}; + +#endif /* __LINUX_SPI_TSC210X_H */ diff --git a/include/linux/spi/tsc2301.h b/include/linux/spi/tsc2301.h new file mode 100644 index 00000000000..62e586286bc --- /dev/null +++ b/include/linux/spi/tsc2301.h @@ -0,0 +1,197 @@ +#ifndef _LINUX_SPI_TSC2301_H +#define _LINUX_SPI_TSC2301_H + +#include +#include + +struct tsc2301_platform_data { + /* + * Keypad + */ + s16 reset_gpio; + s16 keyb_int; + s16 keymap[16]; /* Set a key to a negative value if not used */ + unsigned kp_rep:1; /* Enable keypad repeating */ + char *keyb_name; /* Keyboard device name */ + + /* + * Touchscreen + */ + s16 dav_int; + u16 ts_x_plate_ohm; + u32 ts_stab_time; /* voltage settling time */ + u8 ts_hw_avg; /* HW assiseted averaging. Can be + 0, 4, 8, 16 samples per reading */ + u32 ts_max_pressure;/* Samples with bigger pressure value will + be ignored, since the corresponding X, Y + values are unreliable */ + u32 ts_touch_pressure; /* Pressure limit until we report a + touch event. After that we switch + to ts_max_pressure. */ + u32 ts_pressure_fudge; + u32 ts_x_max; + u32 ts_x_fudge; + u32 ts_y_max; + u32 ts_y_fudge; + + /* + * Audio + */ + unsigned pll_pdc:4; + unsigned pll_a:4; + unsigned pll_n:4; + unsigned pll_output:1; /* Output PLL on GPIO_0 */ + + unsigned mclk_ratio:2; + unsigned i2s_sample_rate:4; + unsigned i2s_format:2; + /* Mask for audio blocks to be powered down */ + u16 power_down_blocks; + + /* Called after codec has been initialized, can be NULL */ + int (* codec_init)(struct device *tsc2301_dev); + /* Called when codec is being removed, can be NULL */ + void (* codec_cleanup)(struct device *tsc2301_dev); + int (*enable_clock)(struct device *dev); + void (*disable_clock)(struct device *dev); + + const struct tsc2301_mixer_gpio { + const char *name; + unsigned gpio:4; + unsigned inverted:1; + unsigned def_enable:1; /* enable by default */ + unsigned deactivate_on_pd:1; /* power-down flag */ + } *mixer_gpios; + int n_mixer_gpios; +}; + +struct tsc2301_kp; +struct tsc2301_ts; +struct tsc2301_mixer; + +struct tsc2301 { + struct spi_device *spi; + + s16 reset_gpio; + u16 config2_shadow; + + struct tsc2301_kp *kp; + struct tsc2301_ts *ts; + struct tsc2301_mixer *mixer; + + int (*enable_clock)(struct device *dev); + void (*disable_clock)(struct device *dev); +}; + + +#define TSC2301_HZ 33000000 + +#define TSC2301_REG(page, addr) (((page) << 11) | ((addr) << 5)) +#define TSC2301_REG_TO_PAGE(reg) (((reg) >> 11) & 0x03) +#define TSC2301_REG_TO_ADDR(reg) (((reg) >> 5) & 0x1f) + +#define TSC2301_REG_X TSC2301_REG(0, 0) +#define TSC2301_REG_Y TSC2301_REG(0, 1) +#define TSC2301_REG_Z1 TSC2301_REG(0, 2) +#define TSC2301_REG_Z2 TSC2301_REG(0, 3) +#define TSC2301_REG_KPDATA TSC2301_REG(0, 4) +#define TSC2301_REG_ADC TSC2301_REG(1, 0) +#define TSC2301_REG_KEY TSC2301_REG(1, 1) +#define TSC2301_REG_DAC TSC2301_REG(1, 2) +#define TSC2301_REG_REF TSC2301_REG(1, 3) +#define TSC2301_REG_RESET TSC2301_REG(1, 4) +#define TSC2301_REG_CONFIG TSC2301_REG(1, 5) +#define TSC2301_REG_CONFIG2 TSC2301_REG(1, 6) +#define TSC2301_REG_KPMASK TSC2301_REG(1, 16) +#define TSC2301_REG_AUDCNTL TSC2301_REG(2, 0) +#define TSC2301_REG_ADCVOL TSC2301_REG(2, 1) +#define TSC2301_REG_DACVOL TSC2301_REG(2, 2) +#define TSC2301_REG_BPVOL TSC2301_REG(2, 3) +#define TSC2301_REG_KEYCTL TSC2301_REG(2, 4) +#define TSC2301_REG_PD_MISC TSC2301_REG(2, 5) +#define TSC2301_REG_GPIO TSC2301_REG(2, 6) +#define TSC2301_REG_ADCLKCFG TSC2301_REG(2, 27) + +#define TSC2301_REG_PD_MISC_APD (1 << 15) +#define TSC2301_REG_PD_MISC_AVPD (1 << 14) +#define TSC2301_REG_PD_MISC_ABPD (1 << 13) +#define TSC2301_REG_PD_MISC_HAPD (1 << 12) +#define TSC2301_REG_PD_MISC_MOPD (1 << 11) +#define TSC2301_REG_PD_MISC_DAPD (1 << 10) +#define TSC2301_REG_PD_MISC_ADPDL (1 << 9) +#define TSC2301_REG_PD_MISC_ADPDR (1 << 8) +#define TSC2301_REG_PD_MISC_PDSTS (1 << 7) +#define TSC2301_REG_PD_MISC_MIBPD (1 << 6) +#define TSC2301_REG_PD_MISC_OTSYN (1 << 2) + +/* I2S sample rate */ +#define TSC2301_I2S_SR_48000 0x00 +#define TSC2301_I2S_SR_44100 0x01 +#define TSC2301_I2S_SR_32000 0x02 +#define TSC2301_I2S_SR_24000 0x03 +#define TSC2301_I2S_SR_22050 0x04 +#define TSC2301_I2S_SR_16000 0x05 +#define TSC2301_I2S_SR_12000 0x06 +#define TSC2301_I2S_SR_11050 0x07 +#define TSC2301_I2S_SR_8000 0x08 + +/* 16-bit, MSB-first. DAC Right-Justified, ADC Left-Justified */ +#define TSC2301_I2S_FORMAT0 0x00 +/* 20-bit, MSB-first. DAC Right-Justified, ADC Left-Justified */ +#define TSC2301_I2S_FORMAT1 0x01 +/* 20-bit, MSB-first. DAC Left-Justified, ADC Left-Justified */ +#define TSC2301_I2S_FORMAT2 0x02 +/* 20-bit, MSB-first */ +#define TSC2301_I2S_FORMAT3 0x03 + +/* Master Clock Ratio */ +#define TSC2301_MCLK_256xFS 0x00 /* default */ +#define TSC2301_MCLK_384xFS 0x01 +#define TSC2301_MCLK_512xFS 0x02 + + +extern u16 tsc2301_read_reg(struct tsc2301 *tsc, int reg); +extern void tsc2301_write_reg(struct tsc2301 *tsc, int reg, u16 val); +extern void tsc2301_write_kbc(struct tsc2301 *tsc, int val); +extern void tsc2301_write_pll(struct tsc2301 *tsc, int pll_n, int pll_a, + int pll_pdc, int pct_e, int pll_o); +extern void tsc2301_read_buf(struct tsc2301 *tsc, int reg, u16 *buf, int len); + +#define TSC2301_DECL_MOD(module) \ +extern int tsc2301_##module##_init(struct tsc2301 *tsc, \ + struct tsc2301_platform_data *pdata); \ +extern void tsc2301_##module##_exit(struct tsc2301 *tsc); \ +extern int tsc2301_##module##_suspend(struct tsc2301 *tsc); \ +extern void tsc2301_##module##_resume(struct tsc2301 *tsc); + +#define TSC2301_DECL_EMPTY_MOD(module) \ +static inline int tsc2301_##module##_init(struct tsc2301 *tsc, \ + struct tsc2301_platform_data *pdata) \ +{ \ + return 0; \ +} \ +static inline void tsc2301_##module##_exit(struct tsc2301 *tsc) {} \ +static inline int tsc2301_##module##_suspend(struct tsc2301 *tsc) \ +{ \ + return 0; \ +} \ +static inline void tsc2301_##module##_resume(struct tsc2301 *tsc) {} + +#ifdef CONFIG_KEYBOARD_TSC2301 +TSC2301_DECL_MOD(kp) +void tsc2301_kp_restart(struct tsc2301 *tsc); +#else +TSC2301_DECL_EMPTY_MOD(kp) +static inline void tsc2301_kp_restart(struct tsc2301 *tsc) {} +#endif + +#ifdef CONFIG_TOUCHSCREEN_TSC2301 +TSC2301_DECL_MOD(ts) +#else +TSC2301_DECL_EMPTY_MOD(ts) +#endif + +extern void tsc2301_mixer_enable_mclk(struct device *tsc_dev); +extern void tsc2301_mixer_disable_mclk(struct device *tsc_dev); + +#endif diff --git a/net/ipv4/netfilter/Kconfig b/net/ipv4/netfilter/Kconfig index 1833bdbf980..147c68730a5 100644 --- a/net/ipv4/netfilter/Kconfig +++ b/net/ipv4/netfilter/Kconfig @@ -202,6 +202,19 @@ config IP_NF_TARGET_REDIRECT come to the local machine instead of passing through. This is useful for transparent proxies. + To compile it as a module, choose M here. If unsure, say N. + +config IP_NF_TARGET_IDLETIMER + tristate "IDLETIMER target support" + depends on IP_NF_IPTABLES + help + This option adds a `IDLETIMER' target. Each matching packet resets + the timer associated with input and/or output interfaces. Timer + expiry causes kobject uevent. Idle timer can be read via sysfs. + + To compile it as a module, choose M here. If unsure, say N. + + To compile it as a module, choose M here. If unsure, say N. config NF_NAT_SNMP_BASIC diff --git a/net/ipv4/netfilter/Makefile b/net/ipv4/netfilter/Makefile index 48111594ee9..60bdaf1abc4 100644 --- a/net/ipv4/netfilter/Makefile +++ b/net/ipv4/netfilter/Makefile @@ -60,6 +60,7 @@ obj-$(CONFIG_IP_NF_TARGET_MASQUERADE) += ipt_MASQUERADE.o obj-$(CONFIG_IP_NF_TARGET_NETMAP) += ipt_NETMAP.o obj-$(CONFIG_IP_NF_TARGET_REDIRECT) += ipt_REDIRECT.o obj-$(CONFIG_IP_NF_TARGET_REJECT) += ipt_REJECT.o +obj-$(CONFIG_IP_NF_TARGET_IDLETIMER) += ipt_IDLETIMER.o obj-$(CONFIG_IP_NF_TARGET_ULOG) += ipt_ULOG.o # generic ARP tables diff --git a/net/ipv4/netfilter/ipt_IDLETIMER.c b/net/ipv4/netfilter/ipt_IDLETIMER.c new file mode 100644 index 00000000000..6432192cdeb --- /dev/null +++ b/net/ipv4/netfilter/ipt_IDLETIMER.c @@ -0,0 +1,305 @@ +/* + * linux/net/ipv4/netfilter/ipt_IDLETIMER.c + * + * Netfilter module to trigger a timer when packet matches. + * After timer expires a kevent will be sent. + * + * Copyright (C) 2004 Nokia Corporation. All rights reserved. + * Written by Timo Teras + * + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if 0 +#define DEBUGP(format, args...) printk("%s:%s:" format, \ + __FILE__, __FUNCTION__ , ## args) +#else +#define DEBUGP(format, args...) +#endif + +/* + * Internal timer management. + */ +static ssize_t utimer_attr_show(struct device *, struct device_attribute *attr, char *buf); +static ssize_t utimer_attr_store(struct device *, struct device_attribute *attr, + const char *buf, size_t count); + +struct utimer_t { + char name[IFNAMSIZ]; + struct list_head entry; + struct timer_list timer; + struct work_struct work; +}; + +static LIST_HEAD(active_utimer_head); +static DEFINE_SPINLOCK(list_lock); +static DEVICE_ATTR(idletimer, 0644, utimer_attr_show, utimer_attr_store); + +static void utimer_delete(struct utimer_t *timer) +{ + DEBUGP("Deleting timer '%s'\n", timer->name); + + list_del(&timer->entry); + del_timer_sync(&timer->timer); + kfree(timer); +} + +static void utimer_work(struct work_struct *work) +{ + struct utimer_t *timer = container_of(work, struct utimer_t, work); + struct net_device *netdev; + + netdev = dev_get_by_name(&init_net, timer->name); + + if (netdev != NULL) { + sysfs_notify(&netdev->dev.kobj, NULL, + "idletimer"); + dev_put(netdev); + } +} + +static void utimer_expired(unsigned long data) +{ + struct utimer_t *timer = (struct utimer_t *) data; + + DEBUGP("Timer '%s' expired\n", timer->name); + + spin_lock_bh(&list_lock); + utimer_delete(timer); + spin_unlock_bh(&list_lock); + + schedule_work(&timer->work); +} + +static struct utimer_t *utimer_create(const char *name) +{ + struct utimer_t *timer; + + timer = kmalloc(sizeof(struct utimer_t), GFP_ATOMIC); + if (timer == NULL) + return NULL; + + list_add(&timer->entry, &active_utimer_head); + strlcpy(timer->name, name, sizeof(timer->name)); + + init_timer(&timer->timer); + timer->timer.function = utimer_expired; + timer->timer.data = (unsigned long) timer; + + INIT_WORK(&timer->work, utimer_work); + + DEBUGP("Created timer '%s'\n", timer->name); + + return timer; +} + +static struct utimer_t *__utimer_find(const char *name) +{ + struct utimer_t *entry; + + list_for_each_entry(entry, &active_utimer_head, entry) { + if (strcmp(name, entry->name) == 0) { + return entry; + } + } + + return NULL; +} + +static void utimer_modify(const char *name, + unsigned long expires) +{ + struct utimer_t *timer; + + DEBUGP("Modifying timer '%s'\n", name); + spin_lock_bh(&list_lock); + timer = __utimer_find(name); + if (timer == NULL) + timer = utimer_create(name); + mod_timer(&timer->timer, expires); + spin_unlock_bh(&list_lock); +} + +static ssize_t utimer_attr_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct utimer_t *timer; + unsigned long expires = 0; + struct net_device *netdev = container_of(dev, struct net_device, dev); + + spin_lock_bh(&list_lock); + timer = __utimer_find(netdev->name); + if (timer) + expires = timer->timer.expires; + spin_unlock_bh(&list_lock); + + if (expires) + return sprintf(buf, "%lu\n", (expires-jiffies) / HZ); + + return sprintf(buf, "0\n"); +} + +static ssize_t utimer_attr_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int expires; + struct net_device *netdev = container_of(dev, struct net_device, dev); + + if (sscanf(buf, "%d", &expires) == 1) { + if (expires > 0) + utimer_modify(netdev->name, + jiffies+HZ*(unsigned long)expires); + } + + return count; +} + +static int utimer_notifier_call(struct notifier_block *this, + unsigned long event, void *ptr) +{ + struct net_device *dev = ptr; + int ret = NOTIFY_DONE; + + switch (event) { + case NETDEV_UP: + DEBUGP("NETDEV_UP: %s\n", dev->name); + ret = device_create_file(&dev->dev, + &dev_attr_idletimer); + break; + case NETDEV_DOWN: + DEBUGP("NETDEV_DOWN: %s\n", dev->name); + device_remove_file(&dev->dev, + &dev_attr_idletimer); + break; + } + + return ret; +} + +static struct notifier_block utimer_notifier_block = { + .notifier_call = utimer_notifier_call, +}; + + +static int utimer_init(void) +{ + return register_netdevice_notifier(&utimer_notifier_block); +} + +static void utimer_fini(void) +{ + struct utimer_t *entry, *next; + struct net_device *dev; + + list_for_each_entry_safe(entry, next, &active_utimer_head, entry) + utimer_delete(entry); + + rtnl_lock(); + unregister_netdevice_notifier(&utimer_notifier_block); + for_each_netdev(&init_net, dev) + utimer_notifier_call(&utimer_notifier_block, + NETDEV_DOWN, dev); + rtnl_unlock(); +} + +/* + * The actual iptables plugin. + */ +static unsigned int ipt_idletimer_target(struct sk_buff *pskb, + const struct net_device *in, + const struct net_device *out, + unsigned int hooknum, + const struct xt_target *xttarget, + const void *targinfo) +{ + struct ipt_idletimer_info *target = (struct ipt_idletimer_info*) targinfo; + unsigned long expires; + + expires = jiffies + HZ*target->timeout; + + if (in != NULL) + utimer_modify(in->name, expires); + + if (out != NULL) + utimer_modify(out->name, expires); + + return XT_CONTINUE; +} + +static bool ipt_idletimer_checkentry(const char *tablename, + const void *e, + const struct xt_target *target, + void *targinfo, + unsigned int hookmask) +{ + struct ipt_idletimer_info *info = + (struct ipt_idletimer_info *) targinfo; + + if (info->timeout == 0) { + DEBUGP("timeout value is zero\n"); + return 0; + } + + return true; +} + +static struct xt_target ipt_idletimer = { + .name = "IDLETIMER", + .target = ipt_idletimer_target, + .checkentry = ipt_idletimer_checkentry, + .me = THIS_MODULE, + .targetsize = sizeof(struct ipt_idletimer_info), +}; + +static int __init init(void) +{ + int ret; + + ret = utimer_init(); + if (ret) + return ret; + + if (xt_register_target(&ipt_idletimer)) { + utimer_fini(); + return -EINVAL; + } + + return 0; +} + +static void __exit fini(void) +{ + xt_unregister_target(&ipt_idletimer); + utimer_fini(); +} + +module_init(init); +module_exit(fini); + +MODULE_AUTHOR("Timo Teras "); +MODULE_DESCRIPTION("iptables idletimer target module"); +MODULE_LICENSE("GPL");