diff options
Diffstat (limited to 'arch')
-rw-r--r-- | arch/arm/plat-omap/Kconfig | 99 | ||||
-rw-r--r-- | arch/arm/plat-omap/Makefile | 16 | ||||
-rw-r--r-- | arch/arm/plat-omap/common.c | 135 | ||||
-rw-r--r-- | arch/arm/plat-omap/common.h | 36 | ||||
-rw-r--r-- | arch/arm/plat-omap/dma.c | 1086 | ||||
-rw-r--r-- | arch/arm/plat-omap/gpio.c | 762 | ||||
-rw-r--r-- | arch/arm/plat-omap/mcbsp.c | 685 | ||||
-rw-r--r-- | arch/arm/plat-omap/mux.c | 163 | ||||
-rw-r--r-- | arch/arm/plat-omap/ocpi.c | 114 | ||||
-rw-r--r-- | arch/arm/plat-omap/pm.c | 632 | ||||
-rw-r--r-- | arch/arm/plat-omap/sleep.S | 314 | ||||
-rw-r--r-- | arch/arm/plat-omap/usb.c | 593 |
12 files changed, 4635 insertions, 0 deletions
diff --git a/arch/arm/plat-omap/Kconfig b/arch/arm/plat-omap/Kconfig new file mode 100644 index 000000000000..a72fe55b513b --- /dev/null +++ b/arch/arm/plat-omap/Kconfig @@ -0,0 +1,99 @@ +if ARCH_OMAP + +menu "TI OMAP Implementations" + +config ARCH_OMAP_OTG + bool + +choice + prompt "OMAP System Type" + default ARCH_OMAP1 + +config ARCH_OMAP1 + bool "TI OMAP1" + +config ARCH_OMAP2 + bool "TI OMAP2" + +endchoice + +comment "OMAP Feature Selections" + +config OMAP_MUX + bool "OMAP multiplexing support" + depends on ARCH_OMAP + default y + help + Pin multiplexing support for OMAP boards. If your bootloader + sets the multiplexing correctly, say N. Otherwise, or if unsure, + say Y. + +config OMAP_MUX_DEBUG + bool "Multiplexing debug output" + depends on OMAP_MUX + default n + help + Makes the multiplexing functions print out a lot of debug info. + This is useful if you want to find out the correct values of the + multiplexing registers. + +config OMAP_MUX_WARNINGS + bool "Warn about pins the bootloader didn't set up" + depends on OMAP_MUX + default y + help + Choose Y here to warn whenever driver initialization logic needs + to change the pin multiplexing setup. When there are no warnings + printed, it's safe to deselect OMAP_MUX for your product. + +choice + prompt "System timer" + default OMAP_MPU_TIMER + +config OMAP_MPU_TIMER + bool "Use mpu timer" + help + Select this option if you want to use the OMAP mpu timer. This + timer provides more intra-tick resolution than the 32KHz timer, + but consumes more power. + +config OMAP_32K_TIMER + bool "Use 32KHz timer" + depends on ARCH_OMAP16XX + help + Select this option if you want to enable the OMAP 32KHz timer. + This timer saves power compared to the OMAP_MPU_TIMER, and has + support for no tick during idle. The 32KHz timer provides less + intra-tick resolution than OMAP_MPU_TIMER. The 32KHz timer is + currently only available for OMAP-16xx. + +endchoice + +config OMAP_32K_TIMER_HZ + int "Kernel internal timer frequency for 32KHz timer" + range 32 1024 + depends on OMAP_32K_TIMER + default "128" + help + Kernel internal timer frequency should be a divisor of 32768, + such as 64 or 128. + +choice + prompt "Low-level debug console UART" + depends on ARCH_OMAP + default OMAP_LL_DEBUG_UART1 + +config OMAP_LL_DEBUG_UART1 + bool "UART1" + +config OMAP_LL_DEBUG_UART2 + bool "UART2" + +config OMAP_LL_DEBUG_UART3 + bool "UART3" + +endchoice + +endmenu + +endif diff --git a/arch/arm/plat-omap/Makefile b/arch/arm/plat-omap/Makefile new file mode 100644 index 000000000000..3be25ebfc45e --- /dev/null +++ b/arch/arm/plat-omap/Makefile @@ -0,0 +1,16 @@ +# +# Makefile for the linux kernel. +# + +# Common support +obj-y := common.o dma.o clock.o mux.o gpio.o mcbsp.o usb.o +obj-m := +obj-n := +obj- := + +# OCPI interconnect support for 1710, 1610 and 5912 +obj-$(CONFIG_ARCH_OMAP16XX) += ocpi.o + +# Power Management +obj-$(CONFIG_PM) += pm.o sleep.o + diff --git a/arch/arm/plat-omap/common.c b/arch/arm/plat-omap/common.c new file mode 100644 index 000000000000..ea967a8f6ce5 --- /dev/null +++ b/arch/arm/plat-omap/common.c @@ -0,0 +1,135 @@ +/* + * linux/arch/arm/plat-omap/common.c + * + * Code common to all OMAP machines. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/console.h> +#include <linux/serial.h> +#include <linux/tty.h> +#include <linux/serial_8250.h> +#include <linux/serial_reg.h> + +#include <asm/hardware.h> +#include <asm/system.h> +#include <asm/pgtable.h> +#include <asm/mach/map.h> +#include <asm/hardware/clock.h> +#include <asm/io.h> +#include <asm/mach-types.h> + +#include <asm/arch/board.h> +#include <asm/arch/mux.h> +#include <asm/arch/fpga.h> + +#include "clock.h" + +#define NO_LENGTH_CHECK 0xffffffff + +extern int omap_bootloader_tag_len; +extern u8 omap_bootloader_tag[]; + +struct omap_board_config_kernel *omap_board_config; +int omap_board_config_size = 0; + +static const void *get_config(u16 tag, size_t len, int skip, size_t *len_out) +{ + struct omap_board_config_kernel *kinfo = NULL; + int i; + +#ifdef CONFIG_OMAP_BOOT_TAG + struct omap_board_config_entry *info = NULL; + + if (omap_bootloader_tag_len > 4) + info = (struct omap_board_config_entry *) omap_bootloader_tag; + while (info != NULL) { + u8 *next; + + if (info->tag == tag) { + if (skip == 0) + break; + skip--; + } + + if ((info->len & 0x03) != 0) { + /* We bail out to avoid an alignment fault */ + printk(KERN_ERR "OMAP peripheral config: Length (%d) not word-aligned (tag %04x)\n", + info->len, info->tag); + return NULL; + } + next = (u8 *) info + sizeof(*info) + info->len; + if (next >= omap_bootloader_tag + omap_bootloader_tag_len) + info = NULL; + else + info = (struct omap_board_config_entry *) next; + } + if (info != NULL) { + /* Check the length as a lame attempt to check for + * binary inconsistancy. */ + if (len != NO_LENGTH_CHECK) { + /* Word-align len */ + if (len & 0x03) + len = (len + 3) & ~0x03; + if (info->len != len) { + printk(KERN_ERR "OMAP peripheral config: Length mismatch with tag %x (want %d, got %d)\n", + tag, len, info->len); + return NULL; + } + } + if (len_out != NULL) + *len_out = info->len; + return info->data; + } +#endif + /* Try to find the config from the board-specific structures + * in the kernel. */ + for (i = 0; i < omap_board_config_size; i++) { + if (omap_board_config[i].tag == tag) { + kinfo = &omap_board_config[i]; + break; + } + } + if (kinfo == NULL) + return NULL; + return kinfo->data; +} + +const void *__omap_get_config(u16 tag, size_t len, int nr) +{ + return get_config(tag, len, nr, NULL); +} +EXPORT_SYMBOL(__omap_get_config); + +const void *omap_get_var_config(u16 tag, size_t *len) +{ + return get_config(tag, NO_LENGTH_CHECK, 0, len); +} +EXPORT_SYMBOL(omap_get_var_config); + +static int __init omap_add_serial_console(void) +{ + const struct omap_serial_console_config *info; + + info = omap_get_config(OMAP_TAG_SERIAL_CONSOLE, + struct omap_serial_console_config); + if (info != NULL && info->console_uart) { + static char speed[11], *opt = NULL; + + if (info->console_speed) { + snprintf(speed, sizeof(speed), "%u", info->console_speed); + opt = speed; + } + return add_preferred_console("ttyS", info->console_uart - 1, opt); + } + return 0; +} +console_initcall(omap_add_serial_console); diff --git a/arch/arm/plat-omap/common.h b/arch/arm/plat-omap/common.h new file mode 100644 index 000000000000..893964dfe18e --- /dev/null +++ b/arch/arm/plat-omap/common.h @@ -0,0 +1,36 @@ +/* + * linux/arch/arm/plat-omap/common.h + * + * Header for code common to all OMAP machines. + * + * 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 __ARCH_ARM_MACH_OMAP_COMMON_H +#define __ARCH_ARM_MACH_OMAP_COMMON_H + +struct sys_timer; + +extern void omap_map_common_io(void); +extern struct sys_timer omap_timer; +extern void omap_serial_init(int ports[]); + +#endif /* __ARCH_ARM_MACH_OMAP_COMMON_H */ diff --git a/arch/arm/plat-omap/dma.c b/arch/arm/plat-omap/dma.c new file mode 100644 index 000000000000..015bd2cf869f --- /dev/null +++ b/arch/arm/plat-omap/dma.c @@ -0,0 +1,1086 @@ +/* + * linux/arch/arm/plat-omap/dma.c + * + * Copyright (C) 2003 Nokia Corporation + * Author: Juha Yrjölä <juha.yrjola@nokia.com> + * DMA channel linking for 1610 by Samuel Ortiz <samuel.ortiz@nokia.com> + * Graphics DMA and LCD DMA graphics tranformations + * by Imre Deak <imre.deak@nokia.com> + * Some functions based on earlier dma-omap.c Copyright (C) 2001 RidgeRun, Inc. + * + * Support functions for the OMAP internal DMA channels. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/spinlock.h> +#include <linux/errno.h> +#include <linux/interrupt.h> + +#include <asm/system.h> +#include <asm/irq.h> +#include <asm/hardware.h> +#include <asm/dma.h> +#include <asm/io.h> + +#include <asm/arch/tc.h> + +#define OMAP_DMA_ACTIVE 0x01 + +#define OMAP_DMA_CCR_EN (1 << 7) + +#define OMAP_FUNC_MUX_ARM_BASE (0xfffe1000 + 0xec) + +static int enable_1510_mode = 0; + +struct omap_dma_lch { + int next_lch; + int dev_id; + u16 saved_csr; + u16 enabled_irqs; + const char *dev_name; + void (* callback)(int lch, u16 ch_status, void *data); + void *data; + long flags; +}; + +static int dma_chan_count; + +static spinlock_t dma_chan_lock; +static struct omap_dma_lch dma_chan[OMAP_LOGICAL_DMA_CH_COUNT]; + +const static u8 dma_irq[OMAP_LOGICAL_DMA_CH_COUNT] = { + INT_DMA_CH0_6, INT_DMA_CH1_7, INT_DMA_CH2_8, INT_DMA_CH3, + INT_DMA_CH4, INT_DMA_CH5, INT_1610_DMA_CH6, INT_1610_DMA_CH7, + INT_1610_DMA_CH8, INT_1610_DMA_CH9, INT_1610_DMA_CH10, + INT_1610_DMA_CH11, INT_1610_DMA_CH12, INT_1610_DMA_CH13, + INT_1610_DMA_CH14, INT_1610_DMA_CH15, INT_DMA_LCD +}; + +static inline int get_gdma_dev(int req) +{ + u32 reg = OMAP_FUNC_MUX_ARM_BASE + ((req - 1) / 5) * 4; + int shift = ((req - 1) % 5) * 6; + + return ((omap_readl(reg) >> shift) & 0x3f) + 1; +} + +static inline void set_gdma_dev(int req, int dev) +{ + u32 reg = OMAP_FUNC_MUX_ARM_BASE + ((req - 1) / 5) * 4; + int shift = ((req - 1) % 5) * 6; + u32 l; + + l = omap_readl(reg); + l &= ~(0x3f << shift); + l |= (dev - 1) << shift; + omap_writel(l, reg); +} + +static void clear_lch_regs(int lch) +{ + int i; + u32 lch_base = OMAP_DMA_BASE + lch * 0x40; + + for (i = 0; i < 0x2c; i += 2) + omap_writew(0, lch_base + i); +} + +void omap_set_dma_priority(int dst_port, int priority) +{ + unsigned long reg; + u32 l; + + switch (dst_port) { + case OMAP_DMA_PORT_OCP_T1: /* FFFECC00 */ + reg = OMAP_TC_OCPT1_PRIOR; + break; + case OMAP_DMA_PORT_OCP_T2: /* FFFECCD0 */ + reg = OMAP_TC_OCPT2_PRIOR; + break; + case OMAP_DMA_PORT_EMIFF: /* FFFECC08 */ + reg = OMAP_TC_EMIFF_PRIOR; + break; + case OMAP_DMA_PORT_EMIFS: /* FFFECC04 */ + reg = OMAP_TC_EMIFS_PRIOR; + break; + default: + BUG(); + return; + } + l = omap_readl(reg); + l &= ~(0xf << 8); + l |= (priority & 0xf) << 8; + omap_writel(l, reg); +} + +void omap_set_dma_transfer_params(int lch, int data_type, int elem_count, + int frame_count, int sync_mode) +{ + u16 w; + + w = omap_readw(OMAP_DMA_CSDP(lch)); + w &= ~0x03; + w |= data_type; + omap_writew(w, OMAP_DMA_CSDP(lch)); + + w = omap_readw(OMAP_DMA_CCR(lch)); + w &= ~(1 << 5); + if (sync_mode == OMAP_DMA_SYNC_FRAME) + w |= 1 << 5; + omap_writew(w, OMAP_DMA_CCR(lch)); + + w = omap_readw(OMAP_DMA_CCR2(lch)); + w &= ~(1 << 2); + if (sync_mode == OMAP_DMA_SYNC_BLOCK) + w |= 1 << 2; + omap_writew(w, OMAP_DMA_CCR2(lch)); + + omap_writew(elem_count, OMAP_DMA_CEN(lch)); + omap_writew(frame_count, OMAP_DMA_CFN(lch)); + +} +void omap_set_dma_color_mode(int lch, enum omap_dma_color_mode mode, u32 color) +{ + u16 w; + + BUG_ON(omap_dma_in_1510_mode()); + + w = omap_readw(OMAP_DMA_CCR2(lch)) & ~0x03; + switch (mode) { + case OMAP_DMA_CONSTANT_FILL: + w |= 0x01; + break; + case OMAP_DMA_TRANSPARENT_COPY: + w |= 0x02; + break; + case OMAP_DMA_COLOR_DIS: + break; + default: + BUG(); + } + omap_writew(w, OMAP_DMA_CCR2(lch)); + + w = omap_readw(OMAP_DMA_LCH_CTRL(lch)) & ~0x0f; + /* Default is channel type 2D */ + if (mode) { + omap_writew((u16)color, OMAP_DMA_COLOR_L(lch)); + omap_writew((u16)(color >> 16), OMAP_DMA_COLOR_U(lch)); + w |= 1; /* Channel type G */ + } + omap_writew(w, OMAP_DMA_LCH_CTRL(lch)); +} + + +void omap_set_dma_src_params(int lch, int src_port, int src_amode, + unsigned long src_start) +{ + u16 w; + + w = omap_readw(OMAP_DMA_CSDP(lch)); + w &= ~(0x1f << 2); + w |= src_port << 2; + omap_writew(w, OMAP_DMA_CSDP(lch)); + + w = omap_readw(OMAP_DMA_CCR(lch)); + w &= ~(0x03 << 12); + w |= src_amode << 12; + omap_writew(w, OMAP_DMA_CCR(lch)); + + omap_writew(src_start >> 16, OMAP_DMA_CSSA_U(lch)); + omap_writew(src_start, OMAP_DMA_CSSA_L(lch)); +} + +void omap_set_dma_src_index(int lch, int eidx, int fidx) +{ + omap_writew(eidx, OMAP_DMA_CSEI(lch)); + omap_writew(fidx, OMAP_DMA_CSFI(lch)); +} + +void omap_set_dma_src_data_pack(int lch, int enable) +{ + u16 w; + + w = omap_readw(OMAP_DMA_CSDP(lch)) & ~(1 << 6); + w |= enable ? (1 << 6) : 0; + omap_writew(w, OMAP_DMA_CSDP(lch)); +} + +void omap_set_dma_src_burst_mode(int lch, enum omap_dma_burst_mode burst_mode) +{ + u16 w; + + w = omap_readw(OMAP_DMA_CSDP(lch)) & ~(0x03 << 7); + switch (burst_mode) { + case OMAP_DMA_DATA_BURST_DIS: + break; + case OMAP_DMA_DATA_BURST_4: + w |= (0x01 << 7); + break; + case OMAP_DMA_DATA_BURST_8: + /* not supported by current hardware + * w |= (0x03 << 7); + * fall through + */ + default: + BUG(); + } + omap_writew(w, OMAP_DMA_CSDP(lch)); +} + +void omap_set_dma_dest_params(int lch, int dest_port, int dest_amode, + unsigned long dest_start) +{ + u16 w; + + w = omap_readw(OMAP_DMA_CSDP(lch)); + w &= ~(0x1f << 9); + w |= dest_port << 9; + omap_writew(w, OMAP_DMA_CSDP(lch)); + + w = omap_readw(OMAP_DMA_CCR(lch)); + w &= ~(0x03 << 14); + w |= dest_amode << 14; + omap_writew(w, OMAP_DMA_CCR(lch)); + + omap_writew(dest_start >> 16, OMAP_DMA_CDSA_U(lch)); + omap_writew(dest_start, OMAP_DMA_CDSA_L(lch)); +} + +void omap_set_dma_dest_index(int lch, int eidx, int fidx) +{ + omap_writew(eidx, OMAP_DMA_CDEI(lch)); + omap_writew(fidx, OMAP_DMA_CDFI(lch)); +} + +void omap_set_dma_dest_data_pack(int lch, int enable) +{ + u16 w; + + w = omap_readw(OMAP_DMA_CSDP(lch)) & ~(1 << 13); + w |= enable ? (1 << 13) : 0; + omap_writew(w, OMAP_DMA_CSDP(lch)); +} + +void omap_set_dma_dest_burst_mode(int lch, enum omap_dma_burst_mode burst_mode) +{ + u16 w; + + w = omap_readw(OMAP_DMA_CSDP(lch)) & ~(0x03 << 14); + switch (burst_mode) { + case OMAP_DMA_DATA_BURST_DIS: + break; + case OMAP_DMA_DATA_BURST_4: + w |= (0x01 << 14); + break; + case OMAP_DMA_DATA_BURST_8: + w |= (0x03 << 14); + break; + default: + printk(KERN_ERR "Invalid DMA burst mode\n"); + BUG(); + return; + } + omap_writew(w, OMAP_DMA_CSDP(lch)); +} + +static inline void init_intr(int lch) +{ + u16 w; + + /* Read CSR to make sure it's cleared. */ + w = omap_readw(OMAP_DMA_CSR(lch)); + /* Enable some nice interrupts. */ + omap_writew(dma_chan[lch].enabled_irqs, OMAP_DMA_CICR(lch)); + dma_chan[lch].flags |= OMAP_DMA_ACTIVE; +} + +static inline void enable_lnk(int lch) +{ + u16 w; + + /* Clear the STOP_LNK bits */ + w = omap_readw(OMAP_DMA_CLNK_CTRL(lch)); + w &= ~(1 << 14); + omap_writew(w, OMAP_DMA_CLNK_CTRL(lch)); + + /* And set the ENABLE_LNK bits */ + if (dma_chan[lch].next_lch != -1) + omap_writew(dma_chan[lch].next_lch | (1 << 15), + OMAP_DMA_CLNK_CTRL(lch)); +} + +static inline void disable_lnk(int lch) +{ + u16 w; + + /* Disable interrupts */ + omap_writew(0, OMAP_DMA_CICR(lch)); + + /* Set the STOP_LNK bit */ + w = omap_readw(OMAP_DMA_CLNK_CTRL(lch)); + w |= (1 << 14); + w = omap_writew(w, OMAP_DMA_CLNK_CTRL(lch)); + + dma_chan[lch].flags &= ~OMAP_DMA_ACTIVE; +} + +void omap_start_dma(int lch) +{ + u16 w; + + if (!omap_dma_in_1510_mode() && dma_chan[lch].next_lch != -1) { + int next_lch, cur_lch; + char dma_chan_link_map[OMAP_LOGICAL_DMA_CH_COUNT]; + + dma_chan_link_map[lch] = 1; + /* Set the link register of the first channel */ + enable_lnk(lch); + + memset(dma_chan_link_map, 0, sizeof(dma_chan_link_map)); + cur_lch = dma_chan[lch].next_lch; + do { + next_lch = dma_chan[cur_lch].next_lch; + + /* The loop case: we've been here already */ + if (dma_chan_link_map[cur_lch]) + break; + /* Mark the current channel */ + dma_chan_link_map[cur_lch] = 1; + + enable_lnk(cur_lch); + init_intr(cur_lch); + + cur_lch = next_lch; + } while (next_lch != -1); + } + + init_intr(lch); + + w = omap_readw(OMAP_DMA_CCR(lch)); + w |= OMAP_DMA_CCR_EN; + omap_writew(w, OMAP_DMA_CCR(lch)); + dma_chan[lch].flags |= OMAP_DMA_ACTIVE; +} + +void omap_stop_dma(int lch) +{ + u16 w; + + if (!omap_dma_in_1510_mode() && dma_chan[lch].next_lch != -1) { + int next_lch, cur_lch = lch; + char dma_chan_link_map[OMAP_LOGICAL_DMA_CH_COUNT]; + + memset(dma_chan_link_map, 0, sizeof(dma_chan_link_map)); + do { + /* The loop case: we've been here already */ + if (dma_chan_link_map[cur_lch]) + break; + /* Mark the current channel */ + dma_chan_link_map[cur_lch] = 1; + + disable_lnk(cur_lch); + + next_lch = dma_chan[cur_lch].next_lch; + cur_lch = next_lch; + } while (next_lch != -1); + + return; + } + /* Disable all interrupts on the channel */ + omap_writew(0, OMAP_DMA_CICR(lch)); + + w = omap_readw(OMAP_DMA_CCR(lch)); + w &= ~OMAP_DMA_CCR_EN; + omap_writew(w, OMAP_DMA_CCR(lch)); + dma_chan[lch].flags &= ~OMAP_DMA_ACTIVE; +} + +void omap_enable_dma_irq(int lch, u16 bits) +{ + dma_chan[lch].enabled_irqs |= bits; +} + +void omap_disable_dma_irq(int lch, u16 bits) +{ + dma_chan[lch].enabled_irqs &= ~bits; +} + +static int dma_handle_ch(int ch) +{ + u16 csr; + + if (enable_1510_mode && ch >= 6) { + csr = dma_chan[ch].saved_csr; + dma_chan[ch].saved_csr = 0; + } else + csr = omap_readw(OMAP_DMA_CSR(ch)); + if (enable_1510_mode && ch <= 2 && (csr >> 7) != 0) { + dma_chan[ch + 6].saved_csr = csr >> 7; + csr &= 0x7f; + } + if (!csr) + return 0; + if (unlikely(dma_chan[ch].dev_id == -1)) { + printk(KERN_WARNING "Spurious interrupt from DMA channel %d (CSR %04x)\n", + ch, csr); + return 0; + } + if (unlikely(csr & OMAP_DMA_TOUT_IRQ)) + printk(KERN_WARNING "DMA timeout with device %d\n", dma_chan[ch].dev_id); + if (unlikely(csr & OMAP_DMA_DROP_IRQ)) + printk(KERN_WARNING "DMA synchronization event drop occurred with device %d\n", + dma_chan[ch].dev_id); + if (likely(csr & OMAP_DMA_BLOCK_IRQ)) + dma_chan[ch].flags &= ~OMAP_DMA_ACTIVE; + if (likely(dma_chan[ch].callback != NULL)) + dma_chan[ch].callback(ch, csr, dma_chan[ch].data); + return 1; +} + +static irqreturn_t dma_irq_handler(int irq, void *dev_id, struct pt_regs *regs) +{ + int ch = ((int) dev_id) - 1; + int handled = 0; + + for (;;) { + int handled_now = 0; + + handled_now += dma_handle_ch(ch); + if (enable_1510_mode && dma_chan[ch + 6].saved_csr) + handled_now += dma_handle_ch(ch + 6); + if (!handled_now) + break; + handled += handled_now; + } + + return handled ? IRQ_HANDLED : IRQ_NONE; +} + +int omap_request_dma(int dev_id, const char *dev_name, + void (* callback)(int lch, u16 ch_status, void *data), + void *data, int *dma_ch_out) +{ + int ch, free_ch = -1; + unsigned long flags; + struct omap_dma_lch *chan; + + spin_lock_irqsave(&dma_chan_lock, flags); + for (ch = 0; ch < dma_chan_count; ch++) { + if (free_ch == -1 && dma_chan[ch].dev_id == -1) { + free_ch = ch; + if (dev_id == 0) + break; + } + } + if (free_ch == -1) { + spin_unlock_irqrestore(&dma_chan_lock, flags); + return -EBUSY; + } + chan = dma_chan + free_ch; + chan->dev_id = dev_id; + clear_lch_regs(free_ch); + spin_unlock_irqrestore(&dma_chan_lock, flags); + + chan->dev_id = dev_id; + chan->dev_name = dev_name; + chan->callback = callback; + chan->data = data; + chan->enabled_irqs = OMAP_DMA_TOUT_IRQ | OMAP_DMA_DROP_IRQ | OMAP_DMA_BLOCK_IRQ; + + if (cpu_is_omap16xx()) { + /* If the sync device is set, configure it dynamically. */ + if (dev_id != 0) { + set_gdma_dev(free_ch + 1, dev_id); + dev_id = free_ch + 1; + } + /* Disable the 1510 compatibility mode and set the sync device + * id. */ + omap_writew(dev_id | (1 << 10), OMAP_DMA_CCR(free_ch)); + } else { + omap_writew(dev_id, OMAP_DMA_CCR(free_ch)); + } + *dma_ch_out = free_ch; + + return 0; +} + +void omap_free_dma(int ch) +{ + unsigned long flags; + + spin_lock_irqsave(&dma_chan_lock, flags); + if (dma_chan[ch].dev_id == -1) { + printk("omap_dma: trying to free nonallocated DMA channel %d\n", ch); + spin_unlock_irqrestore(&dma_chan_lock, flags); + return; + } + dma_chan[ch].dev_id = -1; + spin_unlock_irqrestore(&dma_chan_lock, flags); + + /* Disable all DMA interrupts for the channel. */ + omap_writew(0, OMAP_DMA_CICR(ch)); + /* Make sure the DMA transfer is stopped. */ + omap_writew(0, OMAP_DMA_CCR(ch)); +} + +int omap_dma_in_1510_mode(void) +{ + return enable_1510_mode; +} + +/* + * lch_queue DMA will start right after lch_head one is finished. + * For this DMA link to start, you still need to start (see omap_start_dma) + * the first one. That will fire up the entire queue. + */ +void omap_dma_link_lch (int lch_head, int lch_queue) +{ + if (omap_dma_in_1510_mode()) { + printk(KERN_ERR "DMA linking is not supported in 1510 mode\n"); + BUG(); + return; + } + + if ((dma_chan[lch_head].dev_id == -1) || + (dma_chan[lch_queue].dev_id == -1)) { + printk(KERN_ERR "omap_dma: trying to link non requested channels\n"); + dump_stack(); + } + + dma_chan[lch_head].next_lch = lch_queue; +} + +/* + * Once the DMA queue is stopped, we can destroy it. + */ +void omap_dma_unlink_lch (int lch_head, int lch_queue) +{ + if (omap_dma_in_1510_mode()) { + printk(KERN_ERR "DMA linking is not supported in 1510 mode\n"); + BUG(); + return; + } + + if (dma_chan[lch_head].next_lch != lch_queue || + dma_chan[lch_head].next_lch == -1) { + printk(KERN_ERR "omap_dma: trying to unlink non linked channels\n"); + dump_stack(); + } + + + if ((dma_chan[lch_head].flags & OMAP_DMA_ACTIVE) || + (dma_chan[lch_head].flags & OMAP_DMA_ACTIVE)) { + printk(KERN_ERR "omap_dma: You need to stop the DMA channels before unlinking\n"); + dump_stack(); + } + + dma_chan[lch_head].next_lch = -1; +} + + +static struct lcd_dma_info { + spinlock_t lock; + int reserved; + void (* callback)(u16 status, void *data); + void *cb_data; + + int active; + unsigned long addr, size; + int rotate, data_type, xres, yres; + int vxres; + int mirror; + int xscale, yscale; + int ext_ctrl; + int src_port; + int single_transfer; +} lcd_dma; + +void omap_set_lcd_dma_b1(unsigned long addr, u16 fb_xres, u16 fb_yres, + int data_type) +{ + lcd_dma.addr = addr; + lcd_dma.data_type = data_type; + lcd_dma.xres = fb_xres; + lcd_dma.yres = fb_yres; +} + +void omap_set_lcd_dma_src_port(int port) +{ + lcd_dma.src_port = port; +} + +void omap_set_lcd_dma_ext_controller(int external) +{ + lcd_dma.ext_ctrl = external; +} + +void omap_set_lcd_dma_single_transfer(int single) +{ + lcd_dma.single_transfer = single; +} + + +void omap_set_lcd_dma_b1_rotation(int rotate) +{ + if (omap_dma_in_1510_mode()) { + printk(KERN_ERR "DMA rotation is not supported in 1510 mode\n"); + BUG(); + return; + } + lcd_dma.rotate = rotate; +} + +void omap_set_lcd_dma_b1_mirror(int mirror) +{ + if (omap_dma_in_1510_mode()) { + printk(KERN_ERR "DMA mirror is not supported in 1510 mode\n"); + BUG(); + } + lcd_dma.mirror = mirror; +} + +void omap_set_lcd_dma_b1_vxres(unsigned long vxres) +{ + if (omap_dma_in_1510_mode()) { + printk(KERN_ERR "DMA virtual resulotion is not supported " + "in 1510 mode\n"); + BUG(); + } + lcd_dma.vxres = vxres; +} + +void omap_set_lcd_dma_b1_scale(unsigned int xscale, unsigned int yscale) +{ + if (omap_dma_in_1510_mode()) { + printk(KERN_ERR "DMA scale is not supported in 1510 mode\n"); + BUG(); + } + lcd_dma.xscale = xscale; + lcd_dma.yscale = yscale; +} + +static void set_b1_regs(void) +{ + unsigned long top, bottom; + int es; + u16 w; + unsigned long en, fn; + long ei, fi; + unsigned long vxres; + unsigned int xscale, yscale; + + switch (lcd_dma.data_type) { + case OMAP_DMA_DATA_TYPE_S8: + es = 1; + break; + case OMAP_DMA_DATA_TYPE_S16: + es = 2; + break; + case OMAP_DMA_DATA_TYPE_S32: + es = 4; + break; + default: + BUG(); + return; + } + + vxres = lcd_dma.vxres ? lcd_dma.vxres : lcd_dma.xres; + xscale = lcd_dma.xscale ? lcd_dma.xscale : 1; + yscale = lcd_dma.yscale ? lcd_dma.yscale : 1; + BUG_ON(vxres < lcd_dma.xres); +#define PIXADDR(x,y) (lcd_dma.addr + ((y) * vxres * yscale + (x) * xscale) * es) +#define PIXSTEP(sx, sy, dx, dy) (PIXADDR(dx, dy) - PIXADDR(sx, sy) - es + 1) + switch (lcd_dma.rotate) { + case 0: + if (!lcd_dma.mirror) { + top = PIXADDR(0, 0); + bottom = PIXADDR(lcd_dma.xres - 1, lcd_dma.yres - 1); + /* 1510 DMA requires the bottom address to be 2 more + * than the actual last memory access location. */ + if (omap_dma_in_1510_mode() && + lcd_dma.data_type == OMAP_DMA_DATA_TYPE_S32) + bottom += 2; + ei = PIXSTEP(0, 0, 1, 0); + fi = PIXSTEP(lcd_dma.xres - 1, 0, 0, 1); + } else { + top = PIXADDR(lcd_dma.xres - 1, 0); + bottom = PIXADDR(0, lcd_dma.yres - 1); + ei = PIXSTEP(1, 0, 0, 0); + fi = PIXSTEP(0, 0, lcd_dma.xres - 1, 1); + } + en = lcd_dma.xres; + fn = lcd_dma.yres; + break; + case 90: + if (!lcd_dma.mirror) { + top = PIXADDR(0, lcd_dma.yres - 1); + bottom = PIXADDR(lcd_dma.xres - 1, 0); + ei = PIXSTEP(0, 1, 0, 0); + fi = PIXSTEP(0, 0, 1, lcd_dma.yres - 1); + } else { + top = PIXADDR(lcd_dma.xres - 1, lcd_dma.yres - 1); + bottom = PIXADDR(0, 0); + ei = PIXSTEP(0, 1, 0, 0); + fi = PIXSTEP(1, 0, 0, lcd_dma.yres - 1); + } + en = lcd_dma.yres; + fn = lcd_dma.xres; + break; + case 180: + if (!lcd_dma.mirror) { + top = PIXADDR(lcd_dma.xres - 1, lcd_dma.yres - 1); + bottom = PIXADDR(0, 0); + ei = PIXSTEP(1, 0, 0, 0); + fi = PIXSTEP(0, 1, lcd_dma.xres - 1, 0); + } else { + top = PIXADDR(0, lcd_dma.yres - 1); + bottom = PIXADDR(lcd_dma.xres - 1, 0); + ei = PIXSTEP(0, 0, 1, 0); + fi = PIXSTEP(lcd_dma.xres - 1, 1, 0, 0); + } + en = lcd_dma.xres; + fn = lcd_dma.yres; + break; + case 270: + if (!lcd_dma.mirror) { + top = PIXADDR(lcd_dma.xres - 1, 0); + bottom = PIXADDR(0, lcd_dma.yres - 1); + ei = PIXSTEP(0, 0, 0, 1); + fi = PIXSTEP(1, lcd_dma.yres - 1, 0, 0); + } else { + top = PIXADDR(0, 0); + bottom = PIXADDR(lcd_dma.xres - 1, lcd_dma.yres - 1); + ei = PIXSTEP(0, 0, 0, 1); + fi = PIXSTEP(0, lcd_dma.yres - 1, 1, 0); + } + en = lcd_dma.yres; + fn = lcd_dma.xres; + break; + default: + BUG(); + return; /* Supress warning about uninitialized vars */ + } + + if (omap_dma_in_1510_mode()) { + omap_writew(top >> 16, OMAP1510_DMA_LCD_TOP_F1_U); + omap_writew(top, OMAP1510_DMA_LCD_TOP_F1_L); + omap_writew(bottom >> 16, OMAP1510_DMA_LCD_BOT_F1_U); + omap_writew(bottom, OMAP1510_DMA_LCD_BOT_F1_L); + + return; + } + + /* 1610 regs */ + omap_writew(top >> 16, OMAP1610_DMA_LCD_TOP_B1_U); + omap_writew(top, OMAP1610_DMA_LCD_TOP_B1_L); + omap_writew(bottom >> 16, OMAP1610_DMA_LCD_BOT_B1_U); + omap_writew(bottom, OMAP1610_DMA_LCD_BOT_B1_L); + + omap_writew(en, OMAP1610_DMA_LCD_SRC_EN_B1); + omap_writew(fn, OMAP1610_DMA_LCD_SRC_FN_B1); + + w = omap_readw(OMAP1610_DMA_LCD_CSDP); + w &= ~0x03; + w |= lcd_dma.data_type; + omap_writew(w, OMAP1610_DMA_LCD_CSDP); + + w = omap_readw(OMAP1610_DMA_LCD_CTRL); + /* Always set the source port as SDRAM for now*/ + w &= ~(0x03 << 6); + if (lcd_dma.ext_ctrl) + w |= 1 << 8; + else + w &= ~(1 << 8); + if (lcd_dma.callback != NULL) + w |= 1 << 1; /* Block interrupt enable */ + else + w &= ~(1 << 1); + omap_writew(w, OMAP1610_DMA_LCD_CTRL); + + if (!(lcd_dma.rotate || lcd_dma.mirror || + lcd_dma.vxres || lcd_dma.xscale || lcd_dma.yscale)) + return; + + w = omap_readw(OMAP1610_DMA_LCD_CCR); + /* Set the double-indexed addressing mode */ + w |= (0x03 << 12); + omap_writew(w, OMAP1610_DMA_LCD_CCR); + + omap_writew(ei, OMAP1610_DMA_LCD_SRC_EI_B1); + omap_writew(fi >> 16, OMAP1610_DMA_LCD_SRC_FI_B1_U); + omap_writew(fi, OMAP1610_DMA_LCD_SRC_FI_B1_L); +} + +static irqreturn_t lcd_dma_irq_handler(int irq, void *dev_id, struct pt_regs *regs) +{ + u16 w; + + w = omap_readw(OMAP1610_DMA_LCD_CTRL); + if (unlikely(!(w & (1 << 3)))) { + printk(KERN_WARNING "Spurious LCD DMA IRQ\n"); + return IRQ_NONE; + } + /* Ack the IRQ */ + w |= (1 << 3); + omap_writew(w, OMAP1610_DMA_LCD_CTRL); + lcd_dma.active = 0; + if (lcd_dma.callback != NULL) + lcd_dma.callback(w, lcd_dma.cb_data); + + return IRQ_HANDLED; +} + +int omap_request_lcd_dma(void (* callback)(u16 status, void *data), + void *data) +{ + spin_lock_irq(&lcd_dma.lock); + if (lcd_dma.reserved) { + spin_unlock_irq(&lcd_dma.lock); + printk(KERN_ERR "LCD DMA channel already reserved\n"); + BUG(); + return -EBUSY; + } + lcd_dma.reserved = 1; + spin_unlock_irq(&lcd_dma.lock); + lcd_dma.callback = callback; + lcd_dma.cb_data = data; + lcd_dma.active = 0; + lcd_dma.single_transfer = 0; + lcd_dma.rotate = 0; + lcd_dma.vxres = 0; + lcd_dma.mirror = 0; + lcd_dma.xscale = 0; + lcd_dma.yscale = 0; + lcd_dma.ext_ctrl = 0; + lcd_dma.src_port = 0; + + return 0; +} + +void omap_free_lcd_dma(void) +{ + spin_lock(&lcd_dma.lock); + if (!lcd_dma.reserved) { + spin_unlock(&lcd_dma.lock); + printk(KERN_ERR "LCD DMA is not reserved\n"); + BUG(); + return; + } + if (!enable_1510_mode) + omap_writew(omap_readw(OMAP1610_DMA_LCD_CCR) & ~1, OMAP1610_DMA_LCD_CCR); + lcd_dma.reserved = 0; + spin_unlock(&lcd_dma.lock); +} + +void omap_enable_lcd_dma(void) +{ + u16 w; + + /* Set the Enable bit only if an external controller is + * connected. Otherwise the OMAP internal controller will + * start the transfer when it gets enabled. + */ + if (enable_1510_mode || !lcd_dma.ext_ctrl) + return; + w = omap_readw(OMAP1610_DMA_LCD_CCR); + w |= 1 << 7; + omap_writew(w, OMAP1610_DMA_LCD_CCR); + lcd_dma.active = 1; +} + +void omap_setup_lcd_dma(void) +{ + BUG_ON(lcd_dma.active); + if (!enable_1510_mode) { + /* Set some reasonable defaults */ + omap_writew(0x5440, OMAP1610_DMA_LCD_CCR); + omap_writew(0x9102, OMAP1610_DMA_LCD_CSDP); + omap_writew(0x0004, OMAP1610_DMA_LCD_LCH_CTRL); + } + set_b1_regs(); + if (!enable_1510_mode) { + u16 w; + + w = omap_readw(OMAP1610_DMA_LCD_CCR); + /* If DMA was already active set the end_prog bit to have + * the programmed register set loaded into the active + * register set. + */ + w |= 1 << 11; /* End_prog */ + if (!lcd_dma.single_transfer) + w |= (3 << 8); /* Auto_init, repeat */ + omap_writew(w, OMAP1610_DMA_LCD_CCR); + } +} + +void omap_stop_lcd_dma(void) +{ + lcd_dma.active = 0; + if (!enable_1510_mode && lcd_dma.ext_ctrl) + omap_writew(omap_readw(OMAP1610_DMA_LCD_CCR) & ~(1 << 7), + OMAP1610_DMA_LCD_CCR); +} + +/* + * Clears any DMA state so the DMA engine is ready to restart with new buffers + * through omap_start_dma(). Any buffers in flight are discarded. + */ +void omap_clear_dma(int lch) +{ + unsigned long flags; + int status; + + local_irq_save(flags); + omap_writew(omap_readw(OMAP_DMA_CCR(lch)) & ~OMAP_DMA_CCR_EN, + OMAP_DMA_CCR(lch)); + status = OMAP_DMA_CSR(lch); /* clear pending interrupts */ + local_irq_restore(flags); +} + +/* + * Returns current physical source address for the given DMA channel. + * If the channel is running the caller must disable interrupts prior calling + * this function and process the returned value before re-enabling interrupt to + * prevent races with the interrupt handler. Note that in continuous mode there + * is a chance for CSSA_L register overflow inbetween the two reads resulting + * in incorrect return value. + */ +dma_addr_t omap_get_dma_src_pos(int lch) +{ + return (dma_addr_t) (OMAP_DMA_CSSA_L(lch) | + (OMAP_DMA_CSSA_U(lch) << 16)); +} + +/* + * Returns current physical destination address for the given DMA channel. + * If the channel is running the caller must disable interrupts prior calling + * this function and process the returned value before re-enabling interrupt to + * prevent races with the interrupt handler. Note that in continuous mode there + * is a chance for CDSA_L register overflow inbetween the two reads resulting + * in incorrect return value. + */ +dma_addr_t omap_get_dma_dst_pos(int lch) +{ + return (dma_addr_t) (OMAP_DMA_CDSA_L(lch) | + (OMAP_DMA_CDSA_U(lch) << 16)); +} + +static int __init omap_init_dma(void) +{ + int ch, r; + + if (cpu_is_omap1510()) { + printk(KERN_INFO "DMA support for OMAP1510 initialized\n"); + dma_chan_count = 9; + enable_1510_mode = 1; + } else if (cpu_is_omap16xx() || cpu_is_omap730()) { + printk(KERN_INFO "OMAP DMA hardware version %d\n", + omap_readw(OMAP_DMA_HW_ID)); + printk(KERN_INFO "DMA capabilities: %08x:%08x:%04x:%04x:%04x\n", + (omap_readw(OMAP_DMA_CAPS_0_U) << 16) | omap_readw(OMAP_DMA_CAPS_0_L), + (omap_readw(OMAP_DMA_CAPS_1_U) << 16) | omap_readw(OMAP_DMA_CAPS_1_L), + omap_readw(OMAP_DMA_CAPS_2), omap_readw(OMAP_DMA_CAPS_3), + omap_readw(OMAP_DMA_CAPS_4)); + if (!enable_1510_mode) { + u16 w; + + /* Disable OMAP 3.0/3.1 compatibility mode. */ + w = omap_readw(OMAP_DMA_GSCR); + w |= 1 << 3; + omap_writew(w, OMAP_DMA_GSCR); + dma_chan_count = 16; + } else + dma_chan_count = 9; + } else { + dma_chan_count = 0; + return 0; + } + + memset(&lcd_dma, 0, sizeof(lcd_dma)); + spin_lock_init(&lcd_dma.lock); + spin_lock_init(&dma_chan_lock); + memset(&dma_chan, 0, sizeof(dma_chan)); + + for (ch = 0; ch < dma_chan_count; ch++) { + dma_chan[ch].dev_id = -1; + dma_chan[ch].next_lch = -1; + + if (ch >= 6 && enable_1510_mode) + continue; + + /* request_irq() doesn't like dev_id (ie. ch) being zero, + * so we have to kludge around this. */ + r = request_irq(dma_irq[ch], dma_irq_handler, 0, "DMA", + (void *) (ch + 1)); + if (r != 0) { + int i; + + printk(KERN_ERR "unable to request IRQ %d for DMA (error %d)\n", + dma_irq[ch], r); + for (i = 0; i < ch; i++) + free_irq(dma_irq[i], (void *) (i + 1)); + return r; + } + } + r = request_irq(INT_DMA_LCD, lcd_dma_irq_handler, 0, "LCD DMA", NULL); + if (r != 0) { + int i; + + printk(KERN_ERR "unable to request IRQ for LCD DMA (error %d)\n", r); + for (i = 0; i < dma_chan_count; i++) + free_irq(dma_irq[i], (void *) (i + 1)); + return r; + } + return 0; +} + +arch_initcall(omap_init_dma); + + +EXPORT_SYMBOL(omap_get_dma_src_pos); +EXPORT_SYMBOL(omap_get_dma_dst_pos); +EXPORT_SYMBOL(omap_clear_dma); +EXPORT_SYMBOL(omap_set_dma_priority); +EXPORT_SYMBOL(omap_request_dma); +EXPORT_SYMBOL(omap_free_dma); +EXPORT_SYMBOL(omap_start_dma); +EXPORT_SYMBOL(omap_stop_dma); +EXPORT_SYMBOL(omap_enable_dma_irq); +EXPORT_SYMBOL(omap_disable_dma_irq); + +EXPORT_SYMBOL(omap_set_dma_transfer_params); +EXPORT_SYMBOL(omap_set_dma_color_mode); + +EXPORT_SYMBOL(omap_set_dma_src_params); +EXPORT_SYMBOL(omap_set_dma_src_index); +EXPORT_SYMBOL(omap_set_dma_src_data_pack); +EXPORT_SYMBOL(omap_set_dma_src_burst_mode); + +EXPORT_SYMBOL(omap_set_dma_dest_params); +EXPORT_SYMBOL(omap_set_dma_dest_index); +EXPORT_SYMBOL(omap_set_dma_dest_data_pack); +EXPORT_SYMBOL(omap_set_dma_dest_burst_mode); + +EXPORT_SYMBOL(omap_dma_link_lch); +EXPORT_SYMBOL(omap_dma_unlink_lch); + +EXPORT_SYMBOL(omap_request_lcd_dma); +EXPORT_SYMBOL(omap_free_lcd_dma); +EXPORT_SYMBOL(omap_enable_lcd_dma); +EXPORT_SYMBOL(omap_setup_lcd_dma); +EXPORT_SYMBOL(omap_stop_lcd_dma); +EXPORT_SYMBOL(omap_set_lcd_dma_b1); +EXPORT_SYMBOL(omap_set_lcd_dma_single_transfer); +EXPORT_SYMBOL(omap_set_lcd_dma_ext_controller); +EXPORT_SYMBOL(omap_set_lcd_dma_b1_rotation); +EXPORT_SYMBOL(omap_set_lcd_dma_b1_vxres); +EXPORT_SYMBOL(omap_set_lcd_dma_b1_scale); +EXPORT_SYMBOL(omap_set_lcd_dma_b1_mirror); + diff --git a/arch/arm/plat-omap/gpio.c b/arch/arm/plat-omap/gpio.c new file mode 100644 index 000000000000..1c85b4e536c2 --- /dev/null +++ b/arch/arm/plat-omap/gpio.c @@ -0,0 +1,762 @@ +/* + * linux/arch/arm/plat-omap/gpio.c + * + * Support functions for OMAP GPIO + * + * Copyright (C) 2003 Nokia Corporation + * Written by Juha Yrjölä <juha.yrjola@nokia.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/config.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> + +#include <asm/hardware.h> +#include <asm/irq.h> +#include <asm/arch/irqs.h> +#include <asm/arch/gpio.h> +#include <asm/mach/irq.h> + +#include <asm/io.h> + +/* + * OMAP1510 GPIO registers + */ +#define OMAP1510_GPIO_BASE 0xfffce000 +#define OMAP1510_GPIO_DATA_INPUT 0x00 +#define OMAP1510_GPIO_DATA_OUTPUT 0x04 +#define OMAP1510_GPIO_DIR_CONTROL 0x08 +#define OMAP1510_GPIO_INT_CONTROL 0x0c +#define OMAP1510_GPIO_INT_MASK 0x10 +#define OMAP1510_GPIO_INT_STATUS 0x14 +#define OMAP1510_GPIO_PIN_CONTROL 0x18 + +#define OMAP1510_IH_GPIO_BASE 64 + +/* + * OMAP1610 specific GPIO registers + */ +#define OMAP1610_GPIO1_BASE 0xfffbe400 +#define OMAP1610_GPIO2_BASE 0xfffbec00 +#define OMAP1610_GPIO3_BASE 0xfffbb400 +#define OMAP1610_GPIO4_BASE 0xfffbbc00 +#define OMAP1610_GPIO_REVISION 0x0000 +#define OMAP1610_GPIO_SYSCONFIG 0x0010 +#define OMAP1610_GPIO_SYSSTATUS 0x0014 +#define OMAP1610_GPIO_IRQSTATUS1 0x0018 +#define OMAP1610_GPIO_IRQENABLE1 0x001c +#define OMAP1610_GPIO_DATAIN 0x002c +#define OMAP1610_GPIO_DATAOUT 0x0030 +#define OMAP1610_GPIO_DIRECTION 0x0034 +#define OMAP1610_GPIO_EDGE_CTRL1 0x0038 +#define OMAP1610_GPIO_EDGE_CTRL2 0x003c +#define OMAP1610_GPIO_CLEAR_IRQENABLE1 0x009c +#define OMAP1610_GPIO_CLEAR_DATAOUT 0x00b0 +#define OMAP1610_GPIO_SET_IRQENABLE1 0x00dc +#define OMAP1610_GPIO_SET_DATAOUT 0x00f0 + +/* + * OMAP730 specific GPIO registers + */ +#define OMAP730_GPIO1_BASE 0xfffbc000 +#define OMAP730_GPIO2_BASE 0xfffbc800 +#define OMAP730_GPIO3_BASE 0xfffbd000 +#define OMAP730_GPIO4_BASE 0xfffbd800 +#define OMAP730_GPIO5_BASE 0xfffbe000 +#define OMAP730_GPIO6_BASE 0xfffbe800 +#define OMAP730_GPIO_DATA_INPUT 0x00 +#define OMAP730_GPIO_DATA_OUTPUT 0x04 +#define OMAP730_GPIO_DIR_CONTROL 0x08 +#define OMAP730_GPIO_INT_CONTROL 0x0c +#define OMAP730_GPIO_INT_MASK 0x10 +#define OMAP730_GPIO_INT_STATUS 0x14 + +#define OMAP_MPUIO_MASK (~OMAP_MAX_GPIO_LINES & 0xff) + +struct gpio_bank { + u32 base; + u16 irq; + u16 virtual_irq_start; + u8 method; + u32 reserved_map; + spinlock_t lock; +}; + +#define METHOD_MPUIO 0 +#define METHOD_GPIO_1510 1 +#define METHOD_GPIO_1610 2 +#define METHOD_GPIO_730 3 + +#if defined(CONFIG_ARCH_OMAP16XX) +static struct gpio_bank gpio_bank_1610[5] = { + { OMAP_MPUIO_BASE, INT_MPUIO, IH_MPUIO_BASE, METHOD_MPUIO}, + { OMAP1610_GPIO1_BASE, INT_GPIO_BANK1, IH_GPIO_BASE, METHOD_GPIO_1610 }, + { OMAP1610_GPIO2_BASE, INT_1610_GPIO_BANK2, IH_GPIO_BASE + 16, METHOD_GPIO_1610 }, + { OMAP1610_GPIO3_BASE, INT_1610_GPIO_BANK3, IH_GPIO_BASE + 32, METHOD_GPIO_1610 }, + { OMAP1610_GPIO4_BASE, INT_1610_GPIO_BANK4, IH_GPIO_BASE + 48, METHOD_GPIO_1610 }, +}; +#endif + +#ifdef CONFIG_ARCH_OMAP1510 +static struct gpio_bank gpio_bank_1510[2] = { + { OMAP_MPUIO_BASE, INT_MPUIO, IH_MPUIO_BASE, METHOD_MPUIO }, + { OMAP1510_GPIO_BASE, INT_GPIO_BANK1, IH_GPIO_BASE, METHOD_GPIO_1510 } +}; +#endif + +#ifdef CONFIG_ARCH_OMAP730 +static struct gpio_bank gpio_bank_730[7] = { + { OMAP_MPUIO_BASE, INT_730_MPUIO, IH_MPUIO_BASE, METHOD_MPUIO }, + { OMAP730_GPIO1_BASE, INT_730_GPIO_BANK1, IH_GPIO_BASE, METHOD_GPIO_730 }, + { OMAP730_GPIO2_BASE, INT_730_GPIO_BANK2, IH_GPIO_BASE + 32, METHOD_GPIO_730 }, + { OMAP730_GPIO3_BASE, INT_730_GPIO_BANK3, IH_GPIO_BASE + 64, METHOD_GPIO_730 }, + { OMAP730_GPIO4_BASE, INT_730_GPIO_BANK4, IH_GPIO_BASE + 96, METHOD_GPIO_730 }, + { OMAP730_GPIO5_BASE, INT_730_GPIO_BANK5, IH_GPIO_BASE + 128, METHOD_GPIO_730 }, + { OMAP730_GPIO6_BASE, INT_730_GPIO_BANK6, IH_GPIO_BASE + 160, METHOD_GPIO_730 }, +}; +#endif + +static struct gpio_bank *gpio_bank; +static int gpio_bank_count; + +static inline struct gpio_bank *get_gpio_bank(int gpio) +{ +#ifdef CONFIG_ARCH_OMAP1510 + if (cpu_is_omap1510()) { + if (OMAP_GPIO_IS_MPUIO(gpio)) + return &gpio_bank[0]; + return &gpio_bank[1]; + } +#endif +#if defined(CONFIG_ARCH_OMAP16XX) + if (cpu_is_omap16xx()) { + if (OMAP_GPIO_IS_MPUIO(gpio)) + return &gpio_bank[0]; + return &gpio_bank[1 + (gpio >> 4)]; + } +#endif +#ifdef CONFIG_ARCH_OMAP730 + if (cpu_is_omap730()) { + if (OMAP_GPIO_IS_MPUIO(gpio)) + return &gpio_bank[0]; + return &gpio_bank[1 + (gpio >> 5)]; + } +#endif +} + +static inline int get_gpio_index(int gpio) +{ + if (cpu_is_omap730()) + return gpio & 0x1f; + else + return gpio & 0x0f; +} + +static inline int gpio_valid(int gpio) +{ + if (gpio < 0) + return -1; + if (OMAP_GPIO_IS_MPUIO(gpio)) { + if ((gpio & OMAP_MPUIO_MASK) > 16) + return -1; + return 0; + } +#ifdef CONFIG_ARCH_OMAP1510 + if (cpu_is_omap1510() && gpio < 16) + return 0; +#endif +#if defined(CONFIG_ARCH_OMAP16XX) + if ((cpu_is_omap16xx()) && gpio < 64) + return 0; +#endif +#ifdef CONFIG_ARCH_OMAP730 + if (cpu_is_omap730() && gpio < 192) + return 0; +#endif + return -1; +} + +static int check_gpio(int gpio) +{ + if (unlikely(gpio_valid(gpio)) < 0) { + printk(KERN_ERR "omap-gpio: invalid GPIO %d\n", gpio); + dump_stack(); + return -1; + } + return 0; +} + +static void _set_gpio_direction(struct gpio_bank *bank, int gpio, int is_input) +{ + u32 reg = bank->base; + u32 l; + + switch (bank->method) { + case METHOD_MPUIO: + reg += OMAP_MPUIO_IO_CNTL; + break; + case METHOD_GPIO_1510: + reg += OMAP1510_GPIO_DIR_CONTROL; + break; + case METHOD_GPIO_1610: + reg += OMAP1610_GPIO_DIRECTION; + break; + case METHOD_GPIO_730: + reg += OMAP730_GPIO_DIR_CONTROL; + break; + } + l = __raw_readl(reg); + if (is_input) + l |= 1 << gpio; + else + l &= ~(1 << gpio); + __raw_writel(l, reg); +} + +void omap_set_gpio_direction(int gpio, int is_input) +{ + struct gpio_bank *bank; + + if (check_gpio(gpio) < 0) + return; + bank = get_gpio_bank(gpio); + spin_lock(&bank->lock); + _set_gpio_direction(bank, get_gpio_index(gpio), is_input); + spin_unlock(&bank->lock); +} + +static void _set_gpio_dataout(struct gpio_bank *bank, int gpio, int enable) +{ + u32 reg = bank->base; + u32 l = 0; + + switch (bank->method) { + case METHOD_MPUIO: + reg += OMAP_MPUIO_OUTPUT; + l = __raw_readl(reg); + if (enable) + l |= 1 << gpio; + else + l &= ~(1 << gpio); + break; + case METHOD_GPIO_1510: + reg += OMAP1510_GPIO_DATA_OUTPUT; + l = __raw_readl(reg); + if (enable) + l |= 1 << gpio; + else + l &= ~(1 << gpio); + break; + case METHOD_GPIO_1610: + if (enable) + reg += OMAP1610_GPIO_SET_DATAOUT; + else + reg += OMAP1610_GPIO_CLEAR_DATAOUT; + l = 1 << gpio; + break; + case METHOD_GPIO_730: + reg += OMAP730_GPIO_DATA_OUTPUT; + l = __raw_readl(reg); + if (enable) + l |= 1 << gpio; + else + l &= ~(1 << gpio); + break; + default: + BUG(); + return; + } + __raw_writel(l, reg); +} + +void omap_set_gpio_dataout(int gpio, int enable) +{ + struct gpio_bank *bank; + + if (check_gpio(gpio) < 0) + return; + bank = get_gpio_bank(gpio); + spin_lock(&bank->lock); + _set_gpio_dataout(bank, get_gpio_index(gpio), enable); + spin_unlock(&bank->lock); +} + +int omap_get_gpio_datain(int gpio) +{ + struct gpio_bank *bank; + u32 reg; + + if (check_gpio(gpio) < 0) + return -1; + bank = get_gpio_bank(gpio); + reg = bank->base; + switch (bank->method) { + case METHOD_MPUIO: + reg += OMAP_MPUIO_INPUT_LATCH; + break; + case METHOD_GPIO_1510: + reg += OMAP1510_GPIO_DATA_INPUT; + break; + case METHOD_GPIO_1610: + reg += OMAP1610_GPIO_DATAIN; + break; + case METHOD_GPIO_730: + reg += OMAP730_GPIO_DATA_INPUT; + break; + default: + BUG(); + return -1; + } + return (__raw_readl(reg) & (1 << get_gpio_index(gpio))) != 0; +} + +static void _set_gpio_edge_ctrl(struct gpio_bank *bank, int gpio, int edge) +{ + u32 reg = bank->base; + u32 l; + + switch (bank->method) { + case METHOD_MPUIO: + reg += OMAP_MPUIO_GPIO_INT_EDGE; + l = __raw_readl(reg); + if (edge == OMAP_GPIO_RISING_EDGE) + l |= 1 << gpio; + else + l &= ~(1 << gpio); + __raw_writel(l, reg); + break; + case METHOD_GPIO_1510: + reg += OMAP1510_GPIO_INT_CONTROL; + l = __raw_readl(reg); + if (edge == OMAP_GPIO_RISING_EDGE) + l |= 1 << gpio; + else + l &= ~(1 << gpio); + __raw_writel(l, reg); + break; + case METHOD_GPIO_1610: + edge &= 0x03; + if (gpio & 0x08) + reg += OMAP1610_GPIO_EDGE_CTRL2; + else + reg += OMAP1610_GPIO_EDGE_CTRL1; + gpio &= 0x07; + l = __raw_readl(reg); + l &= ~(3 << (gpio << 1)); + l |= edge << (gpio << 1); + __raw_writel(l, reg); + break; + case METHOD_GPIO_730: + reg += OMAP730_GPIO_INT_CONTROL; + l = __raw_readl(reg); + if (edge == OMAP_GPIO_RISING_EDGE) + l |= 1 << gpio; + else + l &= ~(1 << gpio); + __raw_writel(l, reg); + break; + default: + BUG(); + return; + } +} + +void omap_set_gpio_edge_ctrl(int gpio, int edge) +{ + struct gpio_bank *bank; + + if (check_gpio(gpio) < 0) + return; + bank = get_gpio_bank(gpio); + spin_lock(&bank->lock); + _set_gpio_edge_ctrl(bank, get_gpio_index(gpio), edge); + spin_unlock(&bank->lock); +} + + +static int _get_gpio_edge_ctrl(struct gpio_bank *bank, int gpio) +{ + u32 reg = bank->base, l; + + switch (bank->method) { + case METHOD_MPUIO: + l = __raw_readl(reg + OMAP_MPUIO_GPIO_INT_EDGE); + return (l & (1 << gpio)) ? + OMAP_GPIO_RISING_EDGE : OMAP_GPIO_FALLING_EDGE; + case METHOD_GPIO_1510: + l = __raw_readl(reg + OMAP1510_GPIO_INT_CONTROL); + return (l & (1 << gpio)) ? + OMAP_GPIO_RISING_EDGE : OMAP_GPIO_FALLING_EDGE; + case METHOD_GPIO_1610: + if (gpio & 0x08) + reg += OMAP1610_GPIO_EDGE_CTRL2; + else + reg += OMAP1610_GPIO_EDGE_CTRL1; + return (__raw_readl(reg) >> ((gpio & 0x07) << 1)) & 0x03; + case METHOD_GPIO_730: + l = __raw_readl(reg + OMAP730_GPIO_INT_CONTROL); + return (l & (1 << gpio)) ? + OMAP_GPIO_RISING_EDGE : OMAP_GPIO_FALLING_EDGE; + default: + BUG(); + return -1; + } +} + +static void _clear_gpio_irqbank(struct gpio_bank *bank, int gpio_mask) +{ + u32 reg = bank->base; + + switch (bank->method) { + case METHOD_MPUIO: + /* MPUIO irqstatus is reset by reading the status register, + * so do nothing here */ + return; + case METHOD_GPIO_1510: + reg += OMAP1510_GPIO_INT_STATUS; + break; + case METHOD_GPIO_1610: + reg += OMAP1610_GPIO_IRQSTATUS1; + break; + case METHOD_GPIO_730: + reg += OMAP730_GPIO_INT_STATUS; + break; + default: + BUG(); + return; + } + __raw_writel(gpio_mask, reg); +} + +static inline void _clear_gpio_irqstatus(struct gpio_bank *bank, int gpio) +{ + _clear_gpio_irqbank(bank, 1 << get_gpio_index(gpio)); +} + +static void _enable_gpio_irqbank(struct gpio_bank *bank, int gpio_mask, int enable) +{ + u32 reg = bank->base; + u32 l; + + switch (bank->method) { + case METHOD_MPUIO: + reg += OMAP_MPUIO_GPIO_MASKIT; + l = __raw_readl(reg); + if (enable) + l &= ~(gpio_mask); + else + l |= gpio_mask; + break; + case METHOD_GPIO_1510: + reg += OMAP1510_GPIO_INT_MASK; + l = __raw_readl(reg); + if (enable) + l &= ~(gpio_mask); + else + l |= gpio_mask; + break; + case METHOD_GPIO_1610: + if (enable) + reg += OMAP1610_GPIO_SET_IRQENABLE1; + else + reg += OMAP1610_GPIO_CLEAR_IRQENABLE1; + l = gpio_mask; + break; + case METHOD_GPIO_730: + reg += OMAP730_GPIO_INT_MASK; + l = __raw_readl(reg); + if (enable) + l &= ~(gpio_mask); + else + l |= gpio_mask; + break; + default: + BUG(); + return; + } + __raw_writel(l, reg); +} + +static inline void _set_gpio_irqenable(struct gpio_bank *bank, int gpio, int enable) +{ + _enable_gpio_irqbank(bank, 1 << get_gpio_index(gpio), enable); +} + +int omap_request_gpio(int gpio) +{ + struct gpio_bank *bank; + + if (check_gpio(gpio) < 0) + return -EINVAL; + + bank = get_gpio_bank(gpio); + spin_lock(&bank->lock); + if (unlikely(bank->reserved_map & (1 << get_gpio_index(gpio)))) { + printk(KERN_ERR "omap-gpio: GPIO %d is already reserved!\n", gpio); + dump_stack(); + spin_unlock(&bank->lock); + return -1; + } + bank->reserved_map |= (1 << get_gpio_index(gpio)); +#ifdef CONFIG_ARCH_OMAP1510 + if (bank->method == METHOD_GPIO_1510) { + u32 reg; + + /* Claim the pin for the ARM */ + reg = bank->base + OMAP1510_GPIO_PIN_CONTROL; + __raw_writel(__raw_readl(reg) | (1 << get_gpio_index(gpio)), reg); + } +#endif + spin_unlock(&bank->lock); + + return 0; +} + +void omap_free_gpio(int gpio) +{ + struct gpio_bank *bank; + + if (check_gpio(gpio) < 0) + return; + bank = get_gpio_bank(gpio); + spin_lock(&bank->lock); + if (unlikely(!(bank->reserved_map & (1 << get_gpio_index(gpio))))) { + printk(KERN_ERR "omap-gpio: GPIO %d wasn't reserved!\n", gpio); + dump_stack(); + spin_unlock(&bank->lock); + return; + } + bank->reserved_map &= ~(1 << get_gpio_index(gpio)); + _set_gpio_direction(bank, get_gpio_index(gpio), 1); + _set_gpio_irqenable(bank, gpio, 0); + _clear_gpio_irqstatus(bank, gpio); + spin_unlock(&bank->lock); +} + +/* + * We need to unmask the GPIO bank interrupt as soon as possible to + * avoid missing GPIO interrupts for other lines in the bank. + * Then we need to mask-read-clear-unmask the triggered GPIO lines + * in the bank to avoid missing nested interrupts for a GPIO line. + * If we wait to unmask individual GPIO lines in the bank after the + * line's interrupt handler has been run, we may miss some nested + * interrupts. + */ +static void gpio_irq_handler(unsigned int irq, struct irqdesc *desc, + struct pt_regs *regs) +{ + u32 isr_reg = 0; + u32 isr; + unsigned int gpio_irq; + struct gpio_bank *bank; + + desc->chip->ack(irq); + + bank = (struct gpio_bank *) desc->data; + if (bank->method == METHOD_MPUIO) + isr_reg = bank->base + OMAP_MPUIO_GPIO_INT; +#ifdef CONFIG_ARCH_OMAP1510 + if (bank->method == METHOD_GPIO_1510) + isr_reg = bank->base + OMAP1510_GPIO_INT_STATUS; +#endif +#if defined(CONFIG_ARCH_OMAP16XX) + if (bank->method == METHOD_GPIO_1610) + isr_reg = bank->base + OMAP1610_GPIO_IRQSTATUS1; +#endif +#ifdef CONFIG_ARCH_OMAP730 + if (bank->method == METHOD_GPIO_730) + isr_reg = bank->base + OMAP730_GPIO_INT_STATUS; +#endif + + isr = __raw_readl(isr_reg); + _enable_gpio_irqbank(bank, isr, 0); + _clear_gpio_irqbank(bank, isr); + _enable_gpio_irqbank(bank, isr, 1); + desc->chip->unmask(irq); + + if (unlikely(!isr)) + return; + + gpio_irq = bank->virtual_irq_start; + for (; isr != 0; isr >>= 1, gpio_irq++) { + struct irqdesc *d; + if (!(isr & 1)) + continue; + d = irq_desc + gpio_irq; + d->handle(gpio_irq, d, regs); + } +} + +static void gpio_ack_irq(unsigned int irq) +{ + unsigned int gpio = irq - IH_GPIO_BASE; + struct gpio_bank *bank = get_gpio_bank(gpio); + + _clear_gpio_irqstatus(bank, gpio); +} + +static void gpio_mask_irq(unsigned int irq) +{ + unsigned int gpio = irq - IH_GPIO_BASE; + struct gpio_bank *bank = get_gpio_bank(gpio); + + _set_gpio_irqenable(bank, gpio, 0); +} + +static void gpio_unmask_irq(unsigned int irq) +{ + unsigned int gpio = irq - IH_GPIO_BASE; + struct gpio_bank *bank = get_gpio_bank(gpio); + + if (_get_gpio_edge_ctrl(bank, get_gpio_index(gpio)) == OMAP_GPIO_NO_EDGE) { + printk(KERN_ERR "OMAP GPIO %d: trying to enable GPIO IRQ while no edge is set\n", + gpio); + _set_gpio_edge_ctrl(bank, get_gpio_index(gpio), OMAP_GPIO_RISING_EDGE); + } + _set_gpio_irqenable(bank, gpio, 1); +} + +static void mpuio_ack_irq(unsigned int irq) +{ + /* The ISR is reset automatically, so do nothing here. */ +} + +static void mpuio_mask_irq(unsigned int irq) +{ + unsigned int gpio = OMAP_MPUIO(irq - IH_MPUIO_BASE); + struct gpio_bank *bank = get_gpio_bank(gpio); + + _set_gpio_irqenable(bank, gpio, 0); +} + +static void mpuio_unmask_irq(unsigned int irq) +{ + unsigned int gpio = OMAP_MPUIO(irq - IH_MPUIO_BASE); + struct gpio_bank *bank = get_gpio_bank(gpio); + + _set_gpio_irqenable(bank, gpio, 1); +} + +static struct irqchip gpio_irq_chip = { + .ack = gpio_ack_irq, + .mask = gpio_mask_irq, + .unmask = gpio_unmask_irq, +}; + +static struct irqchip mpuio_irq_chip = { + .ack = mpuio_ack_irq, + .mask = mpuio_mask_irq, + .unmask = mpuio_unmask_irq +}; + +static int initialized = 0; + +static int __init _omap_gpio_init(void) +{ + int i; + struct gpio_bank *bank; + + initialized = 1; + +#ifdef CONFIG_ARCH_OMAP1510 + if (cpu_is_omap1510()) { + printk(KERN_INFO "OMAP1510 GPIO hardware\n"); + gpio_bank_count = 2; + gpio_bank = gpio_bank_1510; + } +#endif +#if defined(CONFIG_ARCH_OMAP16XX) + if (cpu_is_omap16xx()) { + int rev; + + gpio_bank_count = 5; + gpio_bank = gpio_bank_1610; + rev = omap_readw(gpio_bank[1].base + OMAP1610_GPIO_REVISION); + printk(KERN_INFO "OMAP GPIO hardware version %d.%d\n", + (rev >> 4) & 0x0f, rev & 0x0f); + } +#endif +#ifdef CONFIG_ARCH_OMAP730 + if (cpu_is_omap730()) { + printk(KERN_INFO "OMAP730 GPIO hardware\n"); + gpio_bank_count = 7; + gpio_bank = gpio_bank_730; + } +#endif + for (i = 0; i < gpio_bank_count; i++) { + int j, gpio_count = 16; + + bank = &gpio_bank[i]; + bank->reserved_map = 0; + bank->base = IO_ADDRESS(bank->base); + spin_lock_init(&bank->lock); + if (bank->method == METHOD_MPUIO) { + omap_writew(0xFFFF, OMAP_MPUIO_BASE + OMAP_MPUIO_GPIO_MASKIT); + } +#ifdef CONFIG_ARCH_OMAP1510 + if (bank->method == METHOD_GPIO_1510) { + __raw_writew(0xffff, bank->base + OMAP1510_GPIO_INT_MASK); + __raw_writew(0x0000, bank->base + OMAP1510_GPIO_INT_STATUS); + } +#endif +#if defined(CONFIG_ARCH_OMAP16XX) + if (bank->method == METHOD_GPIO_1610) { + __raw_writew(0x0000, bank->base + OMAP1610_GPIO_IRQENABLE1); + __raw_writew(0xffff, bank->base + OMAP1610_GPIO_IRQSTATUS1); + } +#endif +#ifdef CONFIG_ARCH_OMAP730 + if (bank->method == METHOD_GPIO_730) { + __raw_writel(0xffffffff, bank->base + OMAP730_GPIO_INT_MASK); + __raw_writel(0x00000000, bank->base + OMAP730_GPIO_INT_STATUS); + + gpio_count = 32; /* 730 has 32-bit GPIOs */ + } +#endif + for (j = bank->virtual_irq_start; + j < bank->virtual_irq_start + gpio_count; j++) { + if (bank->method == METHOD_MPUIO) + set_irq_chip(j, &mpuio_irq_chip); + else + set_irq_chip(j, &gpio_irq_chip); + set_irq_handler(j, do_simple_IRQ); + set_irq_flags(j, IRQF_VALID); + } + set_irq_chained_handler(bank->irq, gpio_irq_handler); + set_irq_data(bank->irq, bank); + } + + /* Enable system clock for GPIO module. + * The CAM_CLK_CTRL *is* really the right place. */ + if (cpu_is_omap1610() || cpu_is_omap1710()) + omap_writel(omap_readl(ULPD_CAM_CLK_CTRL) | 0x04, ULPD_CAM_CLK_CTRL); + + return 0; +} + +/* + * This may get called early from board specific init + */ +int omap_gpio_init(void) +{ + if (!initialized) + return _omap_gpio_init(); + else + return 0; +} + +EXPORT_SYMBOL(omap_request_gpio); +EXPORT_SYMBOL(omap_free_gpio); +EXPORT_SYMBOL(omap_set_gpio_direction); +EXPORT_SYMBOL(omap_set_gpio_dataout); +EXPORT_SYMBOL(omap_get_gpio_datain); +EXPORT_SYMBOL(omap_set_gpio_edge_ctrl); + +arch_initcall(omap_gpio_init); diff --git a/arch/arm/plat-omap/mcbsp.c b/arch/arm/plat-omap/mcbsp.c new file mode 100644 index 000000000000..10c3f22f9c6a --- /dev/null +++ b/arch/arm/plat-omap/mcbsp.c @@ -0,0 +1,685 @@ +/* + * linux/arch/arm/plat-omap/mcbsp.c + * + * Copyright (C) 2004 Nokia Corporation + * Author: Samuel Ortiz <samuel.ortiz@nokia.com> + * + * + * 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. + * + * Multichannel mode not supported. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/wait.h> +#include <linux/completion.h> +#include <linux/interrupt.h> +#include <linux/err.h> + +#include <asm/delay.h> +#include <asm/io.h> +#include <asm/irq.h> + +#include <asm/arch/dma.h> +#include <asm/arch/mux.h> +#include <asm/arch/irqs.h> +#include <asm/arch/mcbsp.h> + +#include <asm/hardware/clock.h> + +#ifdef CONFIG_MCBSP_DEBUG +#define DBG(x...) printk(x) +#else +#define DBG(x...) do { } while (0) +#endif + +struct omap_mcbsp { + u32 io_base; + u8 id; + u8 free; + omap_mcbsp_word_length rx_word_length; + omap_mcbsp_word_length tx_word_length; + + /* IRQ based TX/RX */ + int rx_irq; + int tx_irq; + + /* DMA stuff */ + u8 dma_rx_sync; + short dma_rx_lch; + u8 dma_tx_sync; + short dma_tx_lch; + + /* Completion queues */ + struct completion tx_irq_completion; + struct completion rx_irq_completion; + struct completion tx_dma_completion; + struct completion rx_dma_completion; + + spinlock_t lock; +}; + +static struct omap_mcbsp mcbsp[OMAP_MAX_MCBSP_COUNT]; +static struct clk *mcbsp_dsp_ck = 0; +static struct clk *mcbsp_api_ck = 0; + + +static void omap_mcbsp_dump_reg(u8 id) +{ + DBG("**** MCBSP%d regs ****\n", mcbsp[id].id); + DBG("DRR2: 0x%04x\n", OMAP_MCBSP_READ(mcbsp[id].io_base, DRR2)); + DBG("DRR1: 0x%04x\n", OMAP_MCBSP_READ(mcbsp[id].io_base, DRR1)); + DBG("DXR2: 0x%04x\n", OMAP_MCBSP_READ(mcbsp[id].io_base, DXR2)); + DBG("DXR1: 0x%04x\n", OMAP_MCBSP_READ(mcbsp[id].io_base, DXR1)); + DBG("SPCR2: 0x%04x\n", OMAP_MCBSP_READ(mcbsp[id].io_base, SPCR2)); + DBG("SPCR1: 0x%04x\n", OMAP_MCBSP_READ(mcbsp[id].io_base, SPCR1)); + DBG("RCR2: 0x%04x\n", OMAP_MCBSP_READ(mcbsp[id].io_base, RCR2)); + DBG("RCR1: 0x%04x\n", OMAP_MCBSP_READ(mcbsp[id].io_base, RCR1)); + DBG("XCR2: 0x%04x\n", OMAP_MCBSP_READ(mcbsp[id].io_base, XCR2)); + DBG("XCR1: 0x%04x\n", OMAP_MCBSP_READ(mcbsp[id].io_base, XCR1)); + DBG("SRGR2: 0x%04x\n", OMAP_MCBSP_READ(mcbsp[id].io_base, SRGR2)); + DBG("SRGR1: 0x%04x\n", OMAP_MCBSP_READ(mcbsp[id].io_base, SRGR1)); + DBG("PCR0: 0x%04x\n", OMAP_MCBSP_READ(mcbsp[id].io_base, PCR0)); + DBG("***********************\n"); +} + + +static irqreturn_t omap_mcbsp_tx_irq_handler(int irq, void *dev_id, struct pt_regs *regs) +{ + struct omap_mcbsp * mcbsp_tx = (struct omap_mcbsp *)(dev_id); + + DBG("TX IRQ callback : 0x%x\n", OMAP_MCBSP_READ(mcbsp_tx->io_base, SPCR2)); + + complete(&mcbsp_tx->tx_irq_completion); + return IRQ_HANDLED; +} + +static irqreturn_t omap_mcbsp_rx_irq_handler(int irq, void *dev_id, struct pt_regs *regs) +{ + struct omap_mcbsp * mcbsp_rx = (struct omap_mcbsp *)(dev_id); + + DBG("RX IRQ callback : 0x%x\n", OMAP_MCBSP_READ(mcbsp_rx->io_base, SPCR2)); + + complete(&mcbsp_rx->rx_irq_completion); + return IRQ_HANDLED; +} + + +static void omap_mcbsp_tx_dma_callback(int lch, u16 ch_status, void *data) +{ + struct omap_mcbsp * mcbsp_dma_tx = (struct omap_mcbsp *)(data); + + DBG("TX DMA callback : 0x%x\n", OMAP_MCBSP_READ(mcbsp_dma_tx->io_base, SPCR2)); + + /* We can free the channels */ + omap_free_dma(mcbsp_dma_tx->dma_tx_lch); + mcbsp_dma_tx->dma_tx_lch = -1; + + complete(&mcbsp_dma_tx->tx_dma_completion); +} + +static void omap_mcbsp_rx_dma_callback(int lch, u16 ch_status, void *data) +{ + struct omap_mcbsp * mcbsp_dma_rx = (struct omap_mcbsp *)(data); + + DBG("RX DMA callback : 0x%x\n", OMAP_MCBSP_READ(mcbsp_dma_rx->io_base, SPCR2)); + + /* We can free the channels */ + omap_free_dma(mcbsp_dma_rx->dma_rx_lch); + mcbsp_dma_rx->dma_rx_lch = -1; + + complete(&mcbsp_dma_rx->rx_dma_completion); +} + + +/* + * omap_mcbsp_config simply write a config to the + * appropriate McBSP. + * You either call this function or set the McBSP registers + * by yourself before calling omap_mcbsp_start(). + */ + +void omap_mcbsp_config(unsigned int id, const struct omap_mcbsp_reg_cfg * config) +{ + u32 io_base = mcbsp[id].io_base; + + DBG("OMAP-McBSP: McBSP%d io_base: 0x%8x\n", id+1, io_base); + + /* We write the given config */ + OMAP_MCBSP_WRITE(io_base, SPCR2, config->spcr2); + OMAP_MCBSP_WRITE(io_base, SPCR1, config->spcr1); + OMAP_MCBSP_WRITE(io_base, RCR2, config->rcr2); + OMAP_MCBSP_WRITE(io_base, RCR1, config->rcr1); + OMAP_MCBSP_WRITE(io_base, XCR2, config->xcr2); + OMAP_MCBSP_WRITE(io_base, XCR1, config->xcr1); + OMAP_MCBSP_WRITE(io_base, SRGR2, config->srgr2); + OMAP_MCBSP_WRITE(io_base, SRGR1, config->srgr1); + OMAP_MCBSP_WRITE(io_base, MCR2, config->mcr2); + OMAP_MCBSP_WRITE(io_base, MCR1, config->mcr1); + OMAP_MCBSP_WRITE(io_base, PCR0, config->pcr0); +} + + + +static int omap_mcbsp_check(unsigned int id) +{ + if (cpu_is_omap730()) { + if (id > OMAP_MAX_MCBSP_COUNT - 1) { + printk(KERN_ERR "OMAP-McBSP: McBSP%d doesn't exist\n", id + 1); + return -1; + } + return 0; + } + + if (cpu_is_omap1510() || cpu_is_omap1610() || cpu_is_omap1710()) { + if (id > OMAP_MAX_MCBSP_COUNT) { + printk(KERN_ERR "OMAP-McBSP: McBSP%d doesn't exist\n", id + 1); + return -1; + } + return 0; + } + + return -1; +} + +#define EN_XORPCK 1 +#define DSP_RSTCT2 0xe1008014 + +static void omap_mcbsp_dsp_request(void) +{ + if (cpu_is_omap1510() || cpu_is_omap1610() || cpu_is_omap1710()) { + omap_writew((omap_readw(ARM_RSTCT1) | (1 << 1) | (1 << 2)), + ARM_RSTCT1); + clk_enable(mcbsp_dsp_ck); + clk_enable(mcbsp_api_ck); + + /* enable 12MHz clock to mcbsp 1 & 3 */ + __raw_writew(__raw_readw(DSP_IDLECT2) | (1 << EN_XORPCK), + DSP_IDLECT2); + __raw_writew(__raw_readw(DSP_RSTCT2) | 1 | 1 << 1, + DSP_RSTCT2); + } +} + +static void omap_mcbsp_dsp_free(void) +{ + /* Useless for now */ +} + + +int omap_mcbsp_request(unsigned int id) +{ + int err; + + if (omap_mcbsp_check(id) < 0) + return -EINVAL; + + /* + * On 1510, 1610 and 1710, McBSP1 and McBSP3 + * are DSP public peripherals. + */ + if (id == OMAP_MCBSP1 || id == OMAP_MCBSP3) + omap_mcbsp_dsp_request(); + + spin_lock(&mcbsp[id].lock); + if (!mcbsp[id].free) { + printk (KERN_ERR "OMAP-McBSP: McBSP%d is currently in use\n", id + 1); + spin_unlock(&mcbsp[id].lock); + return -1; + } + + mcbsp[id].free = 0; + spin_unlock(&mcbsp[id].lock); + + /* We need to get IRQs here */ + err = request_irq(mcbsp[id].tx_irq, omap_mcbsp_tx_irq_handler, 0, + "McBSP", + (void *) (&mcbsp[id])); + if (err != 0) { + printk(KERN_ERR "OMAP-McBSP: Unable to request TX IRQ %d for McBSP%d\n", + mcbsp[id].tx_irq, mcbsp[id].id); + return err; + } + + init_completion(&(mcbsp[id].tx_irq_completion)); + + + err = request_irq(mcbsp[id].rx_irq, omap_mcbsp_rx_irq_handler, 0, + "McBSP", + (void *) (&mcbsp[id])); + if (err != 0) { + printk(KERN_ERR "OMAP-McBSP: Unable to request RX IRQ %d for McBSP%d\n", + mcbsp[id].rx_irq, mcbsp[id].id); + free_irq(mcbsp[id].tx_irq, (void *) (&mcbsp[id])); + return err; + } + + init_completion(&(mcbsp[id].rx_irq_completion)); + return 0; + +} + +void omap_mcbsp_free(unsigned int id) +{ + if (omap_mcbsp_check(id) < 0) + return; + + if (id == OMAP_MCBSP1 || id == OMAP_MCBSP3) + omap_mcbsp_dsp_free(); + + spin_lock(&mcbsp[id].lock); + if (mcbsp[id].free) { + printk (KERN_ERR "OMAP-McBSP: McBSP%d was not reserved\n", id + 1); + spin_unlock(&mcbsp[id].lock); + return; + } + + mcbsp[id].free = 1; + spin_unlock(&mcbsp[id].lock); + + /* Free IRQs */ + free_irq(mcbsp[id].rx_irq, (void *) (&mcbsp[id])); + free_irq(mcbsp[id].tx_irq, (void *) (&mcbsp[id])); +} + +/* + * Here we start the McBSP, by enabling the sample + * generator, both transmitter and receivers, + * and the frame sync. + */ +void omap_mcbsp_start(unsigned int id) +{ + u32 io_base; + u16 w; + + if (omap_mcbsp_check(id) < 0) + return; + + io_base = mcbsp[id].io_base; + + mcbsp[id].rx_word_length = ((OMAP_MCBSP_READ(io_base, RCR1) >> 5) & 0x7); + mcbsp[id].tx_word_length = ((OMAP_MCBSP_READ(io_base, XCR1) >> 5) & 0x7); + + /* Start the sample generator */ + w = OMAP_MCBSP_READ(io_base, SPCR2); + OMAP_MCBSP_WRITE(io_base, SPCR2, w | (1 << 6)); + + /* Enable transmitter and receiver */ + w = OMAP_MCBSP_READ(io_base, SPCR2); + OMAP_MCBSP_WRITE(io_base, SPCR2, w | 1); + + w = OMAP_MCBSP_READ(io_base, SPCR1); + OMAP_MCBSP_WRITE(io_base, SPCR1, w | 1); + + udelay(100); + + /* Start frame sync */ + w = OMAP_MCBSP_READ(io_base, SPCR2); + OMAP_MCBSP_WRITE(io_base, SPCR2, w | (1 << 7)); + + /* Dump McBSP Regs */ + omap_mcbsp_dump_reg(id); + +} + +void omap_mcbsp_stop(unsigned int id) +{ + u32 io_base; + u16 w; + + if (omap_mcbsp_check(id) < 0) + return; + + io_base = mcbsp[id].io_base; + + /* Reset transmitter */ + w = OMAP_MCBSP_READ(io_base, SPCR2); + OMAP_MCBSP_WRITE(io_base, SPCR2, w & ~(1)); + + /* Reset receiver */ + w = OMAP_MCBSP_READ(io_base, SPCR1); + OMAP_MCBSP_WRITE(io_base, SPCR1, w & ~(1)); + + /* Reset the sample rate generator */ + w = OMAP_MCBSP_READ(io_base, SPCR2); + OMAP_MCBSP_WRITE(io_base, SPCR2, w & ~(1 << 6)); +} + + +/* + * IRQ based word transmission. + */ +void omap_mcbsp_xmit_word(unsigned int id, u32 word) +{ + u32 io_base; + omap_mcbsp_word_length word_length = mcbsp[id].tx_word_length; + + if (omap_mcbsp_check(id) < 0) + return; + + io_base = mcbsp[id].io_base; + + wait_for_completion(&(mcbsp[id].tx_irq_completion)); + + if (word_length > OMAP_MCBSP_WORD_16) + OMAP_MCBSP_WRITE(io_base, DXR2, word >> 16); + OMAP_MCBSP_WRITE(io_base, DXR1, word & 0xffff); +} + +u32 omap_mcbsp_recv_word(unsigned int id) +{ + u32 io_base; + u16 word_lsb, word_msb = 0; + omap_mcbsp_word_length word_length = mcbsp[id].rx_word_length; + + if (omap_mcbsp_check(id) < 0) + return -EINVAL; + + io_base = mcbsp[id].io_base; + + wait_for_completion(&(mcbsp[id].rx_irq_completion)); + + if (word_length > OMAP_MCBSP_WORD_16) + word_msb = OMAP_MCBSP_READ(io_base, DRR2); + word_lsb = OMAP_MCBSP_READ(io_base, DRR1); + + return (word_lsb | (word_msb << 16)); +} + + +/* + * Simple DMA based buffer rx/tx routines. + * Nothing fancy, just a single buffer tx/rx through DMA. + * The DMA resources are released once the transfer is done. + * For anything fancier, you should use your own customized DMA + * routines and callbacks. + */ +int omap_mcbsp_xmit_buffer(unsigned int id, dma_addr_t buffer, unsigned int length) +{ + int dma_tx_ch; + + if (omap_mcbsp_check(id) < 0) + return -EINVAL; + + if (omap_request_dma(mcbsp[id].dma_tx_sync, "McBSP TX", omap_mcbsp_tx_dma_callback, + &mcbsp[id], + &dma_tx_ch)) { + printk("OMAP-McBSP: Unable to request DMA channel for McBSP%d TX. Trying IRQ based TX\n", id+1); + return -EAGAIN; + } + mcbsp[id].dma_tx_lch = dma_tx_ch; + + DBG("TX DMA on channel %d\n", dma_tx_ch); + + init_completion(&(mcbsp[id].tx_dma_completion)); + + omap_set_dma_transfer_params(mcbsp[id].dma_tx_lch, + OMAP_DMA_DATA_TYPE_S16, + length >> 1, 1, + OMAP_DMA_SYNC_ELEMENT); + + omap_set_dma_dest_params(mcbsp[id].dma_tx_lch, + OMAP_DMA_PORT_TIPB, + OMAP_DMA_AMODE_CONSTANT, + mcbsp[id].io_base + OMAP_MCBSP_REG_DXR1); + + omap_set_dma_src_params(mcbsp[id].dma_tx_lch, + OMAP_DMA_PORT_EMIFF, + OMAP_DMA_AMODE_POST_INC, + buffer); + + omap_start_dma(mcbsp[id].dma_tx_lch); + wait_for_completion(&(mcbsp[id].tx_dma_completion)); + return 0; +} + + +int omap_mcbsp_recv_buffer(unsigned int id, dma_addr_t buffer, unsigned int length) +{ + int dma_rx_ch; + + if (omap_mcbsp_check(id) < 0) + return -EINVAL; + + if (omap_request_dma(mcbsp[id].dma_rx_sync, "McBSP RX", omap_mcbsp_rx_dma_callback, + &mcbsp[id], + &dma_rx_ch)) { + printk("Unable to request DMA channel for McBSP%d RX. Trying IRQ based RX\n", id+1); + return -EAGAIN; + } + mcbsp[id].dma_rx_lch = dma_rx_ch; + + DBG("RX DMA on channel %d\n", dma_rx_ch); + + init_completion(&(mcbsp[id].rx_dma_completion)); + + omap_set_dma_transfer_params(mcbsp[id].dma_rx_lch, + OMAP_DMA_DATA_TYPE_S16, + length >> 1, 1, + OMAP_DMA_SYNC_ELEMENT); + + omap_set_dma_src_params(mcbsp[id].dma_rx_lch, + OMAP_DMA_PORT_TIPB, + OMAP_DMA_AMODE_CONSTANT, + mcbsp[id].io_base + OMAP_MCBSP_REG_DRR1); + + omap_set_dma_dest_params(mcbsp[id].dma_rx_lch, + OMAP_DMA_PORT_EMIFF, + OMAP_DMA_AMODE_POST_INC, + buffer); + + omap_start_dma(mcbsp[id].dma_rx_lch); + wait_for_completion(&(mcbsp[id].rx_dma_completion)); + return 0; +} + + +/* + * SPI wrapper. + * Since SPI setup is much simpler than the generic McBSP one, + * this wrapper just need an omap_mcbsp_spi_cfg structure as an input. + * Once this is done, you can call omap_mcbsp_start(). + */ +void omap_mcbsp_set_spi_mode(unsigned int id, const struct omap_mcbsp_spi_cfg * spi_cfg) +{ + struct omap_mcbsp_reg_cfg mcbsp_cfg; + + if (omap_mcbsp_check(id) < 0) + return; + + memset(&mcbsp_cfg, 0, sizeof(struct omap_mcbsp_reg_cfg)); + + /* SPI has only one frame */ + mcbsp_cfg.rcr1 |= (RWDLEN1(spi_cfg->word_length) | RFRLEN1(0)); + mcbsp_cfg.xcr1 |= (XWDLEN1(spi_cfg->word_length) | XFRLEN1(0)); + + /* Clock stop mode */ + if (spi_cfg->clk_stp_mode == OMAP_MCBSP_CLK_STP_MODE_NO_DELAY) + mcbsp_cfg.spcr1 |= (1 << 12); + else + mcbsp_cfg.spcr1 |= (3 << 11); + + /* Set clock parities */ + if (spi_cfg->rx_clock_polarity == OMAP_MCBSP_CLK_RISING) + mcbsp_cfg.pcr0 |= CLKRP; + else + mcbsp_cfg.pcr0 &= ~CLKRP; + + if (spi_cfg->tx_clock_polarity == OMAP_MCBSP_CLK_RISING) + mcbsp_cfg.pcr0 &= ~CLKXP; + else + mcbsp_cfg.pcr0 |= CLKXP; + + /* Set SCLKME to 0 and CLKSM to 1 */ + mcbsp_cfg.pcr0 &= ~SCLKME; + mcbsp_cfg.srgr2 |= CLKSM; + + /* Set FSXP */ + if (spi_cfg->fsx_polarity == OMAP_MCBSP_FS_ACTIVE_HIGH) + mcbsp_cfg.pcr0 &= ~FSXP; + else + mcbsp_cfg.pcr0 |= FSXP; + + if (spi_cfg->spi_mode == OMAP_MCBSP_SPI_MASTER) { + mcbsp_cfg.pcr0 |= CLKXM; + mcbsp_cfg.srgr1 |= CLKGDV(spi_cfg->clk_div -1); + mcbsp_cfg.pcr0 |= FSXM; + mcbsp_cfg.srgr2 &= ~FSGM; + mcbsp_cfg.xcr2 |= XDATDLY(1); + mcbsp_cfg.rcr2 |= RDATDLY(1); + } + else { + mcbsp_cfg.pcr0 &= ~CLKXM; + mcbsp_cfg.srgr1 |= CLKGDV(1); + mcbsp_cfg.pcr0 &= ~FSXM; + mcbsp_cfg.xcr2 &= ~XDATDLY(3); + mcbsp_cfg.rcr2 &= ~RDATDLY(3); + } + + mcbsp_cfg.xcr2 &= ~XPHASE; + mcbsp_cfg.rcr2 &= ~RPHASE; + + omap_mcbsp_config(id, &mcbsp_cfg); +} + + +/* + * McBSP1 and McBSP3 are directly mapped on 1610 and 1510. + * 730 has only 2 McBSP, and both of them are MPU peripherals. + */ +struct omap_mcbsp_info { + u32 virt_base; + u8 dma_rx_sync, dma_tx_sync; + u16 rx_irq, tx_irq; +}; + +#ifdef CONFIG_ARCH_OMAP730 +static const struct omap_mcbsp_info mcbsp_730[] = { + [0] = { .virt_base = io_p2v(OMAP730_MCBSP1_BASE), + .dma_rx_sync = OMAP_DMA_MCBSP1_RX, + .dma_tx_sync = OMAP_DMA_MCBSP1_TX, + .rx_irq = INT_730_McBSP1RX, + .tx_irq = INT_730_McBSP1TX }, + [1] = { .virt_base = io_p2v(OMAP730_MCBSP2_BASE), + .dma_rx_sync = OMAP_DMA_MCBSP3_RX, + .dma_tx_sync = OMAP_DMA_MCBSP3_TX, + .rx_irq = INT_730_McBSP2RX, + .tx_irq = INT_730_McBSP2TX }, +}; +#endif + +#ifdef CONFIG_ARCH_OMAP1510 +static const struct omap_mcbsp_info mcbsp_1510[] = { + [0] = { .virt_base = OMAP1510_MCBSP1_BASE, + .dma_rx_sync = OMAP_DMA_MCBSP1_RX, + .dma_tx_sync = OMAP_DMA_MCBSP1_TX, + .rx_irq = INT_McBSP1RX, + .tx_irq = INT_McBSP1TX }, + [1] = { .virt_base = io_p2v(OMAP1510_MCBSP2_BASE), + .dma_rx_sync = OMAP_DMA_MCBSP2_RX, + .dma_tx_sync = OMAP_DMA_MCBSP2_TX, + .rx_irq = INT_1510_SPI_RX, + .tx_irq = INT_1510_SPI_TX }, + [2] = { .virt_base = OMAP1510_MCBSP3_BASE, + .dma_rx_sync = OMAP_DMA_MCBSP3_RX, + .dma_tx_sync = OMAP_DMA_MCBSP3_TX, + .rx_irq = INT_McBSP3RX, + .tx_irq = INT_McBSP3TX }, +}; +#endif + +#if defined(CONFIG_ARCH_OMAP16XX) +static const struct omap_mcbsp_info mcbsp_1610[] = { + [0] = { .virt_base = OMAP1610_MCBSP1_BASE, + .dma_rx_sync = OMAP_DMA_MCBSP1_RX, + .dma_tx_sync = OMAP_DMA_MCBSP1_TX, + .rx_irq = INT_McBSP1RX, + .tx_irq = INT_McBSP1TX }, + [1] = { .virt_base = io_p2v(OMAP1610_MCBSP2_BASE), + .dma_rx_sync = OMAP_DMA_MCBSP2_RX, + .dma_tx_sync = OMAP_DMA_MCBSP2_TX, + .rx_irq = INT_1610_McBSP2_RX, + .tx_irq = INT_1610_McBSP2_TX }, + [2] = { .virt_base = OMAP1610_MCBSP3_BASE, + .dma_rx_sync = OMAP_DMA_MCBSP3_RX, + .dma_tx_sync = OMAP_DMA_MCBSP3_TX, + .rx_irq = INT_McBSP3RX, + .tx_irq = INT_McBSP3TX }, +}; +#endif + +static int __init omap_mcbsp_init(void) +{ + int mcbsp_count = 0, i; + static const struct omap_mcbsp_info *mcbsp_info; + + printk("Initializing OMAP McBSP system\n"); + + mcbsp_dsp_ck = clk_get(0, "dsp_ck"); + if (IS_ERR(mcbsp_dsp_ck)) { + printk(KERN_ERR "mcbsp: could not acquire dsp_ck handle.\n"); + return PTR_ERR(mcbsp_dsp_ck); + } + mcbsp_api_ck = clk_get(0, "api_ck"); + if (IS_ERR(mcbsp_dsp_ck)) { + printk(KERN_ERR "mcbsp: could not acquire api_ck handle.\n"); + return PTR_ERR(mcbsp_api_ck); + } + +#ifdef CONFIG_ARCH_OMAP730 + if (cpu_is_omap730()) { + mcbsp_info = mcbsp_730; + mcbsp_count = ARRAY_SIZE(mcbsp_730); + } +#endif +#ifdef CONFIG_ARCH_OMAP1510 + if (cpu_is_omap1510()) { + mcbsp_info = mcbsp_1510; + mcbsp_count = ARRAY_SIZE(mcbsp_1510); + } +#endif +#if defined(CONFIG_ARCH_OMAP16XX) + if (cpu_is_omap1610() || cpu_is_omap1710()) { + mcbsp_info = mcbsp_1610; + mcbsp_count = ARRAY_SIZE(mcbsp_1610); + } +#endif + for (i = 0; i < OMAP_MAX_MCBSP_COUNT ; i++) { + if (i >= mcbsp_count) { + mcbsp[i].io_base = 0; + mcbsp[i].free = 0; + continue; + } + mcbsp[i].id = i + 1; + mcbsp[i].free = 1; + mcbsp[i].dma_tx_lch = -1; + mcbsp[i].dma_rx_lch = -1; + + mcbsp[i].io_base = mcbsp_info[i].virt_base; + mcbsp[i].tx_irq = mcbsp_info[i].tx_irq; + mcbsp[i].rx_irq = mcbsp_info[i].rx_irq; + mcbsp[i].dma_rx_sync = mcbsp_info[i].dma_rx_sync; + mcbsp[i].dma_tx_sync = mcbsp_info[i].dma_tx_sync; + spin_lock_init(&mcbsp[i].lock); + } + + return 0; +} + + +arch_initcall(omap_mcbsp_init); + +EXPORT_SYMBOL(omap_mcbsp_config); +EXPORT_SYMBOL(omap_mcbsp_request); +EXPORT_SYMBOL(omap_mcbsp_free); +EXPORT_SYMBOL(omap_mcbsp_start); +EXPORT_SYMBOL(omap_mcbsp_stop); +EXPORT_SYMBOL(omap_mcbsp_xmit_word); +EXPORT_SYMBOL(omap_mcbsp_recv_word); +EXPORT_SYMBOL(omap_mcbsp_xmit_buffer); +EXPORT_SYMBOL(omap_mcbsp_recv_buffer); +EXPORT_SYMBOL(omap_mcbsp_set_spi_mode); diff --git a/arch/arm/plat-omap/mux.c b/arch/arm/plat-omap/mux.c new file mode 100644 index 000000000000..cbecd10d0b6c --- /dev/null +++ b/arch/arm/plat-omap/mux.c @@ -0,0 +1,163 @@ +/* + * linux/arch/arm/plat-omap/mux.c + * + * Utility to set the Omap MUX and PULL_DWN registers from a table in mux.h + * + * Copyright (C) 2003 Nokia Corporation + * + * Written by Tony Lindgren <tony.lindgren@nokia.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 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 <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> +#include <asm/system.h> +#include <asm/io.h> +#include <linux/spinlock.h> + +#define __MUX_C__ +#include <asm/arch/mux.h> + +#ifdef CONFIG_OMAP_MUX + +/* + * Sets the Omap MUX and PULL_DWN registers based on the table + */ +int __init_or_module +omap_cfg_reg(const reg_cfg_t reg_cfg) +{ + static DEFINE_SPINLOCK(mux_spin_lock); + + unsigned long flags; + reg_cfg_set *cfg; + unsigned int reg_orig = 0, reg = 0, pu_pd_orig = 0, pu_pd = 0, + pull_orig = 0, pull = 0; + unsigned int mask, warn = 0; + + if (reg_cfg > ARRAY_SIZE(reg_cfg_table)) { + printk(KERN_ERR "MUX: reg_cfg %d\n", reg_cfg); + return -EINVAL; + } + + cfg = ®_cfg_table[reg_cfg]; + + /* + * We do a pretty long section here with lock on, but pin muxing + * should only happen on driver init for each driver, so it's not time + * critical. + */ + spin_lock_irqsave(&mux_spin_lock, flags); + + /* Check the mux register in question */ + if (cfg->mux_reg) { + unsigned tmp1, tmp2; + + reg_orig = omap_readl(cfg->mux_reg); + + /* The mux registers always seem to be 3 bits long */ + mask = (0x7 << cfg->mask_offset); + tmp1 = reg_orig & mask; + reg = reg_orig & ~mask; + + tmp2 = (cfg->mask << cfg->mask_offset); + reg |= tmp2; + + if (tmp1 != tmp2) + warn = 1; + + omap_writel(reg, cfg->mux_reg); + } + + /* Check for pull up or pull down selection on 1610 */ + if (!cpu_is_omap1510()) { + if (cfg->pu_pd_reg && cfg->pull_val) { + pu_pd_orig = omap_readl(cfg->pu_pd_reg); + mask = 1 << cfg->pull_bit; + + if (cfg->pu_pd_val) { + if (!(pu_pd_orig & mask)) + warn = 1; + /* Use pull up */ + pu_pd = pu_pd_orig | mask; + } else { + if (pu_pd_orig & mask) + warn = 1; + /* Use pull down */ + pu_pd = pu_pd_orig & ~mask; + } + omap_writel(pu_pd, cfg->pu_pd_reg); + } + } + + /* Check for an associated pull down register */ + if (cfg->pull_reg) { + pull_orig = omap_readl(cfg->pull_reg); + mask = 1 << cfg->pull_bit; + + if (cfg->pull_val) { + if (pull_orig & mask) + warn = 1; + /* Low bit = pull enabled */ + pull = pull_orig & ~mask; + } else { + if (!(pull_orig & mask)) + warn = 1; + /* High bit = pull disabled */ + pull = pull_orig | mask; + } + + omap_writel(pull, cfg->pull_reg); + } + + if (warn) { +#ifdef CONFIG_OMAP_MUX_WARNINGS + printk(KERN_WARNING "MUX: initialized %s\n", cfg->name); +#endif + } + +#ifdef CONFIG_OMAP_MUX_DEBUG + if (cfg->debug || warn) { + printk("MUX: Setting register %s\n", cfg->name); + printk(" %s (0x%08x) = 0x%08x -> 0x%08x\n", + cfg->mux_reg_name, cfg->mux_reg, reg_orig, reg); + + if (!cpu_is_omap1510()) { + if (cfg->pu_pd_reg && cfg->pull_val) { + printk(" %s (0x%08x) = 0x%08x -> 0x%08x\n", + cfg->pu_pd_name, cfg->pu_pd_reg, + pu_pd_orig, pu_pd); + } + } + + if (cfg->pull_reg) + printk(" %s (0x%08x) = 0x%08x -> 0x%08x\n", + cfg->pull_name, cfg->pull_reg, pull_orig, pull); + } +#endif + + spin_unlock_irqrestore(&mux_spin_lock, flags); + +#ifdef CONFIG_OMAP_MUX_ERRORS + return warn ? -ETXTBSY : 0; +#else + return 0; +#endif +} + +EXPORT_SYMBOL(omap_cfg_reg); + +#endif /* CONFIG_OMAP_MUX */ diff --git a/arch/arm/plat-omap/ocpi.c b/arch/arm/plat-omap/ocpi.c new file mode 100644 index 000000000000..1fb16f9edfd5 --- /dev/null +++ b/arch/arm/plat-omap/ocpi.c @@ -0,0 +1,114 @@ +/* + * linux/arch/arm/plat-omap/ocpi.c + * + * Minimal OCP bus support for omap16xx + * + * Copyright (C) 2003 - 2005 Nokia Corporation + * Written by Tony Lindgren <tony@atomide.com> + * + * Modified for clock framework by Paul Mundt <paul.mundt@nokia.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 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 <linux/config.h> +#include <linux/module.h> +#include <linux/version.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/err.h> + +#include <asm/io.h> +#include <asm/hardware/clock.h> +#include <asm/arch/hardware.h> + +#define OCPI_BASE 0xfffec320 +#define OCPI_FAULT (OCPI_BASE + 0x00) +#define OCPI_CMD_FAULT (OCPI_BASE + 0x04) +#define OCPI_SINT0 (OCPI_BASE + 0x08) +#define OCPI_TABORT (OCPI_BASE + 0x0c) +#define OCPI_SINT1 (OCPI_BASE + 0x10) +#define OCPI_PROT (OCPI_BASE + 0x14) +#define OCPI_SEC (OCPI_BASE + 0x18) + +/* USB OHCI OCPI access error registers */ +#define HOSTUEADDR 0xfffba0e0 +#define HOSTUESTATUS 0xfffba0e4 + +static struct clk *ocpi_ck; + +/* + * Enables device access to OMAP buses via the OCPI bridge + * FIXME: Add locking + */ +int ocpi_enable(void) +{ + unsigned int val; + + if (!cpu_is_omap16xx()) + return -ENODEV; + + /* Make sure there's clock for OCPI */ + clk_enable(ocpi_ck); + + /* Enable access for OHCI in OCPI */ + val = omap_readl(OCPI_PROT); + val &= ~0xff; + //val &= (1 << 0); /* Allow access only to EMIFS */ + omap_writel(val, OCPI_PROT); + + val = omap_readl(OCPI_SEC); + val &= ~0xff; + omap_writel(val, OCPI_SEC); + + return 0; +} +EXPORT_SYMBOL(ocpi_enable); + +static int __init omap_ocpi_init(void) +{ + if (!cpu_is_omap16xx()) + return -ENODEV; + + ocpi_ck = clk_get(NULL, "l3_ocpi_ck"); + if (IS_ERR(ocpi_ck)) + return PTR_ERR(ocpi_ck); + + clk_use(ocpi_ck); + ocpi_enable(); + printk("OMAP OCPI interconnect driver loaded\n"); + + return 0; +} + +static void __exit omap_ocpi_exit(void) +{ + /* REVISIT: Disable OCPI */ + + if (!cpu_is_omap16xx()) + return; + + clk_unuse(ocpi_ck); + clk_put(ocpi_ck); +} + +MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>"); +MODULE_DESCRIPTION("OMAP OCPI bus controller module"); +MODULE_LICENSE("GPL"); +module_init(omap_ocpi_init); +module_exit(omap_ocpi_exit); diff --git a/arch/arm/plat-omap/pm.c b/arch/arm/plat-omap/pm.c new file mode 100644 index 000000000000..e6536b16c385 --- /dev/null +++ b/arch/arm/plat-omap/pm.c @@ -0,0 +1,632 @@ +/* + * linux/arch/arm/plat-omap/pm.c + * + * OMAP Power Management Routines + * + * Original code for the SA11x0: + * Copyright (c) 2001 Cliff Brake <cbrake@accelent.com> + * + * Modified for the PXA250 by Nicolas Pitre: + * Copyright (c) 2002 Monta Vista Software, Inc. + * + * Modified for the OMAP1510 by David Singleton: + * Copyright (c) 2002 Monta Vista Software, Inc. + * + * Cleanup 2004 for OMAP1510/1610 by Dirk Behme <dirk.behme@de.bosch.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. + */ + +#include <linux/pm.h> +#include <linux/sched.h> +#include <linux/proc_fs.h> +#include <linux/pm.h> + +#include <asm/io.h> +#include <asm/mach/time.h> +#include <asm/mach-types.h> + +#include <asm/arch/omap16xx.h> +#include <asm/arch/pm.h> +#include <asm/arch/mux.h> +#include <asm/arch/tc.h> +#include <asm/arch/tps65010.h> + +#include "clock.h" + +static unsigned int arm_sleep_save[ARM_SLEEP_SAVE_SIZE]; +static unsigned short ulpd_sleep_save[ULPD_SLEEP_SAVE_SIZE]; +static unsigned int mpui1510_sleep_save[MPUI1510_SLEEP_SAVE_SIZE]; +static unsigned int mpui1610_sleep_save[MPUI1610_SLEEP_SAVE_SIZE]; + +/* + * Let's power down on idle, but only if we are really + * idle, because once we start down the path of + * going idle we continue to do idle even if we get + * a clock tick interrupt . . + */ +void omap_pm_idle(void) +{ + int (*func_ptr)(void) = 0; + unsigned int mask32 = 0; + + /* + * If the DSP is being used let's just idle the CPU, the overhead + * to wake up from Big Sleep is big, milliseconds versus micro + * seconds for wait for interrupt. + */ + + local_irq_disable(); + local_fiq_disable(); + if (need_resched()) { + local_fiq_enable(); + local_irq_enable(); + return; + } + mask32 = omap_readl(ARM_SYSST); + + /* + * Since an interrupt may set up a timer, we don't want to + * reprogram the hardware timer with interrupts enabled. + * Re-enable interrupts only after returning from idle. + */ + timer_dyn_reprogram(); + + if ((mask32 & DSP_IDLE) == 0) { + __asm__ volatile ("mcr p15, 0, r0, c7, c0, 4"); + } else { + + if (cpu_is_omap1510()) { + func_ptr = (void *)(OMAP1510_SRAM_IDLE_SUSPEND); + } else if (cpu_is_omap1610() || cpu_is_omap1710()) { + func_ptr = (void *)(OMAP1610_SRAM_IDLE_SUSPEND); + } else if (cpu_is_omap5912()) { + func_ptr = (void *)(OMAP5912_SRAM_IDLE_SUSPEND); + } + + func_ptr(); + } + local_fiq_enable(); + local_irq_enable(); +} + +/* + * Configuration of the wakeup event is board specific. For the + * moment we put it into this helper function. Later it may move + * to board specific files. + */ +static void omap_pm_wakeup_setup(void) +{ + /* + * Enable ARM XOR clock and release peripheral from reset by + * writing 1 to PER_EN bit in ARM_RSTCT2, this is required + * for UART configuration to use UART2 to wake up. + */ + + omap_writel(omap_readl(ARM_IDLECT2) | ENABLE_XORCLK, ARM_IDLECT2); + omap_writel(omap_readl(ARM_RSTCT2) | PER_EN, ARM_RSTCT2); + omap_writew(MODEM_32K_EN, ULPD_CLOCK_CTRL); + + /* + * Turn off all interrupts except L1-2nd level cascade, + * and the L2 wakeup interrupts: keypad and UART2. + */ + + omap_writel(~IRQ_LEVEL2, OMAP_IH1_MIR); + + if (cpu_is_omap1510()) { + omap_writel(~(IRQ_UART2 | IRQ_KEYBOARD), OMAP_IH2_MIR); + } + + if (cpu_is_omap16xx()) { + omap_writel(~(IRQ_UART2 | IRQ_KEYBOARD), OMAP_IH2_0_MIR); + + omap_writel(~0x0, OMAP_IH2_1_MIR); + omap_writel(~0x0, OMAP_IH2_2_MIR); + omap_writel(~0x0, OMAP_IH2_3_MIR); + } + + /* New IRQ agreement */ + omap_writel(1, OMAP_IH1_CONTROL); + + /* external PULL to down, bit 22 = 0 */ + omap_writel(omap_readl(PULL_DWN_CTRL_2) & ~(1<<22), PULL_DWN_CTRL_2); +} + +void omap_pm_suspend(void) +{ + unsigned int mask32 = 0; + unsigned long arg0 = 0, arg1 = 0; + int (*func_ptr)(unsigned short, unsigned short) = 0; + unsigned short save_dsp_idlect2; + + printk("PM: OMAP%x is entering deep sleep now ...\n", system_rev); + + if (machine_is_omap_osk()) { + /* Stop LED1 (D9) blink */ + tps65010_set_led(LED1, OFF); + } + + /* + * Step 1: turn off interrupts + */ + + local_irq_disable(); + local_fiq_disable(); + + /* + * Step 2: save registers + * + * The omap is a strange/beautiful device. The caches, memory + * and register state are preserved across power saves. + * We have to save and restore very little register state to + * idle the omap. + * + * Save interrupt, MPUI, ARM and UPLD control registers. + */ + + if (cpu_is_omap1510()) { + MPUI1510_SAVE(OMAP_IH1_MIR); + MPUI1510_SAVE(OMAP_IH2_MIR); + MPUI1510_SAVE(MPUI_CTRL); + MPUI1510_SAVE(MPUI_DSP_BOOT_CONFIG); + MPUI1510_SAVE(MPUI_DSP_API_CONFIG); + MPUI1510_SAVE(EMIFS_CONFIG); + MPUI1510_SAVE(EMIFF_SDRAM_CONFIG); + } else if (cpu_is_omap16xx()) { + MPUI1610_SAVE(OMAP_IH1_MIR); + MPUI1610_SAVE(OMAP_IH2_0_MIR); + MPUI1610_SAVE(OMAP_IH2_1_MIR); + MPUI1610_SAVE(OMAP_IH2_2_MIR); + MPUI1610_SAVE(OMAP_IH2_3_MIR); + MPUI1610_SAVE(MPUI_CTRL); + MPUI1610_SAVE(MPUI_DSP_BOOT_CONFIG); + MPUI1610_SAVE(MPUI_DSP_API_CONFIG); + MPUI1610_SAVE(EMIFS_CONFIG); + MPUI1610_SAVE(EMIFF_SDRAM_CONFIG); + } + + ARM_SAVE(ARM_CKCTL); + ARM_SAVE(ARM_IDLECT1); + ARM_SAVE(ARM_IDLECT2); + ARM_SAVE(ARM_EWUPCT); + ARM_SAVE(ARM_RSTCT1); + ARM_SAVE(ARM_RSTCT2); + ARM_SAVE(ARM_SYSST); + ULPD_SAVE(ULPD_CLOCK_CTRL); + ULPD_SAVE(ULPD_STATUS_REQ); + + /* + * Step 3: LOW_PWR signal enabling + * + * Allow the LOW_PWR signal to be visible on MPUIO5 ball. + */ + if (cpu_is_omap1510()) { + /* POWER_CTRL_REG = 0x1 (LOW_POWER is available) */ + omap_writew(omap_readw(ULPD_POWER_CTRL) | + OMAP1510_ULPD_LOW_POWER_REQ, ULPD_POWER_CTRL); + } else if (cpu_is_omap16xx()) { + /* POWER_CTRL_REG = 0x1 (LOW_POWER is available) */ + omap_writew(omap_readw(ULPD_POWER_CTRL) | + OMAP1610_ULPD_LOW_POWER_REQ, ULPD_POWER_CTRL); + } + + /* configure LOW_PWR pin */ + omap_cfg_reg(T20_1610_LOW_PWR); + + /* + * Step 4: OMAP DSP Shutdown + */ + + /* Set DSP_RST = 1 and DSP_EN = 0, put DSP block into reset */ + omap_writel((omap_readl(ARM_RSTCT1) | DSP_RST) & ~DSP_ENABLE, + ARM_RSTCT1); + + /* Set DSP boot mode to DSP-IDLE, DSP_BOOT_MODE = 0x2 */ + omap_writel(DSP_IDLE_MODE, MPUI_DSP_BOOT_CONFIG); + + /* Set EN_DSPCK = 0, stop DSP block clock */ + omap_writel(omap_readl(ARM_CKCTL) & ~DSP_CLOCK_ENABLE, ARM_CKCTL); + + /* Stop any DSP domain clocks */ + omap_writel(omap_readl(ARM_IDLECT2) | (1<<EN_APICK), ARM_IDLECT2); + save_dsp_idlect2 = __raw_readw(DSP_IDLECT2); + __raw_writew(0, DSP_IDLECT2); + + /* + * Step 5: Wakeup Event Setup + */ + + omap_pm_wakeup_setup(); + + /* + * Step 6a: ARM and Traffic controller shutdown + * + * Step 6 starts here with clock and watchdog disable + */ + + /* stop clocks */ + mask32 = omap_readl(ARM_IDLECT2); + mask32 &= ~(1<<EN_WDTCK); /* bit 0 -> 0 (WDT clock) */ + mask32 |= (1<<EN_XORPCK); /* bit 1 -> 1 (XORPCK clock) */ + mask32 &= ~(1<<EN_PERCK); /* bit 2 -> 0 (MPUPER_CK clock) */ + mask32 &= ~(1<<EN_LCDCK); /* bit 3 -> 0 (LCDC clock) */ + mask32 &= ~(1<<EN_LBCK); /* bit 4 -> 0 (local bus clock) */ + mask32 |= (1<<EN_APICK); /* bit 6 -> 1 (MPUI clock) */ + mask32 &= ~(1<<EN_TIMCK); /* bit 7 -> 0 (MPU timer clock) */ + mask32 &= ~(1<<DMACK_REQ); /* bit 8 -> 0 (DMAC clock) */ + mask32 &= ~(1<<EN_GPIOCK); /* bit 9 -> 0 (GPIO clock) */ + omap_writel(mask32, ARM_IDLECT2); + + /* disable ARM watchdog */ + omap_writel(0x00F5, OMAP_WDT_TIMER_MODE); + omap_writel(0x00A0, OMAP_WDT_TIMER_MODE); + + /* + * Step 6b: ARM and Traffic controller shutdown + * + * Step 6 continues here. Prepare jump to power management + * assembly code in internal SRAM. + * + * Since the omap_cpu_suspend routine has been copied to + * SRAM, we'll do an indirect procedure call to it and pass the + * contents of arm_idlect1 and arm_idlect2 so it can restore + * them when it wakes up and it will return. + */ + + arg0 = arm_sleep_save[ARM_SLEEP_SAVE_ARM_IDLECT1]; + arg1 = arm_sleep_save[ARM_SLEEP_SAVE_ARM_IDLECT2]; + + if (cpu_is_omap1510()) { + func_ptr = (void *)(OMAP1510_SRAM_API_SUSPEND); + } else if (cpu_is_omap1610() || cpu_is_omap1710()) { + func_ptr = (void *)(OMAP1610_SRAM_API_SUSPEND); + } else if (cpu_is_omap5912()) { + func_ptr = (void *)(OMAP5912_SRAM_API_SUSPEND); + } + + /* + * Step 6c: ARM and Traffic controller shutdown + * + * Jump to assembly code. The processor will stay there + * until wake up. + */ + + func_ptr(arg0, arg1); + + /* + * If we are here, processor is woken up! + */ + + if (cpu_is_omap1510()) { + /* POWER_CTRL_REG = 0x0 (LOW_POWER is disabled) */ + omap_writew(omap_readw(ULPD_POWER_CTRL) & + ~OMAP1510_ULPD_LOW_POWER_REQ, ULPD_POWER_CTRL); + } else if (cpu_is_omap16xx()) { + /* POWER_CTRL_REG = 0x0 (LOW_POWER is disabled) */ + omap_writew(omap_readw(ULPD_POWER_CTRL) & + ~OMAP1610_ULPD_LOW_POWER_REQ, ULPD_POWER_CTRL); + } + + + /* Restore DSP clocks */ + omap_writel(omap_readl(ARM_IDLECT2) | (1<<EN_APICK), ARM_IDLECT2); + __raw_writew(save_dsp_idlect2, DSP_IDLECT2); + ARM_RESTORE(ARM_IDLECT2); + + /* + * Restore ARM state, except ARM_IDLECT1/2 which omap_cpu_suspend did + */ + + ARM_RESTORE(ARM_CKCTL); + ARM_RESTORE(ARM_EWUPCT); + ARM_RESTORE(ARM_RSTCT1); + ARM_RESTORE(ARM_RSTCT2); + ARM_RESTORE(ARM_SYSST); + ULPD_RESTORE(ULPD_CLOCK_CTRL); + ULPD_RESTORE(ULPD_STATUS_REQ); + + if (cpu_is_omap1510()) { + MPUI1510_RESTORE(MPUI_CTRL); + MPUI1510_RESTORE(MPUI_DSP_BOOT_CONFIG); + MPUI1510_RESTORE(MPUI_DSP_API_CONFIG); + MPUI1510_RESTORE(EMIFS_CONFIG); + MPUI1510_RESTORE(EMIFF_SDRAM_CONFIG); + MPUI1510_RESTORE(OMAP_IH1_MIR); + MPUI1510_RESTORE(OMAP_IH2_MIR); + } else if (cpu_is_omap16xx()) { + MPUI1610_RESTORE(MPUI_CTRL); + MPUI1610_RESTORE(MPUI_DSP_BOOT_CONFIG); + MPUI1610_RESTORE(MPUI_DSP_API_CONFIG); + MPUI1610_RESTORE(EMIFS_CONFIG); + MPUI1610_RESTORE(EMIFF_SDRAM_CONFIG); + + MPUI1610_RESTORE(OMAP_IH1_MIR); + MPUI1610_RESTORE(OMAP_IH2_0_MIR); + MPUI1610_RESTORE(OMAP_IH2_1_MIR); + MPUI1610_RESTORE(OMAP_IH2_2_MIR); + MPUI1610_RESTORE(OMAP_IH2_3_MIR); + } + + /* + * Reenable interrupts + */ + + local_irq_enable(); + local_fiq_enable(); + + printk("PM: OMAP%x is re-starting from deep sleep...\n", system_rev); + + if (machine_is_omap_osk()) { + /* Let LED1 (D9) blink again */ + tps65010_set_led(LED1, BLINK); + } +} + +#if defined(DEBUG) && defined(CONFIG_PROC_FS) +static int g_read_completed; + +/* + * Read system PM registers for debugging + */ +static int omap_pm_read_proc( + char *page_buffer, + char **my_first_byte, + off_t virtual_start, + int length, + int *eof, + void *data) +{ + int my_buffer_offset = 0; + char * const my_base = page_buffer; + + ARM_SAVE(ARM_CKCTL); + ARM_SAVE(ARM_IDLECT1); + ARM_SAVE(ARM_IDLECT2); + ARM_SAVE(ARM_EWUPCT); + ARM_SAVE(ARM_RSTCT1); + ARM_SAVE(ARM_RSTCT2); + ARM_SAVE(ARM_SYSST); + + ULPD_SAVE(ULPD_IT_STATUS); + ULPD_SAVE(ULPD_CLOCK_CTRL); + ULPD_SAVE(ULPD_SOFT_REQ); + ULPD_SAVE(ULPD_STATUS_REQ); + ULPD_SAVE(ULPD_DPLL_CTRL); + ULPD_SAVE(ULPD_POWER_CTRL); + + if (cpu_is_omap1510()) { + MPUI1510_SAVE(MPUI_CTRL); + MPUI1510_SAVE(MPUI_DSP_STATUS); + MPUI1510_SAVE(MPUI_DSP_BOOT_CONFIG); + MPUI1510_SAVE(MPUI_DSP_API_CONFIG); + MPUI1510_SAVE(EMIFF_SDRAM_CONFIG); + MPUI1510_SAVE(EMIFS_CONFIG); + } else if (cpu_is_omap16xx()) { + MPUI1610_SAVE(MPUI_CTRL); + MPUI1610_SAVE(MPUI_DSP_STATUS); + MPUI1610_SAVE(MPUI_DSP_BOOT_CONFIG); + MPUI1610_SAVE(MPUI_DSP_API_CONFIG); + MPUI1610_SAVE(EMIFF_SDRAM_CONFIG); + MPUI1610_SAVE(EMIFS_CONFIG); + } + + if (virtual_start == 0) { + g_read_completed = 0; + + my_buffer_offset += sprintf(my_base + my_buffer_offset, + "ARM_CKCTL_REG: 0x%-8x \n" + "ARM_IDLECT1_REG: 0x%-8x \n" + "ARM_IDLECT2_REG: 0x%-8x \n" + "ARM_EWUPCT_REG: 0x%-8x \n" + "ARM_RSTCT1_REG: 0x%-8x \n" + "ARM_RSTCT2_REG: 0x%-8x \n" + "ARM_SYSST_REG: 0x%-8x \n" + "ULPD_IT_STATUS_REG: 0x%-4x \n" + "ULPD_CLOCK_CTRL_REG: 0x%-4x \n" + "ULPD_SOFT_REQ_REG: 0x%-4x \n" + "ULPD_DPLL_CTRL_REG: 0x%-4x \n" + "ULPD_STATUS_REQ_REG: 0x%-4x \n" + "ULPD_POWER_CTRL_REG: 0x%-4x \n", + ARM_SHOW(ARM_CKCTL), + ARM_SHOW(ARM_IDLECT1), + ARM_SHOW(ARM_IDLECT2), + ARM_SHOW(ARM_EWUPCT), + ARM_SHOW(ARM_RSTCT1), + ARM_SHOW(ARM_RSTCT2), + ARM_SHOW(ARM_SYSST), + ULPD_SHOW(ULPD_IT_STATUS), + ULPD_SHOW(ULPD_CLOCK_CTRL), + ULPD_SHOW(ULPD_SOFT_REQ), + ULPD_SHOW(ULPD_DPLL_CTRL), + ULPD_SHOW(ULPD_STATUS_REQ), + ULPD_SHOW(ULPD_POWER_CTRL)); + + if (cpu_is_omap1510()) { + my_buffer_offset += sprintf(my_base + my_buffer_offset, + "MPUI1510_CTRL_REG 0x%-8x \n" + "MPUI1510_DSP_STATUS_REG: 0x%-8x \n" + "MPUI1510_DSP_BOOT_CONFIG_REG: 0x%-8x \n" + "MPUI1510_DSP_API_CONFIG_REG: 0x%-8x \n" + "MPUI1510_SDRAM_CONFIG_REG: 0x%-8x \n" + "MPUI1510_EMIFS_CONFIG_REG: 0x%-8x \n", + MPUI1510_SHOW(MPUI_CTRL), + MPUI1510_SHOW(MPUI_DSP_STATUS), + MPUI1510_SHOW(MPUI_DSP_BOOT_CONFIG), + MPUI1510_SHOW(MPUI_DSP_API_CONFIG), + MPUI1510_SHOW(EMIFF_SDRAM_CONFIG), + MPUI1510_SHOW(EMIFS_CONFIG)); + } else if (cpu_is_omap16xx()) { + my_buffer_offset += sprintf(my_base + my_buffer_offset, + "MPUI1610_CTRL_REG 0x%-8x \n" + "MPUI1610_DSP_STATUS_REG: 0x%-8x \n" + "MPUI1610_DSP_BOOT_CONFIG_REG: 0x%-8x \n" + "MPUI1610_DSP_API_CONFIG_REG: 0x%-8x \n" + "MPUI1610_SDRAM_CONFIG_REG: 0x%-8x \n" + "MPUI1610_EMIFS_CONFIG_REG: 0x%-8x \n", + MPUI1610_SHOW(MPUI_CTRL), + MPUI1610_SHOW(MPUI_DSP_STATUS), + MPUI1610_SHOW(MPUI_DSP_BOOT_CONFIG), + MPUI1610_SHOW(MPUI_DSP_API_CONFIG), + MPUI1610_SHOW(EMIFF_SDRAM_CONFIG), + MPUI1610_SHOW(EMIFS_CONFIG)); + } + + g_read_completed++; + } else if (g_read_completed >= 1) { + *eof = 1; + return 0; + } + g_read_completed++; + + *my_first_byte = page_buffer; + return my_buffer_offset; +} + +static void omap_pm_init_proc(void) +{ + struct proc_dir_entry *entry; + + entry = create_proc_read_entry("driver/omap_pm", + S_IWUSR | S_IRUGO, NULL, + omap_pm_read_proc, 0); +} + +#endif /* DEBUG && CONFIG_PROC_FS */ + +/* + * omap_pm_prepare - Do preliminary suspend work. + * @state: suspend state we're entering. + * + */ +//#include <asm/arch/hardware.h> + +static int omap_pm_prepare(suspend_state_t state) +{ + int error = 0; + + switch (state) + { + case PM_SUSPEND_STANDBY: + case PM_SUSPEND_MEM: + break; + + case PM_SUSPEND_DISK: + return -ENOTSUPP; + + default: + return -EINVAL; + } + + return error; +} + + +/* + * omap_pm_enter - Actually enter a sleep state. + * @state: State we're entering. + * + */ + +static int omap_pm_enter(suspend_state_t state) +{ + switch (state) + { + case PM_SUSPEND_STANDBY: + case PM_SUSPEND_MEM: + omap_pm_suspend(); + break; + + case PM_SUSPEND_DISK: + return -ENOTSUPP; + + default: + return -EINVAL; + } + + return 0; +} + + +/** + * omap_pm_finish - Finish up suspend sequence. + * @state: State we're coming out of. + * + * This is called after we wake back up (or if entering the sleep state + * failed). + */ + +static int omap_pm_finish(suspend_state_t state) +{ + return 0; +} + + +struct pm_ops omap_pm_ops ={ + .pm_disk_mode = 0, + .prepare = omap_pm_prepare, + .enter = omap_pm_enter, + .finish = omap_pm_finish, +}; + +static int __init omap_pm_init(void) +{ + printk("Power Management for TI OMAP.\n"); + pm_idle = omap_pm_idle; + /* + * We copy the assembler sleep/wakeup routines to SRAM. + * These routines need to be in SRAM as that's the only + * memory the MPU can see when it wakes up. + */ + +#ifdef CONFIG_ARCH_OMAP1510 + if (cpu_is_omap1510()) { + memcpy((void *)OMAP1510_SRAM_IDLE_SUSPEND, + omap1510_idle_loop_suspend, + omap1510_idle_loop_suspend_sz); + memcpy((void *)OMAP1510_SRAM_API_SUSPEND, omap1510_cpu_suspend, + omap1510_cpu_suspend_sz); + } else +#endif + if (cpu_is_omap1610() || cpu_is_omap1710()) { + memcpy((void *)OMAP1610_SRAM_IDLE_SUSPEND, + omap1610_idle_loop_suspend, + omap1610_idle_loop_suspend_sz); + memcpy((void *)OMAP1610_SRAM_API_SUSPEND, omap1610_cpu_suspend, + omap1610_cpu_suspend_sz); + } else if (cpu_is_omap5912()) { + memcpy((void *)OMAP5912_SRAM_IDLE_SUSPEND, + omap1610_idle_loop_suspend, + omap1610_idle_loop_suspend_sz); + memcpy((void *)OMAP5912_SRAM_API_SUSPEND, omap1610_cpu_suspend, + omap1610_cpu_suspend_sz); + } + + pm_set_ops(&omap_pm_ops); + +#if defined(DEBUG) && defined(CONFIG_PROC_FS) + omap_pm_init_proc(); +#endif + + return 0; +} +__initcall(omap_pm_init); + diff --git a/arch/arm/plat-omap/sleep.S b/arch/arm/plat-omap/sleep.S new file mode 100644 index 000000000000..279490ce772b --- /dev/null +++ b/arch/arm/plat-omap/sleep.S @@ -0,0 +1,314 @@ +/* + * linux/arch/arm/plat-omap/sleep.S + * + * Low-level OMAP1510/1610 sleep/wakeUp support + * + * Initial SA1110 code: + * Copyright (c) 2001 Cliff Brake <cbrake@accelent.com> + * + * Adapted for PXA by Nicolas Pitre: + * Copyright (c) 2002 Monta Vista Software, Inc. + * + * Support for OMAP1510/1610 by Dirk Behme <dirk.behme@de.bosch.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. + */ + +#include <linux/config.h> +#include <linux/linkage.h> +#include <asm/assembler.h> +#include <asm/arch/io.h> +#include <asm/arch/pm.h> + + .text + +/* + * Forces OMAP into idle state + * + * omapXXXX_idle_loop_suspend() + * + * Note: This code get's copied to internal SRAM at boot. When the OMAP + * wakes up it continues execution at the point it went to sleep. + * + * Note: Because of slightly different configuration values we have + * processor specific functions here. + */ + +#ifdef CONFIG_ARCH_OMAP1510 +ENTRY(omap1510_idle_loop_suspend) + + stmfd sp!, {r0 - r12, lr} @ save registers on stack + + @ load base address of ARM_IDLECT1 and ARM_IDLECT2 + mov r4, #CLKGEN_REG_ASM_BASE & 0xff000000 + orr r4, r4, #CLKGEN_REG_ASM_BASE & 0x00ff0000 + orr r4, r4, #CLKGEN_REG_ASM_BASE & 0x0000ff00 + + @ turn off clock domains + @ get ARM_IDLECT2 into r2 + ldrh r2, [r4, #ARM_IDLECT2_ASM_OFFSET & 0xff] + mov r5, #OMAP1510_IDLE_CLOCK_DOMAINS & 0xff + orr r5,r5, #OMAP1510_IDLE_CLOCK_DOMAINS & 0xff00 + strh r5, [r4, #ARM_IDLECT2_ASM_OFFSET & 0xff] + + @ request ARM idle + @ get ARM_IDLECT1 into r1 + ldrh r1, [r4, #ARM_IDLECT1_ASM_OFFSET & 0xff] + orr r3, r1, #OMAP1510_IDLE_LOOP_REQUEST & 0xffff + strh r3, [r4, #ARM_IDLECT1_ASM_OFFSET & 0xff] + + mov r5, #IDLE_WAIT_CYCLES & 0xff + orr r5, r5, #IDLE_WAIT_CYCLES & 0xff00 +l_1510: subs r5, r5, #1 + bne l_1510 +/* + * Let's wait for the next clock tick to wake us up. + */ + mov r0, #0 + mcr p15, 0, r0, c7, c0, 4 @ wait for interrupt +/* + * omap1510_idle_loop_suspend()'s resume point. + * + * It will just start executing here, so we'll restore stuff from the + * stack, reset the ARM_IDLECT1 and ARM_IDLECT2. + */ + + @ restore ARM_IDLECT1 and ARM_IDLECT2 and return + @ r1 has ARM_IDLECT1 and r2 still has ARM_IDLECT2 + strh r2, [r4, #ARM_IDLECT2_ASM_OFFSET & 0xff] + strh r1, [r4, #ARM_IDLECT1_ASM_OFFSET & 0xff] + + ldmfd sp!, {r0 - r12, pc} @ restore regs and return + +ENTRY(omap1510_idle_loop_suspend_sz) + .word . - omap1510_idle_loop_suspend +#endif /* CONFIG_ARCH_OMAP1510 */ + +#if defined(CONFIG_ARCH_OMAP16XX) +ENTRY(omap1610_idle_loop_suspend) + + stmfd sp!, {r0 - r12, lr} @ save registers on stack + + @ load base address of ARM_IDLECT1 and ARM_IDLECT2 + mov r4, #CLKGEN_REG_ASM_BASE & 0xff000000 + orr r4, r4, #CLKGEN_REG_ASM_BASE & 0x00ff0000 + orr r4, r4, #CLKGEN_REG_ASM_BASE & 0x0000ff00 + + @ turn off clock domains + @ get ARM_IDLECT2 into r2 + ldrh r2, [r4, #ARM_IDLECT2_ASM_OFFSET & 0xff] + mov r5, #OMAP1610_IDLE_CLOCK_DOMAINS & 0xff + orr r5,r5, #OMAP1610_IDLE_CLOCK_DOMAINS & 0xff00 + strh r5, [r4, #ARM_IDLECT2_ASM_OFFSET & 0xff] + + @ request ARM idle + @ get ARM_IDLECT1 into r1 + ldrh r1, [r4, #ARM_IDLECT1_ASM_OFFSET & 0xff] + orr r3, r1, #OMAP1610_IDLE_LOOP_REQUEST & 0xffff + strh r3, [r4, #ARM_IDLECT1_ASM_OFFSET & 0xff] + + mov r5, #IDLE_WAIT_CYCLES & 0xff + orr r5, r5, #IDLE_WAIT_CYCLES & 0xff00 +l_1610: subs r5, r5, #1 + bne l_1610 +/* + * Let's wait for the next clock tick to wake us up. + */ + mov r0, #0 + mcr p15, 0, r0, c7, c0, 4 @ wait for interrupt +/* + * omap1610_idle_loop_suspend()'s resume point. + * + * It will just start executing here, so we'll restore stuff from the + * stack, reset the ARM_IDLECT1 and ARM_IDLECT2. + */ + + @ restore ARM_IDLECT1 and ARM_IDLECT2 and return + @ r1 has ARM_IDLECT1 and r2 still has ARM_IDLECT2 + strh r2, [r4, #ARM_IDLECT2_ASM_OFFSET & 0xff] + strh r1, [r4, #ARM_IDLECT1_ASM_OFFSET & 0xff] + + ldmfd sp!, {r0 - r12, pc} @ restore regs and return + +ENTRY(omap1610_idle_loop_suspend_sz) + .word . - omap1610_idle_loop_suspend +#endif /* CONFIG_ARCH_OMAP16XX */ + +/* + * Forces OMAP into deep sleep state + * + * omapXXXX_cpu_suspend() + * + * The values of the registers ARM_IDLECT1 and ARM_IDLECT2 are passed + * as arg0 and arg1 from caller. arg0 is stored in register r0 and arg1 + * in register r1. + * + * Note: This code get's copied to internal SRAM at boot. When the OMAP + * wakes up it continues execution at the point it went to sleep. + * + * Note: Because of errata work arounds we have processor specific functions + * here. They are mostly the same, but slightly different. + * + */ + +#ifdef CONFIG_ARCH_OMAP1510 +ENTRY(omap1510_cpu_suspend) + + @ save registers on stack + stmfd sp!, {r0 - r12, lr} + + @ load base address of Traffic Controller + mov r4, #TCMIF_ASM_BASE & 0xff000000 + orr r4, r4, #TCMIF_ASM_BASE & 0x00ff0000 + orr r4, r4, #TCMIF_ASM_BASE & 0x0000ff00 + + @ work around errata of OMAP1510 PDE bit for TC shut down + @ clear PDE bit + ldr r5, [r4, #EMIFS_CONFIG_ASM_OFFSET & 0xff] + bic r5, r5, #PDE_BIT & 0xff + str r5, [r4, #EMIFS_CONFIG_ASM_OFFSET & 0xff] + + @ set PWD_EN bit + and r5, r5, #PWD_EN_BIT & 0xff + str r5, [r4, #EMIFS_CONFIG_ASM_OFFSET & 0xff] + + @ prepare to put SDRAM into self-refresh manually + ldr r5, [r4, #EMIFF_SDRAM_CONFIG_ASM_OFFSET & 0xff] + orr r5, r5, #SELF_REFRESH_MODE & 0xff000000 + orr r5, r5, #SELF_REFRESH_MODE & 0x000000ff + str r5, [r4, #EMIFF_SDRAM_CONFIG_ASM_OFFSET & 0xff] + + @ prepare to put EMIFS to Sleep + ldr r5, [r4, #EMIFS_CONFIG_ASM_OFFSET & 0xff] + orr r5, r5, #IDLE_EMIFS_REQUEST & 0xff + str r5, [r4, #EMIFS_CONFIG_ASM_OFFSET & 0xff] + + @ load base address of ARM_IDLECT1 and ARM_IDLECT2 + mov r4, #CLKGEN_REG_ASM_BASE & 0xff000000 + orr r4, r4, #CLKGEN_REG_ASM_BASE & 0x00ff0000 + orr r4, r4, #CLKGEN_REG_ASM_BASE & 0x0000ff00 + + @ turn off clock domains + mov r5, #OMAP1510_IDLE_CLOCK_DOMAINS & 0xff + orr r5,r5, #OMAP1510_IDLE_CLOCK_DOMAINS & 0xff00 + strh r5, [r4, #ARM_IDLECT2_ASM_OFFSET & 0xff] + + @ request ARM idle + mov r3, #OMAP1510_DEEP_SLEEP_REQUEST & 0xff + orr r3, r3, #OMAP1510_DEEP_SLEEP_REQUEST & 0xff00 + strh r3, [r4, #ARM_IDLECT1_ASM_OFFSET & 0xff] + + mov r5, #IDLE_WAIT_CYCLES & 0xff + orr r5, r5, #IDLE_WAIT_CYCLES & 0xff00 +l_1510_2: + subs r5, r5, #1 + bne l_1510_2 +/* + * Let's wait for the next wake up event to wake us up. r0 can't be + * used here because r0 holds ARM_IDLECT1 + */ + mov r2, #0 + mcr p15, 0, r2, c7, c0, 4 @ wait for interrupt +/* + * omap1510_cpu_suspend()'s resume point. + * + * It will just start executing here, so we'll restore stuff from the + * stack, reset the ARM_IDLECT1 and ARM_IDLECT2. + */ + strh r1, [r4, #ARM_IDLECT2_ASM_OFFSET & 0xff] + strh r0, [r4, #ARM_IDLECT1_ASM_OFFSET & 0xff] + + @ restore regs and return + ldmfd sp!, {r0 - r12, pc} + +ENTRY(omap1510_cpu_suspend_sz) + .word . - omap1510_cpu_suspend +#endif /* CONFIG_ARCH_OMAP1510 */ + +#if defined(CONFIG_ARCH_OMAP16XX) +ENTRY(omap1610_cpu_suspend) + + @ save registers on stack + stmfd sp!, {r0 - r12, lr} + + @ load base address of Traffic Controller + mov r4, #TCMIF_ASM_BASE & 0xff000000 + orr r4, r4, #TCMIF_ASM_BASE & 0x00ff0000 + orr r4, r4, #TCMIF_ASM_BASE & 0x0000ff00 + + @ prepare to put SDRAM into self-refresh manually + ldr r5, [r4, #EMIFF_SDRAM_CONFIG_ASM_OFFSET & 0xff] + orr r5, r5, #SELF_REFRESH_MODE & 0xff000000 + orr r5, r5, #SELF_REFRESH_MODE & 0x000000ff + str r5, [r4, #EMIFF_SDRAM_CONFIG_ASM_OFFSET & 0xff] + + @ prepare to put EMIFS to Sleep + ldr r5, [r4, #EMIFS_CONFIG_ASM_OFFSET & 0xff] + orr r5, r5, #IDLE_EMIFS_REQUEST & 0xff + str r5, [r4, #EMIFS_CONFIG_ASM_OFFSET & 0xff] + + @ load base address of ARM_IDLECT1 and ARM_IDLECT2 + mov r4, #CLKGEN_REG_ASM_BASE & 0xff000000 + orr r4, r4, #CLKGEN_REG_ASM_BASE & 0x00ff0000 + orr r4, r4, #CLKGEN_REG_ASM_BASE & 0x0000ff00 + + @ turn off clock domains + mov r5, #OMAP1610_IDLE_CLOCK_DOMAINS & 0xff + orr r5,r5, #OMAP1610_IDLE_CLOCK_DOMAINS & 0xff00 + strh r5, [r4, #ARM_IDLECT2_ASM_OFFSET & 0xff] + + @ work around errata of OMAP1610/5912. Enable (!) peripheral + @ clock to let the chip go into deep sleep + ldrh r5, [r4, #ARM_IDLECT2_ASM_OFFSET & 0xff] + orr r5,r5, #EN_PERCK_BIT & 0xff + strh r5, [r4, #ARM_IDLECT2_ASM_OFFSET & 0xff] + + @ request ARM idle + mov r3, #OMAP1610_DEEP_SLEEP_REQUEST & 0xff + orr r3, r3, #OMAP1610_DEEP_SLEEP_REQUEST & 0xff00 + strh r3, [r4, #ARM_IDLECT1_ASM_OFFSET & 0xff] + + mov r5, #IDLE_WAIT_CYCLES & 0xff + orr r5, r5, #IDLE_WAIT_CYCLES & 0xff00 +l_1610_2: + subs r5, r5, #1 + bne l_1610_2 +/* + * Let's wait for the next wake up event to wake us up. r0 can't be + * used here because r0 holds ARM_IDLECT1 + */ + mov r2, #0 + mcr p15, 0, r2, c7, c0, 4 @ wait for interrupt +/* + * omap1610_cpu_suspend()'s resume point. + * + * It will just start executing here, so we'll restore stuff from the + * stack, reset the ARM_IDLECT1 and ARM_IDLECT2. + */ + strh r1, [r4, #ARM_IDLECT2_ASM_OFFSET & 0xff] + strh r0, [r4, #ARM_IDLECT1_ASM_OFFSET & 0xff] + + @ restore regs and return + ldmfd sp!, {r0 - r12, pc} + +ENTRY(omap1610_cpu_suspend_sz) + .word . - omap1610_cpu_suspend +#endif /* CONFIG_ARCH_OMAP16XX */ diff --git a/arch/arm/plat-omap/usb.c b/arch/arm/plat-omap/usb.c new file mode 100644 index 000000000000..ab38e4eb3130 --- /dev/null +++ b/arch/arm/plat-omap/usb.c @@ -0,0 +1,593 @@ +/* + * arch/arm/plat-omap/usb.c -- platform level USB initialization + * + * 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 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 + */ + +#undef DEBUG + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/usb_otg.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> +#include <asm/hardware.h> +#include <asm/mach-types.h> + +#include <asm/arch/mux.h> +#include <asm/arch/usb.h> +#include <asm/arch/board.h> + +/* These routines should handle the standard chip-specific modes + * for usb0/1/2 ports, covering basic mux and transceiver setup. + * + * Some board-*.c files will need to set up additional mux options, + * like for suspend handling, vbus sensing, GPIOs, and the D+ pullup. + */ + +/* TESTED ON: + * - 1611B H2 (with usb1 mini-AB) using standard Mini-B or OTG cables + * - 5912 OSK OHCI (with usb0 standard-A), standard A-to-B cables + * - 5912 OSK UDC, with *nonstandard* A-to-A cable + * - 1510 Innovator UDC with bundled usb0 cable + * - 1510 Innovator OHCI with bundled usb1/usb2 cable + * - 1510 Innovator OHCI with custom usb0 cable, feeding 5V VBUS + * - 1710 custom development board using alternate pin group + * - 1710 H3 (with usb1 mini-AB) using standard Mini-B or OTG cables + */ + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_ARCH_OMAP_OTG + +static struct otg_transceiver *xceiv; + +/** + * otg_get_transceiver - find the (single) OTG transceiver driver + * + * Returns the transceiver driver, after getting a refcount to it; or + * null if there is no such transceiver. The caller is responsible for + * releasing that count. + */ +struct otg_transceiver *otg_get_transceiver(void) +{ + if (xceiv) + get_device(xceiv->dev); + return xceiv; +} +EXPORT_SYMBOL(otg_get_transceiver); + +int otg_set_transceiver(struct otg_transceiver *x) +{ + if (xceiv && x) + return -EBUSY; + xceiv = x; + return 0; +} +EXPORT_SYMBOL(otg_set_transceiver); + +#endif + +/*-------------------------------------------------------------------------*/ + +static u32 __init omap_usb0_init(unsigned nwires, unsigned is_device) +{ + u32 syscon1 = 0; + + if (nwires == 0) { + if (!cpu_is_omap15xx()) { + /* pulldown D+/D- */ + USB_TRANSCEIVER_CTRL_REG &= ~(3 << 1); + } + return 0; + } + + if (is_device) + omap_cfg_reg(W4_USB_PUEN); + + /* internal transceiver */ + if (nwires == 2) { + // omap_cfg_reg(P9_USB_DP); + // omap_cfg_reg(R8_USB_DM); + + if (cpu_is_omap15xx()) { + /* This works on 1510-Innovator */ + return 0; + } + + /* NOTES: + * - peripheral should configure VBUS detection! + * - only peripherals may use the internal D+/D- pulldowns + * - OTG support on this port not yet written + */ + + USB_TRANSCEIVER_CTRL_REG &= ~(7 << 4); + if (!is_device) + USB_TRANSCEIVER_CTRL_REG |= (3 << 1); + + return 3 << 16; + } + + /* alternate pin config, external transceiver */ + if (cpu_is_omap15xx()) { + printk(KERN_ERR "no usb0 alt pin config on 15xx\n"); + return 0; + } + + omap_cfg_reg(V6_USB0_TXD); + omap_cfg_reg(W9_USB0_TXEN); + omap_cfg_reg(W5_USB0_SE0); + + /* NOTE: SPEED and SUSP aren't configured here */ + + if (nwires != 3) + omap_cfg_reg(Y5_USB0_RCV); + if (nwires != 6) + USB_TRANSCEIVER_CTRL_REG &= ~CONF_USB2_UNI_R; + + switch (nwires) { + case 3: + syscon1 = 2; + break; + case 4: + syscon1 = 1; + break; + case 6: + syscon1 = 3; + omap_cfg_reg(AA9_USB0_VP); + omap_cfg_reg(R9_USB0_VM); + USB_TRANSCEIVER_CTRL_REG |= CONF_USB2_UNI_R; + break; + default: + printk(KERN_ERR "illegal usb%d %d-wire transceiver\n", + 0, nwires); + } + return syscon1 << 16; +} + +static u32 __init omap_usb1_init(unsigned nwires) +{ + u32 syscon1 = 0; + + if (nwires != 6 && !cpu_is_omap15xx()) + USB_TRANSCEIVER_CTRL_REG &= ~CONF_USB1_UNI_R; + if (nwires == 0) + return 0; + + /* external transceiver */ + omap_cfg_reg(USB1_TXD); + omap_cfg_reg(USB1_TXEN); + if (cpu_is_omap15xx()) { + omap_cfg_reg(USB1_SEO); + omap_cfg_reg(USB1_SPEED); + // SUSP + } else if (cpu_is_omap1610() || cpu_is_omap5912()) { + omap_cfg_reg(W13_1610_USB1_SE0); + omap_cfg_reg(R13_1610_USB1_SPEED); + // SUSP + } else if (cpu_is_omap1710()) { + omap_cfg_reg(R13_1710_USB1_SE0); + // SUSP + } else { + pr_debug("usb unrecognized\n"); + } + if (nwires != 3) + omap_cfg_reg(USB1_RCV); + + switch (nwires) { + case 3: + syscon1 = 2; + break; + case 4: + syscon1 = 1; + break; + case 6: + syscon1 = 3; + omap_cfg_reg(USB1_VP); + omap_cfg_reg(USB1_VM); + if (!cpu_is_omap15xx()) + USB_TRANSCEIVER_CTRL_REG |= CONF_USB1_UNI_R; + break; + default: + printk(KERN_ERR "illegal usb%d %d-wire transceiver\n", + 1, nwires); + } + return syscon1 << 20; +} + +static u32 __init omap_usb2_init(unsigned nwires, unsigned alt_pingroup) +{ + u32 syscon1 = 0; + + /* NOTE erratum: must leave USB2_UNI_R set if usb0 in use */ + if (alt_pingroup || nwires == 0) + return 0; + if (nwires != 6 && !cpu_is_omap15xx()) + USB_TRANSCEIVER_CTRL_REG &= ~CONF_USB2_UNI_R; + + /* external transceiver */ + if (cpu_is_omap15xx()) { + omap_cfg_reg(USB2_TXD); + omap_cfg_reg(USB2_TXEN); + omap_cfg_reg(USB2_SEO); + if (nwires != 3) + omap_cfg_reg(USB2_RCV); + /* there is no USB2_SPEED */ + } else if (cpu_is_omap16xx()) { + omap_cfg_reg(V6_USB2_TXD); + omap_cfg_reg(W9_USB2_TXEN); + omap_cfg_reg(W5_USB2_SE0); + if (nwires != 3) + omap_cfg_reg(Y5_USB2_RCV); + // FIXME omap_cfg_reg(USB2_SPEED); + } else { + pr_debug("usb unrecognized\n"); + } + // omap_cfg_reg(USB2_SUSP); + + switch (nwires) { + case 3: + syscon1 = 2; + break; + case 4: + syscon1 = 1; + break; + case 6: + syscon1 = 3; + if (cpu_is_omap15xx()) { + omap_cfg_reg(USB2_VP); + omap_cfg_reg(USB2_VM); + } else { + omap_cfg_reg(AA9_USB2_VP); + omap_cfg_reg(R9_USB2_VM); + USB_TRANSCEIVER_CTRL_REG |= CONF_USB2_UNI_R; + } + break; + default: + printk(KERN_ERR "illegal usb%d %d-wire transceiver\n", + 2, nwires); + } + return syscon1 << 24; +} + +/*-------------------------------------------------------------------------*/ + +#if defined(CONFIG_USB_GADGET_OMAP) || \ + defined(CONFIG_USB_OHCI_HCD) || defined(CONFIG_USB_OHCI_HCD_MODULE) || \ + (defined(CONFIG_USB_OTG) && defined(CONFIG_ARCH_OMAP_OTG)) +static void usb_release(struct device *dev) +{ + /* normally not freed */ +} +#endif + +#ifdef CONFIG_USB_GADGET_OMAP + +static struct resource udc_resources[] = { + /* order is significant! */ + { /* registers */ + .start = UDC_BASE, + .end = UDC_BASE + 0xff, + .flags = IORESOURCE_MEM, + }, { /* general IRQ */ + .start = IH2_BASE + 20, + .flags = IORESOURCE_IRQ, + }, { /* PIO IRQ */ + .start = IH2_BASE + 30, + .flags = IORESOURCE_IRQ, + }, { /* SOF IRQ */ + .start = IH2_BASE + 29, + .flags = IORESOURCE_IRQ, + }, +}; + +static u64 udc_dmamask = ~(u32)0; + +static struct platform_device udc_device = { + .name = "omap_udc", + .id = -1, + .dev = { + .release = usb_release, + .dma_mask = &udc_dmamask, + .coherent_dma_mask = 0xffffffff, + }, + .num_resources = ARRAY_SIZE(udc_resources), + .resource = udc_resources, +}; + +#endif + +#if defined(CONFIG_USB_OHCI_HCD) || defined(CONFIG_USB_OHCI_HCD_MODULE) + +/* The dmamask must be set for OHCI to work */ +static u64 ohci_dmamask = ~(u32)0; + +static struct resource ohci_resources[] = { + { + .start = OMAP_OHCI_BASE, + .end = OMAP_OHCI_BASE + 4096, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_USB_HHC_1, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device ohci_device = { + .name = "ohci", + .id = -1, + .dev = { + .release = usb_release, + .dma_mask = &ohci_dmamask, + .coherent_dma_mask = 0xffffffff, + }, + .num_resources = ARRAY_SIZE(ohci_resources), + .resource = ohci_resources, +}; + +#endif + +#if defined(CONFIG_USB_OTG) && defined(CONFIG_ARCH_OMAP_OTG) + +static struct resource otg_resources[] = { + /* order is significant! */ + { + .start = OTG_BASE, + .end = OTG_BASE + 0xff, + .flags = IORESOURCE_MEM, + }, { + .start = IH2_BASE + 8, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device otg_device = { + .name = "omap_otg", + .id = -1, + .dev = { + .release = usb_release, + }, + .num_resources = ARRAY_SIZE(otg_resources), + .resource = otg_resources, +}; + +#endif + +/*-------------------------------------------------------------------------*/ + +#define ULPD_CLOCK_CTRL_REG __REG16(ULPD_CLOCK_CTRL) +#define ULPD_SOFT_REQ_REG __REG16(ULPD_SOFT_REQ) + + +// FIXME correct answer depends on hmc_mode, +// as does any nonzero value for config->otg port number +#ifdef CONFIG_USB_GADGET_OMAP +#define is_usb0_device(config) 1 +#else +#define is_usb0_device(config) 0 +#endif + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_ARCH_OMAP_OTG + +void __init +omap_otg_init(struct omap_usb_config *config) +{ + u32 syscon = OTG_SYSCON_1_REG & 0xffff; + int status; + int alt_pingroup = 0; + + /* NOTE: no bus or clock setup (yet?) */ + + syscon = OTG_SYSCON_1_REG & 0xffff; + if (!(syscon & OTG_RESET_DONE)) + pr_debug("USB resets not complete?\n"); + + // OTG_IRQ_EN_REG = 0; + + /* pin muxing and transceiver pinouts */ + if (config->pins[0] > 2) /* alt pingroup 2 */ + alt_pingroup = 1; + syscon |= omap_usb0_init(config->pins[0], is_usb0_device(config)); + syscon |= omap_usb1_init(config->pins[1]); + syscon |= omap_usb2_init(config->pins[2], alt_pingroup); + pr_debug("OTG_SYSCON_1_REG = %08x\n", syscon); + OTG_SYSCON_1_REG = syscon; + + syscon = config->hmc_mode; + syscon |= USBX_SYNCHRO | (4 << 16) /* B_ASE0_BRST */; +#ifdef CONFIG_USB_OTG + if (config->otg) + syscon |= OTG_EN; +#endif + pr_debug("USB_TRANSCEIVER_CTRL_REG = %03x\n", USB_TRANSCEIVER_CTRL_REG); + pr_debug("OTG_SYSCON_2_REG = %08x\n", syscon); + OTG_SYSCON_2_REG = syscon; + + printk("USB: hmc %d", config->hmc_mode); + if (alt_pingroup) + printk(", usb2 alt %d wires", config->pins[2]); + else if (config->pins[0]) + printk(", usb0 %d wires%s", config->pins[0], + is_usb0_device(config) ? " (dev)" : ""); + if (config->pins[1]) + printk(", usb1 %d wires", config->pins[1]); + if (!alt_pingroup && config->pins[2]) + printk(", usb2 %d wires", config->pins[2]); + if (config->otg) + printk(", Mini-AB on usb%d", config->otg - 1); + printk("\n"); + + /* leave USB clocks/controllers off until needed */ + ULPD_SOFT_REQ_REG &= ~SOFT_USB_CLK_REQ; + ULPD_CLOCK_CTRL_REG &= ~USB_MCLK_EN; + ULPD_CLOCK_CTRL_REG |= DIS_USB_PVCI_CLK; + syscon = OTG_SYSCON_1_REG; + syscon |= HST_IDLE_EN|DEV_IDLE_EN|OTG_IDLE_EN; + +#ifdef CONFIG_USB_GADGET_OMAP + if (config->otg || config->register_dev) { + syscon &= ~DEV_IDLE_EN; + udc_device.dev.platform_data = config; + /* FIXME patch IRQ numbers for omap730 */ + status = platform_device_register(&udc_device); + if (status) + pr_debug("can't register UDC device, %d\n", status); + } +#endif + +#if defined(CONFIG_USB_OHCI_HCD) || defined(CONFIG_USB_OHCI_HCD_MODULE) + if (config->otg || config->register_host) { + syscon &= ~HST_IDLE_EN; + ohci_device.dev.platform_data = config; + if (cpu_is_omap730()) + ohci_resources[1].start = INT_730_USB_HHC_1; + status = platform_device_register(&ohci_device); + if (status) + pr_debug("can't register OHCI device, %d\n", status); + } +#endif + +#ifdef CONFIG_USB_OTG + if (config->otg) { + syscon &= ~OTG_IDLE_EN; + otg_device.dev.platform_data = config; + if (cpu_is_omap730()) + otg_resources[1].start = INT_730_USB_OTG; + status = platform_device_register(&otg_device); + if (status) + pr_debug("can't register OTG device, %d\n", status); + } +#endif + pr_debug("OTG_SYSCON_1_REG = %08x\n", syscon); + OTG_SYSCON_1_REG = syscon; + + status = 0; +} + +#else +static inline void omap_otg_init(struct omap_usb_config *config) {} +#endif + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_ARCH_OMAP1510 + +#define ULPD_DPLL_CTRL_REG __REG16(ULPD_DPLL_CTRL) +#define DPLL_IOB (1 << 13) +#define DPLL_PLL_ENABLE (1 << 4) +#define DPLL_LOCK (1 << 0) + +#define ULPD_APLL_CTRL_REG __REG16(ULPD_APLL_CTRL) +#define APLL_NDPLL_SWITCH (1 << 0) + + +static void __init omap_1510_usb_init(struct omap_usb_config *config) +{ + int status; + unsigned int val; + + omap_usb0_init(config->pins[0], is_usb0_device(config)); + omap_usb1_init(config->pins[1]); + omap_usb2_init(config->pins[2], 0); + + val = omap_readl(MOD_CONF_CTRL_0) & ~(0x3f << 1); + val |= (config->hmc_mode << 1); + omap_writel(val, MOD_CONF_CTRL_0); + + printk("USB: hmc %d", config->hmc_mode); + if (config->pins[0]) + printk(", usb0 %d wires%s", config->pins[0], + is_usb0_device(config) ? " (dev)" : ""); + if (config->pins[1]) + printk(", usb1 %d wires", config->pins[1]); + if (config->pins[2]) + printk(", usb2 %d wires", config->pins[2]); + printk("\n"); + + /* use DPLL for 48 MHz function clock */ + pr_debug("APLL %04x DPLL %04x REQ %04x\n", ULPD_APLL_CTRL_REG, + ULPD_DPLL_CTRL_REG, ULPD_SOFT_REQ_REG); + ULPD_APLL_CTRL_REG &= ~APLL_NDPLL_SWITCH; + ULPD_DPLL_CTRL_REG |= DPLL_IOB | DPLL_PLL_ENABLE; + ULPD_SOFT_REQ_REG |= SOFT_UDC_REQ | SOFT_DPLL_REQ; + while (!(ULPD_DPLL_CTRL_REG & DPLL_LOCK)) + cpu_relax(); + +#ifdef CONFIG_USB_GADGET_OMAP + if (config->register_dev) { + udc_device.dev.platform_data = config; + status = platform_device_register(&udc_device); + if (status) + pr_debug("can't register UDC device, %d\n", status); + /* udc driver gates 48MHz by D+ pullup */ + } +#endif + +#if defined(CONFIG_USB_OHCI_HCD) || defined(CONFIG_USB_OHCI_HCD_MODULE) + if (config->register_host) { + ohci_device.dev.platform_data = config; + status = platform_device_register(&ohci_device); + if (status) + pr_debug("can't register OHCI device, %d\n", status); + /* hcd explicitly gates 48MHz */ + } +#endif +} + +#else +static inline void omap_1510_usb_init(struct omap_usb_config *config) {} +#endif + +/*-------------------------------------------------------------------------*/ + +static struct omap_usb_config platform_data; + +static int __init +omap_usb_init(void) +{ + const struct omap_usb_config *config; + + config = omap_get_config(OMAP_TAG_USB, struct omap_usb_config); + if (config == NULL) { + printk(KERN_ERR "USB: No board-specific " + "platform config found\n"); + return -ENODEV; + } + platform_data = *config; + + if (cpu_is_omap730() || cpu_is_omap16xx()) + omap_otg_init(&platform_data); + else if (cpu_is_omap15xx()) + omap_1510_usb_init(&platform_data); + else { + printk(KERN_ERR "USB: No init for your chip yet\n"); + return -ENODEV; + } + return 0; +} + +subsys_initcall(omap_usb_init); |