From: Tony Lindgren Date: Thu, 14 Aug 2008 09:57:16 +0000 (+0300) Subject: Merge current mainline tree into linux-omap tree X-Git-Tag: v2.6.27-omap1~312 X-Git-Url: http://pilppa.com/gitweb/?a=commitdiff_plain;h=fdee8764947cde1e6933e7d981ce5b9de00e83e6;p=linux-2.6-omap-h63xx.git Merge current mainline tree into linux-omap tree Merge branches 'master' and 'linus' Conflicts: arch/arm/Makefile arch/arm/mach-omap1/board-h2.c arch/arm/mach-omap1/board-h3.c arch/arm/mach-omap1/board-nokia770.c arch/arm/mach-omap1/board-palmte.c arch/arm/mach-omap1/board-sx1.c arch/arm/mach-omap2/board-2430sdp.c arch/arm/mach-omap2/board-apollon.c arch/arm/mach-omap2/board-h4.c arch/arm/mach-omap2/clock.c arch/arm/mach-omap2/clock24xx.c arch/arm/mach-omap2/devices.c arch/arm/mach-omap2/id.c arch/arm/mach-omap2/io.c arch/arm/mach-omap2/memory.c arch/arm/mach-omap2/pm.c arch/arm/mach-omap2/sleep.S arch/arm/plat-omap/devices.c drivers/i2c/chips/menelaus.c drivers/input/keyboard/omap-keypad.c drivers/watchdog/omap_wdt.c include/asm-arm/arch-omap/board-2430sdp.h include/asm-arm/arch-omap/board-apollon.h include/asm-arm/arch-omap/board-h4.h include/asm-arm/arch-omap/board-nokia.h include/asm-arm/arch-omap/board.h include/asm-arm/arch-omap/clock.h include/asm-arm/arch-omap/common.h include/asm-arm/arch-omap/control.h include/asm-arm/arch-omap/cpu.h include/asm-arm/arch-omap/debug-macro.S include/asm-arm/arch-omap/dsp_common.h include/asm-arm/arch-omap/entry-macro.S include/asm-arm/arch-omap/gpio-switch.h include/asm-arm/arch-omap/gpio.h include/asm-arm/arch-omap/gpmc.h include/asm-arm/arch-omap/hardware.h include/asm-arm/arch-omap/io.h include/asm-arm/arch-omap/irqs.h include/asm-arm/arch-omap/mailbox.h include/asm-arm/arch-omap/mcbsp.h include/asm-arm/arch-omap/memory.h include/asm-arm/arch-omap/mmc.h include/asm-arm/arch-omap/mux.h include/asm-arm/arch-omap/omap-alsa.h include/asm-arm/arch-omap/omap1510.h include/asm-arm/arch-omap/omap16xx.h include/asm-arm/arch-omap/omap24xx.h include/asm-arm/arch-omap/omap34xx.h include/asm-arm/arch-omap/omapfb.h include/asm-arm/arch-omap/onenand.h include/asm-arm/arch-omap/pm.h include/asm-arm/arch-omap/prcm.h include/asm-arm/arch-omap/sdrc.h include/asm-arm/arch-omap/serial.h include/asm-arm/arch-omap/sram.h include/asm-arm/arch-omap/system.h include/asm-arm/arch-omap/timex.h include/asm-arm/arch-omap/usb.h include/asm-arm/arch-omap/vmalloc.h --- fdee8764947cde1e6933e7d981ce5b9de00e83e6 diff --cc arch/arm/Makefile index c595df31636,703a44fa0f9..552ea6d71ea --- a/arch/arm/Makefile +++ b/arch/arm/Makefile @@@ -119,9 -118,9 +118,10 @@@ endi machine-$(CONFIG_ARCH_IXP23XX) := ixp23xx machine-$(CONFIG_ARCH_OMAP1) := omap1 machine-$(CONFIG_ARCH_OMAP2) := omap2 - machine-$(CONFIG_ARCH_OMAP3) := omap2 - incdir-$(CONFIG_ARCH_OMAP) := omap - machine-$(CONFIG_ARCH_S3C2410) := s3c2410 ++ machine-$(CONFIG_ARCH_OMAP3) := omap2 + plat-$(CONFIG_ARCH_OMAP) := omap + machine-$(CONFIG_ARCH_S3C2410) := s3c2410 s3c2400 s3c2412 s3c2440 s3c2442 s3c2443 + plat-$(CONFIG_PLAT_S3C24XX) := s3c24xx machine-$(CONFIG_ARCH_LH7A40X) := lh7a40x machine-$(CONFIG_ARCH_VERSATILE) := versatile machine-$(CONFIG_ARCH_IMX) := imx diff --cc arch/arm/mach-omap1/board-h2.c index c9e627df6ff,3b65914b914..1055de1ae4d --- a/arch/arm/mach-omap1/board-h2.c +++ b/arch/arm/mach-omap1/board-h2.c @@@ -29,12 -28,8 +29,12 @@@ #include #include #include +#include +#include +#include +#include - #include + #include #include #include @@@ -42,17 -37,16 +42,17 @@@ #include #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include ++#include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include static int h2_keymap[] = { KEY(0, 0, KEY_LEFT), diff --cc arch/arm/mach-omap1/board-h3.c index b2a12864007,2ced6d9984d..7516d21af86 --- a/arch/arm/mach-omap1/board-h3.c +++ b/arch/arm/mach-omap1/board-h3.c @@@ -42,25 -39,19 +42,25 @@@ #include #include +#include + - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include ++#include ++#include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#include <../drivers/media/video/ov9640.h> + #define H3_TS_GPIO 48 static int h3_keymap[] = { diff --cc arch/arm/mach-omap1/board-nokia770.c index 33a987739b4,38d9783ac6d..0e297f3de01 --- a/arch/arm/mach-omap1/board-nokia770.c +++ b/arch/arm/mach-omap1/board-nokia770.c @@@ -25,17 -25,16 +25,17 @@@ #include #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include + #include + #include + #include + #include + #include + #include + #include + #include + #include ++#include + #include #define ADS7846_PENDOWN_GPIO 15 diff --cc arch/arm/mach-omap1/board-palmte.c index 2a467657588,b58043644a6..4b2c62b3853 --- a/arch/arm/mach-omap1/board-palmte.c +++ b/arch/arm/mach-omap1/board-palmte.c @@@ -33,18 -33,17 +33,18 @@@ #include #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include ++#include static void __init omap_palmte_init_irq(void) { diff --cc arch/arm/mach-omap1/board-sx1.c index 566f970f8c1,130bcc6fd08..d3156b8451d --- a/arch/arm/mach-omap1/board-sx1.c +++ b/arch/arm/mach-omap1/board-sx1.c @@@ -32,17 -32,16 +32,17 @@@ #include #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include + #include ++#include + #include + #include + #include + #include + #include + #include + #include + #include + #include /* Write to I2C device */ int sx1_i2c_write_byte(u8 devaddr, u8 regoffset, u8 value) diff --cc arch/arm/mach-omap1/mmu.c index a894ca2da24,00000000000..75dd310a907 mode 100644,000000..100644 --- a/arch/arm/mach-omap1/mmu.c +++ b/arch/arm/mach-omap1/mmu.c @@@ -1,351 -1,0 +1,351 @@@ +/* + * linux/arch/arm/mach-omap1/mmu.c + * + * Support for non-MPU OMAP1 MMUs. + * + * Copyright (C) 2002-2005 Nokia Corporation + * + * Written by Toshihiro Kobayashi + * and Paul Mundt + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "mmu.h" +#include - #include ++#include + +static void *dspvect_page; +#define DSP_INIT_PAGE 0xfff000 + +#define MMUFAULT_MASK (OMAP_MMU_FAULT_ST_PERM |\ + OMAP_MMU_FAULT_ST_TLB_MISS |\ + OMAP_MMU_FAULT_ST_TRANS) + +static unsigned int get_cam_l_va_mask(u16 pgsz) +{ + switch (pgsz) { + case OMAP_MMU_CAM_PAGESIZE_1MB: + return OMAP_MMU_CAM_L_VA_TAG_L1_MASK | + OMAP_MMU_CAM_L_VA_TAG_L2_MASK_1MB; + case OMAP_MMU_CAM_PAGESIZE_64KB: + return OMAP_MMU_CAM_L_VA_TAG_L1_MASK | + OMAP_MMU_CAM_L_VA_TAG_L2_MASK_64KB; + case OMAP_MMU_CAM_PAGESIZE_4KB: + return OMAP_MMU_CAM_L_VA_TAG_L1_MASK | + OMAP_MMU_CAM_L_VA_TAG_L2_MASK_4KB; + case OMAP_MMU_CAM_PAGESIZE_1KB: + return OMAP_MMU_CAM_L_VA_TAG_L1_MASK | + OMAP_MMU_CAM_L_VA_TAG_L2_MASK_1KB; + } + return 0; +} + +#define get_cam_va_mask(pgsz) \ + ((u32)OMAP_MMU_CAM_H_VA_TAG_H_MASK << 22 | \ + (u32)get_cam_l_va_mask(pgsz) << 6) + +static int intmem_usecount; + +/* for safety */ +void dsp_mem_usecount_clear(void) +{ + if (intmem_usecount != 0) { + printk(KERN_WARNING + "MMU: unbalanced memory request/release detected.\n" + " intmem_usecount is not zero at where " + "it should be! ... fixed to be zero.\n"); + intmem_usecount = 0; + omap_dsp_release_mem(); + } +} +EXPORT_SYMBOL_GPL(dsp_mem_usecount_clear); + +void omap_mmu_itack(struct omap_mmu *mmu) +{ + omap_mmu_write_reg(mmu, OMAP_MMU_IT_ACK_IT_ACK, OMAP_MMU_IT_ACK); +} +EXPORT_SYMBOL(omap_mmu_itack); + +static int omap1_mmu_mem_enable(struct omap_mmu *mmu, void *addr) +{ + int ret = 0; + + if (omap_mmu_internal_memory(mmu, addr)) { + if (intmem_usecount++ == 0) + ret = omap_dsp_request_mem(); + } + + return ret; +} + +static int omap1_mmu_mem_disable(struct omap_mmu *mmu, void *addr) +{ + int ret = 0; + + if (omap_mmu_internal_memory(mmu, addr)) { + if (--intmem_usecount == 0) + omap_dsp_release_mem(); + } else + ret = -EIO; + + return ret; +} + +static inline void +omap1_mmu_read_tlb(struct omap_mmu *mmu, struct cam_ram_regset *cr) +{ + /* read a TLB entry */ + omap_mmu_write_reg(mmu, OMAP_MMU_LD_TLB_RD, OMAP_MMU_LD_TLB); + + cr->cam_h = omap_mmu_read_reg(mmu, OMAP_MMU_READ_CAM_H); + cr->cam_l = omap_mmu_read_reg(mmu, OMAP_MMU_READ_CAM_L); + cr->ram_h = omap_mmu_read_reg(mmu, OMAP_MMU_READ_RAM_H); + cr->ram_l = omap_mmu_read_reg(mmu, OMAP_MMU_READ_RAM_L); +} + +static inline void +omap1_mmu_load_tlb(struct omap_mmu *mmu, struct cam_ram_regset *cr) +{ + /* Set the CAM and RAM entries */ + omap_mmu_write_reg(mmu, cr->cam_h, OMAP_MMU_CAM_H); + omap_mmu_write_reg(mmu, cr->cam_l, OMAP_MMU_CAM_L); + omap_mmu_write_reg(mmu, cr->ram_h, OMAP_MMU_RAM_H); + omap_mmu_write_reg(mmu, cr->ram_l, OMAP_MMU_RAM_L); +} + +static ssize_t omap1_mmu_show(struct omap_mmu *mmu, char *buf, + struct omap_mmu_tlb_lock *tlb_lock) +{ + int i, len; + + len = sprintf(buf, "P: preserved, V: valid\n" + "ety P V size cam_va ram_pa ap\n"); + /* 00: P V 4KB 0x300000 0x10171800 FA */ + + for (i = 0; i < mmu->nr_tlb_entries; i++) { + struct omap_mmu_tlb_entry ent; + struct cam_ram_regset cr; + struct omap_mmu_tlb_lock entry_lock; + char *pgsz_str, *ap_str; + + /* read a TLB entry */ + entry_lock.base = tlb_lock->base; + entry_lock.victim = i; + omap_mmu_read_tlb(mmu, &entry_lock, &cr); + + ent.pgsz = cr.cam_l & OMAP_MMU_CAM_PAGESIZE_MASK; + ent.prsvd = cr.cam_l & OMAP_MMU_CAM_P; + ent.valid = cr.cam_l & OMAP_MMU_CAM_V; + ent.ap = cr.ram_l & OMAP_MMU_RAM_L_AP_MASK; + ent.va = (u32)(cr.cam_h & OMAP_MMU_CAM_H_VA_TAG_H_MASK) << 22 | + (u32)(cr.cam_l & get_cam_l_va_mask(ent.pgsz)) << 6; + ent.pa = (unsigned long)cr.ram_h << 16 | + (cr.ram_l & OMAP_MMU_RAM_L_RAM_LSB_MASK); + + pgsz_str = (ent.pgsz == OMAP_MMU_CAM_PAGESIZE_1MB) ? " 1MB": + (ent.pgsz == OMAP_MMU_CAM_PAGESIZE_64KB) ? "64KB": + (ent.pgsz == OMAP_MMU_CAM_PAGESIZE_4KB) ? " 4KB": + (ent.pgsz == OMAP_MMU_CAM_PAGESIZE_1KB) ? " 1KB": + " ???"; + ap_str = (ent.ap == OMAP_MMU_RAM_L_AP_RO) ? "RO": + (ent.ap == OMAP_MMU_RAM_L_AP_FA) ? "FA": + (ent.ap == OMAP_MMU_RAM_L_AP_NA) ? "NA": + "??"; + + if (i == tlb_lock->base) + len += sprintf(buf + len, "lock base = %d\n", + tlb_lock->base); + if (i == tlb_lock->victim) + len += sprintf(buf + len, "victim = %d\n", + tlb_lock->victim); + len += sprintf(buf + len, + /* 00: P V 4KB 0x300000 0x10171800 FA */ + "%02d: %c %c %s 0x%06lx 0x%08lx %s\n", + i, + ent.prsvd ? 'P' : ' ', + ent.valid ? 'V' : ' ', + pgsz_str, ent.va, ent.pa, ap_str); + } + + return len; +} + +static int exmap_setup_preserved_entries(struct omap_mmu *mmu) +{ + int n = 0; + + exmap_setup_preserved_mem_page(mmu, dspvect_page, DSP_INIT_PAGE, n++); + + return n; +} + +static void exmap_clear_preserved_entries(struct omap_mmu *mmu) +{ + exmap_clear_mem_page(mmu, DSP_INIT_PAGE); +} + +static int omap1_mmu_startup(struct omap_mmu *mmu) +{ + dspvect_page = (void *)__get_dma_pages(GFP_KERNEL, 0); + if (dspvect_page == NULL) { + dev_err(mmu->dev, "MMU %s: failed to allocate memory " + "for vector table\n", mmu->name); + return -ENOMEM; + } + + mmu->nr_exmap_preserved = exmap_setup_preserved_entries(mmu); + + return 0; +} + +static void omap1_mmu_shutdown(struct omap_mmu *mmu) +{ + exmap_clear_preserved_entries(mmu); + + if (dspvect_page != NULL) { + unsigned long virt; + + down_read(&mmu->exmap_sem); + + virt = (unsigned long)omap_mmu_to_virt(mmu, DSP_INIT_PAGE); + flush_tlb_kernel_range(virt, virt + PAGE_SIZE); + free_page((unsigned long)dspvect_page); + dspvect_page = NULL; + + up_read(&mmu->exmap_sem); + } +} + +static inline unsigned long omap1_mmu_cam_va(struct cam_ram_regset *cr) +{ + unsigned int page_size = cr->cam_l & OMAP_MMU_CAM_PAGESIZE_MASK; + + return (u32)(cr->cam_h & OMAP_MMU_CAM_H_VA_TAG_H_MASK) << 22 | + (u32)(cr->cam_l & get_cam_l_va_mask(page_size)) << 6; +} + +static struct cam_ram_regset * +omap1_mmu_cam_ram_alloc(struct omap_mmu *mmu, struct omap_mmu_tlb_entry *entry) +{ + struct cam_ram_regset *cr; + + if (entry->va & ~(get_cam_va_mask(entry->pgsz))) { + dev_err(mmu->dev, "MMU %s: mapping vadr (0x%06lx) is not on" + " an aligned boundary\n", mmu->name, entry->va); + return ERR_PTR(-EINVAL); + } + + cr = kmalloc(sizeof(struct cam_ram_regset), GFP_KERNEL); + + cr->cam_h = entry->va >> 22; + cr->cam_l = (entry->va >> 6 & get_cam_l_va_mask(entry->pgsz)) | + entry->prsvd | entry->pgsz; + cr->ram_h = entry->pa >> 16; + cr->ram_l = (entry->pa & OMAP_MMU_RAM_L_RAM_LSB_MASK) | entry->ap; + + return cr; +} + +static inline int omap1_mmu_cam_ram_valid(struct cam_ram_regset *cr) +{ + return cr->cam_l & OMAP_MMU_CAM_V; +} + +static void omap1_mmu_interrupt(struct omap_mmu *mmu) +{ + unsigned long status; + unsigned long adh, adl; + unsigned long dp; + unsigned long va; + + status = omap_mmu_read_reg(mmu, OMAP_MMU_FAULT_ST); + adh = omap_mmu_read_reg(mmu, OMAP_MMU_FAULT_AD_H); + adl = omap_mmu_read_reg(mmu, OMAP_MMU_FAULT_AD_L); + dp = adh & OMAP_MMU_FAULT_AD_H_DP; + va = (((adh & OMAP_MMU_FAULT_AD_H_ADR_MASK) << 16) | adl); + + /* if the fault is masked, nothing to do */ + if ((status & MMUFAULT_MASK) == 0) { + pr_debug("MMU interrupt, but ignoring.\n"); + /* + * note: in OMAP1710, + * when CACHE + DMA domain gets out of idle in DSP, + * MMU interrupt occurs but MMU_FAULT_ST is not set. + * in this case, we just ignore the interrupt. + */ + if (status) { + pr_debug("%s%s%s%s\n", + (status & OMAP_MMU_FAULT_ST_PREF)? + " (prefetch err)" : "", + (status & OMAP_MMU_FAULT_ST_PERM)? + " (permission fault)" : "", + (status & OMAP_MMU_FAULT_ST_TLB_MISS)? + " (TLB miss)" : "", + (status & OMAP_MMU_FAULT_ST_TRANS) ? + " (translation fault)": ""); + pr_debug("fault address = %#08lx\n", va); + } + enable_irq(mmu->irq); + return; + } + + pr_info("%s%s%s%s\n", + (status & OMAP_MMU_FAULT_ST_PREF)? + (MMUFAULT_MASK & OMAP_MMU_FAULT_ST_PREF)? + " prefetch err": + " (prefetch err)": + "", + (status & OMAP_MMU_FAULT_ST_PERM)? + (MMUFAULT_MASK & OMAP_MMU_FAULT_ST_PERM)? + " permission fault": + " (permission fault)": + "", + (status & OMAP_MMU_FAULT_ST_TLB_MISS)? + (MMUFAULT_MASK & OMAP_MMU_FAULT_ST_TLB_MISS)? + " TLB miss": + " (TLB miss)": + "", + (status & OMAP_MMU_FAULT_ST_TRANS)? + (MMUFAULT_MASK & OMAP_MMU_FAULT_ST_TRANS)? + " translation fault": + " (translation fault)": + ""); + pr_info("fault address = %#08lx\n", va); + + mmu->fault_address = va; + schedule_work(&mmu->irq_work); +} + +static pgprot_t omap1_mmu_pte_get_attr(struct omap_mmu_tlb_entry *entry) +{ + /* 4KB AP position as default */ + u32 attr = entry->ap >> 4; + attr <<= ((entry->pgsz == OMAP_MMU_CAM_PAGESIZE_1MB) ? 6:0); + return attr; +} + +struct omap_mmu_ops omap1_mmu_ops = { + .startup = omap1_mmu_startup, + .shutdown = omap1_mmu_shutdown, + .mem_enable = omap1_mmu_mem_enable, + .mem_disable = omap1_mmu_mem_disable, + .read_tlb = omap1_mmu_read_tlb, + .load_tlb = omap1_mmu_load_tlb, + .show = omap1_mmu_show, + .cam_va = omap1_mmu_cam_va, + .cam_ram_alloc = omap1_mmu_cam_ram_alloc, + .cam_ram_valid = omap1_mmu_cam_ram_valid, + .interrupt = omap1_mmu_interrupt, + .pte_get_attr = omap1_mmu_pte_get_attr, +}; +EXPORT_SYMBOL_GPL(omap1_mmu_ops); diff --cc arch/arm/mach-omap1/mmu.h index 9d9d9bc9dc6,00000000000..b0255af2b1d mode 100644,000000..100644 --- a/arch/arm/mach-omap1/mmu.h +++ b/arch/arm/mach-omap1/mmu.h @@@ -1,119 -1,0 +1,119 @@@ +#ifndef __MACH_OMAP1_MMU_H +#define __MACH_OMAP1_MMU_H + +#include - #include ++#include + +#define MMU_LOCK_BASE_MASK (0x3f << 10) +#define MMU_LOCK_VICTIM_MASK (0x3f << 4) + +#define OMAP_MMU_PREFETCH 0x00 +#define OMAP_MMU_WALKING_ST 0x04 +#define OMAP_MMU_CNTL 0x08 +#define OMAP_MMU_FAULT_AD_H 0x0c +#define OMAP_MMU_FAULT_AD_L 0x10 +#define OMAP_MMU_FAULT_ST 0x14 +#define OMAP_MMU_IT_ACK 0x18 +#define OMAP_MMU_TTB_H 0x1c +#define OMAP_MMU_TTB_L 0x20 +#define OMAP_MMU_LOCK 0x24 +#define OMAP_MMU_LD_TLB 0x28 +#define OMAP_MMU_CAM_H 0x2c +#define OMAP_MMU_CAM_L 0x30 +#define OMAP_MMU_RAM_H 0x34 +#define OMAP_MMU_RAM_L 0x38 +#define OMAP_MMU_GFLUSH 0x3c +#define OMAP_MMU_FLUSH_ENTRY 0x40 +#define OMAP_MMU_READ_CAM_H 0x44 +#define OMAP_MMU_READ_CAM_L 0x48 +#define OMAP_MMU_READ_RAM_H 0x4c +#define OMAP_MMU_READ_RAM_L 0x50 + +#define OMAP_MMU_CNTL_BURST_16MNGT_EN 0x0020 +#define OMAP_MMU_CNTL_WTL_EN 0x0004 +#define OMAP_MMU_CNTL_MMU_EN 0x0002 +#define OMAP_MMU_CNTL_RESET_SW 0x0001 + +#define OMAP_MMU_FAULT_AD_H_DP 0x0100 +#define OMAP_MMU_FAULT_AD_H_ADR_MASK 0x00ff + +#define OMAP_MMU_FAULT_ST_PREF 0x0008 +#define OMAP_MMU_FAULT_ST_PERM 0x0004 +#define OMAP_MMU_FAULT_ST_TLB_MISS 0x0002 +#define OMAP_MMU_FAULT_ST_TRANS 0x0001 + +#define OMAP_MMU_IT_ACK_IT_ACK 0x0001 + +#define OMAP_MMU_CAM_H_VA_TAG_H_MASK 0x0003 + +#define OMAP_MMU_CAM_L_VA_TAG_L1_MASK 0xc000 +#define OMAP_MMU_CAM_L_VA_TAG_L2_MASK_1MB 0x0000 +#define OMAP_MMU_CAM_L_VA_TAG_L2_MASK_64KB 0x3c00 +#define OMAP_MMU_CAM_L_VA_TAG_L2_MASK_4KB 0x3fc0 +#define OMAP_MMU_CAM_L_VA_TAG_L2_MASK_1KB 0x3ff0 +#define OMAP_MMU_CAM_L_P 0x0008 +#define OMAP_MMU_CAM_L_V 0x0004 +#define OMAP_MMU_CAM_L_PAGESIZE_MASK 0x0003 +#define OMAP_MMU_CAM_L_PAGESIZE_1MB 0x0000 +#define OMAP_MMU_CAM_L_PAGESIZE_64KB 0x0001 +#define OMAP_MMU_CAM_L_PAGESIZE_4KB 0x0002 +#define OMAP_MMU_CAM_L_PAGESIZE_1KB 0x0003 + +#define OMAP_MMU_CAM_P OMAP_MMU_CAM_L_P +#define OMAP_MMU_CAM_V OMAP_MMU_CAM_L_V +#define OMAP_MMU_CAM_PAGESIZE_MASK OMAP_MMU_CAM_L_PAGESIZE_MASK +#define OMAP_MMU_CAM_PAGESIZE_1MB OMAP_MMU_CAM_L_PAGESIZE_1MB +#define OMAP_MMU_CAM_PAGESIZE_64KB OMAP_MMU_CAM_L_PAGESIZE_64KB +#define OMAP_MMU_CAM_PAGESIZE_4KB OMAP_MMU_CAM_L_PAGESIZE_4KB +#define OMAP_MMU_CAM_PAGESIZE_1KB OMAP_MMU_CAM_L_PAGESIZE_1KB +#define OMAP_MMU_CAM_PAGESIZE_16MB -1 /* unused in omap1 */ + +#define OMAP_MMU_RAM_L_RAM_LSB_MASK 0xfc00 +#define OMAP_MMU_RAM_L_AP_MASK 0x0300 +#define OMAP_MMU_RAM_L_AP_NA 0x0000 +#define OMAP_MMU_RAM_L_AP_RO 0x0200 +#define OMAP_MMU_RAM_L_AP_FA 0x0300 + +#define OMAP_MMU_LD_TLB_RD 0x0002 + +#define INIT_TLB_ENTRY(ent, v, p, ps) \ +do { \ + (ent)->va = (v); \ + (ent)->pa = (p); \ + (ent)->pgsz = (ps); \ + (ent)->prsvd = 0; \ + (ent)->ap = OMAP_MMU_RAM_L_AP_FA; \ + (ent)->tlb = 1; \ +} while (0) + +#define INIT_TLB_ENTRY_4KB_PRESERVED(ent, v, p) \ +do { \ + (ent)->va = (v); \ + (ent)->pa = (p); \ + (ent)->pgsz = OMAP_MMU_CAM_PAGESIZE_4KB; \ + (ent)->prsvd = OMAP_MMU_CAM_P; \ + (ent)->ap = OMAP_MMU_RAM_L_AP_FA; \ +} while (0) + +struct omap_mmu_tlb_entry { + unsigned long va; + unsigned long pa; + unsigned int pgsz, prsvd, valid; + + u16 ap; + unsigned int tlb; +}; + +static inline unsigned short +omap_mmu_read_reg(struct omap_mmu *mmu, unsigned long reg) +{ + return __raw_readw(mmu->base + reg); +} + +static inline void omap_mmu_write_reg(struct omap_mmu *mmu, + unsigned short val, unsigned long reg) +{ + __raw_writew(val, mmu->base + reg); +} + +#endif /* __MACH_OMAP1_MMU_H */ diff --cc arch/arm/mach-omap2/bci.c index 2bd71d525ad,00000000000..9b9f9d20412 mode 100644,000000..100644 --- a/arch/arm/mach-omap2/bci.c +++ b/arch/arm/mach-omap2/bci.c @@@ -1,57 -1,0 +1,57 @@@ +/* + * linux/arch/arm/mach-omap2/bci.c + * + * TWL4030 BCI platform device setup/initialization + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include - #include ++#include + +#if defined(CONFIG_TWL4030_BCI_BATTERY) || \ + defined(CONFIG_TWL4030_BCI_BATTERY_MODULE) +/* + * Thermistor Calibration for Current Source and MADC + * Tolerance (for THS05-3H103F) + */ +static int sdp3430_batt_table[] = { +/* 0 C*/ +30800, 29500, 28300, 27100, +26000, 24900, 23900, 22900, 22000, 21100, 20300, 19400, 18700, 17900, +17200, 16500, 15900, 15300, 14700, 14100, 13600, 13100, 12600, 12100, +11600, 11200, 10800, 10400, 10000, 9630, 9280, 8950, 8620, 8310, +8020, 7730, 7460, 7200, 6950, 6710, 6470, 6250, 6040, 5830, +5640, 5450, 5260, 5090, 4920, 4760, 4600, 4450, 4310, 4170, +4040, 3910, 3790, 3670, 3550 +}; + +static struct twl4030_bci_platform_data sdp3430_bci_data = { + .battery_tmp_tbl = sdp3430_batt_table, + .tblsize = ARRAY_SIZE(sdp3430_batt_table), +}; + +static struct platform_device twl4030_bci_battery_device = { + .name = "twl4030-bci-battery", + .id = -1, + .dev = { + .platform_data = &sdp3430_bci_data, + }, + .num_resources = 0, +}; + +void __init twl4030_bci_battery_init(void) +{ + (void) platform_device_register(&twl4030_bci_battery_device); +} +#else +void __init twl4030_bci_battery_init(void) +{ +} +#endif diff --cc arch/arm/mach-omap2/board-2430sdp-flash.c index aaa8db46e3c,00000000000..57d3b553715 mode 100644,000000..100644 --- a/arch/arm/mach-omap2/board-2430sdp-flash.c +++ b/arch/arm/mach-omap2/board-2430sdp-flash.c @@@ -1,185 -1,0 +1,185 @@@ +/* + * linux/arch/arm/mach-omap2/board-2430sdp-flash.c + * + * Copyright (C) 2007 MontaVista Software, Inc. + * Author: Kevin Hilman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +#include - #include - #include - #include - #include ++#include ++#include ++#include ++#include + +#define ONENAND_MAP 0x20000000 +#define GPMC_OFF_CONFIG1_0 0x60 + +enum fstype { + NAND = 0, + NOR, + ONENAND, + UNKNOWN = -1 +}; + +static enum fstype flash_type = NAND; + +static struct mtd_partition nand_partitions[] = { + { + .name = "X-Loader", + .offset = 0, + .size = 4*(64*2048), /* 0-3 blks reserved. + Mandated by ROM code */ + .mask_flags = MTD_WRITEABLE /* force read-only */ + }, + { + .name = "U-Boot", + .offset = MTDPART_OFS_APPEND, + .size = 4*(64*2048), + .mask_flags = MTD_WRITEABLE /* force read-only */ + }, + { + .name = "U-Boot Environment", + .offset = MTDPART_OFS_APPEND, + .size = 2*(64*2048), + }, + { + .name = "Kernel", + .offset = MTDPART_OFS_APPEND, + .size = 32*(64*2048), /* 4*1M */ + }, + { + .name = "File System", + .offset = MTDPART_OFS_APPEND, + .size = MTDPART_SIZ_FULL, + }, +}; +static struct omap_nand_platform_data sdp_nand_data = { + .parts = nand_partitions, + .nr_parts = ARRAY_SIZE(nand_partitions), + .dma_channel = -1, /* disable DMA in OMAP OneNAND driver */ +}; + +static struct platform_device sdp_nand_device = { + .name = "omap2-nand", + .id = -1, + .dev = { + .platform_data = &sdp_nand_data, + }, +}; + +static struct mtd_partition onenand_partitions[] = { + { + .name = "(OneNAND)X-Loader", + .offset = 0, + .size = 4*(64*2048), /* 0-3 blks reserved. + Mandated by ROM code */ + .mask_flags = MTD_WRITEABLE /* force read-only */ + }, + { + .name = "(OneNAND)U-Boot", + .offset = MTDPART_OFS_APPEND, + .size = 2*(64*2048), + .mask_flags = MTD_WRITEABLE /* force read-only */ + }, + { + .name = "(OneNAND)U-Boot Environment", + .offset = MTDPART_OFS_APPEND, + .size = 1*(64*2048), + }, + { + .name = "(OneNAND)Kernel", + .offset = MTDPART_OFS_APPEND, + .size = 4*(64*2048), + }, + { + .name = "(OneNAND)File System", + .offset = MTDPART_OFS_APPEND, + .size = MTDPART_SIZ_FULL, + }, +}; + +static struct omap_onenand_platform_data sdp_onenand_data = { + .parts = onenand_partitions, + .nr_parts = ARRAY_SIZE(onenand_partitions), + .dma_channel = -1, /* disable DMA in OMAP OneNAND driver */ +}; + +static struct platform_device sdp_onenand_device = { + .name = "omap2-onenand", + .id = -1, + .dev = { + .platform_data = &sdp_onenand_data, + }, +}; + +void __init sdp2430_flash_init(void) +{ + void __iomem *gpmc_base_add, *gpmc_cs_base_add; + unsigned char cs = 0; + + gpmc_base_add = (__force void __iomem *)OMAP243X_GPMC_VIRT; + while (cs < GPMC_CS_NUM) { + int ret = 0; + + /* Each GPMC set for a single CS is at offset 0x30 */ + gpmc_cs_base_add = (gpmc_base_add + GPMC_OFF_CONFIG1_0 + + (cs*0x30)); + + /* xloader/Uboot would have programmed the NAND/oneNAND + * base address for us This is a ugly hack. The proper + * way of doing this is to pass the setup of u-boot up + * to kernel using kernel params - something on the + * lines of machineID. Check if Nand/oneNAND is + * configured */ + ret = __raw_readl(gpmc_cs_base_add + GPMC_CS_CONFIG1); + if ((ret & 0xC00) == (0x800)) { + /* Found it!! */ + printk(KERN_INFO "NAND: Found NAND on CS %d \n", cs); + flash_type = NAND; + break; + } + ret = __raw_readl(gpmc_cs_base_add + GPMC_CS_CONFIG7); + if ((ret & 0x3F) == (ONENAND_MAP >> 24)) { + /* Found it!! */ + flash_type = ONENAND; + break; + } + cs++; + } + if (cs >= GPMC_CS_NUM) { + printk(KERN_INFO "MTD: Unable to find MTD configuration in " + "GPMC - not registering.\n"); + return; + } + + if (flash_type == NAND) { + sdp_nand_data.cs = cs; + sdp_nand_data.gpmc_cs_baseaddr = gpmc_cs_base_add; + sdp_nand_data.gpmc_baseaddr = gpmc_base_add; + + if (platform_device_register(&sdp_nand_device) < 0) { + printk(KERN_ERR "Unable to register NAND device\n"); + return; + } + } + + if (flash_type == ONENAND) { + sdp_onenand_data.cs = cs; + + if (platform_device_register(&sdp_onenand_device) < 0) { + printk(KERN_ERR "Unable to register OneNAND device\n"); + return; + } + } +} diff --cc arch/arm/mach-omap2/board-2430sdp.c index b2f8b9ccd75,d4d6385cad7..cb38fc236b5 --- a/arch/arm/mach-omap2/board-2430sdp.c +++ b/arch/arm/mach-omap2/board-2430sdp.c @@@ -19,28 -19,20 +19,28 @@@ #include #include #include +#include #include #include +#include +#include +#include - #include + #include #include #include #include #include - #include - #include - #include - #include - #include - #include - #include - #include - #include + #include + #include + #include ++#include ++#include + #include ++#include + #include ++#include #include diff --cc arch/arm/mach-omap2/board-3430sdp-flash.c index 1fddcd67b57,00000000000..945a2ae520b mode 100644,000000..100644 --- a/arch/arm/mach-omap2/board-3430sdp-flash.c +++ b/arch/arm/mach-omap2/board-3430sdp-flash.c @@@ -1,190 -1,0 +1,190 @@@ +/* + * linux/arch/arm/mach-omap2/board-3430sdp-flash.c + * + * Copyright (c) 2007 Texas Instruments + * + * Modified from mach-omap2/board-2430sdp-flash.c + * Author: Rohit Choraria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include - #include - #include - #include ++#include ++#include ++#include + +static struct mtd_partition sdp_nor_partitions[] = { + /* bootloader (U-Boot, etc) in first sector */ + { + .name = "Bootloader-NOR", + .offset = 0, + .size = SZ_256K, + .mask_flags = MTD_WRITEABLE, /* force read-only */ + }, + /* bootloader params in the next sector */ + { + .name = "Params-NOR", + .offset = MTDPART_OFS_APPEND, + .size = SZ_256K, + .mask_flags = 0, + }, + /* kernel */ + { + .name = "Kernel-NOR", + .offset = MTDPART_OFS_APPEND, + .size = SZ_2M, + .mask_flags = 0 + }, + /* file system */ + { + .name = "Filesystem-NOR", + .offset = MTDPART_OFS_APPEND, + .size = MTDPART_SIZ_FULL, + .mask_flags = 0 + } +}; + +static struct flash_platform_data sdp_nor_data = { + .map_name = "cfi_probe", + .width = 2, + .parts = sdp_nor_partitions, + .nr_parts = ARRAY_SIZE(sdp_nor_partitions), +}; + +static struct resource sdp_nor_resource = { + .start = 0, + .end = 0, + .flags = IORESOURCE_MEM, +}; + +static struct platform_device sdp_nor_device = { + .name = "omapflash", + .id = 0, + .dev = { + .platform_data = &sdp_nor_data, + }, + .num_resources = 1, + .resource = &sdp_nor_resource, +}; + +static int sdp_onenand_setup(void __iomem *, int freq); + +static struct mtd_partition sdp_onenand_partitions[] = { + { + .name = "X-Loader-OneNAND", + .offset = 0, + .size = 4 * (64 * 2048), + .mask_flags = MTD_WRITEABLE /* force read-only */ + }, + { + .name = "U-Boot-OneNAND", + .offset = MTDPART_OFS_APPEND, + .size = 2 * (64 * 2048), + .mask_flags = MTD_WRITEABLE /* force read-only */ + }, + { + .name = "U-Boot Environment-OneNAND", + .offset = MTDPART_OFS_APPEND, + .size = 1 * (64 * 2048), + }, + { + .name = "Kernel-OneNAND", + .offset = MTDPART_OFS_APPEND, + .size = 16 * (64 * 2048), + }, + { + .name = "File System-OneNAND", + .offset = MTDPART_OFS_APPEND, + .size = MTDPART_SIZ_FULL, + }, +}; + +static struct omap_onenand_platform_data sdp_onenand_data = { + .parts = sdp_onenand_partitions, + .nr_parts = ARRAY_SIZE(sdp_onenand_partitions), + .onenand_setup = sdp_onenand_setup, + .dma_channel = -1, /* disable DMA in OMAP OneNAND driver */ +}; + +static struct platform_device sdp_onenand_device = { + .name = "omap2-onenand", + .id = -1, + .dev = { + .platform_data = &sdp_onenand_data, + }, +}; + +/* + * sdp_onenand_setup - The function configures the onenand flash. + * @onenand_base: Onenand base address + * + * @return int: Currently always returning zero. + */ +static int sdp_onenand_setup(void __iomem *onenand_base, int freq) +{ + /* Onenand setup does nothing at present */ + return 0; +} +/** + * sdp3430_flash_init - Identify devices connected to GPMC and register. + * + * @return - void. + */ +void __init sdp3430_flash_init(void) +{ + u8 cs = 0; + u8 onenandcs = GPMC_CS_NUM + 1; + + /* Configure start address and size of NOR device */ + if (is_sil_rev_greater_than(OMAP3430_REV_ES1_0)) { + sdp_nor_resource.start = FLASH_BASE_SDPV2; + sdp_nor_resource.end = FLASH_BASE_SDPV2 + + FLASH_SIZE_SDPV2 - 1; + } else { + sdp_nor_resource.start = FLASH_BASE_SDPV1; + sdp_nor_resource.end = FLASH_BASE_SDPV1 + + FLASH_SIZE_SDPV1 - 1; + } + + if (platform_device_register(&sdp_nor_device) < 0) + printk(KERN_ERR "Unable to register NOR device\n"); + + while (cs < GPMC_CS_NUM) { + u32 ret = 0; + ret = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG7); + + /* + * xloader/Uboot would have programmed the oneNAND + * base address for us This is a ugly hack. The proper + * way of doing this is to pass the setup of u-boot up + * to kernel using kernel params - something on the + * lines of machineID. Check if oneNAND is configured + */ + if ((ret & 0x3F) == (ONENAND_MAP >> 24)) + onenandcs = cs; + cs++; + } + if (onenandcs > GPMC_CS_NUM) { + printk(KERN_INFO "OneNAND: Unable to find configuration " + " in GPMC\n "); + return; + } + + if (onenandcs < GPMC_CS_NUM) { + sdp_onenand_data.cs = onenandcs; + if (platform_device_register(&sdp_onenand_device) < 0) + printk(KERN_ERR "Unable to register OneNAND device\n"); + } +} diff --cc arch/arm/mach-omap2/board-3430sdp.c index ee4ec18f375,00000000000..637f1c8e04b mode 100644,000000..100644 --- a/arch/arm/mach-omap2/board-3430sdp.c +++ b/arch/arm/mach-omap2/board-3430sdp.c @@@ -1,391 -1,0 +1,391 @@@ +/* + * linux/arch/arm/mach-omap2/board-3430sdp.c + * + * Copyright (C) 2007 Texas Instruments + * + * Modified from mach-omap2/board-generic.c + * + * Initial code: Syed Mohammed Khasim + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + - #include ++#include +#include +#include +#include + - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include +#include + +#include +#include - #include ++#include + +#include "sdram-qimonda-hyb18m512160af-6.h" + +#define SDP3430_SMC91X_CS 3 + +#define ENABLE_VAUX3_DEDICATED 0x03 +#define ENABLE_VAUX3_DEV_GRP 0x20 + + +#define TWL4030_MSECURE_GPIO 22 + +static struct resource sdp3430_smc91x_resources[] = { + [0] = { + .start = OMAP34XX_ETHR_START, + .end = OMAP34XX_ETHR_START + SZ_4K, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = 0, + .end = 0, + .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_LOWLEVEL, + }, +}; + +static struct platform_device sdp3430_smc91x_device = { + .name = "smc91x", + .id = -1, + .num_resources = ARRAY_SIZE(sdp3430_smc91x_resources), + .resource = sdp3430_smc91x_resources, +}; + +static int sdp3430_keymap[] = { + KEY(0, 0, KEY_LEFT), + KEY(0, 1, KEY_RIGHT), + KEY(0, 2, KEY_A), + KEY(0, 3, KEY_B), + KEY(0, 4, KEY_C), + KEY(1, 0, KEY_DOWN), + KEY(1, 1, KEY_UP), + KEY(1, 2, KEY_E), + KEY(1, 3, KEY_F), + KEY(1, 4, KEY_G), + KEY(2, 0, KEY_ENTER), + KEY(2, 1, KEY_I), + KEY(2, 2, KEY_J), + KEY(2, 3, KEY_K), + KEY(2, 4, KEY_3), + KEY(3, 0, KEY_M), + KEY(3, 1, KEY_N), + KEY(3, 2, KEY_O), + KEY(3, 3, KEY_P), + KEY(3, 4, KEY_Q), + KEY(4, 0, KEY_R), + KEY(4, 1, KEY_4), + KEY(4, 2, KEY_T), + KEY(4, 3, KEY_U), + KEY(4, 4, KEY_D), + KEY(5, 0, KEY_V), + KEY(5, 1, KEY_W), + KEY(5, 2, KEY_L), + KEY(5, 3, KEY_S), + KEY(5, 4, KEY_H), + 0 +}; + +static struct omap_kp_platform_data sdp3430_kp_data = { + .rows = 5, + .cols = 6, + .keymap = sdp3430_keymap, + .keymapsize = ARRAY_SIZE(sdp3430_keymap), + .rep = 1, +}; + +static struct platform_device sdp3430_kp_device = { + .name = "omap_twl4030keypad", + .id = -1, + .dev = { + .platform_data = &sdp3430_kp_data, + }, +}; + +static int ts_gpio; + +#ifdef CONFIG_RTC_DRV_TWL4030 +static int twl4030_rtc_init(void) +{ + int ret = 0; + + /* 3430ES2.0 doesn't have msecure/gpio-22 line connected to T2 */ + if (is_device_type_gp() && is_sil_rev_less_than(OMAP3430_REV_ES2_0)) { + u32 msecure_pad_config_reg = omap_ctrl_base_get() + 0xA3C; + int mux_mask = 0x04; + u16 tmp; + + ret = omap_request_gpio(TWL4030_MSECURE_GPIO); + if (ret < 0) { + printk(KERN_ERR "twl4030_rtc_init: can't" + "reserve GPIO:%d !\n", TWL4030_MSECURE_GPIO); + goto out; + } + /* + * TWL4030 will be in secure mode if msecure line from OMAP + * is low. Make msecure line high in order to change the + * TWL4030 RTC time and calender registers. + */ + omap_set_gpio_direction(TWL4030_MSECURE_GPIO, 0); + + tmp = omap_readw(msecure_pad_config_reg); + tmp &= 0xF8; /* To enable mux mode 03/04 = GPIO_RTC */ + tmp |= mux_mask;/* To enable mux mode 03/04 = GPIO_RTC */ + omap_writew(tmp, msecure_pad_config_reg); + + omap_set_gpio_dataout(TWL4030_MSECURE_GPIO, 1); + } +out: + return ret; +} + +static void twl4030_rtc_exit(void) +{ + if (is_device_type_gp() && + is_sil_rev_less_than(OMAP3430_REV_ES2_0)) { + omap_free_gpio(TWL4030_MSECURE_GPIO); + } +} + +static struct twl4030rtc_platform_data sdp3430_twl4030rtc_data = { + .init = &twl4030_rtc_init, + .exit = &twl4030_rtc_exit, +}; + +static struct platform_device sdp3430_twl4030rtc_device = { + .name = "twl4030_rtc", + .id = -1, + .dev = { + .platform_data = &sdp3430_twl4030rtc_data, + }, +}; +#endif + +/** + * @brief ads7846_dev_init : Requests & sets GPIO line for pen-irq + * + * @return - void. If request gpio fails then Flag KERN_ERR. + */ +static void ads7846_dev_init(void) +{ + if (omap_request_gpio(ts_gpio) < 0) { + printk(KERN_ERR "can't get ads746 pen down GPIO\n"); + return; + } + + omap_set_gpio_direction(ts_gpio, 1); + + omap_set_gpio_debounce(ts_gpio, 1); + omap_set_gpio_debounce_time(ts_gpio, 0xa); +} + +static int ads7846_get_pendown_state(void) +{ + return !omap_get_gpio_datain(ts_gpio); +} + +/* + * This enable(1)/disable(0) the voltage for TS: uses twl4030 calls + */ +static int ads7846_vaux_control(int vaux_cntrl) +{ + int ret = 0; + +#ifdef CONFIG_TWL4030_CORE + /* check for return value of ldo_use: if success it returns 0 */ + if (vaux_cntrl == VAUX_ENABLE) { + if (ret != twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, + ENABLE_VAUX3_DEDICATED, TWL4030_VAUX3_DEDICATED)) + return -EIO; + if (ret != twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, + ENABLE_VAUX3_DEV_GRP, TWL4030_VAUX3_DEV_GRP)) + return -EIO; + } else if (vaux_cntrl == VAUX_DISABLE) { + if (ret != twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, + 0x00, TWL4030_VAUX3_DEDICATED)) + return -EIO; + if (ret != twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, + 0x00, TWL4030_VAUX3_DEV_GRP)) + return -EIO; + } +#else + ret = -EIO; +#endif + return ret; +} + +static struct ads7846_platform_data tsc2046_config __initdata = { + .get_pendown_state = ads7846_get_pendown_state, + .keep_vref_on = 1, + .vaux_control = ads7846_vaux_control, +}; + + +static struct omap2_mcspi_device_config tsc2046_mcspi_config = { + .turbo_mode = 0, + .single_channel = 1, /* 0: slave, 1: master */ +}; + +static struct spi_board_info sdp3430_spi_board_info[] __initdata = { + [0] = { + /* + * TSC2046 operates at a max freqency of 2MHz, so + * operate slightly below at 1.5MHz + */ + .modalias = "ads7846", + .bus_num = 1, + .chip_select = 0, + .max_speed_hz = 1500000, + .controller_data = &tsc2046_mcspi_config, + .irq = 0, + .platform_data = &tsc2046_config, + }, +}; + +static struct platform_device sdp3430_lcd_device = { + .name = "sdp2430_lcd", + .id = -1, +}; + +static struct platform_device *sdp3430_devices[] __initdata = { + &sdp3430_smc91x_device, + &sdp3430_kp_device, + &sdp3430_lcd_device, +#ifdef CONFIG_RTC_DRV_TWL4030 + &sdp3430_twl4030rtc_device, +#endif +}; + +static inline void __init sdp3430_init_smc91x(void) +{ + int eth_cs; + unsigned long cs_mem_base; + int eth_gpio = 0; + + eth_cs = SDP3430_SMC91X_CS; + + if (gpmc_cs_request(eth_cs, SZ_16M, &cs_mem_base) < 0) { + printk(KERN_ERR "Failed to request GPMC mem for smc91x\n"); + return; + } + + sdp3430_smc91x_resources[0].start = cs_mem_base + 0x0; + sdp3430_smc91x_resources[0].end = cs_mem_base + 0xf; + udelay(100); + + if (is_sil_rev_greater_than(OMAP3430_REV_ES1_0)) + eth_gpio = OMAP34XX_ETHR_GPIO_IRQ_SDPV2; + else + eth_gpio = OMAP34XX_ETHR_GPIO_IRQ_SDPV1; + + sdp3430_smc91x_resources[1].start = OMAP_GPIO_IRQ(eth_gpio); + + if (omap_request_gpio(eth_gpio) < 0) { + printk(KERN_ERR "Failed to request GPIO%d for smc91x IRQ\n", + eth_gpio); + return; + } + omap_set_gpio_direction(eth_gpio, 1); +} + +static void __init omap_3430sdp_init_irq(void) +{ + omap2_init_common_hw(hyb18m512160af6_sdrc_params); + omap_init_irq(); + omap_gpio_init(); + sdp3430_init_smc91x(); +} + +static struct omap_uart_config sdp3430_uart_config __initdata = { + .enabled_uarts = ((1 << 0) | (1 << 1) | (1 << 2)), +}; + +static struct omap_lcd_config sdp3430_lcd_config __initdata = { + .ctrl_name = "internal", +}; + +static struct omap_mmc_config sdp3430_mmc_config __initdata = { + .mmc [0] = { + .enabled = 1, + .wire4 = 1, + }, +}; + +static struct omap_board_config_kernel sdp3430_config[] __initdata = { + { OMAP_TAG_UART, &sdp3430_uart_config }, + {OMAP_TAG_LCD, &sdp3430_lcd_config}, + {OMAP_TAG_MMC, &sdp3430_mmc_config }, +}; + +static int __init omap3430_i2c_init(void) +{ + omap_register_i2c_bus(1, 2600, NULL, 0); + omap_register_i2c_bus(2, 400, NULL, 0); + omap_register_i2c_bus(3, 400, NULL, 0); + return 0; +} + +extern void __init sdp3430_flash_init(void); + +static void __init omap_3430sdp_init(void) +{ + platform_add_devices(sdp3430_devices, ARRAY_SIZE(sdp3430_devices)); + omap_board_config = sdp3430_config; + omap_board_config_size = ARRAY_SIZE(sdp3430_config); + if (is_sil_rev_greater_than(OMAP3430_REV_ES1_0)) + ts_gpio = OMAP34XX_TS_GPIO_IRQ_SDPV2; + else + ts_gpio = OMAP34XX_TS_GPIO_IRQ_SDPV1; + sdp3430_spi_board_info[0].irq = OMAP_GPIO_IRQ(ts_gpio); + spi_register_board_info(sdp3430_spi_board_info, + ARRAY_SIZE(sdp3430_spi_board_info)); + ads7846_dev_init(); + sdp3430_flash_init(); + twl4030_bci_battery_init(); + omap_serial_init(); + usb_musb_init(); + usb_ehci_init(); + hsmmc_init(); +} + +static void __init omap_3430sdp_map_io(void) +{ + omap2_set_globals_343x(); + omap2_map_common_io(); +} +arch_initcall(omap3430_i2c_init); + +MACHINE_START(OMAP_3430SDP, "OMAP3430 3430SDP board") + /* Maintainer: Syed Khasim - Texas Instruments Inc */ + .phys_io = 0x48000000, + .io_pg_offst = ((0xd8000000) >> 18) & 0xfffc, + .boot_params = 0x80000100, + .map_io = omap_3430sdp_map_io, + .init_irq = omap_3430sdp_init_irq, + .init_machine = omap_3430sdp_init, + .timer = &omap_timer, +MACHINE_END diff --cc arch/arm/mach-omap2/board-apollon-keys.c index adc4ee649fd,00000000000..e3f74e62a78 mode 100644,000000..100644 --- a/arch/arm/mach-omap2/board-apollon-keys.c +++ b/arch/arm/mach-omap2/board-apollon-keys.c @@@ -1,101 -1,0 +1,101 @@@ +/* + * linux/arch/arm/mach-omap2/board-apollon-keys.c + * + * Copyright (C) 2007 Samsung Electronics + * Author: Kyungmin Park + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + - #include - #include ++#include ++#include + +#define SW_ENTER_GPIO16 16 +#define SW_UP_GPIO17 17 +#define SW_DOWN_GPIO58 58 +#define SW_LEFT_GPIO95 95 +#define SW_RIGHT_GPIO96 96 +#define SW_ESC_GPIO97 97 + +static struct gpio_keys_button apollon_gpio_keys_buttons[] = { + [0] = { + .code = KEY_ENTER, + .gpio = SW_ENTER_GPIO16, + .desc = "enter sw", + }, + [1] = { + .code = KEY_UP, + .gpio = SW_UP_GPIO17, + .desc = "up sw", + }, + [2] = { + .code = KEY_DOWN, + .gpio = SW_DOWN_GPIO58, + .desc = "down sw", + }, + [3] = { + .code = KEY_LEFT, + .gpio = SW_LEFT_GPIO95, + .desc = "left sw", + }, + [4] = { + .code = KEY_RIGHT, + .gpio = SW_RIGHT_GPIO96, + .desc = "right sw", + }, + [5] = { + .code = KEY_ESC, + .gpio = SW_ESC_GPIO97, + .desc = "esc sw", + }, +}; + +static struct gpio_keys_platform_data apollon_gpio_keys = { + .buttons = apollon_gpio_keys_buttons, + .nbuttons = ARRAY_SIZE(apollon_gpio_keys_buttons), +}; + +static struct platform_device apollon_gpio_keys_device = { + .name = "gpio-keys", + .id = -1, + .dev = { + .platform_data = &apollon_gpio_keys, + }, +}; + +static void __init apollon_sw_init(void) +{ + /* Enter SW - Y11 */ + omap_cfg_reg(Y11_242X_GPIO16); + /* Up SW - AA12 */ + omap_cfg_reg(AA12_242X_GPIO17); + /* Down SW - AA8 */ + omap_cfg_reg(AA8_242X_GPIO58); + + if (apollon_plus()) { + /* Left SW - P18 */ + omap_cfg_reg(P18_24XX_GPIO95); + /* Right SW - M18 */ + omap_cfg_reg(M18_24XX_GPIO96); + /* Esc SW - L14 */ + omap_cfg_reg(L14_24XX_GPIO97); + } else + apollon_gpio_keys.nbuttons = 3; +} + +static int __init omap_apollon_keys_init(void) +{ + apollon_sw_init(); + + return platform_device_register(&apollon_gpio_keys_device); +} + +arch_initcall(omap_apollon_keys_init); diff --cc arch/arm/mach-omap2/board-apollon-mmc.c index f77167e1af0,00000000000..71fccd9b3f3 mode 100644,000000..100644 --- a/arch/arm/mach-omap2/board-apollon-mmc.c +++ b/arch/arm/mach-omap2/board-apollon-mmc.c @@@ -1,88 -1,0 +1,88 @@@ +/* + * linux/arch/arm/mach-omap2/board-apollon-mmc.c + * + * Copyright (C) 2005-2007 Samsung Electronics + * Author: Kyungmin Park + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include + - #include - #include ++#include ++#include + +#ifdef CONFIG_MMC_OMAP + +static struct device *mmc_device; + +static int apollon_mmc_set_power(struct device *dev, int slot, int power_on, + int vdd) +{ +#ifdef CONFIG_MMC_DEBUG + dev_dbg(dev, "Set slot %d power: %s (vdd %d)\n", slot + 1, + power_on ? "on" : "off", vdd); +#endif + if (slot != 0) { + dev_err(dev, "No such slot %d\n", slot + 1); + return -ENODEV; + } + + return 0; +} + +static int apollon_mmc_set_bus_mode(struct device *dev, int slot, int bus_mode) +{ +#ifdef CONFIG_MMC_DEBUG + dev_dbg(dev, "Set slot %d bus_mode %s\n", slot + 1, + bus_mode == MMC_BUSMODE_OPENDRAIN ? "open-drain" : "push-pull"); +#endif + if (slot != 0) { + dev_err(dev, "No such slot %d\n", slot + 1); + return -ENODEV; + } + + return 0; +} + +static int apollon_mmc_late_init(struct device *dev) +{ + mmc_device = dev; + + return 0; +} + +static void apollon_mmc_cleanup(struct device *dev) +{ +} + +static struct omap_mmc_platform_data apollon_mmc_data = { + .nr_slots = 1, + .switch_slot = NULL, + .init = apollon_mmc_late_init, + .cleanup = apollon_mmc_cleanup, + .slots[0] = { + .set_power = apollon_mmc_set_power, + .set_bus_mode = apollon_mmc_set_bus_mode, + .get_ro = NULL, + .get_cover_state = NULL, + .ocr_mask = MMC_VDD_30_31 | MMC_VDD_31_32 | + MMC_VDD_32_33 | MMC_VDD_33_34, + .name = "mmcblk", + }, +}; + +void __init apollon_mmc_init(void) +{ + omap_set_mmc_info(1, &apollon_mmc_data); +} + +#else /* !CONFIG_MMC_OMAP */ + +void __init apollon_mmc_init(void) +{ +} + +#endif /* CONFIG_MMC_OMAP */ diff --cc arch/arm/mach-omap2/board-apollon.c index 41542a034a2,989ad152d7f..e76328062c0 --- a/arch/arm/mach-omap2/board-apollon.c +++ b/arch/arm/mach-omap2/board-apollon.c @@@ -28,22 -28,20 +28,22 @@@ #include #include #include +#include +#include +#include - #include + #include #include #include #include - #include - #include - #include - #include - #include - #include - #include + #include + #include + #include + #include + #include + #include + #include -#include /* LED & Switch macros */ #define LED0_GPIO13 13 diff --cc arch/arm/mach-omap2/board-h4-mmc.c index 84b3f4e9e7f,00000000000..0916a3ca5df mode 100644,000000..100644 --- a/arch/arm/mach-omap2/board-h4-mmc.c +++ b/arch/arm/mach-omap2/board-h4-mmc.c @@@ -1,266 -1,0 +1,266 @@@ +/* + * linux/arch/arm/mach-omap2/board-h4-mmc.c + * + * Copyright (C) 2007 Instituto Nokia de Tecnologia - INdT + * Authors: David Cohen + * Carlos Eduardo Aguiar + * + * This code is based on linux/arch/arm/mach-omap2/board-n800-mmc.c, which is: + * Copyright (C) 2006 Nokia Corporation + * Author: Juha Yrjola + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + - #include ++#include + +#include +#include +#include + +#ifdef CONFIG_MMC_OMAP + +/* Bit mask for slots detection interrupts */ +#define SD1_CD_ST (1 << 0) +#define SD2_CD_ST (1 << 1) + +static int slot1_cover_open; +static int slot2_cover_open; +static struct device *mmc_device; + +/* + * VMMC --> slot 1 + * VDCDC3_APE, VMCS2_APE --> slot 2 + */ + +static int h4_mmc_switch_slot(struct device *dev, int slot) +{ + int r = 0; + +#ifdef CONFIG_MMC_DEBUG + dev_dbg(dev, "Choose slot %d\n", slot + 1); +#endif + if (slot == 0) { + r = menelaus_enable_slot(2, 0); + r |= menelaus_enable_slot(1, 1); + } else { + r = menelaus_enable_slot(1, 0); + r |= menelaus_enable_slot(2, 1); + } + + return r ? -ENODEV : 0; +} + +static int h4_mmc_set_power(struct device *dev, int slot, int power_on, + int vdd) +{ + int mV = 0; + +#ifdef CONFIG_MMC_DEBUG + dev_dbg(dev, "Set slot %d power: %s (vdd %d)\n", slot + 1, + power_on ? "on" : "off", vdd); +#endif + if (slot == 0) { + if (!power_on) + return menelaus_set_vmmc(3000); + switch (1 << vdd) { + case MMC_VDD_33_34: + case MMC_VDD_32_33: + case MMC_VDD_31_32: + mV = 3100; + break; + case MMC_VDD_30_31: + mV = 3000; + break; + case MMC_VDD_28_29: + mV = 2800; + break; + case MMC_VDD_165_195: + mV = 1850; + break; + default: + BUG(); + } + return menelaus_set_vmmc(mV); + } else { + if (!power_on) + return menelaus_set_vdcdc(3, 3000); + switch (1 << vdd) { + case MMC_VDD_33_34: + case MMC_VDD_32_33: + mV = 3300; + break; + case MMC_VDD_30_31: + case MMC_VDD_29_30: + mV = 3000; + break; + case MMC_VDD_28_29: + case MMC_VDD_27_28: + mV = 2800; + break; + case MMC_VDD_24_25: + case MMC_VDD_23_24: + mV = 2400; + break; + case MMC_VDD_22_23: + case MMC_VDD_21_22: + mV = 2200; + break; + case MMC_VDD_20_21: + mV = 2000; + break; + case MMC_VDD_165_195: + mV = 1800; + break; + default: + BUG(); + } + return menelaus_set_vdcdc(3, mV); + } + return 0; +} + +static int h4_mmc_set_bus_mode(struct device *dev, int slot, int bus_mode) +{ + int r = 0; + +#ifdef CONFIG_MMC_DEBUG + dev_dbg(dev, "Set slot %d bus mode %s\n", slot + 1, + bus_mode == MMC_BUSMODE_OPENDRAIN ? "open-drain" : "push-pull"); +#endif + BUG_ON(slot != 0 && slot != 1); + slot++; + switch (bus_mode) { + case MMC_BUSMODE_OPENDRAIN: + r = menelaus_set_mmc_opendrain(slot, 1); + break; + case MMC_BUSMODE_PUSHPULL: + r = menelaus_set_mmc_opendrain(slot, 0); + break; + default: + BUG(); + } + if (r != 0 && printk_ratelimit()) { + dev_err(dev, "MMC: unable to set bus mode for slot %d\n", + slot); + } + return r; +} + +static int h4_mmc_slot1_cover_state(struct device *dev, int slot) +{ + BUG_ON(slot != 0); + return slot1_cover_open; +} + +static int h4_mmc_slot2_cover_state(struct device *dev, int slot) +{ + BUG_ON(slot != 1); + return slot2_cover_open; +} + +static void h4_mmc_slot_callback(void *data, u8 card_mask) +{ + int cover_open; + + cover_open = (card_mask & SD1_CD_ST) ? 0 : 1; + if (cover_open != slot1_cover_open) { + slot1_cover_open = cover_open; + omap_mmc_notify_cover_event(mmc_device, 0, slot1_cover_open); + } + + cover_open = (card_mask & SD2_CD_ST) ? 0 : 1; + if (cover_open != slot2_cover_open) { + slot2_cover_open = cover_open; + omap_mmc_notify_cover_event(mmc_device, 1, slot2_cover_open); + } +} + +static int h4_mmc_late_init(struct device *dev) +{ + int r; + + mmc_device = dev; + + r = menelaus_set_mmc_slot(1, 0, 0, 1); + if (r < 0) + goto out; + r = menelaus_set_mmc_slot(2, 0, 0, 1); + if (r < 0) + goto out; + + r = menelaus_get_slot_pin_states(); + if (r < 0) + goto out; + + if (r & SD1_CD_ST) + slot1_cover_open = 1; + else + slot1_cover_open = 0; + + /* Slot pin bits seem to be inversed until first swith change, + * but just for slot 2 + */ + if ((r == 0xf) || (r == (0xf & ~SD2_CD_ST))) + r = ~r; + + if (r & SD2_CD_ST) + slot2_cover_open = 1; + else + slot2_cover_open = 0; + + r = menelaus_register_mmc_callback(h4_mmc_slot_callback, NULL); + +out: + return r; +} + +static void h4_mmc_cleanup(struct device *dev) +{ + menelaus_unregister_mmc_callback(); +} + +static struct omap_mmc_platform_data h4_mmc_data = { + .nr_slots = 2, + .switch_slot = h4_mmc_switch_slot, + .init = h4_mmc_late_init, + .cleanup = h4_mmc_cleanup, + .slots[0] = { + .set_power = h4_mmc_set_power, + .set_bus_mode = h4_mmc_set_bus_mode, + .get_ro = NULL, + .get_cover_state= h4_mmc_slot1_cover_state, + .ocr_mask = MMC_VDD_165_195 | + MMC_VDD_28_29 | MMC_VDD_30_31 | + MMC_VDD_32_33 | MMC_VDD_33_34, + .name = "slot1", + }, + .slots[1] = { + .set_power = h4_mmc_set_power, + .set_bus_mode = h4_mmc_set_bus_mode, + .get_ro = NULL, + .get_cover_state= h4_mmc_slot2_cover_state, + .ocr_mask = MMC_VDD_165_195 | MMC_VDD_20_21 | + MMC_VDD_21_22 | MMC_VDD_22_23 | MMC_VDD_23_24 | + MMC_VDD_24_25 | MMC_VDD_27_28 | MMC_VDD_28_29 | + MMC_VDD_29_30 | MMC_VDD_30_31 | MMC_VDD_32_33 | + MMC_VDD_33_34, + .name = "slot2", + }, +}; + +void __init h4_mmc_init(void) +{ + omap_set_mmc_info(1, &h4_mmc_data); +} + +#else + +void __init h4_mmc_init(void) +{ +} + +#endif + diff --cc arch/arm/mach-omap2/board-h4.c index 5d3088cddf1,9e2624ca70a..54ecab9ed5a --- a/arch/arm/mach-omap2/board-h4.c +++ b/arch/arm/mach-omap2/board-h4.c @@@ -22,30 -21,25 +22,30 @@@ #include #include #include +#include +#include +#include +#include + +#include - #include + #include #include #include #include #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include + #include + #include + #include + #include + #include + #include + #include + #include + #include -#include + #include + #include #include diff --cc arch/arm/mach-omap2/board-ldp.c index 1998d62c46c,00000000000..7f672c6b0b5 mode 100644,000000..100644 --- a/arch/arm/mach-omap2/board-ldp.c +++ b/arch/arm/mach-omap2/board-ldp.c @@@ -1,253 -1,0 +1,253 @@@ +/* + * linux/arch/arm/mach-omap2/board-ldp.c + * + * Copyright (C) 2008 Texas Instruments Inc. + * Nishant Kamat + * + * Modified from mach-omap2/board-3430sdp.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + - #include ++#include +#include +#include +#include + - #include - #include - #include - #include - #include - #include - #include ++#include ++#include ++#include ++#include ++#include ++#include ++#include + +#include +#include - #include ++#include + +#define ENABLE_VAUX1_DEDICATED 0x03 +#define ENABLE_VAUX1_DEV_GRP 0x20 + +#define TWL4030_MSECURE_GPIO 22 + +static int ts_gpio; + +#ifdef CONFIG_RTC_DRV_TWL4030 +static int twl4030_rtc_init(void) +{ + int ret = 0; + + /* 3430ES2.0 doesn't have msecure/gpio-22 line connected to T2 */ + if (is_device_type_gp() && is_sil_rev_less_than(OMAP3430_REV_ES2_0)) { + u32 msecure_pad_config_reg = omap_ctrl_base_get() + 0xA3C; + int mux_mask = 0x04; + u16 tmp; + + ret = omap_request_gpio(TWL4030_MSECURE_GPIO); + if (ret < 0) { + printk(KERN_ERR "twl4030_rtc_init: can't" + "reserve GPIO:%d !\n", TWL4030_MSECURE_GPIO); + goto out; + } + /* + * TWL4030 will be in secure mode if msecure line from OMAP + * is low. Make msecure line high in order to change the + * TWL4030 RTC time and calender registers. + */ + omap_set_gpio_direction(TWL4030_MSECURE_GPIO, 0); + + tmp = omap_readw(msecure_pad_config_reg); + tmp &= 0xF8; /* To enable mux mode 03/04 = GPIO_RTC */ + tmp |= mux_mask;/* To enable mux mode 03/04 = GPIO_RTC */ + omap_writew(tmp, msecure_pad_config_reg); + + omap_set_gpio_dataout(TWL4030_MSECURE_GPIO, 1); + } +out: + return ret; +} + +static void twl4030_rtc_exit(void) +{ + omap_free_gpio(TWL4030_MSECURE_GPIO); +} + +static struct twl4030rtc_platform_data ldp_twl4030rtc_data = { + .init = &twl4030_rtc_init, + .exit = &twl4030_rtc_exit, +}; + +static struct platform_device ldp_twl4030rtc_device = { + .name = "twl4030_rtc", + .id = -1, + .dev = { + .platform_data = &ldp_twl4030rtc_data, + }, +}; +#endif + +/** + * @brief ads7846_dev_init : Requests & sets GPIO line for pen-irq + * + * @return - void. If request gpio fails then Flag KERN_ERR. + */ +static void ads7846_dev_init(void) +{ + if (omap_request_gpio(ts_gpio) < 0) { + printk(KERN_ERR "can't get ads746 pen down GPIO\n"); + return; + } + + omap_set_gpio_direction(ts_gpio, 1); + + omap_set_gpio_debounce(ts_gpio, 1); + omap_set_gpio_debounce_time(ts_gpio, 0xa); +} + +static int ads7846_get_pendown_state(void) +{ + return !omap_get_gpio_datain(ts_gpio); +} + +/* + * This enable(1)/disable(0) the voltage for TS: uses twl4030 calls + */ +static int ads7846_vaux_control(int vaux_cntrl) +{ + int ret = 0; + +#ifdef CONFIG_TWL4030_CORE + /* check for return value of ldo_use: if success it returns 0 */ + if (vaux_cntrl == VAUX_ENABLE) { + if (ret != twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, + ENABLE_VAUX1_DEDICATED, TWL4030_VAUX1_DEDICATED)) + return -EIO; + if (ret != twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, + ENABLE_VAUX1_DEV_GRP, TWL4030_VAUX1_DEV_GRP)) + return -EIO; + } else if (vaux_cntrl == VAUX_DISABLE) { + if (ret != twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, + 0x00, TWL4030_VAUX1_DEDICATED)) + return -EIO; + if (ret != twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, + 0x00, TWL4030_VAUX1_DEV_GRP)) + return -EIO; + } +#else + ret = -EIO; +#endif + return ret; +} + +static struct ads7846_platform_data tsc2046_config __initdata = { + .get_pendown_state = ads7846_get_pendown_state, + .keep_vref_on = 1, + .vaux_control = ads7846_vaux_control, +}; + + +static struct omap2_mcspi_device_config tsc2046_mcspi_config = { + .turbo_mode = 0, + .single_channel = 1, /* 0: slave, 1: master */ +}; + +static struct spi_board_info ldp_spi_board_info[] __initdata = { + [0] = { + /* + * TSC2046 operates at a max freqency of 2MHz, so + * operate slightly below at 1.5MHz + */ + .modalias = "ads7846", + .bus_num = 1, + .chip_select = 0, + .max_speed_hz = 1500000, + .controller_data = &tsc2046_mcspi_config, + .irq = 0, + .platform_data = &tsc2046_config, + }, +}; + +static struct platform_device *ldp_devices[] __initdata = { +#ifdef CONFIG_RTC_DRV_TWL4030 + &ldp_twl4030rtc_device, +#endif +}; + +static void __init omap_ldp_init_irq(void) +{ + omap2_init_common_hw(NULL); + omap_init_irq(); + omap_gpio_init(); +} + +static struct omap_uart_config ldp_uart_config __initdata = { + .enabled_uarts = ((1 << 0) | (1 << 1) | (1 << 2)), +}; + +static struct omap_mmc_config ldp_mmc_config __initdata = { + .mmc [0] = { + .enabled = 1, + .wire4 = 1, + }, +}; + +static struct omap_board_config_kernel ldp_config[] __initdata = { + { OMAP_TAG_UART, &ldp_uart_config }, + { OMAP_TAG_MMC, &ldp_mmc_config }, +}; + +static int __init omap_i2c_init(void) +{ + omap_register_i2c_bus(1, 2600, NULL, 0); + omap_register_i2c_bus(2, 400, NULL, 0); + omap_register_i2c_bus(3, 400, NULL, 0); + return 0; +} + +static void __init omap_ldp_init(void) +{ + platform_add_devices(ldp_devices, ARRAY_SIZE(ldp_devices)); + omap_board_config = ldp_config; + omap_board_config_size = ARRAY_SIZE(ldp_config); + ts_gpio = 54; + ldp_spi_board_info[0].irq = OMAP_GPIO_IRQ(ts_gpio); + spi_register_board_info(ldp_spi_board_info, + ARRAY_SIZE(ldp_spi_board_info)); + ads7846_dev_init(); + omap_serial_init(); + usb_musb_init(); + hsmmc_init(); +} + +static void __init omap_ldp_map_io(void) +{ + omap2_set_globals_343x(); + omap2_map_common_io(); +} +arch_initcall(omap_i2c_init); + +MACHINE_START(OMAP_LDP, "OMAP LDP board") + .phys_io = 0x48000000, + .io_pg_offst = ((0xd8000000) >> 18) & 0xfffc, + .boot_params = 0x80000100, + .map_io = omap_ldp_map_io, + .init_irq = omap_ldp_init_irq, + .init_machine = omap_ldp_init, + .timer = &omap_timer, +MACHINE_END diff --cc arch/arm/mach-omap2/board-n800-audio.c index 233198edbd5,00000000000..ebaf21f689f mode 100644,000000..100644 --- a/arch/arm/mach-omap2/board-n800-audio.c +++ b/arch/arm/mach-omap2/board-n800-audio.c @@@ -1,377 -1,0 +1,377 @@@ +/* + * linux/arch/arm/mach-omap2/board-n800-audio.c + * + * Copyright (C) 2006 Nokia Corporation + * Contact: Juha Yrjola + * Jarkko Nikula + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include + +#include - #include ++#include + - #include ++#include + +#if defined(CONFIG_SPI_TSC2301_AUDIO) && defined(CONFIG_SND_OMAP24XX_EAC) +#define AUDIO_ENABLED + +static struct clk *sys_clkout2; +static struct clk *sys_clkout2_src; +static struct clk *func96m_clk; +static struct device *eac_device; +static struct device *tsc2301_device; + +static int enable_audio; +static int audio_ok; +static spinlock_t audio_lock; + +/* + * Leaving EAC and sys_clkout2 pins multiplexed to those subsystems results + * in about 2 mA extra current leak when audios are powered down. The + * workaround is to multiplex them to protected mode (with pull-ups enabled) + * whenever audio is not being used. + */ +static int eac_mux_disabled = 0; +static int clkout2_mux_disabled = 0; +static u32 saved_mux[2]; + +#define MUX_EAC_IOP2V(x) (__force void __iomem *)io_p2v(x) + +static void n800_enable_eac_mux(void) +{ + if (!eac_mux_disabled) + return; + __raw_writel(saved_mux[1], MUX_EAC_IOP2V(0x48000124)); + eac_mux_disabled = 0; +} + +static void n800_disable_eac_mux(void) +{ + if (eac_mux_disabled) { + WARN_ON(eac_mux_disabled); + return; + } + saved_mux[1] = __raw_readl(MUX_EAC_IOP2V(0x48000124)); + __raw_writel(0x1f1f1f1f, MUX_EAC_IOP2V(0x48000124)); + eac_mux_disabled = 1; +} + +static void n800_enable_clkout2_mux(void) +{ + if (!clkout2_mux_disabled) + return; + __raw_writel(saved_mux[0], MUX_EAC_IOP2V(0x480000e8)); + clkout2_mux_disabled = 0; +} + +static void n800_disable_clkout2_mux(void) +{ + u32 l; + + if (clkout2_mux_disabled) { + WARN_ON(clkout2_mux_disabled); + return; + } + saved_mux[0] = __raw_readl(MUX_EAC_IOP2V(0x480000e8)); + l = saved_mux[0] & ~0xff; + l |= 0x1f; + __raw_writel(l, MUX_EAC_IOP2V(0x480000e8)); + clkout2_mux_disabled = 1; +} + +static int n800_eac_enable_ext_clocks(struct device *dev) +{ + BUG_ON(tsc2301_device == NULL); + n800_enable_eac_mux(); + tsc2301_mixer_enable_mclk(tsc2301_device); + + return 0; +} + +static void n800_eac_disable_ext_clocks(struct device *dev) +{ + BUG_ON(tsc2301_device == NULL); + tsc2301_mixer_disable_mclk(tsc2301_device); + n800_disable_eac_mux(); +} + +static int n800_audio_set_power(void *pdata, int dac, int adc) +{ + BUG_ON(pdata != tsc2301_device); + tsc2301_mixer_set_power(tsc2301_device, dac, adc); + + return 0; +} + +static int n800_audio_register_controls(void *pdata, struct snd_card *card) +{ + BUG_ON(pdata != tsc2301_device); + return tsc2301_mixer_register_controls(tsc2301_device, card); +} + +static struct eac_codec n800_eac_codec = { + .mclk_src = EAC_MCLK_EXT_2x12288000, + .codec_mode = EAC_CODEC_I2S_MASTER, + .codec_conf.i2s.polarity_changed_mode = 0, + .codec_conf.i2s.sync_delay_enable = 0, + .default_rate = 48000, + .set_power = n800_audio_set_power, + .register_controls = n800_audio_register_controls, + .short_name = "TSC2301", +}; + +static int n800_register_codec(void) +{ + int r, do_enable = 0; + unsigned long flags; + + n800_eac_codec.private_data = tsc2301_device; + r = eac_register_codec(eac_device, &n800_eac_codec); + if (r < 0) + return r; + spin_lock_irqsave(&audio_lock, flags); + audio_ok = 1; + if (enable_audio) + do_enable = 1; + spin_unlock_irqrestore(&audio_lock, flags); + if (do_enable) + eac_set_mode(eac_device, 1, 1); + return 0; +} + +static void n800_unregister_codec(void) +{ + audio_ok = 0; + eac_unregister_codec(eac_device); + eac_set_mode(eac_device, 0, 0); +} + +static int n800_eac_init(struct device *dev) +{ + int r; + + BUG_ON(eac_device != NULL); + eac_device = dev; + if (tsc2301_device != NULL) { + r = n800_register_codec(); + if (r < 0) + return r; + } + + return 0; +} + +static void n800_eac_cleanup(struct device *dev) +{ + eac_device = NULL; + if (tsc2301_device != NULL) + n800_unregister_codec(); +} + +static int n800_codec_get_clocks(struct device *dev) +{ + sys_clkout2_src = clk_get(dev, "sys_clkout2_src"); + if (IS_ERR(sys_clkout2_src)) { + dev_err(dev, "Could not get sys_clkout2_src clock\n"); + return -ENODEV; + } + sys_clkout2 = clk_get(dev, "sys_clkout2"); + if (IS_ERR(sys_clkout2)) { + dev_err(dev, "Could not get sys_clkout2 clock\n"); + clk_put(sys_clkout2_src); + return -ENODEV; + } + /* configure 12 MHz output on SYS_CLKOUT2. Therefore we must use + * 96 MHz as its parent in order to get 12 MHz */ + func96m_clk = clk_get(dev, "func_96m_ck"); + if (IS_ERR(func96m_clk)) { + dev_err(dev, "Could not get func 96M clock\n"); + clk_put(sys_clkout2); + clk_put(sys_clkout2_src); + return -ENODEV; + } + + clk_set_parent(sys_clkout2_src, func96m_clk); + clk_set_rate(sys_clkout2, 12000000); + + return 0; +} + +static void n800_codec_put_clocks(struct device *dev) +{ + clk_put(func96m_clk); + clk_put(sys_clkout2); + clk_put(sys_clkout2_src); +} + +static int n800_codec_enable_clock(struct device *dev) +{ + n800_enable_clkout2_mux(); + return clk_enable(sys_clkout2); +} + +static void n800_codec_disable_clock(struct device *dev) +{ + clk_disable(sys_clkout2); + n800_disable_clkout2_mux(); +} + +static int n800_codec_init(struct device *dev) +{ + int r; + + BUG_ON(tsc2301_device != NULL); + tsc2301_device = dev; + if ((r = n800_codec_get_clocks(dev)) < 0) + return r; + if (eac_device != NULL) { + r = n800_register_codec(); + if (r < 0) { + n800_codec_put_clocks(dev); + return r; + } + } + return 0; +} + +static void n800_codec_cleanup(struct device *dev) +{ + tsc2301_device = NULL; + if (eac_device != NULL) + n800_unregister_codec(); + n800_codec_put_clocks(dev); +} + +static struct eac_platform_data n800_eac_data = { + .init = n800_eac_init, + .cleanup = n800_eac_cleanup, + .enable_ext_clocks = n800_eac_enable_ext_clocks, + .disable_ext_clocks = n800_eac_disable_ext_clocks, +}; + +static const struct tsc2301_mixer_gpio n800_mixer_gpios[] = { + { + .name = "Headset Amplifier", + .gpio = 1, + .deactivate_on_pd = 1, + }, { + .name = "Speaker Amplifier", + .gpio = 2, + .def_enable = 1, + .deactivate_on_pd = 1, + }, { + .name = "Headset Mic Select", + .gpio = 3, + } +}; + +static struct platform_device retu_headset_device = { + .name = "retu-headset", + .id = -1, + .dev = { + .release = NULL, + }, +}; + +void __init n800_audio_init(struct tsc2301_platform_data *tc) +{ + spin_lock_init(&audio_lock); + + if (platform_device_register(&retu_headset_device) < 0) + return; + omap_init_eac(&n800_eac_data); + + tc->pll_pdc = 7; + tc->pll_a = 7; + tc->pll_n = 9; + tc->pll_output = 1; + tc->mclk_ratio = TSC2301_MCLK_256xFS; + tc->i2s_sample_rate = TSC2301_I2S_SR_48000; + tc->i2s_format = TSC2301_I2S_FORMAT0; + tc->power_down_blocks = TSC2301_REG_PD_MISC_MOPD; + tc->mixer_gpios = n800_mixer_gpios; + tc->n_mixer_gpios = ARRAY_SIZE(n800_mixer_gpios); + tc->codec_init = n800_codec_init; + tc->codec_cleanup = n800_codec_cleanup; + tc->enable_clock = n800_codec_enable_clock; + tc->disable_clock = n800_codec_disable_clock; +} + +#else + +void __init n800_audio_init(struct tsc2301_platform_data *tc) +{ +} + +#endif + +#ifdef CONFIG_OMAP_DSP + +int n800_audio_enable(struct dsp_kfunc_device *kdev, int stage) +{ +#ifdef AUDIO_ENABLED + unsigned long flags; + int do_enable = 0; + + spin_lock_irqsave(&audio_lock, flags); + + pr_debug("DSP power up request (audio codec %sinitialized)\n", + audio_ok ? "" : "not "); + + if (enable_audio) + goto out; + enable_audio = 1; + if (audio_ok) + do_enable = 1; +out: + spin_unlock_irqrestore(&audio_lock, flags); + if (do_enable) + eac_set_mode(eac_device, 1, 1); +#endif + return 0; +} + +int n800_audio_disable(struct dsp_kfunc_device *kdev, int stage) +{ +#ifdef AUDIO_ENABLED + unsigned long flags; + int do_disable = 0; + + spin_lock_irqsave(&audio_lock, flags); + + pr_debug("DSP power down request (audio codec %sinitialized)\n", + audio_ok ? "" : "not "); + + if (!enable_audio) + goto out; + enable_audio = 0; + if (audio_ok) + do_disable = 1; +out: + spin_unlock_irqrestore(&audio_lock, flags); + if (do_disable) + eac_set_mode(eac_device, 0, 0); +#endif + return 0; +} + +#endif /* CONFIG_OMAP_DSP */ diff --cc arch/arm/mach-omap2/board-n800-bt.c index 4ea19cca83f,00000000000..61afd2b2878 mode 100644,000000..100644 --- a/arch/arm/mach-omap2/board-n800-bt.c +++ b/arch/arm/mach-omap2/board-n800-bt.c @@@ -1,42 -1,0 +1,42 @@@ +/* + * Nokia N800 platform-specific data for Bluetooth + * + * Copyright (C) 2005, 2006 Nokia Corporation + * Contact: Ville Tervo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include +#include - #include ++#include + +static struct platform_device n800_bt_device = { + .name = "hci_h4p", + .id = -1, + .num_resources = 0, +}; + +void __init n800_bt_init(void) +{ + const struct omap_bluetooth_config *bt_config; + + bt_config = (void *) omap_get_config(OMAP_TAG_NOKIA_BT, + struct omap_bluetooth_config); + n800_bt_device.dev.platform_data = (void *) bt_config; + if (platform_device_register(&n800_bt_device) < 0) + BUG(); +} + diff --cc arch/arm/mach-omap2/board-n800-camera.c index 701dedaa258,00000000000..6272263e398 mode 100644,000000..100644 --- a/arch/arm/mach-omap2/board-n800-camera.c +++ b/arch/arm/mach-omap2/board-n800-camera.c @@@ -1,377 -1,0 +1,377 @@@ +/* + * arch/arm/mach-omap2/board-n800-camera.c + * + * Copyright (C) 2007 Nokia Corporation + * + * Contact: Sakari Ailus + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include + +#include + +#include + - #include - #include ++#include ++#include + +#include <../drivers/cbus/retu.h> +#include <../drivers/media/video/tcm825x.h> + +#include "board-n800.h" + +#if defined (CONFIG_VIDEO_TCM825X) || defined (CONFIG_VIDEO_TCM825X_MODULE) + +#define OMAP24XX_CAMERA_JAM_HACK + +#ifdef OMAP24XX_CAMERA_JAM_HACK +/* + * We don't need to check every pixel to assume that the frame is + * corrupt and the sensor is jammed. CHECK_X and CHECK_Y are the + * number of u32s to check per line / row, plus there are two lines in + * the bottom of the frame. + */ +#define CHECK_X 8 +#define CHECK_Y 6 +/* + * Start checking after this many frames since resetting the sensor. + * Sometimes the first frame(s) is(/are) black which could trigger + * unwanted reset(s). + */ +#define JAM_CHECK_AFTER 3 +/* + * If the sensor is quickly brought into bright conditions from dark, + * it may temporarily be saturated, leaving out the normal background + * noise. This many saturated frames may go through before the sensor + * is considered jammed. + */ +#define SATURATED_MAX 30 +#endif + +#define N800_CAM_SENSOR_RESET_GPIO 53 + +static int sensor_okay; +#ifdef OMAP24XX_CAMERA_JAM_HACK +static int frames_after_reset; +static int saturated_count; +#endif + +const static struct tcm825x_reg tcm825x_regs_n800[] = { + /* initial settings for 2.5 V */ + {0x00, 0x03}, {0x03, 0x29}, {0xaa, 0x2a}, {0xc0, 0x2b}, + {0x10, 0x2c}, {0x4c, 0x2d}, {0x9c, 0x3f}, + + /* main settings */ + {0x00, 0x00}, {0x30, 0x01}, {0x0e, 0x02}, /* initial */ + {0x0f, 0x04}, {0x02, 0x05}, {0x0d, 0x06}, {0xc0, 0x07}, + {0x38, 0x08}, {0x50, 0x09}, {0x80, 0x0a}, {0x40, 0x0b}, + {0x40, 0x0c}, {0x00, 0x0d}, {0x04, 0x0e}, {0x04, 0x0f}, + {0x22, 0x10}, {0x96, 0x11}, {0xf0, 0x12}, {0x08, 0x13}, + {0x08, 0x14}, {0x30, 0x15}, {0x30, 0x16}, {0x01, 0x17}, + {0x40, 0x18}, {0x87, 0x19}, {0x2b, 0x1a}, {0x84, 0x1b}, + {0x52, 0x1c}, {0x44, 0x1d}, {0x68, 0x1e}, {0x00, 0x1f}, + {0x00, 0x20}, {0x01, 0x21}, {0x27, 0x22}, {0x40, 0x23}, + {0x27, 0x24}, {0x5f, 0x25}, {0x00, 0x26}, {0x16, 0x27}, + {0x23, 0x28}, /* initial */ /* initial */ /* initial */ + /* initial */ /* initial */ {0x00, 0x2e}, {0x00, 0x2f}, + {0x00, 0x30}, {0x00, 0x31}, {0x00, 0x32}, {0x00, 0x33}, + {0x00, 0x34}, {0x00, 0x35}, {0x00, 0x36}, {0x00, 0x37}, + {0x00, 0x38}, {0x8c, 0x39}, {0xc8, 0x3A}, {0x80, 0x3b}, + {0x00, 0x3c}, {0x17, 0x3d}, {0x85, 0x3e}, /* initial */ + {0xa0, 0x40}, {0x00, 0x41}, {0x00, 0x42}, {0x00, 0x43}, + {0x08, 0x44}, {0x12, 0x45}, {0x00, 0x46}, {0x20, 0x47}, + {0x30, 0x48}, {0x18, 0x49}, {0x20, 0x4a}, {0x4d, 0x4b}, + {0x0c, 0x4c}, {0xe0, 0x4d}, {0x20, 0x4e}, {0x89, 0x4f}, + {0x21, 0x50}, {0x80, 0x51}, {0x02, 0x52}, {0x00, 0x53}, + {0x30, 0x54}, {0x90, 0x55}, {0x40, 0x56}, {0x06, 0x57}, + {0x0f, 0x58}, {0x23, 0x59}, {0x08, 0x5A}, {0x04, 0x5b}, + {0x08, 0x5c}, {0x08, 0x5d}, {0x08, 0x5e}, {0x08, 0x5f}, + {TCM825X_VAL_TERM, TCM825X_REG_TERM} +}; + +const static struct tcm825x_reg tcm825x_regs_n810[] = { + /* initial settings for 2.5 V */ + {0x00, 0x03}, {0x03, 0x29}, {0xaa, 0x2a}, {0xc0, 0x2b}, + {0x10, 0x2c}, {0x4c, 0x2d}, {0x9c, 0x3f}, + + /* main settings */ + {0x00, 0x00}, {0x30, 0x01}, {0x0e, 0x02}, /* initial */ + {0xcf, 0x04}, {0x02, 0x05}, {0x0d, 0x06}, {0xc0, 0x07}, + {0x38, 0x08}, {0x50, 0x09}, {0x80, 0x0a}, {0x40, 0x0b}, + {0x40, 0x0c}, {0x00, 0x0d}, {0x04, 0x0e}, {0x04, 0x0f}, + {0x22, 0x10}, {0x96, 0x11}, {0xf0, 0x12}, {0x08, 0x13}, + {0x08, 0x14}, {0x30, 0x15}, {0x30, 0x16}, {0x01, 0x17}, + {0x40, 0x18}, {0x87, 0x19}, {0x2b, 0x1a}, {0x84, 0x1b}, + {0x52, 0x1c}, {0x44, 0x1d}, {0x68, 0x1e}, {0x00, 0x1f}, + {0x00, 0x20}, {0x01, 0x21}, {0x27, 0x22}, {0x40, 0x23}, + {0x27, 0x24}, {0x5f, 0x25}, {0x00, 0x26}, {0x16, 0x27}, + {0x23, 0x28}, /* initial */ /* initial */ /* initial */ + /* initial */ /* initial */ {0x00, 0x2e}, {0x00, 0x2f}, + {0x00, 0x30}, {0x00, 0x31}, {0x00, 0x32}, {0x00, 0x33}, + {0x00, 0x34}, {0x00, 0x35}, {0x00, 0x36}, {0x00, 0x37}, + {0x00, 0x38}, {0x8c, 0x39}, {0xc8, 0x3A}, {0x80, 0x3b}, + {0x00, 0x3c}, {0x17, 0x3d}, {0x85, 0x3e}, /* initial */ + {0xa0, 0x40}, {0x00, 0x41}, {0x00, 0x42}, {0x00, 0x43}, + {0x08, 0x44}, {0x12, 0x45}, {0x00, 0x46}, {0x20, 0x47}, + {0x30, 0x48}, {0x18, 0x49}, {0x20, 0x4a}, {0x4d, 0x4b}, + {0x0c, 0x4c}, {0xe0, 0x4d}, {0x20, 0x4e}, {0x89, 0x4f}, + {0x21, 0x50}, {0x80, 0x51}, {0x02, 0x52}, {0x00, 0x53}, + {0x30, 0x54}, {0x90, 0x55}, {0x40, 0x56}, {0x06, 0x57}, + {0x0f, 0x58}, {0x23, 0x59}, {0x08, 0x5A}, {0x04, 0x5b}, + {0x08, 0x5c}, {0x08, 0x5d}, {0x08, 0x5e}, {0x08, 0x5f}, + {TCM825X_VAL_TERM, TCM825X_REG_TERM} +}; + +static int tcm825x_is_okay(void) +{ + return sensor_okay; +} + +/* + * VSIM1 --> CAM_IOVDD --> IOVDD (1.8 V) + */ +static int tcm825x_power_on(void) +{ + int ret; + + /* Set VMEM to 1.5V and VIO to 2.5V */ + ret = menelaus_set_vmem(1500); + if (ret < 0) { + /* Try once more, it seems the sensor power up causes + * some problems on the I2C bus. */ + ret = menelaus_set_vmem(1500); + if (ret < 0) + return ret; + } + msleep(1); + + ret = menelaus_set_vio(2500); + if (ret < 0) + return ret; + + /* Set VSim1 on */ + retu_write_reg(RETU_REG_CTRL_SET, 0x0080); + msleep(1); + + omap_set_gpio_dataout(N800_CAM_SENSOR_RESET_GPIO, 1); + msleep(1); + + saturated_count = 0; + frames_after_reset = 0; + + return 0; +} + +static int tcm825x_power_off(void) +{ + int ret; + + omap_set_gpio_dataout(N800_CAM_SENSOR_RESET_GPIO, 0); + msleep(1); + + /* Set VSim1 off */ + retu_write_reg(RETU_REG_CTRL_CLR, 0x0080); + msleep(1); + + /* Set VIO_MODE to off */ + ret = menelaus_set_vio(0); + if (ret < 0) + return ret; + msleep(1); + + /* Set VMEM_MODE to off */ + ret = menelaus_set_vmem(0); + if (ret < 0) + return ret; + msleep(1); + + return 0; +} + +static int tcm825x_power_set(int power) +{ + BUG_ON(!sensor_okay); + + if (power) + return tcm825x_power_on(); + else + return tcm825x_power_off(); +} + +static const struct tcm825x_reg *tcm825x_default_regs(void) +{ + if (machine_is_nokia_n810()) + return tcm825x_regs_n810; + + return tcm825x_regs_n800; +} + +#ifdef OMAP24XX_CAMERA_JAM_HACK +/* + * Check for jammed sensor, in which case all horizontal lines are + * equal. Handle also case where sensor could be saturated awhile in + * case of rapid increase of brightness. + */ +static int tcm825x_needs_reset(struct v4l2_int_device *s, void *buf, + struct v4l2_pix_format *pix) +{ + int i, j; + uint32_t xor, xor2; + uint32_t offset; + uint32_t dx_offset; + uint32_t saturated_pattern; + int is_saturated = 1; + + switch (pix->pixelformat) { + default: + case V4L2_PIX_FMT_RGB565: + saturated_pattern = 0xffffffff; /* guess */ + break; + case V4L2_PIX_FMT_UYVY: + saturated_pattern = 0xe080e080; + break; + } + + /* This won't work for height under 2 at all. */ + if (pix->height < 2) + return 0; + /* Check that there is enough image data. */ + if (pix->width * TCM825X_BYTES_PER_PIXEL < sizeof(uint32_t)) + return 0; + /* + * Don't check for jamming immediately. Sometimes frames + * immediately after reset are black. + */ + if (frames_after_reset < JAM_CHECK_AFTER) { + frames_after_reset++; + return 0; + } + + dx_offset = ((pix->width - sizeof(uint32_t) / TCM825X_BYTES_PER_PIXEL) + * TCM825X_BYTES_PER_PIXEL) / (CHECK_X - 1); + dx_offset = dx_offset - dx_offset % TCM825X_BYTES_PER_PIXEL; + /* + * Check two lines in the bottom first. They're unlikely to be + * saturated and quick to check. + */ + offset = (pix->height - 2) * pix->bytesperline; + xor = xor2 = 0; + for (j = 0; j < CHECK_X; j++) { + uint32_t *val = buf + offset; + uint32_t *val2 = buf + offset + pix->bytesperline; + xor ^= *val; + if (*val != saturated_pattern) + is_saturated = 0; + xor2 ^= *val2; + if (xor2 != xor) { + saturated_count = 0; + return 0; + } + offset += dx_offset; + } + /* Check the rest of the picture. */ + offset = 0; + for (i = 0; i < CHECK_Y; i++) { + uint32_t offset2 = offset; + xor2 = 0; + for (j = 0; j < CHECK_X; j++) { + uint32_t *val = buf + offset2; + xor2 ^= *val; + offset2 += dx_offset; + } + if (xor2 != xor) { + saturated_count = 0; + return 0; + } + offset += pix->bytesperline * ((pix->height - 2) / CHECK_Y); + } + + if (is_saturated && saturated_count++ < SATURATED_MAX) + return 0; + + return -EIO; +} +#else +static int tcm825x_needs_reset(struct v4l2_int_device *s, void *buf, + struct v4l2_pix_format *pix) +{ + return 0; +} +#endif + +static const struct v4l2_ifparm ifparm = { + .if_type = V4L2_IF_TYPE_BT656, + .u = { + .bt656 = { + .frame_start_on_rising_vs = 1, + .latch_clk_inv = 1, + .mode = V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT, + .clock_min = TCM825X_XCLK_MIN, + .clock_max = TCM825X_XCLK_MAX, + }, + }, +}; + +static int tcm825x_ifparm(struct v4l2_ifparm *p) +{ + *p = ifparm; + + return 0; +} + +static int tcm825x_is_upside_down(void) +{ + return machine_is_nokia_n810(); +} + +const struct tcm825x_platform_data n800_tcm825x_platform_data = { + .is_okay = tcm825x_is_okay, + .power_set = tcm825x_power_set, + .default_regs = tcm825x_default_regs, + .needs_reset = tcm825x_needs_reset, + .ifparm = tcm825x_ifparm, + .is_upside_down = tcm825x_is_upside_down, +}; + +void __init n800_cam_init(void) +{ + int r; + + r = omap_request_gpio(N800_CAM_SENSOR_RESET_GPIO); + if (r < 0) { + printk(KERN_WARNING "%s: failed to request gpio\n", + __func__); + return; + } + + omap_set_gpio_dataout(N800_CAM_SENSOR_RESET_GPIO, 0); + omap_set_gpio_direction(N800_CAM_SENSOR_RESET_GPIO, 0); + + sensor_okay = 1; +} + +#else +void __init n800_cam_init(void) +{ +} + +#endif diff --cc arch/arm/mach-omap2/board-n800-dsp.c index d524b620571,00000000000..5f3f0d6e954 mode 100644,000000..100644 --- a/arch/arm/mach-omap2/board-n800-dsp.c +++ b/arch/arm/mach-omap2/board-n800-dsp.c @@@ -1,155 -1,0 +1,155 @@@ +/* + * linux/arch/arm/mach-omap2/board-n800-dsp.c + * + * Copyright (C) 2006 Nokia Corporation. + * + * Contact: Hiroshi DOYU + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include + +#include - #include - #include - #include ++#include ++#include ++#include + +#if defined(CONFIG_OMAP_DSP) + +/* + * dsp peripheral device: AUDIO + */ +static struct dsp_kfunc_device n800_audio_device = { + .name = "audio", + .type = DSP_KFUNC_DEV_TYPE_AUDIO, + .enable = n800_audio_enable, + .disable = n800_audio_disable, +}; + +/* + * dsp peripheral device: TIMER + */ +static int dsp_timer_probe(struct dsp_kfunc_device *kdev, int stage) +{ + char clockname[20]; + + strcpy(clockname, kdev->name); + strcat(clockname, "_fck"); + + kdev->fck = clk_get(NULL, clockname); + if (IS_ERR(kdev->fck)) { + printk(KERN_ERR "couldn't acquire %s\n", clockname); + return PTR_ERR(kdev->fck); + } + pr_debug("%s probed successfully\n", clockname); + + strcpy(clockname, kdev->name); + strcat(clockname, "_ick"); + kdev->ick = clk_get(NULL, clockname); + if (IS_ERR(kdev->ick)) { + printk(KERN_ERR "couldn't acquire %s\n", clockname); + goto fail; + } + pr_debug("%s probed successfully\n", clockname); + + return 0; + fail: + clk_put(kdev->fck); + + return PTR_ERR(kdev->ick); +} + +static int dsp_timer_remove(struct dsp_kfunc_device *kdev, int stage) +{ + clk_put(kdev->ick); + clk_put(kdev->fck); + pr_debug("%s removed successfully\n", kdev->name); + return 0; +} + +static int dsp_timer_enable(struct dsp_kfunc_device *kdev, int stage) +{ + pr_debug("%s enabled(%d)\n", kdev->name, stage); + + spin_lock(&kdev->lock); + + if (kdev->enabled) + goto out; + kdev->enabled = 1; + + clk_enable(kdev->fck); + clk_enable(kdev->ick); + out: + spin_unlock(&kdev->lock); + + return 0; +} + +static int dsp_timer_disable(struct dsp_kfunc_device *kdev, int stage) +{ + pr_debug("%s disabled(%d)\n", kdev->name, stage); + + spin_lock(&kdev->lock); + + if (kdev->enabled == 0) + goto out; + kdev->enabled = 0; + + clk_disable(kdev->ick); + clk_disable(kdev->fck); + out: + spin_unlock(&kdev->lock); + + return 0; +} + +static struct dsp_kfunc_device n800_timer_device = { + .name = "gpt5", + .type = DSP_KFUNC_DEV_TYPE_COMMON, + .probe = dsp_timer_probe, + .remove = dsp_timer_remove, + .enable = dsp_timer_enable, + .disable = dsp_timer_disable, +}; + +static struct dsp_kfunc_device *n800_kfunc_dev[] = { + &n800_audio_device, + &n800_timer_device, +}; + +void __init n800_dsp_init(void) +{ + int i, ret; + struct dsp_kfunc_device **p = n800_kfunc_dev; + + for (i = 0; i < ARRAY_SIZE(n800_kfunc_dev); i++) { + ret = dsp_kfunc_device_register(p[i]); + if (ret) { + printk(KERN_ERR + "KFUNC device registration failed: %s\n", + p[i]->name); + } + } +} + +#else +void __init n800_dsp_init(void) { } +#endif /* CONFIG_OMAP_DSP */ diff --cc arch/arm/mach-omap2/board-n800-flash.c index fbf83b3b0eb,00000000000..52aaf76417e mode 100644,000000..100644 --- a/arch/arm/mach-omap2/board-n800-flash.c +++ b/arch/arm/mach-omap2/board-n800-flash.c @@@ -1,349 -1,0 +1,349 @@@ +/* + * linux/arch/arm/mach-omap2/board-n800-flash.c + * + * Copyright (C) 2006 Nokia Corporation + * Author: Juha Yrjola + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include + +#include - #include - #include - #include ++#include ++#include ++#include + +struct mtd_partition n800_partitions[ONENAND_MAX_PARTITIONS]; + +int n800_onenand_setup(void __iomem *, int freq); + +static struct omap_onenand_platform_data n800_onenand_data = { + .cs = 0, + .parts = n800_partitions, + .nr_parts = 0, /* filled later */ + .onenand_setup = n800_onenand_setup, +}; + +static struct platform_device n800_onenand_device = { + .name = "omap2-onenand", + .id = -1, + .dev = { + .platform_data = &n800_onenand_data, + }, +}; + +static int omap2_onenand_set_async_mode(int cs, void __iomem *onenand_base) +{ + struct gpmc_timings t; + + const int t_cer = 15; + const int t_avdp = 12; + const int t_aavdh = 7; + const int t_ce = 76; + const int t_aa = 76; + const int t_oe = 20; + const int t_cez = 20; /* max of t_cez, t_oez */ + const int t_ds = 30; + const int t_wpl = 40; + const int t_wph = 30; + + memset(&t, 0, sizeof(t)); + t.sync_clk = 0; + t.cs_on = 0; + t.adv_on = 0; + + /* Read */ + t.adv_rd_off = gpmc_round_ns_to_ticks(max_t(int, t_avdp, t_cer)); + t.oe_on = t.adv_rd_off + gpmc_round_ns_to_ticks(t_aavdh); + t.access = t.adv_on + gpmc_round_ns_to_ticks(t_aa); + t.access = max_t(int, t.access, t.cs_on + gpmc_round_ns_to_ticks(t_ce)); + t.access = max_t(int, t.access, t.oe_on + gpmc_round_ns_to_ticks(t_oe)); + t.oe_off = t.access + gpmc_round_ns_to_ticks(1); + t.cs_rd_off = t.oe_off; + t.rd_cycle = t.cs_rd_off + gpmc_round_ns_to_ticks(t_cez); + + /* Write */ + t.adv_wr_off = t.adv_rd_off; + t.we_on = t.oe_on; + if (cpu_is_omap34xx()) { + t.wr_data_mux_bus = t.we_on; + t.wr_access = t.we_on + gpmc_round_ns_to_ticks(t_ds); + } + t.we_off = t.we_on + gpmc_round_ns_to_ticks(t_wpl); + t.cs_wr_off = t.we_off + gpmc_round_ns_to_ticks(t_wph); + t.wr_cycle = t.cs_wr_off + gpmc_round_ns_to_ticks(t_cez); + + /* Configure GPMC for asynchronous read */ + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG1, + GPMC_CONFIG1_DEVICESIZE_16 | + GPMC_CONFIG1_MUXADDDATA); + + return gpmc_cs_set_timings(cs, &t); +} + +static unsigned short omap2_onenand_readw(void __iomem *addr) +{ + return readw(addr); +} + +static void omap2_onenand_writew(unsigned short value, void __iomem *addr) +{ + writew(value, addr); +} + +static void set_onenand_cfg(void __iomem *onenand_base, int latency, + int sync_write, int hf) +{ + u32 reg; + + reg = omap2_onenand_readw(onenand_base + ONENAND_REG_SYS_CFG1); + reg &= ~((0x7 << ONENAND_SYS_CFG1_BRL_SHIFT) | (0x7 << 9)); + reg |= (latency << ONENAND_SYS_CFG1_BRL_SHIFT) | + ONENAND_SYS_CFG1_SYNC_READ | + ONENAND_SYS_CFG1_BL_16; + if (sync_write) + reg |= ONENAND_SYS_CFG1_SYNC_WRITE; + else + reg &= ~ONENAND_SYS_CFG1_SYNC_WRITE; + if (hf) + reg |= ONENAND_SYS_CFG1_HF; + else + reg &= ~ONENAND_SYS_CFG1_HF; + omap2_onenand_writew(reg, onenand_base + ONENAND_REG_SYS_CFG1); +} + +static int omap2_onenand_set_sync_mode(int cs, void __iomem *onenand_base, + int freq) +{ + struct gpmc_timings t; + const int t_cer = 15; + const int t_avdp = 12; + const int t_cez = 20; /* max of t_cez, t_oez */ + const int t_ds = 30; + const int t_wpl = 40; + const int t_wph = 30; + int min_gpmc_clk_period, t_ces, t_avds, t_avdh, t_ach, t_aavdh, t_rdyo; + int tick_ns, div, fclk_offset_ns, fclk_offset, gpmc_clk_ns, latency; + int err, ticks_cez, sync_write = 0, first_time = 0, hf = 0; + u32 reg; + + if (!freq) { + /* Very first call freq is not known */ + err = omap2_onenand_set_async_mode(cs, onenand_base); + if (err) + return err; + reg = omap2_onenand_readw(onenand_base + + ONENAND_REG_VERSION_ID); + switch ((reg >> 4) & 0xf) { + case 0: + freq = 40; + break; + case 1: + freq = 54; + break; + case 2: + freq = 66; + break; + case 3: + freq = 83; + break; + case 4: + freq = 104; + break; + default: + freq = 54; + break; + } + first_time = 1; + } + + switch (freq) { + case 83: + min_gpmc_clk_period = 12; /* 83 MHz */ + t_ces = 5; + t_avds = 4; + t_avdh = 2; + t_ach = 6; + t_aavdh = 6; + t_rdyo = 9; + if (cpu_is_omap34xx()) + sync_write = 1; + break; + case 66: + min_gpmc_clk_period = 15; /* 66 MHz */ + t_ces = 6; + t_avds = 5; + t_avdh = 2; + t_ach = 6; + t_aavdh = 6; + t_rdyo = 11; + if (cpu_is_omap34xx()) + sync_write = 1; + break; + default: + min_gpmc_clk_period = 18; /* 54 MHz */ + t_ces = 7; + t_avds = 7; + t_avdh = 7; + t_ach = 9; + t_aavdh = 7; + t_rdyo = 15; + break; + } + + tick_ns = gpmc_ticks_to_ns(1); + div = gpmc_cs_calc_divider(cs, min_gpmc_clk_period); + gpmc_clk_ns = gpmc_ticks_to_ns(div); + if (gpmc_clk_ns < 15) /* >66Mhz */ + hf = 1; + if (hf) + latency = 6; + else if (gpmc_clk_ns >= 25) /* 40 MHz*/ + latency = 3; + else + latency = 4; + + if (first_time) + set_onenand_cfg(onenand_base, latency, sync_write, hf); + + if (div == 1) { + reg = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG2); + reg |= (1 << 7); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG2, reg); + reg = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG3); + reg |= (1 << 7); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG3, reg); + reg = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG4); + reg |= (1 << 7); + reg |= (1 << 23); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG4, reg); + } else { + reg = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG2); + reg &= ~(1 << 7); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG2, reg); + reg = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG3); + reg &= ~(1 << 7); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG3, reg); + reg = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG4); + reg &= ~(1 << 7); + reg &= ~(1 << 23); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG4, reg); + } + + /* Set synchronous read timings */ + memset(&t, 0, sizeof(t)); + t.sync_clk = min_gpmc_clk_period; + t.cs_on = 0; + t.adv_on = 0; + fclk_offset_ns = gpmc_round_ns_to_ticks(max_t(int, t_ces, t_avds)); + fclk_offset = gpmc_ns_to_ticks(fclk_offset_ns); + t.page_burst_access = gpmc_clk_ns; + + /* Read */ + t.adv_rd_off = gpmc_ticks_to_ns(fclk_offset + gpmc_ns_to_ticks(t_avdh)); + t.oe_on = gpmc_ticks_to_ns(fclk_offset + gpmc_ns_to_ticks(t_ach)); + t.access = gpmc_ticks_to_ns(fclk_offset + (latency + 1) * div); + t.oe_off = t.access + gpmc_round_ns_to_ticks(1); + t.cs_rd_off = t.oe_off; + ticks_cez = ((gpmc_ns_to_ticks(t_cez) + div - 1) / div) * div; + t.rd_cycle = gpmc_ticks_to_ns(fclk_offset + (latency + 1) * div + + ticks_cez); + + /* Write */ + if (sync_write) { + t.adv_wr_off = t.adv_rd_off; + t.we_on = 0; + t.we_off = t.cs_rd_off; + t.cs_wr_off = t.cs_rd_off; + t.wr_cycle = t.rd_cycle; + if (cpu_is_omap34xx()) { + t.wr_data_mux_bus = gpmc_ticks_to_ns(fclk_offset + + gpmc_ns_to_ticks(min_gpmc_clk_period + + t_rdyo)); + t.wr_access = t.access; + } + } else { + t.adv_wr_off = gpmc_round_ns_to_ticks(max_t(int, t_avdp, t_cer)); + t.we_on = t.adv_wr_off + gpmc_round_ns_to_ticks(t_aavdh); + t.we_off = t.we_on + gpmc_round_ns_to_ticks(t_wpl); + t.cs_wr_off = t.we_off + gpmc_round_ns_to_ticks(t_wph); + t.wr_cycle = t.cs_wr_off + gpmc_round_ns_to_ticks(t_cez); + if (cpu_is_omap34xx()) { + t.wr_data_mux_bus = t.we_on; + t.wr_access = t.we_on + gpmc_round_ns_to_ticks(t_ds); + } + } + + /* Configure GPMC for synchronous read */ + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG1, + GPMC_CONFIG1_WRAPBURST_SUPP | + GPMC_CONFIG1_READMULTIPLE_SUPP | + GPMC_CONFIG1_READTYPE_SYNC | + (sync_write ? GPMC_CONFIG1_WRITEMULTIPLE_SUPP : 0) | + (sync_write ? GPMC_CONFIG1_WRITETYPE_SYNC : 0) | + GPMC_CONFIG1_CLKACTIVATIONTIME(fclk_offset) | + GPMC_CONFIG1_PAGE_LEN(2) | + (cpu_is_omap34xx() ? 0 : + (GPMC_CONFIG1_WAIT_READ_MON | + GPMC_CONFIG1_WAIT_PIN_SEL(0))) | + GPMC_CONFIG1_DEVICESIZE_16 | + GPMC_CONFIG1_DEVICETYPE_NOR | + GPMC_CONFIG1_MUXADDDATA); + + err = gpmc_cs_set_timings(cs, &t); + if (err) + return err; + + set_onenand_cfg(onenand_base, latency, sync_write, hf); + + return 0; +} + +int n800_onenand_setup(void __iomem *onenand_base, int freq) +{ + struct omap_onenand_platform_data *datap = &n800_onenand_data; + struct device *dev = &n800_onenand_device.dev; + + /* Set sync timings in GPMC */ + if (omap2_onenand_set_sync_mode(datap->cs, onenand_base, freq) < 0) { + dev_err(dev, "Unable to set synchronous mode\n"); + return -EINVAL; + } + + return 0; +} + +void __init n800_flash_init(void) +{ + const struct omap_partition_config *part; + int i = 0; + + n800_onenand_data.gpio_irq = cpu_is_omap34xx() ? 65 : 26; + + while ((part = omap_get_nr_config(OMAP_TAG_PARTITION, + struct omap_partition_config, i)) != NULL) { + struct mtd_partition *mpart; + + mpart = n800_partitions + i; + mpart->name = (char *) part->name; + mpart->size = part->size; + mpart->offset = part->offset; + mpart->mask_flags = part->mask_flags; + i++; + if (i == ARRAY_SIZE(n800_partitions)) { + printk(KERN_ERR "Too many partitions supplied\n"); + return; + } + } + n800_onenand_data.nr_parts = i; + if (platform_device_register(&n800_onenand_device) < 0) { + printk(KERN_ERR "Unable to register OneNAND device\n"); + return; + } +} diff --cc arch/arm/mach-omap2/board-n800-mmc.c index 2028710c8fe,00000000000..dac159f2f9a mode 100644,000000..100644 --- a/arch/arm/mach-omap2/board-n800-mmc.c +++ b/arch/arm/mach-omap2/board-n800-mmc.c @@@ -1,370 -1,0 +1,370 @@@ +/* + * linux/arch/arm/mach-omap2/board-n800-mmc.c + * + * Copyright (C) 2006 Nokia Corporation + * Author: Juha Yrjola + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + - #include - #include ++#include ++#include + +#include +#include +#include + +#ifdef CONFIG_MMC_OMAP + +static const int slot_switch_gpio = 96; + +static const int n810_slot2_pw_vddf = 23; +static const int n810_slot2_pw_vdd = 9; + +static int slot1_cover_open; +static int slot2_cover_open; +static struct device *mmc_device; + +/* + * VMMC --> slot 1 (N800 & N810) + * VDCDC3_APE, VMCS2_APE --> slot 2 on N800 + * GPIO96 --> Menelaus GPIO2 + * GPIO23 --> controls slot2 VSD (N810 only) + * GPIO9 --> controls slot2 VIO_SD (N810 only) + */ + +static int n800_mmc_switch_slot(struct device *dev, int slot) +{ +#ifdef CONFIG_MMC_DEBUG + dev_dbg(dev, "Choose slot %d\n", slot + 1); +#endif + if (slot == 0) + omap_set_gpio_dataout(slot_switch_gpio, 0); + else + omap_set_gpio_dataout(slot_switch_gpio, 1); + return 0; +} + +static int n800_mmc_set_power_menelaus(struct device *dev, int slot, + int power_on, int vdd) +{ + int mV; + +#ifdef CONFIG_MMC_DEBUG + dev_dbg(dev, "Set slot %d power: %s (vdd %d)\n", slot + 1, + power_on ? "on" : "off", vdd); +#endif + if (slot == 0) { + if (!power_on) + return menelaus_set_vmmc(0); + switch (1 << vdd) { + case MMC_VDD_33_34: + case MMC_VDD_32_33: + case MMC_VDD_31_32: + mV = 3100; + break; + case MMC_VDD_30_31: + mV = 3000; + break; + case MMC_VDD_28_29: + mV = 2800; + break; + case MMC_VDD_165_195: + mV = 1850; + break; + default: + BUG(); + } + return menelaus_set_vmmc(mV); + } else { + if (!power_on) + return menelaus_set_vdcdc(3, 0); + switch (1 << vdd) { + case MMC_VDD_33_34: + case MMC_VDD_32_33: + mV = 3300; + break; + case MMC_VDD_30_31: + case MMC_VDD_29_30: + mV = 3000; + break; + case MMC_VDD_28_29: + case MMC_VDD_27_28: + mV = 2800; + break; + case MMC_VDD_24_25: + case MMC_VDD_23_24: + mV = 2400; + break; + case MMC_VDD_22_23: + case MMC_VDD_21_22: + mV = 2200; + break; + case MMC_VDD_20_21: + mV = 2000; + break; + case MMC_VDD_165_195: + mV = 1800; + break; + default: + BUG(); + } + return menelaus_set_vdcdc(3, mV); + } + return 0; +} + +static void nokia_mmc_set_power_internal(struct device *dev, + int power_on) +{ + dev_dbg(dev, "Set internal slot power %s\n", + power_on ? "on" : "off"); + + if (power_on) { + omap_set_gpio_dataout(n810_slot2_pw_vddf, 1); + udelay(30); + omap_set_gpio_dataout(n810_slot2_pw_vdd, 1); + udelay(100); + } else { + omap_set_gpio_dataout(n810_slot2_pw_vdd, 0); + msleep(50); + omap_set_gpio_dataout(n810_slot2_pw_vddf, 0); + msleep(50); + } +} + +static int n800_mmc_set_power(struct device *dev, int slot, int power_on, + int vdd) +{ + if (machine_is_nokia_n800() || slot == 0) + return n800_mmc_set_power_menelaus(dev, slot, power_on, vdd); + + nokia_mmc_set_power_internal(dev, power_on); + + return 0; +} + +static int n800_mmc_set_bus_mode(struct device *dev, int slot, int bus_mode) +{ + int r; + + dev_dbg(dev, "Set slot %d bus mode %s\n", slot + 1, + bus_mode == MMC_BUSMODE_OPENDRAIN ? "open-drain" : "push-pull"); + BUG_ON(slot != 0 && slot != 1); + slot++; + switch (bus_mode) { + case MMC_BUSMODE_OPENDRAIN: + r = menelaus_set_mmc_opendrain(slot, 1); + break; + case MMC_BUSMODE_PUSHPULL: + r = menelaus_set_mmc_opendrain(slot, 0); + break; + default: + BUG(); + } + if (r != 0 && printk_ratelimit()) + dev_err(dev, "MMC: unable to set bus mode for slot %d\n", + slot); + return r; +} + +static int n800_mmc_get_cover_state(struct device *dev, int slot) +{ + slot++; + BUG_ON(slot != 1 && slot != 2); + if (slot == 1) + return slot1_cover_open; + else + return slot2_cover_open; +} + +static void n800_mmc_callback(void *data, u8 card_mask) +{ + int bit, *openp, index; + + if (machine_is_nokia_n800()) { + bit = 1 << 1; + openp = &slot2_cover_open; + index = 1; + } else { + bit = 1; + openp = &slot1_cover_open; + index = 0; + } + + if (card_mask & bit) + *openp = 1; + else + *openp = 0; + + omap_mmc_notify_cover_event(mmc_device, index, *openp); +} + +void n800_mmc_slot1_cover_handler(void *arg, int closed_state) +{ + if (mmc_device == NULL) + return; + + slot1_cover_open = !closed_state; + omap_mmc_notify_cover_event(mmc_device, 0, closed_state); +} + +static int n800_mmc_late_init(struct device *dev) +{ + int r, bit, *openp; + int vs2sel; + + mmc_device = dev; + + r = menelaus_set_slot_sel(1); + if (r < 0) + return r; + + if (machine_is_nokia_n800()) + vs2sel = 0; + else + vs2sel = 2; + + r = menelaus_set_mmc_slot(2, 0, vs2sel, 1); + if (r < 0) + return r; + + n800_mmc_set_power(dev, 0, MMC_POWER_ON, 16); /* MMC_VDD_28_29 */ + n800_mmc_set_power(dev, 1, MMC_POWER_ON, 16); + + r = menelaus_set_mmc_slot(1, 1, 0, 1); + if (r < 0) + return r; + r = menelaus_set_mmc_slot(2, 1, vs2sel, 1); + if (r < 0) + return r; + + r = menelaus_get_slot_pin_states(); + if (r < 0) + return r; + + if (machine_is_nokia_n800()) { + bit = 1 << 1; + openp = &slot2_cover_open; + } else { + bit = 1; + openp = &slot1_cover_open; + slot2_cover_open = 0; + } + + /* All slot pin bits seem to be inversed until first swith change */ + if (r == 0xf || r == (0xf & ~bit)) + r = ~r; + + if (r & bit) + *openp = 1; + else + *openp = 0; + + r = menelaus_register_mmc_callback(n800_mmc_callback, NULL); + + return r; +} + +static void n800_mmc_shutdown(struct device *dev) +{ + int vs2sel; + + if (machine_is_nokia_n800()) + vs2sel = 0; + else + vs2sel = 2; + + menelaus_set_mmc_slot(1, 0, 0, 0); + menelaus_set_mmc_slot(2, 0, vs2sel, 0); +} + +static void n800_mmc_cleanup(struct device *dev) +{ + menelaus_unregister_mmc_callback(); + + omap_free_gpio(slot_switch_gpio); + + if (machine_is_nokia_n810()) { + omap_free_gpio(n810_slot2_pw_vddf); + omap_free_gpio(n810_slot2_pw_vdd); + } +} + +static struct omap_mmc_platform_data n800_mmc_data = { + .nr_slots = 2, + .switch_slot = n800_mmc_switch_slot, + .init = n800_mmc_late_init, + .cleanup = n800_mmc_cleanup, + .shutdown = n800_mmc_shutdown, + .max_freq = 24000000, + .slots[0] = { + .set_power = n800_mmc_set_power, + .set_bus_mode = n800_mmc_set_bus_mode, + .get_ro = NULL, + .get_cover_state= n800_mmc_get_cover_state, + .ocr_mask = MMC_VDD_165_195 | MMC_VDD_30_31 | + MMC_VDD_32_33 | MMC_VDD_33_34, + .name = "internal", + }, + .slots[1] = { + .set_power = n800_mmc_set_power, + .set_bus_mode = n800_mmc_set_bus_mode, + .get_ro = NULL, + .get_cover_state= n800_mmc_get_cover_state, + .ocr_mask = MMC_VDD_165_195 | MMC_VDD_20_21 | + MMC_VDD_21_22 | MMC_VDD_22_23 | MMC_VDD_23_24 | + MMC_VDD_24_25 | MMC_VDD_27_28 | MMC_VDD_28_29 | + MMC_VDD_29_30 | MMC_VDD_30_31 | MMC_VDD_32_33 | + MMC_VDD_33_34, + .name = "external", + }, +}; + +void __init n800_mmc_init(void) + +{ + if (machine_is_nokia_n810()) { + n800_mmc_data.slots[0].name = "external"; + + /* + * Some Samsung Movinand chips do not like open-ended + * multi-block reads and fall to braind-dead state + * while doing so. Reducing the number of blocks in + * the transfer or delays in clock disable do not help + */ + n800_mmc_data.slots[1].name = "internal"; + n800_mmc_data.slots[1].ban_openended = 1; + } + + omap_set_mmc_info(1, &n800_mmc_data); + if (omap_request_gpio(slot_switch_gpio) < 0) + BUG(); + omap_set_gpio_dataout(slot_switch_gpio, 0); + omap_set_gpio_direction(slot_switch_gpio, 0); + + if (machine_is_nokia_n810()) { + if (omap_request_gpio(n810_slot2_pw_vddf) < 0) + BUG(); + omap_set_gpio_dataout(n810_slot2_pw_vddf, 0); + omap_set_gpio_direction(n810_slot2_pw_vddf, 0); + + if (omap_request_gpio(n810_slot2_pw_vdd) < 0) + BUG(); + omap_set_gpio_dataout(n810_slot2_pw_vdd, 0); + omap_set_gpio_direction(n810_slot2_pw_vdd, 0); + } +} +#else + +void __init n800_mmc_init(void) +{ +} + +void n800_mmc_slot1_cover_handler(void *arg, int state) +{ +} + +#endif diff --cc arch/arm/mach-omap2/board-n800-usb.c index 7599f64775c,00000000000..66dcd0f5aab mode 100644,000000..100644 --- a/arch/arm/mach-omap2/board-n800-usb.c +++ b/arch/arm/mach-omap2/board-n800-usb.c @@@ -1,131 -1,0 +1,131 @@@ +/* + * linux/arch/arm/mach-omap2/board-n800-usb.c + * + * Copyright (C) 2006 Nokia Corporation + * Author: Juha Yrjola + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include - #include - #include - #include ++#include ++#include ++#include + +#define TUSB_ASYNC_CS 1 +#define TUSB_SYNC_CS 4 +#define GPIO_TUSB_INT 58 +#define GPIO_TUSB_ENABLE 0 + +static int tusb_set_power(int state); +static int tusb_set_clock(struct clk *osc_ck, int state); + +#if defined(CONFIG_USB_MUSB_OTG) +# define BOARD_MODE MUSB_OTG +#elif defined(CONFIG_USB_MUSB_PERIPHERAL) +# define BOARD_MODE MUSB_PERIPHERAL +#else /* defined(CONFIG_USB_MUSB_HOST) */ +# define BOARD_MODE MUSB_HOST +#endif + +static struct musb_hdrc_platform_data tusb_data = { + .mode = BOARD_MODE, + .multipoint = 1, + .set_power = tusb_set_power, + .set_clock = tusb_set_clock, + .min_power = 25, /* x2 = 50 mA drawn from VBUS as peripheral */ + .power = 100, /* Max 100 mA VBUS for host mode */ + .clock = "osc_ck", +}; + +/* + * Enable or disable power to TUSB6010. When enabling, turn on 3.3 V and + * 1.5 V voltage regulators of PM companion chip. Companion chip will then + * provide then PGOOD signal to TUSB6010 which will release it from reset. + */ +static int tusb_set_power(int state) +{ + int i, retval = 0; + + if (state) { + omap_set_gpio_dataout(GPIO_TUSB_ENABLE, 1); + msleep(1); + + /* Wait until TUSB6010 pulls INT pin down */ + i = 100; + while (i && omap_get_gpio_datain(GPIO_TUSB_INT)) { + msleep(1); + i--; + } + + if (!i) { + printk(KERN_ERR "tusb: powerup failed\n"); + retval = -ENODEV; + } + } else { + omap_set_gpio_dataout(GPIO_TUSB_ENABLE, 0); + msleep(10); + } + + return retval; +} + +static int osc_ck_on; + +static int tusb_set_clock(struct clk *osc_ck, int state) +{ + if (state) { + if (osc_ck_on > 0) + return -ENODEV; + + omap2_block_sleep(); + clk_enable(osc_ck); + osc_ck_on = 1; + } else { + if (osc_ck_on == 0) + return -ENODEV; + + clk_disable(osc_ck); + osc_ck_on = 0; + omap2_allow_sleep(); + } + + return 0; +} + +void __init n800_usb_init(void) +{ + int ret = 0; + static char announce[] __initdata = KERN_INFO "TUSB 6010\n"; + + /* PM companion chip power control pin */ + ret = omap_request_gpio(GPIO_TUSB_ENABLE); + if (ret != 0) { + printk(KERN_ERR "Could not get TUSB power GPIO%i\n", + GPIO_TUSB_ENABLE); + return; + } + omap_set_gpio_direction(GPIO_TUSB_ENABLE, 0); + + tusb_set_power(0); + + ret = tusb6010_setup_interface(&tusb_data, TUSB6010_REFCLK_19, 2, + TUSB_ASYNC_CS, TUSB_SYNC_CS, + GPIO_TUSB_INT, 0x3f); + if (ret != 0) + goto err; + + printk(announce); + + return; + +err: + omap_free_gpio(GPIO_TUSB_ENABLE); +} diff --cc arch/arm/mach-omap2/board-n800.c index 90909630b92,00000000000..95cc84ab42c mode 100644,000000..100644 --- a/arch/arm/mach-omap2/board-n800.c +++ b/arch/arm/mach-omap2/board-n800.c @@@ -1,738 -1,0 +1,738 @@@ +/* + * linux/arch/arm/mach-omap2/board-n800.c + * + * Copyright (C) 2005-2007 Nokia Corporation + * Author: Juha Yrjola + * + * Modified from mach-omap2/board-generic.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include - #include ++#include +#include +#include +#include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include + +#include <../drivers/cbus/tahvo.h> +#include <../drivers/media/video/tcm825x.h> + +#define N800_BLIZZARD_POWERDOWN_GPIO 15 +#define N800_STI_GPIO 62 +#define N800_KEYB_IRQ_GPIO 109 +#define N800_DAV_IRQ_GPIO 103 +#define N800_TSC2301_RESET_GPIO 118 + +#ifdef CONFIG_MACH_NOKIA_N810 +static s16 rx44_keymap[LM8323_KEYMAP_SIZE] = { + [0x01] = KEY_Q, + [0x02] = KEY_K, + [0x03] = KEY_O, + [0x04] = KEY_P, + [0x05] = KEY_BACKSPACE, + [0x06] = KEY_A, + [0x07] = KEY_S, + [0x08] = KEY_D, + [0x09] = KEY_F, + [0x0a] = KEY_G, + [0x0b] = KEY_H, + [0x0c] = KEY_J, + + [0x11] = KEY_W, + [0x12] = KEY_F4, + [0x13] = KEY_L, + [0x14] = KEY_APOSTROPHE, + [0x16] = KEY_Z, + [0x17] = KEY_X, + [0x18] = KEY_C, + [0x19] = KEY_V, + [0x1a] = KEY_B, + [0x1b] = KEY_N, + [0x1c] = KEY_LEFTSHIFT, /* Actually, this is both shift keys */ + [0x1f] = KEY_F7, + + [0x21] = KEY_E, + [0x22] = KEY_SEMICOLON, + [0x23] = KEY_MINUS, + [0x24] = KEY_EQUAL, + [0x2b] = KEY_FN, + [0x2c] = KEY_M, + [0x2f] = KEY_F8, + + [0x31] = KEY_R, + [0x32] = KEY_RIGHTCTRL, + [0x34] = KEY_SPACE, + [0x35] = KEY_COMMA, + [0x37] = KEY_UP, + [0x3c] = KEY_COMPOSE, + [0x3f] = KEY_F6, + + [0x41] = KEY_T, + [0x44] = KEY_DOT, + [0x46] = KEY_RIGHT, + [0x4f] = KEY_F5, + [0x51] = KEY_Y, + [0x53] = KEY_DOWN, + [0x55] = KEY_ENTER, + [0x5f] = KEY_ESC, + + [0x61] = KEY_U, + [0x64] = KEY_LEFT, + + [0x71] = KEY_I, + [0x75] = KEY_KPENTER, +}; + +static struct lm8323_platform_data lm8323_pdata = { + .repeat = 0, /* Repeat is handled in userspace for now. */ + .keymap = rx44_keymap, + + .name = "Internal keyboard", + .pwm1_name = "keyboard", + .pwm2_name = "cover", +}; +#endif + +void __init nokia_n800_init_irq(void) +{ + omap2_init_common_hw(NULL); + omap_init_irq(); + omap_gpio_init(); + +#ifdef CONFIG_OMAP_STI + if (omap_request_gpio(N800_STI_GPIO) < 0) { + printk(KERN_ERR "Failed to request GPIO %d for STI\n", + N800_STI_GPIO); + return; + } + + omap_set_gpio_direction(N800_STI_GPIO, 0); + omap_set_gpio_dataout(N800_STI_GPIO, 0); +#endif +} + +#if defined(CONFIG_MENELAUS) && defined(CONFIG_SENSORS_TMP105) + +static int n800_tmp105_set_power(int enable) +{ + return menelaus_set_vaux(enable ? 2800 : 0); +} + +#else + +#define n800_tmp105_set_power NULL + +#endif + +static struct omap_uart_config n800_uart_config __initdata = { + .enabled_uarts = (1 << 0) | (1 << 2), +}; + +#include "../../../drivers/cbus/retu.h" + +static struct omap_fbmem_config n800_fbmem0_config __initdata = { + .size = 752 * 1024, +}; + +static struct omap_fbmem_config n800_fbmem1_config __initdata = { + .size = 752 * 1024, +}; + +static struct omap_fbmem_config n800_fbmem2_config __initdata = { + .size = 752 * 1024, +}; + +static struct omap_tmp105_config n800_tmp105_config __initdata = { + .tmp105_irq_pin = 125, + .set_power = n800_tmp105_set_power, +}; + +static void mipid_shutdown(struct mipid_platform_data *pdata) +{ + if (pdata->nreset_gpio != -1) { + pr_info("shutdown LCD\n"); + omap_set_gpio_dataout(pdata->nreset_gpio, 0); + msleep(120); + } +} + +static struct mipid_platform_data n800_mipid_platform_data = { + .shutdown = mipid_shutdown, +}; + +static void __init mipid_dev_init(void) +{ + const struct omap_lcd_config *conf; + + conf = omap_get_config(OMAP_TAG_LCD, struct omap_lcd_config); + if (conf != NULL) { + n800_mipid_platform_data.nreset_gpio = conf->nreset_gpio; + n800_mipid_platform_data.data_lines = conf->data_lines; + } +} + +static struct { + struct clk *sys_ck; +} blizzard; + +static int blizzard_get_clocks(void) +{ + blizzard.sys_ck = clk_get(0, "osc_ck"); + if (IS_ERR(blizzard.sys_ck)) { + printk(KERN_ERR "can't get Blizzard clock\n"); + return PTR_ERR(blizzard.sys_ck); + } + return 0; +} + +static unsigned long blizzard_get_clock_rate(struct device *dev) +{ + return clk_get_rate(blizzard.sys_ck); +} + +static void blizzard_enable_clocks(int enable) +{ + if (enable) + clk_enable(blizzard.sys_ck); + else + clk_disable(blizzard.sys_ck); +} + +static void blizzard_power_up(struct device *dev) +{ + /* Vcore to 1.475V */ + tahvo_set_clear_reg_bits(0x07, 0, 0xf); + msleep(10); + + blizzard_enable_clocks(1); + omap_set_gpio_dataout(N800_BLIZZARD_POWERDOWN_GPIO, 1); +} + +static void blizzard_power_down(struct device *dev) +{ + omap_set_gpio_dataout(N800_BLIZZARD_POWERDOWN_GPIO, 0); + blizzard_enable_clocks(0); + + /* Vcore to 1.005V */ + tahvo_set_clear_reg_bits(0x07, 0xf, 0); +} + +static struct blizzard_platform_data n800_blizzard_data = { + .power_up = blizzard_power_up, + .power_down = blizzard_power_down, + .get_clock_rate = blizzard_get_clock_rate, + .te_connected = 1, +}; + +static void __init blizzard_dev_init(void) +{ + int r; + + r = omap_request_gpio(N800_BLIZZARD_POWERDOWN_GPIO); + if (r < 0) + return; + omap_set_gpio_direction(N800_BLIZZARD_POWERDOWN_GPIO, 0); + omap_set_gpio_dataout(N800_BLIZZARD_POWERDOWN_GPIO, 1); + + blizzard_get_clocks(); + omapfb_set_ctrl_platform_data(&n800_blizzard_data); +} + +static struct omap_mmc_config n800_mmc_config __initdata = { + .mmc [0] = { + .enabled = 1, + .wire4 = 1, + }, +}; + +extern struct omap_mmc_platform_data n800_mmc_data; + +static struct omap_board_config_kernel n800_config[] __initdata = { + { OMAP_TAG_UART, &n800_uart_config }, + { OMAP_TAG_FBMEM, &n800_fbmem0_config }, + { OMAP_TAG_FBMEM, &n800_fbmem1_config }, + { OMAP_TAG_FBMEM, &n800_fbmem2_config }, + { OMAP_TAG_TMP105, &n800_tmp105_config }, + { OMAP_TAG_MMC, &n800_mmc_config }, +}; + +static struct tsc2301_platform_data tsc2301_config = { + .reset_gpio = N800_TSC2301_RESET_GPIO, + .keymap = { + -1, /* Event for bit 0 */ + KEY_UP, /* Event for bit 1 (up) */ + KEY_F5, /* Event for bit 2 (home) */ + -1, /* Event for bit 3 */ + KEY_LEFT, /* Event for bit 4 (left) */ + KEY_ENTER, /* Event for bit 5 (enter) */ + KEY_RIGHT, /* Event for bit 6 (right) */ + -1, /* Event for bit 7 */ + KEY_ESC, /* Event for bit 8 (cycle) */ + KEY_DOWN, /* Event for bit 9 (down) */ + KEY_F4, /* Event for bit 10 (menu) */ + -1, /* Event for bit 11 */ + KEY_F8, /* Event for bit 12 (Zoom-) */ + KEY_F6, /* Event for bit 13 (FS) */ + KEY_F7, /* Event for bit 14 (Zoom+) */ + -1, /* Event for bit 15 */ + }, + .kp_rep = 0, + .keyb_name = "Internal keypad", +}; + +static void tsc2301_dev_init(void) +{ + int r; + int gpio = N800_KEYB_IRQ_GPIO; + + r = gpio_request(gpio, "tsc2301 KBD IRQ"); + if (r >= 0) { + gpio_direction_input(gpio); + tsc2301_config.keyb_int = OMAP_GPIO_IRQ(gpio); + } else { + printk(KERN_ERR "unable to get KBD GPIO"); + } + + gpio = N800_DAV_IRQ_GPIO; + r = gpio_request(gpio, "tsc2301 DAV IRQ"); + if (r >= 0) { + gpio_direction_input(gpio); + tsc2301_config.dav_int = OMAP_GPIO_IRQ(gpio); + } else { + printk(KERN_ERR "unable to get DAV GPIO"); + } +} + +static int __init tea5761_dev_init(void) +{ + const struct omap_tea5761_config *info; + int enable_gpio = 0; + + info = omap_get_config(OMAP_TAG_TEA5761, struct omap_tea5761_config); + if (info) + enable_gpio = info->enable_gpio; + + if (enable_gpio) { + pr_debug("Enabling tea5761 at GPIO %d\n", + enable_gpio); + + if (omap_request_gpio(enable_gpio) < 0) { + printk(KERN_ERR "Can't request GPIO %d\n", + enable_gpio); + return -ENODEV; + } + + omap_set_gpio_direction(enable_gpio, 0); + udelay(50); + omap_set_gpio_dataout(enable_gpio, 1); + } + + return 0; +} + +static struct omap2_mcspi_device_config tsc2301_mcspi_config = { + .turbo_mode = 0, + .single_channel = 1, +}; + +static struct omap2_mcspi_device_config mipid_mcspi_config = { + .turbo_mode = 0, + .single_channel = 1, +}; + +static struct omap2_mcspi_device_config cx3110x_mcspi_config = { + .turbo_mode = 0, + .single_channel = 1, +}; + +#ifdef CONFIG_TOUCHSCREEN_TSC2005 +static struct tsc2005_platform_data tsc2005_config = { + .reset_gpio = 94, + .dav_gpio = 106 +}; + +static struct omap2_mcspi_device_config tsc2005_mcspi_config = { + .turbo_mode = 0, + .single_channel = 1, +}; +#endif + +static struct spi_board_info n800_spi_board_info[] __initdata = { + { + .modalias = "lcd_mipid", + .bus_num = 1, + .chip_select = 1, + .max_speed_hz = 4000000, + .controller_data= &mipid_mcspi_config, + .platform_data = &n800_mipid_platform_data, + }, { + .modalias = "cx3110x", + .bus_num = 2, + .chip_select = 0, + .max_speed_hz = 48000000, + .controller_data= &cx3110x_mcspi_config, + }, + { + .modalias = "tsc2301", + .bus_num = 1, + .chip_select = 0, + .max_speed_hz = 6000000, + .controller_data= &tsc2301_mcspi_config, + .platform_data = &tsc2301_config, + }, +}; + +static struct spi_board_info n810_spi_board_info[] __initdata = { + { + .modalias = "lcd_mipid", + .bus_num = 1, + .chip_select = 1, + .max_speed_hz = 4000000, + .controller_data = &mipid_mcspi_config, + .platform_data = &n800_mipid_platform_data, + }, + { + .modalias = "cx3110x", + .bus_num = 2, + .chip_select = 0, + .max_speed_hz = 48000000, + .controller_data = &cx3110x_mcspi_config, + }, + { + .modalias = "tsc2005", + .bus_num = 1, + .chip_select = 0, + .max_speed_hz = 6000000, + .controller_data = &tsc2005_mcspi_config, + .platform_data = &tsc2005_config, + }, +}; + +static void __init tsc2005_set_config(void) +{ + const struct omap_lcd_config *conf; + + conf = omap_get_config(OMAP_TAG_LCD, struct omap_lcd_config); + if (conf != NULL) { +#ifdef CONFIG_TOUCHSCREEN_TSC2005 + if (strcmp(conf->panel_name, "lph8923") == 0) { + tsc2005_config.ts_x_plate_ohm = 180; + tsc2005_config.ts_hw_avg = 0; + tsc2005_config.ts_ignore_last = 0; + tsc2005_config.ts_touch_pressure = 1500; + tsc2005_config.ts_stab_time = 100; + tsc2005_config.ts_pressure_max = 2048; + tsc2005_config.ts_pressure_fudge = 2; + tsc2005_config.ts_x_max = 4096; + tsc2005_config.ts_x_fudge = 4; + tsc2005_config.ts_y_max = 4096; + tsc2005_config.ts_y_fudge = 7; + } else if (strcmp(conf->panel_name, "ls041y3") == 0) { + tsc2005_config.ts_x_plate_ohm = 280; + tsc2005_config.ts_hw_avg = 0; + tsc2005_config.ts_ignore_last = 0; + tsc2005_config.ts_touch_pressure = 1500; + tsc2005_config.ts_stab_time = 1000; + tsc2005_config.ts_pressure_max = 2048; + tsc2005_config.ts_pressure_fudge = 2; + tsc2005_config.ts_x_max = 4096; + tsc2005_config.ts_x_fudge = 4; + tsc2005_config.ts_y_max = 4096; + tsc2005_config.ts_y_fudge = 7; + } else { + printk(KERN_ERR "Unknown panel type, set default " + "touchscreen configuration\n"); + tsc2005_config.ts_x_plate_ohm = 200; + tsc2005_config.ts_stab_time = 100; + } +#endif + } +} + +#if defined(CONFIG_CBUS_RETU) && defined(CONFIG_LEDS_OMAP_PWM) + +void retu_keypad_led_set_power(struct omap_pwm_led_platform_data *self, + int on_off) +{ + if (on_off) { + retu_write_reg(RETU_REG_CTRL_SET, 1 << 6); + msleep(2); + retu_write_reg(RETU_REG_CTRL_SET, 1 << 3); + } else { + retu_write_reg(RETU_REG_CTRL_CLR, (1 << 6) | (1 << 3)); + } +} + +static struct omap_pwm_led_platform_data n800_keypad_led_data = { + .name = "keypad", + .intensity_timer = 10, + .blink_timer = 9, + .set_power = retu_keypad_led_set_power, +}; + +static struct platform_device n800_keypad_led_device = { + .name = "omap_pwm_led", + .id = -1, + .dev = { + .platform_data = &n800_keypad_led_data, + }, +}; +#endif + +#if defined(CONFIG_TOUCHSCREEN_TSC2301) +static void __init n800_ts_set_config(void) +{ + const struct omap_lcd_config *conf; + + conf = omap_get_config(OMAP_TAG_LCD, struct omap_lcd_config); + if (conf != NULL) { + if (strcmp(conf->panel_name, "lph8923") == 0) { + tsc2301_config.ts_x_plate_ohm = 180; + tsc2301_config.ts_hw_avg = 8; + tsc2301_config.ts_max_pressure = 2048; + tsc2301_config.ts_touch_pressure = 400; + tsc2301_config.ts_stab_time = 100; + tsc2301_config.ts_pressure_fudge = 2; + tsc2301_config.ts_x_max = 4096; + tsc2301_config.ts_x_fudge = 4; + tsc2301_config.ts_y_max = 4096; + tsc2301_config.ts_y_fudge = 7; + } else if (strcmp(conf->panel_name, "ls041y3") == 0) { + tsc2301_config.ts_x_plate_ohm = 280; + tsc2301_config.ts_hw_avg = 8; + tsc2301_config.ts_touch_pressure = 400; + tsc2301_config.ts_max_pressure = 2048; + tsc2301_config.ts_stab_time = 1000; + tsc2301_config.ts_pressure_fudge = 2; + tsc2301_config.ts_x_max = 4096; + tsc2301_config.ts_x_fudge = 4; + tsc2301_config.ts_y_max = 4096; + tsc2301_config.ts_y_fudge = 7; + } else { + printk(KERN_ERR "Unknown panel type, set default " + "touchscreen configuration\n"); + tsc2301_config.ts_x_plate_ohm = 200; + tsc2301_config.ts_stab_time = 100; + } + } +} +#else +static inline void n800_ts_set_config(void) +{ +} +#endif + +static struct omap_gpio_switch n800_gpio_switches[] __initdata = { + { + .name = "bat_cover", + .gpio = -1, + .debounce_rising = 100, + .debounce_falling = 0, + .notify = n800_mmc_slot1_cover_handler, + .notify_data = NULL, + }, { + .name = "headphone", + .gpio = -1, + .debounce_rising = 200, + .debounce_falling = 200, + }, { + .name = "cam_act", + .gpio = -1, + .debounce_rising = 200, + .debounce_falling = 200, + }, { + .name = "cam_turn", + .gpio = -1, + .debounce_rising = 100, + .debounce_falling = 100, + }, +}; + +static struct platform_device *n800_devices[] __initdata = { +#if defined(CONFIG_CBUS_RETU) && defined(CONFIG_LEDS_OMAP_PWM) + &n800_keypad_led_device, +#endif +}; + +#ifdef CONFIG_MENELAUS +static int n800_auto_sleep_regulators(void) +{ + u32 val; + int ret; + + val = EN_VPLL_SLEEP | EN_VMMC_SLEEP \ + | EN_VAUX_SLEEP | EN_VIO_SLEEP \ + | EN_VMEM_SLEEP | EN_DC3_SLEEP \ + | EN_VC_SLEEP | EN_DC2_SLEEP; + + ret = menelaus_set_regulator_sleep(1, val); + if (ret < 0) { + printk(KERN_ERR "Could not set regulators to sleep on " + "menelaus: %u\n", ret); + return ret; + } + return 0; +} + +static int n800_auto_voltage_scale(void) +{ + int ret; + + ret = menelaus_set_vcore_hw(1400, 1050); + if (ret < 0) { + printk(KERN_ERR "Could not set VCORE voltage on " + "menelaus: %u\n", ret); + return ret; + } + return 0; +} + +static int n800_menelaus_init(struct device *dev) +{ + int ret; + + ret = n800_auto_voltage_scale(); + if (ret < 0) + return ret; + ret = n800_auto_sleep_regulators(); + if (ret < 0) + return ret; + return 0; +} + +static struct menelaus_platform_data n800_menelaus_platform_data = { + .late_init = n800_menelaus_init, +}; +#endif + +static struct i2c_board_info __initdata n800_i2c_board_info_1[] = { + { + I2C_BOARD_INFO("menelaus", 0x72), + .irq = INT_24XX_SYS_NIRQ, + .platform_data = &n800_menelaus_platform_data, + }, +}; + +extern struct tcm825x_platform_data n800_tcm825x_platform_data; + +static struct i2c_board_info __initdata_or_module n8x0_i2c_board_info_2[] = { + { + I2C_BOARD_INFO(TCM825X_NAME, TCM825X_I2C_ADDR), +#if defined (CONFIG_VIDEO_TCM825X) || defined (CONFIG_VIDEO_TCM825X_MODULE) + .platform_data = &n800_tcm825x_platform_data, +#endif + }, + { + I2C_BOARD_INFO("tsl2563", 0x29), + }, + { + I2C_BOARD_INFO("lp5521", 0x32), + }, +}; + + +static struct i2c_board_info __initdata_or_module n800_i2c_board_info_2[] = { + { + I2C_BOARD_INFO("tea5761", 0x10), + }, +}; + +static struct i2c_board_info __initdata_or_module n810_i2c_board_info_2[] = { + { + I2C_BOARD_INFO("lm8323", 0x45), + .irq = OMAP_GPIO_IRQ(109), + .platform_data = &lm8323_pdata, + }, +}; + +void __init nokia_n800_common_init(void) +{ + platform_add_devices(n800_devices, ARRAY_SIZE(n800_devices)); + + n800_flash_init(); + n800_mmc_init(); + n800_bt_init(); + n800_dsp_init(); + n800_usb_init(); + n800_cam_init(); + if (machine_is_nokia_n800()) + spi_register_board_info(n800_spi_board_info, + ARRAY_SIZE(n800_spi_board_info)); + if (machine_is_nokia_n810()) { + tsc2005_set_config(); + spi_register_board_info(n810_spi_board_info, + ARRAY_SIZE(n810_spi_board_info)); + } + omap_serial_init(); + omap_register_i2c_bus(1, 400, n800_i2c_board_info_1, + ARRAY_SIZE(n800_i2c_board_info_1)); + omap_register_i2c_bus(2, 400, n8x0_i2c_board_info_2, + ARRAY_SIZE(n800_i2c_board_info_2)); + if (machine_is_nokia_n800()) + i2c_register_board_info(2, n800_i2c_board_info_2, + ARRAY_SIZE(n800_i2c_board_info_2)); + if (machine_is_nokia_n810()) + i2c_register_board_info(2, n810_i2c_board_info_2, + ARRAY_SIZE(n810_i2c_board_info_2)); + + mipid_dev_init(); + blizzard_dev_init(); +} + +static void __init nokia_n800_init(void) +{ + nokia_n800_common_init(); + + n800_audio_init(&tsc2301_config); + n800_ts_set_config(); + tsc2301_dev_init(); + tea5761_dev_init(); + omap_register_gpio_switches(n800_gpio_switches, + ARRAY_SIZE(n800_gpio_switches)); +} + +void __init nokia_n800_map_io(void) +{ + omap_board_config = n800_config; + omap_board_config_size = ARRAY_SIZE(n800_config); + + omap2_set_globals_242x(); + omap2_map_common_io(); +} + +MACHINE_START(NOKIA_N800, "Nokia N800") + .phys_io = 0x48000000, + .io_pg_offst = ((0xd8000000) >> 18) & 0xfffc, + .boot_params = 0x80000100, + .map_io = nokia_n800_map_io, + .init_irq = nokia_n800_init_irq, + .init_machine = nokia_n800_init, + .timer = &omap_timer, +MACHINE_END diff --cc arch/arm/mach-omap2/board-n810.c index 8609a0b6591,00000000000..7984d43aeec mode 100644,000000..100644 --- a/arch/arm/mach-omap2/board-n810.c +++ b/arch/arm/mach-omap2/board-n810.c @@@ -1,47 -1,0 +1,47 @@@ +/* + * linux/arch/arm/mach-omap2/board-n810.c + * + * Copyright (C) 2007 Nokia + * Author: Lauri Leukkunen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include + - #include ++#include +#include +#include - #include - #include ++#include ++#include + +#include "board-n800.h" + +static void __init nokia_n810_init(void) +{ + nokia_n800_common_init(); +} + +MACHINE_START(NOKIA_N810, "Nokia N810") + .phys_io = 0x48000000, + .io_pg_offst = ((0xd8000000) >> 18) & 0xfffc, + .boot_params = 0x80000100, + .map_io = nokia_n800_map_io, + .init_irq = nokia_n800_init_irq, + .init_machine = nokia_n810_init, + .timer = &omap_timer, +MACHINE_END + +MACHINE_START(NOKIA_N810_WIMAX, "Nokia N810 WiMAX") + .phys_io = 0x48000000, + .io_pg_offst = ((0xd8000000) >> 18) & 0xfffc, + .boot_params = 0x80000100, + .map_io = nokia_n800_map_io, + .init_irq = nokia_n800_init_irq, + .init_machine = nokia_n810_init, + .timer = &omap_timer, +MACHINE_END diff --cc arch/arm/mach-omap2/board-omap2evm.c index 9eb93b6802c,00000000000..9bb8fbaf824 mode 100644,000000..100644 --- a/arch/arm/mach-omap2/board-omap2evm.c +++ b/arch/arm/mach-omap2/board-omap2evm.c @@@ -1,180 -1,0 +1,180 @@@ +/* + * linux/arch/arm/mach-omap2/board-omap2evm.c + * + * Copyright (C) 2008 Mistral Solutions Pvt Ltd + * + * Modified from mach-omap2/board-generic.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + - #include ++#include +#include +#include +#include + - #include - #include - #include - #include - #include ++#include ++#include ++#include ++#include ++#include + +static struct resource omap2evm_smc911x_resources[] = { + [0] = { + .start = OMAP2EVM_ETHR_START, + .end = (OMAP2EVM_ETHR_START + OMAP2EVM_ETHR_SIZE - 1), + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = OMAP_GPIO_IRQ(OMAP2EVM_ETHR_GPIO_IRQ), + .end = OMAP_GPIO_IRQ(OMAP2EVM_ETHR_GPIO_IRQ), + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device omap2evm_smc911x_device = { + .name = "smc911x", + .id = -1, + .num_resources = ARRAY_SIZE(omap2evm_smc911x_resources), + .resource = &omap2evm_smc911x_resources [0], +}; + +static inline void __init omap2evm_init_smc911x(void) +{ + int gpio = OMAP2EVM_ETHR_GPIO_IRQ; + int ret; + + ret = gpio_request(gpio, "smc911x IRQ"); + if (ret < 0) { + printk(KERN_ERR "Failed to request GPIO %d for smc911x IRQ\n", + gpio); + return; + } + gpio_direction_input(gpio); + +} + +static struct platform_device omap2_evm_lcd_device = { + .name = "omap2evm_lcd", + .id = -1, +}; + +static struct omap_lcd_config omap2_evm_lcd_config __initdata = { + .ctrl_name = "internal", +}; + +static int omap2evm_keymap[] = { + KEY(0, 0, KEY_LEFT), + KEY(0, 1, KEY_RIGHT), + KEY(0, 2, KEY_A), + KEY(0, 3, KEY_B), + KEY(1, 0, KEY_DOWN), + KEY(1, 1, KEY_UP), + KEY(1, 2, KEY_E), + KEY(1, 3, KEY_F), + KEY(2, 0, KEY_ENTER), + KEY(2, 1, KEY_I), + KEY(2, 2, KEY_J), + KEY(2, 3, KEY_K), + KEY(3, 0, KEY_M), + KEY(3, 1, KEY_N), + KEY(3, 2, KEY_O), + KEY(3, 3, KEY_P) +}; + +static struct omap_kp_platform_data omap2evm_kp_data = { + .rows = 4, + .cols = 4, + .keymap = omap2evm_keymap, + .keymapsize = ARRAY_SIZE(omap2evm_keymap), + .rep = 1, +}; + +static struct platform_device omap2evm_kp_device = { + .name = "omap_twl4030keypad", + .id = -1, + .dev = { + .platform_data = &omap2evm_kp_data, + }, +}; + +static void __init omap2_evm_init_irq(void) +{ + omap2_init_common_hw(NULL); + omap_init_irq(); + omap_gpio_init(); + omap2evm_init_smc911x(); +} + +static struct omap_uart_config omap2_evm_uart_config __initdata = { + .enabled_uarts = ((1 << 0) | (1 << 1) | (1 << 2)), +}; + +static struct omap_mmc_config omap2_evm_mmc_config __initdata = { + .mmc [0] = { + .enabled = 1, + .wire4 = 1, + }, +}; + +static struct omap_board_config_kernel omap2_evm_config[] __initdata = { + { OMAP_TAG_UART, &omap2_evm_uart_config }, + { OMAP_TAG_LCD, &omap2_evm_lcd_config }, + { OMAP_TAG_MMC, &omap2_evm_mmc_config }, +}; + +static int __init omap2_evm_i2c_init(void) +{ + /* + * Registering bus 2 first to avoid twl4030 misbehaving as OMAP2EVM + * has twl4030 on bus 2 + */ + omap_register_i2c_bus(2, 2600, NULL, 0); + omap_register_i2c_bus(1, 400, NULL, 0); + return 0; +} + +static struct platform_device *omap2_evm_devices[] __initdata = { + &omap2_evm_lcd_device, + &omap2evm_smc911x_device, + &omap2evm_kp_device, +}; + +static void __init omap2_evm_init(void) +{ + platform_add_devices(omap2_evm_devices, ARRAY_SIZE(omap2_evm_devices)); + omap_board_config = omap2_evm_config; + omap_board_config_size = ARRAY_SIZE(omap2_evm_config); + omap_serial_init(); + hsmmc_init(); +} + +static void __init omap2_evm_map_io(void) +{ + omap2_set_globals_243x(); + omap2_map_common_io(); +} + +arch_initcall(omap2_evm_i2c_init); + +MACHINE_START(OMAP2EVM, "OMAP2EVM Board") + /* Maintainer: Arun KS */ + .phys_io = 0x48000000, + .io_pg_offst = ((0xd8000000) >> 18) & 0xfffc, + .boot_params = 0x80000100, + .map_io = omap2_evm_map_io, + .init_irq = omap2_evm_init_irq, + .init_machine = omap2_evm_init, + .timer = &omap_timer, +MACHINE_END diff --cc arch/arm/mach-omap2/board-omap3beagle.c index 307cbf84de4,00000000000..85119ce5aa1 mode 100644,000000..100644 --- a/arch/arm/mach-omap2/board-omap3beagle.c +++ b/arch/arm/mach-omap2/board-omap3beagle.c @@@ -1,249 -1,0 +1,249 @@@ +/* + * linux/arch/arm/mach-omap2/board-omap3beagle.c + * + * Copyright (C) 2008 Texas Instruments + * + * Modified from mach-omap2/board-3430sdp.c + * + * Initial code: Syed Mohammed Khasim + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + - #include ++#include +#include +#include +#include +#include + - #include - #include - #include - #include - #include - #include - #include - #include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include + +#define GPMC_CS0_BASE 0x60 +#define GPMC_CS_SIZE 0x30 + +static struct mtd_partition omap3beagle_nand_partitions[] = { + /* All the partition sizes are listed in terms of NAND block size */ + { + .name = "X-Loader", + .offset = 0, + .size = 4*(64 * 2048), + .mask_flags = MTD_WRITEABLE, /* force read-only */ + }, + { + .name = "U-Boot", + .offset = MTDPART_OFS_APPEND, /* Offset = 0x80000 */ + .size = 15*(64 * 2048), + .mask_flags = MTD_WRITEABLE, /* force read-only */ + }, + { + .name = "U-Boot Env", + .offset = MTDPART_OFS_APPEND, /* Offset = 0x260000 */ + .size = 1*(64 * 2048), + }, + { + .name = "Kernel", + .offset = MTDPART_OFS_APPEND, /* Offset = 0x280000 */ + .size = 32*(64 * 2048), + }, + { + .name = "File System", + .offset = MTDPART_OFS_APPEND, /* Offset = 0x680000 */ + .size = MTDPART_SIZ_FULL, + }, +}; + +static struct omap_nand_platform_data omap3beagle_nand_data = { + .parts = omap3beagle_nand_partitions, + .nr_parts = ARRAY_SIZE(omap3beagle_nand_partitions), + .dma_channel = -1, /* disable DMA in OMAP NAND driver */ + .nand_setup = NULL, + .dev_ready = NULL, +}; + +static struct resource omap3beagle_nand_resource = { + .flags = IORESOURCE_MEM, +}; + +static struct platform_device omap3beagle_nand_device = { + .name = "omap2-nand", + .id = -1, + .dev = { + .platform_data = &omap3beagle_nand_data, + }, + .num_resources = 1, + .resource = &omap3beagle_nand_resource, +}; + +#include "sdram-micron-mt46h32m32lf-6.h" + +static struct omap_uart_config omap3_beagle_uart_config __initdata = { + .enabled_uarts = ((1 << 0) | (1 << 1) | (1 << 2)), +}; + +static int __init omap3_beagle_i2c_init(void) +{ + omap_register_i2c_bus(1, 2600, NULL, 0); +#ifdef CONFIG_I2C2_OMAP_BEAGLE + omap_register_i2c_bus(2, 400, NULL, 0); +#endif + omap_register_i2c_bus(3, 400, NULL, 0); + return 0; +} + +static void __init omap3_beagle_init_irq(void) +{ + omap2_init_common_hw(mt46h32m32lf6_sdrc_params); + omap_init_irq(); + omap_gpio_init(); +} + +static struct omap_mmc_config omap3beagle_mmc_config __initdata = { + .mmc [0] = { + .enabled = 1, + .wire4 = 1, + }, +}; + +static struct platform_device omap3_beagle_twl4030rtc_device = { + .name = "twl4030_rtc", + .id = -1, +}; + +static struct platform_device omap3_beagle_lcd_device = { + .name = "omap3beagle_lcd", + .id = -1, +}; + +static struct omap_lcd_config omap3_beagle_lcd_config __initdata = { + .ctrl_name = "internal", +}; + +struct gpio_led gpio_leds[] = { + { + .name = "beagleboard::usr0", + .default_trigger = "none", + .gpio = 150, + }, + { + .name = "beagleboard::usr1", + .default_trigger = "none", + .gpio = 149, + }, +}; + +static struct gpio_led_platform_data gpio_led_info = { + .leds = gpio_leds, + .num_leds = ARRAY_SIZE(gpio_leds), +}; + +static struct platform_device leds_gpio = { + .name = "leds-gpio", + .id = -1, + .dev = { + .platform_data = &gpio_led_info, + }, +}; + +static struct omap_board_config_kernel omap3_beagle_config[] __initdata = { + { OMAP_TAG_UART, &omap3_beagle_uart_config }, + { OMAP_TAG_MMC, &omap3beagle_mmc_config }, + { OMAP_TAG_LCD, &omap3_beagle_lcd_config }, +}; + +static struct platform_device *omap3_beagle_devices[] __initdata = { + &omap3_beagle_lcd_device, +#ifdef CONFIG_RTC_DRV_TWL4030 + &omap3_beagle_twl4030rtc_device, +#endif + &leds_gpio, +}; + +void __init omap3beagle_flash_init(void) +{ + u8 cs = 0; + u8 nandcs = GPMC_CS_NUM + 1; + + u32 gpmc_base_add = OMAP34XX_GPMC_VIRT; + + /* find out the chip-select on which NAND exists */ + while (cs < GPMC_CS_NUM) { + u32 ret = 0; + ret = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG1); + + if ((ret & 0xC00) == 0x800) { + printk(KERN_INFO "Found NAND on CS%d\n", cs); + if (nandcs > GPMC_CS_NUM) + nandcs = cs; + } + cs++; + } + + if (nandcs > GPMC_CS_NUM) { + printk(KERN_INFO "NAND: Unable to find configuration " + "in GPMC\n "); + return; + } + + if (nandcs < GPMC_CS_NUM) { + omap3beagle_nand_data.cs = nandcs; + omap3beagle_nand_data.gpmc_cs_baseaddr = (void *) + (gpmc_base_add + GPMC_CS0_BASE + nandcs * GPMC_CS_SIZE); + omap3beagle_nand_data.gpmc_baseaddr = (void *) (gpmc_base_add); + + printk(KERN_INFO "Registering NAND on CS%d\n", nandcs); + if (platform_device_register(&omap3beagle_nand_device) < 0) + printk(KERN_ERR "Unable to register NAND device\n"); + } +} + +static void __init omap3_beagle_init(void) +{ + platform_add_devices(omap3_beagle_devices, ARRAY_SIZE(omap3_beagle_devices)); + omap_board_config = omap3_beagle_config; + omap_board_config_size = ARRAY_SIZE(omap3_beagle_config); + omap_serial_init(); + hsmmc_init(); + usb_musb_init(); + usb_ehci_init(); + omap3beagle_flash_init(); +} + +arch_initcall(omap3_beagle_i2c_init); + +static void __init omap3_beagle_map_io(void) +{ + omap2_set_globals_343x(); + omap2_map_common_io(); +} + +MACHINE_START(OMAP3_BEAGLE, "OMAP3 Beagle Board") + /* Maintainer: Syed Mohammed Khasim - http://beagleboard.org */ + .phys_io = 0x48000000, + .io_pg_offst = ((0xd8000000) >> 18) & 0xfffc, + .boot_params = 0x80000100, + .map_io = omap3_beagle_map_io, + .init_irq = omap3_beagle_init_irq, + .init_machine = omap3_beagle_init, + .timer = &omap_timer, +MACHINE_END diff --cc arch/arm/mach-omap2/board-omap3evm-flash.c index 109d8f22abd,00000000000..0aa8ef26879 mode 100644,000000..100644 --- a/arch/arm/mach-omap2/board-omap3evm-flash.c +++ b/arch/arm/mach-omap2/board-omap3evm-flash.c @@@ -1,117 -1,0 +1,117 @@@ +/* + * board-omap3evm-flash.c + * + * Copyright (c) 2008 Texas Instruments, + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include - #include - #include - #include - #include ++#include ++#include ++#include ++#include + +static int omap3evm_onenand_setup(void __iomem *); + +static struct mtd_partition omap3evm_onenand_partitions[] = { + { + .name = "xloader", + .offset = 0, + .size = 4*(64*2048), + .mask_flags = MTD_WRITEABLE + }, + { + .name = "uboot", + .offset = MTDPART_OFS_APPEND, + .size = 15*(64*2048), + .mask_flags = MTD_WRITEABLE + }, + { + .name = "params", + .offset = MTDPART_OFS_APPEND, + .size = 1*(64*2048), + }, + { + .name = "linux", + .offset = MTDPART_OFS_APPEND, + .size = 40*(64*2048), + }, + { + .name = "jffs2", + .offset = MTDPART_OFS_APPEND, + .size = MTDPART_SIZ_FULL, + }, +}; + +static struct omap_onenand_platform_data omap3evm_onenand_data = { + .parts = omap3evm_onenand_partitions, + .nr_parts = ARRAY_SIZE(omap3evm_onenand_partitions), + .onenand_setup = omap3evm_onenand_setup, + .dma_channel = -1, /* disable DMA in OMAP OneNAND driver */ +}; + +static struct platform_device omap3evm_onenand_device = { + .name = "omap2-onenand", + .id = -1, + .dev = { + .platform_data = &omap3evm_onenand_data, + }, +}; + +/* + * omap3evm_onenand_setup - Set the onenand sync mode + * @onenand_base: The onenand base address in GPMC memory map + * + */ + +static int omap3evm_onenand_setup(void __iomem *onenand_base) + { + /* nothing is required to be setup for onenand as of now */ + return 0; +} + +void __init omap3evm_flash_init(void) +{ + u8 cs = 0; + u8 onenandcs = GPMC_CS_NUM + 1; + + while (cs < GPMC_CS_NUM) { + u32 ret = 0; + ret = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG7); + + /* + * xloader/Uboot would have programmed the oneNAND + * base address for us This is a ugly hack. The proper + * way of doing this is to pass the setup of u-boot up + * to kernel using kernel params - something on the + * lines of machineID. Check if oneNAND is configured + */ + if ((ret & 0x3F) == (ONENAND_MAP >> 24)) + onenandcs = cs; + cs++; + } + if (onenandcs > GPMC_CS_NUM) { + printk(KERN_INFO "OneNAND: Unable to find configuration " + " in GPMC\n "); + return; + } + + if (onenandcs < GPMC_CS_NUM) { + omap3evm_onenand_data.cs = onenandcs; + if (platform_device_register(&omap3evm_onenand_device) < 0) + printk(KERN_ERR "Unable to register OneNAND device\n"); + } +} + diff --cc arch/arm/mach-omap2/board-omap3evm.c index df81706d268,00000000000..4963f0fad31 mode 100644,000000..100644 --- a/arch/arm/mach-omap2/board-omap3evm.c +++ b/arch/arm/mach-omap2/board-omap3evm.c @@@ -1,248 -1,0 +1,248 @@@ +/* + * linux/arch/arm/mach-omap2/board-omap3evm.c + * + * Copyright (C) 2008 Texas Instruments + * + * Modified from mach-omap2/board-3430sdp.c + * + * Initial code: Syed Mohammed Khasim + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + - #include ++#include +#include +#include +#include + - #include - #include - #include - #include - #include - #include - #include - #include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include + +#include "sdram-micron-mt46h32m32lf-6.h" + +static struct resource omap3evm_smc911x_resources[] = { + [0] = { + .start = OMAP3EVM_ETHR_START, + .end = (OMAP3EVM_ETHR_START + OMAP3EVM_ETHR_SIZE - 1), + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = OMAP_GPIO_IRQ(OMAP3EVM_ETHR_GPIO_IRQ), + .end = OMAP_GPIO_IRQ(OMAP3EVM_ETHR_GPIO_IRQ), + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device omap3evm_smc911x_device = { + .name = "smc911x", + .id = -1, + .num_resources = ARRAY_SIZE(omap3evm_smc911x_resources), + .resource = &omap3evm_smc911x_resources [0], +}; + +static inline void __init omap3evm_init_smc911x(void) +{ + int eth_cs; + struct clk *l3ck; + unsigned int rate; + + eth_cs = OMAP3EVM_SMC911X_CS; + + l3ck = clk_get(NULL, "l3_ck"); + if (IS_ERR(l3ck)) + rate = 100000000; + else + rate = clk_get_rate(l3ck); + + if (omap_request_gpio(OMAP3EVM_ETHR_GPIO_IRQ) < 0) { + printk(KERN_ERR "Failed to request GPIO%d for smc911x IRQ\n", + OMAP3EVM_ETHR_GPIO_IRQ); + return; + } + + omap_set_gpio_direction(OMAP3EVM_ETHR_GPIO_IRQ, 1); +} + +static struct omap_uart_config omap3_evm_uart_config __initdata = { + .enabled_uarts = ((1 << 0) | (1 << 1) | (1 << 2)), +}; + +static int __init omap3_evm_i2c_init(void) +{ + omap_register_i2c_bus(1, 2600, NULL, 0); + omap_register_i2c_bus(2, 400, NULL, 0); + omap_register_i2c_bus(3, 400, NULL, 0); + return 0; +} + +static struct omap_mmc_config omap3_evm_mmc_config __initdata = { + .mmc [0] = { + .enabled = 1, + .wire4 = 1, + }, +}; + +static struct platform_device omap3_evm_lcd_device = { + .name = "omap3evm_lcd", + .id = -1, +}; + +static struct omap_lcd_config omap3_evm_lcd_config __initdata = { + .ctrl_name = "internal", +}; + +static struct platform_device omap3_evm_twl4030rtc_device = { + .name = "twl4030_rtc", + .id = -1, +}; + +static void ads7846_dev_init(void) +{ + if (omap_request_gpio(OMAP3_EVM_TS_GPIO) < 0) + printk(KERN_ERR "can't get ads7846 pen down GPIO\n"); + + omap_set_gpio_direction(OMAP3_EVM_TS_GPIO, 1); + + omap_set_gpio_debounce(OMAP3_EVM_TS_GPIO, 1); + omap_set_gpio_debounce_time(OMAP3_EVM_TS_GPIO, 0xa); +} + +static int ads7846_get_pendown_state(void) +{ + return !omap_get_gpio_datain(OMAP3_EVM_TS_GPIO); +} + +struct ads7846_platform_data ads7846_config = { + .get_pendown_state = ads7846_get_pendown_state, + .keep_vref_on = 1, +}; + +static struct omap2_mcspi_device_config ads7846_mcspi_config = { + .turbo_mode = 0, + .single_channel = 1, /* 0: slave, 1: master */ +}; + +struct spi_board_info omap3evm_spi_board_info[] = { + [0] = { + .modalias = "ads7846", + .bus_num = 1, + .chip_select = 0, + .max_speed_hz = 1500000, + .controller_data = &ads7846_mcspi_config, + .irq = OMAP_GPIO_IRQ(OMAP3_EVM_TS_GPIO), + .platform_data = &ads7846_config, + }, +}; + +static int omap3evm_keymap[] = { + KEY(0, 0, KEY_LEFT), + KEY(0, 1, KEY_RIGHT), + KEY(0, 2, KEY_A), + KEY(0, 3, KEY_B), + KEY(1, 0, KEY_DOWN), + KEY(1, 1, KEY_UP), + KEY(1, 2, KEY_E), + KEY(1, 3, KEY_F), + KEY(2, 0, KEY_ENTER), + KEY(2, 1, KEY_I), + KEY(2, 2, KEY_J), + KEY(2, 3, KEY_K), + KEY(3, 0, KEY_M), + KEY(3, 1, KEY_N), + KEY(3, 2, KEY_O), + KEY(3, 3, KEY_P) +}; + +static struct omap_kp_platform_data omap3evm_kp_data = { + .rows = 4, + .cols = 4, + .keymap = omap3evm_keymap, + .keymapsize = ARRAY_SIZE(omap3evm_keymap), + .rep = 1, +}; + +static struct platform_device omap3evm_kp_device = { + .name = "omap_twl4030keypad", + .id = -1, + .dev = { + .platform_data = &omap3evm_kp_data, + }, +}; + +static void __init omap3_evm_init_irq(void) +{ + omap2_init_common_hw(mt46h32m32lf6_sdrc_params); + omap_init_irq(); + omap_gpio_init(); + omap3evm_init_smc911x(); +} + +static struct omap_board_config_kernel omap3_evm_config[] __initdata = { + { OMAP_TAG_UART, &omap3_evm_uart_config }, + { OMAP_TAG_MMC, &omap3_evm_mmc_config }, + { OMAP_TAG_LCD, &omap3_evm_lcd_config }, +}; + +static struct platform_device *omap3_evm_devices[] __initdata = { + &omap3_evm_lcd_device, + &omap3evm_kp_device, +#ifdef CONFIG_RTC_DRV_TWL4030 + &omap3_evm_twl4030rtc_device, +#endif + &omap3evm_smc911x_device, +}; + +static void __init omap3_evm_init(void) +{ + platform_add_devices(omap3_evm_devices, ARRAY_SIZE(omap3_evm_devices)); + omap_board_config = omap3_evm_config; + omap_board_config_size = ARRAY_SIZE(omap3_evm_config); + + spi_register_board_info(omap3evm_spi_board_info, + ARRAY_SIZE(omap3evm_spi_board_info)); + + omap_serial_init(); + hsmmc_init(); + usb_musb_init(); + usb_ehci_init(); + omap3evm_flash_init(); + ads7846_dev_init(); +} + +arch_initcall(omap3_evm_i2c_init); + +static void __init omap3_evm_map_io(void) +{ + omap2_set_globals_343x(); + omap2_map_common_io(); +} + +MACHINE_START(OMAP3EVM, "OMAP3 EVM") + /* Maintainer: Syed Mohammed Khasim - Texas Instruments */ + .phys_io = 0x48000000, + .io_pg_offst = ((0xd8000000) >> 18) & 0xfffc, + .boot_params = 0x80000100, + .map_io = omap3_evm_map_io, + .init_irq = omap3_evm_init_irq, + .init_machine = omap3_evm_init, + .timer = &omap_timer, +MACHINE_END diff --cc arch/arm/mach-omap2/clock.c index 3e15681cd29,1d891e4a693..be5c6169f47 --- a/arch/arm/mach-omap2/clock.c +++ b/arch/arm/mach-omap2/clock.c @@@ -21,17 -21,16 +21,17 @@@ #include #include #include -#include - -#include +#include +#include - #include - #include - #include - #include - #include + #include ++#include + #include + #include ++#include #include - #include -#include "memory.h" ++#include #include "sdrc.h" #include "clock.h" #include "prm.h" diff --cc arch/arm/mach-omap2/clock24xx.c index 0d9d91f7072,295e671e9cf..c26d9d8e732 --- a/arch/arm/mach-omap2/clock24xx.c +++ b/arch/arm/mach-omap2/clock24xx.c @@@ -28,12 -28,12 +28,12 @@@ #include #include - #include - #include - #include ++#include + #include + #include #include -#include - #include -#include "memory.h" ++#include #include "clock.h" #include "clock24xx.h" #include "prm.h" diff --cc arch/arm/mach-omap2/clock34xx.c index 0caa4ddb492,3ff74952f83..21492c2f7a2 --- a/arch/arm/mach-omap2/clock34xx.c +++ b/arch/arm/mach-omap2/clock34xx.c @@@ -31,7 -31,7 +31,7 @@@ #include #include - #include -#include "memory.h" ++#include #include "clock.h" #include "clock34xx.h" #include "prm.h" diff --cc arch/arm/mach-omap2/clockdomain.c index 49741e8c9cd,00000000000..fa62f14de2a mode 100644,000000..100644 --- a/arch/arm/mach-omap2/clockdomain.c +++ b/arch/arm/mach-omap2/clockdomain.c @@@ -1,623 -1,0 +1,623 @@@ +/* + * OMAP2/3 clockdomain framework functions + * + * Copyright (C) 2008 Texas Instruments, Inc. + * Copyright (C) 2008 Nokia Corporation + * + * Written by Paul Walmsley and Jouni Högander + * + * 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. + */ +#ifdef CONFIG_OMAP_DEBUG_CLOCKDOMAIN +# define DEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + - #include ++#include + +#include "prm.h" +#include "prm-regbits-24xx.h" +#include "cm.h" + - #include - #include ++#include ++#include + +/* clkdm_list contains all registered struct clockdomains */ +static LIST_HEAD(clkdm_list); + +/* clkdm_mutex protects clkdm_list add and del ops */ +static DEFINE_MUTEX(clkdm_mutex); + +/* array of powerdomain deps to be added/removed when clkdm in hwsup mode */ +static struct clkdm_pwrdm_autodep *autodeps; + + +/* Private functions */ + +/* + * _autodep_lookup - resolve autodep pwrdm names to pwrdm pointers; store + * @autodep: struct clkdm_pwrdm_autodep * to resolve + * + * Resolve autodep powerdomain names to powerdomain pointers via + * pwrdm_lookup() and store the pointers in the autodep structure. An + * "autodep" is a powerdomain sleep/wakeup dependency that is + * automatically added and removed whenever clocks in the associated + * clockdomain are enabled or disabled (respectively) when the + * clockdomain is in hardware-supervised mode. Meant to be called + * once at clockdomain layer initialization, since these should remain + * fixed for a particular architecture. No return value. + */ +static void _autodep_lookup(struct clkdm_pwrdm_autodep *autodep) +{ + struct powerdomain *pwrdm; + + if (!autodep) + return; + + if (!omap_chip_is(autodep->omap_chip)) + return; + + pwrdm = pwrdm_lookup(autodep->pwrdm.name); + if (!pwrdm) { + pr_debug("clockdomain: _autodep_lookup: powerdomain %s " + "does not exist\n", autodep->pwrdm.name); + WARN_ON(1); + return; + } + autodep->pwrdm.ptr = pwrdm; + + return; +} + +/* + * _clkdm_add_autodeps - add auto sleepdeps/wkdeps to clkdm upon clock enable + * @clkdm: struct clockdomain * + * + * Add the "autodep" sleep & wakeup dependencies to clockdomain 'clkdm' + * in hardware-supervised mode. Meant to be called from clock framework + * when a clock inside clockdomain 'clkdm' is enabled. No return value. + */ +static void _clkdm_add_autodeps(struct clockdomain *clkdm) +{ + struct clkdm_pwrdm_autodep *autodep; + + for (autodep = autodeps; autodep->pwrdm.ptr; autodep++) { + if (!omap_chip_is(autodep->omap_chip)) + continue; + + pr_debug("clockdomain: adding %s sleepdep/wkdep for " + "pwrdm %s\n", autodep->pwrdm.ptr->name, + clkdm->pwrdm.ptr->name); + + pwrdm_add_sleepdep(clkdm->pwrdm.ptr, autodep->pwrdm.ptr); + pwrdm_add_wkdep(clkdm->pwrdm.ptr, autodep->pwrdm.ptr); + } +} + +/* + * _clkdm_add_autodeps - remove auto sleepdeps/wkdeps from clkdm + * @clkdm: struct clockdomain * + * + * Remove the "autodep" sleep & wakeup dependencies from clockdomain 'clkdm' + * in hardware-supervised mode. Meant to be called from clock framework + * when a clock inside clockdomain 'clkdm' is disabled. No return value. + */ +static void _clkdm_del_autodeps(struct clockdomain *clkdm) +{ + struct clkdm_pwrdm_autodep *autodep; + + for (autodep = autodeps; autodep->pwrdm.ptr; autodep++) { + if (!omap_chip_is(autodep->omap_chip)) + continue; + + pr_debug("clockdomain: removing %s sleepdep/wkdep for " + "pwrdm %s\n", autodep->pwrdm.ptr->name, + clkdm->pwrdm.ptr->name); + + pwrdm_del_sleepdep(clkdm->pwrdm.ptr, autodep->pwrdm.ptr); + pwrdm_del_wkdep(clkdm->pwrdm.ptr, autodep->pwrdm.ptr); + } +} + + +static struct clockdomain *_clkdm_lookup(const char *name) +{ + struct clockdomain *clkdm, *temp_clkdm; + + if (!name) + return NULL; + + clkdm = NULL; + + list_for_each_entry(temp_clkdm, &clkdm_list, node) { + if (!strcmp(name, temp_clkdm->name)) { + clkdm = temp_clkdm; + break; + } + } + + return clkdm; +} + + +/* Public functions */ + +/** + * clkdm_init - set up the clockdomain layer + * @clkdms: optional pointer to an array of clockdomains to register + * @init_autodeps: optional pointer to an array of autodeps to register + * + * Set up internal state. If a pointer to an array of clockdomains + * was supplied, loop through the list of clockdomains, register all + * that are available on the current platform. Similarly, if a + * pointer to an array of clockdomain-powerdomain autodependencies was + * provided, register those. No return value. + */ +void clkdm_init(struct clockdomain **clkdms, + struct clkdm_pwrdm_autodep *init_autodeps) +{ + struct clockdomain **c = NULL; + struct clkdm_pwrdm_autodep *autodep = NULL; + + if (clkdms) + for (c = clkdms; *c; c++) + clkdm_register(*c); + + autodeps = init_autodeps; + if (autodeps) + for (autodep = autodeps; autodep->pwrdm.ptr; autodep++) + _autodep_lookup(autodep); +} + +/** + * clkdm_register - register a clockdomain + * @clkdm: struct clockdomain * to register + * + * Adds a clockdomain to the internal clockdomain list. + * Returns -EINVAL if given a null pointer, -EEXIST if a clockdomain is + * already registered by the provided name, or 0 upon success. + */ +int clkdm_register(struct clockdomain *clkdm) +{ + int ret = -EINVAL; + struct powerdomain *pwrdm; + + if (!clkdm || !clkdm->name) + return -EINVAL; + + if (!omap_chip_is(clkdm->omap_chip)) + return -EINVAL; + + pwrdm = pwrdm_lookup(clkdm->pwrdm.name); + if (!pwrdm) { + pr_debug("clockdomain: clkdm_register %s: powerdomain %s " + "does not exist\n", clkdm->name, clkdm->pwrdm.name); + return -EINVAL; + } + clkdm->pwrdm.ptr = pwrdm; + + mutex_lock(&clkdm_mutex); + /* Verify that the clockdomain is not already registered */ + if (_clkdm_lookup(clkdm->name)) { + ret = -EEXIST; + goto cr_unlock; + }; + + list_add(&clkdm->node, &clkdm_list); + + pwrdm_add_clkdm(pwrdm, clkdm); + + pr_debug("clockdomain: registered %s\n", clkdm->name); + ret = 0; + +cr_unlock: + mutex_unlock(&clkdm_mutex); + + return ret; +} + +/** + * clkdm_unregister - unregister a clockdomain + * @clkdm: struct clockdomain * to unregister + * + * Removes a clockdomain from the internal clockdomain list. Returns + * -EINVAL if clkdm argument is NULL. + */ +int clkdm_unregister(struct clockdomain *clkdm) +{ + if (!clkdm) + return -EINVAL; + + pwrdm_del_clkdm(clkdm->pwrdm.ptr, clkdm); + + mutex_lock(&clkdm_mutex); + list_del(&clkdm->node); + mutex_unlock(&clkdm_mutex); + + pr_debug("clockdomain: unregistered %s\n", clkdm->name); + + return 0; +} + +/** + * clkdm_lookup - look up a clockdomain by name, return a pointer + * @name: name of clockdomain + * + * Find a registered clockdomain by its name. Returns a pointer to the + * struct clockdomain if found, or NULL otherwise. + */ +struct clockdomain *clkdm_lookup(const char *name) +{ + struct clockdomain *clkdm, *temp_clkdm; + + if (!name) + return NULL; + + clkdm = NULL; + + mutex_lock(&clkdm_mutex); + list_for_each_entry(temp_clkdm, &clkdm_list, node) { + if (!strcmp(name, temp_clkdm->name)) { + clkdm = temp_clkdm; + break; + } + } + mutex_unlock(&clkdm_mutex); + + return clkdm; +} + +/** + * clkdm_for_each - call function on each registered clockdomain + * @fn: callback function * + * + * Call the supplied function for each registered clockdomain. + * The callback function can return anything but 0 to bail + * out early from the iterator. The callback function is called with + * the clkdm_mutex held, so no clockdomain structure manipulation + * functions should be called from the callback, although hardware + * clockdomain control functions are fine. Returns the last return + * value of the callback function, which should be 0 for success or + * anything else to indicate failure; or -EINVAL if the function pointer + * is null. + */ +int clkdm_for_each(int (*fn)(struct clockdomain *clkdm)) +{ + struct clockdomain *clkdm; + int ret = 0; + + if (!fn) + return -EINVAL; + + mutex_lock(&clkdm_mutex); + list_for_each_entry(clkdm, &clkdm_list, node) { + ret = (*fn)(clkdm); + if (ret) + break; + } + mutex_unlock(&clkdm_mutex); + + return ret; +} + + +/** + * clkdm_get_pwrdm - return a ptr to the pwrdm that this clkdm resides in + * @clkdm: struct clockdomain * + * + * Return a pointer to the struct powerdomain that the specified clockdomain + * 'clkdm' exists in, or returns NULL if clkdm argument is NULL. + */ +struct powerdomain *clkdm_get_pwrdm(struct clockdomain *clkdm) +{ + if (!clkdm) + return NULL; + + return clkdm->pwrdm.ptr; +} + + +/* Hardware clockdomain control */ + +/** + * omap2_clkdm_clktrctrl_read - read the clkdm's current state transition mode + * @clk: struct clk * of a clockdomain + * + * Return the clockdomain's current state transition mode from the + * corresponding domain CM_CLKSTCTRL register. Returns -EINVAL if clk + * is NULL or the current mode upon success. + */ +static int omap2_clkdm_clktrctrl_read(struct clockdomain *clkdm) +{ + u32 v; + + if (!clkdm) + return -EINVAL; + + v = cm_read_mod_reg(clkdm->pwrdm.ptr->prcm_offs, CM_CLKSTCTRL); + v &= clkdm->clktrctrl_mask; + v >>= __ffs(clkdm->clktrctrl_mask); + + return v; +} + +/** + * omap2_clkdm_sleep - force clockdomain sleep transition + * @clkdm: struct clockdomain * + * + * Instruct the CM to force a sleep transition on the specified + * clockdomain 'clkdm'. Returns -EINVAL if clk is NULL or if + * clockdomain does not support software-initiated sleep; 0 upon + * success. + */ +int omap2_clkdm_sleep(struct clockdomain *clkdm) +{ + if (!clkdm) + return -EINVAL; + + if (!(clkdm->flags & CLKDM_CAN_FORCE_SLEEP)) { + pr_debug("clockdomain: %s does not support forcing " + "sleep via software\n", clkdm->name); + return -EINVAL; + } + + pr_debug("clockdomain: forcing sleep on %s\n", clkdm->name); + + if (cpu_is_omap24xx()) { + + cm_set_mod_reg_bits(OMAP24XX_FORCESTATE, + clkdm->pwrdm.ptr->prcm_offs, PM_PWSTCTRL); + + } else if (cpu_is_omap34xx()) { + + u32 v = (OMAP34XX_CLKSTCTRL_FORCE_SLEEP << + __ffs(clkdm->clktrctrl_mask)); + + cm_rmw_mod_reg_bits(clkdm->clktrctrl_mask, v, + clkdm->pwrdm.ptr->prcm_offs, CM_CLKSTCTRL); + + } else { + BUG(); + }; + + return 0; +} + +/** + * omap2_clkdm_wakeup - force clockdomain wakeup transition + * @clkdm: struct clockdomain * + * + * Instruct the CM to force a wakeup transition on the specified + * clockdomain 'clkdm'. Returns -EINVAL if clkdm is NULL or if the + * clockdomain does not support software-controlled wakeup; 0 upon + * success. + */ +int omap2_clkdm_wakeup(struct clockdomain *clkdm) +{ + if (!clkdm) + return -EINVAL; + + if (!(clkdm->flags & CLKDM_CAN_FORCE_WAKEUP)) { + pr_debug("clockdomain: %s does not support forcing " + "wakeup via software\n", clkdm->name); + return -EINVAL; + } + + pr_debug("clockdomain: forcing wakeup on %s\n", clkdm->name); + + if (cpu_is_omap24xx()) { + + cm_clear_mod_reg_bits(OMAP24XX_FORCESTATE, + clkdm->pwrdm.ptr->prcm_offs, PM_PWSTCTRL); + + } else if (cpu_is_omap34xx()) { + + u32 v = (OMAP34XX_CLKSTCTRL_FORCE_WAKEUP << + __ffs(clkdm->clktrctrl_mask)); + + cm_rmw_mod_reg_bits(clkdm->clktrctrl_mask, v, + clkdm->pwrdm.ptr->prcm_offs, CM_CLKSTCTRL); + + } else { + BUG(); + }; + + return 0; +} + +/** + * omap2_clkdm_allow_idle - enable hwsup idle transitions for clkdm + * @clkdm: struct clockdomain * + * + * Allow the hardware to automatically switch the clockdomain into + * active or idle states, as needed by downstream clocks. If the + * clockdomain has any downstream clocks enabled in the clock + * framework, wkdep/sleepdep autodependencies are added; this is so + * device drivers can read and write to the device. No return value. + */ +void omap2_clkdm_allow_idle(struct clockdomain *clkdm) +{ + u32 v; + + if (!clkdm) + return; + + if (!(clkdm->flags & CLKDM_CAN_ENABLE_AUTO)) { + pr_debug("clock: automatic idle transitions cannot be enabled " + "on clockdomain %s\n", clkdm->name); + return; + } + + pr_debug("clockdomain: enabling automatic idle transitions for %s\n", + clkdm->name); + + if (atomic_read(&clkdm->usecount) > 0) + _clkdm_add_autodeps(clkdm); + + if (cpu_is_omap24xx()) + v = OMAP24XX_CLKSTCTRL_ENABLE_AUTO; + else if (cpu_is_omap34xx()) + v = OMAP34XX_CLKSTCTRL_ENABLE_AUTO; + else + BUG(); + + + cm_rmw_mod_reg_bits(clkdm->clktrctrl_mask, + v << __ffs(clkdm->clktrctrl_mask), + clkdm->pwrdm.ptr->prcm_offs, + CM_CLKSTCTRL); +} + +/** + * omap2_clkdm_deny_idle - disable hwsup idle transitions for clkdm + * @clkdm: struct clockdomain * + * + * Prevent the hardware from automatically switching the clockdomain + * into inactive or idle states. If the clockdomain has downstream + * clocks enabled in the clock framework, wkdep/sleepdep + * autodependencies are removed. No return value. + */ +void omap2_clkdm_deny_idle(struct clockdomain *clkdm) +{ + u32 v; + + if (!clkdm) + return; + + if (!(clkdm->flags & CLKDM_CAN_DISABLE_AUTO)) { + pr_debug("clockdomain: automatic idle transitions cannot be " + "disabled on %s\n", clkdm->name); + return; + } + + pr_debug("clockdomain: disabling automatic idle transitions for %s\n", + clkdm->name); + + if (cpu_is_omap24xx()) + v = OMAP24XX_CLKSTCTRL_DISABLE_AUTO; + else if (cpu_is_omap34xx()) + v = OMAP34XX_CLKSTCTRL_DISABLE_AUTO; + else + BUG(); + + cm_rmw_mod_reg_bits(clkdm->clktrctrl_mask, + v << __ffs(clkdm->clktrctrl_mask), + clkdm->pwrdm.ptr->prcm_offs, CM_CLKSTCTRL); + + if (atomic_read(&clkdm->usecount) > 0) + _clkdm_del_autodeps(clkdm); +} + + +/* Clockdomain-to-clock framework interface code */ + +/** + * omap2_clkdm_clk_enable - add an enabled downstream clock to this clkdm + * @clkdm: struct clockdomain * + * @clk: struct clk * of the enabled downstream clock + * + * Increment the usecount of this clockdomain 'clkdm' and ensure that + * it is awake. Intended to be called by clk_enable() code. If the + * clockdomain is in software-supervised idle mode, force the + * clockdomain to wake. If the clockdomain is in hardware-supervised + * idle mode, add clkdm-pwrdm autodependencies, to ensure that devices + * in the clockdomain can be read from/written to by on-chip processors. + * Returns -EINVAL if passed null pointers; returns 0 upon success or + * if the clockdomain is in hwsup idle mode. + */ +int omap2_clkdm_clk_enable(struct clockdomain *clkdm, struct clk *clk) +{ + int v; + + /* + * XXX Rewrite this code to maintain a list of enabled + * downstream clocks for debugging purposes? + */ + + if (!clkdm || !clk) + return -EINVAL; + + if (atomic_inc_return(&clkdm->usecount) > 1) + return 0; + + /* Clockdomain now has one enabled downstream clock */ + + pr_debug("clockdomain: clkdm %s: clk %s now enabled\n", clkdm->name, + clk->name); + + v = omap2_clkdm_clktrctrl_read(clkdm); + + if ((cpu_is_omap34xx() && v == OMAP34XX_CLKSTCTRL_ENABLE_AUTO) || + (cpu_is_omap24xx() && v == OMAP24XX_CLKSTCTRL_ENABLE_AUTO)) + _clkdm_add_autodeps(clkdm); + else + omap2_clkdm_wakeup(clkdm); + + return 0; +} + +/** + * omap2_clkdm_clk_disable - remove an enabled downstream clock from this clkdm + * @clkdm: struct clockdomain * + * @clk: struct clk * of the disabled downstream clock + * + * Decrement the usecount of this clockdomain 'clkdm'. Intended to be + * called by clk_disable() code. If the usecount goes to 0, put the + * clockdomain to sleep (software-supervised mode) or remove the + * clkdm-pwrdm autodependencies (hardware-supervised mode). Returns + * -EINVAL if passed null pointers; -ERANGE if the clkdm usecount + * underflows and debugging is enabled; or returns 0 upon success or + * if the clockdomain is in hwsup idle mode. + */ +int omap2_clkdm_clk_disable(struct clockdomain *clkdm, struct clk *clk) +{ + int v; + + /* + * XXX Rewrite this code to maintain a list of enabled + * downstream clocks for debugging purposes? + */ + + if (!clkdm || !clk) + return -EINVAL; + +#ifdef DEBUG + if (atomic_read(&clkdm->usecount) == 0) { + WARN_ON(1); /* underflow */ + return -ERANGE; + } +#endif + + if (atomic_dec_return(&clkdm->usecount) > 0) + return 0; + + /* All downstream clocks of this clockdomain are now disabled */ + + pr_debug("clockdomain: clkdm %s: clk %s now disabled\n", clkdm->name, + clk->name); + + v = omap2_clkdm_clktrctrl_read(clkdm); + + if ((cpu_is_omap34xx() && v == OMAP34XX_CLKSTCTRL_ENABLE_AUTO) || + (cpu_is_omap24xx() && v == OMAP24XX_CLKSTCTRL_ENABLE_AUTO)) + _clkdm_del_autodeps(clkdm); + else + omap2_clkdm_sleep(clkdm); + + return 0; +} + diff --cc arch/arm/mach-omap2/clockdomains.h index 85590809ab2,00000000000..e17c3693542 mode 100644,000000..100644 --- a/arch/arm/mach-omap2/clockdomains.h +++ b/arch/arm/mach-omap2/clockdomains.h @@@ -1,307 -1,0 +1,307 @@@ +/* + * OMAP2/3 clockdomains + * + * Copyright (C) 2008 Texas Instruments, Inc. + * Copyright (C) 2008 Nokia Corporation + * + * Written by Paul Walmsley + */ + +#ifndef __ARCH_ARM_MACH_OMAP2_CLOCKDOMAINS_H +#define __ARCH_ARM_MACH_OMAP2_CLOCKDOMAINS_H + - #include ++#include + +/* + * OMAP2/3-common clockdomains + */ + +/* This is an implicit clockdomain - it is never defined as such in TRM */ +static struct clockdomain wkup_clkdm = { + .name = "wkup_clkdm", + .pwrdm = { .name = "wkup_pwrdm" }, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP24XX | CHIP_IS_OMAP3430), +}; + +/* + * 2420-only clockdomains + */ + +#if defined(CONFIG_ARCH_OMAP2420) + +static struct clockdomain mpu_2420_clkdm = { + .name = "mpu_clkdm", + .pwrdm = { .name = "mpu_pwrdm" }, + .flags = CLKDM_CAN_HWSUP, + .clktrctrl_mask = OMAP24XX_AUTOSTATE_MPU_MASK, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP2420), +}; + +static struct clockdomain iva1_2420_clkdm = { + .name = "iva1_clkdm", + .pwrdm = { .name = "dsp_pwrdm" }, + .flags = CLKDM_CAN_HWSUP_SWSUP, + .clktrctrl_mask = OMAP2420_AUTOSTATE_IVA_MASK, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP2420), +}; + +#endif /* CONFIG_ARCH_OMAP2420 */ + + +/* + * 2430-only clockdomains + */ + +#if defined(CONFIG_ARCH_OMAP2430) + +static struct clockdomain mpu_2430_clkdm = { + .name = "mpu_clkdm", + .pwrdm = { .name = "mpu_pwrdm" }, + .flags = CLKDM_CAN_HWSUP_SWSUP, + .clktrctrl_mask = OMAP24XX_AUTOSTATE_MPU_MASK, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP2430), +}; + +static struct clockdomain mdm_clkdm = { + .name = "mdm_clkdm", + .pwrdm = { .name = "mdm_pwrdm" }, + .flags = CLKDM_CAN_HWSUP_SWSUP, + .clktrctrl_mask = OMAP2430_AUTOSTATE_MDM_MASK, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP2430), +}; + +#endif /* CONFIG_ARCH_OMAP2430 */ + + +/* + * 24XX-only clockdomains + */ + +#if defined(CONFIG_ARCH_OMAP24XX) + +static struct clockdomain dsp_clkdm = { + .name = "dsp_clkdm", + .pwrdm = { .name = "dsp_pwrdm" }, + .flags = CLKDM_CAN_HWSUP_SWSUP, + .clktrctrl_mask = OMAP24XX_AUTOSTATE_DSP_MASK, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP24XX), +}; + +static struct clockdomain gfx_24xx_clkdm = { + .name = "gfx_clkdm", + .pwrdm = { .name = "gfx_pwrdm" }, + .flags = CLKDM_CAN_HWSUP_SWSUP, + .clktrctrl_mask = OMAP24XX_AUTOSTATE_GFX_MASK, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP24XX), +}; + +static struct clockdomain core_l3_24xx_clkdm = { + .name = "core_l3_clkdm", + .pwrdm = { .name = "core_pwrdm" }, + .flags = CLKDM_CAN_HWSUP, + .clktrctrl_mask = OMAP24XX_AUTOSTATE_L3_MASK, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP24XX), +}; + +static struct clockdomain core_l4_24xx_clkdm = { + .name = "core_l4_clkdm", + .pwrdm = { .name = "core_pwrdm" }, + .flags = CLKDM_CAN_HWSUP, + .clktrctrl_mask = OMAP24XX_AUTOSTATE_L4_MASK, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP24XX), +}; + +static struct clockdomain dss_24xx_clkdm = { + .name = "dss_clkdm", + .pwrdm = { .name = "core_pwrdm" }, + .flags = CLKDM_CAN_HWSUP, + .clktrctrl_mask = OMAP24XX_AUTOSTATE_DSS_MASK, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP24XX), +}; + +#endif /* CONFIG_ARCH_OMAP24XX */ + + +/* + * 34xx clockdomains + */ + +#if defined(CONFIG_ARCH_OMAP34XX) + +static struct clockdomain mpu_34xx_clkdm = { + .name = "mpu_clkdm", + .pwrdm = { .name = "mpu_pwrdm" }, + .flags = CLKDM_CAN_HWSUP | CLKDM_CAN_FORCE_WAKEUP, + .clktrctrl_mask = OMAP3430_CLKTRCTRL_MPU_MASK, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), +}; + +static struct clockdomain neon_clkdm = { + .name = "neon_clkdm", + .pwrdm = { .name = "neon_pwrdm" }, + .flags = CLKDM_CAN_HWSUP_SWSUP, + .clktrctrl_mask = OMAP3430_CLKTRCTRL_NEON_MASK, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), +}; + +static struct clockdomain iva2_clkdm = { + .name = "iva2_clkdm", + .pwrdm = { .name = "iva2_pwrdm" }, + .flags = CLKDM_CAN_HWSUP_SWSUP, + .clktrctrl_mask = OMAP3430_CLKTRCTRL_IVA2_MASK, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), +}; + +static struct clockdomain gfx_3430es1_clkdm = { + .name = "gfx_clkdm", + .pwrdm = { .name = "gfx_pwrdm" }, + .flags = CLKDM_CAN_HWSUP_SWSUP, + .clktrctrl_mask = OMAP3430ES1_CLKTRCTRL_GFX_MASK, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430ES1), +}; + +static struct clockdomain sgx_clkdm = { + .name = "sgx_clkdm", + .pwrdm = { .name = "sgx_pwrdm" }, + .flags = CLKDM_CAN_HWSUP_SWSUP, + .clktrctrl_mask = OMAP3430ES2_CLKTRCTRL_SGX_MASK, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430ES2), +}; + +/* + * The die-to-die clockdomain was documented in the 34xx ES1 TRM, but + * then that information was removed from the 34xx ES2+ TRM. It is + * unclear whether the core is still there, but the clockdomain logic + * is there, and must be programmed to an appropriate state if the + * CORE clockdomain is to become inactive. + */ +static struct clockdomain d2d_clkdm = { + .name = "d2d_clkdm", + .pwrdm = { .name = "core_pwrdm" }, + .flags = CLKDM_CAN_HWSUP, + .clktrctrl_mask = OMAP3430ES1_CLKTRCTRL_D2D_MASK, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), +}; + +static struct clockdomain core_l3_34xx_clkdm = { + .name = "core_l3_clkdm", + .pwrdm = { .name = "core_pwrdm" }, + .flags = CLKDM_CAN_HWSUP, + .clktrctrl_mask = OMAP3430_CLKTRCTRL_L3_MASK, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), +}; + +static struct clockdomain core_l4_34xx_clkdm = { + .name = "core_l4_clkdm", + .pwrdm = { .name = "core_pwrdm" }, + .flags = CLKDM_CAN_HWSUP, + .clktrctrl_mask = OMAP3430_CLKTRCTRL_L4_MASK, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), +}; + +static struct clockdomain dss_34xx_clkdm = { + .name = "dss_clkdm", + .pwrdm = { .name = "dss_pwrdm" }, + .flags = CLKDM_CAN_HWSUP_SWSUP, + .clktrctrl_mask = OMAP3430_CLKTRCTRL_DSS_MASK, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), +}; + +static struct clockdomain cam_clkdm = { + .name = "cam_clkdm", + .pwrdm = { .name = "cam_pwrdm" }, + .flags = CLKDM_CAN_HWSUP_SWSUP, + .clktrctrl_mask = OMAP3430_CLKTRCTRL_CAM_MASK, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), +}; + +static struct clockdomain usbhost_clkdm = { + .name = "usbhost_clkdm", + .pwrdm = { .name = "usbhost_pwrdm" }, + .flags = CLKDM_CAN_HWSUP_SWSUP, + .clktrctrl_mask = OMAP3430ES2_CLKTRCTRL_USBHOST_MASK, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430ES2), +}; + +static struct clockdomain per_clkdm = { + .name = "per_clkdm", + .pwrdm = { .name = "per_pwrdm" }, + .flags = CLKDM_CAN_HWSUP_SWSUP, + .clktrctrl_mask = OMAP3430_CLKTRCTRL_PER_MASK, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), +}; + +static struct clockdomain emu_clkdm = { + .name = "emu_clkdm", + .pwrdm = { .name = "emu_pwrdm" }, + .flags = CLKDM_CAN_ENABLE_AUTO | CLKDM_CAN_SWSUP, + .clktrctrl_mask = OMAP3430_CLKTRCTRL_EMU_MASK, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), +}; + +#endif /* CONFIG_ARCH_OMAP34XX */ + +/* + * Clockdomain-powerdomain hwsup dependencies (34XX only) + */ + +static struct clkdm_pwrdm_autodep clkdm_pwrdm_autodeps[] = { + { + .pwrdm = { .name = "mpu_pwrdm" }, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430) + }, + { + .pwrdm = { .name = "iva2_pwrdm" }, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430) + }, + { + .pwrdm = { .name = NULL }, + } +}; + +/* + * + */ + +static struct clockdomain *clockdomains_omap[] = { + + &wkup_clkdm, + +#ifdef CONFIG_ARCH_OMAP2420 + &mpu_2420_clkdm, + &iva1_2420_clkdm, +#endif + +#ifdef CONFIG_ARCH_OMAP2430 + &mpu_2430_clkdm, + &mdm_clkdm, +#endif + +#ifdef CONFIG_ARCH_OMAP24XX + &dsp_clkdm, + &gfx_24xx_clkdm, + &core_l3_24xx_clkdm, + &core_l4_24xx_clkdm, + &dss_24xx_clkdm, +#endif + +#ifdef CONFIG_ARCH_OMAP34XX + &mpu_34xx_clkdm, + &neon_clkdm, + &iva2_clkdm, + &gfx_3430es1_clkdm, + &sgx_clkdm, + &d2d_clkdm, + &core_l3_34xx_clkdm, + &core_l4_34xx_clkdm, + &dss_34xx_clkdm, + &cam_clkdm, + &usbhost_clkdm, + &per_clkdm, + &emu_clkdm, +#endif + + NULL, +}; + +#endif diff --cc arch/arm/mach-omap2/devices.c index d8fb3f888c5,7a7f0255907..cc5c294b9dc --- a/arch/arm/mach-omap2/devices.c +++ b/arch/arm/mach-omap2/devices.c @@@ -13,24 -13,26 +13,24 @@@ #include #include #include +#include - #include + #include -#include #include #include - #include - #include - #include - #include - #include + #include + #include + #include + #include ++#include -#if defined(CONFIG_I2C_OMAP) || defined(CONFIG_I2C_OMAP_MODULE) +#if defined(CONFIG_VIDEO_OMAP2) || defined(CONFIG_VIDEO_OMAP2_MODULE) -#define OMAP2_I2C_BASE2 0x48072000 -#define OMAP2_I2C_INT2 57 - -static struct resource i2c_resources2[] = { +static struct resource cam_resources[] = { { - .start = OMAP2_I2C_BASE2, - .end = OMAP2_I2C_BASE2 + 0x3f, + .start = OMAP24XX_CAMERA_BASE, + .end = OMAP24XX_CAMERA_BASE + 0xfff, .flags = IORESOURCE_MEM, }, { @@@ -149,9 -140,9 +149,9 @@@ static inline void omap_init_sti(void static inline void omap_init_sti(void) {} #endif -#if defined(CONFIG_SPI_OMAP24XX) +#if defined(CONFIG_SPI_OMAP24XX) || defined(CONFIG_SPI_OMAP24XX_MODULE) - #include + #include #define OMAP2_MCSPI1_BASE 0x48098000 #define OMAP2_MCSPI2_BASE 0x4809a000 diff --cc arch/arm/mach-omap2/gpmc.c index 11f19ff7225,f51d69bc457..9d6c916ae3e --- a/arch/arm/mach-omap2/gpmc.c +++ b/arch/arm/mach-omap2/gpmc.c @@@ -17,15 -15,21 +17,15 @@@ #include #include #include +#include +#include -#include #include - #include + #include - #include -#undef DEBUG - -#ifdef CONFIG_ARCH_OMAP2420 -#define GPMC_BASE 0x6800a000 -#endif - -#ifdef CONFIG_ARCH_OMAP2430 -#define GPMC_BASE 0x6E000000 -#endif ++#include +/* GPMC register offsets */ #define GPMC_REVISION 0x00 #define GPMC_SYSCONFIG 0x10 #define GPMC_SYSSTATUS 0x14 diff --cc arch/arm/mach-omap2/hsmmc.c index e16d6c0504f,00000000000..3805b930e0a mode 100644,000000..100644 --- a/arch/arm/mach-omap2/hsmmc.c +++ b/arch/arm/mach-omap2/hsmmc.c @@@ -1,293 -1,0 +1,293 @@@ +/* + * linux/arch/arm/mach-omap2/board-sdp-hsmmc.c + * + * Copyright (C) 2007-2008 Texas Instruments + * Copyright (C) 2008 Nokia Corporation + * Author: Texas Instruments + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include - #include - #include - #include ++#include ++#include ++#include + +#if defined(CONFIG_MMC_OMAP_HS) || defined(CONFIG_MMC_OMAP_HS_MODULE) + +#define VMMC1_DEV_GRP 0x27 +#define P1_DEV_GRP 0x20 +#define VMMC1_DEDICATED 0x2A +#define VSEL_3V 0x02 +#define VSEL_18V 0x00 +#define TWL_GPIO_PUPDCTR1 0x13 +#define TWL_GPIO_IMR1A 0x1C +#define TWL_GPIO_ISR1A 0x19 +#define LDO_CLR 0x00 +#define VSEL_S2_CLR 0x40 +#define GPIO_0_BIT_POS (1 << 0) +#define MMC1_CD_IRQ 0 +#define MMC2_CD_IRQ 1 + +#define OMAP2_CONTROL_DEVCONF0 0x48002274 +#define OMAP2_CONTROL_DEVCONF1 0x490022E8 + +#define OMAP2_CONTROL_DEVCONF0_LBCLK (1 << 24) +#define OMAP2_CONTROL_DEVCONF1_ACTOV (1 << 31) + +#define OMAP2_CONTROL_PBIAS_VMODE (1 << 0) +#define OMAP2_CONTROL_PBIAS_PWRDNZ (1 << 1) +#define OMAP2_CONTROL_PBIAS_SCTRL (1 << 2) + +static int hsmmc_card_detect(int irq) +{ + return twl4030_get_gpio_datain(irq - TWL4030_GPIO_IRQ_BASE); +} + +/* + * MMC Slot Initialization. + */ +static int hsmmc_late_init(struct device *dev) +{ + int ret = 0; + + /* + * Configure TWL4030 GPIO parameters for MMC hotplug irq + */ + ret = twl4030_request_gpio(MMC1_CD_IRQ); + if (ret) + goto err; + + ret = twl4030_set_gpio_edge_ctrl(MMC1_CD_IRQ, + TWL4030_GPIO_EDGE_RISING | TWL4030_GPIO_EDGE_FALLING); + if (ret) + goto err; + + ret = twl4030_i2c_write_u8(TWL4030_MODULE_GPIO, 0x02, + TWL_GPIO_PUPDCTR1); + if (ret) + goto err; + + ret = twl4030_set_gpio_debounce(MMC1_CD_IRQ, TWL4030_GPIO_IS_ENABLE); + if (ret) + goto err; + + return ret; + +err: + dev_err(dev, "Failed to configure TWL4030 GPIO IRQ\n"); + return ret; +} + +static void hsmmc_cleanup(struct device *dev) +{ + int ret = 0; + + ret = twl4030_free_gpio(MMC1_CD_IRQ); + if (ret) + dev_err(dev, "Failed to configure TWL4030 GPIO IRQ\n"); +} + +#ifdef CONFIG_PM + +/* + * To mask and unmask MMC Card Detect Interrupt + * mask : 1 + * unmask : 0 + */ +static int mask_cd_interrupt(int mask) +{ + u8 reg = 0, ret = 0; + + ret = twl4030_i2c_read_u8(TWL4030_MODULE_GPIO, ®, TWL_GPIO_IMR1A); + if (ret) + goto err; + + reg = (mask == 1) ? (reg | GPIO_0_BIT_POS) : (reg & ~GPIO_0_BIT_POS); + + ret = twl4030_i2c_write_u8(TWL4030_MODULE_GPIO, reg, TWL_GPIO_IMR1A); + if (ret) + goto err; + + ret = twl4030_i2c_read_u8(TWL4030_MODULE_GPIO, ®, TWL_GPIO_ISR1A); + if (ret) + goto err; + + reg = (mask == 1) ? (reg | GPIO_0_BIT_POS) : (reg & ~GPIO_0_BIT_POS); + + ret = twl4030_i2c_write_u8(TWL4030_MODULE_GPIO, reg, TWL_GPIO_ISR1A); + if (ret) + goto err; + +err: + return ret; +} + +static int hsmmc_suspend(struct device *dev, int slot) +{ + int ret = 0; + + disable_irq(TWL4030_GPIO_IRQ_NO(MMC1_CD_IRQ)); + ret = mask_cd_interrupt(1); + + return ret; +} + +static int hsmmc_resume(struct device *dev, int slot) +{ + int ret = 0; + + enable_irq(TWL4030_GPIO_IRQ_NO(MMC1_CD_IRQ)); + ret = mask_cd_interrupt(0); + + return ret; +} + +#endif + +static int hsmmc_set_power(struct device *dev, int slot, int power_on, + int vdd) +{ + u32 vdd_sel = 0, devconf = 0, reg = 0; + int ret = 0; + + /* REVISIT: Using address directly till the control.h defines + * are settled. + */ +#if defined(CONFIG_ARCH_OMAP2430) + #define OMAP2_CONTROL_PBIAS 0x490024A0 +#else + #define OMAP2_CONTROL_PBIAS 0x48002520 +#endif + + if (power_on) { + if (cpu_is_omap24xx()) + devconf = omap_readl(OMAP2_CONTROL_DEVCONF1); + else + devconf = omap_readl(OMAP2_CONTROL_DEVCONF0); + + switch (1 << vdd) { + case MMC_VDD_33_34: + case MMC_VDD_32_33: + vdd_sel = VSEL_3V; + if (cpu_is_omap24xx()) + devconf |= OMAP2_CONTROL_DEVCONF1_ACTOV; + break; + case MMC_VDD_165_195: + vdd_sel = VSEL_18V; + if (cpu_is_omap24xx()) + devconf &= ~OMAP2_CONTROL_DEVCONF1_ACTOV; + } + + if (cpu_is_omap24xx()) + omap_writel(devconf, OMAP2_CONTROL_DEVCONF1); + else + omap_writel(devconf | OMAP2_CONTROL_DEVCONF0_LBCLK, + OMAP2_CONTROL_DEVCONF0); + + reg = omap_readl(OMAP2_CONTROL_PBIAS); + reg |= OMAP2_CONTROL_PBIAS_SCTRL; + omap_writel(reg, OMAP2_CONTROL_PBIAS); + + reg = omap_readl(OMAP2_CONTROL_PBIAS); + reg &= ~OMAP2_CONTROL_PBIAS_PWRDNZ; + omap_writel(reg, OMAP2_CONTROL_PBIAS); + + ret = twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, + P1_DEV_GRP, VMMC1_DEV_GRP); + if (ret) + goto err; + + ret = twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, + vdd_sel, VMMC1_DEDICATED); + if (ret) + goto err; + + msleep(100); + reg = omap_readl(OMAP2_CONTROL_PBIAS); + reg |= (OMAP2_CONTROL_PBIAS_SCTRL | + OMAP2_CONTROL_PBIAS_PWRDNZ); + if (vdd_sel == VSEL_18V) + reg &= ~OMAP2_CONTROL_PBIAS_VMODE; + else + reg |= OMAP2_CONTROL_PBIAS_VMODE; + omap_writel(reg, OMAP2_CONTROL_PBIAS); + + return ret; + + } else { + /* Power OFF */ + + /* For MMC1, Toggle PBIAS before every power up sequence */ + reg = omap_readl(OMAP2_CONTROL_PBIAS); + reg &= ~OMAP2_CONTROL_PBIAS_PWRDNZ; + omap_writel(reg, OMAP2_CONTROL_PBIAS); + + ret = twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, + LDO_CLR, VMMC1_DEV_GRP); + if (ret) + goto err; + + ret = twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, + VSEL_S2_CLR, VMMC1_DEDICATED); + if (ret) + goto err; + + /* 100ms delay required for PBIAS configuration */ + msleep(100); + reg = omap_readl(OMAP2_CONTROL_PBIAS); + reg |= (OMAP2_CONTROL_PBIAS_VMODE | + OMAP2_CONTROL_PBIAS_PWRDNZ | + OMAP2_CONTROL_PBIAS_SCTRL); + omap_writel(reg, OMAP2_CONTROL_PBIAS); + } + + return 0; + +err: + return 1; +} + +static struct omap_mmc_platform_data hsmmc_data = { + .nr_slots = 1, + .switch_slot = NULL, + .init = hsmmc_late_init, + .cleanup = hsmmc_cleanup, +#ifdef CONFIG_PM + .suspend = hsmmc_suspend, + .resume = hsmmc_resume, +#endif + .slots[0] = { + .set_power = hsmmc_set_power, + .set_bus_mode = NULL, + .get_ro = NULL, + .get_cover_state = NULL, + .ocr_mask = MMC_VDD_32_33 | MMC_VDD_33_34 | + MMC_VDD_165_195, + .name = "first slot", + + .card_detect_irq = TWL4030_GPIO_IRQ_NO(MMC1_CD_IRQ), + .card_detect = hsmmc_card_detect, + }, +}; + +void __init hsmmc_init(void) +{ + omap_set_mmc_info(1, &hsmmc_data); +} + +#else + +void __init hsmmc_init(void) +{ + +} + +#endif diff --cc arch/arm/mach-omap2/id.c index c7f9ab7efdc,a5d4526ac4d..a526c0fc628 --- a/arch/arm/mach-omap2/id.c +++ b/arch/arm/mach-omap2/id.c @@@ -14,17 -14,27 +14,17 @@@ #include #include #include +#include - #include - #include - #include -#include - ++#include + #include + #include -#if defined(CONFIG_ARCH_OMAP2420) -#define TAP_BASE io_p2v(0x48014000) -#elif defined(CONFIG_ARCH_OMAP2430) -#define TAP_BASE io_p2v(0x4900A000) -#elif defined(CONFIG_ARCH_OMAP34XX) -#define TAP_BASE io_p2v(0x4830A000) -#endif +static u32 class; +static void __iomem *tap_base; +static u16 tap_prod_id; #define OMAP_TAP_IDCODE 0x0204 -#if defined(CONFIG_ARCH_OMAP34XX) -#define OMAP_TAP_PROD_ID 0x0210 -#else -#define OMAP_TAP_PROD_ID 0x0208 -#endif - #define OMAP_TAP_DIE_ID_0 0x0218 #define OMAP_TAP_DIE_ID_1 0x021C #define OMAP_TAP_DIE_ID_2 0x0220 diff --cc arch/arm/mach-omap2/io.c index e0191aa3a14,987351f07d7..adbe21fcd68 --- a/arch/arm/mach-omap2/io.c +++ b/arch/arm/mach-omap2/io.c @@@ -18,25 -15,21 +18,25 @@@ #include #include #include +#include #include -#include #include - #include - #include - #include - #include - #include - + #include + #include ++#include ++#include ++#include + +#include "clock.h" + - #include ++#include + +#include "powerdomains.h" - #include -extern void omap_sram_init(void); -extern int omap2_clk_init(void); -extern void omap2_check_revision(void); -extern void omap2_init_memory(void); -extern void gpmc_init(void); -extern void omapfb_reserve_sdram(void); ++#include +#include "clockdomains.h" /* * The machine specific code may provide the extra mapping besides the diff --cc arch/arm/mach-omap2/irq.c index 441463e16e2,9ef15b31d8f..82e954a7221 --- a/arch/arm/mach-omap2/irq.c +++ b/arch/arm/mach-omap2/irq.c @@@ -13,23 -13,17 +13,23 @@@ #include #include #include - #include + #include #include -#include -#include +#include +#include -#define INTC_REVISION 0x0000 -#define INTC_SYSCONFIG 0x0010 -#define INTC_SYSSTATUS 0x0014 -#define INTC_CONTROL 0x0048 -#define INTC_MIR_CLEAR0 0x0088 -#define INTC_MIR_SET0 0x008c +/* selected INTC register offsets */ + +#define INTC_REVISION 0x0000 +#define INTC_SYSCONFIG 0x0010 +#define INTC_SYSSTATUS 0x0014 +#define INTC_CONTROL 0x0048 +#define INTC_MIR_CLEAR0 0x0088 +#define INTC_MIR_SET0 0x008c +#define INTC_PENDING_IRQ0 0x0098 + +/* Number of IRQ state bits in each MIR register */ +#define IRQ_BITS_PER_REG 32 /* * OMAP2 has a number of different interrupt controllers, each interrupt diff --cc arch/arm/mach-omap2/mmu.c index 11718ce5afb,00000000000..6fc3303af51 mode 100644,000000..100644 --- a/arch/arm/mach-omap2/mmu.c +++ b/arch/arm/mach-omap2/mmu.c @@@ -1,330 -1,0 +1,330 @@@ +/* + * linux/arch/arm/mach-omap2/mmu.c + * + * Support for non-MPU OMAP2 MMUs. + * + * Copyright (C) 2002-2007 Nokia Corporation + * + * Written by Toshihiro Kobayashi + * and Paul Mundt + * + * TWL support: Hiroshi DOYU + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "mmu.h" - #include ++#include +#include +#include + +static void *dspvect_page; +#define DSP_INIT_PAGE 0xfff000 + +static inline void +omap2_mmu_read_tlb(struct omap_mmu *mmu, struct cam_ram_regset *cr) +{ + cr->cam = omap_mmu_read_reg(mmu, OMAP_MMU_READ_CAM); + cr->ram = omap_mmu_read_reg(mmu, OMAP_MMU_READ_RAM); +} + +static inline void +omap2_mmu_load_tlb(struct omap_mmu *mmu, struct cam_ram_regset *cr) +{ + /* Set the CAM and RAM entries */ + omap_mmu_write_reg(mmu, cr->cam | OMAP_MMU_CAM_V, OMAP_MMU_CAM); + omap_mmu_write_reg(mmu, cr->ram, OMAP_MMU_RAM); +} + +static void exmap_setup_iomap_page(struct omap_mmu *mmu, unsigned long phys, + unsigned long dsp_io_adr, int index) +{ + unsigned long dspadr; + void *virt; + struct omap_mmu_tlb_entry tlb_ent; + + dspadr = (IOMAP_VAL << 18) + (dsp_io_adr << 1); + virt = omap_mmu_to_virt(mmu, dspadr); + exmap_set_armmmu(mmu, (unsigned long)virt, phys, PAGE_SIZE); + INIT_EXMAP_TBL_ENTRY_4KB_PRESERVED(mmu->exmap_tbl + index, NULL, virt); + INIT_TLB_ENTRY_4KB_ES32_PRESERVED(&tlb_ent, dspadr, phys); + omap_mmu_load_pte_entry(mmu, &tlb_ent); +} + +static void exmap_clear_iomap_page(struct omap_mmu *mmu, + unsigned long dsp_io_adr) +{ + unsigned long dspadr; + void *virt; + + dspadr = (IOMAP_VAL << 18) + (dsp_io_adr << 1); + virt = omap_mmu_to_virt(mmu, dspadr); + exmap_clear_armmmu(mmu, (unsigned long)virt, PAGE_SIZE); + /* DSP MMU is shutting down. not handled here. */ +} + +#define OMAP24XX_MAILBOX_BASE (L4_24XX_BASE + 0x94000) +#define OMAP2420_GPT5_BASE (L4_24XX_BASE + 0x7c000) +#define OMAP2420_GPT6_BASE (L4_24XX_BASE + 0x7e000) +#define OMAP2420_GPT7_BASE (L4_24XX_BASE + 0x80000) +#define OMAP2420_GPT8_BASE (L4_24XX_BASE + 0x82000) +#define OMAP24XX_EAC_BASE (L4_24XX_BASE + 0x90000) +#define OMAP24XX_STI_BASE (L4_24XX_BASE + 0x68000) +#define OMAP24XX_STI_CH_BASE (L4_24XX_BASE + 0x0c000000) + +static int exmap_setup_preserved_entries(struct omap_mmu *mmu) +{ + int i, n = 0; + + exmap_setup_preserved_mem_page(mmu, dspvect_page, DSP_INIT_PAGE, n++); + + /* REVISIT: This will need to be revisited for 3430 */ + exmap_setup_iomap_page(mmu, OMAP2_PRCM_BASE, 0x7000, n++); + exmap_setup_iomap_page(mmu, OMAP24XX_MAILBOX_BASE, 0x11000, n++); + + if (cpu_is_omap2420()) { + exmap_setup_iomap_page(mmu, OMAP2420_GPT5_BASE, 0xe000, n++); + exmap_setup_iomap_page(mmu, OMAP2420_GPT6_BASE, 0xe800, n++); + exmap_setup_iomap_page(mmu, OMAP2420_GPT7_BASE, 0xf000, n++); + exmap_setup_iomap_page(mmu, OMAP2420_GPT8_BASE, 0xf800, n++); + exmap_setup_iomap_page(mmu, OMAP24XX_EAC_BASE, 0x10000, n++); + exmap_setup_iomap_page(mmu, OMAP24XX_STI_BASE, 0xc800, n++); + for (i = 0; i < 5; i++) + exmap_setup_preserved_mem_page(mmu, + __va(OMAP24XX_STI_CH_BASE + i*SZ_4K), + 0xfb0000 + i*SZ_4K, n++); + } + + return n; +} + +static void exmap_clear_preserved_entries(struct omap_mmu *mmu) +{ + int i; + + exmap_clear_iomap_page(mmu, 0x7000); /* PRCM registers */ + exmap_clear_iomap_page(mmu, 0x11000); /* MAILBOX registers */ + + if (cpu_is_omap2420()) { + exmap_clear_iomap_page(mmu, 0xe000); /* GPT5 */ + exmap_clear_iomap_page(mmu, 0xe800); /* GPT6 */ + exmap_clear_iomap_page(mmu, 0xf000); /* GPT7 */ + exmap_clear_iomap_page(mmu, 0xf800); /* GPT8 */ + exmap_clear_iomap_page(mmu, 0x10000); /* EAC */ + exmap_clear_iomap_page(mmu, 0xc800); /* STI */ + for (i = 0; i < 5; i++) /* STI CH */ + exmap_clear_mem_page(mmu, 0xfb0000 + i*SZ_4K); + } + + exmap_clear_mem_page(mmu, DSP_INIT_PAGE); +} + +#define MMU_IRQ_MASK \ + (OMAP_MMU_IRQ_MULTIHITFAULT | \ + OMAP_MMU_IRQ_TABLEWALKFAULT | \ + OMAP_MMU_IRQ_EMUMISS | \ + OMAP_MMU_IRQ_TRANSLATIONFAULT) + +static int omap2_mmu_startup(struct omap_mmu *mmu) +{ + u32 rev = omap_mmu_read_reg(mmu, OMAP_MMU_REVISION); + + pr_info("MMU: OMAP %s MMU initialized (HW v%d.%d)\n", mmu->name, + (rev >> 4) & 0xf, rev & 0xf); + + dspvect_page = (void *)__get_dma_pages(GFP_KERNEL, 0); + if (dspvect_page == NULL) { + dev_err(mmu->dev, "MMU %s: failed to allocate memory " + "for vector table\n", mmu->name); + return -ENOMEM; + } + + mmu->nr_exmap_preserved = exmap_setup_preserved_entries(mmu); + + omap_mmu_write_reg(mmu, MMU_IRQ_MASK, OMAP_MMU_IRQENABLE); + + return 0; +} + +static void omap2_mmu_shutdown(struct omap_mmu *mmu) +{ + exmap_clear_preserved_entries(mmu); + + if (dspvect_page != NULL) { + unsigned long virt; + + down_read(&mmu->exmap_sem); + + virt = (unsigned long)omap_mmu_to_virt(mmu, DSP_INIT_PAGE); + flush_tlb_kernel_range(virt, virt + PAGE_SIZE); + free_page((unsigned long)dspvect_page); + dspvect_page = NULL; + + up_read(&mmu->exmap_sem); + } +} + +static ssize_t omap2_mmu_show(struct omap_mmu *mmu, char *buf, + struct omap_mmu_tlb_lock *tlb_lock) +{ + int i, len; + + len = sprintf(buf, "P: preserved, V: valid\n" + "B: big endian, L:little endian, " + "M: mixed page attribute\n" + "ety P V size cam_va ram_pa E ES M\n"); + /* 00: P V 4KB 0x300000 0x10171800 B 16 M */ + + for (i = 0; i < mmu->nr_tlb_entries; i++) { + struct omap_mmu_tlb_entry ent; + struct cam_ram_regset cr; + struct omap_mmu_tlb_lock entry_lock; + char *pgsz_str, *elsz_str; + + /* read a TLB entry */ + entry_lock.base = tlb_lock->base; + entry_lock.victim = i; + omap_mmu_read_tlb(mmu, &entry_lock, &cr); + + ent.pgsz = cr.cam & OMAP_MMU_CAM_PAGESIZE_MASK; + ent.prsvd = cr.cam & OMAP_MMU_CAM_P; + ent.valid = cr.cam & OMAP_MMU_CAM_V; + ent.va = cr.cam & OMAP_MMU_CAM_VATAG_MASK; + ent.endian = cr.ram & OMAP_MMU_RAM_ENDIANNESS; + ent.elsz = cr.ram & OMAP_MMU_RAM_ELEMENTSIZE_MASK; + ent.pa = cr.ram & OMAP_MMU_RAM_PADDR_MASK; + ent.mixed = cr.ram & OMAP_MMU_RAM_MIXED; + + pgsz_str = (ent.pgsz == OMAP_MMU_CAM_PAGESIZE_16MB) ? "64MB": + (ent.pgsz == OMAP_MMU_CAM_PAGESIZE_1MB) ? " 1MB": + (ent.pgsz == OMAP_MMU_CAM_PAGESIZE_64KB) ? "64KB": + (ent.pgsz == OMAP_MMU_CAM_PAGESIZE_4KB) ? " 4KB": + " ???"; + elsz_str = (ent.elsz == OMAP_MMU_RAM_ELEMENTSIZE_8) ? " 8": + (ent.elsz == OMAP_MMU_RAM_ELEMENTSIZE_16) ? "16": + (ent.elsz == OMAP_MMU_RAM_ELEMENTSIZE_32) ? "32": + "??"; + + if (i == tlb_lock->base) + len += sprintf(buf + len, "lock base = %d\n", + tlb_lock->base); + if (i == tlb_lock->victim) + len += sprintf(buf + len, "victim = %d\n", + tlb_lock->victim); + + len += sprintf(buf + len, + /* 00: P V 4KB 0x300000 0x10171800 B 16 M */ + "%02d: %c %c %s 0x%06lx 0x%08lx %c %s %c\n", + i, + ent.prsvd ? 'P' : ' ', + ent.valid ? 'V' : ' ', + pgsz_str, ent.va, ent.pa, + ent.endian ? 'B' : 'L', + elsz_str, + ent.mixed ? 'M' : ' '); + } + + return len; +} + +#define get_cam_va_mask(pgsz) \ + (((pgsz) == OMAP_MMU_CAM_PAGESIZE_16MB) ? 0xff000000 : \ + ((pgsz) == OMAP_MMU_CAM_PAGESIZE_1MB) ? 0xfff00000 : \ + ((pgsz) == OMAP_MMU_CAM_PAGESIZE_64KB) ? 0xffff0000 : \ + ((pgsz) == OMAP_MMU_CAM_PAGESIZE_4KB) ? 0xfffff000 : 0) + +static inline unsigned long omap2_mmu_cam_va(struct cam_ram_regset *cr) +{ + unsigned int page_size = cr->cam & OMAP_MMU_CAM_PAGESIZE_MASK; + unsigned int mask = get_cam_va_mask(cr->cam & page_size); + + return cr->cam & mask; +} + +static struct cam_ram_regset * +omap2_mmu_cam_ram_alloc(struct omap_mmu *mmu, struct omap_mmu_tlb_entry *entry) +{ + struct cam_ram_regset *cr; + + if (entry->va & ~(get_cam_va_mask(entry->pgsz))) { + dev_err(mmu->dev, "MMU %s: mapping vadr (0x%06lx) is not on" + " an aligned boundary\n", mmu->name, entry->va); + return ERR_PTR(-EINVAL); + } + + cr = kmalloc(sizeof(struct cam_ram_regset), GFP_KERNEL); + + cr->cam = (entry->va & OMAP_MMU_CAM_VATAG_MASK) | + entry->prsvd | entry->pgsz; + cr->ram = entry->pa | entry->endian | entry->elsz; + + return cr; +} + +static inline int omap2_mmu_cam_ram_valid(struct cam_ram_regset *cr) +{ + return cr->cam & OMAP_MMU_CAM_V; +} + +static void omap2_mmu_interrupt(struct omap_mmu *mmu) +{ + unsigned long status, va; + + status = MMU_IRQ_MASK & omap_mmu_read_reg(mmu, OMAP_MMU_IRQSTATUS); + va = omap_mmu_read_reg(mmu, OMAP_MMU_FAULT_AD); + + pr_info("%s\n", (status & OMAP_MMU_IRQ_MULTIHITFAULT)? + "multi hit":""); + pr_info("%s\n", (status & OMAP_MMU_IRQ_TABLEWALKFAULT)? + "table walk fault":""); + pr_info("%s\n", (status & OMAP_MMU_IRQ_EMUMISS)? + "EMU miss":""); + pr_info("%s\n", (status & OMAP_MMU_IRQ_TRANSLATIONFAULT)? + "translation fault":""); + pr_info("%s\n", (status & OMAP_MMU_IRQ_TLBMISS)? + "TLB miss":""); + pr_info("fault address = %#08lx\n", va); + + omap_mmu_disable(mmu); + omap_mmu_write_reg(mmu, status, OMAP_MMU_IRQSTATUS); + + mmu->fault_address = va; + schedule_work(&mmu->irq_work); +} + +static pgprot_t omap2_mmu_pte_get_attr(struct omap_mmu_tlb_entry *entry) +{ + u32 attr; + + attr = entry->mixed << 5; + attr |= entry->endian; + attr |= entry->elsz >> 3; + attr <<= ((entry->pgsz & OMAP_MMU_CAM_PAGESIZE_4KB) ? 0:6); + + return attr; +} + +struct omap_mmu_ops omap2_mmu_ops = { + .startup = omap2_mmu_startup, + .shutdown = omap2_mmu_shutdown, + .read_tlb = omap2_mmu_read_tlb, + .load_tlb = omap2_mmu_load_tlb, + .show = omap2_mmu_show, + .cam_va = omap2_mmu_cam_va, + .cam_ram_alloc = omap2_mmu_cam_ram_alloc, + .cam_ram_valid = omap2_mmu_cam_ram_valid, + .interrupt = omap2_mmu_interrupt, + .pte_get_attr = omap2_mmu_pte_get_attr, +}; +EXPORT_SYMBOL_GPL(omap2_mmu_ops); + +MODULE_LICENSE("GPL"); diff --cc arch/arm/mach-omap2/mmu.h index 363eaa1e9cb,00000000000..c9eabf0993f mode 100644,000000..100644 --- a/arch/arm/mach-omap2/mmu.h +++ b/arch/arm/mach-omap2/mmu.h @@@ -1,117 -1,0 +1,117 @@@ +#ifndef __MACH_OMAP2_MMU_H +#define __MACH_OMAP2_MMU_H + +#include - #include ++#include + +#define MMU_LOCK_BASE_MASK (0x1f << 10) +#define MMU_LOCK_VICTIM_MASK (0x1f << 4) + +#define OMAP_MMU_REVISION 0x00 +#define OMAP_MMU_SYSCONFIG 0x10 +#define OMAP_MMU_SYSSTATUS 0x14 +#define OMAP_MMU_IRQSTATUS 0x18 +#define OMAP_MMU_IRQENABLE 0x1c +#define OMAP_MMU_WALKING_ST 0x40 +#define OMAP_MMU_CNTL 0x44 +#define OMAP_MMU_FAULT_AD 0x48 +#define OMAP_MMU_TTB 0x4c +#define OMAP_MMU_LOCK 0x50 +#define OMAP_MMU_LD_TLB 0x54 +#define OMAP_MMU_CAM 0x58 +#define OMAP_MMU_RAM 0x5c +#define OMAP_MMU_GFLUSH 0x60 +#define OMAP_MMU_FLUSH_ENTRY 0x64 +#define OMAP_MMU_READ_CAM 0x68 +#define OMAP_MMU_READ_RAM 0x6c +#define OMAP_MMU_EMU_FAULT_AD 0x70 + +#define OMAP_MMU_CNTL_BURST_16MNGT_EN 0x0020 +#define OMAP_MMU_CNTL_WTL_EN 0x0004 +#define OMAP_MMU_CNTL_MMU_EN 0x0002 +#define OMAP_MMU_CNTL_RESET_SW 0x0001 + +#define OMAP_MMU_IRQ_MULTIHITFAULT 0x00000010 +#define OMAP_MMU_IRQ_TABLEWALKFAULT 0x00000008 +#define OMAP_MMU_IRQ_EMUMISS 0x00000004 +#define OMAP_MMU_IRQ_TRANSLATIONFAULT 0x00000002 +#define OMAP_MMU_IRQ_TLBMISS 0x00000001 + +#define OMAP_MMU_CAM_VATAG_MASK 0xfffff000 +#define OMAP_MMU_CAM_P 0x00000008 +#define OMAP_MMU_CAM_V 0x00000004 +#define OMAP_MMU_CAM_PAGESIZE_MASK 0x00000003 +#define OMAP_MMU_CAM_PAGESIZE_1MB 0x00000000 +#define OMAP_MMU_CAM_PAGESIZE_64KB 0x00000001 +#define OMAP_MMU_CAM_PAGESIZE_4KB 0x00000002 +#define OMAP_MMU_CAM_PAGESIZE_16MB 0x00000003 + +#define OMAP_MMU_RAM_PADDR_MASK 0xfffff000 +#define OMAP_MMU_RAM_ENDIANNESS 0x00000200 +#define OMAP_MMU_RAM_ENDIANNESS_BIG 0x00000200 +#define OMAP_MMU_RAM_ENDIANNESS_LITTLE 0x00000000 +#define OMAP_MMU_RAM_ELEMENTSIZE_MASK 0x00000180 +#define OMAP_MMU_RAM_ELEMENTSIZE_8 0x00000000 +#define OMAP_MMU_RAM_ELEMENTSIZE_16 0x00000080 +#define OMAP_MMU_RAM_ELEMENTSIZE_32 0x00000100 +#define OMAP_MMU_RAM_ELEMENTSIZE_NONE 0x00000180 +#define OMAP_MMU_RAM_MIXED 0x00000040 + +#define IOMAP_VAL 0x3f + +#define INIT_TLB_ENTRY(ent, v, p, ps) \ +do { \ + (ent)->va = (v); \ + (ent)->pa = (p); \ + (ent)->pgsz = (ps); \ + (ent)->prsvd = 0; \ + (ent)->endian = OMAP_MMU_RAM_ENDIANNESS_LITTLE; \ + (ent)->elsz = OMAP_MMU_RAM_ELEMENTSIZE_16; \ + (ent)->mixed = 0; \ + (ent)->tlb = 1; \ +} while (0) + +#define INIT_TLB_ENTRY_4KB_PRESERVED(ent, v, p) \ +do { \ + (ent)->va = (v); \ + (ent)->pa = (p); \ + (ent)->pgsz = OMAP_MMU_CAM_PAGESIZE_4KB; \ + (ent)->prsvd = OMAP_MMU_CAM_P; \ + (ent)->endian = OMAP_MMU_RAM_ENDIANNESS_LITTLE; \ + (ent)->elsz = OMAP_MMU_RAM_ELEMENTSIZE_16; \ + (ent)->mixed = 0; \ +} while (0) + +#define INIT_TLB_ENTRY_4KB_ES32_PRESERVED(ent, v, p) \ +do { \ + (ent)->va = (v); \ + (ent)->pa = (p); \ + (ent)->pgsz = OMAP_MMU_CAM_PAGESIZE_4KB; \ + (ent)->prsvd = OMAP_MMU_CAM_P; \ + (ent)->endian = OMAP_MMU_RAM_ENDIANNESS_LITTLE; \ + (ent)->elsz = OMAP_MMU_RAM_ELEMENTSIZE_32; \ + (ent)->mixed = 0; \ +} while (0) + +struct omap_mmu_tlb_entry { + unsigned long va; + unsigned long pa; + unsigned int pgsz, prsvd, valid; + + u32 endian, elsz, mixed; + unsigned int tlb; +}; + +static inline unsigned long +omap_mmu_read_reg(struct omap_mmu *mmu, unsigned long reg) +{ + return __raw_readl((void __iomem *)(mmu->base + reg)); +} + +static inline void omap_mmu_write_reg(struct omap_mmu *mmu, + unsigned long val, unsigned long reg) +{ + __raw_writel(val, (void __iomem *)(mmu->base + reg)); +} + +#endif /* __MACH_OMAP2_MMU_H */ diff --cc arch/arm/mach-omap2/mux.c index 545e0924ba5,443d07fef7f..741ab189fc4 --- a/arch/arm/mach-omap2/mux.c +++ b/arch/arm/mach-omap2/mux.c @@@ -26,11 -26,11 +26,11 @@@ #include #include #include -#include +#include #include - #include - #include + #include + #include #ifdef CONFIG_OMAP_MUX diff --cc arch/arm/mach-omap2/pm-debug.c index a32f11f4adb,00000000000..1b14bcf6cb8 mode 100644,000000..100644 --- a/arch/arm/mach-omap2/pm-debug.c +++ b/arch/arm/mach-omap2/pm-debug.c @@@ -1,288 -1,0 +1,288 @@@ +/* + * linux/arch/arm/mach-omap2/pm_debug.c + * + * OMAP Power Management debug routines + * + * Copyright (C) 2005 Texas Instruments, Inc. + * Copyright (C) 2006-2008 Nokia Corporation + * + * Written by: + * Richard Woodruff + * Tony Lindgren + * Juha Yrjola + * Amit Kucheria + * Igor Stoppa + * Jouni Hogander + * + * Based on pm.c for omap2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + - #include - #include ++#include ++#include + +#include "prm.h" +#include "cm.h" +#include "pm.h" + +#ifdef CONFIG_PM_DEBUG +int omap2_pm_debug = 0; + +static int serial_console_clock_disabled; +static int serial_console_uart; +static unsigned int serial_console_next_disable; + +static struct clk *console_iclk, *console_fclk; + +static void serial_console_kick(void) +{ + serial_console_next_disable = omap2_read_32k_sync_counter(); + /* Keep the clocks on for 4 secs */ + serial_console_next_disable += 4 * 32768; +} + +static void serial_wait_tx(void) +{ + static const unsigned long uart_bases[3] = { + 0x4806a000, 0x4806c000, 0x4806e000 + }; + unsigned long lsr_reg; + int looped = 0; + + /* Wait for TX FIFO and THR to get empty */ + lsr_reg = IO_ADDRESS(uart_bases[serial_console_uart - 1] + (5 << 2)); + while ((__raw_readb(lsr_reg) & 0x60) != 0x60) + looped = 1; + if (looped) + serial_console_kick(); +} + +u32 omap2_read_32k_sync_counter(void) +{ + return omap_readl(OMAP2_32KSYNCT_BASE + 0x0010); +} + +void serial_console_fclk_mask(u32 *f1, u32 *f2) +{ + switch (serial_console_uart) { + case 1: + *f1 &= ~(1 << 21); + break; + case 2: + *f1 &= ~(1 << 22); + break; + case 3: + *f2 &= ~(1 << 2); + break; + } +} + +void serial_console_sleep(int enable) +{ + if (console_iclk == NULL || console_fclk == NULL) + return; + + if (enable) { + BUG_ON(serial_console_clock_disabled); + if (clk_get_usecount(console_fclk) == 0) + return; + if ((int) serial_console_next_disable - (int) omap2_read_32k_sync_counter() >= 0) + return; + serial_wait_tx(); + clk_disable(console_iclk); + clk_disable(console_fclk); + serial_console_clock_disabled = 1; + } else { + int serial_wakeup = 0; + u32 l; + + switch (serial_console_uart) { + case 1: + l = prm_read_mod_reg(CORE_MOD, PM_WKST1); + if (l & OMAP24XX_ST_UART1) + serial_wakeup = 1; + break; + case 2: + l = prm_read_mod_reg(CORE_MOD, PM_WKST1); + if (l & OMAP24XX_ST_UART2) + serial_wakeup = 1; + break; + case 3: + l = prm_read_mod_reg(CORE_MOD, OMAP24XX_PM_WKST2); + if (l & OMAP24XX_ST_UART3) + serial_wakeup = 1; + break; + } + if (serial_wakeup) + serial_console_kick(); + if (!serial_console_clock_disabled) + return; + clk_enable(console_iclk); + clk_enable(console_fclk); + serial_console_clock_disabled = 0; + } +} + +void pm_init_serial_console(void) +{ + const struct omap_serial_console_config *conf; + char name[16]; + + conf = omap_get_config(OMAP_TAG_SERIAL_CONSOLE, + struct omap_serial_console_config); + if (conf == NULL) + return; + if (conf->console_uart > 3 || conf->console_uart < 1) + return; + serial_console_uart = conf->console_uart; + sprintf(name, "uart%d_fck", conf->console_uart); + console_fclk = clk_get(NULL, name); + if (IS_ERR(console_fclk)) + console_fclk = NULL; + name[6] = 'i'; + console_iclk = clk_get(NULL, name); + if (IS_ERR(console_fclk)) + console_iclk = NULL; + if (console_fclk == NULL || console_iclk == NULL) { + serial_console_uart = 0; + return; + } + switch (serial_console_uart) { + case 1: + prm_set_mod_reg_bits(OMAP24XX_ST_UART1, CORE_MOD, PM_WKEN1); + break; + case 2: + prm_set_mod_reg_bits(OMAP24XX_ST_UART2, CORE_MOD, PM_WKEN1); + break; + case 3: + prm_set_mod_reg_bits(OMAP24XX_ST_UART3, CORE_MOD, OMAP24XX_PM_WKEN2); + break; + } +} + +#define DUMP_PRM_MOD_REG(mod, reg) \ + regs[reg_count].name = #mod "." #reg; \ + regs[reg_count++].val = prm_read_mod_reg(mod, reg) +#define DUMP_CM_MOD_REG(mod, reg) \ + regs[reg_count].name = #mod "." #reg; \ + regs[reg_count++].val = cm_read_mod_reg(mod, reg) +#define DUMP_PRM_REG(reg) \ + regs[reg_count].name = #reg; \ + regs[reg_count++].val = __raw_readl(reg) +#define DUMP_CM_REG(reg) \ + regs[reg_count].name = #reg; \ + regs[reg_count++].val = __raw_readl(reg) +#define DUMP_INTC_REG(reg, off) \ + regs[reg_count].name = #reg; \ + regs[reg_count++].val = __raw_readl(IO_ADDRESS(0x480fe000 + (off))) + +void omap2_pm_dump(int mode, int resume, unsigned int us) +{ + struct reg { + const char *name; + u32 val; + } regs[32]; + int reg_count = 0, i; + const char *s1 = NULL, *s2 = NULL; + + if (!resume) { +#if 0 + /* MPU */ + DUMP_PRM_MOD_REG(OCP_MOD, OMAP2_PRM_IRQENABLE_MPU_OFFSET); + DUMP_CM_MOD_REG(MPU_MOD, CM_CLKSTCTRL); + DUMP_PRM_MOD_REG(MPU_MOD, PM_PWSTCTRL); + DUMP_PRM_MOD_REG(MPU_MOD, PM_PWSTST); + DUMP_PRM_MOD_REG(MPU_MOD, PM_WKDEP); +#endif +#if 0 + /* INTC */ + DUMP_INTC_REG(INTC_MIR0, 0x0084); + DUMP_INTC_REG(INTC_MIR1, 0x00a4); + DUMP_INTC_REG(INTC_MIR2, 0x00c4); +#endif +#if 0 + DUMP_CM_MOD_REG(CORE_MOD, CM_FCLKEN1); + if (cpu_is_omap24xx()) { + DUMP_CM_MOD_REG(CORE_MOD, OMAP24XX_CM_FCLKEN2); + DUMP_PRM_MOD_REG(OMAP24XX_GR_MOD, + OMAP24XX_PRCM_CLKEMUL_CTRL_OFFSET); + DUMP_PRM_MOD_REG(OMAP24XX_GR_MOD, + OMAP24XX_PRCM_CLKSRC_CTRL_OFFSET); + } + DUMP_CM_MOD_REG(WKUP_MOD, CM_FCLKEN); + DUMP_CM_MOD_REG(CORE_MOD, CM_ICLKEN1); + DUMP_CM_MOD_REG(CORE_MOD, CM_ICLKEN2); + DUMP_CM_MOD_REG(WKUP_MOD, CM_ICLKEN); + DUMP_CM_MOD_REG(PLL_MOD, CM_CLKEN); + DUMP_CM_MOD_REG(PLL_MOD, CM_AUTOIDLE); + DUMP_PRM_MOD_REG(CORE_MOD, PM_PWSTST); +#endif +#if 0 + /* DSP */ + if (cpu_is_omap24xx()) { + DUMP_CM_MOD_REG(OMAP24XX_DSP_MOD, CM_FCLKEN); + DUMP_CM_MOD_REG(OMAP24XX_DSP_MOD, CM_ICLKEN); + DUMP_CM_MOD_REG(OMAP24XX_DSP_MOD, CM_IDLEST); + DUMP_CM_MOD_REG(OMAP24XX_DSP_MOD, CM_AUTOIDLE); + DUMP_CM_MOD_REG(OMAP24XX_DSP_MOD, CM_CLKSEL); + DUMP_CM_MOD_REG(OMAP24XX_DSP_MOD, CM_CLKSTCTRL); + DUMP_PRM_MOD_REG(OMAP24XX_DSP_MOD, RM_RSTCTRL); + DUMP_PRM_MOD_REG(OMAP24XX_DSP_MOD, RM_RSTST); + DUMP_PRM_MOD_REG(OMAP24XX_DSP_MOD, PM_PWSTCTRL); + DUMP_PRM_MOD_REG(OMAP24XX_DSP_MOD, PM_PWSTST); + } +#endif + } else { + DUMP_PRM_MOD_REG(CORE_MOD, PM_WKST1); + if (cpu_is_omap24xx()) + DUMP_PRM_MOD_REG(CORE_MOD, OMAP24XX_PM_WKST2); + DUMP_PRM_MOD_REG(WKUP_MOD, PM_WKST); + DUMP_PRM_MOD_REG(OCP_MOD, OMAP2_PRM_IRQSTATUS_MPU_OFFSET); +#if 1 + DUMP_INTC_REG(INTC_PENDING_IRQ0, 0x0098); + DUMP_INTC_REG(INTC_PENDING_IRQ1, 0x00b8); + DUMP_INTC_REG(INTC_PENDING_IRQ2, 0x00d8); +#endif + } + + switch (mode) { + case 0: + s1 = "full"; + s2 = "retention"; + break; + case 1: + s1 = "MPU"; + s2 = "retention"; + break; + case 2: + s1 = "MPU"; + s2 = "idle"; + break; + } + + if (!resume) +#if defined(CONFIG_NO_IDLE_HZ) || defined(CONFIG_NO_HZ) + printk("--- Going to %s %s (next timer after %u ms)\n", s1, s2, + jiffies_to_msecs(get_next_timer_interrupt(jiffies) - + jiffies)); +#else + printk("--- Going to %s %s\n", s1, s2); +#endif + else + printk("--- Woke up (slept for %u.%03u ms)\n", + us / 1000, us % 1000); + + for (i = 0; i < reg_count; i++) + printk("%-20s: 0x%08x\n", regs[i].name, regs[i].val); +} + +#endif diff --cc arch/arm/mach-omap2/pm.c index 524b4db06dc,8671e1079ab..4652136de15 --- a/arch/arm/mach-omap2/pm.c +++ b/arch/arm/mach-omap2/pm.c @@@ -22,98 -17,95 +22,98 @@@ */ #include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include +#include + - #include ++#include #include -#include +#include - #include -#include -#include -#include + #include +#include "pm.h" -static struct clk *vclk; -static void (*omap2_sram_idle)(void); -static void (*omap2_sram_suspend)(int dllctrl, int cpu_rev); -static void (*saved_idle)(void); +unsigned short enable_dyn_sleep; +unsigned short clocks_off_while_idle; +atomic_t sleep_block = ATOMIC_INIT(0); -extern void __init pmdomain_init(void); -extern void pmdomain_set_autoidle(void); +static ssize_t idle_show(struct kobject *, struct kobj_attribute *, char *); +static ssize_t idle_store(struct kobject *k, struct kobj_attribute *, + const char *buf, size_t n); -static unsigned int omap24xx_sleep_save[OMAP24XX_SLEEP_SAVE_SIZE]; +static struct kobj_attribute sleep_while_idle_attr = + __ATTR(sleep_while_idle, 0644, idle_show, idle_store); -void omap2_pm_idle(void) -{ - local_irq_disable(); - local_fiq_disable(); - if (need_resched()) { - local_fiq_enable(); - local_irq_enable(); - return; - } - - omap2_sram_idle(); - local_fiq_enable(); - local_irq_enable(); -} +static struct kobj_attribute clocks_off_while_idle_attr = + __ATTR(clocks_off_while_idle, 0644, idle_show, idle_store); -static int omap2_pm_prepare(void) +static ssize_t idle_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) { - /* We cannot sleep in idle until we have resumed */ - saved_idle = pm_idle; - pm_idle = NULL; - return 0; + if (attr == &sleep_while_idle_attr) + return sprintf(buf, "%hu\n", enable_dyn_sleep); + else if (attr == &clocks_off_while_idle_attr) + return sprintf(buf, "%hu\n", clocks_off_while_idle); + else + return -EINVAL; } -static int omap2_pm_suspend(void) +static ssize_t idle_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t n) { - return 0; -} + unsigned short value; -static int omap2_pm_enter(suspend_state_t state) -{ - int ret = 0; - - switch (state) - { - case PM_SUSPEND_STANDBY: - case PM_SUSPEND_MEM: - ret = omap2_pm_suspend(); - break; - default: - ret = -EINVAL; + if (sscanf(buf, "%hu", &value) != 1 || + (value != 0 && value != 1)) { + printk(KERN_ERR "idle_store: Invalid value\n"); + return -EINVAL; } - return ret; + if (attr == &sleep_while_idle_attr) + enable_dyn_sleep = value; + else if (attr == &clocks_off_while_idle_attr) + clocks_off_while_idle = value; + else + return -EINVAL; + + return n; } -static void omap2_pm_finish(void) +void omap2_block_sleep(void) { - pm_idle = saved_idle; + atomic_inc(&sleep_block); } -static struct platform_suspend_ops omap_pm_ops = { - .prepare = omap2_pm_prepare, - .enter = omap2_pm_enter, - .finish = omap2_pm_finish, - .valid = suspend_valid_only_mem, -}; +void omap2_allow_sleep(void) +{ + int i; + + i = atomic_dec_return(&sleep_block); + BUG_ON(i < 0); +} -int __init omap2_pm_init(void) +static int __init omap_pm_init(void) { - return 0; + int error = -1; + + if (cpu_is_omap24xx()) + error = omap2_pm_init(); + if (cpu_is_omap34xx()) + error = omap3_pm_init(); + if (error) { + printk(KERN_ERR "omap2|3_pm_init failed: %d\n", error); + return error; + } + + /* disabled till drivers are fixed */ + enable_dyn_sleep = 0; + error = sysfs_create_file(power_kobj, &sleep_while_idle_attr.attr); + if (error) + printk(KERN_ERR "sysfs_create_file failed: %d\n", error); + error = sysfs_create_file(power_kobj, + &clocks_off_while_idle_attr.attr); + if (error) + printk(KERN_ERR "sysfs_create_file failed: %d\n", error); + + return error; } -__initcall(omap2_pm_init); +late_initcall(omap_pm_init); diff --cc arch/arm/mach-omap2/pm24xx.c index 69972a2f988,00000000000..5c7612069d3 mode 100644,000000..100644 --- a/arch/arm/mach-omap2/pm24xx.c +++ b/arch/arm/mach-omap2/pm24xx.c @@@ -1,555 -1,0 +1,555 @@@ +/* + * linux/arch/arm/mach-omap2/pm.c + * + * OMAP2 Power Management Routines + * + * Copyright (C) 2005 Texas Instruments, Inc. + * Copyright (C) 2006-2008 Nokia Corporation + * + * Written by: + * Richard Woodruff + * Tony Lindgren + * Juha Yrjola + * Amit Kucheria + * Igor Stoppa + * + * Based on pm.c for omap1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + - #include - #include - #include - #include - #include - #include - #include - #include - #include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include + +#include "prm.h" +#include "prm-regbits-24xx.h" +#include "cm.h" +#include "cm-regbits-24xx.h" +#include "sdrc.h" +#include "pm.h" + - #include - #include ++#include ++#include + +static void (*omap2_sram_idle)(void); +static void (*omap2_sram_suspend)(void __iomem *dllctrl); +static void (*saved_idle)(void); + +static struct powerdomain *mpu_pwrdm; +static struct powerdomain *core_pwrdm; + +static struct clockdomain *dsp_clkdm; +static struct clockdomain *gfx_clkdm; + +static struct clk *osc_ck, *emul_ck; + +static int omap2_fclks_active(void) +{ + u32 f1, f2; + + f1 = cm_read_mod_reg(CORE_MOD, CM_FCLKEN1); + f2 = cm_read_mod_reg(CORE_MOD, OMAP24XX_CM_FCLKEN2); + serial_console_fclk_mask(&f1, &f2); + if (f1 | f2) + return 1; + return 0; +} + +static void omap2_enter_full_retention(void) +{ + u32 l, sleep_time = 0; + + /* There is 1 reference hold for all children of the oscillator + * clock, the following will remove it. If no one else uses the + * oscillator itself it will be disabled if/when we enter retention + * mode. + */ + clk_disable(osc_ck); + + /* Clear old wake-up events */ + /* REVISIT: These write to reserved bits? */ + prm_write_mod_reg(0xffffffff, CORE_MOD, PM_WKST1); + prm_write_mod_reg(0xffffffff, CORE_MOD, OMAP24XX_PM_WKST2); + prm_write_mod_reg(0xffffffff, WKUP_MOD, PM_WKST); + + /* + * Set MPU powerdomain's next power state to RETENTION; + * preserve logic state during retention + */ + pwrdm_set_logic_retst(mpu_pwrdm, PWRDM_POWER_RET); + pwrdm_set_next_pwrst(mpu_pwrdm, PWRDM_POWER_RET); + + /* Workaround to kill USB */ + l = omap_ctrl_readl(OMAP2_CONTROL_DEVCONF0) | OMAP24XX_USBSTANDBYCTRL; + omap_ctrl_writel(l, OMAP2_CONTROL_DEVCONF0); + + omap2_gpio_prepare_for_retention(); + + if (omap2_pm_debug) { + omap2_pm_dump(0, 0, 0); + sleep_time = omap2_read_32k_sync_counter(); + } + + /* One last check for pending IRQs to avoid extra latency due + * to sleeping unnecessarily. */ + if (omap_irq_pending()) + goto no_sleep; + + serial_console_sleep(1); + /* Jump to SRAM suspend code */ + omap2_sram_suspend(OMAP_SDRC_REGADDR(SDRC_DLLA_CTRL)); +no_sleep: + serial_console_sleep(0); + + if (omap2_pm_debug) { + unsigned long long tmp; + u32 resume_time; + + resume_time = omap2_read_32k_sync_counter(); + tmp = resume_time - sleep_time; + tmp *= 1000000; + omap2_pm_dump(0, 1, tmp / 32768); + } + omap2_gpio_resume_after_retention(); + + clk_enable(osc_ck); + + /* clear CORE wake-up events */ + prm_write_mod_reg(0xffffffff, CORE_MOD, PM_WKST1); + prm_write_mod_reg(0xffffffff, CORE_MOD, OMAP24XX_PM_WKST2); + + /* wakeup domain events - bit 1: GPT1, bit5 GPIO */ + prm_clear_mod_reg_bits(0x4 | 0x1, WKUP_MOD, PM_WKST); + + /* MPU domain wake events */ + l = prm_read_mod_reg(OCP_MOD, OMAP2_PRM_IRQSTATUS_MPU_OFFSET); + if (l & 0x01) + prm_write_mod_reg(0x01, OCP_MOD, + OMAP2_PRM_IRQSTATUS_MPU_OFFSET); + if (l & 0x20) + prm_write_mod_reg(0x20, OCP_MOD, + OMAP2_PRM_IRQSTATUS_MPU_OFFSET); + + /* Mask future PRCM-to-MPU interrupts */ + prm_write_mod_reg(0x0, OCP_MOD, OMAP2_PRM_IRQSTATUS_MPU_OFFSET); +} + +static int omap2_i2c_active(void) +{ + u32 l; + + l = cm_read_mod_reg(CORE_MOD, CM_FCLKEN1); + return l & (OMAP2420_EN_I2C2 | OMAP2420_EN_I2C1); +} + +static int sti_console_enabled; + +static int omap2_allow_mpu_retention(void) +{ + u32 l; + + if (atomic_read(&sleep_block)) + return 0; + + /* Check for MMC, UART2, UART1, McSPI2, McSPI1 and DSS1. */ + l = cm_read_mod_reg(CORE_MOD, CM_FCLKEN1); + if (l & (OMAP2420_EN_MMC | OMAP24XX_EN_UART2 | + OMAP24XX_EN_UART1 | OMAP24XX_EN_MCSPI2 | + OMAP24XX_EN_MCSPI1 | OMAP24XX_EN_DSS1)) + return 0; + /* Check for UART3. */ + l = cm_read_mod_reg(CORE_MOD, OMAP24XX_CM_FCLKEN2); + if (l & OMAP24XX_EN_UART3) + return 0; + if (sti_console_enabled) + return 0; + + return 1; +} + +static void omap2_enter_mpu_retention(void) +{ + u32 sleep_time = 0; + int only_idle = 0; + + /* Putting MPU into the WFI state while a transfer is active + * seems to cause the I2C block to timeout. Why? Good question. */ + if (omap2_i2c_active()) + return; + + /* The peripherals seem not to be able to wake up the MPU when + * it is in retention mode. */ + if (omap2_allow_mpu_retention()) { + /* REVISIT: These write to reserved bits? */ + prm_write_mod_reg(0xffffffff, CORE_MOD, PM_WKST1); + prm_write_mod_reg(0xffffffff, CORE_MOD, OMAP24XX_PM_WKST2); + prm_write_mod_reg(0xffffffff, WKUP_MOD, PM_WKST); + + /* Try to enter MPU retention */ + prm_write_mod_reg((0x01 << OMAP_POWERSTATE_SHIFT) | + OMAP_LOGICRETSTATE, + MPU_MOD, PM_PWSTCTRL); + } else { + /* Block MPU retention */ + + prm_write_mod_reg(OMAP_LOGICRETSTATE, MPU_MOD, PM_PWSTCTRL); + only_idle = 1; + } + + if (omap2_pm_debug) { + omap2_pm_dump(only_idle ? 2 : 1, 0, 0); + sleep_time = omap2_read_32k_sync_counter(); + } + + omap2_sram_idle(); + + if (omap2_pm_debug) { + unsigned long long tmp; + u32 resume_time; + + resume_time = omap2_read_32k_sync_counter(); + tmp = resume_time - sleep_time; + tmp *= 1000000; + omap2_pm_dump(only_idle ? 2 : 1, 1, tmp / 32768); + } +} + +static int omap2_can_sleep(void) +{ + if (!enable_dyn_sleep) + return 0; + if (omap2_fclks_active()) + return 0; + if (atomic_read(&sleep_block) > 0) + return 0; + if (clk_get_usecount(osc_ck) > 1) + return 0; + if (omap_dma_running()) + return 0; + + return 1; +} + +/* + * Note that you can use clock_event_device->min_delta_ns if you want to + * avoid reprogramming timer too often when using CONFIG_NO_HZ. + */ +static void omap2_pm_idle(void) +{ + local_irq_disable(); + local_fiq_disable(); + + if (!omap2_can_sleep()) { + if (!atomic_read(&sleep_block) && omap_irq_pending()) + goto out; + omap2_enter_mpu_retention(); + goto out; + } + + if (omap_irq_pending()) + goto out; + + omap2_enter_full_retention(); + +out: + local_fiq_enable(); + local_irq_enable(); +} + +static int omap2_pm_prepare(void) +{ + /* We cannot sleep in idle until we have resumed */ + saved_idle = pm_idle; + pm_idle = NULL; + + return 0; +} + +static int omap2_pm_suspend(void) +{ + u32 wken_wkup, mir1; + + wken_wkup = prm_read_mod_reg(WKUP_MOD, PM_WKEN); + prm_write_mod_reg(wken_wkup & ~OMAP24XX_EN_GPT1, WKUP_MOD, PM_WKEN); + + /* Mask GPT1 */ + mir1 = omap_readl(0x480fe0a4); + omap_writel(1 << 5, 0x480fe0ac); + + omap2_enter_full_retention(); + + omap_writel(mir1, 0x480fe0a4); + prm_write_mod_reg(wken_wkup, WKUP_MOD, PM_WKEN); + + return 0; +} + +static int omap2_pm_enter(suspend_state_t state) +{ + int ret = 0; + + switch (state) { + case PM_SUSPEND_STANDBY: + case PM_SUSPEND_MEM: + ret = omap2_pm_suspend(); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static void omap2_pm_finish(void) +{ + pm_idle = saved_idle; +} + +static struct platform_suspend_ops omap_pm_ops = { + .prepare = omap2_pm_prepare, + .enter = omap2_pm_enter, + .finish = omap2_pm_finish, + .valid = suspend_valid_only_mem, +}; + +static int _pm_clkdm_enable_hwsup(struct clockdomain *clkdm) +{ + omap2_clkdm_allow_idle(clkdm); + return 0; +} + +static void __init prcm_setup_regs(void) +{ + int i, num_mem_banks; + struct powerdomain *pwrdm; + + /* Enable autoidle */ + prm_write_mod_reg(OMAP24XX_AUTOIDLE, OCP_MOD, + OMAP24XX_PRM_SYSCONFIG_OFFSET); + + /* Set all domain wakeup dependencies */ + prm_write_mod_reg(OMAP_EN_WKUP_MASK, MPU_MOD, PM_WKDEP); + prm_write_mod_reg(0, OMAP24XX_DSP_MOD, PM_WKDEP); + prm_write_mod_reg(0, GFX_MOD, PM_WKDEP); + prm_write_mod_reg(0, CORE_MOD, PM_WKDEP); + if (cpu_is_omap2430()) + prm_write_mod_reg(0, OMAP2430_MDM_MOD, PM_WKDEP); + + /* + * Set CORE powerdomain memory banks to retain their contents + * during RETENTION + */ + num_mem_banks = pwrdm_get_mem_bank_count(core_pwrdm); + for (i = 0; i < num_mem_banks; i++) + pwrdm_set_mem_retst(core_pwrdm, i, PWRDM_POWER_RET); + + /* Set CORE powerdomain's next power state to RETENTION */ + pwrdm_set_next_pwrst(core_pwrdm, PWRDM_POWER_RET); + + /* + * Set MPU powerdomain's next power state to RETENTION; + * preserve logic state during retention + */ + pwrdm_set_logic_retst(mpu_pwrdm, PWRDM_POWER_RET); + pwrdm_set_next_pwrst(mpu_pwrdm, PWRDM_POWER_RET); + + /* Force-power down DSP, GFX powerdomains */ + + pwrdm = clkdm_get_pwrdm(dsp_clkdm); + pwrdm_set_next_pwrst(pwrdm, PWRDM_POWER_OFF); + omap2_clkdm_sleep(dsp_clkdm); + + pwrdm = clkdm_get_pwrdm(gfx_clkdm); + pwrdm_set_next_pwrst(pwrdm, PWRDM_POWER_OFF); + omap2_clkdm_sleep(gfx_clkdm); + + /* Enable clockdomain hardware-supervised control for all clkdms */ + clkdm_for_each(_pm_clkdm_enable_hwsup); + + /* Enable clock autoidle for all domains */ + cm_write_mod_reg(OMAP24XX_AUTO_CAM | + OMAP24XX_AUTO_MAILBOXES | + OMAP24XX_AUTO_WDT4 | + OMAP2420_AUTO_WDT3 | + OMAP24XX_AUTO_MSPRO | + OMAP2420_AUTO_MMC | + OMAP24XX_AUTO_FAC | + OMAP2420_AUTO_EAC | + OMAP24XX_AUTO_HDQ | + OMAP24XX_AUTO_UART2 | + OMAP24XX_AUTO_UART1 | + OMAP24XX_AUTO_I2C2 | + OMAP24XX_AUTO_I2C1 | + OMAP24XX_AUTO_MCSPI2 | + OMAP24XX_AUTO_MCSPI1 | + OMAP24XX_AUTO_MCBSP2 | + OMAP24XX_AUTO_MCBSP1 | + OMAP24XX_AUTO_GPT12 | + OMAP24XX_AUTO_GPT11 | + OMAP24XX_AUTO_GPT10 | + OMAP24XX_AUTO_GPT9 | + OMAP24XX_AUTO_GPT8 | + OMAP24XX_AUTO_GPT7 | + OMAP24XX_AUTO_GPT6 | + OMAP24XX_AUTO_GPT5 | + OMAP24XX_AUTO_GPT4 | + OMAP24XX_AUTO_GPT3 | + OMAP24XX_AUTO_GPT2 | + OMAP2420_AUTO_VLYNQ | + OMAP24XX_AUTO_DSS, + CORE_MOD, CM_AUTOIDLE1); + cm_write_mod_reg(OMAP24XX_AUTO_UART3 | + OMAP24XX_AUTO_SSI | + OMAP24XX_AUTO_USB, + CORE_MOD, CM_AUTOIDLE2); + cm_write_mod_reg(OMAP24XX_AUTO_SDRC | + OMAP24XX_AUTO_GPMC | + OMAP24XX_AUTO_SDMA, + CORE_MOD, CM_AUTOIDLE3); + cm_write_mod_reg(OMAP24XX_AUTO_PKA | + OMAP24XX_AUTO_AES | + OMAP24XX_AUTO_RNG | + OMAP24XX_AUTO_SHA | + OMAP24XX_AUTO_DES, + CORE_MOD, OMAP24XX_CM_AUTOIDLE4); + + cm_write_mod_reg(OMAP2420_AUTO_DSP_IPI, OMAP24XX_DSP_MOD, CM_AUTOIDLE); + + /* Put DPLL and both APLLs into autoidle mode */ + cm_write_mod_reg((0x03 << OMAP24XX_AUTO_DPLL_SHIFT) | + (0x03 << OMAP24XX_AUTO_96M_SHIFT) | + (0x03 << OMAP24XX_AUTO_54M_SHIFT), + PLL_MOD, CM_AUTOIDLE); + + cm_write_mod_reg(OMAP24XX_AUTO_OMAPCTRL | + OMAP24XX_AUTO_WDT1 | + OMAP24XX_AUTO_MPU_WDT | + OMAP24XX_AUTO_GPIOS | + OMAP24XX_AUTO_32KSYNC | + OMAP24XX_AUTO_GPT1, + WKUP_MOD, CM_AUTOIDLE); + + /* REVISIT: Configure number of 32 kHz clock cycles for sys_clk + * stabilisation */ + prm_write_mod_reg(15 << OMAP_SETUP_TIME_SHIFT, OMAP24XX_GR_MOD, + OMAP24XX_PRCM_CLKSSETUP_OFFSET); + + /* Configure automatic voltage transition */ + prm_write_mod_reg(2 << OMAP_SETUP_TIME_SHIFT, OMAP24XX_GR_MOD, + OMAP24XX_PRCM_VOLTSETUP_OFFSET); + prm_write_mod_reg(OMAP24XX_AUTO_EXTVOLT | + (0x1 << OMAP24XX_SETOFF_LEVEL_SHIFT) | + OMAP24XX_MEMRETCTRL | + (0x1 << OMAP24XX_SETRET_LEVEL_SHIFT) | + (0x0 << OMAP24XX_VOLT_LEVEL_SHIFT), + OMAP24XX_GR_MOD, OMAP24XX_PRCM_VOLTCTRL_OFFSET); + + /* Enable wake-up events */ + prm_write_mod_reg(OMAP24XX_EN_GPIOS | OMAP24XX_EN_GPT1, + WKUP_MOD, PM_WKEN); +} + +int __init omap2_pm_init(void) +{ + u32 l; + + printk(KERN_INFO "Power Management for OMAP2 initializing\n"); + l = prm_read_mod_reg(OCP_MOD, OMAP24XX_PRM_REVISION_OFFSET); + printk(KERN_INFO "PRCM revision %d.%d\n", (l >> 4) & 0x0f, l & 0x0f); + + /* Look up important powerdomains, clockdomains */ + + mpu_pwrdm = pwrdm_lookup("mpu_pwrdm"); + if (!mpu_pwrdm) + pr_err("PM: mpu_pwrdm not found\n"); + + core_pwrdm = pwrdm_lookup("core_pwrdm"); + if (!core_pwrdm) + pr_err("PM: core_pwrdm not found\n"); + + dsp_clkdm = clkdm_lookup("dsp_clkdm"); + if (!dsp_clkdm) + pr_err("PM: mpu_clkdm not found\n"); + + gfx_clkdm = clkdm_lookup("gfx_clkdm"); + if (!gfx_clkdm) + pr_err("PM: gfx_clkdm not found\n"); + + + osc_ck = clk_get(NULL, "osc_ck"); + if (IS_ERR(osc_ck)) { + printk(KERN_ERR "could not get osc_ck\n"); + return -ENODEV; + } + + if (cpu_is_omap242x()) { + emul_ck = clk_get(NULL, "emul_ck"); + if (IS_ERR(emul_ck)) { + printk(KERN_ERR "could not get emul_ck\n"); + clk_put(osc_ck); + return -ENODEV; + } + } + + prcm_setup_regs(); + + pm_init_serial_console(); + + /* Hack to prevent MPU retention when STI console is enabled. */ + { + const struct omap_sti_console_config *sti; + + sti = omap_get_config(OMAP_TAG_STI_CONSOLE, + struct omap_sti_console_config); + if (sti != NULL && sti->enable) + sti_console_enabled = 1; + } + + /* + * 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. + */ + if (cpu_is_omap242x()) { + omap2_sram_idle = omap_sram_push(omap242x_idle_loop_suspend, + omap242x_idle_loop_suspend_sz); + + omap2_sram_suspend = omap_sram_push(omap242x_cpu_suspend, + omap242x_cpu_suspend_sz); + } else { + omap2_sram_idle = omap_sram_push(omap243x_idle_loop_suspend, + omap243x_idle_loop_suspend_sz); + + omap2_sram_suspend = omap_sram_push(omap243x_cpu_suspend, + omap243x_cpu_suspend_sz); + } + + suspend_set_ops(&omap_pm_ops); + pm_idle = omap2_pm_idle; + + return 0; +} diff --cc arch/arm/mach-omap2/pm34xx.c index fc72c11e582,00000000000..a57cf4172a0 mode 100644,000000..100644 --- a/arch/arm/mach-omap2/pm34xx.c +++ b/arch/arm/mach-omap2/pm34xx.c @@@ -1,498 -1,0 +1,498 @@@ +/* + * linux/arch/arm/mach-omap2/pm34xx.c + * + * OMAP3 Power Management Routines + * + * Copyright (C) 2006-2008 Nokia Corporation + * Tony Lindgren + * Jouni Hogander + * + * Copyright (C) 2005 Texas Instruments, Inc. + * Richard Woodruff + * + * Based on pm.c for omap1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + - #include - #include - #include - #include - #include ++#include ++#include ++#include ++#include ++#include + +#include "cm.h" +#include "cm-regbits-34xx.h" +#include "prm-regbits-34xx.h" + +#include "prm.h" +#include "pm.h" +#include "smartreflex.h" + +struct power_state { + struct powerdomain *pwrdm; + u32 next_state; + u32 saved_state; + struct list_head node; +}; + +static LIST_HEAD(pwrst_list); + +static void (*_omap_sram_idle)(u32 *addr, int save_state); + +static void (*saved_idle)(void); + +static struct powerdomain *mpu_pwrdm; + +/* PRCM Interrupt Handler for wakeups */ +static irqreturn_t prcm_interrupt_handler (int irq, void *dev_id) +{ + u32 wkst, irqstatus_mpu; + u32 fclk, iclk; + + /* WKUP */ + wkst = prm_read_mod_reg(WKUP_MOD, PM_WKST); + if (wkst) { + iclk = cm_read_mod_reg(WKUP_MOD, CM_ICLKEN); + fclk = cm_read_mod_reg(WKUP_MOD, CM_FCLKEN); + cm_set_mod_reg_bits(wkst, WKUP_MOD, CM_ICLKEN); + cm_set_mod_reg_bits(wkst, WKUP_MOD, CM_FCLKEN); + prm_write_mod_reg(wkst, WKUP_MOD, PM_WKST); + while (prm_read_mod_reg(WKUP_MOD, PM_WKST)); + cm_write_mod_reg(iclk, WKUP_MOD, CM_ICLKEN); + cm_write_mod_reg(fclk, WKUP_MOD, CM_FCLKEN); + } + + /* CORE */ + wkst = prm_read_mod_reg(CORE_MOD, PM_WKST1); + if (wkst) { + iclk = cm_read_mod_reg(CORE_MOD, CM_ICLKEN1); + fclk = cm_read_mod_reg(CORE_MOD, CM_FCLKEN1); + cm_set_mod_reg_bits(wkst, CORE_MOD, CM_ICLKEN1); + cm_set_mod_reg_bits(wkst, CORE_MOD, CM_FCLKEN1); + prm_write_mod_reg(wkst, CORE_MOD, PM_WKST1); + while (prm_read_mod_reg(CORE_MOD, PM_WKST1)); + cm_write_mod_reg(iclk, CORE_MOD, CM_ICLKEN1); + cm_write_mod_reg(fclk, CORE_MOD, CM_FCLKEN1); + } + wkst = prm_read_mod_reg(CORE_MOD, OMAP3430ES2_PM_WKST3); + if (wkst) { + iclk = cm_read_mod_reg(CORE_MOD, CM_ICLKEN3); + fclk = cm_read_mod_reg(CORE_MOD, OMAP3430ES2_CM_FCLKEN3); + cm_set_mod_reg_bits(wkst, CORE_MOD, CM_ICLKEN3); + cm_set_mod_reg_bits(wkst, CORE_MOD, OMAP3430ES2_CM_FCLKEN3); + prm_write_mod_reg(wkst, CORE_MOD, OMAP3430ES2_PM_WKST3); + while (prm_read_mod_reg(CORE_MOD, OMAP3430ES2_PM_WKST3)); + cm_write_mod_reg(iclk, CORE_MOD, CM_ICLKEN3); + cm_write_mod_reg(fclk, CORE_MOD, OMAP3430ES2_CM_FCLKEN3); + } + + /* PER */ + wkst = prm_read_mod_reg(OMAP3430_PER_MOD, PM_WKST); + if (wkst) { + iclk = cm_read_mod_reg(OMAP3430_PER_MOD, CM_ICLKEN); + fclk = cm_read_mod_reg(OMAP3430_PER_MOD, CM_FCLKEN); + cm_set_mod_reg_bits(wkst, OMAP3430_PER_MOD, CM_ICLKEN); + cm_set_mod_reg_bits(wkst, OMAP3430_PER_MOD, CM_FCLKEN); + prm_write_mod_reg(wkst, OMAP3430_PER_MOD, PM_WKST); + while (prm_read_mod_reg(OMAP3430_PER_MOD, PM_WKST)); + cm_write_mod_reg(iclk, OMAP3430_PER_MOD, CM_ICLKEN); + cm_write_mod_reg(fclk, OMAP3430_PER_MOD, CM_FCLKEN); + } + + if (is_sil_rev_greater_than(OMAP3430_REV_ES1_0)) { + /* USBHOST */ + wkst = prm_read_mod_reg(OMAP3430ES2_USBHOST_MOD, PM_WKST); + if (wkst) { + iclk = cm_read_mod_reg(OMAP3430ES2_USBHOST_MOD, + CM_ICLKEN); + fclk = cm_read_mod_reg(OMAP3430ES2_USBHOST_MOD, + CM_FCLKEN); + cm_set_mod_reg_bits(wkst, OMAP3430ES2_USBHOST_MOD, + CM_ICLKEN); + cm_set_mod_reg_bits(wkst, OMAP3430ES2_USBHOST_MOD, + CM_FCLKEN); + prm_write_mod_reg(wkst, OMAP3430ES2_USBHOST_MOD, + PM_WKST); + while (prm_read_mod_reg(OMAP3430ES2_USBHOST_MOD, + PM_WKST)); + cm_write_mod_reg(iclk, OMAP3430ES2_USBHOST_MOD, + CM_ICLKEN); + cm_write_mod_reg(fclk, OMAP3430ES2_USBHOST_MOD, + CM_FCLKEN); + } + } + + irqstatus_mpu = prm_read_mod_reg(OCP_MOD, + OMAP2_PRM_IRQSTATUS_MPU_OFFSET); + prm_write_mod_reg(irqstatus_mpu, OCP_MOD, + OMAP2_PRM_IRQSTATUS_MPU_OFFSET); + + while (prm_read_mod_reg(OCP_MOD, OMAP2_PRM_IRQSTATUS_MPU_OFFSET)); + + return IRQ_HANDLED; +} + +static void omap_sram_idle(void) +{ + /* Variable to tell what needs to be saved and restored + * in omap_sram_idle*/ + /* save_state = 0 => Nothing to save and restored */ + /* save_state = 1 => Only L1 and logic lost */ + /* save_state = 2 => Only L2 lost */ + /* save_state = 3 => L1, L2 and logic lost */ + int save_state = 0, mpu_next_state; + + if (!_omap_sram_idle) + return; + + mpu_next_state = pwrdm_read_next_pwrst(mpu_pwrdm); + switch (mpu_next_state) { + case PWRDM_POWER_RET: + /* No need to save context */ + save_state = 0; + break; + default: + /* Invalid state */ + printk(KERN_ERR "Invalid mpu state in sram_idle\n"); + return; + } + + omap2_gpio_prepare_for_retention(); + + _omap_sram_idle(NULL, save_state); + + omap2_gpio_resume_after_retention(); +} + +static int omap3_can_sleep(void) +{ + if (!enable_dyn_sleep) + return 0; + if (atomic_read(&sleep_block) > 0) + return 0; + return 1; +} + +/* _clkdm_deny_idle - private callback function used by set_pwrdm_state() */ +static int _clkdm_deny_idle(struct powerdomain *pwrdm, + struct clockdomain *clkdm) +{ + omap2_clkdm_deny_idle(clkdm); + return 0; +} + +/* _clkdm_allow_idle - private callback function used by set_pwrdm_state() */ +static int _clkdm_allow_idle(struct powerdomain *pwrdm, + struct clockdomain *clkdm) +{ + omap2_clkdm_allow_idle(clkdm); + return 0; +} + +/* This sets pwrdm state (other than mpu & core. Currently only ON & + * RET are supported. Function is assuming that clkdm doesn't have + * hw_sup mode enabled. */ +static int set_pwrdm_state(struct powerdomain *pwrdm, u32 state) +{ + u32 cur_state; + int ret = 0; + + if (pwrdm == NULL || IS_ERR(pwrdm)) + return -EINVAL; + + cur_state = pwrdm_read_next_pwrst(pwrdm); + + if (cur_state == state) + return ret; + + pwrdm_for_each_clkdm(pwrdm, _clkdm_deny_idle); + + ret = pwrdm_set_next_pwrst(pwrdm, state); + if (ret) { + printk(KERN_ERR "Unable to set state of powerdomain: %s\n", + pwrdm->name); + goto err; + } + + pwrdm_for_each_clkdm(pwrdm, _clkdm_allow_idle); + +err: + return ret; +} + +static void omap3_pm_idle(void) +{ + local_irq_disable(); + local_fiq_disable(); + + if (!omap3_can_sleep()) + goto out; + + if (omap_irq_pending()) + goto out; + + omap_sram_idle(); + +out: + local_fiq_enable(); + local_irq_enable(); +} + +static int omap3_pm_prepare(void) +{ + saved_idle = pm_idle; + pm_idle = NULL; + return 0; +} + +static int omap3_pm_suspend(void) +{ + struct power_state *pwrst; + int state, ret = 0; + + /* XXX Disable smartreflex before entering suspend */ + disable_smartreflex(SR1); + disable_smartreflex(SR2); + + /* Read current next_pwrsts */ + list_for_each_entry(pwrst, &pwrst_list, node) + pwrst->saved_state = pwrdm_read_next_pwrst(pwrst->pwrdm); + /* Set ones wanted by suspend */ + list_for_each_entry(pwrst, &pwrst_list, node) { + if (set_pwrdm_state(pwrst->pwrdm, pwrst->next_state)) + goto restore; + if (pwrdm_clear_all_prev_pwrst(pwrst->pwrdm)) + goto restore; + } + + omap_sram_idle(); + +restore: + /* Restore next_pwrsts */ + list_for_each_entry(pwrst, &pwrst_list, node) { + set_pwrdm_state(pwrst->pwrdm, pwrst->saved_state); + state = pwrdm_read_prev_pwrst(pwrst->pwrdm); + if (state != pwrst->next_state) { + printk(KERN_INFO "Powerdomain (%s) didn't enter " + "target state %d\n", + pwrst->pwrdm->name, pwrst->next_state); + ret = -1; + } + } + if (ret) + printk(KERN_ERR "Could not enter target state in pm_suspend\n"); + else + printk(KERN_INFO "Successfully put all powerdomains " + "to target state\n"); + + /* XXX Enable smartreflex after suspend */ + enable_smartreflex(SR1); + enable_smartreflex(SR2); + + return ret; +} + +static int omap3_pm_enter(suspend_state_t state) +{ + int ret = 0; + + switch (state) { + case PM_SUSPEND_STANDBY: + case PM_SUSPEND_MEM: + ret = omap3_pm_suspend(); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static void omap3_pm_finish(void) +{ + pm_idle = saved_idle; +} + +static struct platform_suspend_ops omap_pm_ops = { + .prepare = omap3_pm_prepare, + .enter = omap3_pm_enter, + .finish = omap3_pm_finish, + .valid = suspend_valid_only_mem, +}; + +static void __init prcm_setup_regs(void) +{ + /* XXX Reset all wkdeps. This should be done when initializing + * powerdomains */ + prm_write_mod_reg(0, OMAP3430_IVA2_MOD, PM_WKDEP); + prm_write_mod_reg(0, MPU_MOD, PM_WKDEP); + prm_write_mod_reg(0, OMAP3430_DSS_MOD, PM_WKDEP); + prm_write_mod_reg(0, OMAP3430_NEON_MOD, PM_WKDEP); + prm_write_mod_reg(0, OMAP3430_CAM_MOD, PM_WKDEP); + prm_write_mod_reg(0, OMAP3430_PER_MOD, PM_WKDEP); + if (is_sil_rev_greater_than(OMAP3430_REV_ES1_0)) { + prm_write_mod_reg(0, OMAP3430ES2_SGX_MOD, PM_WKDEP); + prm_write_mod_reg(0, OMAP3430ES2_USBHOST_MOD, PM_WKDEP); + } else + prm_write_mod_reg(0, GFX_MOD, PM_WKDEP); + + /* setup wakup source */ + prm_write_mod_reg(OMAP3430_EN_IO | OMAP3430_EN_GPIO1 | OMAP3430_EN_GPT1, + WKUP_MOD, PM_WKEN); + /* No need to write EN_IO, that is always enabled */ + prm_write_mod_reg(OMAP3430_EN_GPIO1 | OMAP3430_EN_GPT1, + WKUP_MOD, OMAP3430_PM_MPUGRPSEL); + /* For some reason IO doesn't generate wakeup event even if + * it is selected to mpu wakeup goup */ + prm_write_mod_reg(OMAP3430_IO_EN | OMAP3430_WKUP_EN, + OCP_MOD, OMAP2_PRM_IRQENABLE_MPU_OFFSET); +} + +static int __init pwrdms_setup(struct powerdomain *pwrdm) +{ + struct power_state *pwrst; + + if (!pwrdm->pwrsts) + return 0; + + pwrst = kmalloc(sizeof(struct power_state), GFP_KERNEL); + if (!pwrst) + return -ENOMEM; + pwrst->pwrdm = pwrdm; + pwrst->next_state = PWRDM_POWER_RET; + list_add(&pwrst->node, &pwrst_list); + + if (pwrdm_has_hdwr_sar(pwrdm)) + pwrdm_enable_hdwr_sar(pwrdm); + + return set_pwrdm_state(pwrst->pwrdm, pwrst->next_state); +} + +int __init omap3_pm_init(void) +{ + struct power_state *pwrst; + int ret; + + printk(KERN_ERR "Power Management for TI OMAP3.\n"); + + /* XXX prcm_setup_regs needs to be before enabling hw + * supervised mode for powerdomains */ + prcm_setup_regs(); + + ret = request_irq(INT_34XX_PRCM_MPU_IRQ, + (irq_handler_t)prcm_interrupt_handler, + IRQF_DISABLED, "prcm", NULL); + if (ret) { + printk(KERN_ERR "request_irq failed to register for 0x%x\n", + INT_34XX_PRCM_MPU_IRQ); + goto err1; + } + + ret = pwrdm_for_each(pwrdms_setup); + if (ret) { + printk(KERN_ERR "Failed to setup powerdomains\n"); + goto err2; + } + + mpu_pwrdm = pwrdm_lookup("mpu_pwrdm"); + if (mpu_pwrdm == NULL) { + printk(KERN_ERR "Failed to get mpu_pwrdm\n"); + goto err2; + } + + _omap_sram_idle = omap_sram_push(omap34xx_cpu_suspend, + omap34xx_cpu_suspend_sz); + + suspend_set_ops(&omap_pm_ops); + + pm_idle = omap3_pm_idle; + +err1: + return ret; +err2: + free_irq(INT_34XX_PRCM_MPU_IRQ, NULL); + list_for_each_entry(pwrst, &pwrst_list, node) { + list_del(&pwrst->node); + kfree(pwrst); + } + return ret; +} + +static void __init configure_vc(void) +{ + prm_write_mod_reg((R_SRI2C_SLAVE_ADDR << OMAP3430_SMPS_SA1_SHIFT) | + (R_SRI2C_SLAVE_ADDR << OMAP3430_SMPS_SA0_SHIFT), + OMAP3430_GR_MOD, OMAP3_PRM_VC_SMPS_SA_OFFSET); + prm_write_mod_reg((R_VDD2_SR_CONTROL << OMAP3430_VOLRA1_SHIFT) | + (R_VDD1_SR_CONTROL << OMAP3430_VOLRA0_SHIFT), + OMAP3430_GR_MOD, OMAP3_PRM_VC_SMPS_VOL_RA_OFFSET); + + prm_write_mod_reg((OMAP3430_VC_CMD_VAL0_ON << + OMAP3430_VC_CMD_ON_SHIFT) | + (OMAP3430_VC_CMD_VAL0_ONLP << OMAP3430_VC_CMD_ONLP_SHIFT) | + (OMAP3430_VC_CMD_VAL0_RET << OMAP3430_VC_CMD_RET_SHIFT) | + (OMAP3430_VC_CMD_VAL0_OFF << OMAP3430_VC_CMD_OFF_SHIFT), + OMAP3430_GR_MOD, OMAP3_PRM_VC_CMD_VAL_0_OFFSET); + + prm_write_mod_reg((OMAP3430_VC_CMD_VAL1_ON << + OMAP3430_VC_CMD_ON_SHIFT) | + (OMAP3430_VC_CMD_VAL1_ONLP << OMAP3430_VC_CMD_ONLP_SHIFT) | + (OMAP3430_VC_CMD_VAL1_RET << OMAP3430_VC_CMD_RET_SHIFT) | + (OMAP3430_VC_CMD_VAL1_OFF << OMAP3430_VC_CMD_OFF_SHIFT), + OMAP3430_GR_MOD, OMAP3_PRM_VC_CMD_VAL_1_OFFSET); + + prm_write_mod_reg(OMAP3430_CMD1 | OMAP3430_RAV1, + OMAP3430_GR_MOD, + OMAP3_PRM_VC_CH_CONF_OFFSET); + + prm_write_mod_reg(OMAP3430_MCODE_SHIFT | OMAP3430_HSEN | OMAP3430_SREN, + OMAP3430_GR_MOD, + OMAP3_PRM_VC_I2C_CFG_OFFSET); + + /* Setup voltctrl and other setup times */ + +#ifdef CONFIG_OMAP_SYSOFFMODE + prm_write_mod_reg(OMAP3430_AUTO_OFF | OMAP3430_AUTO_RET | + OMAP3430_SEL_OFF, OMAP3430_GR_MOD, + OMAP3_PRM_VOLTCTRL_OFFSET); + + prm_write_mod_reg(OMAP3430_CLKSETUP_DURATION, OMAP3430_GR_MOD, + OMAP3_PRM_CLKSETUP_OFFSET); + prm_write_mod_reg((OMAP3430_VOLTSETUP_TIME2 << + OMAP3430_SETUP_TIME2_SHIFT) | + (OMAP3430_VOLTSETUP_TIME1 << + OMAP3430_SETUP_TIME1_SHIFT), + OMAP3430_GR_MOD, OMAP3_PRM_VOLTSETUP1_OFFSET); + + prm_write_mod_reg(OMAP3430_VOLTOFFSET_DURATION, OMAP3430_GR_MOD, + OMAP3_PRM_VOLTOFFSET_OFFSET); + prm_write_mod_reg(OMAP3430_VOLTSETUP2_DURATION, OMAP3430_GR_MOD, + OMAP3_PRM_VOLTSETUP2_OFFSET); +#else + prm_set_mod_reg_bits(OMAP3430_AUTO_RET, OMAP3430_GR_MOD, + OMAP3_PRM_VOLTCTRL_OFFSET); +#endif + +} + +static int __init omap3_pm_early_init(void) +{ + prm_clear_mod_reg_bits(OMAP3430_OFFMODE_POL, OMAP3430_GR_MOD, + OMAP3_PRM_POLCTRL_OFFSET); + + configure_vc(); + + return 0; +} + +arch_initcall(omap3_pm_early_init); diff --cc arch/arm/mach-omap2/powerdomain.c index 7615f9d2ed7,00000000000..73e2971b175 mode 100644,000000..100644 --- a/arch/arm/mach-omap2/powerdomain.c +++ b/arch/arm/mach-omap2/powerdomain.c @@@ -1,1113 -1,0 +1,1113 @@@ +/* + * OMAP powerdomain control + * + * Copyright (C) 2007-2008 Texas Instruments, Inc. + * Copyright (C) 2007-2008 Nokia Corporation + * + * Written by Paul Walmsley + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifdef CONFIG_OMAP_DEBUG_POWERDOMAIN +# define DEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "cm.h" +#include "cm-regbits-34xx.h" +#include "prm.h" +#include "prm-regbits-34xx.h" + - #include - #include - #include ++#include ++#include ++#include + +/* pwrdm_list contains all registered struct powerdomains */ +static LIST_HEAD(pwrdm_list); + +/* + * pwrdm_rwlock protects pwrdm_list add and del ops - also reused to + * protect pwrdm_clkdms[] during clkdm add/del ops + */ +static DEFINE_RWLOCK(pwrdm_rwlock); + + +/* Private functions */ + +static u32 prm_read_mod_bits_shift(s16 domain, s16 idx, u32 mask) +{ + u32 v; + + v = prm_read_mod_reg(domain, idx); + v &= mask; + v >>= __ffs(mask); + + return v; +} + +static struct powerdomain *_pwrdm_lookup(const char *name) +{ + struct powerdomain *pwrdm, *temp_pwrdm; + + pwrdm = NULL; + + list_for_each_entry(temp_pwrdm, &pwrdm_list, node) { + if (!strcmp(name, temp_pwrdm->name)) { + pwrdm = temp_pwrdm; + break; + } + } + + return pwrdm; +} + +/* _pwrdm_deps_lookup - look up the specified powerdomain in a pwrdm list */ +static struct powerdomain *_pwrdm_deps_lookup(struct powerdomain *pwrdm, + struct pwrdm_dep *deps) +{ + struct pwrdm_dep *pd; + + if (!pwrdm || !deps || !omap_chip_is(pwrdm->omap_chip)) + return ERR_PTR(-EINVAL); + + for (pd = deps; pd; pd++) { + + if (!omap_chip_is(pd->omap_chip)) + continue; + + if (!pd->pwrdm && pd->pwrdm_name) + pd->pwrdm = pwrdm_lookup(pd->pwrdm_name); + + if (pd->pwrdm == pwrdm) + break; + + } + + if (!pd) + return ERR_PTR(-ENOENT); + + return pd->pwrdm; +} + + +/* Public functions */ + +/** + * pwrdm_init - set up the powerdomain layer + * + * Loop through the list of powerdomains, registering all that are + * available on the current CPU. If pwrdm_list is supplied and not + * null, all of the referenced powerdomains will be registered. No + * return value. + */ +void pwrdm_init(struct powerdomain **pwrdm_list) +{ + struct powerdomain **p = NULL; + + if (pwrdm_list) + for (p = pwrdm_list; *p; p++) + pwrdm_register(*p); +} + +/** + * pwrdm_register - register a powerdomain + * @pwrdm: struct powerdomain * to register + * + * Adds a powerdomain to the internal powerdomain list. Returns + * -EINVAL if given a null pointer, -EEXIST if a powerdomain is + * already registered by the provided name, or 0 upon success. + */ +int pwrdm_register(struct powerdomain *pwrdm) +{ + unsigned long flags; + int ret = -EINVAL; + + if (!pwrdm) + return -EINVAL; + + if (!omap_chip_is(pwrdm->omap_chip)) + return -EINVAL; + + write_lock_irqsave(&pwrdm_rwlock, flags); + if (_pwrdm_lookup(pwrdm->name)) { + ret = -EEXIST; + goto pr_unlock; + } + + list_add(&pwrdm->node, &pwrdm_list); + + pr_debug("powerdomain: registered %s\n", pwrdm->name); + ret = 0; + +pr_unlock: + write_unlock_irqrestore(&pwrdm_rwlock, flags); + + return ret; +} + +/** + * pwrdm_unregister - unregister a powerdomain + * @pwrdm: struct powerdomain * to unregister + * + * Removes a powerdomain from the internal powerdomain list. Returns + * -EINVAL if pwrdm argument is NULL. + */ +int pwrdm_unregister(struct powerdomain *pwrdm) +{ + unsigned long flags; + + if (!pwrdm) + return -EINVAL; + + write_lock_irqsave(&pwrdm_rwlock, flags); + list_del(&pwrdm->node); + write_unlock_irqrestore(&pwrdm_rwlock, flags); + + pr_debug("powerdomain: unregistered %s\n", pwrdm->name); + + return 0; +} + +/** + * pwrdm_lookup - look up a powerdomain by name, return a pointer + * @name: name of powerdomain + * + * Find a registered powerdomain by its name. Returns a pointer to the + * struct powerdomain if found, or NULL otherwise. + */ +struct powerdomain *pwrdm_lookup(const char *name) +{ + struct powerdomain *pwrdm; + unsigned long flags; + + if (!name) + return NULL; + + read_lock_irqsave(&pwrdm_rwlock, flags); + pwrdm = _pwrdm_lookup(name); + read_unlock_irqrestore(&pwrdm_rwlock, flags); + + return pwrdm; +} + +/** + * pwrdm_for_each - call function on each registered clockdomain + * @fn: callback function * + * + * Call the supplied function for each registered powerdomain. The + * callback function can return anything but 0 to bail out early from + * the iterator. The callback function is called with the pwrdm_rwlock + * held for reading, so no powerdomain structure manipulation + * functions should be called from the callback, although hardware + * powerdomain control functions are fine. Returns the last return + * value of the callback function, which should be 0 for success or + * anything else to indicate failure; or -EINVAL if the function + * pointer is null. + */ +int pwrdm_for_each(int (*fn)(struct powerdomain *pwrdm)) +{ + struct powerdomain *temp_pwrdm; + unsigned long flags; + int ret = 0; + + if (!fn) + return -EINVAL; + + read_lock_irqsave(&pwrdm_rwlock, flags); + list_for_each_entry(temp_pwrdm, &pwrdm_list, node) { + ret = (*fn)(temp_pwrdm); + if (ret) + break; + } + read_unlock_irqrestore(&pwrdm_rwlock, flags); + + return ret; +} + +/** + * pwrdm_add_clkdm - add a clockdomain to a powerdomain + * @pwrdm: struct powerdomain * to add the clockdomain to + * @clkdm: struct clockdomain * to associate with a powerdomain + * + * Associate the clockdomain 'clkdm' with a powerdomain 'pwrdm'. This + * enables the use of pwrdm_for_each_clkdm(). Returns -EINVAL if + * presented with invalid pointers; -ENOMEM if memory could not be allocated; + * or 0 upon success. + */ +int pwrdm_add_clkdm(struct powerdomain *pwrdm, struct clockdomain *clkdm) +{ + unsigned long flags; + int i; + int ret = -EINVAL; + + if (!pwrdm || !clkdm) + return -EINVAL; + + pr_debug("powerdomain: associating clockdomain %s with powerdomain " + "%s\n", clkdm->name, pwrdm->name); + + write_lock_irqsave(&pwrdm_rwlock, flags); + + for (i = 0; i < PWRDM_MAX_CLKDMS; i++) { + if (!pwrdm->pwrdm_clkdms[i]) + break; +#ifdef DEBUG + if (pwrdm->pwrdm_clkdms[i] == clkdm) { + ret = -EINVAL; + goto pac_exit; + } +#endif + } + + if (i == PWRDM_MAX_CLKDMS) { + pr_debug("powerdomain: increase PWRDM_MAX_CLKDMS for " + "pwrdm %s clkdm %s\n", pwrdm->name, clkdm->name); + WARN_ON(1); + ret = -ENOMEM; + goto pac_exit; + } + + pwrdm->pwrdm_clkdms[i] = clkdm; + + ret = 0; + +pac_exit: + write_unlock_irqrestore(&pwrdm_rwlock, flags); + + return ret; +} + +/** + * pwrdm_del_clkdm - remove a clockdomain from a powerdomain + * @pwrdm: struct powerdomain * to add the clockdomain to + * @clkdm: struct clockdomain * to associate with a powerdomain + * + * Dissociate the clockdomain 'clkdm' from the powerdomain + * 'pwrdm'. Returns -EINVAL if presented with invalid pointers; + * -ENOENT if the clkdm was not associated with the powerdomain, or 0 + * upon success. + */ +int pwrdm_del_clkdm(struct powerdomain *pwrdm, struct clockdomain *clkdm) +{ + unsigned long flags; + int ret = -EINVAL; + int i; + + if (!pwrdm || !clkdm) + return -EINVAL; + + pr_debug("powerdomain: dissociating clockdomain %s from powerdomain " + "%s\n", clkdm->name, pwrdm->name); + + write_lock_irqsave(&pwrdm_rwlock, flags); + + for (i = 0; i < PWRDM_MAX_CLKDMS; i++) + if (pwrdm->pwrdm_clkdms[i] == clkdm) + break; + + if (i == PWRDM_MAX_CLKDMS) { + pr_debug("powerdomain: clkdm %s not associated with pwrdm " + "%s ?!\n", clkdm->name, pwrdm->name); + ret = -ENOENT; + goto pdc_exit; + } + + pwrdm->pwrdm_clkdms[i] = NULL; + + ret = 0; + +pdc_exit: + write_unlock_irqrestore(&pwrdm_rwlock, flags); + + return ret; +} + +/** + * pwrdm_for_each_clkdm - call function on each clkdm in a pwrdm + * @pwrdm: struct powerdomain * to iterate over + * @fn: callback function * + * + * Call the supplied function for each clockdomain in the powerdomain + * 'pwrdm'. The callback function can return anything but 0 to bail + * out early from the iterator. The callback function is called with + * the pwrdm_rwlock held for reading, so no powerdomain structure + * manipulation functions should be called from the callback, although + * hardware powerdomain control functions are fine. Returns -EINVAL + * if presented with invalid pointers; or passes along the last return + * value of the callback function, which should be 0 for success or + * anything else to indicate failure. + */ +int pwrdm_for_each_clkdm(struct powerdomain *pwrdm, + int (*fn)(struct powerdomain *pwrdm, + struct clockdomain *clkdm)) +{ + unsigned long flags; + int ret = 0; + int i; + + if (!fn) + return -EINVAL; + + read_lock_irqsave(&pwrdm_rwlock, flags); + + for (i = 0; i < PWRDM_MAX_CLKDMS && !ret; i++) + ret = (*fn)(pwrdm, pwrdm->pwrdm_clkdms[i]); + + read_unlock_irqrestore(&pwrdm_rwlock, flags); + + return ret; +} + + +/** + * pwrdm_add_wkdep - add a wakeup dependency from pwrdm2 to pwrdm1 + * @pwrdm1: wake this struct powerdomain * up (dependent) + * @pwrdm2: when this struct powerdomain * wakes up (source) + * + * When the powerdomain represented by pwrdm2 wakes up (due to an + * interrupt), wake up pwrdm1. Implemented in hardware on the OMAP, + * this feature is designed to reduce wakeup latency of the dependent + * powerdomain. Returns -EINVAL if presented with invalid powerdomain + * pointers, -ENOENT if pwrdm2 cannot wake up pwrdm1 in hardware, or + * 0 upon success. + */ +int pwrdm_add_wkdep(struct powerdomain *pwrdm1, struct powerdomain *pwrdm2) +{ + struct powerdomain *p; + + if (!pwrdm1) + return -EINVAL; + + p = _pwrdm_deps_lookup(pwrdm2, pwrdm1->wkdep_srcs); + if (IS_ERR(p)) { + pr_debug("powerdomain: hardware cannot set/clear wake up of " + "%s when %s wakes up\n", pwrdm1->name, pwrdm2->name); + return IS_ERR(p); + } + + pr_debug("powerdomain: hardware will wake up %s when %s wakes up\n", + pwrdm1->name, pwrdm2->name); + + prm_set_mod_reg_bits((1 << pwrdm2->dep_bit), + pwrdm1->prcm_offs, PM_WKDEP); + + return 0; +} + +/** + * pwrdm_del_wkdep - remove a wakeup dependency from pwrdm2 to pwrdm1 + * @pwrdm1: wake this struct powerdomain * up (dependent) + * @pwrdm2: when this struct powerdomain * wakes up (source) + * + * Remove a wakeup dependency that causes pwrdm1 to wake up when pwrdm2 + * wakes up. Returns -EINVAL if presented with invalid powerdomain + * pointers, -ENOENT if pwrdm2 cannot wake up pwrdm1 in hardware, or + * 0 upon success. + */ +int pwrdm_del_wkdep(struct powerdomain *pwrdm1, struct powerdomain *pwrdm2) +{ + struct powerdomain *p; + + if (!pwrdm1) + return -EINVAL; + + p = _pwrdm_deps_lookup(pwrdm2, pwrdm1->wkdep_srcs); + if (IS_ERR(p)) { + pr_debug("powerdomain: hardware cannot set/clear wake up of " + "%s when %s wakes up\n", pwrdm1->name, pwrdm2->name); + return IS_ERR(p); + } + + pr_debug("powerdomain: hardware will no longer wake up %s after %s " + "wakes up\n", pwrdm1->name, pwrdm2->name); + + prm_clear_mod_reg_bits((1 << pwrdm2->dep_bit), + pwrdm1->prcm_offs, PM_WKDEP); + + return 0; +} + +/** + * pwrdm_read_wkdep - read wakeup dependency state from pwrdm2 to pwrdm1 + * @pwrdm1: wake this struct powerdomain * up (dependent) + * @pwrdm2: when this struct powerdomain * wakes up (source) + * + * Return 1 if a hardware wakeup dependency exists wherein pwrdm1 will be + * awoken when pwrdm2 wakes up; 0 if dependency is not set; -EINVAL + * if either powerdomain pointer is invalid; or -ENOENT if the hardware + * is incapable. + * + * REVISIT: Currently this function only represents software-controllable + * wakeup dependencies. Wakeup dependencies fixed in hardware are not + * yet handled here. + */ +int pwrdm_read_wkdep(struct powerdomain *pwrdm1, struct powerdomain *pwrdm2) +{ + struct powerdomain *p; + + if (!pwrdm1) + return -EINVAL; + + p = _pwrdm_deps_lookup(pwrdm2, pwrdm1->wkdep_srcs); + if (IS_ERR(p)) { + pr_debug("powerdomain: hardware cannot set/clear wake up of " + "%s when %s wakes up\n", pwrdm1->name, pwrdm2->name); + return IS_ERR(p); + } + + return prm_read_mod_bits_shift(pwrdm1->prcm_offs, PM_WKDEP, + (1 << pwrdm2->dep_bit)); +} + +/** + * pwrdm_add_sleepdep - add a sleep dependency from pwrdm2 to pwrdm1 + * @pwrdm1: prevent this struct powerdomain * from sleeping (dependent) + * @pwrdm2: when this struct powerdomain * is active (source) + * + * Prevent pwrdm1 from automatically going inactive (and then to + * retention or off) if pwrdm2 is still active. Returns -EINVAL if + * presented with invalid powerdomain pointers or called on a machine + * that does not support software-configurable hardware sleep dependencies, + * -ENOENT if the specified dependency cannot be set in hardware, or + * 0 upon success. + */ +int pwrdm_add_sleepdep(struct powerdomain *pwrdm1, struct powerdomain *pwrdm2) +{ + struct powerdomain *p; + + if (!pwrdm1) + return -EINVAL; + + if (!cpu_is_omap34xx()) + return -EINVAL; + + p = _pwrdm_deps_lookup(pwrdm2, pwrdm1->sleepdep_srcs); + if (IS_ERR(p)) { + pr_debug("powerdomain: hardware cannot set/clear sleep " + "dependency affecting %s from %s\n", pwrdm1->name, + pwrdm2->name); + return IS_ERR(p); + } + + pr_debug("powerdomain: will prevent %s from sleeping if %s is active\n", + pwrdm1->name, pwrdm2->name); + + cm_set_mod_reg_bits((1 << pwrdm2->dep_bit), + pwrdm1->prcm_offs, OMAP3430_CM_SLEEPDEP); + + return 0; +} + +/** + * pwrdm_del_sleepdep - remove a sleep dependency from pwrdm2 to pwrdm1 + * @pwrdm1: prevent this struct powerdomain * from sleeping (dependent) + * @pwrdm2: when this struct powerdomain * is active (source) + * + * Allow pwrdm1 to automatically go inactive (and then to retention or + * off), independent of the activity state of pwrdm2. Returns -EINVAL + * if presented with invalid powerdomain pointers or called on a machine + * that does not support software-configurable hardware sleep dependencies, + * -ENOENT if the specified dependency cannot be cleared in hardware, or + * 0 upon success. + */ +int pwrdm_del_sleepdep(struct powerdomain *pwrdm1, struct powerdomain *pwrdm2) +{ + struct powerdomain *p; + + if (!pwrdm1) + return -EINVAL; + + if (!cpu_is_omap34xx()) + return -EINVAL; + + p = _pwrdm_deps_lookup(pwrdm2, pwrdm1->sleepdep_srcs); + if (IS_ERR(p)) { + pr_debug("powerdomain: hardware cannot set/clear sleep " + "dependency affecting %s from %s\n", pwrdm1->name, + pwrdm2->name); + return IS_ERR(p); + } + + pr_debug("powerdomain: will no longer prevent %s from sleeping if " + "%s is active\n", pwrdm1->name, pwrdm2->name); + + cm_clear_mod_reg_bits((1 << pwrdm2->dep_bit), + pwrdm1->prcm_offs, OMAP3430_CM_SLEEPDEP); + + return 0; +} + +/** + * pwrdm_read_sleepdep - read sleep dependency state from pwrdm2 to pwrdm1 + * @pwrdm1: prevent this struct powerdomain * from sleeping (dependent) + * @pwrdm2: when this struct powerdomain * is active (source) + * + * Return 1 if a hardware sleep dependency exists wherein pwrdm1 will + * not be allowed to automatically go inactive if pwrdm2 is active; + * 0 if pwrdm1's automatic power state inactivity transition is independent + * of pwrdm2's; -EINVAL if either powerdomain pointer is invalid or called + * on a machine that does not support software-configurable hardware sleep + * dependencies; or -ENOENT if the hardware is incapable. + * + * REVISIT: Currently this function only represents software-controllable + * sleep dependencies. Sleep dependencies fixed in hardware are not + * yet handled here. + */ +int pwrdm_read_sleepdep(struct powerdomain *pwrdm1, struct powerdomain *pwrdm2) +{ + struct powerdomain *p; + + if (!pwrdm1) + return -EINVAL; + + if (!cpu_is_omap34xx()) + return -EINVAL; + + p = _pwrdm_deps_lookup(pwrdm2, pwrdm1->sleepdep_srcs); + if (IS_ERR(p)) { + pr_debug("powerdomain: hardware cannot set/clear sleep " + "dependency affecting %s from %s\n", pwrdm1->name, + pwrdm2->name); + return IS_ERR(p); + } + + return prm_read_mod_bits_shift(pwrdm1->prcm_offs, OMAP3430_CM_SLEEPDEP, + (1 << pwrdm2->dep_bit)); +} + +/** + * pwrdm_get_mem_bank_count - get number of memory banks in this powerdomain + * @pwrdm: struct powerdomain * + * + * Return the number of controllable memory banks in powerdomain pwrdm, + * starting with 1. Returns -EINVAL if the powerdomain pointer is null. + */ +int pwrdm_get_mem_bank_count(struct powerdomain *pwrdm) +{ + if (!pwrdm) + return -EINVAL; + + return pwrdm->banks; +} + +/** + * pwrdm_set_next_pwrst - set next powerdomain power state + * @pwrdm: struct powerdomain * to set + * @pwrst: one of the PWRDM_POWER_* macros + * + * Set the powerdomain pwrdm's next power state to pwrst. The powerdomain + * may not enter this state immediately if the preconditions for this state + * have not been satisfied. Returns -EINVAL if the powerdomain pointer is + * null or if the power state is invalid for the powerdomin, or returns 0 + * upon success. + */ +int pwrdm_set_next_pwrst(struct powerdomain *pwrdm, u8 pwrst) +{ + if (!pwrdm) + return -EINVAL; + + if (!(pwrdm->pwrsts & (1 << pwrst))) + return -EINVAL; + + pr_debug("powerdomain: setting next powerstate for %s to %0x\n", + pwrdm->name, pwrst); + + prm_rmw_mod_reg_bits(OMAP_POWERSTATE_MASK, + (pwrst << OMAP_POWERSTATE_SHIFT), + pwrdm->prcm_offs, PM_PWSTCTRL); + + return 0; +} + +/** + * pwrdm_read_next_pwrst - get next powerdomain power state + * @pwrdm: struct powerdomain * to get power state + * + * Return the powerdomain pwrdm's next power state. Returns -EINVAL + * if the powerdomain pointer is null or returns the next power state + * upon success. + */ +int pwrdm_read_next_pwrst(struct powerdomain *pwrdm) +{ + if (!pwrdm) + return -EINVAL; + + return prm_read_mod_bits_shift(pwrdm->prcm_offs, PM_PWSTCTRL, + OMAP_POWERSTATE_MASK); +} + +/** + * pwrdm_read_pwrst - get current powerdomain power state + * @pwrdm: struct powerdomain * to get power state + * + * Return the powerdomain pwrdm's current power state. Returns -EINVAL + * if the powerdomain pointer is null or returns the current power state + * upon success. + */ +int pwrdm_read_pwrst(struct powerdomain *pwrdm) +{ + if (!pwrdm) + return -EINVAL; + + return prm_read_mod_bits_shift(pwrdm->prcm_offs, PM_PWSTST, + OMAP_POWERSTATEST_MASK); +} + +/** + * pwrdm_read_prev_pwrst - get previous powerdomain power state + * @pwrdm: struct powerdomain * to get previous power state + * + * Return the powerdomain pwrdm's previous power state. Returns -EINVAL + * if the powerdomain pointer is null or returns the previous power state + * upon success. + */ +int pwrdm_read_prev_pwrst(struct powerdomain *pwrdm) +{ + if (!pwrdm) + return -EINVAL; + + return prm_read_mod_bits_shift(pwrdm->prcm_offs, OMAP3430_PM_PREPWSTST, + OMAP3430_LASTPOWERSTATEENTERED_MASK); +} + +/** + * pwrdm_set_logic_retst - set powerdomain logic power state upon retention + * @pwrdm: struct powerdomain * to set + * @pwrst: one of the PWRDM_POWER_* macros + * + * Set the next power state that the logic portion of the powerdomain + * pwrdm will enter when the powerdomain enters retention. This will + * be either RETENTION or OFF, if supported. Returns -EINVAL if the + * powerdomain pointer is null or the target power state is not not + * supported, or returns 0 upon success. + */ +int pwrdm_set_logic_retst(struct powerdomain *pwrdm, u8 pwrst) +{ + if (!pwrdm) + return -EINVAL; + + if (!(pwrdm->pwrsts_logic_ret & (1 << pwrst))) + return -EINVAL; + + pr_debug("powerdomain: setting next logic powerstate for %s to %0x\n", + pwrdm->name, pwrst); + + /* + * The register bit names below may not correspond to the + * actual names of the bits in each powerdomain's register, + * but the type of value returned is the same for each + * powerdomain. + */ + prm_rmw_mod_reg_bits(OMAP3430_LOGICL1CACHERETSTATE, + (pwrst << __ffs(OMAP3430_LOGICL1CACHERETSTATE)), + pwrdm->prcm_offs, PM_PWSTCTRL); + + return 0; +} + +/** + * pwrdm_set_mem_onst - set memory power state while powerdomain ON + * @pwrdm: struct powerdomain * to set + * @bank: memory bank number to set (0-3) + * @pwrst: one of the PWRDM_POWER_* macros + * + * Set the next power state that memory bank x of the powerdomain + * pwrdm will enter when the powerdomain enters the ON state. Bank + * will be a number from 0 to 3, and represents different types of + * memory, depending on the powerdomain. Returns -EINVAL if the + * powerdomain pointer is null or the target power state is not not + * supported for this memory bank, -EEXIST if the target memory bank + * does not exist or is not controllable, or returns 0 upon success. + */ +int pwrdm_set_mem_onst(struct powerdomain *pwrdm, u8 bank, u8 pwrst) +{ + u32 m; + + if (!pwrdm) + return -EINVAL; + + if (pwrdm->banks < (bank + 1)) + return -EEXIST; + + if (!(pwrdm->pwrsts_mem_on[bank] & (1 << pwrst))) + return -EINVAL; + + pr_debug("powerdomain: setting next memory powerstate for domain %s " + "bank %0x while pwrdm-ON to %0x\n", pwrdm->name, bank, pwrst); + + /* + * The register bit names below may not correspond to the + * actual names of the bits in each powerdomain's register, + * but the type of value returned is the same for each + * powerdomain. + */ + switch (bank) { + case 0: + m = OMAP3430_SHAREDL1CACHEFLATONSTATE_MASK; + break; + case 1: + m = OMAP3430_L1FLATMEMONSTATE_MASK; + break; + case 2: + m = OMAP3430_SHAREDL2CACHEFLATONSTATE_MASK; + break; + case 3: + m = OMAP3430_L2FLATMEMONSTATE_MASK; + break; + default: + WARN_ON(1); /* should never happen */ + return -EEXIST; + } + + prm_rmw_mod_reg_bits(m, (pwrst << __ffs(m)), + pwrdm->prcm_offs, PM_PWSTCTRL); + + return 0; +} + +/** + * pwrdm_set_mem_retst - set memory power state while powerdomain in RET + * @pwrdm: struct powerdomain * to set + * @bank: memory bank number to set (0-3) + * @pwrst: one of the PWRDM_POWER_* macros + * + * Set the next power state that memory bank x of the powerdomain + * pwrdm will enter when the powerdomain enters the RETENTION state. + * Bank will be a number from 0 to 3, and represents different types + * of memory, depending on the powerdomain. pwrst will be either + * RETENTION or OFF, if supported. Returns -EINVAL if the powerdomain + * pointer is null or the target power state is not not supported for + * this memory bank, -EEXIST if the target memory bank does not exist + * or is not controllable, or returns 0 upon success. + */ +int pwrdm_set_mem_retst(struct powerdomain *pwrdm, u8 bank, u8 pwrst) +{ + u32 m; + + if (!pwrdm) + return -EINVAL; + + if (pwrdm->banks < (bank + 1)) + return -EEXIST; + + if (!(pwrdm->pwrsts_mem_ret[bank] & (1 << pwrst))) + return -EINVAL; + + pr_debug("powerdomain: setting next memory powerstate for domain %s " + "bank %0x while pwrdm-RET to %0x\n", pwrdm->name, bank, pwrst); + + /* + * The register bit names below may not correspond to the + * actual names of the bits in each powerdomain's register, + * but the type of value returned is the same for each + * powerdomain. + */ + switch (bank) { + case 0: + m = OMAP3430_SHAREDL1CACHEFLATRETSTATE; + break; + case 1: + m = OMAP3430_L1FLATMEMRETSTATE; + break; + case 2: + m = OMAP3430_SHAREDL2CACHEFLATRETSTATE; + break; + case 3: + m = OMAP3430_L2FLATMEMRETSTATE; + break; + default: + WARN_ON(1); /* should never happen */ + return -EEXIST; + } + + prm_rmw_mod_reg_bits(m, (pwrst << __ffs(m)), pwrdm->prcm_offs, + PM_PWSTCTRL); + + return 0; +} + +/** + * pwrdm_read_logic_pwrst - get current powerdomain logic retention power state + * @pwrdm: struct powerdomain * to get current logic retention power state + * + * Return the current power state that the logic portion of + * powerdomain pwrdm will enter + * Returns -EINVAL if the powerdomain pointer is null or returns the + * current logic retention power state upon success. + */ +int pwrdm_read_logic_pwrst(struct powerdomain *pwrdm) +{ + if (!pwrdm) + return -EINVAL; + + return prm_read_mod_bits_shift(pwrdm->prcm_offs, PM_PWSTST, + OMAP3430_LOGICSTATEST); +} + +/** + * pwrdm_read_prev_logic_pwrst - get previous powerdomain logic power state + * @pwrdm: struct powerdomain * to get previous logic power state + * + * Return the powerdomain pwrdm's logic power state. Returns -EINVAL + * if the powerdomain pointer is null or returns the previous logic + * power state upon success. + */ +int pwrdm_read_prev_logic_pwrst(struct powerdomain *pwrdm) +{ + if (!pwrdm) + return -EINVAL; + + /* + * The register bit names below may not correspond to the + * actual names of the bits in each powerdomain's register, + * but the type of value returned is the same for each + * powerdomain. + */ + return prm_read_mod_bits_shift(pwrdm->prcm_offs, OMAP3430_PM_PREPWSTST, + OMAP3430_LASTLOGICSTATEENTERED); +} + +/** + * pwrdm_read_mem_pwrst - get current memory bank power state + * @pwrdm: struct powerdomain * to get current memory bank power state + * @bank: memory bank number (0-3) + * + * Return the powerdomain pwrdm's current memory power state for bank + * x. Returns -EINVAL if the powerdomain pointer is null, -EEXIST if + * the target memory bank does not exist or is not controllable, or + * returns the current memory power state upon success. + */ +int pwrdm_read_mem_pwrst(struct powerdomain *pwrdm, u8 bank) +{ + u32 m; + + if (!pwrdm) + return -EINVAL; + + if (pwrdm->banks < (bank + 1)) + return -EEXIST; + + /* + * The register bit names below may not correspond to the + * actual names of the bits in each powerdomain's register, + * but the type of value returned is the same for each + * powerdomain. + */ + switch (bank) { + case 0: + m = OMAP3430_SHAREDL1CACHEFLATSTATEST_MASK; + break; + case 1: + m = OMAP3430_L1FLATMEMSTATEST_MASK; + break; + case 2: + m = OMAP3430_SHAREDL2CACHEFLATSTATEST_MASK; + break; + case 3: + m = OMAP3430_L2FLATMEMSTATEST_MASK; + break; + default: + WARN_ON(1); /* should never happen */ + return -EEXIST; + } + + return prm_read_mod_bits_shift(pwrdm->prcm_offs, PM_PWSTST, m); +} + +/** + * pwrdm_read_prev_mem_pwrst - get previous memory bank power state + * @pwrdm: struct powerdomain * to get previous memory bank power state + * @bank: memory bank number (0-3) + * + * Return the powerdomain pwrdm's previous memory power state for bank + * x. Returns -EINVAL if the powerdomain pointer is null, -EEXIST if + * the target memory bank does not exist or is not controllable, or + * returns the previous memory power state upon success. + */ +int pwrdm_read_prev_mem_pwrst(struct powerdomain *pwrdm, u8 bank) +{ + u32 m; + + if (!pwrdm) + return -EINVAL; + + if (pwrdm->banks < (bank + 1)) + return -EEXIST; + + /* + * The register bit names below may not correspond to the + * actual names of the bits in each powerdomain's register, + * but the type of value returned is the same for each + * powerdomain. + */ + switch (bank) { + case 0: + m = OMAP3430_LASTMEM1STATEENTERED_MASK; + break; + case 1: + m = OMAP3430_LASTMEM2STATEENTERED_MASK; + break; + case 2: + m = OMAP3430_LASTSHAREDL2CACHEFLATSTATEENTERED_MASK; + break; + case 3: + m = OMAP3430_LASTL2FLATMEMSTATEENTERED_MASK; + break; + default: + WARN_ON(1); /* should never happen */ + return -EEXIST; + } + + return prm_read_mod_bits_shift(pwrdm->prcm_offs, + OMAP3430_PM_PREPWSTST, m); +} + +/** + * pwrdm_clear_all_prev_pwrst - clear previous powerstate register for a pwrdm + * @pwrdm: struct powerdomain * to clear + * + * Clear the powerdomain's previous power state register. Clears the + * entire register, including logic and memory bank previous power states. + * Returns -EINVAL if the powerdomain pointer is null, or returns 0 upon + * success. + */ +int pwrdm_clear_all_prev_pwrst(struct powerdomain *pwrdm) +{ + if (!pwrdm) + return -EINVAL; + + /* + * XXX should get the powerdomain's current state here; + * warn & fail if it is not ON. + */ + + pr_debug("powerdomain: clearing previous power state reg for %s\n", + pwrdm->name); + + prm_write_mod_reg(0, pwrdm->prcm_offs, OMAP3430_PM_PREPWSTST); + + return 0; +} + +/** + * pwrdm_enable_hdwr_sar - enable automatic hardware SAR for a pwrdm + * @pwrdm: struct powerdomain * + * + * Enable automatic context save-and-restore upon power state change + * for some devices in a powerdomain. Warning: this only affects a + * subset of devices in a powerdomain; check the TRM closely. Returns + * -EINVAL if the powerdomain pointer is null or if the powerdomain + * does not support automatic save-and-restore, or returns 0 upon + * success. + */ +int pwrdm_enable_hdwr_sar(struct powerdomain *pwrdm) +{ + if (!pwrdm) + return -EINVAL; + + if (!(pwrdm->flags & PWRDM_HAS_HDWR_SAR)) + return -EINVAL; + + pr_debug("powerdomain: %s: setting SAVEANDRESTORE bit\n", + pwrdm->name); + + prm_rmw_mod_reg_bits(0, 1 << OMAP3430ES2_SAVEANDRESTORE_SHIFT, + pwrdm->prcm_offs, PM_PWSTCTRL); + + return 0; +} + +/** + * pwrdm_disable_hdwr_sar - disable automatic hardware SAR for a pwrdm + * @pwrdm: struct powerdomain * + * + * Disable automatic context save-and-restore upon power state change + * for some devices in a powerdomain. Warning: this only affects a + * subset of devices in a powerdomain; check the TRM closely. Returns + * -EINVAL if the powerdomain pointer is null or if the powerdomain + * does not support automatic save-and-restore, or returns 0 upon + * success. + */ +int pwrdm_disable_hdwr_sar(struct powerdomain *pwrdm) +{ + if (!pwrdm) + return -EINVAL; + + if (!(pwrdm->flags & PWRDM_HAS_HDWR_SAR)) + return -EINVAL; + + pr_debug("powerdomain: %s: clearing SAVEANDRESTORE bit\n", + pwrdm->name); + + prm_rmw_mod_reg_bits(1 << OMAP3430ES2_SAVEANDRESTORE_SHIFT, 0, + pwrdm->prcm_offs, PM_PWSTCTRL); + + return 0; +} + +/** + * pwrdm_has_hdwr_sar - test whether powerdomain supports hardware SAR + * @pwrdm: struct powerdomain * + * + * Returns 1 if powerdomain 'pwrdm' supports hardware save-and-restore + * for some devices, or 0 if it does not. + */ +bool pwrdm_has_hdwr_sar(struct powerdomain *pwrdm) +{ + return (pwrdm && pwrdm->flags & PWRDM_HAS_HDWR_SAR) ? 1 : 0; +} + +/** + * pwrdm_wait_transition - wait for powerdomain power transition to finish + * @pwrdm: struct powerdomain * to wait for + * + * If the powerdomain pwrdm is in the process of a state transition, + * spin until it completes the power transition, or until an iteration + * bailout value is reached. Returns -EINVAL if the powerdomain + * pointer is null, -EAGAIN if the bailout value was reached, or + * returns 0 upon success. + */ +int pwrdm_wait_transition(struct powerdomain *pwrdm) +{ + u32 c = 0; + + if (!pwrdm) + return -EINVAL; + + /* + * REVISIT: pwrdm_wait_transition() may be better implemented + * via a callback and a periodic timer check -- how long do we expect + * powerdomain transitions to take? + */ + + /* XXX Is this udelay() value meaningful? */ + while ((prm_read_mod_reg(pwrdm->prcm_offs, PM_PWSTST) & + OMAP_INTRANSITION) && + (c++ < PWRDM_TRANSITION_BAILOUT)) + udelay(1); + + if (c >= PWRDM_TRANSITION_BAILOUT) { + printk(KERN_ERR "powerdomain: waited too long for " + "powerdomain %s to complete transition\n", pwrdm->name); + return -EAGAIN; + } + + pr_debug("powerdomain: completed transition in %d loops\n", c); + + return 0; +} + + diff --cc arch/arm/mach-omap2/powerdomains.h index 315281148c0,00000000000..fba34406e65 mode 100644,000000..100644 --- a/arch/arm/mach-omap2/powerdomains.h +++ b/arch/arm/mach-omap2/powerdomains.h @@@ -1,188 -1,0 +1,188 @@@ +/* + * OMAP2/3 common powerdomain definitions + * + * Copyright (C) 2007-8 Texas Instruments, Inc. + * Copyright (C) 2007-8 Nokia Corporation + * + * Written by Paul Walmsley + * Debugging and integration fixes by Jouni Högander + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef ARCH_ARM_MACH_OMAP2_POWERDOMAINS +#define ARCH_ARM_MACH_OMAP2_POWERDOMAINS + +/* + * This file contains all of the powerdomains that have some element + * of software control for the OMAP24xx and OMAP34XX chips. + * + * A few notes: + * + * This is not an exhaustive listing of powerdomains on the chips; only + * powerdomains that can be controlled in software. + * + * A useful validation rule for struct powerdomain: + * Any powerdomain referenced by a wkdep_srcs or sleepdep_srcs array + * must have a dep_bit assigned. So wkdep_srcs/sleepdep_srcs are really + * just software-controllable dependencies. Non-software-controllable + * dependencies do exist, but they are not encoded below (yet). + * + * 24xx does not support programmable sleep dependencies (SLEEPDEP) + * + */ + +/* + * The names for the DSP/IVA2 powerdomains are confusing. + * + * Most OMAP chips have an on-board DSP. + * + * On the 2420, this is a 'C55 DSP called, simply, the DSP. Its + * powerdomain is called the "DSP power domain." On the 2430, the + * on-board DSP is a 'C64 DSP, now called the IVA2 or IVA2.1. Its + * powerdomain is still called the "DSP power domain." On the 3430, + * the DSP is a 'C64 DSP like the 2430, also known as the IVA2; but + * its powerdomain is now called the "IVA2 power domain." + * + * The 2420 also has something called the IVA, which is a separate ARM + * core, and has nothing to do with the DSP/IVA2. + * + * Ideally the DSP/IVA2 could just be the same powerdomain, but the PRCM + * address offset is different between the C55 and C64 DSPs. + * + * The overly-specific dep_bit names are due to a bit name collision + * with CM_FCLKEN_{DSP,IVA2}. The DSP/IVA2 PM_WKDEP and CM_SLEEPDEP shift + * value are the same for all powerdomains: 2 + */ + +/* + * XXX should dep_bit be a mask, so we can test to see if it is 0 as a + * sanity check? + * XXX encode hardware fixed wakeup dependencies -- esp. for 3430 CORE + */ + - #include ++#include + +#include "prcm-common.h" +#include "prm.h" +#include "cm.h" + +/* OMAP2/3-common powerdomains and wakeup dependencies */ + +/* + * 2420/2430 PM_WKDEP_GFX: CORE, MPU, WKUP + * 3430ES1 PM_WKDEP_GFX: adds IVA2, removes CORE + * 3430ES2 PM_WKDEP_SGX: adds IVA2, removes CORE + */ +static struct pwrdm_dep gfx_sgx_wkdeps[] = { + { + .pwrdm_name = "core_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP24XX) + }, + { + .pwrdm_name = "iva2_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430) + }, + { + .pwrdm_name = "mpu_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP24XX | + CHIP_IS_OMAP3430) + }, + { + .pwrdm_name = "wkup_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP24XX | + CHIP_IS_OMAP3430) + }, + { NULL }, +}; + +/* + * 3430: CM_SLEEPDEP_CAM: MPU + * 3430ES1: CM_SLEEPDEP_GFX: MPU + * 3430ES2: CM_SLEEPDEP_SGX: MPU + */ +static struct pwrdm_dep cam_gfx_sleepdeps[] = { + { + .pwrdm_name = "mpu_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430) + }, + { NULL }, +}; + + +#include "powerdomains24xx.h" +#include "powerdomains34xx.h" + + +/* + * OMAP2/3 common powerdomains + */ + +/* + * The GFX powerdomain is not present on 3430ES2, but currently we do not + * have a macro to filter it out at compile-time. + */ +static struct powerdomain gfx_pwrdm = { + .name = "gfx_pwrdm", + .prcm_offs = GFX_MOD, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP24XX | + CHIP_IS_OMAP3430ES1), + .wkdep_srcs = gfx_sgx_wkdeps, + .sleepdep_srcs = cam_gfx_sleepdeps, + .pwrsts = PWRSTS_OFF_RET_ON, + .pwrsts_logic_ret = PWRDM_POWER_RET, + .banks = 1, + .pwrsts_mem_ret = { + [0] = PWRDM_POWER_RET, /* MEMRETSTATE */ + }, + .pwrsts_mem_on = { + [0] = PWRDM_POWER_ON, /* MEMONSTATE */ + }, +}; + +static struct powerdomain wkup_pwrdm = { + .name = "wkup_pwrdm", + .prcm_offs = WKUP_MOD, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP24XX | CHIP_IS_OMAP3430), + .dep_bit = OMAP_EN_WKUP_SHIFT, +}; + + + +/* As powerdomains are added or removed above, this list must also be changed */ +static struct powerdomain *powerdomains_omap[] __initdata = { + + &gfx_pwrdm, + &wkup_pwrdm, + +#ifdef CONFIG_ARCH_OMAP24XX + &dsp_pwrdm, + &mpu_24xx_pwrdm, + &core_24xx_pwrdm, +#endif + +#ifdef CONFIG_ARCH_OMAP2430 + &mdm_pwrdm, +#endif + +#ifdef CONFIG_ARCH_OMAP34XX + &iva2_pwrdm, + &mpu_34xx_pwrdm, + &neon_pwrdm, + &core_34xx_es1_pwrdm, + &core_34xx_es2_pwrdm, + &cam_pwrdm, + &dss_pwrdm, + &per_pwrdm, + &emu_pwrdm, + &sgx_pwrdm, + &usbhost_pwrdm, +#endif + + NULL +}; + + +#endif diff --cc arch/arm/mach-omap2/powerdomains24xx.h index 6f97c91b234,00000000000..9f08dc3f7fd mode 100644,000000..100644 --- a/arch/arm/mach-omap2/powerdomains24xx.h +++ b/arch/arm/mach-omap2/powerdomains24xx.h @@@ -1,200 -1,0 +1,200 @@@ +/* + * OMAP24XX powerdomain definitions + * + * Copyright (C) 2007-2008 Texas Instruments, Inc. + * Copyright (C) 2007-2008 Nokia Corporation + * + * Written by Paul Walmsley + * Debugging and integration fixes by Jouni Högander + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef ARCH_ARM_MACH_OMAP2_POWERDOMAINS24XX +#define ARCH_ARM_MACH_OMAP2_POWERDOMAINS24XX + +/* + * N.B. If powerdomains are added or removed from this file, update + * the array in mach-omap2/powerdomains.h. + */ + - #include ++#include + +#include "prcm-common.h" +#include "prm.h" +#include "prm-regbits-24xx.h" +#include "cm.h" +#include "cm-regbits-24xx.h" + +/* 24XX powerdomains and dependencies */ + +#ifdef CONFIG_ARCH_OMAP24XX + + +/* Wakeup dependency source arrays */ + +/* + * 2420/2430 PM_WKDEP_DSP: CORE, MPU, WKUP + * 2430 PM_WKDEP_MDM: same as above + */ +static struct pwrdm_dep dsp_mdm_24xx_wkdeps[] = { + { + .pwrdm_name = "core_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP24XX) + }, + { + .pwrdm_name = "mpu_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP24XX) + }, + { + .pwrdm_name = "wkup_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP24XX) + }, + { NULL }, +}; + +/* + * 2420 PM_WKDEP_MPU: CORE, DSP, WKUP + * 2430 adds MDM + */ +static struct pwrdm_dep mpu_24xx_wkdeps[] = { + { + .pwrdm_name = "core_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP24XX) + }, + { + .pwrdm_name = "dsp_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP24XX) + }, + { + .pwrdm_name = "wkup_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP24XX) + }, + { + .pwrdm_name = "mdm_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP2430) + }, + { NULL }, +}; + +/* + * 2420 PM_WKDEP_CORE: DSP, GFX, MPU, WKUP + * 2430 adds MDM + */ +static struct pwrdm_dep core_24xx_wkdeps[] = { + { + .pwrdm_name = "dsp_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP24XX) + }, + { + .pwrdm_name = "gfx_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP24XX) + }, + { + .pwrdm_name = "mpu_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP24XX) + }, + { + .pwrdm_name = "wkup_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP24XX) + }, + { + .pwrdm_name = "mdm_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP2430) + }, + { NULL }, +}; + + +/* Powerdomains */ + +static struct powerdomain dsp_pwrdm = { + .name = "dsp_pwrdm", + .prcm_offs = OMAP24XX_DSP_MOD, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP24XX), + .dep_bit = OMAP24XX_PM_WKDEP_MPU_EN_DSP_SHIFT, + .wkdep_srcs = dsp_mdm_24xx_wkdeps, + .pwrsts = PWRSTS_OFF_RET_ON, + .pwrsts_logic_ret = PWRDM_POWER_RET, + .banks = 1, + .pwrsts_mem_ret = { + [0] = PWRDM_POWER_RET, + }, + .pwrsts_mem_on = { + [0] = PWRDM_POWER_ON, + }, +}; + +static struct powerdomain mpu_24xx_pwrdm = { + .name = "mpu_pwrdm", + .prcm_offs = MPU_MOD, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP24XX), + .dep_bit = OMAP24XX_EN_MPU_SHIFT, + .wkdep_srcs = mpu_24xx_wkdeps, + .pwrsts = PWRSTS_OFF_RET_ON, + .pwrsts_logic_ret = PWRSTS_OFF_RET, + .banks = 1, + .pwrsts_mem_ret = { + [0] = PWRDM_POWER_RET, + }, + .pwrsts_mem_on = { + [0] = PWRDM_POWER_ON, + }, +}; + +static struct powerdomain core_24xx_pwrdm = { + .name = "core_pwrdm", + .prcm_offs = CORE_MOD, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP24XX), + .wkdep_srcs = core_24xx_wkdeps, + .pwrsts = PWRSTS_OFF_RET_ON, + .dep_bit = OMAP24XX_EN_CORE_SHIFT, + .banks = 3, + .pwrsts_mem_ret = { + [0] = PWRSTS_OFF_RET, /* MEM1RETSTATE */ + [1] = PWRSTS_OFF_RET, /* MEM2RETSTATE */ + [2] = PWRSTS_OFF_RET, /* MEM3RETSTATE */ + }, + .pwrsts_mem_on = { + [0] = PWRSTS_OFF_RET_ON, /* MEM1ONSTATE */ + [1] = PWRSTS_OFF_RET_ON, /* MEM2ONSTATE */ + [2] = PWRSTS_OFF_RET_ON, /* MEM3ONSTATE */ + }, +}; + +#endif /* CONFIG_ARCH_OMAP24XX */ + + + +/* + * 2430-specific powerdomains + */ + +#ifdef CONFIG_ARCH_OMAP2430 + +/* XXX 2430 KILLDOMAINWKUP bit? No current users apparently */ + +/* Another case of bit name collisions between several registers: EN_MDM */ +static struct powerdomain mdm_pwrdm = { + .name = "mdm_pwrdm", + .prcm_offs = OMAP2430_MDM_MOD, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP2430), + .dep_bit = OMAP2430_PM_WKDEP_MPU_EN_MDM_SHIFT, + .wkdep_srcs = dsp_mdm_24xx_wkdeps, + .pwrsts = PWRSTS_OFF_RET_ON, + .pwrsts_logic_ret = PWRDM_POWER_RET, + .banks = 1, + .pwrsts_mem_ret = { + [0] = PWRDM_POWER_RET, /* MEMRETSTATE */ + }, + .pwrsts_mem_on = { + [0] = PWRDM_POWER_ON, /* MEMONSTATE */ + }, +}; + +#endif /* CONFIG_ARCH_OMAP2430 */ + + +#endif diff --cc arch/arm/mach-omap2/powerdomains34xx.h index 0c07bf75715,00000000000..adbfa9187a6 mode 100644,000000..100644 --- a/arch/arm/mach-omap2/powerdomains34xx.h +++ b/arch/arm/mach-omap2/powerdomains34xx.h @@@ -1,347 -1,0 +1,347 @@@ +/* + * OMAP34XX powerdomain definitions + * + * Copyright (C) 2007-2008 Texas Instruments, Inc. + * Copyright (C) 2007-2008 Nokia Corporation + * + * Written by Paul Walmsley + * Debugging and integration fixes by Jouni Högander + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef ARCH_ARM_MACH_OMAP2_POWERDOMAINS34XX +#define ARCH_ARM_MACH_OMAP2_POWERDOMAINS34XX + +/* + * N.B. If powerdomains are added or removed from this file, update + * the array in mach-omap2/powerdomains.h. + */ + - #include ++#include + +#include "prcm-common.h" +#include "prm.h" +#include "prm-regbits-34xx.h" +#include "cm.h" +#include "cm-regbits-34xx.h" + +/* + * 34XX-specific powerdomains, dependencies + */ + +#ifdef CONFIG_ARCH_OMAP34XX + +/* + * 3430: PM_WKDEP_{PER,USBHOST}: CORE, IVA2, MPU, WKUP + * (USBHOST is ES2 only) + */ +static struct pwrdm_dep per_usbhost_wkdeps[] = { + { + .pwrdm_name = "core_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430) + }, + { + .pwrdm_name = "iva2_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430) + }, + { + .pwrdm_name = "mpu_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430) + }, + { + .pwrdm_name = "wkup_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430) + }, + { NULL }, +}; + +/* + * 3430 PM_WKDEP_MPU: CORE, IVA2, DSS, PER + */ +static struct pwrdm_dep mpu_34xx_wkdeps[] = { + { + .pwrdm_name = "core_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430) + }, + { + .pwrdm_name = "iva2_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430) + }, + { + .pwrdm_name = "dss_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430) + }, + { + .pwrdm_name = "per_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430) + }, + { NULL }, +}; + +/* + * 3430 PM_WKDEP_IVA2: CORE, MPU, WKUP, DSS, PER + */ +static struct pwrdm_dep iva2_wkdeps[] = { + { + .pwrdm_name = "core_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430) + }, + { + .pwrdm_name = "mpu_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430) + }, + { + .pwrdm_name = "wkup_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430) + }, + { + .pwrdm_name = "dss_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430) + }, + { + .pwrdm_name = "per_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430) + }, + { NULL }, +}; + + +/* 3430 PM_WKDEP_{CAM,DSS}: IVA2, MPU, WKUP */ +static struct pwrdm_dep cam_dss_wkdeps[] = { + { + .pwrdm_name = "iva2_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430) + }, + { + .pwrdm_name = "mpu_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430) + }, + { + .pwrdm_name = "wkup_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430) + }, + { NULL }, +}; + +/* 3430: PM_WKDEP_NEON: MPU */ +static struct pwrdm_dep neon_wkdeps[] = { + { + .pwrdm_name = "mpu_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430) + }, + { NULL }, +}; + + +/* Sleep dependency source arrays for 34xx-specific pwrdms - 34XX only */ + +/* + * 3430: CM_SLEEPDEP_{DSS,PER}: MPU, IVA + * 3430ES2: CM_SLEEPDEP_USBHOST: MPU, IVA + */ +static struct pwrdm_dep dss_per_usbhost_sleepdeps[] = { + { + .pwrdm_name = "mpu_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430) + }, + { + .pwrdm_name = "iva2_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430) + }, + { NULL }, +}; + + +/* + * Powerdomains + */ + +static struct powerdomain iva2_pwrdm = { + .name = "iva2_pwrdm", + .prcm_offs = OMAP3430_IVA2_MOD, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + .dep_bit = OMAP3430_PM_WKDEP_MPU_EN_IVA2_SHIFT, + .wkdep_srcs = iva2_wkdeps, + .pwrsts = PWRSTS_OFF_RET_ON, + .pwrsts_logic_ret = PWRSTS_OFF_RET, + .banks = 4, + .pwrsts_mem_ret = { + [0] = PWRSTS_OFF_RET, + [1] = PWRSTS_OFF_RET, + [2] = PWRSTS_OFF_RET, + [3] = PWRSTS_OFF_RET, + }, + .pwrsts_mem_on = { + [0] = PWRDM_POWER_ON, + [1] = PWRDM_POWER_ON, + [2] = PWRSTS_OFF_ON, + [3] = PWRDM_POWER_ON, + }, +}; + +static struct powerdomain mpu_34xx_pwrdm = { + .name = "mpu_pwrdm", + .prcm_offs = MPU_MOD, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + .dep_bit = OMAP3430_EN_MPU_SHIFT, + .wkdep_srcs = mpu_34xx_wkdeps, + .pwrsts = PWRSTS_OFF_RET_ON, + .pwrsts_logic_ret = PWRSTS_OFF_RET, + .banks = 1, + .pwrsts_mem_ret = { + [0] = PWRSTS_OFF_RET, + }, + .pwrsts_mem_on = { + [0] = PWRSTS_OFF_ON, + }, +}; + +/* No wkdeps or sleepdeps for 34xx core apparently */ +static struct powerdomain core_34xx_es1_pwrdm = { + .name = "core_pwrdm", + .prcm_offs = CORE_MOD, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430ES1), + .pwrsts = PWRSTS_OFF_RET_ON, + .dep_bit = OMAP3430_EN_CORE_SHIFT, + .banks = 2, + .pwrsts_mem_ret = { + [0] = PWRSTS_OFF_RET, /* MEM1RETSTATE */ + [1] = PWRSTS_OFF_RET, /* MEM2RETSTATE */ + }, + .pwrsts_mem_on = { + [0] = PWRSTS_OFF_RET_ON, /* MEM1ONSTATE */ + [1] = PWRSTS_OFF_RET_ON, /* MEM2ONSTATE */ + }, +}; + +/* No wkdeps or sleepdeps for 34xx core apparently */ +static struct powerdomain core_34xx_es2_pwrdm = { + .name = "core_pwrdm", + .prcm_offs = CORE_MOD, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430ES2), + .pwrsts = PWRSTS_OFF_RET_ON, + .dep_bit = OMAP3430_EN_CORE_SHIFT, + .flags = PWRDM_HAS_HDWR_SAR, /* for USBTLL only */ + .banks = 2, + .pwrsts_mem_ret = { + [0] = PWRSTS_OFF_RET, /* MEM1RETSTATE */ + [1] = PWRSTS_OFF_RET, /* MEM2RETSTATE */ + }, + .pwrsts_mem_on = { + [0] = PWRSTS_OFF_RET_ON, /* MEM1ONSTATE */ + [1] = PWRSTS_OFF_RET_ON, /* MEM2ONSTATE */ + }, +}; + +/* Another case of bit name collisions between several registers: EN_DSS */ +static struct powerdomain dss_pwrdm = { + .name = "dss_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + .prcm_offs = OMAP3430_DSS_MOD, + .dep_bit = OMAP3430_PM_WKDEP_MPU_EN_DSS_SHIFT, + .wkdep_srcs = cam_dss_wkdeps, + .sleepdep_srcs = dss_per_usbhost_sleepdeps, + .pwrsts = PWRSTS_OFF_RET_ON, + .pwrsts_logic_ret = PWRDM_POWER_RET, + .banks = 1, + .pwrsts_mem_ret = { + [0] = PWRDM_POWER_RET, /* MEMRETSTATE */ + }, + .pwrsts_mem_on = { + [0] = PWRDM_POWER_ON, /* MEMONSTATE */ + }, +}; + +static struct powerdomain sgx_pwrdm = { + .name = "sgx_pwrdm", + .prcm_offs = OMAP3430ES2_SGX_MOD, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430ES2), + .wkdep_srcs = gfx_sgx_wkdeps, + .sleepdep_srcs = cam_gfx_sleepdeps, + /* XXX This is accurate for 3430 SGX, but what about GFX? */ + .pwrsts = PWRSTS_OFF_RET_ON, + .pwrsts_logic_ret = PWRDM_POWER_RET, + .banks = 1, + .pwrsts_mem_ret = { + [0] = PWRDM_POWER_RET, /* MEMRETSTATE */ + }, + .pwrsts_mem_on = { + [0] = PWRDM_POWER_ON, /* MEMONSTATE */ + }, +}; + +static struct powerdomain cam_pwrdm = { + .name = "cam_pwrdm", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + .prcm_offs = OMAP3430_CAM_MOD, + .wkdep_srcs = cam_dss_wkdeps, + .sleepdep_srcs = cam_gfx_sleepdeps, + .pwrsts = PWRSTS_OFF_RET_ON, + .pwrsts_logic_ret = PWRDM_POWER_RET, + .banks = 1, + .pwrsts_mem_ret = { + [0] = PWRDM_POWER_RET, /* MEMRETSTATE */ + }, + .pwrsts_mem_on = { + [0] = PWRDM_POWER_ON, /* MEMONSTATE */ + }, +}; + +static struct powerdomain per_pwrdm = { + .name = "per_pwrdm", + .prcm_offs = OMAP3430_PER_MOD, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + .dep_bit = OMAP3430_EN_PER_SHIFT, + .wkdep_srcs = per_usbhost_wkdeps, + .sleepdep_srcs = dss_per_usbhost_sleepdeps, + .pwrsts = PWRSTS_OFF_RET_ON, + .pwrsts_logic_ret = PWRSTS_OFF_RET, + .banks = 1, + .pwrsts_mem_ret = { + [0] = PWRDM_POWER_RET, /* MEMRETSTATE */ + }, + .pwrsts_mem_on = { + [0] = PWRDM_POWER_ON, /* MEMONSTATE */ + }, +}; + +static struct powerdomain emu_pwrdm = { + .name = "emu_pwrdm", + .prcm_offs = OMAP3430_EMU_MOD, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), +}; + +static struct powerdomain neon_pwrdm = { + .name = "neon_pwrdm", + .prcm_offs = OMAP3430_NEON_MOD, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + .wkdep_srcs = neon_wkdeps, + .pwrsts = PWRSTS_OFF_RET_ON, + .pwrsts_logic_ret = PWRDM_POWER_RET, +}; + +static struct powerdomain usbhost_pwrdm = { + .name = "usbhost_pwrdm", + .prcm_offs = OMAP3430ES2_USBHOST_MOD, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430ES2), + .wkdep_srcs = per_usbhost_wkdeps, + .sleepdep_srcs = dss_per_usbhost_sleepdeps, + .pwrsts = PWRSTS_OFF_RET_ON, + .pwrsts_logic_ret = PWRDM_POWER_RET, + .flags = PWRDM_HAS_HDWR_SAR, /* for USBHOST ctrlr only */ + .banks = 1, + .pwrsts_mem_ret = { + [0] = PWRDM_POWER_RET, /* MEMRETSTATE */ + }, + .pwrsts_mem_on = { + [0] = PWRDM_POWER_ON, /* MEMONSTATE */ + }, +}; + +#endif /* CONFIG_ARCH_OMAP34XX */ + + +#endif diff --cc arch/arm/mach-omap2/sdram-micron-mt46h32m32lf-6.h index d7c41939604,00000000000..ef35415ca89 mode 100644,000000..100644 --- a/arch/arm/mach-omap2/sdram-micron-mt46h32m32lf-6.h +++ b/arch/arm/mach-omap2/sdram-micron-mt46h32m32lf-6.h @@@ -1,55 -1,0 +1,55 @@@ +/* + * SDRC register values for the Micron MT46H32M32LF-6 + * + * Copyright (C) 2008 Texas Instruments, Inc. + * Copyright (C) 2008 Nokia Corporation + * + * Paul Walmsley + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef ARCH_ARM_MACH_OMAP2_SDRAM_MICRON_MT46H32M32LF +#define ARCH_ARM_MACH_OMAP2_SDRAM_MICRON_MT46H32M32LF + - #include ++#include + +/* Micron MT46H32M32LF-6 */ +/* XXX Using ARE = 0x1 (no autorefresh burst) -- can this be changed? */ +static struct omap_sdrc_params mt46h32m32lf6_sdrc_params[] = { + [0] = { + .rate = 165941176, + .actim_ctrla = 0x9a9db4c6, + .actim_ctrlb = 0x00011217, + .rfr_ctrl = 0x0004dc01, + .mr = 0x00000032, + }, + [1] = { + .rate = 133333333, + .actim_ctrla = 0x7a19b485, + .actim_ctrlb = 0x00011213, + .rfr_ctrl = 0x0003de01, + .mr = 0x00000032, + }, + [2] = { + .rate = 82970588, + .actim_ctrla = 0x51512283, + .actim_ctrlb = 0x0001120c, + .rfr_ctrl = 0x00025501, + .mr = 0x00000032, + }, + [3] = { + .rate = 66666666, + .actim_ctrla = 0x410d2243, + .actim_ctrlb = 0x0001120a, + .rfr_ctrl = 0x0001d601, + .mr = 0x00000032, + }, + [4] = { + .rate = 0 + }, +}; + +#endif diff --cc arch/arm/mach-omap2/sdram-qimonda-hyb18m512160af-6.h index c932a6d2eb9,00000000000..74a92c862e6 mode 100644,000000..100644 --- a/arch/arm/mach-omap2/sdram-qimonda-hyb18m512160af-6.h +++ b/arch/arm/mach-omap2/sdram-qimonda-hyb18m512160af-6.h @@@ -1,55 -1,0 +1,55 @@@ +/* + * SDRC register values for the Qimonda HYB18M512160AF-6 + * + * Copyright (C) 2008 Texas Instruments, Inc. + * Copyright (C) 2008 Nokia Corporation + * + * Paul Walmsley + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef ARCH_ARM_MACH_OMAP2_SDRAM_QIMONDA_HYB18M512160AF6 +#define ARCH_ARM_MACH_OMAP2_SDRAM_QIMONDA_HYB18M512160AF6 + - #include ++#include + +/* Qimonda HYB18M512160AF-6 */ +/* XXX Using ARE = 0x1 (no autorefresh burst) -- can this be changed? */ +static struct omap_sdrc_params hyb18m512160af6_sdrc_params[] = { + [0] = { + .rate = 165941176, + .actim_ctrla = 0x629db4c6, + .actim_ctrlb = 0x00012214, + .rfr_ctrl = 0x0004dc01, + .mr = 0x00000032, + }, + [1] = { + .rate = 133333333, + .actim_ctrla = 0x5219b485, + .actim_ctrlb = 0x00012210, + .rfr_ctrl = 0x0003de01, + .mr = 0x00000032, + }, + [2] = { + .rate = 82970588, + .actim_ctrla = 0x31512283, + .actim_ctrlb = 0x0001220a, + .rfr_ctrl = 0x00025501, + .mr = 0x00000022, + }, + [3] = { + .rate = 66666666, + .actim_ctrla = 0x290d2243, + .actim_ctrlb = 0x00012208, + .rfr_ctrl = 0x0001d601, + .mr = 0x00000022, + }, + [4] = { + .rate = 0 + }, +}; + +#endif diff --cc arch/arm/mach-omap2/sdrc.c index 7c2e36cf516,00000000000..2a30060cb4b mode 100644,000000..100644 --- a/arch/arm/mach-omap2/sdrc.c +++ b/arch/arm/mach-omap2/sdrc.c @@@ -1,93 -1,0 +1,93 @@@ +/* + * SMS/SDRC (SDRAM controller) common code for OMAP2/3 + * + * Copyright (C) 2005, 2008 Texas Instruments Inc. + * Copyright (C) 2005, 2008 Nokia Corporation + * + * Tony Lindgren + * Paul Walmsley + * Richard Woodruff + * + * 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. + */ +#undef DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include + - #include - #include - #include ++#include ++#include ++#include + +#include "prm.h" + - #include ++#include +#include "sdrc.h" + +static struct omap_sdrc_params *sdrc_init_params; + +void __iomem *omap2_sdrc_base; +void __iomem *omap2_sms_base; + + +/** + * omap2_sdrc_get_params - return SDRC register values for a given clock rate + * @r: SDRC clock rate (in Hz) + * + * Return pre-calculated values for the SDRC_ACTIM_CTRLA, + * SDRC_ACTIM_CTRLB, SDRC_RFR_CTRL, and SDRC_MR registers, for a given + * SDRC clock rate 'r'. These parameters control various timing + * delays in the SDRAM controller that are expressed in terms of the + * number of SDRC clock cycles to wait; hence the clock rate + * dependency. Note that sdrc_init_params must be sorted rate + * descending. Also assumes that both chip-selects use the same + * timing parameters. Returns a struct omap_sdrc_params * upon + * success, or NULL upon failure. + */ +struct omap_sdrc_params *omap2_sdrc_get_params(unsigned long r) +{ + struct omap_sdrc_params *sp; + + sp = sdrc_init_params; + + while (sp->rate != r) + sp++; + + if (!sp->rate) + return NULL; + + return sp; +} + + +void __init omap2_set_globals_sdrc(struct omap_globals *omap2_globals) +{ + omap2_sdrc_base = omap2_globals->sdrc; + omap2_sms_base = omap2_globals->sms; +} + +/* turn on smart idle modes for SDRAM scheduler and controller */ +void __init omap2_sdrc_init(struct omap_sdrc_params *sp) +{ + u32 l; + + l = sms_read_reg(SMS_SYSCONFIG); + l &= ~(0x3 << 3); + l |= (0x2 << 3); + sms_write_reg(l, SMS_SYSCONFIG); + + l = sdrc_read_reg(SDRC_SYSCONFIG); + l &= ~(0x3 << 3); + l |= (0x2 << 3); + sdrc_write_reg(l, SDRC_SYSCONFIG); + + sdrc_init_params = sp; +} diff --cc arch/arm/mach-omap2/sdrc2xxx.c index e98da5c4785,6b49cc9cbdc..0723e59b587 --- a/arch/arm/mach-omap2/sdrc2xxx.c +++ b/arch/arm/mach-omap2/sdrc2xxx.c @@@ -22,23 -21,20 +22,23 @@@ #include #include #include - -#include +#include - #include - #include - #include + #include + #include + #include #include "prm.h" - #include -#include "memory.h" ++#include #include "sdrc.h" -void __iomem *omap2_sdrc_base; -void __iomem *omap2_sms_base; +/* Memory timing, DLL mode flags */ +#define M_DDR 1 +#define M_LOCK_CTRL (1 << 2) +#define M_UNLOCK 0 +#define M_LOCK 1 + static struct memory_timings mem_timings; static u32 curr_perf_level = CORE_CLK_SRC_DPLL_X2; diff --cc arch/arm/mach-omap2/serial.c index b0fa582f307,adc8a26a8fb..d9fca6f343f --- a/arch/arm/mach-omap2/serial.c +++ b/arch/arm/mach-omap2/serial.c @@@ -18,13 -18,17 +18,13 @@@ #include #include -#include +#include - #include - #include + #include + #include -static struct clk * uart1_ick = NULL; -static struct clk * uart1_fck = NULL; -static struct clk * uart2_ick = NULL; -static struct clk * uart2_fck = NULL; -static struct clk * uart3_ick = NULL; -static struct clk * uart3_fck = NULL; +static struct clk *uart_ick[OMAP_MAX_NR_PORTS]; +static struct clk *uart_fck[OMAP_MAX_NR_PORTS]; static struct plat_serial8250_port serial_platform_data[] = { { diff --cc arch/arm/mach-omap2/sleep242x.S index 33bdd019f33,87a706fd5f8..5e38ab7822b --- a/arch/arm/mach-omap2/sleep242x.S +++ b/arch/arm/mach-omap2/sleep242x.S @@@ -27,11 -23,9 +27,11 @@@ #include #include - #include - #include + #include + #include - #include ++#include + #include "sdrc.h" /* First address of reserved address space? apparently valid for OMAP2 & 3 */ diff --cc arch/arm/mach-omap2/sleep243x.S index 9a2b4b7ff43,00000000000..e39ab556177 mode 100644,000000..100644 --- a/arch/arm/mach-omap2/sleep243x.S +++ b/arch/arm/mach-omap2/sleep243x.S @@@ -1,131 -1,0 +1,131 @@@ +/* + * linux/arch/arm/mach-omap2/sleep.S + * + * (C) Copyright 2004 + * Texas Instruments, + * Richard Woodruff + * + * (C) Copyright 2006 Nokia Corporation + * Fixed idle loop sleep + * Igor Stoppa + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR /PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include +#include - #include - #include ++#include ++#include + - #include ++#include + +#include "sdrc.h" + +/* First address of reserved address space? apparently valid for OMAP2 & 3 */ +#define A_SDRC0_V (0xC0000000) + + .text + +/* + * Forces OMAP into idle state + * + * omap243x_idle_loop_suspend() - This bit of code just executes the WFI + * for normal idles. + * + * 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. + */ +ENTRY(omap243x_idle_loop_suspend) + stmfd sp!, {r0, lr} @ save registers on stack + mov r0, #0x0 @ clear for mrc call + mcr p15, 0, r0, c7, c0, 4 @ wait for interrupt + ldmfd sp!, {r0, pc} @ restore regs and return + +ENTRY(omap243x_idle_loop_suspend_sz) + .word . - omap243x_idle_loop_suspend + +/* + * omap243x_cpu_suspend() - Forces OMAP into deep sleep state by completing + * SDRC shutdown then ARM shutdown. Upon wake MPU is back on so just restore + * SDRC. + * + * Input: + * R0 : DLL ctrl value pre-Sleep + * + * The if the DPLL is going to AutoIdle. It seems like the DPLL may be back on + * when we get called, but the DLL probably isn't. We will wait a bit more in + * case the DPLL isn't quite there yet. The code will wait on DLL for DDR even + * if in unlocked mode. + * + * For less than 242x-ES2.2 upon wake from a sleep mode where the external + * oscillator was stopped, a timing bug exists where a non-stabilized 12MHz + * clock can pass into the PRCM can cause problems at DSP and IVA. + * To work around this the code will switch to the 32kHz source prior to sleep. + * Post sleep we will shift back to using the DPLL. Apparently, + * CM_IDLEST_CLKGEN does not reflect the full clock change so you need to wait + * 3x12MHz + 3x32kHz clocks for a full switch. + * + * The DLL load value is not kept in RETENTION or OFF. It needs to be restored + * at wake + */ +ENTRY(omap243x_cpu_suspend) + stmfd sp!, {r0 - r12, lr} @ save registers on stack + mov r3, #0x0 @ clear for mrc call + mcr p15, 0, r3, c7, c10, 4 @ memory barrier, hope SDR/DDR finished + nop + nop + ldr r3, omap2_ocs_sdrc_power @ addr of sdrc power + ldr r4, [r3] @ value of sdrc power + orr r4, r4, #0x40 @ enable self refresh on idle req + mov r5, #0x2000 @ set delay (DPLL relock + DLL relock) + str r4, [r3] @ make it so + mov r2, #0 + nop + mcr p15, 0, r2, c7, c0, 4 @ wait for interrupt + nop +loop: + subs r5, r5, #0x1 @ awake, wait just a bit + bne loop + + /* The DPLL has on before we take the DDR out of self refresh */ + bic r4, r4, #0x40 @ now clear self refresh bit. + str r4, [r3] @ put vlaue back. + ldr r4, A_SDRC0 @ make a clock happen + ldr r4, [r4] + nop @ start auto refresh only after clk ok + movs r0, r0 @ see if DDR or SDR + ldrne r1, omap2_ocs_sdrc_dlla_ctrl @ get addr of DLL ctrl + strne r0, [r1] @ rewrite DLLA to force DLL reload + addne r1, r1, #0x8 @ move to DLLB + strne r0, [r1] @ rewrite DLLB to force DLL reload + + mov r5, #0x1000 +loop2: + subs r5, r5, #0x1 + bne loop2 + /* resume*/ + ldmfd sp!, {r0 - r12, pc} @ restore regs and return + +omap2_ocs_sdrc_power: + .word OMAP243X_SDRC_REGADDR(SDRC_POWER) +A_SDRC0: + .word A_SDRC0_V +omap2_ocs_sdrc_dlla_ctrl: + .word OMAP243X_SDRC_REGADDR(SDRC_DLLA_CTRL) + +ENTRY(omap243x_cpu_suspend_sz) + .word . - omap243x_cpu_suspend + diff --cc arch/arm/mach-omap2/sleep34xx.S index ebc7eb3a7f5,00000000000..125b75affeb mode 100644,000000..100644 --- a/arch/arm/mach-omap2/sleep34xx.S +++ b/arch/arm/mach-omap2/sleep34xx.S @@@ -1,544 -1,0 +1,544 @@@ +/* + * linux/arch/arm/mach-omap2/sleep.S + * + * (C) Copyright 2007 + * Texas Instruments + * Karthik Dasu + * + * (C) Copyright 2004 + * Texas Instruments, + * Richard Woodruff + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR /PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ +#include +#include - #include - #include - #include ++#include ++#include ++#include + +#include "prm.h" +#include "sdrc.h" + +#define PM_PREPWSTST_CORE_V OMAP34XX_PRM_REGADDR(CORE_MOD, \ + OMAP3430_PM_PREPWSTST) +#define PM_PREPWSTST_MPU_V OMAP34XX_PRM_REGADDR(MPU_MOD, \ + OMAP3430_PM_PREPWSTST) +#define PM_PWSTCTRL_MPU_P OMAP34XX_PRM_REGADDR(MPU_MOD, PM_PWSTCTRL) +#define SCRATCHPAD_MEM_OFFS 0x310 /* Move this as correct place is + * available */ +#define SCRATCHPAD_BASE_P OMAP343X_CTRL_REGADDR(\ + OMAP343X_CONTROL_MEM_WKUP +\ + SCRATCHPAD_MEM_OFFS) +#define SDRC_POWER_V OMAP34XX_SDRC_REGADDR(SDRC_POWER) + + .text +/* Function call to get the restore pointer for resume from OFF */ +ENTRY(get_restore_pointer) + stmfd sp!, {lr} @ save registers on stack + adr r0, restore + ldmfd sp!, {pc} @ restore regs and return +ENTRY(get_restore_pointer_sz) + .word . - get_restore_pointer_sz +/* + * Forces OMAP into idle state + * + * omap34xx_suspend() - This bit of code just executes the WFI + * for normal idles. + * + * 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. + */ +ENTRY(omap34xx_cpu_suspend) + stmfd sp!, {r0-r12, lr} @ save registers on stack +loop: + /*b loop*/ @Enable to debug by stepping through code + /* r0 contains restore pointer in sdram */ + /* r1 contains information about saving context */ + ldr r4, sdrc_power @ read the SDRC_POWER register + ldr r5, [r4] @ read the contents of SDRC_POWER + orr r5, r5, #0x40 @ enable self refresh on idle req + str r5, [r4] @ write back to SDRC_POWER register + + cmp r1, #0x0 + /* If context save is required, do that and execute wfi */ + bne save_context_wfi + /* Data memory barrier and Data sync barrier */ + mov r1, #0 + mcr p15, 0, r1, c7, c10, 4 + mcr p15, 0, r1, c7, c10, 5 + + wfi @ wait for interrupt + + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + bl i_dll_wait + + ldmfd sp!, {r0-r12, pc} @ restore regs and return +restore: + /* b restore*/ @ Enable to debug restore code + /* Check what was the reason for mpu reset and store the reason in r9*/ + /* 1 - Only L1 and logic lost */ + /* 2 - Only L2 lost - In this case, we wont be here */ + /* 3 - Both L1 and L2 lost */ + ldr r1, pm_pwstctrl_mpu + ldr r2, [r1] + and r2, r2, #0x3 + cmp r2, #0x0 @ Check if target power state was OFF or RET + moveq r9, #0x3 @ MPU OFF => L1 and L2 lost + movne r9, #0x1 @ Only L1 and L2 lost => avoid L2 invalidation + bne logic_l1_restore + /* Execute smi to invalidate L2 cache */ + mov r12, #0x1 @ set up to invalide L2 +smi: .word 0xE1600070 @ Call SMI monitor (smieq) +logic_l1_restore: + mov r1, #0 + /* Invalidate all instruction caches to PoU + * and flush branch target cache */ + mcr p15, 0, r1, c7, c5, 0 + + ldr r4, scratchpad_base + ldr r3, [r4,#0xBC] + ldmia r3!, {r4-r6} + mov sp, r4 + msr spsr_cxsf, r5 + mov lr, r6 + + ldmia r3!, {r4-r9} + /* Coprocessor access Control Register */ + mcr p15, 0, r4, c1, c0, 2 + + /* TTBR0 */ + MCR p15, 0, r5, c2, c0, 0 + /* TTBR1 */ + MCR p15, 0, r6, c2, c0, 1 + /* Translation table base control register */ + MCR p15, 0, r7, c2, c0, 2 + /*domain access Control Register */ + MCR p15, 0, r8, c3, c0, 0 + /* data fault status Register */ + MCR p15, 0, r9, c5, c0, 0 + + ldmia r3!,{r4-r8} + /* instruction fault status Register */ + MCR p15, 0, r4, c5, c0, 1 + /*Data Auxiliary Fault Status Register */ + MCR p15, 0, r5, c5, c1, 0 + /*Instruction Auxiliary Fault Status Register*/ + MCR p15, 0, r6, c5, c1, 1 + /*Data Fault Address Register */ + MCR p15, 0, r7, c6, c0, 0 + /*Instruction Fault Address Register*/ + MCR p15, 0, r8, c6, c0, 2 + ldmia r3!,{r4-r7} + + /* user r/w thread and process ID */ + MCR p15, 0, r4, c13, c0, 2 + /* user ro thread and process ID */ + MCR p15, 0, r5, c13, c0, 3 + /*Privileged only thread and process ID */ + MCR p15, 0, r6, c13, c0, 4 + /* cache size selection */ + MCR p15, 2, r7, c0, c0, 0 + ldmia r3!,{r4-r8} + /* Data TLB lockdown registers */ + MCR p15, 0, r4, c10, c0, 0 + /* Instruction TLB lockdown registers */ + MCR p15, 0, r5, c10, c0, 1 + /* Secure or Nonsecure Vector Base Address */ + MCR p15, 0, r6, c12, c0, 0 + /* FCSE PID */ + MCR p15, 0, r7, c13, c0, 0 + /* Context PID */ + MCR p15, 0, r8, c13, c0, 1 + + ldmia r3!,{r4-r5} + /* primary memory remap register */ + MCR p15, 0, r4, c10, c2, 0 + /*normal memory remap register */ + MCR p15, 0, r5, c10, c2, 1 + + /* Restore registers for other modes from SDRAM */ + /* Save current mode */ + mrs r7, cpsr + + /* FIQ mode */ + bic r0, r7, #0x1F + orr r0, r0, #0x11 + msr cpsr, r0 + ldmia r3!, {r8-r12} + /* load the SP and LR from SDRAM */ + ldmia r3!,{r4-r6} + mov sp, r4 /*update the SP */ + mov lr, r5 /*update the LR */ + msr spsr, r6 /*update the SPSR*/ + + /* IRQ mode */ + bic r0, r7, #0x1F + orr r0, r0, #0x12 + msr cpsr, r0 /*go into IRQ mode*/ + ldmia r3!,{r4-r6} /*load the SP and LR from SDRAM*/ + mov sp, r4 /*update the SP */ + mov lr, r5 /*update the LR */ + msr spsr, r6 /*update the SPSR */ + + /* ABORT mode */ + bic r0, r7, #0x1F + orr r0, r0, #0x17 + msr cpsr, r0 /* go into ABORT mode */ + ldmia r3!,{r4-r6} /*load the SP and LR from SDRAM */ + mov sp, r4 /*update the SP */ + mov lr, r5 /*update the LR */ + msr spsr, r6 /*update the SPSR */ + + /* UNDEEF mode */ + bic r0, r7, #0x1F + orr r0, r0, #0x1B + msr cpsr, r0 /*go into UNDEF mode */ + ldmia r3!,{r4-r6} /*load the SP and LR from SDRAM */ + mov sp, r4 /*update the SP*/ + mov lr, r5 /*update the LR*/ + msr spsr, r6 /*update the SPSR*/ + + /* SYSTEM (USER) mode */ + bic r0, r7, #0x1F + orr r0, r0, #0x1F + msr cpsr, r0 /*go into USR mode */ + ldmia r3!,{r4-r6} /*load the SP and LR from SDRAM*/ + mov sp, r4 /*update the SP */ + mov lr, r5 /*update the LR */ + msr spsr, r6 /*update the SPSR */ + msr cpsr, r7 /*back to original mode*/ + + /* Restore cpsr */ + ldmia r3!,{r4} /*load CPSR from SDRAM*/ + msr cpsr, r4 /*store cpsr */ + + /* Enabling MMU here */ + mrc p15, 0, r7, c2, c0, 2 /* Read TTBRControl */ + /* Extract N (0:2) bits and decide whether to use TTBR0 or TTBR1*/ + and r7, #0x7 + cmp r7, #0x0 + beq usettbr0 +ttbr_error: + /* More work needs to be done to support N[0:2] value other than 0 + * So looping here so that the error can be detected + */ + b ttbr_error +usettbr0: + mrc p15, 0, r2, c2, c0, 0 + ldr r5, ttbrbit_mask + and r2, r5 + mov r4, pc + ldr r5, table_index_mask + and r4, r5 /* r4 = 31 to 20 bits of pc */ + /* Extract the value to be written to table entry */ + ldr r1, table_entry + add r1, r1, r4 /* r1 has value to be written to table entry*/ + /* Getting the address of table entry to modify */ + lsr r4, #18 + add r2, r4 /* r2 has the location which needs to be modified */ + /* Storing previous entry of location being modified */ + ldr r5, scratchpad_base + ldr r4, [r2] + str r4, [r5, #0xC0] + /* Modify the table entry */ + str r1, [r2] + /* Storing address of entry being modified + * - will be restored after enabling MMU */ + ldr r5, scratchpad_base + str r2, [r5, #0xC4] + + mov r0, #0 + mcr p15, 0, r0, c7, c5, 4 @ Flush prefetch buffer + mcr p15, 0, r0, c7, c5, 6 @ Invalidate branch predictor array + mcr p15, 0, r0, c8, c5, 0 @ Invalidate instruction TLB + mcr p15, 0, r0, c8, c6, 0 @ Invalidate data TLB + /* Restore control register but dont enable caches here*/ + /* Caches will be enabled after restoring MMU table entry */ + ldmia r3!, {r4} + /* Store previous value of control register in scratchpad */ + str r4, [r5, #0xC8] + ldr r2, cache_pred_disable_mask + and r4, r2 + mcr p15, 0, r4, c1, c0, 0 + + ldmfd sp!, {r0-r12, pc} @ restore regs and return +save_context_wfi: + /*b save_context_wfi*/ @ enable to debug save code + mov r8, r0 /* Store SDRAM address in r8 */ + /* Check what that target sleep state is:stored in r1*/ + /* 1 - Only L1 and logic lost */ + /* 2 - Only L2 lost */ + /* 3 - Both L1 and L2 lost */ + cmp r1, #0x2 /* Only L2 lost */ + beq clean_l2 + cmp r1, #0x1 /* L2 retained */ + /* r9 stores whether to clean L2 or not*/ + moveq r9, #0x0 /* Dont Clean L2 */ + movne r9, #0x1 /* Clean L2 */ +l1_logic_lost: + /* Store sp and spsr to SDRAM */ + mov r4, sp + mrs r5, spsr + mov r6, lr + stmia r8!, {r4-r6} + /* Save all ARM registers */ + /* Coprocessor access control register */ + mrc p15, 0, r6, c1, c0, 2 + stmia r8!, {r6} + /* TTBR0, TTBR1 and Translation table base control */ + mrc p15, 0, r4, c2, c0, 0 + mrc p15, 0, r5, c2, c0, 1 + mrc p15, 0, r6, c2, c0, 2 + stmia r8!, {r4-r6} + /* Domain access control register, data fault status register, + and instruction fault status register */ + mrc p15, 0, r4, c3, c0, 0 + mrc p15, 0, r5, c5, c0, 0 + mrc p15, 0, r6, c5, c0, 1 + stmia r8!, {r4-r6} + /* Data aux fault status register, instruction aux fault status, + datat fault address register and instruction fault address register*/ + mrc p15, 0, r4, c5, c1, 0 + mrc p15, 0, r5, c5, c1, 1 + mrc p15, 0, r6, c6, c0, 0 + mrc p15, 0, r7, c6, c0, 2 + stmia r8!, {r4-r7} + /* user r/w thread and process ID, user r/o thread and process ID, + priv only thread and process ID, cache size selection */ + mrc p15, 0, r4, c13, c0, 2 + mrc p15, 0, r5, c13, c0, 3 + mrc p15, 0, r6, c13, c0, 4 + mrc p15, 2, r7, c0, c0, 0 + stmia r8!, {r4-r7} + /* Data TLB lockdown, instruction TLB lockdown registers */ + mrc p15, 0, r5, c10, c0, 0 + mrc p15, 0, r6, c10, c0, 1 + stmia r8!, {r5-r6} + /* Secure or non secure vector base address, FCSE PID, Context PID*/ + mrc p15, 0, r4, c12, c0, 0 + mrc p15, 0, r5, c13, c0, 0 + mrc p15, 0, r6, c13, c0, 1 + stmia r8!, {r4-r6} + /* Primary remap, normal remap registers */ + mrc p15, 0, r4, c10, c2, 0 + mrc p15, 0, r5, c10, c2, 1 + stmia r8!,{r4-r5} + /* Store SP, LR, SPSR registers for SUP, FIQ, IRQ, ABORT and USER + modes into SDRAM */ + + /* move SDRAM address to r7 as r8 is banked in FIQ*/ + mov r7, r8 + + /* Save current mode */ + mrs r2, cpsr + /* FIQ mode */ + bic r0, r2, #0x1F + orr r0, r0, #0x11 + msr cpsr, r0 /* go to FIQ mode */ + stmia r7!, {r8-r12} + mov r4, r13 /* move SP into r4*/ + mov r5, r14 + mrs r6, spsr + stmia r7!, {r4-r6} + + /* IRQ mode */ + bic r0, r2, #0x1F + orr r0, r0, #0x12 + msr cpsr, r0 + mov r4, r13 + mov r5, r14 + mrs r6, spsr + stmia r7!, {r4-r6} + + /* Abort mode */ + bic r0, r2, #0x1F + orr r0, r0, #0x17 + msr cpsr, r0 + mov r4, r13 + mov r5, r14 + mrs r6, spsr + stmia r7!, {r4-r6} + + /* UNDEF mode */ + bic r0, r2, #0x1F + orr r0, r0, #0x1B + msr cpsr, r0 + mov r4, r13 + mov r5, r14 + mrs r6, spsr + stmia r7!, {r4-r6} + + /* System (USER mode) */ + bic r0, r2, #0x1F + orr r0, r0, #0x1F + msr cpsr, r0 + mov r4, r13 + mov r5, r14 + mrs r6, spsr + stmia r7!, {r4-r6} + + /* Back to original mode */ + msr cpsr, r2 + + /* Store current cpsr*/ + stmia r7!, {r2} + + mrc p15, 0, r4, c1, c0, 0 + /* save control register */ + stmia r7!, {r4} +clean_caches: + /* Clean Data or unified cache to POU*/ + /* How to invalidate only L1 cache???? - #FIX_ME# */ + /* mcr p15, 0, r11, c7, c11, 1 */ + cmp r9, #1 /* Check whether L2 inval is required or not*/ + bne skip_l2_inval +clean_l2: + /* read clidr */ + mrc p15, 1, r0, c0, c0, 1 + /* extract loc from clidr */ + ands r3, r0, #0x7000000 + /* left align loc bit field */ + mov r3, r3, lsr #23 + /* if loc is 0, then no need to clean */ + beq finished + /* start clean at cache level 0 */ + mov r10, #0 +loop1: + /* work out 3x current cache level */ + add r2, r10, r10, lsr #1 + /* extract cache type bits from clidr*/ + mov r1, r0, lsr r2 + /* mask of the bits for current cache only */ + and r1, r1, #7 + /* see what cache we have at this level */ + cmp r1, #2 + /* skip if no cache, or just i-cache */ + blt skip + /* select current cache level in cssr */ + mcr p15, 2, r10, c0, c0, 0 + /* isb to sych the new cssr&csidr */ + isb + /* read the new csidr */ + mrc p15, 1, r1, c0, c0, 0 + /* extract the length of the cache lines */ + and r2, r1, #7 + /* add 4 (line length offset) */ + add r2, r2, #4 + ldr r4, assoc_mask + /* find maximum number on the way size */ + ands r4, r4, r1, lsr #3 + /* find bit position of way size increment */ + clz r5, r4 + ldr r7, numset_mask + /* extract max number of the index size*/ + ands r7, r7, r1, lsr #13 +loop2: + mov r9, r4 + /* create working copy of max way size*/ +loop3: + /* factor way and cache number into r11 */ + orr r11, r10, r9, lsl r5 + /* factor index number into r11 */ + orr r11, r11, r7, lsl r2 + /*clean & invalidate by set/way */ + mcr p15, 0, r11, c7, c10, 2 + /* decrement the way*/ + subs r9, r9, #1 + bge loop3 + /*decrement the index */ + subs r7, r7, #1 + bge loop2 +skip: + add r10, r10, #2 + /* increment cache number */ + cmp r3, r10 + bgt loop1 +finished: + /*swith back to cache level 0 */ + mov r10, #0 + /* select current cache level in cssr */ + mcr p15, 2, r10, c0, c0, 0 + isb +skip_l2_inval: + /* Data memory barrier and Data sync barrier */ + mov r1, #0 + mcr p15, 0, r1, c7, c10, 4 + mcr p15, 0, r1, c7, c10, 5 + + wfi @ wait for interrupt + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + bl i_dll_wait + /* restore regs and return */ + ldmfd sp!, {r0-r12, pc} + +i_dll_wait: + ldr r4, clk_stabilize_delay + +i_dll_delay: + subs r4, r4, #0x1 + bne i_dll_delay + ldr r4, sdrc_power + ldr r5, [r4] + bic r5, r5, #0x40 + str r5, [r4] + bx lr +pm_prepwstst_core: + .word PM_PREPWSTST_CORE_V +pm_prepwstst_mpu: + .word PM_PREPWSTST_MPU_V +pm_pwstctrl_mpu: + .word PM_PWSTCTRL_MPU_P +scratchpad_base: + .word SCRATCHPAD_BASE_P +sdrc_power: + .word SDRC_POWER_V +context_mem: + .word 0x803E3E14 +clk_stabilize_delay: + .word 0x000001FF +assoc_mask: + .word 0x3ff +numset_mask: + .word 0x7fff +ttbrbit_mask: + .word 0xFFFFC000 +table_index_mask: + .word 0xFFF00000 +table_entry: + .word 0x00000C02 +cache_pred_disable_mask: + .word 0xFFFFE7FB +ENTRY(omap34xx_cpu_suspend_sz) + .word . - omap34xx_cpu_suspend diff --cc arch/arm/mach-omap2/smartreflex.c index 5b15d18a073,00000000000..ffd49d11490 mode 100644,000000..100644 --- a/arch/arm/mach-omap2/smartreflex.c +++ b/arch/arm/mach-omap2/smartreflex.c @@@ -1,813 -1,0 +1,813 @@@ +/* + * linux/arch/arm/mach-omap3/smartreflex.c + * + * OMAP34XX SmartReflex Voltage Control + * + * Copyright (C) 2008 Nokia Corporation + * Kalle Jokiniemi + * + * Copyright (C) 2007 Texas Instruments, Inc. + * Lesly A M + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + - #include - #include - #include ++#include ++#include ++#include + +#include "prm.h" +#include "smartreflex.h" +#include "prm-regbits-34xx.h" + +/* XXX: These should be relocated where-ever the OPP implementation will be */ +u32 current_vdd1_opp; +u32 current_vdd2_opp; + +struct omap_sr { + int srid; + int is_sr_reset; + int is_autocomp_active; + struct clk *clk; + u32 clk_length; + u32 req_opp_no; + u32 opp1_nvalue, opp2_nvalue, opp3_nvalue, opp4_nvalue; + u32 opp5_nvalue; + u32 senp_mod, senn_mod; + u32 srbase_addr; + u32 vpbase_addr; +}; + +/* Custom clocks to enable SR specific enable/disable functions. */ +struct sr_custom_clk { + struct clk clk; /* meta-clock with custom enable/disable calls */ + struct clk *fck; /* actual functional clock */ + struct omap_sr *sr; +}; + +#define SR_REGADDR(offs) (__force void __iomem *)(sr->srbase_addr + offset) + +static inline void sr_write_reg(struct omap_sr *sr, int offset, u32 value) +{ + __raw_writel(value, SR_REGADDR(offset)); +} + +static inline void sr_modify_reg(struct omap_sr *sr, int offset, u32 mask, + u32 value) +{ + u32 reg_val; + + reg_val = __raw_readl(SR_REGADDR(offset)); + reg_val &= ~mask; + reg_val |= value; + + __raw_writel(reg_val, SR_REGADDR(offset)); +} + +static inline u32 sr_read_reg(struct omap_sr *sr, int offset) +{ + return __raw_readl(SR_REGADDR(offset)); +} + +/* Custom clock handling functions */ +static int sr_clk_enable(struct clk *clk) +{ + struct sr_custom_clk *sr_clk = container_of(clk, struct sr_custom_clk, + clk); + + if (clk_enable(sr_clk->fck) != 0) { + printk(KERN_ERR "Could not enable %s\n", sr_clk->fck->name); + goto clk_enable_err; + } + + /* set fclk- active , iclk- idle */ + sr_modify_reg(sr_clk->sr, ERRCONFIG, SR_CLKACTIVITY_MASK, + SR_CLKACTIVITY_IOFF_FON); + + return 0; + +clk_enable_err: + return -1; +} + +static void sr_clk_disable(struct clk *clk) +{ + struct sr_custom_clk *sr_clk = container_of(clk, struct sr_custom_clk, + clk); + + /* set fclk, iclk- idle */ + sr_modify_reg(sr_clk->sr, ERRCONFIG, SR_CLKACTIVITY_MASK, + SR_CLKACTIVITY_IOFF_FOFF); + + clk_disable(sr_clk->fck); + sr_clk->sr->is_sr_reset = 1; +} + +static struct omap_sr sr1 = { + .srid = SR1, + .is_sr_reset = 1, + .is_autocomp_active = 0, + .clk_length = 0, + .srbase_addr = OMAP2_IO_ADDRESS(OMAP34XX_SR1_BASE), +}; + +static struct omap_sr sr2 = { + .srid = SR2, + .is_sr_reset = 1, + .is_autocomp_active = 0, + .clk_length = 0, + .srbase_addr = OMAP2_IO_ADDRESS(OMAP34XX_SR2_BASE), +}; + +static struct sr_custom_clk sr1_custom_clk = { + .clk = { + .name = "sr1_custom_clk", + .enable = sr_clk_enable, + .disable = sr_clk_disable, + }, + .sr = &sr1, +}; + +static struct sr_custom_clk sr2_custom_clk = { + .clk = { + .name = "sr2_custom_clk", + .enable = sr_clk_enable, + .disable = sr_clk_disable, + }, + .sr = &sr2, +}; + +static void cal_reciprocal(u32 sensor, u32 *sengain, u32 *rnsen) +{ + u32 gn, rn, mul; + + for (gn = 0; gn < GAIN_MAXLIMIT; gn++) { + mul = 1 << (gn + 8); + rn = mul / sensor; + if (rn < R_MAXLIMIT) { + *sengain = gn; + *rnsen = rn; + } + } +} + +static u32 cal_test_nvalue(u32 sennval, u32 senpval) +{ + u32 senpgain, senngain; + u32 rnsenp, rnsenn; + + /* Calculating the gain and reciprocal of the SenN and SenP values */ + cal_reciprocal(senpval, &senpgain, &rnsenp); + cal_reciprocal(sennval, &senngain, &rnsenn); + + return ((senpgain << NVALUERECIPROCAL_SENPGAIN_SHIFT) | + (senngain << NVALUERECIPROCAL_SENNGAIN_SHIFT) | + (rnsenp << NVALUERECIPROCAL_RNSENP_SHIFT) | + (rnsenn << NVALUERECIPROCAL_RNSENN_SHIFT)); +} + +static void sr_clk_init(struct sr_custom_clk *sr_clk) +{ + if (sr_clk->sr->srid == SR1) { + sr_clk->fck = clk_get(NULL, "sr1_fck"); + if (IS_ERR(sr_clk->fck)) + printk(KERN_ERR "Could not get sr1_fck\n"); + } else if (sr_clk->sr->srid == SR2) { + sr_clk->fck = clk_get(NULL, "sr2_fck"); + if (IS_ERR(sr_clk->fck)) + printk(KERN_ERR "Could not get sr2_fck\n"); + } + clk_register(&sr_clk->clk); +} + +static void sr_set_clk_length(struct omap_sr *sr) +{ + struct clk *osc_sys_ck; + u32 sys_clk = 0; + + osc_sys_ck = clk_get(NULL, "osc_sys_ck"); + sys_clk = clk_get_rate(osc_sys_ck); + clk_put(osc_sys_ck); + + switch (sys_clk) { + case 12000000: + sr->clk_length = SRCLKLENGTH_12MHZ_SYSCLK; + break; + case 13000000: + sr->clk_length = SRCLKLENGTH_13MHZ_SYSCLK; + break; + case 19200000: + sr->clk_length = SRCLKLENGTH_19MHZ_SYSCLK; + break; + case 26000000: + sr->clk_length = SRCLKLENGTH_26MHZ_SYSCLK; + break; + case 38400000: + sr->clk_length = SRCLKLENGTH_38MHZ_SYSCLK; + break; + default : + printk(KERN_ERR "Invalid sysclk value: %d\n", sys_clk); + break; + } +} + +static void sr_set_efuse_nvalues(struct omap_sr *sr) +{ + if (sr->srid == SR1) { + sr->senn_mod = (omap_ctrl_readl(OMAP343X_CONTROL_FUSE_SR) & + OMAP343X_SR1_SENNENABLE_MASK) >> + OMAP343X_SR1_SENNENABLE_SHIFT; + + sr->senp_mod = (omap_ctrl_readl(OMAP343X_CONTROL_FUSE_SR) & + OMAP343X_SR1_SENPENABLE_MASK) >> + OMAP343X_SR1_SENPENABLE_SHIFT; + + sr->opp5_nvalue = omap_ctrl_readl( + OMAP343X_CONTROL_FUSE_OPP5_VDD1); + sr->opp4_nvalue = omap_ctrl_readl( + OMAP343X_CONTROL_FUSE_OPP4_VDD1); + sr->opp3_nvalue = omap_ctrl_readl( + OMAP343X_CONTROL_FUSE_OPP3_VDD1); + sr->opp2_nvalue = omap_ctrl_readl( + OMAP343X_CONTROL_FUSE_OPP2_VDD1); + sr->opp1_nvalue = omap_ctrl_readl( + OMAP343X_CONTROL_FUSE_OPP1_VDD1); + } else if (sr->srid == SR2) { + sr->senn_mod = (omap_ctrl_readl(OMAP343X_CONTROL_FUSE_SR) & + OMAP343X_SR2_SENNENABLE_MASK) >> + OMAP343X_SR2_SENNENABLE_SHIFT; + + sr->senp_mod = (omap_ctrl_readl(OMAP343X_CONTROL_FUSE_SR) & + OMAP343X_SR2_SENPENABLE_MASK) >> + OMAP343X_SR2_SENPENABLE_SHIFT; + + sr->opp3_nvalue = omap_ctrl_readl( + OMAP343X_CONTROL_FUSE_OPP3_VDD2); + sr->opp2_nvalue = omap_ctrl_readl( + OMAP343X_CONTROL_FUSE_OPP2_VDD2); + sr->opp1_nvalue = omap_ctrl_readl( + OMAP343X_CONTROL_FUSE_OPP1_VDD2); + } +} + +/* Hard coded nvalues for testing purposes, may cause device to hang! */ +static void sr_set_testing_nvalues(struct omap_sr *sr) +{ + if (sr->srid == SR1) { + sr->senp_mod = 0x03; /* SenN-M5 enabled */ + sr->senn_mod = 0x03; + + /* calculate nvalues for each opp */ + sr->opp5_nvalue = cal_test_nvalue(0xacd + 0x330, 0x848 + 0x330); + sr->opp4_nvalue = cal_test_nvalue(0x964 + 0x2a0, 0x727 + 0x2a0); + sr->opp3_nvalue = cal_test_nvalue(0x85b + 0x200, 0x655 + 0x200); + sr->opp2_nvalue = cal_test_nvalue(0x506 + 0x1a0, 0x3be + 0x1a0); + sr->opp1_nvalue = cal_test_nvalue(0x373 + 0x100, 0x28c + 0x100); + } else if (sr->srid == SR2) { + sr->senp_mod = 0x03; + sr->senn_mod = 0x03; + + sr->opp3_nvalue = cal_test_nvalue(0x76f + 0x200, 0x579 + 0x200); + sr->opp2_nvalue = cal_test_nvalue(0x4f5 + 0x1c0, 0x390 + 0x1c0); + sr->opp1_nvalue = cal_test_nvalue(0x359, 0x25d); + } + +} + +static void sr_set_nvalues(struct omap_sr *sr) +{ + if (SR_TESTING_NVALUES) + sr_set_testing_nvalues(sr); + else + sr_set_efuse_nvalues(sr); +} + +static void sr_configure_vp(int srid) +{ + u32 vpconfig; + + if (srid == SR1) { + vpconfig = PRM_VP1_CONFIG_ERROROFFSET | PRM_VP1_CONFIG_ERRORGAIN + | PRM_VP1_CONFIG_INITVOLTAGE + | PRM_VP1_CONFIG_TIMEOUTEN; + + prm_write_mod_reg(vpconfig, OMAP3430_GR_MOD, + OMAP3_PRM_VP1_CONFIG_OFFSET); + prm_write_mod_reg(PRM_VP1_VSTEPMIN_SMPSWAITTIMEMIN | + PRM_VP1_VSTEPMIN_VSTEPMIN, + OMAP3430_GR_MOD, + OMAP3_PRM_VP1_VSTEPMIN_OFFSET); + + prm_write_mod_reg(PRM_VP1_VSTEPMAX_SMPSWAITTIMEMAX | + PRM_VP1_VSTEPMAX_VSTEPMAX, + OMAP3430_GR_MOD, + OMAP3_PRM_VP1_VSTEPMAX_OFFSET); + + prm_write_mod_reg(PRM_VP1_VLIMITTO_VDDMAX | + PRM_VP1_VLIMITTO_VDDMIN | + PRM_VP1_VLIMITTO_TIMEOUT, + OMAP3430_GR_MOD, + OMAP3_PRM_VP1_VLIMITTO_OFFSET); + + /* Trigger initVDD value copy to voltage processor */ + prm_set_mod_reg_bits(PRM_VP1_CONFIG_INITVDD, OMAP3430_GR_MOD, + OMAP3_PRM_VP1_CONFIG_OFFSET); + /* Clear initVDD copy trigger bit */ + prm_clear_mod_reg_bits(PRM_VP1_CONFIG_INITVDD, OMAP3430_GR_MOD, + OMAP3_PRM_VP1_CONFIG_OFFSET); + + } else if (srid == SR2) { + vpconfig = PRM_VP2_CONFIG_ERROROFFSET | PRM_VP2_CONFIG_ERRORGAIN + | PRM_VP2_CONFIG_INITVOLTAGE + | PRM_VP2_CONFIG_TIMEOUTEN; + + prm_write_mod_reg(vpconfig, OMAP3430_GR_MOD, + OMAP3_PRM_VP2_CONFIG_OFFSET); + prm_write_mod_reg(PRM_VP2_VSTEPMIN_SMPSWAITTIMEMIN | + PRM_VP2_VSTEPMIN_VSTEPMIN, + OMAP3430_GR_MOD, + OMAP3_PRM_VP2_VSTEPMIN_OFFSET); + + prm_write_mod_reg(PRM_VP2_VSTEPMAX_SMPSWAITTIMEMAX | + PRM_VP2_VSTEPMAX_VSTEPMAX, + OMAP3430_GR_MOD, + OMAP3_PRM_VP2_VSTEPMAX_OFFSET); + + prm_write_mod_reg(PRM_VP2_VLIMITTO_VDDMAX | + PRM_VP2_VLIMITTO_VDDMIN | + PRM_VP2_VLIMITTO_TIMEOUT, + OMAP3430_GR_MOD, + OMAP3_PRM_VP2_VLIMITTO_OFFSET); + + /* Trigger initVDD value copy to voltage processor */ + prm_set_mod_reg_bits(PRM_VP2_CONFIG_INITVDD, OMAP3430_GR_MOD, + OMAP3_PRM_VP2_CONFIG_OFFSET); + /* Reset initVDD copy trigger bit */ + prm_clear_mod_reg_bits(PRM_VP2_CONFIG_INITVDD, OMAP3430_GR_MOD, + OMAP3_PRM_VP2_CONFIG_OFFSET); + + } +} + +static void sr_configure(struct omap_sr *sr) +{ + u32 sr_config; + u32 senp_en , senn_en; + + if (sr->clk_length == 0) + sr_set_clk_length(sr); + + senp_en = sr->senp_mod; + senn_en = sr->senn_mod; + if (sr->srid == SR1) { + sr_config = SR1_SRCONFIG_ACCUMDATA | + (sr->clk_length << SRCONFIG_SRCLKLENGTH_SHIFT) | + SRCONFIG_SENENABLE | SRCONFIG_ERRGEN_EN | + SRCONFIG_MINMAXAVG_EN | + (senn_en << SRCONFIG_SENNENABLE_SHIFT) | + (senp_en << SRCONFIG_SENPENABLE_SHIFT) | + SRCONFIG_DELAYCTRL; + + sr_write_reg(sr, SRCONFIG, sr_config); + sr_write_reg(sr, AVGWEIGHT, SR1_AVGWEIGHT_SENPAVGWEIGHT | + SR1_AVGWEIGHT_SENNAVGWEIGHT); + + sr_modify_reg(sr, ERRCONFIG, (SR_ERRWEIGHT_MASK | + SR_ERRMAXLIMIT_MASK | SR_ERRMINLIMIT_MASK), + (SR1_ERRWEIGHT | SR1_ERRMAXLIMIT | SR1_ERRMINLIMIT)); + + } else if (sr->srid == SR2) { + sr_config = SR2_SRCONFIG_ACCUMDATA | + (sr->clk_length << SRCONFIG_SRCLKLENGTH_SHIFT) | + SRCONFIG_SENENABLE | SRCONFIG_ERRGEN_EN | + SRCONFIG_MINMAXAVG_EN | + (senn_en << SRCONFIG_SENNENABLE_SHIFT) | + (senp_en << SRCONFIG_SENPENABLE_SHIFT) | + SRCONFIG_DELAYCTRL; + + sr_write_reg(sr, SRCONFIG, sr_config); + sr_write_reg(sr, AVGWEIGHT, SR2_AVGWEIGHT_SENPAVGWEIGHT | + SR2_AVGWEIGHT_SENNAVGWEIGHT); + sr_modify_reg(sr, ERRCONFIG, (SR_ERRWEIGHT_MASK | + SR_ERRMAXLIMIT_MASK | SR_ERRMINLIMIT_MASK), + (SR2_ERRWEIGHT | SR2_ERRMAXLIMIT | SR2_ERRMINLIMIT)); + + } + sr->is_sr_reset = 0; +} + +static int sr_enable(struct omap_sr *sr, u32 target_opp_no) +{ + u32 nvalue_reciprocal; + + sr->req_opp_no = target_opp_no; + + if (sr->srid == SR1) { + switch (target_opp_no) { + case 5: + nvalue_reciprocal = sr->opp5_nvalue; + break; + case 4: + nvalue_reciprocal = sr->opp4_nvalue; + break; + case 3: + nvalue_reciprocal = sr->opp3_nvalue; + break; + case 2: + nvalue_reciprocal = sr->opp2_nvalue; + break; + case 1: + nvalue_reciprocal = sr->opp1_nvalue; + break; + default: + nvalue_reciprocal = sr->opp3_nvalue; + break; + } + } else { + switch (target_opp_no) { + case 3: + nvalue_reciprocal = sr->opp3_nvalue; + break; + case 2: + nvalue_reciprocal = sr->opp2_nvalue; + break; + case 1: + nvalue_reciprocal = sr->opp1_nvalue; + break; + default: + nvalue_reciprocal = sr->opp3_nvalue; + break; + } + } + + if (nvalue_reciprocal == 0) { + printk(KERN_NOTICE "OPP%d doesn't support SmartReflex\n", + target_opp_no); + return SR_FALSE; + } + + sr_write_reg(sr, NVALUERECIPROCAL, nvalue_reciprocal); + + /* Enable the interrupt */ + sr_modify_reg(sr, ERRCONFIG, + (ERRCONFIG_VPBOUNDINTEN | ERRCONFIG_VPBOUNDINTST), + (ERRCONFIG_VPBOUNDINTEN | ERRCONFIG_VPBOUNDINTST)); + if (sr->srid == SR1) { + /* Enable VP1 */ + prm_set_mod_reg_bits(PRM_VP1_CONFIG_VPENABLE, OMAP3430_GR_MOD, + OMAP3_PRM_VP1_CONFIG_OFFSET); + } else if (sr->srid == SR2) { + /* Enable VP2 */ + prm_set_mod_reg_bits(PRM_VP2_CONFIG_VPENABLE, OMAP3430_GR_MOD, + OMAP3_PRM_VP2_CONFIG_OFFSET); + } + + /* SRCONFIG - enable SR */ + sr_modify_reg(sr, SRCONFIG, SRCONFIG_SRENABLE, SRCONFIG_SRENABLE); + return SR_TRUE; +} + +static void sr_disable(struct omap_sr *sr) +{ + sr->is_sr_reset = 1; + + /* SRCONFIG - disable SR */ + sr_modify_reg(sr, SRCONFIG, SRCONFIG_SRENABLE, ~SRCONFIG_SRENABLE); + + if (sr->srid == SR1) { + /* Disable VP1 */ + prm_clear_mod_reg_bits(PRM_VP1_CONFIG_VPENABLE, OMAP3430_GR_MOD, + OMAP3_PRM_VP1_CONFIG_OFFSET); + } else if (sr->srid == SR2) { + /* Disable VP2 */ + prm_clear_mod_reg_bits(PRM_VP2_CONFIG_VPENABLE, OMAP3430_GR_MOD, + OMAP3_PRM_VP2_CONFIG_OFFSET); + } +} + + +void sr_start_vddautocomap(int srid, u32 target_opp_no) +{ + struct omap_sr *sr = NULL; + + if (srid == SR1) + sr = &sr1; + else if (srid == SR2) + sr = &sr2; + + if (sr->is_sr_reset == 1) { + clk_enable(sr->clk); + sr_configure(sr); + } + + if (sr->is_autocomp_active == 1) + printk(KERN_WARNING "SR%d: VDD autocomp is already active\n", + srid); + + sr->is_autocomp_active = 1; + if (!sr_enable(sr, target_opp_no)) { + printk(KERN_WARNING "SR%d: VDD autocomp not activated\n", srid); + sr->is_autocomp_active = 0; + if (sr->is_sr_reset == 1) + clk_disable(sr->clk); + } +} +EXPORT_SYMBOL(sr_start_vddautocomap); + +int sr_stop_vddautocomap(int srid) +{ + struct omap_sr *sr = NULL; + + if (srid == SR1) + sr = &sr1; + else if (srid == SR2) + sr = &sr2; + + if (sr->is_autocomp_active == 1) { + sr_disable(sr); + clk_disable(sr->clk); + sr->is_autocomp_active = 0; + return SR_TRUE; + } else { + printk(KERN_WARNING "SR%d: VDD autocomp is not active\n", + srid); + return SR_FALSE; + } + +} +EXPORT_SYMBOL(sr_stop_vddautocomap); + +void enable_smartreflex(int srid) +{ + u32 target_opp_no = 0; + struct omap_sr *sr = NULL; + + if (srid == SR1) + sr = &sr1; + else if (srid == SR2) + sr = &sr2; + + if (sr->is_autocomp_active == 1) { + if (sr->is_sr_reset == 1) { + /* Enable SR clks */ + clk_enable(sr->clk); + + if (srid == SR1) + target_opp_no = get_opp_no(current_vdd1_opp); + else if (srid == SR2) + target_opp_no = get_opp_no(current_vdd2_opp); + + sr_configure(sr); + + if (!sr_enable(sr, target_opp_no)) + clk_disable(sr->clk); + } + } +} + +void disable_smartreflex(int srid) +{ + struct omap_sr *sr = NULL; + + if (srid == SR1) + sr = &sr1; + else if (srid == SR2) + sr = &sr2; + + if (sr->is_autocomp_active == 1) { + if (sr->is_sr_reset == 0) { + + sr->is_sr_reset = 1; + /* SRCONFIG - disable SR */ + sr_modify_reg(sr, SRCONFIG, SRCONFIG_SRENABLE, + ~SRCONFIG_SRENABLE); + + /* Disable SR clk */ + clk_disable(sr->clk); + if (sr->srid == SR1) { + /* Disable VP1 */ + prm_clear_mod_reg_bits(PRM_VP1_CONFIG_VPENABLE, + OMAP3430_GR_MOD, + OMAP3_PRM_VP1_CONFIG_OFFSET); + } else if (sr->srid == SR2) { + /* Disable VP2 */ + prm_clear_mod_reg_bits(PRM_VP2_CONFIG_VPENABLE, + OMAP3430_GR_MOD, + OMAP3_PRM_VP2_CONFIG_OFFSET); + } + } + } +} + +/* Voltage Scaling using SR VCBYPASS */ +int sr_voltagescale_vcbypass(u32 target_opp, u8 vsel) +{ + int sr_status = 0; + u32 vdd, target_opp_no; + u32 vc_bypass_value; + u32 reg_addr = 0; + u32 loop_cnt = 0, retries_cnt = 0; + + vdd = get_vdd(target_opp); + target_opp_no = get_opp_no(target_opp); + + if (vdd == PRCM_VDD1) { + sr_status = sr_stop_vddautocomap(SR1); + + prm_rmw_mod_reg_bits(OMAP3430_VC_CMD_ON_MASK, + (vsel << OMAP3430_VC_CMD_ON_SHIFT), + OMAP3430_GR_MOD, + OMAP3_PRM_VC_CMD_VAL_0_OFFSET); + reg_addr = R_VDD1_SR_CONTROL; + + } else if (vdd == PRCM_VDD2) { + sr_status = sr_stop_vddautocomap(SR2); + + prm_rmw_mod_reg_bits(OMAP3430_VC_CMD_ON_MASK, + (vsel << OMAP3430_VC_CMD_ON_SHIFT), + OMAP3430_GR_MOD, + OMAP3_PRM_VC_CMD_VAL_1_OFFSET); + reg_addr = R_VDD2_SR_CONTROL; + } + + vc_bypass_value = (vsel << OMAP3430_DATA_SHIFT) | + (reg_addr << OMAP3430_REGADDR_SHIFT) | + (R_SRI2C_SLAVE_ADDR << OMAP3430_SLAVEADDR_SHIFT); + + prm_write_mod_reg(vc_bypass_value, OMAP3430_GR_MOD, + OMAP3_PRM_VC_BYPASS_VAL_OFFSET); + + vc_bypass_value = prm_set_mod_reg_bits(OMAP3430_VALID, OMAP3430_GR_MOD, + OMAP3_PRM_VC_BYPASS_VAL_OFFSET); + + while ((vc_bypass_value & OMAP3430_VALID) != 0x0) { + loop_cnt++; + if (retries_cnt > 10) { + printk(KERN_INFO "Loop count exceeded in check SR I2C" + "write\n"); + return SR_FAIL; + } + if (loop_cnt > 50) { + retries_cnt++; + loop_cnt = 0; + udelay(10); + } + vc_bypass_value = prm_read_mod_reg(OMAP3430_GR_MOD, + OMAP3_PRM_VC_BYPASS_VAL_OFFSET); + } + + udelay(T2_SMPS_UPDATE_DELAY); + + if (sr_status) { + if (vdd == PRCM_VDD1) + sr_start_vddautocomap(SR1, target_opp_no); + else if (vdd == PRCM_VDD2) + sr_start_vddautocomap(SR2, target_opp_no); + } + + return SR_PASS; +} + +/* Sysfs interface to select SR VDD1 auto compensation */ +static ssize_t omap_sr_vdd1_autocomp_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", sr1.is_autocomp_active); +} + +static ssize_t omap_sr_vdd1_autocomp_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + u32 current_vdd1opp_no; + unsigned short value; + + if (sscanf(buf, "%hu", &value) != 1 || (value > 1)) { + printk(KERN_ERR "sr_vdd1_autocomp: Invalid value\n"); + return -EINVAL; + } + + current_vdd1opp_no = get_opp_no(current_vdd1_opp); + + if (value == 0) + sr_stop_vddautocomap(SR1); + else + sr_start_vddautocomap(SR1, current_vdd1opp_no); + + return n; +} + +static struct kobj_attribute sr_vdd1_autocomp = { + .attr = { + .name = __stringify(sr_vdd1_autocomp), + .mode = 0644, + }, + .show = omap_sr_vdd1_autocomp_show, + .store = omap_sr_vdd1_autocomp_store, +}; + +/* Sysfs interface to select SR VDD2 auto compensation */ +static ssize_t omap_sr_vdd2_autocomp_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", sr2.is_autocomp_active); +} + +static ssize_t omap_sr_vdd2_autocomp_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + u32 current_vdd2opp_no; + unsigned short value; + + if (sscanf(buf, "%hu", &value) != 1 || (value > 1)) { + printk(KERN_ERR "sr_vdd2_autocomp: Invalid value\n"); + return -EINVAL; + } + + current_vdd2opp_no = get_opp_no(current_vdd2_opp); + + if (value == 0) + sr_stop_vddautocomap(SR2); + else + sr_start_vddautocomap(SR2, current_vdd2opp_no); + + return n; +} + +static struct kobj_attribute sr_vdd2_autocomp = { + .attr = { + .name = __stringify(sr_vdd2_autocomp), + .mode = 0644, + }, + .show = omap_sr_vdd2_autocomp_show, + .store = omap_sr_vdd2_autocomp_store, +}; + + + +static int __init omap3_sr_init(void) +{ + int ret = 0; + u8 RdReg; + + if (is_sil_rev_greater_than(OMAP3430_REV_ES1_0)) { + current_vdd1_opp = PRCM_VDD1_OPP3; + current_vdd2_opp = PRCM_VDD2_OPP3; + } else { + current_vdd1_opp = PRCM_VDD1_OPP1; + current_vdd2_opp = PRCM_VDD1_OPP1; + } + if (cpu_is_omap34xx()) { + sr_clk_init(&sr1_custom_clk); + sr_clk_init(&sr2_custom_clk); + sr1.clk = clk_get(NULL, "sr1_custom_clk"); + sr2.clk = clk_get(NULL, "sr2_custom_clk"); + } + sr_set_clk_length(&sr1); + sr_set_clk_length(&sr2); + + /* Call the VPConfig, VCConfig, set N Values. */ + sr_set_nvalues(&sr1); + sr_configure_vp(SR1); + + sr_set_nvalues(&sr2); + sr_configure_vp(SR2); + + /* Enable SR on T2 */ + ret = twl4030_i2c_read_u8(TWL4030_MODULE_PM_RECEIVER, &RdReg, + R_DCDC_GLOBAL_CFG); + + RdReg |= DCDC_GLOBAL_CFG_ENABLE_SRFLX; + ret |= twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, RdReg, + R_DCDC_GLOBAL_CFG); + + printk(KERN_INFO "SmartReflex driver initialized\n"); + + ret = sysfs_create_file(power_kobj, &sr_vdd1_autocomp.attr); + if (ret) + printk(KERN_ERR "sysfs_create_file failed: %d\n", ret); + + ret = sysfs_create_file(power_kobj, &sr_vdd2_autocomp.attr); + if (ret) + printk(KERN_ERR "sysfs_create_file failed: %d\n", ret); + + return 0; +} + +late_initcall(omap3_sr_init); diff --cc arch/arm/mach-omap2/sram34xx.S index 1acdbe89a80,00000000000..5185a31122d mode 100644,000000..100644 --- a/arch/arm/mach-omap2/sram34xx.S +++ b/arch/arm/mach-omap2/sram34xx.S @@@ -1,179 -1,0 +1,179 @@@ +/* + * linux/arch/arm/mach-omap3/sram.S + * + * Omap3 specific functions that need to be run in internal SRAM + * + * (C) Copyright 2007 + * Texas Instruments Inc. + * Rajendra Nayak + * + * (C) Copyright 2004 + * Texas Instruments, + * Richard Woodruff + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR /PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ +#include +#include - #include ++#include + - #include ++#include + +#include "sdrc.h" +#include "cm.h" + + .text + +/* + * Change frequency of core dpll + * r0 = sdrc_rfr_ctrl r1 = sdrc_actim_ctrla r2 = sdrc_actim_ctrlb r3 = M2 + */ +ENTRY(omap3_sram_configure_core_dpll) + stmfd sp!, {r1-r12, lr} @ store regs to stack + cmp r3, #0x2 + blne configure_sdrc + cmp r3, #0x2 + blne lock_dll + cmp r3, #0x1 + blne unlock_dll + bl sdram_in_selfrefresh @ put the SDRAM in self refresh + bl configure_core_dpll + bl enable_sdrc + cmp r3, #0x1 + blne wait_dll_unlock + cmp r3, #0x2 + blne wait_dll_lock + cmp r3, #0x1 + blne configure_sdrc + mov r0, #0 @ return value + ldmfd sp!, {r1-r12, pc} @ restore regs and return +unlock_dll: + ldr r4, omap3_sdrc_dlla_ctrl + ldr r5, [r4] + orr r5, r5, #0x4 + str r5, [r4] + bx lr +lock_dll: + ldr r4, omap3_sdrc_dlla_ctrl + ldr r5, [r4] + bic r5, r5, #0x4 + str r5, [r4] + bx lr +sdram_in_selfrefresh: + mov r5, #0x0 @ Move 0 to R5 + mcr p15, 0, r5, c7, c10, 5 @ memory barrier + ldr r4, omap3_sdrc_power @ read the SDRC_POWER register + ldr r5, [r4] @ read the contents of SDRC_POWER + orr r5, r5, #0x40 @ enable self refresh on idle req + str r5, [r4] @ write back to SDRC_POWER register + ldr r4, omap3_cm_iclken1_core @ read the CM_ICLKEN1_CORE reg + ldr r5, [r4] + bic r5, r5, #0x2 @ disable iclk bit for SRDC + str r5, [r4] +wait_sdrc_idle: + ldr r4, omap3_cm_idlest1_core + ldr r5, [r4] + and r5, r5, #0x2 @ check for SDRC idle + cmp r5, #2 + bne wait_sdrc_idle + bx lr +configure_core_dpll: + ldr r4, omap3_cm_clksel1_pll + ldr r5, [r4] + ldr r6, core_m2_mask_val @ modify m2 for core dpll + and r5, r5, r6 + orr r5, r5, r3, lsl #0x1B @ r3 contains the M2 val + str r5, [r4] + mov r5, #0x800 @ wait for the clock to stabilise + cmp r3, #2 + bne wait_clk_stable + bx lr +wait_clk_stable: + subs r5, r5, #1 + bne wait_clk_stable + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + bx lr +enable_sdrc: + ldr r4, omap3_cm_iclken1_core + ldr r5, [r4] + orr r5, r5, #0x2 @ enable iclk bit for SDRC + str r5, [r4] +wait_sdrc_idle1: + ldr r4, omap3_cm_idlest1_core + ldr r5, [r4] + and r5, r5, #0x2 + cmp r5, #0 + bne wait_sdrc_idle1 + ldr r4, omap3_sdrc_power + ldr r5, [r4] + bic r5, r5, #0x40 + str r5, [r4] + bx lr +wait_dll_lock: + ldr r4, omap3_sdrc_dlla_status + ldr r5, [r4] + and r5, r5, #0x4 + cmp r5, #0x4 + bne wait_dll_lock + bx lr +wait_dll_unlock: + ldr r4, omap3_sdrc_dlla_status + ldr r5, [r4] + and r5, r5, #0x4 + cmp r5, #0x0 + bne wait_dll_unlock + bx lr +configure_sdrc: + ldr r4, omap3_sdrc_rfr_ctrl + str r0, [r4] + ldr r4, omap3_sdrc_actim_ctrla + str r1, [r4] + ldr r4, omap3_sdrc_actim_ctrlb + str r2, [r4] + bx lr + +omap3_sdrc_power: + .word OMAP34XX_SDRC_REGADDR(SDRC_POWER) +omap3_cm_clksel1_pll: + .word OMAP34XX_CM_REGADDR(PLL_MOD, CM_CLKSEL1) +omap3_cm_idlest1_core: + .word OMAP34XX_CM_REGADDR(CORE_MOD, CM_IDLEST) +omap3_cm_iclken1_core: + .word OMAP34XX_CM_REGADDR(CORE_MOD, CM_ICLKEN1) +omap3_sdrc_rfr_ctrl: + .word OMAP34XX_SDRC_REGADDR(SDRC_RFR_CTRL_0) +omap3_sdrc_actim_ctrla: + .word OMAP34XX_SDRC_REGADDR(SDRC_ACTIM_CTRL_A) +omap3_sdrc_actim_ctrlb: + .word OMAP34XX_SDRC_REGADDR(SDRC_ACTIM_CTRL_B) +omap3_sdrc_dlla_status: + .word OMAP34XX_SDRC_REGADDR(SDRC_DLLA_STATUS) +omap3_sdrc_dlla_ctrl: + .word OMAP34XX_SDRC_REGADDR(SDRC_DLLA_CTRL) +core_m2_mask_val: + .word 0x07FFFFFF + +ENTRY(omap3_sram_configure_core_dpll_sz) + .word . - omap3_sram_configure_core_dpll diff --cc arch/arm/mach-omap2/usb-ehci.c index e178a0d4d92,00000000000..489439ddb0d mode 100644,000000..100644 --- a/arch/arm/mach-omap2/usb-ehci.c +++ b/arch/arm/mach-omap2/usb-ehci.c @@@ -1,165 -1,0 +1,165 @@@ +/* + * linux/arch/arm/mach-omap2/usb-ehci.c + * + * This file will contain the board specific details for the + * Synopsys EHCI host controller on OMAP3430 + * + * Copyright (C) 2007 Texas Instruments + * Author: Vikram Pandita + * + * Generalization by: + * Felipe Balbi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include - #include ++#include +#include + - #include - #include - #include ++#include ++#include ++#include + +#if defined(CONFIG_USB_EHCI_HCD) || defined(CONFIG_USB_EHCI_HCD_MODULE) +static struct resource ehci_resources[] = { + [0] = { + .start = OMAP34XX_HSUSB_HOST_BASE + 0x800, + .end = OMAP34XX_HSUSB_HOST_BASE + 0x800 + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { /* general IRQ */ + .start = INT_34XX_EHCI_IRQ, + .flags = IORESOURCE_IRQ, + } +}; + +static u64 ehci_dmamask = ~(u32)0; +static struct platform_device ehci_device = { + .name = "ehci-omap", + .id = 0, + .dev = { + .dma_mask = &ehci_dmamask, + .coherent_dma_mask = 0xffffffff, + .platform_data = NULL, + }, + .num_resources = ARRAY_SIZE(ehci_resources), + .resource = ehci_resources, +}; + + +/* MUX settings for EHCI pins */ +/* + * setup_ehci_io_mux - initialize IO pad mux for USBHOST + */ +static void setup_ehci_io_mux(void) +{ +#ifdef CONFIG_OMAP_EHCI_PHY_MODE + /* PHY mode of operation for board: 750-2083-001 + * ISP1504 connected to Port1 and Port2 + * Do Func Mux setting for 12-pin ULPI PHY mode + */ + + /* Port1 */ + omap_cfg_reg(Y9_3430_USB1HS_PHY_STP); + omap_cfg_reg(Y8_3430_USB1HS_PHY_CLK); + omap_cfg_reg(AA14_3430_USB1HS_PHY_DIR); + omap_cfg_reg(AA11_3430_USB1HS_PHY_NXT); + omap_cfg_reg(W13_3430_USB1HS_PHY_DATA0); + omap_cfg_reg(W12_3430_USB1HS_PHY_DATA1); + omap_cfg_reg(W11_3430_USB1HS_PHY_DATA2); + omap_cfg_reg(Y11_3430_USB1HS_PHY_DATA3); + omap_cfg_reg(W9_3430_USB1HS_PHY_DATA4); + omap_cfg_reg(Y12_3430_USB1HS_PHY_DATA5); + omap_cfg_reg(W8_3430_USB1HS_PHY_DATA6); + omap_cfg_reg(Y13_3430_USB1HS_PHY_DATA7); + + /* Port2 */ + omap_cfg_reg(AA10_3430_USB2HS_PHY_STP); + omap_cfg_reg(AA8_3430_USB2HS_PHY_CLK); + omap_cfg_reg(AA9_3430_USB2HS_PHY_DIR); + omap_cfg_reg(AB11_3430_USB2HS_PHY_NXT); + omap_cfg_reg(AB10_3430_USB2HS_PHY_DATA0); + omap_cfg_reg(AB9_3430_USB2HS_PHY_DATA1); + omap_cfg_reg(W3_3430_USB2HS_PHY_DATA2); + omap_cfg_reg(T4_3430_USB2HS_PHY_DATA3); + omap_cfg_reg(T3_3430_USB2HS_PHY_DATA4); + omap_cfg_reg(R3_3430_USB2HS_PHY_DATA5); + omap_cfg_reg(R4_3430_USB2HS_PHY_DATA6); + omap_cfg_reg(T2_3430_USB2HS_PHY_DATA7); + +#else + /* Set Func mux for : + * TLL mode of operation + * 12-pin ULPI SDR TLL mode for Port1/2/3 + */ + + /* Port1 */ + omap_cfg_reg(Y9_3430_USB1HS_TLL_STP); + omap_cfg_reg(Y8_3430_USB1HS_TLL_CLK); + omap_cfg_reg(AA14_3430_USB1HS_TLL_DIR); + omap_cfg_reg(AA11_3430_USB1HS_TLL_NXT); + omap_cfg_reg(W13_3430_USB1HS_TLL_DATA0); + omap_cfg_reg(W12_3430_USB1HS_TLL_DATA1); + omap_cfg_reg(W11_3430_USB1HS_TLL_DATA2); + omap_cfg_reg(Y11_3430_USB1HS_TLL_DATA3); + omap_cfg_reg(W9_3430_USB1HS_TLL_DATA4); + omap_cfg_reg(Y12_3430_USB1HS_TLL_DATA5); + omap_cfg_reg(W8_3430_USB1HS_TLL_DATA6); + omap_cfg_reg(Y13_3430_USB1HS_TLL_DATA7); + + /* Port2 */ + omap_cfg_reg(AA10_3430_USB2HS_TLL_STP); + omap_cfg_reg(AA8_3430_USB2HS_TLL_CLK); + omap_cfg_reg(AA9_3430_USB2HS_TLL_DIR); + omap_cfg_reg(AB11_3430_USB2HS_TLL_NXT); + omap_cfg_reg(AB10_3430_USB2HS_TLL_DATA0); + omap_cfg_reg(AB9_3430_USB2HS_TLL_DATA1); + omap_cfg_reg(W3_3430_USB2HS_TLL_DATA2); + omap_cfg_reg(T4_3430_USB2HS_TLL_DATA3); + omap_cfg_reg(T3_3430_USB2HS_TLL_DATA4); + omap_cfg_reg(R3_3430_USB2HS_TLL_DATA5); + omap_cfg_reg(R4_3430_USB2HS_TLL_DATA6); + omap_cfg_reg(T2_3430_USB2HS_TLL_DATA7); + + /* Port3 */ + omap_cfg_reg(AB3_3430_USB3HS_TLL_STP); + omap_cfg_reg(AA6_3430_USB3HS_TLL_CLK); + omap_cfg_reg(AA3_3430_USB3HS_TLL_DIR); + omap_cfg_reg(Y3_3430_USB3HS_TLL_NXT); + omap_cfg_reg(AA5_3430_USB3HS_TLL_DATA0); + omap_cfg_reg(Y4_3430_USB3HS_TLL_DATA1); + omap_cfg_reg(Y5_3430_USB3HS_TLL_DATA2); + omap_cfg_reg(W5_3430_USB3HS_TLL_DATA3); + omap_cfg_reg(AB12_3430_USB3HS_TLL_DATA4); + omap_cfg_reg(AB13_3430_USB3HS_TLL_DATA5); + omap_cfg_reg(AA13_3430_USB3HS_TLL_DATA6); + omap_cfg_reg(AA12_3430_USB3HS_TLL_DATA7); +#endif /* CONFIG_OMAP_EHCI_PHY_MODE */ + + return; +} + +#endif /* EHCI specific data */ + +void __init usb_ehci_init(void) +{ +#if defined(CONFIG_USB_EHCI_HCD) || defined(CONFIG_USB_EHCI_HCD_MODULE) + /* Setup Pin IO MUX for EHCI */ + if (cpu_is_omap34xx()) + setup_ehci_io_mux(); + + if (platform_device_register(&ehci_device) < 0) { + printk(KERN_ERR "Unable to register HS-USB (EHCI) device\n"); + return; + } +#endif +} + diff --cc arch/arm/mach-omap2/usb-musb.c index cbd59f8e81c,00000000000..2a3593e7bfb mode 100644,000000..100644 --- a/arch/arm/mach-omap2/usb-musb.c +++ b/arch/arm/mach-omap2/usb-musb.c @@@ -1,116 -1,0 +1,116 @@@ +/* + * linux/arch/arm/mach-omap2/usb-musb.c + * + * This file will contain the board specific details for the + * MENTOR USB OTG controller on OMAP3430 + * + * Copyright (C) 2007-2008 Texas Instruments + * Copyright (C) 2008 Nokia Corporation + * Author: Vikram Pandita + * + * Generalization by: + * Felipe Balbi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include - #include ++#include +#include + - #include - #include - #include ++#include ++#include ++#include + +#ifdef CONFIG_USB_MUSB_SOC +static struct resource musb_resources[] = { + [0] = { + .start = cpu_is_omap34xx() + ? OMAP34XX_HSUSB_OTG_BASE + : OMAP243X_HS_BASE, + .end = cpu_is_omap34xx() + ? OMAP34XX_HSUSB_OTG_BASE + SZ_8K - 1 + : OMAP243X_HS_BASE + SZ_8K -1, + .flags = IORESOURCE_MEM, + }, + [1] = { /* general IRQ */ + .start = INT_243X_HS_USB_MC, + .flags = IORESOURCE_IRQ, + }, + [2] = { /* DMA IRQ */ + .start = INT_243X_HS_USB_DMA, + .flags = IORESOURCE_IRQ, + }, +}; + +static int clk_on; + +static int musb_set_clock(struct clk *clk, int state) +{ + if (state) { + if (clk_on > 0) + return -ENODEV; + + omap2_block_sleep(); + clk_enable(clk); + clk_on = 1; + } else { + if (clk_on == 0) + return -ENODEV; + + clk_disable(clk); + clk_on = 0; + omap2_allow_sleep(); + } + + return 0; +} + +static struct musb_hdrc_platform_data musb_plat = { +#ifdef CONFIG_USB_MUSB_OTG + .mode = MUSB_OTG, +#elif defined(CONFIG_USB_MUSB_HDRC_HCD) + .mode = MUSB_HOST, +#elif defined(CONFIG_USB_GADGET_MUSB_HDRC) + .mode = MUSB_PERIPHERAL, +#endif + .multipoint = 1, + .clock = cpu_is_omap34xx() + ? "hsotgusb_ick" + : "usbhs_ick", + .set_clock = musb_set_clock, +}; + +static u64 musb_dmamask = ~(u32)0; + +static struct platform_device musb_device = { + .name = "musb_hdrc", + .id = 0, + .dev = { + .dma_mask = &musb_dmamask, + .coherent_dma_mask = 0xffffffff, + .platform_data = &musb_plat, + }, + .num_resources = ARRAY_SIZE(musb_resources), + .resource = musb_resources, +}; +#endif + + +void __init usb_musb_init(void) +{ +#ifdef CONFIG_USB_MUSB_SOC + if (platform_device_register(&musb_device) < 0) { + printk(KERN_ERR "Unable to register HS-USB (MUSB) device\n"); + return; + } +#endif +} + diff --cc arch/arm/plat-omap/bootreason.c index 253dfcf1ecc,00000000000..d527b1bb06f mode 100644,000000..100644 --- a/arch/arm/plat-omap/bootreason.c +++ b/arch/arm/plat-omap/bootreason.c @@@ -1,79 -1,0 +1,79 @@@ +/* + * linux/arch/arm/plat-omap/bootreason.c + * + * OMAP Bootreason passing + * + * Copyright (c) 2004 Nokia + * + * Written by David Weinehall + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include +#include - #include ++#include + +static char boot_reason[16]; + +static int omap_bootreason_read_proc(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int len = 0; + + len += sprintf(page + len, "%s\n", boot_reason); + + *start = page + off; + + if (len > off) + len -= off; + else + len = 0; + + return len < count ? len : count; +} + +static int __init bootreason_init(void) +{ + const struct omap_boot_reason_config *cfg; + int reason_valid = 0; + + cfg = omap_get_config(OMAP_TAG_BOOT_REASON, struct omap_boot_reason_config); + if (cfg != NULL) { + strncpy(boot_reason, cfg->reason_str, sizeof(cfg->reason_str)); + boot_reason[sizeof(cfg->reason_str)] = 0; + reason_valid = 1; + } else { + /* Read the boot reason from the OMAP registers */ + } + + if (!reason_valid) + return -ENOENT; + + printk(KERN_INFO "Bootup reason: %s\n", boot_reason); + + if (!create_proc_read_entry("bootreason", S_IRUGO, NULL, + omap_bootreason_read_proc, NULL)) + return -ENOMEM; + + return 0; +} + +late_initcall(bootreason_init); diff --cc arch/arm/plat-omap/component-version.c index a9fe63d5e72,00000000000..3c9d5230cd8 mode 100644,000000..100644 --- a/arch/arm/plat-omap/component-version.c +++ b/arch/arm/plat-omap/component-version.c @@@ -1,65 -1,0 +1,65 @@@ +/* + * linux/arch/arm/plat-omap/component-version.c + * + * Copyright (C) 2005 Nokia Corporation + * Written by Juha Yrjölä + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include - #include - #include ++#include ++#include + +static int component_version_read_proc(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int len, i; + const struct omap_version_config *ver; + char *p; + + i = 0; + p = page; + while ((ver = omap_get_nr_config(OMAP_TAG_VERSION_STR, + struct omap_version_config, i)) != NULL) { + p += sprintf(p, "%-12s%s\n", ver->component, ver->version); + i++; + } + + len = (p - page) - off; + if (len < 0) + len = 0; + + *eof = (len <= count) ? 1 : 0; + *start = page + off; + + return len; +} + +static int __init component_version_init(void) +{ + if (omap_get_config(OMAP_TAG_VERSION_STR, struct omap_version_config) == NULL) + return -ENODEV; + if (!create_proc_read_entry("component_version", S_IRUGO, NULL, + component_version_read_proc, NULL)) + return -ENOMEM; + + return 0; +} + +static void __exit component_version_exit(void) +{ + remove_proc_entry("component_version", NULL); +} + +late_initcall(component_version_init); +module_exit(component_version_exit); + +MODULE_AUTHOR("Juha Yrjölä "); +MODULE_DESCRIPTION("Component version driver"); +MODULE_LICENSE("GPL"); diff --cc arch/arm/plat-omap/cpu-omap.c index fc6cd5e2b08,ae1de308aaa..dbfcca90851 --- a/arch/arm/plat-omap/cpu-omap.c +++ b/arch/arm/plat-omap/cpu-omap.c @@@ -21,10 -21,9 +21,10 @@@ #include #include - #include + #include #include #include - #include ++#include #define VERY_HI_RATE 900000000 diff --cc arch/arm/plat-omap/devices.c index 5a113119775,187e3d8bfdf..f22ccbb280c --- a/arch/arm/plat-omap/devices.c +++ b/arch/arm/plat-omap/devices.c @@@ -13,21 -13,18 +13,21 @@@ #include #include #include +#include - #include + #include #include #include #include - #include - #include - #include - #include - #include - #include - #include - #include + #include ++#include + #include ++#include + #include + #include -#include ++#include + #include #if defined(CONFIG_OMAP_DSP) || defined(CONFIG_OMAP_DSP_MODULE) diff --cc arch/arm/plat-omap/gpio-switch.c index 7c94fc3c080,00000000000..f2dd67a6764 mode 100644,000000..100644 --- a/arch/arm/plat-omap/gpio-switch.c +++ b/arch/arm/plat-omap/gpio-switch.c @@@ -1,550 -1,0 +1,550 @@@ +/* + * linux/arch/arm/plat-omap/gpio-switch.c + * + * Copyright (C) 2004-2006 Nokia Corporation + * Written by Juha Yrjölä + * and Paul Mundt + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include - #include - #include - #include - #include - #include - #include ++#include ++#include ++#include ++#include ++#include ++#include + +struct gpio_switch { + char name[14]; + u16 gpio; + unsigned flags:4; + unsigned type:4; + unsigned state:1; + unsigned both_edges:1; + + u16 debounce_rising; + u16 debounce_falling; + + void (* notify)(void *data, int state); + void *notify_data; + + struct work_struct work; + struct timer_list timer; + struct platform_device pdev; + + struct list_head node; +}; + +static LIST_HEAD(gpio_switches); +static struct platform_device *gpio_sw_platform_dev; +static struct platform_driver gpio_sw_driver; + +static const struct omap_gpio_switch *board_gpio_sw_table; +static int board_gpio_sw_count; + +static const char *cover_str[2] = { "open", "closed" }; +static const char *connection_str[2] = { "disconnected", "connected" }; +static const char *activity_str[2] = { "inactive", "active" }; + +/* + * GPIO switch state default debounce delay in ms + */ +#define OMAP_GPIO_SW_DEFAULT_DEBOUNCE 10 + +static const char **get_sw_str(struct gpio_switch *sw) +{ + switch (sw->type) { + case OMAP_GPIO_SWITCH_TYPE_COVER: + return cover_str; + case OMAP_GPIO_SWITCH_TYPE_CONNECTION: + return connection_str; + case OMAP_GPIO_SWITCH_TYPE_ACTIVITY: + return activity_str; + default: + BUG(); + return NULL; + } +} + +static const char *get_sw_type(struct gpio_switch *sw) +{ + switch (sw->type) { + case OMAP_GPIO_SWITCH_TYPE_COVER: + return "cover"; + case OMAP_GPIO_SWITCH_TYPE_CONNECTION: + return "connection"; + case OMAP_GPIO_SWITCH_TYPE_ACTIVITY: + return "activity"; + default: + BUG(); + return NULL; + } +} + +static void print_sw_state(struct gpio_switch *sw, int state) +{ + const char **str; + + str = get_sw_str(sw); + if (str != NULL) + printk(KERN_INFO "%s (GPIO %d) is now %s\n", sw->name, sw->gpio, str[state]); +} + +static int gpio_sw_get_state(struct gpio_switch *sw) +{ + int state; + + state = omap_get_gpio_datain(sw->gpio); + if (sw->flags & OMAP_GPIO_SWITCH_FLAG_INVERTED) + state = !state; + + return state; +} + +static ssize_t gpio_sw_state_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct gpio_switch *sw = dev_get_drvdata(dev); + const char **str; + char state[16]; + int enable; + + if (!(sw->flags & OMAP_GPIO_SWITCH_FLAG_OUTPUT)) + return -EPERM; + + if (sscanf(buf, "%15s", state) != 1) + return -EINVAL; + + str = get_sw_str(sw); + if (strcmp(state, str[0]) == 0) + sw->state = enable = 0; + else if (strcmp(state, str[1]) == 0) + sw->state = enable = 1; + else + return -EINVAL; + + if (sw->flags & OMAP_GPIO_SWITCH_FLAG_INVERTED) + enable = !enable; + omap_set_gpio_dataout(sw->gpio, enable); + + return count; +} + +static ssize_t gpio_sw_state_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct gpio_switch *sw = dev_get_drvdata(dev); + const char **str; + + str = get_sw_str(sw); + return sprintf(buf, "%s\n", str[sw->state]); +} + +static DEVICE_ATTR(state, S_IRUGO | S_IWUSR, gpio_sw_state_show, + gpio_sw_state_store); + +static ssize_t gpio_sw_type_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct gpio_switch *sw = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", get_sw_type(sw)); +} + +static DEVICE_ATTR(type, S_IRUGO, gpio_sw_type_show, NULL); + +static ssize_t gpio_sw_direction_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct gpio_switch *sw = dev_get_drvdata(dev); + int is_output; + + is_output = sw->flags & OMAP_GPIO_SWITCH_FLAG_OUTPUT; + return sprintf(buf, "%s\n", is_output ? "output" : "input"); +} + +static DEVICE_ATTR(direction, S_IRUGO, gpio_sw_direction_show, NULL); + + +static irqreturn_t gpio_sw_irq_handler(int irq, void *arg) +{ + struct gpio_switch *sw = arg; + unsigned long timeout; + int state; + + if (!sw->both_edges) { + if (omap_get_gpio_datain(sw->gpio)) + set_irq_type(OMAP_GPIO_IRQ(sw->gpio), IRQ_TYPE_EDGE_FALLING); + else + set_irq_type(OMAP_GPIO_IRQ(sw->gpio), IRQ_TYPE_EDGE_RISING); + } + + state = gpio_sw_get_state(sw); + if (sw->state == state) + return IRQ_HANDLED; + + if (state) + timeout = sw->debounce_rising; + else + timeout = sw->debounce_falling; + if (!timeout) + schedule_work(&sw->work); + else + mod_timer(&sw->timer, jiffies + msecs_to_jiffies(timeout)); + + return IRQ_HANDLED; +} + +static void gpio_sw_timer(unsigned long arg) +{ + struct gpio_switch *sw = (struct gpio_switch *) arg; + + schedule_work(&sw->work); +} + +static void gpio_sw_handler(struct work_struct *work) +{ + struct gpio_switch *sw = container_of(work, struct gpio_switch, work); + int state; + + state = gpio_sw_get_state(sw); + if (sw->state == state) + return; + + sw->state = state; + if (sw->notify != NULL) + sw->notify(sw->notify_data, state); + sysfs_notify(&sw->pdev.dev.kobj, NULL, "state"); + print_sw_state(sw, state); +} + +static int __init can_do_both_edges(struct gpio_switch *sw) +{ + if (!cpu_class_is_omap1()) + return 1; + if (OMAP_GPIO_IS_MPUIO(sw->gpio)) + return 0; + else + return 1; +} + +static void gpio_sw_release(struct device *dev) +{ +} + +static int __init new_switch(struct gpio_switch *sw) +{ + int r, direction, trigger; + + switch (sw->type) { + case OMAP_GPIO_SWITCH_TYPE_COVER: + case OMAP_GPIO_SWITCH_TYPE_CONNECTION: + case OMAP_GPIO_SWITCH_TYPE_ACTIVITY: + break; + default: + printk(KERN_ERR "invalid GPIO switch type: %d\n", sw->type); + return -EINVAL; + } + + sw->pdev.name = sw->name; + sw->pdev.id = -1; + + sw->pdev.dev.parent = &gpio_sw_platform_dev->dev; + sw->pdev.dev.driver = &gpio_sw_driver.driver; + sw->pdev.dev.release = gpio_sw_release; + + r = platform_device_register(&sw->pdev); + if (r) { + printk(KERN_ERR "gpio-switch: platform device registration " + "failed for %s", sw->name); + return r; + } + dev_set_drvdata(&sw->pdev.dev, sw); + + r = omap_request_gpio(sw->gpio); + if (r < 0) { + platform_device_unregister(&sw->pdev); + return r; + } + + /* input: 1, output: 0 */ + direction = !(sw->flags & OMAP_GPIO_SWITCH_FLAG_OUTPUT); + omap_set_gpio_direction(sw->gpio, direction); + + sw->state = gpio_sw_get_state(sw); + + r = 0; + r |= device_create_file(&sw->pdev.dev, &dev_attr_state); + r |= device_create_file(&sw->pdev.dev, &dev_attr_type); + r |= device_create_file(&sw->pdev.dev, &dev_attr_direction); + if (r) + printk(KERN_ERR "gpio-switch: attribute file creation " + "failed for %s\n", sw->name); + + if (!direction) + return 0; + + if (can_do_both_edges(sw)) { + trigger = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING; + sw->both_edges = 1; + } else { + if (omap_get_gpio_datain(sw->gpio)) + trigger = IRQF_TRIGGER_FALLING; + else + trigger = IRQF_TRIGGER_RISING; + } + r = request_irq(OMAP_GPIO_IRQ(sw->gpio), gpio_sw_irq_handler, + IRQF_SHARED | trigger, sw->name, sw); + if (r < 0) { + printk(KERN_ERR "gpio-switch: request_irq() failed " + "for GPIO %d\n", sw->gpio); + platform_device_unregister(&sw->pdev); + omap_free_gpio(sw->gpio); + return r; + } + + INIT_WORK(&sw->work, gpio_sw_handler); + init_timer(&sw->timer); + + sw->timer.function = gpio_sw_timer; + sw->timer.data = (unsigned long)sw; + + list_add(&sw->node, &gpio_switches); + + return 0; +} + +static int __init add_atag_switches(void) +{ + const struct omap_gpio_switch_config *cfg; + struct gpio_switch *sw; + int i, r; + + for (i = 0; ; i++) { + cfg = omap_get_nr_config(OMAP_TAG_GPIO_SWITCH, + struct omap_gpio_switch_config, i); + if (cfg == NULL) + break; + sw = kzalloc(sizeof(*sw), GFP_KERNEL); + if (sw == NULL) { + printk(KERN_ERR "gpio-switch: kmalloc failed\n"); + return -ENOMEM; + } + strncpy(sw->name, cfg->name, sizeof(cfg->name)); + sw->gpio = cfg->gpio; + sw->flags = cfg->flags; + sw->type = cfg->type; + sw->debounce_rising = OMAP_GPIO_SW_DEFAULT_DEBOUNCE; + sw->debounce_falling = OMAP_GPIO_SW_DEFAULT_DEBOUNCE; + if ((r = new_switch(sw)) < 0) { + kfree(sw); + return r; + } + } + return 0; +} + +static struct gpio_switch * __init find_switch(int gpio, const char *name) +{ + struct gpio_switch *sw; + + list_for_each_entry(sw, &gpio_switches, node) { + if ((gpio < 0 || sw->gpio != gpio) && + (name == NULL || strcmp(sw->name, name) != 0)) + continue; + + if (gpio < 0 || name == NULL) + goto no_check; + + if (strcmp(sw->name, name) != 0) + printk("gpio-switch: name mismatch for %d (%s, %s)\n", + gpio, name, sw->name); + else if (sw->gpio != gpio) + printk("gpio-switch: GPIO mismatch for %s (%d, %d)\n", + name, gpio, sw->gpio); +no_check: + return sw; + } + return NULL; +} + +static int __init add_board_switches(void) +{ + int i; + + for (i = 0; i < board_gpio_sw_count; i++) { + const struct omap_gpio_switch *cfg; + struct gpio_switch *sw; + int r; + + cfg = board_gpio_sw_table + i; + if (strlen(cfg->name) > sizeof(sw->name) - 1) + return -EINVAL; + /* Check whether we only update an existing switch + * or add a new switch. */ + sw = find_switch(cfg->gpio, cfg->name); + if (sw != NULL) { + sw->debounce_rising = cfg->debounce_rising; + sw->debounce_falling = cfg->debounce_falling; + sw->notify = cfg->notify; + sw->notify_data = cfg->notify_data; + continue; + } else { + if (cfg->gpio < 0 || cfg->name == NULL) { + printk("gpio-switch: required switch not " + "found (%d, %s)\n", cfg->gpio, + cfg->name); + continue; + } + } + sw = kzalloc(sizeof(*sw), GFP_KERNEL); + if (sw == NULL) { + printk(KERN_ERR "gpio-switch: kmalloc failed\n"); + return -ENOMEM; + } + strlcpy(sw->name, cfg->name, sizeof(sw->name)); + sw->gpio = cfg->gpio; + sw->flags = cfg->flags; + sw->type = cfg->type; + sw->debounce_rising = cfg->debounce_rising; + sw->debounce_falling = cfg->debounce_falling; + sw->notify = cfg->notify; + sw->notify_data = cfg->notify_data; + if ((r = new_switch(sw)) < 0) { + kfree(sw); + return r; + } + } + return 0; +} + +static void gpio_sw_cleanup(void) +{ + struct gpio_switch *sw = NULL, *old = NULL; + + list_for_each_entry(sw, &gpio_switches, node) { + if (old != NULL) + kfree(old); + flush_scheduled_work(); + del_timer_sync(&sw->timer); + + free_irq(OMAP_GPIO_IRQ(sw->gpio), sw); + + device_remove_file(&sw->pdev.dev, &dev_attr_state); + device_remove_file(&sw->pdev.dev, &dev_attr_type); + device_remove_file(&sw->pdev.dev, &dev_attr_direction); + + platform_device_unregister(&sw->pdev); + omap_free_gpio(sw->gpio); + old = sw; + } + kfree(old); +} + +static void __init report_initial_state(void) +{ + struct gpio_switch *sw; + + list_for_each_entry(sw, &gpio_switches, node) { + int state; + + state = omap_get_gpio_datain(sw->gpio); + if (sw->flags & OMAP_GPIO_SWITCH_FLAG_INVERTED) + state = !state; + if (sw->notify != NULL) + sw->notify(sw->notify_data, state); + print_sw_state(sw, state); + } +} + +static int gpio_sw_remove(struct platform_device *dev) +{ + return 0; +} + +static struct platform_driver gpio_sw_driver = { + .remove = gpio_sw_remove, + .driver = { + .name = "gpio-switch", + }, +}; + +void __init omap_register_gpio_switches(const struct omap_gpio_switch *tbl, + int count) +{ + BUG_ON(board_gpio_sw_table != NULL); + + board_gpio_sw_table = tbl; + board_gpio_sw_count = count; +} + +static int __init gpio_sw_init(void) +{ + int r; + + printk(KERN_INFO "OMAP GPIO switch handler initializing\n"); + + r = platform_driver_register(&gpio_sw_driver); + if (r) + return r; + + gpio_sw_platform_dev = platform_device_register_simple("gpio-switch", + -1, NULL, 0); + if (IS_ERR(gpio_sw_platform_dev)) { + r = PTR_ERR(gpio_sw_platform_dev); + goto err1; + } + + r = add_atag_switches(); + if (r < 0) + goto err2; + + r = add_board_switches(); + if (r < 0) + goto err2; + + report_initial_state(); + + return 0; +err2: + gpio_sw_cleanup(); + platform_device_unregister(gpio_sw_platform_dev); +err1: + platform_driver_unregister(&gpio_sw_driver); + return r; +} + +static void __exit gpio_sw_exit(void) +{ + gpio_sw_cleanup(); + platform_device_unregister(gpio_sw_platform_dev); + platform_driver_unregister(&gpio_sw_driver); +} + +#ifndef MODULE +late_initcall(gpio_sw_init); +#else +module_init(gpio_sw_init); +#endif +module_exit(gpio_sw_exit); + +MODULE_AUTHOR("Juha Yrjölä , Paul Mundt * diff --cc arch/arm/plat-omap/include/mach/board-ldp.h index b22756136cb,00000000000..283fa01c9b2 mode 100644,000000..100644 --- a/arch/arm/plat-omap/include/mach/board-ldp.h +++ b/arch/arm/plat-omap/include/mach/board-ldp.h @@@ -1,34 -1,0 +1,34 @@@ +/* - * linux/include/asm-arm/arch-omap/board-ldp.h ++ * arch/arm/plat-omap/include/mach/board-ldp.h + * + * Hardware definitions for TI OMAP3 LDP. + * + * Copyright (C) 2008 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __ASM_ARCH_OMAP_LDP_H +#define __ASM_ARCH_OMAP_LDP_H + +#define TWL4030_IRQNUM INT_34XX_SYS_NIRQ + +#endif /* __ASM_ARCH_OMAP_LDP_H */ diff --cc arch/arm/plat-omap/include/mach/board-omap2evm.h index d770c5824c1,00000000000..ec548d4b705 mode 100644,000000..100644 --- a/arch/arm/plat-omap/include/mach/board-omap2evm.h +++ b/arch/arm/plat-omap/include/mach/board-omap2evm.h @@@ -1,37 -1,0 +1,37 @@@ +/* - * linux/include/asm-arm/arch-omap/board-omap2evm.h ++ * arch/arm/plat-omap/include/mach/board-omap2evm.h + * + * Hardware definitions for Mistral's OMAP2EVM board. + * + * Based on board-2430sdp.h + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __ASM_ARCH_OMAP2_EVM_H +#define __ASM_ARCH_OMAP2_EVM_H + +/* Placeholder for OMAP2EVM specific defines */ +#define OMAP2EVM_ETHR_START 0x2c000000 +#define OMAP2EVM_ETHR_SIZE 1024 +#define OMAP2EVM_ETHR_GPIO_IRQ 149 + +#endif /* __ASM_ARCH_OMAP2_EVM_H */ diff --cc arch/arm/plat-omap/include/mach/board-omap3beagle.h index 46dff31032e,00000000000..3080d52d877 mode 100644,000000..100644 --- a/arch/arm/plat-omap/include/mach/board-omap3beagle.h +++ b/arch/arm/plat-omap/include/mach/board-omap3beagle.h @@@ -1,33 -1,0 +1,33 @@@ +/* - * linux/include/asm-arm/arch-omap/board-omap3beagle.h ++ * arch/arm/plat-omap/include/mach/board-omap3beagle.h + * + * Hardware definitions for TI OMAP3 BEAGLE. + * + * Initial creation by Syed Mohammed Khasim + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __ASM_ARCH_OMAP3_BEAGLE_H +#define __ASM_ARCH_OMAP3_BEAGLE_H + +#endif /* __ASM_ARCH_OMAP3_BEAGLE_H */ + diff --cc arch/arm/plat-omap/include/mach/board-omap3evm.h index 784d7f2f830,00000000000..7a540e9e1af mode 100644,000000..100644 --- a/arch/arm/plat-omap/include/mach/board-omap3evm.h +++ b/arch/arm/plat-omap/include/mach/board-omap3evm.h @@@ -1,44 -1,0 +1,44 @@@ +/* - * linux/include/asm-arm/arch-omap/board-omap3evm.h ++ * arch/arm/plat-omap/include/mach/board-omap3evm.h + * + * Hardware definitions for TI OMAP3 EVM. + * + * Initial creation by Syed Mohammed Khasim + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __ASM_ARCH_OMAP3_EVM_H +#define __ASM_ARCH_OMAP3_EVM_H + +extern void omap3evm_flash_init(void); + +#define OMAP3_EVM_TS_GPIO 175 + +#define ONENAND_MAP 0x20000000 + +#define OMAP3EVM_ETHR_START 0x2c000000 +#define OMAP3EVM_ETHR_SIZE 1024 +#define OMAP3EVM_ETHR_GPIO_IRQ 176 +#define OMAP3EVM_SMC911X_CS 5 + +#endif /* __ASM_ARCH_OMAP3_EVM_H */ + diff --cc arch/arm/plat-omap/include/mach/clockdomain.h index b58bb73b589,00000000000..b9d0dd2da89 mode 100644,000000..100644 --- a/arch/arm/plat-omap/include/mach/clockdomain.h +++ b/arch/arm/plat-omap/include/mach/clockdomain.h @@@ -1,110 -1,0 +1,110 @@@ +/* - * linux/include/asm-arm/arch-omap/clockdomain.h ++ * arch/arm/plat-omap/include/mach/clockdomain.h + * + * OMAP2/3 clockdomain framework functions + * + * Copyright (C) 2008 Texas Instruments, Inc. + * Copyright (C) 2008 Nokia Corporation + * + * Written by Paul Walmsley + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __ASM_ARM_ARCH_OMAP_CLOCKDOMAIN_H +#define __ASM_ARM_ARCH_OMAP_CLOCKDOMAIN_H + - #include - #include - #include ++#include ++#include ++#include + +/* Clockdomain capability flags */ +#define CLKDM_CAN_FORCE_SLEEP (1 << 0) +#define CLKDM_CAN_FORCE_WAKEUP (1 << 1) +#define CLKDM_CAN_ENABLE_AUTO (1 << 2) +#define CLKDM_CAN_DISABLE_AUTO (1 << 3) + +#define CLKDM_CAN_HWSUP (CLKDM_CAN_ENABLE_AUTO | CLKDM_CAN_DISABLE_AUTO) +#define CLKDM_CAN_SWSUP (CLKDM_CAN_FORCE_SLEEP | CLKDM_CAN_FORCE_WAKEUP) +#define CLKDM_CAN_HWSUP_SWSUP (CLKDM_CAN_SWSUP | CLKDM_CAN_HWSUP) + +/* OMAP24XX CM_CLKSTCTRL_*.AUTOSTATE_* register bit values */ +#define OMAP24XX_CLKSTCTRL_DISABLE_AUTO 0x0 +#define OMAP24XX_CLKSTCTRL_ENABLE_AUTO 0x1 + +/* OMAP3XXX CM_CLKSTCTRL_*.CLKTRCTRL_* register bit values */ +#define OMAP34XX_CLKSTCTRL_DISABLE_AUTO 0x0 +#define OMAP34XX_CLKSTCTRL_FORCE_SLEEP 0x1 +#define OMAP34XX_CLKSTCTRL_FORCE_WAKEUP 0x2 +#define OMAP34XX_CLKSTCTRL_ENABLE_AUTO 0x3 + +/* + * struct clkdm_pwrdm_autodep - a powerdomain that should have wkdeps + * and sleepdeps added when a powerdomain should stay active in hwsup mode; + * and conversely, removed when the powerdomain should be allowed to go + * inactive in hwsup mode. + */ +struct clkdm_pwrdm_autodep { + + union { + /* Name of the powerdomain to add a wkdep/sleepdep on */ + const char *name; + + /* Powerdomain pointer (looked up at clkdm_init() time) */ + struct powerdomain *ptr; + } pwrdm; + + /* OMAP chip types that this clockdomain dep is valid on */ + const struct omap_chip_id omap_chip; + +}; + +struct clockdomain { + + /* Clockdomain name */ + const char *name; + + union { + /* Powerdomain enclosing this clockdomain */ + const char *name; + + /* Powerdomain pointer assigned at clkdm_register() */ + struct powerdomain *ptr; + } pwrdm; + + /* CLKTRCTRL/AUTOSTATE field mask in CM_CLKSTCTRL reg */ + const u16 clktrctrl_mask; + + /* Clockdomain capability flags */ + const u8 flags; + + /* OMAP chip types that this clockdomain is valid on */ + const struct omap_chip_id omap_chip; + + /* Usecount tracking */ + atomic_t usecount; + + struct list_head node; + +}; + +void clkdm_init(struct clockdomain **clkdms, struct clkdm_pwrdm_autodep *autodeps); +int clkdm_register(struct clockdomain *clkdm); +int clkdm_unregister(struct clockdomain *clkdm); +struct clockdomain *clkdm_lookup(const char *name); + +int clkdm_for_each(int (*fn)(struct clockdomain *clkdm)); +struct powerdomain *clkdm_get_pwrdm(struct clockdomain *clkdm); + +void omap2_clkdm_allow_idle(struct clockdomain *clkdm); +void omap2_clkdm_deny_idle(struct clockdomain *clkdm); + +int omap2_clkdm_wakeup(struct clockdomain *clkdm); +int omap2_clkdm_sleep(struct clockdomain *clkdm); + +int omap2_clkdm_clk_enable(struct clockdomain *clkdm, struct clk *clk); +int omap2_clkdm_clk_disable(struct clockdomain *clkdm, struct clk *clk); + +#endif diff --cc arch/arm/plat-omap/include/mach/control.h index 6e64fe76016,e3fd62d9a99..e15d6e98818 --- a/arch/arm/plat-omap/include/mach/control.h +++ b/arch/arm/plat-omap/include/mach/control.h @@@ -13,23 -16,14 +13,23 @@@ * the Free Software Foundation. */ +#ifndef __ASM_ARCH_CONTROL_H +#define __ASM_ARCH_CONTROL_H + - #include + #include +#ifndef __ASSEMBLY__ #define OMAP242X_CTRL_REGADDR(reg) \ - (void __iomem *)IO_ADDRESS(OMAP242X_CTRL_BASE + (reg)) + (__force void __iomem *)IO_ADDRESS(OMAP242X_CTRL_BASE + (reg)) #define OMAP243X_CTRL_REGADDR(reg) \ - (void __iomem *)IO_ADDRESS(OMAP243X_CTRL_BASE + (reg)) + (__force void __iomem *)IO_ADDRESS(OMAP243X_CTRL_BASE + (reg)) #define OMAP343X_CTRL_REGADDR(reg) \ - (void __iomem *)IO_ADDRESS(OMAP343X_CTRL_BASE + (reg)) + (__force void __iomem *)IO_ADDRESS(OMAP343X_CTRL_BASE + (reg)) +#else +#define OMAP242X_CTRL_REGADDR(reg) IO_ADDRESS(OMAP242X_CTRL_BASE + (reg)) +#define OMAP243X_CTRL_REGADDR(reg) IO_ADDRESS(OMAP243X_CTRL_BASE + (reg)) +#define OMAP343X_CTRL_REGADDR(reg) IO_ADDRESS(OMAP343X_CTRL_BASE + (reg)) +#endif /* __ASSEMBLY__ */ /* * As elsewhere, the "OMAP2_" prefix indicates that the macro is valid for diff --cc arch/arm/plat-omap/include/mach/dsp.h index ae71eb80483,00000000000..ae71eb80483 mode 100644,000000..100644 --- a/arch/arm/plat-omap/include/mach/dsp.h +++ b/arch/arm/plat-omap/include/mach/dsp.h diff --cc arch/arm/plat-omap/include/mach/entry-macro.S index 5951fdb9f8f,d4e9043bf20..a8fca9d9845 --- a/arch/arm/plat-omap/include/mach/entry-macro.S +++ b/arch/arm/plat-omap/include/mach/entry-macro.S @@@ -55,17 -55,9 +55,17 @@@ 1510: .endm -#elif defined(CONFIG_ARCH_OMAP24XX) +#endif +#if defined(CONFIG_ARCH_OMAP24XX) || defined(CONFIG_ARCH_OMAP34XX) +#if defined(CONFIG_ARCH_OMAP24XX) - #include + #include +#endif +#if defined(CONFIG_ARCH_OMAP34XX) - #include ++#include +#endif + +#define INTCPS_SIR_IRQ_OFFSET 0x0040 /* Active interrupt number */ .macro disable_fiq .endm diff --cc arch/arm/plat-omap/include/mach/hdrc_cnf.h index 74c84328761,00000000000..74c84328761 mode 100644,000000..100644 --- a/arch/arm/plat-omap/include/mach/hdrc_cnf.h +++ b/arch/arm/plat-omap/include/mach/hdrc_cnf.h diff --cc arch/arm/plat-omap/include/mach/hsmmc.h index 587e8abb33a,00000000000..587e8abb33a mode 100644,000000..100644 --- a/arch/arm/plat-omap/include/mach/hsmmc.h +++ b/arch/arm/plat-omap/include/mach/hsmmc.h diff --cc arch/arm/plat-omap/include/mach/irqs.h index f901e8b4257,17248bbf3f2..e51e5e65920 --- a/arch/arm/plat-omap/include/mach/irqs.h +++ b/arch/arm/plat-omap/include/mach/irqs.h @@@ -369,9 -325,8 +369,9 @@@ #ifndef __ASSEMBLY__ extern void omap_init_irq(void); +extern int omap_irq_pending(void); #endif - #include + #include #endif diff --cc arch/arm/plat-omap/include/mach/mmu.h index d970b71a490,00000000000..d970b71a490 mode 100644,000000..100644 --- a/arch/arm/plat-omap/include/mach/mmu.h +++ b/arch/arm/plat-omap/include/mach/mmu.h diff --cc arch/arm/plat-omap/include/mach/omap-alsa.h index 58879bcec0c,bdf30a0f87f..b0341e7a0cf --- a/arch/arm/plat-omap/include/mach/omap-alsa.h +++ b/arch/arm/plat-omap/include/mach/omap-alsa.h @@@ -40,38 -40,11 +40,38 @@@ #ifndef __OMAP_ALSA_H #define __OMAP_ALSA_H - #include + #include #include #include - #include + #include #include +/* + * Debug functions + */ +#undef DEBUG +/* #define DEBUG */ + +#define ERR(ARGS...) \ + do { \ + printk(KERN_ERR "{%s}-ERROR: ", __func__); \ + printk(ARGS); \ + } while (0) + +#ifdef DEBUG +#define DPRINTK(ARGS...) \ + do { \ + printk(KERN_INFO "<%s>: ", __func__); \ + printk(ARGS); \ + } while (0) +#define ADEBUG() printk("XXX Alsa debug f:%s, l:%d\n", __func__, __LINE__) +#define FN_IN printk(KERN_INFO "[%s]: start\n", __func__) +#define FN_OUT(n) printk(KERN_INFO "[%s]: end(%u)\n", __func__, n) +#else +#define DPRINTK(ARGS...) /* nop */ +#define ADEBUG() /* nop */ +#define FN_IN /* nop */ +#define FN_OUT(n) /* nop */ +#endif #define DMA_BUF_SIZE (1024 * 8) diff --cc arch/arm/plat-omap/include/mach/pm.h index bafe109f29e,bfa09325a5f..713bcc9e143 --- a/arch/arm/plat-omap/include/mach/pm.h +++ b/arch/arm/plat-omap/include/mach/pm.h @@@ -1,5 -1,5 +1,5 @@@ /* - * linux/include/asm/arch-omap/pm.h - * arch/arm/plat-omap/include/mach/pm.h ++ * linux/include/mach-omap/pm.h * * Header file for OMAP Power Management Routines * diff --cc arch/arm/plat-omap/include/mach/powerdomain.h index 0e794594404,00000000000..4948cb7af5b mode 100644,000000..100644 --- a/arch/arm/plat-omap/include/mach/powerdomain.h +++ b/arch/arm/plat-omap/include/mach/powerdomain.h @@@ -1,167 -1,0 +1,167 @@@ +/* + * OMAP2/3 powerdomain control + * + * Copyright (C) 2007-8 Texas Instruments, Inc. + * Copyright (C) 2007-8 Nokia Corporation + * + * Written by Paul Walmsley + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef ASM_ARM_ARCH_OMAP_POWERDOMAIN +#define ASM_ARM_ARCH_OMAP_POWERDOMAIN + +#include +#include + +#include + - #include ++#include + + +/* Powerdomain basic power states */ +#define PWRDM_POWER_OFF 0x0 +#define PWRDM_POWER_RET 0x1 +#define PWRDM_POWER_INACTIVE 0x2 +#define PWRDM_POWER_ON 0x3 + +/* Powerdomain allowable state bitfields */ +#define PWRSTS_OFF_ON ((1 << PWRDM_POWER_OFF) | \ + (1 << PWRDM_POWER_ON)) + +#define PWRSTS_OFF_RET ((1 << PWRDM_POWER_OFF) | \ + (1 << PWRDM_POWER_RET)) + +#define PWRSTS_OFF_RET_ON (PWRSTS_OFF_RET | (1 << PWRDM_POWER_ON)) + + +/* Powerdomain flags */ +#define PWRDM_HAS_HDWR_SAR (1 << 0) /* hardware save-and-restore support */ + + +/* + * Number of memory banks that are power-controllable. On OMAP3430, the + * maximum is 4. + */ +#define PWRDM_MAX_MEM_BANKS 4 + +/* + * Maximum number of clockdomains that can be associated with a powerdomain. + * CORE powerdomain is probably the worst case. + */ +#define PWRDM_MAX_CLKDMS 3 + +/* XXX A completely arbitrary number. What is reasonable here? */ +#define PWRDM_TRANSITION_BAILOUT 100000 + +struct clockdomain; +struct powerdomain; + +/* Encodes dependencies between powerdomains - statically defined */ +struct pwrdm_dep { + + /* Powerdomain name */ + const char *pwrdm_name; + + /* Powerdomain pointer - resolved by the powerdomain code */ + struct powerdomain *pwrdm; + + /* Flags to mark OMAP chip restrictions, etc. */ + const struct omap_chip_id omap_chip; + +}; + +struct powerdomain { + + /* Powerdomain name */ + const char *name; + + /* the address offset from CM_BASE/PRM_BASE */ + const s16 prcm_offs; + + /* Used to represent the OMAP chip types containing this pwrdm */ + const struct omap_chip_id omap_chip; + + /* Bit shift of this powerdomain's PM_WKDEP/CM_SLEEPDEP bit */ + const u8 dep_bit; + + /* Powerdomains that can be told to wake this powerdomain up */ + struct pwrdm_dep *wkdep_srcs; + + /* Powerdomains that can be told to keep this pwrdm from inactivity */ + struct pwrdm_dep *sleepdep_srcs; + + /* Possible powerdomain power states */ + const u8 pwrsts; + + /* Possible logic power states when pwrdm in RETENTION */ + const u8 pwrsts_logic_ret; + + /* Powerdomain flags */ + const u8 flags; + + /* Number of software-controllable memory banks in this powerdomain */ + const u8 banks; + + /* Possible memory bank pwrstates when pwrdm in RETENTION */ + const u8 pwrsts_mem_ret[PWRDM_MAX_MEM_BANKS]; + + /* Possible memory bank pwrstates when pwrdm is ON */ + const u8 pwrsts_mem_on[PWRDM_MAX_MEM_BANKS]; + + /* Clockdomains in this powerdomain */ + struct clockdomain *pwrdm_clkdms[PWRDM_MAX_CLKDMS]; + + struct list_head node; + +}; + + +void pwrdm_init(struct powerdomain **pwrdm_list); + +int pwrdm_register(struct powerdomain *pwrdm); +int pwrdm_unregister(struct powerdomain *pwrdm); +struct powerdomain *pwrdm_lookup(const char *name); + +int pwrdm_for_each(int (*fn)(struct powerdomain *pwrdm)); + +int pwrdm_add_clkdm(struct powerdomain *pwrdm, struct clockdomain *clkdm); +int pwrdm_del_clkdm(struct powerdomain *pwrdm, struct clockdomain *clkdm); +int pwrdm_for_each_clkdm(struct powerdomain *pwrdm, + int (*fn)(struct powerdomain *pwrdm, + struct clockdomain *clkdm)); + +int pwrdm_add_wkdep(struct powerdomain *pwrdm1, struct powerdomain *pwrdm2); +int pwrdm_del_wkdep(struct powerdomain *pwrdm1, struct powerdomain *pwrdm2); +int pwrdm_read_wkdep(struct powerdomain *pwrdm1, struct powerdomain *pwrdm2); +int pwrdm_add_sleepdep(struct powerdomain *pwrdm1, struct powerdomain *pwrdm2); +int pwrdm_del_sleepdep(struct powerdomain *pwrdm1, struct powerdomain *pwrdm2); +int pwrdm_read_sleepdep(struct powerdomain *pwrdm1, struct powerdomain *pwrdm2); + +int pwrdm_get_mem_bank_count(struct powerdomain *pwrdm); + +int pwrdm_set_next_pwrst(struct powerdomain *pwrdm, u8 pwrst); +int pwrdm_read_next_pwrst(struct powerdomain *pwrdm); +int pwrdm_read_pwrst(struct powerdomain *pwrdm); +int pwrdm_read_prev_pwrst(struct powerdomain *pwrdm); +int pwrdm_clear_all_prev_pwrst(struct powerdomain *pwrdm); + +int pwrdm_set_logic_retst(struct powerdomain *pwrdm, u8 pwrst); +int pwrdm_set_mem_onst(struct powerdomain *pwrdm, u8 bank, u8 pwrst); +int pwrdm_set_mem_retst(struct powerdomain *pwrdm, u8 bank, u8 pwrst); + +int pwrdm_read_logic_pwrst(struct powerdomain *pwrdm); +int pwrdm_read_prev_logic_pwrst(struct powerdomain *pwrdm); +int pwrdm_read_mem_pwrst(struct powerdomain *pwrdm, u8 bank); +int pwrdm_read_prev_mem_pwrst(struct powerdomain *pwrdm, u8 bank); + +int pwrdm_enable_hdwr_sar(struct powerdomain *pwrdm); +int pwrdm_disable_hdwr_sar(struct powerdomain *pwrdm); +bool pwrdm_has_hdwr_sar(struct powerdomain *pwrdm); + +int pwrdm_wait_transition(struct powerdomain *pwrdm); + +#endif diff --cc arch/arm/plat-omap/include/mach/sti.h index d818dc6552d,00000000000..d818dc6552d mode 100644,000000..100644 --- a/arch/arm/plat-omap/include/mach/sti.h +++ b/arch/arm/plat-omap/include/mach/sti.h diff --cc arch/arm/plat-omap/include/mach/system.h index 4faeb9fc7ae,06a28c7b98d..61d9f36722a --- a/arch/arm/plat-omap/include/mach/system.h +++ b/arch/arm/plat-omap/include/mach/system.h @@@ -7,10 -7,8 +7,10 @@@ #include #include - #include + #include - #include ++#include + #ifndef CONFIG_MACH_VOICEBLUE #define voiceblue_reset() do {} while (0) #endif diff --cc arch/arm/plat-omap/include/mach/usb-ehci.h index 2ae3eeac2b0,00000000000..4419bd14dfb mode 100644,000000..100644 --- a/arch/arm/plat-omap/include/mach/usb-ehci.h +++ b/arch/arm/plat-omap/include/mach/usb-ehci.h @@@ -1,35 -1,0 +1,35 @@@ +/* - * linux/include/asm-arm/arch-omap/usb-ehci.h ++ * arch/arm/plat-omap/include/mach/usb-ehci.h + * + * Hardware definitions for Synopsys EHCI host controller. + * + * Initial creation by Felipe Balbi. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __ASM_ARCH_OMAP_USB_EHCI_H +#define __ASM_ARCH_OMAP_USB_EHCI_H + +extern void usb_ehci_init(void); + +#endif /* __ASM_ARCH_OMAP_USB_EHCI_H */ + diff --cc arch/arm/plat-omap/include/mach/usb-musb.h index 4f0c8309668,00000000000..b455b29a3ad mode 100644,000000..100644 --- a/arch/arm/plat-omap/include/mach/usb-musb.h +++ b/arch/arm/plat-omap/include/mach/usb-musb.h @@@ -1,35 -1,0 +1,35 @@@ +/* - * linux/include/asm-arm/arch-omap/usb-musb.h ++ * arch/arm/plat-omap/include/mach/usb-musb.h + * + * Hardware definitions for Mentor Graphics MUSBMHDRC. + * + * Initial creation by Felipe Balbi. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __ASM_ARCH_OMAP_USB_MUSB_H +#define __ASM_ARCH_OMAP_USB_MUSB_H + +extern void usb_musb_init(void); + +#endif /* __ASM_ARCH_OMAP_USB_MUSB_H */ + diff --cc arch/arm/plat-omap/mcbsp.c index 70944a5c615,d0844050f2d..0f0e3f3cd2a --- a/arch/arm/plat-omap/mcbsp.c +++ b/arch/arm/plat-omap/mcbsp.c @@@ -24,35 -24,15 +24,35 @@@ #include #include - #include - #include + #include + #include -static struct omap_mcbsp mcbsp[OMAP_MAX_MCBSP_COUNT]; +struct omap_mcbsp **mcbsp_ptr; +int omap_mcbsp_count; -#define omap_mcbsp_check_valid_id(id) (mcbsp[id].pdata && \ - mcbsp[id].pdata->ops && \ - mcbsp[id].pdata->ops->check && \ - (mcbsp[id].pdata->ops->check(id) == 0)) +void omap_mcbsp_write(u32 io_base, u16 reg, u32 val) +{ + if (cpu_class_is_omap1() || cpu_is_omap2420()) + __raw_writew((u16)val, io_base + reg); + else + __raw_writel(val, io_base + reg); +} + +int omap_mcbsp_read(u32 io_base, u16 reg) +{ + if (cpu_class_is_omap1() || cpu_is_omap2420()) + return __raw_readw(io_base + reg); + else + return __raw_readl(io_base + reg); +} + +#define OMAP_MCBSP_READ(base, reg) \ + omap_mcbsp_read(base, OMAP_MCBSP_REG_##reg) +#define OMAP_MCBSP_WRITE(base, reg, val) \ + omap_mcbsp_write(base, OMAP_MCBSP_REG_##reg, val) + +#define omap_mcbsp_check_valid_id(id) (id < omap_mcbsp_count) +#define id_to_mcbsp_ptr(id) mcbsp_ptr[id]; static void omap_mcbsp_dump_reg(u8 id) { diff --cc arch/arm/plat-omap/mmu.c index 4b1ba8e9983,00000000000..d81a1488bd9 mode 100644,000000..100644 --- a/arch/arm/plat-omap/mmu.c +++ b/arch/arm/plat-omap/mmu.c @@@ -1,1563 -1,0 +1,1563 @@@ +/* + * linux/arch/arm/plat-omap/mmu.c + * + * OMAP MMU management framework + * + * Copyright (C) 2002-2006 Nokia Corporation + * + * Written by Toshihiro Kobayashi + * and Paul Mundt + * + * TWL support: Hiroshi DOYU + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include - #include ++#include +#include - #include ++#include + +#if defined(CONFIG_ARCH_OMAP1) +#include "../mach-omap1/mmu.h" +#elif defined(CONFIG_ARCH_OMAP2) +#include "../mach-omap2/mmu.h" +#endif + +/* + * On OMAP2 MMU_LOCK_xxx_MASK only applies to the IVA and DSP, the camera + * MMU has base and victim implemented in different bits in the LOCK + * register (shifts are still the same), all of the other registers are + * the same on all of the MMUs.. + */ +#define MMU_LOCK_BASE_SHIFT 10 +#define MMU_LOCK_VICTIM_SHIFT 4 + +#define CAMERA_MMU_LOCK_BASE_MASK (0x7 << MMU_LOCK_BASE_SHIFT) +#define CAMERA_MMU_LOCK_VICTIM_MASK (0x7 << MMU_LOCK_VICTIM_SHIFT) + +#define is_aligned(adr, align) (!((adr)&((align)-1))) +#define ORDER_1MB (20 - PAGE_SHIFT) +#define ORDER_64KB (16 - PAGE_SHIFT) +#define ORDER_4KB (12 - PAGE_SHIFT) + +#define MMU_CNTL_EMUTLBUPDATE (1<<3) +#define MMU_CNTL_TWLENABLE (1<<2) +#define MMU_CNTL_MMUENABLE (1<<1) + +static mempool_t *mempool_1M; +static mempool_t *mempool_64K; + +#define omap_mmu_for_each_tlb_entry(mmu, entry) \ + for (entry = mmu->exmap_tbl; prefetch(entry + 1), \ + entry < (mmu->exmap_tbl + mmu->nr_tlb_entries); \ + entry++) + +#define to_dev(obj) container_of(obj, struct device, kobj) + +static void *mempool_alloc_from_pool(mempool_t *pool, + unsigned int __nocast gfp_mask) +{ + spin_lock_irq(&pool->lock); + if (likely(pool->curr_nr)) { + void *element = pool->elements[--pool->curr_nr]; + spin_unlock_irq(&pool->lock); + return element; + } + + spin_unlock_irq(&pool->lock); + return mempool_alloc(pool, gfp_mask); +} + +/* + * kmem_reserve(), kmem_release(): + * reserve or release kernel memory for exmap(). + * + * exmap() might request consecutive 1MB or 64kB, + * but it will be difficult after memory pages are fragmented. + * So, user can reserve such memory blocks in the early phase + * through kmem_reserve(). + */ +static void *omap_mmu_pool_alloc(unsigned int __nocast gfp, void *order) +{ + return (void *)__get_dma_pages(gfp, (unsigned int)order); +} + +static void omap_mmu_pool_free(void *buf, void *order) +{ + free_pages((unsigned long)buf, (unsigned int)order); +} + +int omap_mmu_kmem_reserve(struct omap_mmu *mmu, unsigned long size) +{ + unsigned long len = size; + + /* alignment check */ + if (!is_aligned(size, SZ_64K)) { + dev_err(mmu->dev, + "MMU %s: size(0x%lx) is not multiple of 64KB.\n", + mmu->name, size); + return -EINVAL; + } + + if (size > (1 << mmu->addrspace)) { + dev_err(mmu->dev, + "MMU %s: size(0x%lx) is larger than external device " + " memory space size (0x%x.\n", mmu->name, size, + (1 << mmu->addrspace)); + return -EINVAL; + } + + if (size >= SZ_1M) { + int nr = size >> 20; + + if (likely(!mempool_1M)) + mempool_1M = mempool_create(nr, omap_mmu_pool_alloc, + omap_mmu_pool_free, + (void *)ORDER_1MB); + else + mempool_resize(mempool_1M, mempool_1M->min_nr + nr, + GFP_KERNEL); + + size &= ~(0xf << 20); + } + + if (size >= SZ_64K) { + int nr = size >> 16; + + if (likely(!mempool_64K)) + mempool_64K = mempool_create(nr, omap_mmu_pool_alloc, + omap_mmu_pool_free, + (void *)ORDER_64KB); + else + mempool_resize(mempool_64K, mempool_64K->min_nr + nr, + GFP_KERNEL); + + size &= ~(0xf << 16); + } + + if (size) + len -= size; + + return len; +} +EXPORT_SYMBOL_GPL(omap_mmu_kmem_reserve); + +void omap_mmu_kmem_release(void) +{ + if (mempool_64K) { + mempool_destroy(mempool_64K); + mempool_64K = NULL; + } + + if (mempool_1M) { + mempool_destroy(mempool_1M); + mempool_1M = NULL; + } +} +EXPORT_SYMBOL_GPL(omap_mmu_kmem_release); + +static void omap_mmu_free_pages(unsigned long buf, unsigned int order) +{ + struct page *page, *ps, *pe; + + ps = virt_to_page(buf); + pe = virt_to_page(buf + (1 << (PAGE_SHIFT + order))); + + for (page = ps; page < pe; page++) + ClearPageReserved(page); + + if ((order == ORDER_64KB) && likely(mempool_64K)) + mempool_free((void *)buf, mempool_64K); + else if ((order == ORDER_1MB) && likely(mempool_1M)) + mempool_free((void *)buf, mempool_1M); + else + free_pages(buf, order); +} + +/* + * ARM MMU operations + */ +int exmap_set_armmmu(struct omap_mmu *mmu, unsigned long virt, + unsigned long phys, unsigned long size) +{ + long off; + unsigned long sz_left; + pmd_t *pmdp; + pte_t *ptep; + int prot_pmd, prot_pte; + + dev_dbg(mmu->dev, + "MMU %s: mapping in ARM MMU, v=0x%08lx, p=0x%08lx, sz=0x%lx\n", + mmu->name, virt, phys, size); + + prot_pmd = PMD_TYPE_TABLE | PMD_DOMAIN(DOMAIN_IO); + prot_pte = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY | L_PTE_WRITE; + + pmdp = pmd_offset(pgd_offset_k(virt), virt); + if (pmd_none(*pmdp)) { + ptep = pte_alloc_one_kernel(&init_mm, 0); + if (ptep == NULL) + return -ENOMEM; + /* note: two PMDs will be set */ + pmd_populate_kernel(&init_mm, pmdp, ptep); + } + + off = phys - virt; + for (sz_left = size; + sz_left >= PAGE_SIZE; + sz_left -= PAGE_SIZE, virt += PAGE_SIZE) { + ptep = pte_offset_kernel(pmdp, virt); + set_pte_ext(ptep, __pte((virt + off) | prot_pte), 0); + } + if (sz_left) + BUG(); + + return 0; +} +EXPORT_SYMBOL_GPL(exmap_set_armmmu); + +void exmap_clear_armmmu(struct omap_mmu *mmu, unsigned long virt, + unsigned long size) +{ + unsigned long sz_left; + pmd_t *pmdp; + pte_t *ptep; + + dev_dbg(mmu->dev, + "MMU %s: unmapping in ARM MMU, v=0x%08lx, sz=0x%lx\n", + mmu->name, virt, size); + + for (sz_left = size; + sz_left >= PAGE_SIZE; + sz_left -= PAGE_SIZE, virt += PAGE_SIZE) { + pmdp = pmd_offset(pgd_offset_k(virt), virt); + ptep = pte_offset_kernel(pmdp, virt); + pte_clear(&init_mm, virt, ptep); + } + if (sz_left) + BUG(); +} +EXPORT_SYMBOL_GPL(exmap_clear_armmmu); + +int exmap_valid(struct omap_mmu *mmu, void *vadr, size_t len) +{ + /* exmap_sem should be held before calling this function */ + struct exmap_tbl *ent; + +start: + omap_mmu_for_each_tlb_entry(mmu, ent) { + void *mapadr; + unsigned long mapsize; + + if (!ent->valid) + continue; + mapadr = (void *)ent->vadr; + mapsize = 1 << (ent->order + PAGE_SHIFT); + if ((vadr >= mapadr) && (vadr < mapadr + mapsize)) { + if (vadr + len <= mapadr + mapsize) { + /* this map covers whole address. */ + return 1; + } else { + /* + * this map covers partially. + * check rest portion. + */ + len -= mapadr + mapsize - vadr; + vadr = mapadr + mapsize; + goto start; + } + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(exmap_valid); + +/* + * omap_mmu_exmap_use(), unuse(): + * when the mapped area is exported to user space with mmap, + * the usecount is incremented. + * while the usecount > 0, that area can't be released. + */ +void omap_mmu_exmap_use(struct omap_mmu *mmu, void *vadr, size_t len) +{ + struct exmap_tbl *ent; + + down_write(&mmu->exmap_sem); + omap_mmu_for_each_tlb_entry(mmu, ent) { + void *mapadr; + unsigned long mapsize; + + if (!ent->valid) + continue; + mapadr = (void *)ent->vadr; + mapsize = 1 << (ent->order + PAGE_SHIFT); + if ((vadr + len > mapadr) && (vadr < mapadr + mapsize)) + ent->usecount++; + } + up_write(&mmu->exmap_sem); +} +EXPORT_SYMBOL_GPL(omap_mmu_exmap_use); + +void omap_mmu_exmap_unuse(struct omap_mmu *mmu, void *vadr, size_t len) +{ + struct exmap_tbl *ent; + + down_write(&mmu->exmap_sem); + omap_mmu_for_each_tlb_entry(mmu, ent) { + void *mapadr; + unsigned long mapsize; + + if (!ent->valid) + continue; + mapadr = (void *)ent->vadr; + mapsize = 1 << (ent->order + PAGE_SHIFT); + if ((vadr + len > mapadr) && (vadr < mapadr + mapsize)) + ent->usecount--; + } + up_write(&mmu->exmap_sem); +} +EXPORT_SYMBOL_GPL(omap_mmu_exmap_unuse); + +/* + * omap_mmu_virt_to_phys() + * returns physical address, and sets len to valid length + */ +unsigned long +omap_mmu_virt_to_phys(struct omap_mmu *mmu, void *vadr, size_t *len) +{ + struct exmap_tbl *ent; + + if (omap_mmu_internal_memory(mmu, vadr)) { + unsigned long addr = (unsigned long)vadr; + *len = mmu->membase + mmu->memsize - addr; + return addr; + } + + /* EXRAM */ + omap_mmu_for_each_tlb_entry(mmu, ent) { + void *mapadr; + unsigned long mapsize; + + if (!ent->valid) + continue; + mapadr = (void *)ent->vadr; + mapsize = 1 << (ent->order + PAGE_SHIFT); + if ((vadr >= mapadr) && (vadr < mapadr + mapsize)) { + *len = mapadr + mapsize - vadr; + return __pa(ent->buf) + vadr - mapadr; + } + } + + /* valid mapping not found */ + return 0; +} +EXPORT_SYMBOL_GPL(omap_mmu_virt_to_phys); + +/* + * PTE operations + */ +static inline void +omap_mmu_alloc_section(struct mm_struct *mm, unsigned long virt, + unsigned long phys, int prot) +{ + pmd_t *pmdp = pmd_offset(pgd_offset(mm, virt), virt); + if (virt & (1 << SECTION_SHIFT)) + pmdp++; + *pmdp = __pmd((phys & SECTION_MASK) | prot | PMD_TYPE_SECT); + flush_pmd_entry(pmdp); +} + +static inline void +omap_mmu_alloc_supersection(struct mm_struct *mm, unsigned long virt, + unsigned long phys, int prot) +{ + int i; + for (i = 0; i < 16; i += 1) { + omap_mmu_alloc_section(mm, virt, phys, prot | PMD_SECT_SUPER); + virt += (PGDIR_SIZE / 2); + } +} + +static inline int +omap_mmu_alloc_page(struct mm_struct *mm, unsigned long virt, + unsigned long phys, pgprot_t prot) +{ + pte_t *ptep; + pmd_t *pmdp = pmd_offset(pgd_offset(mm, virt), virt); + + if (!(prot & PTE_TYPE_MASK)) + prot |= PTE_TYPE_SMALL; + + if (pmd_none(*pmdp)) { + ptep = pte_alloc_one_kernel(mm, virt); + if (ptep == NULL) + return -ENOMEM; + pmd_populate_kernel(mm, pmdp, ptep); + } + ptep = pte_offset_kernel(pmdp, virt); + ptep -= PTRS_PER_PTE; + *ptep = pfn_pte(phys >> PAGE_SHIFT, prot); + flush_pmd_entry((pmd_t *)ptep); + return 0; +} + +static inline int +omap_mmu_alloc_largepage(struct mm_struct *mm, unsigned long virt, + unsigned long phys, pgprot_t prot) +{ + int i, ret; + for (i = 0; i < 16; i += 1) { + ret = omap_mmu_alloc_page(mm, virt, phys, + prot | PTE_TYPE_LARGE); + if (ret) + return -ENOMEM; /* only 1st time */ + virt += PAGE_SIZE; + } + return 0; +} + +static int omap_mmu_load_pte(struct omap_mmu *mmu, + struct omap_mmu_tlb_entry *e) +{ + int ret = 0; + struct mm_struct *mm = mmu->twl_mm; + const unsigned long va = e->va; + const unsigned long pa = e->pa; + const pgprot_t prot = mmu->ops->pte_get_attr(e); + + spin_lock(&mm->page_table_lock); + + switch (e->pgsz) { + case OMAP_MMU_CAM_PAGESIZE_16MB: + omap_mmu_alloc_supersection(mm, va, pa, prot); + break; + case OMAP_MMU_CAM_PAGESIZE_1MB: + omap_mmu_alloc_section(mm, va, pa, prot); + break; + case OMAP_MMU_CAM_PAGESIZE_64KB: + ret = omap_mmu_alloc_largepage(mm, va, pa, prot); + break; + case OMAP_MMU_CAM_PAGESIZE_4KB: + ret = omap_mmu_alloc_page(mm, va, pa, prot); + break; + default: + BUG(); + break; + } + + spin_unlock(&mm->page_table_lock); + + return ret; +} + +static void omap_mmu_clear_pte(struct omap_mmu *mmu, unsigned long virt) +{ + pte_t *ptep, *end; + pmd_t *pmdp; + struct mm_struct *mm = mmu->twl_mm; + + spin_lock(&mm->page_table_lock); + + pmdp = pmd_offset(pgd_offset(mm, virt), virt); + + if (pmd_none(*pmdp)) + goto out; + + if (!pmd_table(*pmdp)) + goto invalidate_pmd; + + ptep = pte_offset_kernel(pmdp, virt); + pte_clear(mm, virt, ptep); + flush_pmd_entry((pmd_t *)ptep); + + /* zap pte */ + end = pmd_page_vaddr(*pmdp); + ptep = end - PTRS_PER_PTE; + while (ptep < end) { + if (!pte_none(*ptep)) + goto out; + ptep++; + } + pte_free_kernel(mm, pmd_page_vaddr(*pmdp)); + + invalidate_pmd: + pmd_clear(pmdp); + flush_pmd_entry(pmdp); + out: + spin_unlock(&mm->page_table_lock); +} + +/* + * TLB operations + */ +static struct cam_ram_regset * +omap_mmu_cam_ram_alloc(struct omap_mmu *mmu, struct omap_mmu_tlb_entry *entry) +{ + return mmu->ops->cam_ram_alloc(mmu, entry); +} + +static int omap_mmu_cam_ram_valid(struct omap_mmu *mmu, + struct cam_ram_regset *cr) +{ + return mmu->ops->cam_ram_valid(cr); +} + +static inline void +omap_mmu_get_tlb_lock(struct omap_mmu *mmu, struct omap_mmu_tlb_lock *tlb_lock) +{ + unsigned long lock = omap_mmu_read_reg(mmu, OMAP_MMU_LOCK); + int mask; + + mask = (mmu->type == OMAP_MMU_CAMERA) ? + CAMERA_MMU_LOCK_BASE_MASK : MMU_LOCK_BASE_MASK; + tlb_lock->base = (lock & mask) >> MMU_LOCK_BASE_SHIFT; + + mask = (mmu->type == OMAP_MMU_CAMERA) ? + CAMERA_MMU_LOCK_VICTIM_MASK : MMU_LOCK_VICTIM_MASK; + tlb_lock->victim = (lock & mask) >> MMU_LOCK_VICTIM_SHIFT; +} + +static inline void +omap_mmu_set_tlb_lock(struct omap_mmu *mmu, struct omap_mmu_tlb_lock *lock) +{ + omap_mmu_write_reg(mmu, + (lock->base << MMU_LOCK_BASE_SHIFT) | + (lock->victim << MMU_LOCK_VICTIM_SHIFT), + OMAP_MMU_LOCK); +} + +static inline void omap_mmu_flush(struct omap_mmu *mmu) +{ + omap_mmu_write_reg(mmu, 0x1, OMAP_MMU_FLUSH_ENTRY); +} + +static inline void omap_mmu_ldtlb(struct omap_mmu *mmu) +{ + omap_mmu_write_reg(mmu, 0x1, OMAP_MMU_LD_TLB); +} + +void omap_mmu_read_tlb(struct omap_mmu *mmu, struct omap_mmu_tlb_lock *lock, + struct cam_ram_regset *cr) +{ + /* set victim */ + omap_mmu_set_tlb_lock(mmu, lock); + + if (likely(mmu->ops->read_tlb)) + mmu->ops->read_tlb(mmu, cr); +} +EXPORT_SYMBOL_GPL(omap_mmu_read_tlb); + +void omap_mmu_load_tlb(struct omap_mmu *mmu, struct cam_ram_regset *cr) +{ + if (likely(mmu->ops->load_tlb)) + mmu->ops->load_tlb(mmu, cr); + + /* flush the entry */ + omap_mmu_flush(mmu); + + /* load a TLB entry */ + omap_mmu_ldtlb(mmu); +} + +int omap_mmu_load_tlb_entry(struct omap_mmu *mmu, + struct omap_mmu_tlb_entry *entry) +{ + struct omap_mmu_tlb_lock lock; + struct cam_ram_regset *cr; + int ret; + + clk_enable(mmu->clk); + ret = omap_dsp_request_mem(); + if (ret < 0) + goto out; + + omap_mmu_get_tlb_lock(mmu, &lock); + for (lock.victim = 0; lock.victim < lock.base; lock.victim++) { + struct cam_ram_regset tmp; + + /* read a TLB entry */ + omap_mmu_read_tlb(mmu, &lock, &tmp); + if (!omap_mmu_cam_ram_valid(mmu, &tmp)) + goto found_victim; + } + omap_mmu_set_tlb_lock(mmu, &lock); + +found_victim: + /* The last entry cannot be locked? */ + if (lock.victim == (mmu->nr_tlb_entries - 1)) { + dev_err(mmu->dev, "MMU %s: TLB is full.\n", mmu->name); + return -EBUSY; + } + + cr = omap_mmu_cam_ram_alloc(mmu, entry); + if (IS_ERR(cr)) + return PTR_ERR(cr); + + omap_mmu_load_tlb(mmu, cr); + kfree(cr); + + /* update lock base */ + if (lock.victim == lock.base) + lock.base++; + + omap_mmu_set_tlb_lock(mmu, &lock); + + omap_dsp_release_mem(); +out: + clk_disable(mmu->clk); + return 0; +} +EXPORT_SYMBOL_GPL(omap_mmu_load_tlb_entry); + +static inline unsigned long +omap_mmu_cam_va(struct omap_mmu *mmu, struct cam_ram_regset *cr) +{ + return mmu->ops->cam_va(cr); +} + +int omap_mmu_clear_tlb_entry(struct omap_mmu *mmu, unsigned long vadr) +{ + struct omap_mmu_tlb_lock lock; + int i, ret = 0; + int max_valid = 0; + + clk_enable(mmu->clk); + ret = omap_dsp_request_mem(); + if (ret < 0) + goto out; + + omap_mmu_get_tlb_lock(mmu, &lock); + for (i = 0; i < lock.base; i++) { + struct cam_ram_regset cr; + + /* read a TLB entry */ + lock.victim = i; + omap_mmu_read_tlb(mmu, &lock, &cr); + if (!omap_mmu_cam_ram_valid(mmu, &cr)) + continue; + + if (omap_mmu_cam_va(mmu, &cr) == vadr) + /* flush the entry */ + omap_mmu_flush(mmu); + else + max_valid = i; + } + + /* set new lock base */ + lock.base = lock.victim = max_valid + 1; + omap_mmu_set_tlb_lock(mmu, &lock); + + omap_dsp_release_mem(); +out: + clk_disable(mmu->clk); + return ret; +} +EXPORT_SYMBOL_GPL(omap_mmu_clear_tlb_entry); + +static void omap_mmu_gflush(struct omap_mmu *mmu) +{ + struct omap_mmu_tlb_lock lock; + int ret; + + clk_enable(mmu->clk); + ret = omap_dsp_request_mem(); + if (ret < 0) + goto out; + + omap_mmu_write_reg(mmu, 0x1, OMAP_MMU_GFLUSH); + lock.base = lock.victim = mmu->nr_exmap_preserved; + omap_mmu_set_tlb_lock(mmu, &lock); + + omap_dsp_release_mem(); +out: + clk_disable(mmu->clk); +} + +int omap_mmu_load_pte_entry(struct omap_mmu *mmu, + struct omap_mmu_tlb_entry *entry) +{ + int ret = -1; + /*XXX use PG_flag for prsvd */ + ret = omap_mmu_load_pte(mmu, entry); + if (ret) + return ret; + if (entry->tlb) + ret = omap_mmu_load_tlb_entry(mmu, entry); + return ret; +} +EXPORT_SYMBOL_GPL(omap_mmu_load_pte_entry); + +int omap_mmu_clear_pte_entry(struct omap_mmu *mmu, unsigned long vadr) +{ + int ret = omap_mmu_clear_tlb_entry(mmu, vadr); + if (ret) + return ret; + omap_mmu_clear_pte(mmu, vadr); + return ret; +} +EXPORT_SYMBOL_GPL(omap_mmu_clear_pte_entry); + +/* + * omap_mmu_exmap() + * + * MEM_IOCTL_EXMAP ioctl calls this function with padr=0. + * In this case, the buffer for external device is allocated in this routine, + * then it is mapped. + * On the other hand, for example - frame buffer sharing, calls + * this function with padr set. It means some known address space + * pointed with padr is going to be shared with external device. + */ +int omap_mmu_exmap(struct omap_mmu *mmu, unsigned long devadr, + unsigned long padr, unsigned long size, + enum exmap_type type) +{ + unsigned long pgsz; + void *buf; + unsigned int order = 0; + unsigned long unit; + int prev = -1; + unsigned long _devadr = devadr; + unsigned long _padr = padr; + void *_vadr = omap_mmu_to_virt(mmu, devadr); + unsigned long _size = size; + struct omap_mmu_tlb_entry tlb_ent; + struct exmap_tbl *exmap_ent, *tmp_ent; + int status; + int idx; + +#define MINIMUM_PAGESZ SZ_4K + /* + * alignment check + */ + if (!is_aligned(size, MINIMUM_PAGESZ)) { + dev_err(mmu->dev, + "MMU %s: size(0x%lx) is not multiple of 4KB.\n", + mmu->name, size); + return -EINVAL; + } + if (!is_aligned(devadr, MINIMUM_PAGESZ)) { + dev_err(mmu->dev, + "MMU %s: external device address(0x%lx) is not" + " aligned.\n", mmu->name, devadr); + return -EINVAL; + } + if (!is_aligned(padr, MINIMUM_PAGESZ)) { + dev_err(mmu->dev, + "MMU %s: physical address(0x%lx) is not aligned.\n", + mmu->name, padr); + return -EINVAL; + } + + /* address validity check */ + if ((devadr < mmu->memsize) || + (devadr >= (1 << mmu->addrspace))) { + dev_err(mmu->dev, + "MMU %s: illegal address/size for %s().\n", + mmu->name, __func__); + return -EINVAL; + } + + down_write(&mmu->exmap_sem); + + /* overlap check */ + omap_mmu_for_each_tlb_entry(mmu, tmp_ent) { + unsigned long mapsize; + + if (!tmp_ent->valid) + continue; + mapsize = 1 << (tmp_ent->order + PAGE_SHIFT); + if ((_vadr + size > tmp_ent->vadr) && + (_vadr < tmp_ent->vadr + mapsize)) { + dev_err(mmu->dev, "MMU %s: exmap page overlap!\n", + mmu->name); + up_write(&mmu->exmap_sem); + return -EINVAL; + } + } + +start: + buf = NULL; + /* Are there any free TLB lines? */ + for (idx = 0; idx < mmu->nr_tlb_entries; idx++) + if (!mmu->exmap_tbl[idx].valid) + goto found_free; + + dev_err(mmu->dev, "MMU %s: TLB is full.\n", mmu->name); + status = -EBUSY; + goto fail; + +found_free: + exmap_ent = mmu->exmap_tbl + idx; + + if ((_size >= SZ_1M) && + (is_aligned(_padr, SZ_1M) || (padr == 0)) && + is_aligned(_devadr, SZ_1M)) { + unit = SZ_1M; + pgsz = OMAP_MMU_CAM_PAGESIZE_1MB; + } else if ((_size >= SZ_64K) && + (is_aligned(_padr, SZ_64K) || (padr == 0)) && + is_aligned(_devadr, SZ_64K)) { + unit = SZ_64K; + pgsz = OMAP_MMU_CAM_PAGESIZE_64KB; + } else { + unit = SZ_4K; + pgsz = OMAP_MMU_CAM_PAGESIZE_4KB; + } + + order = get_order(unit); + + /* buffer allocation */ + if (type == EXMAP_TYPE_MEM) { + struct page *page, *ps, *pe; + + if ((order == ORDER_1MB) && likely(mempool_1M)) + buf = mempool_alloc_from_pool(mempool_1M, GFP_KERNEL); + else if ((order == ORDER_64KB) && likely(mempool_64K)) + buf = mempool_alloc_from_pool(mempool_64K, GFP_KERNEL); + else { + buf = (void *)__get_dma_pages(GFP_KERNEL, order); + if (buf == NULL) { + status = -ENOMEM; + goto fail; + } + } + + /* mark the pages as reserved; this is needed for mmap */ + ps = virt_to_page(buf); + pe = virt_to_page(buf + unit); + + for (page = ps; page < pe; page++) + SetPageReserved(page); + + _padr = __pa(buf); + } + + /* + * mapping for ARM MMU: + * we should not access to the allocated memory through 'buf' + * since this area should not be cached. + */ + status = exmap_set_armmmu(mmu, (unsigned long)_vadr, _padr, unit); + if (status < 0) + goto fail; + + /* loading external device PTE entry */ + INIT_TLB_ENTRY(&tlb_ent, _devadr, _padr, pgsz); + status = omap_mmu_load_pte_entry(mmu, &tlb_ent); + if (status < 0) { + exmap_clear_armmmu(mmu, (unsigned long)_vadr, unit); + goto fail; + } + + INIT_EXMAP_TBL_ENTRY(exmap_ent, buf, _vadr, type, order); + exmap_ent->link.prev = prev; + if (prev >= 0) + mmu->exmap_tbl[prev].link.next = idx; + + if ((_size -= unit) == 0) { /* normal completion */ + up_write(&mmu->exmap_sem); + return size; + } + + _devadr += unit; + _vadr += unit; + _padr = padr ? _padr + unit : 0; + prev = idx; + goto start; + +fail: + up_write(&mmu->exmap_sem); + if (buf) + omap_mmu_free_pages((unsigned long)buf, order); + omap_mmu_exunmap(mmu, devadr); + return status; +} +EXPORT_SYMBOL_GPL(omap_mmu_exmap); + +static unsigned long unmap_free_arm(struct omap_mmu *mmu, + struct exmap_tbl *ent) +{ + unsigned long size; + + /* clearing ARM MMU */ + size = 1 << (ent->order + PAGE_SHIFT); + exmap_clear_armmmu(mmu, (unsigned long)ent->vadr, size); + + /* freeing allocated memory */ + if (ent->type == EXMAP_TYPE_MEM) { + omap_mmu_free_pages((unsigned long)ent->buf, ent->order); + dev_dbg(mmu->dev, "MMU %s: freeing 0x%lx bytes @ adr 0x%8p\n", + mmu->name, size, ent->buf); + } + + ent->valid = 0; + return size; +} + +int omap_mmu_exunmap(struct omap_mmu *mmu, unsigned long devadr) +{ + void *vadr; + unsigned long size; + int total = 0; + struct exmap_tbl *ent; + int idx; + + vadr = omap_mmu_to_virt(mmu, devadr); + down_write(&mmu->exmap_sem); + for (idx = 0; idx < mmu->nr_tlb_entries; idx++) { + ent = mmu->exmap_tbl + idx; + if (!ent->valid || ent->prsvd) + continue; + if (ent->vadr == vadr) + goto found_map; + } + up_write(&mmu->exmap_sem); + dev_warn(mmu->dev, "MMU %s: address %06lx not found in exmap_tbl.\n", + mmu->name, devadr); + return -EINVAL; + +found_map: + if (ent->usecount > 0) { + dev_err(mmu->dev, "MMU %s: exmap reference count is not 0.\n" + " idx=%d, vadr=%p, order=%d, usecount=%d\n", + mmu->name, idx, ent->vadr, ent->order, ent->usecount); + up_write(&mmu->exmap_sem); + return -EINVAL; + } + /* clearing external device PTE entry */ + omap_mmu_clear_pte_entry(mmu, devadr); + + /* clear ARM MMU and free buffer */ + size = unmap_free_arm(mmu, ent); + total += size; + + /* we don't free PTEs */ + + /* flush TLB */ + flush_tlb_kernel_range((unsigned long)vadr, (unsigned long)vadr + size); + + /* check if next mapping is in same group */ + idx = ent->link.next; + if (idx < 0) + goto up_out; /* normal completion */ + ent = mmu->exmap_tbl + idx; + devadr += size; + vadr += size; + if (ent->vadr == vadr) + goto found_map; /* continue */ + + dev_err(mmu->dev, "MMU %s: illegal exmap_tbl grouping!\n" + "expected vadr = %p, exmap_tbl[%d].vadr = %p\n", + mmu->name, vadr, idx, ent->vadr); + up_write(&mmu->exmap_sem); + return -EINVAL; + +up_out: + up_write(&mmu->exmap_sem); + return total; +} +EXPORT_SYMBOL_GPL(omap_mmu_exunmap); + +void omap_mmu_exmap_flush(struct omap_mmu *mmu) +{ + struct exmap_tbl *ent; + + down_write(&mmu->exmap_sem); + + /* clearing TLB entry */ + omap_mmu_gflush(mmu); + + omap_mmu_for_each_tlb_entry(mmu, ent) + if (ent->valid && !ent->prsvd) + unmap_free_arm(mmu, ent); + + /* flush TLB */ + if (likely(mmu->membase)) + flush_tlb_kernel_range(mmu->membase + mmu->memsize, + mmu->membase + (1 << mmu->addrspace)); + + up_write(&mmu->exmap_sem); +} +EXPORT_SYMBOL_GPL(omap_mmu_exmap_flush); + +void exmap_setup_preserved_mem_page(struct omap_mmu *mmu, void *buf, + unsigned long devadr, int index) +{ + unsigned long phys; + void *virt; + struct omap_mmu_tlb_entry tlb_ent; + + phys = __pa(buf); + virt = omap_mmu_to_virt(mmu, devadr); + exmap_set_armmmu(mmu, (unsigned long)virt, phys, PAGE_SIZE); + INIT_EXMAP_TBL_ENTRY_4KB_PRESERVED(mmu->exmap_tbl + index, buf, virt); + INIT_TLB_ENTRY_4KB_PRESERVED(&tlb_ent, devadr, phys); + omap_mmu_load_pte_entry(mmu, &tlb_ent); +} +EXPORT_SYMBOL_GPL(exmap_setup_preserved_mem_page); + +void exmap_clear_mem_page(struct omap_mmu *mmu, unsigned long devadr) +{ + void *virt = omap_mmu_to_virt(mmu, devadr); + + exmap_clear_armmmu(mmu, (unsigned long)virt, PAGE_SIZE); + /* DSP MMU is shutting down. not handled here. */ +} +EXPORT_SYMBOL_GPL(exmap_clear_mem_page); + +static void omap_mmu_reset(struct omap_mmu *mmu) +{ +#if defined(CONFIG_ARCH_OMAP2) /* FIXME */ + int i; + + omap_mmu_write_reg(mmu, 0x2, OMAP_MMU_SYSCONFIG); + + for (i = 0; i < 10000; i++) + if (likely(omap_mmu_read_reg(mmu, OMAP_MMU_SYSSTATUS) & 0x1)) + break; +#endif +} + +void omap_mmu_disable(struct omap_mmu *mmu) +{ + omap_mmu_write_reg(mmu, 0x00, OMAP_MMU_CNTL); +} +EXPORT_SYMBOL_GPL(omap_mmu_disable); + +void omap_mmu_enable(struct omap_mmu *mmu, int reset) +{ + u32 val = OMAP_MMU_CNTL_MMU_EN | MMU_CNTL_TWLENABLE; + + if (likely(reset)) + omap_mmu_reset(mmu); +#if defined(CONFIG_ARCH_OMAP2) /* FIXME */ + omap_mmu_write_reg(mmu, (u32)virt_to_phys(mmu->twl_mm->pgd), + OMAP_MMU_TTB); +#else + omap_mmu_write_reg(mmu, (u32)virt_to_phys(mmu->twl_mm->pgd) & 0xffff, + OMAP_MMU_TTB_L); + omap_mmu_write_reg(mmu, (u32)virt_to_phys(mmu->twl_mm->pgd) >> 16, + OMAP_MMU_TTB_H); + val |= OMAP_MMU_CNTL_RESET_SW; +#endif + omap_mmu_write_reg(mmu, val, OMAP_MMU_CNTL); +} +EXPORT_SYMBOL_GPL(omap_mmu_enable); + +static irqreturn_t omap_mmu_interrupt(int irq, void *dev_id) +{ + struct omap_mmu *mmu = dev_id; + + if (likely(mmu->ops->interrupt)) + mmu->ops->interrupt(mmu); + + return IRQ_HANDLED; +} + +static int omap_mmu_init(struct omap_mmu *mmu) +{ + struct omap_mmu_tlb_lock tlb_lock; + int ret = 0; + + clk_enable(mmu->clk); + ret = omap_dsp_request_mem(); + if (ret < 0) + goto out; + + down_write(&mmu->exmap_sem); + + ret = request_irq(mmu->irq, omap_mmu_interrupt, IRQF_DISABLED, + mmu->name, mmu); + if (ret < 0) { + dev_err(mmu->dev, "MMU %s: failed to register MMU interrupt:" + " %d\n", mmu->name, ret); + goto fail; + } + + omap_mmu_disable(mmu); /* clear all */ + udelay(100); + omap_mmu_enable(mmu, 1); + + memset(&tlb_lock, 0, sizeof(struct omap_mmu_tlb_lock)); + omap_mmu_set_tlb_lock(mmu, &tlb_lock); + + if (unlikely(mmu->ops->startup)) + ret = mmu->ops->startup(mmu); +fail: + up_write(&mmu->exmap_sem); + omap_dsp_release_mem(); +out: + clk_disable(mmu->clk); + + return ret; +} + +static void omap_mmu_shutdown(struct omap_mmu *mmu) +{ + free_irq(mmu->irq, mmu); + + if (unlikely(mmu->ops->shutdown)) + mmu->ops->shutdown(mmu); + + omap_mmu_exmap_flush(mmu); + omap_mmu_disable(mmu); /* clear all */ +} + +/* + * omap_mmu_mem_enable() / disable() + */ +int omap_mmu_mem_enable(struct omap_mmu *mmu, void *addr) +{ + if (unlikely(mmu->ops->mem_enable)) + return mmu->ops->mem_enable(mmu, addr); + + down_read(&mmu->exmap_sem); + return 0; +} +EXPORT_SYMBOL_GPL(omap_mmu_mem_enable); + +void omap_mmu_mem_disable(struct omap_mmu *mmu, void *addr) +{ + if (unlikely(mmu->ops->mem_disable)) { + mmu->ops->mem_disable(mmu, addr); + return; + } + + up_read(&mmu->exmap_sem); +} +EXPORT_SYMBOL_GPL(omap_mmu_mem_disable); + +/* + * dsp_mem file operations + */ +static ssize_t intmem_read(struct omap_mmu *mmu, char *buf, size_t count, + loff_t *ppos) +{ + unsigned long p = *ppos; + void *vadr = omap_mmu_to_virt(mmu, p); + ssize_t size = mmu->memsize; + ssize_t read; + + if (p >= size) + return 0; + clk_enable(mmu->memclk); + read = count; + if (count > size - p) + read = size - p; + if (copy_to_user(buf, vadr, read)) { + read = -EFAULT; + goto out; + } + *ppos += read; +out: + clk_disable(mmu->memclk); + return read; +} + +static ssize_t exmem_read(struct omap_mmu *mmu, char *buf, size_t count, + loff_t *ppos) +{ + unsigned long p = *ppos; + void *vadr = omap_mmu_to_virt(mmu, p); + + if (!exmap_valid(mmu, vadr, count)) { + dev_err(mmu->dev, "MMU %s: external device address %08lx / " + "size %08x is not valid!\n", mmu->name, p, count); + return -EFAULT; + } + if (count > (1 << mmu->addrspace) - p) + count = (1 << mmu->addrspace) - p; + if (copy_to_user(buf, vadr, count)) + return -EFAULT; + *ppos += count; + + return count; +} + +static ssize_t omap_mmu_mem_read(struct kobject *kobj, + struct bin_attribute *attr, + char *buf, loff_t offset, size_t count) +{ + struct device *dev = to_dev(kobj); + struct omap_mmu *mmu = dev_get_drvdata(dev); + unsigned long p = (unsigned long)offset; + void *vadr = omap_mmu_to_virt(mmu, p); + int ret; + + if (omap_mmu_mem_enable(mmu, vadr) < 0) + return -EBUSY; + + if (p < mmu->memsize) + ret = intmem_read(mmu, buf, count, &offset); + else + ret = exmem_read(mmu, buf, count, &offset); + + omap_mmu_mem_disable(mmu, vadr); + + return ret; +} + +static ssize_t intmem_write(struct omap_mmu *mmu, const char *buf, size_t count, + loff_t *ppos) +{ + unsigned long p = *ppos; + void *vadr = omap_mmu_to_virt(mmu, p); + ssize_t size = mmu->memsize; + ssize_t written; + + if (p >= size) + return 0; + clk_enable(mmu->memclk); + written = count; + if (count > size - p) + written = size - p; + if (copy_from_user(vadr, buf, written)) { + written = -EFAULT; + goto out; + } + *ppos += written; +out: + clk_disable(mmu->memclk); + return written; +} + +static ssize_t exmem_write(struct omap_mmu *mmu, char *buf, size_t count, + loff_t *ppos) +{ + unsigned long p = *ppos; + void *vadr = omap_mmu_to_virt(mmu, p); + + if (!exmap_valid(mmu, vadr, count)) { + dev_err(mmu->dev, "MMU %s: external device address %08lx " + "/ size %08x is not valid!\n", mmu->name, p, count); + return -EFAULT; + } + if (count > (1 << mmu->addrspace) - p) + count = (1 << mmu->addrspace) - p; + if (copy_from_user(vadr, buf, count)) + return -EFAULT; + *ppos += count; + + return count; +} + +static ssize_t omap_mmu_mem_write(struct kobject *kobj, + struct bin_attribute *attr, + char *buf, loff_t offset, size_t count) +{ + struct device *dev = to_dev(kobj); + struct omap_mmu *mmu = dev_get_drvdata(dev); + unsigned long p = (unsigned long)offset; + void *vadr = omap_mmu_to_virt(mmu, p); + int ret; + + if (omap_mmu_mem_enable(mmu, vadr) < 0) + return -EBUSY; + + if (p < mmu->memsize) + ret = intmem_write(mmu, buf, count, &offset); + else + ret = exmem_write(mmu, buf, count, &offset); + + omap_mmu_mem_disable(mmu, vadr); + + return ret; +} + +static struct bin_attribute dev_attr_mem = { + .attr = { + .name = "mem", + .owner = THIS_MODULE, + .mode = S_IRUSR | S_IWUSR | S_IRGRP, + }, + + .read = omap_mmu_mem_read, + .write = omap_mmu_mem_write, +}; + +/* To be obsolete for backward compatibility */ +ssize_t __omap_mmu_mem_read(struct omap_mmu *mmu, + struct bin_attribute *attr, + char *buf, loff_t offset, size_t count) +{ + return omap_mmu_mem_read(&mmu->dev->kobj, attr, buf, offset, count); +} +EXPORT_SYMBOL_GPL(__omap_mmu_mem_read); + +ssize_t __omap_mmu_mem_write(struct omap_mmu *mmu, + struct bin_attribute *attr, + char *buf, loff_t offset, size_t count) +{ + return omap_mmu_mem_write(&mmu->dev->kobj, attr, buf, offset, count); +} +EXPORT_SYMBOL_GPL(__omap_mmu_mem_write); + +/* + * sysfs files + */ +static ssize_t omap_mmu_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct omap_mmu *mmu = dev_get_drvdata(dev); + struct omap_mmu_tlb_lock tlb_lock; + int ret; + + clk_enable(mmu->clk); + ret = omap_dsp_request_mem(); + if (ret < 0) + goto out; + + down_read(&mmu->exmap_sem); + + omap_mmu_get_tlb_lock(mmu, &tlb_lock); + + ret = -EIO; + if (likely(mmu->ops->show)) + ret = mmu->ops->show(mmu, buf, &tlb_lock); + + /* restore victim entry */ + omap_mmu_set_tlb_lock(mmu, &tlb_lock); + + up_read(&mmu->exmap_sem); + omap_dsp_release_mem(); +out: + clk_disable(mmu->clk); + + return ret; +} + +static DEVICE_ATTR(mmu, S_IRUGO, omap_mmu_show, NULL); + +static ssize_t exmap_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct omap_mmu *mmu = dev_get_drvdata(dev); + struct exmap_tbl *ent; + int len; + int i = 0; + + down_read(&mmu->exmap_sem); + len = sprintf(buf, " devadr size buf size uc\n"); + /* 0x300000 0x123000 0xc0171000 0x100000 0*/ + + omap_mmu_for_each_tlb_entry(mmu, ent) { + void *vadr; + unsigned long size; + enum exmap_type type; + int idx; + + /* find a top of link */ + if (!ent->valid || (ent->link.prev >= 0)) + continue; + + vadr = ent->vadr; + type = ent->type; + size = 0; + idx = i; + do { + ent = mmu->exmap_tbl + idx; + size += PAGE_SIZE << ent->order; + } while ((idx = ent->link.next) >= 0); + + len += sprintf(buf + len, "0x%06lx %#8lx", + virt_to_omap_mmu(mmu, vadr), size); + + if (type == EXMAP_TYPE_FB) { + len += sprintf(buf + len, " framebuf\n"); + } else { + len += sprintf(buf + len, "\n"); + idx = i; + do { + ent = mmu->exmap_tbl + idx; + len += sprintf(buf + len, + /* 0xc0171000 0x100000 0*/ + "%19s0x%8p %#8lx %2d\n", + "", ent->buf, + PAGE_SIZE << ent->order, + ent->usecount); + } while ((idx = ent->link.next) >= 0); + } + + i++; + } + + up_read(&mmu->exmap_sem); + return len; +} + +static ssize_t exmap_store(struct device *dev, struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct omap_mmu *mmu = dev_get_drvdata(dev); + unsigned long base = 0, len = 0; + int ret; + + sscanf(buf, "%lx %lx", &base, &len); + + if (!base) + return -EINVAL; + + if (len) { + /* Add the mapping */ + ret = omap_mmu_exmap(mmu, base, 0, len, EXMAP_TYPE_MEM); + if (ret < 0) + return ret; + } else { + /* Remove the mapping */ + ret = omap_mmu_exunmap(mmu, base); + if (ret < 0) + return ret; + } + + return count; +} + +static DEVICE_ATTR(exmap, S_IRUGO | S_IWUSR, exmap_show, exmap_store); + +static ssize_t mempool_show(struct class *class, char *buf) +{ + int min_nr_1M = 0, curr_nr_1M = 0; + int min_nr_64K = 0, curr_nr_64K = 0; + int total = 0; + + if (likely(mempool_1M)) { + min_nr_1M = mempool_1M->min_nr; + curr_nr_1M = mempool_1M->curr_nr; + total += min_nr_1M * SZ_1M; + } + if (likely(mempool_64K)) { + min_nr_64K = mempool_64K->min_nr; + curr_nr_64K = mempool_64K->curr_nr; + total += min_nr_64K * SZ_64K; + } + + return sprintf(buf, + "0x%x\n" + "1M buffer: %d (%d free)\n" + "64K buffer: %d (%d free)\n", + total, min_nr_1M, curr_nr_1M, min_nr_64K, curr_nr_64K); +} + + +static CLASS_ATTR(mempool, S_IRUGO, mempool_show, NULL); + +static struct class omap_mmu_class = { + .name = "mmu", +}; + +int omap_mmu_register(struct omap_mmu *mmu) +{ + int ret; + + mmu->dev = device_create(&omap_mmu_class, NULL, 0, "%s", mmu->name); + if (unlikely(IS_ERR(mmu->dev))) + return PTR_ERR(mmu->dev); + dev_set_drvdata(mmu->dev, mmu); + + mmu->exmap_tbl = kcalloc(mmu->nr_tlb_entries, sizeof(struct exmap_tbl), + GFP_KERNEL); + if (!mmu->exmap_tbl) + return -ENOMEM; + + mmu->twl_mm = mm_alloc(); + if (!mmu->twl_mm) { + ret = -ENOMEM; + goto err_mm_alloc; + } + + init_rwsem(&mmu->exmap_sem); + + ret = omap_mmu_init(mmu); + if (unlikely(ret)) + goto err_mmu_init; + + ret = device_create_file(mmu->dev, &dev_attr_mmu); + if (unlikely(ret)) + goto err_dev_create_mmu; + ret = device_create_file(mmu->dev, &dev_attr_exmap); + if (unlikely(ret)) + goto err_dev_create_exmap; + + if (likely(mmu->membase)) { + dev_attr_mem.size = mmu->memsize; + ret = device_create_bin_file(mmu->dev, + &dev_attr_mem); + if (unlikely(ret)) + goto err_bin_create_mem; + } + return 0; + +err_bin_create_mem: + device_remove_file(mmu->dev, &dev_attr_exmap); +err_dev_create_exmap: + device_remove_file(mmu->dev, &dev_attr_mmu); +err_dev_create_mmu: + omap_mmu_shutdown(mmu); +err_mmu_init: + kfree(mmu->twl_mm); + mmu->twl_mm = NULL; +err_mm_alloc: + kfree(mmu->exmap_tbl); + mmu->exmap_tbl = NULL; + device_unregister(mmu->dev); + return ret; +} +EXPORT_SYMBOL_GPL(omap_mmu_register); + +void omap_mmu_unregister(struct omap_mmu *mmu) +{ + omap_mmu_shutdown(mmu); + omap_mmu_kmem_release(); + + device_remove_file(mmu->dev, &dev_attr_mmu); + device_remove_file(mmu->dev, &dev_attr_exmap); + + if (likely(mmu->membase)) + device_remove_bin_file(mmu->dev, &dev_attr_mem); + + device_unregister(mmu->dev); + + kfree(mmu->exmap_tbl); + mmu->exmap_tbl = NULL; + + if (mmu->twl_mm) { + __mmdrop(mmu->twl_mm); + mmu->twl_mm = NULL; + } +} +EXPORT_SYMBOL_GPL(omap_mmu_unregister); + +static int __init omap_mmu_class_init(void) +{ + int ret = class_register(&omap_mmu_class); + if (!ret) + ret = class_create_file(&omap_mmu_class, &class_attr_mempool); + + return ret; +} + +static void __exit omap_mmu_class_exit(void) +{ + class_remove_file(&omap_mmu_class, &class_attr_mempool); + class_unregister(&omap_mmu_class); +} + +subsys_initcall(omap_mmu_class_init); +module_exit(omap_mmu_class_exit); + +MODULE_LICENSE("GPL"); diff --cc drivers/bluetooth/brf6150.c index ccb7f089d1b,00000000000..f47b22fd7d0 mode 100644,000000..100644 --- a/drivers/bluetooth/brf6150.c +++ b/drivers/bluetooth/brf6150.c @@@ -1,1041 -1,0 +1,1041 @@@ +/* + * linux/drivers/bluetooth/brf6150/brf6150.c + * + * Copyright (C) 2005 Nokia Corporation + * Written by Ville Tervo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + - #include - #include - #include - #include ++#include ++#include ++#include ++#include + +#include +#include +#include + +#include "brf6150.h" + +#if 0 +#define NBT_DBG(fmt, arg...) printk("%s: " fmt "" , __FUNCTION__ , ## arg) +#else +#define NBT_DBG(...) +#endif + +#if 0 +#define NBT_DBG_FW(fmt, arg...) printk("%s: " fmt "" , __FUNCTION__ , ## arg) +#else +#define NBT_DBG_FW(...) +#endif + +#if 0 +#define NBT_DBG_POWER(fmt, arg...) printk("%s: " fmt "" , __FUNCTION__ , ## arg) +#else +#define NBT_DBG_POWER(...) +#endif + +#if 0 +#define NBT_DBG_TRANSFER(fmt, arg...) printk("%s: " fmt "" , __FUNCTION__ , ## arg) +#else +#define NBT_DBG_TRANSFER(...) +#endif + +#if 0 +#define NBT_DBG_TRANSFER_NF(fmt, arg...) printk(fmt "" , ## arg) +#else +#define NBT_DBG_TRANSFER_NF(...) +#endif + +#define PM_TIMEOUT (2000) + +static void brf6150_device_release(struct device *dev); +static struct brf6150_info *exit_info; + +static struct platform_device brf6150_device = { + .name = BT_DEVICE, + .id = -1, + .num_resources = 0, + .dev = { + .release = brf6150_device_release, + } +}; + +static struct device_driver brf6150_driver = { + .name = BT_DRIVER, + .bus = &platform_bus_type, +}; + +static inline void brf6150_outb(struct brf6150_info *info, unsigned int offset, u8 val) +{ + outb(val, info->uart_base + (offset << 2)); +} + +static inline u8 brf6150_inb(struct brf6150_info *info, unsigned int offset) +{ + return inb(info->uart_base + (offset << 2)); +} + +static void brf6150_set_rts(struct brf6150_info *info, int active) +{ + u8 b; + + b = brf6150_inb(info, UART_MCR); + if (active) + b |= UART_MCR_RTS; + else + b &= ~UART_MCR_RTS; + brf6150_outb(info, UART_MCR, b); +} + +static void brf6150_wait_for_cts(struct brf6150_info *info, int active, + int timeout_ms) +{ + int okay; + unsigned long timeout; + + okay = 0; + timeout = jiffies + msecs_to_jiffies(timeout_ms); + for (;;) { + int state; + + state = brf6150_inb(info, UART_MSR) & UART_MSR_CTS; + if (active) { + if (state) + break; + } else { + if (!state) + break; + } + if (jiffies > timeout) + break; + } +} + +static inline void brf6150_set_auto_ctsrts(struct brf6150_info *info, int on) +{ + u8 lcr, b; + + lcr = brf6150_inb(info, UART_LCR); + brf6150_outb(info, UART_LCR, 0xbf); + b = brf6150_inb(info, UART_EFR); + if (on) + b |= UART_EFR_CTS | UART_EFR_RTS; + else + b &= ~(UART_EFR_CTS | UART_EFR_RTS); + brf6150_outb(info, UART_EFR, b); + brf6150_outb(info, UART_LCR, lcr); +} + +static inline void brf6150_enable_pm_rx(struct brf6150_info *info) +{ + if (info->pm_enabled) { + info->rx_pm_enabled = 1; + } +} + +static inline void brf6150_disable_pm_rx(struct brf6150_info *info) +{ + if (info->pm_enabled) { + info->rx_pm_enabled = 0; + } +} + +static void brf6150_enable_pm_tx(struct brf6150_info *info) +{ + if (info->pm_enabled) { + mod_timer(&info->pm_timer, jiffies + msecs_to_jiffies(PM_TIMEOUT)); + info->tx_pm_enabled = 1; + } +} + +static void brf6150_disable_pm_tx(struct brf6150_info *info) +{ + if (info->pm_enabled) { + info->tx_pm_enabled = 0; + omap_set_gpio_dataout(info->btinfo->bt_wakeup_gpio, 1); + } + if (omap_get_gpio_datain(info->btinfo->host_wakeup_gpio)) + tasklet_schedule(&info->tx_task); +} + +static void brf6150_pm_timer(unsigned long data) +{ + struct brf6150_info *info; + + info = (struct brf6150_info *)data; + if (info->tx_pm_enabled && info->rx_pm_enabled && !test_bit(HCI_INQUIRY, &info->hdev->flags)) + omap_set_gpio_dataout(info->btinfo->bt_wakeup_gpio, 0); + else + mod_timer(&info->pm_timer, jiffies + msecs_to_jiffies(PM_TIMEOUT)); +} + +static int brf6150_change_speed(struct brf6150_info *info, unsigned long speed) +{ + unsigned int divisor; + u8 lcr, mdr1; + + NBT_DBG("Setting speed %lu\n", speed); + + if (speed >= 460800) { + divisor = UART_CLOCK / 13 / speed; + mdr1 = 3; + } else { + divisor = UART_CLOCK / 16 / speed; + mdr1 = 0; + } + + brf6150_outb(info, UART_OMAP_MDR1, 7); /* Make sure UART mode is disabled */ + lcr = brf6150_inb(info, UART_LCR); + brf6150_outb(info, UART_LCR, UART_LCR_DLAB); /* Set DLAB */ + brf6150_outb(info, UART_DLL, divisor & 0xff); /* Set speed */ + brf6150_outb(info, UART_DLM, divisor >> 8); + brf6150_outb(info, UART_LCR, lcr); + brf6150_outb(info, UART_OMAP_MDR1, mdr1); /* Make sure UART mode is enabled */ + + return 0; +} + +/* Firmware handling */ +static int brf6150_open_firmware(struct brf6150_info *info) +{ + int err; + + info->fw_pos = 0; + err = request_firmware(&info->fw_entry, "brf6150fw.bin", &brf6150_device.dev); + + return err; +} + +static struct sk_buff *brf6150_read_fw_cmd(struct brf6150_info *info, int how) +{ + struct sk_buff *skb; + unsigned int cmd_len; + + if (info->fw_pos >= info->fw_entry->size) { + return NULL; + } + + cmd_len = info->fw_entry->data[info->fw_pos++]; + if (!cmd_len) + return NULL; + + if (info->fw_pos + cmd_len > info->fw_entry->size) { + printk(KERN_WARNING "Corrupted firmware image\n"); + return NULL; + } + + skb = bt_skb_alloc(cmd_len, how); + if (!skb) { + printk(KERN_WARNING "Cannot reserve memory for buffer\n"); + return NULL; + } + memcpy(skb_put(skb, cmd_len), &info->fw_entry->data[info->fw_pos], cmd_len); + + info->fw_pos += cmd_len; + + return skb; +} + +static int brf6150_close_firmware(struct brf6150_info *info) +{ + release_firmware(info->fw_entry); + return 0; +} + +static int brf6150_send_alive_packet(struct brf6150_info *info) +{ + struct sk_buff *skb; + + NBT_DBG("Sending alive packet\n"); + skb = brf6150_read_fw_cmd(info, GFP_ATOMIC); + if (!skb) { + printk(KERN_WARNING "Cannot read alive command"); + return -1; + } + + clk_enable(info->uart_ck); + skb_queue_tail(&info->txq, skb); + tasklet_schedule(&info->tx_task); + + NBT_DBG("Alive packet sent\n"); + return 0; +} + +static void brf6150_alive_packet(struct brf6150_info *info, struct sk_buff *skb) +{ + NBT_DBG("Received alive packet\n"); + if (skb->data[1] == 0xCC) { + complete(&info->init_completion); + } + + kfree_skb(skb); +} + +static int brf6150_send_negotiation(struct brf6150_info *info) +{ + struct sk_buff *skb; + NBT_DBG("Sending negotiation..\n"); + + brf6150_change_speed(info, INIT_SPEED); + + skb = brf6150_read_fw_cmd(info, GFP_KERNEL); + + if (!skb) { + printk(KERN_WARNING "Cannot read negoatiation message"); + return -1; + } + + clk_enable(info->uart_ck); + skb_queue_tail(&info->txq, skb); + tasklet_schedule(&info->tx_task); + + + NBT_DBG("Negotiation sent\n"); + return 0; +} + +static void brf6150_negotiation_packet(struct brf6150_info *info, + struct sk_buff *skb) +{ + if (skb->data[1] == 0x20) { + /* Change to operational settings */ + brf6150_set_rts(info, 0); + brf6150_wait_for_cts(info, 0, 100); + brf6150_change_speed(info, MAX_BAUD_RATE); + brf6150_set_rts(info, 1); + brf6150_wait_for_cts(info, 1, 100); + brf6150_set_auto_ctsrts(info, 1); + brf6150_send_alive_packet(info); + } else { + printk(KERN_WARNING "Could not negotiate brf6150 settings\n"); + } + kfree_skb(skb); +} + +static int brf6150_get_hdr_len(u8 pkt_type) +{ + long retval; + + switch (pkt_type) { + case H4_EVT_PKT: + retval = HCI_EVENT_HDR_SIZE; + break; + case H4_ACL_PKT: + retval = HCI_ACL_HDR_SIZE; + break; + case H4_SCO_PKT: + retval = HCI_SCO_HDR_SIZE; + break; + case H4_NEG_PKT: + retval = 9; + break; + case H4_ALIVE_PKT: + retval = 3; + break; + default: + printk(KERN_ERR "brf6150: Unknown H4 packet"); + retval = -1; + break; + } + + return retval; +} + +static unsigned int brf6150_get_data_len(struct brf6150_info *info, + struct sk_buff *skb) +{ + long retval = -1; + struct hci_event_hdr *evt_hdr; + struct hci_acl_hdr *acl_hdr; + struct hci_sco_hdr *sco_hdr; + + switch (bt_cb(skb)->pkt_type) { + case H4_EVT_PKT: + evt_hdr = (struct hci_event_hdr *)skb->data; + retval = evt_hdr->plen; + break; + case H4_ACL_PKT: + acl_hdr = (struct hci_acl_hdr *)skb->data; + retval = le16_to_cpu(acl_hdr->dlen); + break; + case H4_SCO_PKT: + sco_hdr = (struct hci_sco_hdr *)skb->data; + retval = sco_hdr->dlen; + break; + case H4_NEG_PKT: + retval = 0; + break; + case H4_ALIVE_PKT: + retval = 0; + break; + } + + return retval; +} + +static void brf6150_parse_fw_event(struct brf6150_info *info) +{ + struct hci_fw_event *ev; + + if (bt_cb(info->rx_skb)->pkt_type != H4_EVT_PKT) { + printk(KERN_WARNING "Got non event fw packet.\n"); + info->fw_error = 1; + return; + } + + ev = (struct hci_fw_event *)info->rx_skb->data; + if (ev->hev.evt != HCI_EV_CMD_COMPLETE) { + printk(KERN_WARNING "Got non cmd complete fw event\n"); + info->fw_error = 1; + return; + } + + if (ev->status != 0) { + printk(KERN_WARNING "Got error status from fw command\n"); + info->fw_error = 1; + return; + } + + complete(&info->fw_completion); +} + +static inline void brf6150_recv_frame(struct brf6150_info *info, + struct sk_buff *skb) +{ + if (unlikely(!test_bit(HCI_RUNNING, &info->hdev->flags))) { + NBT_DBG("fw_event\n"); + brf6150_parse_fw_event(info); + kfree_skb(skb); + } else { + hci_recv_frame(skb); + if (!(brf6150_inb(info, UART_LSR) & UART_LSR_DR)) + brf6150_enable_pm_rx(info); + NBT_DBG("Frame sent to upper layer\n"); + } + +} + +static inline void brf6150_rx(struct brf6150_info *info) +{ + u8 byte; + + NBT_DBG_TRANSFER("rx_tasklet woke up\ndata "); + + while (brf6150_inb(info, UART_LSR) & UART_LSR_DR) { + if (info->rx_skb == NULL) { + info->rx_skb = bt_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC); + if (!info->rx_skb) { + printk(KERN_WARNING "brf6150: Can't allocate memory for new packet\n"); + return; + } + info->rx_state = WAIT_FOR_PKT_TYPE; + info->rx_skb->dev = (void *)info->hdev; + brf6150_disable_pm_rx(info); + clk_enable(info->uart_ck); + } + + byte = brf6150_inb(info, UART_RX); + if (info->garbage_bytes) { + info->garbage_bytes--; + info->hdev->stat.err_rx++; + continue; + } + info->hdev->stat.byte_rx++; + NBT_DBG_TRANSFER_NF("0x%.2x ", byte); + switch (info->rx_state) { + case WAIT_FOR_PKT_TYPE: + bt_cb(info->rx_skb)->pkt_type = byte; + info->rx_count = brf6150_get_hdr_len(byte); + if (info->rx_count >= 0) { + info->rx_state = WAIT_FOR_HEADER; + } else { + info->hdev->stat.err_rx++; + kfree_skb(info->rx_skb); + info->rx_skb = NULL; + clk_disable(info->uart_ck); + } + break; + case WAIT_FOR_HEADER: + info->rx_count--; + *skb_put(info->rx_skb, 1) = byte; + if (info->rx_count == 0) { + info->rx_count = brf6150_get_data_len(info, info->rx_skb); + if (info->rx_count > skb_tailroom(info->rx_skb)) { + printk(KERN_WARNING "brf6150: Frame is %ld bytes too long.\n", + info->rx_count - skb_tailroom(info->rx_skb)); + info->rx_skb = NULL; + info->garbage_bytes = info->rx_count - skb_tailroom(info->rx_skb); + clk_disable(info->uart_ck); + break; + } + info->rx_state = WAIT_FOR_DATA; + if (bt_cb(info->rx_skb)->pkt_type == H4_NEG_PKT) { + brf6150_negotiation_packet(info, info->rx_skb); + info->rx_skb = NULL; + clk_disable(info->uart_ck); + return; + } + if (bt_cb(info->rx_skb)->pkt_type == H4_ALIVE_PKT) { + brf6150_alive_packet(info, info->rx_skb); + info->rx_skb = NULL; + clk_disable(info->uart_ck); + return; + } + } + break; + case WAIT_FOR_DATA: + info->rx_count--; + *skb_put(info->rx_skb, 1) = byte; + if (info->rx_count == 0) { + brf6150_recv_frame(info, info->rx_skb); + info->rx_skb = NULL; + clk_disable(info->uart_ck); + } + break; + default: + WARN_ON(1); + break; + } + } + + NBT_DBG_TRANSFER_NF("\n"); +} + +static void brf6150_tx_tasklet(unsigned long data) +{ + unsigned int sent = 0; + unsigned long flags; + struct sk_buff *skb; + struct brf6150_info *info = (struct brf6150_info *)data; + + NBT_DBG_TRANSFER("tx_tasklet woke up\n data "); + + skb = skb_dequeue(&info->txq); + if (!skb) { + /* No data in buffer */ + brf6150_enable_pm_tx(info); + return; + } + + /* Copy data to tx fifo */ + while (!(brf6150_inb(info, UART_OMAP_SSR) & UART_OMAP_SSR_TXFULL) && + (sent < skb->len)) { + NBT_DBG_TRANSFER_NF("0x%.2x ", skb->data[sent]); + brf6150_outb(info, UART_TX, skb->data[sent]); + sent++; + } + + info->hdev->stat.byte_tx += sent; + NBT_DBG_TRANSFER_NF("\n"); + if (skb->len == sent) { + kfree_skb(skb); + clk_disable(info->uart_ck); + } else { + skb_pull(skb, sent); + skb_queue_head(&info->txq, skb); + } + + spin_lock_irqsave(&info->lock, flags); + brf6150_outb(info, UART_IER, brf6150_inb(info, UART_IER) | UART_IER_THRI); + spin_unlock_irqrestore(&info->lock, flags); +} + +static irqreturn_t brf6150_interrupt(int irq, void *data) +{ + struct brf6150_info *info = (struct brf6150_info *)data; + u8 iir, msr; + int ret; + unsigned long flags; + + ret = IRQ_NONE; + + clk_enable(info->uart_ck); + iir = brf6150_inb(info, UART_IIR); + if (iir & UART_IIR_NO_INT) { + printk("Interrupt but no reason irq 0x%.2x\n", iir); + clk_disable(info->uart_ck); + return IRQ_HANDLED; + } + + NBT_DBG("In interrupt handler iir 0x%.2x\n", iir); + + iir &= UART_IIR_ID; + + if (iir == UART_IIR_MSI) { + msr = brf6150_inb(info, UART_MSR); + ret = IRQ_HANDLED; + } + if (iir == UART_IIR_RLSI) { + brf6150_inb(info, UART_RX); + brf6150_inb(info, UART_LSR); + ret = IRQ_HANDLED; + } + + if (iir == UART_IIR_RDI) { + brf6150_rx(info); + ret = IRQ_HANDLED; + } + + if (iir == UART_IIR_THRI) { + spin_lock_irqsave(&info->lock, flags); + brf6150_outb(info, UART_IER, brf6150_inb(info, UART_IER) & ~UART_IER_THRI); + spin_unlock_irqrestore(&info->lock, flags); + tasklet_schedule(&info->tx_task); + ret = IRQ_HANDLED; + } + + clk_disable(info->uart_ck); + return ret; +} + +static irqreturn_t brf6150_wakeup_interrupt(int irq, void *dev_inst) +{ + struct brf6150_info *info = dev_inst; + int should_wakeup; + unsigned long flags; + + spin_lock_irqsave(&info->lock, flags); + should_wakeup = omap_get_gpio_datain(info->btinfo->host_wakeup_gpio); + NBT_DBG_POWER("gpio interrupt %d\n", should_wakeup); + if (should_wakeup) { + clk_enable(info->uart_ck); + brf6150_set_auto_ctsrts(info, 1); + brf6150_rx(info); + tasklet_schedule(&info->tx_task); + } else { + brf6150_set_auto_ctsrts(info, 0); + brf6150_set_rts(info, 0); + clk_disable(info->uart_ck); + } + + spin_unlock_irqrestore(&info->lock, flags); + return IRQ_HANDLED; +} + +static int brf6150_init_uart(struct brf6150_info *info) +{ + int count = 0; + + /* Reset the UART */ + brf6150_outb(info, UART_OMAP_SYSC, UART_SYSC_OMAP_RESET); + while (!(brf6150_inb(info, UART_OMAP_SYSS) & UART_SYSS_RESETDONE)) { + if (count++ > 100) { + printk(KERN_ERR "brf6150: UART reset timeout\n"); + return -1; + } + udelay(1); + } + + /* Enable and setup FIFO */ + brf6150_outb(info, UART_LCR, UART_LCR_WLEN8); + brf6150_outb(info, UART_OMAP_MDR1, 0x00); /* Make sure UART mode is enabled */ + brf6150_outb(info, UART_OMAP_SCR, 0x00); + brf6150_outb(info, UART_EFR, brf6150_inb(info, UART_EFR) | UART_EFR_ECB); + brf6150_outb(info, UART_MCR, brf6150_inb(info, UART_MCR) | UART_MCR_TCRTLR); + brf6150_outb(info, UART_TI752_TLR, 0xff); + brf6150_outb(info, UART_TI752_TCR, 0x1f); + brf6150_outb(info, UART_FCR, UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT); + brf6150_outb(info, UART_IER, UART_IER_RDI); + + return 0; +} + +static int brf6150_reset(struct brf6150_info *info) +{ + omap_set_gpio_dataout(info->btinfo->bt_wakeup_gpio, 0); + omap_set_gpio_dataout(info->btinfo->reset_gpio, 0); + current->state = TASK_UNINTERRUPTIBLE; + schedule_timeout(msecs_to_jiffies(10)); + omap_set_gpio_dataout(info->btinfo->bt_wakeup_gpio, 1); + current->state = TASK_UNINTERRUPTIBLE; + schedule_timeout(msecs_to_jiffies(100)); + omap_set_gpio_dataout(info->btinfo->reset_gpio, 1); + current->state = TASK_UNINTERRUPTIBLE; + schedule_timeout(msecs_to_jiffies(100)); + + return 0; +} + +static int brf6150_send_firmware(struct brf6150_info *info) +{ + struct sk_buff *skb; + + init_completion(&info->fw_completion); + info->fw_error = 0; + + while ((skb = brf6150_read_fw_cmd(info, GFP_KERNEL)) != NULL) { + clk_enable(info->uart_ck); + skb_queue_tail(&info->txq, skb); + tasklet_schedule(&info->tx_task); + + if (!wait_for_completion_timeout(&info->fw_completion, HZ)) { + return -1; + } + + if (info->fw_error) { + return -1; + } + } + NBT_DBG_FW("Firmware sent\n"); + + return 0; + +} + +/* hci callback functions */ +static int brf6150_hci_flush(struct hci_dev *hdev) +{ + struct brf6150_info *info; + info = hdev->driver_data; + + skb_queue_purge(&info->txq); + + return 0; +} + +static int brf6150_hci_open(struct hci_dev *hdev) +{ + struct brf6150_info *info; + int err; + + info = hdev->driver_data; + + if (test_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + if (brf6150_open_firmware(info) < 0) { + printk("Cannot open firmware\n"); + return -1; + } + + info->rx_state = WAIT_FOR_PKT_TYPE; + info->rx_count = 0; + info->garbage_bytes = 0; + info->rx_skb = NULL; + info->pm_enabled = 0; + set_irq_type(OMAP_GPIO_IRQ(info->btinfo->host_wakeup_gpio), IRQ_TYPE_NONE); + init_completion(&info->fw_completion); + + clk_enable(info->uart_ck); + + brf6150_init_uart(info); + brf6150_set_auto_ctsrts(info, 0); + brf6150_set_rts(info, 0); + brf6150_reset(info); + brf6150_wait_for_cts(info, 1, 10); + brf6150_set_rts(info, 1); + if (brf6150_send_negotiation(info)) { + brf6150_close_firmware(info); + return -1; + } + + if (!wait_for_completion_interruptible_timeout(&info->init_completion, HZ)) { + brf6150_close_firmware(info); + clk_disable(info->uart_ck); + clear_bit(HCI_RUNNING, &hdev->flags); + return -1; + } + brf6150_set_auto_ctsrts(info, 1); + + err = brf6150_send_firmware(info); + brf6150_close_firmware(info); + if (err < 0) + printk(KERN_ERR "brf6150: Sending firmware failed. Bluetooth won't work properly\n"); + + set_irq_type(OMAP_GPIO_IRQ(info->btinfo->host_wakeup_gpio), IRQ_TYPE_EDGE_BOTH); + info->pm_enabled = 1; + set_bit(HCI_RUNNING, &hdev->flags); + return 0; +} + +static int brf6150_hci_close(struct hci_dev *hdev) +{ + struct brf6150_info *info = hdev->driver_data; + if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + brf6150_hci_flush(hdev); + clk_disable(info->uart_ck); + del_timer_sync(&info->pm_timer); + omap_set_gpio_dataout(info->btinfo->bt_wakeup_gpio, 0); + set_irq_type(OMAP_GPIO_IRQ(info->btinfo->host_wakeup_gpio), IRQ_TYPE_NONE); + + return 0; +} + +static void brf6150_hci_destruct(struct hci_dev *hdev) +{ +} + +static int brf6150_hci_send_frame(struct sk_buff *skb) +{ + struct brf6150_info *info; + struct hci_dev *hdev = (struct hci_dev *)skb->dev; + + if (!hdev) { + printk(KERN_WARNING "brf6150: Frame for unknown device\n"); + return -ENODEV; + } + + if (!test_bit(HCI_RUNNING, &hdev->flags)) { + printk(KERN_WARNING "brf6150: Frame for non-running device\n"); + return -EIO; + } + + info = hdev->driver_data; + + switch (bt_cb(skb)->pkt_type) { + case HCI_COMMAND_PKT: + hdev->stat.cmd_tx++; + break; + case HCI_ACLDATA_PKT: + hdev->stat.acl_tx++; + break; + case HCI_SCODATA_PKT: + hdev->stat.sco_tx++; + break; + }; + + /* Push frame type to skb */ + clk_enable(info->uart_ck); + *skb_push(skb, 1) = bt_cb(skb)->pkt_type; + skb_queue_tail(&info->txq, skb); + + brf6150_disable_pm_tx(info); + + return 0; +} + +static int brf6150_hci_ioctl(struct hci_dev *hdev, unsigned int cmd, unsigned long arg) +{ + return -ENOIOCTLCMD; +} + +static void brf6150_device_release(struct device *dev) +{ +} + +static int brf6150_register_hdev(struct brf6150_info *info) +{ + struct hci_dev *hdev; + + /* Initialize and register HCI device */ + + hdev = hci_alloc_dev(); + if (!hdev) { + printk(KERN_WARNING "brf6150: Can't allocate memory for device\n"); + return -ENOMEM; + } + info->hdev = hdev; + + hdev->type = HCI_UART; + hdev->driver_data = info; + + hdev->open = brf6150_hci_open; + hdev->close = brf6150_hci_close; + hdev->destruct = brf6150_hci_destruct; + hdev->flush = brf6150_hci_flush; + hdev->send = brf6150_hci_send_frame; + hdev->destruct = brf6150_hci_destruct; + hdev->ioctl = brf6150_hci_ioctl; + + hdev->owner = THIS_MODULE; + + if (hci_register_dev(hdev) < 0) { + printk(KERN_WARNING "brf6150: Can't register HCI device %s.\n", hdev->name); + return -ENODEV; + } + + return 0; +} + +static int __init brf6150_init(void) +{ + struct brf6150_info *info; + int irq, err; + + info = kmalloc(sizeof(struct brf6150_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + memset(info, 0, sizeof(struct brf6150_info)); + + brf6150_device.dev.driver_data = info; + init_completion(&info->init_completion); + init_completion(&info->fw_completion); + info->pm_enabled = 0; + info->rx_pm_enabled = 0; + info->tx_pm_enabled = 0; + info->garbage_bytes = 0; + tasklet_init(&info->tx_task, brf6150_tx_tasklet, (unsigned long)info); + spin_lock_init(&info->lock); + skb_queue_head_init(&info->txq); + init_timer(&info->pm_timer); + info->pm_timer.function = brf6150_pm_timer; + info->pm_timer.data = (unsigned long)info; + exit_info = NULL; + + info->btinfo = omap_get_config(OMAP_TAG_NOKIA_BT, struct omap_bluetooth_config); + if (info->btinfo == NULL) + return -1; + + NBT_DBG("RESET gpio: %d\n", info->btinfo->reset_gpio); + NBT_DBG("BTWU gpio: %d\n", info->btinfo->bt_wakeup_gpio); + NBT_DBG("HOSTWU gpio: %d\n", info->btinfo->host_wakeup_gpio); + NBT_DBG("Uart: %d\n", info->btinfo->bt_uart); + NBT_DBG("sysclk: %d\n", info->btinfo->bt_sysclk); + + err = omap_request_gpio(info->btinfo->reset_gpio); + if (err < 0) + { + printk(KERN_WARNING "Cannot get GPIO line %d", + info->btinfo->reset_gpio); + kfree(info); + return err; + } + + err = omap_request_gpio(info->btinfo->bt_wakeup_gpio); + if (err < 0) + { + printk(KERN_WARNING "Cannot get GPIO line 0x%d", + info->btinfo->bt_wakeup_gpio); + omap_free_gpio(info->btinfo->reset_gpio); + kfree(info); + return err; + } + + err = omap_request_gpio(info->btinfo->host_wakeup_gpio); + if (err < 0) + { + printk(KERN_WARNING "Cannot get GPIO line %d", + info->btinfo->host_wakeup_gpio); + omap_free_gpio(info->btinfo->reset_gpio); + omap_free_gpio(info->btinfo->bt_wakeup_gpio); + kfree(info); + return err; + } + + omap_set_gpio_direction(info->btinfo->reset_gpio, 0); + omap_set_gpio_direction(info->btinfo->bt_wakeup_gpio, 0); + omap_set_gpio_direction(info->btinfo->host_wakeup_gpio, 1); + set_irq_type(OMAP_GPIO_IRQ(info->btinfo->host_wakeup_gpio), IRQ_TYPE_NONE); + + switch (info->btinfo->bt_uart) { + case 1: + irq = INT_UART1; + info->uart_ck = clk_get(NULL, "uart1_ck"); + info->uart_base = io_p2v((unsigned long)OMAP_UART1_BASE); + break; + case 2: + irq = INT_UART2; + info->uart_ck = clk_get(NULL, "uart2_ck"); + info->uart_base = io_p2v((unsigned long)OMAP_UART2_BASE); + break; + case 3: + irq = INT_UART3; + info->uart_ck = clk_get(NULL, "uart3_ck"); + info->uart_base = io_p2v((unsigned long)OMAP_UART3_BASE); + break; + default: + printk(KERN_ERR "No uart defined\n"); + goto cleanup; + } + + info->irq = irq; + err = request_irq(irq, brf6150_interrupt, 0, "brf6150", (void *)info); + if (err < 0) { + printk(KERN_ERR "brf6150: unable to get IRQ %d\n", irq); + goto cleanup; + } + + err = request_irq(OMAP_GPIO_IRQ(info->btinfo->host_wakeup_gpio), + brf6150_wakeup_interrupt, 0, "brf6150_wkup", (void *)info); + if (err < 0) { + printk(KERN_ERR "brf6150: unable to get wakeup IRQ %d\n", + OMAP_GPIO_IRQ(info->btinfo->host_wakeup_gpio)); + free_irq(irq, (void *)info); + goto cleanup; + } + + /* Register with LDM */ + if (platform_device_register(&brf6150_device)) { + printk(KERN_ERR "failed to register brf6150 device\n"); + err = -ENODEV; + goto cleanup_irq; + } + /* Register the driver with LDM */ + if (driver_register(&brf6150_driver)) { + printk(KERN_WARNING "failed to register brf6150 driver\n"); + platform_device_unregister(&brf6150_device); + err = -ENODEV; + goto cleanup_irq; + } + + if (brf6150_register_hdev(info) < 0) { + printk(KERN_WARNING "failed to register brf6150 hci device\n"); + platform_device_unregister(&brf6150_device); + driver_unregister(&brf6150_driver); + goto cleanup_irq; + } + + exit_info = info; + return 0; + +cleanup_irq: + free_irq(irq, (void *)info); + free_irq(OMAP_GPIO_IRQ(info->btinfo->host_wakeup_gpio), (void *)info); +cleanup: + omap_free_gpio(info->btinfo->reset_gpio); + omap_free_gpio(info->btinfo->bt_wakeup_gpio); + omap_free_gpio(info->btinfo->host_wakeup_gpio); + kfree(info); + + return err; +} + +static void __exit brf6150_exit(void) +{ + brf6150_hci_close(exit_info->hdev); + hci_free_dev(exit_info->hdev); + omap_free_gpio(exit_info->btinfo->reset_gpio); + omap_free_gpio(exit_info->btinfo->bt_wakeup_gpio); + omap_free_gpio(exit_info->btinfo->host_wakeup_gpio); + free_irq(exit_info->irq, (void *)exit_info); + free_irq(OMAP_GPIO_IRQ(exit_info->btinfo->host_wakeup_gpio), (void *)exit_info); + kfree(exit_info); +} + +module_init(brf6150_init); +module_exit(brf6150_exit); + +MODULE_DESCRIPTION("brf6150 hci driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ville Tervo "); diff --cc drivers/bluetooth/brf6150.h index 334a72e5908,00000000000..e8b29880e9f mode 100644,000000..100644 --- a/drivers/bluetooth/brf6150.h +++ b/drivers/bluetooth/brf6150.h @@@ -1,91 -1,0 +1,91 @@@ +/* + * linux/drivers/bluetooth/brf6150/brf6150.h + * + * Copyright (C) 2005 Nokia Corporation + * Written by Ville Tervo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + - #include ++#include + +#ifndef __DRIVERS_BLUETOOTH_BRF6150_H +#define __DRIVERS_BLUETOOTH_BRF6150_H + +#define UART_SYSC_OMAP_RESET 0x02 +#define UART_SYSS_RESETDONE 0x01 +#define UART_OMAP_SCR_EMPTY_THR 0x08 +#define UART_OMAP_SCR_WAKEUP 0x10 +#define UART_OMAP_SSR_WAKEUP 0x02 +#define UART_OMAP_SSR_TXFULL 0x01 + +struct brf6150_info { + struct hci_dev *hdev; + spinlock_t lock; + + struct clk *uart_ck; + unsigned long uart_base; + unsigned int irq; + + struct sk_buff_head txq; + struct sk_buff *rx_skb; + const struct omap_bluetooth_config *btinfo; + const struct firmware *fw_entry; + int fw_pos; + int fw_error; + struct completion fw_completion; + struct completion init_completion; + struct tasklet_struct tx_task; + long rx_count; + unsigned long garbage_bytes; + unsigned long rx_state; + int pm_enabled; + int rx_pm_enabled; + int tx_pm_enabled; + struct timer_list pm_timer; +}; + +#define BT_DEVICE "nokia_btuart" +#define BT_DRIVER "nokia_btuart" + +#define MAX_BAUD_RATE 921600 +#define UART_CLOCK 48000000 +#define BT_INIT_DIVIDER 320 +#define BT_BAUDRATE_DIVIDER 384000000 +#define BT_SYSCLK_DIV 1000 +#define INIT_SPEED 120000 + +#define H4_TYPE_SIZE 1 + +/* H4+ packet types */ +#define H4_CMD_PKT 0x01 +#define H4_ACL_PKT 0x02 +#define H4_SCO_PKT 0x03 +#define H4_EVT_PKT 0x04 +#define H4_NEG_PKT 0x06 +#define H4_ALIVE_PKT 0x07 + +/* TX states */ +#define WAIT_FOR_PKT_TYPE 1 +#define WAIT_FOR_HEADER 2 +#define WAIT_FOR_DATA 3 + +struct hci_fw_event { + struct hci_event_hdr hev; + struct hci_ev_cmd_complete cmd; + __u8 status; +} __attribute__ ((packed)); + +#endif /* __DRIVERS_BLUETOOTH_BRF6150_H */ diff --cc drivers/cbus/cbus.c index eff38be5088,00000000000..289557c4a24 mode 100644,000000..100644 --- a/drivers/cbus/cbus.c +++ b/drivers/cbus/cbus.c @@@ -1,293 -1,0 +1,293 @@@ +/* + * drivers/cbus/cbus.c + * + * Support functions for CBUS serial protocol + * + * Copyright (C) 2004, 2005 Nokia Corporation + * + * Written by Juha Yrjölä , + * David Weinehall , and + * Mikko Ylinen + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include + - #include - #include ++#include ++#include + +#include + +#include "cbus.h" + +struct cbus_host *cbus_host = NULL; + +#ifdef CONFIG_ARCH_OMAP1 +/* We use our own MPUIO functions to get closer to 1MHz bus speed */ + +static inline void cbus_set_gpio_direction(u32 base, int mpuio, int is_input) +{ + u16 w; + + mpuio &= 0x0f; + w = __raw_readw(base + OMAP_MPUIO_IO_CNTL); + if (is_input) + w |= 1 << mpuio; + else + w &= ~(1 << mpuio); + __raw_writew(w, base + OMAP_MPUIO_IO_CNTL); + +} + +static inline void cbus_set_gpio_dataout(u32 base, int mpuio, int enable) +{ + u16 w; + + mpuio &= 0x0f; + w = __raw_readw(base + OMAP_MPUIO_OUTPUT); + if (enable) + w |= 1 << mpuio; + else + w &= ~(1 << mpuio); + __raw_writew(w, base + OMAP_MPUIO_OUTPUT); +} + +static inline int cbus_get_gpio_datain(u32 base, int mpuio) +{ + mpuio &= 0x0f; + + return (__raw_readw(base + OMAP_MPUIO_INPUT_LATCH) & (1 << mpuio)) != 0; +} + +static void cbus_send_bit(struct cbus_host *host, u32 base, int bit, + int set_to_input) +{ + cbus_set_gpio_dataout(base, host->dat_gpio, bit ? 1 : 0); + cbus_set_gpio_dataout(base, host->clk_gpio, 1); + + /* The data bit is read on the rising edge of CLK */ + if (set_to_input) + cbus_set_gpio_direction(base, host->dat_gpio, 1); + + cbus_set_gpio_dataout(base, host->clk_gpio, 0); +} + +static u8 cbus_receive_bit(struct cbus_host *host, u32 base) +{ + u8 ret; + + cbus_set_gpio_dataout(base, host->clk_gpio, 1); + ret = cbus_get_gpio_datain(base, host->dat_gpio); + cbus_set_gpio_dataout(base, host->clk_gpio, 0); + + return ret; +} + +#else + +#define cbus_set_gpio_direction(base, gpio, is_input) omap_set_gpio_direction(gpio, is_input) +#define cbus_set_gpio_dataout(base, gpio, enable) omap_set_gpio_dataout(gpio, enable) +#define cbus_get_gpio_datain(base, int, gpio) omap_get_gpio_datain(gpio) + +static void _cbus_send_bit(struct cbus_host *host, int bit, int set_to_input) +{ + omap_set_gpio_dataout(host->dat_gpio, bit ? 1 : 0); + omap_set_gpio_dataout(host->clk_gpio, 1); + + /* The data bit is read on the rising edge of CLK */ + if (set_to_input) + omap_set_gpio_direction(host->dat_gpio, 1); + + omap_set_gpio_dataout(host->clk_gpio, 0); +} + +static u8 _cbus_receive_bit(struct cbus_host *host) +{ + u8 ret; + + omap_set_gpio_dataout(host->clk_gpio, 1); + ret = omap_get_gpio_datain(host->dat_gpio); + omap_set_gpio_dataout(host->clk_gpio, 0); + + return ret; +} + +#define cbus_send_bit(host, base, bit, set_to_input) _cbus_send_bit(host, bit, set_to_input) +#define cbus_receive_bit(host, base) _cbus_receive_bit(host) + +#endif + +static int cbus_transfer(struct cbus_host *host, int dev, int reg, int data) +{ + int i; + int is_read = 0; + unsigned long flags; + u32 base; + +#ifdef CONFIG_ARCH_OMAP1 + base = (u32) io_p2v(OMAP_MPUIO_BASE); +#else + base = 0; +#endif + + if (data < 0) + is_read = 1; + + /* We don't want interrupts disturbing our transfer */ + spin_lock_irqsave(&host->lock, flags); + + /* Reset state and start of transfer, SEL stays down during transfer */ + cbus_set_gpio_dataout(base, host->sel_gpio, 0); + + /* Set the DAT pin to output */ + cbus_set_gpio_direction(base, host->dat_gpio, 0); + + /* Send the device address */ + for (i = 3; i > 0; i--) + cbus_send_bit(host, base, dev & (1 << (i - 1)), 0); + + /* Send the rw flag */ + cbus_send_bit(host, base, is_read, 0); + + /* Send the register address */ + for (i = 5; i > 0; i--) { + int set_to_input = 0; + + if (is_read && i == 1) + set_to_input = 1; + + cbus_send_bit(host, base, reg & (1 << (i - 1)), set_to_input); + } + + if (!is_read) { + for (i = 16; i > 0; i--) + cbus_send_bit(host, base, data & (1 << (i - 1)), 0); + } else { + cbus_set_gpio_dataout(base, host->clk_gpio, 1); + data = 0; + + for (i = 16; i > 0; i--) { + u8 bit = cbus_receive_bit(host, base); + + if (bit) + data |= 1 << (i - 1); + } + } + + /* Indicate end of transfer, SEL goes up until next transfer */ + cbus_set_gpio_dataout(base, host->sel_gpio, 1); + cbus_set_gpio_dataout(base, host->clk_gpio, 1); + cbus_set_gpio_dataout(base, host->clk_gpio, 0); + + spin_unlock_irqrestore(&host->lock, flags); + + return is_read ? data : 0; +} + +/* + * Read a given register from the device + */ +int cbus_read_reg(struct cbus_host *host, int dev, int reg) +{ + return cbus_host ? cbus_transfer(host, dev, reg, -1) : -ENODEV; +} + +/* + * Write to a given register of the device + */ +int cbus_write_reg(struct cbus_host *host, int dev, int reg, u16 val) +{ + return cbus_host ? cbus_transfer(host, dev, reg, (int)val) : -ENODEV; +} + +int __init cbus_bus_init(void) +{ + const struct omap_cbus_config * cbus_config; + struct cbus_host *chost; + int ret; + + chost = kmalloc(sizeof (*chost), GFP_KERNEL); + if (chost == NULL) + return -ENOMEM; + + memset(chost, 0, sizeof (*chost)); + + spin_lock_init(&chost->lock); + + cbus_config = omap_get_config(OMAP_TAG_CBUS, struct omap_cbus_config); + + if (cbus_config == NULL) { + printk(KERN_ERR "cbus: Unable to retrieve config data\n"); + return -ENODATA; + } + + chost->clk_gpio = cbus_config->clk_gpio; + chost->dat_gpio = cbus_config->dat_gpio; + chost->sel_gpio = cbus_config->sel_gpio; + +#ifdef CONFIG_ARCH_OMAP1 + if (!OMAP_GPIO_IS_MPUIO(chost->clk_gpio) || + !OMAP_GPIO_IS_MPUIO(chost->dat_gpio) || + !OMAP_GPIO_IS_MPUIO(chost->sel_gpio)) { + printk(KERN_ERR "cbus: Only MPUIO pins supported\n"); + ret = -ENODEV; + goto exit1; + } +#endif + + if ((ret = omap_request_gpio(chost->clk_gpio)) < 0) + goto exit1; + + if ((ret = omap_request_gpio(chost->dat_gpio)) < 0) + goto exit2; + + if ((ret = omap_request_gpio(chost->sel_gpio)) < 0) + goto exit3; + + omap_set_gpio_dataout(chost->clk_gpio, 0); + omap_set_gpio_dataout(chost->sel_gpio, 1); + + omap_set_gpio_direction(chost->clk_gpio, 0); + omap_set_gpio_direction(chost->dat_gpio, 1); + omap_set_gpio_direction(chost->sel_gpio, 0); + + omap_set_gpio_dataout(chost->clk_gpio, 1); + omap_set_gpio_dataout(chost->clk_gpio, 0); + + cbus_host = chost; + + return 0; +exit3: + omap_free_gpio(chost->dat_gpio); +exit2: + omap_free_gpio(chost->clk_gpio); +exit1: + kfree(chost); + return ret; +} + +subsys_initcall(cbus_bus_init); + +EXPORT_SYMBOL(cbus_host); +EXPORT_SYMBOL(cbus_read_reg); +EXPORT_SYMBOL(cbus_write_reg); + +MODULE_DESCRIPTION("CBUS serial protocol"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Juha Yrjölä, David Weinehall, and Mikko Ylinen"); diff --cc drivers/cbus/retu.c index ceb93d03e2d,00000000000..d94110b8a0b mode 100644,000000..100644 --- a/drivers/cbus/retu.c +++ b/drivers/cbus/retu.c @@@ -1,466 -1,0 +1,466 @@@ +/** + * drivers/cbus/retu.c + * + * Support functions for Retu ASIC + * + * Copyright (C) 2004, 2005 Nokia Corporation + * + * Written by Juha Yrjölä , + * David Weinehall , and + * Mikko Ylinen + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + - #include - #include - #include ++#include ++#include ++#include + +#include "cbus.h" +#include "retu.h" + +#define RETU_ID 0x01 +#define PFX "retu: " + +static int retu_initialized; +static int retu_irq_pin; +static int retu_is_vilma; + +static struct tasklet_struct retu_tasklet; +spinlock_t retu_lock = SPIN_LOCK_UNLOCKED; + +static struct completion device_release; + +struct retu_irq_handler_desc { + int (*func)(unsigned long); + unsigned long arg; + char name[8]; +}; + +static struct retu_irq_handler_desc retu_irq_handlers[MAX_RETU_IRQ_HANDLERS]; + +/** + * retu_read_reg - Read a value from a register in Retu + * @reg: the register to read from + * + * This function returns the contents of the specified register + */ +int retu_read_reg(int reg) +{ + BUG_ON(!retu_initialized); + return cbus_read_reg(cbus_host, RETU_ID, reg); +} + +/** + * retu_write_reg - Write a value to a register in Retu + * @reg: the register to write to + * @reg: the value to write to the register + * + * This function writes a value to the specified register + */ +void retu_write_reg(int reg, u16 val) +{ + BUG_ON(!retu_initialized); + cbus_write_reg(cbus_host, RETU_ID, reg, val); +} + +void retu_set_clear_reg_bits(int reg, u16 set, u16 clear) +{ + unsigned long flags; + u16 w; + + spin_lock_irqsave(&retu_lock, flags); + w = retu_read_reg(reg); + w &= ~clear; + w |= set; + retu_write_reg(reg, w); + spin_unlock_irqrestore(&retu_lock, flags); +} + +#define ADC_MAX_CHAN_NUMBER 13 + +int retu_read_adc(int channel) +{ + unsigned long flags; + int res; + + if (channel < 0 || channel > ADC_MAX_CHAN_NUMBER) + return -EINVAL; + + spin_lock_irqsave(&retu_lock, flags); + + if ((channel == 8) && retu_is_vilma) { + int scr = retu_read_reg(RETU_REG_ADCSCR); + int ch = (retu_read_reg(RETU_REG_ADCR) >> 10) & 0xf; + if (((scr & 0xff) != 0) && (ch != 8)) + retu_write_reg (RETU_REG_ADCSCR, (scr & ~0xff)); + } + + /* Select the channel and read result */ + retu_write_reg(RETU_REG_ADCR, channel << 10); + res = retu_read_reg(RETU_REG_ADCR) & 0x3ff; + + if (retu_is_vilma) + retu_write_reg(RETU_REG_ADCR, (1 << 13)); + + /* Unlock retu */ + spin_unlock_irqrestore(&retu_lock, flags); + + return res; +} + + +static u16 retu_disable_bogus_irqs(u16 mask) +{ + int i; + + for (i = 0; i < MAX_RETU_IRQ_HANDLERS; i++) { + if (mask & (1 << i)) + continue; + if (retu_irq_handlers[i].func != NULL) + continue; + /* an IRQ was enabled but we don't have a handler for it */ + printk(KERN_INFO PFX "disabling bogus IRQ %d\n", i); + mask |= (1 << i); + } + return mask; +} + +/* + * Disable given RETU interrupt + */ +void retu_disable_irq(int id) +{ + unsigned long flags; + u16 mask; + + spin_lock_irqsave(&retu_lock, flags); + mask = retu_read_reg(RETU_REG_IMR); + mask |= 1 << id; + mask = retu_disable_bogus_irqs(mask); + retu_write_reg(RETU_REG_IMR, mask); + spin_unlock_irqrestore(&retu_lock, flags); +} + +/* + * Enable given RETU interrupt + */ +void retu_enable_irq(int id) +{ + unsigned long flags; + u16 mask; + + if (id == 3) { + printk("Enabling Retu IRQ %d\n", id); + dump_stack(); + } + spin_lock_irqsave(&retu_lock, flags); + mask = retu_read_reg(RETU_REG_IMR); + mask &= ~(1 << id); + mask = retu_disable_bogus_irqs(mask); + retu_write_reg(RETU_REG_IMR, mask); + spin_unlock_irqrestore(&retu_lock, flags); +} + +/* + * Acknowledge given RETU interrupt + */ +void retu_ack_irq(int id) +{ + retu_write_reg(RETU_REG_IDR, 1 << id); +} + +/* + * RETU interrupt handler. Only schedules the tasklet. + */ +static irqreturn_t retu_irq_handler(int irq, void *dev_id) +{ + tasklet_schedule(&retu_tasklet); + return IRQ_HANDLED; +} + +/* + * Tasklet handler + */ +static void retu_tasklet_handler(unsigned long data) +{ + struct retu_irq_handler_desc *hnd; + u16 id; + u16 im; + int i; + + for (;;) { + id = retu_read_reg(RETU_REG_IDR); + im = ~retu_read_reg(RETU_REG_IMR); + id &= im; + + if (!id) + break; + + for (i = 0; id != 0; i++, id >>= 1) { + if (!(id & 1)) + continue; + hnd = &retu_irq_handlers[i]; + if (hnd->func == NULL) { + /* Spurious retu interrupt - disable and ack it */ + printk(KERN_INFO "Spurious Retu interrupt " + "(id %d)\n", i); + retu_disable_irq(i); + retu_ack_irq(i); + continue; + } + hnd->func(hnd->arg); + /* + * Don't acknowledge the interrupt here + * It must be done explicitly + */ + } + } +} + +/* + * Register the handler for a given RETU interrupt source. + */ +int retu_request_irq(int id, void *irq_handler, unsigned long arg, char *name) +{ + struct retu_irq_handler_desc *hnd; + + if (irq_handler == NULL || id >= MAX_RETU_IRQ_HANDLERS || + name == NULL) { + printk(KERN_ERR PFX "Invalid arguments to %s\n", + __FUNCTION__); + return -EINVAL; + } + hnd = &retu_irq_handlers[id]; + if (hnd->func != NULL) { + printk(KERN_ERR PFX "IRQ %d already reserved\n", id); + return -EBUSY; + } + printk(KERN_INFO PFX "Registering interrupt %d for device %s\n", + id, name); + hnd->func = irq_handler; + hnd->arg = arg; + strlcpy(hnd->name, name, sizeof(hnd->name)); + + retu_ack_irq(id); + retu_enable_irq(id); + + return 0; +} + +/* + * Unregister the handler for a given RETU interrupt source. + */ +void retu_free_irq(int id) +{ + struct retu_irq_handler_desc *hnd; + + if (id >= MAX_RETU_IRQ_HANDLERS) { + printk(KERN_ERR PFX "Invalid argument to %s\n", + __FUNCTION__); + return; + } + hnd = &retu_irq_handlers[id]; + if (hnd->func == NULL) { + printk(KERN_ERR PFX "IRQ %d already freed\n", id); + return; + } + + retu_disable_irq(id); + hnd->func = NULL; +} + +/** + * retu_power_off - Shut down power to system + * + * This function puts the system in power off state + */ +static void retu_power_off(void) +{ + /* Ignore power button state */ + retu_write_reg(RETU_REG_CC1, retu_read_reg(RETU_REG_CC1) | 2); + /* Expire watchdog immediately */ + retu_write_reg(RETU_REG_WATCHDOG, 0); + /* Wait for poweroff*/ + for (;;); +} + +/** + * retu_probe - Probe for Retu ASIC + * @dev: the Retu device + * + * Probe for the Retu ASIC and allocate memory + * for its device-struct if found + */ +static int __devinit retu_probe(struct device *dev) +{ + const struct omap_em_asic_bb5_config * em_asic_config; + int rev, ret; + + /* Prepare tasklet */ + tasklet_init(&retu_tasklet, retu_tasklet_handler, 0); + + em_asic_config = omap_get_config(OMAP_TAG_EM_ASIC_BB5, + struct omap_em_asic_bb5_config); + if (em_asic_config == NULL) { + printk(KERN_ERR PFX "Unable to retrieve config data\n"); + return -ENODATA; + } + + retu_irq_pin = em_asic_config->retu_irq_gpio; + + if ((ret = omap_request_gpio(retu_irq_pin)) < 0) { + printk(KERN_ERR PFX "Unable to reserve IRQ GPIO\n"); + return ret; + } + + /* Set the pin as input */ + omap_set_gpio_direction(retu_irq_pin, 1); + + /* Rising edge triggers the IRQ */ + set_irq_type(OMAP_GPIO_IRQ(retu_irq_pin), IRQ_TYPE_EDGE_RISING); + + retu_initialized = 1; + + rev = retu_read_reg(RETU_REG_ASICR) & 0xff; + if (rev & (1 << 7)) + retu_is_vilma = 1; + + printk(KERN_INFO "%s v%d.%d found\n", retu_is_vilma ? "Vilma" : "Retu", + (rev >> 4) & 0x07, rev & 0x0f); + + /* Mask all RETU interrupts */ + retu_write_reg(RETU_REG_IMR, 0xffff); + + ret = request_irq(OMAP_GPIO_IRQ(retu_irq_pin), retu_irq_handler, 0, + "retu", 0); + if (ret < 0) { + printk(KERN_ERR PFX "Unable to register IRQ handler\n"); + omap_free_gpio(retu_irq_pin); + return ret; + } + set_irq_wake(OMAP_GPIO_IRQ(retu_irq_pin), 1); + + /* Register power off function */ + pm_power_off = retu_power_off; + +#ifdef CONFIG_CBUS_RETU_USER + /* Initialize user-space interface */ + if (retu_user_init() < 0) { + printk(KERN_ERR "Unable to initialize driver\n"); + free_irq(OMAP_GPIO_IRQ(retu_irq_pin), 0); + omap_free_gpio(retu_irq_pin); + return ret; + } +#endif + + return 0; +} + +static int retu_remove(struct device *dev) +{ +#ifdef CONFIG_CBUS_RETU_USER + retu_user_cleanup(); +#endif + /* Mask all RETU interrupts */ + retu_write_reg(RETU_REG_IMR, 0xffff); + free_irq(OMAP_GPIO_IRQ(retu_irq_pin), 0); + omap_free_gpio(retu_irq_pin); + tasklet_kill(&retu_tasklet); + + return 0; +} + +static void retu_device_release(struct device *dev) +{ + complete(&device_release); +} + +static struct device_driver retu_driver = { + .name = "retu", + .bus = &platform_bus_type, + .probe = retu_probe, + .remove = retu_remove, +}; + +static struct platform_device retu_device = { + .name = "retu", + .id = -1, + .dev = { + .release = retu_device_release, + } +}; + +/** + * retu_init - initialise Retu driver + * + * Initialise the Retu driver and return 0 if everything worked ok + */ +static int __init retu_init(void) +{ + int ret = 0; + + printk(KERN_INFO "Retu/Vilma driver initialising\n"); + + init_completion(&device_release); + + if ((ret = driver_register(&retu_driver)) < 0) + return ret; + + if ((ret = platform_device_register(&retu_device)) < 0) { + driver_unregister(&retu_driver); + return ret; + } + return 0; +} + +/* + * Cleanup + */ +static void __exit retu_exit(void) +{ + platform_device_unregister(&retu_device); + driver_unregister(&retu_driver); + wait_for_completion(&device_release); +} + +EXPORT_SYMBOL(retu_request_irq); +EXPORT_SYMBOL(retu_free_irq); +EXPORT_SYMBOL(retu_enable_irq); +EXPORT_SYMBOL(retu_disable_irq); +EXPORT_SYMBOL(retu_ack_irq); +EXPORT_SYMBOL(retu_read_reg); +EXPORT_SYMBOL(retu_write_reg); + +subsys_initcall(retu_init); +module_exit(retu_exit); + +MODULE_DESCRIPTION("Retu ASIC control"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Juha Yrjölä, David Weinehall, and Mikko Ylinen"); diff --cc drivers/cbus/tahvo-usb.c index 65ea14ffe6e,00000000000..d8ad836d501 mode 100644,000000..100644 --- a/drivers/cbus/tahvo-usb.c +++ b/drivers/cbus/tahvo-usb.c @@@ -1,777 -1,0 +1,777 @@@ +/** + * drivers/cbus/tahvo-usb.c + * + * Tahvo USB transeiver + * + * Copyright (C) 2005-2006 Nokia Corporation + * + * Parts copied from drivers/i2c/chips/isp1301_omap.c + * Copyright (C) 2004 Texas Instruments + * Copyright (C) 2004 David Brownell + * + * Written by Juha Yrjölä , + * Tony Lindgren , and + * Timo Teräs + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include - #include ++#include + +#include "cbus.h" +#include "tahvo.h" + +#define DRIVER_NAME "tahvo-usb" + +#define USBR_SLAVE_CONTROL (1 << 8) +#define USBR_VPPVIO_SW (1 << 7) +#define USBR_SPEED (1 << 6) +#define USBR_REGOUT (1 << 5) +#define USBR_MASTER_SW2 (1 << 4) +#define USBR_MASTER_SW1 (1 << 3) +#define USBR_SLAVE_SW (1 << 2) +#define USBR_NSUSPEND (1 << 1) +#define USBR_SEMODE (1 << 0) + +/* bits in OTG_CTRL */ + +/* Bits that are controlled by OMAP OTG and are read-only */ +#define OTG_CTRL_OMAP_MASK (OTG_PULLDOWN|OTG_PULLUP|OTG_DRV_VBUS|\ + OTG_PD_VBUS|OTG_PU_VBUS|OTG_PU_ID) +/* Bits that are controlled by transceiver */ +#define OTG_CTRL_XCVR_MASK (OTG_ASESSVLD|OTG_BSESSEND|\ + OTG_BSESSVLD|OTG_VBUSVLD|OTG_ID) +/* Bits that are controlled by system */ +#define OTG_CTRL_SYS_MASK (OTG_A_BUSREQ|OTG_A_SETB_HNPEN|OTG_B_BUSREQ|\ + OTG_B_HNPEN|OTG_BUSDROP) + +#if defined(CONFIG_USB_OHCI_HCD) && !defined(CONFIG_USB_OTG) +#error tahvo-otg.c does not work with OCHI yet! +#endif + +#define TAHVO_MODE_HOST 0 +#define TAHVO_MODE_PERIPHERAL 1 + +#ifdef CONFIG_USB_OTG +#define TAHVO_MODE(tu) (tu)->tahvo_mode +#elif defined(CONFIG_USB_GADGET_OMAP) +#define TAHVO_MODE(tu) TAHVO_MODE_PERIPHERAL +#else +#define TAHVO_MODE(tu) TAHVO_MODE_HOST +#endif + +struct tahvo_usb { + struct platform_device *pt_dev; + struct otg_transceiver otg; + int vbus_state; + struct work_struct irq_work; + struct mutex serialize; +#ifdef CONFIG_USB_OTG + int tahvo_mode; +#endif +}; +static struct platform_device tahvo_usb_device; + +/* + * --------------------------------------------------------------------------- + * OTG related functions + * + * These shoud be separated into omap-otg.c driver module, as they are used + * by various transceivers. These functions are needed in the UDC-only case + * as well. These functions are copied from GPL isp1301_omap.c + * --------------------------------------------------------------------------- + */ +static struct platform_device *tahvo_otg_dev; + +static irqreturn_t omap_otg_irq(int irq, void *arg) +{ + struct platform_device *otg_dev = (struct platform_device *) arg; + struct tahvo_usb *tu = (struct tahvo_usb *) otg_dev->dev.driver_data; + u16 otg_irq; + + otg_irq = omap_readw(OTG_IRQ_SRC); + if (otg_irq & OPRT_CHG) { + omap_writew(OPRT_CHG, OTG_IRQ_SRC); + } else if (otg_irq & B_SRP_TMROUT) { + omap_writew(B_SRP_TMROUT, OTG_IRQ_SRC); + } else if (otg_irq & B_HNP_FAIL) { + omap_writew(B_HNP_FAIL, OTG_IRQ_SRC); + } else if (otg_irq & A_SRP_DETECT) { + omap_writew(A_SRP_DETECT, OTG_IRQ_SRC); + } else if (otg_irq & A_REQ_TMROUT) { + omap_writew(A_REQ_TMROUT, OTG_IRQ_SRC); + } else if (otg_irq & A_VBUS_ERR) { + omap_writew(A_VBUS_ERR, OTG_IRQ_SRC); + } else if (otg_irq & DRIVER_SWITCH) { + if ((!(omap_readl(OTG_CTRL) & OTG_DRIVER_SEL)) && + tu->otg.host && tu->otg.state == OTG_STATE_A_HOST) { + /* role is host */ + usb_bus_start_enum(tu->otg.host, + tu->otg.host->otg_port); + } + omap_writew(DRIVER_SWITCH, OTG_IRQ_SRC); + } else + return IRQ_NONE; + + return IRQ_HANDLED; + +} + +static int omap_otg_init(void) +{ + u32 l; + +#ifdef CONFIG_USB_OTG + if (!tahvo_otg_dev) { + printk("tahvo-usb: no tahvo_otg_dev\n"); + return -ENODEV; + } +#endif + + l = omap_readl(OTG_SYSCON_1); + l &= ~OTG_IDLE_EN; + omap_writel(l, OTG_SYSCON_1); + udelay(100); + + /* some of these values are board-specific... */ + l = omap_readl(OTG_SYSCON_2); + l |= OTG_EN + /* for B-device: */ + | SRP_GPDATA /* 9msec Bdev D+ pulse */ + | SRP_GPDVBUS /* discharge after VBUS pulse */ + // | (3 << 24) /* 2msec VBUS pulse */ + /* for A-device: */ + | (0 << 20) /* 200ms nominal A_WAIT_VRISE timer */ + | SRP_DPW /* detect 167+ns SRP pulses */ + | SRP_DATA | SRP_VBUS; /* accept both kinds of SRP pulse */ + omap_writel(l, OTG_SYSCON_2); + + omap_writew(DRIVER_SWITCH | OPRT_CHG + | B_SRP_TMROUT | B_HNP_FAIL + | A_VBUS_ERR | A_SRP_DETECT | A_REQ_TMROUT, + OTG_IRQ_EN); + l = omap_readl(OTG_SYSCON_2); + l |= OTG_EN; + omap_writel(l, OTG_SYSCON_2); + + return 0; +} + +static int omap_otg_probe(struct device *dev) +{ + int ret; + + tahvo_otg_dev = to_platform_device(dev); + ret = omap_otg_init(); + if (ret != 0) { + printk(KERN_ERR "tahvo-usb: omap_otg_init failed\n"); + return ret; + } + + return request_irq(tahvo_otg_dev->resource[1].start, + omap_otg_irq, IRQF_DISABLED, DRIVER_NAME, + &tahvo_usb_device); +} + +static int omap_otg_remove(struct device *dev) +{ + free_irq(tahvo_otg_dev->resource[1].start, &tahvo_usb_device); + tahvo_otg_dev = NULL; + + return 0; +} + +struct device_driver omap_otg_driver = { + .name = "omap_otg", + .bus = &platform_bus_type, + .probe = omap_otg_probe, + .remove = omap_otg_remove, +}; + +/* + * --------------------------------------------------------------------------- + * Tahvo related functions + * These are Nokia proprietary code, except for the OTG register settings, + * which are copied from isp1301.c + * --------------------------------------------------------------------------- + */ +static ssize_t vbus_state_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct tahvo_usb *tu = (struct tahvo_usb*) device->driver_data; + return sprintf(buf, "%d\n", tu->vbus_state); +} +static DEVICE_ATTR(vbus_state, 0444, vbus_state_show, NULL); + +int vbus_active = 0; + +#if 0 + +static int host_suspend(struct tahvo_usb *tu) +{ + struct device *dev; + + if (!tu->otg.host) + return -ENODEV; + + /* Currently ASSUMES only the OTG port matters; + * other ports could be active... + */ + dev = tu->otg.host->controller; + return dev->driver->suspend(dev, PMSG_SUSPEND); +} + +static int host_resume(struct tahvo_usb *tu) +{ + struct device *dev; + + if (!tu->otg.host) + return -ENODEV; + + dev = tu->otg.host->controller; + return dev->driver->resume(dev); +} + +#else + +static int host_suspend(struct tahvo_usb *tu) +{ + return 0; +} + +static int host_resume(struct tahvo_usb *tu) +{ + return 0; +} + +#endif + +static void check_vbus_state(struct tahvo_usb *tu) +{ + int reg, prev_state; + + reg = tahvo_read_reg(TAHVO_REG_IDSR); + if (reg & 0x01) { + u32 l; + + vbus_active = 1; + switch (tu->otg.state) { + case OTG_STATE_B_IDLE: + /* Enable the gadget driver */ + if (tu->otg.gadget) + usb_gadget_vbus_connect(tu->otg.gadget); + /* Set B-session valid and not B-sessio ended to indicate + * Vbus to be ok. */ + l = omap_readl(OTG_CTRL); + l &= ~OTG_BSESSEND; + l |= OTG_BSESSVLD; + omap_writel(l, OTG_CTRL); + + tu->otg.state = OTG_STATE_B_PERIPHERAL; + break; + case OTG_STATE_A_IDLE: + /* Session is now valid assuming the USB hub is driving Vbus */ + tu->otg.state = OTG_STATE_A_HOST; + host_resume(tu); + break; + default: + break; + } + printk("USB cable connected\n"); + } else { + switch (tu->otg.state) { + case OTG_STATE_B_PERIPHERAL: + if (tu->otg.gadget) + usb_gadget_vbus_disconnect(tu->otg.gadget); + tu->otg.state = OTG_STATE_B_IDLE; + break; + case OTG_STATE_A_HOST: + tu->otg.state = OTG_STATE_A_IDLE; + break; + default: + break; + } + printk("USB cable disconnected\n"); + vbus_active = 0; + } + + prev_state = tu->vbus_state; + tu->vbus_state = reg & 0x01; + if (prev_state != tu->vbus_state) + sysfs_notify(&tu->pt_dev->dev.kobj, NULL, "vbus_state"); +} + +static void tahvo_usb_become_host(struct tahvo_usb *tu) +{ + u32 l; + + /* Clear system and transceiver controlled bits + * also mark the A-session is always valid */ + omap_otg_init(); + + l = omap_readl(OTG_CTRL); + l &= ~(OTG_CTRL_XCVR_MASK | OTG_CTRL_SYS_MASK); + l |= OTG_ASESSVLD; + omap_writel(l, OTG_CTRL); + + /* Power up the transceiver in USB host mode */ + tahvo_write_reg(TAHVO_REG_USBR, USBR_REGOUT | USBR_NSUSPEND | + USBR_MASTER_SW2 | USBR_MASTER_SW1); + tu->otg.state = OTG_STATE_A_IDLE; + + check_vbus_state(tu); +} + +static void tahvo_usb_stop_host(struct tahvo_usb *tu) +{ + host_suspend(tu); + tu->otg.state = OTG_STATE_A_IDLE; +} + +static void tahvo_usb_become_peripheral(struct tahvo_usb *tu) +{ + u32 l; + + /* Clear system and transceiver controlled bits + * and enable ID to mark peripheral mode and + * BSESSEND to mark no Vbus */ + omap_otg_init(); + l = omap_readl(OTG_CTRL); + l &= ~(OTG_CTRL_XCVR_MASK | OTG_CTRL_SYS_MASK | OTG_BSESSVLD); + l |= OTG_ID | OTG_BSESSEND; + omap_writel(l, OTG_CTRL); + + /* Power up transceiver and set it in USB perhiperal mode */ + tahvo_write_reg(TAHVO_REG_USBR, USBR_SLAVE_CONTROL | USBR_REGOUT | USBR_NSUSPEND | USBR_SLAVE_SW); + tu->otg.state = OTG_STATE_B_IDLE; + + check_vbus_state(tu); +} + +static void tahvo_usb_stop_peripheral(struct tahvo_usb *tu) +{ + u32 l; + + l = omap_readl(OTG_CTRL); + l &= ~OTG_BSESSVLD; + l |= OTG_BSESSEND; + omap_writel(l, OTG_CTRL); + + if (tu->otg.gadget) + usb_gadget_vbus_disconnect(tu->otg.gadget); + tu->otg.state = OTG_STATE_B_IDLE; + +} + +static void tahvo_usb_power_off(struct tahvo_usb *tu) +{ + u32 l; + int id; + + /* Disable gadget controller if any */ + if (tu->otg.gadget) + usb_gadget_vbus_disconnect(tu->otg.gadget); + + host_suspend(tu); + + /* Disable OTG and interrupts */ + if (TAHVO_MODE(tu) == TAHVO_MODE_PERIPHERAL) + id = OTG_ID; + else + id = 0; + l = omap_readl(OTG_CTRL); + l &= ~(OTG_CTRL_XCVR_MASK | OTG_CTRL_SYS_MASK | OTG_BSESSVLD); + l |= id | OTG_BSESSEND; + omap_writel(l, OTG_CTRL); + omap_writew(0, OTG_IRQ_EN); + + l = omap_readl(OTG_SYSCON_2); + l &= ~OTG_EN; + omap_writel(l, OTG_SYSCON_2); + + l = omap_readl(OTG_SYSCON_1); + l |= OTG_IDLE_EN; + omap_writel(l, OTG_SYSCON_1); + + /* Power off transceiver */ + tahvo_write_reg(TAHVO_REG_USBR, 0); + tu->otg.state = OTG_STATE_UNDEFINED; +} + + +static int tahvo_usb_set_power(struct otg_transceiver *dev, unsigned mA) +{ + struct tahvo_usb *tu = container_of(dev, struct tahvo_usb, otg); + + dev_dbg(&tu->pt_dev->dev, "set_power %d mA\n", mA); + + if (dev->state == OTG_STATE_B_PERIPHERAL) { + /* REVISIT: Can Tahvo charge battery from VBUS? */ + } + return 0; +} + +static int tahvo_usb_set_suspend(struct otg_transceiver *dev, int suspend) +{ + struct tahvo_usb *tu = container_of(dev, struct tahvo_usb, otg); + u16 w; + + dev_dbg(&tu->pt_dev->dev, "set_suspend\n"); + + w = tahvo_read_reg(TAHVO_REG_USBR); + if (suspend) + w &= ~USBR_NSUSPEND; + else + w |= USBR_NSUSPEND; + tahvo_write_reg(TAHVO_REG_USBR, w); + + return 0; +} + +static int tahvo_usb_start_srp(struct otg_transceiver *dev) +{ + struct tahvo_usb *tu = container_of(dev, struct tahvo_usb, otg); + u32 otg_ctrl; + + dev_dbg(&tu->pt_dev->dev, "start_srp\n"); + + if (!dev || tu->otg.state != OTG_STATE_B_IDLE) + return -ENODEV; + + otg_ctrl = omap_readl(OTG_CTRL); + if (!(otg_ctrl & OTG_BSESSEND)) + return -EINVAL; + + otg_ctrl |= OTG_B_BUSREQ; + otg_ctrl &= ~OTG_A_BUSREQ & OTG_CTRL_SYS_MASK; + omap_writel(otg_ctrl, OTG_CTRL); + tu->otg.state = OTG_STATE_B_SRP_INIT; + + return 0; +} + +static int tahvo_usb_start_hnp(struct otg_transceiver *otg) +{ + struct tahvo_usb *tu = container_of(otg, struct tahvo_usb, otg); + + dev_dbg(&tu->pt_dev->dev, "start_hnp\n"); +#ifdef CONFIG_USB_OTG + /* REVISIT: Add this for OTG */ +#endif + return -EINVAL; +} + +static int tahvo_usb_set_host(struct otg_transceiver *otg, struct usb_bus *host) +{ + struct tahvo_usb *tu = container_of(otg, struct tahvo_usb, otg); + u32 l; + + dev_dbg(&tu->pt_dev->dev, "set_host %p\n", host); + + if (otg == NULL) + return -ENODEV; + +#if defined(CONFIG_USB_OTG) || !defined(CONFIG_USB_GADGET_OMAP) + + mutex_lock(&tu->serialize); + + if (host == NULL) { + if (TAHVO_MODE(tu) == TAHVO_MODE_HOST) + tahvo_usb_power_off(tu); + tu->otg.host = NULL; + mutex_unlock(&tu->serialize); + return 0; + } + + l = omap_readl(OTG_SYSCON_1); + l &= ~(OTG_IDLE_EN | HST_IDLE_EN | DEV_IDLE_EN); + omap_writel(l, OTG_SYSCON_1); + + if (TAHVO_MODE(tu) == TAHVO_MODE_HOST) { + tu->otg.host = NULL; + tahvo_usb_become_host(tu); + } else + host_suspend(tu); + + tu->otg.host = host; + + mutex_unlock(&tu->serialize); +#else + /* No host mode configured, so do not allow host controlled to be set */ + return -EINVAL; +#endif + + return 0; +} + +static int tahvo_usb_set_peripheral(struct otg_transceiver *otg, struct usb_gadget *gadget) +{ + struct tahvo_usb *tu = container_of(otg, struct tahvo_usb, otg); + + dev_dbg(&tu->pt_dev->dev, "set_peripheral %p\n", gadget); + + if (!otg) + return -ENODEV; + +#if defined(CONFIG_USB_OTG) || defined(CONFIG_USB_GADGET_OMAP) + + mutex_lock(&tu->serialize); + + if (!gadget) { + if (TAHVO_MODE(tu) == TAHVO_MODE_PERIPHERAL) + tahvo_usb_power_off(tu); + tu->otg.gadget = NULL; + mutex_unlock(&tu->serialize); + return 0; + } + + tu->otg.gadget = gadget; + if (TAHVO_MODE(tu) == TAHVO_MODE_PERIPHERAL) + tahvo_usb_become_peripheral(tu); + + mutex_unlock(&tu->serialize); +#else + /* No gadget mode configured, so do not allow host controlled to be set */ + return -EINVAL; +#endif + + return 0; +} + +static void tahvo_usb_irq_work(struct work_struct *work) +{ + struct tahvo_usb *tu = container_of(work, struct tahvo_usb, irq_work); + + mutex_lock(&tu->serialize); + check_vbus_state(tu); + mutex_unlock(&tu->serialize); +} + +static void tahvo_usb_vbus_interrupt(unsigned long arg) +{ + struct tahvo_usb *tu = (struct tahvo_usb *) arg; + + tahvo_ack_irq(TAHVO_INT_VBUSON); + /* Seems we need this to acknowledge the interrupt */ + tahvo_read_reg(TAHVO_REG_IDSR); + schedule_work(&tu->irq_work); +} + +#ifdef CONFIG_USB_OTG +static ssize_t otg_mode_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct tahvo_usb *tu = (struct tahvo_usb*) device->driver_data; + switch (tu->tahvo_mode) { + case TAHVO_MODE_HOST: + return sprintf(buf, "host\n"); + case TAHVO_MODE_PERIPHERAL: + return sprintf(buf, "peripheral\n"); + } + return sprintf(buf, "unknown\n"); +} + +static ssize_t otg_mode_store(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct tahvo_usb *tu = (struct tahvo_usb*) device->driver_data; + int r; + + r = strlen(buf); + mutex_lock(&tu->serialize); + if (strncmp(buf, "host", 4) == 0) { + if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL) + tahvo_usb_stop_peripheral(tu); + tu->tahvo_mode = TAHVO_MODE_HOST; + if (tu->otg.host) { + printk(KERN_INFO "Selected HOST mode: host controller present.\n"); + tahvo_usb_become_host(tu); + } else { + printk(KERN_INFO "Selected HOST mode: no host controller, powering off.\n"); + tahvo_usb_power_off(tu); + } + } else if (strncmp(buf, "peripheral", 10) == 0) { + if (tu->tahvo_mode == TAHVO_MODE_HOST) + tahvo_usb_stop_host(tu); + tu->tahvo_mode = TAHVO_MODE_PERIPHERAL; + if (tu->otg.gadget) { + printk(KERN_INFO "Selected PERIPHERAL mode: gadget driver present.\n"); + tahvo_usb_become_peripheral(tu); + } else { + printk(KERN_INFO "Selected PERIPHERAL mode: no gadget driver, powering off.\n"); + tahvo_usb_power_off(tu); + } + } else + r = -EINVAL; + + mutex_unlock(&tu->serialize); + return r; +} + +static DEVICE_ATTR(otg_mode, 0644, otg_mode_show, otg_mode_store); +#endif + +static int tahvo_usb_probe(struct device *dev) +{ + struct tahvo_usb *tu; + int ret; + + dev_dbg(dev, "probe\n"); + + /* Create driver data */ + tu = kmalloc(sizeof(*tu), GFP_KERNEL); + if (!tu) + return -ENOMEM; + memset(tu, 0, sizeof(*tu)); + tu->pt_dev = container_of(dev, struct platform_device, dev); +#ifdef CONFIG_USB_OTG + /* Default mode */ +#ifdef CONFIG_CBUS_TAHVO_USB_HOST_BY_DEFAULT + tu->tahvo_mode = TAHVO_MODE_HOST; +#else + tu->tahvo_mode = TAHVO_MODE_PERIPHERAL; +#endif +#endif + + INIT_WORK(&tu->irq_work, tahvo_usb_irq_work); + mutex_init(&tu->serialize); + + /* Set initial state, so that we generate kevents only on + * state changes */ + tu->vbus_state = tahvo_read_reg(TAHVO_REG_IDSR) & 0x01; + + /* We cannot enable interrupt until omap_udc is initialized */ + ret = tahvo_request_irq(TAHVO_INT_VBUSON, tahvo_usb_vbus_interrupt, + (unsigned long) tu, "vbus_interrupt"); + if (ret != 0) { + kfree(tu); + printk(KERN_ERR "Could not register Tahvo interrupt for VBUS\n"); + return ret; + } + + /* Attributes */ + ret = device_create_file(dev, &dev_attr_vbus_state); +#ifdef CONFIG_USB_OTG + ret |= device_create_file(dev, &dev_attr_otg_mode); +#endif + if (ret) + printk(KERN_ERR "attribute creation failed: %d\n", ret); + + /* Create OTG interface */ + tahvo_usb_power_off(tu); + tu->otg.state = OTG_STATE_UNDEFINED; + tu->otg.label = DRIVER_NAME; + tu->otg.set_host = tahvo_usb_set_host; + tu->otg.set_peripheral = tahvo_usb_set_peripheral; + tu->otg.set_power = tahvo_usb_set_power; + tu->otg.set_suspend = tahvo_usb_set_suspend; + tu->otg.start_srp = tahvo_usb_start_srp; + tu->otg.start_hnp = tahvo_usb_start_hnp; + + ret = otg_set_transceiver(&tu->otg); + if (ret < 0) { + printk(KERN_ERR "Cannot register USB transceiver\n"); + kfree(tu); + tahvo_free_irq(TAHVO_INT_VBUSON); + return ret; + } + + dev->driver_data = tu; + + /* Act upon current vbus state once at startup. A vbus state irq may or + * may not be generated in addition to this. */ + schedule_work(&tu->irq_work); + return 0; +} + +static int tahvo_usb_remove(struct device *dev) +{ + dev_dbg(dev, "remove\n"); + + tahvo_free_irq(TAHVO_INT_VBUSON); + flush_scheduled_work(); + otg_set_transceiver(0); + device_remove_file(dev, &dev_attr_vbus_state); +#ifdef CONFIG_USB_OTG + device_remove_file(dev, &dev_attr_otg_mode); +#endif + return 0; +} + +static struct device_driver tahvo_usb_driver = { + .name = "tahvo-usb", + .bus = &platform_bus_type, + .probe = tahvo_usb_probe, + .remove = tahvo_usb_remove, +}; + +static struct platform_device tahvo_usb_device = { + .name = "tahvo-usb", + .id = -1, +}; + +static int __init tahvo_usb_init(void) +{ + int ret = 0; + + printk(KERN_INFO "Tahvo USB transceiver driver initializing\n"); + ret = driver_register(&tahvo_usb_driver); + if (ret) + return ret; + ret = platform_device_register(&tahvo_usb_device); + if (ret < 0) { + driver_unregister(&tahvo_usb_driver); + return ret; + } + ret = driver_register(&omap_otg_driver); + if (ret) { + platform_device_unregister(&tahvo_usb_device); + driver_unregister(&tahvo_usb_driver); + return ret; + } + return 0; +} + +subsys_initcall(tahvo_usb_init); + +static void __exit tahvo_usb_exit(void) +{ + driver_unregister(&omap_otg_driver); + platform_device_unregister(&tahvo_usb_device); + driver_unregister(&tahvo_usb_driver); +} +module_exit(tahvo_usb_exit); + +MODULE_DESCRIPTION("Tahvo USB OTG Transceiver Driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Juha Yrjölä, Tony Lindgren, and Timo Teräs"); diff --cc drivers/cbus/tahvo.c index bc7a4b6c69a,00000000000..5876b684662 mode 100644,000000..100644 --- a/drivers/cbus/tahvo.c +++ b/drivers/cbus/tahvo.c @@@ -1,441 -1,0 +1,441 @@@ +/** + * drivers/cbus/tahvo.c + * + * Support functions for Tahvo ASIC + * + * Copyright (C) 2004, 2005 Nokia Corporation + * + * Written by Juha Yrjölä , + * David Weinehall , and + * Mikko Ylinen + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + - #include - #include - #include ++#include ++#include ++#include + +#include "cbus.h" +#include "tahvo.h" + +#define TAHVO_ID 0x02 +#define PFX "tahvo: " + +static int tahvo_initialized; +static int tahvo_irq_pin; +static int tahvo_is_betty; + +static struct tasklet_struct tahvo_tasklet; +spinlock_t tahvo_lock = SPIN_LOCK_UNLOCKED; + +static struct completion device_release; + +struct tahvo_irq_handler_desc { + int (*func)(unsigned long); + unsigned long arg; + char name[8]; +}; + +static struct tahvo_irq_handler_desc tahvo_irq_handlers[MAX_TAHVO_IRQ_HANDLERS]; + +/** + * tahvo_read_reg - Read a value from a register in Tahvo + * @reg: the register to read from + * + * This function returns the contents of the specified register + */ +int tahvo_read_reg(int reg) +{ + BUG_ON(!tahvo_initialized); + return cbus_read_reg(cbus_host, TAHVO_ID, reg); +} + +/** + * tahvo_write_reg - Write a value to a register in Tahvo + * @reg: the register to write to + * @reg: the value to write to the register + * + * This function writes a value to the specified register + */ +void tahvo_write_reg(int reg, u16 val) +{ + BUG_ON(!tahvo_initialized); + cbus_write_reg(cbus_host, TAHVO_ID, reg, val); +} + +/** + * tahvo_set_clear_reg_bits - set and clear register bits atomically + * @reg: the register to write to + * @bits: the bits to set + * + * This function sets and clears the specified Tahvo register bits atomically + */ +void tahvo_set_clear_reg_bits(int reg, u16 set, u16 clear) +{ + unsigned long flags; + u16 w; + + spin_lock_irqsave(&tahvo_lock, flags); + w = tahvo_read_reg(reg); + w &= ~clear; + w |= set; + tahvo_write_reg(reg, w); + spin_unlock_irqrestore(&tahvo_lock, flags); +} + +/* + * Disable given TAHVO interrupt + */ +void tahvo_disable_irq(int id) +{ + unsigned long flags; + u16 mask; + + spin_lock_irqsave(&tahvo_lock, flags); + mask = tahvo_read_reg(TAHVO_REG_IMR); + mask |= 1 << id; + tahvo_write_reg(TAHVO_REG_IMR, mask); + spin_unlock_irqrestore(&tahvo_lock, flags); +} + +/* + * Enable given TAHVO interrupt + */ +void tahvo_enable_irq(int id) +{ + unsigned long flags; + u16 mask; + + spin_lock_irqsave(&tahvo_lock, flags); + mask = tahvo_read_reg(TAHVO_REG_IMR); + mask &= ~(1 << id); + tahvo_write_reg(TAHVO_REG_IMR, mask); + spin_unlock_irqrestore(&tahvo_lock, flags); +} + +/* + * Acknowledge given TAHVO interrupt + */ +void tahvo_ack_irq(int id) +{ + tahvo_write_reg(TAHVO_REG_IDR, 1 << id); +} + +static int tahvo_7bit_backlight; + +int tahvo_get_backlight_level(void) +{ + int mask; + + if (tahvo_7bit_backlight) + mask = 0x7f; + else + mask = 0x0f; + return tahvo_read_reg(TAHVO_REG_LEDPWMR) & mask; +} + +int tahvo_get_max_backlight_level(void) +{ + if (tahvo_7bit_backlight) + return 0x7f; + else + return 0x0f; +} + +void tahvo_set_backlight_level(int level) +{ + int max_level; + + max_level = tahvo_get_max_backlight_level(); + if (level > max_level) + level = max_level; + tahvo_write_reg(TAHVO_REG_LEDPWMR, level); +} + +/* + * TAHVO interrupt handler. Only schedules the tasklet. + */ +static irqreturn_t tahvo_irq_handler(int irq, void *dev_id) +{ + tasklet_schedule(&tahvo_tasklet); + return IRQ_HANDLED; +} + +/* + * Tasklet handler + */ +static void tahvo_tasklet_handler(unsigned long data) +{ + struct tahvo_irq_handler_desc *hnd; + u16 id; + u16 im; + int i; + + for (;;) { + id = tahvo_read_reg(TAHVO_REG_IDR); + im = ~tahvo_read_reg(TAHVO_REG_IMR); + id &= im; + + if (!id) + break; + + for (i = 0; id != 0; i++, id >>= 1) { + if (!(id & 1)) + continue; + hnd = &tahvo_irq_handlers[i]; + if (hnd->func == NULL) { + /* Spurious tahvo interrupt - just ack it */ + printk(KERN_INFO "Spurious Tahvo interrupt " + "(id %d)\n", i); + tahvo_disable_irq(i); + tahvo_ack_irq(i); + continue; + } + hnd->func(hnd->arg); + /* + * Don't acknowledge the interrupt here + * It must be done explicitly + */ + } + } +} + +/* + * Register the handler for a given TAHVO interrupt source. + */ +int tahvo_request_irq(int id, void *irq_handler, unsigned long arg, char *name) +{ + struct tahvo_irq_handler_desc *hnd; + + if (irq_handler == NULL || id >= MAX_TAHVO_IRQ_HANDLERS || + name == NULL) { + printk(KERN_ERR PFX "Invalid arguments to %s\n", + __FUNCTION__); + return -EINVAL; + } + hnd = &tahvo_irq_handlers[id]; + if (hnd->func != NULL) { + printk(KERN_ERR PFX "IRQ %d already reserved\n", id); + return -EBUSY; + } + printk(KERN_INFO PFX "Registering interrupt %d for device %s\n", + id, name); + hnd->func = irq_handler; + hnd->arg = arg; + strlcpy(hnd->name, name, sizeof(hnd->name)); + + tahvo_ack_irq(id); + tahvo_enable_irq(id); + + return 0; +} + +/* + * Unregister the handler for a given TAHVO interrupt source. + */ +void tahvo_free_irq(int id) +{ + struct tahvo_irq_handler_desc *hnd; + + if (id >= MAX_TAHVO_IRQ_HANDLERS) { + printk(KERN_ERR PFX "Invalid argument to %s\n", + __FUNCTION__); + return; + } + hnd = &tahvo_irq_handlers[id]; + if (hnd->func == NULL) { + printk(KERN_ERR PFX "IRQ %d already freed\n", id); + return; + } + + tahvo_disable_irq(id); + hnd->func = NULL; +} + +/** + * tahvo_probe - Probe for Tahvo ASIC + * @dev: the Tahvo device + * + * Probe for the Tahvo ASIC and allocate memory + * for its device-struct if found + */ +static int __devinit tahvo_probe(struct device *dev) +{ + const struct omap_em_asic_bb5_config * em_asic_config; + int rev, id, ret; + + /* Prepare tasklet */ + tasklet_init(&tahvo_tasklet, tahvo_tasklet_handler, 0); + + em_asic_config = omap_get_config(OMAP_TAG_EM_ASIC_BB5, + struct omap_em_asic_bb5_config); + if (em_asic_config == NULL) { + printk(KERN_ERR PFX "Unable to retrieve config data\n"); + return -ENODATA; + } + + tahvo_initialized = 1; + + rev = tahvo_read_reg(TAHVO_REG_ASICR); + + id = (rev >> 8) & 0xff; + if (id == 0x03) { + if ((rev & 0xff) >= 0x50) + tahvo_7bit_backlight = 1; + } else if (id == 0x0b) { + tahvo_is_betty = 1; + tahvo_7bit_backlight = 1; + } else { + printk(KERN_ERR "Tahvo/Betty chip not found"); + return -ENODEV; + } + + printk(KERN_INFO "%s v%d.%d found\n", tahvo_is_betty ? "Betty" : "Tahvo", + (rev >> 4) & 0x0f, rev & 0x0f); + + tahvo_irq_pin = em_asic_config->tahvo_irq_gpio; + + if ((ret = omap_request_gpio(tahvo_irq_pin)) < 0) { + printk(KERN_ERR PFX "Unable to reserve IRQ GPIO\n"); + return ret; + } + + /* Set the pin as input */ + omap_set_gpio_direction(tahvo_irq_pin, 1); + + /* Rising edge triggers the IRQ */ + set_irq_type(OMAP_GPIO_IRQ(tahvo_irq_pin), IRQ_TYPE_EDGE_RISING); + + /* Mask all TAHVO interrupts */ + tahvo_write_reg(TAHVO_REG_IMR, 0xffff); + + ret = request_irq(OMAP_GPIO_IRQ(tahvo_irq_pin), tahvo_irq_handler, 0, + "tahvo", 0); + if (ret < 0) { + printk(KERN_ERR PFX "Unable to register IRQ handler\n"); + omap_free_gpio(tahvo_irq_pin); + return ret; + } +#ifdef CONFIG_CBUS_TAHVO_USER + /* Initialize user-space interface */ + if (tahvo_user_init() < 0) { + printk(KERN_ERR "Unable to initialize driver\n"); + free_irq(OMAP_GPIO_IRQ(tahvo_irq_pin), 0); + omap_free_gpio(tahvo_irq_pin); + return ret; + } +#endif + return 0; +} + +static int tahvo_remove(struct device *dev) +{ +#ifdef CONFIG_CBUS_TAHVO_USER + tahvo_user_cleanup(); +#endif + /* Mask all TAHVO interrupts */ + tahvo_write_reg(TAHVO_REG_IMR, 0xffff); + free_irq(OMAP_GPIO_IRQ(tahvo_irq_pin), 0); + omap_free_gpio(tahvo_irq_pin); + tasklet_kill(&tahvo_tasklet); + + return 0; +} + +static void tahvo_device_release(struct device *dev) +{ + complete(&device_release); +} + +static struct device_driver tahvo_driver = { + .name = "tahvo", + .bus = &platform_bus_type, + .probe = tahvo_probe, + .remove = tahvo_remove, +}; + +static struct platform_device tahvo_device = { + .name = "tahvo", + .id = -1, + .dev = { + .release = tahvo_device_release, + } +}; + +/** + * tahvo_init - initialise Tahvo driver + * + * Initialise the Tahvo driver and return 0 if everything worked ok + */ +static int __init tahvo_init(void) +{ + int ret = 0; + + printk(KERN_INFO "Tahvo/Betty driver initialising\n"); + + init_completion(&device_release); + + if ((ret = driver_register(&tahvo_driver)) < 0) + return ret; + + if ((ret = platform_device_register(&tahvo_device)) < 0) { + driver_unregister(&tahvo_driver); + return ret; + } + return 0; +} + +/* + * Cleanup + */ +static void __exit tahvo_exit(void) +{ + platform_device_unregister(&tahvo_device); + driver_unregister(&tahvo_driver); + wait_for_completion(&device_release); +} + +EXPORT_SYMBOL(tahvo_request_irq); +EXPORT_SYMBOL(tahvo_free_irq); +EXPORT_SYMBOL(tahvo_enable_irq); +EXPORT_SYMBOL(tahvo_disable_irq); +EXPORT_SYMBOL(tahvo_ack_irq); +EXPORT_SYMBOL(tahvo_read_reg); +EXPORT_SYMBOL(tahvo_write_reg); +EXPORT_SYMBOL(tahvo_get_backlight_level); +EXPORT_SYMBOL(tahvo_get_max_backlight_level); +EXPORT_SYMBOL(tahvo_set_backlight_level); + +subsys_initcall(tahvo_init); +module_exit(tahvo_exit); + +MODULE_DESCRIPTION("Tahvo ASIC control"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Juha Yrjölä, David Weinehall, and Mikko Ylinen"); diff --cc drivers/dsp/dspgateway/dsp.h index 2a3dc244813,00000000000..84e7ceaa037 mode 100644,000000..100644 --- a/drivers/dsp/dspgateway/dsp.h +++ b/drivers/dsp/dspgateway/dsp.h @@@ -1,391 -1,0 +1,391 @@@ +/* + * This file is part of OMAP DSP driver (DSP Gateway version 3.3.1) + * + * Copyright (C) 2002-2006 Nokia Corporation. All rights reserved. + * + * Contact: Toshihiro Kobayashi + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __PLAT_OMAP_DSP_DSP_H +#define __PLAT_OMAP_DSP_DSP_H + +#include "hardware_dsp.h" - #include - #include ++#include ++#include + + +#ifdef CONFIG_ARCH_OMAP2 +#include "../../../arch/arm/mach-omap2/prm.h" +#include "../../../arch/arm/mach-omap2/prm-regbits-24xx.h" +#include "../../../arch/arm/mach-omap2/cm.h" +#include "../../../arch/arm/mach-omap2/cm-regbits-24xx.h" +#endif + +/* + * MAJOR device number: !! allocated arbitrary !! + */ +#define OMAP_DSP_CTL_MAJOR 96 +#define OMAP_DSP_TASK_MAJOR 97 + +#define OLD_BINARY_SUPPORT y + +#ifdef OLD_BINARY_SUPPORT +#define MBREV_3_0 0x0017 +#define MBREV_3_2 0x0018 +#endif + +#define DSP_INIT_PAGE 0xfff000 + +#ifdef CONFIG_ARCH_OMAP1 +/* idle program will be placed at IDLEPG_BASE. */ +#define IDLEPG_BASE 0xfffe00 +#define IDLEPG_SIZE 0x100 +#endif /* CONFIG_ARCH_OMAP1 */ + +/* timeout value for DSP response */ +#define DSP_TIMEOUT (10 * HZ) + +enum dsp_mem_type_e { + MEM_TYPE_CROSSING = -1, + MEM_TYPE_NONE = 0, + MEM_TYPE_DARAM, + MEM_TYPE_SARAM, + MEM_TYPE_EXTERN, +}; + + +typedef int __bitwise arm_dsp_dir_t; +#define DIR_A2D ((__force arm_dsp_dir_t) 1) +#define DIR_D2A ((__force arm_dsp_dir_t) 2) + +enum cfgstat_e { + CFGSTAT_CLEAN = 0, + CFGSTAT_READY, + CFGSTAT_SUSPEND, + CFGSTAT_RESUME, /* request only */ + CFGSTAT_MAX +}; + +enum errcode_e { + ERRCODE_WDT = 0, + ERRCODE_MMU, + ERRCODE_MAX +}; + +/* keep 2 entries for TID_FREE and TID_ANON */ +#define TASKDEV_MAX 254 + +#define MK32(uw,lw) (((u32)(uw)) << 16 | (lw)) +#define MKLONG(uw,lw) (((unsigned long)(uw)) << 16 | (lw)) +#define MKVIRT(uw,lw) dspword_to_virt(MKLONG((uw), (lw))); + +struct sync_seq { + u16 da_dsp; + u16 da_arm; + u16 ad_dsp; + u16 ad_arm; +}; + +struct mem_sync_struct { + struct sync_seq *DARAM; + struct sync_seq *SARAM; + struct sync_seq *SDRAM; +}; + +/* struct mbcmd and union mbcmd_hw must be compatible */ +struct mbcmd { + u32 data:16; + u32 cmd_l:8; + u32 cmd_h:7; + u32 seq:1; +}; + +#define MBCMD_INIT(h, l, d) { \ + .cmd_h = (h), \ + .cmd_l = (l), \ + .data = (d), \ + } + +struct mb_exarg { + u8 tid; + int argc; + u16 *argv; +}; + +typedef u32 dsp_long_t; /* must have ability to carry TADD_ABORTADR */ + +extern void dsp_mbox_start(void); +extern void dsp_mbox_stop(void); +extern int dsp_mbox_config(void *p); +extern int sync_with_dsp(u16 *syncwd, u16 tid, int try_cnt); +extern int __dsp_mbcmd_send_exarg(struct mbcmd *mb, struct mb_exarg *arg, + int recovery_flag); +#define dsp_mbcmd_send(mb) __dsp_mbcmd_send_exarg((mb), NULL, 0) +#define dsp_mbcmd_send_exarg(mb, arg) __dsp_mbcmd_send_exarg((mb), (arg), 0) +extern int dsp_mbcmd_send_and_wait_exarg(struct mbcmd *mb, struct mb_exarg *arg, + wait_queue_head_t *q); +#define dsp_mbcmd_send_and_wait(mb, q) \ + dsp_mbcmd_send_and_wait_exarg((mb), NULL, (q)) + +static inline int __mbcompose_send_exarg(u8 cmd_h, u8 cmd_l, u16 data, + struct mb_exarg *arg, + int recovery_flag) +{ + struct mbcmd mb = MBCMD_INIT(cmd_h, cmd_l, data); + return __dsp_mbcmd_send_exarg(&mb, arg, recovery_flag); +} +#define mbcompose_send(cmd_h, cmd_l, data) \ + __mbcompose_send_exarg(MBOX_CMD_DSP_##cmd_h, (cmd_l), (data), NULL, 0) +#define mbcompose_send_exarg(cmd_h, cmd_l, data, arg) \ + __mbcompose_send_exarg(MBOX_CMD_DSP_##cmd_h, (cmd_l), (data), arg, 0) +#define mbcompose_send_recovery(cmd_h, cmd_l, data) \ + __mbcompose_send_exarg(MBOX_CMD_DSP_##cmd_h, (cmd_l), (data), NULL, 1) + +static inline int __mbcompose_send_and_wait_exarg(u8 cmd_h, u8 cmd_l, + u16 data, + struct mb_exarg *arg, + wait_queue_head_t *q) +{ + struct mbcmd mb = MBCMD_INIT(cmd_h, cmd_l, data); + return dsp_mbcmd_send_and_wait_exarg(&mb, arg, q); +} +#define mbcompose_send_and_wait(cmd_h, cmd_l, data, q) \ + __mbcompose_send_and_wait_exarg(MBOX_CMD_DSP_##cmd_h, (cmd_l), (data), \ + NULL, (q)) +#define mbcompose_send_and_wait_exarg(cmd_h, cmd_l, data, arg, q) \ + __mbcompose_send_and_wait_exarg(MBOX_CMD_DSP_##cmd_h, (cmd_l), (data), \ + (arg), (q)) + +extern struct ipbuf_head *bid_to_ipbuf(u16 bid); +extern void ipbuf_start(void); +extern void ipbuf_stop(void); +extern int ipbuf_config(u16 ln, u16 lsz, void *base); +extern int ipbuf_sys_config(void *p, arm_dsp_dir_t dir); +extern int ipbuf_p_validate(void *p, arm_dsp_dir_t dir); +extern struct ipbuf_head *get_free_ipbuf(u8 tid); +extern void release_ipbuf(struct ipbuf_head *ipb_h); +extern void balance_ipbuf(void); +extern void unuse_ipbuf(struct ipbuf_head *ipb_h); +extern void unuse_ipbuf_nowait(struct ipbuf_head *ipb_h); + +#define release_ipbuf_pvt(ipbuf_pvt) \ + do { \ + (ipbuf_pvt)->s = TID_FREE; \ + } while(0) + +extern int mbox_revision; + +extern int dsp_cfgstat_request(enum cfgstat_e st); +extern enum cfgstat_e dsp_cfgstat_get_stat(void); +extern int dsp_set_runlevel(u8 level); + +extern int dsp_task_config_all(u8 n); +extern void dsp_task_unconfig_all(void); +extern u8 dsp_task_count(void); +extern int dsp_taskmod_busy(void); +extern int dsp_mkdev(char *name); +extern int dsp_rmdev(char *name); +extern int dsp_tadd_minor(unsigned char minor, dsp_long_t adr); +extern int dsp_tdel_minor(unsigned char minor); +extern int dsp_tkill_minor(unsigned char minor); +extern long taskdev_state_stale(unsigned char minor); +extern int dsp_dbg_config(u16 *buf, u16 sz, u16 lsz); +extern void dsp_dbg_stop(void); + +extern int ipbuf_is_held(u8 tid, u16 bid); + +extern int dsp_mem_sync_inc(void); +extern int dsp_mem_sync_config(struct mem_sync_struct *sync); +extern enum dsp_mem_type_e dsp_mem_type(void *vadr, size_t len); +extern int dsp_address_validate(void *p, size_t len, char *fmt, ...); +#ifdef CONFIG_ARCH_OMAP1 +extern void dsp_mem_usecount_clear(void); +#endif +extern void exmap_use(void *vadr, size_t len); +extern void exmap_unuse(void *vadr, size_t len); +extern unsigned long dsp_virt_to_phys(void *vadr, size_t *len); +extern void dsp_mem_start(void); +extern void dsp_mem_stop(void); + +extern void dsp_twch_start(void); +extern void dsp_twch_stop(void); +extern void dsp_twch_touch(void); + +extern void dsp_err_start(void); +extern void dsp_err_stop(void); +extern void dsp_err_set(enum errcode_e code, unsigned long arg); +extern void dsp_err_clear(enum errcode_e code); +extern int dsp_err_isset(enum errcode_e code); + +enum cmd_l_type_e { + CMD_L_TYPE_NULL, + CMD_L_TYPE_TID, + CMD_L_TYPE_SUBCMD, +}; + +struct cmdinfo { + char *name; + enum cmd_l_type_e cmd_l_type; + void (*handler)(struct mbcmd *mb); +}; + +extern const struct cmdinfo *cmdinfo[]; + +#define cmd_name(mb) (cmdinfo[(mb).cmd_h]->name) +extern char *subcmd_name(struct mbcmd *mb); + +extern void mblog_add(struct mbcmd *mb, arm_dsp_dir_t dir); + +extern struct omap_mmu dsp_mmu; + +#define dsp_mem_enable(addr) omap_mmu_mem_enable(&dsp_mmu, (addr)) +#define dsp_mem_disable(addr) omap_mmu_mem_disable(&dsp_mmu, (addr)) + +#define DSPSPACE_SIZE 0x1000000 + +#define omap_set_bit_regw(b,r) \ + do { omap_writew(omap_readw(r) | (b), (r)); } while(0) +#define omap_clr_bit_regw(b,r) \ + do { omap_writew(omap_readw(r) & ~(b), (r)); } while(0) +#define omap_set_bit_regl(b,r) \ + do { omap_writel(omap_readl(r) | (b), (r)); } while(0) +#define omap_clr_bit_regl(b,r) \ + do { omap_writel(omap_readl(r) & ~(b), (r)); } while(0) +#define omap_set_bits_regl(val,mask,r) \ + do { omap_writel((omap_readl(r) & ~(mask)) | (val), (r)); } while(0) + +#define dspword_to_virt(dw) ((void *)(dspmem_base + ((dw) << 1))) +#define dspbyte_to_virt(db) ((void *)(dspmem_base + (db))) +#define virt_to_dspword(va) \ + ((dsp_long_t)(((unsigned long)(va) - dspmem_base) >> 1)) +#define virt_to_dspbyte(va) \ + ((dsp_long_t)((unsigned long)(va) - dspmem_base)) +#define is_dsp_internal_mem(va) \ + (((unsigned long)(va) >= dspmem_base) && \ + ((unsigned long)(va) < dspmem_base + dspmem_size)) +#define is_dspbyte_internal_mem(db) ((db) < dspmem_size) +#define is_dspword_internal_mem(dw) (((dw) << 1) < dspmem_size) + +#ifdef CONFIG_ARCH_OMAP1 +/* + * MPUI byteswap/wordswap on/off + * default setting: wordswap = all, byteswap = APIMEM only + */ +#define mpui_wordswap_on() \ + omap_set_bits_regl(MPUI_CTRL_WORDSWAP_ALL, MPUI_CTRL_WORDSWAP_MASK, \ + MPUI_CTRL) + +#define mpui_wordswap_off() \ + omap_set_bits_regl(MPUI_CTRL_WORDSWAP_NONE, MPUI_CTRL_WORDSWAP_MASK, \ + MPUI_CTRL) + +#define mpui_byteswap_on() \ + omap_set_bits_regl(MPUI_CTRL_BYTESWAP_API, MPUI_CTRL_BYTESWAP_MASK, \ + MPUI_CTRL) + +#define mpui_byteswap_off() \ + omap_set_bits_regl(MPUI_CTRL_BYTESWAP_NONE, MPUI_CTRL_BYTESWAP_MASK, \ + MPUI_CTRL) + +/* + * TC wordswap on / off + */ +#define tc_wordswap() \ + do { \ + omap_writel(TC_ENDIANISM_SWAP_WORD | TC_ENDIANISM_EN, \ + TC_ENDIANISM); \ + } while(0) + +#define tc_noswap() omap_clr_bit_regl(TC_ENDIANISM_EN, TC_ENDIANISM) + +/* + * enable priority registers, EMIF, MPUI control logic + */ +#define __dsp_enable() omap_set_bit_regw(ARM_RSTCT1_DSP_RST, ARM_RSTCT1) +#define __dsp_disable() omap_clr_bit_regw(ARM_RSTCT1_DSP_RST, ARM_RSTCT1) +#define __dsp_run() omap_set_bit_regw(ARM_RSTCT1_DSP_EN, ARM_RSTCT1) +#define __dsp_reset() omap_clr_bit_regw(ARM_RSTCT1_DSP_EN, ARM_RSTCT1) +#endif /* CONFIG_ARCH_OMAP1 */ + +#ifdef CONFIG_ARCH_OMAP2 +/* + * PRCM / IPI control logic + * + * REVISIT: these macros should probably be static inline functions + */ +#define __dsp_core_enable() \ + do { prm_write_mod_reg(prm_read_mod_reg(OMAP24XX_DSP_MOD, RM_RSTCTRL) \ + & ~OMAP24XX_RST1_DSP, OMAP24XX_DSP_MOD, RM_RSTCTRL); } while (0) +#define __dsp_core_disable() \ + do { prm_write_mod_reg(prm_read_mod_reg(OMAP24XX_DSP_MOD, RM_RSTCTRL) \ + | OMAP24XX_RST1_DSP, OMAP24XX_DSP_MOD, RM_RSTCTRL); } while (0) +#define __dsp_per_enable() \ + do { prm_write_mod_reg(prm_read_mod_reg(OMAP24XX_DSP_MOD, RM_RSTCTRL) \ + & ~OMAP24XX_RST2_DSP, OMAP24XX_DSP_MOD, RM_RSTCTRL); } while (0) +#define __dsp_per_disable() \ + do { prm_write_mod_reg(prm_read_mod_reg(OMAP24XX_DSP_MOD, RM_RSTCTRL) \ + | OMAP24XX_RST2_DSP, OMAP24XX_DSP_MOD, RM_RSTCTRL); } while (0) +#endif /* CONFIG_ARCH_OMAP2 */ + +#if defined(CONFIG_ARCH_OMAP1) +extern struct clk *dsp_ck_handle; +extern struct clk *api_ck_handle; +#elif defined(CONFIG_ARCH_OMAP2) +extern struct clk *dsp_fck_handle; +extern struct clk *dsp_ick_handle; +#endif +extern dsp_long_t dspmem_base, dspmem_size, + daram_base, daram_size, + saram_base, saram_size; + +enum cpustat_e { + CPUSTAT_RESET = 0, +#ifdef CONFIG_ARCH_OMAP1 + CPUSTAT_GBL_IDLE, + CPUSTAT_CPU_IDLE, +#endif + CPUSTAT_RUN, + CPUSTAT_MAX +}; + +int dsp_set_rstvect(dsp_long_t adr); +dsp_long_t dsp_get_rstvect(void); +void dsp_set_idle_boot_base(dsp_long_t adr, size_t size); +void dsp_reset_idle_boot_base(void); +void dsp_cpustat_request(enum cpustat_e req); +enum cpustat_e dsp_cpustat_get_stat(void); +u16 dsp_cpustat_get_icrmask(void); +void dsp_cpustat_set_icrmask(u16 mask); +void dsp_register_mem_cb(int (*req_cb)(void), void (*rel_cb)(void)); +void dsp_unregister_mem_cb(void); + +#if defined(CONFIG_ARCH_OMAP1) +#define command_dvfs_stop(m) (0) +#define command_dvfs_start(m) (0) +#elif defined(CONFIG_ARCH_OMAP2) +#define command_dvfs_stop(m) \ + (((m)->cmd_l == KFUNC_POWER) && ((m)->data == DVFS_STOP)) +#define command_dvfs_start(m) \ + (((m)->cmd_l == KFUNC_POWER) && ((m)->data == DVFS_START)) +#endif + +extern struct omap_dsp *omap_dsp; + +extern int dsp_late_init(void); + +#endif /* __PLAT_OMAP_DSP_DSP_H */ diff --cc drivers/dsp/dspgateway/dsp_common.c index 7a4be53b563,00000000000..9dffda2252e mode 100644,000000..100644 --- a/drivers/dsp/dspgateway/dsp_common.c +++ b/drivers/dsp/dspgateway/dsp_common.c @@@ -1,639 -1,0 +1,639 @@@ +/* + * This file is part of OMAP DSP driver (DSP Gateway version 3.3.1) + * + * Copyright (C) 2002-2006 Nokia Corporation. All rights reserved. + * + * Contact: Toshihiro Kobayashi + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include - #include ++#include +#include "dsp.h" + +#ifdef CONFIG_ARCH_OMAP1 - #include ++#include +#endif + +#if defined(CONFIG_ARCH_OMAP1) +#define dsp_boot_config(mode) omap_writew((mode), MPUI_DSP_BOOT_CONFIG) +#endif +#if defined(CONFIG_ARCH_OMAP2) || defined(CONFIG_ARCH_OMAP3) +#define dsp_boot_config(mode) writel((mode), DSP_IPI_DSPBOOTCONFIG) +#endif + +struct omap_dsp *omap_dsp; + +#if defined(CONFIG_ARCH_OMAP1) +struct clk *dsp_ck_handle; +struct clk *api_ck_handle; +#elif defined(CONFIG_ARCH_OMAP2) +struct clk *dsp_fck_handle; +struct clk *dsp_ick_handle; +#endif +dsp_long_t dspmem_base, dspmem_size, + daram_base, daram_size, + saram_base, saram_size; + +static struct cpustat { + struct mutex lock; + enum cpustat_e stat; + enum cpustat_e req; + u16 icrmask; +#ifdef CONFIG_ARCH_OMAP1 + struct { + int mpui; + int mem; + int mem_delayed; + } usecount; + int (*mem_req_cb)(void); + void (*mem_rel_cb)(void); +#endif +} cpustat = { + .stat = CPUSTAT_RESET, + .icrmask = 0xffff, +}; + +int dsp_set_rstvect(dsp_long_t adr) +{ + unsigned long *dst_adr; + + if (adr >= DSPSPACE_SIZE) + return -EINVAL; + + dst_adr = dspbyte_to_virt(DSP_BOOT_ADR_DIRECT); + /* word swap */ + *dst_adr = ((adr & 0xffff) << 16) | (adr >> 16); + /* fill 8 bytes! */ + *(dst_adr + 1) = 0; + /* direct boot */ + dsp_boot_config(DSP_BOOT_CONFIG_DIRECT); + + return 0; +} + +dsp_long_t dsp_get_rstvect(void) +{ + unsigned long *dst_adr; + + dst_adr = dspbyte_to_virt(DSP_BOOT_ADR_DIRECT); + return ((*dst_adr & 0xffff) << 16) | (*dst_adr >> 16); +} + +#ifdef CONFIG_ARCH_OMAP1 +static void simple_load_code(unsigned char *src_c, u16 *dst, int len) +{ + int i; + u16 *src = (u16 *)src_c; + int len_w; + + /* len must be multiple of 2. */ + if (len & 1) + BUG(); + + len_w = len / 2; + for (i = 0; i < len_w; i++) { + /* byte swap copy */ + *dst = ((*src & 0x00ff) << 8) | + ((*src & 0xff00) >> 8); + src++; + dst++; + } +} + +/* program size must be multiple of 2 */ +#define GBL_IDLE_TEXT_SIZE 52 +#define GBL_IDLE_TEXT_INIT { \ + /* SAM */ \ + 0x3c, 0x4a, /* 0x3c4a: MOV 0x4, AR2 */ \ + 0xf4, 0x41, 0xfc, 0xff, /* 0xf441fcff: AND 0xfcff, *AR2 */ \ + /* disable WDT */ \ + 0x76, 0x34, 0x04, 0xb8, /* 0x763404b8: MOV 0x3404, AR3 */ \ + 0xfb, 0x61, 0x00, 0xf5, /* 0xfb6100f5: MOV 0x00f5, *AR3 */ \ + 0x9a, /* 0x9a: PORT */ \ + 0xfb, 0x61, 0x00, 0xa0, /* 0xfb6100a0: MOV 0x00a0, *AR3 */ \ + 0x9a, /* 0x9a: PORT */ \ + /* *IER0 = 0, *IER1 = 0 */ \ + 0x3c, 0x0b, /* 0x3c0b: MOV 0x0, AR3 */ \ + 0xe6, 0x61, 0x00, /* 0xe66100: MOV 0, *AR3 */ \ + 0x76, 0x00, 0x45, 0xb8, /* 0x76004508: MOV 0x45, AR3 */ \ + 0xe6, 0x61, 0x00, /* 0xe66100: MOV 0, *AR3 */ \ + /* *ICR = 0xffff */ \ + 0x3c, 0x1b, /* 0x3c1b: MOV 0x1, AR3 */ \ + 0xfb, 0x61, 0xff, 0xff, /* 0xfb61ffff: MOV 0xffff, *AR3 */ \ + 0x9a, /* 0x9a: PORT */ \ + /* HOM */ \ + 0xf5, 0x41, 0x03, 0x00, /* 0xf5410300: OR 0x0300, *AR2 */ \ + /* idle and loop forever */ \ + 0x7a, 0x00, 0x00, 0x0c, /* 0x7a00000c: IDLE */ \ + 0x4a, 0x7a, /* 0x4a7a: B -6 (infinite loop) */ \ + 0x20, 0x20, 0x20, /* 0x20: NOP */ \ +} + +/* program size must be multiple of 2 */ +#define CPU_IDLE_TEXT_SIZE 48 +#define CPU_IDLE_TEXT_INIT(icrh, icrl) { \ + /* SAM */ \ + 0x3c, 0x4b, /* 0x3c4b: MOV 0x4, AR3 */ \ + 0xf4, 0x61, 0xfc, 0xff, /* 0xf461fcff: AND 0xfcff, *AR3 */ \ + /* disable WDT */ \ + 0x76, 0x34, 0x04, 0xb8, /* 0x763404b8: MOV 0x3404, AR3 */ \ + 0xfb, 0x61, 0x00, 0xf5, /* 0xfb6100f5: MOV 0x00f5, *AR3 */ \ + 0x9a, /* 0x9a: PORT */ \ + 0xfb, 0x61, 0x00, 0xa0, /* 0xfb6100a0: MOV 0x00a0, *AR3 */ \ + 0x9a, /* 0x9a: PORT */ \ + /* *IER0 = 0, *IER1 = 0 */ \ + 0x3c, 0x0b, /* 0x3c0b: MOV 0x0, AR3 */ \ + 0xe6, 0x61, 0x00, /* 0xe66100: MOV 0, *AR3 */ \ + 0x76, 0x00, 0x45, 0xb8, /* 0x76004508: MOV 0x45, AR3 */ \ + 0xe6, 0x61, 0x00, /* 0xe66100: MOV 0, *AR3 */ \ + /* set ICR = icr */ \ + 0x3c, 0x1b, /* 0x3c1b: MOV AR3 0x1 */ \ + 0xfb, 0x61, (icrh), (icrl), /* 0xfb61****: MOV *AR3, icr */ \ + 0x9a, /* 0x9a: PORT */ \ + /* idle and loop forever */ \ + 0x7a, 0x00, 0x00, 0x0c, /* 0x7a00000c: IDLE */ \ + 0x4a, 0x7a, /* 0x4a7a: B -6 (infinite loop) */ \ + 0x20, 0x20, 0x20 /* 0x20: nop */ \ +} + +/* + * idle_boot base: + * Initialized with DSP_BOOT_ADR_MPUI (=0x010000). + * This value is used before DSP Gateway driver is initialized. + * DSP Gateway driver will overwrite this value with other value, + * to avoid confliction with the user program. + */ +static dsp_long_t idle_boot_base = DSP_BOOT_ADR_MPUI; + +static void dsp_gbl_idle(void) +{ + unsigned char idle_text[GBL_IDLE_TEXT_SIZE] = GBL_IDLE_TEXT_INIT; + + __dsp_reset(); + clk_enable(api_ck_handle); + +#if 0 + dsp_boot_config(DSP_BOOT_CONFIG_IDLE); +#endif + simple_load_code(idle_text, dspbyte_to_virt(idle_boot_base), + GBL_IDLE_TEXT_SIZE); + if (idle_boot_base == DSP_BOOT_ADR_MPUI) + dsp_boot_config(DSP_BOOT_CONFIG_MPUI); + else + dsp_set_rstvect(idle_boot_base); + + __dsp_run(); + udelay(100); /* to make things stable */ + clk_disable(api_ck_handle); +} + +static void dsp_cpu_idle(void) +{ + u16 icr_tmp; + unsigned char icrh, icrl; + + __dsp_reset(); + clk_enable(api_ck_handle); + + /* + * icr settings: + * DMA should not sleep for DARAM/SARAM access + * DPLL should not sleep while any other domain is active + */ + icr_tmp = cpustat.icrmask & ~(DSPREG_ICR_DMA | DSPREG_ICR_DPLL); + icrh = icr_tmp >> 8; + icrl = icr_tmp & 0xff; + { + unsigned char idle_text[CPU_IDLE_TEXT_SIZE] = CPU_IDLE_TEXT_INIT(icrh, icrl); + simple_load_code(idle_text, dspbyte_to_virt(idle_boot_base), + CPU_IDLE_TEXT_SIZE); + } + if (idle_boot_base == DSP_BOOT_ADR_MPUI) + dsp_boot_config(DSP_BOOT_CONFIG_MPUI); + else + dsp_set_rstvect(idle_boot_base); + __dsp_run(); + udelay(100); /* to make things stable */ + clk_disable(api_ck_handle); +} + +void dsp_set_idle_boot_base(dsp_long_t adr, size_t size) +{ + if (adr == idle_boot_base) + return; + idle_boot_base = adr; + if ((size < GBL_IDLE_TEXT_SIZE) || + (size < CPU_IDLE_TEXT_SIZE)) { + printk(KERN_ERR + "omapdsp: size for idle program is not enough!\n"); + BUG(); + } + + /* restart idle program with new base address */ + if (cpustat.stat == CPUSTAT_GBL_IDLE) + dsp_gbl_idle(); + if (cpustat.stat == CPUSTAT_CPU_IDLE) + dsp_cpu_idle(); +} + +void dsp_reset_idle_boot_base(void) +{ + idle_boot_base = DSP_BOOT_ADR_MPUI; +} +#else +void dsp_reset_idle_boot_base(void) { } +#endif /* CONFIG_ARCH_OMAP1 */ + +static int init_done; + +static int omap_dsp_init(void) +{ + mutex_init(&cpustat.lock); + + dspmem_size = 0; +#ifdef CONFIG_ARCH_OMAP15XX + if (cpu_is_omap15xx()) { + dspmem_base = OMAP1510_DSP_BASE; + dspmem_size = OMAP1510_DSP_SIZE; + daram_base = OMAP1510_DARAM_BASE; + daram_size = OMAP1510_DARAM_SIZE; + saram_base = OMAP1510_SARAM_BASE; + saram_size = OMAP1510_SARAM_SIZE; + } +#endif +#ifdef CONFIG_ARCH_OMAP16XX + if (cpu_is_omap16xx()) { + dspmem_base = OMAP16XX_DSP_BASE; + dspmem_size = OMAP16XX_DSP_SIZE; + daram_base = OMAP16XX_DARAM_BASE; + daram_size = OMAP16XX_DARAM_SIZE; + saram_base = OMAP16XX_SARAM_BASE; + saram_size = OMAP16XX_SARAM_SIZE; + } +#endif +#ifdef CONFIG_ARCH_OMAP24XX + if (cpu_is_omap24xx()) { + dspmem_base = DSP_MEM_24XX_VIRT; + dspmem_size = DSP_MEM_24XX_SIZE; + daram_base = OMAP24XX_DARAM_BASE; + daram_size = OMAP24XX_DARAM_SIZE; + saram_base = OMAP24XX_SARAM_BASE; + saram_size = OMAP24XX_SARAM_SIZE; + } +#endif +#ifdef CONFIG_ARCH_OMAP34XX + /* To be Revisited for 3430 */ + if (cpu_is_omap34xx()) { + return -ENODEV; + } +#endif + if (dspmem_size == 0) { + printk(KERN_ERR "omapdsp: unsupported omap architecture.\n"); + return -ENODEV; + } + +#if defined(CONFIG_ARCH_OMAP1) + dsp_ck_handle = clk_get(NULL, "dsp_ck"); + if (IS_ERR(dsp_ck_handle)) { + printk(KERN_ERR "omapdsp: could not acquire dsp_ck handle.\n"); + return PTR_ERR(dsp_ck_handle); + } + + api_ck_handle = clk_get(NULL, "api_ck"); + if (IS_ERR(api_ck_handle)) { + printk(KERN_ERR "omapdsp: could not acquire api_ck handle.\n"); + if (dsp_ck_handle != NULL) + clk_put(dsp_ck_handle); + return PTR_ERR(api_ck_handle); + } + + /* This is needed for McBSP init, released in late_initcall */ + clk_enable(api_ck_handle); + + __dsp_enable(); + mpui_byteswap_off(); + mpui_wordswap_on(); + tc_wordswap(); +#elif defined(CONFIG_ARCH_OMAP2) + dsp_fck_handle = clk_get(NULL, "dsp_fck"); + if (IS_ERR(dsp_fck_handle)) { + printk(KERN_ERR "omapdsp: could not acquire dsp_fck handle.\n"); + return PTR_ERR(dsp_fck_handle); + } + +# if defined(CONFIG_ARCH_OMAP2420) + dsp_ick_handle = clk_get(NULL, "dsp_ick"); +# elif defined(CONFIG_ARCH_OMAP2430) + /* + * 2430 has no separate switch for DSP ICLK, but this at least + * involves the minimal change to the rest of the code. + */ + dsp_ick_handle = clk_get(NULL, "iva2_1_ick"); +# endif + if (IS_ERR(dsp_ick_handle)) { + printk(KERN_ERR "omapdsp: could not acquire dsp_ick handle.\n"); + if (dsp_fck_handle != NULL) + clk_put(dsp_fck_handle); + return PTR_ERR(dsp_ick_handle); + } +#endif + + init_done = 1; + pr_info("omap_dsp_init() done\n"); + return 0; +} + +#if defined(CONFIG_ARCH_OMAP1) +static int __dsp_late_init(void) +{ + clk_disable(api_ck_handle); + return 0; +} +late_initcall(__dsp_late_init); +#endif + +static void dsp_cpustat_update(void) +{ + if (!init_done) + omap_dsp_init(); + + if (cpustat.req == CPUSTAT_RUN) { + if (cpustat.stat < CPUSTAT_RUN) { +#if defined(CONFIG_ARCH_OMAP1) + __dsp_reset(); + clk_enable(api_ck_handle); + udelay(10); + __dsp_run(); +#elif defined(CONFIG_ARCH_OMAP2) + __dsp_core_disable(); + udelay(10); + __dsp_core_enable(); +#endif + cpustat.stat = CPUSTAT_RUN; + } + return; + } + + /* cpustat.req < CPUSTAT_RUN */ + + if (cpustat.stat == CPUSTAT_RUN) { +#ifdef CONFIG_ARCH_OMAP1 + clk_disable(api_ck_handle); +#endif + } + +#ifdef CONFIG_ARCH_OMAP1 + /* + * (1) when ARM wants DARAM access, MPUI should be SAM and + * DSP needs to be on. + * (2) if any bits of icr is masked, we can not enter global idle. + */ + if ((cpustat.req == CPUSTAT_CPU_IDLE) || + (cpustat.usecount.mem > 0) || + (cpustat.usecount.mem_delayed > 0) || + ((cpustat.usecount.mpui > 0) && (cpustat.icrmask != 0xffff))) { + if (cpustat.stat != CPUSTAT_CPU_IDLE) { + dsp_cpu_idle(); + cpustat.stat = CPUSTAT_CPU_IDLE; + } + return; + } + + /* + * when ARM only needs MPUI access, MPUI can be HOM and + * DSP can be idling. + */ + if ((cpustat.req == CPUSTAT_GBL_IDLE) || + (cpustat.usecount.mpui > 0)) { + if (cpustat.stat != CPUSTAT_GBL_IDLE) { + dsp_gbl_idle(); + cpustat.stat = CPUSTAT_GBL_IDLE; + } + return; + } +#endif /* CONFIG_ARCH_OMAP1 */ + + /* + * no user, no request + */ + if (cpustat.stat != CPUSTAT_RESET) { +#if defined(CONFIG_ARCH_OMAP1) + __dsp_reset(); +#elif defined(CONFIG_ARCH_OMAP2) + __dsp_core_disable(); +#endif + cpustat.stat = CPUSTAT_RESET; + } +} + +void dsp_cpustat_request(enum cpustat_e req) +{ + mutex_lock(&cpustat.lock); + cpustat.req = req; + dsp_cpustat_update(); + mutex_unlock(&cpustat.lock); +} + +enum cpustat_e dsp_cpustat_get_stat(void) +{ + return cpustat.stat; +} + +u16 dsp_cpustat_get_icrmask(void) +{ + return cpustat.icrmask; +} + +void dsp_cpustat_set_icrmask(u16 mask) +{ + mutex_lock(&cpustat.lock); + cpustat.icrmask = mask; + dsp_cpustat_update(); + mutex_unlock(&cpustat.lock); +} + +#ifdef CONFIG_ARCH_OMAP1 +void omap_dsp_request_mpui(void) +{ + mutex_lock(&cpustat.lock); + if (cpustat.usecount.mpui++ == 0) + dsp_cpustat_update(); + mutex_unlock(&cpustat.lock); +} + +void omap_dsp_release_mpui(void) +{ + mutex_lock(&cpustat.lock); + if (cpustat.usecount.mpui-- == 0) { + printk(KERN_ERR + "omapdsp: unbalanced mpui request/release detected.\n" + " cpustat.usecount.mpui is going to be " + "less than zero! ... fixed to be zero.\n"); + cpustat.usecount.mpui = 0; + } + if (cpustat.usecount.mpui == 0) + dsp_cpustat_update(); + mutex_unlock(&cpustat.lock); +} + +#if defined(CONFIG_ARCH_OMAP1) && defined(CONFIG_OMAP_MMU_FWK) +int omap_dsp_request_mem(void) +{ + int ret = 0; + + mutex_lock(&cpustat.lock); + if ((cpustat.usecount.mem++ == 0) && + (cpustat.usecount.mem_delayed == 0)) { + if (cpustat.mem_req_cb) { + if ((ret = cpustat.mem_req_cb()) < 0) { + cpustat.usecount.mem--; + goto out; + } + } + dsp_cpustat_update(); + } +out: + mutex_unlock(&cpustat.lock); + + return ret; +} + +/* + * release_mem will be delayed. + */ +static void do_release_mem(struct work_struct *dummy) +{ + mutex_lock(&cpustat.lock); + cpustat.usecount.mem_delayed = 0; + if (cpustat.usecount.mem == 0) { + dsp_cpustat_update(); + if (cpustat.mem_rel_cb) + cpustat.mem_rel_cb(); + } + mutex_unlock(&cpustat.lock); +} + +static DECLARE_DELAYED_WORK(mem_rel_work, do_release_mem); + +int omap_dsp_release_mem(void) +{ + mutex_lock(&cpustat.lock); + + /* cancel previous release work */ + cancel_delayed_work(&mem_rel_work); + cpustat.usecount.mem_delayed = 0; + + if (cpustat.usecount.mem-- == 0) { + printk(KERN_ERR + "omapdsp: unbalanced memory request/release detected.\n" + " cpustat.usecount.mem is going to be " + "less than zero! ... fixed to be zero.\n"); + cpustat.usecount.mem = 0; + } + if (cpustat.usecount.mem == 0) { + cpustat.usecount.mem_delayed = 1; + schedule_delayed_work(&mem_rel_work, HZ); + } + + mutex_unlock(&cpustat.lock); + + return 0; +} +#endif + +void dsp_register_mem_cb(int (*req_cb)(void), void (*rel_cb)(void)) +{ + mutex_lock(&cpustat.lock); + + cpustat.mem_req_cb = req_cb; + cpustat.mem_rel_cb = rel_cb; + + /* + * This function must be called while mem is enabled! + */ + BUG_ON(cpustat.usecount.mem == 0); + + mutex_unlock(&cpustat.lock); +} + +void dsp_unregister_mem_cb(void) +{ + mutex_lock(&cpustat.lock); + cpustat.mem_req_cb = NULL; + cpustat.mem_rel_cb = NULL; + mutex_unlock(&cpustat.lock); +} +#else +void dsp_register_mem_cb(int (*req_cb)(void), void (*rel_cb)(void)) { } +void dsp_unregister_mem_cb(void) { } +#endif /* CONFIG_ARCH_OMAP1 */ + +arch_initcall(omap_dsp_init); + +#ifdef CONFIG_ARCH_OMAP1 +EXPORT_SYMBOL(omap_dsp_request_mpui); +EXPORT_SYMBOL(omap_dsp_release_mpui); +#if defined(CONFIG_ARCH_OMAP1) && defined(CONFIG_OMAP_MMU_FWK) +EXPORT_SYMBOL(omap_dsp_request_mem); +EXPORT_SYMBOL(omap_dsp_release_mem); +#endif +#endif /* CONFIG_ARCH_OMAP1 */ + +#ifdef CONFIG_OMAP_DSP_MODULE +#if defined(CONFIG_ARCH_OMAP1) +EXPORT_SYMBOL(dsp_ck_handle); +EXPORT_SYMBOL(api_ck_handle); +#elif defined(CONFIG_ARCH_OMAP2) +EXPORT_SYMBOL(dsp_fck_handle); +EXPORT_SYMBOL(dsp_ick_handle); +#endif +EXPORT_SYMBOL(omap_dsp); +EXPORT_SYMBOL(dspmem_base); +EXPORT_SYMBOL(dspmem_size); +EXPORT_SYMBOL(daram_base); +EXPORT_SYMBOL(daram_size); +EXPORT_SYMBOL(saram_base); +EXPORT_SYMBOL(saram_size); +EXPORT_SYMBOL(dsp_set_rstvect); +EXPORT_SYMBOL(dsp_get_rstvect); +#ifdef CONFIG_ARCH_OMAP1 +EXPORT_SYMBOL(dsp_set_idle_boot_base); +EXPORT_SYMBOL(dsp_reset_idle_boot_base); +#endif /* CONFIG_ARCH_OMAP1 */ +EXPORT_SYMBOL(dsp_cpustat_request); +EXPORT_SYMBOL(dsp_cpustat_get_stat); +EXPORT_SYMBOL(dsp_cpustat_get_icrmask); +EXPORT_SYMBOL(dsp_cpustat_set_icrmask); +EXPORT_SYMBOL(dsp_register_mem_cb); +EXPORT_SYMBOL(dsp_unregister_mem_cb); + +EXPORT_SYMBOL(__cpu_flush_kern_tlb_range); +EXPORT_SYMBOL(cpu_architecture); +EXPORT_SYMBOL(pmd_clear_bad); +#endif diff --cc drivers/dsp/dspgateway/dsp_core.c index 68cc9e643ff,00000000000..f2368fb32d8 mode 100644,000000..100644 --- a/drivers/dsp/dspgateway/dsp_core.c +++ b/drivers/dsp/dspgateway/dsp_core.c @@@ -1,646 -1,0 +1,646 @@@ +/* + * This file is part of OMAP DSP driver (DSP Gateway version 3.3.1) + * + * Copyright (C) 2002-2006 Nokia Corporation. All rights reserved. + * + * Contact: Toshihiro Kobayashi + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include - #include - #include - #include ++#include ++#include ++#include +#include "dsp_mbcmd.h" +#include "dsp.h" +#include "ipbuf.h" + +MODULE_AUTHOR("Toshihiro Kobayashi "); +MODULE_DESCRIPTION("OMAP DSP driver module"); +MODULE_LICENSE("GPL"); + +static struct sync_seq *mbseq; +static u16 mbseq_expect_tmp; +static u16 *mbseq_expect = &mbseq_expect_tmp; + +extern int dsp_mem_late_init(void); + +/* + * mailbox commands + */ +extern void mbox_wdsnd(struct mbcmd *mb); +extern void mbox_wdreq(struct mbcmd *mb); +extern void mbox_bksnd(struct mbcmd *mb); +extern void mbox_bkreq(struct mbcmd *mb); +extern void mbox_bkyld(struct mbcmd *mb); +extern void mbox_bksndp(struct mbcmd *mb); +extern void mbox_bkreqp(struct mbcmd *mb); +extern void mbox_tctl(struct mbcmd *mb); +extern void mbox_poll(struct mbcmd *mb); +#ifdef OLD_BINARY_SUPPORT +/* v3.3 obsolete */ +extern void mbox_wdt(struct mbcmd *mb); +#endif +extern void mbox_suspend(struct mbcmd *mb); +static void mbox_kfunc(struct mbcmd *mb); +extern void mbox_tcfg(struct mbcmd *mb); +extern void mbox_tadd(struct mbcmd *mb); +extern void mbox_tdel(struct mbcmd *mb); +extern void mbox_dspcfg(struct mbcmd *mb); +extern void mbox_regrw(struct mbcmd *mb); +extern void mbox_getvar(struct mbcmd *mb); +extern void mbox_err(struct mbcmd *mb); +extern void mbox_dbg(struct mbcmd *mb); + +static const struct cmdinfo + cif_wdsnd = { "WDSND", CMD_L_TYPE_TID, mbox_wdsnd }, + cif_wdreq = { "WDREQ", CMD_L_TYPE_TID, mbox_wdreq }, + cif_bksnd = { "BKSND", CMD_L_TYPE_TID, mbox_bksnd }, + cif_bkreq = { "BKREQ", CMD_L_TYPE_TID, mbox_bkreq }, + cif_bkyld = { "BKYLD", CMD_L_TYPE_NULL, mbox_bkyld }, + cif_bksndp = { "BKSNDP", CMD_L_TYPE_TID, mbox_bksndp }, + cif_bkreqp = { "BKREQP", CMD_L_TYPE_TID, mbox_bkreqp }, + cif_tctl = { "TCTL", CMD_L_TYPE_TID, mbox_tctl }, + cif_poll = { "POLL", CMD_L_TYPE_NULL, mbox_poll }, +#ifdef OLD_BINARY_SUPPORT + /* v3.3 obsolete */ + cif_wdt = { "WDT", CMD_L_TYPE_NULL, mbox_wdt }, +#endif + cif_runlevel = { "RUNLEVEL", CMD_L_TYPE_SUBCMD, NULL }, + cif_pm = { "PM", CMD_L_TYPE_SUBCMD, NULL }, + cif_suspend = { "SUSPEND", CMD_L_TYPE_NULL, mbox_suspend }, + cif_kfunc = { "KFUNC", CMD_L_TYPE_SUBCMD, mbox_kfunc }, + cif_tcfg = { "TCFG", CMD_L_TYPE_TID, mbox_tcfg }, + cif_tadd = { "TADD", CMD_L_TYPE_TID, mbox_tadd }, + cif_tdel = { "TDEL", CMD_L_TYPE_TID, mbox_tdel }, + cif_tstop = { "TSTOP", CMD_L_TYPE_TID, NULL }, + cif_dspcfg = { "DSPCFG", CMD_L_TYPE_SUBCMD, mbox_dspcfg }, + cif_regrw = { "REGRW", CMD_L_TYPE_SUBCMD, mbox_regrw }, + cif_getvar = { "GETVAR", CMD_L_TYPE_SUBCMD, mbox_getvar }, + cif_setvar = { "SETVAR", CMD_L_TYPE_SUBCMD, NULL }, + cif_err = { "ERR", CMD_L_TYPE_SUBCMD, mbox_err }, + cif_dbg = { "DBG", CMD_L_TYPE_NULL, mbox_dbg }; + +#define MBOX_CMD_MAX 0x80 +const struct cmdinfo *cmdinfo[MBOX_CMD_MAX] = { + [MBOX_CMD_DSP_WDSND] = &cif_wdsnd, + [MBOX_CMD_DSP_WDREQ] = &cif_wdreq, + [MBOX_CMD_DSP_BKSND] = &cif_bksnd, + [MBOX_CMD_DSP_BKREQ] = &cif_bkreq, + [MBOX_CMD_DSP_BKYLD] = &cif_bkyld, + [MBOX_CMD_DSP_BKSNDP] = &cif_bksndp, + [MBOX_CMD_DSP_BKREQP] = &cif_bkreqp, + [MBOX_CMD_DSP_TCTL] = &cif_tctl, + [MBOX_CMD_DSP_POLL] = &cif_poll, +#ifdef OLD_BINARY_SUPPORT + [MBOX_CMD_DSP_WDT] = &cif_wdt, /* v3.3 obsolete */ +#endif + [MBOX_CMD_DSP_RUNLEVEL] = &cif_runlevel, + [MBOX_CMD_DSP_PM] = &cif_pm, + [MBOX_CMD_DSP_SUSPEND] = &cif_suspend, + [MBOX_CMD_DSP_KFUNC] = &cif_kfunc, + [MBOX_CMD_DSP_TCFG] = &cif_tcfg, + [MBOX_CMD_DSP_TADD] = &cif_tadd, + [MBOX_CMD_DSP_TDEL] = &cif_tdel, + [MBOX_CMD_DSP_TSTOP] = &cif_tstop, + [MBOX_CMD_DSP_DSPCFG] = &cif_dspcfg, + [MBOX_CMD_DSP_REGRW] = &cif_regrw, + [MBOX_CMD_DSP_GETVAR] = &cif_getvar, + [MBOX_CMD_DSP_SETVAR] = &cif_setvar, + [MBOX_CMD_DSP_ERR] = &cif_err, + [MBOX_CMD_DSP_DBG] = &cif_dbg, +}; + +#define list_for_each_entry_safe_natural(p,n,h,m) \ + list_for_each_entry_safe(p,n,h,m) +#define __BUILD_KFUNC(fn, dir) \ +static int __dsp_kfunc_##fn##_devices(struct omap_dsp *dsp, int type, int stage)\ +{ \ + struct dsp_kfunc_device *p, *tmp; \ + int ret, fail = 0; \ + \ + list_for_each_entry_safe_##dir(p, tmp, dsp->kdev_list, entry) { \ + if (type && (p->type != type)) \ + continue; \ + if (p->fn == NULL) \ + continue; \ + ret = p->fn(p, stage); \ + if (ret) { \ + printk(KERN_ERR "%s %s failed\n", #fn, p->name); \ + fail++; \ + } \ + } \ + return fail; \ +} +#define BUILD_KFUNC(fn, dir) \ +__BUILD_KFUNC(fn, dir) \ +static inline int dsp_kfunc_##fn##_devices(struct omap_dsp *dsp) \ +{ \ + return __dsp_kfunc_##fn##_devices(dsp, 0, 0); \ +} +#define BUILD_KFUNC_CTL(fn, dir) \ +__BUILD_KFUNC(fn, dir) \ +static inline int dsp_kfunc_##fn##_devices(struct omap_dsp *dsp, int type, int stage) \ +{ \ + return __dsp_kfunc_##fn##_devices(dsp, type, stage); \ +} + +BUILD_KFUNC(probe, natural) +BUILD_KFUNC(remove, reverse) +BUILD_KFUNC_CTL(enable, natural) +BUILD_KFUNC_CTL(disable, reverse) + +int sync_with_dsp(u16 *adr, u16 val, int try_cnt) +{ + int try; + + if (*(volatile u16 *)adr == val) + return 0; + + for (try = 0; try < try_cnt; try++) { + udelay(1); + if (*(volatile u16 *)adr == val) { + /* success! */ + pr_info("omapdsp: sync_with_dsp(): try = %d\n", try); + return 0; + } + } + + /* fail! */ + return -1; +} + +static int mbcmd_sender_prepare(void *data) +{ + struct mb_exarg *arg = data; + int i, ret = 0; + /* + * even if ipbuf_sys_ad is in DSP internal memory, + * dsp_mem_enable() never cause to call PM mailbox command + * because in that case DSP memory should be always enabled. + * (see ipbuf_sys_hold_mem_active in ipbuf.c) + * + * Therefore, we can call this function here safely. + */ + dsp_mem_enable(ipbuf_sys_ad); + if (sync_with_dsp(&ipbuf_sys_ad->s, TID_FREE, 10) < 0) { + printk(KERN_ERR "omapdsp: ipbuf_sys_ad is busy.\n"); + ret = -EBUSY; + goto out; + } + + for (i = 0; i < arg->argc; i++) { + ipbuf_sys_ad->d[i] = arg->argv[i]; + } + ipbuf_sys_ad->s = arg->tid; + out: + dsp_mem_disable(ipbuf_sys_ad); + return ret; +} + +/* + * __dsp_mbcmd_send_exarg(): mailbox dispatcher + */ +int __dsp_mbcmd_send_exarg(struct mbcmd *mb, struct mb_exarg *arg, + int recovery_flag) +{ + int ret = 0; + + if (unlikely(omap_dsp->enabled == 0)) { + ret = dsp_kfunc_enable_devices(omap_dsp, + DSP_KFUNC_DEV_TYPE_COMMON, 0); + if (ret == 0) + omap_dsp->enabled = 1; + } + + /* + * while MMU fault is set, + * only recovery command can be executed + */ + if (dsp_err_isset(ERRCODE_MMU) && !recovery_flag) { + printk(KERN_ERR + "mbox: mmu interrupt is set. %s is aborting.\n", + cmd_name(*mb)); + goto out; + } + + ret = omap_mbox_msg_send(omap_dsp->mbox, + *(mbox_msg_t *)mb, (void*)arg); + if (ret) + goto out; + + if (mbseq) + mbseq->ad_arm++; + + mblog_add(mb, DIR_A2D); + out: + return ret; +} + +int dsp_mbcmd_send_and_wait_exarg(struct mbcmd *mb, struct mb_exarg *arg, + wait_queue_head_t *q) +{ + int ret; + + DEFINE_WAIT(wait); + prepare_to_wait(q, &wait, TASK_INTERRUPTIBLE); + ret = dsp_mbcmd_send_exarg(mb, arg); + if (ret < 0) + goto out; + schedule_timeout(DSP_TIMEOUT); + out: + finish_wait(q, &wait); + return ret; +} + +/* + * mbcmd receiver + */ +static int mbcmd_receiver(void* msg) +{ + struct mbcmd *mb = (struct mbcmd *)&msg; + + if (cmdinfo[mb->cmd_h] == NULL) { + printk(KERN_ERR + "invalid message (%08x) for mbcmd_receiver().\n", + (mbox_msg_t)msg); + return -1; + } + + (*mbseq_expect)++; + + mblog_add(mb, DIR_D2A); + + /* call handler for the command */ + if (cmdinfo[mb->cmd_h]->handler) + cmdinfo[mb->cmd_h]->handler(mb); + else + printk(KERN_ERR "mbox: %s is not allowed from DSP.\n", + cmd_name(*mb)); + return 0; +} + +static int mbsync_hold_mem_active; + +void dsp_mbox_start(void) +{ + omap_mbox_init_seq(omap_dsp->mbox); + mbseq_expect_tmp = 0; +} + +void dsp_mbox_stop(void) +{ + mbseq = NULL; + mbseq_expect = &mbseq_expect_tmp; +} + +int dsp_mbox_config(void *p) +{ + unsigned long flags; + + if (dsp_address_validate(p, sizeof(struct sync_seq), "mbseq") < 0) + return -1; + if (dsp_mem_type(p, sizeof(struct sync_seq)) != MEM_TYPE_EXTERN) { + printk(KERN_WARNING + "omapdsp: mbseq is placed in DSP internal memory.\n" + " It will prevent DSP from idling.\n"); + mbsync_hold_mem_active = 1; + /* + * dsp_mem_enable() never fails because + * it has been already enabled in dspcfg process and + * this will just increment the usecount. + */ + dsp_mem_enable((void *)daram_base); + } + + local_irq_save(flags); + mbseq = p; + mbseq->da_arm = mbseq_expect_tmp; + mbseq_expect = &mbseq->da_arm; + local_irq_restore(flags); + + return 0; +} + +static int __init dsp_mbox_init(void) +{ + omap_dsp->mbox = omap_mbox_get("dsp"); + if (IS_ERR(omap_dsp->mbox)) { + printk(KERN_ERR "failed to get mailbox handler for DSP.\n"); + return -ENODEV; + } + + omap_dsp->mbox->rxq->callback = mbcmd_receiver; + omap_dsp->mbox->txq->callback = mbcmd_sender_prepare; + + return 0; +} + +static void dsp_mbox_exit(void) +{ + omap_dsp->mbox->txq->callback = NULL; + omap_dsp->mbox->rxq->callback = NULL; + + omap_mbox_put(omap_dsp->mbox); + + if (mbsync_hold_mem_active) { + dsp_mem_disable((void *)daram_base); + mbsync_hold_mem_active = 0; + } +} + +/* + * kernel function dispatcher + */ +extern void mbox_fbctl_upd(void); +extern void mbox_fbctl_disable(struct mbcmd *mb); + +static void mbox_kfunc_fbctl(struct mbcmd *mb) +{ + switch (mb->data) { + case FBCTL_UPD: + mbox_fbctl_upd(); + break; + case FBCTL_DISABLE: + mbox_fbctl_disable(mb); + break; + default: + printk(KERN_ERR + "mbox: Unknown FBCTL from DSP: 0x%04x\n", mb->data); + } +} + +/* + * dspgw: KFUNC message handler + */ +static void mbox_kfunc_power(unsigned short data) +{ + int ret = -1; + + switch (data) { + case DVFS_START: /* ACK from DSP */ + /* TBD */ + break; + case AUDIO_PWR_UP: + ret = dsp_kfunc_enable_devices(omap_dsp, + DSP_KFUNC_DEV_TYPE_AUDIO, 0); + if (ret == 0) + ret++; + break; + case AUDIO_PWR_DOWN: /* == AUDIO_PWR_DOWN1 */ + ret = dsp_kfunc_disable_devices(omap_dsp, + DSP_KFUNC_DEV_TYPE_AUDIO, 1); + break; + case AUDIO_PWR_DOWN2: + ret = dsp_kfunc_disable_devices(omap_dsp, + DSP_KFUNC_DEV_TYPE_AUDIO, 2); + break; + case DSP_PWR_DOWN: + ret = dsp_kfunc_disable_devices(omap_dsp, + DSP_KFUNC_DEV_TYPE_COMMON, 0); + if (ret == 0) + omap_dsp->enabled = 0; + break; + default: + printk(KERN_ERR + "mailbox: Unknown PWR from DSP: 0x%04x\n", data); + break; + } + + if (unlikely(ret < 0)) { + printk(KERN_ERR "mailbox: PWR(0x%04x) failed\n", data); + return; + } + + if (likely(ret == 0)) + return; + + mbcompose_send(KFUNC, KFUNC_POWER, data); +} + +static void mbox_kfunc(struct mbcmd *mb) +{ + switch (mb->cmd_l) { + case KFUNC_FBCTL: + mbox_kfunc_fbctl(mb); + break; + case KFUNC_POWER: + mbox_kfunc_power(mb->data); + break; + default: + printk(KERN_ERR + "mbox: Unknown KFUNC from DSP: 0x%02x\n", mb->cmd_l); + } +} + +#if defined(CONFIG_ARCH_OMAP1) +static inline void dsp_clk_enable(void) {} +static inline void dsp_clk_disable(void) {} +#elif defined(CONFIG_ARCH_OMAP2) +static inline void dsp_clk_enable(void) +{ + clk_enable(dsp_fck_handle); + clk_enable(dsp_ick_handle); + __dsp_per_enable(); +} +static inline void dsp_clk_disable(void) +{ + __dsp_per_disable(); + clk_disable(dsp_ick_handle); + clk_disable(dsp_fck_handle); +} +#endif + +int dsp_late_init(void) +{ + int ret; + + dsp_clk_enable(); + ret = dsp_mem_late_init(); + if (ret) + return ret; + ret = dsp_mbox_init(); + if (ret) + goto fail_mbox; +#ifdef CONFIG_ARCH_OMAP1 + dsp_set_idle_boot_base(IDLEPG_BASE, IDLEPG_SIZE); +#endif + ret = dsp_kfunc_enable_devices(omap_dsp, + DSP_KFUNC_DEV_TYPE_COMMON, 0); + if (ret) + goto fail_kfunc; + omap_dsp->enabled = 1; + + return 0; + + fail_kfunc: + dsp_mbox_exit(); + fail_mbox: + dsp_clk_disable(); + + return ret; +} + +extern int dsp_ctl_core_init(void); +extern void dsp_ctl_core_exit(void); +extern int dsp_ctl_init(void); +extern void dsp_ctl_exit(void); +extern int dsp_mem_init(void); +extern void dsp_mem_exit(void); +extern void mblog_init(void); +extern void mblog_exit(void); +extern int dsp_taskmod_init(void); +extern void dsp_taskmod_exit(void); + +/* + * driver functions + */ +static int __init dsp_drv_probe(struct platform_device *pdev) +{ + int ret; + struct omap_dsp *info; + struct dsp_platform_data *pdata = pdev->dev.platform_data; + + dev_info(&pdev->dev, "OMAP DSP driver initialization\n"); + + info = kzalloc(sizeof(struct omap_dsp), GFP_KERNEL); + if (unlikely(info == NULL)) { + dev_dbg(&pdev->dev, "no memory for info\n"); + return -ENOMEM; + } + platform_set_drvdata(pdev, info); + omap_dsp = info; + + mutex_init(&info->lock); + info->dev = &pdev->dev; + info->kdev_list = &pdata->kdev_list; + + ret = dsp_kfunc_probe_devices(info); + if (ret) { + ret = -ENXIO; + goto fail_kfunc; + } + + ret = dsp_ctl_core_init(); + if (ret) + goto fail_ctl_core; + ret = dsp_mem_init(); + if (ret) + goto fail_mem; + ret = dsp_ctl_init(); + if (unlikely(ret)) + goto fail_ctl_init; + mblog_init(); + ret = dsp_taskmod_init(); + if (ret) + goto fail_taskmod; + + return 0; + + fail_taskmod: + mblog_exit(); + dsp_ctl_exit(); + fail_ctl_init: + dsp_mem_exit(); + fail_mem: + dsp_ctl_core_exit(); + fail_ctl_core: + dsp_kfunc_remove_devices(info); + fail_kfunc: + kfree(info); + + return ret; +} + +static int dsp_drv_remove(struct platform_device *pdev) +{ + struct omap_dsp *info = platform_get_drvdata(pdev); + + dsp_cpustat_request(CPUSTAT_RESET); + + dsp_cfgstat_request(CFGSTAT_CLEAN); + dsp_mbox_exit(); + dsp_taskmod_exit(); + mblog_exit(); + dsp_ctl_exit(); + dsp_mem_exit(); + + dsp_ctl_core_exit(); + +#ifdef CONFIG_ARCH_OMAP2 + __dsp_per_disable(); + clk_disable(dsp_ick_handle); + clk_disable(dsp_fck_handle); +#endif + dsp_kfunc_remove_devices(info); + kfree(info); + + return 0; +} + +#if defined(CONFIG_PM) && defined(CONFIG_ARCH_OMAP1) +static int dsp_drv_suspend(struct platform_device *pdev, pm_message_t state) +{ + dsp_cfgstat_request(CFGSTAT_SUSPEND); + + return 0; +} + +static int dsp_drv_resume(struct platform_device *pdev) +{ + dsp_cfgstat_request(CFGSTAT_RESUME); + + return 0; +} +#else +#define dsp_drv_suspend NULL +#define dsp_drv_resume NULL +#endif /* CONFIG_PM */ + +static struct platform_driver dsp_driver = { + .probe = dsp_drv_probe, + .remove = dsp_drv_remove, + .suspend = dsp_drv_suspend, + .resume = dsp_drv_resume, + .driver = { + .name = "dsp", + }, +}; + +static int __init omap_dsp_mod_init(void) +{ + return platform_driver_register(&dsp_driver); +} + +static void __exit omap_dsp_mod_exit(void) +{ + platform_driver_unregister(&dsp_driver); +} + +/* module dependency: need mailbox module that have mbox_dsp_info */ +extern struct omap_mbox mbox_dsp_info; +struct omap_mbox *mbox_dep = &mbox_dsp_info; + +module_init(omap_dsp_mod_init); +module_exit(omap_dsp_mod_exit); diff --cc drivers/dsp/dspgateway/dsp_ctl.c index 79c1fdf89af,00000000000..712c3cfad09 mode 100644,000000..100644 --- a/drivers/dsp/dspgateway/dsp_ctl.c +++ b/drivers/dsp/dspgateway/dsp_ctl.c @@@ -1,1069 -1,0 +1,1069 @@@ +/* + * This file is part of OMAP DSP driver (DSP Gateway version 3.3.1) + * + * Copyright (C) 2002-2006 Nokia Corporation. All rights reserved. + * + * Contact: Toshihiro Kobayashi + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include - #include - #include ++#include ++#include +#include "hardware_dsp.h" +#include "dsp_mbcmd.h" +#include "dsp.h" +#include "ipbuf.h" + +enum dsp_space_e { + SPACE_MEM, + SPACE_IO, +}; + +#ifdef CONFIG_OMAP_DSP_FBEXPORT +static enum fbstat_e { + FBSTAT_DISABLED = 0, + FBSTAT_ENABLED, + FBSTAT_MAX, +} fbstat = FBSTAT_ENABLED; +#endif + +static enum cfgstat_e cfgstat; +int mbox_revision; +static u8 n_stask; + +static ssize_t ifver_show(struct device *dev, struct device_attribute *attr, + char *buf); +static ssize_t cpustat_show(struct device *dev, struct device_attribute *attr, + char *buf); +static ssize_t icrmask_show(struct device *dev, struct device_attribute *attr, + char *buf); +static ssize_t icrmask_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count); +static ssize_t loadinfo_show(struct device *dev, struct device_attribute *attr, + char *buf); + +#define __ATTR_RW(_name, _mode) { \ + .attr = {.name = __stringify(_name), .mode = _mode, .owner = THIS_MODULE }, \ + .show = _name##_show, \ + .store = _name##_store, \ +} + +static struct device_attribute dev_attr_ifver = __ATTR_RO(ifver); +static struct device_attribute dev_attr_cpustat = __ATTR_RO(cpustat); +static struct device_attribute dev_attr_icrmask = __ATTR_RW(icrmask, 0644); +static struct device_attribute dev_attr_loadinfo = __ATTR_RO(loadinfo); + +/* + * misc interactive mailbox command operations + */ +static struct misc_mb_wait_struct { + struct mutex lock; + wait_queue_head_t wait_q; + u8 cmd_h; + u8 cmd_l; + u16 *retvp; +} misc_mb_wait = { + .lock = __MUTEX_INITIALIZER(misc_mb_wait.lock), + .wait_q = __WAIT_QUEUE_HEAD_INITIALIZER(misc_mb_wait.wait_q), +}; + +static int __misc_mbcompose_send_and_wait(u8 cmd_h, u8 cmd_l, u16 data, + u16 *retvp) +{ + struct mbcmd mb = MBCMD_INIT(cmd_h, cmd_l, data); + int ret = 0; + + if (mutex_lock_interruptible(&misc_mb_wait.lock)) + return -EINTR; + + misc_mb_wait.cmd_h = mb.cmd_h; + misc_mb_wait.cmd_l = mb.cmd_l; + misc_mb_wait.retvp = retvp; + dsp_mbcmd_send_and_wait(&mb, &misc_mb_wait.wait_q); + + if (misc_mb_wait.cmd_h != 0) + ret = -EINVAL; + + mutex_unlock(&misc_mb_wait.lock); + return ret; +} + +#define misc_mbcompose_send_and_wait(cmd_h, cmd_l, data, retvp) \ + __misc_mbcompose_send_and_wait(MBOX_CMD_DSP_##cmd_h, (cmd_l), \ + (data), (retvp)); + +static int misc_mbcmd_response(struct mbcmd *mb, int argc, int match_cmd_l_flag) +{ + volatile u16 *buf; + int i; + + /* if match_cmd_l_v flag is set, cmd_l needs to be matched as well. */ + if (!waitqueue_active(&misc_mb_wait.wait_q) || + (misc_mb_wait.cmd_h != mb->cmd_h) || + (match_cmd_l_flag && (misc_mb_wait.cmd_l != mb->cmd_l))) { + const struct cmdinfo *ci = cmdinfo[mb->cmd_h]; + char cmdstr[32]; + + if (ci->cmd_l_type == CMD_L_TYPE_SUBCMD) + sprintf(cmdstr, "%s:%s", ci->name, subcmd_name(mb)); + else + strcpy(cmdstr, ci->name); + printk(KERN_WARNING + "mbox: unexpected command %s received!\n", cmdstr); + return -1; + } + + /* + * if argc == 1, receive data through mbox:data register. + * if argc > 1, receive through ipbuf_sys. + */ + if (argc == 1) + misc_mb_wait.retvp[0] = mb->data; + else if (argc > 1) { + if (dsp_mem_enable(ipbuf_sys_da) < 0) { + printk(KERN_ERR "mbox: %s - ipbuf_sys_da read failed!\n", + cmdinfo[mb->cmd_h]->name); + return -1; + } + if (sync_with_dsp(&ipbuf_sys_da->s, TID_ANON, 10) < 0) { + printk(KERN_ERR "mbox: %s - IPBUF sync failed!\n", + cmdinfo[mb->cmd_h]->name); + dsp_mem_disable(ipbuf_sys_da); + return -1; + } + /* need word access. do not use memcpy. */ + buf = ipbuf_sys_da->d; + for (i = 0; i < argc; i++) + misc_mb_wait.retvp[i] = buf[i]; + release_ipbuf_pvt(ipbuf_sys_da); + dsp_mem_disable(ipbuf_sys_da); + } + + misc_mb_wait.cmd_h = 0; + wake_up_interruptible(&misc_mb_wait.wait_q); + return 0; +} + +static int dsp_regread(enum dsp_space_e space, u16 adr, u16 *val) +{ + u8 cmd_l = (space == SPACE_MEM) ? REGRW_MEMR : REGRW_IOR; + int ret; + + ret = misc_mbcompose_send_and_wait(REGRW, cmd_l, adr, val); + if ((ret < 0) && (ret != -EINTR)) + printk(KERN_ERR "omapdsp: register read error!\n"); + + return ret; +} + +static int dsp_regwrite(enum dsp_space_e space, u16 adr, u16 val) +{ + u8 cmd_l = (space == SPACE_MEM) ? REGRW_MEMW : REGRW_IOW; + struct mb_exarg arg = { + .tid = TID_ANON, + .argc = 1, + .argv = &val, + }; + + mbcompose_send_exarg(REGRW, cmd_l, adr, &arg); + return 0; +} + +static int dsp_getvar(u8 varid, u16 *val) +{ + int ret; + + ret = misc_mbcompose_send_and_wait(GETVAR, varid, 0, val); + if ((ret < 0) && (ret != -EINTR)) + printk(KERN_ERR "omapdsp: variable read error!\n"); + + return ret; +} + +static int dsp_setvar(u8 varid, u16 val) +{ + mbcompose_send(SETVAR, varid, val); + return 0; +} + +/* + * dsp_cfg() return value + * = 0: OK + * = 1: failed, but state is clear. (DSPCFG command failed) + * < 0: failed. need cleanup. + */ +static int dsp_cfg(void) +{ + int ret = 0; + +#ifdef CONFIG_ARCH_OMAP1 + /* for safety */ + dsp_mem_usecount_clear(); +#endif + + /* + * DSPCFG command and dsp_mem_start() must be called + * while internal mem is on. + */ + dsp_mem_enable((void *)dspmem_base); + + dsp_mbox_start(); + dsp_twch_start(); + dsp_mem_start(); + dsp_err_start(); + + mbox_revision = -1; + + ret = misc_mbcompose_send_and_wait(DSPCFG, DSPCFG_REQ, 0, NULL); + if (ret < 0) { + if (ret != -EINTR) + printk(KERN_ERR "omapdsp: configuration error!\n"); + ret = 1; + goto out; + } + +#if defined(CONFIG_ARCH_OMAP1) && defined(OLD_BINARY_SUPPORT) + /* + * MBREV 3.2 or earlier doesn't assume DMA domain is on + * when DSPCFG command is sent + */ + if ((mbox_revision == MBREV_3_0) || + (mbox_revision == MBREV_3_2)) { + if ((ret = mbcompose_send(PM, PM_ENABLE, DSPREG_ICR_DMA)) < 0) + goto out; + } +#endif + + if ((ret = dsp_task_config_all(n_stask)) < 0) + goto out; + + /* initialization */ +#ifdef CONFIG_OMAP_DSP_FBEXPORT + fbstat = FBSTAT_ENABLED; +#endif + + /* send parameter */ + ret = dsp_setvar(VARID_ICRMASK, dsp_cpustat_get_icrmask()); + if (ret < 0) + goto out; + + /* create runtime sysfs entries */ + ret = device_create_file(omap_dsp->dev, &dev_attr_loadinfo); + if (ret) + printk(KERN_ERR "device_create_file failed: %d\n", ret); + out: + dsp_mem_disable((void *)dspmem_base); + return ret; +} + +static int dsp_uncfg(void) +{ + if (dsp_taskmod_busy()) { + printk(KERN_WARNING "omapdsp: tasks are busy.\n"); + return -EBUSY; + } + + /* FIXME: lock task module */ + + /* remove runtime sysfs entries */ + device_remove_file(omap_dsp->dev, &dev_attr_loadinfo); + + dsp_mbox_stop(); + dsp_twch_stop(); + dsp_mem_stop(); + dsp_err_stop(); + dsp_dbg_stop(); + dsp_task_unconfig_all(); + ipbuf_stop(); + + return 0; +} + +static int dsp_suspend(void) +{ + int ret; + + ret = misc_mbcompose_send_and_wait(SUSPEND, 0, 0, NULL); + if (ret < 0) { + if (ret != -EINVAL) + printk(KERN_ERR "omapdsp: DSP suspend error!\n"); + return ret; + } + + udelay(100); /* wait for DSP-side execution */ + return 0; +} + +int dsp_cfgstat_request(enum cfgstat_e st_req) +{ + static DEFINE_MUTEX(cfgstat_lock); + int ret = 0, ret_override = 0; + + if (mutex_lock_interruptible(&cfgstat_lock)) + return -EINTR; + +again: + switch (st_req) { + + /* cfgstat takes CLEAN, READY or SUSPEND, + while st_req can take SUSPEND in addition. */ + + case CFGSTAT_CLEAN: + if (cfgstat == CFGSTAT_CLEAN) + goto up_out; + if ((ret = dsp_uncfg()) < 0) + goto up_out; + break; + + case CFGSTAT_READY: + if (cfgstat != CFGSTAT_CLEAN) { + printk(KERN_ERR "omapdsp: DSP is ready already!\n"); + ret = -EINVAL; + goto up_out; + } + + ret = dsp_cfg(); + if (ret > 0) { /* failed, but state is clear. */ + ret = -EINVAL; + goto up_out; + } else if (ret < 0) { /* failed, need cleanup. */ + st_req = CFGSTAT_CLEAN; + ret_override = ret; + goto again; + } + break; + + /* + * suspend / resume + * DSP is not reset within this code, but done in omap_pm_suspend. + * so if these functions are called from sysfs, + * DSP should be reset / unreset out of these functions. + */ + case CFGSTAT_SUSPEND: + switch (cfgstat) { + + case CFGSTAT_CLEAN: + if (dsp_cpustat_get_stat() == CPUSTAT_RUN) { + printk(KERN_WARNING + "omapdsp: illegal operation -- trying " + "suspend DSP while it is running but " + "not configured.\n" + " Resetting DSP.\n"); + dsp_cpustat_request(CPUSTAT_RESET); + ret = -EINVAL; + } + goto up_out; + + case CFGSTAT_READY: + if ((ret = dsp_suspend()) < 0) + goto up_out; + break; + + case CFGSTAT_SUSPEND: + goto up_out; + + default: + BUG(); + + } + + break; + + case CFGSTAT_RESUME: + if (cfgstat != CFGSTAT_SUSPEND) { + printk(KERN_WARNING + "omapdsp: DSP resume request, but DSP is not in " + "suspend state.\n"); + ret = -EINVAL; + goto up_out; + } + st_req = CFGSTAT_READY; + break; + + default: + BUG(); + + } + + cfgstat = st_req; +up_out: + mutex_unlock(&cfgstat_lock); + return ret_override ? ret_override : ret; +} + +enum cfgstat_e dsp_cfgstat_get_stat(void) +{ + return cfgstat; +} + +/* + * polls all tasks + */ +static int dsp_poll(void) +{ + int ret; + + ret = misc_mbcompose_send_and_wait(POLL, 0, 0, NULL); + if ((ret < 0) && (ret != -EINTR)) + printk(KERN_ERR "omapdsp: poll error!\n"); + + return ret; +} + +int dsp_set_runlevel(u8 level) +{ + if (level == RUNLEVEL_RECOVERY) { + if (mbcompose_send_recovery(RUNLEVEL, level, 0) < 0) + return -EINVAL; + } else { + if ((level < RUNLEVEL_USER) || + (level > RUNLEVEL_SUPER)) + return -EINVAL; + if (mbcompose_send(RUNLEVEL, level, 0) < 0) + return -EINVAL; + } + + return 0; +} + +#ifdef CONFIG_OMAP_DSP_FBEXPORT +static void dsp_fbctl_enable(void) +{ + mbcompose_send(KFUNC, KFUNC_FBCTL, FBCTL_ENABLE); +} + +static int dsp_fbctl_disable(void) +{ + int ret; + + ret = misc_mbcompose_send_and_wait(KFUNC, KFUNC_FBCTL, FBCTL_DISABLE, + NULL); + if ((ret < 0) && (ret != -EINTR)) + printk(KERN_ERR "omapdsp: fb disable error!\n"); + + return 0; +} + +static int dsp_fbstat_request(enum fbstat_e st) +{ + static DEFINE_MUTEX(fbstat_lock); + int ret = 0; + + if (mutex_lock_interruptible(&fbstat_lock)) + return -EINTR; + + if (st == fbstat) + goto up_out; + + switch (st) { + case FBSTAT_ENABLED: + dsp_fbctl_enable(); + break; + case FBSTAT_DISABLED: + if ((ret = dsp_fbctl_disable()) < 0) + goto up_out; + break; + default: + BUG(); + } + + fbstat = st; +up_out: + mutex_unlock(&fbstat_lock); + return 0; +} +#endif /* CONFIG_OMAP_DSP_FBEXPORT */ + +/* + * DSP control device file operations + */ +static int dsp_ctl_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int ret = 0; + + switch (cmd) { + /* + * command level 1: commands which don't need lock + */ + case DSPCTL_IOCTL_RUN: + dsp_cpustat_request(CPUSTAT_RUN); + break; + + case DSPCTL_IOCTL_RESET: + dsp_cpustat_request(CPUSTAT_RESET); + break; + + case DSPCTL_IOCTL_SETRSTVECT: + ret = dsp_set_rstvect((dsp_long_t)arg); + break; + +#ifdef CONFIG_ARCH_OMAP1 + case DSPCTL_IOCTL_CPU_IDLE: + dsp_cpustat_request(CPUSTAT_CPU_IDLE); + break; + + case DSPCTL_IOCTL_GBL_IDLE: + dsp_cpustat_request(CPUSTAT_GBL_IDLE); + break; + + case DSPCTL_IOCTL_MPUI_WORDSWAP_ON: + mpui_wordswap_on(); + break; + + case DSPCTL_IOCTL_MPUI_WORDSWAP_OFF: + mpui_wordswap_off(); + break; + + case DSPCTL_IOCTL_MPUI_BYTESWAP_ON: + mpui_byteswap_on(); + break; + + case DSPCTL_IOCTL_MPUI_BYTESWAP_OFF: + mpui_byteswap_off(); + break; +#endif /* CONFIG_ARCH_OMAP1 */ + + case DSPCTL_IOCTL_TASKCNT: + ret = dsp_task_count(); + break; + + case DSPCTL_IOCTL_MBSEND: + { + struct omap_dsp_mailbox_cmd u_cmd; + mbox_msg_t msg; + if (copy_from_user(&u_cmd, (void *)arg, sizeof(u_cmd))) + return -EFAULT; + msg = (u_cmd.cmd << 16) | u_cmd.data; + ret = dsp_mbcmd_send((struct mbcmd *)&msg); + break; + } + + case DSPCTL_IOCTL_SETVAR: + { + struct omap_dsp_varinfo var; + if (copy_from_user(&var, (void *)arg, sizeof(var))) + return -EFAULT; + ret = dsp_setvar(var.varid, var.val[0]); + break; + } + + case DSPCTL_IOCTL_RUNLEVEL: + ret = dsp_set_runlevel(arg); + break; + +#ifdef CONFIG_OMAP_DSP_FBEXPORT + case DSPCTL_IOCTL_FBEN: + ret = dsp_fbstat_request(FBSTAT_ENABLED); + break; +#endif + + /* + * command level 2: commands which need lock + */ + case DSPCTL_IOCTL_DSPCFG: + ret = dsp_cfgstat_request(CFGSTAT_READY); + break; + + case DSPCTL_IOCTL_DSPUNCFG: + ret = dsp_cfgstat_request(CFGSTAT_CLEAN); + break; + + case DSPCTL_IOCTL_POLL: + ret = dsp_poll(); + break; + +#ifdef CONFIG_OMAP_DSP_FBEXPORT + case DSPCTL_IOCTL_FBDIS: + ret = dsp_fbstat_request(FBSTAT_DISABLED); + break; +#endif + + case DSPCTL_IOCTL_SUSPEND: + if ((ret = dsp_cfgstat_request(CFGSTAT_SUSPEND)) < 0) + break; + dsp_cpustat_request(CPUSTAT_RESET); + break; + + case DSPCTL_IOCTL_RESUME: + if ((ret = dsp_cfgstat_request(CFGSTAT_RESUME)) < 0) + break; + dsp_cpustat_request(CPUSTAT_RUN); + break; + + case DSPCTL_IOCTL_REGMEMR: + { + struct omap_dsp_reginfo *u_reg = (void *)arg; + u16 adr, val; + + if (copy_from_user(&adr, &u_reg->adr, sizeof(u16))) + return -EFAULT; + if ((ret = dsp_regread(SPACE_MEM, adr, &val)) < 0) + return ret; + if (copy_to_user(&u_reg->val, &val, sizeof(u16))) + return -EFAULT; + break; + } + + case DSPCTL_IOCTL_REGMEMW: + { + struct omap_dsp_reginfo reg; + + if (copy_from_user(®, (void *)arg, sizeof(reg))) + return -EFAULT; + ret = dsp_regwrite(SPACE_MEM, reg.adr, reg.val); + break; + } + + case DSPCTL_IOCTL_REGIOR: + { + struct omap_dsp_reginfo *u_reg = (void *)arg; + u16 adr, val; + + if (copy_from_user(&adr, &u_reg->adr, sizeof(u16))) + return -EFAULT; + if ((ret = dsp_regread(SPACE_IO, adr, &val)) < 0) + return ret; + if (copy_to_user(&u_reg->val, &val, sizeof(u16))) + return -EFAULT; + break; + } + + case DSPCTL_IOCTL_REGIOW: + { + struct omap_dsp_reginfo reg; + + if (copy_from_user(®, (void *)arg, sizeof(reg))) + return -EFAULT; + ret = dsp_regwrite(SPACE_IO, reg.adr, reg.val); + break; + } + + case DSPCTL_IOCTL_GETVAR: + { + struct omap_dsp_varinfo *u_var = (void *)arg; + u8 varid; + u16 val[5]; /* maximum */ + int argc; + + if (copy_from_user(&varid, &u_var->varid, sizeof(u8))) + return -EFAULT; + switch (varid) { + case VARID_ICRMASK: + argc = 1; + break; + case VARID_LOADINFO: + argc = 5; + break; + default: + return -EINVAL; + } + if ((ret = dsp_getvar(varid, val)) < 0) + return ret; + if (copy_to_user(&u_var->val, val, sizeof(u16) * argc)) + return -EFAULT; + break; + } + + default: + return -ENOIOCTLCMD; + } + + return ret; +} + +/* + * functions called from mailbox interrupt routine + */ +void mbox_suspend(struct mbcmd *mb) +{ + misc_mbcmd_response(mb, 0, 0); +} + +void mbox_dspcfg(struct mbcmd *mb) +{ + u8 last = mb->cmd_l & 0x80; + u8 cfgcmd = mb->cmd_l & 0x7f; + static dsp_long_t tmp_ipb_adr; + + if (!waitqueue_active(&misc_mb_wait.wait_q) || + (misc_mb_wait.cmd_h != MBOX_CMD_DSP_DSPCFG)) { + printk(KERN_WARNING + "mbox: DSPCFG command received, " + "but nobody is waiting for it...\n"); + return; + } + + /* mailbox protocol check */ + if (cfgcmd == DSPCFG_PROTREV) { + mbox_revision = mb->data; + if (mbox_revision == MBPROT_REVISION) + return; +#ifdef OLD_BINARY_SUPPORT + else if ((mbox_revision == MBREV_3_0) || + (mbox_revision == MBREV_3_2)) { + printk(KERN_WARNING + "mbox: ***** old DSP binary *****\n" + " Please update your DSP application.\n"); + return; + } +#endif + else { + printk(KERN_ERR + "mbox: protocol revision check error!\n" + " expected=0x%04x, received=0x%04x\n", + MBPROT_REVISION, mb->data); + mbox_revision = -1; + goto abort1; + } + } + + /* + * following commands are accepted only after + * revision check has been passed. + */ + if (!mbox_revision < 0) { + pr_info("mbox: DSPCFG command received, " + "but revision check has not been passed.\n"); + return; + } + + switch (cfgcmd) { + case DSPCFG_SYSADRH: + tmp_ipb_adr = (u32)mb->data << 16; + break; + + case DSPCFG_SYSADRL: + tmp_ipb_adr |= mb->data; + break; + + case DSPCFG_ABORT: + goto abort1; + + default: + printk(KERN_ERR + "mbox: Unknown CFG command: cmd_l=0x%02x, data=0x%04x\n", + mb->cmd_l, mb->data); + return; + } + + if (last) { + void *badr; + u16 bln; + u16 bsz; + volatile u16 *buf; + void *ipb_sys_da, *ipb_sys_ad; + void *mbseq; /* FIXME: 3.4 obsolete */ + short *dbg_buf; + u16 dbg_buf_sz, dbg_line_sz; + struct mem_sync_struct mem_sync, *mem_syncp; + + ipb_sys_da = dspword_to_virt(tmp_ipb_adr); + if (ipbuf_sys_config(ipb_sys_da, DIR_D2A) < 0) + goto abort1; + + if (dsp_mem_enable(ipbuf_sys_da) < 0) { + printk(KERN_ERR "mbox: DSPCFG - ipbuf_sys_da read failed!\n"); + goto abort1; + } + if (sync_with_dsp(&ipbuf_sys_da->s, TID_ANON, 10) < 0) { + printk(KERN_ERR "mbox: DSPCFG - IPBUF sync failed!\n"); + dsp_mem_disable(ipbuf_sys_da); + goto abort1; + } + /* + * read configuration data on system IPBUF + * we must read with 16bit-access + */ +#ifdef OLD_BINARY_SUPPORT + if (mbox_revision == MBPROT_REVISION) { +#endif + buf = ipbuf_sys_da->d; + n_stask = buf[0]; + bln = buf[1]; + bsz = buf[2]; + badr = MKVIRT(buf[3], buf[4]); + /* ipb_sys_da = MKVIRT(buf[5], buf[6]); */ + ipb_sys_ad = MKVIRT(buf[7], buf[8]); + mbseq = MKVIRT(buf[9], buf[10]); + dbg_buf = MKVIRT(buf[11], buf[12]); + dbg_buf_sz = buf[13]; + dbg_line_sz = buf[14]; + mem_sync.DARAM = MKVIRT(buf[15], buf[16]); + mem_sync.SARAM = MKVIRT(buf[17], buf[18]); + mem_sync.SDRAM = MKVIRT(buf[19], buf[20]); + mem_syncp = &mem_sync; +#ifdef OLD_BINARY_SUPPORT + } else if (mbox_revision == MBREV_3_2) { + buf = ipbuf_sys_da->d; + n_stask = buf[0]; + bln = buf[1]; + bsz = buf[2]; + badr = MKVIRT(buf[3], buf[4]); + /* ipb_sys_da = MKVIRT(buf[5], buf[6]); */ + ipb_sys_ad = MKVIRT(buf[7], buf[8]); + mbseq = MKVIRT(buf[9], buf[10]); + dbg_buf = NULL; + dbg_buf_sz = 0; + dbg_line_sz = 0; + mem_syncp = NULL; + } else if (mbox_revision == MBREV_3_0) { + buf = ipbuf_sys_da->d; + n_stask = buf[0]; + bln = buf[1]; + bsz = buf[2]; + badr = MKVIRT(buf[3], buf[4]); + /* bkeep = buf[5]; */ + /* ipb_sys_da = MKVIRT(buf[6], buf[7]); */ + ipb_sys_ad = MKVIRT(buf[8], buf[9]); + mbseq = MKVIRT(buf[10], buf[11]); + dbg_buf = NULL; + dbg_buf_sz = 0; + dbg_line_sz = 0; + mem_syncp = NULL; + } else { /* should not occur */ + dsp_mem_disable(ipbuf_sys_da); + goto abort1; + } +#endif /* OLD_BINARY_SUPPORT */ + + release_ipbuf_pvt(ipbuf_sys_da); + dsp_mem_disable(ipbuf_sys_da); + + /* + * following configurations need to be done before + * waking up the dspcfg initiator process. + */ + if (ipbuf_sys_config(ipb_sys_ad, DIR_A2D) < 0) + goto abort1; + if (ipbuf_config(bln, bsz, badr) < 0) + goto abort1; + if (dsp_mbox_config(mbseq) < 0) + goto abort2; + if (dsp_dbg_config(dbg_buf, dbg_buf_sz, dbg_line_sz) < 0) + goto abort2; + if (dsp_mem_sync_config(mem_syncp) < 0) + goto abort2; + + misc_mb_wait.cmd_h = 0; + wake_up_interruptible(&misc_mb_wait.wait_q); + } + return; + +abort2: + ipbuf_stop(); +abort1: + wake_up_interruptible(&misc_mb_wait.wait_q); + return; +} + +void mbox_poll(struct mbcmd *mb) +{ + misc_mbcmd_response(mb, 0, 0); +} + +void mbox_regrw(struct mbcmd *mb) +{ + switch (mb->cmd_l) { + case REGRW_DATA: + misc_mbcmd_response(mb, 1, 0); + break; + default: + printk(KERN_ERR + "mbox: Illegal REGRW command: " + "cmd_l=0x%02x, data=0x%04x\n", mb->cmd_l, mb->data); + return; + } +} + +void mbox_getvar(struct mbcmd *mb) +{ + switch (mb->cmd_l) { + case VARID_ICRMASK: + misc_mbcmd_response(mb, 1, 1); + break; + case VARID_LOADINFO: + misc_mbcmd_response(mb, 5, 1); + break; + default: + printk(KERN_ERR + "mbox: Illegal GETVAR command: " + "cmd_l=0x%02x, data=0x%04x\n", mb->cmd_l, mb->data); + return; + } +} + +void mbox_fbctl_disable(struct mbcmd *mb) +{ + misc_mbcmd_response(mb, 0, 0); +} + +struct file_operations dsp_ctl_fops = { + .owner = THIS_MODULE, + .ioctl = dsp_ctl_ioctl, +}; + +/* + * sysfs files + */ + +/* ifver */ +static ssize_t ifver_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int len = 0; + + /* + * I/F VERSION descriptions: + * + * 3.2: sysfs / udev support + * KMEM_RESERVE / KMEM_RELEASE ioctls for mem device + * 3.3: added following ioctls + * DSPCTL_IOCTL_GBL_IDLE + * DSPCTL_IOCTL_CPU_IDLE (instead of DSPCTL_IOCTL_IDLE) + * DSPCTL_IOCTL_POLL + */ + + /* + * print all supporting I/F VERSIONs, like followings. + * + * len += sprintf(buf, "3.2\n"); + * len += sprintf(buf, "3.3\n"); + */ + len += sprintf(buf + len, "3.2\n"); + len += sprintf(buf + len, "3.3\n"); + + return len; +} + +/* cpustat */ +static char *cpustat_name[CPUSTAT_MAX] = { + [CPUSTAT_RESET] = "reset", +#ifdef CONFIG_ARCH_OMAP1 + [CPUSTAT_GBL_IDLE] = "gbl_idle", + [CPUSTAT_CPU_IDLE] = "cpu_idle", +#endif + [CPUSTAT_RUN] = "run", +}; + +static ssize_t cpustat_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%s\n", cpustat_name[dsp_cpustat_get_stat()]); +} + +/* icrmask */ +static ssize_t icrmask_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "0x%04x\n", dsp_cpustat_get_icrmask()); +} + +static ssize_t icrmask_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + u16 mask; + int ret; + + mask = simple_strtol(buf, NULL, 16); + dsp_cpustat_set_icrmask(mask); + + if (dsp_cfgstat_get_stat() == CFGSTAT_READY) { + ret = dsp_setvar(VARID_ICRMASK, mask); + if (ret < 0) + return ret; + } + + return count; +} + +/* loadinfo */ +static ssize_t loadinfo_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int len; + int ret; + u16 val[5]; + + if ((ret = dsp_getvar(VARID_LOADINFO, val)) < 0) + return ret; + + /* + * load info value range is 0(free) - 10000(busy): + * if CPU load is not measured on DSP, it sets 0xffff at val[0]. + */ + + if (val[0] == 0xffff) { + len = sprintf(buf, + "currently DSP load info is not available.\n"); + goto out; + } + + len = sprintf(buf, + "DSP load info:\n" + " 10ms average = %3d.%02d%%\n" + " 1sec average = %3d.%02d%% busiest 10ms = %3d.%02d%%\n" + " 1min average = %3d.%02d%% busiest 1s = %3d.%02d%%\n", + val[0]/100, val[0]%100, + val[1]/100, val[1]%100, val[2]/100, val[2]%100, + val[3]/100, val[3]%100, val[4]/100, val[4]%100); +out: + return len; +} + +int __init dsp_ctl_init(void) +{ + int ret; + + ret = device_create_file(omap_dsp->dev, &dev_attr_ifver); + if (unlikely(ret)) + return ret; + ret = device_create_file(omap_dsp->dev, &dev_attr_cpustat); + if (unlikely(ret)) + goto fail_create_cpustat; + ret = device_create_file(omap_dsp->dev, &dev_attr_icrmask); + if (unlikely(ret)) + goto fail_create_icrmask; + + return 0; + +fail_create_icrmask: + device_remove_file(omap_dsp->dev, &dev_attr_cpustat); +fail_create_cpustat: + device_remove_file(omap_dsp->dev, &dev_attr_ifver); + + return ret; +} + +void dsp_ctl_exit(void) +{ + device_remove_file(omap_dsp->dev, &dev_attr_ifver); + device_remove_file(omap_dsp->dev, &dev_attr_cpustat); + device_remove_file(omap_dsp->dev, &dev_attr_icrmask); +} diff --cc drivers/dsp/dspgateway/dsp_mem.c index 6d3148ff142,00000000000..e21bd069619 mode 100644,000000..100644 --- a/drivers/dsp/dspgateway/dsp_mem.c +++ b/drivers/dsp/dspgateway/dsp_mem.c @@@ -1,484 -1,0 +1,484 @@@ +/* + * This file is part of OMAP DSP driver (DSP Gateway version 3.3.1) + * + * Copyright (C) 2002-2006 Nokia Corporation. All rights reserved. + * + * Contact: Toshihiro Kobayashi + * + * Conversion to mempool API and ARM MMU section mapping + * by Paul Mundt + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include - #include - #include - #include - #include - #include ++#include ++#include ++#include ++#include ++#include +#include "dsp_mbcmd.h" +#include "dsp.h" +#include "ipbuf.h" + +#if 0 +#if defined(CONFIG_ARCH_OMAP1) +#include "../../mach-omap1/mmu.h" +#elif defined(CONFIG_ARCH_OMAP2) +#include "../../mach-omap2/mmu.h" +#endif +#endif + +#include "mmu.h" + +static struct mem_sync_struct mem_sync; + +int dsp_mem_sync_inc(void) +{ + if (dsp_mem_enable((void *)dspmem_base) < 0) + return -1; + if (mem_sync.DARAM) + mem_sync.DARAM->ad_arm++; + if (mem_sync.SARAM) + mem_sync.SARAM->ad_arm++; + if (mem_sync.SDRAM) + mem_sync.SDRAM->ad_arm++; + dsp_mem_disable((void *)dspmem_base); + + return 0; +} + +/* + * dsp_mem_sync_config() is called from mbox1 workqueue + */ +int dsp_mem_sync_config(struct mem_sync_struct *sync) +{ + size_t sync_seq_sz = sizeof(struct sync_seq); + +#ifdef OLD_BINARY_SUPPORT + if (sync == NULL) { + memset(&mem_sync, 0, sizeof(struct mem_sync_struct)); + return 0; + } +#endif + if ((dsp_mem_type(sync->DARAM, sync_seq_sz) != MEM_TYPE_DARAM) || + (dsp_mem_type(sync->SARAM, sync_seq_sz) != MEM_TYPE_SARAM) || + (dsp_mem_type(sync->SDRAM, sync_seq_sz) != MEM_TYPE_EXTERN)) { + printk(KERN_ERR + "omapdsp: mem_sync address validation failure!\n" + " mem_sync.DARAM = 0x%p,\n" + " mem_sync.SARAM = 0x%p,\n" + " mem_sync.SDRAM = 0x%p,\n", + sync->DARAM, sync->SARAM, sync->SDRAM); + return -1; + } + + memcpy(&mem_sync, sync, sizeof(struct mem_sync_struct)); + + return 0; +} + + +enum dsp_mem_type_e dsp_mem_type(void *vadr, size_t len) +{ + void *ds = (void *)daram_base; + void *de = (void *)daram_base + daram_size; + void *ss = (void *)saram_base; + void *se = (void *)saram_base + saram_size; + int ret; + + if ((vadr >= ds) && (vadr < de)) { + if (vadr + len > de) + return MEM_TYPE_CROSSING; + else + return MEM_TYPE_DARAM; + } else if ((vadr >= ss) && (vadr < se)) { + if (vadr + len > se) + return MEM_TYPE_CROSSING; + else + return MEM_TYPE_SARAM; + } else { + down_read(&dsp_mmu.exmap_sem); + if (exmap_valid(&dsp_mmu, vadr, len)) + ret = MEM_TYPE_EXTERN; + else + ret = MEM_TYPE_NONE; + up_read(&dsp_mmu.exmap_sem); + return ret; + } +} + +int dsp_address_validate(void *p, size_t len, char *fmt, ...) +{ + char s[64]; + va_list args; + + if (dsp_mem_type(p, len) > 0) + return 0; + + if (fmt == NULL) + goto out; + + va_start(args, fmt); + vsprintf(s, fmt, args); + va_end(args); + printk(KERN_ERR + "omapdsp: %s address(0x%p) and size(0x%x) is not valid!\n" + "(crossing different type of memories, or external memory\n" + "space where no actual memory is mapped)\n", s, p, len); + out: + return -1; +} + +#ifdef CONFIG_OMAP_DSP_FBEXPORT + +static inline unsigned long lineup_offset(unsigned long adr, + unsigned long ref, + unsigned long mask) +{ + unsigned long newadr; + + newadr = (adr & ~mask) | (ref & mask); + if (newadr < adr) + newadr += mask + 1; + return newadr; +} + +/* + * fb update functions: + * fbupd_response() is executed by the workqueue. + * fbupd_cb() is called when fb update is done, in interrupt context. + * mbox_fbupd() is called when KFUNC:FBCTL:UPD is received from DSP. + */ +static void fbupd_response(struct work_struct *unused) +{ + int status; + + status = mbcompose_send(KFUNC, KFUNC_FBCTL, FBCTL_UPD); + if (status == 0) + return; + + /* FIXME: DSP is busy !! */ + printk(KERN_ERR + "omapdsp:" + "DSP is busy when trying to send FBCTL:UPD response!\n"); +} + +static DECLARE_WORK(fbupd_response_work, fbupd_response); + +static void fbupd_cb(void *arg) +{ + schedule_work(&fbupd_response_work); +} + +void mbox_fbctl_upd(void) +{ + struct omapfb_update_window win; + volatile unsigned short *buf = ipbuf_sys_da->d; + + if (sync_with_dsp(&ipbuf_sys_da->s, TID_ANON, 5000) < 0) { + printk(KERN_ERR "mbox: FBCTL:UPD - IPBUF sync failed!\n"); + return; + } + win.x = buf[0]; + win.y = buf[1]; + win.width = buf[2]; + win.height = buf[3]; + win.format = buf[4]; + release_ipbuf_pvt(ipbuf_sys_da); + +#ifdef CONFIG_FB_OMAP_LCDC_EXTERNAL + if (!omapfb_ready) { + printk(KERN_WARNING + "omapdsp: fbupd() called while HWA742 is not ready!\n"); + return; + } +#endif + omapfb_update_window_async(registered_fb[0], &win, fbupd_cb, NULL); +} + +#ifdef CONFIG_FB_OMAP_LCDC_EXTERNAL +static int omapfb_notifier_cb(struct notifier_block *omapfb_nb, + unsigned long event, void *fbi) +{ + pr_info("omapfb_notifier_cb(): event = %s\n", + (event == OMAPFB_EVENT_READY) ? "READY" : + (event == OMAPFB_EVENT_DISABLED) ? "DISABLED" : "Unknown"); + if (event == OMAPFB_EVENT_READY) + omapfb_ready = 1; + else if (event == OMAPFB_EVENT_DISABLED) + omapfb_ready = 0; + return 0; +} +#endif + +static int dsp_fbexport(dsp_long_t *dspadr) +{ + dsp_long_t dspadr_actual; + unsigned long padr_sys, padr, fbsz_sys, fbsz; + int cnt; +#ifdef CONFIG_FB_OMAP_LCDC_EXTERNAL + int status; +#endif + + pr_debug( "omapdsp: frame buffer export\n"); + +#ifdef CONFIG_FB_OMAP_LCDC_EXTERNAL + if (omapfb_nb) { + printk(KERN_WARNING + "omapdsp: frame buffer has been exported already!\n"); + return -EBUSY; + } +#endif + + if (num_registered_fb == 0) { + pr_info("omapdsp: frame buffer not registered.\n"); + return -EINVAL; + } + if (num_registered_fb != 1) { + pr_info("omapdsp: %d frame buffers found. we use first one.\n", + num_registered_fb); + } + padr_sys = registered_fb[0]->fix.smem_start; + fbsz_sys = registered_fb[0]->fix.smem_len; + if (fbsz_sys == 0) { + printk(KERN_ERR + "omapdsp: framebuffer doesn't seem to be configured " + "correctly! (size=0)\n"); + return -EINVAL; + } + + /* + * align padr and fbsz to 4kB boundary + * (should be noted to the user afterwards!) + */ + padr = padr_sys & ~(SZ_4K-1); + fbsz = (fbsz_sys + padr_sys - padr + SZ_4K-1) & ~(SZ_4K-1); + + /* line up dspadr offset with padr */ + dspadr_actual = + (fbsz > SZ_1M) ? lineup_offset(*dspadr, padr, SZ_1M-1) : + (fbsz > SZ_64K) ? lineup_offset(*dspadr, padr, SZ_64K-1) : + /* (fbsz > SZ_4KB) ? */ *dspadr; + if (dspadr_actual != *dspadr) + pr_debug( + "omapdsp: actual dspadr for FBEXPORT = %08x\n", + dspadr_actual); + *dspadr = dspadr_actual; + + cnt = omap_mmu_exmap(&dsp_mmu, dspadr_actual, padr, fbsz, + EXMAP_TYPE_FB); + if (cnt < 0) { + printk(KERN_ERR "omapdsp: exmap failure.\n"); + return cnt; + } + + if ((padr != padr_sys) || (fbsz != fbsz_sys)) { + printk(KERN_WARNING +" !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" +" !! screen base address or size is not aligned in 4kB: !!\n" +" !! actual screen adr = %08lx, size = %08lx !!\n" +" !! exporting adr = %08lx, size = %08lx !!\n" +" !! Make sure that the framebuffer is allocated with 4kB-order! !!\n" +" !! Otherwise DSP can corrupt the kernel memory. !!\n" +" !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n", + padr_sys, fbsz_sys, padr, fbsz); + } + + /* increase the DMA priority */ + set_emiff_dma_prio(15); + +#ifdef CONFIG_FB_OMAP_LCDC_EXTERNAL + omapfb_nb = kzalloc(sizeof(struct omapfb_notifier_block), GFP_KERNEL); + if (omapfb_nb == NULL) { + printk(KERN_ERR + "omapdsp: failed to allocate memory for omapfb_nb!\n"); + omap_mmu_exunmap(&dsp_mmu, (unsigned long)dspadr); + return -ENOMEM; + } + + status = omapfb_register_client(omapfb_nb, omapfb_notifier_cb, NULL); + if (status) + pr_info("omapfb_register_client(): failure(%d)\n", status); +#endif + + return cnt; +} +#else +void mbox_fbctl_upd(void) { } +#endif + +/* dsp/mem fops: backward compatibility */ +static ssize_t dsp_mem_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + struct bin_attribute attr; + + return __omap_mmu_mem_read(&dsp_mmu, &attr, + (char __user *)buf, *ppos, count); +} + +static ssize_t dsp_mem_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct bin_attribute attr; + + return __omap_mmu_mem_write(&dsp_mmu, &attr, + (char __user *)buf, *ppos, count); +} + +static int dsp_mem_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct omap_dsp_mapinfo mapinfo; + __u32 size; + + switch (cmd) { + case MEM_IOCTL_MMUINIT: + if (dsp_mmu.exmap_tbl) + omap_mmu_unregister(&dsp_mmu); + dsp_mem_ipi_init(); + return omap_mmu_register(&dsp_mmu); + + case MEM_IOCTL_EXMAP: + if (copy_from_user(&mapinfo, (void __user *)arg, + sizeof(mapinfo))) + return -EFAULT; + return omap_mmu_exmap(&dsp_mmu, mapinfo.dspadr, + 0, mapinfo.size, EXMAP_TYPE_MEM); + + case MEM_IOCTL_EXUNMAP: + return omap_mmu_exunmap(&dsp_mmu, (unsigned long)arg); + + case MEM_IOCTL_EXMAP_FLUSH: + omap_mmu_exmap_flush(&dsp_mmu); + return 0; +#ifdef CONFIG_OMAP_DSP_FBEXPORT + case MEM_IOCTL_FBEXPORT: + { + dsp_long_t dspadr; + int ret; + if (copy_from_user(&dspadr, (void __user *)arg, + sizeof(dsp_long_t))) + return -EFAULT; + ret = dsp_fbexport(&dspadr); + if (copy_to_user((void __user *)arg, &dspadr, + sizeof(dsp_long_t))) + return -EFAULT; + return ret; + } +#endif + case MEM_IOCTL_MMUITACK: + return dsp_mmu_itack(); + + case MEM_IOCTL_KMEM_RESERVE: + + if (copy_from_user(&size, (void __user *)arg, + sizeof(__u32))) + return -EFAULT; + return omap_mmu_kmem_reserve(&dsp_mmu, size); + + + case MEM_IOCTL_KMEM_RELEASE: + omap_mmu_kmem_release(); + return 0; + + default: + return -ENOIOCTLCMD; + } +} + +struct file_operations dsp_mem_fops = { + .owner = THIS_MODULE, + .read = dsp_mem_read, + .write = dsp_mem_write, + .ioctl = dsp_mem_ioctl, +}; + +void dsp_mem_start(void) +{ + dsp_register_mem_cb(intmem_enable, intmem_disable); +} + +void dsp_mem_stop(void) +{ + memset(&mem_sync, 0, sizeof(struct mem_sync_struct)); + dsp_unregister_mem_cb(); +} + +static void dsp_mmu_irq_work(struct work_struct *work) +{ + struct omap_mmu *mmu = container_of(work, struct omap_mmu, irq_work); + + if (dsp_cfgstat_get_stat() == CFGSTAT_READY) { + dsp_err_set(ERRCODE_MMU, mmu->fault_address); + return; + } + omap_mmu_itack(mmu); + pr_info("Resetting DSP...\n"); + dsp_cpustat_request(CPUSTAT_RESET); + omap_mmu_enable(mmu, 0); +} + +/* + * later half of dsp memory initialization + */ +int dsp_mem_late_init(void) +{ + int ret; + + dsp_mem_ipi_init(); + + INIT_WORK(&dsp_mmu.irq_work, dsp_mmu_irq_work); + ret = omap_mmu_register(&dsp_mmu); + if (ret) { + dsp_reset_idle_boot_base(); + goto out; + } + omap_dsp->mmu = &dsp_mmu; + out: + return ret; +} + +int __init dsp_mem_init(void) +{ +#ifdef CONFIG_ARCH_OMAP2 + dsp_mmu.clk = dsp_fck_handle; + dsp_mmu.memclk = dsp_ick_handle; +#elif defined(CONFIG_ARCH_OMAP1) + dsp_mmu.clk = dsp_ck_handle; + dsp_mmu.memclk = api_ck_handle; +#endif + return 0; +} + +void dsp_mem_exit(void) +{ + dsp_reset_idle_boot_base(); + omap_mmu_unregister(&dsp_mmu); +} diff --cc drivers/dsp/dspgateway/error.c index d2276f93919,00000000000..66d9c8aa51f mode 100644,000000..100644 --- a/drivers/dsp/dspgateway/error.c +++ b/drivers/dsp/dspgateway/error.c @@@ -1,227 -1,0 +1,227 @@@ +/* + * This file is part of OMAP DSP driver (DSP Gateway version 3.3.1) + * + * Copyright (C) 2002-2006 Nokia Corporation. All rights reserved. + * + * Contact: Toshihiro Kobayashi + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include - #include ++#include +#include +#include "dsp_mbcmd.h" +#include "dsp.h" + +/* + * value seen through read() + */ +#define DSP_ERR_WDT 0x00000001 +#define DSP_ERR_MMU 0x00000002 +static unsigned long errval; + +static DECLARE_WAIT_QUEUE_HEAD(err_wait_q); +static int errcnt; +static u16 wdtval; /* FIXME: read through ioctl */ +static u32 mmu_fadr; /* FIXME: read through ioctl */ + +/* + * DSP error detection device file operations + */ +static ssize_t dsp_err_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + unsigned long flags; + int status; + DEFINE_WAIT(wait); + + if (count < 4) + return 0; + + prepare_to_wait(&err_wait_q, &wait, TASK_INTERRUPTIBLE); + if (errcnt == 0) + schedule(); + finish_wait(&err_wait_q, &wait); + if (signal_pending(current)) + return -EINTR; + + local_irq_save(flags); + status = copy_to_user(buf, &errval, 4); + if (status) { + local_irq_restore(flags); + return -EFAULT; + } + errcnt = 0; + local_irq_restore(flags); + + return 4; +} + +static unsigned int dsp_err_poll(struct file *file, poll_table *wait) +{ + unsigned int mask = 0; + + poll_wait(file, &err_wait_q, wait); + if (errcnt != 0) + mask |= POLLIN | POLLRDNORM; + + return mask; +} + +struct file_operations dsp_err_fops = { + .owner = THIS_MODULE, + .poll = dsp_err_poll, + .read = dsp_err_read, +}; + +/* + * set / clear functions + */ + +/* DSP MMU */ +static void dsp_err_mmu_set(unsigned long arg) +{ + disable_irq(omap_dsp->mmu->irq); + mmu_fadr = (u32)arg; +} + +static void dsp_err_mmu_clr(void) +{ + enable_irq(omap_dsp->mmu->irq); +} + +/* WDT */ +static void dsp_err_wdt_set(unsigned long arg) +{ + wdtval = (u16)arg; +} + +/* + * error code handler + */ +static struct { + unsigned long val; + void (*set)(unsigned long arg); + void (*clr)(void); +} dsp_err_desc[ERRCODE_MAX] = { + [ERRCODE_MMU] = { DSP_ERR_MMU, dsp_err_mmu_set, dsp_err_mmu_clr }, + [ERRCODE_WDT] = { DSP_ERR_WDT, dsp_err_wdt_set, NULL }, +}; + +void dsp_err_set(enum errcode_e code, unsigned long arg) +{ + if (dsp_err_desc[code].set != NULL) + dsp_err_desc[code].set(arg); + + errval |= dsp_err_desc[code].val; + errcnt++; + wake_up_interruptible(&err_wait_q); +} + +void dsp_err_clear(enum errcode_e code) +{ + errval &= ~dsp_err_desc[code].val; + + if (dsp_err_desc[code].clr != NULL) + dsp_err_desc[code].clr(); +} + +int dsp_err_isset(enum errcode_e code) +{ + return (errval & dsp_err_desc[code].val) ? 1 : 0; +} + +void dsp_err_notify(void) +{ + /* new error code should be assigned */ + dsp_err_set(DSP_ERR_WDT, 0); +} + +/* + * functions called from mailbox interrupt routine + */ +static void mbox_err_wdt(u16 data) +{ + dsp_err_set(DSP_ERR_WDT, (unsigned long)data); +} + +#ifdef OLD_BINARY_SUPPORT +/* v3.3 obsolete */ +void mbox_wdt(struct mbcmd *mb) +{ + mbox_err_wdt(mb->data); +} +#endif + +extern void mbox_err_ipbfull(void); +extern void mbox_err_fatal(u8 tid); + +void mbox_err(struct mbcmd *mb) +{ + u8 eid = mb->cmd_l; + char *eidnm = subcmd_name(mb); + u8 tid; + + if (eidnm) { + printk(KERN_WARNING + "mbox: ERR from DSP (%s): 0x%04x\n", eidnm, mb->data); + } else { + printk(KERN_WARNING + "mbox: ERR from DSP (unknown EID=%02x): %04x\n", + eid, mb->data); + } + + switch (eid) { + case EID_IPBFULL: + mbox_err_ipbfull(); + break; + + case EID_FATAL: + tid = mb->data & 0x00ff; + mbox_err_fatal(tid); + break; + + case EID_WDT: + mbox_err_wdt(mb->data); + break; + } +} + +/* + * + */ +void dsp_err_start(void) +{ + enum errcode_e i; + + for (i = 0; i < ERRCODE_MAX; i++) { + if (dsp_err_isset(i)) + dsp_err_clear(i); + } + omap_dsp->mbox->err_notify = dsp_err_notify; + errcnt = 0; +} + +void dsp_err_stop(void) +{ + wake_up_interruptible(&err_wait_q); + omap_dsp->mbox->err_notify = NULL; +} diff --cc drivers/dsp/dspgateway/ipbuf.c index aba8e7422b6,00000000000..b05ba070777 mode 100644,000000..100644 --- a/drivers/dsp/dspgateway/ipbuf.c +++ b/drivers/dsp/dspgateway/ipbuf.c @@@ -1,353 -1,0 +1,353 @@@ +/* + * This file is part of OMAP DSP driver (DSP Gateway version 3.3.1) + * + * Copyright (C) 2002-2006 Nokia Corporation. All rights reserved. + * + * Contact: Toshihiro Kobayashi + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include - #include ++#include +#include "dsp_mbcmd.h" +#include "dsp.h" +#include "ipbuf.h" + +static struct ipbuf_head *g_ipbuf; +struct ipbcfg ipbcfg; +struct ipbuf_sys *ipbuf_sys_da, *ipbuf_sys_ad; +static struct ipblink ipb_free = IPBLINK_INIT; +static int ipbuf_sys_hold_mem_active; + +static ssize_t ipbuf_show(struct device *dev, struct device_attribute *attr, + char *buf); +static struct device_attribute dev_attr_ipbuf = __ATTR_RO(ipbuf); + +void ipbuf_stop(void) +{ + int i; + + device_remove_file(omap_dsp->dev, &dev_attr_ipbuf); + + spin_lock(&ipb_free.lock); + RESET_IPBLINK(&ipb_free); + spin_unlock(&ipb_free.lock); + + ipbcfg.ln = 0; + if (g_ipbuf) { + kfree(g_ipbuf); + g_ipbuf = NULL; + } + for (i = 0; i < ipbuf_sys_hold_mem_active; i++) { + dsp_mem_disable((void *)daram_base); + } + ipbuf_sys_hold_mem_active = 0; +} + +int ipbuf_config(u16 ln, u16 lsz, void *base) +{ + size_t lsz_byte = ((size_t)lsz) << 1; + size_t size; + int ret = 0; + int i; + + /* + * global IPBUF + */ + if (((unsigned long)base) & 0x3) { + printk(KERN_ERR + "omapdsp: global ipbuf address(0x%p) is not " + "32-bit aligned!\n", base); + return -EINVAL; + } + size = lsz_byte * ln; + if (dsp_address_validate(base, size, "global ipbuf") < 0) + return -EINVAL; + + g_ipbuf = kmalloc(sizeof(struct ipbuf_head) * ln, GFP_KERNEL); + if (g_ipbuf == NULL) { + printk(KERN_ERR + "omapdsp: memory allocation for ipbuf failed.\n"); + return -ENOMEM; + } + for (i = 0; i < ln; i++) { + void *top, *btm; + + top = base + (sizeof(struct ipbuf) + lsz_byte) * i; + btm = base + (sizeof(struct ipbuf) + lsz_byte) * (i+1) - 1; + g_ipbuf[i].p = (struct ipbuf *)top; + g_ipbuf[i].bid = i; + if (((unsigned long)top & 0xfffe0000) != + ((unsigned long)btm & 0xfffe0000)) { + /* + * an ipbuf line should not cross + * 64k-word boundary. + */ + printk(KERN_ERR + "omapdsp: ipbuf[%d] crosses 64k-word boundary!\n" + " @0x%p, size=0x%08x\n", i, top, lsz_byte); + ret = -EINVAL; + goto free_out; + } + } + ipbcfg.ln = ln; + ipbcfg.lsz = lsz; + ipbcfg.base = base; + ipbcfg.bsycnt = ln; /* DSP holds all ipbufs initially. */ + ipbcfg.cnt_full = 0; + + pr_info("omapdsp: IPBUF configuration\n" + " %d words * %d lines at 0x%p.\n", + ipbcfg.lsz, ipbcfg.ln, ipbcfg.base); + + ret = device_create_file(omap_dsp->dev, &dev_attr_ipbuf); + if (ret) + printk(KERN_ERR "device_create_file failed: %d\n", ret); + + return ret; + + free_out: + kfree(g_ipbuf); + g_ipbuf = NULL; + return ret; +} + +int ipbuf_sys_config(void *p, arm_dsp_dir_t dir) +{ + char *dir_str = (dir == DIR_D2A) ? "D2A" : "A2D"; + + if (((unsigned long)p) & 0x3) { + printk(KERN_ERR + "omapdsp: system ipbuf(%s) address(0x%p) is " + "not 32-bit aligned!\n", dir_str, p); + return -1; + } + if (dsp_address_validate(p, sizeof(struct ipbuf_sys), + "system ipbuf(%s)", dir_str) < 0) + return -1; + if (dsp_mem_type(p, sizeof(struct ipbuf_sys)) != MEM_TYPE_EXTERN) { + printk(KERN_WARNING + "omapdsp: system ipbuf(%s) is placed in" + " DSP internal memory.\n" + " It will prevent DSP from idling.\n", dir_str); + ipbuf_sys_hold_mem_active++; + /* + * dsp_mem_enable() never fails because + * it has been already enabled in dspcfg process and + * this will just increment the usecount. + */ + dsp_mem_enable((void *)daram_base); + } + + if (dir == DIR_D2A) + ipbuf_sys_da = p; + else + ipbuf_sys_ad = p; + + return 0; +} + +int ipbuf_p_validate(void *p, arm_dsp_dir_t dir) +{ + char *dir_str = (dir == DIR_D2A) ? "D2A" : "A2D"; + + if (((unsigned long)p) & 0x3) { + printk(KERN_ERR + "omapdsp: private ipbuf(%s) address(0x%p) is " + "not 32-bit aligned!\n", dir_str, p); + return -1; + } + return dsp_address_validate(p, sizeof(struct ipbuf_p), + "private ipbuf(%s)", dir_str); +} + +/* + * Global IPBUF operations + */ +struct ipbuf_head *bid_to_ipbuf(u16 bid) +{ + return &g_ipbuf[bid]; +} + +struct ipbuf_head *get_free_ipbuf(u8 tid) +{ + struct ipbuf_head *ipb_h; + + if (dsp_mem_enable_ipbuf() < 0) + return NULL; + + spin_lock(&ipb_free.lock); + + if (ipblink_empty(&ipb_free)) { + /* FIXME: wait on queue when not available. */ + ipb_h = NULL; + goto out; + } + ipb_h = &g_ipbuf[ipb_free.top]; + ipb_h->p->la = tid; /* lock */ + __ipblink_del_top(&ipb_free); +out: + spin_unlock(&ipb_free.lock); + dsp_mem_disable_ipbuf(); + + return ipb_h; +} + +void release_ipbuf(struct ipbuf_head *ipb_h) +{ + if (ipb_h->p->la == TID_FREE) { + printk(KERN_WARNING + "omapdsp: attempt to release unlocked IPBUF[%d].\n", + ipb_h->bid); + /* + * FIXME: re-calc bsycnt + */ + return; + } + ipb_h->p->la = TID_FREE; + ipb_h->p->sa = TID_FREE; + ipblink_add_tail(&ipb_free, ipb_h->bid); +} + +static int try_yld(struct ipbuf_head *ipb_h) +{ + int status; + + ipb_h->p->sa = TID_ANON; + status = mbcompose_send(BKYLD, 0, ipb_h->bid); + if (status < 0) { + /* DSP is busy and ARM keeps this line. */ + release_ipbuf(ipb_h); + return status; + } + + ipb_bsycnt_inc(&ipbcfg); + return 0; +} + +/* + * balancing ipbuf lines with DSP + */ +static void do_balance_ipbuf(struct work_struct *unused) +{ + while (ipbcfg.bsycnt <= ipbcfg.ln / 4) { + struct ipbuf_head *ipb_h; + + if ((ipb_h = get_free_ipbuf(TID_ANON)) == NULL) + return; + if (try_yld(ipb_h) < 0) + return; + } +} + +static DECLARE_WORK(balance_ipbuf_work, do_balance_ipbuf); + +void balance_ipbuf(void) +{ + schedule_work(&balance_ipbuf_work); +} + +/* for process context */ +void unuse_ipbuf(struct ipbuf_head *ipb_h) +{ + if (ipbcfg.bsycnt > ipbcfg.ln / 4) { + /* we don't have enough IPBUF lines. let's keep it. */ + release_ipbuf(ipb_h); + } else { + /* we have enough IPBUF lines. let's return this line to DSP. */ + ipb_h->p->la = TID_ANON; + try_yld(ipb_h); + balance_ipbuf(); + } +} + +/* for interrupt context */ +void unuse_ipbuf_nowait(struct ipbuf_head *ipb_h) +{ + release_ipbuf(ipb_h); + balance_ipbuf(); +} + +/* + * functions called from mailbox interrupt routine + */ + +void mbox_err_ipbfull(void) +{ + ipbcfg.cnt_full++; +} + +/* + * sysfs files + */ +static ssize_t ipbuf_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int len = 0; + u16 bid; + + for (bid = 0; bid < ipbcfg.ln; bid++) { + struct ipbuf_head *ipb_h = &g_ipbuf[bid]; + u16 la = ipb_h->p->la; + u16 ld = ipb_h->p->ld; + u16 c = ipb_h->p->c; + + if (len > PAGE_SIZE - 100) { + len += sprintf(buf + len, "out of buffer.\n"); + goto finish; + } + + len += sprintf(buf + len, "ipbuf[%d]: adr = 0x%p\n", + bid, ipb_h->p); + if (la == TID_FREE) { + len += sprintf(buf + len, + " DSPtask[%d]->Linux " + "(already read and now free for Linux)\n", + ld); + } else if (ld == TID_FREE) { + len += sprintf(buf + len, + " Linux->DSPtask[%d] " + "(already read and now free for DSP)\n", + la); + } else if (ipbuf_is_held(ld, bid)) { + len += sprintf(buf + len, + " DSPtask[%d]->Linux " + "(waiting to be read)\n" + " count = %d\n", ld, c); + } else { + len += sprintf(buf + len, + " Linux->DSPtask[%d] " + "(waiting to be read)\n" + " count = %d\n", la, c); + } + } + + len += sprintf(buf + len, "\nFree IPBUF link: "); + spin_lock(&ipb_free.lock); + ipblink_for_each(bid, &ipb_free) { + len += sprintf(buf + len, "%d ", bid); + } + spin_unlock(&ipb_free.lock); + len += sprintf(buf + len, "\n"); + len += sprintf(buf + len, "IPBFULL error count: %ld\n", + ipbcfg.cnt_full); + +finish: + return len; +} diff --cc drivers/dsp/dspgateway/mblog.c index 2b1e113794c,00000000000..7e008afb6c9 mode 100644,000000..100644 --- a/drivers/dsp/dspgateway/mblog.c +++ b/drivers/dsp/dspgateway/mblog.c @@@ -1,280 -1,0 +1,280 @@@ +/* + * This file is part of OMAP DSP driver (DSP Gateway version 3.3.1) + * + * Copyright (C) 2003-2006 Nokia Corporation. All rights reserved. + * + * Contact: Toshihiro Kobayashi + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include - #include ++#include +#include "dsp_mbcmd.h" +#include "dsp.h" + +char *subcmd_name(struct mbcmd *mb) +{ + u8 cmd_h = mb->cmd_h; + u8 cmd_l = mb->cmd_l; + char *s; + + switch (cmd_h) { + case MBOX_CMD_DSP_RUNLEVEL: + s = (cmd_l == RUNLEVEL_USER) ? "USER": + (cmd_l == RUNLEVEL_SUPER) ? "SUPER": + (cmd_l == RUNLEVEL_RECOVERY) ? "RECOVERY": + NULL; + break; + case MBOX_CMD_DSP_PM: + s = (cmd_l == PM_DISABLE) ? "DISABLE": + (cmd_l == PM_ENABLE) ? "ENABLE": + NULL; + break; + case MBOX_CMD_DSP_KFUNC: + s = (cmd_l == KFUNC_FBCTL) ? "FBCTL": + (cmd_l == KFUNC_POWER) ? + ((mb->data == AUDIO_PWR_UP) ? "PWR AUD /UP": + (mb->data == AUDIO_PWR_DOWN) ? "PWR AUD /DOWN": + (mb->data == AUDIO_PWR_DOWN2) ? "PWR AUD /DOWN(2)": + (mb->data == DSP_PWR_UP) ? "PWR DSP /UP": + (mb->data == DSP_PWR_DOWN) ? "PWR DSP /DOWN": + (mb->data == DVFS_START) ? "PWR DVFS/START": + (mb->data == DVFS_STOP) ? "PWR DVFS/STOP": + NULL): + + NULL; + break; + case MBOX_CMD_DSP_DSPCFG: + { + u8 cfgc = cmd_l & 0x7f; + s = (cfgc == DSPCFG_REQ) ? "REQ": + (cfgc == DSPCFG_SYSADRH) ? "SYSADRH": + (cfgc == DSPCFG_SYSADRL) ? "SYSADRL": + (cfgc == DSPCFG_ABORT) ? "ABORT": + (cfgc == DSPCFG_PROTREV) ? "PROTREV": + NULL; + break; + } + case MBOX_CMD_DSP_REGRW: + s = (cmd_l == REGRW_MEMR) ? "MEMR": + (cmd_l == REGRW_MEMW) ? "MEMW": + (cmd_l == REGRW_IOR) ? "IOR": + (cmd_l == REGRW_IOW) ? "IOW": + (cmd_l == REGRW_DATA) ? "DATA": + NULL; + break; + case MBOX_CMD_DSP_GETVAR: + case MBOX_CMD_DSP_SETVAR: + s = (cmd_l == VARID_ICRMASK) ? "ICRMASK": + (cmd_l == VARID_LOADINFO) ? "LOADINFO": + NULL; + break; + case MBOX_CMD_DSP_ERR: + s = (cmd_l == EID_BADTID) ? "BADTID": + (cmd_l == EID_BADTCN) ? "BADTCN": + (cmd_l == EID_BADBID) ? "BADBID": + (cmd_l == EID_BADCNT) ? "BADCNT": + (cmd_l == EID_NOTLOCKED) ? "NOTLOCKED": + (cmd_l == EID_STVBUF) ? "STVBUF": + (cmd_l == EID_BADADR) ? "BADADR": + (cmd_l == EID_BADTCTL) ? "BADTCTL": + (cmd_l == EID_BADPARAM) ? "BADPARAM": + (cmd_l == EID_FATAL) ? "FATAL": + (cmd_l == EID_WDT) ? "WDT": + (cmd_l == EID_NOMEM) ? "NOMEM": + (cmd_l == EID_NORES) ? "NORES": + (cmd_l == EID_IPBFULL) ? "IPBFULL": + (cmd_l == EID_TASKNOTRDY) ? "TASKNOTRDY": + (cmd_l == EID_TASKBSY) ? "TASKBSY": + (cmd_l == EID_TASKERR) ? "TASKERR": + (cmd_l == EID_BADCFGTYP) ? "BADCFGTYP": + (cmd_l == EID_DEBUG) ? "DEBUG": + (cmd_l == EID_BADSEQ) ? "BADSEQ": + (cmd_l == EID_BADCMD) ? "BADCMD": + NULL; + break; + default: + s = NULL; + } + + return s; +} + +/* output of show() method should fit to PAGE_SIZE */ +#define MBLOG_DEPTH 64 + +struct mblogent { + unsigned long jiffies; + mbox_msg_t msg; + arm_dsp_dir_t dir; +}; + +static struct { + spinlock_t lock; + int wp; + unsigned long cnt, cnt_ad, cnt_da; + struct mblogent ent[MBLOG_DEPTH]; +} mblog = { + .lock = SPIN_LOCK_UNLOCKED, +}; + +#ifdef CONFIG_OMAP_DSP_MBCMD_VERBOSE +static inline void mblog_print_cmd(struct mbcmd *mb, arm_dsp_dir_t dir) +{ + const struct cmdinfo *ci = cmdinfo[mb->cmd_h]; + char *dir_str; + char *subname; + + dir_str = (dir == DIR_A2D) ? "sending " : "receiving"; + switch (ci->cmd_l_type) { + case CMD_L_TYPE_SUBCMD: + subname = subcmd_name(mb); + if (unlikely(!subname)) + subname = "Unknown"; + pr_debug("mbox: %s seq=%d, cmd=%02x:%02x(%s:%s), data=%04x\n", + dir_str, mb->seq, mb->cmd_h, mb->cmd_l, + ci->name, subname, mb->data); + break; + case CMD_L_TYPE_TID: + pr_debug("mbox: %s seq=%d, cmd=%02x:%02x(%s:task %d), data=%04x\n", + dir_str, mb->seq, mb->cmd_h, mb->cmd_l, + ci->name, mb->cmd_l, mb->data); + break; + case CMD_L_TYPE_NULL: + pr_debug("mbox: %s seq=%d, cmd=%02x:%02x(%s), data=%04x\n", + dir_str, mb->seq, mb->cmd_h, mb->cmd_l, + ci->name, mb->data); + break; + } +} +#else +static inline void mblog_print_cmd(struct mbcmd *mb, arm_dsp_dir_t dir) { } +#endif + +void mblog_add(struct mbcmd *mb, arm_dsp_dir_t dir) +{ + struct mblogent *ent; + + spin_lock(&mblog.lock); + ent = &mblog.ent[mblog.wp]; + ent->jiffies = jiffies; + ent->msg = *(mbox_msg_t *)mb; + ent->dir = dir; + if (mblog.cnt < 0xffffffff) + mblog.cnt++; + switch (dir) { + case DIR_A2D: + if (mblog.cnt_ad < 0xffffffff) + mblog.cnt_ad++; + break; + case DIR_D2A: + if (mblog.cnt_da < 0xffffffff) + mblog.cnt_da++; + break; + } + if (++mblog.wp == MBLOG_DEPTH) + mblog.wp = 0; + spin_unlock(&mblog.lock); + + mblog_print_cmd(mb, dir); +} + +/* + * sysfs file + */ +static ssize_t mblog_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int len = 0; + int wp; + int i; + + spin_lock(&mblog.lock); + + wp = mblog.wp; + len += sprintf(buf + len, + "log count:%ld / ARM->DSP:%ld, DSP->ARM:%ld\n", + mblog.cnt, mblog.cnt_ad, mblog.cnt_da); + if (mblog.cnt == 0) + goto done; + + len += sprintf(buf + len, " ARM->DSP ARM<-DSP\n"); + len += sprintf(buf + len, " jiffies cmd data cmd data\n"); + i = (mblog.cnt >= MBLOG_DEPTH) ? wp : 0; + do { + struct mblogent *ent = &mblog.ent[i]; + struct mbcmd *mb = (struct mbcmd *)&ent->msg; + char *subname; + struct cmdinfo ci_null = { + .name = "Unknown", + .cmd_l_type = CMD_L_TYPE_NULL, + }; + const struct cmdinfo *ci; + + len += sprintf(buf + len, + (ent->dir == DIR_A2D) ? + "%08lx %04x %04x ": + "%08lx %04x %04x ", + ent->jiffies, + (ent->msg >> 16) & 0x7fff, ent->msg & 0xffff); + + if ((ci = cmdinfo[mb->cmd_h]) == NULL) + ci = &ci_null; + + switch (ci->cmd_l_type) { + case CMD_L_TYPE_SUBCMD: + if ((subname = subcmd_name(mb)) == NULL) + subname = "Unknown"; + len += sprintf(buf + len, "%s:%s\n", + ci->name, subname); + break; + case CMD_L_TYPE_TID: + len += sprintf(buf + len, "%s:task %d\n", + ci->name, mb->cmd_l); + break; + case CMD_L_TYPE_NULL: + len += sprintf(buf + len, "%s\n", ci->name); + break; + } + + if (++i == MBLOG_DEPTH) + i = 0; + } while (i != wp); + +done: + spin_unlock(&mblog.lock); + + return len; +} + +static struct device_attribute dev_attr_mblog = __ATTR_RO(mblog); + +void __init mblog_init(void) +{ + int ret; + + ret = device_create_file(omap_dsp->dev, &dev_attr_mblog); + if (ret) + printk(KERN_ERR "device_create_file failed: %d\n", ret); +} + +void mblog_exit(void) +{ + device_remove_file(omap_dsp->dev, &dev_attr_mblog); +} diff --cc drivers/dsp/dspgateway/omap2_dsp.h index 0dc43f0edcb,00000000000..4ac51806eeb mode 100644,000000..100644 --- a/drivers/dsp/dspgateway/omap2_dsp.h +++ b/drivers/dsp/dspgateway/omap2_dsp.h @@@ -1,95 -1,0 +1,95 @@@ +/* + * This file is part of OMAP DSP driver (DSP Gateway version 3.3.1) + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * Contact: Toshihiro Kobayashi + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __OMAP_DSP_OMAP2_DSP_H +#define __OMAP_DSP_OMAP2_DSP_H + +#ifdef CONFIG_ARCH_OMAP24XX +#define OMAP24XX_DARAM_BASE (DSP_MEM_24XX_VIRT + 0x0) +#define OMAP24XX_DARAM_SIZE 0x10000 +#define OMAP24XX_SARAM_BASE (DSP_MEM_24XX_VIRT + 0x10000) +#define OMAP24XX_SARAM_SIZE 0x18000 +#endif + - #include ++#include + +/* + * DSP IPI registers: mapped to 0xe1000000 -- use readX(), writeX() + */ +#ifdef CONFIG_ARCH_OMAP24XX +#define DSP_IPI_BASE DSP_IPI_24XX_VIRT +#endif + +#ifdef CONFIG_ARCH_OMAP34XX +#define DSP_IPI_BASE DSP_IPI_34XX_VIRT +#endif + +#define DSP_IPI_REVISION (DSP_IPI_BASE + 0x00) +#define DSP_IPI_SYSCONFIG (DSP_IPI_BASE + 0x10) +#define DSP_IPI_INDEX (DSP_IPI_BASE + 0x40) +#define DSP_IPI_ENTRY (DSP_IPI_BASE + 0x44) +#define DSP_IPI_ENABLE (DSP_IPI_BASE + 0x48) +#define DSP_IPI_IOMAP (DSP_IPI_BASE + 0x4c) +#define DSP_IPI_DSPBOOTCONFIG (DSP_IPI_BASE + 0x50) + +#define DSP_IPI_ENTRY_ELMSIZEVALUE_MASK 0x00000003 +#define DSP_IPI_ENTRY_ELMSIZEVALUE_8 0x00000000 +#define DSP_IPI_ENTRY_ELMSIZEVALUE_16 0x00000001 +#define DSP_IPI_ENTRY_ELMSIZEVALUE_32 0x00000002 + +#define DSP_BOOT_CONFIG_DIRECT 0x00000000 +#define DSP_BOOT_CONFIG_PSD_DIRECT 0x00000001 +#define DSP_BOOT_CONFIG_IDLE 0x00000002 +#define DSP_BOOT_CONFIG_DL16 0x00000003 +#define DSP_BOOT_CONFIG_DL32 0x00000004 +#define DSP_BOOT_CONFIG_API 0x00000005 +#define DSP_BOOT_CONFIG_INTERNAL 0x00000006 + +/* + * DSP boot mode + * direct: 0xffff00 + * pseudo direct: 0x080000 + * API: branch 0x010000 + * internel: branch 0x024000 + */ +#define DSP_BOOT_ADR_DIRECT 0xffff00 +#define DSP_BOOT_ADR_PSD_DIRECT 0x080000 +#define DSP_BOOT_ADR_API 0x010000 +#define DSP_BOOT_ADR_INTERNAL 0x024000 + +/* + * DSP ICR + */ +#define DSPREG_ICR_RESERVED_BITS 0xfc00 +#define DSPREG_ICR_HWA 0x0200 +#define DSPREG_ICR_IPORT 0x0100 +#define DSPREG_ICR_MPORT 0x0080 +#define DSPREG_ICR_XPORT 0x0040 +#define DSPREG_ICR_DPORT 0x0020 +#define DSPREG_ICR_DPLL 0x0010 +#define DSPREG_ICR_PER 0x0008 +#define DSPREG_ICR_CACHE 0x0004 +#define DSPREG_ICR_DMA 0x0002 +#define DSPREG_ICR_CPU 0x0001 + +#endif /* __OMAP_DSP_OMAP2_DSP_H */ diff --cc drivers/dsp/dspgateway/task.c index ab189349507,00000000000..a371911bffa mode 100644,000000..100644 --- a/drivers/dsp/dspgateway/task.c +++ b/drivers/dsp/dspgateway/task.c @@@ -1,3041 -1,0 +1,3041 @@@ +/* + * This file is part of OMAP DSP driver (DSP Gateway version 3.3.1) + * + * Copyright (C) 2002-2006 Nokia Corporation. All rights reserved. + * + * Contact: Toshihiro Kobayashi + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include - #include - #include ++#include ++#include +#include "uaccess_dsp.h" +#include "dsp_mbcmd.h" +#include "dsp.h" +#include "ipbuf.h" +#include "proclist.h" + +/* + * devstate: task device state machine + * NOTASK: task is not attached. + * ATTACHED: task is attached. + * GARBAGE: task is detached. waiting for all processes to close this device. + * ADDREQ: requesting for tadd + * DELREQ: requesting for tdel. no process is opening this device. + * FREEZED: task is attached, but reserved to be killed. + * ADDFAIL: tadd failed. + * ADDING: tadd in process. + * DELING: tdel in process. + * KILLING: tkill in process. + */ +#define TASKDEV_ST_NOTASK 0x00000001 +#define TASKDEV_ST_ATTACHED 0x00000002 +#define TASKDEV_ST_GARBAGE 0x00000004 +#define TASKDEV_ST_INVALID 0x00000008 +#define TASKDEV_ST_ADDREQ 0x00000100 +#define TASKDEV_ST_DELREQ 0x00000200 +#define TASKDEV_ST_FREEZED 0x00000400 +#define TASKDEV_ST_ADDFAIL 0x00001000 +#define TASKDEV_ST_ADDING 0x00010000 +#define TASKDEV_ST_DELING 0x00020000 +#define TASKDEV_ST_KILLING 0x00040000 +#define TASKDEV_ST_STATE_MASK 0x7fffffff +#define TASKDEV_ST_STALE 0x80000000 + +static struct { + long state; + char *name; +} devstate_desc[] = { + { TASKDEV_ST_NOTASK, "notask" }, + { TASKDEV_ST_ATTACHED, "attached" }, + { TASKDEV_ST_GARBAGE, "garbage" }, + { TASKDEV_ST_INVALID, "invalid" }, + { TASKDEV_ST_ADDREQ, "addreq" }, + { TASKDEV_ST_DELREQ, "delreq" }, + { TASKDEV_ST_FREEZED, "freezed" }, + { TASKDEV_ST_ADDFAIL, "addfail" }, + { TASKDEV_ST_ADDING, "adding" }, + { TASKDEV_ST_DELING, "deling" }, + { TASKDEV_ST_KILLING, "killing" }, +}; + +static char *devstate_name(long state) +{ + int i; + int max = ARRAY_SIZE(devstate_desc); + + for (i = 0; i < max; i++) { + if (state & devstate_desc[i].state) + return devstate_desc[i].name; + } + return "unknown"; +} + +struct rcvdt_bk_struct { + struct ipblink link; + unsigned int rp; +}; + +struct taskdev { + struct bus_type *bus; + struct device dev; /* Generic device interface */ + + long state; + struct rw_semaphore state_sem; + wait_queue_head_t state_wait_q; + struct mutex usecount_lock; + unsigned int usecount; + char name[TNM_LEN]; + struct file_operations fops; + spinlock_t proc_list_lock; + struct list_head proc_list; + struct dsptask *task; + + /* read stuff */ + wait_queue_head_t read_wait_q; + struct mutex read_mutex; + spinlock_t read_lock; + union { + struct kfifo *fifo; /* for active word */ + struct rcvdt_bk_struct bk; + } rcvdt; + + /* write stuff */ + wait_queue_head_t write_wait_q; + struct mutex write_mutex; + spinlock_t wsz_lock; + size_t wsz; + + /* tctl stuff */ + wait_queue_head_t tctl_wait_q; + struct mutex tctl_mutex; + int tctl_stat; + int tctl_ret; /* return value for tctl_show() */ + + /* device lock */ + struct mutex lock; + pid_t lock_pid; +}; + +#define to_taskdev(n) container_of(n, struct taskdev, dev) + +struct dsptask { + enum { + TASK_ST_ERR = 0, + TASK_ST_READY, + TASK_ST_CFGREQ + } state; + u8 tid; + char name[TNM_LEN]; + u16 ttyp; + struct taskdev *dev; + + /* read stuff */ + struct ipbuf_p *ipbuf_pvt_r; + + /* write stuff */ + struct ipbuf_p *ipbuf_pvt_w; + + /* mmap stuff */ + void *map_base; + size_t map_length; +}; + +#define sndtyp_acv(ttyp) ((ttyp) & TTYP_ASND) +#define sndtyp_psv(ttyp) (!((ttyp) & TTYP_ASND)) +#define sndtyp_bk(ttyp) ((ttyp) & TTYP_BKDM) +#define sndtyp_wd(ttyp) (!((ttyp) & TTYP_BKDM)) +#define sndtyp_pvt(ttyp) ((ttyp) & TTYP_PVDM) +#define sndtyp_gbl(ttyp) (!((ttyp) & TTYP_PVDM)) +#define rcvtyp_acv(ttyp) ((ttyp) & TTYP_ARCV) +#define rcvtyp_psv(ttyp) (!((ttyp) & TTYP_ARCV)) +#define rcvtyp_bk(ttyp) ((ttyp) & TTYP_BKMD) +#define rcvtyp_wd(ttyp) (!((ttyp) & TTYP_BKMD)) +#define rcvtyp_pvt(ttyp) ((ttyp) & TTYP_PVMD) +#define rcvtyp_gbl(ttyp) (!((ttyp) & TTYP_PVMD)) + +static inline int has_taskdev_lock(struct taskdev *dev); +static int dsp_rmdev_minor(unsigned char minor); +static int taskdev_init(struct taskdev *dev, char *name, unsigned char minor); +static void taskdev_delete(unsigned char minor); +static int taskdev_attach_task(struct taskdev *dev, struct dsptask *task); +static int dsp_tdel_bh(struct taskdev *dev, u16 type); + +static struct bus_type dsptask_bus = { + .name = "dsptask", +}; + +static struct class *dsp_task_class; +static DEFINE_MUTEX(devmgr_lock); +static struct taskdev *taskdev[TASKDEV_MAX]; +static struct dsptask *dsptask[TASKDEV_MAX]; +static DEFINE_MUTEX(cfg_lock); +static u16 cfg_cmd; +static u8 cfg_tid; +static DECLARE_WAIT_QUEUE_HEAD(cfg_wait_q); +static u8 n_task; /* static task count */ +static void *heap; + +#define is_dynamic_task(tid) ((tid) >= n_task) + +#define devstate_read_lock(dev, devstate) \ + devstate_read_lock_timeout(dev, devstate, 0) +#define devstate_read_unlock(dev) up_read(&(dev)->state_sem) +#define devstate_write_lock(dev, devstate) \ + devstate_write_lock_timeout(dev, devstate, 0) +#define devstate_write_unlock(dev) up_write(&(dev)->state_sem) + +static ssize_t devname_show(struct device *d, struct device_attribute *attr, + char *buf); +static ssize_t devstate_show(struct device *d, struct device_attribute *attr, + char *buf); +static ssize_t proc_list_show(struct device *d, struct device_attribute *attr, + char *buf); +static ssize_t taskname_show(struct device *d, struct device_attribute *attr, + char *buf); +static ssize_t ttyp_show(struct device *d, struct device_attribute *attr, + char *buf); +static ssize_t fifosz_show(struct device *d, struct device_attribute *attr, + char *buf); +static int fifosz_store(struct device *d, struct device_attribute *attr, + const char *buf, size_t count); +static ssize_t fifocnt_show(struct device *d, struct device_attribute *attr, + char *buf); +static ssize_t ipblink_show(struct device *d, struct device_attribute *attr, + char *buf); +static ssize_t wsz_show(struct device *d, struct device_attribute *attr, + char *buf); +static ssize_t mmap_show(struct device *d, struct device_attribute *attr, + char *buf); + +#define __ATTR_RW(_name,_mode) { \ + .attr = {.name = __stringify(_name), .mode = _mode, .owner = THIS_MODULE }, \ + .show = _name##_show, \ + .store = _name##_store, \ +} + +static struct device_attribute dev_attr_devname = __ATTR_RO(devname); +static struct device_attribute dev_attr_devstate = __ATTR_RO(devstate); +static struct device_attribute dev_attr_proc_list = __ATTR_RO(proc_list); +static struct device_attribute dev_attr_taskname = __ATTR_RO(taskname); +static struct device_attribute dev_attr_ttyp = __ATTR_RO(ttyp); +static struct device_attribute dev_attr_fifosz = __ATTR_RW(fifosz, 0666); +static struct device_attribute dev_attr_fifocnt = __ATTR_RO(fifocnt); +static struct device_attribute dev_attr_ipblink = __ATTR_RO(ipblink); +static struct device_attribute dev_attr_wsz = __ATTR_RO(wsz); +static struct device_attribute dev_attr_mmap = __ATTR_RO(mmap); + +static inline void set_taskdev_state(struct taskdev *dev, int state) +{ + pr_debug("omapdsp: devstate: CHANGE %s[%d]:\"%s\"->\"%s\"\n", + dev->name, + (dev->task ? dev->task->tid : -1), + devstate_name(dev->state), + devstate_name(state)); + dev->state = state; +} + +/* + * devstate_read_lock_timeout() + * devstate_write_lock_timeout(): + * timeout != 0: dev->state can be diffeent from what you want. + * timeout == 0: no timeout + */ +#define BUILD_DEVSTATE_LOCK_TIMEOUT(rw) \ +static int devstate_##rw##_lock_timeout(struct taskdev *dev, long devstate, \ + int timeout) \ +{ \ + DEFINE_WAIT(wait); \ + down_##rw(&dev->state_sem); \ + while (!(dev->state & devstate)) { \ + up_##rw(&dev->state_sem); \ + prepare_to_wait(&dev->state_wait_q, &wait, TASK_INTERRUPTIBLE); \ + if (!timeout) \ + timeout = MAX_SCHEDULE_TIMEOUT; \ + timeout = schedule_timeout(timeout); \ + finish_wait(&dev->state_wait_q, &wait); \ + if (timeout == 0) \ + return -ETIME; \ + if (signal_pending(current)) \ + return -EINTR; \ + down_##rw(&dev->state_sem); \ + } \ + return 0; \ +} +BUILD_DEVSTATE_LOCK_TIMEOUT(read) +BUILD_DEVSTATE_LOCK_TIMEOUT(write) + +#define BUILD_DEVSTATE_LOCK_AND_TEST(rw) \ +static int devstate_##rw##_lock_and_test(struct taskdev *dev, long devstate) \ +{ \ + down_##rw(&dev->state_sem); \ + if (dev->state & devstate) \ + return 1; /* success */ \ + /* failure */ \ + up_##rw(&dev->state_sem); \ + return 0; \ +} +BUILD_DEVSTATE_LOCK_AND_TEST(read) +BUILD_DEVSTATE_LOCK_AND_TEST(write) + +static int taskdev_lock_interruptible(struct taskdev *dev, + struct mutex *lock) +{ + int ret; + + if (has_taskdev_lock(dev)) + ret = mutex_lock_interruptible(lock); + else { + if ((ret = mutex_lock_interruptible(&dev->lock)) != 0) + return ret; + ret = mutex_lock_interruptible(lock); + mutex_unlock(&dev->lock); + } + + return ret; +} + +static int taskdev_lock_and_statelock_attached(struct taskdev *dev, + struct mutex *lock) +{ + int ret; + + if (!devstate_read_lock_and_test(dev, TASKDEV_ST_ATTACHED)) + return -ENODEV; + + if ((ret = taskdev_lock_interruptible(dev, lock)) != 0) + devstate_read_unlock(dev); + + return ret; +} + +static inline void taskdev_unlock_and_stateunlock(struct taskdev *dev, + struct mutex *lock) +{ + mutex_unlock(lock); + devstate_read_unlock(dev); +} + +/* + * taskdev_flush_buf() + * must be called under state_lock(ATTACHED) and dev->read_mutex. + */ +static int taskdev_flush_buf(struct taskdev *dev) +{ + u16 ttyp = dev->task->ttyp; + + if (sndtyp_wd(ttyp)) { + /* word receiving */ + kfifo_reset(dev->rcvdt.fifo); + } else { + /* block receiving */ + struct rcvdt_bk_struct *rcvdt = &dev->rcvdt.bk; + + if (sndtyp_gbl(ttyp)) + ipblink_flush(&rcvdt->link); + else { + ipblink_flush_pvt(&rcvdt->link); + release_ipbuf_pvt(dev->task->ipbuf_pvt_r); + } + } + + return 0; +} + +/* + * taskdev_set_fifosz() + * must be called under dev->read_mutex. + */ +static int taskdev_set_fifosz(struct taskdev *dev, unsigned long sz) +{ + u16 ttyp = dev->task->ttyp; + + if (!(sndtyp_wd(ttyp) && sndtyp_acv(ttyp))) { + printk(KERN_ERR + "omapdsp: buffer size can be changed only for " + "active word sending task.\n"); + return -EINVAL; + } + if ((sz == 0) || (sz & 1)) { + printk(KERN_ERR "omapdsp: illegal buffer size! (%ld)\n" + "it must be even and non-zero value.\n", sz); + return -EINVAL; + } + + if (kfifo_len(dev->rcvdt.fifo)) { + printk(KERN_ERR "omapdsp: buffer is not empty!\n"); + return -EIO; + } + + kfifo_free(dev->rcvdt.fifo); + dev->rcvdt.fifo = kfifo_alloc(sz, GFP_KERNEL, &dev->read_lock); + if (IS_ERR(dev->rcvdt.fifo)) { + printk(KERN_ERR + "omapdsp: unable to change receive buffer size. " + "(%ld bytes for %s)\n", sz, dev->name); + return -ENOMEM; + } + + return 0; +} + +static inline int has_taskdev_lock(struct taskdev *dev) +{ + return (dev->lock_pid == current->pid); +} + +static int taskdev_lock(struct taskdev *dev) +{ + if (mutex_lock_interruptible(&dev->lock)) + return -EINTR; + dev->lock_pid = current->pid; + return 0; +} + +static int taskdev_unlock(struct taskdev *dev) +{ + if (!has_taskdev_lock(dev)) { + printk(KERN_ERR + "omapdsp: an illegal process attempted to " + "unlock the dsptask lock!\n"); + return -EINVAL; + } + dev->lock_pid = 0; + mutex_unlock(&dev->lock); + return 0; +} + +static int dsp_task_config(struct dsptask *task, u8 tid) +{ + u16 ttyp; + int ret; + + task->tid = tid; + dsptask[tid] = task; + + /* TCFG request */ + task->state = TASK_ST_CFGREQ; + if (mutex_lock_interruptible(&cfg_lock)) { + ret = -EINTR; + goto fail_out; + } + cfg_cmd = MBOX_CMD_DSP_TCFG; + mbcompose_send_and_wait(TCFG, tid, 0, &cfg_wait_q); + cfg_cmd = 0; + mutex_unlock(&cfg_lock); + + if (task->state != TASK_ST_READY) { + printk(KERN_ERR "omapdsp: task %d configuration error!\n", tid); + ret = -EINVAL; + goto fail_out; + } + + if (strlen(task->name) <= 1) + sprintf(task->name, "%d", tid); + pr_info("omapdsp: task %d: name %s\n", tid, task->name); + + ttyp = task->ttyp; + + /* + * task info sanity check + */ + + /* task type check */ + if (rcvtyp_psv(ttyp) && rcvtyp_pvt(ttyp)) { + printk(KERN_ERR "omapdsp: illegal task type(0x%04x), tid=%d\n", + tid, ttyp); + ret = -EINVAL; + goto fail_out; + } + + /* private buffer address check */ + if (sndtyp_pvt(ttyp) && + (ipbuf_p_validate(task->ipbuf_pvt_r, DIR_D2A) < 0)) { + ret = -EINVAL; + goto fail_out; + } + if (rcvtyp_pvt(ttyp) && + (ipbuf_p_validate(task->ipbuf_pvt_w, DIR_A2D) < 0)) { + ret = -EINVAL; + goto fail_out; + } + + /* mmap buffer configuration check */ + if ((task->map_length > 0) && + ((!ALIGN((unsigned long)task->map_base, PAGE_SIZE)) || + (!ALIGN(task->map_length, PAGE_SIZE)) || + (dsp_mem_type(task->map_base, task->map_length) != MEM_TYPE_EXTERN))) { + printk(KERN_ERR + "omapdsp: illegal mmap buffer address(0x%p) or " + "length(0x%x).\n" + " It needs to be page-aligned and located at " + "external memory.\n", + task->map_base, task->map_length); + ret = -EINVAL; + goto fail_out; + } + + return 0; + +fail_out: + dsptask[tid] = NULL; + return ret; +} + +static void dsp_task_init(struct dsptask *task) +{ + mbcompose_send(TCTL, task->tid, TCTL_TINIT); +} + +int dsp_task_config_all(u8 n) +{ + int i, ret; + struct taskdev *devheap; + struct dsptask *taskheap; + size_t devheapsz, taskheapsz; + + pr_info("omapdsp: found %d task(s)\n", n); + if (n == 0) + return 0; + + /* + * reducing kmalloc! + */ + devheapsz = sizeof(struct taskdev) * n; + taskheapsz = sizeof(struct dsptask) * n; + heap = kzalloc(devheapsz + taskheapsz, GFP_KERNEL); + if (heap == NULL) + return -ENOMEM; + devheap = heap; + taskheap = heap + devheapsz; + + n_task = n; + for (i = 0; i < n; i++) { + struct taskdev *dev = &devheap[i]; + struct dsptask *task = &taskheap[i]; + + if ((ret = dsp_task_config(task, i)) < 0) + return ret; + if ((ret = taskdev_init(dev, task->name, i)) < 0) + return ret; + if ((ret = taskdev_attach_task(dev, task)) < 0) + return ret; + dsp_task_init(task); + pr_info("omapdsp: taskdev %s enabled.\n", dev->name); + } + + return 0; +} + +static void dsp_task_unconfig(struct dsptask *task) +{ + dsptask[task->tid] = NULL; +} + +void dsp_task_unconfig_all(void) +{ + unsigned char minor; + u8 tid; + struct dsptask *task; + + for (minor = 0; minor < n_task; minor++) { + /* + * taskdev[minor] can be NULL in case of + * configuration failure + */ + if (taskdev[minor]) + taskdev_delete(minor); + } + for (; minor < TASKDEV_MAX; minor++) { + if (taskdev[minor]) + dsp_rmdev_minor(minor); + } + + for (tid = 0; tid < n_task; tid++) { + /* + * dsptask[tid] can be NULL in case of + * configuration failure + */ + task = dsptask[tid]; + if (task) + dsp_task_unconfig(task); + } + for (; tid < TASKDEV_MAX; tid++) { + task = dsptask[tid]; + if (task) { + /* + * on-demand tasks should be deleted in + * rmdev_minor(), but just in case. + */ + dsp_task_unconfig(task); + kfree(task); + } + } + + if (heap) { + kfree(heap); + heap = NULL; + } + + n_task = 0; +} + +static struct device_driver dsptask_driver = { + .name = "dsptask", + .bus = &dsptask_bus, +}; + +u8 dsp_task_count(void) +{ + return n_task; +} + +int dsp_taskmod_busy(void) +{ + struct taskdev *dev; + unsigned char minor; + unsigned int usecount; + + for (minor = 0; minor < TASKDEV_MAX; minor++) { + dev = taskdev[minor]; + if (dev == NULL) + continue; + if ((usecount = dev->usecount) > 0) { + printk("dsp_taskmod_busy(): %s: usecount=%d\n", + dev->name, usecount); + return 1; + } +/* + if ((dev->state & (TASKDEV_ST_ADDREQ | + TASKDEV_ST_DELREQ)) { +*/ + if (dev->state & TASKDEV_ST_ADDREQ) { + printk("dsp_taskmod_busy(): %s is in %s\n", + dev->name, devstate_name(dev->state)); + return 1; + } + } + return 0; +} + +/* + * DSP task device file operations + */ +static ssize_t dsp_task_read_wd_acv(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + unsigned int minor = MINOR(file->f_dentry->d_inode->i_rdev); + struct taskdev *dev = taskdev[minor]; + int ret = 0; + DEFINE_WAIT(wait); + + if (count == 0) { + return 0; + } else if (count & 0x1) { + printk(KERN_ERR + "omapdsp: odd count is illegal for DSP task device.\n"); + return -EINVAL; + } + + if (taskdev_lock_and_statelock_attached(dev, &dev->read_mutex)) + return -ENODEV; + + + prepare_to_wait(&dev->read_wait_q, &wait, TASK_INTERRUPTIBLE); + if (kfifo_len(dev->rcvdt.fifo) == 0) + schedule(); + finish_wait(&dev->read_wait_q, &wait); + if (kfifo_len(dev->rcvdt.fifo) == 0) { + /* failure */ + if (signal_pending(current)) + ret = -EINTR; + goto up_out; + } + + + ret = kfifo_get_to_user(dev->rcvdt.fifo, buf, count); + + up_out: + taskdev_unlock_and_stateunlock(dev, &dev->read_mutex); + return ret; +} + +static ssize_t dsp_task_read_bk_acv(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + unsigned int minor = MINOR(file->f_dentry->d_inode->i_rdev); + struct taskdev *dev = taskdev[minor]; + struct rcvdt_bk_struct *rcvdt = &dev->rcvdt.bk; + ssize_t ret = 0; + DEFINE_WAIT(wait); + + if (count == 0) { + return 0; + } else if (count & 0x1) { + printk(KERN_ERR + "omapdsp: odd count is illegal for DSP task device.\n"); + return -EINVAL; + } else if ((int)buf & 0x1) { + printk(KERN_ERR + "omapdsp: buf should be word aligned for " + "dsp_task_read().\n"); + return -EINVAL; + } + + if (taskdev_lock_and_statelock_attached(dev, &dev->read_mutex)) + return -ENODEV; + + prepare_to_wait(&dev->read_wait_q, &wait, TASK_INTERRUPTIBLE); + if (ipblink_empty(&rcvdt->link)) + schedule(); + finish_wait(&dev->read_wait_q, &wait); + if (ipblink_empty(&rcvdt->link)) { + /* failure */ + if (signal_pending(current)) + ret = -EINTR; + goto up_out; + } + + /* copy from delayed IPBUF */ + if (sndtyp_pvt(dev->task->ttyp)) { + /* private */ + if (!ipblink_empty(&rcvdt->link)) { + struct ipbuf_p *ipbp = dev->task->ipbuf_pvt_r; + unsigned char *base, *src; + size_t bkcnt; + + if (dsp_mem_enable(ipbp) < 0) { + ret = -EBUSY; + goto up_out; + } + base = MKVIRT(ipbp->ah, ipbp->al); + bkcnt = ((unsigned long)ipbp->c) * 2 - rcvdt->rp; + if (dsp_address_validate(base, bkcnt, + "task %s read buffer", + dev->task->name) < 0) { + ret = -EINVAL; + goto pv_out1; + } + if (dsp_mem_enable(base) < 0) { + ret = -EBUSY; + goto pv_out1; + } + src = base + rcvdt->rp; + if (bkcnt > count) { + if (copy_to_user_dsp(buf, src, count)) { + ret = -EFAULT; + goto pv_out2; + } + ret = count; + rcvdt->rp += count; + } else { + if (copy_to_user_dsp(buf, src, bkcnt)) { + ret = -EFAULT; + goto pv_out2; + } + ret = bkcnt; + ipblink_del_pvt(&rcvdt->link); + release_ipbuf_pvt(ipbp); + rcvdt->rp = 0; + } + pv_out2: + dsp_mem_disable(src); + pv_out1: + dsp_mem_disable(ipbp); + } + } else { + /* global */ + if (dsp_mem_enable_ipbuf() < 0) { + ret = -EBUSY; + goto up_out; + } + while (!ipblink_empty(&rcvdt->link)) { + unsigned char *src; + size_t bkcnt; + struct ipbuf_head *ipb_h = bid_to_ipbuf(rcvdt->link.top); + + src = ipb_h->p->d + rcvdt->rp; + bkcnt = ((unsigned long)ipb_h->p->c) * 2 - rcvdt->rp; + if (bkcnt > count) { + if (copy_to_user_dsp(buf, src, count)) { + ret = -EFAULT; + goto gb_out; + } + ret += count; + rcvdt->rp += count; + break; + } else { + if (copy_to_user_dsp(buf, src, bkcnt)) { + ret = -EFAULT; + goto gb_out; + } + ret += bkcnt; + buf += bkcnt; + count -= bkcnt; + ipblink_del_top(&rcvdt->link); + unuse_ipbuf(ipb_h); + rcvdt->rp = 0; + } + } + gb_out: + dsp_mem_disable_ipbuf(); + } + + up_out: + taskdev_unlock_and_stateunlock(dev, &dev->read_mutex); + return ret; +} + +static ssize_t dsp_task_read_wd_psv(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + unsigned int minor = MINOR(file->f_dentry->d_inode->i_rdev); + struct taskdev *dev = taskdev[minor]; + int ret = 0; + + if (count == 0) { + return 0; + } else if (count & 0x1) { + printk(KERN_ERR + "omapdsp: odd count is illegal for DSP task device.\n"); + return -EINVAL; + } else { + /* force! */ + count = 2; + } + + if (taskdev_lock_and_statelock_attached(dev, &dev->read_mutex)) + return -ENODEV; + + mbcompose_send_and_wait(WDREQ, dev->task->tid, 0, &dev->read_wait_q); + + if (kfifo_len(dev->rcvdt.fifo) == 0) { + /* failure */ + if (signal_pending(current)) + ret = -EINTR; + goto up_out; + } + + ret = kfifo_get_to_user(dev->rcvdt.fifo, buf, count); + +up_out: + taskdev_unlock_and_stateunlock(dev, &dev->read_mutex); + return ret; +} + +static ssize_t dsp_task_read_bk_psv(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + unsigned int minor = MINOR(file->f_dentry->d_inode->i_rdev); + struct taskdev *dev = taskdev[minor]; + struct rcvdt_bk_struct *rcvdt = &dev->rcvdt.bk; + int ret = 0; + + if (count == 0) { + return 0; + } else if (count & 0x1) { + printk(KERN_ERR + "omapdsp: odd count is illegal for DSP task device.\n"); + return -EINVAL; + } else if ((int)buf & 0x1) { + printk(KERN_ERR + "omapdsp: buf should be word aligned for " + "dsp_task_read().\n"); + return -EINVAL; + } + + if (taskdev_lock_and_statelock_attached(dev, &dev->read_mutex)) + return -ENODEV; + + mbcompose_send_and_wait(BKREQ, dev->task->tid, count/2, + &dev->read_wait_q); + + if (ipblink_empty(&rcvdt->link)) { + /* failure */ + if (signal_pending(current)) + ret = -EINTR; + goto up_out; + } + + /* + * We will not receive more than requested count. + */ + if (sndtyp_pvt(dev->task->ttyp)) { + /* private */ + struct ipbuf_p *ipbp = dev->task->ipbuf_pvt_r; + size_t rcvcnt; + void *src; + + if (dsp_mem_enable(ipbp) < 0) { + ret = -EBUSY; + goto up_out; + } + src = MKVIRT(ipbp->ah, ipbp->al); + rcvcnt = ((unsigned long)ipbp->c) * 2; + if (dsp_address_validate(src, rcvcnt, "task %s read buffer", + dev->task->name) < 0) { + ret = -EINVAL; + goto pv_out1; + } + if (dsp_mem_enable(src) < 0) { + ret = -EBUSY; + goto pv_out1; + } + if (count > rcvcnt) + count = rcvcnt; + if (copy_to_user_dsp(buf, src, count)) { + ret = -EFAULT; + goto pv_out2; + } + ipblink_del_pvt(&rcvdt->link); + release_ipbuf_pvt(ipbp); + ret = count; +pv_out2: + dsp_mem_disable(src); +pv_out1: + dsp_mem_disable(ipbp); + } else { + /* global */ + struct ipbuf_head *ipb_h = bid_to_ipbuf(rcvdt->link.top); + size_t rcvcnt; + + if (dsp_mem_enable_ipbuf() < 0) { + ret = -EBUSY; + goto up_out; + } + rcvcnt = ((unsigned long)ipb_h->p->c) * 2; + if (count > rcvcnt) + count = rcvcnt; + if (copy_to_user_dsp(buf, ipb_h->p->d, count)) { + ret = -EFAULT; + goto gb_out; + } + ipblink_del_top(&rcvdt->link); + unuse_ipbuf(ipb_h); + ret = count; +gb_out: + dsp_mem_disable_ipbuf(); + } + +up_out: + taskdev_unlock_and_stateunlock(dev, &dev->read_mutex); + return ret; +} + +static ssize_t dsp_task_write_wd(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + unsigned int minor = MINOR(file->f_dentry->d_inode->i_rdev); + struct taskdev *dev = taskdev[minor]; + u16 wd; + int ret = 0; + DEFINE_WAIT(wait); + + if (count == 0) { + return 0; + } else if (count & 0x1) { + printk(KERN_ERR + "omapdsp: odd count is illegal for DSP task device.\n"); + return -EINVAL; + } else { + /* force! */ + count = 2; + } + + if (taskdev_lock_and_statelock_attached(dev, &dev->write_mutex)) + return -ENODEV; + + prepare_to_wait(&dev->write_wait_q, &wait, TASK_INTERRUPTIBLE); + if (dev->wsz == 0) + schedule(); + finish_wait(&dev->write_wait_q, &wait); + if (dev->wsz == 0) { + /* failure */ + if (signal_pending(current)) + ret = -EINTR; + goto up_out; + } + + if (copy_from_user(&wd, buf, count)) { + ret = -EFAULT; + goto up_out; + } + + spin_lock(&dev->wsz_lock); + if (mbcompose_send(WDSND, dev->task->tid, wd) < 0) { + spin_unlock(&dev->wsz_lock); + goto up_out; + } + ret = count; + if (rcvtyp_acv(dev->task->ttyp)) + dev->wsz = 0; + spin_unlock(&dev->wsz_lock); + + up_out: + taskdev_unlock_and_stateunlock(dev, &dev->write_mutex); + return ret; +} + +static ssize_t dsp_task_write_bk(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + unsigned int minor = MINOR(file->f_dentry->d_inode->i_rdev); + struct taskdev *dev = taskdev[minor]; + int ret = 0; + DEFINE_WAIT(wait); + + if (count == 0) { + return 0; + } else if (count & 0x1) { + printk(KERN_ERR + "omapdsp: odd count is illegal for DSP task device.\n"); + return -EINVAL; + } else if ((int)buf & 0x1) { + printk(KERN_ERR + "omapdsp: buf should be word aligned for " + "dsp_task_write().\n"); + return -EINVAL; + } + + if (taskdev_lock_and_statelock_attached(dev, &dev->write_mutex)) + return -ENODEV; + + prepare_to_wait(&dev->write_wait_q, &wait, TASK_INTERRUPTIBLE); + if (dev->wsz == 0) + schedule(); + finish_wait(&dev->write_wait_q, &wait); + if (dev->wsz == 0) { + /* failure */ + if (signal_pending(current)) + ret = -EINTR; + goto up_out; + } + + if (count > dev->wsz) + count = dev->wsz; + + if (rcvtyp_pvt(dev->task->ttyp)) { + /* private */ + struct ipbuf_p *ipbp = dev->task->ipbuf_pvt_w; + unsigned char *dst; + + if (dsp_mem_enable(ipbp) < 0) { + ret = -EBUSY; + goto up_out; + } + dst = MKVIRT(ipbp->ah, ipbp->al); + if (dsp_address_validate(dst, count, "task %s write buffer", + dev->task->name) < 0) { + ret = -EINVAL; + goto pv_out1; + } + if (dsp_mem_enable(dst) < 0) { + ret = -EBUSY; + goto pv_out1; + } + if (copy_from_user_dsp(dst, buf, count)) { + ret = -EFAULT; + goto pv_out2; + } + ipbp->c = count/2; + ipbp->s = dev->task->tid; + spin_lock(&dev->wsz_lock); + if (mbcompose_send(BKSNDP, dev->task->tid, 0) == 0) { + if (rcvtyp_acv(dev->task->ttyp)) + dev->wsz = 0; + ret = count; + } + spin_unlock(&dev->wsz_lock); + pv_out2: + dsp_mem_disable(dst); + pv_out1: + dsp_mem_disable(ipbp); + } else { + /* global */ + struct ipbuf_head *ipb_h; + + if (dsp_mem_enable_ipbuf() < 0) { + ret = -EBUSY; + goto up_out; + } + if ((ipb_h = get_free_ipbuf(dev->task->tid)) == NULL) + goto gb_out; + if (copy_from_user_dsp(ipb_h->p->d, buf, count)) { + release_ipbuf(ipb_h); + ret = -EFAULT; + goto gb_out; + } + ipb_h->p->c = count/2; + ipb_h->p->sa = dev->task->tid; + spin_lock(&dev->wsz_lock); + if (mbcompose_send(BKSND, dev->task->tid, ipb_h->bid) == 0) { + if (rcvtyp_acv(dev->task->ttyp)) + dev->wsz = 0; + ret = count; + ipb_bsycnt_inc(&ipbcfg); + } else + release_ipbuf(ipb_h); + spin_unlock(&dev->wsz_lock); + gb_out: + dsp_mem_disable_ipbuf(); + } + + up_out: + taskdev_unlock_and_stateunlock(dev, &dev->write_mutex); + return ret; +} + +static unsigned int dsp_task_poll(struct file * file, poll_table * wait) +{ + unsigned int minor = MINOR(file->f_dentry->d_inode->i_rdev); + struct taskdev *dev = taskdev[minor]; + struct dsptask *task = dev->task; + unsigned int mask = 0; + + if (!devstate_read_lock_and_test(dev, TASKDEV_ST_ATTACHED)) + return 0; + poll_wait(file, &dev->read_wait_q, wait); + poll_wait(file, &dev->write_wait_q, wait); + if (sndtyp_psv(task->ttyp) || + (sndtyp_wd(task->ttyp) && kfifo_len(dev->rcvdt.fifo)) || + (sndtyp_bk(task->ttyp) && !ipblink_empty(&dev->rcvdt.bk.link))) + mask |= POLLIN | POLLRDNORM; + if (dev->wsz) + mask |= POLLOUT | POLLWRNORM; + devstate_read_unlock(dev); + + return mask; +} + +static int dsp_tctl_issue(struct taskdev *dev, u16 cmd, int argc, u16 argv[]) +{ + int tctl_argc; + struct mb_exarg mbarg, *mbargp; + int interactive; + u8 tid; + int ret = 0; + + if (cmd < 0x8000) { + /* + * 0x0000 - 0x7fff + * system reserved TCTL commands + */ + switch (cmd) { + case TCTL_TEN: + case TCTL_TDIS: + tctl_argc = 0; + interactive = 0; + break; + default: + return -EINVAL; + } + } + /* + * 0x8000 - 0xffff + * user-defined TCTL commands + */ + else if (cmd < 0x8100) { + /* 0x8000-0x80ff: no arg, non-interactive */ + tctl_argc = 0; + interactive = 0; + } else if (cmd < 0x8200) { + /* 0x8100-0x81ff: 1 arg, non-interactive */ + tctl_argc = 1; + interactive = 0; + } else if (cmd < 0x9000) { + /* 0x8200-0x8fff: reserved */ + return -EINVAL; + } else if (cmd < 0x9100) { + /* 0x9000-0x90ff: no arg, interactive */ + tctl_argc = 0; + interactive = 1; + } else if (cmd < 0x9200) { + /* 0x9100-0x91ff: 1 arg, interactive */ + tctl_argc = 1; + interactive = 1; + } else { + /* 0x9200-0xffff: reserved */ + return -EINVAL; + } + + /* + * if argc < 0, use tctl_argc as is. + * if argc >= 0, check arg count. + */ + if ((argc >= 0) && (argc != tctl_argc)) + return -EINVAL; + + /* + * issue TCTL + */ + if (taskdev_lock_interruptible(dev, &dev->tctl_mutex)) + return -EINTR; + + tid = dev->task->tid; + if (tctl_argc > 0) { + mbarg.argc = tctl_argc; + mbarg.tid = tid; + mbarg.argv = argv; + mbargp = &mbarg; + } else + mbargp = NULL; + + if (interactive) { + dev->tctl_stat = -EINVAL; + + mbcompose_send_and_wait_exarg(TCTL, tid, cmd, mbargp, + &dev->tctl_wait_q); + if (signal_pending(current)) { + ret = -EINTR; + goto up_out; + } + if ((ret = dev->tctl_stat) < 0) { + printk(KERN_ERR "omapdsp: TCTL not responding.\n"); + goto up_out; + } + } else + mbcompose_send_exarg(TCTL, tid, cmd, mbargp); + +up_out: + mutex_unlock(&dev->tctl_mutex); + return ret; +} + +static int dsp_task_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + unsigned int minor = MINOR(inode->i_rdev); + struct taskdev *dev = taskdev[minor]; + int ret; + + if (cmd < 0x10000) { + /* issue TCTL */ + u16 mbargv[1]; + + mbargv[0] = arg & 0xffff; + return dsp_tctl_issue(dev, cmd, -1, mbargv); + } + + /* non TCTL ioctls */ + switch (cmd) { + + case TASK_IOCTL_LOCK: + ret = taskdev_lock(dev); + break; + + case TASK_IOCTL_UNLOCK: + ret = taskdev_unlock(dev); + break; + + case TASK_IOCTL_BFLSH: + if (taskdev_lock_and_statelock_attached(dev, &dev->read_mutex)) + return -ENODEV; + ret = taskdev_flush_buf(dev); + taskdev_unlock_and_stateunlock(dev, &dev->read_mutex); + break; + + case TASK_IOCTL_SETBSZ: + if (taskdev_lock_and_statelock_attached(dev, &dev->read_mutex)) + return -ENODEV; + ret = taskdev_set_fifosz(dev, arg); + taskdev_unlock_and_stateunlock(dev, &dev->read_mutex); + break; + + case TASK_IOCTL_GETNAME: + ret = 0; + if (copy_to_user((void __user *)arg, dev->name, + strlen(dev->name) + 1)) + ret = -EFAULT; + break; + + default: + ret = -ENOIOCTLCMD; + + } + + return ret; +} + +static void dsp_task_mmap_open(struct vm_area_struct *vma) +{ + struct taskdev *dev = (struct taskdev *)vma->vm_private_data; + struct dsptask *task; + size_t len = vma->vm_end - vma->vm_start; + + BUG_ON(!(dev->state & TASKDEV_ST_ATTACHED)); + task = dev->task; + omap_mmu_exmap_use(&dsp_mmu, task->map_base, len); +} + +static void dsp_task_mmap_close(struct vm_area_struct *vma) +{ + struct taskdev *dev = (struct taskdev *)vma->vm_private_data; + struct dsptask *task; + size_t len = vma->vm_end - vma->vm_start; + + BUG_ON(!(dev->state & TASKDEV_ST_ATTACHED)); + task = dev->task; + omap_mmu_exmap_unuse(&dsp_mmu, task->map_base, len); +} + +/** + * On demand page allocation is not allowed. The mapping area is defined by + * corresponding DSP tasks. + */ +static int dsp_task_mmap_fault(struct vm_area_struct *vma, struct vm_fault *vmf) +{ + return VM_FAULT_NOPAGE; +} + +static struct vm_operations_struct dsp_task_vm_ops = { + .open = dsp_task_mmap_open, + .close = dsp_task_mmap_close, + .fault = dsp_task_mmap_fault, +}; + +static int dsp_task_mmap(struct file *filp, struct vm_area_struct *vma) +{ + void *tmp_vadr; + unsigned long tmp_padr, tmp_vmadr, off; + size_t req_len, tmp_len; + unsigned int minor = MINOR(filp->f_dentry->d_inode->i_rdev); + struct taskdev *dev = taskdev[minor]; + struct dsptask *task; + int ret = 0; + + if (!devstate_read_lock_and_test(dev, TASKDEV_ST_ATTACHED)) + return -ENODEV; + task = dev->task; + + /* + * Don't swap this area out + * Don't dump this area to a core file + */ + vma->vm_flags |= VM_RESERVED | VM_IO; + + /* Do not cache this area */ + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + req_len = vma->vm_end - vma->vm_start; + off = vma->vm_pgoff << PAGE_SHIFT; + tmp_vmadr = vma->vm_start; + tmp_vadr = task->map_base + off; + do { + tmp_padr = omap_mmu_virt_to_phys(&dsp_mmu, tmp_vadr, &tmp_len); + if (tmp_padr == 0) { + printk(KERN_ERR + "omapdsp: task %s: illegal address " + "for mmap: %p", task->name, tmp_vadr); + /* partial mapping will be cleared in upper layer */ + ret = -EINVAL; + goto unlock_out; + } + if (tmp_len > req_len) + tmp_len = req_len; + + pr_debug("omapdsp: mmap info: " + "vmadr = %08lx, padr = %08lx, len = %x\n", + tmp_vmadr, tmp_padr, tmp_len); + if (remap_pfn_range(vma, tmp_vmadr, tmp_padr >> PAGE_SHIFT, + tmp_len, vma->vm_page_prot) != 0) { + printk(KERN_ERR + "omapdsp: task %s: remap_page_range() failed.\n", + task->name); + /* partial mapping will be cleared in upper layer */ + ret = -EINVAL; + goto unlock_out; + } + + req_len -= tmp_len; + tmp_vmadr += tmp_len; + tmp_vadr += tmp_len; + } while (req_len); + + vma->vm_ops = &dsp_task_vm_ops; + vma->vm_private_data = dev; + omap_mmu_exmap_use(&dsp_mmu, task->map_base, vma->vm_end - vma->vm_start); + +unlock_out: + devstate_read_unlock(dev); + return ret; +} + +static int dsp_task_open(struct inode *inode, struct file *file) +{ + unsigned int minor = MINOR(inode->i_rdev); + struct taskdev *dev; + int ret = 0; + + if ((minor >= TASKDEV_MAX) || ((dev = taskdev[minor]) == NULL)) + return -ENODEV; + + restart: + mutex_lock(&dev->usecount_lock); + down_write(&dev->state_sem); + + /* state can be NOTASK, ATTACHED/FREEZED, KILLING, GARBAGE or INVALID here. */ + switch (dev->state & TASKDEV_ST_STATE_MASK) { + case TASKDEV_ST_NOTASK: + break; + case TASKDEV_ST_ATTACHED: + goto attached; + + case TASKDEV_ST_INVALID: + up_write(&dev->state_sem); + mutex_unlock(&dev->usecount_lock); + return -ENODEV; + + case TASKDEV_ST_FREEZED: + case TASKDEV_ST_KILLING: + case TASKDEV_ST_GARBAGE: + case TASKDEV_ST_DELREQ: + /* on the kill process. wait until it becomes NOTASK. */ + up_write(&dev->state_sem); + mutex_unlock(&dev->usecount_lock); + if (devstate_write_lock(dev, TASKDEV_ST_NOTASK) < 0) + return -EINTR; + devstate_write_unlock(dev); + goto restart; + } + + /* NOTASK */ + set_taskdev_state(dev, TASKDEV_ST_ADDREQ); + /* wake up twch daemon for tadd */ + dsp_twch_touch(); + up_write(&dev->state_sem); + if (devstate_write_lock(dev, TASKDEV_ST_ATTACHED | + TASKDEV_ST_ADDFAIL) < 0) { + /* cancelled */ + if (!devstate_write_lock_and_test(dev, TASKDEV_ST_ADDREQ)) { + mutex_unlock(&dev->usecount_lock); + /* out of control ??? */ + return -EINTR; + } + set_taskdev_state(dev, TASKDEV_ST_NOTASK); + ret = -EINTR; + goto change_out; + } + if (dev->state & TASKDEV_ST_ADDFAIL) { + printk(KERN_ERR "omapdsp: task attach failed for %s!\n", + dev->name); + ret = -EBUSY; + set_taskdev_state(dev, TASKDEV_ST_NOTASK); + goto change_out; + } + + attached: + ret = proc_list_add(&dev->proc_list_lock, + &dev->proc_list, current, file); + if (ret) + goto out; + + dev->usecount++; + file->f_op = &dev->fops; + up_write(&dev->state_sem); + mutex_unlock(&dev->usecount_lock); + +#ifdef DSP_PTE_FREE /* not used currently. */ + dsp_map_update(current); + dsp_cur_users_add(current); +#endif /* DSP_PTE_FREE */ + return 0; + + change_out: + wake_up_interruptible_all(&dev->state_wait_q); + out: + up_write(&dev->state_sem); + mutex_unlock(&dev->usecount_lock); + return ret; +} + +static int dsp_task_release(struct inode *inode, struct file *file) +{ + unsigned int minor = MINOR(inode->i_rdev); + struct taskdev *dev = taskdev[minor]; + +#ifdef DSP_PTE_FREE /* not used currently. */ + dsp_cur_users_del(current); +#endif /* DSP_PTE_FREE */ + + if (has_taskdev_lock(dev)) + taskdev_unlock(dev); + + proc_list_del(&dev->proc_list_lock, &dev->proc_list, current, file); + mutex_lock(&dev->usecount_lock); + if (--dev->usecount > 0) { + /* other processes are using this device. no state change. */ + mutex_unlock(&dev->usecount_lock); + return 0; + } + + /* usecount == 0 */ + down_write(&dev->state_sem); + + /* state can be ATTACHED/FREEZED, KILLING or GARBAGE here. */ + switch (dev->state & TASKDEV_ST_STATE_MASK) { + + case TASKDEV_ST_KILLING: + break; + + case TASKDEV_ST_GARBAGE: + set_taskdev_state(dev, TASKDEV_ST_NOTASK); + wake_up_interruptible_all(&dev->state_wait_q); + break; + + case TASKDEV_ST_ATTACHED: + case TASKDEV_ST_FREEZED: + if (is_dynamic_task(minor)) { + set_taskdev_state(dev, TASKDEV_ST_DELREQ); + /* wake up twch daemon for tdel */ + dsp_twch_touch(); + } + break; + + } + + up_write(&dev->state_sem); + mutex_unlock(&dev->usecount_lock); + return 0; +} + +/* + * mkdev / rmdev + */ +int dsp_mkdev(char *name) +{ + struct taskdev *dev; + int status; + unsigned char minor; + int ret; + + if (dsp_cfgstat_get_stat() != CFGSTAT_READY) { + printk(KERN_ERR "omapdsp: dsp has not been configured.\n"); + return -EINVAL; + } + + if (mutex_lock_interruptible(&devmgr_lock)) + return -EINTR; + + /* naming check */ + for (minor = 0; minor < TASKDEV_MAX; minor++) { + if (taskdev[minor] && !strcmp(taskdev[minor]->name, name)) { + printk(KERN_ERR + "omapdsp: task device name %s is already " + "in use.\n", name); + ret = -EINVAL; + goto out; + } + } + + /* find free minor number */ + for (minor = n_task; minor < TASKDEV_MAX; minor++) { + if (taskdev[minor] == NULL) + goto do_make; + } + printk(KERN_ERR "omapdsp: Too many task devices.\n"); + ret = -EBUSY; + goto out; + +do_make: + if ((dev = kzalloc(sizeof(struct taskdev), GFP_KERNEL)) == NULL) { + ret = -ENOMEM; + goto out; + } + if ((status = taskdev_init(dev, name, minor)) < 0) { + kfree(dev); + ret = status; + goto out; + } + ret = minor; + +out: + mutex_unlock(&devmgr_lock); + return ret; +} + +int dsp_rmdev(char *name) +{ + unsigned char minor; + int status; + int ret; + + if (dsp_cfgstat_get_stat() != CFGSTAT_READY) { + printk(KERN_ERR "omapdsp: dsp has not been configured.\n"); + return -EINVAL; + } + + if (mutex_lock_interruptible(&devmgr_lock)) + return -EINTR; + + /* find in dynamic devices */ + for (minor = n_task; minor < TASKDEV_MAX; minor++) { + if (taskdev[minor] && !strcmp(taskdev[minor]->name, name)) + goto do_remove; + } + + /* find in static devices */ + for (minor = 0; minor < n_task; minor++) { + if (taskdev[minor] && !strcmp(taskdev[minor]->name, name)) { + printk(KERN_ERR + "omapdsp: task device %s is static.\n", name); + ret = -EINVAL; + goto out; + } + } + + printk(KERN_ERR "omapdsp: task device %s not found.\n", name); + return -EINVAL; + +do_remove: + ret = minor; + if ((status = dsp_rmdev_minor(minor)) < 0) + ret = status; +out: + mutex_unlock(&devmgr_lock); + return ret; +} + +static int dsp_rmdev_minor(unsigned char minor) +{ + struct taskdev *dev = taskdev[minor]; + + while (!down_write_trylock(&dev->state_sem)) { + down_read(&dev->state_sem); + if (dev->state & (TASKDEV_ST_ATTACHED | + TASKDEV_ST_FREEZED)) { + /* + * task is working. kill it. + * ATTACHED -> FREEZED can be changed under + * down_read of state_sem.. + */ + set_taskdev_state(dev, TASKDEV_ST_FREEZED); + wake_up_interruptible_all(&dev->read_wait_q); + wake_up_interruptible_all(&dev->write_wait_q); + wake_up_interruptible_all(&dev->tctl_wait_q); + } + up_read(&dev->state_sem); + schedule(); + } + + switch (dev->state & TASKDEV_ST_STATE_MASK) { + + case TASKDEV_ST_NOTASK: + case TASKDEV_ST_INVALID: + /* fine */ + goto notask; + + case TASKDEV_ST_ATTACHED: + case TASKDEV_ST_FREEZED: + /* task is working. kill it. */ + set_taskdev_state(dev, TASKDEV_ST_KILLING); + up_write(&dev->state_sem); + dsp_tdel_bh(dev, TDEL_KILL); + goto invalidate; + + case TASKDEV_ST_ADDREQ: + /* open() is waiting. drain it. */ + set_taskdev_state(dev, TASKDEV_ST_ADDFAIL); + wake_up_interruptible_all(&dev->state_wait_q); + break; + + case TASKDEV_ST_DELREQ: + /* nobody is waiting. */ + set_taskdev_state(dev, TASKDEV_ST_NOTASK); + wake_up_interruptible_all(&dev->state_wait_q); + break; + + case TASKDEV_ST_ADDING: + case TASKDEV_ST_DELING: + case TASKDEV_ST_KILLING: + case TASKDEV_ST_GARBAGE: + case TASKDEV_ST_ADDFAIL: + /* transient state. wait for a moment. */ + break; + + } + + up_write(&dev->state_sem); + +invalidate: + /* wait for some time and hope the state is settled */ + devstate_read_lock_timeout(dev, TASKDEV_ST_NOTASK, 5 * HZ); + if (!(dev->state & TASKDEV_ST_NOTASK)) { + printk(KERN_WARNING + "omapdsp: illegal device state (%s) on rmdev %s.\n", + devstate_name(dev->state), dev->name); + } +notask: + set_taskdev_state(dev, TASKDEV_ST_INVALID); + devstate_read_unlock(dev); + + taskdev_delete(minor); + kfree(dev); + + return 0; +} + +static struct file_operations dsp_task_fops = { + .owner = THIS_MODULE, + .poll = dsp_task_poll, + .ioctl = dsp_task_ioctl, + .open = dsp_task_open, + .release = dsp_task_release, +}; + +static void dsptask_dev_release(struct device *dev) +{ +} + +static int taskdev_init(struct taskdev *dev, char *name, unsigned char minor) +{ + int ret; + struct device *task_dev; + + taskdev[minor] = dev; + + spin_lock_init(&dev->proc_list_lock); + INIT_LIST_HEAD(&dev->proc_list); + init_waitqueue_head(&dev->read_wait_q); + init_waitqueue_head(&dev->write_wait_q); + init_waitqueue_head(&dev->tctl_wait_q); + mutex_init(&dev->read_mutex); + mutex_init(&dev->write_mutex); + mutex_init(&dev->tctl_mutex); + mutex_init(&dev->lock); + spin_lock_init(&dev->wsz_lock); + dev->tctl_ret = -EINVAL; + dev->lock_pid = 0; + + strncpy(dev->name, name, TNM_LEN); + dev->name[TNM_LEN-1] = '\0'; + set_taskdev_state(dev, (minor < n_task) ? TASKDEV_ST_ATTACHED : TASKDEV_ST_NOTASK); + dev->usecount = 0; + mutex_init(&dev->usecount_lock); + memcpy(&dev->fops, &dsp_task_fops, sizeof(struct file_operations)); + + dev->dev.parent = omap_dsp->dev; + dev->dev.bus = &dsptask_bus; + sprintf(dev->dev.bus_id, "dsptask%d", minor); + dev->dev.release = dsptask_dev_release; + ret = device_register(&dev->dev); + if (ret) { + printk(KERN_ERR "device_register failed: %d\n", ret); + return ret; + } + ret = device_create_file(&dev->dev, &dev_attr_devname); + if (ret) + goto fail_create_devname; + ret = device_create_file(&dev->dev, &dev_attr_devstate); + if (ret) + goto fail_create_devstate; + ret = device_create_file(&dev->dev, &dev_attr_proc_list); + if (ret) + goto fail_create_proclist; + + task_dev = device_create(dsp_task_class, NULL, + MKDEV(OMAP_DSP_TASK_MAJOR, minor), NULL, + "dsptask%d", (int)minor); + + if (unlikely(IS_ERR(task_dev))) { + ret = -EINVAL; + goto fail_create_taskclass; + } + + init_waitqueue_head(&dev->state_wait_q); + init_rwsem(&dev->state_sem); + + return 0; + + fail_create_taskclass: + device_remove_file(&dev->dev, &dev_attr_proc_list); + fail_create_proclist: + device_remove_file(&dev->dev, &dev_attr_devstate); + fail_create_devstate: + device_remove_file(&dev->dev, &dev_attr_devname); + fail_create_devname: + device_unregister(&dev->dev); + return ret; +} + +static void taskdev_delete(unsigned char minor) +{ + struct taskdev *dev = taskdev[minor]; + + if (!dev) + return; + device_remove_file(&dev->dev, &dev_attr_devname); + device_remove_file(&dev->dev, &dev_attr_devstate); + device_remove_file(&dev->dev, &dev_attr_proc_list); + device_destroy(dsp_task_class, MKDEV(OMAP_DSP_TASK_MAJOR, minor)); + device_unregister(&dev->dev); + proc_list_flush(&dev->proc_list_lock, &dev->proc_list); + taskdev[minor] = NULL; +} + +static int taskdev_attach_task(struct taskdev *dev, struct dsptask *task) +{ + u16 ttyp = task->ttyp; + int ret; + + dev->fops.read = + sndtyp_acv(ttyp) ? + sndtyp_wd(ttyp) ? dsp_task_read_wd_acv: + /* sndtyp_bk */ dsp_task_read_bk_acv: + /* sndtyp_psv */ + sndtyp_wd(ttyp) ? dsp_task_read_wd_psv: + /* sndtyp_bk */ dsp_task_read_bk_psv; + if (sndtyp_wd(ttyp)) { + /* word */ + size_t fifosz = sndtyp_psv(ttyp) ? 2:32; /* passive:active */ + + dev->rcvdt.fifo = kfifo_alloc(fifosz, GFP_KERNEL, + &dev->read_lock); + if (IS_ERR(dev->rcvdt.fifo)) { + printk(KERN_ERR + "omapdsp: unable to allocate receive buffer. " + "(%d bytes for %s)\n", fifosz, dev->name); + return -ENOMEM; + } + } else { + /* block */ + INIT_IPBLINK(&dev->rcvdt.bk.link); + dev->rcvdt.bk.rp = 0; + } + + dev->fops.write = + rcvtyp_wd(ttyp) ? dsp_task_write_wd: + /* rcvbyp_bk */ dsp_task_write_bk; + dev->wsz = rcvtyp_acv(ttyp) ? 0 : /* active */ + rcvtyp_wd(ttyp) ? 2 : /* passive word */ + ipbcfg.lsz*2; /* passive block */ + + if (task->map_length) + dev->fops.mmap = dsp_task_mmap; + + ret = device_create_file(&dev->dev, &dev_attr_taskname); + if (unlikely(ret)) + goto fail_create_taskname; + ret = device_create_file(&dev->dev, &dev_attr_ttyp); + if (unlikely(ret)) + goto fail_create_ttyp; + ret = device_create_file(&dev->dev, &dev_attr_wsz); + if (unlikely(ret)) + goto fail_create_wsz; + if (task->map_length) { + ret = device_create_file(&dev->dev, &dev_attr_mmap); + if (unlikely(ret)) + goto fail_create_mmap; + } + if (sndtyp_wd(ttyp)) { + ret = device_create_file(&dev->dev, &dev_attr_fifosz); + if (unlikely(ret)) + goto fail_create_fifosz; + ret = device_create_file(&dev->dev, &dev_attr_fifocnt); + if (unlikely(ret)) + goto fail_create_fifocnt; + } else { + ret = device_create_file(&dev->dev, &dev_attr_ipblink); + if (unlikely(ret)) + goto fail_create_ipblink; + } + + dev->task = task; + task->dev = dev; + + return 0; + + fail_create_fifocnt: + device_remove_file(&dev->dev, &dev_attr_fifosz); + fail_create_ipblink: + fail_create_fifosz: + if (task->map_length) + device_remove_file(&dev->dev, &dev_attr_mmap); + fail_create_mmap: + device_remove_file(&dev->dev, &dev_attr_wsz); + fail_create_wsz: + device_remove_file(&dev->dev, &dev_attr_ttyp); + fail_create_ttyp: + device_remove_file(&dev->dev, &dev_attr_taskname); + fail_create_taskname: + if (task->map_length) + dev->fops.mmap = NULL; + + dev->fops.write = NULL; + dev->wsz = 0; + + dev->fops.read = NULL; + taskdev_flush_buf(dev); + + if (sndtyp_wd(ttyp)) + kfifo_free(dev->rcvdt.fifo); + + dev->task = NULL; + + return ret; +} + +static void taskdev_detach_task(struct taskdev *dev) +{ + u16 ttyp = dev->task->ttyp; + + device_remove_file(&dev->dev, &dev_attr_taskname); + device_remove_file(&dev->dev, &dev_attr_ttyp); + if (sndtyp_wd(ttyp)) { + device_remove_file(&dev->dev, &dev_attr_fifosz); + device_remove_file(&dev->dev, &dev_attr_fifocnt); + } else + device_remove_file(&dev->dev, &dev_attr_ipblink); + device_remove_file(&dev->dev, &dev_attr_wsz); + if (dev->task->map_length) { + device_remove_file(&dev->dev, &dev_attr_mmap); + dev->fops.mmap = NULL; + } + + dev->fops.read = NULL; + taskdev_flush_buf(dev); + if (sndtyp_wd(ttyp)) + kfifo_free(dev->rcvdt.fifo); + + dev->fops.write = NULL; + dev->wsz = 0; + + pr_info("omapdsp: taskdev %s disabled.\n", dev->name); + dev->task = NULL; +} + +/* + * tadd / tdel / tkill + */ +static int dsp_tadd(struct taskdev *dev, dsp_long_t adr) +{ + struct dsptask *task; + struct mb_exarg arg; + u8 tid, tid_response; + u16 argv[2]; + int ret = 0; + + if (!devstate_write_lock_and_test(dev, TASKDEV_ST_ADDREQ)) { + printk(KERN_ERR + "omapdsp: taskdev %s is not requesting for tadd. " + "(state is %s)\n", dev->name, devstate_name(dev->state)); + return -EINVAL; + } + set_taskdev_state(dev, TASKDEV_ST_ADDING); + devstate_write_unlock(dev); + + if (adr == TADD_ABORTADR) { + /* aborting tadd intentionally */ + pr_info("omapdsp: tadd address is ABORTADR.\n"); + goto fail_out; + } + if (adr >= DSPSPACE_SIZE) { + printk(KERN_ERR + "omapdsp: illegal address 0x%08x for tadd\n", adr); + ret = -EINVAL; + goto fail_out; + } + + adr >>= 1; /* word address */ + argv[0] = adr >> 16; /* addrh */ + argv[1] = adr & 0xffff; /* addrl */ + + if (mutex_lock_interruptible(&cfg_lock)) { + ret = -EINTR; + goto fail_out; + } + cfg_tid = TID_ANON; + cfg_cmd = MBOX_CMD_DSP_TADD; + arg.tid = TID_ANON; + arg.argc = 2; + arg.argv = argv; + + if (dsp_mem_sync_inc() < 0) { + printk(KERN_ERR "omapdsp: memory sync failed!\n"); + ret = -EBUSY; + goto fail_out; + } + mbcompose_send_and_wait_exarg(TADD, 0, 0, &arg, &cfg_wait_q); + + tid = cfg_tid; + cfg_tid = TID_ANON; + cfg_cmd = 0; + mutex_unlock(&cfg_lock); + + if (tid == TID_ANON) { + printk(KERN_ERR "omapdsp: tadd failed!\n"); + ret = -EINVAL; + goto fail_out; + } + if ((tid < n_task) || dsptask[tid]) { + printk(KERN_ERR "omapdsp: illegal tid (%d)!\n", tid); + ret = -EINVAL; + goto fail_out; + } + if ((task = kzalloc(sizeof(struct dsptask), GFP_KERNEL)) == NULL) { + ret = -ENOMEM; + goto del_out; + } + + if ((ret = dsp_task_config(task, tid)) < 0) + goto free_out; + + if (strcmp(dev->name, task->name)) { + printk(KERN_ERR + "omapdsp: task name (%s) doesn't match with " + "device name (%s).\n", task->name, dev->name); + ret = -EINVAL; + goto free_out; + } + + if ((ret = taskdev_attach_task(dev, task)) < 0) + goto free_out; + + dsp_task_init(task); + pr_info("omapdsp: taskdev %s enabled.\n", dev->name); + set_taskdev_state(dev, TASKDEV_ST_ATTACHED); + wake_up_interruptible_all(&dev->state_wait_q); + return 0; + +free_out: + kfree(task); + +del_out: + printk(KERN_ERR "omapdsp: deleting the task...\n"); + + set_taskdev_state(dev, TASKDEV_ST_DELING); + + if (mutex_lock_interruptible(&cfg_lock)) { + printk(KERN_ERR "omapdsp: aborting tdel process. " + "DSP side could be corrupted.\n"); + goto fail_out; + } + cfg_tid = TID_ANON; + cfg_cmd = MBOX_CMD_DSP_TDEL; + mbcompose_send_and_wait(TDEL, tid, TDEL_KILL, &cfg_wait_q); + tid_response = cfg_tid; + cfg_tid = TID_ANON; + cfg_cmd = 0; + mutex_unlock(&cfg_lock); + + if (tid_response != tid) + printk(KERN_ERR "omapdsp: tdel failed. " + "DSP side could be corrupted.\n"); + +fail_out: + set_taskdev_state(dev, TASKDEV_ST_ADDFAIL); + wake_up_interruptible_all(&dev->state_wait_q); + return ret; +} + +int dsp_tadd_minor(unsigned char minor, dsp_long_t adr) +{ + struct taskdev *dev; + int status; + int ret; + + if (mutex_lock_interruptible(&devmgr_lock)) + return -EINTR; + + if ((minor >= TASKDEV_MAX) || ((dev = taskdev[minor]) == NULL)) { + printk(KERN_ERR + "omapdsp: no task device with minor %d\n", minor); + ret = -EINVAL; + goto out; + } + ret = minor; + if ((status = dsp_tadd(dev, adr)) < 0) + ret = status; + +out: + mutex_unlock(&devmgr_lock); + return ret; +} + +static int dsp_tdel(struct taskdev *dev) +{ + if (!devstate_write_lock_and_test(dev, TASKDEV_ST_DELREQ)) { + printk(KERN_ERR + "omapdsp: taskdev %s is not requesting for tdel. " + "(state is %s)\n", dev->name, devstate_name(dev->state)); + return -EINVAL; + } + set_taskdev_state(dev, TASKDEV_ST_DELING); + devstate_write_unlock(dev); + + return dsp_tdel_bh(dev, TDEL_SAFE); +} + +int dsp_tdel_minor(unsigned char minor) +{ + struct taskdev *dev; + int status; + int ret; + + if (mutex_lock_interruptible(&devmgr_lock)) + return -EINTR; + + if ((minor >= TASKDEV_MAX) || ((dev = taskdev[minor]) == NULL)) { + printk(KERN_ERR + "omapdsp: no task device with minor %d\n", minor); + ret = -EINVAL; + goto out; + } + + ret = minor; + if ((status = dsp_tdel(dev)) < 0) + ret = status; + +out: + mutex_unlock(&devmgr_lock); + return ret; +} + +static int dsp_tkill(struct taskdev *dev) +{ + while (!down_write_trylock(&dev->state_sem)) { + if (!devstate_read_lock_and_test(dev, (TASKDEV_ST_ATTACHED | + TASKDEV_ST_FREEZED))) { + printk(KERN_ERR + "omapdsp: task has not been attached for " + "taskdev %s\n", dev->name); + return -EINVAL; + } + /* ATTACHED -> FREEZED can be changed under read semaphore. */ + set_taskdev_state(dev, TASKDEV_ST_FREEZED); + wake_up_interruptible_all(&dev->read_wait_q); + wake_up_interruptible_all(&dev->write_wait_q); + wake_up_interruptible_all(&dev->tctl_wait_q); + devstate_read_unlock(dev); + schedule(); + } + + if (!(dev->state & (TASKDEV_ST_ATTACHED | + TASKDEV_ST_FREEZED))) { + printk(KERN_ERR + "omapdsp: task has not been attached for taskdev %s\n", + dev->name); + devstate_write_unlock(dev); + return -EINVAL; + } + if (!is_dynamic_task(dev->task->tid)) { + printk(KERN_ERR "omapdsp: task %s is not a dynamic task.\n", + dev->name); + devstate_write_unlock(dev); + return -EINVAL; + } + set_taskdev_state(dev, TASKDEV_ST_KILLING); + devstate_write_unlock(dev); + + return dsp_tdel_bh(dev, TDEL_KILL); +} + +int dsp_tkill_minor(unsigned char minor) +{ + struct taskdev *dev; + int status; + int ret; + + if (mutex_lock_interruptible(&devmgr_lock)) + return -EINTR; + + if ((minor >= TASKDEV_MAX) || ((dev = taskdev[minor]) == NULL)) { + printk(KERN_ERR + "omapdsp: no task device with minor %d\n", minor); + ret = -EINVAL; + goto out; + } + + ret = minor; + if ((status = dsp_tkill(dev)) < 0) + ret = status; + +out: + mutex_unlock(&devmgr_lock); + return ret; +} + +static int dsp_tdel_bh(struct taskdev *dev, u16 type) +{ + struct dsptask *task; + u8 tid, tid_response; + int ret = 0; + + task = dev->task; + tid = task->tid; + if (mutex_lock_interruptible(&cfg_lock)) { + if (type == TDEL_SAFE) { + set_taskdev_state(dev, TASKDEV_ST_DELREQ); + return -EINTR; + } else { + tid_response = TID_ANON; + ret = -EINTR; + goto detach_out; + } + } + cfg_tid = TID_ANON; + cfg_cmd = MBOX_CMD_DSP_TDEL; + mbcompose_send_and_wait(TDEL, tid, type, &cfg_wait_q); + tid_response = cfg_tid; + cfg_tid = TID_ANON; + cfg_cmd = 0; + mutex_unlock(&cfg_lock); + +detach_out: + taskdev_detach_task(dev); + dsp_task_unconfig(task); + kfree(task); + + if (tid_response != tid) { + printk(KERN_ERR "omapdsp: %s failed!\n", + (type == TDEL_SAFE) ? "tdel" : "tkill"); + ret = -EINVAL; + } + down_write(&dev->state_sem); + set_taskdev_state(dev, (dev->usecount > 0) ? TASKDEV_ST_GARBAGE : + TASKDEV_ST_NOTASK); + wake_up_interruptible_all(&dev->state_wait_q); + up_write(&dev->state_sem); + + return ret; +} + +/* + * state inquiry + */ +long taskdev_state_stale(unsigned char minor) +{ + if (taskdev[minor]) { + long state = taskdev[minor]->state; + taskdev[minor]->state |= TASKDEV_ST_STALE; + return state; + } else + return TASKDEV_ST_NOTASK; +} + +/* + * functions called from mailbox interrupt routine + */ +void mbox_wdsnd(struct mbcmd *mb) +{ + unsigned int n; + u8 tid = mb->cmd_l; + u16 data = mb->data; + struct dsptask *task = dsptask[tid]; + + if ((tid >= TASKDEV_MAX) || (task == NULL)) { + printk(KERN_ERR "mbox: WDSND with illegal tid! %d\n", tid); + return; + } + if (sndtyp_bk(task->ttyp)) { + printk(KERN_ERR + "mbox: WDSND from block sending task! (task%d)\n", tid); + return; + } + if (sndtyp_psv(task->ttyp) && + !waitqueue_active(&task->dev->read_wait_q)) { + printk(KERN_WARNING + "mbox: WDSND from passive sending task (task%d) " + "without request!\n", tid); + return; + } + + n = kfifo_put(task->dev->rcvdt.fifo, (unsigned char *)&data, + sizeof(data)); + if (n != sizeof(data)) + printk(KERN_WARNING "Receive FIFO(%d) is full\n", tid); + + wake_up_interruptible(&task->dev->read_wait_q); +} + +void mbox_wdreq(struct mbcmd *mb) +{ + u8 tid = mb->cmd_l; + struct dsptask *task = dsptask[tid]; + struct taskdev *dev; + + if ((tid >= TASKDEV_MAX) || (task == NULL)) { + printk(KERN_ERR "mbox: WDREQ with illegal tid! %d\n", tid); + return; + } + if (rcvtyp_psv(task->ttyp)) { + printk(KERN_ERR + "mbox: WDREQ from passive receiving task! (task%d)\n", + tid); + return; + } + + dev = task->dev; + spin_lock(&dev->wsz_lock); + dev->wsz = 2; + spin_unlock(&dev->wsz_lock); + wake_up_interruptible(&dev->write_wait_q); +} + +void mbox_bksnd(struct mbcmd *mb) +{ + u8 tid = mb->cmd_l; + u16 bid = mb->data; + struct dsptask *task = dsptask[tid]; + struct ipbuf_head *ipb_h; + u16 cnt; + + if (bid >= ipbcfg.ln) { + printk(KERN_ERR "mbox: BKSND with illegal bid! %d\n", bid); + return; + } + ipb_h = bid_to_ipbuf(bid); + ipb_bsycnt_dec(&ipbcfg); + if ((tid >= TASKDEV_MAX) || (task == NULL)) { + printk(KERN_ERR "mbox: BKSND with illegal tid! %d\n", tid); + goto unuse_ipbuf_out; + } + if (sndtyp_wd(task->ttyp)) { + printk(KERN_ERR + "mbox: BKSND from word sending task! (task%d)\n", tid); + goto unuse_ipbuf_out; + } + if (sndtyp_pvt(task->ttyp)) { + printk(KERN_ERR + "mbox: BKSND from private sending task! (task%d)\n", tid); + goto unuse_ipbuf_out; + } + if (sync_with_dsp(&ipb_h->p->sd, tid, 10) < 0) { + printk(KERN_ERR "mbox: BKSND - IPBUF sync failed!\n"); + return; + } + + /* should be done in DSP, but just in case. */ + ipb_h->p->next = BID_NULL; + + cnt = ipb_h->p->c; + if (cnt > ipbcfg.lsz) { + printk(KERN_ERR "mbox: BKSND cnt(%d) > ipbuf line size(%d)!\n", + cnt, ipbcfg.lsz); + goto unuse_ipbuf_out; + } + + if (cnt == 0) { + /* 0-byte send from DSP */ + unuse_ipbuf_nowait(ipb_h); + goto done; + } + ipblink_add_tail(&task->dev->rcvdt.bk.link, bid); + /* we keep coming bid and return alternative line to DSP. */ + balance_ipbuf(); + +done: + wake_up_interruptible(&task->dev->read_wait_q); + return; + +unuse_ipbuf_out: + unuse_ipbuf_nowait(ipb_h); + return; +} + +void mbox_bkreq(struct mbcmd *mb) +{ + u8 tid = mb->cmd_l; + u16 cnt = mb->data; + struct dsptask *task = dsptask[tid]; + struct taskdev *dev; + + if ((tid >= TASKDEV_MAX) || (task == NULL)) { + printk(KERN_ERR "mbox: BKREQ with illegal tid! %d\n", tid); + return; + } + if (rcvtyp_wd(task->ttyp)) { + printk(KERN_ERR + "mbox: BKREQ from word receiving task! (task%d)\n", tid); + return; + } + if (rcvtyp_pvt(task->ttyp)) { + printk(KERN_ERR + "mbox: BKREQ from private receiving task! (task%d)\n", + tid); + return; + } + if (rcvtyp_psv(task->ttyp)) { + printk(KERN_ERR + "mbox: BKREQ from passive receiving task! (task%d)\n", + tid); + return; + } + + dev = task->dev; + spin_lock(&dev->wsz_lock); + dev->wsz = cnt*2; + spin_unlock(&dev->wsz_lock); + wake_up_interruptible(&dev->write_wait_q); +} + +void mbox_bkyld(struct mbcmd *mb) +{ + u16 bid = mb->data; + struct ipbuf_head *ipb_h; + + if (bid >= ipbcfg.ln) { + printk(KERN_ERR "mbox: BKYLD with illegal bid! %d\n", bid); + return; + } + ipb_h = bid_to_ipbuf(bid); + + /* should be done in DSP, but just in case. */ + ipb_h->p->next = BID_NULL; + + /* we don't need to sync with DSP */ + ipb_bsycnt_dec(&ipbcfg); + release_ipbuf(ipb_h); +} + +void mbox_bksndp(struct mbcmd *mb) +{ + u8 tid = mb->cmd_l; + struct dsptask *task = dsptask[tid]; + struct ipbuf_p *ipbp; + + if ((tid >= TASKDEV_MAX) || (task == NULL)) { + printk(KERN_ERR "mbox: BKSNDP with illegal tid! %d\n", tid); + return; + } + if (sndtyp_wd(task->ttyp)) { + printk(KERN_ERR + "mbox: BKSNDP from word sending task! (task%d)\n", tid); + return; + } + if (sndtyp_gbl(task->ttyp)) { + printk(KERN_ERR + "mbox: BKSNDP from non-private sending task! (task%d)\n", + tid); + return; + } + + /* + * we should not have delayed block at this point + * because read() routine releases the lock of the buffer and + * until then DSP can't send next data. + */ + + ipbp = task->ipbuf_pvt_r; + if (sync_with_dsp(&ipbp->s, tid, 10) < 0) { + printk(KERN_ERR "mbox: BKSNDP - IPBUF sync failed!\n"); + return; + } + pr_debug("mbox: ipbuf_pvt_r->a = 0x%08lx\n", + MKLONG(ipbp->ah, ipbp->al)); + ipblink_add_pvt(&task->dev->rcvdt.bk.link); + wake_up_interruptible(&task->dev->read_wait_q); +} + +void mbox_bkreqp(struct mbcmd *mb) +{ + u8 tid = mb->cmd_l; + struct dsptask *task = dsptask[tid]; + struct taskdev *dev; + struct ipbuf_p *ipbp; + + if ((tid >= TASKDEV_MAX) || (task == NULL)) { + printk(KERN_ERR "mbox: BKREQP with illegal tid! %d\n", tid); + return; + } + if (rcvtyp_wd(task->ttyp)) { + printk(KERN_ERR + "mbox: BKREQP from word receiving task! (task%d)\n", tid); + return; + } + if (rcvtyp_gbl(task->ttyp)) { + printk(KERN_ERR + "mbox: BKREQP from non-private receiving task! (task%d)\n", tid); + return; + } + if (rcvtyp_psv(task->ttyp)) { + printk(KERN_ERR + "mbox: BKREQP from passive receiving task! (task%d)\n", tid); + return; + } + + ipbp = task->ipbuf_pvt_w; + if (sync_with_dsp(&ipbp->s, TID_FREE, 10) < 0) { + printk(KERN_ERR "mbox: BKREQP - IPBUF sync failed!\n"); + return; + } + pr_debug("mbox: ipbuf_pvt_w->a = 0x%08lx\n", + MKLONG(ipbp->ah, ipbp->al)); + dev = task->dev; + spin_lock(&dev->wsz_lock); + dev->wsz = ipbp->c*2; + spin_unlock(&dev->wsz_lock); + wake_up_interruptible(&dev->write_wait_q); +} + +void mbox_tctl(struct mbcmd *mb) +{ + u8 tid = mb->cmd_l; + struct dsptask *task = dsptask[tid]; + + if ((tid >= TASKDEV_MAX) || (task == NULL)) { + printk(KERN_ERR "mbox: TCTL with illegal tid! %d\n", tid); + return; + } + + if (!waitqueue_active(&task->dev->tctl_wait_q)) { + printk(KERN_WARNING "mbox: unexpected TCTL from DSP!\n"); + return; + } + + task->dev->tctl_stat = mb->data; + wake_up_interruptible(&task->dev->tctl_wait_q); +} + +void mbox_tcfg(struct mbcmd *mb) +{ + u8 tid = mb->cmd_l; + struct dsptask *task = dsptask[tid]; + u16 *tnm; + volatile u16 *buf; + int i; + + if ((tid >= TASKDEV_MAX) || (task == NULL)) { + printk(KERN_ERR "mbox: TCFG with illegal tid! %d\n", tid); + return; + } + if ((task->state != TASK_ST_CFGREQ) || (cfg_cmd != MBOX_CMD_DSP_TCFG)) { + printk(KERN_WARNING "mbox: unexpected TCFG from DSP!\n"); + return; + } + + if (dsp_mem_enable(ipbuf_sys_da) < 0) { + printk(KERN_ERR "mbox: TCFG - ipbuf_sys_da read failed!\n"); + dsp_mem_disable(ipbuf_sys_da); + goto out; + } + if (sync_with_dsp(&ipbuf_sys_da->s, tid, 10) < 0) { + printk(KERN_ERR "mbox: TCFG - IPBUF sync failed!\n"); + dsp_mem_disable(ipbuf_sys_da); + goto out; + } + + /* + * read configuration data on system IPBUF + */ + buf = ipbuf_sys_da->d; + task->ttyp = buf[0]; + task->ipbuf_pvt_r = MKVIRT(buf[1], buf[2]); + task->ipbuf_pvt_w = MKVIRT(buf[3], buf[4]); + task->map_base = MKVIRT(buf[5], buf[6]); + task->map_length = MKLONG(buf[7], buf[8]) << 1; /* word -> byte */ + tnm = MKVIRT(buf[9], buf[10]); + release_ipbuf_pvt(ipbuf_sys_da); + dsp_mem_disable(ipbuf_sys_da); + + /* + * copy task name string + */ + if (dsp_address_validate(tnm, TNM_LEN, "task name buffer") < 0) { + task->name[0] = '\0'; + goto out; + } + + for (i = 0; i < TNM_LEN-1; i++) { + /* avoiding byte access */ + u16 tmp = tnm[i]; + task->name[i] = tmp & 0x00ff; + if (!tmp) + break; + } + task->name[TNM_LEN-1] = '\0'; + + task->state = TASK_ST_READY; +out: + wake_up_interruptible(&cfg_wait_q); +} + +void mbox_tadd(struct mbcmd *mb) +{ + u8 tid = mb->cmd_l; + + if ((!waitqueue_active(&cfg_wait_q)) || (cfg_cmd != MBOX_CMD_DSP_TADD)) { + printk(KERN_WARNING "mbox: unexpected TADD from DSP!\n"); + return; + } + cfg_tid = tid; + wake_up_interruptible(&cfg_wait_q); +} + +void mbox_tdel(struct mbcmd *mb) +{ + u8 tid = mb->cmd_l; + + if ((!waitqueue_active(&cfg_wait_q)) || (cfg_cmd != MBOX_CMD_DSP_TDEL)) { + printk(KERN_WARNING "mbox: unexpected TDEL from DSP!\n"); + return; + } + cfg_tid = tid; + wake_up_interruptible(&cfg_wait_q); +} + +void mbox_err_fatal(u8 tid) +{ + struct dsptask *task = dsptask[tid]; + struct taskdev *dev; + + if ((tid >= TASKDEV_MAX) || (task == NULL)) { + printk(KERN_ERR "mbox: FATAL ERR with illegal tid! %d\n", tid); + return; + } + + /* wake up waiting processes */ + dev = task->dev; + wake_up_interruptible_all(&dev->read_wait_q); + wake_up_interruptible_all(&dev->write_wait_q); + wake_up_interruptible_all(&dev->tctl_wait_q); +} + +static u16 *dbg_buf; +static u16 dbg_buf_sz, dbg_line_sz; +static int dbg_rp; + +int dsp_dbg_config(u16 *buf, u16 sz, u16 lsz) +{ +#ifdef OLD_BINARY_SUPPORT + if ((mbox_revision == MBREV_3_0) || (mbox_revision == MBREV_3_2)) { + dbg_buf = NULL; + dbg_buf_sz = 0; + dbg_line_sz = 0; + dbg_rp = 0; + return 0; + } +#endif + + if (dsp_address_validate(buf, sz, "debug buffer") < 0) + return -1; + + if (lsz > sz) { + printk(KERN_ERR + "omapdsp: dbg_buf lsz (%d) is greater than its " + "buffer size (%d)\n", lsz, sz); + return -1; + } + + dbg_buf = buf; + dbg_buf_sz = sz; + dbg_line_sz = lsz; + dbg_rp = 0; + + return 0; +} + +void dsp_dbg_stop(void) +{ + dbg_buf = NULL; +} + +#ifdef OLD_BINARY_SUPPORT +static void mbox_dbg_old(struct mbcmd *mb); +#endif + +void mbox_dbg(struct mbcmd *mb) +{ + u8 tid = mb->cmd_l; + int cnt = mb->data; + char s[80], *s_end = &s[79], *p; + u16 *src; + int i; + +#ifdef OLD_BINARY_SUPPORT + if ((mbox_revision == MBREV_3_0) || (mbox_revision == MBREV_3_2)) { + mbox_dbg_old(mb); + return; + } +#endif + + if (((tid >= TASKDEV_MAX) || (dsptask[tid] == NULL)) && + (tid != TID_ANON)) { + printk(KERN_ERR "mbox: DBG with illegal tid! %d\n", tid); + return; + } + if (dbg_buf == NULL) { + printk(KERN_ERR "mbox: DBG command received, but " + "dbg_buf has not been configured yet.\n"); + return; + } + + if (dsp_mem_enable(dbg_buf) < 0) + return; + + src = &dbg_buf[dbg_rp]; + p = s; + for (i = 0; i < cnt; i++) { + u16 tmp; + /* + * Be carefull that dbg_buf should not be read with + * 1-byte access since it might be placed in DARAM/SARAM + * and it can cause unexpected byteswap. + * For example, + * *(p++) = *(src++) & 0xff; + * causes 1-byte access! + */ + tmp = *src++; + *(p++) = tmp & 0xff; + if (*(p-1) == '\n') { + *p = '\0'; + pr_info("%s", s); + p = s; + continue; + } + if (p == s_end) { + *p = '\0'; + pr_info("%s\n", s); + p = s; + continue; + } + } + if (p > s) { + *p = '\0'; + pr_info("%s\n", s); + } + if ((dbg_rp += cnt + 1) > dbg_buf_sz - dbg_line_sz) + dbg_rp = 0; + + dsp_mem_disable(dbg_buf); +} + +#ifdef OLD_BINARY_SUPPORT +static void mbox_dbg_old(struct mbcmd *mb) +{ + u8 tid = mb->cmd_l; + char s[80], *s_end = &s[79], *p; + u16 *src; + volatile u16 *buf; + int cnt; + int i; + + if (((tid >= TASKDEV_MAX) || (dsptask[tid] == NULL)) && + (tid != TID_ANON)) { + printk(KERN_ERR "mbox: DBG with illegal tid! %d\n", tid); + return; + } + if (dsp_mem_enable(ipbuf_sys_da) < 0) { + printk(KERN_ERR "mbox: DBG - ipbuf_sys_da read failed!\n"); + return; + } + if (sync_with_dsp(&ipbuf_sys_da->s, tid, 10) < 0) { + printk(KERN_ERR "mbox: DBG - IPBUF sync failed!\n"); + goto out1; + } + buf = ipbuf_sys_da->d; + cnt = buf[0]; + src = MKVIRT(buf[1], buf[2]); + if (dsp_address_validate(src, cnt, "dbg buffer") < 0) + goto out2; + + if (dsp_mem_enable(src) < 0) + goto out2; + + p = s; + for (i = 0; i < cnt; i++) { + u16 tmp; + /* + * Be carefull that ipbuf should not be read with + * 1-byte access since it might be placed in DARAM/SARAM + * and it can cause unexpected byteswap. + * For example, + * *(p++) = *(src++) & 0xff; + * causes 1-byte access! + */ + tmp = *src++; + *(p++) = tmp & 0xff; + if (*(p-1) == '\n') { + *p = '\0'; + pr_info("%s", s); + p = s; + continue; + } + if (p == s_end) { + *p = '\0'; + pr_info("%s\n", s); + p = s; + continue; + } + } + if (p > s) { + *p = '\0'; + pr_info("%s\n", s); + } + + dsp_mem_disable(src); +out2: + release_ipbuf_pvt(ipbuf_sys_da); +out1: + dsp_mem_disable(ipbuf_sys_da); +} +#endif /* OLD_BINARY_SUPPORT */ + +/* + * sysfs files: for each device + */ + +/* devname */ +static ssize_t devname_show(struct device *d, struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%s\n", to_taskdev(d)->name); +} + +/* devstate */ +static ssize_t devstate_show(struct device *d, struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%s\n", devstate_name(to_taskdev(d)->state)); +} + +/* proc_list */ +static ssize_t proc_list_show(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct taskdev *dev; + struct proc_list *pl; + int len = 0; + + dev = to_taskdev(d); + spin_lock(&dev->proc_list_lock); + list_for_each_entry(pl, &dev->proc_list, list_head) { + /* need to lock tasklist_lock before calling + * find_task_by_pid_type. */ + if (find_task_by_pid_type_ns(PIDTYPE_PID, pl->pid, &init_pid_ns) != NULL) + len += sprintf(buf + len, "%d\n", pl->pid); + read_unlock(&tasklist_lock); + } + spin_unlock(&dev->proc_list_lock); + + return len; +} + +/* taskname */ +static ssize_t taskname_show(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct taskdev *dev = to_taskdev(d); + int len; + + if (!devstate_read_lock_and_test(dev, TASKDEV_ST_ATTACHED)) + return -ENODEV; + + len = sprintf(buf, "%s\n", dev->task->name); + + devstate_read_unlock(dev); + return len; +} + +/* ttyp */ +static ssize_t ttyp_show(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct taskdev *dev = to_taskdev(d); + u16 ttyp; + int len = 0; + + if (!devstate_read_lock_and_test(dev, TASKDEV_ST_ATTACHED)) + return -ENODEV; + + ttyp = dev->task->ttyp; + len += sprintf(buf + len, "0x%04x\n", ttyp); + len += sprintf(buf + len, "%s %s send\n", + (sndtyp_acv(ttyp)) ? "active" : + "passive", + (sndtyp_wd(ttyp)) ? "word" : + (sndtyp_pvt(ttyp)) ? "private block" : + "global block"); + len += sprintf(buf + len, "%s %s receive\n", + (rcvtyp_acv(ttyp)) ? "active" : + "passive", + (rcvtyp_wd(ttyp)) ? "word" : + (rcvtyp_pvt(ttyp)) ? "private block" : + "global block"); + + devstate_read_unlock(dev); + return len; +} + +/* fifosz */ +static ssize_t fifosz_show(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct kfifo *fifo = to_taskdev(d)->rcvdt.fifo; + return sprintf(buf, "%d\n", fifo->size); +} + +static int fifosz_store(struct device *d, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct taskdev *dev = to_taskdev(d); + unsigned long fifosz; + int ret; + + fifosz = simple_strtol(buf, NULL, 10); + if (taskdev_lock_and_statelock_attached(dev, &dev->read_mutex)) + return -ENODEV; + ret = taskdev_set_fifosz(dev, fifosz); + taskdev_unlock_and_stateunlock(dev, &dev->read_mutex); + + return (ret < 0) ? ret : strlen(buf); +} + +/* fifocnt */ +static ssize_t fifocnt_show(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct kfifo *fifo = to_taskdev(d)->rcvdt.fifo; + return sprintf(buf, "%d\n", fifo->size); +} + +/* ipblink */ +static inline char *bid_name(u16 bid) +{ + static char s[6]; + + switch (bid) { + case BID_NULL: + return "NULL"; + case BID_PVT: + return "PRIVATE"; + default: + sprintf(s, "%d", bid); + return s; + } +} + +static ssize_t ipblink_show(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct rcvdt_bk_struct *rcvdt = &to_taskdev(d)->rcvdt.bk; + int len; + + spin_lock(&rcvdt->link.lock); + len = sprintf(buf, "top %s\ntail %s\n", + bid_name(rcvdt->link.top), bid_name(rcvdt->link.tail)); + spin_unlock(&rcvdt->link.lock); + + return len; +} + +/* wsz */ +static ssize_t wsz_show(struct device *d, struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", to_taskdev(d)->wsz); +} + +/* mmap */ +static ssize_t mmap_show(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct dsptask *task = to_taskdev(d)->task; + return sprintf(buf, "0x%p 0x%x\n", task->map_base, task->map_length); +} + +/* + * called from ipbuf_show() + */ +int ipbuf_is_held(u8 tid, u16 bid) +{ + struct dsptask *task = dsptask[tid]; + struct ipblink *link; + u16 b; + int ret = 0; + + if (task == NULL) + return 0; + + link = &task->dev->rcvdt.bk.link; + spin_lock(&link->lock); + ipblink_for_each(b, link) { + if (b == bid) { /* found */ + ret = 1; + break; + } + } + spin_unlock(&link->lock); + + return ret; +} + +int __init dsp_taskmod_init(void) +{ + int retval; + + memset(taskdev, 0, sizeof(void *) * TASKDEV_MAX); + memset(dsptask, 0, sizeof(void *) * TASKDEV_MAX); + + retval = register_chrdev(OMAP_DSP_TASK_MAJOR, "dsptask", + &dsp_task_fops); + if (retval < 0) { + printk(KERN_ERR + "omapdsp: failed to register task device: %d\n", retval); + return retval; + } + + retval = bus_register(&dsptask_bus); + if (retval) { + printk(KERN_ERR + "omapdsp: failed to register DSP task bus: %d\n", + retval); + unregister_chrdev(OMAP_DSP_TASK_MAJOR, "dsptask"); + return -EINVAL; + } + retval = driver_register(&dsptask_driver); + if (retval) { + printk(KERN_ERR + "omapdsp: failed to register DSP task driver: %d\n", + retval); + bus_unregister(&dsptask_bus); + unregister_chrdev(OMAP_DSP_TASK_MAJOR, "dsptask"); + return -EINVAL; + } + dsp_task_class = class_create(THIS_MODULE, "dsptask"); + if (IS_ERR(dsp_task_class)) { + printk(KERN_ERR "omapdsp: failed to create DSP task class\n"); + driver_unregister(&dsptask_driver); + bus_unregister(&dsptask_bus); + unregister_chrdev(OMAP_DSP_TASK_MAJOR, "dsptask"); + return -EINVAL; + } + + return 0; +} + +void dsp_taskmod_exit(void) +{ + class_destroy(dsp_task_class); + driver_unregister(&dsptask_driver); + bus_unregister(&dsptask_bus); + unregister_chrdev(OMAP_DSP_TASK_MAJOR, "dsptask"); +} diff --cc drivers/dsp/dspgateway/taskwatch.c index 0fab20e8b6d,00000000000..a5fea3df1a1 mode 100644,000000..100644 --- a/drivers/dsp/dspgateway/taskwatch.c +++ b/drivers/dsp/dspgateway/taskwatch.c @@@ -1,164 -1,0 +1,164 @@@ +/* + * This file is part of OMAP DSP driver (DSP Gateway version 3.3.1) + * + * Copyright (C) 2002-2006 Nokia Corporation. All rights reserved. + * + * Contact: Toshihiro Kobayashi + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include - #include ++#include +#include +#include "dsp_mbcmd.h" +#include "dsp.h" + +static DECLARE_WAIT_QUEUE_HEAD(read_wait_q); +static unsigned int change_cnt; + +void dsp_twch_touch(void) +{ + change_cnt++; + wake_up_interruptible(&read_wait_q); +} + +/* + * @count: represents the device counts of the user's interst + */ +static ssize_t dsp_twch_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + long taskstat[TASKDEV_MAX]; + int devcount = count / sizeof(long); + int i; + DEFINE_WAIT(wait); + + if (dsp_cfgstat_get_stat() != CFGSTAT_READY) { + printk(KERN_ERR "omapdsp: dsp has not been configured.\n"); + return -EINVAL; + } + + prepare_to_wait(&read_wait_q, &wait, TASK_INTERRUPTIBLE); + if (change_cnt == 0) /* last check */ + schedule(); + finish_wait(&read_wait_q, &wait); + + /* unconfigured while waiting ;-( */ + if ((change_cnt == 0) && (dsp_cfgstat_get_stat() != CFGSTAT_READY)) + return -EINVAL; + + if (devcount > TASKDEV_MAX) + devcount = TASKDEV_MAX; + + count = devcount * sizeof(long); + change_cnt = 0; + for (i = 0; i < devcount; i++) { + /* + * once the device state is read, the 'STALE' bit will be set + * so that the Dynamic Loader can distinguish the new request + * from the old one. + */ + taskstat[i] = taskdev_state_stale(i); + } + + if (copy_to_user(buf, taskstat, count)) + return -EFAULT; + + return count; +} + +static unsigned int dsp_twch_poll(struct file *file, poll_table *wait) +{ + unsigned int mask = 0; + + poll_wait(file, &read_wait_q, wait); + if (change_cnt) + mask |= POLLIN | POLLRDNORM; + + return mask; +} + +static int dsp_twch_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int ret; + + switch (cmd) { + case TWCH_IOCTL_MKDEV: + { + char name[TNM_LEN]; + if (copy_from_user(name, (void __user *)arg, TNM_LEN)) + return -EFAULT; + name[TNM_LEN-1] = '\0'; + ret = dsp_mkdev(name); + break; + } + + case TWCH_IOCTL_RMDEV: + { + char name[TNM_LEN]; + if (copy_from_user(name, (void __user *)arg, TNM_LEN)) + return -EFAULT; + name[TNM_LEN-1] = '\0'; + ret = dsp_rmdev(name); + break; + } + + case TWCH_IOCTL_TADD: + { + struct omap_dsp_taddinfo ti; + if (copy_from_user(&ti, (void __user *)arg, sizeof(ti))) + return -EFAULT; + ret = dsp_tadd_minor(ti.minor, ti.taskadr); + break; + } + + case TWCH_IOCTL_TDEL: + ret = dsp_tdel_minor(arg); + break; + + case TWCH_IOCTL_TKILL: + ret = dsp_tkill_minor(arg); + break; + + default: + return -ENOIOCTLCMD; + } + + return ret; +} + +struct file_operations dsp_twch_fops = { + .owner = THIS_MODULE, + .read = dsp_twch_read, + .poll = dsp_twch_poll, + .ioctl = dsp_twch_ioctl, +}; + +void dsp_twch_start(void) +{ + change_cnt = 1; /* first read will not wait */ +} + +void dsp_twch_stop(void) +{ + wake_up_interruptible(&read_wait_q); +} diff --cc drivers/dsp/dspgateway/uaccess_dsp.h index 028814ffbe9,00000000000..839a51aa071 mode 100644,000000..100644 --- a/drivers/dsp/dspgateway/uaccess_dsp.h +++ b/drivers/dsp/dspgateway/uaccess_dsp.h @@@ -1,176 -1,0 +1,176 @@@ +/* + * This file is part of OMAP DSP driver (DSP Gateway version 3.3.1) + * + * Copyright (C) 2002-2006 Nokia Corporation. All rights reserved. + * + * Contact: Toshihiro Kobayashi + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef _OMAP_DSP_UACCESS_DSP_H +#define _OMAP_DSP_UACCESS_DSP_H + +#include - #include ++#include +#include "dsp.h" + +#define HAVE_ASM_COPY_FROM_USER_DSP_2B + +#ifdef HAVE_ASM_COPY_FROM_USER_DSP_2B +extern unsigned long __copy_from_user_dsp_2b(void *to, + const void __user *from); +extern unsigned long __copy_to_user_dsp_2b(void __user *to, + const void *from); +#endif + +#ifndef HAVE_ASM_COPY_FROM_USER_DSP_2B +static inline unsigned long copy_from_user_dsp_2b(void *to, + const void *from) +{ + unsigned short tmp; + + if (__copy_from_user(&tmp, from, 2)) + return 2; + /* expecting compiler to generate "strh" instruction */ + *((unsigned short *)to) = tmp; + return 0; +} +#endif + +/* + * @n must be multiple of 2 + */ +static inline unsigned long copy_from_user_dsp(void *to, const void *from, + unsigned long n) +{ + if (access_ok(VERIFY_READ, from, n)) { + if ((is_dsp_internal_mem(to)) && + (((unsigned long)to & 2) || (n & 2))) { + /* + * DARAM/SARAM with odd word alignment + */ + unsigned long n4; + unsigned long last_n; + + /* dest not aligned -- copy 2 bytes */ + if (((unsigned long)to & 2) && (n >= 2)) { +#ifdef HAVE_ASM_COPY_FROM_USER_DSP_2B + if (__copy_from_user_dsp_2b(to, from)) +#else + if (copy_from_user_dsp_2b(to, from)) +#endif + return n; + to += 2; + from += 2; + n -= 2; + } + /* middle 4*n bytes */ + last_n = n & 2; + n4 = n - last_n; + if ((n = __copy_from_user(to, from, n4)) != 0) + return n + last_n; + /* last 2 bytes */ + if (last_n) { + to += n4; + from += n4; +#ifdef HAVE_ASM_COPY_FROM_USER_DSP_2B + if (__copy_from_user_dsp_2b(to, from)) +#else + if (copy_from_user_dsp_2b(to, from)) +#endif + return 2; + n = 0; + } + } else { + /* + * DARAM/SARAM with 4-byte alignment or + * external memory + */ + n = __copy_from_user(to, from, n); + } + } + else /* security hole - plug it */ + memzero(to, n); + return n; +} + +#ifndef HAVE_ASM_COPY_FROM_USER_DSP_2B +static inline unsigned long copy_to_user_dsp_2b(void *to, const void *from) +{ + /* expecting compiler to generate "strh" instruction */ + unsigned short tmp = *(unsigned short *)from; + + return __copy_to_user(to, &tmp, 2); +} +#endif + +/* + * @n must be multiple of 2 + */ +static inline unsigned long copy_to_user_dsp(void *to, const void *from, + unsigned long n) +{ + if (access_ok(VERIFY_WRITE, to, n)) { + if ((is_dsp_internal_mem(from)) && + (((unsigned long)to & 2) || (n & 2))) { + /* + * DARAM/SARAM with odd word alignment + */ + unsigned long n4; + unsigned long last_n; + + /* dest not aligned -- copy 2 bytes */ + if (((unsigned long)to & 2) && (n >= 2)) { +#ifdef HAVE_ASM_COPY_FROM_USER_DSP_2B + if (__copy_to_user_dsp_2b(to, from)) +#else + if (copy_to_user_dsp_2b(to, from)) +#endif + return n; + to += 2; + from += 2; + n -= 2; + } + /* middle 4*n bytes */ + last_n = n & 2; + n4 = n - last_n; + if ((n = __copy_to_user(to, from, n4)) != 0) + return n + last_n; + /* last 2 bytes */ + if (last_n) { + to += n4; + from += n4; +#ifdef HAVE_ASM_COPY_FROM_USER_DSP_2B + if (__copy_to_user_dsp_2b(to, from)) +#else + if (copy_to_user_dsp_2b(to, from)) +#endif + return 2; + n = 0; + } + } else { + /* + * DARAM/SARAM with 4-byte alignment or + * external memory + */ + n = __copy_to_user(to, from, n); + } + } + return n; +} + +#endif /* _OMAP_DSP_UACCESS_DSP_H */ diff --cc drivers/hwmon/omap34xx_temp.c index bcb3c9ae153,00000000000..18e07643165 mode 100644,000000..100644 --- a/drivers/hwmon/omap34xx_temp.c +++ b/drivers/hwmon/omap34xx_temp.c @@@ -1,268 -1,0 +1,268 @@@ +/* + * omap34xx_temp.c - Linux kernel module for OMAP34xx hardware monitoring + * + * Copyright (C) 2008 Nokia Corporation + * + * Written by Peter De Schrijver + * + * Inspired by k8temp.c + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include - #include - #include ++#include ++#include + +#define TEMP_SENSOR_SOC BIT(8) +#define TEMP_SENSOR_EOCZ BIT(7) + +/* minimum delay for EOCZ rise after SOC rise is + * 11 cycles of the 32.768Khz clock */ +#define EOCZ_MIN_RISING_DELAY (11 * 30518) + +/* maximum delay for EOCZ rise after SOC rise is + * 14 cycles of the 32.768Khz clock */ +#define EOCZ_MAX_RISING_DELAY (14 * 30518) + +/* minimum delay for EOCZ falling is + * 36 cycles of the 32.768Khz clock */ +#define EOCZ_MIN_FALLING_DELAY (36 * 30518) + +/* maximum delay for EOCZ falling is + * 40 cycles of the 32.768Khz clock */ +#define EOCZ_MAX_FALLING_DELAY (40 * 30518) + +struct omap34xx_data { + struct device *hwmon_dev; + struct clk *clk_32k; + struct mutex update_lock; + const char *name; + char valid; + unsigned long last_updated; + u32 temp; +}; + +static struct platform_device omap34xx_temp_device = { + .name = "omap34xx_temp", + .id = -1, +}; + +static int adc_to_temp[] = { + -40, -40, -40, -40, -40, -39, -38, -36, -34, -32, -31, -29, -28, -26, + -25, -24, -22, -21, -19, -18, -17, -15, -14, -12, -11, -9, -8, -7, -5, + -4, -2, -1, 0, 1, 3, 4, 5, 7, 8, 10, 11, 13, 14, 15, 17, 18, 20, 21, + 22, 24, 25, 27, 28, 30, 31, 32, 34, 35, 37, 38, 39, 41, 42, 44, 45, + 47, 48, 49, 51, 52, 53, 55, 56, 58, 59, 60, 62, 63, 65, 66, 67, 69, + 70, 72, 73, 74, 76, 77, 79, 80, 81, 83, 84, 85, 87, 88, 89, 91, 92, + 94, 95, 96, 98, 99, 100, 102, 103, 105, 106, 107, 109, 110, 111, 113, + 114, 116, 117, 118, 120, 121, 122, 124, 124, 125, 125, 125, 125, 125}; + +static inline u32 wait_for_eocz(int min_delay, int max_delay, u32 level) +{ + struct timespec timeout; + ktime_t expire; + u32 temp_sensor_reg; + + level &= 1; + level *= TEMP_SENSOR_EOCZ; + + expire = ktime_add_ns(ktime_get(), max_delay); + timeout = ns_to_timespec(min_delay); + hrtimer_nanosleep(&timeout, NULL, HRTIMER_MODE_REL, CLOCK_MONOTONIC); + do { + temp_sensor_reg = omap_ctrl_readl(OMAP343X_CONTROL_TEMP_SENSOR); + if ((temp_sensor_reg & TEMP_SENSOR_EOCZ) == level) + break; + } while (ktime_us_delta(expire, ktime_get()) > 0); + + return (temp_sensor_reg & TEMP_SENSOR_EOCZ) == level; +} + +static void omap34xx_update(struct omap34xx_data *data) +{ + u32 temp_sensor_reg; + + mutex_lock(&data->update_lock); + + if (!data->valid + || time_after(jiffies, data->last_updated + HZ)) { + + clk_enable(data->clk_32k); + + temp_sensor_reg = omap_ctrl_readl(OMAP343X_CONTROL_TEMP_SENSOR); + temp_sensor_reg |= TEMP_SENSOR_SOC; + omap_ctrl_writel(temp_sensor_reg, OMAP343X_CONTROL_TEMP_SENSOR); + + if (!wait_for_eocz(EOCZ_MIN_RISING_DELAY, + EOCZ_MAX_RISING_DELAY, 1)) + goto err; + + temp_sensor_reg = omap_ctrl_readl(OMAP343X_CONTROL_TEMP_SENSOR); + temp_sensor_reg &= ~TEMP_SENSOR_SOC; + omap_ctrl_writel(temp_sensor_reg, OMAP343X_CONTROL_TEMP_SENSOR); + + if (!wait_for_eocz(EOCZ_MIN_FALLING_DELAY, + EOCZ_MAX_FALLING_DELAY, 0)) + goto err; + + data->temp = omap_ctrl_readl(OMAP343X_CONTROL_TEMP_SENSOR) & + ((1<<7) - 1); + data->last_updated = jiffies; + data->valid = 1; + +err: + clk_disable(data->clk_32k); + } + + mutex_unlock(&data->update_lock); +} + +static ssize_t show_name(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct omap34xx_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", data->name); +} + +static ssize_t show_temp_raw(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct omap34xx_data *data = dev_get_drvdata(dev); + + omap34xx_update(data); + + return sprintf(buf, "%d\n", data->temp); +} + +static ssize_t show_temp(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct omap34xx_data *data = dev_get_drvdata(dev); + + omap34xx_update(data); + + return sprintf(buf, "%d\n", adc_to_temp[data->temp]); +} + +static SENSOR_DEVICE_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, 0); +static SENSOR_DEVICE_ATTR_2(temp1_input_raw, S_IRUGO, show_temp_raw, + NULL, 0, 0); +static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); + +static int __devinit omap34xx_temp_probe(void) +{ + int err; + struct omap34xx_data *data; + + err = platform_device_register(&omap34xx_temp_device); + if (err) { + printk(KERN_ERR + "Unable to register omap34xx temperature device\n"); + goto exit; + } + + data = kzalloc(sizeof(struct omap34xx_data), GFP_KERNEL); + if (!data) { + err = -ENOMEM; + goto exit_platform; + } + + dev_set_drvdata(&omap34xx_temp_device.dev, data); + mutex_init(&data->update_lock); + data->name = "omap34xx_temp"; + + data->clk_32k = clk_get(&omap34xx_temp_device.dev, "ts_fck"); + if (IS_ERR(data->clk_32k)) { + err = PTR_ERR(data->clk_32k); + goto exit_free; + } + + err = device_create_file(&omap34xx_temp_device.dev, + &sensor_dev_attr_temp1_input.dev_attr); + if (err) + goto clock_free; + + err = device_create_file(&omap34xx_temp_device.dev, + &sensor_dev_attr_temp1_input_raw.dev_attr); + if (err) + goto exit_remove; + + err = device_create_file(&omap34xx_temp_device.dev, &dev_attr_name); + if (err) + goto exit_remove_raw; + + data->hwmon_dev = hwmon_device_register(&omap34xx_temp_device.dev); + + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto exit_remove_all; + } + + return 0; + +exit_remove_all: + device_remove_file(&omap34xx_temp_device.dev, + &dev_attr_name); +exit_remove_raw: + device_remove_file(&omap34xx_temp_device.dev, + &sensor_dev_attr_temp1_input_raw.dev_attr); +exit_remove: + device_remove_file(&omap34xx_temp_device.dev, + &sensor_dev_attr_temp1_input.dev_attr); +clock_free: + clk_put(data->clk_32k); + +exit_free: + kfree(data); +exit_platform: + platform_device_unregister(&omap34xx_temp_device); +exit: + return err; +} + +static int __init omap34xx_temp_init(void) +{ + return omap34xx_temp_probe(); +} + +static void __exit omap34xx_temp_exit(void) +{ + struct omap34xx_data *data = + dev_get_drvdata(&omap34xx_temp_device.dev); + + clk_put(data->clk_32k); + hwmon_device_unregister(data->hwmon_dev); + device_remove_file(&omap34xx_temp_device.dev, + &sensor_dev_attr_temp1_input.dev_attr); + device_remove_file(&omap34xx_temp_device.dev, &dev_attr_name); + kfree(data); + platform_device_unregister(&omap34xx_temp_device); +} + +MODULE_AUTHOR("Peter De Schrijver"); +MODULE_DESCRIPTION("Omap34xx temperature sensor"); +MODULE_LICENSE("GPL"); + +module_init(omap34xx_temp_init) +module_exit(omap34xx_temp_exit) + diff --cc drivers/i2c/chips/lp5521.c index 32664c0e184,00000000000..c0862d9f269 mode 100644,000000..100644 --- a/drivers/i2c/chips/lp5521.c +++ b/drivers/i2c/chips/lp5521.c @@@ -1,585 -1,0 +1,585 @@@ +/* + * drivers/i2c/chips/lp5521.c + * + * Copyright (C) 2007 Nokia Corporation + * + * Written by Mathias Nyman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include - #include ++#include + +#define LP5521_DRIVER_NAME "lp5521" + +#ifdef LED_CONNECTED_WRONG +#define LP5521_REG_R_PWM 0x04 +#define LP5521_REG_B_PWM 0x02 +#else +#define LP5521_REG_R_PWM 0x02 +#define LP5521_REG_B_PWM 0x04 +#endif +#define LP5521_REG_ENABLE 0x00 +#define LP5521_REG_OP_MODE 0x01 +#define LP5521_REG_G_PWM 0x03 +#define LP5521_REG_R_CNTRL 0x05 +#define LP5521_REG_G_CNTRL 0x06 +#define LP5521_REG_B_CNTRL 0x07 +#define LP5521_REG_MISC 0x08 +#define LP5521_REG_R_CHANNEL_PC 0x09 +#define LP5521_REG_G_CHANNEL_PC 0x0a +#define LP5521_REG_B_CHANNEL_PC 0x0b +#define LP5521_REG_STATUS 0x0c +#define LP5521_REG_RESET 0x0d +#define LP5521_REG_GPO 0x0e +#define LP5521_REG_R_PROG_MEM 0x10 +#define LP5521_REG_G_PROG_MEM 0x30 +#define LP5521_REG_B_PROG_MEM 0x50 + +#define LP5521_MODE_LOAD "load" +#define LP5521_MODE_RUN "run" +#define LP5521_MODE_DIRECT_CONTROL "direct" + +#define LP5521_CURRENT_1m5 0x0f +#define LP5521_CURRENT_3m1 0x1f +#define LP5521_CURRENT_4m7 0x2f +#define LP5521_CURRENT_6m3 0x3f +#define LP5521_CURRENT_7m9 0x4f +#define LP5521_CURRENT_9m5 0x5f +#define LP5521_CURRENT_11m1 0x6f +#define LP5521_CURRENT_12m7 0x7f +#define LP5521_CURRENT_14m3 0x8f +#define LP5521_CURRENT_15m9 0x9f +#define LP5521_CURRENT_17m5 0xaf +#define LP5521_CURRENT_19m1 0xbf +#define LP5521_CURRENT_20m7 0xcf +#define LP5521_CURRENT_22m3 0xdf +#define LP5521_CURRENT_23m9 0xef +#define LP5521_CURRENT_25m5 0xff + +#define LP5521_PROGRAM_LENGTH 32 /* in bytes */ + +struct lp5521_chip { + struct mutex lock; + struct i2c_client *client; + char *mode; + int red; + int green; + int blue; +}; + +static int lp5521_set_mode(struct lp5521_chip *chip, char *mode); + +static int lp5521_write(struct i2c_client *client, u8 reg, u8 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +static int lp5521_read(struct i2c_client *client, u8 reg, u8 *buf) +{ + s32 ret = i2c_smbus_read_byte_data(client, reg); + + if (ret < 0) + return -EIO; + + *buf = ret; + return 0; +} + +static int lp5521_configure(struct i2c_client *client) +{ + int ret = 0; + + /* Enable chip and set light to logarithmic mode*/ + ret |= lp5521_write(client, LP5521_REG_ENABLE, 0xc0); + + /* setting all color pwms to direct control mode */ + ret |= lp5521_write(client, LP5521_REG_OP_MODE, 0x3f); + + /* setting current to 4.7 mA for all channels */ + ret |= lp5521_write(client, LP5521_REG_R_CNTRL, LP5521_CURRENT_4m7); + ret |= lp5521_write(client, LP5521_REG_G_CNTRL, LP5521_CURRENT_4m7); + ret |= lp5521_write(client, LP5521_REG_B_CNTRL, LP5521_CURRENT_4m7); + + /* Enable auto-powersave, set charge pump to auto, red to battery */ + ret |= lp5521_write(client, LP5521_REG_MISC, 0x3c); + + /* initialize all channels pwm to zero */ + ret |= lp5521_write(client, LP5521_REG_R_PWM, 0); + ret |= lp5521_write(client, LP5521_REG_G_PWM, 0); + ret |= lp5521_write(client, LP5521_REG_B_PWM, 0); + + /* Not much can be done about errors at this point */ + return ret; +} + +static int lp5521_load_program(struct lp5521_chip *chip, u8 *pattern) +{ + struct i2c_client *client = chip->client; + int ret = 0; + + /* Enter load program mode for all led channels */ + ret |= lp5521_write(client, LP5521_REG_OP_MODE, 0x15); /* 0001 0101 */ + if (ret) + return ret; + + if (chip->red) + ret |= i2c_smbus_write_i2c_block_data(client, + LP5521_REG_R_PROG_MEM, + LP5521_PROGRAM_LENGTH, + pattern); + if (chip->green) + ret |= i2c_smbus_write_i2c_block_data(client, + LP5521_REG_G_PROG_MEM, + LP5521_PROGRAM_LENGTH, + pattern); + if (chip->blue) + ret |= i2c_smbus_write_i2c_block_data(client, + LP5521_REG_B_PROG_MEM, + LP5521_PROGRAM_LENGTH, + pattern); + + return ret; +} + +static int lp5521_run_program(struct lp5521_chip *chip) +{ + struct i2c_client *client = chip->client; + int ret; + u8 mask = 0xc0; + u8 exec_state = 0; + u8 enable_reg; + + ret = lp5521_read(client, LP5521_REG_ENABLE, &enable_reg); + if (ret) + goto fail; + + enable_reg &= mask; + + /* set all active channels exec state to countinous run*/ + exec_state |= (chip->red << 5); + exec_state |= (chip->green << 3); + exec_state |= (chip->blue << 1); + + enable_reg |= exec_state; + + ret |= lp5521_write(client, LP5521_REG_ENABLE, enable_reg); + + /* set op-mode to run for active channels, disabled for others */ + ret |= lp5521_write(client, LP5521_REG_OP_MODE, exec_state); + +fail: + return ret; +} + +/*--------------------------------------------------------------*/ +/* Sysfs interface */ +/*--------------------------------------------------------------*/ + +static ssize_t show_active_channels(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct lp5521_chip *chip = dev_get_drvdata(dev); + char channels[4]; + int pos = 0; + +#ifdef LED_CONNECTED_WRONG + if (chip->blue) + pos += sprintf(channels + pos, "r"); + if (chip->green) + pos += sprintf(channels + pos, "g"); + if (chip->red) + pos += sprintf(channels + pos, "b"); + +#else + if (chip->red) + pos += sprintf(channels + pos, "r"); + if (chip->green) + pos += sprintf(channels + pos, "g"); + if (chip->blue) + pos += sprintf(channels + pos, "b"); +#endif + + channels[pos] = '\0'; + + return sprintf(buf, "%s\n", channels); +} + +static ssize_t store_active_channels(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct lp5521_chip *chip = dev_get_drvdata(dev); + + chip->red = 0; + chip->green = 0; + chip->blue = 0; + +#ifdef LED_CONNECTED_WRONG + if (strchr(buf, 'r') != NULL) + chip->blue = 1; + if (strchr(buf, 'b') != NULL) + chip->red = 1; +#else + if (strchr(buf, 'r') != NULL) + chip->red = 1; + if (strchr(buf, 'b') != NULL) + chip->blue = 1; +#endif + if (strchr(buf, 'g') != NULL) + chip->green = 1; + + return len; +} + +static ssize_t show_color(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + int ret = 0; + u8 r, g, b; + + ret |= lp5521_read(client, LP5521_REG_R_PWM, &r); + ret |= lp5521_read(client, LP5521_REG_G_PWM, &g); + ret |= lp5521_read(client, LP5521_REG_B_PWM, &b); + + if (ret) + return ret; + + return sprintf(buf, "%.2x:%.2x:%.2x\n", r, g, b); +} + +static ssize_t store_color(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lp5521_chip *chip = i2c_get_clientdata(client); + int ret; + unsigned r, g, b; + + + ret = sscanf(buf, "%2x:%2x:%2x", &r, &g, &b); + if (ret != 3) + return -EINVAL; + + mutex_lock(&chip->lock); + + ret = lp5521_write(client, LP5521_REG_R_PWM, (u8)r); + ret = lp5521_write(client, LP5521_REG_G_PWM, (u8)g); + ret = lp5521_write(client, LP5521_REG_B_PWM, (u8)b); + + mutex_unlock(&chip->lock); + + return len; +} + +static ssize_t store_load(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct lp5521_chip *chip = dev_get_drvdata(dev); + int ret, nrchars, offset = 0, i = 0; + char c[3]; + unsigned cmd; + u8 pattern[LP5521_PROGRAM_LENGTH] = {0}; + + while ((offset < len - 1) && (i < LP5521_PROGRAM_LENGTH)) { + + /* separate sscanfs because length is working only for %s */ + ret = sscanf(buf + offset, "%2s%n ", c, &nrchars); + ret = sscanf(c, "%2x", &cmd); + if (ret != 1) + goto fail; + pattern[i] = (u8)cmd; + + offset += nrchars; + i++; + } + + /* pattern commands are always two bytes long */ + if (i % 2) + goto fail; + + mutex_lock(&chip->lock); + + ret = lp5521_load_program(chip, pattern); + mutex_unlock(&chip->lock); + + if (ret) { + dev_err(dev, "lp5521 failed loading pattern\n"); + return ret; + } + + return len; +fail: + dev_err(dev, "lp5521 wrong pattern format\n"); + return -EINVAL; +} + +static ssize_t show_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct lp5521_chip *chip = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", chip->mode); +} + +static ssize_t store_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct lp5521_chip *chip = dev_get_drvdata(dev); + + mutex_lock(&chip->lock); + + if (!strncmp(buf, "run", 3)) + lp5521_set_mode(chip, LP5521_MODE_RUN); + else if (!strncmp(buf, "load", 4)) + lp5521_set_mode(chip, LP5521_MODE_LOAD); + else if (!strncmp(buf, "direct", 6)) + lp5521_set_mode(chip, LP5521_MODE_DIRECT_CONTROL); + + mutex_unlock(&chip->lock); + + return len; +} + +static ssize_t show_current(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + int ret = 0; + u8 r_curr, g_curr, b_curr; + + ret |= lp5521_read(client, LP5521_REG_R_CNTRL, &r_curr); + ret |= lp5521_read(client, LP5521_REG_G_CNTRL, &g_curr); + ret |= lp5521_read(client, LP5521_REG_B_CNTRL, &b_curr); + + if (ret) + return ret; + + r_curr = r_curr >> 4; + g_curr = g_curr >> 4; + b_curr = b_curr >> 4; + + if (r_curr == g_curr && g_curr == b_curr) + return sprintf(buf, "%x\n", r_curr); + else + return sprintf(buf, "%x %x %x\n", r_curr, g_curr, b_curr); +} + +static ssize_t store_current(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct lp5521_chip *chip = dev_get_drvdata(dev); + struct i2c_client *client = chip->client; + int ret; + unsigned curr; + + ret = sscanf(buf, "%1x", &curr); + if (ret != 1) + return -EINVAL; + + /* current level is determined by the 4 upper bits, rest is ones */ + curr = (curr << 4) | 0x0f; + + mutex_lock(&chip->lock); + + ret |= lp5521_write(client, LP5521_REG_R_CNTRL, (u8)curr); + ret |= lp5521_write(client, LP5521_REG_G_CNTRL, (u8)curr); + ret |= lp5521_write(client, LP5521_REG_B_CNTRL, (u8)curr); + + mutex_unlock(&chip->lock); + + return len; +} + +static DEVICE_ATTR(color, S_IRUGO | S_IWUGO, show_color, store_color); +static DEVICE_ATTR(load, S_IWUGO, NULL, store_load); +static DEVICE_ATTR(mode, S_IRUGO | S_IWUGO, show_mode, store_mode); +static DEVICE_ATTR(active_channels, S_IRUGO | S_IWUGO, + show_active_channels, store_active_channels); +static DEVICE_ATTR(led_current, S_IRUGO | S_IWUGO, show_current, store_current); + +static int lp5521_register_sysfs(struct i2c_client *client) +{ + struct device *dev = &client->dev; + int ret; + + ret = device_create_file(dev, &dev_attr_color); + if (ret) + goto fail1; + ret = device_create_file(dev, &dev_attr_load); + if (ret) + goto fail2; + ret = device_create_file(dev, &dev_attr_active_channels); + if (ret) + goto fail3; + ret = device_create_file(dev, &dev_attr_mode); + if (ret) + goto fail4; + ret = device_create_file(dev, &dev_attr_led_current); + if (ret) + goto fail5; + return 0; + +fail5: + device_remove_file(dev, &dev_attr_mode); +fail4: + device_remove_file(dev, &dev_attr_active_channels); +fail3: + device_remove_file(dev, &dev_attr_load); +fail2: + device_remove_file(dev, &dev_attr_color); +fail1: + return ret; +} + +static void lp5521_unregister_sysfs(struct i2c_client *client) +{ + struct lp5521_chip *chip = i2c_get_clientdata(client); + struct device *dev = &client->dev; + + device_remove_file(dev, &dev_attr_led_current); + device_remove_file(dev, &dev_attr_mode); + device_remove_file(dev, &dev_attr_active_channels); + device_remove_file(dev, &dev_attr_color); + + if (!strcmp(chip->mode, LP5521_MODE_LOAD)) + device_remove_file(dev, &dev_attr_load); +} + +/*--------------------------------------------------------------*/ +/* Set chip operating mode */ +/*--------------------------------------------------------------*/ + +static int lp5521_set_mode(struct lp5521_chip *chip, char *mode) +{ + struct i2c_client *client = chip->client ; + int ret = 0; + + /* if in that mode already do nothing, except for run */ + if (!strcmp(mode, chip->mode) && strcmp(mode, LP5521_MODE_RUN)) + return 0; + + if (!strcmp(mode, LP5521_MODE_RUN)) + ret = lp5521_run_program(chip); + + if (!strcmp(mode, LP5521_MODE_LOAD)) + ret |= lp5521_write(client, LP5521_REG_OP_MODE, 0x15); + + if (!strcmp(mode, LP5521_MODE_DIRECT_CONTROL)) + ret |= lp5521_write(client, LP5521_REG_OP_MODE, 0x3F); + + chip->mode = mode; + + return ret; +} + +/*--------------------------------------------------------------*/ +/* Probe, Attach, Remove */ +/*--------------------------------------------------------------*/ +static struct i2c_driver lp5521_driver; + +static int lp5521_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct lp5521_chip *chip; + int ret = 0; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->client = client; + strncpy(client->name, LP5521_DRIVER_NAME, I2C_NAME_SIZE); + i2c_set_clientdata(client, chip); + + mutex_init(&chip->lock); + + ret = lp5521_configure(client); + if (ret < 0) { + dev_err(&client->dev, "lp5521 error configuring chip \n"); + goto fail1; + } + + /* Set default values */ + chip->mode = LP5521_MODE_DIRECT_CONTROL; + chip->red = 1; + chip->green = 1; + chip->blue = 1; + + ret = lp5521_register_sysfs(client); + if (ret) + dev_err(&client->dev, "lp5521 registering sysfs failed \n"); + + return ret; + +fail1: + kfree(chip); + return ret; +} + +static int lp5521_remove(struct i2c_client *client) +{ + struct lp5521_chip *chip = i2c_get_clientdata(client); + + lp5521_unregister_sysfs(client); + kfree(chip); + + return 0; +} + +static const struct i2c_device_id lp5521_id[] = { + { LP5521_DRIVER_NAME, 0}, + { }, +}; +MODULE_DEVICE_TABLE(i2c, lp5521_id); + +static struct i2c_driver lp5521_driver = { + .driver = { + .name = LP5521_DRIVER_NAME, + }, + .probe = lp5521_probe, + .remove = __devexit_p(lp5521_remove), + .id_table = lp5521_id, +}; + +static int __init lp5521_init(void) +{ + return i2c_add_driver(&lp5521_driver); +} + +static void __exit lp5521_exit(void) +{ + i2c_del_driver(&lp5521_driver); +} + +MODULE_AUTHOR("Mathias Nyman "); +MODULE_DESCRIPTION("lp5521 LED driver"); +MODULE_LICENSE("GPL"); + +module_init(lp5521_init); +module_exit(lp5521_exit); diff --cc drivers/i2c/chips/menelaus.c index 40eb14af7e0,176126d3a01..3e6e2ade92c --- a/drivers/i2c/chips/menelaus.c +++ b/drivers/i2c/chips/menelaus.c @@@ -41,12 -40,11 +41,12 @@@ #include #include #include +#include +#include #include - #include + #include -#include #define DRIVER_NAME "menelaus" diff --cc drivers/i2c/chips/tlv320aic23.c index 544cc28fb23,00000000000..10671e803e4 mode 100644,000000..100644 --- a/drivers/i2c/chips/tlv320aic23.c +++ b/drivers/i2c/chips/tlv320aic23.c @@@ -1,675 -1,0 +1,675 @@@ +/* + * Texas Instrumens TLV320AIC23 audio codec's i2c interface. + * + * Copyright (c) by Kai Svahn + * Copyright (c) by Jussi Laako + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include - #include - #include ++#include ++#include + +#define TLV320AIC23_VERSION "1.8" +#define TLV320AIC23_DATE "10-Feb-2006" +#define MAX_VOL 100 +#define MIN_VOL 0 +#define MAX_GAIN 100 +#define MIN_GAIN 0 +#define OUTPUT_VOLUME_MIN LHV_MIN +#define OUTPUT_VOLUME_MAX LHV_MAX +#define OUTPUT_VOLUME_RANGE (OUTPUT_VOLUME_MAX - OUTPUT_VOLUME_MIN) +#define INPUT_VOLUME_MIN LIV_MIN +#define INPUT_VOLUME_MAX LIV_MAX +#define INPUT_VOLUME_RANGE (INPUT_VOLUME_MAX - INPUT_VOLUME_MIN) + +/* I2C Addresses to scan */ +static unsigned short normal_i2c[] = { TLV320AIC23ID1, TLV320AIC23ID2, \ + I2C_CLIENT_END }; +/*static unsigned short normal_i2c_range[] = { I2C_CLIENT_END };*/ + +/* This makes all addr_data:s */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver aic23_driver; +static struct i2c_client *new_client; +static int selftest; +static struct platform_device audio_i2c_device; + +static struct aic23_info { + u16 volume_reg_left; + u16 volume_reg_right; + u16 input_gain_reg_left; + u16 input_gain_reg_right; + u16 power; /* For POWER_DOWN_CONTROL_ADDR */ + u16 mask; /* For ANALOG_AUDIO_CONTROL_ADDR */ + int mic_loopback; + int mic_enable; + int sta; + int power_down; + int initialized; +} aic23_info_l; + +static int _aic23_write_value(struct i2c_client *client, u8 reg, u16 value) +{ + u8 val, wreg; + + /* TLV320AIC23 has 7 bit address and 9 bits of data + * so we need to switch one data bit into reg and rest + * of data into val + */ + + wreg = (reg << 1); + val = (0x01 & (value >> 8)); + wreg = (wreg | val); + val = (0x00ff & value); + + return i2c_smbus_write_byte_data(client, wreg, val); +} + +int aic23_write_value(u8 reg, u16 value) +{ + static struct i2c_client *client; + client = new_client; + _aic23_write_value(client, reg, value); + + return 0; +} + +/* + * Configures the McBSP3 which is used to send clock to the AIC23 codec. + * The input clock rate from DSP is 12MHz. + * The DSP clock must be on before this is called. + */ +static int omap_mcbsp3_aic23_clock_init(void) +{ + u16 w; + + /* enable 12MHz clock to mcbsp 1 & 3 */ + __raw_writew(__raw_readw(DSP_IDLECT2) | (1<<1), DSP_IDLECT2); + __raw_writew(__raw_readw(DSP_RSTCT2) | 1 | 1<<1, DSP_RSTCT2); + + /* disable sample rate generator */ + OMAP_MCBSP_WRITE(OMAP1610_MCBSP3_BASE, SPCR1, 0x0000); + OMAP_MCBSP_WRITE(OMAP1610_MCBSP3_BASE, SPCR2, 0x0000); + + /* pin control register */ + OMAP_MCBSP_WRITE(OMAP1610_MCBSP3_BASE, PCR0,(CLKXM | CLKXP | CLKRP)); + + /* configure srg to send 12MHz pulse from dsp peripheral clock */ + OMAP_MCBSP_WRITE(OMAP1610_MCBSP3_BASE, SRGR1, 0x0000); + OMAP_MCBSP_WRITE(OMAP1610_MCBSP3_BASE, SRGR2, CLKSM); + + /* enable sample rate generator */ + w = OMAP_MCBSP_READ(OMAP1610_MCBSP3_BASE, SPCR2); + OMAP_MCBSP_WRITE(OMAP1610_MCBSP3_BASE, SPCR2, (w | FREE | GRST)); + printk("Clock enabled to MCBSP1 & 3 \n"); + + return 0; +} + +static int aic23_detect_client(struct i2c_adapter *adapter, int address, + int kind) +{ + int err = 0; + const char *client_name = "TLV320AIC23 Audio Codec"; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_WRITE_BYTE)) { + printk(KERN_WARNING "%s functionality check failed\n", + client_name); + return err; + } + + if (!(new_client = kmalloc(sizeof(struct i2c_client), + GFP_KERNEL))) { + err = -ENOMEM; + printk(KERN_WARNING "Couldn't allocate memory for %s\n", + client_name); + return err; + } + + memset(new_client, 0x00, sizeof(struct i2c_client)); + new_client->addr = address; + new_client->adapter = adapter; + new_client->driver = &aic23_driver; + new_client->flags = 0; + strlcpy(new_client->name, client_name, I2C_NAME_SIZE); + + if ((err = i2c_attach_client(new_client))) { + printk(KERN_WARNING "Couldn't attach %s\n", client_name); + kfree(new_client); + return err; + } + + if (platform_device_register(&audio_i2c_device)) { + printk(KERN_WARNING "Failed to register audio i2c device\n"); + selftest = -ENODEV; + return selftest; + } + /* FIXME: Do in board-specific file */ + omap_mcbsp3_aic23_clock_init(); + + if (!aic23_info_l.power_down) + aic23_power_up(); + aic23_info_l.initialized = 1; + + return 0; +} + +static int aic23_detach_client(struct i2c_client *client) +{ + int err; + + platform_device_unregister(&audio_i2c_device); + + if ((err = i2c_detach_client(client))) { + printk("aic23.o: Client deregistration failed, \ + client not detached.\n"); + return err; + } + kfree(client); + return 0; +} + +static int aic23_attach_adapter(struct i2c_adapter *adapter) +{ + int res; + + res = i2c_probe(adapter, &addr_data, &aic23_detect_client); + return res; +} + +static struct i2c_driver aic23_driver = { + .driver = { + .name = "OMAP+TLV320AIC23 codec", + /*.flags = I2C_DF_NOTIFY,*/ + }, + .id = I2C_DRIVERID_MISC, /* Experimental ID */ + .attach_adapter = aic23_attach_adapter, + .detach_client = aic23_detach_client, +}; + +static void update_volume_left(int volume) +{ + u16 val = 0; + val = ((volume * OUTPUT_VOLUME_RANGE) / 100) + OUTPUT_VOLUME_MIN; + aic23_write_value(LEFT_CHANNEL_VOLUME_ADDR, val); + aic23_info_l.volume_reg_left = volume; +} + +static void update_volume_right(int volume) +{ + u16 val = 0; + val = ((volume * OUTPUT_VOLUME_RANGE) / 100) + OUTPUT_VOLUME_MIN; + aic23_write_value(RIGHT_CHANNEL_VOLUME_ADDR, val); + aic23_info_l.volume_reg_right = volume; +} + +static void set_mic(int mic_en) +{ + u16 dg_ctrl; + + if (mic_en) { + aic23_info_l.power = OSC_OFF | LINE_OFF; + dg_ctrl = ADCHP_ON; + aic23_info_l.mask &= ~MICM_MUTED; + aic23_info_l.mask |= MICB_20DB; /* STE_ENABLED */ + } else { + aic23_info_l.power = + OSC_OFF | ADC_OFF | MIC_OFF | LINE_OFF; + dg_ctrl = 0x00; + aic23_info_l.mask = + DAC_SELECTED | INSEL_MIC | MICM_MUTED; + } + aic23_write_value(POWER_DOWN_CONTROL_ADDR, + aic23_info_l.power); + aic23_write_value(DIGITAL_AUDIO_CONTROL_ADDR, dg_ctrl); + aic23_write_value(ANALOG_AUDIO_CONTROL_ADDR, + aic23_info_l.mask); + aic23_info_l.mic_enable = mic_en; + + printk(KERN_INFO "aic23 mic state: %i\n", mic_en); +} + +static void aic23_init_power(void) +{ + aic23_write_value(RESET_CONTROL_ADDR, 0x00); + + if (aic23_info_l.initialized == 0) { + aic23_write_value(LEFT_CHANNEL_VOLUME_ADDR, LHV_MIN); + aic23_write_value(RIGHT_CHANNEL_VOLUME_ADDR, LHV_MIN); + } + else { + update_volume_left(aic23_info_l.volume_reg_left); + update_volume_right(aic23_info_l.volume_reg_right); + } + + aic23_info_l.mask = DAC_SELECTED | INSEL_MIC | MICM_MUTED; + aic23_write_value(ANALOG_AUDIO_CONTROL_ADDR, + aic23_info_l.mask); + aic23_write_value(DIGITAL_AUDIO_CONTROL_ADDR, 0x00); + aic23_write_value(DIGITAL_AUDIO_FORMAT_ADDR, LRP_ON | FOR_DSP); + aic23_write_value(SAMPLE_RATE_CONTROL_ADDR, USB_CLK_ON); + aic23_write_value(DIGITAL_INTERFACE_ACT_ADDR, ACT_ON); + aic23_info_l.power = OSC_OFF | ADC_OFF | MIC_OFF | LINE_OFF; + aic23_write_value(POWER_DOWN_CONTROL_ADDR, + aic23_info_l.power); + + /* enable mic input */ + if (aic23_info_l.mic_enable) + set_mic(aic23_info_l.mic_enable); + + printk(KERN_INFO "aic23_init_power() done\n"); +} + +void aic23_power_down(void) +{ + if (aic23_info_l.initialized) { + printk("aic23 powering down\n"); + aic23_write_value(POWER_DOWN_CONTROL_ADDR, 0xff); + } + aic23_info_l.power_down = 1; +} + +void aic23_power_up(void) +{ + if (aic23_info_l.initialized) { + printk("aic23 powering up\n"); + aic23_init_power(); + } + aic23_info_l.power_down = 0; +} + +/*----------------------------------------------------------------------*/ +/* sysfs initializations */ +/*----------------------------------------------------------------------*/ + +static ssize_t store_volume_left(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + signed volume; + + sscanf(buf, "%i", &volume); + + if (volume < MIN_VOL) { + aic23_power_down(); + return count; + } else if (volume > MIN_VOL && aic23_info_l.power_down) { + aic23_info_l.volume_reg_left = volume; + aic23_power_up(); + return count; + } + if (volume > MAX_VOL) + volume = MAX_VOL; + + update_volume_left(volume); + return count; +} + +static ssize_t show_volume_left(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%u\n", aic23_info_l.volume_reg_left); +} + +static DEVICE_ATTR(volume_left, S_IRUGO | S_IWUGO, + show_volume_left, store_volume_left); + +static ssize_t store_volume_right(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + signed volume; + + sscanf(buf, "%i", &volume); + if (volume < MIN_VOL) { + aic23_power_down(); + return count; + } else if (volume > MIN_VOL && aic23_info_l.power_down) { + aic23_info_l.volume_reg_right = volume; + aic23_power_up(); + return count; + } + if (volume > MAX_VOL) + volume = MAX_VOL; + + update_volume_right(volume); + return count; +} + +static ssize_t show_volume_right(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%u\n", aic23_info_l.volume_reg_right); +} + +static DEVICE_ATTR(volume_right, S_IRUGO | S_IWUGO, + show_volume_right, store_volume_right); + +static ssize_t store_gain_left(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + u16 val = 0; + unsigned gain; + + sscanf(buf, "%u", &gain); + if (gain > MAX_VOL) + gain = MAX_VOL; + + val = ((gain * INPUT_VOLUME_RANGE) / 100) + INPUT_VOLUME_MIN; + aic23_write_value(LEFT_LINE_VOLUME_ADDR, val); + aic23_info_l.input_gain_reg_left = gain; + + return count; +} + +static ssize_t show_gain_left(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%u\n", aic23_info_l.input_gain_reg_left); +} + +static DEVICE_ATTR(gain_left, S_IRUGO | S_IWUSR, show_gain_left, + store_gain_left); + +static ssize_t store_gain_right(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + u16 val = 0; + unsigned gain; + + sscanf(buf, "%u", &gain); + if (gain > MAX_VOL) + gain = MAX_VOL; + + val = ((gain * INPUT_VOLUME_RANGE) / 100) + INPUT_VOLUME_MIN; + aic23_write_value(RIGHT_LINE_VOLUME_ADDR, val); + aic23_info_l.input_gain_reg_right = gain; + + return count; +} + +static ssize_t show_gain_right(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%u\n", aic23_info_l.input_gain_reg_right); +} + +static DEVICE_ATTR(gain_right, S_IRUGO | S_IWUSR, show_gain_right, + store_gain_right); + +static ssize_t store_mic_loopback(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int mic; + + sscanf(buf, "%i", &mic); + if (mic > 0) { + aic23_write_value(POWER_DOWN_CONTROL_ADDR, \ + OSC_OFF | ADC_OFF | LINE_OFF); + aic23_info_l.mask = STE_ENABLED | DAC_SELECTED \ + | INSEL_MIC | MICB_20DB; + aic23_write_value(ANALOG_AUDIO_CONTROL_ADDR, + aic23_info_l.mask); + mic = 1; + } + else { + aic23_write_value(POWER_DOWN_CONTROL_ADDR, \ + OSC_OFF | ADC_OFF | MIC_OFF | LINE_OFF); + mic = 0; + } + aic23_info_l.mic_loopback = mic; + + return count; +} + +static ssize_t show_mic_loopback(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%i\n", aic23_info_l.mic_loopback); +} + +static DEVICE_ATTR(mic_loopback, S_IRUGO | S_IWUSR, + show_mic_loopback, store_mic_loopback); + +static ssize_t store_st_attenuation(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned sta; + u16 tmp; + + sscanf(buf, "%u", &sta); + if (sta > 3) + sta = 3; + + tmp = aic23_info_l.mask; + tmp &= 0x3f; + + aic23_info_l.mask = tmp | STA_REG(sta); + aic23_write_value(ANALOG_AUDIO_CONTROL_ADDR, + aic23_info_l.mask); + aic23_info_l.sta = sta; + + return count; +} + +static ssize_t show_st_attenuation(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%i\n", aic23_info_l.sta); +} + +static DEVICE_ATTR(st_attenuation, S_IRUGO | S_IWUSR, + show_st_attenuation, store_st_attenuation); + +static ssize_t store_mic_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int mic; + + sscanf(buf, "%i", &mic); + set_mic(mic); + + return count; +} + +static ssize_t show_mic_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%i\n", aic23_info_l.mic_enable); +} + +static DEVICE_ATTR(mic_enable, S_IRUGO | S_IWUSR, + show_mic_enable, store_mic_enable); + +static ssize_t show_audio_selftest(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%i\n", selftest); +} + +static DEVICE_ATTR(audio_selftest, S_IRUGO | S_IWUSR, + show_audio_selftest, NULL); + +static int audio_i2c_probe(struct platform_device *dev) +{ + int r; + + if ((r = device_create_file(&dev->dev, &dev_attr_volume_left)) != 0) + return r; + else if ((r = device_create_file(&dev->dev, + &dev_attr_volume_right)) != 0) + goto err_volume_left; + else if ((r = device_create_file(&dev->dev, + &dev_attr_gain_right)) != 0) + goto err_volume_right; + else if ((r = device_create_file(&dev->dev, + &dev_attr_gain_left)) != 0) + goto err_gain_right; + else if ((r = device_create_file(&dev->dev, + &dev_attr_mic_loopback)) != 0) + goto err_gain_left; + else if ((r = device_create_file(&dev->dev, + &dev_attr_mic_enable)) != 0) + goto err_mic_loopback; + else if ((r = device_create_file(&dev->dev, + &dev_attr_st_attenuation)) != 0) + goto err_mic_enable; + else if ((r = device_create_file(&dev->dev, + &dev_attr_audio_selftest)) != 0) + goto err_st_attenuation; + else + return r; + +err_st_attenuation: + device_remove_file(&dev->dev, &dev_attr_st_attenuation); +err_mic_enable: + device_remove_file(&dev->dev, &dev_attr_mic_enable); +err_mic_loopback: + device_remove_file(&dev->dev, &dev_attr_mic_loopback); +err_gain_left: + device_remove_file(&dev->dev, &dev_attr_gain_left); +err_gain_right: + device_remove_file(&dev->dev, &dev_attr_gain_right); +err_volume_right: + device_remove_file(&dev->dev, &dev_attr_volume_right); +err_volume_left: + device_remove_file(&dev->dev, &dev_attr_volume_left); + + return r; +} + +static int audio_i2c_remove(struct platform_device *dev) +{ + device_remove_file(&dev->dev, &dev_attr_st_attenuation); + device_remove_file(&dev->dev, &dev_attr_mic_enable); + device_remove_file(&dev->dev, &dev_attr_mic_loopback); + device_remove_file(&dev->dev, &dev_attr_gain_left); + device_remove_file(&dev->dev, &dev_attr_gain_right); + device_remove_file(&dev->dev, &dev_attr_volume_right); + device_remove_file(&dev->dev, &dev_attr_volume_left); + + return 0; +} + +/*----------------------------------------------------------------*/ +/* PM functions */ +/*----------------------------------------------------------------*/ + +static void audio_i2c_shutdown(struct platform_device *dev) +{ + /* Let's mute the codec before powering off to prevent + * glitch in the sound + */ + aic23_write_value(LEFT_CHANNEL_VOLUME_ADDR, LHV_MIN); + aic23_write_value(RIGHT_CHANNEL_VOLUME_ADDR, LHV_MIN); + aic23_power_down(); +} + +static int audio_i2c_suspend(struct platform_device *dev, pm_message_t state) +{ + /* Let's mute the codec before powering off to prevent + * glitch in the sound + */ + aic23_write_value(LEFT_CHANNEL_VOLUME_ADDR, LHV_MIN); + aic23_write_value(RIGHT_CHANNEL_VOLUME_ADDR, LHV_MIN); + aic23_power_down(); + + return 0; +} + +static int audio_i2c_resume(struct platform_device *dev) +{ + aic23_power_up(); + + return 0; +} + +static struct platform_driver audio_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "audio-i2c", + }, + .shutdown = audio_i2c_shutdown, + .probe = audio_i2c_probe, + .remove = audio_i2c_remove, + .suspend = audio_i2c_suspend, + .resume = audio_i2c_resume, +}; + +static struct platform_device audio_i2c_device = { + .name = "audio-i2c", + .id = -1, +}; + +/*----------------------------------------------------------------*/ + +static int __init aic23_init(void) +{ + selftest = 0; + aic23_info_l.initialized = 0; + + if (i2c_add_driver(&aic23_driver)) { + printk("aic23 i2c: Driver registration failed, \ + module not inserted.\n"); + selftest = -ENODEV; + return selftest; + } + + if (platform_driver_register(&audio_i2c_driver)) { + printk(KERN_WARNING "Failed to register audio i2c driver\n"); + selftest = -ENODEV; + return selftest; + } + + printk("TLV320AIC23 I2C version %s (%s)\n", + TLV320AIC23_VERSION, TLV320AIC23_DATE); + + return selftest; +} + +static void __exit aic23_exit(void) +{ + aic23_power_down(); + i2c_del_driver(&aic23_driver); + + platform_driver_unregister(&audio_i2c_driver); +} + +MODULE_AUTHOR("Kai Svahn "); +MODULE_DESCRIPTION("I2C interface for TLV320AIC23 codec."); +MODULE_LICENSE("GPL"); + +module_init(aic23_init) +module_exit(aic23_exit) + +EXPORT_SYMBOL(aic23_write_value); +EXPORT_SYMBOL(aic23_power_up); +EXPORT_SYMBOL(aic23_power_down); diff --cc drivers/i2c/chips/tsl2563.c index ddca5e30d04,00000000000..e05b88007b1 mode 100644,000000..100644 --- a/drivers/i2c/chips/tsl2563.c +++ b/drivers/i2c/chips/tsl2563.c @@@ -1,739 -1,0 +1,739 @@@ +/* + * drivers/i2c/chips/tsl2563.c + * + * Copyright (C) 2008 Nokia Corporation + * + * Written by Timo O. Karjalainen + * Contact: Mathias Nyman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include - #include ++#include + +#define DRIVER_NAME "tsl2563" + +/* Use this many bits for fraction part. */ +#define ADC_FRAC_BITS (14) + +/* Given number of 1/10000's in ADC_FRAC_BITS precision. */ +#define FRAC10K(f) (((f) * (1L << (ADC_FRAC_BITS))) / (10000)) + +/* Bits used for fraction in calibration coefficients.*/ +#define CALIB_FRAC_BITS (10) +/* 0.5 in CALIB_FRAC_BITS precision */ +#define CALIB_FRAC_HALF (1 << (CALIB_FRAC_BITS - 1)) +/* Make a fraction from a number n that was multiplied with b. */ +#define CALIB_FRAC(n, b) (((n) << CALIB_FRAC_BITS) / (b)) +/* Decimal 10^(digits in sysfs presentation) */ +#define CALIB_BASE_SYSFS (1000) + +#define TSL2563_CMD (0x80) +#define TSL2563_CLEARINT (0x40) + +#define TSL2563_REG_CTRL (0x00) +#define TSL2563_REG_TIMING (0x01) +#define TSL2563_REG_LOWLOW (0x02) /* data0 low threshold, 2 bytes */ +#define TSL2563_REG_LOWHIGH (0x03) +#define TSL2563_REG_HIGHLOW (0x04) /* data0 high threshold, 2 bytes */ +#define TSL2563_REG_HIGHHIGH (0x05) +#define TSL2563_REG_INT (0x06) +#define TSL2563_REG_ID (0x0a) +#define TSL2563_REG_DATA0LOW (0x0c) /* broadband sensor value, 2 bytes */ +#define TSL2563_REG_DATA0HIGH (0x0d) +#define TSL2563_REG_DATA1LOW (0x0e) /* infrared sensor value, 2 bytes */ +#define TSL2563_REG_DATA1HIGH (0x0f) + +#define TSL2563_CMD_POWER_ON (0x03) +#define TSL2563_CMD_POWER_OFF (0x00) +#define TSL2563_CTRL_POWER_MASK (0x03) + +#define TSL2563_TIMING_13MS (0x00) +#define TSL2563_TIMING_100MS (0x01) +#define TSL2563_TIMING_400MS (0x02) +#define TSL2563_TIMING_MASK (0x03) +#define TSL2563_TIMING_GAIN16 (0x10) +#define TSL2563_TIMING_GAIN1 (0x00) + +#define TSL2563_INT_DISBLED (0x00) +#define TSL2563_INT_LEVEL (0x10) +#define TSL2563_INT_PERSIST(n) ((n) & 0x0F) + +struct tsl2563_gainlevel_coeff { + u8 gaintime; + u16 min; + u16 max; +}; + +static struct tsl2563_gainlevel_coeff tsl2563_gainlevel_table[] = { + { + .gaintime = TSL2563_TIMING_400MS | TSL2563_TIMING_GAIN16, + .min = 0, + .max = 65534, + }, { + .gaintime = TSL2563_TIMING_400MS | TSL2563_TIMING_GAIN1, + .min = 2048, + .max = 65534, + }, { + .gaintime = TSL2563_TIMING_100MS | TSL2563_TIMING_GAIN1, + .min = 4095, + .max = 37177, + }, { + .gaintime = TSL2563_TIMING_13MS | TSL2563_TIMING_GAIN1, + .min = 3000, + .max = 65535, + }, +}; + +struct tsl2563_chip { + struct mutex lock; + struct i2c_client *client; + struct device *hwmon_dev; + + /* Remember state for suspend and resume functions */ + pm_message_t state; + + struct tsl2563_gainlevel_coeff *gainlevel; + + /* Thresholds are in lux */ + u16 low_thres; + u16 high_thres; + u8 intr; + + /* Calibration coefficients */ + u32 calib0; + u32 calib1; + + /* Cache current values, to be returned while suspended */ + u32 data0; + u32 data1; +}; + +static int tsl2563_write(struct i2c_client *client, u8 reg, u8 value) +{ + int ret; + u8 buf[2]; + + buf[0] = TSL2563_CMD | reg; + buf[1] = value; + + ret = i2c_master_send(client, buf, sizeof(buf)); + return (ret == sizeof(buf)) ? 0 : ret; +} + +static int tsl2563_read(struct i2c_client *client, u8 reg, void *buf, int len) +{ + int ret; + u8 cmd = TSL2563_CMD | reg; + + ret = i2c_master_send(client, &cmd, sizeof(cmd)); + if (ret != sizeof(cmd)) + return ret; + + return i2c_master_recv(client, buf, len); +} + +static int tsl2563_set_power(struct tsl2563_chip *chip, int on) +{ + struct i2c_client *client = chip->client; + u8 cmd; + + cmd = on ? TSL2563_CMD_POWER_ON : TSL2563_CMD_POWER_OFF; + return tsl2563_write(client, TSL2563_REG_CTRL, cmd); +} + +/* + * Return value is 0 for off, 1 for on, or a negative error + * code if reading failed. + */ +static int tsl2563_get_power(struct tsl2563_chip *chip) +{ + struct i2c_client *client = chip->client; + int ret; + u8 val; + + ret = tsl2563_read(client, TSL2563_REG_CTRL, &val, sizeof(val)); + if (ret != sizeof(val)) + return ret; + + return (val & TSL2563_CTRL_POWER_MASK) == TSL2563_CMD_POWER_ON; +} + +static int tsl2563_configure(struct tsl2563_chip *chip) +{ + struct i2c_client *client = chip->client; + int ret; + + ret = tsl2563_write(client, TSL2563_REG_TIMING, + chip->gainlevel->gaintime); + if (ret) + goto out; + + ret = tsl2563_write(client, TSL2563_REG_INT, chip->intr); + +out: + return ret; +} + +static int tsl2563_detect(struct tsl2563_chip *chip) +{ + int ret; + + ret = tsl2563_set_power(chip, 1); + if (ret) + return ret; + + ret = tsl2563_get_power(chip); + if (ret < 0) + return ret; + + return ret ? 0 : -ENODEV; +} + +static int tsl2563_read_id(struct tsl2563_chip *chip, u8 *id) +{ + struct i2c_client *client = chip->client; + int ret; + + ret = tsl2563_read(client, TSL2563_REG_ID, id, sizeof(*id)); + if (ret != sizeof(*id)) + return ret; + + return 0; +} + +/* + * "Normalized" ADC value is one obtained with 400ms of integration time and + * 16x gain. This function returns the number of bits of shift needed to + * convert between normalized values and HW values obtained using given + * timing and gain settings. + */ +static int adc_shiftbits(u8 timing) +{ + int shift = 0; + + switch (timing & TSL2563_TIMING_MASK) { + case TSL2563_TIMING_13MS: + shift += 5; + break; + case TSL2563_TIMING_100MS: + shift += 2; + break; + case TSL2563_TIMING_400MS: + /* no-op */ + break; + } + + if (!(timing & TSL2563_TIMING_GAIN16)) + shift += 4; + + return shift; +} + +/* Convert a HW ADC value to normalized scale. */ +static u32 normalize_adc(u16 adc, u8 timing) +{ + return adc << adc_shiftbits(timing); +} + +static void tsl2563_wait_adc(struct tsl2563_chip *chip) +{ + unsigned int delay; + + switch (chip->gainlevel->gaintime & TSL2563_TIMING_MASK) { + case TSL2563_TIMING_13MS: + delay = 14; + break; + case TSL2563_TIMING_100MS: + delay = 101; + break; + default: + delay = 402; + } + /* + * TODO: Make sure that we wait at least required delay but why we + * have to extend it one tick more? + */ + schedule_timeout_interruptible(msecs_to_jiffies(delay) + 2); +} + +static int tsl2563_adjust_gainlevel(struct tsl2563_chip *chip, u16 adc) +{ + struct i2c_client *client = chip->client; + + if (adc > chip->gainlevel->max || adc < chip->gainlevel->min) { + + (adc > chip->gainlevel->max) ? + chip->gainlevel++ : chip->gainlevel--; + + tsl2563_write(client, TSL2563_REG_TIMING, + chip->gainlevel->gaintime); + + tsl2563_wait_adc(chip); + tsl2563_wait_adc(chip); + + return 1; + } else + return 0; +} + +static int tsl2563_get_adc(struct tsl2563_chip *chip) +{ + struct i2c_client *client = chip->client; + u8 buf0[2], buf1[2]; + u16 adc0, adc1; + int retry = 1; + int ret = 0; + + if (chip->state.event != PM_EVENT_ON) + goto out; + + while (retry) { + ret = tsl2563_read(client, + TSL2563_REG_DATA0LOW | TSL2563_CLEARINT, + buf0, sizeof(buf0)); + if (ret != sizeof(buf0)) + goto out; + + ret = tsl2563_read(client, TSL2563_REG_DATA1LOW, + buf1, sizeof(buf1)); + if (ret != sizeof(buf1)) + goto out; + + adc0 = (buf0[1] << 8) + buf0[0]; + adc1 = (buf1[1] << 8) + buf1[0]; + + retry = tsl2563_adjust_gainlevel(chip, adc0); + } + + chip->data0 = normalize_adc(adc0, chip->gainlevel->gaintime); + chip->data1 = normalize_adc(adc1, chip->gainlevel->gaintime); + + ret = 0; +out: + return ret; +} + +static inline int calib_to_sysfs(u32 calib) +{ + return (int) (((calib * CALIB_BASE_SYSFS) + + CALIB_FRAC_HALF) >> CALIB_FRAC_BITS); +} + +static inline u32 calib_from_sysfs(int value) +{ + return (((u32) value) << CALIB_FRAC_BITS) / CALIB_BASE_SYSFS; +} + +/* + * Conversions between lux and ADC values. + * + * The basic formula is lux = c0 * adc0 - c1 * adc1, where c0 and c1 are + * appropriate constants. Different constants are needed for different + * kinds of light, determined by the ratio adc1/adc0 (basically the ratio + * of the intensities in infrared and visible wavelengths). lux_table below + * lists the upper threshold of the adc1/adc0 ratio and the corresponding + * constants. + */ + +struct tsl2563_lux_coeff { + unsigned long ch_ratio; + unsigned long ch0_coeff; + unsigned long ch1_coeff; +}; + +static const struct tsl2563_lux_coeff lux_table[] = { + { + .ch_ratio = FRAC10K(1300), + .ch0_coeff = FRAC10K(315), + .ch1_coeff = FRAC10K(262), + }, { + .ch_ratio = FRAC10K(2600), + .ch0_coeff = FRAC10K(337), + .ch1_coeff = FRAC10K(430), + }, { + .ch_ratio = FRAC10K(3900), + .ch0_coeff = FRAC10K(363), + .ch1_coeff = FRAC10K(529), + }, { + .ch_ratio = FRAC10K(5200), + .ch0_coeff = FRAC10K(392), + .ch1_coeff = FRAC10K(605), + }, { + .ch_ratio = FRAC10K(6500), + .ch0_coeff = FRAC10K(229), + .ch1_coeff = FRAC10K(291), + }, { + .ch_ratio = FRAC10K(8000), + .ch0_coeff = FRAC10K(157), + .ch1_coeff = FRAC10K(180), + }, { + .ch_ratio = FRAC10K(13000), + .ch0_coeff = FRAC10K(34), + .ch1_coeff = FRAC10K(26), + }, { + .ch_ratio = ULONG_MAX, + .ch0_coeff = 0, + .ch1_coeff = 0, + }, +}; + +/* + * Convert normalized, scaled ADC values to lux. + */ +static unsigned int adc_to_lux(u32 adc0, u32 adc1) +{ + const struct tsl2563_lux_coeff *lp = lux_table; + unsigned long ratio, lux, ch0 = adc0, ch1 = adc1; + + ratio = ch0 ? ((ch1 << ADC_FRAC_BITS) / ch0) : ULONG_MAX; + + while (lp->ch_ratio < ratio) + lp++; + + lux = ch0 * lp->ch0_coeff - ch1 * lp->ch1_coeff; + + return (unsigned int) (lux >> ADC_FRAC_BITS); +} + +/*--------------------------------------------------------------*/ +/* Sysfs interface */ +/*--------------------------------------------------------------*/ + +static ssize_t tsl2563_adc0_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tsl2563_chip *chip = dev_get_drvdata(dev); + int ret; + + mutex_lock(&chip->lock); + + ret = tsl2563_get_adc(chip); + if (ret) + return ret; + + ret = snprintf(buf, PAGE_SIZE, "%d\n", chip->data0); + mutex_unlock(&chip->lock); + + return ret; +} + +static ssize_t tsl2563_adc1_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tsl2563_chip *chip = dev_get_drvdata(dev); + int ret; + + mutex_lock(&chip->lock); + + ret = tsl2563_get_adc(chip); + if (ret) + return ret; + + ret = snprintf(buf, PAGE_SIZE, "%d\n", chip->data1); + mutex_unlock(&chip->lock); + + return ret; +} + +/* Apply calibration coefficient to ADC count. */ +static u32 calib_adc(u32 adc, u32 calib) +{ + unsigned long scaled = adc; + + scaled *= calib; + scaled >>= CALIB_FRAC_BITS; + + return (u32) scaled; +} + +static ssize_t tsl2563_lux_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tsl2563_chip *chip = dev_get_drvdata(dev); + u32 calib0, calib1; + int ret; + + mutex_lock(&chip->lock); + + ret = tsl2563_get_adc(chip); + if (ret) + goto out; + + calib0 = calib_adc(chip->data0, chip->calib0); + calib1 = calib_adc(chip->data1, chip->calib1); + + ret = snprintf(buf, PAGE_SIZE, "%d\n", adc_to_lux(calib0, calib1)); + +out: + mutex_unlock(&chip->lock); + return ret; +} + +static ssize_t format_calib(char *buf, int len, u32 calib) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", calib_to_sysfs(calib)); +} + +static ssize_t tsl2563_calib0_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tsl2563_chip *chip = dev_get_drvdata(dev); + int ret; + + mutex_lock(&chip->lock); + ret = format_calib(buf, PAGE_SIZE, chip->calib0); + mutex_unlock(&chip->lock); + return ret; +} + +static ssize_t tsl2563_calib1_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tsl2563_chip *chip = dev_get_drvdata(dev); + int ret; + + mutex_lock(&chip->lock); + ret = format_calib(buf, PAGE_SIZE, chip->calib1); + mutex_unlock(&chip->lock); + return ret; +} + +static int do_calib_store(struct device *dev, const char *buf, size_t len, + int ch) +{ + struct tsl2563_chip *chip = dev_get_drvdata(dev); + int value; + u32 calib; + + if (1 != sscanf(buf, "%d", &value)) + return -EINVAL; + + calib = calib_from_sysfs(value); + + if (ch) + chip->calib1 = calib; + else + chip->calib0 = calib; + + return len; +} + +static ssize_t tsl2563_calib0_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + return do_calib_store(dev, buf, len, 0); +} + +static ssize_t tsl2563_calib1_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + return do_calib_store(dev, buf, len, 1); +} + +static DEVICE_ATTR(adc0, S_IRUGO, tsl2563_adc0_show, NULL); +static DEVICE_ATTR(adc1, S_IRUGO, tsl2563_adc1_show, NULL); +static DEVICE_ATTR(lux, S_IRUGO, tsl2563_lux_show, NULL); +static DEVICE_ATTR(calib0, S_IRUGO | S_IWUSR, + tsl2563_calib0_show, tsl2563_calib0_store); +static DEVICE_ATTR(calib1, S_IRUGO | S_IWUSR, + tsl2563_calib1_show, tsl2563_calib1_store); + +static struct attribute *tsl2563_attributes[] = { + &dev_attr_adc0.attr, + &dev_attr_adc1.attr, + &dev_attr_lux.attr, + &dev_attr_calib0.attr, + &dev_attr_calib1.attr, + NULL +}; + +static const struct attribute_group tsl2563_group = { + .attrs = tsl2563_attributes, +}; + +static int tsl2563_register_sysfs(struct i2c_client *client) +{ + struct device *dev = &client->dev; + + return sysfs_create_group(&dev->kobj, &tsl2563_group); +} + +static void tsl2563_unregister_sysfs(struct i2c_client *client) +{ + struct device *dev = &client->dev; + + sysfs_remove_group(&dev->kobj, &tsl2563_group); +} + +/*--------------------------------------------------------------*/ +/* Probe, Attach, Remove */ +/*--------------------------------------------------------------*/ +static struct i2c_driver tsl2563_i2c_driver; + +static int tsl2563_probe(struct i2c_client *client, + const struct i2c_device_id *device_id) +{ + struct tsl2563_chip *chip; + int err = 0; + u8 id; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + i2c_set_clientdata(client, chip); + chip->client = client; + + err = tsl2563_detect(chip); + if (err) { + dev_err(&client->dev, "device not found, error %d \n", -err); + goto fail1; + } + + err = tsl2563_read_id(chip, &id); + if (err) + goto fail1; + + mutex_init(&chip->lock); + + /* Default values used until userspace says otherwise */ + chip->low_thres = 0x0; + chip->high_thres = 0xffff; + chip->gainlevel = tsl2563_gainlevel_table; + chip->intr = TSL2563_INT_PERSIST(4); + chip->calib0 = calib_from_sysfs(CALIB_BASE_SYSFS); + chip->calib1 = calib_from_sysfs(CALIB_BASE_SYSFS); + + dev_info(&client->dev, "model %d, rev. %d\n", id >> 4, id & 0x0f); + + err = tsl2563_configure(chip); + if (err) + goto fail1; + + chip->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(chip->hwmon_dev)) + goto fail1; + + err = tsl2563_register_sysfs(client); + if (err) { + dev_err(&client->dev, "sysfs registration failed, %d\n", err); + goto fail2; + } + + return 0; +fail2: + hwmon_device_unregister(chip->hwmon_dev); +fail1: + kfree(chip); + return err; +} + +static int tsl2563_remove(struct i2c_client *client) +{ + struct tsl2563_chip *chip = i2c_get_clientdata(client); + + tsl2563_unregister_sysfs(client); + hwmon_device_unregister(chip->hwmon_dev); + + kfree(chip); + return 0; +} + +static int tsl2563_suspend(struct i2c_client *client, pm_message_t state) +{ + struct tsl2563_chip *chip = i2c_get_clientdata(client); + int ret; + + mutex_lock(&chip->lock); + + ret = tsl2563_set_power(chip, 0); + if (ret) + goto out; + + chip->state = state; + +out: + mutex_unlock(&chip->lock); + return ret; +} + +static int tsl2563_resume(struct i2c_client *client) +{ + struct tsl2563_chip *chip = i2c_get_clientdata(client); + int ret; + + mutex_lock(&chip->lock); + + ret = tsl2563_set_power(chip, 1); + if (ret) + goto out; + + ret = tsl2563_configure(chip); + if (ret) + goto out; + + chip->state.event = PM_EVENT_ON; + +out: + mutex_unlock(&chip->lock); + return ret; +} + +static const struct i2c_device_id tsl2563_id[] = { + { DRIVER_NAME, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, tsl2563_id); + +static struct i2c_driver tsl2563_i2c_driver = { + .driver = { + .name = DRIVER_NAME, + }, + .suspend = tsl2563_suspend, + .resume = tsl2563_resume, + .probe = tsl2563_probe, + .remove = __devexit_p(tsl2563_remove), + .id_table = tsl2563_id, +}; + +static int __init tsl2563_init(void) +{ + return i2c_add_driver(&tsl2563_i2c_driver); +} + +static void __exit tsl2563_exit(void) +{ + i2c_del_driver(&tsl2563_i2c_driver); +} + +MODULE_AUTHOR("Nokia Corporation"); +MODULE_DESCRIPTION("tsl2563 light sensor driver"); +MODULE_LICENSE("GPL"); + +module_init(tsl2563_init); +module_exit(tsl2563_exit); diff --cc drivers/i2c/chips/twl4030-core.c index 1a8e4137c05,00000000000..713947d5b18 mode 100644,000000..100644 --- a/drivers/i2c/chips/twl4030-core.c +++ b/drivers/i2c/chips/twl4030-core.c @@@ -1,1006 -1,0 +1,1006 @@@ +/* + * twl4030_core.c - driver for TWL4030 PM and audio CODEC device + * + * Copyright (C) 2005-2006 Texas Instruments, Inc. + * + * Modifications to defer interrupt handling to a kernel thread: + * Copyright (C) 2006 MontaVista Software, Inc. + * + * Based on tlv320aic23.c: + * Copyright (c) by Kai Svahn + * + * Code cleanup and modifications to IRQ handler. + * by syed khasim + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + - #include - #include ++#include ++#include + +#define DRIVER_NAME "twl4030" + +/* Macro Definitions */ +#define TWL_CLIENT_STRING "TWL4030-ID" +#define TWL_CLIENT_USED 1 +#define TWL_CLIENT_FREE 0 + +/* IRQ Flags */ +#define FREE 0 +#define USED 1 + +/* Primary Interrupt Handler on TWL4030 Registers */ + +/* Register Definitions */ + +#define REG_PIH_ISR_P1 (0x1) +#define REG_PIH_ISR_P2 (0x2) +#define REG_PIH_SIR (0x3) + +/* Triton Core internal information (BEGIN) */ + +/* Last - for index max*/ +#define TWL4030_MODULE_LAST TWL4030_MODULE_SECURED_REG + +/* Slave address */ +#define TWL4030_NUM_SLAVES 0x04 +#define TWL4030_SLAVENUM_NUM0 0x00 +#define TWL4030_SLAVENUM_NUM1 0x01 +#define TWL4030_SLAVENUM_NUM2 0x02 +#define TWL4030_SLAVENUM_NUM3 0x03 +#define TWL4030_SLAVEID_ID0 0x48 +#define TWL4030_SLAVEID_ID1 0x49 +#define TWL4030_SLAVEID_ID2 0x4A +#define TWL4030_SLAVEID_ID3 0x4B + +/* Base Address defns */ +/* USB ID */ +#define TWL4030_BASEADD_USB 0x0000 +/* AUD ID */ +#define TWL4030_BASEADD_AUDIO_VOICE 0x0000 +#define TWL4030_BASEADD_GPIO 0x0098 + +#define TWL4030_BASEADD_INTBR 0x0085 +#define TWL4030_BASEADD_PIH 0x0080 +#define TWL4030_BASEADD_TEST 0x004C +/* AUX ID */ +#define TWL4030_BASEADD_INTERRUPTS 0x00B9 +#define TWL4030_BASEADD_LED 0x00EE +#define TWL4030_BASEADD_MADC 0x0000 +#define TWL4030_BASEADD_MAIN_CHARGE 0x0074 +#define TWL4030_BASEADD_PRECHARGE 0x00AA +#define TWL4030_BASEADD_PWM0 0x00F8 +#define TWL4030_BASEADD_PWM1 0x00FB +#define TWL4030_BASEADD_PWMA 0x00EF +#define TWL4030_BASEADD_PWMB 0x00F1 +#define TWL4030_BASEADD_KEYPAD 0x00D2 +/* POWER ID */ +#define TWL4030_BASEADD_BACKUP 0x0014 +#define TWL4030_BASEADD_INT 0x002E +#define TWL4030_BASEADD_PM_MASTER 0x0036 +#define TWL4030_BASEADD_PM_RECEIVER 0x005B +#define TWL4030_BASEADD_RTC 0x001C +#define TWL4030_BASEADD_SECURED_REG 0x0000 + +/* TWL4030 BCI registers */ +#define TWL4030_INTERRUPTS_BCIIMR1A 0x2 +#define TWL4030_INTERRUPTS_BCIIMR2A 0x3 +#define TWL4030_INTERRUPTS_BCIIMR1B 0x6 +#define TWL4030_INTERRUPTS_BCIIMR2B 0x7 +#define TWL4030_INTERRUPTS_BCIISR1A 0x0 +#define TWL4030_INTERRUPTS_BCIISR2A 0x1 +#define TWL4030_INTERRUPTS_BCIISR1B 0x4 +#define TWL4030_INTERRUPTS_BCIISR2B 0x5 + +/* TWL4030 keypad registers */ +#define TWL4030_KEYPAD_KEYP_IMR1 0x12 +#define TWL4030_KEYPAD_KEYP_IMR2 0x14 +#define TWL4030_KEYPAD_KEYP_ISR1 0x11 +#define TWL4030_KEYPAD_KEYP_ISR2 0x13 + + +/* Triton Core internal information (END) */ + +/* Few power values */ +#define R_CFG_BOOT 0x05 +#define R_PROTECT_KEY 0x0E + +/* access control */ +#define KEY_UNLOCK1 0xce +#define KEY_UNLOCK2 0xec +#define KEY_LOCK 0x00 + +#define HFCLK_FREQ_19p2_MHZ (1 << 0) +#define HFCLK_FREQ_26_MHZ (2 << 0) +#define HFCLK_FREQ_38p4_MHZ (3 << 0) +#define HIGH_PERF_SQ (1 << 3) + +/* on I2C-1 for 2430SDP */ +#define CONFIG_I2C_TWL4030_ID 1 + +/* SIH_CTRL registers that aren't defined elsewhere */ +#define TWL4030_INTERRUPTS_BCISIHCTRL 0x0d +#define TWL4030_MADC_MADC_SIH_CTRL 0x67 +#define TWL4030_KEYPAD_KEYP_SIH_CTRL 0x17 + +#define TWL4030_SIH_CTRL_COR_MASK (1 << 2) + +/** + * struct twl4030_mod_iregs - TWL module IMR/ISR regs to mask/clear at init + * @mod_no: TWL4030 module number (e.g., TWL4030_MODULE_GPIO) + * @sih_ctrl: address of module SIH_CTRL register + * @reg_cnt: number of IMR/ISR regs + * @imrs: pointer to array of TWL module interrupt mask register indices + * @isrs: pointer to array of TWL module interrupt status register indices + * + * Ties together TWL4030 modules and lists of IMR/ISR registers to mask/clear + * during twl_init_irq(). + */ +struct twl4030_mod_iregs { + const u8 mod_no; + const u8 sih_ctrl; + const u8 reg_cnt; + const u8 *imrs; + const u8 *isrs; +}; + +/* TWL4030 INT module interrupt mask registers */ +static const u8 __initconst twl4030_int_imr_regs[] = { + TWL4030_INT_PWR_IMR1, + TWL4030_INT_PWR_IMR2, +}; + +/* TWL4030 INT module interrupt status registers */ +static const u8 __initconst twl4030_int_isr_regs[] = { + TWL4030_INT_PWR_ISR1, + TWL4030_INT_PWR_ISR2, +}; + +/* TWL4030 INTERRUPTS module interrupt mask registers */ +static const u8 __initconst twl4030_interrupts_imr_regs[] = { + TWL4030_INTERRUPTS_BCIIMR1A, + TWL4030_INTERRUPTS_BCIIMR1B, + TWL4030_INTERRUPTS_BCIIMR2A, + TWL4030_INTERRUPTS_BCIIMR2B, +}; + +/* TWL4030 INTERRUPTS module interrupt status registers */ +static const u8 __initconst twl4030_interrupts_isr_regs[] = { + TWL4030_INTERRUPTS_BCIISR1A, + TWL4030_INTERRUPTS_BCIISR1B, + TWL4030_INTERRUPTS_BCIISR2A, + TWL4030_INTERRUPTS_BCIISR2B, +}; + +/* TWL4030 MADC module interrupt mask registers */ +static const u8 __initconst twl4030_madc_imr_regs[] = { + TWL4030_MADC_IMR1, + TWL4030_MADC_IMR2, +}; + +/* TWL4030 MADC module interrupt status registers */ +static const u8 __initconst twl4030_madc_isr_regs[] = { + TWL4030_MADC_ISR1, + TWL4030_MADC_ISR2, +}; + +/* TWL4030 keypad module interrupt mask registers */ +static const u8 __initconst twl4030_keypad_imr_regs[] = { + TWL4030_KEYPAD_KEYP_IMR1, + TWL4030_KEYPAD_KEYP_IMR2, +}; + +/* TWL4030 keypad module interrupt status registers */ +static const u8 __initconst twl4030_keypad_isr_regs[] = { + TWL4030_KEYPAD_KEYP_ISR1, + TWL4030_KEYPAD_KEYP_ISR2, +}; + +/* TWL4030 GPIO module interrupt mask registers */ +static const u8 __initconst twl4030_gpio_imr_regs[] = { + REG_GPIO_IMR1A, + REG_GPIO_IMR1B, + REG_GPIO_IMR2A, + REG_GPIO_IMR2B, + REG_GPIO_IMR3A, + REG_GPIO_IMR3B, +}; + +/* TWL4030 GPIO module interrupt status registers */ +static const u8 __initconst twl4030_gpio_isr_regs[] = { + REG_GPIO_ISR1A, + REG_GPIO_ISR1B, + REG_GPIO_ISR2A, + REG_GPIO_ISR2B, + REG_GPIO_ISR3A, + REG_GPIO_ISR3B, +}; + +/* TWL4030 modules that have IMR/ISR registers that must be masked/cleared */ +static const struct twl4030_mod_iregs __initconst twl4030_mod_regs[] = { + { + .mod_no = TWL4030_MODULE_INT, + .sih_ctrl = TWL4030_INT_PWR_SIH_CTRL, + .reg_cnt = ARRAY_SIZE(twl4030_int_imr_regs), + .imrs = twl4030_int_imr_regs, + .isrs = twl4030_int_isr_regs, + }, + { + .mod_no = TWL4030_MODULE_INTERRUPTS, + .sih_ctrl = TWL4030_INTERRUPTS_BCISIHCTRL, + .reg_cnt = ARRAY_SIZE(twl4030_interrupts_imr_regs), + .imrs = twl4030_interrupts_imr_regs, + .isrs = twl4030_interrupts_isr_regs, + }, + { + .mod_no = TWL4030_MODULE_MADC, + .sih_ctrl = TWL4030_MADC_MADC_SIH_CTRL, + .reg_cnt = ARRAY_SIZE(twl4030_madc_imr_regs), + .imrs = twl4030_madc_imr_regs, + .isrs = twl4030_madc_isr_regs, + }, + { + .mod_no = TWL4030_MODULE_KEYPAD, + .sih_ctrl = TWL4030_KEYPAD_KEYP_SIH_CTRL, + .reg_cnt = ARRAY_SIZE(twl4030_keypad_imr_regs), + .imrs = twl4030_keypad_imr_regs, + .isrs = twl4030_keypad_isr_regs, + }, + { + .mod_no = TWL4030_MODULE_GPIO, + .sih_ctrl = REG_GPIO_SIH_CTRL, + .reg_cnt = ARRAY_SIZE(twl4030_gpio_imr_regs), + .imrs = twl4030_gpio_imr_regs, + .isrs = twl4030_gpio_isr_regs, + }, +}; + + +/* Helper functions */ +static int +twl4030_detect_client(struct i2c_adapter *adapter, unsigned char sid); +static int twl4030_attach_adapter(struct i2c_adapter *adapter); +static int twl4030_detach_client(struct i2c_client *client); +static void do_twl4030_irq(unsigned int irq, irq_desc_t *desc); + +static void twl_init_irq(void); + +/* Data Structures */ +/* To have info on T2 IRQ substem activated or not */ +static unsigned char twl_irq_used = FREE; +static struct completion irq_event; + +/* Structure to define on TWL4030 Slave ID */ +struct twl4030_client { + struct i2c_client client; + const char client_name[sizeof(TWL_CLIENT_STRING) + 1]; + const unsigned char address; + const char adapter_index; + unsigned char inuse; + + /* max numb of i2c_msg required is for read =2 */ + struct i2c_msg xfer_msg[2]; + + /* To lock access to xfer_msg */ + struct mutex xfer_lock; +}; + +/* Module Mapping */ +struct twl4030mapping { + unsigned char sid; /* Slave ID */ + unsigned char base; /* base address */ +}; + +/* mapping the module id to slave id and base address */ +static struct twl4030mapping twl4030_map[TWL4030_MODULE_LAST + 1] = { + { TWL4030_SLAVENUM_NUM0, TWL4030_BASEADD_USB }, + { TWL4030_SLAVENUM_NUM1, TWL4030_BASEADD_AUDIO_VOICE }, + { TWL4030_SLAVENUM_NUM1, TWL4030_BASEADD_GPIO }, + { TWL4030_SLAVENUM_NUM1, TWL4030_BASEADD_INTBR }, + { TWL4030_SLAVENUM_NUM1, TWL4030_BASEADD_PIH }, + { TWL4030_SLAVENUM_NUM1, TWL4030_BASEADD_TEST }, + { TWL4030_SLAVENUM_NUM2, TWL4030_BASEADD_KEYPAD }, + { TWL4030_SLAVENUM_NUM2, TWL4030_BASEADD_MADC }, + { TWL4030_SLAVENUM_NUM2, TWL4030_BASEADD_INTERRUPTS }, + { TWL4030_SLAVENUM_NUM2, TWL4030_BASEADD_LED }, + { TWL4030_SLAVENUM_NUM2, TWL4030_BASEADD_MAIN_CHARGE }, + { TWL4030_SLAVENUM_NUM2, TWL4030_BASEADD_PRECHARGE }, + { TWL4030_SLAVENUM_NUM2, TWL4030_BASEADD_PWM0 }, + { TWL4030_SLAVENUM_NUM2, TWL4030_BASEADD_PWM1 }, + { TWL4030_SLAVENUM_NUM2, TWL4030_BASEADD_PWMA }, + { TWL4030_SLAVENUM_NUM2, TWL4030_BASEADD_PWMB }, + { TWL4030_SLAVENUM_NUM3, TWL4030_BASEADD_BACKUP }, + { TWL4030_SLAVENUM_NUM3, TWL4030_BASEADD_INT }, + { TWL4030_SLAVENUM_NUM3, TWL4030_BASEADD_PM_MASTER }, + { TWL4030_SLAVENUM_NUM3, TWL4030_BASEADD_PM_RECEIVER }, + { TWL4030_SLAVENUM_NUM3, TWL4030_BASEADD_RTC }, + { TWL4030_SLAVENUM_NUM3, TWL4030_BASEADD_SECURED_REG }, +}; + +static struct twl4030_client twl4030_modules[TWL4030_NUM_SLAVES] = { + { + .address = TWL4030_SLAVEID_ID0, + .client_name = TWL_CLIENT_STRING "0", + .adapter_index = CONFIG_I2C_TWL4030_ID, + }, + { + .address = TWL4030_SLAVEID_ID1, + .client_name = TWL_CLIENT_STRING "1", + .adapter_index = CONFIG_I2C_TWL4030_ID, + }, + { + .address = TWL4030_SLAVEID_ID2, + .client_name = TWL_CLIENT_STRING "2", + .adapter_index = CONFIG_I2C_TWL4030_ID, + }, + { + .address = TWL4030_SLAVEID_ID3, + .client_name = TWL_CLIENT_STRING "3", + .adapter_index = CONFIG_I2C_TWL4030_ID, + }, +}; + +/* One Client Driver , 4 Clients */ +static struct i2c_driver twl4030_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, + .attach_adapter = twl4030_attach_adapter, + .detach_client = twl4030_detach_client, +}; + +/* + * TWL4030 doesn't have PIH mask, hence dummy function for mask + * and unmask. + */ + +static void twl4030_i2c_ackirq(unsigned int irq) {} +static void twl4030_i2c_disableint(unsigned int irq) {} +static void twl4030_i2c_enableint(unsigned int irq) {} + +/* information for processing in the Work Item */ +static struct irq_chip twl4030_irq_chip = { + .name = "twl4030", + .ack = twl4030_i2c_ackirq, + .mask = twl4030_i2c_disableint, + .unmask = twl4030_i2c_enableint, +}; + +/* Global Functions */ + +/** + * twl4030_i2c_write - Writes a n bit register in TWL4030 + * @mod_no: module number + * @value: an array of num_bytes+1 containing data to write + * @reg: register address (just offset will do) + * @num_bytes: number of bytes to transfer + * + * IMPORTANT: for 'value' parameter: Allocate value num_bytes+1 and + * valid data starts at Offset 1. + * + * Returns the result of operation - 0 is success + */ +int twl4030_i2c_write(u8 mod_no, u8 *value, u8 reg, u8 num_bytes) +{ + int ret; + int sid; + struct twl4030_client *twl; + struct i2c_msg *msg; + + if (unlikely(mod_no > TWL4030_MODULE_LAST)) { + pr_err("Invalid module Number\n"); + return -EPERM; + } + sid = twl4030_map[mod_no].sid; + twl = &twl4030_modules[sid]; + + if (unlikely(twl->inuse != TWL_CLIENT_USED)) { + pr_err("I2C Client[%d] is not initialized[%d]\n", + sid, __LINE__); + return -EPERM; + } + mutex_lock(&twl->xfer_lock); + /* + * [MSG1]: fill the register address data + * fill the data Tx buffer + */ + msg = &twl->xfer_msg[0]; + msg->addr = twl->address; + msg->len = num_bytes + 1; + msg->flags = 0; + msg->buf = value; + /* over write the first byte of buffer with the register address */ + *value = twl4030_map[mod_no].base + reg; + ret = i2c_transfer(twl->client.adapter, twl->xfer_msg, 1); + mutex_unlock(&twl->xfer_lock); + + /* i2cTransfer returns num messages.translate it pls.. */ + if (ret >= 0) + ret = 0; + return ret; +} +EXPORT_SYMBOL(twl4030_i2c_write); + +/** + * twl4030_i2c_read - Reads a n bit register in TWL4030 + * @mod_no: module number + * @value: an array of num_bytes containing data to be read + * @reg: register address (just offset will do) + * @num_bytes: number of bytes to transfer + * + * Returns result of operation - num_bytes is success else failure. + */ +int twl4030_i2c_read(u8 mod_no, u8 *value, u8 reg, u8 num_bytes) +{ + int ret; + u8 val; + int sid; + struct twl4030_client *twl; + struct i2c_msg *msg; + + if (unlikely(mod_no > TWL4030_MODULE_LAST)) { + pr_err("Invalid module Number\n"); + return -EPERM; + } + sid = twl4030_map[mod_no].sid; + twl = &twl4030_modules[sid]; + + if (unlikely(twl->inuse != TWL_CLIENT_USED)) { + pr_err("I2C Client[%d] is not initialized[%d]\n", sid, + __LINE__); + return -EPERM; + } + mutex_lock(&twl->xfer_lock); + /* [MSG1] fill the register address data */ + msg = &twl->xfer_msg[0]; + msg->addr = twl->address; + msg->len = 1; + msg->flags = 0; /* Read the register value */ + val = twl4030_map[mod_no].base + reg; + msg->buf = &val; + /* [MSG2] fill the data rx buffer */ + msg = &twl->xfer_msg[1]; + msg->addr = twl->address; + msg->flags = I2C_M_RD; /* Read the register value */ + msg->len = num_bytes; /* only n bytes */ + msg->buf = value; + ret = i2c_transfer(twl->client.adapter, twl->xfer_msg, 2); + mutex_unlock(&twl->xfer_lock); + + /* i2cTransfer returns num messages.translate it pls.. */ + if (ret >= 0) + ret = 0; + return ret; +} +EXPORT_SYMBOL(twl4030_i2c_read); + +/** + * twl4030_i2c_write_u8 - Writes a 8 bit register in TWL4030 + * @mod_no: module number + * @value: the value to be written 8 bit + * @reg: register address (just offset will do) + * + * Returns result of operation - 0 is success + */ +int twl4030_i2c_write_u8(u8 mod_no, u8 value, u8 reg) +{ + + /* 2 bytes offset 1 contains the data offset 0 is used by i2c_write */ + u8 temp_buffer[2] = { 0 }; + /* offset 1 contains the data */ + temp_buffer[1] = value; + return twl4030_i2c_write(mod_no, temp_buffer, reg, 1); +} +EXPORT_SYMBOL(twl4030_i2c_write_u8); + +/** + * twl4030_i2c_read_u8 - Reads a 8 bit register from TWL4030 + * @mod_no: module number + * @value: the value read 8 bit + * @reg: register address (just offset will do) + * + * Returns result of operation - 0 is success + */ +int twl4030_i2c_read_u8(u8 mod_no, u8 *value, u8 reg) +{ + return twl4030_i2c_read(mod_no, value, reg, 1); +} +EXPORT_SYMBOL(twl4030_i2c_read_u8); + +/* Helper Functions */ + +/* + * do_twl4030_module_irq() is the desc->handle method for each of the twl4030 + * module interrupts. It executes in kernel thread context. + * On entry, cpu interrupts are disabled. + */ +static void do_twl4030_module_irq(unsigned int irq, irq_desc_t *desc) +{ + struct irqaction *action; + const unsigned int cpu = smp_processor_id(); + + /* + * Earlier this was desc->triggered = 1; + */ + desc->status |= IRQ_LEVEL; + + /* + * The desc->handle method would normally call the desc->chip->ack + * method here, but we won't bother since our ack method is NULL. + */ + + if (!desc->depth) { + kstat_cpu(cpu).irqs[irq]++; + + action = desc->action; + if (action) { + int ret; + int status = 0; + int retval = 0; + + local_irq_enable(); + + do { + /* Call the ISR with cpu interrupts enabled */ + ret = action->handler(irq, action->dev_id); + if (ret == IRQ_HANDLED) + status |= action->flags; + retval |= ret; + action = action->next; + } while (action); + + if (status & IRQF_SAMPLE_RANDOM) + add_interrupt_randomness(irq); + + local_irq_disable(); + + if (retval != IRQ_HANDLED) + printk(KERN_ERR "ISR for TWL4030 module" + " irq %d can't handle interrupt\n", + irq); + + /* + * Here is where we should call the unmask method, but + * again we won't bother since it is NULL. + */ + } else + printk(KERN_CRIT "TWL4030 module irq %d has no ISR" + " but can't be masked!\n", irq); + } else + printk(KERN_CRIT "TWL4030 module irq %d is disabled but can't" + " be masked!\n", irq); +} + +/* + * twl4030_irq_thread() runs as a kernel thread. It queries the twl4030 + * interrupt controller to see which modules are generating interrupt requests + * and then calls the desc->handle method for each module requesting service. + */ +static int twl4030_irq_thread(void *data) +{ + int irq = (int)data; + irq_desc_t *desc = irq_desc + irq; + static unsigned i2c_errors; + const static unsigned max_i2c_errors = 100; + + daemonize("twl4030-irq"); + current->flags |= PF_NOFREEZE; + + while (!kthread_should_stop()) { + int ret; + int module_irq; + u8 pih_isr; + + wait_for_completion_interruptible(&irq_event); + + ret = twl4030_i2c_read_u8(TWL4030_MODULE_PIH, &pih_isr, + REG_PIH_ISR_P1); + if (ret) { + printk(KERN_WARNING "I2C error %d while reading TWL4030" + " PIH ISR register.\n", ret); + if (++i2c_errors >= max_i2c_errors) { + printk(KERN_ERR "Maximum I2C error count" + " exceeded. Terminating %s.\n", + __func__); + break; + } + continue; + } + + for (module_irq = TWL4030_IRQ_BASE; 0 != pih_isr; + pih_isr >>= 1, module_irq++) { + if (pih_isr & 0x1) { + irq_desc_t *d = irq_desc + module_irq; + + local_irq_disable(); + + d->handle_irq(module_irq, d); + + local_irq_enable(); + } + } + + desc->chip->unmask(irq); + } + + return 0; +} + +/* + * do_twl4030_irq() is the desc->handle method for the twl4030 interrupt. + * This is a chained interrupt, so there is no desc->action method for it. + * Now we need to query the interrupt controller in the twl4030 to determine + * which module is generating the interrupt request. However, we can't do i2c + * transactions in interrupt context, so we must defer that work to a kernel + * thread. All we do here is acknowledge and mask the interrupt and wakeup + * the kernel thread. + */ +static void do_twl4030_irq(unsigned int irq, irq_desc_t *desc) +{ + const unsigned int cpu = smp_processor_id(); + + /* + * Earlier this was desc->triggered = 1; + */ + desc->status |= IRQ_LEVEL; + + /* + * Acknowledge, clear _AND_ disable the interrupt. + */ + desc->chip->ack(irq); + + if (!desc->depth) { + kstat_cpu(cpu).irqs[irq]++; + + complete(&irq_event); + } +} + +/* attach a client to the adapter */ +static int __init twl4030_detect_client(struct i2c_adapter *adapter, + unsigned char sid) +{ + int err = 0; + struct twl4030_client *twl; + + if (unlikely(sid >= TWL4030_NUM_SLAVES)) { + pr_err("sid[%d] > MOD_LAST[%d]\n", sid, TWL4030_NUM_SLAVES); + return -EPERM; + } + + /* Check basic functionality */ + err = i2c_check_functionality(adapter, + I2C_FUNC_SMBUS_WORD_DATA + | I2C_FUNC_SMBUS_WRITE_BYTE); + if (!err) { + pr_err("SlaveID=%d functionality check failed\n", sid); + return err; + } + twl = &twl4030_modules[sid]; + if (unlikely(twl->inuse)) { + pr_err("Client %s is already in use\n", twl->client_name); + return -EPERM; + } + + memset(&twl->client, 0, sizeof(struct i2c_client)); + + twl->client.addr = twl->address; + twl->client.adapter = adapter; + twl->client.driver = &twl4030_driver; + + memcpy(&twl->client.name, twl->client_name, + sizeof(TWL_CLIENT_STRING) + 1); + + pr_info("TWL4030: TRY attach Slave %s on Adapter %s [%x]\n", + twl->client_name, adapter->name, err); + + err = i2c_attach_client(&twl->client); + if (err) { + pr_err("Couldn't attach Slave %s on Adapter" + "%s [%x]\n", twl->client_name, adapter->name, err); + } else { + twl->inuse = TWL_CLIENT_USED; + mutex_init(&twl->xfer_lock); + } + + return err; +} + +/* adapter callback */ +static int __init twl4030_attach_adapter(struct i2c_adapter *adapter) +{ + int i; + int ret = 0; + static int twl_i2c_adapter = 1; + + for (i = 0; i < TWL4030_NUM_SLAVES; i++) { + /* Check if I need to hook on to this adapter or not */ + if (twl4030_modules[i].adapter_index == twl_i2c_adapter) { + ret = twl4030_detect_client(adapter, i); + if (ret) + goto free_client; + } + } + twl_i2c_adapter++; + + /* + * Check if the PIH module is initialized, if yes, then init + * the T2 Interrupt subsystem + */ + if ((twl4030_modules[twl4030_map[TWL4030_MODULE_PIH].sid].inuse == + TWL_CLIENT_USED) && (twl_irq_used != USED)) { + twl_init_irq(); + twl_irq_used = USED; + } + return 0; + +free_client: + pr_err("TWL_CLIENT(Idx=%d] registration failed[0x%x]\n", i, ret); + + /* ignore current slave..it never got registered */ + i--; + while (i >= 0) { + /* now remove all those from the current adapter... */ + if (twl4030_modules[i].adapter_index == twl_i2c_adapter) + (void)twl4030_detach_client(&twl4030_modules[i].client); + i--; + } + return ret; +} + +/* adapter's callback */ +static int twl4030_detach_client(struct i2c_client *client) +{ + int err; + err = i2c_detach_client(client); + if (err) { + pr_err("Client detach failed\n"); + return err; + } + return 0; +} + +static struct task_struct * __init start_twl4030_irq_thread(int irq) +{ + struct task_struct *thread; + + init_completion(&irq_event); + thread = kthread_run(twl4030_irq_thread, (void *)irq, + "twl4030 irq %d", irq); + if (!thread) + pr_err("%s: could not create twl4030 irq %d thread!\n", + __func__, irq); + + return thread; +} + +/* + * These three functions should be part of Voltage frame work + * added here to complete the functionality for now. + */ +static int __init protect_pm_master(void) +{ + int e = 0; + + e = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, KEY_LOCK, + R_PROTECT_KEY); + return e; +} + +static int __init unprotect_pm_master(void) +{ + int e = 0; + + e |= twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, KEY_UNLOCK1, + R_PROTECT_KEY); + e |= twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, KEY_UNLOCK2, + R_PROTECT_KEY); + return e; +} + +static int __init power_companion_init(void) +{ + struct clk *osc; + u32 rate; + u8 ctrl = HFCLK_FREQ_26_MHZ; + int e = 0; + + if (cpu_is_omap2430()) + osc = clk_get(NULL, "osc_ck"); + else + osc = clk_get(NULL, "osc_sys_ck"); + if (IS_ERR(osc)) { + printk(KERN_WARNING "Skipping twl3040 internal clock init and " + "using bootloader value (unknown osc rate)\n"); + return 0; + } + + rate = clk_get_rate(osc); + clk_put(osc); + + switch (rate) { + case 19200000 : ctrl = HFCLK_FREQ_19p2_MHZ; break; + case 26000000 : ctrl = HFCLK_FREQ_26_MHZ; break; + case 38400000 : ctrl = HFCLK_FREQ_38p4_MHZ; break; + } + + ctrl |= HIGH_PERF_SQ; + e |= unprotect_pm_master(); + /* effect->MADC+USB ck en */ + e |= twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, ctrl, R_CFG_BOOT); + e |= protect_pm_master(); + + return e; +} + +/** + * twl4030_i2c_clear_isr - clear TWL4030 SIH ISR regs via read + write + * @mod_no: TWL4030 module number + * @reg: register index to clear + * @cor: value of the _SIH_CTRL.COR bit (1 or 0) + * + * Either reads (cor == 1) or writes (cor == 0) to a TWL4030 interrupt + * status register to ensure that any prior interrupts are cleared. + * Returns the status from the I2C read operation. + */ +static int __init twl4030_i2c_clear_isr(u8 mod_no, u8 reg, u8 cor) +{ + u8 tmp; + + return (cor) ? twl4030_i2c_read_u8(mod_no, &tmp, reg) : + twl4030_i2c_write_u8(mod_no, 0xff, reg); +} + +/** + * twl4030_read_cor_bit - are TWL module ISRs cleared by reads or writes? + * @mod_no: TWL4030 module number + * @reg: register index to clear + * + * Returns 1 if the TWL4030 SIH interrupt status registers (ISRs) for + * the specified TWL module are cleared by reads, or 0 if cleared by + * writes. + */ +static int twl4030_read_cor_bit(u8 mod_no, u8 reg) +{ + u8 tmp = 0; + + WARN_ON(twl4030_i2c_read_u8(mod_no, &tmp, reg) < 0); + + tmp &= TWL4030_SIH_CTRL_COR_MASK; + tmp >>= __ffs(TWL4030_SIH_CTRL_COR_MASK); + + return tmp; +} + +/** + * twl4030_mask_clear_intrs - mask and clear all TWL4030 interrupts + * @t: pointer to twl4030_mod_iregs array + * @t_sz: ARRAY_SIZE(t) (starting at 1) + * + * Mask all TWL4030 interrupt mask registers (IMRs) and clear all + * interrupt status registers (ISRs). No return value, but will WARN if + * any I2C operations fail. + */ +static void __init twl4030_mask_clear_intrs(const struct twl4030_mod_iregs *t, + const u8 t_sz) +{ + int i, j; + + /* + * N.B. - further efficiency is possible here. Eight I2C + * operations on BCI and GPIO modules are avoidable if I2C + * burst read/write transactions were implemented. Would + * probably save about 1ms of boot time and a small amount of + * power. + */ + for (i = 0; i < t_sz; i++) { + const struct twl4030_mod_iregs tmr = t[i]; + int cor; + + /* Are ISRs cleared by reads or writes? */ + cor = twl4030_read_cor_bit(tmr.mod_no, tmr.sih_ctrl); + WARN_ON(cor < 0); + + for (j = 0; j < tmr.reg_cnt; j++) { + + /* Mask interrupts at the TWL4030 */ + WARN_ON(twl4030_i2c_write_u8(tmr.mod_no, 0xff, + tmr.imrs[j]) < 0); + + /* Clear TWL4030 ISRs */ + WARN_ON(twl4030_i2c_clear_isr(tmr.mod_no, + tmr.isrs[j], cor) < 0); + } + } + + return; +} + + +static void twl_init_irq(void) +{ + int i; + int res = 0; + char *msg = "Unable to register interrupt subsystem"; + unsigned int irq_num; + + /* + * Mask and clear all TWL4030 interrupts since initially we do + * not have any TWL4030 module interrupt handlers present + */ + twl4030_mask_clear_intrs(twl4030_mod_regs, + ARRAY_SIZE(twl4030_mod_regs)); + + /* install an irq handler for each of the PIH modules */ + for (i = TWL4030_IRQ_BASE; i < TWL4030_IRQ_END; i++) { + set_irq_chip(i, &twl4030_irq_chip); + set_irq_handler(i, do_twl4030_module_irq); + set_irq_flags(i, IRQF_VALID); + } + + irq_num = (cpu_is_omap2430()) ? INT_24XX_SYS_NIRQ : INT_34XX_SYS_NIRQ; + + /* install an irq handler to demultiplex the TWL4030 interrupt */ + set_irq_data(irq_num, start_twl4030_irq_thread(irq_num)); + set_irq_type(irq_num, IRQ_TYPE_EDGE_FALLING); + set_irq_chained_handler(irq_num, do_twl4030_irq); + + res = power_companion_init(); + if (res < 0) + pr_err("%s[%d][%d]\n", msg, res, __LINE__); +} + +static int __init twl4030_init(void) +{ + return i2c_add_driver(&twl4030_driver); +} + +static void __exit twl4030_exit(void) +{ + i2c_del_driver(&twl4030_driver); + twl_irq_used = FREE; +} + +subsys_initcall(twl4030_init); +module_exit(twl4030_exit); + +MODULE_ALIAS("i2c:" DRIVER_NAME); +MODULE_AUTHOR("Texas Instruments, Inc."); +MODULE_DESCRIPTION("I2C Core interface for TWL4030"); +MODULE_LICENSE("GPL"); diff --cc drivers/i2c/chips/twl4030-gpio.c index 9d17f4574fd,00000000000..494124cc43c mode 100644,000000..100644 --- a/drivers/i2c/chips/twl4030-gpio.c +++ b/drivers/i2c/chips/twl4030-gpio.c @@@ -1,787 -1,0 +1,787 @@@ +/* + * linux/drivers/i2c/chips/twl4030_gpio.c + * + * Copyright (C) 2006-2007 Texas Instruments, Inc. + * Copyright (C) 2006 MontaVista Software, Inc. + * + * Code re-arranged and cleaned up by: + * Syed Mohammed Khasim + * + * Initial Code: + * Andy Lowe / Nishanth Menon + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + - #include ++#include +#include - #include - #include ++#include ++#include + +#include + +/* BitField Definitions */ + +/* Data banks : 3 banks for 8 gpios each */ +#define DATA_BANK_MAX 8 +#define GET_GPIO_DATA_BANK(x) ((x)/DATA_BANK_MAX) +#define GET_GPIO_DATA_OFF(x) ((x)%DATA_BANK_MAX) + +/* GPIODATADIR Fields each block 0-7 */ +#define BIT_GPIODATADIR_GPIOxDIR(x) (x) +#define MASK_GPIODATADIR_GPIOxDIR(x) (0x01 << (x)) + +/* GPIODATAIN Fields each block 0-7 */ +#define BIT_GPIODATAIN_GPIOxIN(x) (x) +#define MASK_GPIODATAIN_GPIOxIN(x) (0x01 << (x)) + +/* GPIODATAOUT Fields each block 0-7 */ +#define BIT_GPIODATAOUT_GPIOxOUT(x) (x) +#define MASK_GPIODATAOUT_GPIOxOUT(x) (0x01 << (x)) + +/* CLEARGPIODATAOUT Fields */ +#define BIT_CLEARGPIODATAOUT_GPIOxOUT(x) (x) +#define MASK_CLEARGPIODATAOUT_GPIOxOUT(x) (0x01 << (x)) + +/* SETGPIODATAOUT Fields */ +#define BIT_SETGPIODATAOUT_GPIOxOUT(x) (x) +#define MASK_SETGPIODATAOUT_GPIOxOUT(x) (0x01 << (x)) + +/* GPIO_DEBEN Fields */ +#define BIT_GPIO_DEBEN_GPIOxDEB(x) (x) +#define MASK_GPIO_DEBEN_GPIOxDEB(x) (0x01 << (x)) + +/* GPIO_ISR1A Fields */ +#define BIT_GPIO_ISR_GPIOxISR(x) (x) +#define MASK_GPIO_ISR_GPIOxISR(x) (0x01 << (x)) + +/* GPIO_IMR1A Fields */ +#define BIT_GPIO_IMR1A_GPIOxIMR(x) (x) +#define MASK_GPIO_IMR1A_GPIOxIMR(x) (0x01 << (x)) + +/* GPIO_SIR1 Fields */ +#define BIT_GPIO_SIR1_GPIOxSIR(x) (x) +#define MASK_GPIO_SIR1_GPIO0SIR (0x01 << (x)) + + +/* Control banks : 5 banks for 4 gpios each */ +#define DATA_CTL_MAX 4 +#define GET_GPIO_CTL_BANK(x) ((x)/DATA_CTL_MAX) +#define GET_GPIO_CTL_OFF(x) ((x)%DATA_CTL_MAX) +#define GPIO_BANK_MAX GET_GPIO_CTL_BANK(TWL4030_GPIO_MAX) + +/* GPIOPUPDCTRx Fields 5 banks of 4 gpios each */ +#define BIT_GPIOPUPDCTR1_GPIOxPD(x) (2 * (x)) +#define MASK_GPIOPUPDCTR1_GPIOxPD(x) (0x01 << (2 * (x))) +#define BIT_GPIOPUPDCTR1_GPIOxPU(x) ((x) + 1) +#define MASK_GPIOPUPDCTR1_GPIOxPU(x) (0x01 << (((2 * (x)) + 1))) + +/* GPIO_EDR1 Fields */ +#define BIT_GPIO_EDR1_GPIOxFALLING(x) (2 * (x)) +#define MASK_GPIO_EDR1_GPIOxFALLING(x) (0x01 << (2 * (x))) +#define BIT_GPIO_EDR1_GPIOxRISING(x) ((x) + 1) +#define MASK_GPIO_EDR1_GPIOxRISING(x) (0x01 << (((2 * (x)) + 1))) + +/* GPIO_SIH_CTRL Fields */ +#define BIT_GPIO_SIH_CTRL_EXCLEN (0x000) +#define MASK_GPIO_SIH_CTRL_EXCLEN (0x00000001) +#define BIT_GPIO_SIH_CTRL_PENDDIS (0x001) +#define MASK_GPIO_SIH_CTRL_PENDDIS (0x00000002) +#define BIT_GPIO_SIH_CTRL_COR (0x002) +#define MASK_GPIO_SIH_CTRL_COR (0x00000004) + +/* GPIO_CTRL Fields */ +#define BIT_GPIO_CTRL_GPIO0CD1 (0x000) +#define MASK_GPIO_CTRL_GPIO0CD1 (0x00000001) +#define BIT_GPIO_CTRL_GPIO1CD2 (0x001) +#define MASK_GPIO_CTRL_GPIO1CD2 (0x00000002) +#define BIT_GPIO_CTRL_GPIO_ON (0x002) +#define MASK_GPIO_CTRL_GPIO_ON (0x00000004) + +/* Mask for GPIO registers when aggregated into a 32-bit integer */ +#define GPIO_32_MASK 0x0003ffff + +/* Data structures */ +static struct semaphore gpio_sem; + +/* store usage of each GPIO. - each bit represents one GPIO */ +static unsigned int gpio_usage_count; + +/* shadow the imr register */ +static unsigned int gpio_imr_shadow; + +/* bitmask of pending requests to unmask gpio interrupts */ +static unsigned int gpio_pending_unmask; + +/* pointer to gpio unmask thread struct */ +static struct task_struct *gpio_unmask_thread; + +/* + * Helper functions to read and write the GPIO ISR and IMR registers as + * 32-bit integers. Functions return 0 on success, non-zero otherwise. + * The caller must hold a lock on gpio_sem. + */ + +static int gpio_read_isr(unsigned int *isr) +{ + int ret; + + *isr = 0; + ret = twl4030_i2c_read(TWL4030_MODULE_GPIO, (u8 *) isr, + REG_GPIO_ISR1A, 3); + le32_to_cpup(isr); + *isr &= GPIO_32_MASK; + + return ret; +} + +static int gpio_write_isr(unsigned int isr) +{ + isr &= GPIO_32_MASK; + /* + * The buffer passed to the twl4030_i2c_write() routine must have an + * extra byte at the beginning reserved for its internal use. + */ + isr <<= 8; + isr = cpu_to_le32(isr); + return twl4030_i2c_write(TWL4030_MODULE_GPIO, (u8 *) &isr, + REG_GPIO_ISR1A, 3); +} + +static int gpio_write_imr(unsigned int imr) +{ + imr &= GPIO_32_MASK; + /* + * The buffer passed to the twl4030_i2c_write() routine must have an + * extra byte at the beginning reserved for its internal use. + */ + imr <<= 8; + imr = cpu_to_le32(imr); + return twl4030_i2c_write(TWL4030_MODULE_GPIO, (u8 *) &imr, + REG_GPIO_IMR1A, 3); +} + +/* + * These routines are analagous to the irqchip methods, but they are designed + * to be called from thread context with cpu interrupts enabled and with no + * locked spinlocks. We call these routines from our custom IRQ handler + * instead of the usual irqchip methods. + */ +static void twl4030_gpio_mask_and_ack(unsigned int irq) +{ + int gpio = irq - TWL4030_GPIO_IRQ_BASE; + + down(&gpio_sem); + /* mask */ + gpio_imr_shadow |= (1 << gpio); + gpio_write_imr(gpio_imr_shadow); + /* ack */ + gpio_write_isr(1 << gpio); + up(&gpio_sem); +} + +static void twl4030_gpio_unmask(unsigned int irq) +{ + int gpio = irq - TWL4030_GPIO_IRQ_BASE; + + down(&gpio_sem); + gpio_imr_shadow &= ~(1 << gpio); + gpio_write_imr(gpio_imr_shadow); + up(&gpio_sem); +} + +/* + * These are the irqchip methods for the TWL4030 GPIO interrupts. + * Our IRQ handle method doesn't call these, but they will be called by + * other routines such as setup_irq() and enable_irq(). They are called + * with cpu interrupts disabled and with a lock on the irq_controller_lock + * spinlock. This complicates matters, because accessing the TWL4030 GPIO + * interrupt controller requires I2C bus transactions that can't be initiated + * in this context. Our solution is to defer accessing the interrupt + * controller to a kernel thread. We only need to support the unmask method. + */ + +static void twl4030_gpio_mask_and_ack_irqchip(unsigned int irq) {} +static void twl4030_gpio_mask_irqchip(unsigned int irq) {} + +static void twl4030_gpio_unmask_irqchip(unsigned int irq) +{ + int gpio = irq - TWL4030_GPIO_IRQ_BASE; + + gpio_pending_unmask |= (1 << gpio); + if (gpio_unmask_thread && gpio_unmask_thread->state != TASK_RUNNING) + wake_up_process(gpio_unmask_thread); +} + +static struct irq_chip twl4030_gpio_irq_chip = { + .name = "twl4030-gpio", + .ack = twl4030_gpio_mask_and_ack_irqchip, + .mask = twl4030_gpio_mask_irqchip, + .unmask = twl4030_gpio_unmask_irqchip, +}; + +/* + * These are the irqchip methods for the TWL4030 PIH GPIO module interrupt. + * The PIH module doesn't have interrupt masking capability, so these + * methods are NULL. + */ +static void twl4030_gpio_module_ack(unsigned int irq) {} +static void twl4030_gpio_module_mask(unsigned int irq) {} +static void twl4030_gpio_module_unmask(unsigned int irq) {} +static struct irq_chip twl4030_gpio_module_irq_chip = { + .ack = twl4030_gpio_module_ack, + .mask = twl4030_gpio_module_mask, + .unmask = twl4030_gpio_module_unmask, +}; + +/* + * To configure TWL4030 GPIO module registers + */ +static inline int gpio_twl4030_write(u8 address, u8 data) +{ + int ret = 0; + + ret = twl4030_i2c_write_u8(TWL4030_MODULE_GPIO, data, address); + return ret; +} + +/* + * To read a TWL4030 GPIO module register + */ +static inline int gpio_twl4030_read(u8 address) +{ + u8 data; + int ret = 0; + + ret = twl4030_i2c_read_u8(TWL4030_MODULE_GPIO, &data, address); + if (ret >= 0) + ret = data; + return ret; +} + +/* + * twl4030 GPIO request function + */ +int twl4030_request_gpio(int gpio) +{ + int ret = 0; + + if (unlikely(gpio >= TWL4030_GPIO_MAX)) + return -EPERM; + + down(&gpio_sem); + if (gpio_usage_count & (0x1 << gpio)) + ret = -EBUSY; + else { + u8 clear_pull[6] = { 0, 0, 0, 0, 0, 0 }; + /* First time usage? - switch on GPIO module */ + if (!gpio_usage_count) { + ret = + gpio_twl4030_write(REG_GPIO_CTRL, + MASK_GPIO_CTRL_GPIO_ON); + ret = gpio_twl4030_write(REG_GPIO_SIH_CTRL, 0x00); + } + if (!ret) + gpio_usage_count |= (0x1 << gpio); + + ret = + twl4030_i2c_write(TWL4030_MODULE_GPIO, clear_pull, + REG_GPIOPUPDCTR1, 5); + } + up(&gpio_sem); + return ret; +} +EXPORT_SYMBOL(twl4030_request_gpio); + +/* + * TWL4030 GPIO free module + */ +int twl4030_free_gpio(int gpio) +{ + int ret = 0; + + if (unlikely(gpio >= TWL4030_GPIO_MAX)) + return -EPERM; + + down(&gpio_sem); + + if ((gpio_usage_count & (0x1 << gpio)) == 0) + ret = -EPERM; + else + gpio_usage_count &= ~(0x1 << gpio); + + /* Last time usage? - switch off GPIO module */ + if (!gpio_usage_count) + ret = gpio_twl4030_write(REG_GPIO_CTRL, 0x0); + + up(&gpio_sem); + return ret; +} +EXPORT_SYMBOL(twl4030_free_gpio); + +/* + * Set direction for TWL4030 GPIO + */ +int twl4030_set_gpio_direction(int gpio, int is_input) +{ + u8 d_bnk = GET_GPIO_DATA_BANK(gpio); + u8 d_msk = MASK_GPIODATADIR_GPIOxDIR(GET_GPIO_DATA_OFF(gpio)); + u8 reg = 0; + u8 base = 0; + int ret = 0; + + if (unlikely((gpio >= TWL4030_GPIO_MAX) + || !(gpio_usage_count & (0x1 << gpio)))) + return -EPERM; + + base = REG_GPIODATADIR1 + d_bnk; + + down(&gpio_sem); + ret = gpio_twl4030_read(base); + if (ret >= 0) { + if (is_input) + reg = (u8) ((ret) & ~(d_msk)); + else + reg = (u8) ((ret) | (d_msk)); + + ret = gpio_twl4030_write(base, reg); + } + up(&gpio_sem); + return ret; +} +EXPORT_SYMBOL(twl4030_set_gpio_direction); + +/* + * To enable/disable GPIO pin on TWL4030 + */ +int twl4030_set_gpio_dataout(int gpio, int enable) +{ + u8 d_bnk = GET_GPIO_DATA_BANK(gpio); + u8 d_msk = MASK_GPIODATAOUT_GPIOxOUT(GET_GPIO_DATA_OFF(gpio)); + u8 base = 0; + int ret = 0; + + if (unlikely((gpio >= TWL4030_GPIO_MAX) + || !(gpio_usage_count & (0x1 << gpio)))) + return -EPERM; + + if (enable) + base = REG_SETGPIODATAOUT1 + d_bnk; + else + base = REG_CLEARGPIODATAOUT1 + d_bnk; + + down(&gpio_sem); + ret = gpio_twl4030_write(base, d_msk); + up(&gpio_sem); + return ret; +} +EXPORT_SYMBOL(twl4030_set_gpio_dataout); + +/* + * To get the status of a GPIO pin on TWL4030 + */ +int twl4030_get_gpio_datain(int gpio) +{ + u8 d_bnk = GET_GPIO_DATA_BANK(gpio); + u8 d_off = BIT_GPIODATAIN_GPIOxIN(GET_GPIO_DATA_OFF(gpio)); + u8 base = 0; + int ret = 0; + + if (unlikely((gpio >= TWL4030_GPIO_MAX) + || !(gpio_usage_count & (0x1 << gpio)))) + return -EPERM; + + base = REG_GPIODATAIN1 + d_bnk; + down(&gpio_sem); + ret = gpio_twl4030_read(base); + up(&gpio_sem); + if (ret > 0) + ret = (ret >> d_off) & 0x1; + + return ret; +} +EXPORT_SYMBOL(twl4030_get_gpio_datain); + +/* + * Configure PULL type for a GPIO pin on TWL4030 + */ +int twl4030_set_gpio_pull(int gpio, int pull_dircn) +{ + u8 c_bnk = GET_GPIO_CTL_BANK(gpio); + u8 c_off = GET_GPIO_CTL_OFF(gpio); + u8 c_msk = 0; + u8 reg = 0; + u8 base = 0; + int ret = 0; + + if (unlikely((gpio >= TWL4030_GPIO_MAX) || + !(gpio_usage_count & (0x1 << gpio)))) + return -EPERM; + + base = REG_GPIOPUPDCTR1 + c_bnk; + if (pull_dircn == TWL4030_GPIO_PULL_DOWN) + c_msk = MASK_GPIOPUPDCTR1_GPIOxPD(c_off); + else if (pull_dircn == TWL4030_GPIO_PULL_UP) + c_msk = MASK_GPIOPUPDCTR1_GPIOxPU(c_off); + + down(&gpio_sem); + ret = gpio_twl4030_read(base); + if (ret >= 0) { + /* clear the previous up/down values */ + reg = (u8) (ret); + reg &= ~(MASK_GPIOPUPDCTR1_GPIOxPU(c_off) | + MASK_GPIOPUPDCTR1_GPIOxPD(c_off)); + reg |= c_msk; + ret = gpio_twl4030_write(base, reg); + } + up(&gpio_sem); + return ret; +} +EXPORT_SYMBOL(twl4030_set_gpio_pull); + +/* + * Configure Edge control for a GPIO pin on TWL4030 + */ +int twl4030_set_gpio_edge_ctrl(int gpio, int edge) +{ + u8 c_bnk = GET_GPIO_CTL_BANK(gpio); + u8 c_off = GET_GPIO_CTL_OFF(gpio); + u8 c_msk = 0; + u8 reg = 0; + u8 base = 0; + int ret = 0; + + if (unlikely((gpio >= TWL4030_GPIO_MAX) + || !(gpio_usage_count & (0x1 << gpio)))) + return -EPERM; + + base = REG_GPIO_EDR1 + c_bnk; + + if (edge & TWL4030_GPIO_EDGE_RISING) + c_msk |= MASK_GPIO_EDR1_GPIOxRISING(c_off); + + if (edge & TWL4030_GPIO_EDGE_FALLING) + c_msk |= MASK_GPIO_EDR1_GPIOxFALLING(c_off); + + down(&gpio_sem); + ret = gpio_twl4030_read(base); + if (ret >= 0) { + /* clear the previous rising/falling values */ + reg = + (u8) (ret & + ~(MASK_GPIO_EDR1_GPIOxFALLING(c_off) | + MASK_GPIO_EDR1_GPIOxRISING(c_off))); + reg |= c_msk; + ret = gpio_twl4030_write(base, reg); + } + up(&gpio_sem); + return ret; +} +EXPORT_SYMBOL(twl4030_set_gpio_edge_ctrl); + +/* + * Configure debounce timing value for a GPIO pin on TWL4030 + */ +int twl4030_set_gpio_debounce(int gpio, int enable) +{ + u8 d_bnk = GET_GPIO_DATA_BANK(gpio); + u8 d_msk = MASK_GPIO_DEBEN_GPIOxDEB(GET_GPIO_DATA_OFF(gpio)); + u8 reg = 0; + u8 base = 0; + int ret = 0; + + if (unlikely((gpio >= TWL4030_GPIO_MAX) + || !(gpio_usage_count & (0x1 << gpio)))) + return -EPERM; + + base = REG_GPIO_DEBEN1 + d_bnk; + down(&gpio_sem); + ret = gpio_twl4030_read(base); + if (ret >= 0) { + if (enable) + reg = (u8) ((ret) | (d_msk)); + else + reg = (u8) ((ret) & ~(d_msk)); + + ret = gpio_twl4030_write(base, reg); + } + up(&gpio_sem); + return ret; +} +EXPORT_SYMBOL(twl4030_set_gpio_debounce); + +/* + * Configure Card detect for GPIO pin on TWL4030 + */ +int twl4030_set_gpio_card_detect(int gpio, int enable) +{ + u8 reg = 0; + u8 msk = (1 << gpio); + int ret = 0; + + /* Only GPIO 0 or 1 can be used for CD feature.. */ + if (unlikely((gpio >= TWL4030_GPIO_MAX) + || !(gpio_usage_count & (0x1 << gpio)) + || (gpio >= TWL4030_GPIO_MAX_CD))) { + return -EPERM; + } + + down(&gpio_sem); + ret = gpio_twl4030_read(REG_GPIO_CTRL); + if (ret >= 0) { + if (enable) + reg = (u8) (ret | msk); + else + reg = (u8) (ret & ~msk); + + ret = gpio_twl4030_write(REG_GPIO_CTRL, reg); + } + up(&gpio_sem); + return (ret); +} +EXPORT_SYMBOL(twl4030_set_gpio_card_detect); + +/* MODULE FUNCTIONS */ + +/* + * gpio_unmask_thread() runs as a kernel thread. It is awakened by the unmask + * method for the GPIO interrupts. It unmasks all of the GPIO interrupts + * specified in the gpio_pending_unmask bitmask. We have to do the unmasking + * in a kernel thread rather than directly in the unmask method because of the + * need to access the TWL4030 via the I2C bus. Note that we don't need to be + * concerned about race conditions where the request to unmask a GPIO interrupt + * has already been cancelled before this thread does the unmasking. If a GPIO + * interrupt is improperly unmasked, then the IRQ handler for it will mask it + * when an interrupt occurs. + */ +static int twl4030_gpio_unmask_thread(void *data) +{ + current->flags |= PF_NOFREEZE; + + while (!kthread_should_stop()) { + int irq; + unsigned int gpio_unmask; + + local_irq_disable(); + gpio_unmask = gpio_pending_unmask; + gpio_pending_unmask = 0; + local_irq_enable(); + + for (irq = TWL4030_GPIO_IRQ_BASE; 0 != gpio_unmask; + gpio_unmask >>= 1, irq++) { + if (gpio_unmask & 0x1) + twl4030_gpio_unmask(irq); + } + + local_irq_disable(); + if (!gpio_pending_unmask) + set_current_state(TASK_INTERRUPTIBLE); + local_irq_enable(); + + schedule(); + } + set_current_state(TASK_RUNNING); + return 0; +} + +/* + * do_twl4030_gpio_irq() is the desc->handle method for each of the twl4030 + * gpio interrupts. It executes in kernel thread context. + * On entry, cpu interrupts are enabled. + */ +static void do_twl4030_gpio_irq(unsigned int irq, irq_desc_t *desc) +{ + struct irqaction *action; + const unsigned int cpu = smp_processor_id(); + + desc->status |= IRQ_LEVEL; + + /* + * Acknowledge, clear _AND_ disable the interrupt. + */ + twl4030_gpio_mask_and_ack(irq); + + if (!desc->depth) { + kstat_cpu(cpu).irqs[irq]++; + + action = desc->action; + if (action) { + int ret; + int status = 0; + int retval = 0; + do { + /* Call the ISR with cpu interrupts enabled. */ + ret = action->handler(irq, action->dev_id); + if (ret == IRQ_HANDLED) + status |= action->flags; + retval |= ret; + action = action->next; + } while (action); + + if (retval != IRQ_HANDLED) + printk(KERN_ERR "ISR for TWL4030 GPIO" + " irq %d can't handle interrupt\n", + irq); + + if (!desc->depth) + twl4030_gpio_unmask(irq); + } + } +} + +/* + * do_twl4030_gpio_module_irq() is the desc->handle method for the twl4030 gpio + * module interrupt. It executes in kernel thread context. + * This is a chained interrupt, so there is no desc->action method for it. + * We query the gpio module interrupt controller in the twl4030 to determine + * which gpio lines are generating interrupt requests, and then call the + * desc->handle method for each gpio that needs service. + * On entry, cpu interrupts are disabled. + */ +static void do_twl4030_gpio_module_irq(unsigned int irq, irq_desc_t *desc) +{ + const unsigned int cpu = smp_processor_id(); + + desc->status |= IRQ_LEVEL; + /* + * The desc->handle method would normally call the desc->chip->ack + * method here, but we won't bother since our ack method is NULL. + */ + if (!desc->depth) { + int gpio_irq; + unsigned int gpio_isr; + + kstat_cpu(cpu).irqs[irq]++; + local_irq_enable(); + + down(&gpio_sem); + if (gpio_read_isr(&gpio_isr)) + gpio_isr = 0; + up(&gpio_sem); + + for (gpio_irq = TWL4030_GPIO_IRQ_BASE; 0 != gpio_isr; + gpio_isr >>= 1, gpio_irq++) { + if (gpio_isr & 0x1) { + irq_desc_t *d = irq_desc + gpio_irq; + d->handle_irq(gpio_irq, d); + } + } + + local_irq_disable(); + /* + * Here is where we should call the unmask method, but again we + * won't bother since it is NULL. + */ + } +} + +/* TWL4030 Initialization module */ +static int __init gpio_twl4030_init(void) +{ + int ret; + int irq = 0; + + /* init the global locking sem */ + sema_init(&gpio_sem, 1); + + /* All GPIO interrupts are initially masked */ + gpio_pending_unmask = 0; + gpio_imr_shadow = GPIO_32_MASK; + ret = gpio_write_imr(gpio_imr_shadow); + if (!ret) { + /* + * Create a kernel thread to handle deferred unmasking of gpio + * interrupts. + */ + gpio_unmask_thread = kthread_create(twl4030_gpio_unmask_thread, + NULL, "twl4030 gpio"); + if (!gpio_unmask_thread) { + printk(KERN_ERR + "%s: could not create twl4030 gpio unmask" + " thread!\n", __func__); + ret = -ENOMEM; + } + } + + if (!ret) { + /* install an irq handler for each of the gpio interrupts */ + for (irq = TWL4030_GPIO_IRQ_BASE; irq < TWL4030_GPIO_IRQ_END; + irq++) { + set_irq_chip(irq, &twl4030_gpio_irq_chip); + set_irq_handler(irq, do_twl4030_gpio_irq); + set_irq_flags(irq, IRQF_VALID); + } + + /* + * Install an irq handler to demultiplex the gpio module + * interrupt. + */ + set_irq_chip(TWL4030_MODIRQ_GPIO, + &twl4030_gpio_module_irq_chip); + set_irq_chained_handler(TWL4030_MODIRQ_GPIO, + do_twl4030_gpio_module_irq); + } + + printk(KERN_INFO "TWL4030 GPIO Demux: IRQ Range %d to %d," + " Initialization %s\n", TWL4030_GPIO_IRQ_BASE, + TWL4030_GPIO_IRQ_END, (ret) ? "Failed" : "Success"); + return ret; +} + +/* TWL GPIO exit module */ +static void __exit gpio_twl4030_exit(void) +{ + int irq; + + /* uninstall the gpio demultiplexing interrupt handler */ + set_irq_handler(TWL4030_MODIRQ_GPIO, NULL); + set_irq_flags(TWL4030_MODIRQ_GPIO, 0); + + /* uninstall the irq handler for each of the gpio interrupts */ + for (irq = TWL4030_GPIO_IRQ_BASE; irq < TWL4030_GPIO_IRQ_END; irq++) { + set_irq_handler(irq, NULL); + set_irq_flags(irq, 0); + } + + /* stop the gpio unmask kernel thread */ + if (gpio_unmask_thread) { + kthread_stop(gpio_unmask_thread); + gpio_unmask_thread = NULL; + } +} + +module_init(gpio_twl4030_init); +module_exit(gpio_twl4030_exit); + +MODULE_ALIAS("i2c:twl4030-gpio"); +MODULE_AUTHOR("Texas Instruments, Inc."); +MODULE_DESCRIPTION("GPIO interface for TWL4030"); +MODULE_LICENSE("GPL"); diff --cc drivers/i2c/chips/twl4030-usb.c index d9760e41631,00000000000..2906b82cebb mode 100644,000000..100644 --- a/drivers/i2c/chips/twl4030-usb.c +++ b/drivers/i2c/chips/twl4030-usb.c @@@ -1,779 -1,0 +1,779 @@@ +/* + * twl4030_usb - TWL4030 USB transceiver, talking to OMAP OTG controller + * + * Copyright (C) 2004-2007 Texas Instruments + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Current status: + * - HS USB ULPI mode works. + * - 3-pin mode support may be added in future. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include - #include ++#include + +/* Register defines */ + +#define VENDOR_ID_LO 0x00 +#define VENDOR_ID_HI 0x01 +#define PRODUCT_ID_LO 0x02 +#define PRODUCT_ID_HI 0x03 + +#define FUNC_CTRL 0x04 +#define FUNC_CTRL_SET 0x05 +#define FUNC_CTRL_CLR 0x06 +#define FUNC_CTRL_SUSPENDM (1 << 6) +#define FUNC_CTRL_RESET (1 << 5) +#define FUNC_CTRL_OPMODE_MASK (3 << 3) /* bits 3 and 4 */ +#define FUNC_CTRL_OPMODE_NORMAL (0 << 3) +#define FUNC_CTRL_OPMODE_NONDRIVING (1 << 3) +#define FUNC_CTRL_OPMODE_DISABLE_BIT_NRZI (2 << 3) +#define FUNC_CTRL_TERMSELECT (1 << 2) +#define FUNC_CTRL_XCVRSELECT_MASK (3 << 0) /* bits 0 and 1 */ +#define FUNC_CTRL_XCVRSELECT_HS (0 << 0) +#define FUNC_CTRL_XCVRSELECT_FS (1 << 0) +#define FUNC_CTRL_XCVRSELECT_LS (2 << 0) +#define FUNC_CTRL_XCVRSELECT_FS4LS (3 << 0) + +#define IFC_CTRL 0x07 +#define IFC_CTRL_SET 0x08 +#define IFC_CTRL_CLR 0x09 +#define IFC_CTRL_INTERFACE_PROTECT_DISABLE (1 << 7) +#define IFC_CTRL_AUTORESUME (1 << 4) +#define IFC_CTRL_CLOCKSUSPENDM (1 << 3) +#define IFC_CTRL_CARKITMODE (1 << 2) +#define IFC_CTRL_FSLSSERIALMODE_3PIN (1 << 1) + +#define TWL4030_OTG_CTRL 0x0A +#define TWL4030_OTG_CTRL_SET 0x0B +#define TWL4030_OTG_CTRL_CLR 0x0C +#define TWL4030_OTG_CTRL_DRVVBUS (1 << 5) +#define TWL4030_OTG_CTRL_CHRGVBUS (1 << 4) +#define TWL4030_OTG_CTRL_DISCHRGVBUS (1 << 3) +#define TWL4030_OTG_CTRL_DMPULLDOWN (1 << 2) +#define TWL4030_OTG_CTRL_DPPULLDOWN (1 << 1) +#define TWL4030_OTG_CTRL_IDPULLUP (1 << 0) + +#define USB_INT_EN_RISE 0x0D +#define USB_INT_EN_RISE_SET 0x0E +#define USB_INT_EN_RISE_CLR 0x0F +#define USB_INT_EN_FALL 0x10 +#define USB_INT_EN_FALL_SET 0x11 +#define USB_INT_EN_FALL_CLR 0x12 +#define USB_INT_STS 0x13 +#define USB_INT_LATCH 0x14 +#define USB_INT_IDGND (1 << 4) +#define USB_INT_SESSEND (1 << 3) +#define USB_INT_SESSVALID (1 << 2) +#define USB_INT_VBUSVALID (1 << 1) +#define USB_INT_HOSTDISCONNECT (1 << 0) + +#define CARKIT_CTRL 0x19 +#define CARKIT_CTRL_SET 0x1A +#define CARKIT_CTRL_CLR 0x1B +#define CARKIT_CTRL_MICEN (1 << 6) +#define CARKIT_CTRL_SPKRIGHTEN (1 << 5) +#define CARKIT_CTRL_SPKLEFTEN (1 << 4) +#define CARKIT_CTRL_RXDEN (1 << 3) +#define CARKIT_CTRL_TXDEN (1 << 2) +#define CARKIT_CTRL_IDGNDDRV (1 << 1) +#define CARKIT_CTRL_CARKITPWR (1 << 0) +#define CARKIT_PLS_CTRL 0x22 +#define CARKIT_PLS_CTRL_SET 0x23 +#define CARKIT_PLS_CTRL_CLR 0x24 +#define CARKIT_PLS_CTRL_SPKRRIGHT_BIASEN (1 << 3) +#define CARKIT_PLS_CTRL_SPKRLEFT_BIASEN (1 << 2) +#define CARKIT_PLS_CTRL_RXPLSEN (1 << 1) +#define CARKIT_PLS_CTRL_TXPLSEN (1 << 0) + +#define MCPC_CTRL 0x30 +#define MCPC_CTRL_SET 0x31 +#define MCPC_CTRL_CLR 0x32 +#define MCPC_CTRL_RTSOL (1 << 7) +#define MCPC_CTRL_EXTSWR (1 << 6) +#define MCPC_CTRL_EXTSWC (1 << 5) +#define MCPC_CTRL_VOICESW (1 << 4) +#define MCPC_CTRL_OUT64K (1 << 3) +#define MCPC_CTRL_RTSCTSSW (1 << 2) +#define MCPC_CTRL_HS_UART (1 << 0) + +#define MCPC_IO_CTRL 0x33 +#define MCPC_IO_CTRL_SET 0x34 +#define MCPC_IO_CTRL_CLR 0x35 +#define MCPC_IO_CTRL_MICBIASEN (1 << 5) +#define MCPC_IO_CTRL_CTS_NPU (1 << 4) +#define MCPC_IO_CTRL_RXD_PU (1 << 3) +#define MCPC_IO_CTRL_TXDTYP (1 << 2) +#define MCPC_IO_CTRL_CTSTYP (1 << 1) +#define MCPC_IO_CTRL_RTSTYP (1 << 0) + +#define MCPC_CTRL2 0x36 +#define MCPC_CTRL2_SET 0x37 +#define MCPC_CTRL2_CLR 0x38 +#define MCPC_CTRL2_MCPC_CK_EN (1 << 0) + +#define OTHER_FUNC_CTRL 0x80 +#define OTHER_FUNC_CTRL_SET 0x81 +#define OTHER_FUNC_CTRL_CLR 0x82 +#define OTHER_FUNC_CTRL_BDIS_ACON_EN (1 << 4) +#define OTHER_FUNC_CTRL_FIVEWIRE_MODE (1 << 2) + +#define OTHER_IFC_CTRL 0x83 +#define OTHER_IFC_CTRL_SET 0x84 +#define OTHER_IFC_CTRL_CLR 0x85 +#define OTHER_IFC_CTRL_OE_INT_EN (1 << 6) +#define OTHER_IFC_CTRL_CEA2011_MODE (1 << 5) +#define OTHER_IFC_CTRL_FSLSSERIALMODE_4PIN (1 << 4) +#define OTHER_IFC_CTRL_HIZ_ULPI_60MHZ_OUT (1 << 3) +#define OTHER_IFC_CTRL_HIZ_ULPI (1 << 2) +#define OTHER_IFC_CTRL_ALT_INT_REROUTE (1 << 0) + +#define OTHER_INT_EN_RISE 0x86 +#define OTHER_INT_EN_RISE_SET 0x87 +#define OTHER_INT_EN_RISE_CLR 0x88 +#define OTHER_INT_EN_FALL 0x89 +#define OTHER_INT_EN_FALL_SET 0x8A +#define OTHER_INT_EN_FALL_CLR 0x8B +#define OTHER_INT_STS 0x8C +#define OTHER_INT_LATCH 0x8D +#define OTHER_INT_VB_SESS_VLD (1 << 7) +#define OTHER_INT_DM_HI (1 << 6) /* not valid for "latch" reg */ +#define OTHER_INT_DP_HI (1 << 5) /* not valid for "latch" reg */ +#define OTHER_INT_BDIS_ACON (1 << 3) /* not valid for "fall" regs */ +#define OTHER_INT_MANU (1 << 1) +#define OTHER_INT_ABNORMAL_STRESS (1 << 0) + +#define ID_STATUS 0x96 +#define ID_RES_FLOAT (1 << 4) +#define ID_RES_440K (1 << 3) +#define ID_RES_200K (1 << 2) +#define ID_RES_102K (1 << 1) +#define ID_RES_GND (1 << 0) + +#define POWER_CTRL 0xAC +#define POWER_CTRL_SET 0xAD +#define POWER_CTRL_CLR 0xAE +#define POWER_CTRL_OTG_ENAB (1 << 5) + +#define OTHER_IFC_CTRL2 0xAF +#define OTHER_IFC_CTRL2_SET 0xB0 +#define OTHER_IFC_CTRL2_CLR 0xB1 +#define OTHER_IFC_CTRL2_ULPI_STP_LOW (1 << 4) +#define OTHER_IFC_CTRL2_ULPI_TXEN_POL (1 << 3) +#define OTHER_IFC_CTRL2_ULPI_4PIN_2430 (1 << 2) +#define OTHER_IFC_CTRL2_USB_INT_OUTSEL_MASK (3 << 0) /* bits 0 and 1 */ +#define OTHER_IFC_CTRL2_USB_INT_OUTSEL_INT1N (0 << 0) +#define OTHER_IFC_CTRL2_USB_INT_OUTSEL_INT2N (1 << 0) + +#define REG_CTRL_EN 0xB2 +#define REG_CTRL_EN_SET 0xB3 +#define REG_CTRL_EN_CLR 0xB4 +#define REG_CTRL_ERROR 0xB5 +#define ULPI_I2C_CONFLICT_INTEN (1 << 0) + +#define OTHER_FUNC_CTRL2 0xB8 +#define OTHER_FUNC_CTRL2_SET 0xB9 +#define OTHER_FUNC_CTRL2_CLR 0xBA +#define OTHER_FUNC_CTRL2_VBAT_TIMER_EN (1 << 0) + +/* following registers do not have separate _clr and _set registers */ +#define VBUS_DEBOUNCE 0xC0 +#define ID_DEBOUNCE 0xC1 +#define VBAT_TIMER 0xD3 +#define PHY_PWR_CTRL 0xFD +#define PHY_PWR_PHYPWD (1 << 0) +#define PHY_CLK_CTRL 0xFE +#define PHY_CLK_CTRL_CLOCKGATING_EN (1 << 2) +#define PHY_CLK_CTRL_CLK32K_EN (1 << 1) +#define REQ_PHY_DPLL_CLK (1 << 0) +#define PHY_CLK_CTRL_STS 0xFF +#define PHY_DPLL_CLK (1 << 0) + +/* In module TWL4030_MODULE_PM_MASTER */ +#define PROTECT_KEY 0x0E + +/* In module TWL4030_MODULE_PM_RECEIVER */ +#define VUSB_DEDICATED1 0x7D +#define VUSB_DEDICATED2 0x7E +#define VUSB1V5_DEV_GRP 0x71 +#define VUSB1V5_TYPE 0x72 +#define VUSB1V5_REMAP 0x73 +#define VUSB1V8_DEV_GRP 0x74 +#define VUSB1V8_TYPE 0x75 +#define VUSB1V8_REMAP 0x76 +#define VUSB3V1_DEV_GRP 0x77 +#define VUSB3V1_TYPE 0x78 +#define VUSB3V1_REMAP 0x79 + +#define ID_STATUS 0x96 +#define ID_RES_FLOAT (1 << 4) /* mini-B */ +#define ID_RES_440K (1 << 3) /* type 2 charger */ +#define ID_RES_200K (1 << 2) /* 5-wire carkit or + type 1 charger */ +#define ID_RES_102K (1 << 1) /* phone */ +#define ID_RES_GND (1 << 0) /* mini-A */ + +/* In module TWL4030_MODULE_INTBR */ +#define PMBR1 0x0D +#define GPIO_USB_4PIN_ULPI_2430C (3 << 0) + +/* In module TWL4030_MODULE_INT */ +#define REG_PWR_ISR1 0x00 +#define REG_PWR_IMR1 0x01 +#define USB_PRES (1 << 2) +#define REG_PWR_EDR1 0x05 +#define USB_PRES_FALLING (1 << 4) +#define USB_PRES_RISING (1 << 5) +#define REG_PWR_SIH_CTRL 0x07 +#define COR (1 << 2) + +/* internal define on top of container_of */ +#define xceiv_to_twl(x) container_of((x), struct twl4030_usb, otg); + +/* bits in OTG_CTRL */ + +#define OTG_XCEIV_OUTPUTS \ + (OTG_ASESSVLD|OTG_BSESSEND|OTG_BSESSVLD|OTG_VBUSVLD|OTG_ID) +#define OTG_XCEIV_INPUTS \ + (OTG_PULLDOWN|OTG_PULLUP|OTG_DRV_VBUS|OTG_PD_VBUS|OTG_PU_VBUS|OTG_PU_ID) +#define OTG_CTRL_BITS \ + (OTG_A_BUSREQ|OTG_A_SETB_HNPEN|OTG_B_BUSREQ|OTG_B_HNPEN|OTG_BUSDROP) + /* and OTG_PULLUP is sometimes written */ + +#define OTG_CTRL_MASK (OTG_DRIVER_SEL| \ + OTG_XCEIV_OUTPUTS|OTG_XCEIV_INPUTS| \ + OTG_CTRL_BITS) + + +/*-------------------------------------------------------------------------*/ + +struct twl4030_usb { + struct otg_transceiver otg; + int irq; + u8 usb_mode; /* pin configuration */ +#define T2_USB_MODE_ULPI 1 +/* #define T2_USB_MODE_CEA2011_3PIN 2 */ + u8 asleep; +}; + +static struct twl4030_usb *the_transceiver; + +/*-------------------------------------------------------------------------*/ + +static int twl4030_i2c_write_u8_verify(u8 module, u8 data, u8 address) +{ + u8 check; + + if ((twl4030_i2c_write_u8(module, data, address) >= 0) && + (twl4030_i2c_read_u8(module, &check, address) >= 0) && + (check == data)) + return 0; + /* Failed once: Try again */ + if ((twl4030_i2c_write_u8(module, data, address) >= 0) && + (twl4030_i2c_read_u8(module, &check, address) >= 0) && + (check == data)) + return 0; + /* Failed again: Return error */ + return -EBUSY; +} + +#define twl4030_usb_write_verify(address, data) \ + twl4030_i2c_write_u8_verify(TWL4030_MODULE_USB, (data), (address)) + +static inline int twl4030_usb_write(u8 address, u8 data) +{ + int ret = 0; + ret = twl4030_i2c_write_u8(TWL4030_MODULE_USB, data, address); + if (ret >= 0) { +#if 0 /* debug */ + u8 data1; + if (twl4030_i2c_read_u8(TWL4030_MODULE_USB, &data1, + address) < 0) + printk(KERN_ERR "re-read failed\n"); + else + printk(KERN_INFO + "Write %s wrote %x read %x from reg %x\n", + (data1 == data) ? "succeed" : "mismatch", + data, data1, address); +#endif + } else { + printk(KERN_WARNING + "TWL4030:USB:Write[0x%x] Error %d\n", address, ret); + } + return ret; +} + +static inline int twl4030_usb_read(u8 address) +{ + u8 data; + int ret = 0; + ret = twl4030_i2c_read_u8(TWL4030_MODULE_USB, &data, address); + if (ret >= 0) { + ret = data; + } else { + printk(KERN_WARNING + "TWL4030:USB:Read[0x%x] Error %d\n", address, ret); + } + return ret; +} + +/*-------------------------------------------------------------------------*/ + +static inline int +twl4030_usb_set_bits(struct twl4030_usb *twl, u8 reg, u8 bits) +{ + return twl4030_usb_write(reg + 1, bits); +} + +static inline int +twl4030_usb_clear_bits(struct twl4030_usb *twl, u8 reg, u8 bits) +{ + return twl4030_usb_write(reg + 2, bits); + +} + +/*-------------------------------------------------------------------------*/ + +static void twl4030_usb_set_mode(struct twl4030_usb *twl, int mode) +{ + twl->usb_mode = mode; + + switch (mode) { + case T2_USB_MODE_ULPI: + twl4030_usb_clear_bits(twl, IFC_CTRL, IFC_CTRL_CARKITMODE); + twl4030_usb_set_bits(twl, POWER_CTRL, POWER_CTRL_OTG_ENAB); + twl4030_usb_clear_bits(twl, FUNC_CTRL, + FUNC_CTRL_XCVRSELECT_MASK | + FUNC_CTRL_OPMODE_MASK); + break; +/* + case T2_USB_MODE_CEA2011_3PIN: + twl4030_cea2011_3_pin_FS_setup(twl); + break; +*/ + default: + /* FIXME: power on defaults */ + break; + }; +} + +#ifdef CONFIG_TWL4030_USB_HS_ULPI +static void hs_usb_init(struct twl4030_usb *twl) +{ + twl->usb_mode = T2_USB_MODE_ULPI; + return; +} + +#endif + +static void twl4030_i2c_access(int on) +{ + unsigned long timeout; + int val = twl4030_usb_read(PHY_CLK_CTRL); + + if (val >= 0) { + if (on) { + /* enable DPLL to access PHY registers over I2C */ + val |= REQ_PHY_DPLL_CLK; + if (twl4030_usb_write_verify(PHY_CLK_CTRL, + (u8)val) < 0) { + printk(KERN_ERR "twl4030_usb: i2c write failed," + " line %d\n", __LINE__); + return; + } + + timeout = jiffies + HZ; + while (!(twl4030_usb_read(PHY_CLK_CTRL_STS) & + PHY_DPLL_CLK) + && time_before(jiffies, timeout)) + udelay(10); + if (!(twl4030_usb_read(PHY_CLK_CTRL_STS) & + PHY_DPLL_CLK)) + printk(KERN_ERR "Timeout setting T2 HSUSB " + "PHY DPLL clock\n"); + } else { + /* let ULPI control the DPLL clock */ + val &= ~REQ_PHY_DPLL_CLK; + if (twl4030_usb_write_verify(PHY_CLK_CTRL, + (u8)val) < 0) { + printk(KERN_ERR "twl4030_usb: i2c write failed," + " line %d\n", __LINE__); + } + } + } + return; +} + +static void usb_irq_enable(int rising, int falling) +{ + u8 val; + + /* edge setup */ + if (twl4030_i2c_read_u8(TWL4030_MODULE_INT, &val, REG_PWR_EDR1) < 0) { + printk(KERN_ERR "twl4030_usb: i2c read failed," + " line %d\n", __LINE__); + return; + } + val &= ~(USB_PRES_RISING | USB_PRES_FALLING); + if (rising) + val = val | USB_PRES_RISING; + if (falling) + val = val | USB_PRES_FALLING; + if (twl4030_i2c_write_u8_verify(TWL4030_MODULE_INT, val, + REG_PWR_EDR1) < 0) { + printk(KERN_ERR "twl4030_usb: i2c write failed," + " line %d\n", __LINE__); + return; + } + + /* un-mask interrupt */ + if (twl4030_i2c_read_u8(TWL4030_MODULE_INT, &val, REG_PWR_IMR1) < 0) { + printk(KERN_ERR "twl4030_usb: i2c read failed," + " line %d\n", __LINE__); + return; + } + val &= ~USB_PRES; + if (twl4030_i2c_write_u8_verify(TWL4030_MODULE_INT, val, + REG_PWR_IMR1) < 0) + printk(KERN_ERR "twl4030_usb: i2c write failed," + " line %d\n", __LINE__); + + return; +} + +static void usb_irq_disable(void) +{ + u8 val; + + /* undo edge setup */ + if (twl4030_i2c_read_u8(TWL4030_MODULE_INT, &val, REG_PWR_EDR1) < 0) { + printk(KERN_ERR "twl4030_usb: i2c read failed," + " line %d\n", __LINE__); + return; + } + val &= ~(USB_PRES_RISING | USB_PRES_FALLING); + if (twl4030_i2c_write_u8_verify(TWL4030_MODULE_INT, val, + REG_PWR_EDR1) < 0) { + printk(KERN_ERR "twl4030_usb: i2c write failed," + " line %d\n", __LINE__); + return; + } + + /* mask interrupt */ + if (twl4030_i2c_read_u8(TWL4030_MODULE_INT, &val, REG_PWR_IMR1) < 0) { + printk(KERN_ERR "twl4030_usb: i2c read failed," + " line %d\n", __LINE__); + return; + } + val |= USB_PRES; + if (twl4030_i2c_write_u8_verify(TWL4030_MODULE_INT, val, + REG_PWR_IMR1) < 0) + printk(KERN_ERR "twl4030_usb: i2c write failed," + " line %d\n", __LINE__); + + return; +} + +static void twl4030_phy_power(struct twl4030_usb *twl, int on) +{ + u8 pwr; + + pwr = twl4030_usb_read(PHY_PWR_CTRL); + if (on) { + pwr &= ~PHY_PWR_PHYPWD; + if (twl4030_usb_write_verify(PHY_PWR_CTRL, pwr) < 0) { + printk(KERN_ERR "twl4030_usb: i2c write failed," + " line %d\n", __LINE__); + return; + } + twl4030_usb_write(PHY_CLK_CTRL, + twl4030_usb_read(PHY_CLK_CTRL) | + (PHY_CLK_CTRL_CLOCKGATING_EN | + PHY_CLK_CTRL_CLK32K_EN)); + } else { + pwr |= PHY_PWR_PHYPWD; + if (twl4030_usb_write_verify(PHY_PWR_CTRL, pwr) < 0) { + printk(KERN_ERR "twl4030_usb: i2c write failed," + " line %d\n", __LINE__); + } + } + return; +} + +static void twl4030_phy_suspend(int controller_off) +{ + struct twl4030_usb *twl = the_transceiver; + + if (controller_off) + usb_irq_disable(); + + if (twl->asleep) + return; + + if (!controller_off) + /* enable rising edge interrupt to detect cable attach */ + usb_irq_enable(1, 0); + + twl4030_phy_power(twl, 0); + twl->asleep = 1; + return; +} + +static void twl4030_phy_resume(void) +{ + struct twl4030_usb *twl = the_transceiver; + + if (!twl->asleep) + return; + + /* enable falling edge interrupt to detect cable detach */ + usb_irq_enable(0, 1); + + twl4030_phy_power(twl, 1); + twl4030_i2c_access(1); + twl4030_usb_set_mode(twl, twl->usb_mode); + if (twl->usb_mode == T2_USB_MODE_ULPI) + twl4030_i2c_access(0); + twl->asleep = 0; + return; +} + +static void twl4030_usb_ldo_init(struct twl4030_usb *twl) +{ + /* Enable writing to power configuration registers */ + twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, 0xC0, PROTECT_KEY); + twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, 0x0C, PROTECT_KEY); + + /* put VUSB3V1 LDO in active state */ + twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0, VUSB_DEDICATED2); + + /* input to VUSB3V1 LDO is from VBAT, not VBUS */ + twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0x14, VUSB_DEDICATED1); + + /* turn on 3.1V regulator */ + twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0x20, VUSB3V1_DEV_GRP); + twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0, VUSB3V1_TYPE); + + /* turn on 1.5V regulator */ + twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0x20, VUSB1V5_DEV_GRP); + twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0, VUSB1V5_TYPE); + + /* turn on 1.8V regulator */ + twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0x20, VUSB1V8_DEV_GRP); + twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0, VUSB1V8_TYPE); + + /* disable access to power configuration registers */ + twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, 0, PROTECT_KEY); +} + +static irqreturn_t twl4030_usb_irq(int irq, void *_twl) +{ + int ret = IRQ_NONE; + u8 val; + + /* action based on cable attach or detach */ + if (twl4030_i2c_read_u8(TWL4030_MODULE_INT, &val, REG_PWR_EDR1) < 0) { + printk(KERN_ERR "twl4030_usb: i2c read failed," + " line %d\n", __LINE__); + goto done; + } + + if (val & USB_PRES_RISING) { + twl4030_phy_resume(); + twl4030charger_usb_en(1); + } else { + twl4030charger_usb_en(0); + twl4030_phy_suspend(0); + } + + ret = IRQ_HANDLED; + +done: + return ret; +} + +static int twl4030_set_suspend(struct otg_transceiver *x, int suspend) +{ + if (suspend) + twl4030_phy_suspend(1); + else + twl4030_phy_resume(); + + return 0; +} + +static int twl4030_set_peripheral(struct otg_transceiver *xceiv, + struct usb_gadget *gadget) +{ + u32 l; + struct twl4030_usb *twl = xceiv_to_twl(xceiv); + + if (!xceiv) + return -ENODEV; + + if (!gadget) { + omap_writew(0, OTG_IRQ_EN); + twl4030_phy_suspend(1); + twl->otg.gadget = NULL; + + return -ENODEV; + } + + twl->otg.gadget = gadget; + twl4030_phy_resume(); + + l = omap_readl(OTG_CTRL) & OTG_CTRL_MASK; + l &= ~(OTG_XCEIV_OUTPUTS|OTG_CTRL_BITS); + l |= OTG_ID; + omap_writel(l, OTG_CTRL); + + twl->otg.state = OTG_STATE_B_IDLE; + + twl4030_usb_set_bits(twl, USB_INT_EN_RISE, + USB_INT_SESSVALID | USB_INT_VBUSVALID); + twl4030_usb_set_bits(twl, USB_INT_EN_FALL, + USB_INT_SESSVALID | USB_INT_VBUSVALID); + + return 0; +} + +static int twl4030_set_host(struct otg_transceiver *xceiv, struct usb_bus *host) +{ + struct twl4030_usb *twl = xceiv_to_twl(xceiv); + + if (!xceiv) + return -ENODEV; + + if (!host) { + omap_writew(0, OTG_IRQ_EN); + twl4030_phy_suspend(1); + twl->otg.host = NULL; + + return -ENODEV; + } + + twl->otg.host = host; + twl4030_phy_resume(); + + twl4030_usb_set_bits(twl, TWL4030_OTG_CTRL, + TWL4030_OTG_CTRL_DMPULLDOWN + | TWL4030_OTG_CTRL_DPPULLDOWN); + twl4030_usb_set_bits(twl, USB_INT_EN_RISE, USB_INT_IDGND); + twl4030_usb_set_bits(twl, USB_INT_EN_FALL, USB_INT_IDGND); + twl4030_usb_set_bits(twl, FUNC_CTRL, FUNC_CTRL_SUSPENDM); + twl4030_usb_set_bits(twl, TWL4030_OTG_CTRL, TWL4030_OTG_CTRL_DRVVBUS); + + return 0; +} + +static int __init twl4030_usb_init(void) +{ + struct twl4030_usb *twl; + int status; + + if (the_transceiver) + return 0; + + twl = kzalloc(sizeof *twl, GFP_KERNEL); + if (!twl) + return 0; + + the_transceiver = twl; + + twl->irq = TWL4030_PWRIRQ_USB_PRES; + twl->otg.set_host = twl4030_set_host; + twl->otg.set_peripheral = twl4030_set_peripheral; + twl->otg.set_suspend = twl4030_set_suspend; + + usb_irq_disable(); + status = request_irq(twl->irq, twl4030_usb_irq, 0, "twl4030_usb", twl); + if (status < 0) { + printk(KERN_DEBUG "can't get IRQ %d, err %d\n", + twl->irq, status); + kfree(twl); + return -ENODEV; + } + +#if defined(CONFIG_TWL4030_USB_HS_ULPI) + hs_usb_init(twl); +#endif + twl4030_usb_ldo_init(twl); + twl4030_phy_power(twl, 1); + twl4030_i2c_access(1); + twl4030_usb_set_mode(twl, twl->usb_mode); + if (twl->usb_mode == T2_USB_MODE_ULPI) + twl4030_i2c_access(0); + + twl->asleep = 0; + + if (twl->usb_mode == T2_USB_MODE_ULPI) + twl4030_phy_suspend(1); + + otg_set_transceiver(&twl->otg); + + printk(KERN_INFO "Initialized TWL4030 USB module\n"); + + return 0; +} + + +static void __exit twl4030_usb_exit(void) +{ + struct twl4030_usb *twl = the_transceiver; + int val; + + usb_irq_disable(); + free_irq(twl->irq, twl); + + /* set transceiver mode to power on defaults */ + twl4030_usb_set_mode(twl, -1); + + /* autogate 60MHz ULPI clock, + * clear dpll clock request for i2c access, + * disable 32KHz + */ + val = twl4030_usb_read(PHY_CLK_CTRL); + if (val >= 0) { + val |= PHY_CLK_CTRL_CLOCKGATING_EN; + val &= ~(PHY_CLK_CTRL_CLK32K_EN | REQ_PHY_DPLL_CLK); + twl4030_usb_write(PHY_CLK_CTRL, (u8)val); + } + + /* disable complete OTG block */ + twl4030_usb_clear_bits(twl, POWER_CTRL, POWER_CTRL_OTG_ENAB); + + twl4030_phy_power(twl, 0); + + kfree(twl); +} + +subsys_initcall(twl4030_usb_init); +module_exit(twl4030_usb_exit); + +MODULE_ALIAS("i2c:twl4030-usb"); +MODULE_AUTHOR("Texas Instruments, Inc."); +MODULE_DESCRIPTION("TWL4030 USB transceiver driver"); +MODULE_LICENSE("GPL"); diff --cc drivers/input/keyboard/omap-keypad.c index 90c092f2030,dcea87a0bc5..38b1a79b4a5 --- a/drivers/input/keyboard/omap-keypad.c +++ b/drivers/input/keyboard/omap-keypad.c @@@ -33,16 -33,14 +33,16 @@@ #include #include #include +#include #include +#include - #include - #include + #include + #include -#include #include - #include + #include #include +#include - #include + #include #undef NEW_BOARD_LEARNING_MODE diff --cc drivers/input/keyboard/omap-twl4030keypad.c index 68c5b8e5112,00000000000..5dbb80f51c4 mode 100644,000000..100644 --- a/drivers/input/keyboard/omap-twl4030keypad.c +++ b/drivers/input/keyboard/omap-twl4030keypad.c @@@ -1,375 -1,0 +1,375 @@@ +/* + * drivers/input/keyboard/omap-twl4030keypad.c + * + * Copyright (C) 2007 Texas Instruments, Inc. + * Copyright (C) 2008 Nokia Corporation + * + * Code re-written for 2430SDP by: + * Syed Mohammed Khasim + * + * Initial Code: + * Manjunatha G K + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include - #include ++#include +#include "twl4030-keypad.h" + +#define PTV_PRESCALER 4 + +#define MAX_ROWS 8 /* TWL4030 hardlimit */ +#define ROWCOL_MASK 0xFF000000 +#define KEYNUM_MASK 0x00FFFFFF + +/* Global variables */ +static int *keymap; +static u16 kp_state[MAX_ROWS]; +static int n_rows, n_cols; + +static struct device *dbg_dev; +static struct input_dev *omap_twl4030kp; + +static int twl4030_kpread(u32 module, u8 *data, u32 reg, u8 num_bytes) +{ + int ret; + + ret = twl4030_i2c_read(module, data, reg, num_bytes); + if (ret < 0) { + dev_warn(dbg_dev, "Couldn't read TWL4030: %X - ret %d[%x]\n", + reg, ret, ret); + return ret; + } + return ret; +} + +static int twl4030_kpwrite_u8(u32 module, u8 data, u32 reg) +{ + int ret; + + ret = twl4030_i2c_write_u8(module, data, reg); + if (ret < 0) { + dev_warn(dbg_dev, "Could not write TWL4030: %X - ret %d[%x]\n", + reg, ret, ret); + return ret; + } + return ret; +} + +static int omap_kp_find_key(int col, int row) +{ + int i, rc; + + rc = KEY(col, row, 0); + for (i = 0; keymap[i] != 0; i++) + if ((keymap[i] & ROWCOL_MASK) == rc) + return keymap[i] & KEYNUM_MASK; + + return -EINVAL; +} + +static inline u16 omap_kp_col_xlate(u8 col) +{ + /* If all bits in a row are active for all coloumns then + * we have that row line connected to gnd. Mark this + * key on as if it was on matrix position n_cols (ie + * one higher than the size of the matrix). + */ + if (col == 0xFF) + return (1 << n_cols); + else + return col & ((1 << n_cols) - 1); +} + +static int omap_kp_read_kp_matrix_state(u16 *state) +{ + u8 new_state[MAX_ROWS]; + int row; + int ret = twl4030_kpread(TWL4030_MODULE_KEYPAD, + new_state, KEYP_FULL_CODE_7_0, n_rows); + if (ret >= 0) { + for (row = 0; row < n_rows; row++) + state[row] = omap_kp_col_xlate(new_state[row]); + } + return ret; +} + +static int omap_kp_is_in_ghost_state(u16 *key_state) +{ + int i; + u16 check = 0; + + for (i = 0; i < n_rows; i++) { + u16 col = key_state[i]; + + if ((col & check) && hweight16(col) > 1) + return 1; + check |= col; + } + + return 0; +} + +static void twl4030_kp_scan(int release_all) +{ + u16 new_state[MAX_ROWS]; + int col, row; + + if (release_all) + memset(new_state, 0, sizeof(new_state)); + else { + /* check for any changes */ + int ret = omap_kp_read_kp_matrix_state(new_state); + if (ret < 0) /* panic ... */ + return; + + if (omap_kp_is_in_ghost_state(new_state)) + return; + } + + /* check for changes and print those */ + for (row = 0; row < n_rows; row++) { + int changed = new_state[row] ^ kp_state[row]; + + if (!changed) + continue; + + for (col = 0; col < n_cols; col++) { + int key; + + if (!(changed & (1 << col))) + continue; + + dev_dbg(dbg_dev, "key [%d:%d] %s\n", row, col, + (new_state[row] & (1 << col)) ? + "press" : "release"); + + key = omap_kp_find_key(col, row); + if (key < 0) + dev_warn(dbg_dev, "Spurious key event %d-%d\n", + col, row); + else + input_report_key(omap_twl4030kp, key, + new_state[row] & (1 << col)); + } + kp_state[row] = new_state[row]; + } +} + +/* + * Keypad interrupt handler + */ +static irqreturn_t do_kp_irq(int irq, void *dev_id) +{ + u8 reg; + int ret; + + /* Read & Clear TWL4030 pending interrupt */ + ret = twl4030_kpread(TWL4030_MODULE_KEYPAD, ®, KEYP_ISR1, 1); + + /* Release all keys if I2C has gone bad or + * the KEYP has gone to idle state */ + if ((ret >= 0) && (reg & KEYP_IMR1_KP)) + twl4030_kp_scan(0); + else + twl4030_kp_scan(1); + + return IRQ_HANDLED; +} + +/* + * Registers keypad device with input sub system + * and configures TWL4030 keypad registers + */ +static int __init omap_kp_probe(struct platform_device *pdev) +{ + u8 reg; + int i; + int ret = 0; + struct omap_kp_platform_data *pdata = pdev->dev.platform_data; + + /* Get the debug Device */ + dbg_dev = &(pdev->dev); + + if (!pdata->rows || !pdata->cols || !pdata->keymap) { + dev_err(dbg_dev, "No rows, cols or keymap from pdata\n"); + return -EINVAL; + } + + omap_twl4030kp = input_allocate_device(); + if (omap_twl4030kp == NULL) + return -ENOMEM; + + keymap = pdata->keymap; + n_rows = pdata->rows; + n_cols = pdata->cols; + + /* setup input device */ + set_bit(EV_KEY, omap_twl4030kp->evbit); + + /* Enable auto repeat feature of Linux input subsystem */ + if (pdata->rep) + set_bit(EV_REP, omap_twl4030kp->evbit); + + for (i = 0; keymap[i] != 0; i++) + set_bit(keymap[i] & KEYNUM_MASK, omap_twl4030kp->keybit); + + omap_twl4030kp->name = "omap_twl4030keypad"; + omap_twl4030kp->phys = "omap_twl4030keypad/input0"; + omap_twl4030kp->dev.parent = &pdev->dev; + + omap_twl4030kp->id.bustype = BUS_HOST; + omap_twl4030kp->id.vendor = 0x0001; + omap_twl4030kp->id.product = 0x0001; + omap_twl4030kp->id.version = 0x0003; + + omap_twl4030kp->keycode = keymap; + omap_twl4030kp->keycodesize = sizeof(unsigned int); + omap_twl4030kp->keycodemax = pdata->keymapsize; + + ret = input_register_device(omap_twl4030kp); + if (ret < 0) { + dev_err(dbg_dev, "Unable to register twl4030 keypad device\n"); + goto err2; + } + + /* Disable auto-repeat */ + reg = KEYP_CTRL_NOAUTORPT; + ret = twl4030_kpwrite_u8(TWL4030_MODULE_KEYPAD, reg, KEYP_CTRL); + if (ret < 0) + goto err3; + + /* Enable TO rising and KP rising and falling edge detection */ + reg = KEYP_EDR_KP_BOTH | KEYP_EDR_TO_RISING; + ret = twl4030_kpwrite_u8(TWL4030_MODULE_KEYPAD, reg, KEYP_EDR); + if (ret < 0) + goto err3; + + /* Set PTV prescaler Field */ + reg = (PTV_PRESCALER << KEYP_LK_PTV_PTV_SHIFT); + ret = twl4030_kpwrite_u8(TWL4030_MODULE_KEYPAD, reg, KEYP_LK_PTV); + if (ret < 0) + goto err3; + + /* Set key debounce time to 20 ms */ + i = KEYP_PERIOD_US(20000, PTV_PRESCALER); + ret = twl4030_kpwrite_u8(TWL4030_MODULE_KEYPAD, i, KEYP_DEB); + if (ret < 0) + goto err3; + + /* Set timeout period to 100 ms */ + i = KEYP_PERIOD_US(200000, PTV_PRESCALER); + ret = twl4030_kpwrite_u8(TWL4030_MODULE_KEYPAD, + (i & 0xFF), KEYP_TIMEOUT_L); + if (ret < 0) + goto err3; + + ret = twl4030_kpwrite_u8(TWL4030_MODULE_KEYPAD, + (i >> 8), KEYP_TIMEOUT_H); + if (ret < 0) + goto err3; + + /* Enable Clear-on-Read */ + reg = KEYP_SIH_CTRL_COR | KEYP_SIH_CTRL_PEND_DIS; + ret = twl4030_kpwrite_u8(TWL4030_MODULE_KEYPAD, + reg, KEYP_SIH_CTRL); + if (ret < 0) + goto err3; + + /* + * This ISR will always execute in kernel thread context because of + * the need to access the TWL4030 over the I2C bus. + */ + ret = request_irq(TWL4030_MODIRQ_KEYPAD, do_kp_irq, + IRQF_DISABLED, "TWL4030 Keypad", omap_twl4030kp); + if (ret < 0) { + dev_info(dbg_dev, "request_irq failed for irq no=%d\n", + TWL4030_MODIRQ_KEYPAD); + goto err3; + } else { + /* Enable KP and TO interrupts now. */ + reg = ~(KEYP_IMR1_KP | KEYP_IMR1_TO); + ret = twl4030_kpwrite_u8(TWL4030_MODULE_KEYPAD, + reg, KEYP_IMR1); + if (ret < 0) + goto err5; + } + + ret = omap_kp_read_kp_matrix_state(kp_state); + if (ret < 0) + goto err4; + + return ret; +err5: + /* mask all events - we don't care about the result */ + (void) twl4030_kpwrite_u8(TWL4030_MODULE_KEYPAD, 0xff, KEYP_IMR1); +err4: + free_irq(TWL4030_MODIRQ_KEYPAD, NULL); +err3: + input_unregister_device(omap_twl4030kp); +err2: + input_free_device(omap_twl4030kp); + return -ENODEV; +} + +static int omap_kp_remove(struct platform_device *pdev) +{ + free_irq(TWL4030_MODIRQ_KEYPAD, NULL); + + input_unregister_device(omap_twl4030kp); + return 0; +} + + +static struct platform_driver omap_kp_driver = { + .probe = omap_kp_probe, + .remove = omap_kp_remove, + .driver = { + .name = "omap_twl4030keypad", + .owner = THIS_MODULE, + }, +}; + +/* + * OMAP TWL4030 Keypad init + */ +static int __devinit omap_kp_init(void) +{ + return platform_driver_register(&omap_kp_driver); +} + +static void __exit omap_kp_exit(void) +{ + platform_driver_unregister(&omap_kp_driver); +} + +module_init(omap_kp_init); +module_exit(omap_kp_exit); +MODULE_ALIAS("platform:omap_twl4030keypad"); +MODULE_AUTHOR("Texas Instruments"); +MODULE_DESCRIPTION("OMAP TWL4030 Keypad Driver"); +MODULE_LICENSE("GPL"); diff --cc drivers/input/touchscreen/tsc2005.c index 1e4ed3c25db,00000000000..7fb107ec69e mode 100644,000000..100644 --- a/drivers/input/touchscreen/tsc2005.c +++ b/drivers/input/touchscreen/tsc2005.c @@@ -1,736 -1,0 +1,736 @@@ +/* + * TSC2005 touchscreen driver + * + * Copyright (C) 2006-2008 Nokia Corporation + * + * Author: Lauri Leukkunen + * based on TSC2301 driver by Klaus K. Pedersen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_ARCH_OMAP - #include ++#include +#endif + +#include + +/** + * The touchscreen interface operates as follows: + * + * Initialize: + * Request access to GPIO103 (DAV) + * tsc2005_dav_irq_handler will trigger when DAV line goes down + * + * 1) Pen is pressed against touchscreeen + * 2) TSC2005 performs AD conversion + * 3) After the conversion is done TSC2005 drives DAV line down + * 4) GPIO IRQ is received and tsc2005_dav_irq_handler is called + * 5) tsc2005_ts_irq_handler queues up an spi transfer to fetch + * the x, y, z1, z2 values + * 6) tsc2005_ts_rx() reports coordinates to input layer and + * sets up tsc2005_ts_timer() to be called after TSC2005_TS_SCAN_TIME + * 7) When the penup_timer expires, there have not been DAV interrupts + * during the last 20ms which means the pen has been lifted. + */ + +#define TSC2005_VDD_LOWER_27 + +#ifdef TSC2005_VDD_LOWER_27 +#define TSC2005_HZ (10000000) +#else +#define TSC2005_HZ (25000000) +#endif + +#define TSC2005_CMD (0x80) +#define TSC2005_REG (0x00) + +#define TSC2005_CMD_STOP (1) +#define TSC2005_CMD_10BIT (0 << 2) +#define TSC2005_CMD_12BIT (1 << 2) + +#define TSC2005_CMD_SCAN_XYZZ (0 << 3) +#define TSC2005_CMD_SCAN_XY (1 << 3) +#define TSC2005_CMD_SCAN_X (2 << 3) +#define TSC2005_CMD_SCAN_Y (3 << 3) +#define TSC2005_CMD_SCAN_ZZ (4 << 3) +#define TSC2005_CMD_AUX_SINGLE (5 << 3) +#define TSC2005_CMD_TEMP1 (6 << 3) +#define TSC2005_CMD_TEMP2 (7 << 3) +#define TSC2005_CMD_AUX_CONT (8 << 3) +#define TSC2005_CMD_TEST_X_CONN (9 << 3) +#define TSC2005_CMD_TEST_Y_CONN (10 << 3) +/* command 11 reserved */ +#define TSC2005_CMD_TEST_SHORT (12 << 3) +#define TSC2005_CMD_DRIVE_XX (13 << 3) +#define TSC2005_CMD_DRIVE_YY (14 << 3) +#define TSC2005_CMD_DRIVE_YX (15 << 3) + +#define TSC2005_REG_X (0 << 3) +#define TSC2005_REG_Y (1 << 3) +#define TSC2005_REG_Z1 (2 << 3) +#define TSC2005_REG_Z2 (3 << 3) +#define TSC2005_REG_AUX (4 << 3) +#define TSC2005_REG_TEMP1 (5 << 3) +#define TSC2005_REG_TEMP2 (6 << 3) +#define TSC2005_REG_STATUS (7 << 3) +#define TSC2005_REG_AUX_HIGH (8 << 3) +#define TSC2005_REG_AUX_LOW (9 << 3) +#define TSC2005_REG_TEMP_HIGH (10 << 3) +#define TSC2005_REG_TEMP_LOW (11 << 3) +#define TSC2005_REG_CFR0 (12 << 3) +#define TSC2005_REG_CFR1 (13 << 3) +#define TSC2005_REG_CFR2 (14 << 3) +#define TSC2005_REG_FUNCTION (15 << 3) + +#define TSC2005_REG_PND0 (1 << 1) +#define TSC2005_REG_READ (0x01) +#define TSC2005_REG_WRITE (0x00) + + +#define TSC2005_CFR0_LONGSAMPLING (1) +#define TSC2005_CFR0_DETECTINWAIT (1 << 1) +#define TSC2005_CFR0_SENSETIME_32US (0) +#define TSC2005_CFR0_SENSETIME_96US (1 << 2) +#define TSC2005_CFR0_SENSETIME_544US (1 << 3) +#define TSC2005_CFR0_SENSETIME_2080US (1 << 4) +#define TSC2005_CFR0_SENSETIME_2656US (0x001C) +#define TSC2005_CFR0_PRECHARGE_20US (0x0000) +#define TSC2005_CFR0_PRECHARGE_84US (0x0020) +#define TSC2005_CFR0_PRECHARGE_276US (0x0040) +#define TSC2005_CFR0_PRECHARGE_1044US (0x0080) +#define TSC2005_CFR0_PRECHARGE_1364US (0x00E0) +#define TSC2005_CFR0_STABTIME_0US (0x0000) +#define TSC2005_CFR0_STABTIME_100US (0x0100) +#define TSC2005_CFR0_STABTIME_500US (0x0200) +#define TSC2005_CFR0_STABTIME_1MS (0x0300) +#define TSC2005_CFR0_STABTIME_5MS (0x0400) +#define TSC2005_CFR0_STABTIME_100MS (0x0700) +#define TSC2005_CFR0_CLOCK_4MHZ (0x0000) +#define TSC2005_CFR0_CLOCK_2MHZ (0x0800) +#define TSC2005_CFR0_CLOCK_1MHZ (0x1000) +#define TSC2005_CFR0_RESOLUTION12 (0x2000) +#define TSC2005_CFR0_STATUS (0x4000) +#define TSC2005_CFR0_PENMODE (0x8000) + +#define TSC2005_CFR0_INITVALUE (TSC2005_CFR0_STABTIME_1MS | \ + TSC2005_CFR0_CLOCK_1MHZ | \ + TSC2005_CFR0_RESOLUTION12 | \ + TSC2005_CFR0_PRECHARGE_276US | \ + TSC2005_CFR0_PENMODE) + +#define TSC2005_CFR1_BATCHDELAY_0MS (0x0000) +#define TSC2005_CFR1_BATCHDELAY_1MS (0x0001) +#define TSC2005_CFR1_BATCHDELAY_2MS (0x0002) +#define TSC2005_CFR1_BATCHDELAY_4MS (0x0003) +#define TSC2005_CFR1_BATCHDELAY_10MS (0x0004) +#define TSC2005_CFR1_BATCHDELAY_20MS (0x0005) +#define TSC2005_CFR1_BATCHDELAY_40MS (0x0006) +#define TSC2005_CFR1_BATCHDELAY_100MS (0x0007) + +#define TSC2005_CFR1_INITVALUE (TSC2005_CFR1_BATCHDELAY_2MS) + +#define TSC2005_CFR2_MAVE_TEMP (0x0001) +#define TSC2005_CFR2_MAVE_AUX (0x0002) +#define TSC2005_CFR2_MAVE_Z (0x0004) +#define TSC2005_CFR2_MAVE_Y (0x0008) +#define TSC2005_CFR2_MAVE_X (0x0010) +#define TSC2005_CFR2_AVG_1 (0x0000) +#define TSC2005_CFR2_AVG_3 (0x0400) +#define TSC2005_CFR2_AVG_7 (0x0800) +#define TSC2005_CFR2_MEDIUM_1 (0x0000) +#define TSC2005_CFR2_MEDIUM_3 (0x1000) +#define TSC2005_CFR2_MEDIUM_7 (0x2000) +#define TSC2005_CFR2_MEDIUM_15 (0x3000) + +#define TSC2005_CFR2_IRQ_DAV (0x4000) +#define TSC2005_CFR2_IRQ_PEN (0x8000) +#define TSC2005_CFR2_IRQ_PENDAV (0x0000) + +#define TSC2005_CFR2_INITVALUE (TSC2005_CFR2_IRQ_DAV | \ + TSC2005_CFR2_MAVE_X | \ + TSC2005_CFR2_MAVE_Y | \ + TSC2005_CFR2_MAVE_Z | \ + TSC2005_CFR2_MEDIUM_15 | \ + TSC2005_CFR2_AVG_7) + +#define MAX_12BIT ((1 << 12) - 1) +#define TS_SAMPLES 4 +#define TS_RECT_SIZE 8 +#define TSC2005_TS_PENUP_TIME 20 + +static const u32 tsc2005_read_reg[] = { + (TSC2005_REG | TSC2005_REG_X | TSC2005_REG_READ) << 16, + (TSC2005_REG | TSC2005_REG_Y | TSC2005_REG_READ) << 16, + (TSC2005_REG | TSC2005_REG_Z1 | TSC2005_REG_READ) << 16, + (TSC2005_REG | TSC2005_REG_Z2 | TSC2005_REG_READ) << 16, +}; +#define NUM_READ_REGS (sizeof(tsc2005_read_reg)/sizeof(tsc2005_read_reg[0])) + +struct tsc2005 { + struct spi_device *spi; + + struct input_dev *idev; + char phys[32]; + struct timer_list penup_timer; + spinlock_t lock; + struct mutex mutex; + + struct spi_message read_msg; + struct spi_transfer read_xfer[NUM_READ_REGS]; + u32 data[NUM_READ_REGS]; + + /* previous x,y,z */ + int x; + int y; + int p; + /* average accumulators for each component */ + int sample_cnt; + int avg_x; + int avg_y; + int avg_z1; + int avg_z2; + /* configuration */ + int x_plate_ohm; + int hw_avg_max; + int stab_time; + int p_max; + int touch_pressure; + int irq; + s16 dav_gpio; + /* status */ + u8 sample_sent; + u8 pen_down; + u8 disabled; + u8 disable_depth; + u8 spi_active; +}; + +static void tsc2005_cmd(struct tsc2005 *ts, u8 cmd) +{ + u16 data = TSC2005_CMD | TSC2005_CMD_12BIT | cmd; + struct spi_message msg; + struct spi_transfer xfer = { 0 }; + + xfer.tx_buf = &data; + xfer.rx_buf = NULL; + xfer.len = 1; + xfer.bits_per_word = 8; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + spi_sync(ts->spi, &msg); +} + +static void tsc2005_write(struct tsc2005 *ts, u8 reg, u16 value) +{ + u32 tx; + struct spi_message msg; + struct spi_transfer xfer = { 0 }; + + tx = (TSC2005_REG | reg | TSC2005_REG_PND0 | + TSC2005_REG_WRITE) << 16; + tx |= value; + + xfer.tx_buf = &tx; + xfer.rx_buf = NULL; + xfer.len = 4; + xfer.bits_per_word = 24; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + spi_sync(ts->spi, &msg); +} + +static void tsc2005_ts_update_pen_state(struct tsc2005 *ts, + int x, int y, int pressure) +{ + if (pressure) { + input_report_abs(ts->idev, ABS_X, x); + input_report_abs(ts->idev, ABS_Y, y); + input_report_abs(ts->idev, ABS_PRESSURE, pressure); + if (!ts->pen_down) { + input_report_key(ts->idev, BTN_TOUCH, 1); + ts->pen_down = 1; + } + } else { + input_report_abs(ts->idev, ABS_PRESSURE, 0); + if (ts->pen_down) { + input_report_key(ts->idev, BTN_TOUCH, 0); + ts->pen_down = 0; + } + } + + input_sync(ts->idev); +} + +/* + * This function is called by the SPI framework after the coordinates + * have been read from TSC2005 + */ +static void tsc2005_ts_rx(void *arg) +{ + struct tsc2005 *ts = arg; + unsigned long flags; + int inside_rect, pressure_limit; + int x, y, z1, z2, pressure; + + spin_lock_irqsave(&ts->lock, flags); + + x = ts->data[0]; + y = ts->data[1]; + z1 = ts->data[2]; + z2 = ts->data[3]; + + /* validate pressure and position */ + if (x > MAX_12BIT || y > MAX_12BIT) + goto out; + + /* skip coords if the pressure-components are out of range */ + if (z1 < 100 || z2 > 4000) + goto out; + + /* don't run average on the "pen down" event */ + if (ts->sample_sent) { + ts->avg_x += x; + ts->avg_y += y; + ts->avg_z1 += z1; + ts->avg_z2 += z2; + + if (++ts->sample_cnt < TS_SAMPLES) + goto out; + + x = ts->avg_x / TS_SAMPLES; + y = ts->avg_y / TS_SAMPLES; + z1 = ts->avg_z1 / TS_SAMPLES; + z2 = ts->avg_z2 / TS_SAMPLES; + } + + ts->sample_cnt = 0; + ts->avg_x = 0; + ts->avg_y = 0; + ts->avg_z1 = 0; + ts->avg_z2 = 0; + + if (z1) { + pressure = x * (z2 - z1) / z1; + pressure = pressure * ts->x_plate_ohm / 4096; + } else + goto out; + + pressure_limit = ts->sample_sent? ts->p_max: ts->touch_pressure; + if (pressure > pressure_limit) + goto out; + + /* discard the event if it still is within the previous rect - unless + * if the pressure is harder, but then use previous x,y position */ + inside_rect = (ts->sample_sent && + x > (int)ts->x - TS_RECT_SIZE && + x < (int)ts->x + TS_RECT_SIZE && + y > (int)ts->y - TS_RECT_SIZE && + y < (int)ts->y + TS_RECT_SIZE); + if (inside_rect) + x = ts->x, y = ts->y; + + if (!inside_rect || pressure < ts->p) { + tsc2005_ts_update_pen_state(ts, x, y, pressure); + ts->sample_sent = 1; + ts->x = x; + ts->y = y; + ts->p = pressure; + } +out: + ts->spi_active = 0; + spin_unlock_irqrestore(&ts->lock, flags); + + /* kick pen up timer - to make sure it expires again(!) */ + if (ts->sample_sent) + mod_timer(&ts->penup_timer, + jiffies + msecs_to_jiffies(TSC2005_TS_PENUP_TIME)); +} + +static void tsc2005_ts_penup_timer_handler(unsigned long data) +{ + struct tsc2005 *ts = (struct tsc2005 *)data; + + if (ts->sample_sent) { + tsc2005_ts_update_pen_state(ts, 0, 0, 0); + ts->sample_sent = 0; + } +} + +/* + * This interrupt is called when pen is down and coordinates are + * available. That is indicated by a falling edge on DAV line. + */ +static irqreturn_t tsc2005_ts_irq_handler(int irq, void *dev_id) +{ + struct tsc2005 *ts = dev_id; + int r; + + if (ts->spi_active) + return IRQ_HANDLED; + + ts->spi_active = 1; + r = spi_async(ts->spi, &ts->read_msg); + if (r) + dev_err(&ts->spi->dev, "ts: spi_async() failed"); + + /* kick pen up timer */ + mod_timer(&ts->penup_timer, + jiffies + msecs_to_jiffies(TSC2005_TS_PENUP_TIME)); + + return IRQ_HANDLED; +} + +static void tsc2005_ts_setup_spi_xfer(struct tsc2005 *ts) +{ + struct spi_message *m = &ts->read_msg; + struct spi_transfer *x = &ts->read_xfer[0]; + int i; + + spi_message_init(m); + + for (i = 0; i < NUM_READ_REGS; i++, x++) { + x->tx_buf = &tsc2005_read_reg[i]; + x->rx_buf = &ts->data[i]; + x->len = 4; + x->bits_per_word = 24; + x->cs_change = i < (NUM_READ_REGS - 1); + spi_message_add_tail(x, m); + } + + m->complete = tsc2005_ts_rx; + m->context = ts; +} + +static ssize_t tsc2005_ts_pen_down_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct tsc2005 *tsc = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", tsc->pen_down); +} + +static DEVICE_ATTR(pen_down, S_IRUGO, tsc2005_ts_pen_down_show, NULL); + +static int tsc2005_configure(struct tsc2005 *tsc, int flags) +{ + tsc2005_write(tsc, TSC2005_REG_CFR0, TSC2005_CFR0_INITVALUE); + tsc2005_write(tsc, TSC2005_REG_CFR1, TSC2005_CFR1_INITVALUE); + tsc2005_write(tsc, TSC2005_REG_CFR2, TSC2005_CFR2_INITVALUE); + tsc2005_cmd(tsc, flags); + + return 0; +} + +static void tsc2005_start_scan(struct tsc2005 *tsc) +{ + tsc2005_configure(tsc, TSC2005_CMD_SCAN_XYZZ); +} + +static void tsc2005_stop_scan(struct tsc2005 *tsc) +{ + tsc2005_cmd(tsc, TSC2005_CMD_STOP); +} + +/* Must be called with mutex held */ +static void tsc2005_disable(struct tsc2005 *ts) +{ + if (ts->disable_depth++ != 0) + return; + + disable_irq(ts->irq); + + /* wait until penup timer expire normally */ + do { + msleep(4); + } while (ts->sample_sent); + + tsc2005_stop_scan(ts); +} + +static void tsc2005_enable(struct tsc2005 *ts) +{ + if (--ts->disable_depth != 0) + return; + + enable_irq(ts->irq); + + tsc2005_start_scan(ts); +} + +static ssize_t tsc2005_disable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tsc2005 *ts = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", ts->disabled); +} + +static ssize_t tsc2005_disable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct tsc2005 *tsc = dev_get_drvdata(dev); + unsigned long res; + int i; + + i = strict_strtoul(buf, 10, &res); + i = i ? 1 : 0; + + mutex_lock(&tsc->mutex); + if (i == tsc->disabled) + goto out; + tsc->disabled = i; + + if (i) + tsc2005_disable(tsc); + else + tsc2005_enable(tsc); +out: + mutex_unlock(&tsc->mutex); + return count; +} + +static DEVICE_ATTR(disable_ts, 0664, tsc2005_disable_show, + tsc2005_disable_store); + + +static int __devinit tsc2005_ts_init(struct tsc2005 *ts, + struct tsc2005_platform_data *pdata) +{ + struct input_dev *idev; + int dav_gpio, r; + int x_max, y_max; + int x_fudge, y_fudge, p_fudge; + + if (pdata->dav_gpio < 0) { + dev_err(&ts->spi->dev, "need DAV GPIO"); + return -EINVAL; + } + dav_gpio = pdata->dav_gpio; + ts->dav_gpio = dav_gpio; + dev_dbg(&ts->spi->dev, "TSC2005: DAV GPIO = %d\n", dav_gpio); + +#ifdef CONFIG_ARCH_OMAP + r = omap_request_gpio(dav_gpio); + if (r < 0) { + dev_err(&ts->spi->dev, "unable to get DAV GPIO"); + goto err1; + } + omap_set_gpio_direction(dav_gpio, 1); + ts->irq = OMAP_GPIO_IRQ(dav_gpio); + dev_dbg(&ts->spi->dev, "TSC2005: DAV IRQ = %d\n", ts->irq); +#endif + init_timer(&ts->penup_timer); + setup_timer(&ts->penup_timer, tsc2005_ts_penup_timer_handler, + (unsigned long)ts); + + spin_lock_init(&ts->lock); + mutex_init(&ts->mutex); + + ts->x_plate_ohm = pdata->ts_x_plate_ohm ? : 280; + ts->hw_avg_max = pdata->ts_hw_avg; + ts->stab_time = pdata->ts_stab_time; + x_max = pdata->ts_x_max ? : 4096; + x_fudge = pdata->ts_x_fudge ? : 4; + y_max = pdata->ts_y_max ? : 4096; + y_fudge = pdata->ts_y_fudge ? : 8; + ts->p_max = pdata->ts_pressure_max ? : MAX_12BIT; + ts->touch_pressure = pdata->ts_touch_pressure ? : ts->p_max; + p_fudge = pdata->ts_pressure_fudge ? : 2; + + idev = input_allocate_device(); + if (idev == NULL) { + r = -ENOMEM; + goto err2; + } + + idev->name = "TSC2005 touchscreen"; + snprintf(ts->phys, sizeof(ts->phys), "%s/input-ts", + ts->spi->dev.bus_id); + idev->phys = ts->phys; + + idev->evbit[0] = BIT(EV_ABS) | BIT(EV_KEY); + idev->absbit[0] = BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_PRESSURE); + ts->idev = idev; + + tsc2005_ts_setup_spi_xfer(ts); + + input_set_abs_params(idev, ABS_X, 0, x_max, x_fudge, 0); + input_set_abs_params(idev, ABS_Y, 0, y_max, y_fudge, 0); + input_set_abs_params(idev, ABS_PRESSURE, 0, ts->p_max, p_fudge, 0); + + tsc2005_start_scan(ts); + + r = request_irq(ts->irq, tsc2005_ts_irq_handler, + IRQF_TRIGGER_FALLING | IRQF_DISABLED | + IRQF_SAMPLE_RANDOM, "tsc2005", ts); + if (r < 0) { + dev_err(&ts->spi->dev, "unable to get DAV IRQ"); + goto err3; + } + + set_irq_wake(ts->irq, 1); + + r = input_register_device(idev); + if (r < 0) { + dev_err(&ts->spi->dev, "can't register touchscreen device\n"); + goto err4; + } + + /* We can tolerate these failing */ + if (device_create_file(&ts->spi->dev, &dev_attr_pen_down)); + if (device_create_file(&ts->spi->dev, &dev_attr_disable_ts)); + + return 0; +err4: + free_irq(ts->irq, ts); +err3: + tsc2005_stop_scan(ts); + input_free_device(idev); +err2: +#ifdef CONFIG_ARCH_OMAP + omap_free_gpio(dav_gpio); +#endif +err1: + return r; +} + +static int __devinit tsc2005_probe(struct spi_device *spi) +{ + struct tsc2005 *tsc; + struct tsc2005_platform_data *pdata = spi->dev.platform_data; + int r; + + if (!pdata) { + dev_dbg(&spi->dev, "no platform data?\n"); + return -ENODEV; + } + + tsc = kzalloc(sizeof(*tsc), GFP_KERNEL); + if (tsc == NULL) + return -ENOMEM; + + dev_set_drvdata(&spi->dev, tsc); + tsc->spi = spi; + spi->dev.power.power_state = PMSG_ON; + + spi->mode = SPI_MODE_0; + spi->bits_per_word = 8; + /* The max speed might've been defined by the board-specific + * struct */ + if (!spi->max_speed_hz) + spi->max_speed_hz = TSC2005_HZ; + + spi_setup(spi); + + r = tsc2005_ts_init(tsc, pdata); + if (r) + goto err1; + + return 0; + +err1: + kfree(tsc); + return r; +} + +static int __devexit tsc2005_remove(struct spi_device *spi) +{ + struct tsc2005 *ts = dev_get_drvdata(&spi->dev); + + mutex_lock(&ts->mutex); + tsc2005_disable(ts); + mutex_unlock(&ts->mutex); + + device_remove_file(&ts->spi->dev, &dev_attr_disable_ts); + device_remove_file(&ts->spi->dev, &dev_attr_pen_down); + + free_irq(ts->irq, ts); + input_unregister_device(ts->idev); + +#ifdef CONFIG_ARCH_OMAP + omap_free_gpio(ts->dav_gpio); +#endif + kfree(ts); + + return 0; +} + +#ifdef CONFIG_PM +static int tsc2005_suspend(struct spi_device *spi, pm_message_t mesg) +{ + struct tsc2005 *ts = dev_get_drvdata(&spi->dev); + + mutex_lock(&ts->mutex); + tsc2005_disable(ts); + mutex_unlock(&ts->mutex); + + return 0; +} + +static int tsc2005_resume(struct spi_device *spi) +{ + struct tsc2005 *ts = dev_get_drvdata(&spi->dev); + + mutex_lock(&ts->mutex); + tsc2005_enable(ts); + mutex_unlock(&ts->mutex); + + return 0; +} +#endif + +static struct spi_driver tsc2005_driver = { + .driver = { + .name = "tsc2005", + .owner = THIS_MODULE, + }, +#ifdef CONFIG_PM + .suspend = tsc2005_suspend, + .resume = tsc2005_resume, +#endif + .probe = tsc2005_probe, + .remove = __devexit_p(tsc2005_remove), +}; + +static int __init tsc2005_init(void) +{ + printk(KERN_INFO "TSC2005 driver initializing\n"); + + return spi_register_driver(&tsc2005_driver); +} +module_init(tsc2005_init); + +static void __exit tsc2005_exit(void) +{ + spi_unregister_driver(&tsc2005_driver); +} +module_exit(tsc2005_exit); + +MODULE_AUTHOR("Lauri Leukkunen "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:tsc2005"); diff --cc drivers/leds/leds-omap-pwm.c index 1aecd4f1ac6,00000000000..57eb38359f3 mode 100644,000000..100644 --- a/drivers/leds/leds-omap-pwm.c +++ b/drivers/leds/leds-omap-pwm.c @@@ -1,376 -1,0 +1,376 @@@ +/* drivers/leds/leds-omap_pwm.c + * + * Driver to blink LEDs using OMAP PWM timers + * + * Copyright (C) 2006 Nokia Corporation + * Author: Timo Teras + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include - #include - #include ++#include ++#include + +struct omap_pwm_led { + struct led_classdev cdev; + struct work_struct work; + struct omap_pwm_led_platform_data *pdata; + struct omap_dm_timer *intensity_timer; + struct omap_dm_timer *blink_timer; + int powered; + unsigned int on_period, off_period; + enum led_brightness brightness; +}; + +static inline struct omap_pwm_led *pdev_to_omap_pwm_led(struct platform_device *pdev) +{ + return platform_get_drvdata(pdev); +} + +static inline struct omap_pwm_led *cdev_to_omap_pwm_led(struct led_classdev *led_cdev) +{ + return container_of(led_cdev, struct omap_pwm_led, cdev); +} + +static inline struct omap_pwm_led *work_to_omap_pwm_led(struct work_struct *work) +{ + return container_of(work, struct omap_pwm_led, work); +} + +static void omap_pwm_led_set_blink(struct omap_pwm_led *led) +{ + if (!led->powered) + return; + + if (led->on_period != 0 && led->off_period != 0) { + unsigned long load_reg, cmp_reg; + + load_reg = 32768 * (led->on_period + led->off_period) / 1000; + cmp_reg = 32768 * led->on_period / 1000; + + omap_dm_timer_stop(led->blink_timer); + omap_dm_timer_set_load(led->blink_timer, 1, -load_reg); + omap_dm_timer_set_match(led->blink_timer, 1, -cmp_reg); + omap_dm_timer_set_pwm(led->blink_timer, 1, 1, + OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE); + omap_dm_timer_write_counter(led->blink_timer, -2); + omap_dm_timer_start(led->blink_timer); + } else { + omap_dm_timer_set_pwm(led->blink_timer, 1, 1, + OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE); + omap_dm_timer_stop(led->blink_timer); + } +} + +static void omap_pwm_led_power_on(struct omap_pwm_led *led) +{ + if (led->powered) + return; + led->powered = 1; + + /* Select clock */ + omap_dm_timer_enable(led->intensity_timer); + omap_dm_timer_set_source(led->intensity_timer, OMAP_TIMER_SRC_32_KHZ); + + /* Turn voltage on */ + if (led->pdata->set_power != NULL) + led->pdata->set_power(led->pdata, 1); + + /* Enable PWM timers */ + if (led->blink_timer != NULL) { + omap_dm_timer_enable(led->blink_timer); + omap_dm_timer_set_source(led->blink_timer, + OMAP_TIMER_SRC_32_KHZ); + omap_pwm_led_set_blink(led); + } + + omap_dm_timer_set_load(led->intensity_timer, 1, 0xffffff00); +} + +static void omap_pwm_led_power_off(struct omap_pwm_led *led) +{ + if (!led->powered) + return; + led->powered = 0; + + /* Everything off */ + omap_dm_timer_stop(led->intensity_timer); + omap_dm_timer_disable(led->intensity_timer); + + if (led->blink_timer != NULL) { + omap_dm_timer_stop(led->blink_timer); + omap_dm_timer_disable(led->blink_timer); + } + + if (led->pdata->set_power != NULL) + led->pdata->set_power(led->pdata, 0); +} + +static void omap_pwm_led_set_pwm_cycle(struct omap_pwm_led *led, int cycle) +{ + int n; + + if (cycle == 0) + n = 0xff; + else n = cycle - 1; + + if (cycle == LED_FULL) { + omap_dm_timer_set_pwm(led->intensity_timer, 1, 1, + OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE); + omap_dm_timer_stop(led->intensity_timer); + } else { + omap_dm_timer_set_pwm(led->intensity_timer, 0, 1, + OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE); + omap_dm_timer_set_match(led->intensity_timer, 1, + (0xffffff00) | cycle); + omap_dm_timer_start(led->intensity_timer); + } +} + +static void omap_pwm_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct omap_pwm_led *led = cdev_to_omap_pwm_led(led_cdev); + + led->brightness = value; + schedule_work(&led->work); +} + +static void omap_pwm_led_work(struct work_struct *work) +{ + struct omap_pwm_led *led = work_to_omap_pwm_led(work); + + if (led->brightness != LED_OFF) { + omap_pwm_led_power_on(led); + omap_pwm_led_set_pwm_cycle(led, led->brightness); + } else { + omap_pwm_led_power_off(led); + } +} + +static ssize_t omap_pwm_led_on_period_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct omap_pwm_led *led = cdev_to_omap_pwm_led(led_cdev); + + return sprintf(buf, "%u\n", led->on_period) + 1; +} + +static ssize_t omap_pwm_led_on_period_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct omap_pwm_led *led = cdev_to_omap_pwm_led(led_cdev); + int ret = -EINVAL; + unsigned long val; + char *after; + size_t count; + + val = simple_strtoul(buf, &after, 10); + count = after - buf; + if (*after && isspace(*after)) + count++; + + if (count == size) { + led->on_period = val; + omap_pwm_led_set_blink(led); + ret = count; + } + + return ret; +} + +static ssize_t omap_pwm_led_off_period_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct omap_pwm_led *led = cdev_to_omap_pwm_led(led_cdev); + + return sprintf(buf, "%u\n", led->off_period) + 1; +} + +static ssize_t omap_pwm_led_off_period_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct omap_pwm_led *led = cdev_to_omap_pwm_led(led_cdev); + int ret = -EINVAL; + unsigned long val; + char *after; + size_t count; + + val = simple_strtoul(buf, &after, 10); + count = after - buf; + if (*after && isspace(*after)) + count++; + + if (count == size) { + led->off_period = val; + omap_pwm_led_set_blink(led); + ret = count; + } + + return ret; +} + +static DEVICE_ATTR(on_period, 0644, omap_pwm_led_on_period_show, + omap_pwm_led_on_period_store); +static DEVICE_ATTR(off_period, 0644, omap_pwm_led_off_period_show, + omap_pwm_led_off_period_store); + +static int omap_pwm_led_probe(struct platform_device *pdev) +{ + struct omap_pwm_led_platform_data *pdata = pdev->dev.platform_data; + struct omap_pwm_led *led; + int ret; + + led = kzalloc(sizeof(struct omap_pwm_led), GFP_KERNEL); + if (led == NULL) { + dev_err(&pdev->dev, "No memory for device\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, led); + led->cdev.brightness_set = omap_pwm_led_set; + led->cdev.default_trigger = NULL; + led->cdev.name = pdata->name; + led->pdata = pdata; + led->brightness = LED_OFF; + INIT_WORK(&led->work, omap_pwm_led_work); + + dev_info(&pdev->dev, "OMAP PWM LED (%s) at GP timer %d/%d\n", + pdata->name, pdata->intensity_timer, pdata->blink_timer); + + /* register our new led device */ + ret = led_classdev_register(&pdev->dev, &led->cdev); + if (ret < 0) { + dev_err(&pdev->dev, "led_classdev_register failed\n"); + goto error_classdev; + } + + /* get related dm timers */ + led->intensity_timer = omap_dm_timer_request_specific(pdata->intensity_timer); + if (led->intensity_timer == NULL) { + dev_err(&pdev->dev, "failed to request intensity pwm timer\n"); + ret = -ENODEV; + goto error_intensity; + } + omap_dm_timer_disable(led->intensity_timer); + + if (pdata->blink_timer != 0) { + led->blink_timer = omap_dm_timer_request_specific(pdata->blink_timer); + if (led->blink_timer == NULL) { + dev_err(&pdev->dev, "failed to request blinking pwm timer\n"); + ret = -ENODEV; + goto error_blink1; + } + omap_dm_timer_disable(led->blink_timer); + + ret = device_create_file(led->cdev.dev, + &dev_attr_on_period); + if(ret) + goto error_blink2; + + ret = device_create_file(led->cdev.dev, + &dev_attr_off_period); + if(ret) + goto error_blink3; + + } + + return 0; + +error_blink3: + device_remove_file(led->cdev.dev, + &dev_attr_on_period); +error_blink2: + dev_err(&pdev->dev, "failed to create device file(s)\n"); +error_blink1: + omap_dm_timer_free(led->intensity_timer); +error_intensity: + led_classdev_unregister(&led->cdev); +error_classdev: + kfree(led); + return ret; +} + +static int omap_pwm_led_remove(struct platform_device *pdev) +{ + struct omap_pwm_led *led = pdev_to_omap_pwm_led(pdev); + + device_remove_file(led->cdev.dev, + &dev_attr_on_period); + device_remove_file(led->cdev.dev, + &dev_attr_off_period); + led_classdev_unregister(&led->cdev); + + omap_pwm_led_set(&led->cdev, LED_OFF); + if (led->blink_timer != NULL) + omap_dm_timer_free(led->blink_timer); + omap_dm_timer_free(led->intensity_timer); + kfree(led); + + return 0; +} + +#ifdef CONFIG_PM +static int omap_pwm_led_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct omap_pwm_led *led = pdev_to_omap_pwm_led(pdev); + + led_classdev_suspend(&led->cdev); + return 0; +} + +static int omap_pwm_led_resume(struct platform_device *pdev) +{ + struct omap_pwm_led *led = pdev_to_omap_pwm_led(pdev); + + led_classdev_resume(&led->cdev); + return 0; +} +#else +#define omap_pwm_led_suspend NULL +#define omap_pwm_led_resume NULL +#endif + +static struct platform_driver omap_pwm_led_driver = { + .probe = omap_pwm_led_probe, + .remove = omap_pwm_led_remove, + .suspend = omap_pwm_led_suspend, + .resume = omap_pwm_led_resume, + .driver = { + .name = "omap_pwm_led", + .owner = THIS_MODULE, + }, +}; + +static int __init omap_pwm_led_init(void) +{ + return platform_driver_register(&omap_pwm_led_driver); +} + +static void __exit omap_pwm_led_exit(void) +{ + platform_driver_unregister(&omap_pwm_led_driver); +} + +module_init(omap_pwm_led_init); +module_exit(omap_pwm_led_exit); + +MODULE_AUTHOR("Timo Teras"); +MODULE_DESCRIPTION("OMAP PWM LED driver"); +MODULE_LICENSE("GPL"); diff --cc drivers/leds/leds-omap.c index 040b7e41d84,00000000000..5c14c4b5275 mode 100644,000000..100644 --- a/drivers/leds/leds-omap.c +++ b/drivers/leds/leds-omap.c @@@ -1,135 -1,0 +1,135 @@@ +/* drivers/leds/leds-omap.c + * + * (C) 2006 Samsung Electronics + * Kyungmin Park + * + * OMAP - LEDs GPIO driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include +#include +#include +#include + - #include - #include - #include ++#include ++#include ++#include + +/* our context */ + +static void omap_set_led_gpio(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct omap_led_config *led_dev; + + led_dev = container_of(led_cdev, struct omap_led_config, cdev); + + if (value) + omap_set_gpio_dataout(led_dev->gpio, 1); + else + omap_set_gpio_dataout(led_dev->gpio, 0); +} + +static void omap_configure_led_gpio(int gpio) +{ + if (omap_request_gpio(gpio) < 0) { + printk(KERN_ERR "Failed to request GPIO%d for LEDs\n", gpio); + return; + } + omap_set_gpio_direction(gpio, 0); /* OUT */ +} + +static int omap_led_probe(struct platform_device *dev) +{ + struct omap_led_platform_data *pdata = dev->dev.platform_data; + struct omap_led_config *leds = pdata->leds; + int i, ret = 0; + + for (i = 0; ret >= 0 && i < pdata->nr_leds; i++) { + omap_configure_led_gpio(leds[i].gpio); + if (!leds[i].cdev.brightness_set) + leds[i].cdev.brightness_set = omap_set_led_gpio; + + ret = led_classdev_register(&dev->dev, &leds[i].cdev); + } + + if (ret < 0 && i > 1) { + for (i = i - 2; i >= 0; i--) + led_classdev_unregister(&leds[i].cdev); + } + + return ret; +} + +static int omap_led_remove(struct platform_device *dev) +{ + struct omap_led_platform_data *pdata = dev->dev.platform_data; + struct omap_led_config *leds = pdata->leds; + int i; + + for (i = 0; i < pdata->nr_leds; i++) + led_classdev_unregister(&leds[i].cdev); + + return 0; +} + +#ifdef CONFIG_PM +static int omap_led_suspend(struct platform_device *dev, pm_message_t state) +{ + struct omap_led_platform_data *pdata = dev->dev.platform_data; + struct omap_led_config *leds = pdata->leds; + int i; + + for (i = 0; i < pdata->nr_leds; i++) + led_classdev_suspend(&leds[i].cdev); + + return 0; +} + +static int omap_led_resume(struct platform_device *dev) +{ + struct omap_led_platform_data *pdata = dev->dev.platform_data; + struct omap_led_config *leds = pdata->leds; + int i; + + for (i = 0; i < pdata->nr_leds; i++) + led_classdev_resume(&leds[i].cdev); + + return 0; +} +#else +#define omap_led_suspend NULL +#define omap_led_resume NULL +#endif + +static struct platform_driver omap_led_driver = { + .probe = omap_led_probe, + .remove = omap_led_remove, + .suspend = omap_led_suspend, + .resume = omap_led_resume, + .driver = { + .name = "omap-led", + .owner = THIS_MODULE, + }, +}; + +static int __init omap_led_init(void) +{ + return platform_driver_register(&omap_led_driver); +} + +static void __exit omap_led_exit(void) +{ + platform_driver_unregister(&omap_led_driver); +} + +module_init(omap_led_init); +module_exit(omap_led_exit); + +MODULE_AUTHOR("Kyungmin Park"); +MODULE_DESCRIPTION("OMAP LED driver"); +MODULE_LICENSE("GPL"); diff --cc drivers/media/video/omap/omap16xxcam.c index 82c006b4c02,00000000000..1615fb7eceb mode 100644,000000..100644 --- a/drivers/media/video/omap/omap16xxcam.c +++ b/drivers/media/video/omap/omap16xxcam.c @@@ -1,579 -1,0 +1,579 @@@ +/* + * drivers/media/video/omap/omap16xxcam.c + * + * Copyright (C) 2004 Texas Instruments, Inc. + * + * Video-for-Linux (Version 2) camera capture driver for + * the OMAP H2 and H3 camera controller. + * + * leverage some code from CEE distribution + * Copyright (C) 2003-2004 MontaVista Software, Inc. + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + - #include - #include - #include ++#include ++#include ++#include +#include +#include +#include + +#include "omap16xxcam.h" +#include "camera_hw_if.h" +#include "camera_core.h" + +#define CONF_CAMERAIF_RESET_R 5 +#define EN_PER 0 + +/* NUM_CAMDMA_CHANNELS is the number of logical channels used for + * DMA data transfer. + */ +#define NUM_CAMDMA_CHANNELS 2 + +typedef struct { + unsigned int ctrlclock; /* 00 */ + unsigned int it_status; /* 04 */ + unsigned int mode; /* 08 */ + unsigned int status; /* 0C */ + unsigned int camdata; /* 10 */ + unsigned int gpio; /* 14 */ + unsigned int peak_counter; /* 18 */ +} camera_regs_t; + +struct camdma_state { + dma_callback_t callback; + void *arg1; + void *arg2; +}; + +struct omap16xxcam { + camera_regs_t *camera_regs; + unsigned long iobase_phys; + + /* Frequency (in Hz) of camera interface functional clock (ocp_clk) */ + unsigned long ocp_clk; + + struct clk *func_clk; + + /* DMA related stuff */ + spinlock_t dma_lock; + int free_dmach; + int next_dmach; + struct camdma_state camdma[NUM_CAMDMA_CHANNELS]; + int dma_channel_number1; + int dma_channel_number2; + + wait_queue_head_t vsync_wait; + + int new; +}; +static struct omap16xxcam hardware_data; + +static int omap16xxcam_set_xclk(int, void *); +static void omap16xx_cam_dma_link_callback(int, unsigned short, void *); + +/* Clears the camera data FIFO by setting RAZ_FIFO bit in MODE configuration + * register. + */ +static void omap16xx_cam_clear_fifo(struct omap16xxcam *data) +{ + data->camera_regs->mode |= RAZ_FIFO; + udelay(10); + data->camera_regs->mode &= ~RAZ_FIFO; +} + +static void omap16xx_cam_reset(struct omap16xxcam *data, int yes) +{ + if (machine_is_omap_h3()) + data->camera_regs->gpio = yes ? 0 : 1; + else + data->camera_regs->gpio = yes ? 1 : 0; +} + +static void omap16xx_cam_init(void) +{ + /* + * FIXME - Use mux API's instead of directly writing in to MUX registers + */ + omap_writel(omap_readl(FUNC_MUX_CTRL_4) & ~(0x1ff << 21), + FUNC_MUX_CTRL_4); + omap_writel(0, FUNC_MUX_CTRL_5); + omap_writel(omap_readl(PULL_DWN_CTRL_0) & ~(0x1FFF << 17), + PULL_DWN_CTRL_0); + omap_writel(omap_readl(PU_PD_SEL_0) & ~(0x1FFF << 17), PU_PD_SEL_0); + + omap_writel(0xeaef, COMP_MODE_CTRL_0); + omap_writel(omap_readl(OMAP1610_RESET_CONTROL) & + ~(1 << CONF_CAMERAIF_RESET_R), OMAP1610_RESET_CONTROL); + omap_writel(omap_readl(OMAP1610_RESET_CONTROL) | + (1 << CONF_CAMERAIF_RESET_R), OMAP1610_RESET_CONTROL); + + /* Enable peripheral reset */ + omap_writew(omap_readw(ARM_RSTCT2) | (1 << EN_PER), ARM_RSTCT2); + + /* Enable peripheral clock */ + clk_enable(hardware_data.func_clk); +} + +static void omap16xx_cam_waitfor_syncedge(struct omap16xxcam *data, + u32 edge_mask) +{ + data->camera_regs->mode = + (FIFO_TRIGGER_LVL << THRESHOLD_BIT) | edge_mask; + do { + interruptible_sleep_on(&data->vsync_wait); + } while (signal_pending(current)); +} + +static void omap16xx_cam_configure_dma(struct omap16xxcam *data) +{ + + data->camera_regs->mode = (FIFO_TRIGGER_LVL << THRESHOLD_BIT) + | EN_DMA | EN_FIFO_FULL; + data->camera_regs->ctrlclock |= LCLK_EN; +} + +/* Acquire h/w resources DMA */ +static int omap16xx_cam_link_open(struct omap16xxcam *data) +{ + int ret; + + /* Acquire first DMA channel */ + ret = omap_request_dma(OMAP_DMA_CAMERA_IF_RX, + "camera dma 1", + omap16xx_cam_dma_link_callback, + (void *)data, &data->dma_channel_number1); + if (ret) + return ret; + + /* Acquire second DMA channel */ + ret = omap_request_dma(OMAP_DMA_CAMERA_IF_RX, + "camera dma 2", + omap16xx_cam_dma_link_callback, + (void *)data, &data->dma_channel_number2); + if (ret) { + printk(KERN_ERR "No DMA available for camera\n"); + return ret; + } + data->next_dmach = data->dma_channel_number1; + OMAP_DMA_CLNK_CTRL_REG(data->dma_channel_number1) = + data->dma_channel_number2; + OMAP_DMA_CLNK_CTRL_REG(data->dma_channel_number2) = + data->dma_channel_number1; + + return 0; +} + +/* Free h/w resources, stop i/f */ +static int omap16xx_cam_link_close(struct omap16xxcam *data) +{ + /* Free DMA channels */ + omap_stop_dma(data->dma_channel_number1); + omap_stop_dma(data->dma_channel_number2); + + omap_free_dma(data->dma_channel_number1); + omap_free_dma(data->dma_channel_number2); + + return 0; +} + +/* DMA callback routine. */ +static void omap16xx_cam_dma_link_callback(int lch, unsigned short ch_status, + void *data) +{ + int count; + void *arg1, *arg2; + struct sgdma_state *sgdma = sgdma; + struct omap16xxcam *cam = (struct omap16xxcam *)data; + dma_callback_t callback; + + spin_lock(&cam->dma_lock); + if (cam->free_dmach == 2) { + printk(KERN_ERR "callback all CHANNELS WERE IDLE \n"); + spin_unlock(&cam->dma_lock); + return; + } + if (cam->free_dmach == 0) { + lch = cam->next_dmach; + } else { + lch = cam->next_dmach == cam->dma_channel_number1 ? + cam->dma_channel_number2 : cam->dma_channel_number1; + } + + while (cam->free_dmach < 2) { + if (OMAP_DMA_CCR_REG(lch) & (1 << 7)) + break; + + count = (lch == cam->dma_channel_number2) ? 1 : 0; + + callback = cam->camdma[count].callback; + arg1 = cam->camdma[count].arg1; + arg2 = cam->camdma[count].arg2; + cam->free_dmach++; + + spin_unlock(&cam->dma_lock); + callback(arg1, arg2); + spin_lock(&cam->dma_lock); + + lch = + (lch == + cam->dma_channel_number2) ? cam-> + dma_channel_number1 : cam->dma_channel_number2; + } + spin_unlock(&cam->dma_lock); + +} + +static irqreturn_t omap16xx_cam_isr(int irq, void *client_data) +{ + struct omap16xxcam *data = (struct omap16xxcam *)client_data; + unsigned int itstat = data->camera_regs->it_status; + + /* VSYNC UP interrupt, start filling FIFO and enabling DMA */ + if (itstat & V_UP) { + data->camera_regs->mode &= ~EN_V_UP; + omap16xx_cam_clear_fifo(data); + omap16xx_cam_configure_dma(data); + omap_start_dma(data->next_dmach); + wake_up_interruptible(&data->vsync_wait); + } + + if (itstat & V_DOWN) { + data->camera_regs->mode &= ~EN_V_DOWN; + wake_up_interruptible(&data->vsync_wait); + } + + if (itstat & H_UP) + printk(KERN_INFO "H_UP\n"); + + if (itstat & H_DOWN) + printk(KERN_INFO "H_DOWN\n"); + + if (itstat & FIFO_FULL) { + omap16xx_cam_clear_fifo(data); + printk(KERN_INFO "FIFO_FULL\n"); + } + + if (itstat & DATA_XFER) + printk(KERN_INFO "DATA_TRANS\n"); + + return IRQ_HANDLED; +} + +/* ------------- Below are interface functions ----------------- + * ------------- These functions are named omap16xxcam_ -- + */ +static int omap16xxcam_init_dma(void *priv) +{ + int ch; + struct omap16xxcam *data = (struct omap16xxcam *)priv; + + data->free_dmach = 2; + for (ch = 0; ch < 2; ++ch) { + data->camdma[ch].callback = NULL; + data->camdma[ch].arg1 = NULL; + data->camdma[ch].arg2 = NULL; + } + + return 0; +} + +/* Start the DMA of chains */ +static int omap16xxcam_start_dma(struct sgdma_state *sgdma, + dma_callback_t callback, void *arg1, + void *arg2, void *priv) +{ + struct omap16xxcam *data = (struct omap16xxcam *)priv; + struct scatterlist *sglist; + unsigned long irqflags; + int dmach; + int prev_dmach; + int count; + + spin_lock_irqsave(&data->dma_lock, irqflags); + sglist = (struct scatterlist *)(sgdma->sglist + sgdma->next_sglist); + + if (!data->free_dmach) { + spin_unlock_irqrestore(&data->dma_lock, irqflags); + return -EBUSY; + } + dmach = data->next_dmach; + count = (dmach == data->dma_channel_number2) ? 1 : 0; + data->camdma[count].callback = callback; + data->camdma[count].arg1 = arg1; + data->camdma[count].arg2 = arg2; + + if (cpu_is_omap1710()) + omap_set_dma_src_params(dmach, OMAP_DMA_PORT_OCP_T1, + OMAP_DMA_AMODE_CONSTANT, + CAM_CAMDATA_REG, 0, 0); + else + omap_set_dma_src_params(dmach, OMAP_DMA_PORT_TIPB, + OMAP_DMA_AMODE_CONSTANT, + CAM_CAMDATA_REG, 0, 0); + + omap_set_dma_dest_params(dmach, OMAP_DMA_PORT_EMIFF, + OMAP_DMA_AMODE_POST_INC, + sg_dma_address(sglist), 0, 0); + + omap_set_dma_transfer_params(dmach, OMAP_DMA_DATA_TYPE_S32, + FIFO_TRIGGER_LVL, + sg_dma_len(sglist) / (4 * + FIFO_TRIGGER_LVL), + OMAP_DMA_SYNC_FRAME, 0, 0); + + OMAP_DMA_CLNK_CTRL_REG(dmach) &= ~(1 << 15); + + prev_dmach = (dmach == data->dma_channel_number2) ? + data->dma_channel_number1 : data->dma_channel_number2; + + if (data->new) { + data->new = 0; + omap16xx_cam_waitfor_syncedge(data, EN_V_UP); + } else { + if (OMAP_DMA_CCR_REG(prev_dmach) & (1 << 7)) + OMAP_DMA_CLNK_CTRL_REG(prev_dmach) |= (1 << 15); + else { + /* No transfer is in progress */ + omap_start_dma(dmach); + } + } + + data->next_dmach = prev_dmach; + data->free_dmach--; + spin_unlock_irqrestore(&data->dma_lock, irqflags); + return 0; +} + +int static omap16xxcam_finish_dma(void *priv) +{ + struct omap16xxcam *data = (struct omap16xxcam *)priv; + + while (data->free_dmach < 2) + mdelay(1); + + return 0; +} + +/* Enables the camera. Takes camera out of reset. Enables the clocks. */ +static int omap16xxcam_enable(void *priv) +{ + struct omap16xxcam *data = (struct omap16xxcam *)priv; + + omap16xx_cam_reset(data, 1); + + /* Give clock to camera_module */ + data->camera_regs->mode = (FIFO_TRIGGER_LVL << THRESHOLD_BIT); + data->camera_regs->ctrlclock = MCLK_EN | CAMEXCLK_EN; + + omap16xx_cam_clear_fifo(data); + + /* Wait for camera to settle down */ + mdelay(5); + + return 0; +} + +/* Disables all the camera clocks. Put the camera interface in reset. */ +static int omap16xxcam_disable(void *priv) +{ + struct omap16xxcam *data = (struct omap16xxcam *)priv; + + omap16xx_cam_clear_fifo(data); + + data->camera_regs->ctrlclock = 0x00000000; + data->camera_regs->mode = 0x00000000; + + omap16xx_cam_reset(data, 0); + + return 0; +} + +/* Abort the data transfer */ +static int omap16xxcam_abort(void *priv) +{ + return omap16xxcam_disable(priv); +} + +static int omap16xxcam_set_xclk(int xclk, void *priv) +{ + struct omap16xxcam *data = (struct omap16xxcam *)priv; + int xclk_val; + int divisor = 1; + divisor = data->ocp_clk / xclk; + if (divisor * xclk < data->ocp_clk) + ++divisor; + + switch (divisor) { + case 1: + case 2: + xclk_val = FOSCMOD_TC2_CK2; + break; + case 3: + xclk_val = FOSCMOD_TC2_CK3; + break; + case 4: + case 5: + case 6: + case 7: + xclk_val = FOSCMOD_TC2_CK4; + break; + case 8: + case 9: + xclk_val = FOSCMOD_TC2_CK8; + break; + case 10: + case 11: + xclk_val = FOSCMOD_TC2_CK10; + break; + case 12: + case 13: + case 14: + case 15: + xclk_val = FOSCMOD_TC2_CK12; + break; + case 16: + xclk_val = FOSCMOD_TC2_CK16; + break; + default: + xclk_val = FOSCMOD_TC2_CK16; + } + + /* Follow the protocol to change the XCLK clock */ + data->camera_regs->ctrlclock &= ~CAMEXCLK_EN; + data->camera_regs->ctrlclock |= xclk_val; + data->camera_regs->ctrlclock |= CAMEXCLK_EN; + + return (data->ocp_clk / divisor); +} + +static int omap16xxcam_open(void *priv) +{ + struct omap16xxcam *data = (struct omap16xxcam *)priv; + int ret; + + ret = request_irq(INT_CAMERA, omap16xx_cam_isr, IRQF_DISABLED, + "camera", data); + if (ret) { + printk(KERN_ERR "FAILED to acquire IRQ\n"); + return ret; + } + + data->new = 1; + omap16xxcam_enable(data); + omap16xxcam_init_dma(data); + + return omap16xx_cam_link_open(data); +} + +static int omap16xxcam_close(void *priv) +{ + struct omap16xxcam *data = (struct omap16xxcam *)priv; + + omap16xxcam_disable(priv); + + free_irq(INT_CAMERA, data); + + return omap16xx_cam_link_close(data); +} + +static int omap16xxcam_cleanup(void *priv) +{ + struct omap16xxcam *data = (struct omap16xxcam *)priv; + + if (!data->camera_regs) + return -EINVAL; + + omap16xxcam_disable(data); + if (cpu_is_omap1710()) + iounmap((void *)data->camera_regs); + data->camera_regs = NULL; + + if (data->iobase_phys) { + release_mem_region(data->iobase_phys, CAMERA_IOSIZE); + data->iobase_phys = 0; + } + + if (hardware_data.func_clk) { + clk_disable(hardware_data.func_clk); + clk_put(hardware_data.func_clk); + hardware_data.func_clk = NULL; + } + + return 0; +} + +/* Initialize the OMAP camera interface */ +static void *omap16xxcam_init(void) +{ + unsigned long cam_iobase; + + if (!request_mem_region(CAMERA_BASE, CAMERA_IOSIZE, + camera_hardware_if.name)) { + pr_debug("%s is already in use\n", camera_hardware_if.name); + return NULL; + } + + if (cpu_is_omap1710()) { + cam_iobase = (unsigned long)ioremap(CAMERA_BASE, CAMERA_IOSIZE); + if (!cam_iobase) { + printk(KERN_ERR "CANNOT MAP CAMERA REGISTER\n"); + return NULL; + } + } else + cam_iobase = io_p2v(CAMERA_BASE); + + /* Set the base address of the camera registers */ + hardware_data.camera_regs = (camera_regs_t *) cam_iobase; + hardware_data.iobase_phys = (unsigned long)CAMERA_BASE; + + /* Get the input clock value to camera interface and store it */ + if (cpu_is_omap1710()) + hardware_data.func_clk = clk_get(0, "tc2_ck"); + else + hardware_data.func_clk = clk_get(0, "armper_ck"); + hardware_data.ocp_clk = clk_get_rate(hardware_data.func_clk); + + /* Initialize the camera IF */ + omap16xx_cam_init(); + /* Enable it. This is needed for sensor detection */ + omap16xxcam_enable((void *)&hardware_data); + /* Initialize DMA data */ + spin_lock_init(&hardware_data.dma_lock); + + init_waitqueue_head(&hardware_data.vsync_wait); + return (void *)&hardware_data; +} + +struct camera_hardware camera_hardware_if = { + .version = 0x01, + .name = "OMAP16xx Parallel Camera", + .init = omap16xxcam_init, + .cleanup = omap16xxcam_cleanup, + .open = omap16xxcam_open, + .close = omap16xxcam_close, + .enable = omap16xxcam_enable, + .disable = omap16xxcam_disable, + .abort = omap16xxcam_abort, + .set_xclk = omap16xxcam_set_xclk, + .init_dma = omap16xxcam_init_dma, + .start_dma = omap16xxcam_start_dma, + .finish_dma = omap16xxcam_finish_dma, +}; diff --cc drivers/misc/sti/sdti.c index e12fb120bba,00000000000..c35821d5b4d mode 100644,000000..100644 --- a/drivers/misc/sti/sdti.c +++ b/drivers/misc/sti/sdti.c @@@ -1,185 -1,0 +1,185 @@@ +/* + * Support functions for OMAP3 SDTI (Serial Debug Tracing Interface) + * + * Copyright (C) 2008 Nokia Corporation + * Written by: Roman Tereshonkov + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include - #include ++#include +#include +#include + +#define SDTI_REVISION 0x000 +#define SDTI_SYSCONFIG 0x010 +#define SDTI_SYSSTATUS 0x014 +#define SDTI_WINCTRL 0x024 +#define SDTI_SCONFIG 0x028 +#define SDTI_TESTCTRL 0x02C +#define SDTI_LOCK_ACCESS 0xFB0 + +#define CPU1_TRACE_EN 0x01 +#define CPU2_TRACE_EN 0x02 + +static struct clk *sdti_ck; +unsigned long sti_base, sti_channel_base; +static DEFINE_SPINLOCK(sdti_lock); + +void omap_sti_channel_write_trace(int len, int id, void *data, + unsigned int channel) +{ + const u8 *tpntr = data; + + spin_lock_irq(&sdti_lock); + + sti_channel_writeb(id, channel); + while (len--) + sti_channel_writeb(*tpntr++, channel); + sti_channel_flush(channel); + + spin_unlock_irq(&sdti_lock); +} +EXPORT_SYMBOL(omap_sti_channel_write_trace); + +static void omap_sdti_reset(void) +{ + int i; + + sti_writel(0x02, SDTI_SYSCONFIG); + + for (i = 0; i < 10000; i++) + if (sti_readl(SDTI_SYSSTATUS) & 1) + break; + if (i == 10000) + printk(KERN_WARNING "XTI: no real reset\n"); +} + +static int __init omap_sdti_init(void) +{ + char buf[64]; + int i; + + sdti_ck = clk_get(NULL, "emu_per_alwon_ck"); + if (IS_ERR(sdti_ck)) { + printk(KERN_ERR "Cannot get clk emu_per_alwon_ck\n"); + return PTR_ERR(sdti_ck); + } + clk_enable(sdti_ck); + + omap_sdti_reset(); + sti_writel(0xC5ACCE55, SDTI_LOCK_ACCESS); + + /* Claim SDTI */ + sti_writel(1 << 30, SDTI_WINCTRL); + i = sti_readl(SDTI_WINCTRL); + if (!(i & (1 << 30))) + printk(KERN_WARNING "SDTI: cannot claim SDTI\n"); + + /* 4 bits dual, fclk/3 */ + sti_writel(0x43, SDTI_SCONFIG); + + /* CPU2 trace enable */ + sti_writel(i | CPU2_TRACE_EN, SDTI_WINCTRL); + i = sti_readl(SDTI_WINCTRL); + + /* Enable SDTI */ + sti_writel((1 << 31) | (i & 0x3FFFFFFF), SDTI_WINCTRL); + + i = sti_readl(SDTI_REVISION); + snprintf(buf, sizeof(buf), "OMAP SDTI support loaded (HW v%u.%u)\n", + (i >> 4) & 0x0f, i & 0x0f); + printk(KERN_INFO "%s", buf); + omap_sti_channel_write_trace(strlen(buf), 0xc3, buf, 239); + + return 0; +} + +static void omap_sdti_exit(void) +{ + sti_writel(0, SDTI_WINCTRL); + clk_disable(sdti_ck); + clk_put(sdti_ck); +} + +static int __devinit omap_sdti_probe(struct platform_device *pdev) +{ + struct resource *res, *cres; + unsigned int size; + + if (pdev->num_resources != 2) { + dev_err(&pdev->dev, "invalid number of resources: %d\n", + pdev->num_resources); + return -ENODEV; + } + + /* SDTI base */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (unlikely(!res)) { + dev_err(&pdev->dev, "invalid mem resource\n"); + return -ENODEV; + } + + /* Channel base */ + cres = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (unlikely(!cres)) { + dev_err(&pdev->dev, "invalid channel mem resource\n"); + return -ENODEV; + } + + size = res->end - res->start; + sti_base = (unsigned long)ioremap(res->start, size); + if (unlikely(!sti_base)) + return -ENODEV; + + size = cres->end - cres->start; + sti_channel_base = (unsigned long)ioremap(cres->start, size); + if (unlikely(!sti_channel_base)) { + iounmap((void *)sti_base); + return -ENODEV; + } + + return omap_sdti_init(); +} + +static int __devexit omap_sdti_remove(struct platform_device *pdev) +{ + iounmap((void *)sti_channel_base); + iounmap((void *)sti_base); + omap_sdti_exit(); + + return 0; +} + +static struct platform_driver omap_sdti_driver = { + .probe = omap_sdti_probe, + .remove = __devexit_p(omap_sdti_remove), + .driver = { + .name = "sti", + .owner = THIS_MODULE, + }, +}; + +static int __init omap_sdti_module_init(void) +{ + return platform_driver_register(&omap_sdti_driver); +} + +static void __exit omap_sdti_module_exit(void) +{ + platform_driver_unregister(&omap_sdti_driver); +} +subsys_initcall(omap_sdti_module_init); +module_exit(omap_sdti_module_exit); + +MODULE_AUTHOR("Roman Tereshonkov"); +MODULE_LICENSE("GPL"); diff --cc drivers/misc/sti/sti-console.c index 451a139d46b,00000000000..2062d23dca0 mode 100644,000000..100644 --- a/drivers/misc/sti/sti-console.c +++ b/drivers/misc/sti/sti-console.c @@@ -1,189 -1,0 +1,189 @@@ +/* + * Console support for OMAP STI/XTI + * + * Copyright (C) 2004, 2005, 2006 Nokia Corporation + * Written by: Paul Mundt + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#include +#include +#include +#include +#include +#include - #include - #include ++#include ++#include + +#define DRV_NAME "sticon" + +static struct tty_driver *tty_driver; +static DEFINE_SPINLOCK(sti_console_lock); +static unsigned int sti_console_channel = -1; +static int sti_line_done = -1; + +/* + * Write a string to any channel (including terminating NULL) + * Returns number of characters written. + */ +static int sti_channel_puts(const char *string, unsigned int channel, int len) +{ + int count = 0; + + /* + * sti_line_done is needed to determine when we have reached the + * end of the line. write() has a tendency to hand us small + * strings which otherwise end up creating newlines.. we need to + * keep the channel open and in append mode until the line has + * been terminated. + */ + if (sti_line_done != 0) { +#ifdef __LITTLE_ENDIAN + sti_channel_writeb(0xc3, channel); +#else + sti_channel_writeb(0xc0, channel); +#endif + xchg(&sti_line_done, 0); + } + + while (*string && count != len) { + char c = *string++; + + count++; + + if (c == '\n') { + xchg(&sti_line_done, 1); + sti_channel_writeb(0, channel); + break; + } else + sti_channel_writeb(c, channel); + } + + if (sti_line_done) + sti_channel_flush(channel); + + return count; +} + +static int sti_tty_open(struct tty_struct *tty, struct file *filp) +{ + return 0; +} + +static int sti_tty_write(struct tty_struct *tty, + const unsigned char *buf, int len) +{ + unsigned long flags; + int bytes; + + spin_lock_irqsave(&sti_console_lock, flags); + bytes = sti_channel_puts(buf, sti_console_channel, len); + spin_unlock_irqrestore(&sti_console_lock, flags); + + return bytes; +} + +static int sti_tty_write_room(struct tty_struct *tty) +{ + return 0x100000; +} + +static int sti_tty_chars_in_buffer(struct tty_struct *tty) +{ + return 0; +} + +static struct tty_operations sti_tty_ops = { + .open = sti_tty_open, + .write = sti_tty_write, + .write_room = sti_tty_write_room, + .chars_in_buffer = sti_tty_chars_in_buffer, +}; + +static void sti_console_write(struct console *c, const char *s, unsigned n) +{ + unsigned long flags; + + spin_lock_irqsave(&sti_console_lock, flags); + sti_channel_puts(s, sti_console_channel, n); + spin_unlock_irqrestore(&sti_console_lock, flags); +} + +static struct tty_driver *sti_console_device(struct console *c, int *index) +{ + *index = c->index; + return tty_driver; +} + +static int sti_console_setup(struct console *c, char *opts) +{ + return 0; +} + +static struct console sti_console = { + .name = DRV_NAME, + .write = sti_console_write, + .device = sti_console_device, + .setup = sti_console_setup, + .flags = CON_PRINTBUFFER | CON_ENABLED, + .index = -1, +}; + +static int __init sti_console_init(void) +{ + const struct omap_sti_console_config *info; + + info = omap_get_config(OMAP_TAG_STI_CONSOLE, + struct omap_sti_console_config); + if (info && info->enable) { + add_preferred_console(DRV_NAME, 0, NULL); + + sti_console_channel = info->channel; + } + + if (unlikely(sti_console_channel == -1)) + return -EINVAL; + + register_console(&sti_console); + + return 0; +} +__initcall(sti_console_init); + +static int __init sti_tty_init(void) +{ + struct tty_driver *tty; + int ret; + + tty = alloc_tty_driver(1); + if (!tty) + return -ENOMEM; + + tty->name = DRV_NAME; + tty->driver_name = DRV_NAME; + tty->major = 0; /* dynamic major */ + tty->minor_start = 0; + tty->type = TTY_DRIVER_TYPE_SYSTEM; + tty->subtype = SYSTEM_TYPE_SYSCONS; + tty->init_termios = tty_std_termios; + + tty_set_operations(tty, &sti_tty_ops); + + ret = tty_register_driver(tty); + if (ret) { + put_tty_driver(tty); + return ret; + } + + tty_driver = tty; + return 0; +} +late_initcall(sti_tty_init); + +module_param(sti_console_channel, uint, 0); +MODULE_PARM_DESC(sti_console_channel, "STI console channel"); +MODULE_AUTHOR("Paul Mundt"); +MODULE_DESCRIPTION("OMAP STI console support"); +MODULE_LICENSE("GPL"); diff --cc drivers/misc/sti/sti-fifo.c index 4069d9b2d39,00000000000..1ea5b1896fb mode 100644,000000..100644 --- a/drivers/misc/sti/sti-fifo.c +++ b/drivers/misc/sti/sti-fifo.c @@@ -1,117 -1,0 +1,117 @@@ +/* + * STI RX FIFO Support + * + * Copyright (C) 2005, 2006 Nokia Corporation + * Written by: Paul Mundt and + * Roman Tereshonkov + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#include +#include +#include +#include - #include ++#include + +#define STI_READ_BUFFER_SIZE 1024 +#define sti_buf_pos(pos) ((sti_crb->bufpos + (pos)) % \ + STI_READ_BUFFER_SIZE) + +static struct sti_cycle_buffer { + int bufpos; + int datalen; + unsigned char *buf; +} *sti_crb; + +/** + * sti_read_packet - STI read packet (read an entire STI packet) + * @buf: Buffer to store the packet. + * @maxsize: Maximum size requested. + * + * This reads in a single completed STI packet from the RX FIFOs and + * places it in @buf for further processing. + * + * The return value is < 0 on error, and >= 0 for the number of bytes + * actually read. As per the STI specification, we require a 0xC1 to + * indicate the end of the packet, and we don't return the packet until + * we've read the entire thing in. + * + * Due to the size of the FIFOs, it's unrealistic to constantly drain + * this for 1 or 2 bytes at a time, so we assemble it here and return + * the whole thing. + */ +int sti_read_packet(unsigned char *buf, int maxsize) +{ + unsigned int pos; + + if (unlikely(!buf)) + return -EINVAL; + if (!sti_crb->datalen) + return 0; + + pos = sti_buf_pos(sti_crb->datalen - 1); + /* End of packet */ + if (sti_crb->buf[pos] == 0xC1) { + int i; + + for (i = 0; i < sti_crb->datalen && i < maxsize; i++) { + pos = sti_buf_pos(i); + buf[i] = sti_crb->buf[pos]; + } + + sti_crb->bufpos = sti_buf_pos(i); + sti_crb->datalen -= i; + + return i; + } + + return 0; +} +EXPORT_SYMBOL(sti_read_packet); + +static void sti_fifo_irq(unsigned long arg) +{ + /* If there is data read it */ + while (!(sti_readl(STI_RX_STATUS) & STI_RXFIFO_EMPTY)) { + unsigned int pos = sti_buf_pos(sti_crb->datalen); + + sti_crb->buf[pos] = sti_readl(STI_RX_DR); + sti_crb->datalen++; + } + + sti_ack_irq(STI_RX_INT); +} + +static int __init sti_fifo_init(void) +{ + unsigned int size; + int ret; + + size = sizeof(struct sti_cycle_buffer) + STI_READ_BUFFER_SIZE; + sti_crb = kmalloc(size, GFP_KERNEL); + if (!sti_crb) + return -ENOMEM; + + sti_crb->bufpos = sti_crb->datalen = 0; + sti_crb->buf = (unsigned char *)(sti_crb + sizeof(*sti_crb)); + + ret = sti_request_irq(STI_RX_INT, sti_fifo_irq, 0); + if (ret != 0) + kfree(sti_crb); + + return ret; +} + +static void __exit sti_fifo_exit(void) +{ + sti_free_irq(STI_RX_INT); + kfree(sti_crb); +} + +module_init(sti_fifo_init); +module_exit(sti_fifo_exit); + +MODULE_AUTHOR("Paul Mundt, Roman Tereshonkov"); +MODULE_LICENSE("GPL"); diff --cc drivers/misc/sti/sti-netlink.c index ca3533e7519,00000000000..f1f8a48f4aa mode 100644,000000..100644 --- a/drivers/misc/sti/sti-netlink.c +++ b/drivers/misc/sti/sti-netlink.c @@@ -1,152 -1,0 +1,152 @@@ +/* + * OMAP STI/XTI communications interface via netlink socket. + * + * Copyright (C) 2004, 2005, 2006 Nokia Corporation + * Written by: Paul Mundt + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#include +#include +#include +#include +#include +#include +#include - #include ++#include + +static struct sock *sti_sock; +static DEFINE_MUTEX(sti_netlink_mutex); + +enum { + STI_READ, + STI_WRITE, +}; + +static int sti_netlink_read(int pid, int seq, void *payload, int size) +{ + struct sk_buff *skb; + struct nlmsghdr *nlh; + int ret, len = NLMSG_SPACE(size); + unsigned char *tail; + + skb = alloc_skb(len, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + tail = skb->tail; + nlh = NLMSG_PUT(skb, pid, seq, STI_READ, + len - (sizeof(struct nlmsghdr))); + nlh->nlmsg_flags = 0; + memcpy(NLMSG_DATA(nlh), payload, size); + nlh->nlmsg_len = skb->tail - tail; + + ret = netlink_unicast(sti_sock, skb, pid, MSG_DONTWAIT); + if (ret > 0) + ret = 0; + + return ret; + +nlmsg_failure: + if (skb) + kfree_skb(skb); + + return -EINVAL; +} + +/* + * We abuse nlmsg_type and nlmsg_flags for our purposes. + * + * The ID is encoded into the upper 8 bits of the nlmsg_type, while the + * channel number is encoded into the upper 8 bits of the nlmsg_flags. + */ +static int sti_netlink_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh) +{ + void *data; + u8 chan, id; + int size, ret = 0, len = 0; + + data = NLMSG_DATA(nlh); + chan = (nlh->nlmsg_flags >> 8) & 0xff; + id = (nlh->nlmsg_type >> 8) & 0xff; + size = (int)(nlh->nlmsg_len - ((char *)data - (char *)nlh)); + + switch (nlh->nlmsg_type & 0xff) { + case STI_WRITE: + sti_channel_write_trace(size, id, data, chan); + break; + case STI_READ: + data = kmalloc(size, GFP_KERNEL); + if (!data) + return -ENOMEM; + memset(data, 0, size); + + len = sti_read_packet(data, size); + ret = sti_netlink_read(NETLINK_CB(skb).pid, nlh->nlmsg_seq, + data, len); + kfree(data); + break; + default: + return -ENOTTY; + } + + return ret; +} + +static int sti_netlink_receive_skb(struct sk_buff *skb) +{ + while (skb->len >= NLMSG_SPACE(0)) { + struct nlmsghdr *nlh; + u32 rlen; + int ret; + + nlh = (struct nlmsghdr *)skb->data; + if (nlh->nlmsg_len < sizeof(struct nlmsghdr) || + skb->len < nlh->nlmsg_len) + break; + + rlen = NLMSG_ALIGN(nlh->nlmsg_len); + if (rlen > skb->len) + rlen = skb->len; + + ret = sti_netlink_receive_msg(skb, nlh); + if (ret) + netlink_ack(skb, nlh, -ret); + else if (nlh->nlmsg_flags & NLM_F_ACK) + netlink_ack(skb, nlh, 0); + + skb_pull(skb, rlen); + } + + return 0; +} + +static void sti_netlink_receive(struct sk_buff *skb) +{ + if (!mutex_trylock(&sti_netlink_mutex)) + return; + + sti_netlink_receive_skb(skb); + mutex_unlock(&sti_netlink_mutex); +} + +static int __init sti_netlink_init(void) +{ + sti_sock = netlink_kernel_create(&init_net, NETLINK_USERSOCK, 0, + sti_netlink_receive, NULL, + THIS_MODULE); + if (!sti_sock) { + printk(KERN_ERR "STI: Failed to create netlink socket\n"); + return -ENODEV; + } + + return 0; +} + +module_init(sti_netlink_init); + +MODULE_AUTHOR("Paul Mundt"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("STI netlink-driven communications interface"); diff --cc drivers/misc/sti/sti.c index 1140fed048f,00000000000..28fac623313 mode 100644,000000..100644 --- a/drivers/misc/sti/sti.c +++ b/drivers/misc/sti/sti.c @@@ -1,432 -1,0 +1,432 @@@ +/* + * Support functions for OMAP STI/XTI (Serial Tracing Interface) + * + * Copyright (C) 2004, 2005, 2006 Nokia Corporation + * Written by: Paul Mundt + * + * STI initialization code and channel handling + * from Juha Yrjölä . + * + * XTI initialization + * from Roman Tereshonkov . + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include - #include ++#include +#include + +static struct clk *sti_ck; +unsigned long sti_base, sti_channel_base; +static unsigned long sti_kern_mask = STIEn; +static unsigned long sti_irq_mask = STI_IRQSTATUS_MASK; +static DEFINE_SPINLOCK(sti_lock); + +static struct sti_irqdesc { + irqreturn_t (*func)(unsigned long); + unsigned long data; +} ____cacheline_aligned sti_irq_desc[STI_NR_IRQS]; + +void sti_channel_write_trace(int len, int id, void *data, unsigned int channel) +{ + const u8 *tpntr = data; + + sti_channel_writeb(id, channel); + + if (cpu_is_omap16xx()) + /* Check u32 boundary */ + if (!((u32)data & (STI_PERCHANNEL_SIZE - 1)) && + (len >= STI_PERCHANNEL_SIZE)) { + const u32 *asrc = data; + + do { + sti_channel_writel(cpu_to_be32(*asrc++), + channel); + len -= STI_PERCHANNEL_SIZE; + } while (len >= STI_PERCHANNEL_SIZE); + + tpntr = (const u8 *)asrc; + } + + while (len--) + sti_channel_writeb(*tpntr++, channel); + + sti_channel_flush(channel); +} +EXPORT_SYMBOL(sti_channel_write_trace); + +void sti_enable_irq(unsigned int id) +{ + spin_lock_irq(&sti_lock); + sti_writel(1 << id, STI_IRQSETEN); + spin_unlock_irq(&sti_lock); +} +EXPORT_SYMBOL(sti_enable_irq); + +void sti_disable_irq(unsigned int id) +{ + spin_lock_irq(&sti_lock); + + if (cpu_is_omap16xx()) + sti_writel(1 << id, STI_IRQCLREN); + else if (cpu_is_omap24xx()) + sti_writel(sti_readl(STI_IRQSETEN) & ~(1 << id), STI_IRQSETEN); + else + BUG(); + + spin_unlock_irq(&sti_lock); +} +EXPORT_SYMBOL(sti_disable_irq); + +void sti_ack_irq(unsigned int id) +{ + /* Even though the clear state is 0, we have to write 1 to clear */ + sti_writel(1 << id, STI_IRQSTATUS); +} +EXPORT_SYMBOL(sti_ack_irq); + +int sti_request_irq(unsigned int irq, void *handler, unsigned long arg) +{ + struct sti_irqdesc *desc; + + if (unlikely(!handler || irq > STI_NR_IRQS)) + return -EINVAL; + + desc = sti_irq_desc + irq; + if (unlikely(desc->func)) { + printk(KERN_WARNING "STI: Attempting to request in-use IRQ " + "%d, consider fixing your code..\n", irq); + return -EBUSY; + } + + desc->func = handler; + desc->data = arg; + + sti_enable_irq(irq); + return 0; +} +EXPORT_SYMBOL(sti_request_irq); + +void sti_free_irq(unsigned int irq) +{ + struct sti_irqdesc *desc = sti_irq_desc + irq; + + if (unlikely(irq > STI_NR_IRQS)) + return; + + sti_disable_irq(irq); + + desc->func = NULL; + desc->data = 0; +} +EXPORT_SYMBOL(sti_free_irq); + +/* + * This is a bit heavy, so normally we would defer this to a tasklet. + * Unfortunately tasklets are too slow for the RX FIFO interrupt (and + * possibly some others), so we just do the irqdesc walking here. + */ +static irqreturn_t sti_interrupt(int irq, void *dev_id) +{ + int ret = IRQ_NONE; + u16 status; + int i; + + status = sti_readl(STI_IRQSTATUS) & sti_irq_mask; + + for (i = 0; status; i++) { + struct sti_irqdesc *desc = sti_irq_desc + i; + u16 id = 1 << i; + + if (!(status & id)) + continue; + + if (likely(desc && desc->func)) + ret |= desc->func(desc->data); + if (unlikely(ret == IRQ_NONE)) { + printk("STI: spurious interrupt (id %d)\n", id); + sti_disable_irq(i); + sti_ack_irq(i); + ret = IRQ_HANDLED; + } + + status &= ~id; + } + + return IRQ_RETVAL(ret); +} + +static void omap_sti_reset(void) +{ + int i; + + /* Reset STI module */ + sti_writel(0x02, STI_SYSCONFIG); + + /* Wait a while for the STI module to complete its reset */ + for (i = 0; i < 10000; i++) + if (sti_readl(STI_SYSSTATUS) & 1) + break; +} + +static int __init sti_init(void) +{ + char buf[64]; + int i; + + if (cpu_is_omap16xx()) { + /* Release ARM Rhea buses peripherals enable */ + sti_writel(sti_readl(ARM_RSTCT2) | 0x0001, ARM_RSTCT2); + + /* Enable TC1_CK (functional clock) */ + sti_ck = clk_get(NULL, "tc1_ck"); + } else if (cpu_is_omap24xx()) + /* Enable emulation tools clock */ + sti_ck = clk_get(NULL, "emul_ck"); + + if (IS_ERR(sti_ck)) + return PTR_ERR(sti_ck); + + clk_enable(sti_ck); + + /* Reset STI module */ + omap_sti_reset(); + + /* Enable STI */ + sti_trace_enable(MPUCmdEn); + + /* Change to custom serial protocol */ + sti_writel(0x01, STI_SERIAL_CFG); + + /* Set STI clock control register to normal mode */ + sti_writel(0x00, STI_CLK_CTRL); + + i = sti_readl(STI_REVISION); + snprintf(buf, sizeof(buf), "OMAP STI support loaded (HW v%u.%u)\n", + (i >> 4) & 0x0f, i & 0x0f); + printk(KERN_INFO "%s", buf); + + sti_channel_write_trace(strlen(buf), 0xc3, buf, 239); + + return 0; +} + +static void sti_exit(void) +{ + u32 tmp; + + /* + * This should have already been done by reset, but we switch off + * STI entirely just for added sanity.. + */ + tmp = sti_readl(STI_ER); + tmp &= ~STIEn; + sti_writel(tmp, STI_ER); + + clk_disable(sti_ck); + clk_put(sti_ck); +} + +static void __sti_trace_enable(int event) +{ + u32 tmp; + + tmp = sti_readl(STI_ER); + tmp |= sti_kern_mask | event; + sti_writel(tmp, STI_ER); +} + +int sti_trace_enable(int event) +{ + spin_lock_irq(&sti_lock); + sti_kern_mask |= event; + __sti_trace_enable(event); + spin_unlock_irq(&sti_lock); + + return 0; +} +EXPORT_SYMBOL(sti_trace_enable); + +static void __sti_trace_disable(int event) +{ + u32 tmp; + + tmp = sti_readl(STI_DR); + + if (cpu_is_omap16xx()) { + tmp |= event; + tmp &= ~sti_kern_mask; + } else if (cpu_is_omap24xx()) { + tmp &= ~event; + tmp |= sti_kern_mask; + } else + BUG(); + + sti_writel(tmp, STI_DR); +} + +void sti_trace_disable(int event) +{ + spin_lock_irq(&sti_lock); + sti_kern_mask &= ~event; + __sti_trace_disable(event); + spin_unlock_irq(&sti_lock); +} +EXPORT_SYMBOL(sti_trace_disable); + +static ssize_t +sti_trace_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "0x%08lx\n", sti_readl(STI_ER)); +} + +static ssize_t +sti_trace_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int evt = simple_strtoul(buf, NULL, 0); + int mask = ~evt; + + spin_lock_irq(&sti_lock); + __sti_trace_disable(mask); + __sti_trace_enable(evt); + spin_unlock_irq(&sti_lock); + + return count; +} +static DEVICE_ATTR(trace, S_IRUGO | S_IWUSR, sti_trace_show, sti_trace_store); + +static ssize_t +sti_imask_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "0x%04lx\n", sti_irq_mask); +} + +static ssize_t +sti_imask_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + spin_lock_irq(&sti_lock); + sti_irq_mask = simple_strtoul(buf, NULL, 0); + spin_unlock_irq(&sti_lock); + + return count; +} +static DEVICE_ATTR(imask, S_IRUGO | S_IWUSR, sti_imask_show, sti_imask_store); + +static int __devinit sti_probe(struct platform_device *pdev) +{ + struct resource *res, *cres; + int ret; + + if (pdev->num_resources != 3) { + dev_err(&pdev->dev, "invalid number of resources: %d\n", + pdev->num_resources); + return -ENODEV; + } + + /* STI base */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (unlikely(!res)) { + dev_err(&pdev->dev, "invalid mem resource\n"); + return -ENODEV; + } + + /* Channel base */ + cres = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (unlikely(!cres)) { + dev_err(&pdev->dev, "invalid channel mem resource\n"); + return -ENODEV; + } + + ret = device_create_file(&pdev->dev, &dev_attr_trace); + if (unlikely(ret != 0)) + return ret; + + ret = device_create_file(&pdev->dev, &dev_attr_imask); + if (unlikely(ret != 0)) + goto err; + + sti_base = io_p2v(res->start); + + /* + * OMAP 16xx keeps channels in a relatively sane location, + * whereas 24xx maps them much further out, and so they must be + * remapped. + */ + if (cpu_is_omap16xx()) + sti_channel_base = io_p2v(cres->start); + else if (cpu_is_omap24xx()) { + unsigned int size = cres->end - cres->start; + + sti_channel_base = (unsigned long)ioremap(cres->start, size); + if (unlikely(!sti_channel_base)) { + ret = -ENODEV; + goto err_badremap; + } + } + + ret = request_irq(platform_get_irq(pdev, 0), sti_interrupt, + IRQF_DISABLED, "sti", NULL); + if (unlikely(ret != 0)) + goto err_badirq; + + return sti_init(); + +err_badirq: + iounmap((void *)sti_channel_base); +err_badremap: + device_remove_file(&pdev->dev, &dev_attr_imask); +err: + device_remove_file(&pdev->dev, &dev_attr_trace); + + return ret; +} + +static int __devexit sti_remove(struct platform_device *pdev) +{ + unsigned int irq = platform_get_irq(pdev, 0); + + if (cpu_is_omap24xx()) + iounmap((void *)sti_channel_base); + + device_remove_file(&pdev->dev, &dev_attr_trace); + device_remove_file(&pdev->dev, &dev_attr_imask); + free_irq(irq, NULL); + sti_exit(); + + return 0; +} + +static struct platform_driver sti_driver = { + .probe = sti_probe, + .remove = __devexit_p(sti_remove), + .driver = { + .name = "sti", + .owner = THIS_MODULE, + }, +}; + +static int __init sti_module_init(void) +{ + return platform_driver_register(&sti_driver); +} + +static void __exit sti_module_exit(void) +{ + platform_driver_unregister(&sti_driver); +} +subsys_initcall(sti_module_init); +module_exit(sti_module_exit); + +MODULE_AUTHOR("Paul Mundt, Juha Yrjölä, Roman Tereshonkov"); +MODULE_LICENSE("GPL"); diff --cc drivers/mmc/host/omap_hsmmc.c index 7a6586f9507,00000000000..af3487164a9 mode 100644,000000..100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@@ -1,1069 -1,0 +1,1069 @@@ +/* + * drivers/mmc/host/omap_hsmmc.c + * + * Driver for OMAP2430/3430 MMC controller. + * + * Copyright (C) 2007 Texas Instruments. + * + * Authors: + * Syed Mohammed Khasim + * Madhusudhan + * Mohit Jalori + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include - #include - #include - #include - #include ++#include ++#include ++#include ++#include + +/* OMAP HSMMC Host Controller Registers */ +#define OMAP_HSMMC_SYSCONFIG 0x0010 +#define OMAP_HSMMC_CON 0x002C +#define OMAP_HSMMC_BLK 0x0104 +#define OMAP_HSMMC_ARG 0x0108 +#define OMAP_HSMMC_CMD 0x010C +#define OMAP_HSMMC_RSP10 0x0110 +#define OMAP_HSMMC_RSP32 0x0114 +#define OMAP_HSMMC_RSP54 0x0118 +#define OMAP_HSMMC_RSP76 0x011C +#define OMAP_HSMMC_DATA 0x0120 +#define OMAP_HSMMC_HCTL 0x0128 +#define OMAP_HSMMC_SYSCTL 0x012C +#define OMAP_HSMMC_STAT 0x0130 +#define OMAP_HSMMC_IE 0x0134 +#define OMAP_HSMMC_ISE 0x0138 +#define OMAP_HSMMC_CAPA 0x0140 + +#define VS18 (1<<26) +#define VS30 (1<<25) +#define SDVS18 (0x5<<9) +#define SDVS30 (0x6<<9) +#define SDVSCLR 0xFFFFF1FF +#define SDVSDET 0x00000400 +#define AUTOIDLE 0x1 +#define SDBP (1<<8) +#define DTO 0xe +#define ICE 0x1 +#define ICS 0x2 +#define CEN (1<<2) +#define CLKD_MASK 0x0000FFC0 +#define INT_EN_MASK 0x307F0033 +#define INIT_STREAM (1<<1) +#define DP_SELECT (1<<21) +#define DDIR (1<<4) +#define DMA_EN 0x1 +#define MSBS 1<<5 +#define BCE 1<<1 +#define FOUR_BIT 1 << 1 +#define CC 0x1 +#define TC 0x02 +#define OD 0x1 +#define ERR (1 << 15) +#define CMD_TIMEOUT (1 << 16) +#define DATA_TIMEOUT (1 << 20) +#define CMD_CRC (1 << 17) +#define DATA_CRC (1 << 21) +#define CARD_ERR (1 << 28) +#define STAT_CLEAR 0xFFFFFFFF +#define INIT_STREAM_CMD 0x00000000 +#define DUAL_VOLT_OCR_BIT 7 +#define SRC (1 << 25) +#define SRD (1 << 26) + +#define OMAP_MMC1_DEVID 1 +#define OMAP_MMC2_DEVID 2 +#define OMAP_MMC_DATADIR_NONE 0 +#define OMAP_MMC_DATADIR_READ 1 +#define OMAP_MMC_DATADIR_WRITE 2 +#define MMC_TIMEOUT_MS 20 +#define OMAP_MMC_MASTER_CLOCK 96000000 +#define DRIVER_NAME "mmci-omap" +/* + * slot_id is device id - 1, device id is a static value + * of 1 to represent device 1 etc.. + */ +#define mmc_slot(host) (host->pdata->slots[host->slot_id]) + +/* + * MMC Host controller read/write API's + */ +#define OMAP_HSMMC_READ(base, reg) \ + __raw_readl((base) + OMAP_HSMMC_##reg) + +#define OMAP_HSMMC_WRITE(base, reg, val) \ + __raw_writel((val), (base) + OMAP_HSMMC_##reg) + +struct mmc_omap_host { + struct device *dev; + struct mmc_host *mmc; + struct mmc_request *mrq; + struct mmc_command *cmd; + struct mmc_data *data; + struct clk *fclk; + struct clk *iclk; + struct clk *dbclk; + struct semaphore sem; + struct work_struct mmc_carddetect_work; + void __iomem *base; + resource_size_t mapbase; + unsigned int id; + unsigned int dma_len; + unsigned int dma_dir; + unsigned char bus_mode; + unsigned char datadir; + u32 *buffer; + u32 bytesleft; + int suspended; + int irq; + int carddetect; + int use_dma, dma_ch; + int initstr; + int slot_id; + int dbclk_enabled; + struct omap_mmc_platform_data *pdata; +}; + +/* + * Stop clock to the card + */ +static void omap_mmc_stop_clock(struct mmc_omap_host *host) +{ + OMAP_HSMMC_WRITE(host->base, SYSCTL, + OMAP_HSMMC_READ(host->base, SYSCTL) & ~CEN); + if ((OMAP_HSMMC_READ(host->base, SYSCTL) & CEN) != 0x0) + dev_dbg(mmc_dev(host->mmc), "MMC Clock is not stoped\n"); +} + +/* + * Send init stream sequence to card + * before sending IDLE command + */ +static void send_init_stream(struct mmc_omap_host *host) +{ + int reg = 0; + unsigned long timeout; + + disable_irq(host->irq); + OMAP_HSMMC_WRITE(host->base, CON, + OMAP_HSMMC_READ(host->base, CON) | INIT_STREAM); + OMAP_HSMMC_WRITE(host->base, CMD, INIT_STREAM_CMD); + + timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS); + while ((reg != CC) && time_before(jiffies, timeout)) + reg = OMAP_HSMMC_READ(host->base, STAT) & CC; + + OMAP_HSMMC_WRITE(host->base, CON, + OMAP_HSMMC_READ(host->base, CON) & ~INIT_STREAM); + enable_irq(host->irq); +} + +/* + * Configure the response type and send the cmd. + */ +static void +mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd, + struct mmc_data *data) +{ + int cmdreg = 0, resptype = 0, cmdtype = 0; + + dev_dbg(mmc_dev(host->mmc), "%s: CMD%d, argument 0x%08x\n", + mmc_hostname(host->mmc), cmd->opcode, cmd->arg); + host->cmd = cmd; + + /* + * Clear status bits and enable interrupts + */ + OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR); + OMAP_HSMMC_WRITE(host->base, ISE, INT_EN_MASK); + OMAP_HSMMC_WRITE(host->base, IE, INT_EN_MASK); + + if (cmd->flags & MMC_RSP_PRESENT) { + if (cmd->flags & MMC_RSP_136) + resptype = 1; + else + resptype = 2; + } + + /* + * Unlike OMAP1 controller, the cmdtype does not seem to be based on + * ac, bc, adtc, bcr. Only CMD12 needs a val of 0x3, rest 0x0. + */ + if (cmd->opcode == 12) + cmdtype = 0x3; + + cmdreg = (cmd->opcode << 24) | (resptype << 16) | (cmdtype << 22); + + if (data) { + cmdreg |= DP_SELECT | MSBS | BCE; + if (data->flags & MMC_DATA_READ) + cmdreg |= DDIR; + else + cmdreg &= ~(DDIR); + } + + if (host->use_dma) + cmdreg |= DMA_EN; + + OMAP_HSMMC_WRITE(host->base, ARG, cmd->arg); + OMAP_HSMMC_WRITE(host->base, CMD, cmdreg); +} + +/* + * Notify the transfer complete to MMC core + */ +static void +mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data) +{ + host->data = NULL; + + if (host->use_dma && host->dma_ch != -1) + dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->dma_len, + host->dma_dir); + + host->datadir = OMAP_MMC_DATADIR_NONE; + + if (!data->error) + data->bytes_xfered += data->blocks * (data->blksz); + else + data->bytes_xfered = 0; + + if (!data->stop) { + host->mrq = NULL; + mmc_request_done(host->mmc, data->mrq); + return; + } + mmc_omap_start_command(host, data->stop, NULL); +} + +/* + * Notify the core about command completion + */ +static void +mmc_omap_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd) +{ + host->cmd = NULL; + + if (cmd->flags & MMC_RSP_PRESENT) { + if (cmd->flags & MMC_RSP_136) { + /* response type 2 */ + cmd->resp[3] = OMAP_HSMMC_READ(host->base, RSP10); + cmd->resp[2] = OMAP_HSMMC_READ(host->base, RSP32); + cmd->resp[1] = OMAP_HSMMC_READ(host->base, RSP54); + cmd->resp[0] = OMAP_HSMMC_READ(host->base, RSP76); + } else { + /* response types 1, 1b, 3, 4, 5, 6 */ + cmd->resp[0] = OMAP_HSMMC_READ(host->base, RSP10); + } + } + if (host->data == NULL || cmd->error) { + host->mrq = NULL; + mmc_request_done(host->mmc, cmd->mrq); + } +} + +/* + * DMA clean up for command errors + */ +static void mmc_dma_cleanup(struct mmc_omap_host *host) +{ + host->data->error = -ETIMEDOUT; + + if (host->use_dma && host->dma_ch != -1) { + dma_unmap_sg(mmc_dev(host->mmc), host->data->sg, host->dma_len, + host->dma_dir); + omap_free_dma(host->dma_ch); + host->dma_ch = -1; + up(&host->sem); + } + host->data = NULL; + host->datadir = OMAP_MMC_DATADIR_NONE; +} + +/* + * MMC controller IRQ handler + */ +static irqreturn_t mmc_omap_irq(int irq, void *dev_id) +{ + struct mmc_omap_host *host = dev_id; + struct mmc_data *data; + int end_cmd = 0, end_trans = 0, status; + + if (host->cmd == NULL && host->data == NULL) { + OMAP_HSMMC_WRITE(host->base, STAT, + OMAP_HSMMC_READ(host->base, STAT)); + return IRQ_HANDLED; + } + + data = host->data; + status = OMAP_HSMMC_READ(host->base, STAT); + dev_dbg(mmc_dev(host->mmc), "IRQ Status is %x\n", status); + + if (status & ERR) { + if ((status & CMD_TIMEOUT) || + (status & CMD_CRC)) { + if (host->cmd) { + if (status & CMD_TIMEOUT) { + OMAP_HSMMC_WRITE(host->base, SYSCTL, + OMAP_HSMMC_READ(host->base, + SYSCTL) | SRC); + while (OMAP_HSMMC_READ(host->base, + SYSCTL) & SRC) ; + host->cmd->error = -ETIMEDOUT; + } else { + host->cmd->error = -EILSEQ; + } + end_cmd = 1; + } + if (host->data) + mmc_dma_cleanup(host); + } + if ((status & DATA_TIMEOUT) || + (status & DATA_CRC)) { + if (host->data) { + if (status & DATA_TIMEOUT) + mmc_dma_cleanup(host); + else + host->data->error = -EILSEQ; + end_trans = 1; + } + } + if (status & CARD_ERR) { + dev_dbg(mmc_dev(host->mmc), + "Ignoring card err CMD%d\n", host->cmd->opcode); + if (host->cmd) + end_cmd = 1; + if (host->data) + end_trans = 1; + } + } + + OMAP_HSMMC_WRITE(host->base, STAT, status); + + if (end_cmd || (status & CC)) + mmc_omap_cmd_done(host, host->cmd); + if (end_trans || (status & TC)) + mmc_omap_xfer_done(host, data); + + return IRQ_HANDLED; +} + +/* + * Switch MMC operating voltage + */ +static int omap_mmc_switch_opcond(struct mmc_omap_host *host, int vdd) +{ + u32 reg_val = 0; + int ret; + + /* Disable the clocks */ + clk_disable(host->fclk); + clk_disable(host->iclk); + clk_disable(host->dbclk); + + /* Turn the power off */ + ret = mmc_slot(host).set_power(host->dev, host->slot_id, 0, 0); + if (ret != 0) + goto err; + + /* Turn the power ON with given VDD 1.8 or 3.0v */ + ret = mmc_slot(host).set_power(host->dev, host->slot_id, 1, vdd); + if (ret != 0) + goto err; + + clk_enable(host->fclk); + clk_enable(host->iclk); + clk_enable(host->dbclk); + + OMAP_HSMMC_WRITE(host->base, HCTL, + OMAP_HSMMC_READ(host->base, HCTL) & SDVSCLR); + reg_val = OMAP_HSMMC_READ(host->base, HCTL); + /* + * If a MMC dual voltage card is detected, the set_ios fn calls + * this fn with VDD bit set for 1.8V. Upon card removal from the + * slot, mmc_omap_detect fn sets the VDD back to 3V. + * + * Only MMC1 supports 3.0V. MMC2 will not function if SDVS30 is + * set in HCTL. + */ + if (host->id == OMAP_MMC1_DEVID && (((1 << vdd) == MMC_VDD_32_33) || + ((1 << vdd) == MMC_VDD_33_34))) + reg_val |= SDVS30; + if ((1 << vdd) == MMC_VDD_165_195) + reg_val |= SDVS18; + + OMAP_HSMMC_WRITE(host->base, HCTL, reg_val); + + OMAP_HSMMC_WRITE(host->base, HCTL, + OMAP_HSMMC_READ(host->base, HCTL) | SDBP); + + return 0; +err: + dev_dbg(mmc_dev(host->mmc), "Unable to switch operating voltage\n"); + return ret; +} + +/* + * Work Item to notify the core about card insertion/removal + */ +static void mmc_omap_detect(struct work_struct *work) +{ + u16 vdd = 0; + struct mmc_omap_host *host = container_of(work, struct mmc_omap_host, + mmc_carddetect_work); + + if (host->carddetect) { + if (!(OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET)) { + /* + * Set the VDD back to 3V when the card is removed + * before the set_ios fn turns off the power. + */ + vdd = fls(host->mmc->ocr_avail) - 1; + if (omap_mmc_switch_opcond(host, vdd) != 0) + host->mmc->ios.vdd = vdd; + } + mmc_detect_change(host->mmc, (HZ * 200) / 1000); + } else { + OMAP_HSMMC_WRITE(host->base, SYSCTL, + OMAP_HSMMC_READ(host->base, SYSCTL) | SRD); + while (OMAP_HSMMC_READ(host->base, SYSCTL) & SRD) ; + mmc_detect_change(host->mmc, (HZ * 50) / 1000); + } +} + +/* + * ISR for handling card insertion and removal + */ +static irqreturn_t omap_mmc_cd_handler(int irq, void *dev_id) +{ + struct mmc_omap_host *host = (struct mmc_omap_host *)dev_id; + + host->carddetect = mmc_slot(host).card_detect(irq); + schedule_work(&host->mmc_carddetect_work); + + return IRQ_HANDLED; +} + +/* + * DMA call back function + */ +static void mmc_omap_dma_cb(int lch, u16 ch_status, void *data) +{ + struct mmc_omap_host *host = data; + + if (ch_status & OMAP2_DMA_MISALIGNED_ERR_IRQ) + dev_dbg(mmc_dev(host->mmc), "MISALIGNED_ADRS_ERR\n"); + + if (host->dma_ch < 0) + return; + + omap_free_dma(host->dma_ch); + host->dma_ch = -1; + /* + * DMA Callback: run in interrupt context. + * mutex_unlock will through a kernel warning if used. + */ + up(&host->sem); +} + +/* + * Configure dma src and destination parameters + */ +static int mmc_omap_config_dma_param(int sync_dir, struct mmc_omap_host *host, + struct mmc_data *data) +{ + if (sync_dir == 0) { + omap_set_dma_dest_params(host->dma_ch, 0, + OMAP_DMA_AMODE_CONSTANT, + (host->mapbase + OMAP_HSMMC_DATA), 0, 0); + omap_set_dma_src_params(host->dma_ch, 0, + OMAP_DMA_AMODE_POST_INC, + sg_dma_address(&data->sg[0]), 0, 0); + } else { + omap_set_dma_src_params(host->dma_ch, 0, + OMAP_DMA_AMODE_CONSTANT, + (host->mapbase + OMAP_HSMMC_DATA), 0, 0); + omap_set_dma_dest_params(host->dma_ch, 0, + OMAP_DMA_AMODE_POST_INC, + sg_dma_address(&data->sg[0]), 0, 0); + } + return 0; +} +/* + * Routine to configure and start DMA for the MMC card + */ +static int +mmc_omap_start_dma_transfer(struct mmc_omap_host *host, struct mmc_request *req) +{ + int sync_dev, sync_dir = 0; + int dma_ch = 0, ret = 0, err = 1; + struct mmc_data *data = req->data; + + /* + * If for some reason the DMA transfer is still active, + * we wait for timeout period and free the dma + */ + if (host->dma_ch != -1) { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(100); + if (down_trylock(&host->sem)) { + omap_free_dma(host->dma_ch); + host->dma_ch = -1; + up(&host->sem); + return err; + } + } else { + if (down_trylock(&host->sem)) + return err; + } + + if (!(data->flags & MMC_DATA_WRITE)) { + host->dma_dir = DMA_FROM_DEVICE; + if (host->id == OMAP_MMC1_DEVID) + sync_dev = OMAP24XX_DMA_MMC1_RX; + else + sync_dev = OMAP24XX_DMA_MMC2_RX; + } else { + host->dma_dir = DMA_TO_DEVICE; + if (host->id == OMAP_MMC1_DEVID) + sync_dev = OMAP24XX_DMA_MMC1_TX; + else + sync_dev = OMAP24XX_DMA_MMC2_TX; + } + + ret = omap_request_dma(sync_dev, "MMC/SD", mmc_omap_dma_cb, + host, &dma_ch); + if (ret != 0) { + dev_dbg(mmc_dev(host->mmc), + "%s: omap_request_dma() failed with %d\n", + mmc_hostname(host->mmc), ret); + return ret; + } + + host->dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, + data->sg_len, host->dma_dir); + host->dma_ch = dma_ch; + + if (!(data->flags & MMC_DATA_WRITE)) + mmc_omap_config_dma_param(1, host, data); + else + mmc_omap_config_dma_param(0, host, data); + + if ((data->blksz % 4) == 0) + omap_set_dma_transfer_params(dma_ch, OMAP_DMA_DATA_TYPE_S32, + (data->blksz / 4), data->blocks, OMAP_DMA_SYNC_FRAME, + sync_dev, sync_dir); + else + /* REVISIT: The MMC buffer increments only when MSB is written. + * Return error for blksz which is non multiple of four. + */ + return -EINVAL; + + omap_start_dma(dma_ch); + return 0; +} + +/* + * Configure block length for MMC/SD cards and initiate the transfer. + */ +static int +mmc_omap_prepare_data(struct mmc_omap_host *host, struct mmc_request *req) +{ + int ret; + host->data = req->data; + + if (req->data == NULL) { + host->datadir = OMAP_MMC_DATADIR_NONE; + OMAP_HSMMC_WRITE(host->base, BLK, 0); + return 0; + } + + OMAP_HSMMC_WRITE(host->base, BLK, (req->data->blksz) + | (req->data->blocks << 16)); + + host->datadir = (req->data->flags & MMC_DATA_WRITE) ? + OMAP_MMC_DATADIR_WRITE : OMAP_MMC_DATADIR_READ; + + if (host->use_dma) { + ret = mmc_omap_start_dma_transfer(host, req); + if (ret != 0) { + dev_dbg(mmc_dev(host->mmc), "MMC start dma failure\n"); + return ret; + } + } + return 0; +} + +/* + * Request function. for read/write operation + */ +static void omap_mmc_request(struct mmc_host *mmc, struct mmc_request *req) +{ + struct mmc_omap_host *host = mmc_priv(mmc); + + WARN_ON(host->mrq != NULL); + host->mrq = req; + mmc_omap_prepare_data(host, req); + mmc_omap_start_command(host, req->cmd, req->data); +} + + +/* Routine to configure clock values. Exposed API to core */ +static void omap_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct mmc_omap_host *host = mmc_priv(mmc); + u16 dsor = 0; + unsigned long regval; + unsigned long timeout; + + switch (ios->power_mode) { + case MMC_POWER_OFF: + mmc_slot(host).set_power(host->dev, host->slot_id, 0, 0); + break; + case MMC_POWER_UP: + mmc_slot(host).set_power(host->dev, host->slot_id, 1, ios->vdd); + break; + } + + switch (mmc->ios.bus_width) { + case MMC_BUS_WIDTH_4: + OMAP_HSMMC_WRITE(host->base, HCTL, + OMAP_HSMMC_READ(host->base, HCTL) | FOUR_BIT); + break; + case MMC_BUS_WIDTH_1: + OMAP_HSMMC_WRITE(host->base, HCTL, + OMAP_HSMMC_READ(host->base, HCTL) & ~FOUR_BIT); + break; + } + + if (host->id == OMAP_MMC1_DEVID) { + /* Only MMC1 can operate at 3V/1.8V */ + if ((OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET) && + (ios->vdd == DUAL_VOLT_OCR_BIT)) { + /* + * The mmc_select_voltage fn of the core does + * not seem to set the power_mode to + * MMC_POWER_UP upon recalculating the voltage. + * vdd 1.8v. + */ + if (omap_mmc_switch_opcond(host, ios->vdd) != 0) + dev_dbg(mmc_dev(host->mmc), + "Switch operation failed\n"); + } + } + + if (ios->clock) { + dsor = OMAP_MMC_MASTER_CLOCK / ios->clock; + if (dsor < 1) + dsor = 1; + + if (OMAP_MMC_MASTER_CLOCK / dsor > ios->clock) + dsor++; + + if (dsor > 250) + dsor = 250; + } + omap_mmc_stop_clock(host); + regval = OMAP_HSMMC_READ(host->base, SYSCTL); + regval = regval & ~(CLKD_MASK); + regval = regval | (dsor << 6) | (DTO << 16); + OMAP_HSMMC_WRITE(host->base, SYSCTL, regval); + OMAP_HSMMC_WRITE(host->base, SYSCTL, + OMAP_HSMMC_READ(host->base, SYSCTL) | ICE); + + /* Wait till the ICS bit is set */ + timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS); + while ((OMAP_HSMMC_READ(host->base, SYSCTL) & ICS) != 0x2 + && time_before(jiffies, timeout)) + msleep(1); + + OMAP_HSMMC_WRITE(host->base, SYSCTL, + OMAP_HSMMC_READ(host->base, SYSCTL) | CEN); + + if (ios->power_mode == MMC_POWER_ON) + send_init_stream(host); + + if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN) + OMAP_HSMMC_WRITE(host->base, CON, + OMAP_HSMMC_READ(host->base, CON) | OD); +} +/* NOTE: Read only switch not supported yet */ +static struct mmc_host_ops mmc_omap_ops = { + .request = omap_mmc_request, + .set_ios = omap_mmc_set_ios, +}; + +static int __init omap_mmc_probe(struct platform_device *pdev) +{ + struct omap_mmc_platform_data *pdata = pdev->dev.platform_data; + struct mmc_host *mmc; + struct mmc_omap_host *host = NULL; + struct resource *res; + int ret = 0, irq; + u32 hctl, capa; + + if (pdata == NULL) { + dev_err(&pdev->dev, "Platform Data is missing\n"); + return -ENXIO; + } + + if (pdata->nr_slots == 0) { + dev_err(&pdev->dev, "No Slots\n"); + return -ENXIO; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq = platform_get_irq(pdev, 0); + if (res == NULL || irq < 0) + return -ENXIO; + + res = request_mem_region(res->start, res->end - res->start + 1, + pdev->name); + if (res == NULL) + return -EBUSY; + + mmc = mmc_alloc_host(sizeof(struct mmc_omap_host), &pdev->dev); + if (!mmc) { + ret = -ENOMEM; + goto err; + } + + host = mmc_priv(mmc); + host->mmc = mmc; + host->pdata = pdata; + host->use_dma = 1; + host->dma_ch = -1; + host->irq = irq; + host->id = pdev->id; + host->slot_id = 0; + host->mapbase = res->start; + host->base = ioremap(host->mapbase, SZ_4K); + mmc->ops = &mmc_omap_ops; + mmc->f_min = 400000; + mmc->f_max = 52000000; + + sema_init(&host->sem, 1); + + host->iclk = clk_get(&pdev->dev, "mmchs_ick"); + if (IS_ERR(host->iclk)) { + ret = PTR_ERR(host->iclk); + host->iclk = NULL; + goto err1; + } + host->fclk = clk_get(&pdev->dev, "mmchs_fck"); + if (IS_ERR(host->fclk)) { + ret = PTR_ERR(host->fclk); + host->fclk = NULL; + clk_put(host->iclk); + goto err1; + } + + if (clk_enable(host->fclk) != 0) { + clk_put(host->iclk); + clk_put(host->fclk); + goto err1; + } + + if (clk_enable(host->iclk) != 0) { + clk_disable(host->fclk); + clk_put(host->iclk); + clk_put(host->fclk); + goto err1; + } + + host->dbclk = clk_get(&pdev->dev, "mmchsdb_fck"); + /* + * MMC can still work without debounce clock. + */ + if (IS_ERR(host->dbclk)) + dev_dbg(mmc_dev(host->mmc), "Failed to get debounce clock\n"); + else + if (clk_enable(host->dbclk) != 0) + dev_dbg(mmc_dev(host->mmc), "Enabling debounce" + " clk failed\n"); + else + host->dbclk_enabled = 1; + +#ifdef CONFIG_MMC_BLOCK_BOUNCE + mmc->max_phys_segs = 1; + mmc->max_hw_segs = 1; +#endif + mmc->max_blk_size = 512; /* Block Length at max can be 1024 */ + mmc->max_blk_count = 0xFFFF; /* No. of Blocks is 16 bits */ + mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count; + mmc->max_seg_size = mmc->max_req_size; + + mmc->ocr_avail = mmc_slot(host).ocr_mask; + mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED; + + if (pdata->conf.wire4) + mmc->caps |= MMC_CAP_4_BIT_DATA; + + /* Only MMC1 supports 3.0V */ + if (host->id == OMAP_MMC1_DEVID) { + hctl = SDVS30; + capa = VS30 | VS18; + } else { + hctl = SDVS18; + capa = VS18; + } + + OMAP_HSMMC_WRITE(host->base, HCTL, + OMAP_HSMMC_READ(host->base, HCTL) | hctl); + + OMAP_HSMMC_WRITE(host->base, CAPA, + OMAP_HSMMC_READ(host->base, CAPA) | capa); + + /* Set the controller to AUTO IDLE mode */ + OMAP_HSMMC_WRITE(host->base, SYSCONFIG, + OMAP_HSMMC_READ(host->base, SYSCONFIG) | AUTOIDLE); + + /* Set SD bus power bit */ + OMAP_HSMMC_WRITE(host->base, HCTL, + OMAP_HSMMC_READ(host->base, HCTL) | SDBP); + + /* Request IRQ for MMC operations */ + ret = request_irq(host->irq, mmc_omap_irq, IRQF_DISABLED, pdev->name, + host); + if (ret) { + dev_dbg(mmc_dev(host->mmc), "Unable to grab HSMMC IRQ\n"); + goto irq_err; + } + + /* Request IRQ for card detect */ + if ((mmc_slot(host).card_detect_irq) && (mmc_slot(host).card_detect)) { + ret = request_irq(mmc_slot(host).card_detect_irq, + omap_mmc_cd_handler, IRQF_DISABLED, "MMC CD", + host); + if (ret) { + dev_dbg(mmc_dev(host->mmc), + "Unable to grab MMC CD IRQ"); + free_irq(host->irq, host); + goto irq_err; + } + } + + INIT_WORK(&host->mmc_carddetect_work, mmc_omap_detect); + if (pdata->init != NULL) { + if (pdata->init(&pdev->dev) != 0) { + free_irq(mmc_slot(host).card_detect_irq, host); + free_irq(host->irq, host); + goto irq_err; + } + } + + OMAP_HSMMC_WRITE(host->base, ISE, INT_EN_MASK); + OMAP_HSMMC_WRITE(host->base, IE, INT_EN_MASK); + + platform_set_drvdata(pdev, host); + mmc_add_host(mmc); + + return 0; + +irq_err: + dev_dbg(mmc_dev(host->mmc), "Unable to configure MMC IRQs\n"); + clk_disable(host->fclk); + clk_disable(host->iclk); + clk_put(host->fclk); + clk_put(host->iclk); + if (host->dbclk_enabled) { + clk_disable(host->dbclk); + clk_put(host->dbclk); + } + +err1: + iounmap(host->base); +err: + dev_dbg(mmc_dev(host->mmc), "Probe Failed\n"); + release_mem_region(res->start, res->end - res->start + 1); + if (host) + mmc_free_host(mmc); + return ret; +} + +static int omap_mmc_remove(struct platform_device *pdev) +{ + struct mmc_omap_host *host = platform_get_drvdata(pdev); + struct resource *res; + u16 vdd = 0; + + if (!(OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET)) { + /* + * Set the vdd back to 3V, + * applicable for dual volt support. + */ + vdd = fls(host->mmc->ocr_avail) - 1; + if (omap_mmc_switch_opcond(host, vdd) != 0) + host->mmc->ios.vdd = vdd; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res) + release_mem_region(res->start, res->end - res->start + 1); + + platform_set_drvdata(pdev, NULL); + if (host) { + mmc_remove_host(host->mmc); + if (host->pdata->cleanup) + host->pdata->cleanup(&pdev->dev); + free_irq(host->irq, host); + if (mmc_slot(host).card_detect_irq) + free_irq(mmc_slot(host).card_detect_irq, host); + flush_scheduled_work(); + + clk_disable(host->fclk); + clk_disable(host->iclk); + clk_put(host->fclk); + clk_put(host->iclk); + if (host->dbclk_enabled) { + clk_disable(host->dbclk); + clk_put(host->dbclk); + } + + mmc_free_host(host->mmc); + iounmap(host->base); + } + + return 0; +} + +#ifdef CONFIG_PM +static int omap_mmc_suspend(struct platform_device *pdev, pm_message_t state) +{ + int ret = 0; + struct mmc_omap_host *host = platform_get_drvdata(pdev); + + if (host && host->suspended) + return 0; + + if (host) { + ret = mmc_suspend_host(host->mmc, state); + if (ret == 0) { + host->suspended = 1; + + OMAP_HSMMC_WRITE(host->base, ISE, 0); + OMAP_HSMMC_WRITE(host->base, IE, 0); + + ret = host->pdata->suspend(&pdev->dev, host->slot_id); + if (ret) + dev_dbg(mmc_dev(host->mmc), + "Unable to handle MMC board" + " level suspend\n"); + + if (!(OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET)) { + OMAP_HSMMC_WRITE(host->base, HCTL, + OMAP_HSMMC_READ(host->base, HCTL) + & SDVSCLR); + OMAP_HSMMC_WRITE(host->base, HCTL, + OMAP_HSMMC_READ(host->base, HCTL) + | SDVS30); + OMAP_HSMMC_WRITE(host->base, HCTL, + OMAP_HSMMC_READ(host->base, HCTL) + | SDBP); + } + + clk_disable(host->fclk); + clk_disable(host->iclk); + clk_disable(host->dbclk); + } + + } + return ret; +} + +/* Routine to resume the MMC device */ +static int omap_mmc_resume(struct platform_device *pdev) +{ + int ret = 0; + struct mmc_omap_host *host = platform_get_drvdata(pdev); + + if (host && !host->suspended) + return 0; + + if (host) { + + ret = clk_enable(host->fclk); + if (ret) + goto clk_en_err; + + ret = clk_enable(host->iclk); + if (ret) { + clk_disable(host->fclk); + clk_put(host->fclk); + goto clk_en_err; + } + + if (clk_enable(host->dbclk) != 0) + dev_dbg(mmc_dev(host->mmc), + "Enabling debounce clk failed\n"); + + ret = host->pdata->resume(&pdev->dev, host->slot_id); + if (ret) + dev_dbg(mmc_dev(host->mmc), + "Unmask interrupt failed\n"); + + /* Notify the core to resume the host */ + ret = mmc_resume_host(host->mmc); + if (ret == 0) + host->suspended = 0; + } + + return ret; + +clk_en_err: + dev_dbg(mmc_dev(host->mmc), + "Failed to enable MMC clocks during resume\n"); + return ret; +} + +#else +#define omap_mmc_suspend NULL +#define omap_mmc_resume NULL +#endif + +static struct platform_driver omap_mmc_driver = { + .probe = omap_mmc_probe, + .remove = omap_mmc_remove, + .suspend = omap_mmc_suspend, + .resume = omap_mmc_resume, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init omap_mmc_init(void) +{ + /* Register the MMC driver */ + return platform_driver_register(&omap_mmc_driver); +} + +static void __exit omap_mmc_cleanup(void) +{ + /* Unregister MMC driver */ + platform_driver_unregister(&omap_mmc_driver); +} + +module_init(omap_mmc_init); +module_exit(omap_mmc_cleanup); + +MODULE_DESCRIPTION("OMAP High Speed Multimedia Card driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRIVER_NAME); +MODULE_AUTHOR("Texas Instruments Inc"); diff --cc drivers/mtd/maps/omap-toto-flash.c index 0a60ebbc217,0a60ebbc217..2cbd75e1b2d --- a/drivers/mtd/maps/omap-toto-flash.c +++ b/drivers/mtd/maps/omap-toto-flash.c @@@ -17,7 -17,7 +17,7 @@@ #include #include --#include ++#include #include diff --cc drivers/mtd/nand/omap-hw.c index 16029777c88,00000000000..c598d9d58b7 mode 100644,000000..100644 --- a/drivers/mtd/nand/omap-hw.c +++ b/drivers/mtd/nand/omap-hw.c @@@ -1,860 -1,0 +1,860 @@@ +/* + * drivers/mtd/nand/omap-hw.c + * + * This is the MTD driver for OMAP1710 internal HW NAND controller. + * + * Copyright (C) 2004-2006 Nokia Corporation + * + * Author: Jarkko Lavinen and + * Juha Yrjölä + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; see the file COPYING. If not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + - #include - #include ++#include ++#include + +#define NAND_BASE 0xfffbcc00 +#define NND_REVISION 0x00 +#define NND_ACCESS 0x04 +#define NND_ADDR_SRC 0x08 +#define NND_CTRL 0x10 +#define NND_MASK 0x14 +#define NND_STATUS 0x18 +#define NND_READY 0x1c +#define NND_COMMAND 0x20 +#define NND_COMMAND_SEC 0x24 +#define NND_ECC_SELECT 0x28 +#define NND_ECC_START 0x2c +#define NND_ECC_9 0x4c +#define NND_RESET 0x50 +#define NND_FIFO 0x54 +#define NND_FIFOCTRL 0x58 +#define NND_PSC_CLK 0x5c +#define NND_SYSTEST 0x60 +#define NND_SYSCFG 0x64 +#define NND_SYSSTATUS 0x68 +#define NND_FIFOTEST1 0x6c +#define NND_FIFOTEST2 0x70 +#define NND_FIFOTEST3 0x74 +#define NND_FIFOTEST4 0x78 +#define NND_PSC1_CLK 0x8c +#define NND_PSC2_CLK 0x90 + + +#define NND_CMD_READ1_LOWER 0x00 +#define NND_CMD_WRITE1_LOWER 0x00 +#define NND_CMD_READ1_UPPER 0x01 +#define NND_CMD_WRITE1_UPPER 0x01 +#define NND_CMD_PROGRAM_END 0x10 +#define NND_CMD_READ2_SPARE 0x50 +#define NND_CMD_WRITE2_SPARE 0x50 +#define NND_CMD_ERASE 0x60 +#define NND_CMD_STATUS 0x70 +#define NND_CMD_PROGRAM 0x80 +#define NND_CMD_READ_ID 0x90 +#define NND_CMD_ERASE_END 0xD0 +#define NND_CMD_RESET 0xFF + + +#define NAND_Ecc_P1e (1 << 0) +#define NAND_Ecc_P2e (1 << 1) +#define NAND_Ecc_P4e (1 << 2) +#define NAND_Ecc_P8e (1 << 3) +#define NAND_Ecc_P16e (1 << 4) +#define NAND_Ecc_P32e (1 << 5) +#define NAND_Ecc_P64e (1 << 6) +#define NAND_Ecc_P128e (1 << 7) +#define NAND_Ecc_P256e (1 << 8) +#define NAND_Ecc_P512e (1 << 9) +#define NAND_Ecc_P1024e (1 << 10) +#define NAND_Ecc_P2048e (1 << 11) + +#define NAND_Ecc_P1o (1 << 16) +#define NAND_Ecc_P2o (1 << 17) +#define NAND_Ecc_P4o (1 << 18) +#define NAND_Ecc_P8o (1 << 19) +#define NAND_Ecc_P16o (1 << 20) +#define NAND_Ecc_P32o (1 << 21) +#define NAND_Ecc_P64o (1 << 22) +#define NAND_Ecc_P128o (1 << 23) +#define NAND_Ecc_P256o (1 << 24) +#define NAND_Ecc_P512o (1 << 25) +#define NAND_Ecc_P1024o (1 << 26) +#define NAND_Ecc_P2048o (1 << 27) + +#define TF(value) (value ? 1 : 0) + +#define P2048e(a) (TF(a & NAND_Ecc_P2048e) << 0 ) +#define P2048o(a) (TF(a & NAND_Ecc_P2048o) << 1 ) +#define P1e(a) (TF(a & NAND_Ecc_P1e) << 2 ) +#define P1o(a) (TF(a & NAND_Ecc_P1o) << 3 ) +#define P2e(a) (TF(a & NAND_Ecc_P2e) << 4 ) +#define P2o(a) (TF(a & NAND_Ecc_P2o) << 5 ) +#define P4e(a) (TF(a & NAND_Ecc_P4e) << 6 ) +#define P4o(a) (TF(a & NAND_Ecc_P4o) << 7 ) + +#define P8e(a) (TF(a & NAND_Ecc_P8e) << 0 ) +#define P8o(a) (TF(a & NAND_Ecc_P8o) << 1 ) +#define P16e(a) (TF(a & NAND_Ecc_P16e) << 2 ) +#define P16o(a) (TF(a & NAND_Ecc_P16o) << 3 ) +#define P32e(a) (TF(a & NAND_Ecc_P32e) << 4 ) +#define P32o(a) (TF(a & NAND_Ecc_P32o) << 5 ) +#define P64e(a) (TF(a & NAND_Ecc_P64e) << 6 ) +#define P64o(a) (TF(a & NAND_Ecc_P64o) << 7 ) + +#define P128e(a) (TF(a & NAND_Ecc_P128e) << 0 ) +#define P128o(a) (TF(a & NAND_Ecc_P128o) << 1 ) +#define P256e(a) (TF(a & NAND_Ecc_P256e) << 2 ) +#define P256o(a) (TF(a & NAND_Ecc_P256o) << 3 ) +#define P512e(a) (TF(a & NAND_Ecc_P512e) << 4 ) +#define P512o(a) (TF(a & NAND_Ecc_P512o) << 5 ) +#define P1024e(a) (TF(a & NAND_Ecc_P1024e) << 6 ) +#define P1024o(a) (TF(a & NAND_Ecc_P1024o) << 7 ) + +#define P8e_s(a) (TF(a & NAND_Ecc_P8e) << 0 ) +#define P8o_s(a) (TF(a & NAND_Ecc_P8o) << 1 ) +#define P16e_s(a) (TF(a & NAND_Ecc_P16e) << 2 ) +#define P16o_s(a) (TF(a & NAND_Ecc_P16o) << 3 ) +#define P1e_s(a) (TF(a & NAND_Ecc_P1e) << 4 ) +#define P1o_s(a) (TF(a & NAND_Ecc_P1o) << 5 ) +#define P2e_s(a) (TF(a & NAND_Ecc_P2e) << 6 ) +#define P2o_s(a) (TF(a & NAND_Ecc_P2o) << 7 ) + +#define P4e_s(a) (TF(a & NAND_Ecc_P4e) << 0 ) +#define P4o_s(a) (TF(a & NAND_Ecc_P4o) << 1 ) + +extern struct nand_oobinfo jffs2_oobinfo; + +/* + * MTD structure for OMAP board + */ +static struct mtd_info *omap_mtd; +static struct clk *omap_nand_clk; +static int omap_nand_dma_ch; +static struct completion omap_nand_dma_comp; +static unsigned long omap_nand_base = io_p2v(NAND_BASE); + +static inline u32 nand_read_reg(int idx) +{ + return __raw_readl(omap_nand_base + idx); +} + +static inline void nand_write_reg(int idx, u32 val) +{ + __raw_writel(val, omap_nand_base + idx); +} + +static inline u8 nand_read_reg8(int idx) +{ + return __raw_readb(omap_nand_base + idx); +} + +static inline void nand_write_reg8(int idx, u8 val) +{ + __raw_writeb(val, omap_nand_base + idx); +} + +static void omap_nand_select_chip(struct mtd_info *mtd, int chip) +{ + u32 l; + + switch(chip) { + case -1: + l = nand_read_reg(NND_CTRL); + l |= (1 << 8) | (1 << 10) | (1 << 12) | (1 << 14); + nand_write_reg(NND_CTRL, l); + break; + case 0: + /* Also CS1, CS2, CS4 would be available */ + l = nand_read_reg(NND_CTRL); + l &= ~(1 << 8); + nand_write_reg(NND_CTRL, l); + break; + default: + BUG(); + } +} + +static void nand_dma_cb(int lch, u16 ch_status, void *data) +{ + complete((struct completion *) data); +} + +static void omap_nand_dma_transfer(struct mtd_info *mtd, void *addr, + unsigned int u32_count, int is_write) +{ + const int block_size = 16; + unsigned int block_count, len; + int dma_ch; + unsigned long fifo_reg, timeout, jiffies_before, jiffies_spent; + static unsigned long max_jiffies = 0; + + dma_ch = omap_nand_dma_ch; + block_count = u32_count * 4 / block_size; + nand_write_reg(NND_STATUS, 0x0f); + nand_write_reg(NND_FIFOCTRL, (block_size << 24) | block_count); + fifo_reg = NAND_BASE + NND_FIFO; + if (is_write) { + omap_set_dma_dest_params(dma_ch, OMAP_DMA_PORT_TIPB, + OMAP_DMA_AMODE_CONSTANT, fifo_reg, + 0, 0); + omap_set_dma_src_params(dma_ch, OMAP_DMA_PORT_EMIFF, + OMAP_DMA_AMODE_POST_INC, + virt_to_phys(addr), + 0, 0); +// omap_set_dma_src_burst_mode(dma_ch, OMAP_DMA_DATA_BURST_4); + /* Set POSTWRITE bit */ + nand_write_reg(NND_CTRL, nand_read_reg(NND_CTRL) | (1 << 16)); + } else { + omap_set_dma_src_params(dma_ch, OMAP_DMA_PORT_TIPB, + OMAP_DMA_AMODE_CONSTANT, fifo_reg, + 0, 0); + omap_set_dma_dest_params(dma_ch, OMAP_DMA_PORT_EMIFF, + OMAP_DMA_AMODE_POST_INC, + virt_to_phys(addr), + 0, 0); +// omap_set_dma_dest_burst_mode(dma_ch, OMAP_DMA_DATA_BURST_8); + /* Set PREFETCH bit */ + nand_write_reg(NND_CTRL, nand_read_reg(NND_CTRL) | (1 << 17)); + } + omap_set_dma_transfer_params(dma_ch, OMAP_DMA_DATA_TYPE_S32, block_size / 4, + block_count, OMAP_DMA_SYNC_FRAME, + 0, 0); + init_completion(&omap_nand_dma_comp); + + len = u32_count << 2; + dma_cache_maint(addr, len, DMA_TO_DEVICE); + omap_start_dma(dma_ch); + jiffies_before = jiffies; + timeout = wait_for_completion_timeout(&omap_nand_dma_comp, + msecs_to_jiffies(1000)); + jiffies_spent = (unsigned long)((long)jiffies - (long)jiffies_before); + if (jiffies_spent > max_jiffies) + max_jiffies = jiffies_spent; + + if (timeout == 0) { + printk(KERN_WARNING "omap-hw-nand: DMA timeout after %u ms, max. seen latency %u ms\n", + jiffies_to_msecs(jiffies_spent), + jiffies_to_msecs(max_jiffies)); + } + if (!is_write) + dma_cache_maint(addr, len, DMA_FROM_DEVICE); + + nand_write_reg(NND_CTRL, nand_read_reg(NND_CTRL) & ~((1 << 16) | (1 << 17))); +} + +static void fifo_read(u32 *out, unsigned int len) +{ + const int block_size = 16; + unsigned long status_reg, fifo_reg; + int c; + + status_reg = omap_nand_base + NND_STATUS; + fifo_reg = omap_nand_base + NND_FIFO; + len = len * 4 / block_size; + nand_write_reg(NND_FIFOCTRL, (block_size << 24) | len); + nand_write_reg(NND_STATUS, 0x0f); + nand_write_reg(NND_CTRL, nand_read_reg(NND_CTRL) | (1 << 17)); + c = block_size / 4; + while (len--) { + int i; + + while ((__raw_readl(status_reg) & (1 << 2)) == 0); + __raw_writel(0x0f, status_reg); + for (i = 0; i < c; i++) { + u32 l = __raw_readl(fifo_reg); + *out++ = l; + } + } + nand_write_reg(NND_CTRL, nand_read_reg(NND_CTRL) & ~(1 << 17)); + nand_write_reg(NND_STATUS, 0x0f); +} + +static void omap_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len) +{ + unsigned long access_reg; + + if (likely(((unsigned long) buf & 3) == 0 && (len & 3) == 0)) { + int u32_count = len >> 2; + u32 *dest = (u32 *) buf; + /* If the transfer is big enough and the length divisible by + * 16, we try to use DMA transfer, or FIFO copy in case of + * DMA failure (e.g. all channels busy) */ + if (u32_count > 64 && (u32_count & 3) == 0) { + if (omap_nand_dma_ch >= 0) { + omap_nand_dma_transfer(mtd, buf, u32_count, 0); + return; + } + /* In case of an error, fallback to FIFO copy */ + fifo_read((u32 *) buf, u32_count); + return; + } + access_reg = omap_nand_base + NND_ACCESS; + /* Small buffers we just read directly */ + while (u32_count--) + *dest++ = __raw_readl(access_reg); + } else { + /* If we're not word-aligned, we use byte copy */ + access_reg = omap_nand_base + NND_ACCESS; + while (len--) + *buf++ = __raw_readb(access_reg); + } +} + +static void omap_nand_write_buf(struct mtd_info *mtd, const u_char *buf, int len) +{ + if (likely(((unsigned long) buf & 3) == 0 && (len & 3) == 0)) { + const u32 *src = (const u32 *) buf; + + len >>= 2; +#if 0 + /* If the transfer is big enough and length divisible by 16, + * we try to use DMA transfer. */ + if (len > 256 / 4 && (len & 3) == 0) { + if (omap_nand_dma_transfer(mtd, (void *) buf, len, 1) == 0) + return; + /* In case of an error, fallback to CPU copy */ + } +#endif + while (len--) + nand_write_reg(NND_ACCESS, *src++); + } else { + while (len--) + nand_write_reg8(NND_ACCESS, *buf++); + } +} + +static int omap_nand_verify_buf(struct mtd_info *mtd, const u_char *buf, int len) +{ + if (likely(((unsigned long) buf & 3) == 0 && (len & 3) == 0)) { + const u32 *dest = (const u32 *) buf; + len >>= 2; + while (len--) + if (*dest++ != nand_read_reg(NND_ACCESS)) + return -EFAULT; + } else { + while (len--) + if (*buf++ != nand_read_reg8(NND_ACCESS)) + return -EFAULT; + } + return 0; +} + +static u_char omap_nand_read_byte(struct mtd_info *mtd) +{ + return nand_read_reg8(NND_ACCESS); +} + +static int omap_nand_dev_ready(struct mtd_info *mtd) +{ + u32 l; + + l = nand_read_reg(NND_READY); + return l & 0x01; +} + +static int nand_write_command(u8 cmd, u32 addr, int addr_valid) +{ + if (addr_valid) { + nand_write_reg(NND_ADDR_SRC, addr); + nand_write_reg8(NND_COMMAND, cmd); + } else { + nand_write_reg(NND_ADDR_SRC, 0); + nand_write_reg8(NND_COMMAND_SEC, cmd); + } + while (!omap_nand_dev_ready(NULL)); + return 0; +} + +/* + * Send command to NAND device + */ +static void omap_nand_command(struct mtd_info *mtd, unsigned command, int column, int page_addr) +{ + struct nand_chip *this = mtd->priv; + + /* + * Write out the command to the device. + */ + if (command == NAND_CMD_SEQIN) { + int readcmd; + + if (column >= mtd->writesize) { + /* OOB area */ + column -= mtd->writesize; + readcmd = NAND_CMD_READOOB; + } else if (column < 256) { + /* First 256 bytes --> READ0 */ + readcmd = NAND_CMD_READ0; + } else { + column -= 256; + readcmd = NAND_CMD_READ1; + } + nand_write_command(readcmd, 0, 0); + } + switch (command) { + case NAND_CMD_RESET: + case NAND_CMD_PAGEPROG: + case NAND_CMD_STATUS: + case NAND_CMD_ERASE2: + nand_write_command(command, 0, 0); + break; + case NAND_CMD_ERASE1: + nand_write_command(command, ((page_addr & 0xFFFFFF00) << 1) | (page_addr & 0XFF), 1); + break; + default: + nand_write_command(command, (page_addr << this->page_shift) | column, 1); + } +} + +static void omap_nand_command_lp(struct mtd_info *mtd, unsigned command, int column, int page_addr) +{ + struct nand_chip *this = mtd->priv; + + if (command == NAND_CMD_READOOB) { + column += mtd->writesize; + command = NAND_CMD_READ0; + } + switch (command) { + case NAND_CMD_RESET: + case NAND_CMD_PAGEPROG: + case NAND_CMD_STATUS: + case NAND_CMD_ERASE2: + nand_write_command(command, 0, 0); + break; + case NAND_CMD_ERASE1: + nand_write_command(command, page_addr << this->page_shift >> 11, 1); + break; + default: + nand_write_command(command, (page_addr << 16) | column, 1); + } + if (command == NAND_CMD_READ0) + nand_write_command(NAND_CMD_READSTART, 0, 0); +} + +/* + * Generate non-inverted ECC bytes. + * + * Using noninverted ECC can be considered ugly since writing a blank + * page ie. padding will clear the ECC bytes. This is no problem as long + * nobody is trying to write data on the seemingly unused page. + * + * Reading an erased page will produce an ECC mismatch between + * generated and read ECC bytes that has to be dealt with separately. + */ +static int omap_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code) +{ + u32 l; + int reg; + int n; + struct nand_chip *this = mtd->priv; + + /* Ex NAND_ECC_HW12_2048 */ + if ((this->ecc.mode == NAND_ECC_HW) && (this->ecc.size == 2048)) + n = 4; + else + n = 1; + reg = NND_ECC_START; + while (n--) { + l = nand_read_reg(reg); + *ecc_code++ = l; // P128e, ..., P1e + *ecc_code++ = l >> 16; // P128o, ..., P1o + // P2048o, P1024o, P512o, P256o, P2048e, P1024e, P512e, P256e + *ecc_code++ = ((l >> 8) & 0x0f) | ((l >> 20) & 0xf0); + reg += 4; + } + return 0; +} + +/* + * This function will generate true ECC value, which can be used + * when correcting data read from NAND flash memory core + */ +static void gen_true_ecc(u8 *ecc_buf) +{ + u32 tmp = ecc_buf[0] | (ecc_buf[1] << 16) | ((ecc_buf[2] & 0xF0) << 20) | ((ecc_buf[2] & 0x0F) << 8); + + ecc_buf[0] = ~(P64o(tmp) | P64e(tmp) | P32o(tmp) | P32e(tmp) | P16o(tmp) | P16e(tmp) | P8o(tmp) | P8e(tmp) ); + ecc_buf[1] = ~(P1024o(tmp) | P1024e(tmp) | P512o(tmp) | P512e(tmp) | P256o(tmp) | P256e(tmp) | P128o(tmp) | P128e(tmp)); + ecc_buf[2] = ~( P4o(tmp) | P4e(tmp) | P2o(tmp) | P2e(tmp) | P1o(tmp) | P1e(tmp) | P2048o(tmp) | P2048e(tmp)); +} + +/* + * This function compares two ECC's and indicates if there is an error. + * If the error can be corrected it will be corrected to the buffer + */ +static int omap_nand_compare_ecc(u8 *ecc_data1, /* read from NAND memory */ + u8 *ecc_data2, /* read from register */ + u8 *page_data) +{ + uint i; + u8 tmp0_bit[8], tmp1_bit[8], tmp2_bit[8]; + u8 comp0_bit[8], comp1_bit[8], comp2_bit[8]; + u8 ecc_bit[24]; + u8 ecc_sum = 0; + u8 find_bit = 0; + uint find_byte = 0; + int isEccFF; + + isEccFF = ((*(u32 *)ecc_data1 & 0xFFFFFF) == 0xFFFFFF); + + gen_true_ecc(ecc_data1); + gen_true_ecc(ecc_data2); + + for (i = 0; i <= 2; i++) { + *(ecc_data1 + i) = ~(*(ecc_data1 + i)); + *(ecc_data2 + i) = ~(*(ecc_data2 + i)); + } + + for (i = 0; i < 8; i++) { + tmp0_bit[i] = *ecc_data1 % 2; + *ecc_data1 = *ecc_data1 / 2; + } + + for (i = 0; i < 8; i++) { + tmp1_bit[i] = *(ecc_data1 + 1) % 2; + *(ecc_data1 + 1) = *(ecc_data1 + 1) / 2; + } + + for (i = 0; i < 8; i++) { + tmp2_bit[i] = *(ecc_data1 + 2) % 2; + *(ecc_data1 + 2) = *(ecc_data1 + 2) / 2; + } + + for (i = 0; i < 8; i++) { + comp0_bit[i] = *ecc_data2 % 2; + *ecc_data2 = *ecc_data2 / 2; + } + + for (i = 0; i < 8; i++) { + comp1_bit[i] = *(ecc_data2 + 1) % 2; + *(ecc_data2 + 1) = *(ecc_data2 + 1) / 2; + } + + for (i = 0; i < 8; i++) { + comp2_bit[i] = *(ecc_data2 + 2) % 2; + *(ecc_data2 + 2) = *(ecc_data2 + 2) / 2; + } + + for (i = 0; i< 6; i++ ) + ecc_bit[i] = tmp2_bit[i + 2] ^ comp2_bit[i + 2]; + + for (i = 0; i < 8; i++) + ecc_bit[i + 6] = tmp0_bit[i] ^ comp0_bit[i]; + + for (i = 0; i < 8; i++) + ecc_bit[i + 14] = tmp1_bit[i] ^ comp1_bit[i]; + + ecc_bit[22] = tmp2_bit[0] ^ comp2_bit[0]; + ecc_bit[23] = tmp2_bit[1] ^ comp2_bit[1]; + + for (i = 0; i < 24; i++) + ecc_sum += ecc_bit[i]; + + switch (ecc_sum) { + case 0: + /* Not reached because this function is not called if + ECC values are equal */ + return 0; + + case 1: + /* Uncorrectable error */ + DEBUG (MTD_DEBUG_LEVEL0, "ECC UNCORRECTED_ERROR 1\n"); + return -1; + + case 12: + /* Correctable error */ + find_byte = (ecc_bit[23] << 8) + + (ecc_bit[21] << 7) + + (ecc_bit[19] << 6) + + (ecc_bit[17] << 5) + + (ecc_bit[15] << 4) + + (ecc_bit[13] << 3) + + (ecc_bit[11] << 2) + + (ecc_bit[9] << 1) + + ecc_bit[7]; + + find_bit = (ecc_bit[5] << 2) + (ecc_bit[3] << 1) + ecc_bit[1]; + + DEBUG (MTD_DEBUG_LEVEL0, "Correcting single bit ECC error at offset: %d, bit: %d\n", find_byte, find_bit); + + page_data[find_byte] ^= (1 << find_bit); + + return 0; + default: + if (isEccFF) { + if (ecc_data2[0] == 0 && ecc_data2[1] == 0 && ecc_data2[2] == 0) + return 0; + } + DEBUG (MTD_DEBUG_LEVEL0, "UNCORRECTED_ERROR default\n"); + return -1; + } +} + +static int omap_nand_correct_data(struct mtd_info *mtd, u_char *dat, u_char *read_ecc, u_char *calc_ecc) +{ + struct nand_chip *this; + int block_count = 0, i, r; + + this = mtd->priv; + /* Ex NAND_ECC_HW12_2048 */ + if ((this->ecc.mode == NAND_ECC_HW) && (this->ecc.size == 2048)) + block_count = 4; + else + block_count = 1; + for (i = 0; i < block_count; i++) { + if (memcmp(read_ecc, calc_ecc, 3) != 0) { + r = omap_nand_compare_ecc(read_ecc, calc_ecc, dat); + if (r < 0) + return r; + } + read_ecc += 3; + calc_ecc += 3; + dat += 512; + } + return 0; +} + +static void omap_nand_enable_hwecc(struct mtd_info *mtd, int mode) +{ + nand_write_reg(NND_RESET, 0x01); +} + +#ifdef CONFIG_MTD_CMDLINE_PARTS + +extern int mtdpart_setup(char *); + +static int __init add_dynamic_parts(struct mtd_info *mtd) +{ + static const char *part_parsers[] = { "cmdlinepart", NULL }; + struct mtd_partition *parts; + const struct omap_flash_part_str_config *cfg; + char *part_str = NULL; + size_t part_str_len; + int c; + + cfg = omap_get_var_config(OMAP_TAG_FLASH_PART_STR, &part_str_len); + if (cfg != NULL) { + part_str = kmalloc(part_str_len + 1, GFP_KERNEL); + if (part_str == NULL) + return -ENOMEM; + memcpy(part_str, cfg->part_table, part_str_len); + part_str[part_str_len] = '\0'; + mtdpart_setup(part_str); + } + c = parse_mtd_partitions(omap_mtd, part_parsers, &parts, 0); + if (part_str != NULL) { + mtdpart_setup(NULL); + kfree(part_str); + } + if (c <= 0) + return -1; + + add_mtd_partitions(mtd, parts, c); + + return 0; +} + +#else + +static inline int add_dynamic_parts(struct mtd_info *mtd) +{ + return -1; +} + +#endif + +static inline int calc_psc(int ns, int cycle_ps) +{ + return (ns * 1000 + (cycle_ps - 1)) / cycle_ps; +} + +static void set_psc_regs(int psc_ns, int psc1_ns, int psc2_ns) +{ + int psc[3], i; + unsigned long rate, ps; + + rate = clk_get_rate(omap_nand_clk); + ps = 1000000000 / (rate / 1000); + psc[0] = calc_psc(psc_ns, ps); + psc[1] = calc_psc(psc1_ns, ps); + psc[2] = calc_psc(psc2_ns, ps); + for (i = 0; i < 3; i++) { + if (psc[i] < 2) + psc[i] = 2; + else if (psc[i] > 256) + psc[i] = 256; + } + nand_write_reg(NND_PSC_CLK, psc[0] - 1); + nand_write_reg(NND_PSC1_CLK, psc[1] - 1); + nand_write_reg(NND_PSC2_CLK, psc[2] - 1); + printk(KERN_INFO "omap-hw-nand: using PSC values %d, %d, %d\n", psc[0], psc[1], psc[2]); +} + +/* + * Main initialization routine + */ +static int __init omap_nand_init(void) +{ + struct nand_chip *this; + int err = 0; + u32 l; + + omap_nand_clk = clk_get(NULL, "armper_ck"); + BUG_ON(omap_nand_clk == NULL); + clk_enable(omap_nand_clk); + + l = nand_read_reg(NND_REVISION); + printk(KERN_INFO "omap-hw-nand: OMAP NAND Controller rev. %d.%d\n", l>>4, l & 0xf); + + /* Reset the NAND Controller */ + nand_write_reg(NND_SYSCFG, 0x02); + while ((nand_read_reg(NND_SYSSTATUS) & 0x01) == 0); + + /* No Prefetch, no postwrite, write prot & enable pairs disabled, + addres counter set to send 4 byte addresses to flash, + A8 is set not to be sent to flash (erase addre needs formatting), + choose little endian, enable 512 byte ECC logic, + */ + nand_write_reg(NND_CTRL, 0xFF01); + + /* Allocate memory for MTD device structure and private data */ + omap_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip), GFP_KERNEL); + if (!omap_mtd) { + printk(KERN_WARNING "omap-hw-nand: Unable to allocate OMAP NAND MTD device structure.\n"); + err = -ENOMEM; + goto free_clock; + } +#if 1 + err = omap_request_dma(OMAP_DMA_NAND, "NAND", nand_dma_cb, + &omap_nand_dma_comp, &omap_nand_dma_ch); + if (err < 0) { + printk(KERN_WARNING "omap-hw-nand: Unable to reserve DMA channel\n"); + omap_nand_dma_ch = -1; + } +#else + omap_nand_dma_ch = -1; +#endif + /* Get pointer to private data */ + this = (struct nand_chip *) (&omap_mtd[1]); + + /* Initialize structures */ + memset((char *) omap_mtd, 0, sizeof(struct mtd_info)); + memset((char *) this, 0, sizeof(struct nand_chip)); + + /* Link the private data with the MTD structure */ + omap_mtd->priv = this; + omap_mtd->name = "omap-nand"; + + this->options = NAND_SKIP_BBTSCAN; + + /* Used from chip select and nand_command() */ + this->read_byte = omap_nand_read_byte; + + this->select_chip = omap_nand_select_chip; + this->dev_ready = omap_nand_dev_ready; + this->chip_delay = 0; + this->ecc.mode = NAND_ECC_HW; + this->ecc.bytes = 3; + this->ecc.size = 512; + this->cmdfunc = omap_nand_command; + this->write_buf = omap_nand_write_buf; + this->read_buf = omap_nand_read_buf; + this->verify_buf = omap_nand_verify_buf; + this->ecc.calculate = omap_nand_calculate_ecc; + this->ecc.correct = omap_nand_correct_data; + this->ecc.hwctl = omap_nand_enable_hwecc; + + nand_write_reg(NND_SYSCFG, 0x1); /* Enable auto idle */ + nand_write_reg(NND_PSC_CLK, 10); + /* Scan to find existance of the device */ + if (nand_scan(omap_mtd, 1)) { + err = -ENXIO; + goto out_mtd; + } + + set_psc_regs(25, 15, 35); + if (this->page_shift == 11) { + this->cmdfunc = omap_nand_command_lp; + l = nand_read_reg(NND_CTRL); + l |= 1 << 4; /* Set the A8 bit in CTRL reg */ + nand_write_reg(NND_CTRL, l); + this->ecc.mode = NAND_ECC_HW; + this->ecc.steps = 1; + this->ecc.size = 2048; + this->ecc.bytes = 12; + nand_write_reg(NND_ECC_SELECT, 6); + } + + /* We have to do bbt scanning ourselves */ + if (this->scan_bbt (omap_mtd)) { + err = -ENXIO; + goto out_mtd; + } + + err = add_dynamic_parts(omap_mtd); + if (err < 0) { + printk(KERN_ERR "omap-hw-nand: no partitions defined\n"); + err = -ENODEV; + nand_release(omap_mtd); + goto out_mtd; + } + /* init completed */ + return 0; +out_mtd: + if (omap_nand_dma_ch >= 0) + omap_free_dma(omap_nand_dma_ch); + kfree(omap_mtd); +free_clock: + clk_put(omap_nand_clk); + return err; +} + +module_init(omap_nand_init); + +/* + * Clean up routine + */ +static void __exit omap_nand_cleanup (void) +{ + clk_disable(omap_nand_clk); + clk_put(omap_nand_clk); + nand_release(omap_mtd); + kfree(omap_mtd); +} + +module_exit(omap_nand_cleanup); + diff --cc drivers/mtd/nand/omap-nand-flash.c index e9663af457c,00000000000..63a79682802 mode 100644,000000..100644 --- a/drivers/mtd/nand/omap-nand-flash.c +++ b/drivers/mtd/nand/omap-nand-flash.c @@@ -1,186 -1,0 +1,186 @@@ +/* + * drivers/mtd/nand/omap-nand-flash.c + * + * Copyright (c) 2004 Texas Instruments, Jian Zhang + * Copyright (c) 2004 David Brownell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include - #include ++#include +#include +#include - #include ++#include + +#include - #include - #include ++#include ++#include + +#define DRIVER_NAME "omapnand" + +#ifdef CONFIG_MTD_PARTITIONS +static const char *part_probes[] = { "cmdlinepart", NULL }; +#endif + +struct omap_nand_info { + struct omap_nand_platform_data *pdata; + struct mtd_partition *parts; + struct mtd_info mtd; + struct nand_chip nand; +}; + +/* + * hardware specific access to control-lines + * NOTE: boards may use different bits for these!! + * + * ctrl: + * NAND_NCE: bit 0 - don't care + * NAND_CLE: bit 1 -> bit 1 (0x0002) + * NAND_ALE: bit 2 -> bit 2 (0x0004) + */ + +static void omap_nand_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl) +{ + struct nand_chip *chip = mtd->priv; + unsigned long mask; + + if (cmd == NAND_CMD_NONE) + return; + + mask = (ctrl & NAND_CLE) ? 0x02 : 0; + if (ctrl & NAND_ALE) + mask |= 0x04; + writeb(cmd, (unsigned long)chip->IO_ADDR_W | mask); +} + +static int omap_nand_dev_ready(struct mtd_info *mtd) +{ + struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, mtd); + + return info->pdata->dev_ready(info->pdata); +} + +static int __devinit omap_nand_probe(struct platform_device *pdev) +{ + struct omap_nand_info *info; + struct omap_nand_platform_data *pdata = pdev->dev.platform_data; + struct resource *res = pdev->resource; + unsigned long size = res->end - res->start + 1; + int err; + + info = kzalloc(sizeof(struct omap_nand_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + if (!request_mem_region(res->start, size, pdev->dev.driver->name)) { + err = -EBUSY; + goto out_free_info; + } + + info->nand.IO_ADDR_R = ioremap(res->start, size); + if (!info->nand.IO_ADDR_R) { + err = -ENOMEM; + goto out_release_mem_region; + } + info->nand.IO_ADDR_W = info->nand.IO_ADDR_R; + info->nand.cmd_ctrl = omap_nand_hwcontrol; + info->nand.ecc.mode = NAND_ECC_SOFT; + info->nand.options = pdata->options; + if (pdata->dev_ready) + info->nand.dev_ready = omap_nand_dev_ready; + else + info->nand.chip_delay = 20; + + info->mtd.name = pdev->dev.bus_id; + info->mtd.priv = &info->nand; + + info->pdata = pdata; + + /* DIP switches on H2 and some other boards change between 8 and 16 bit + * bus widths for flash. Try the other width if the first try fails. + */ + if (nand_scan(&info->mtd, 1)) { + info->nand.options ^= NAND_BUSWIDTH_16; + if (nand_scan(&info->mtd, 1)) { + err = -ENXIO; + goto out_iounmap; + } + } + info->mtd.owner = THIS_MODULE; + +#ifdef CONFIG_MTD_PARTITIONS + err = parse_mtd_partitions(&info->mtd, part_probes, &info->parts, 0); + if (err > 0) + add_mtd_partitions(&info->mtd, info->parts, err); + else if (err < 0 && pdata->parts) + add_mtd_partitions(&info->mtd, pdata->parts, pdata->nr_parts); + else +#endif + add_mtd_device(&info->mtd); + + platform_set_drvdata(pdev, info); + + return 0; + +out_iounmap: + iounmap(info->nand.IO_ADDR_R); +out_release_mem_region: + release_mem_region(res->start, size); +out_free_info: + kfree(info); + + return err; +} + +static int omap_nand_remove(struct platform_device *pdev) +{ + struct omap_nand_info *info = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + /* Release NAND device, its internal structures and partitions */ + nand_release(&info->mtd); + iounmap(info->nand.IO_ADDR_R); + kfree(info); + return 0; +} + +static struct platform_driver omap_nand_driver = { + .probe = omap_nand_probe, + .remove = omap_nand_remove, + .driver = { + .name = DRIVER_NAME, + }, +}; +MODULE_ALIAS(DRIVER_NAME); + +static int __init omap_nand_init(void) +{ + return platform_driver_register(&omap_nand_driver); +} + +static void __exit omap_nand_exit(void) +{ + platform_driver_unregister(&omap_nand_driver); +} + +module_init(omap_nand_init); +module_exit(omap_nand_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jian Zhang (and others)"); +MODULE_DESCRIPTION("Glue layer for NAND flash on TI OMAP boards"); + diff --cc drivers/mtd/nand/omap2.c index b4ce8085f27,00000000000..a33944c9d5f mode 100644,000000..100644 --- a/drivers/mtd/nand/omap2.c +++ b/drivers/mtd/nand/omap2.c @@@ -1,757 -1,0 +1,757 @@@ +/* + * drivers/mtd/nand/omap2.c + * + * Copyright (c) 2004 Texas Instruments, Jian Zhang + * Copyright (c) 2004 Micron Technology Inc. + * Copyright (c) 2004 David Brownell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + - #include - #include ++#include ++#include + +#define GPMC_IRQ_STATUS 0x18 +#define GPMC_ECC_CONFIG 0x1F4 +#define GPMC_ECC_CONTROL 0x1F8 +#define GPMC_ECC_SIZE_CONFIG 0x1FC +#define GPMC_ECC1_RESULT 0x200 + +#define DRIVER_NAME "omap2-nand" +#define NAND_IO_SIZE SZ_4K + +#define NAND_WP_ON 1 +#define NAND_WP_OFF 0 +#define NAND_WP_BIT 0x00000010 +#define WR_RD_PIN_MONITORING 0x00600000 + +#define GPMC_BUF_FULL 0x00000001 +#define GPMC_BUF_EMPTY 0x00000000 + +#define NAND_Ecc_P1e (1 << 0) +#define NAND_Ecc_P2e (1 << 1) +#define NAND_Ecc_P4e (1 << 2) +#define NAND_Ecc_P8e (1 << 3) +#define NAND_Ecc_P16e (1 << 4) +#define NAND_Ecc_P32e (1 << 5) +#define NAND_Ecc_P64e (1 << 6) +#define NAND_Ecc_P128e (1 << 7) +#define NAND_Ecc_P256e (1 << 8) +#define NAND_Ecc_P512e (1 << 9) +#define NAND_Ecc_P1024e (1 << 10) +#define NAND_Ecc_P2048e (1 << 11) + +#define NAND_Ecc_P1o (1 << 16) +#define NAND_Ecc_P2o (1 << 17) +#define NAND_Ecc_P4o (1 << 18) +#define NAND_Ecc_P8o (1 << 19) +#define NAND_Ecc_P16o (1 << 20) +#define NAND_Ecc_P32o (1 << 21) +#define NAND_Ecc_P64o (1 << 22) +#define NAND_Ecc_P128o (1 << 23) +#define NAND_Ecc_P256o (1 << 24) +#define NAND_Ecc_P512o (1 << 25) +#define NAND_Ecc_P1024o (1 << 26) +#define NAND_Ecc_P2048o (1 << 27) + +#define TF(value) (value ? 1 : 0) + +#define P2048e(a) (TF(a & NAND_Ecc_P2048e) << 0) +#define P2048o(a) (TF(a & NAND_Ecc_P2048o) << 1) +#define P1e(a) (TF(a & NAND_Ecc_P1e) << 2) +#define P1o(a) (TF(a & NAND_Ecc_P1o) << 3) +#define P2e(a) (TF(a & NAND_Ecc_P2e) << 4) +#define P2o(a) (TF(a & NAND_Ecc_P2o) << 5) +#define P4e(a) (TF(a & NAND_Ecc_P4e) << 6) +#define P4o(a) (TF(a & NAND_Ecc_P4o) << 7) + +#define P8e(a) (TF(a & NAND_Ecc_P8e) << 0) +#define P8o(a) (TF(a & NAND_Ecc_P8o) << 1) +#define P16e(a) (TF(a & NAND_Ecc_P16e) << 2) +#define P16o(a) (TF(a & NAND_Ecc_P16o) << 3) +#define P32e(a) (TF(a & NAND_Ecc_P32e) << 4) +#define P32o(a) (TF(a & NAND_Ecc_P32o) << 5) +#define P64e(a) (TF(a & NAND_Ecc_P64e) << 6) +#define P64o(a) (TF(a & NAND_Ecc_P64o) << 7) + +#define P128e(a) (TF(a & NAND_Ecc_P128e) << 0) +#define P128o(a) (TF(a & NAND_Ecc_P128o) << 1) +#define P256e(a) (TF(a & NAND_Ecc_P256e) << 2) +#define P256o(a) (TF(a & NAND_Ecc_P256o) << 3) +#define P512e(a) (TF(a & NAND_Ecc_P512e) << 4) +#define P512o(a) (TF(a & NAND_Ecc_P512o) << 5) +#define P1024e(a) (TF(a & NAND_Ecc_P1024e) << 6) +#define P1024o(a) (TF(a & NAND_Ecc_P1024o) << 7) + +#define P8e_s(a) (TF(a & NAND_Ecc_P8e) << 0) +#define P8o_s(a) (TF(a & NAND_Ecc_P8o) << 1) +#define P16e_s(a) (TF(a & NAND_Ecc_P16e) << 2) +#define P16o_s(a) (TF(a & NAND_Ecc_P16o) << 3) +#define P1e_s(a) (TF(a & NAND_Ecc_P1e) << 4) +#define P1o_s(a) (TF(a & NAND_Ecc_P1o) << 5) +#define P2e_s(a) (TF(a & NAND_Ecc_P2e) << 6) +#define P2o_s(a) (TF(a & NAND_Ecc_P2o) << 7) + +#define P4e_s(a) (TF(a & NAND_Ecc_P4e) << 0) +#define P4o_s(a) (TF(a & NAND_Ecc_P4o) << 1) + +#ifdef CONFIG_MTD_PARTITIONS +static const char *part_probes[] = { "cmdlinepart", NULL }; +#endif + +struct omap_nand_info { + struct nand_hw_control controller; + struct omap_nand_platform_data *pdata; + struct mtd_info mtd; + struct mtd_partition *parts; + struct nand_chip nand; + struct platform_device *pdev; + + int gpmc_cs; + unsigned long phys_base; + void __iomem *gpmc_cs_baseaddr; + void __iomem *gpmc_baseaddr; +}; + +/* + * omap_nand_wp - This function enable or disable the Write Protect feature on + * NAND device + * @mtd: MTD device structure + * @mode: WP ON/OFF + */ +static void omap_nand_wp(struct mtd_info *mtd, int mode) +{ + struct omap_nand_info *info = container_of(mtd, + struct omap_nand_info, mtd); + + unsigned long config = __raw_readl(info->gpmc_baseaddr + GPMC_CONFIG); + + if (mode) + config &= ~(NAND_WP_BIT); /* WP is ON */ + else + config |= (NAND_WP_BIT); /* WP is OFF */ + + __raw_writel(config, (info->gpmc_baseaddr + GPMC_CONFIG)); +} + +/* + * hardware specific access to control-lines + * NOTE: boards may use different bits for these!! + * + * ctrl: + * NAND_NCE: bit 0 - don't care + * NAND_CLE: bit 1 -> Command Latch + * NAND_ALE: bit 2 -> Address Latch + */ +static void omap_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl) +{ + struct omap_nand_info *info = container_of(mtd, + struct omap_nand_info, mtd); + switch (ctrl) { + case NAND_CTRL_CHANGE | NAND_CTRL_CLE: + info->nand.IO_ADDR_W = info->gpmc_cs_baseaddr + + GPMC_CS_NAND_COMMAND; + info->nand.IO_ADDR_R = info->gpmc_cs_baseaddr + + GPMC_CS_NAND_DATA; + break; + + case NAND_CTRL_CHANGE | NAND_CTRL_ALE: + info->nand.IO_ADDR_W = info->gpmc_cs_baseaddr + + GPMC_CS_NAND_ADDRESS; + info->nand.IO_ADDR_R = info->gpmc_cs_baseaddr + + GPMC_CS_NAND_DATA; + break; + + case NAND_CTRL_CHANGE | NAND_NCE: + info->nand.IO_ADDR_W = info->gpmc_cs_baseaddr + + GPMC_CS_NAND_DATA; + info->nand.IO_ADDR_R = info->gpmc_cs_baseaddr + + GPMC_CS_NAND_DATA; + break; + } + + if (cmd != NAND_CMD_NONE) + __raw_writeb(cmd, info->nand.IO_ADDR_W); +} + +/* + * omap_read_buf - read data from NAND controller into buffer + * @mtd: MTD device structure + * @buf: buffer to store date + * @len: number of bytes to read + */ +static void omap_read_buf(struct mtd_info *mtd, u_char *buf, int len) +{ + struct omap_nand_info *info = container_of(mtd, + struct omap_nand_info, mtd); + u16 *p = (u16 *) buf; + + len >>= 1; + + while (len--) + *p++ = cpu_to_le16(readw(info->nand.IO_ADDR_R)); +} + +/* + * omap_write_buf - write buffer to NAND controller + * @mtd: MTD device structure + * @buf: data buffer + * @len: number of bytes to write + */ +static void omap_write_buf(struct mtd_info *mtd, const u_char * buf, int len) +{ + struct omap_nand_info *info = container_of(mtd, + struct omap_nand_info, mtd); + u16 *p = (u16 *) buf; + + len >>= 1; + + while (len--) { + writew(cpu_to_le16(*p++), info->nand.IO_ADDR_W); + + while (GPMC_BUF_EMPTY == (readl(info->gpmc_baseaddr + + GPMC_STATUS) & GPMC_BUF_FULL)); + } +} +/* + * omap_verify_buf - Verify chip data against buffer + * @mtd: MTD device structure + * @buf: buffer containing the data to compare + * @len: number of bytes to compare + */ +static int omap_verify_buf(struct mtd_info *mtd, const u_char * buf, int len) +{ + struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, + mtd); + u16 *p = (u16 *) buf; + + len >>= 1; + + while (len--) { + + if (*p++ != cpu_to_le16(readw(info->nand.IO_ADDR_R))) + return -EFAULT; + } + + return 0; +} + +#ifdef CONFIG_MTD_NAND_OMAP_HWECC +/* + * omap_hwecc_init-Initialize the Hardware ECC for NAND flash in GPMC controller + * @mtd: MTD device structure + */ +static void omap_hwecc_init(struct mtd_info *mtd) +{ + struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, + mtd); + register struct nand_chip *chip = mtd->priv; + unsigned long val = 0x0; + + /* Read from ECC Control Register */ + val = __raw_readl(info->gpmc_baseaddr + GPMC_ECC_CONTROL); + /* Clear all ECC | Enable Reg1 */ + val = ((0x00000001<<8) | 0x00000001); + __raw_writel(val, info->gpmc_baseaddr + GPMC_ECC_CONTROL); + + /* Read from ECC Size Config Register */ + val = __raw_readl(info->gpmc_baseaddr + GPMC_ECC_SIZE_CONFIG); + /* ECCSIZE1=512 | Select eccResultsize[0-3] */ + val = ((((chip->ecc.size >> 1) - 1) << 22) | (0x0000000F)); + __raw_writel(val, info->gpmc_baseaddr + GPMC_ECC_SIZE_CONFIG); +} + +/* + * gen_true_ecc - This function will generate true ECC value, which can be used + * when correcting data read from NAND flash memory core + * @ecc_buf: buffer to store ecc code + */ +static void gen_true_ecc(u8 *ecc_buf) +{ + u32 tmp = ecc_buf[0] | (ecc_buf[1] << 16) | + ((ecc_buf[2] & 0xF0) << 20) | ((ecc_buf[2] & 0x0F) << 8); + + ecc_buf[0] = ~(P64o(tmp) | P64e(tmp) | P32o(tmp) | P32e(tmp) | + P16o(tmp) | P16e(tmp) | P8o(tmp) | P8e(tmp)); + ecc_buf[1] = ~(P1024o(tmp) | P1024e(tmp) | P512o(tmp) | P512e(tmp) | + P256o(tmp) | P256e(tmp) | P128o(tmp) | P128e(tmp)); + ecc_buf[2] = ~(P4o(tmp) | P4e(tmp) | P2o(tmp) | P2e(tmp) | P1o(tmp) | + P1e(tmp) | P2048o(tmp) | P2048e(tmp)); +} + +/* + * omap_compare_ecc - This function compares two ECC's and indicates if there + * is an error. If the error can be corrected it will be corrected to the + * buffer + * @ecc_data1: ecc code from nand spare area + * @ecc_data2: ecc code from hardware register obtained from hardware ecc + * @page_data: page data + */ +static int omap_compare_ecc(u8 *ecc_data1, /* read from NAND memory */ + u8 *ecc_data2, /* read from register */ + u8 *page_data) +{ + uint i; + u8 tmp0_bit[8], tmp1_bit[8], tmp2_bit[8]; + u8 comp0_bit[8], comp1_bit[8], comp2_bit[8]; + u8 ecc_bit[24]; + u8 ecc_sum = 0; + u8 find_bit = 0; + uint find_byte = 0; + int isEccFF; + + isEccFF = ((*(u32 *)ecc_data1 & 0xFFFFFF) == 0xFFFFFF); + + gen_true_ecc(ecc_data1); + gen_true_ecc(ecc_data2); + + for (i = 0; i <= 2; i++) { + *(ecc_data1 + i) = ~(*(ecc_data1 + i)); + *(ecc_data2 + i) = ~(*(ecc_data2 + i)); + } + + for (i = 0; i < 8; i++) { + tmp0_bit[i] = *ecc_data1 % 2; + *ecc_data1 = *ecc_data1 / 2; + } + + for (i = 0; i < 8; i++) { + tmp1_bit[i] = *(ecc_data1 + 1) % 2; + *(ecc_data1 + 1) = *(ecc_data1 + 1) / 2; + } + + for (i = 0; i < 8; i++) { + tmp2_bit[i] = *(ecc_data1 + 2) % 2; + *(ecc_data1 + 2) = *(ecc_data1 + 2) / 2; + } + + for (i = 0; i < 8; i++) { + comp0_bit[i] = *ecc_data2 % 2; + *ecc_data2 = *ecc_data2 / 2; + } + + for (i = 0; i < 8; i++) { + comp1_bit[i] = *(ecc_data2 + 1) % 2; + *(ecc_data2 + 1) = *(ecc_data2 + 1) / 2; + } + + for (i = 0; i < 8; i++) { + comp2_bit[i] = *(ecc_data2 + 2) % 2; + *(ecc_data2 + 2) = *(ecc_data2 + 2) / 2; + } + + for (i = 0; i < 6; i++) + ecc_bit[i] = tmp2_bit[i + 2] ^ comp2_bit[i + 2]; + + for (i = 0; i < 8; i++) + ecc_bit[i + 6] = tmp0_bit[i] ^ comp0_bit[i]; + + for (i = 0; i < 8; i++) + ecc_bit[i + 14] = tmp1_bit[i] ^ comp1_bit[i]; + + ecc_bit[22] = tmp2_bit[0] ^ comp2_bit[0]; + ecc_bit[23] = tmp2_bit[1] ^ comp2_bit[1]; + + for (i = 0; i < 24; i++) + ecc_sum += ecc_bit[i]; + + switch (ecc_sum) { + case 0: + /* Not reached because this function is not called if + * ECC values are equal + */ + return 0; + + case 1: + /* Uncorrectable error */ + DEBUG(MTD_DEBUG_LEVEL0, "ECC UNCORRECTED_ERROR 1\n"); + return -1; + + case 11: + /* UN-Correctable error */ + DEBUG(MTD_DEBUG_LEVEL0, "ECC UNCORRECTED_ERROR B\n"); + return -1; + + case 12: + /* Correctable error */ + find_byte = (ecc_bit[23] << 8) + + (ecc_bit[21] << 7) + + (ecc_bit[19] << 6) + + (ecc_bit[17] << 5) + + (ecc_bit[15] << 4) + + (ecc_bit[13] << 3) + + (ecc_bit[11] << 2) + + (ecc_bit[9] << 1) + + ecc_bit[7]; + + find_bit = (ecc_bit[5] << 2) + (ecc_bit[3] << 1) + ecc_bit[1]; + + DEBUG(MTD_DEBUG_LEVEL0, "Correcting single bit ECC error at " + "offset: %d, bit: %d\n", find_byte, find_bit); + + page_data[find_byte] ^= (1 << find_bit); + + return 0; + default: + if (isEccFF) { + if (ecc_data2[0] == 0 && + ecc_data2[1] == 0 && + ecc_data2[2] == 0) + return 0; + } + DEBUG(MTD_DEBUG_LEVEL0, "UNCORRECTED_ERROR default\n"); + return -1; + } +} + +/* + * omap_correct_data - Compares the ecc read from nand spare area with ECC + * registers values and corrects one bit error if it has occured + * @mtd: MTD device structure + * @dat: page data + * @read_ecc: ecc read from nand flash + * @calc_ecc: ecc read from ECC registers + */ +static int omap_correct_data(struct mtd_info *mtd, u_char * dat, + u_char * read_ecc, u_char * calc_ecc) +{ + struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, + mtd); + int blockCnt = 0, i = 0, ret = 0; + + /* Ex NAND_ECC_HW12_2048 */ + if ((info->nand.ecc.mode == NAND_ECC_HW) && + (info->nand.ecc.size == 2048)) + blockCnt = 4; + else + blockCnt = 1; + + for (i = 0; i < blockCnt; i++) { + if (memcmp(read_ecc, calc_ecc, 3) != 0) { + ret = omap_compare_ecc(read_ecc, calc_ecc, dat); + if (ret < 0) return ret; + } + read_ecc += 3; + calc_ecc += 3; + dat += 512; + } + return 0; +} + +/* + * omap_calcuate_ecc - Generate non-inverted ECC bytes. + * Using noninverted ECC can be considered ugly since writing a blank + * page ie. padding will clear the ECC bytes. This is no problem as long + * nobody is trying to write data on the seemingly unused page. Reading + * an erased page will produce an ECC mismatch between generated and read + * ECC bytes that has to be dealt with separately. + * @mtd: MTD device structure + * @dat: The pointer to data on which ecc is computed + * @ecc_code: The ecc_code buffer + */ +static int omap_calculate_ecc(struct mtd_info *mtd, const u_char *dat, + u_char *ecc_code) +{ + struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, + mtd); + unsigned long val = 0x0; + unsigned long reg; + + /* Start Reading from HW ECC1_Result = 0x200 */ + reg = (unsigned long)(info->gpmc_baseaddr + GPMC_ECC1_RESULT); + val = __raw_readl(reg); + *ecc_code++ = val; /* P128e, ..., P1e */ + *ecc_code++ = val >> 16; /* P128o, ..., P1o */ + /* P2048o, P1024o, P512o, P256o, P2048e, P1024e, P512e, P256e */ + *ecc_code++ = ((val >> 8) & 0x0f) | ((val >> 20) & 0xf0); + reg += 4; + + return 0; +} + +/* + * omap_enable_hwecc - This function enables the hardware ecc functionality + * @mtd: MTD device structure + * @mode: Read/Write mode + */ +static void omap_enable_hwecc(struct mtd_info *mtd, int mode) +{ + struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, + mtd); + register struct nand_chip *chip = mtd->priv; + unsigned int dev_width = (chip->options & NAND_BUSWIDTH_16) ? 1 : 0; + unsigned long val = __raw_readl(info->gpmc_baseaddr + GPMC_ECC_CONFIG); + + switch (mode) { + case NAND_ECC_READ : + __raw_writel(0x101, info->gpmc_baseaddr + GPMC_ECC_CONTROL); + /* (ECC 16 or 8 bit col) | ( CS ) | ECC Enable */ + val = (dev_width << 7) | (info->gpmc_cs << 1) | (0x1); + break; + case NAND_ECC_READSYN : + __raw_writel(0x100, info->gpmc_baseaddr + GPMC_ECC_CONTROL); + /* (ECC 16 or 8 bit col) | ( CS ) | ECC Enable */ + val = (dev_width << 7) | (info->gpmc_cs << 1) | (0x1); + break; + case NAND_ECC_WRITE : + __raw_writel(0x101, info->gpmc_baseaddr + GPMC_ECC_CONTROL); + /* (ECC 16 or 8 bit col) | ( CS ) | ECC Enable */ + val = (dev_width << 7) | (info->gpmc_cs << 1) | (0x1); + break; + default: + DEBUG(MTD_DEBUG_LEVEL0, "Error: Unrecognized Mode[%d]!\n", + mode); + break; + } + + __raw_writel(val, info->gpmc_baseaddr + GPMC_ECC_CONFIG); +} +#endif + +/* + * omap_wait - Wait function is called during Program and erase + * operations and the way it is called from MTD layer, we should wait + * till the NAND chip is ready after the programming/erase operation + * has completed. + * @mtd: MTD device structure + * @chip: NAND Chip structure + */ +static int omap_wait(struct mtd_info *mtd, struct nand_chip *chip) +{ + register struct nand_chip *this = mtd->priv; + struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, + mtd); + int status = 0; + + this->IO_ADDR_W = (void *) info->gpmc_cs_baseaddr + + GPMC_CS_NAND_COMMAND; + this->IO_ADDR_R = (void *) info->gpmc_cs_baseaddr + GPMC_CS_NAND_DATA; + + while (!(status & 0x40)) { + __raw_writeb(NAND_CMD_STATUS & 0xFF, this->IO_ADDR_W); + status = __raw_readb(this->IO_ADDR_R); + } + return status; +} + +/* + * omap_dev_ready - calls the platform specific dev_ready function + * @mtd: MTD device structure + */ +static int omap_dev_ready(struct mtd_info *mtd) +{ + struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, + mtd); + unsigned int val = __raw_readl(info->gpmc_baseaddr + GPMC_IRQ_STATUS); + + if ((val & 0x100) == 0x100) { + /* Clear IRQ Interrupt */ + val |= 0x100; + val &= ~(0x0); + __raw_writel(val, info->gpmc_baseaddr + GPMC_IRQ_STATUS); + } else { + unsigned int cnt = 0; + while (cnt++ < 0x1FF) { + if ((val & 0x100) == 0x100) + return 0; + val = __raw_readl(info->gpmc_baseaddr + + GPMC_IRQ_STATUS); + } + } + + return 1; +} + +static int __devinit omap_nand_probe(struct platform_device *pdev) +{ + struct omap_nand_info *info; + struct omap_nand_platform_data *pdata; + int err; + unsigned long val; + + + pdata = pdev->dev.platform_data; + if (pdata == NULL) { + dev_err(&pdev->dev, "platform data missing\n"); + return -ENODEV; + } + + info = kzalloc(sizeof(struct omap_nand_info), GFP_KERNEL); + if (!info) return -ENOMEM; + + platform_set_drvdata(pdev, info); + + spin_lock_init(&info->controller.lock); + init_waitqueue_head(&info->controller.wq); + + info->pdev = pdev; + + info->gpmc_cs = pdata->cs; + info->gpmc_baseaddr = pdata->gpmc_baseaddr; + info->gpmc_cs_baseaddr = pdata->gpmc_cs_baseaddr; + + info->mtd.priv = &info->nand; + info->mtd.name = pdev->dev.bus_id; + info->mtd.owner = THIS_MODULE; + + err = gpmc_cs_request(info->gpmc_cs, NAND_IO_SIZE, &info->phys_base); + if (err < 0) { + dev_err(&pdev->dev, "Cannot request GPMC CS\n"); + goto out_free_info; + } + + /* Enable RD PIN Monitoring Reg */ + if (pdata->dev_ready) { + val = gpmc_cs_read_reg(info->gpmc_cs, GPMC_CS_CONFIG1); + val |= WR_RD_PIN_MONITORING; + gpmc_cs_write_reg(info->gpmc_cs, GPMC_CS_CONFIG1, val); + } + + val = gpmc_cs_read_reg(info->gpmc_cs, GPMC_CS_CONFIG7); + val &= ~(0xf << 8); + val |= (0xc & 0xf) << 8; + gpmc_cs_write_reg(info->gpmc_cs, GPMC_CS_CONFIG7, val); + + /* NAND write protect off */ + omap_nand_wp(&info->mtd, NAND_WP_OFF); + + if (!request_mem_region(info->phys_base, NAND_IO_SIZE, + pdev->dev.driver->name)) { + err = -EBUSY; + goto out_free_cs; + } + + info->nand.IO_ADDR_R = ioremap(info->phys_base, NAND_IO_SIZE); + if (!info->nand.IO_ADDR_R) { + err = -ENOMEM; + goto out_release_mem_region; + } + info->nand.controller = &info->controller; + + info->nand.IO_ADDR_W = info->nand.IO_ADDR_R; + info->nand.cmd_ctrl = omap_hwcontrol; + + info->nand.read_buf = omap_read_buf; + info->nand.write_buf = omap_write_buf; + info->nand.verify_buf = omap_verify_buf; + + /* + * If RDY/BSY line is connected to OMAP then use the omap ready funcrtion + * and the generic nand_wait function which reads the status register + * after monitoring the RDY/BSY line.Otherwise use a standard chip delay + * which is slightly more than tR (AC Timing) of the NAND device and read + * status register until you get a failure or success + */ + if (pdata->dev_ready) { + info->nand.dev_ready = omap_dev_ready; + info->nand.chip_delay = 0; + } else { + info->nand.waitfunc = omap_wait; + info->nand.chip_delay = 50; + } + + info->nand.options |= NAND_SKIP_BBTSCAN; + if ((gpmc_cs_read_reg(info->gpmc_cs, GPMC_CS_CONFIG1) & 0x3000) + == 0x1000) + info->nand.options |= NAND_BUSWIDTH_16; + +#ifdef CONFIG_MTD_NAND_OMAP_HWECC + info->nand.ecc.bytes = 3; + info->nand.ecc.size = 512; + info->nand.ecc.calculate = omap_calculate_ecc; + info->nand.ecc.hwctl = omap_enable_hwecc; + info->nand.ecc.correct = omap_correct_data; + info->nand.ecc.mode = NAND_ECC_HW; + + /* init HW ECC */ + omap_hwecc_init(&info->mtd); +#else + info->nand.ecc.mode = NAND_ECC_SOFT; +#endif + + /* DIP switches on some boards change between 8 and 16 bit + * bus widths for flash. Try the other width if the first try fails. + */ + if (nand_scan(&info->mtd, 1)) { + info->nand.options ^= NAND_BUSWIDTH_16; + if (nand_scan(&info->mtd, 1)) { + err = -ENXIO; + goto out_release_mem_region; + } + } + +#ifdef CONFIG_MTD_PARTITIONS + err = parse_mtd_partitions(&info->mtd, part_probes, &info->parts, 0); + if (err > 0) + add_mtd_partitions(&info->mtd, info->parts, err); + else if (pdata->parts) + add_mtd_partitions(&info->mtd, pdata->parts, pdata->nr_parts); + else +#endif + add_mtd_device(&info->mtd); + + platform_set_drvdata(pdev, &info->mtd); + + return 0; + +out_release_mem_region: + release_mem_region(info->phys_base, NAND_IO_SIZE); +out_free_cs: + gpmc_cs_free(info->gpmc_cs); +out_free_info: + kfree(info); + + return err; +} + +static int omap_nand_remove(struct platform_device *pdev) +{ + struct mtd_info *mtd = platform_get_drvdata(pdev); + struct omap_nand_info *info = mtd->priv; + + platform_set_drvdata(pdev, NULL); + /* Release NAND device, its internal structures and partitions */ + nand_release(&info->mtd); + iounmap(info->nand.IO_ADDR_R); + kfree(&info->mtd); + return 0; +} + +static struct platform_driver omap_nand_driver = { + .probe = omap_nand_probe, + .remove = omap_nand_remove, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; +MODULE_ALIAS(DRIVER_NAME); + +static int __init omap_nand_init(void) +{ + printk(KERN_INFO "%s driver initializing\n", DRIVER_NAME); + return platform_driver_register(&omap_nand_driver); +} + +static void __exit omap_nand_exit(void) +{ + platform_driver_unregister(&omap_nand_driver); +} + +module_init(omap_nand_init); +module_exit(omap_nand_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Glue layer for NAND flash on TI OMAP boards"); diff --cc drivers/mtd/onenand/omap2.c index f279d896eed,00000000000..d265215e862 mode 100644,000000..100644 --- a/drivers/mtd/onenand/omap2.c +++ b/drivers/mtd/onenand/omap2.c @@@ -1,777 -1,0 +1,777 @@@ +/* + * linux/drivers/mtd/onenand/omap2.c + * + * OneNAND driver for OMAP2 / OMAP3 + * + * Copyright (C) 2005-2006 Nokia Corporation + * + * Author: Jarkko Lavinen and Juha Yrjola + * IRQ and DMA support written by Timo Teras + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; see the file COPYING. If not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include - #include - #include - #include - #include - #include ++#include ++#include ++#include ++#include ++#include + +#include +#include - #include ++#include + - #include ++#include + +#define DRIVER_NAME "omap2-onenand" + +#define ONENAND_IO_SIZE SZ_128K +#define ONENAND_BUFRAM_SIZE (1024 * 5) + +struct omap2_onenand { + struct platform_device *pdev; + int gpmc_cs; + unsigned long phys_base; + int gpio_irq; + struct mtd_info mtd; + struct mtd_partition *parts; + struct onenand_chip onenand; + struct completion irq_done; + struct completion dma_done; + int dma_channel; + int freq; + int (*setup)(void __iomem *base, int freq); +}; + +static void omap2_onenand_dma_cb(int lch, u16 ch_status, void *data) +{ + struct omap2_onenand *c = data; + + complete(&c->dma_done); +} + +static irqreturn_t omap2_onenand_interrupt(int irq, void *dev_id) +{ + struct omap2_onenand *c = dev_id; + + complete(&c->irq_done); + + return IRQ_HANDLED; +} + +static inline unsigned short read_reg(struct omap2_onenand *c, int reg) +{ + return readw(c->onenand.base + reg); +} + +static inline void write_reg(struct omap2_onenand *c, unsigned short value, + int reg) +{ + writew(value, c->onenand.base + reg); +} + +static void wait_err(char *msg, int state, unsigned int ctrl, unsigned int intr) +{ + printk(KERN_ERR "onenand_wait: %s! state %d ctrl 0x%04x intr 0x%04x\n", + msg, state, ctrl, intr); +} + +static void wait_warn(char *msg, int state, unsigned int ctrl, + unsigned int intr) +{ + printk(KERN_WARNING "onenand_wait: %s! state %d ctrl 0x%04x " + "intr 0x%04x\n", msg, state, ctrl, intr); +} + +static int omap2_onenand_wait(struct mtd_info *mtd, int state) +{ + struct omap2_onenand *c = container_of(mtd, struct omap2_onenand, mtd); + unsigned int intr = 0; + unsigned int ctrl; + unsigned long timeout; + u32 syscfg; + + if (state == FL_RESETING) { + int i; + + for (i = 0; i < 20; i++) { + udelay(1); + intr = read_reg(c, ONENAND_REG_INTERRUPT); + if (intr & ONENAND_INT_MASTER) + break; + } + ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS); + if (ctrl & ONENAND_CTRL_ERROR) { + wait_err("controller error", state, ctrl, intr); + return -EIO; + } + if (!(intr & ONENAND_INT_RESET)) { + wait_err("timeout", state, ctrl, intr); + return -EIO; + } + return 0; + } + + if (state != FL_READING) { + int result; + + /* Turn interrupts on */ + syscfg = read_reg(c, ONENAND_REG_SYS_CFG1); + syscfg |= ONENAND_SYS_CFG1_IOBE; + write_reg(c, syscfg, ONENAND_REG_SYS_CFG1); + + INIT_COMPLETION(c->irq_done); + if (c->gpio_irq) { + result = omap_get_gpio_datain(c->gpio_irq); + if (result == -1) { + ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS); + intr = read_reg(c, ONENAND_REG_INTERRUPT); + wait_err("gpio error", state, ctrl, intr); + return -EIO; + } + } else + result = 0; + if (result == 0) { + int retry_cnt = 0; +retry: + result = wait_for_completion_timeout(&c->irq_done, + msecs_to_jiffies(20)); + if (result == 0) { + /* Timeout after 20ms */ + ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS); + if (ctrl & ONENAND_CTRL_ONGO) { + /* + * The operation seems to be still going + * so give it some more time. + */ + retry_cnt += 1; + if (retry_cnt < 3) + goto retry; + intr = read_reg(c, + ONENAND_REG_INTERRUPT); + wait_err("timeout", state, ctrl, intr); + return -EIO; + } + intr = read_reg(c, ONENAND_REG_INTERRUPT); + if ((intr & ONENAND_INT_MASTER) == 0) + wait_warn("timeout", state, ctrl, intr); + } + } + } else { + /* Turn interrupts off */ + syscfg = read_reg(c, ONENAND_REG_SYS_CFG1); + syscfg &= ~ONENAND_SYS_CFG1_IOBE; + write_reg(c, syscfg, ONENAND_REG_SYS_CFG1); + + timeout = jiffies + msecs_to_jiffies(20); + while (time_before(jiffies, timeout)) { + intr = read_reg(c, ONENAND_REG_INTERRUPT); + if (intr & ONENAND_INT_MASTER) + break; + } + } + + intr = read_reg(c, ONENAND_REG_INTERRUPT); + ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS); + + if (intr & ONENAND_INT_READ) { + int ecc = read_reg(c, ONENAND_REG_ECC_STATUS); + + if (ecc) { + unsigned int addr1, addr8; + + addr1 = read_reg(c, ONENAND_REG_START_ADDRESS1); + addr8 = read_reg(c, ONENAND_REG_START_ADDRESS8); + if (ecc & ONENAND_ECC_2BIT_ALL) { + printk(KERN_ERR "onenand_wait: ECC error = " + "0x%04x, addr1 %#x, addr8 %#x\n", + ecc, addr1, addr8); + mtd->ecc_stats.failed++; + return -EBADMSG; + } else if (ecc & ONENAND_ECC_1BIT_ALL) { + printk(KERN_NOTICE "onenand_wait: correctable " + "ECC error = 0x%04x, addr1 %#x, " + "addr8 %#x\n", ecc, addr1, addr8); + mtd->ecc_stats.corrected++; + } + } + } else if (state == FL_READING) { + wait_err("timeout", state, ctrl, intr); + return -EIO; + } + + if (ctrl & ONENAND_CTRL_ERROR) { + wait_err("controller error", state, ctrl, intr); + if (ctrl & ONENAND_CTRL_LOCK) + printk(KERN_ERR "onenand_wait: " + "Device is write protected!!!\n"); + return -EIO; + } + + if (ctrl & 0xFE9F) + wait_warn("unexpected controller status", state, ctrl, intr); + + return 0; +} + +static inline int omap2_onenand_bufferram_offset(struct mtd_info *mtd, int area) +{ + struct onenand_chip *this = mtd->priv; + + if (ONENAND_CURRENT_BUFFERRAM(this)) { + if (area == ONENAND_DATARAM) + return mtd->writesize; + if (area == ONENAND_SPARERAM) + return mtd->oobsize; + } + + return 0; +} + +#if defined(CONFIG_ARCH_OMAP3) || defined(MULTI_OMAP2) + +static int omap3_onenand_read_bufferram(struct mtd_info *mtd, int area, + unsigned char *buffer, int offset, + size_t count) +{ + struct omap2_onenand *c = container_of(mtd, struct omap2_onenand, mtd); + struct onenand_chip *this = mtd->priv; + dma_addr_t dma_src, dma_dst; + int bram_offset; + unsigned long timeout; + void *buf = (void *)buffer; + size_t xtra; + volatile unsigned *done; + + bram_offset = omap2_onenand_bufferram_offset(mtd, area) + area + offset; + if (bram_offset & 3 || (size_t)buf & 3 || count < 384) + goto out_copy; + + if (buf >= high_memory) { + struct page *p1; + + if (((size_t)buf & PAGE_MASK) != + ((size_t)(buf + count - 1) & PAGE_MASK)) + goto out_copy; + p1 = vmalloc_to_page(buf); + if (!p1) + goto out_copy; + buf = page_address(p1) + ((size_t)buf & ~PAGE_MASK); + } + + xtra = count & 3; + if (xtra) { + count -= xtra; + memcpy(buf + count, this->base + bram_offset + count, xtra); + } + + dma_src = c->phys_base + bram_offset; + dma_dst = dma_map_single(&c->pdev->dev, buf, count, DMA_FROM_DEVICE); + if (dma_mapping_error(&c->pdev->dev, dma_dst)) { + dev_err(&c->pdev->dev, + "Couldn't DMA map a %d byte buffer\n", + count); + goto out_copy; + } + + omap_set_dma_transfer_params(c->dma_channel, OMAP_DMA_DATA_TYPE_S32, + count >> 2, 1, 0, 0, 0); + omap_set_dma_src_params(c->dma_channel, 0, OMAP_DMA_AMODE_POST_INC, + dma_src, 0, 0); + omap_set_dma_dest_params(c->dma_channel, 0, OMAP_DMA_AMODE_POST_INC, + dma_dst, 0, 0); + + INIT_COMPLETION(c->dma_done); + omap_start_dma(c->dma_channel); + + timeout = jiffies + msecs_to_jiffies(20); + done = &c->dma_done.done; + while (time_before(jiffies, timeout)) + if (*done) + break; + + dma_unmap_single(&c->pdev->dev, dma_dst, count, DMA_FROM_DEVICE); + + if (!*done) { + dev_err(&c->pdev->dev, "timeout waiting for DMA\n"); + goto out_copy; + } + + return 0; + +out_copy: + memcpy(buf, this->base + bram_offset, count); + return 0; +} + +static int omap3_onenand_write_bufferram(struct mtd_info *mtd, int area, + const unsigned char *buffer, + int offset, size_t count) +{ + struct omap2_onenand *c = container_of(mtd, struct omap2_onenand, mtd); + struct onenand_chip *this = mtd->priv; + dma_addr_t dma_src, dma_dst; + int bram_offset; + unsigned long timeout; + void *buf = (void *)buffer; + volatile unsigned *done; + + bram_offset = omap2_onenand_bufferram_offset(mtd, area) + area + offset; + if (bram_offset & 3 || (size_t)buf & 3 || count < 384) + goto out_copy; + + /* panic_write() may be in an interrupt context */ + if (in_interrupt()) + goto out_copy; + + if (buf >= high_memory) { + struct page *p1; + + if (((size_t)buf & PAGE_MASK) != + ((size_t)(buf + count - 1) & PAGE_MASK)) + goto out_copy; + p1 = vmalloc_to_page(buf); + if (!p1) + goto out_copy; + buf = page_address(p1) + ((size_t)buf & ~PAGE_MASK); + } + + dma_src = dma_map_single(&c->pdev->dev, buf, count, DMA_TO_DEVICE); + dma_dst = c->phys_base + bram_offset; + if (dma_mapping_error(&c->pdev->dev, dma_dst)) { + dev_err(&c->pdev->dev, + "Couldn't DMA map a %d byte buffer\n", + count); + return -1; + } + + omap_set_dma_transfer_params(c->dma_channel, OMAP_DMA_DATA_TYPE_S32, + count >> 2, 1, 0, 0, 0); + omap_set_dma_src_params(c->dma_channel, 0, OMAP_DMA_AMODE_POST_INC, + dma_src, 0, 0); + omap_set_dma_dest_params(c->dma_channel, 0, OMAP_DMA_AMODE_POST_INC, + dma_dst, 0, 0); + + INIT_COMPLETION(c->dma_done); + omap_start_dma(c->dma_channel); + + timeout = jiffies + msecs_to_jiffies(20); + done = &c->dma_done.done; + while (time_before(jiffies, timeout)) + if (*done) + break; + + dma_unmap_single(&c->pdev->dev, dma_dst, count, DMA_TO_DEVICE); + + if (!*done) { + dev_err(&c->pdev->dev, "timeout waiting for DMA\n"); + goto out_copy; + } + + return 0; + +out_copy: + memcpy(this->base + bram_offset, buf, count); + return 0; +} + +#else + +int omap3_onenand_read_bufferram(struct mtd_info *mtd, int area, + unsigned char *buffer, int offset, + size_t count); + +int omap3_onenand_write_bufferram(struct mtd_info *mtd, int area, + const unsigned char *buffer, + int offset, size_t count); + +#endif + +#if defined(CONFIG_ARCH_OMAP2) || defined(MULTI_OMAP2) + +static int omap2_onenand_read_bufferram(struct mtd_info *mtd, int area, + unsigned char *buffer, int offset, + size_t count) +{ + struct omap2_onenand *c = container_of(mtd, struct omap2_onenand, mtd); + struct onenand_chip *this = mtd->priv; + dma_addr_t dma_src, dma_dst; + int bram_offset; + + bram_offset = omap2_onenand_bufferram_offset(mtd, area) + area + offset; + /* DMA is not used. Revisit PM requirements before enabling it. */ + if (1 || (c->dma_channel < 0) || + ((void *) buffer >= (void *) high_memory) || (bram_offset & 3) || + (((unsigned int) buffer) & 3) || (count < 1024) || (count & 3)) { + memcpy(buffer, (__force void *)(this->base + bram_offset), + count); + return 0; + } + + dma_src = c->phys_base + bram_offset; + dma_dst = dma_map_single(&c->pdev->dev, buffer, count, + DMA_FROM_DEVICE); + if (dma_mapping_error(&c->pdev->dev, dma_dst)) { + dev_err(&c->pdev->dev, + "Couldn't DMA map a %d byte buffer\n", + count); + return -1; + } + + omap_set_dma_transfer_params(c->dma_channel, OMAP_DMA_DATA_TYPE_S32, + count / 4, 1, 0, 0, 0); + omap_set_dma_src_params(c->dma_channel, 0, OMAP_DMA_AMODE_POST_INC, + dma_src, 0, 0); + omap_set_dma_dest_params(c->dma_channel, 0, OMAP_DMA_AMODE_POST_INC, + dma_dst, 0, 0); + + INIT_COMPLETION(c->dma_done); + omap_start_dma(c->dma_channel); + wait_for_completion(&c->dma_done); + + dma_unmap_single(&c->pdev->dev, dma_dst, count, DMA_FROM_DEVICE); + + return 0; +} + +static int omap2_onenand_write_bufferram(struct mtd_info *mtd, int area, + const unsigned char *buffer, + int offset, size_t count) +{ + struct omap2_onenand *c = container_of(mtd, struct omap2_onenand, mtd); + struct onenand_chip *this = mtd->priv; + dma_addr_t dma_src, dma_dst; + int bram_offset; + + bram_offset = omap2_onenand_bufferram_offset(mtd, area) + area + offset; + /* DMA is not used. Revisit PM requirements before enabling it. */ + if (1 || (c->dma_channel < 0) || + ((void *) buffer >= (void *) high_memory) || (bram_offset & 3) || + (((unsigned int) buffer) & 3) || (count < 1024) || (count & 3)) { + memcpy((__force void *)(this->base + bram_offset), buffer, + count); + return 0; + } + + dma_src = dma_map_single(&c->pdev->dev, (void *) buffer, count, + DMA_TO_DEVICE); + dma_dst = c->phys_base + bram_offset; + if (dma_mapping_error(&c->pdev->dev, dma_dst)) { + dev_err(&c->pdev->dev, + "Couldn't DMA map a %d byte buffer\n", + count); + return -1; + } + + omap_set_dma_transfer_params(c->dma_channel, OMAP_DMA_DATA_TYPE_S16, + count / 2, 1, 0, 0, 0); + omap_set_dma_src_params(c->dma_channel, 0, OMAP_DMA_AMODE_POST_INC, + dma_src, 0, 0); + omap_set_dma_dest_params(c->dma_channel, 0, OMAP_DMA_AMODE_POST_INC, + dma_dst, 0, 0); + + INIT_COMPLETION(c->dma_done); + omap_start_dma(c->dma_channel); + wait_for_completion(&c->dma_done); + + dma_unmap_single(&c->pdev->dev, dma_dst, count, DMA_TO_DEVICE); + + return 0; +} + +#else + +int omap2_onenand_read_bufferram(struct mtd_info *mtd, int area, + unsigned char *buffer, int offset, + size_t count); + +int omap2_onenand_write_bufferram(struct mtd_info *mtd, int area, + const unsigned char *buffer, + int offset, size_t count); + +#endif + +static struct platform_driver omap2_onenand_driver; + +static int __adjust_timing(struct device *dev, void *data) +{ + int ret = 0; + struct omap2_onenand *c; + + c = dev_get_drvdata(dev); + + BUG_ON(c->setup == NULL); + + /* DMA is not in use so this is all that is needed */ + /* Revisit for OMAP3! */ + ret = c->setup(c->onenand.base, c->freq); + + return ret; +} + +int omap2_onenand_rephase(void) +{ + return driver_for_each_device(&omap2_onenand_driver.driver, NULL, + NULL, __adjust_timing); +} + +static void __devexit omap2_onenand_shutdown(struct platform_device *pdev) +{ + struct omap2_onenand *c = dev_get_drvdata(&pdev->dev); + + /* With certain content in the buffer RAM, the OMAP boot ROM code + * can recognize the flash chip incorrectly. Zero it out before + * soft reset. + */ + memset((__force void *)c->onenand.base, 0, ONENAND_BUFRAM_SIZE); +} + +static int __devinit omap2_onenand_probe(struct platform_device *pdev) +{ + struct omap_onenand_platform_data *pdata; + struct omap2_onenand *c; + int r; + + pdata = pdev->dev.platform_data; + if (pdata == NULL) { + dev_err(&pdev->dev, "platform data missing\n"); + return -ENODEV; + } + + c = kzalloc(sizeof(struct omap2_onenand), GFP_KERNEL); + if (!c) + return -ENOMEM; + + init_completion(&c->irq_done); + init_completion(&c->dma_done); + c->gpmc_cs = pdata->cs; + c->gpio_irq = pdata->gpio_irq; + c->dma_channel = pdata->dma_channel; + if (c->dma_channel < 0) { + /* if -1, don't use DMA */ + c->gpio_irq = 0; + } + + r = gpmc_cs_request(c->gpmc_cs, ONENAND_IO_SIZE, &c->phys_base); + if (r < 0) { + dev_err(&pdev->dev, "Cannot request GPMC CS\n"); + goto err_kfree; + } + + if (request_mem_region(c->phys_base, ONENAND_IO_SIZE, + pdev->dev.driver->name) == NULL) { + dev_err(&pdev->dev, "Cannot reserve memory region at 0x%08lx, " + "size: 0x%x\n", c->phys_base, ONENAND_IO_SIZE); + r = -EBUSY; + goto err_free_cs; + } + c->onenand.base = ioremap(c->phys_base, ONENAND_IO_SIZE); + if (c->onenand.base == NULL) { + r = -ENOMEM; + goto err_release_mem_region; + } + + if (pdata->onenand_setup != NULL) { + r = pdata->onenand_setup(c->onenand.base, c->freq); + if (r < 0) { + dev_err(&pdev->dev, "Onenand platform setup failed: " + "%d\n", r); + goto err_iounmap; + } + c->setup = pdata->onenand_setup; + } + + if (c->gpio_irq) { + if ((r = omap_request_gpio(c->gpio_irq)) < 0) { + dev_err(&pdev->dev, "Failed to request GPIO%d for " + "OneNAND\n", c->gpio_irq); + goto err_iounmap; + } + omap_set_gpio_direction(c->gpio_irq, 1); + + if ((r = request_irq(OMAP_GPIO_IRQ(c->gpio_irq), + omap2_onenand_interrupt, IRQF_TRIGGER_RISING, + pdev->dev.driver->name, c)) < 0) + goto err_release_gpio; + } + + if (c->dma_channel >= 0) { + r = omap_request_dma(0, pdev->dev.driver->name, + omap2_onenand_dma_cb, (void *) c, + &c->dma_channel); + if (r == 0) { + omap_set_dma_write_mode(c->dma_channel, + OMAP_DMA_WRITE_NON_POSTED); + omap_set_dma_src_data_pack(c->dma_channel, 1); + omap_set_dma_src_burst_mode(c->dma_channel, + OMAP_DMA_DATA_BURST_8); + omap_set_dma_dest_data_pack(c->dma_channel, 1); + omap_set_dma_dest_burst_mode(c->dma_channel, + OMAP_DMA_DATA_BURST_8); + } else { + dev_info(&pdev->dev, + "failed to allocate DMA for OneNAND, " + "using PIO instead\n"); + c->dma_channel = -1; + } + } + + dev_info(&pdev->dev, "initializing on CS%d, phys base 0x%08lx, virtual " + "base %p\n", c->gpmc_cs, c->phys_base, + c->onenand.base); + + c->pdev = pdev; + c->mtd.name = pdev->dev.bus_id; + c->mtd.priv = &c->onenand; + c->mtd.owner = THIS_MODULE; + + if (c->dma_channel >= 0) { + struct onenand_chip *this = &c->onenand; + + this->wait = omap2_onenand_wait; + if (cpu_is_omap34xx()) { + this->read_bufferram = omap3_onenand_read_bufferram; + this->write_bufferram = omap3_onenand_write_bufferram; + } else { + this->read_bufferram = omap2_onenand_read_bufferram; + this->write_bufferram = omap2_onenand_write_bufferram; + } + } + + if ((r = onenand_scan(&c->mtd, 1)) < 0) + goto err_release_dma; + + switch ((c->onenand.version_id >> 4) & 0xf) { + case 0: + c->freq = 40; + break; + case 1: + c->freq = 54; + break; + case 2: + c->freq = 66; + break; + case 3: + c->freq = 83; + break; + } + +#ifdef CONFIG_MTD_PARTITIONS + if (pdata->parts != NULL) + r = add_mtd_partitions(&c->mtd, pdata->parts, + pdata->nr_parts); + else +#endif + r = add_mtd_device(&c->mtd); + if (r < 0) + goto err_release_onenand; + + platform_set_drvdata(pdev, c); + + return 0; + +err_release_onenand: + onenand_release(&c->mtd); +err_release_dma: + if (c->dma_channel != -1) + omap_free_dma(c->dma_channel); + if (c->gpio_irq) + free_irq(OMAP_GPIO_IRQ(c->gpio_irq), c); +err_release_gpio: + if (c->gpio_irq) + omap_free_gpio(c->gpio_irq); +err_iounmap: + iounmap(c->onenand.base); +err_release_mem_region: + release_mem_region(c->phys_base, ONENAND_IO_SIZE); +err_free_cs: + gpmc_cs_free(c->gpmc_cs); +err_kfree: + kfree(c); + + return r; +} + +static int __devexit omap2_onenand_remove(struct platform_device *pdev) +{ + struct omap2_onenand *c = dev_get_drvdata(&pdev->dev); + + BUG_ON(c == NULL); + +#ifdef CONFIG_MTD_PARTITIONS + if (c->parts) + del_mtd_partitions(&c->mtd); + else + del_mtd_device(&c->mtd); +#else + del_mtd_device(&c->mtd); +#endif + + onenand_release(&c->mtd); + if (c->dma_channel != -1) + omap_free_dma(c->dma_channel); + omap2_onenand_shutdown(pdev); + platform_set_drvdata(pdev, NULL); + if (c->gpio_irq) { + free_irq(OMAP_GPIO_IRQ(c->gpio_irq), c); + omap_free_gpio(c->gpio_irq); + } + iounmap(c->onenand.base); + release_mem_region(c->phys_base, ONENAND_IO_SIZE); + kfree(c); + + return 0; +} + +static struct platform_driver omap2_onenand_driver = { + .probe = omap2_onenand_probe, + .remove = omap2_onenand_remove, + .shutdown = omap2_onenand_shutdown, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init omap2_onenand_init(void) +{ + printk(KERN_INFO "OneNAND driver initializing\n"); + return platform_driver_register(&omap2_onenand_driver); +} + +static void __exit omap2_onenand_exit(void) +{ + platform_driver_unregister(&omap2_onenand_driver); +} + +module_init(omap2_onenand_init); +module_exit(omap2_onenand_exit); + +MODULE_ALIAS(DRIVER_NAME); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jarkko Lavinen "); +MODULE_DESCRIPTION("Glue layer for OneNAND flash on OMAP2 / OMAP3"); diff --cc drivers/net/irda/omap-ir.c index 7e2f74e9160,00000000000..b706253aae0 mode 100644,000000..100644 --- a/drivers/net/irda/omap-ir.c +++ b/drivers/net/irda/omap-ir.c @@@ -1,901 -1,0 +1,901 @@@ +/* + * BRIEF MODULE DESCRIPTION + * + * Infra-red driver for the OMAP1610-H2 and OMAP1710-H3 and H4 Platforms + * (SIR/MIR/FIR modes) + * (based on omap-sir.c) + * + * Copyright 2003 MontaVista Software Inc. + * Author: MontaVista Software, Inc. + * source@mvista.com + * + * Copyright 2004 Texas Instruments. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + Modifications: + Feb 2004, Texas Instruments + - Ported to 2.6 kernel (Feb 2004). + * + Apr 2004, Texas Instruments + - Added support for H3 (Apr 2004). + Nov 2004, Texas Instruments + - Added support for Power Management. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include - #include ++#include +#include +#include +#include - #include - #include - #include ++#include ++#include ++#include + +#define UART3_EFR_EN (1 << 4) +#define UART3_MCR_EN_TCR_TLR (1 << 6) + +#define UART3_LCR_WL_8 (3 << 0) +#define UART3_LCR_SP2 (1 << 2) +#define UART3_LCR_DIVEN (1 << 7) + +#define UART3_FCR_FIFO_EN (1 << 0) +#define UART3_FCR_FIFO_RX (1 << 1) +#define UART3_FCR_FIFO_TX (1 << 2) +#define UART3_FCR_FIFO_DMA1 (1 << 3) +#define UART3_FCR_FIFO_TX_TRIG16 (1 << 4) +#define UART3_FCR_FIFO_RX_TRIG16 (1 << 6) +#define UART3_FCR_CONFIG (\ + UART3_FCR_FIFO_EN | UART3_FCR_FIFO_RX |\ + UART3_FCR_FIFO_TX | UART3_FCR_FIFO_DMA1 |\ + UART3_FCR_FIFO_TX_TRIG16 |\ + UART3_FCR_FIFO_RX_TRIG16) + +#define UART3_SCR_TX_TRIG1 (1 << 6) +#define UART3_SCR_RX_TRIG1 (1 << 7) + +#define UART3_MDR1_RESET (0x07) +#define UART3_MDR1_SIR (1 << 0) +#define UART3_MDR1_MIR (4 << 0) +#define UART3_MDR1_FIR (5 << 0) +#define UART3_MDR1_SIP_AUTO (1 << 6) + +#define UART3_MDR2_TRIG1 (0 << 1) +#define UART3_MDR2_IRTX_UNDERRUN (1 << 0) + +#define UART3_ACERG_TX_UNDERRUN_DIS (1 << 4) +#define UART3_ACERG_SD_MODE_LOW (1 << 6) +#define UART3_ACERG_DIS_IR_RX (1 << 5) + +#define UART3_IER_EOF (1 << 5) +#define UART3_IER_CTS (1 << 7) + +#define UART3_IIR_TX_STATUS (1 << 5) +#define UART3_IIR_EOF (0x80) + +#define IS_FIR(omap_ir) ((omap_ir)->speed >= 4000000) + +struct omap_irda { + unsigned char open; + int speed; /* Current IrDA speed */ + int newspeed; + + struct net_device_stats stats; + struct irlap_cb *irlap; + struct qos_info qos; + + int rx_dma_channel; + int tx_dma_channel; + + dma_addr_t rx_buf_dma_phys; /* Physical address of RX DMA buffer */ + dma_addr_t tx_buf_dma_phys; /* Physical address of TX DMA buffer */ + + void *rx_buf_dma_virt; /* Virtual address of RX DMA buffer */ + void *tx_buf_dma_virt; /* Virtual address of TX DMA buffer */ + + struct device *dev; + struct omap_irda_config *pdata; +}; + +static void inline uart_reg_out(int idx, u8 val) +{ + omap_writeb(val, idx); +} + +static u8 inline uart_reg_in(int idx) +{ + u8 b = omap_readb(idx); + return b; +} + +/* forward declarations */ +extern void omap_stop_dma(int lch); +static int omap_irda_set_speed(struct net_device *dev, int speed); + +static void omap_irda_start_rx_dma(struct omap_irda *omap_ir) +{ + /* Configure DMA */ + omap_set_dma_src_params(omap_ir->rx_dma_channel, 0x3, 0x0, + omap_ir->pdata->src_start, + 0, 0); + + omap_enable_dma_irq(omap_ir->rx_dma_channel, 0x01); + + omap_set_dma_dest_params(omap_ir->rx_dma_channel, 0x0, 0x1, + omap_ir->rx_buf_dma_phys, + 0, 0); + + omap_set_dma_transfer_params(omap_ir->rx_dma_channel, 0x0, + IRDA_SKB_MAX_MTU, 0x1, + 0x0, omap_ir->pdata->rx_trigger, 0); + + omap_start_dma(omap_ir->rx_dma_channel); +} + +static void omap_start_tx_dma(struct omap_irda *omap_ir, int size) +{ + /* Configure DMA */ + omap_set_dma_dest_params(omap_ir->tx_dma_channel, 0x03, 0x0, + omap_ir->pdata->dest_start, 0, 0); + + omap_enable_dma_irq(omap_ir->tx_dma_channel, 0x01); + + omap_set_dma_src_params(omap_ir->tx_dma_channel, 0x0, 0x1, + omap_ir->tx_buf_dma_phys, + 0, 0); + + omap_set_dma_transfer_params(omap_ir->tx_dma_channel, 0x0, size, 0x1, + 0x0, omap_ir->pdata->tx_trigger, 0); + + /* Start DMA */ + omap_start_dma(omap_ir->tx_dma_channel); +} + +/* DMA RX callback - normally, we should not go here, + * it calls only if something is going wrong + */ +static void omap_irda_rx_dma_callback(int lch, u16 ch_status, void *data) +{ + struct net_device *dev = data; + struct omap_irda *omap_ir = netdev_priv(dev); + + printk(KERN_ERR "RX Transfer error or very big frame\n"); + + /* Clear interrupts */ + uart_reg_in(UART3_IIR); + + omap_ir->stats.rx_frame_errors++; + + uart_reg_in(UART3_RESUME); + + /* Re-init RX DMA */ + omap_irda_start_rx_dma(omap_ir); +} + +/* DMA TX callback - calling when frame transfer has been finished */ +static void omap_irda_tx_dma_callback(int lch, u16 ch_status, void *data) +{ + struct net_device *dev = data; + struct omap_irda *omap_ir = netdev_priv(dev); + + /*Stop DMA controller */ + omap_stop_dma(omap_ir->tx_dma_channel); +} + +/* + * Set the IrDA communications speed. + * Interrupt have to be disabled here. + */ +static int omap_irda_startup(struct net_device *dev) +{ + struct omap_irda *omap_ir = netdev_priv(dev); + + /* FIXME: use clk_* apis for UART3 clock*/ + /* Enable UART3 clock and set UART3 to IrDA mode */ + if (machine_is_omap_h2() || machine_is_omap_h3()) + omap_writel(omap_readl(MOD_CONF_CTRL_0) | (1 << 31) | (1 << 15), + MOD_CONF_CTRL_0); + + /* Only for H2? + */ + if (omap_ir->pdata->transceiver_mode && machine_is_omap_h2()) { + /* Is it select_irda on H2 ? */ + omap_writel(omap_readl(FUNC_MUX_CTRL_A) | 7, + FUNC_MUX_CTRL_A); + omap_ir->pdata->transceiver_mode(omap_ir->dev, IR_SIRMODE); + } + + uart_reg_out(UART3_MDR1, UART3_MDR1_RESET); /* Reset mode */ + + /* Clear DLH and DLL */ + uart_reg_out(UART3_LCR, UART3_LCR_DIVEN); + + uart_reg_out(UART3_DLL, 0); + uart_reg_out(UART3_DLH, 0); + uart_reg_out(UART3_LCR, 0xbf); /* FIXME: Add #define */ + + uart_reg_out(UART3_EFR, UART3_EFR_EN); + uart_reg_out(UART3_LCR, UART3_LCR_DIVEN); + + /* Enable access to UART3_TLR and UART3_TCR registers */ + uart_reg_out(UART3_MCR, UART3_MCR_EN_TCR_TLR); + + uart_reg_out(UART3_SCR, 0); + /* Set Rx trigger to 1 and Tx trigger to 1 */ + uart_reg_out(UART3_TLR, 0); + + /* Set LCR to 8 bits and 1 stop bit */ + uart_reg_out(UART3_LCR, 0x03); + + /* Clear RX and TX FIFO and enable FIFO */ + /* Use DMA Req for transfers */ + uart_reg_out(UART3_FCR, UART3_FCR_CONFIG); + + uart_reg_out(UART3_MCR, 0); + + uart_reg_out(UART3_SCR, UART3_SCR_TX_TRIG1 | + UART3_SCR_RX_TRIG1); + + /* Enable UART3 SIR Mode,(Frame-length method to end frames) */ + uart_reg_out(UART3_MDR1, UART3_MDR1_SIR); + + /* Set Status FIFO trig to 1 */ + uart_reg_out(UART3_MDR2, 0); + + /* Enables RXIR input */ + /* and disable TX underrun */ + /* SEND_SIP pulse */ + uart_reg_out(UART3_ACREG, UART3_ACERG_SD_MODE_LOW | + UART3_ACERG_TX_UNDERRUN_DIS); + + /* Enable EOF Interrupt only */ + uart_reg_out(UART3_IER, UART3_IER_CTS | UART3_IER_EOF); + + /* Set Maximum Received Frame size to 2048 bytes */ + uart_reg_out(UART3_RXFLL, 0x00); + uart_reg_out(UART3_RXFLH, 0x08); + + uart_reg_in(UART3_RESUME); + + return 0; +} + +static int omap_irda_shutdown(struct omap_irda *omap_ir) +{ + unsigned long flags; + + local_irq_save(flags); + + /* Disable all UART3 Interrupts */ + uart_reg_out(UART3_IER, 0); + + /* Disable UART3 and disable baud rate generator */ + uart_reg_out(UART3_MDR1, UART3_MDR1_RESET); + + /* set SD_MODE pin to high and Disable RX IR */ + uart_reg_out(UART3_ACREG, (UART3_ACERG_DIS_IR_RX | + ~(UART3_ACERG_SD_MODE_LOW))); + + /* Clear DLH and DLL */ + uart_reg_out(UART3_LCR, UART3_LCR_DIVEN); + uart_reg_out(UART3_DLL, 0); + uart_reg_out(UART3_DLH, 0); + + local_irq_restore(flags); + + return 0; +} + +static irqreturn_t +omap_irda_irq(int irq, void *dev_id) +{ + struct net_device *dev = dev_id; + struct omap_irda *omap_ir = netdev_priv(dev); + struct sk_buff *skb; + + u8 status; + int w = 0; + + /* Clear EOF interrupt */ + status = uart_reg_in(UART3_IIR); + + if (status & UART3_IIR_TX_STATUS) { + u8 mdr2 = uart_reg_in(UART3_MDR2); + if (mdr2 & UART3_MDR2_IRTX_UNDERRUN) + printk(KERN_ERR "IrDA Buffer underrun error\n"); + + omap_ir->stats.tx_packets++; + + if (omap_ir->newspeed) { + omap_irda_set_speed(dev, omap_ir->newspeed); + omap_ir->newspeed = 0; + } + + netif_wake_queue(dev); + if (!(status & UART3_IIR_EOF)) + return IRQ_HANDLED; + } + + /* Stop DMA and if there are no errors, send frame to upper layer */ + omap_stop_dma(omap_ir->rx_dma_channel); + + status = uart_reg_in(UART3_SFLSR); /* Take a frame status */ + + if (status != 0) { /* Bad frame? */ + omap_ir->stats.rx_frame_errors++; + uart_reg_in(UART3_RESUME); + } else { + /* We got a frame! */ + skb = dev_alloc_skb(IRDA_SKB_MAX_MTU); + + if (!skb) { + printk(KERN_ERR "omap_sir: out of memory for RX SKB\n"); + return IRQ_HANDLED; + } + /* + * Align any IP headers that may be contained + * within the frame. + */ + + skb_reserve(skb, 1); + + w = omap_get_dma_dst_pos(omap_ir->rx_dma_channel) - + omap_ir->rx_buf_dma_phys; + + if (!IS_FIR(omap_ir)) + /* Copy DMA buffer to skb */ + memcpy(skb_put(skb, w - 2), omap_ir->rx_buf_dma_virt, + w - 2); + else + /* Copy DMA buffer to skb */ + memcpy(skb_put(skb, w - 4), omap_ir->rx_buf_dma_virt, + w - 4); + + skb->dev = dev; + skb_reset_mac_header(skb); + skb->protocol = htons(ETH_P_IRDA); + omap_ir->stats.rx_packets++; + omap_ir->stats.rx_bytes += skb->len; + netif_receive_skb(skb); /* Send data to upper level */ + } + + /* Re-init RX DMA */ + omap_irda_start_rx_dma(omap_ir); + + dev->last_rx = jiffies; + + return IRQ_HANDLED; +} + +static int omap_irda_hard_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct omap_irda *omap_ir = netdev_priv(dev); + int speed = irda_get_next_speed(skb); + int mtt = irda_get_mtt(skb); + int xbofs = irda_get_next_xbofs(skb); + + + /* + * Does this packet contain a request to change the interface + * speed? If so, remember it until we complete the transmission + * of this frame. + */ + if (speed != omap_ir->speed && speed != -1) + omap_ir->newspeed = speed; + + if (xbofs) /* Set number of addtional BOFS */ + uart_reg_out(UART3_EBLR, xbofs + 1); + + /* + * If this is an empty frame, we can bypass a lot. + */ + if (skb->len == 0) { + if (omap_ir->newspeed) { + omap_ir->newspeed = 0; + omap_irda_set_speed(dev, speed); + } + dev_kfree_skb(skb); + return 0; + } + + netif_stop_queue(dev); + + /* Copy skb data to DMA buffer */ + skb_copy_from_linear_data(skb, omap_ir->tx_buf_dma_virt, skb->len); + + /* Copy skb data to DMA buffer */ + omap_ir->stats.tx_bytes += skb->len; + + /* Set frame length */ + uart_reg_out(UART3_TXFLL, (skb->len & 0xff)); + uart_reg_out(UART3_TXFLH, (skb->len >> 8)); + + if (mtt > 1000) + mdelay(mtt / 1000); + else + udelay(mtt); + + /* Start TX DMA transfer */ + omap_start_tx_dma(omap_ir, skb->len); + + /* We can free skb now because it's already in DMA buffer */ + dev_kfree_skb(skb); + + dev->trans_start = jiffies; + + return 0; +} + +static int +omap_irda_ioctl(struct net_device *dev, struct ifreq *ifreq, int cmd) +{ + struct if_irda_req *rq = (struct if_irda_req *)ifreq; + struct omap_irda *omap_ir = netdev_priv(dev); + int ret = -EOPNOTSUPP; + + + switch (cmd) { + case SIOCSBANDWIDTH: + if (capable(CAP_NET_ADMIN)) { + /* + * We are unable to set the speed if the + * device is not running. + */ + if (omap_ir->open) + ret = omap_irda_set_speed(dev, + rq->ifr_baudrate); + else { + printk(KERN_ERR "omap_ir: SIOCSBANDWIDTH:" + " !netif_running\n"); + ret = 0; + } + } + break; + + case SIOCSMEDIABUSY: + ret = -EPERM; + if (capable(CAP_NET_ADMIN)) { + irda_device_set_media_busy(dev, TRUE); + ret = 0; + } + break; + + case SIOCGRECEIVING: + rq->ifr_receiving = 0; + break; + + default: + break; + } + + return ret; +} + +static struct net_device_stats *omap_irda_stats(struct net_device *dev) +{ + struct omap_irda *omap_ir = netdev_priv(dev); + return &omap_ir->stats; +} + +static int omap_irda_start(struct net_device *dev) +{ + struct omap_irda *omap_ir = netdev_priv(dev); + int err; + + omap_ir->speed = 9600; + + err = request_irq(dev->irq, omap_irda_irq, 0, dev->name, dev); + if (err) + goto err_irq; + + /* + * The interrupt must remain disabled for now. + */ + disable_irq(dev->irq); + + /* Request DMA channels for IrDA hardware */ + if (omap_request_dma(omap_ir->pdata->rx_channel, "IrDA Rx DMA", + (void *)omap_irda_rx_dma_callback, + dev, &(omap_ir->rx_dma_channel))) { + printk(KERN_ERR "Failed to request IrDA Rx DMA\n"); + goto err_irq; + } + + if (omap_request_dma(omap_ir->pdata->tx_channel, "IrDA Tx DMA", + (void *)omap_irda_tx_dma_callback, + dev, &(omap_ir->tx_dma_channel))) { + printk(KERN_ERR "Failed to request IrDA Tx DMA\n"); + goto err_irq; + } + + /* Allocate TX and RX buffers for DMA channels */ + omap_ir->rx_buf_dma_virt = + dma_alloc_coherent(NULL, IRDA_SKB_MAX_MTU, + &(omap_ir->rx_buf_dma_phys), + GFP_KERNEL); + + if (!omap_ir->rx_buf_dma_virt) { + printk(KERN_ERR "Unable to allocate memory for rx_buf_dma\n"); + goto err_irq; + } + + omap_ir->tx_buf_dma_virt = + dma_alloc_coherent(NULL, IRDA_SIR_MAX_FRAME, + &(omap_ir->tx_buf_dma_phys), + GFP_KERNEL); + + if (!omap_ir->tx_buf_dma_virt) { + printk(KERN_ERR "Unable to allocate memory for tx_buf_dma\n"); + goto err_mem1; + } + + /* + * Setup the serial port for the specified config. + */ + if (omap_ir->pdata->select_irda) + omap_ir->pdata->select_irda(omap_ir->dev, IR_SEL); + + err = omap_irda_startup(dev); + + if (err) + goto err_startup; + + omap_irda_set_speed(dev, omap_ir->speed = 9600); + + /* + * Open a new IrLAP layer instance. + */ + omap_ir->irlap = irlap_open(dev, &omap_ir->qos, "omap_sir"); + + err = -ENOMEM; + if (!omap_ir->irlap) + goto err_irlap; + + /* Now enable the interrupt and start the queue */ + omap_ir->open = 1; + + /* Start RX DMA */ + omap_irda_start_rx_dma(omap_ir); + + enable_irq(dev->irq); + netif_start_queue(dev); + + return 0; + +err_irlap: + omap_ir->open = 0; + omap_irda_shutdown(omap_ir); +err_startup: + dma_free_coherent(NULL, IRDA_SIR_MAX_FRAME, + omap_ir->tx_buf_dma_virt, omap_ir->tx_buf_dma_phys); +err_mem1: + dma_free_coherent(NULL, IRDA_SKB_MAX_MTU, + omap_ir->rx_buf_dma_virt, omap_ir->rx_buf_dma_phys); +err_irq: + free_irq(dev->irq, dev); + return err; +} + +static int omap_irda_stop(struct net_device *dev) +{ + struct omap_irda *omap_ir = netdev_priv(dev); + + disable_irq(dev->irq); + + netif_stop_queue(dev); + + omap_free_dma(omap_ir->rx_dma_channel); + omap_free_dma(omap_ir->tx_dma_channel); + + if (omap_ir->rx_buf_dma_virt) + dma_free_coherent(NULL, IRDA_SKB_MAX_MTU, + omap_ir->rx_buf_dma_virt, + omap_ir->rx_buf_dma_phys); + if (omap_ir->tx_buf_dma_virt) + dma_free_coherent(NULL, IRDA_SIR_MAX_FRAME, + omap_ir->tx_buf_dma_virt, + omap_ir->tx_buf_dma_phys); + + omap_irda_shutdown(omap_ir); + + /* Stop IrLAP */ + if (omap_ir->irlap) { + irlap_close(omap_ir->irlap); + omap_ir->irlap = NULL; + } + + omap_ir->open = 0; + + /* + * Free resources + */ + free_irq(dev->irq, dev); + + return 0; +} + +static int omap_irda_set_speed(struct net_device *dev, int speed) +{ + struct omap_irda *omap_ir = netdev_priv(dev); + int divisor; + unsigned long flags; + + /* Set IrDA speed */ + if (speed <= 115200) { + + local_irq_save(flags); + + /* SIR mode */ + if (omap_ir->pdata->transceiver_mode) + omap_ir->pdata->transceiver_mode(omap_ir->dev, + IR_SIRMODE); + + /* Set SIR mode */ + uart_reg_out(UART3_MDR1, 1); + uart_reg_out(UART3_EBLR, 1); + + divisor = 48000000 / (16 * speed); /* Base clock 48 MHz */ + + uart_reg_out(UART3_LCR, UART3_LCR_DIVEN); + uart_reg_out(UART3_DLL, (divisor & 0xff)); + uart_reg_out(UART3_DLH, (divisor >> 8)); + uart_reg_out(UART3_LCR, 0x03); + + uart_reg_out(UART3_MCR, 0); + + local_irq_restore(flags); + } else if (speed <= 1152000) { + + local_irq_save(flags); + + /* Set MIR mode, auto SIP */ + uart_reg_out(UART3_MDR1, UART3_MDR1_MIR | + UART3_MDR1_SIP_AUTO); + + uart_reg_out(UART3_EBLR, 2); + + divisor = 48000000 / (41 * speed); /* Base clock 48 MHz */ + + uart_reg_out(UART3_LCR, UART3_LCR_DIVEN); + uart_reg_out(UART3_DLL, (divisor & 0xff)); + uart_reg_out(UART3_DLH, (divisor >> 8)); + uart_reg_out(UART3_LCR, 0x03); + + if (omap_ir->pdata->transceiver_mode) + omap_ir->pdata->transceiver_mode(omap_ir->dev, + IR_MIRMODE); + + local_irq_restore(flags); + } else { + local_irq_save(flags); + + /* FIR mode */ + uart_reg_out(UART3_MDR1, UART3_MDR1_FIR | + UART3_MDR1_SIP_AUTO); + + if (omap_ir->pdata->transceiver_mode) + omap_ir->pdata->transceiver_mode(omap_ir->dev, + IR_FIRMODE); + + local_irq_restore(flags); + } + + omap_ir->speed = speed; + + return 0; +} + +#ifdef CONFIG_PM +/* + * Suspend the IrDA interface. + */ +static int omap_irda_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct net_device *dev = platform_get_drvdata(pdev); + struct omap_irda *omap_ir = netdev_priv(dev); + + if (!dev) + return 0; + + if (omap_ir->open) { + /* + * Stop the transmit queue + */ + netif_device_detach(dev); + disable_irq(dev->irq); + omap_irda_shutdown(omap_ir); + } + return 0; +} + +/* + * Resume the IrDA interface. + */ +static int omap_irda_resume(struct platform_device *pdev) +{ + struct net_device *dev = platform_get_drvdata(pdev); + struct omap_irda *omap_ir= netdev_priv(dev); + + if (!dev) + return 0; + + if (omap_ir->open) { + /* + * If we missed a speed change, initialise at the new speed + * directly. It is debatable whether this is actually + * required, but in the interests of continuing from where + * we left off it is desireable. The converse argument is + * that we should re-negotiate at 9600 baud again. + */ + if (omap_ir->newspeed) { + omap_ir->speed = omap_ir->newspeed; + omap_ir->newspeed = 0; + } + + omap_irda_startup(dev); + omap_irda_set_speed(dev, omap_ir->speed); + enable_irq(dev->irq); + + /* + * This automatically wakes up the queue + */ + netif_device_attach(dev); + } + + return 0; +} +#else +#define omap_irda_suspend NULL +#define omap_irda_resume NULL +#endif + +static int omap_irda_probe(struct platform_device *pdev) +{ + struct net_device *dev; + struct omap_irda *omap_ir; + struct omap_irda_config *pdata = pdev->dev.platform_data; + unsigned int baudrate_mask; + int err = 0; + int irq = NO_IRQ; + + if (!pdata) { + printk(KERN_ERR "IrDA Platform data not supplied\n"); + return -ENOENT; + } + + if (!pdata->rx_channel || !pdata->tx_channel) { + printk(KERN_ERR "IrDA invalid rx/tx channel value\n"); + return -ENOENT; + } + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) { + printk(KERN_WARNING "no irq for IrDA\n"); + return -ENOENT; + } + + dev = alloc_irdadev(sizeof(struct omap_irda)); + if (!dev) + goto err_mem_1; + + + omap_ir = netdev_priv(dev); + omap_ir->dev = &pdev->dev; + omap_ir->pdata = pdata; + + dev->hard_start_xmit = omap_irda_hard_xmit; + dev->open = omap_irda_start; + dev->stop = omap_irda_stop; + dev->do_ioctl = omap_irda_ioctl; + dev->get_stats = omap_irda_stats; + dev->irq = irq; + + irda_init_max_qos_capabilies(&omap_ir->qos); + + baudrate_mask = 0; + if (omap_ir->pdata->transceiver_cap & IR_SIRMODE) + baudrate_mask |= IR_9600|IR_19200|IR_38400|IR_57600|IR_115200; + if (omap_ir->pdata->transceiver_cap & IR_MIRMODE) + baudrate_mask |= IR_57600 | IR_1152000; + if (omap_ir->pdata->transceiver_cap & IR_FIRMODE) + baudrate_mask |= IR_4000000 << 8; + + omap_ir->qos.baud_rate.bits &= baudrate_mask; + omap_ir->qos.min_turn_time.bits = 7; + + irda_qos_bits_to_value(&omap_ir->qos); + + /* Any better way to avoid this? No. */ + if (machine_is_omap_h3() || machine_is_omap_h4()) + INIT_DELAYED_WORK(&omap_ir->pdata->gpio_expa, NULL); + + err = register_netdev(dev); + if (!err) + platform_set_drvdata(pdev, dev); + else + free_netdev(dev); + +err_mem_1: + return err; +} + +static int omap_irda_remove(struct platform_device *pdev) +{ + struct net_device *dev = platform_get_drvdata(pdev); + + if (pdev) { + unregister_netdev(dev); + free_netdev(dev); + } + return 0; +} + +static struct platform_driver omapir_driver = { + .probe = omap_irda_probe, + .remove = omap_irda_remove, + .suspend = omap_irda_suspend, + .resume = omap_irda_resume, + .driver = { + .name = "omapirda", + }, +}; + +static char __initdata banner[] = KERN_INFO "OMAP IrDA driver initializing\n"; + +static int __init omap_irda_init(void) +{ + printk(banner); + return platform_driver_register(&omapir_driver); +} + +static void __exit omap_irda_exit(void) +{ + platform_driver_unregister(&omapir_driver); +} + +module_init(omap_irda_init); +module_exit(omap_irda_exit); + +MODULE_AUTHOR("MontaVista"); +MODULE_DESCRIPTION("OMAP IrDA Driver"); +MODULE_LICENSE("GPL"); + diff --cc drivers/rtc/rtc-twl4030.c index 269ff615c0f,00000000000..bfcf5151141 mode 100644,000000..100644 --- a/drivers/rtc/rtc-twl4030.c +++ b/drivers/rtc/rtc-twl4030.c @@@ -1,655 -1,0 +1,655 @@@ +/* + * drivers/rtc/rtc-twl4030.c + * + * TWL4030 Real Time Clock interface + * + * Copyright (C) 2007 MontaVista Software, Inc + * Author: Alexandre Rusev + * + * Based on original TI driver twl4030-rtc.c + * Copyright (C) 2006 Texas Instruments, Inc. + * + * Based on rtc-omap.c + * Copyright (C) 2003 MontaVista Software, Inc. + * Author: George G. Davis or + * + * Copyright (C) 2006 David Brownell + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include - #include ++#include + +#define ALL_TIME_REGS 6 + +/* + * If this driver ever becomes modularised, it will be really nice + * to make the epoch retain its value across module reload... + */ +static int epoch = 1900; /* year corresponding to 0x00 */ + +/* + * Supports 1 byte read from TWL4030 RTC register. + */ +static int twl4030_rtc_read_u8(u8 *data, u8 reg) +{ + int ret; + + ret = twl4030_i2c_read_u8(TWL4030_MODULE_RTC, data, reg); + if (ret < 0) { + printk(KERN_WARNING "twl4030_rtc: Could not read TWL4030" + "register %X - returned %d[%x]\n", reg, ret, ret); + } + return ret; +} + +/* + * Supports 1 byte write to TWL4030 RTC registers. + */ +static int twl4030_rtc_write_u8(u8 data, u8 reg) +{ + int ret; + + ret = twl4030_i2c_write_u8(TWL4030_MODULE_RTC, data, reg); + if (ret < 0) { + printk(KERN_WARNING "twl4030_rtc: Could not write TWL4030" + "register %X - returned %d[%x]\n", reg, ret, ret); + } + return ret; +} + +/* + * Enables timer or alarm interrupts. + */ +static int set_rtc_irq_bit(unsigned char bit) +{ + unsigned char val; + int ret; + + ret = twl4030_rtc_read_u8(&val, REG_RTC_INTERRUPTS_REG); + if (ret < 0) + goto set_irq_out; + + val |= bit; + ret = twl4030_rtc_write_u8(val, REG_RTC_INTERRUPTS_REG); + +set_irq_out: + return ret; +} + +#ifdef CONFIG_PM +/* + * Read timer or alarm interrupts register. + */ +static int get_rtc_irq_bit(unsigned char *val) +{ + int ret; + + ret = twl4030_rtc_read_u8(val, REG_RTC_INTERRUPTS_REG); + return ret; +} +#endif +/* + * Disables timer or alarm interrupts. + */ +static int mask_rtc_irq_bit(unsigned char bit) +{ + unsigned char val; + int ret; + + ret = twl4030_rtc_read_u8(&val, REG_RTC_INTERRUPTS_REG); + if (ret < 0) + goto mask_irq_out; + + val &= ~bit; + ret = twl4030_rtc_write_u8(val, REG_RTC_INTERRUPTS_REG); + +mask_irq_out: + return ret; +} + +static int twl4030_rtc_alarm_irq_set_state(struct device *dev, int enabled) +{ + int ret; + + /* Allow ints for RTC ALARM updates. */ + if (enabled) + ret = set_rtc_irq_bit(BIT_RTC_INTERRUPTS_REG_IT_ALARM_M); + else + ret = mask_rtc_irq_bit(BIT_RTC_INTERRUPTS_REG_IT_ALARM_M); + + return ret; +} + +/* + * Gets current TWL4030 RTC time and date parameters. + */ +static int get_rtc_time(struct rtc_time *rtc_tm) +{ + unsigned char rtc_data[ALL_TIME_REGS + 1]; + int ret; + u8 save_control; + + ret = twl4030_rtc_read_u8(&save_control, REG_RTC_CTRL_REG); + if (ret < 0) + return ret; + + save_control |= BIT_RTC_CTRL_REG_GET_TIME_M; + + ret = twl4030_rtc_write_u8(save_control, REG_RTC_CTRL_REG); + if (ret < 0) + return ret; + + ret = twl4030_i2c_read(TWL4030_MODULE_RTC, rtc_data, + REG_SECONDS_REG, ALL_TIME_REGS); + + if (ret < 0) { + printk(KERN_ERR "twl4030_rtc: twl4030_i2c_read error.\n"); + return ret; + } + + rtc_tm->tm_sec = BCD2BIN(rtc_data[0]); + rtc_tm->tm_min = BCD2BIN(rtc_data[1]); + rtc_tm->tm_hour = BCD2BIN(rtc_data[2]); + rtc_tm->tm_mday = BCD2BIN(rtc_data[3]); + rtc_tm->tm_mon = BCD2BIN(rtc_data[4]); + rtc_tm->tm_year = BCD2BIN(rtc_data[5]); + + /* + * Account for differences between how the RTC uses the values + * and how they are defined in a struct rtc_time; + */ + rtc_tm->tm_year += (epoch - 1900); + if (rtc_tm->tm_year <= 69) + rtc_tm->tm_year += 100; + + rtc_tm->tm_mon--; + + return ret; +} + +static int twl4030_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + unsigned char save_control; + unsigned char rtc_data[ALL_TIME_REGS + 1]; + int ret; + + /* Month range is 01..12 */ + tm->tm_mon++; + + rtc_data[1] = BIN2BCD(tm->tm_sec); + rtc_data[2] = BIN2BCD(tm->tm_min); + rtc_data[3] = BIN2BCD(tm->tm_hour); + rtc_data[4] = BIN2BCD(tm->tm_mday); + rtc_data[5] = BIN2BCD(tm->tm_mon); + rtc_data[6] = BIN2BCD(tm->tm_year); + + /* Stop RTC while updating the TC registers */ + ret = twl4030_rtc_read_u8(&save_control, REG_RTC_CTRL_REG); + if (ret < 0) + goto out; + + save_control &= ~BIT_RTC_CTRL_REG_STOP_RTC_M; + twl4030_rtc_write_u8(save_control, REG_RTC_CTRL_REG); + if (ret < 0) + goto out; + + /* update all the alarm registers in one shot */ + ret = twl4030_i2c_write(TWL4030_MODULE_RTC, rtc_data, + REG_SECONDS_REG, ALL_TIME_REGS); + if (ret < 0) { + printk(KERN_ERR "twl4030: twl4030_i2c_write error.\n"); + goto out; + } + + /* Start back RTC */ + save_control |= BIT_RTC_CTRL_REG_STOP_RTC_M; + ret = twl4030_rtc_write_u8(save_control, REG_RTC_CTRL_REG); + +out: + return ret; +} + +/* + * Gets current TWL4030 RTC alarm time. + */ +static int get_rtc_alm_time(struct rtc_time *alm_tm) +{ + unsigned char rtc_data[ALL_TIME_REGS + 1]; + int ret; + + ret = twl4030_i2c_read(TWL4030_MODULE_RTC, rtc_data, + REG_ALARM_SECONDS_REG, ALL_TIME_REGS); + if (ret < 0) { + printk(KERN_ERR "twl4030_rtc: twl4030_i2c_read error.\n"); + return ret; + } + + alm_tm->tm_sec = BCD2BIN(rtc_data[0]); + alm_tm->tm_min = BCD2BIN(rtc_data[1]); + alm_tm->tm_hour = BCD2BIN(rtc_data[2]); + alm_tm->tm_mday = BCD2BIN(rtc_data[3]); + alm_tm->tm_mon = BCD2BIN(rtc_data[4]); + alm_tm->tm_year = BCD2BIN(rtc_data[5]); + + /* + * Account for differences between how the RTC uses the values + * and how they are defined in a struct rtc_time; + */ + alm_tm->tm_year += (epoch - 1900); + if (alm_tm->tm_year <= 69) + alm_tm->tm_year += 100; + + alm_tm->tm_mon--; + + return ret; +} + +static int twl4030_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + int ret; + + memset(tm, 0, sizeof(struct rtc_time)); + ret = get_rtc_time(tm); + + return ret; +} + +/* + * Gets current TWL4030 RTC alarm time. + */ +static int twl4030_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm) +{ + int ret; + u8 rtc_interrupts_reg = 0; + + /* + * This returns a struct rtc_time. Reading >= 0xc0 + * means "don't care" or "match all". Only the tm_hour, + * tm_min, and tm_sec values are filled in. + */ + memset(&alm->time, 0, sizeof(struct rtc_time)); + ret = get_rtc_alm_time(&alm->time); + + if (ret) + goto out; + + /* Check alarm enabled flag state */ + ret = + ret | twl4030_i2c_read_u8(TWL4030_MODULE_RTC, &rtc_interrupts_reg, + REG_RTC_INTERRUPTS_REG); + + if (ret) + goto out; + + if ((rtc_interrupts_reg & BIT_RTC_INTERRUPTS_REG_IT_ALARM_M) != 0) + alm->enabled = 1; + else + alm->enabled = 0; + +out: + return ret; +} + +static int twl4030_rtc_irq_set_state(struct device *dev, int enabled) +{ + int ret; + + /* Allow ints for RTC updates. */ + if (enabled) + ret = set_rtc_irq_bit(BIT_RTC_INTERRUPTS_REG_IT_TIMER_M); + else + ret = mask_rtc_irq_bit(BIT_RTC_INTERRUPTS_REG_IT_TIMER_M); + + return ret; +} + +static int twl4030_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm) +{ + unsigned char alarm_data[ALL_TIME_REGS + 1]; + int ret; + + /* Month range is 01..12 */ + alm->time.tm_mon++; + + alarm_data[1] = BIN2BCD(alm->time.tm_sec); + alarm_data[2] = BIN2BCD(alm->time.tm_min); + alarm_data[3] = BIN2BCD(alm->time.tm_hour); + alarm_data[4] = BIN2BCD(alm->time.tm_mday); + alarm_data[5] = BIN2BCD(alm->time.tm_mon); + alarm_data[6] = BIN2BCD(alm->time.tm_year); + + /* update all the alarm registers in one shot */ + ret = twl4030_i2c_write(TWL4030_MODULE_RTC, alarm_data, + REG_ALARM_SECONDS_REG, ALL_TIME_REGS); + if (ret) { + printk(KERN_ERR "twl4030: twl4030_i2c_write error.\n"); + goto out; + } + + ret = twl4030_rtc_alarm_irq_set_state(dev, alm->enabled); +out: + return ret; +} + +/* + * We will just handle setting the frequency and make use the framework for + * reading the periodic interupts. + * @freq: Current periodic IRQ freq + */ +static int twl4030_rtc_irq_set_freq(struct device *dev, int freq) +{ + struct rtc_device *rtc = dev_get_drvdata(dev); + + if (freq < 0 || freq > 3) + return -EINVAL; + + rtc->irq_freq = freq; + + /* set rtc irq freq to user defined value */ + set_rtc_irq_bit(freq); + + return 0; +} + +#ifdef CONFIG_RTC_INTF_DEV + +static int twl4030_rtc_ioctl(struct device *dev, unsigned int cmd, + unsigned long arg) +{ + + switch (cmd) { + case RTC_AIE_OFF: + return twl4030_rtc_alarm_irq_set_state(dev, 0); + case RTC_AIE_ON: + return twl4030_rtc_alarm_irq_set_state(dev, 1); + + case RTC_UIE_OFF: + /* Fall Through */ + case RTC_PIE_OFF: + /* Mask ints from RTC updates. */ + return twl4030_rtc_irq_set_state(dev, 0); + case RTC_UIE_ON: + /* Fall Through */ + case RTC_PIE_ON: + /* Allow ints for RTC updates. */ + return twl4030_rtc_irq_set_state(dev, 1); + + case RTC_EPOCH_READ: + return put_user(epoch, (unsigned long *)arg); + case RTC_EPOCH_SET: + /* + * There were no RTC clocks before 1900. + */ + if (arg < 1900) + return -EINVAL; + + if (!capable(CAP_SYS_TIME)) + return -EACCES; + + epoch = arg; + return 0; + default: + return -ENOIOCTLCMD; + } +} + +#else +#define omap_rtc_ioctl NULL +#endif + +static irqreturn_t twl4030_rtc_interrupt(int irq, void *rtc) +{ + unsigned long events = 0; + int ret = IRQ_NONE; + int res; + u8 rd_reg; + + res = twl4030_rtc_read_u8(&rd_reg, REG_RTC_STATUS_REG); + if (res) + goto out; + /* + * Figure out source of interrupt: ALARM or TIMER in RTC_STATUS_REG. + * only one (ALARM or RTC) interrupt source may be enabled + * at time, we also could check our results + * by reading RTS_INTERRUPTS_REGISTER[IT_TIMER,IT_ALARM] + */ + if (rd_reg & BIT_RTC_STATUS_REG_ALARM_M) + events |= RTC_IRQF | RTC_AF; + else + events |= RTC_IRQF | RTC_UF; + + res = twl4030_rtc_write_u8(rd_reg | BIT_RTC_STATUS_REG_ALARM_M, + REG_RTC_STATUS_REG); + if (res) + goto out; + /* + * Workaround for strange behaviour with T2. Need to write into ISR + * register one more time to clear the interrupt. Otherwise, the same + * RTC event generates 2 interrupts in a row. + * (no errata document available) + */ + res = twl4030_i2c_write_u8(TWL4030_MODULE_INT, + PWR_RTC_INT_CLR, REG_PWR_ISR1); + if (res) + goto out; + + /* Notify RTC core on event */ + rtc_update_irq(rtc, 1, events); + + ret = IRQ_HANDLED; +out: + return ret; +} + +static struct rtc_class_ops twl4030_rtc_ops = { + .ioctl = twl4030_rtc_ioctl, + .read_time = twl4030_rtc_read_time, + .set_time = twl4030_rtc_set_time, + .read_alarm = twl4030_rtc_read_alarm, + .set_alarm = twl4030_rtc_set_alarm, + .irq_set_freq = twl4030_rtc_irq_set_freq, +}; + +static int __devinit twl4030_rtc_probe(struct platform_device *pdev) +{ + struct twl4030rtc_platform_data *pdata = pdev->dev.platform_data; + struct rtc_device *rtc; + int ret = 0; + u8 rd_reg; + + if (pdata != NULL && pdata->init != NULL) { + ret = pdata->init(); + if (ret < 0) + goto out; + } + + rtc = rtc_device_register(pdev->name, + &pdev->dev, &twl4030_rtc_ops, THIS_MODULE); + if (IS_ERR(rtc)) { + ret = -EINVAL; + dev_err(&pdev->dev, "can't register RTC device, err %ld\n", + PTR_ERR(rtc)); + goto out0; + + } + + /* Set the irq freq to every second */ + rtc->irq_freq = 0; + + platform_set_drvdata(pdev, rtc); + + ret = twl4030_rtc_read_u8(&rd_reg, REG_RTC_STATUS_REG); + + if (ret < 0) + goto out1; + + if (rd_reg & BIT_RTC_STATUS_REG_POWER_UP_M) + dev_warn(&pdev->dev, "Power up reset detected.\n"); + + if (rd_reg & BIT_RTC_STATUS_REG_ALARM_M) + dev_warn(&pdev->dev, "Pending Alarm interrupt detected.\n"); + + /* Clear RTC Power up reset and pending alarm interrupts */ + ret = twl4030_rtc_write_u8(rd_reg, REG_RTC_STATUS_REG); + if (ret < 0) + goto out1; + + ret = request_irq(TWL4030_PWRIRQ_RTC, twl4030_rtc_interrupt, + 0, rtc->dev.bus_id, rtc); + if (ret < 0) { + dev_err(&pdev->dev, "IRQ is not free.\n"); + goto out1; + } + + /* Check RTC module status, Enable if it is off */ + ret = twl4030_rtc_read_u8(&rd_reg, REG_RTC_CTRL_REG); + if (ret < 0) + goto out2; + + if (!(rd_reg & BIT_RTC_CTRL_REG_STOP_RTC_M)) { + dev_info(&pdev->dev, "Enabling TWL4030-RTC.\n"); + rd_reg = BIT_RTC_CTRL_REG_STOP_RTC_M; + ret = twl4030_rtc_write_u8(rd_reg, REG_RTC_CTRL_REG); + if (ret < 0) + goto out2; + } + + ret = twl4030_i2c_read_u8(TWL4030_MODULE_INT, &rd_reg, REG_PWR_IMR1); + if (ret < 0) + goto out2; + + rd_reg &= PWR_RTC_IT_UNMASK; + /* MASK PWR - we will need this */ + ret = twl4030_i2c_write_u8(TWL4030_MODULE_INT, rd_reg, REG_PWR_IMR1); + if (ret < 0) + goto out2; + + ret = twl4030_i2c_read_u8(TWL4030_MODULE_INT, &rd_reg, REG_PWR_EDR1); + if (ret < 0) + goto out2; + + /* Rising edge detection enabled, needed for RTC alarm */ + rd_reg |= 0x80; + ret = twl4030_i2c_write_u8(TWL4030_MODULE_INT, rd_reg, REG_PWR_EDR1); + if (ret < 0) + goto out2; + + return ret; + + +out2: + free_irq(TWL4030_MODIRQ_PWR, rtc); +out1: + rtc_device_unregister(rtc); +out0: + if (pdata != NULL && pdata->exit != NULL) + pdata->exit(); +out: + return ret; +} + +/* + * Disable all TWL4030 RTC module interrupts. + * Sets status flag to free. + */ +static int __devexit twl4030_rtc_remove(struct platform_device *pdev) +{ + /* leave rtc running, but disable irqs */ + struct twl4030rtc_platform_data *pdata = pdev->dev.platform_data; + struct rtc_device *rtc = platform_get_drvdata(pdev); + + mask_rtc_irq_bit(BIT_RTC_INTERRUPTS_REG_IT_ALARM_M); + mask_rtc_irq_bit(BIT_RTC_INTERRUPTS_REG_IT_TIMER_M); + + free_irq(TWL4030_MODIRQ_PWR, rtc); + + if (pdata != NULL && pdata->exit != NULL) + pdata->exit(); + + rtc_device_unregister(rtc); + platform_set_drvdata(pdev, NULL); + return 0; +} + +static void twl4030_rtc_shutdown(struct platform_device *pdev) +{ + twl4030_rtc_alarm_irq_set_state(&pdev->dev, 0); + twl4030_rtc_irq_set_state(&pdev->dev, 0); +} + +#ifdef CONFIG_PM + +static unsigned char irqstat; + +static int twl4030_rtc_suspend(struct platform_device *pdev, pm_message_t state) +{ + get_rtc_irq_bit(&irqstat); + + mask_rtc_irq_bit(BIT_RTC_INTERRUPTS_REG_IT_TIMER_M | + BIT_RTC_INTERRUPTS_REG_IT_ALARM_M); + return 0; +} + +static int twl4030_rtc_resume(struct platform_device *pdev) +{ + set_rtc_irq_bit(irqstat); + return 0; +} +#else +#define twl4030_rtc_suspend NULL +#define twl4030_rtc_resume NULL +#endif + +MODULE_ALIAS("platform:twl4030_rtc"); +static struct platform_driver twl4030rtc_driver = { + .probe = twl4030_rtc_probe, + .remove = __devexit_p(twl4030_rtc_remove), + .shutdown = twl4030_rtc_shutdown, + .suspend = twl4030_rtc_suspend, + .resume = twl4030_rtc_resume, + .driver = { + .owner = THIS_MODULE, + .name = "twl4030_rtc", + }, +}; + +static int __init twl4030_rtc_init(void) +{ + return platform_driver_register(&twl4030rtc_driver); +} + +static void __exit twl4030_rtc_exit(void) +{ + platform_driver_unregister(&twl4030rtc_driver); +} + +MODULE_ALIAS("platform:twl4030_rtc"); +MODULE_AUTHOR("Texas Instruments, MontaVista Software"); +MODULE_LICENSE("GPL");; + +module_init(twl4030_rtc_init); +module_exit(twl4030_rtc_exit); diff --cc drivers/spi/tsc2301-core.c index 3980943e27e,00000000000..939bc48c2b5 mode 100644,000000..100644 --- a/drivers/spi/tsc2301-core.c +++ b/drivers/spi/tsc2301-core.c @@@ -1,301 -1,0 +1,301 @@@ +/* + * TSC2301 driver + * + * Copyright (C) 2005, 2006 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include + +#ifdef CONFIG_ARCH_OMAP - #include ++#include +#endif + +u16 tsc2301_read_reg(struct tsc2301 *tsc, int reg) +{ + struct spi_transfer t[2]; + struct spi_message m; + u16 data = 0, cmd; + + cmd = reg; + cmd |= 0x8000; + + memset(t, 0, sizeof(t)); + spi_message_init(&m); + m.spi = tsc->spi; + + t[0].tx_buf = &cmd; + t[0].rx_buf = NULL; + t[0].len = 2; + spi_message_add_tail(&t[0], &m); + + t[1].tx_buf = NULL; + t[1].rx_buf = &data; + t[1].len = 2; + spi_message_add_tail(&t[1], &m); + + spi_sync(m.spi, &m); + + return data; +} + +void tsc2301_write_reg(struct tsc2301 *tsc, int reg, u16 val) +{ + struct spi_transfer t; + struct spi_message m; + u16 data[2]; + + /* Now we prepare the command for transferring */ + data[0] = reg; + data[1] = val; + + spi_message_init(&m); + m.spi = tsc->spi; + + memset(&t, 0, sizeof(t)); + t.tx_buf = data; + t.rx_buf = NULL; + t.len = 4; + spi_message_add_tail(&t, &m); + + spi_sync(m.spi, &m); +} + +void tsc2301_write_kbc(struct tsc2301 *tsc, int val) +{ + u16 w; + + w = tsc->config2_shadow; + w &= ~(0x03 << 14); + w |= (val & 0x03) << 14; + tsc2301_write_reg(tsc, TSC2301_REG_CONFIG2, w); + tsc->config2_shadow = w; +} + +void tsc2301_write_pll(struct tsc2301 *tsc, + int pll_n, int pll_a, int pll_pdc, int pct_e, int pll_o) +{ + u16 w; + + w = tsc->config2_shadow; + w &= ~0x3fff; + w |= (pll_n & 0x0f) | ((pll_a & 0x0f) << 4) | ((pll_pdc & 0x0f) << 8); + w |= pct_e ? (1 << 12) : 0; + w |= pll_o ? (1 << 13) : 0; + tsc2301_write_reg(tsc, TSC2301_REG_CONFIG2, w); + tsc->config2_shadow = w; +} + +void tsc2301_read_buf(struct tsc2301 *tsc, int reg, u16 *rx_buf, int len) +{ + struct spi_transfer t[2]; + struct spi_message m; + u16 cmd, i; + + cmd = reg; + cmd |= 0x8000; + + spi_message_init(&m); + m.spi = tsc->spi; + + memset(t, 0, sizeof(t)); + t[0].tx_buf = &cmd; + t[0].rx_buf = NULL; + t[0].len = 2; + spi_message_add_tail(&t[0], &m); + + t[1].tx_buf = NULL; + t[1].rx_buf = rx_buf; + t[1].len = 2 * len; + spi_message_add_tail(&t[1], &m); + + spi_sync(m.spi, &m); + + for (i = 0; i < len; i++) + printk(KERN_DEBUG "rx_buf[%d]: %04x\n", i, rx_buf[i]); +} + +static int __devinit tsc2301_probe(struct spi_device *spi) +{ + struct tsc2301 *tsc; + struct tsc2301_platform_data *pdata = spi->dev.platform_data; + int r; + u16 w; + + if (!pdata) { + dev_dbg(&spi->dev, "no platform data?\n"); + return -ENODEV; + } + + tsc = kzalloc(sizeof(*tsc), GFP_KERNEL); + if (tsc == NULL) + return -ENOMEM; + + dev_set_drvdata(&spi->dev, tsc); + tsc->spi = spi; + + tsc->enable_clock = pdata->enable_clock; + tsc->disable_clock = pdata->disable_clock; + + if (pdata->reset_gpio >= 0) { + tsc->reset_gpio = pdata->reset_gpio; +#ifdef CONFIG_ARCH_OMAP + r = omap_request_gpio(tsc->reset_gpio); + if (r < 0) + goto err1; + omap_set_gpio_dataout(tsc->reset_gpio, 1); + omap_set_gpio_direction(tsc->reset_gpio, 0); + mdelay(1); + omap_set_gpio_dataout(tsc->reset_gpio, 0); +#endif + } else + tsc->reset_gpio = -1; + + spi->mode = SPI_MODE_1; + spi->bits_per_word = 16; + /* The max speed might've been defined by the board-specific + * struct */ + if (!spi->max_speed_hz) + spi->max_speed_hz = TSC2301_HZ; + spi_setup(spi); + + /* Soft reset */ + tsc2301_write_reg(tsc, TSC2301_REG_RESET, 0xbb00); + msleep(1); + + w = tsc2301_read_reg(tsc, TSC2301_REG_ADC); + if (!(w & (1 << 14))) { + dev_err(&spi->dev, "invalid ADC reg value: %04x\n", w); + r = -ENODEV; + goto err1; + } + + w = tsc2301_read_reg(tsc, TSC2301_REG_DAC); + if (!(w & (1 << 15))) { + dev_err(&spi->dev, "invalid DAC reg value: %04x\n", w); + r = -ENODEV; + goto err1; + } + + /* Stop keypad scanning */ + tsc2301_write_reg(tsc, TSC2301_REG_KEY, 0x4000); + + /* We have to cache this for read-modify-write, since we can't + * read back BIT15 */ + w = tsc2301_read_reg(tsc, TSC2301_REG_CONFIG2); + /* By default BIT15 is set */ + w |= 1 << 15; + tsc->config2_shadow = w; + + r = tsc2301_kp_init(tsc, pdata); + if (r) + goto err1; + r = tsc2301_ts_init(tsc, pdata); + if (r) + goto err2; + r = tsc2301_mixer_init(tsc, pdata); + if (r) + goto err3; + return 0; + +err3: + tsc2301_ts_exit(tsc); +err2: + tsc2301_kp_exit(tsc); +err1: + kfree(tsc); + return r; +} + +static int __devexit tsc2301_remove(struct spi_device *spi) +{ + struct tsc2301 *tsc = dev_get_drvdata(&spi->dev); + + tsc2301_mixer_exit(tsc); + tsc2301_ts_exit(tsc); + tsc2301_kp_exit(tsc); + kfree(tsc); + + return 0; +} + +#ifdef CONFIG_PM +static int tsc2301_suspend(struct spi_device *spi, pm_message_t mesg) +{ + struct tsc2301 *tsc = dev_get_drvdata(&spi->dev); + int r; + + if ((r = tsc2301_mixer_suspend(tsc)) < 0) + return r; + if ((r = tsc2301_kp_suspend(tsc)) < 0) + goto err1; + if ((r = tsc2301_ts_suspend(tsc)) < 0) + goto err2; + + return 0; +err2: + tsc2301_kp_resume(tsc); +err1: + tsc2301_mixer_resume(tsc); + return r; +} + +static int tsc2301_resume(struct spi_device *spi) +{ + struct tsc2301 *tsc = dev_get_drvdata(&spi->dev); + + tsc2301_ts_resume(tsc); + tsc2301_kp_resume(tsc); + tsc2301_mixer_resume(tsc); + return 0; +} +#endif + +static struct spi_driver tsc2301_driver = { + .driver = { + .name = "tsc2301", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, +#ifdef CONFIG_PM + .suspend = tsc2301_suspend, + .resume = tsc2301_resume, +#endif + .probe = tsc2301_probe, + .remove = __devexit_p(tsc2301_remove), +}; + +static int __init tsc2301_init(void) +{ + printk("TSC2301 driver initializing\n"); + + return spi_register_driver(&tsc2301_driver); +} +module_init(tsc2301_init); + +static void __exit tsc2301_exit(void) +{ + spi_unregister_driver(&tsc2301_driver); +} +module_exit(tsc2301_exit); + +MODULE_AUTHOR("Juha Yrjölä "); +MODULE_LICENSE("GPL"); diff --cc drivers/usb/host/ehci-omap.c index 8ca21c276ed,00000000000..8f122e511e7 mode 100644,000000..100644 --- a/drivers/usb/host/ehci-omap.c +++ b/drivers/usb/host/ehci-omap.c @@@ -1,562 -1,0 +1,562 @@@ +/* + * ehci-omap.c - driver for USBHOST on OMAP 34xx processor + * + * Bus Glue for OMAP34xx USBHOST 3 port EHCI controller + * Tested on OMAP3430 ES2.0 SDP + * + * Copyright (C) 2007-2008 Texas Instruments, Inc. + * Author: Vikram Pandita + * + * Based on "ehci-fsl.c" and "ehci-au1xxx.c" ehci glue layers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include - #include ++#include + +#include "ehci-omap.h" + + +#ifdef CONFIG_OMAP_EHCI_PHY_MODE +/* EHCI connected to External PHY */ + +/* External USB connectivity board: 750-2083-001 + * Connected to OMAP3430 SDP + * The board has Port1 and Port2 connected to ISP1504 in 12-pin ULPI mode + */ + +/* ISSUE1: + * ISP1504 for input clocking mode needs special reset handling + * Hold the PHY in reset by asserting RESET_N signal + * Then start the 60Mhz clock input to PHY + * Release the reset after a delay - + * to get the PHY state machine in working state + */ +#define EXTERNAL_PHY_RESET +#define EXT_PHY_RESET_GPIO_PORT1 (57) +#define EXT_PHY_RESET_GPIO_PORT2 (61) +#define EXT_PHY_RESET_DELAY (10) + +/* ISSUE2: + * USBHOST supports External charge pump PHYs only + * Use the VBUS from Port1 to power VBUS of Port2 externally + * So use Port2 as the working ULPI port + */ +#define VBUS_INTERNAL_CHARGEPUMP_HACK + +#endif /* CONFIG_OMAP_EHCI_PHY_MODE */ + +/*-------------------------------------------------------------------------*/ + +/* Define USBHOST clocks for clock management */ +struct ehci_omap_clock_defs { + struct clk *usbhost_ick_clk; + struct clk *usbhost2_120m_fck_clk; + struct clk *usbhost1_48m_fck_clk; + struct clk *usbtll_fck_clk; + struct clk *usbtll_ick_clk; +}; + +/* Clock names as per clock framework: May change so keep as #defs */ +#define USBHOST_ICKL "usbhost_ick" +#define USBHOST_120M_FCLK "usbhost_120m_fck" +#define USBHOST_48M_FCLK "usbhost_48m_fck" +#define USBHOST_TLL_ICKL "usbtll_ick" +#define USBHOST_TLL_FCLK "usbtll_fck" +/*-------------------------------------------------------------------------*/ + + +#ifndef CONFIG_OMAP_EHCI_PHY_MODE + +static void omap_usb_utmi_init(struct usb_hcd *hcd, u8 tll_channel_mask) +{ + int i; + + /* Use UTMI Ports of TLL */ + omap_writel((1 << OMAP_UHH_HOSTCONFIG_ULPI_BYPASS_SHIFT)| + (1<self.controller, "\nEntered UTMI MODE: success\n"); + + /* Program the 3 TLL channels upfront */ + + for (i = 0; i < OMAP_TLL_CHANNEL_COUNT; i++) { + + /* Disable AutoIdle */ + omap_writel(omap_readl(OMAP_TLL_CHANNEL_CONF(i)) & + ~(1<self.controller, "\nULPI_SCRATCH_REG[ch=%d]" + "= 0x%02x\n", + i+1, omap_readb(OMAP_TLL_ULPI_SCRATCH_REGISTER(i))); + } +} + +#else +# define omap_usb_utmi_init(x, y) 0 +#endif + + +/* omap_start_ehc + * - Start the TI USBHOST controller + */ +static int omap_start_ehc(struct platform_device *dev, struct usb_hcd *hcd) +{ + struct ehci_omap_clock_defs *ehci_clocks; + + dev_dbg(hcd->self.controller, ": starting TI EHCI USB Controller\n"); + + ehci_clocks = (struct ehci_omap_clock_defs *)( + ((char *)hcd_to_ehci(hcd)) + + sizeof(struct ehci_hcd)); + + /* Start DPLL5 Programming: + * Clock Framework is not doing this now: + * This will be done in clock framework later + */ + /* Enable DPLL 5 : Based on Input of 13Mhz*/ + cm_write_mod_reg((12 << OMAP3430ES2_PERIPH2_DPLL_DIV_SHIFT)| + (120 << OMAP3430ES2_PERIPH2_DPLL_MULT_SHIFT), + PLL_MOD, OMAP3430ES2_CM_CLKSEL4); + + cm_write_mod_reg(1 << OMAP3430ES2_DIV_120M_SHIFT, + PLL_MOD, OMAP3430ES2_CM_CLKSEL5); + + cm_write_mod_reg((7 << OMAP3430ES2_PERIPH2_DPLL_FREQSEL_SHIFT) | + (7 << OMAP3430ES2_EN_PERIPH2_DPLL_SHIFT), + PLL_MOD, OMAP3430ES2_CM_CLKEN2); + + while (!(cm_read_mod_reg(PLL_MOD, CM_IDLEST2) & + OMAP3430ES2_ST_PERIPH2_CLK_MASK)) + dev_dbg(hcd->self.controller, + "idlest2 = 0x%x\n", + cm_read_mod_reg(PLL_MOD, CM_IDLEST2)); + /* End DPLL5 programming */ + + + /* PRCM settings for USBHOST: + * Interface clk un-related to domain transition + */ + cm_write_mod_reg(0 << OMAP3430ES2_AUTO_USBHOST_SHIFT, + OMAP3430ES2_USBHOST_MOD, CM_AUTOIDLE); + + /* Disable sleep dependency with MPU and IVA */ + cm_write_mod_reg((0 << OMAP3430ES2_EN_MPU_SHIFT) | + (0 << OMAP3430ES2_EN_IVA2_SHIFT), + OMAP3430ES2_USBHOST_MOD, OMAP3430_CM_SLEEPDEP); + + /* Disable Automatic transition of clock */ + cm_write_mod_reg(0 << OMAP3430ES2_CLKTRCTRL_USBHOST_SHIFT, + OMAP3430ES2_USBHOST_MOD, CM_CLKSTCTRL); + + /* Enable Clocks for USBHOST */ + ehci_clocks->usbhost_ick_clk = clk_get(&dev->dev, + USBHOST_ICKL); + if (IS_ERR(ehci_clocks->usbhost_ick_clk)) + return PTR_ERR(ehci_clocks->usbhost_ick_clk); + clk_enable(ehci_clocks->usbhost_ick_clk); + + + ehci_clocks->usbhost2_120m_fck_clk = clk_get(&dev->dev, + USBHOST_120M_FCLK); + if (IS_ERR(ehci_clocks->usbhost2_120m_fck_clk)) + return PTR_ERR(ehci_clocks->usbhost2_120m_fck_clk); + clk_enable(ehci_clocks->usbhost2_120m_fck_clk); + + ehci_clocks->usbhost1_48m_fck_clk = clk_get(&dev->dev, + USBHOST_48M_FCLK); + if (IS_ERR(ehci_clocks->usbhost1_48m_fck_clk)) + return PTR_ERR(ehci_clocks->usbhost1_48m_fck_clk); + clk_enable(ehci_clocks->usbhost1_48m_fck_clk); + + +#ifdef EXTERNAL_PHY_RESET + /* Refer: ISSUE1 */ + omap_request_gpio(EXT_PHY_RESET_GPIO_PORT1); + omap_set_gpio_direction(EXT_PHY_RESET_GPIO_PORT1, 0); + omap_request_gpio(EXT_PHY_RESET_GPIO_PORT2); + omap_set_gpio_direction(EXT_PHY_RESET_GPIO_PORT2, 0); + omap_set_gpio_dataout(EXT_PHY_RESET_GPIO_PORT1, 0); + omap_set_gpio_dataout(EXT_PHY_RESET_GPIO_PORT2, 0); + /* Hold the PHY in RESET for enough time till DIR is high */ + udelay(EXT_PHY_RESET_DELAY); +#endif + + /* Configure TLL for 60Mhz clk for ULPI */ + ehci_clocks->usbtll_fck_clk = clk_get(&dev->dev, USBHOST_TLL_FCLK); + if (IS_ERR(ehci_clocks->usbtll_fck_clk)) + return PTR_ERR(ehci_clocks->usbtll_fck_clk); + clk_enable(ehci_clocks->usbtll_fck_clk); + + ehci_clocks->usbtll_ick_clk = clk_get(&dev->dev, USBHOST_TLL_ICKL); + if (IS_ERR(ehci_clocks->usbtll_ick_clk)) + return PTR_ERR(ehci_clocks->usbtll_ick_clk); + clk_enable(ehci_clocks->usbtll_ick_clk); + + /* Disable Auto Idle of USBTLL */ + cm_write_mod_reg((0 << OMAP3430ES2_AUTO_USBTLL_SHIFT), + CORE_MOD, CM_AUTOIDLE3); + + /* Wait for TLL to be Active */ + while ((cm_read_mod_reg(CORE_MOD, OMAP2430_CM_IDLEST3) & + (1 << OMAP3430ES2_ST_USBTLL_SHIFT))); + + /* perform TLL soft reset, and wait until reset is complete */ + omap_writel(1 << OMAP_USBTLL_SYSCONFIG_SOFTRESET_SHIFT, + OMAP_USBTLL_SYSCONFIG); + /* Wait for TLL reset to complete */ + while (!(omap_readl(OMAP_USBTLL_SYSSTATUS) & + (1 << OMAP_USBTLL_SYSSTATUS_RESETDONE_SHIFT))); + + dev_dbg(hcd->self.controller, "\n TLL RESET DONE\n"); + + /* (1<<3) = no idle mode only for initial debugging */ + omap_writel((1 << OMAP_USBTLL_SYSCONFIG_ENAWAKEUP_SHIFT) | + (1 << OMAP_USBTLL_SYSCONFIG_SIDLEMODE_SHIFT) | + (1 << OMAP_USBTLL_SYSCONFIG_CACTIVITY_SHIFT), + OMAP_USBTLL_SYSCONFIG); + + + /* Put UHH in NoIdle/NoStandby mode */ + omap_writel((0 << OMAP_UHH_SYSCONFIG_AUTOIDLE_SHIFT) | + (1 << OMAP_UHH_SYSCONFIG_ENAWAKEUP_SHIFT) | + (1 << OMAP_UHH_SYSCONFIG_SIDLEMODE_SHIFT) | + (1 << OMAP_UHH_SYSCONFIG_CACTIVITY_SHIFT) | + (1 << OMAP_UHH_SYSCONFIG_MIDLEMODE_SHIFT), + OMAP_UHH_SYSCONFIG); + +#ifdef CONFIG_OMAP_EHCI_PHY_MODE + /* Bypass the TLL module for PHY mode operation */ + omap_writel((0 << OMAP_UHH_HOSTCONFIG_ULPI_BYPASS_SHIFT)| + (1<self.controller, "Entered ULPI PHY MODE: success"); + +#else + /* Enable UTMI mode for all 3 TLL channels */ + omap_usb_utmi_init(hcd, + OMAP_TLL_CHANNEL_1_EN_MASK | + OMAP_TLL_CHANNEL_2_EN_MASK | + OMAP_TLL_CHANNEL_3_EN_MASK + ); +#endif + +#ifdef EXTERNAL_PHY_RESET + /* Refer ISSUE1: + * Hold the PHY in RESET for enough time till PHY is settled and ready + */ + udelay(EXT_PHY_RESET_DELAY); + omap_set_gpio_dataout(EXT_PHY_RESET_GPIO_PORT1, 1); + omap_set_gpio_dataout(EXT_PHY_RESET_GPIO_PORT2, 1); +#endif + +#ifdef VBUS_INTERNAL_CHARGEPUMP_HACK + /* Refer ISSUE2: LINK assumes external charge pump */ + + /* use Port1 VBUS to charge externally Port2: + * So for PHY mode operation use Port2 only + */ + omap_writel((0xA << EHCI_INSNREG05_ULPI_REGADD_SHIFT) |/* OTG ctrl reg*/ + (2 << EHCI_INSNREG05_ULPI_OPSEL_SHIFT) |/* Write */ + (1 << EHCI_INSNREG05_ULPI_PORTSEL_SHIFT) |/* Port1 */ + (1 << EHCI_INSNREG05_ULPI_CONTROL_SHIFT) |/* Start */ + (0x26), + EHCI_INSNREG05_ULPI); + + while (!(omap_readl(EHCI_INSNREG05_ULPI) & + (1<self.controller, ": stopping TI EHCI USB Controller\n"); + + /* Reset OMAP modules for insmod/rmmod to work */ + omap_writel((1<<1), OMAP_UHH_SYSCONFIG); + while (!(omap_readl(OMAP_UHH_SYSSTATUS) & (1<<0))); + while (!(omap_readl(OMAP_UHH_SYSSTATUS) & (1<<1))); + while (!(omap_readl(OMAP_UHH_SYSSTATUS) & (1<<2))); + dev_dbg(hcd->self.controller, + "UHH RESET DONE OMAP_UHH_SYSSTATUS %x !!\n", + omap_readl(OMAP_UHH_SYSSTATUS)); + + omap_writel((1<<1), OMAP_USBTLL_SYSCONFIG); + while (!(omap_readl(OMAP_USBTLL_SYSSTATUS) & (1<<0))); + dev_dbg(hcd->self.controller, ":TLL RESEET DONE"); + + if (ehci_clocks->usbtll_fck_clk != NULL) { + clk_disable(ehci_clocks->usbtll_fck_clk); + clk_put(ehci_clocks->usbtll_fck_clk); + ehci_clocks->usbtll_fck_clk = NULL; + } + + if (ehci_clocks->usbhost_ick_clk != NULL) { + clk_disable(ehci_clocks->usbhost_ick_clk); + clk_put(ehci_clocks->usbhost_ick_clk); + ehci_clocks->usbhost_ick_clk = NULL; + } + + if (ehci_clocks->usbhost1_48m_fck_clk != NULL) { + clk_disable(ehci_clocks->usbhost1_48m_fck_clk); + clk_put(ehci_clocks->usbhost1_48m_fck_clk); + ehci_clocks->usbhost1_48m_fck_clk = NULL; + } + + if (ehci_clocks->usbhost2_120m_fck_clk != NULL) { + clk_disable(ehci_clocks->usbhost2_120m_fck_clk); + clk_put(ehci_clocks->usbhost2_120m_fck_clk); + ehci_clocks->usbhost2_120m_fck_clk = NULL; + } + + if (ehci_clocks->usbtll_ick_clk != NULL) { + clk_disable(ehci_clocks->usbtll_ick_clk); + clk_put(ehci_clocks->usbtll_ick_clk); + ehci_clocks->usbtll_ick_clk = NULL; + } + + +#ifdef EXTERNAL_PHY_RESET + omap_free_gpio(EXT_PHY_RESET_GPIO_PORT1); + omap_free_gpio(EXT_PHY_RESET_GPIO_PORT2); +#endif + + dev_dbg(hcd->self.controller, + ": Clock to USB host has been disabled\n"); +} + +static const struct hc_driver ehci_omap_hc_driver; + +/*-------------------------------------------------------------------------*/ +/* configure so an HC device and id are always provided */ +/* always called with process context; sleeping is OK */ + +/** + * ehci_hcd_omap_drv_probe - initialize TI-based HCDs + * Context: !in_interrupt() + * + * Allocates basic resources for this USB host controller, and + * then invokes the start() method for the HCD associated with it + * through the hotplug entry's driver_data. + * + */ +static int ehci_hcd_omap_drv_probe(struct platform_device *dev) +{ + int retval = 0; + struct usb_hcd *hcd; + struct ehci_hcd *ehci; + + dev_dbg(&dev->dev, "ehci_hcd_omap_drv_probe()"); + + if (usb_disabled()) + return -ENODEV; + + if (dev->resource[1].flags != IORESOURCE_IRQ) { + dev_dbg(&dev->dev, "resource[1] is not IORESOURCE_IRQ"); + retval = -ENOMEM; + } + + hcd = usb_create_hcd(&ehci_omap_hc_driver, &dev->dev, dev->dev.bus_id); + if (!hcd) + return -ENOMEM; + + retval = omap_start_ehc(dev, hcd); + if (retval) + return retval; + + hcd->rsrc_start = 0; + hcd->rsrc_len = 0; + hcd->rsrc_start = dev->resource[0].start; + hcd->rsrc_len = dev->resource[0].end - dev->resource[0].start + 1; + + hcd->regs = (void __iomem *) (int) IO_ADDRESS(hcd->rsrc_start); + + ehci = hcd_to_ehci(hcd); + ehci->caps = hcd->regs; + + ehci->regs = hcd->regs + HC_LENGTH(readl(&ehci->caps->hc_capbase)); + /* cache this readonly data; minimize chip reads */ + ehci->hcs_params = readl(&ehci->caps->hcs_params); + + /* SET 1 micro-frame Interrupt interval */ + writel(readl(&ehci->regs->command) | (1<<16), &ehci->regs->command); + + retval = usb_add_hcd(hcd, dev->resource[1].start, + IRQF_DISABLED | IRQF_SHARED); + if (retval == 0) + return retval; + + dev_dbg(hcd->self.controller, "ERR: add_hcd"); + omap_stop_ehc(dev, hcd); + + usb_put_hcd(hcd); + return retval; +} + +/*-------------------------------------------------------------------------*/ + +/* may be called without controller electrically present */ +/* may be called with controller, bus, and devices active */ + +/** + * ehci_hcd_omap_drv_remove - shutdown processing for EHCI HCDs + * @dev: USB Host Controller being removed + * Context: !in_interrupt() + * + * Reverses the effect of usb_ehci_hcd_omap_probe(), first invoking + * the HCD's stop() method. It is always called from a thread + * context, normally "rmmod", "apmd", or something similar. + * + */ +static int ehci_hcd_omap_drv_remove(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + + dev_dbg(&dev->dev, "ehci_hcd_omap_drv_remove()"); + + usb_remove_hcd(hcd); + usb_put_hcd(hcd); + omap_stop_ehc(dev, hcd); + + return 0; +} + +/*-------------------------------------------------------------------------*/ +#ifdef CONFIG_PM +static int omap_ehci_bus_suspend(struct usb_hcd *hcd) +{ + return ehci_bus_suspend(hcd); +} + +static int omap_ehci_bus_resume(struct usb_hcd *hcd) +{ + return ehci_bus_resume(hcd); +} +#endif +/*-------------------------------------------------------------------------*/ + +static const struct hc_driver ehci_omap_hc_driver = { + .description = hcd_name, + .product_desc = "OMAP-EHCI Host Controller", + .hcd_priv_size = sizeof(struct ehci_hcd) + + sizeof(struct ehci_omap_clock_defs), + + /* + * generic hardware linkage + */ + .irq = ehci_irq, + .flags = HCD_MEMORY | HCD_USB2, + + /* + * basic lifecycle operations + */ + .reset = ehci_init, + .start = ehci_run, + .stop = ehci_stop, + .shutdown = ehci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ehci_urb_enqueue, + .urb_dequeue = ehci_urb_dequeue, + .endpoint_disable = ehci_endpoint_disable, + + /* + * scheduling support + */ + .get_frame_number = ehci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ehci_hub_status_data, + .hub_control = ehci_hub_control, +#ifdef CONFIG_PM + .bus_suspend = omap_ehci_bus_suspend, + .bus_resume = omap_ehci_bus_resume, +#endif +}; + +/*-------------------------------------------------------------------------*/ +MODULE_ALIAS("omap-ehci"); +static struct platform_driver ehci_hcd_omap_driver = { + .probe = ehci_hcd_omap_drv_probe, + .remove = ehci_hcd_omap_drv_remove, + .shutdown = usb_hcd_platform_shutdown, + /*.suspend = ehci_hcd_omap_drv_suspend, */ + /*.resume = ehci_hcd_omap_drv_resume, */ + .driver = { + .name = "ehci-omap", + .bus = &platform_bus_type + } +}; diff --cc drivers/usb/host/ehci-omap.h index f75eda702a4,00000000000..9e4378f6bbb mode 100644,000000..100644 --- a/drivers/usb/host/ehci-omap.h +++ b/drivers/usb/host/ehci-omap.h @@@ -1,125 -1,0 +1,125 @@@ +/* + * ehci-omap.h - register definitions for USBHOST in OMAP 34xx + * + * Copyright (C) 2007-2008 Texas Instruments, Inc. + * Author: Vikram Pandita + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __EHCI_OMAP_H +#define __EHCI_OMAP_H + - #include ++#include +#include "../../../arch/arm/mach-omap2/cm.h" +#include "../../../arch/arm/mach-omap2/cm-regbits-34xx.h" + +/* + * OMAP USBHOST Register addresses: PHYSICAL ADDRESSES + * Use omap_readl()/omap_writel() functions + */ + +/* USBHOST: TLL, UUH, OHCI, EHCI */ +#define OMAP_USBHOST_BASE (L4_34XX_BASE + 0x60000) + +/* TLL Register Set */ +#define OMAP_USBHOST_TLL_BASE (OMAP_USBHOST_BASE + 0x2000) +#define OMAP_USBTLL_REVISION (OMAP_USBHOST_TLL_BASE + 0x00) +#define OMAP_USBTLL_SYSCONFIG (OMAP_USBHOST_TLL_BASE + 0x10) + #define OMAP_USBTLL_SYSCONFIG_CACTIVITY_SHIFT 8 + #define OMAP_USBTLL_SYSCONFIG_SIDLEMODE_SHIFT 3 + #define OMAP_USBTLL_SYSCONFIG_ENAWAKEUP_SHIFT 2 + #define OMAP_USBTLL_SYSCONFIG_SOFTRESET_SHIFT 1 + #define OMAP_USBTLL_SYSCONFIG_AUTOIDLE_SHIFT 0 +#define OMAP_USBTLL_SYSSTATUS (OMAP_USBHOST_TLL_BASE + 0x14) + #define OMAP_USBTLL_SYSSTATUS_RESETDONE_SHIFT 0 +#define OMAP_USBTLL_IRQSTATUS (OMAP_USBHOST_TLL_BASE + 0x18) +#define OMAP_USBTLL_IRQENABLE (OMAP_USBHOST_TLL_BASE + 0x1C) + +#define OMAP_TLL_SHARED_CONF (OMAP_USBHOST_TLL_BASE + 0x30) + #define OMAP_TLL_SHARED_CONF_USB_90D_DDR_EN_SHFT 6 + #define OMAP_TLL_SHARED_CONF_USB_180D_SDR_EN_SHIFT 5 + #define OMAP_TLL_SHARED_CONF_USB_DIVRATION_SHIFT 2 + #define OMAP_TLL_SHARED_CONF_FCLK_REQ_SHIFT 1 + #define OMAP_TLL_SHARED_CONF_FCLK_IS_ON_SHIFT 0 + +#define OMAP_TLL_CHANNEL_CONF(num)\ + (OMAP_USBHOST_TLL_BASE + (0x040 + 0x004 * num)) + #define OMAP_TLL_CHANNEL_CONF_ULPINOBITSTUFF_SHIFT 11 + #define OMAP_TLL_CHANNEL_CONF_ULPI_ULPIAUTOIDLE_SHIFT 10 + #define OMAP_TLL_CHANNEL_CONF_UTMIAUTOIDLE_SHIFT 9 + #define OMAP_TLL_CHANNEL_CONF_ULPIDDRMODE_SHIFT 8 + #define OMAP_TLL_CHANNEL_CONF_CHANEN_SHIFT 0 + +#define OMAP_TLL_ULPI_FUNCTION_CTRL(num)\ + (OMAP_USBHOST_TLL_BASE + (0x804 + 0x100 * num)) +#define OMAP_TLL_ULPI_INTERFACE_CTRL(num)\ + (OMAP_USBHOST_TLL_BASE + (0x807 + 0x100 * num)) +#define OMAP_TLL_ULPI_OTG_CTRL(num)\ + (OMAP_USBHOST_TLL_BASE + (0x80A + 0x100 * num)) +#define OMAP_TLL_ULPI_INT_EN_RISE(num)\ + (OMAP_USBHOST_TLL_BASE + (0x80D + 0x100 * num)) +#define OMAP_TLL_ULPI_INT_EN_FALL(num)\ + (OMAP_USBHOST_TLL_BASE + (0x810 + 0x100 * num)) +#define OMAP_TLL_ULPI_INT_STATUS(num)\ + (OMAP_USBHOST_TLL_BASE + (0x813 + 0x100 * num)) +#define OMAP_TLL_ULPI_INT_LATCH(num)\ + (OMAP_USBHOST_TLL_BASE + (0x814 + 0x100 * num)) +#define OMAP_TLL_ULPI_DEBUG(num)\ + (OMAP_USBHOST_TLL_BASE + (0x815 + 0x100 * num)) +#define OMAP_TLL_ULPI_SCRATCH_REGISTER(num)\ + (OMAP_USBHOST_TLL_BASE + (0x816 + 0x100 * num)) + +#define OMAP_TLL_CHANNEL_COUNT 3 + #define OMAP_TLL_CHANNEL_1_EN_MASK 1 + #define OMAP_TLL_CHANNEL_2_EN_MASK 2 + #define OMAP_TLL_CHANNEL_3_EN_MASK 4 + +/* UHH Register Set */ +#define OMAP_USBHOST_UHH_BASE (OMAP_USBHOST_BASE + 0x4000) +#define OMAP_UHH_REVISION (OMAP_USBHOST_UHH_BASE + 0x00) +#define OMAP_UHH_SYSCONFIG (OMAP_USBHOST_UHH_BASE + 0x10) + #define OMAP_UHH_SYSCONFIG_MIDLEMODE_SHIFT 12 + #define OMAP_UHH_SYSCONFIG_CACTIVITY_SHIFT 8 + #define OMAP_UHH_SYSCONFIG_SIDLEMODE_SHIFT 3 + #define OMAP_UHH_SYSCONFIG_ENAWAKEUP_SHIFT 2 + #define OMAP_UHH_SYSCONFIG_SOFTRESET_SHIFT 1 + #define OMAP_UHH_SYSCONFIG_AUTOIDLE_SHIFT 0 + +#define OMAP_UHH_SYSSTATUS (OMAP_USBHOST_UHH_BASE + 0x14) +#define OMAP_UHH_HOSTCONFIG (OMAP_USBHOST_UHH_BASE + 0x40) + #define OMAP_UHH_HOSTCONFIG_ULPI_BYPASS_SHIFT 0 + #define OMAP_UHH_HOSTCONFIG_INCR4_BURST_EN_SHIFT 2 + #define OMAP_UHH_HOSTCONFIG_INCR8_BURST_EN_SHIFT 3 + #define OMAP_UHH_HOSTCONFIG_INCR16_BURST_EN_SHIFT 4 + #define OMAP_UHH_HOSTCONFIG_INCRX_ALIGN_EN_SHIFT 5 + +#define OMAP_UHH_DEBUG_CSR (OMAP_USBHOST_UHH_BASE + 0x44) + +/* EHCI Register Set */ +#define OMAP_USBHOST_EHCI_BASE (OMAP_USBHOST_BASE + 0x4800) +#define EHCI_INSNREG05_ULPI (OMAP_USBHOST_EHCI_BASE + 0xA4) + #define EHCI_INSNREG05_ULPI_CONTROL_SHIFT 31 + #define EHCI_INSNREG05_ULPI_PORTSEL_SHIFT 24 + #define EHCI_INSNREG05_ULPI_OPSEL_SHIFT 22 + #define EHCI_INSNREG05_ULPI_REGADD_SHIFT 16 + #define EHCI_INSNREG05_ULPI_EXTREGADD_SHIFT 8 + #define EHCI_INSNREG05_ULPI_WRDATA_SHIFT 0 + +/* OHCI Register Set */ +#define OMAP_USBHOST_OHCI_BASE (OMAP_USBHOST_BASE + 0x4400) + +#endif/* __EHCI_OMAP_H*/ diff --cc drivers/usb/musb/davinci.c index 75baf181a8c,00000000000..d443a61e5c3 mode 100644,000000..100644 --- a/drivers/usb/musb/davinci.c +++ b/drivers/usb/musb/davinci.c @@@ -1,462 -1,0 +1,462 @@@ +/* + * Copyright (C) 2005-2006 by Texas Instruments + * + * This file is part of the Inventra Controller Driver for Linux. + * + * The Inventra Controller Driver for Linux 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. + * + * The Inventra Controller Driver for Linux 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 The Inventra Controller Driver for Linux ; if not, + * write to the Free Software Foundation, Inc., 59 Temple Place, + * Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + - #include - #include - #include ++#include ++#include ++#include +#include + +#include "musb_core.h" + +#ifdef CONFIG_MACH_DAVINCI_EVM - #include ++#include +#endif + +#include "davinci.h" +#include "cppi_dma.h" + + +/* REVISIT (PM) we should be able to keep the PHY in low power mode most + * of the time (24 MHZ oscillator and PLL off, etc) by setting POWER.D0 + * and, when in host mode, autosuspending idle root ports... PHYPLLON + * (overriding SUSPENDM?) then likely needs to stay off. + */ + +static inline void phy_on(void) +{ + /* start the on-chip PHY and its PLL */ + __raw_writel(USBPHY_SESNDEN | USBPHY_VBDTCTEN | USBPHY_PHYPLLON, + (void __force __iomem *) IO_ADDRESS(USBPHY_CTL_PADDR)); + while ((__raw_readl((void __force __iomem *) + IO_ADDRESS(USBPHY_CTL_PADDR)) + & USBPHY_PHYCLKGD) == 0) + cpu_relax(); +} + +static inline void phy_off(void) +{ + /* powerdown the on-chip PHY and its oscillator */ + __raw_writel(USBPHY_OSCPDWN | USBPHY_PHYPDWN, (void __force __iomem *) + IO_ADDRESS(USBPHY_CTL_PADDR)); +} + +static int dma_off = 1; + +void musb_platform_enable(struct musb *musb) +{ + u32 tmp, old, val; + + /* workaround: setup irqs through both register sets */ + tmp = (musb->epmask & DAVINCI_USB_TX_ENDPTS_MASK) + << DAVINCI_USB_TXINT_SHIFT; + musb_writel(musb->ctrl_base, DAVINCI_USB_INT_MASK_SET_REG, tmp); + old = tmp; + tmp = (musb->epmask & (0xfffe & DAVINCI_USB_RX_ENDPTS_MASK)) + << DAVINCI_USB_RXINT_SHIFT; + musb_writel(musb->ctrl_base, DAVINCI_USB_INT_MASK_SET_REG, tmp); + tmp |= old; + + val = ~MUSB_INTR_SOF; + tmp |= ((val & 0x01ff) << DAVINCI_USB_USBINT_SHIFT); + musb_writel(musb->ctrl_base, DAVINCI_USB_INT_MASK_SET_REG, tmp); + + if (is_dma_capable() && !dma_off) + printk(KERN_WARNING "%s %s: dma not reactivated\n", + __FILE__, __func__); + else + dma_off = 0; + + /* force a DRVVBUS irq so we can start polling for ID change */ + if (is_otg_enabled(musb)) + musb_writel(musb->ctrl_base, DAVINCI_USB_INT_SET_REG, + DAVINCI_INTR_DRVVBUS << DAVINCI_USB_USBINT_SHIFT); +} + +/* + * Disable the HDRC and flush interrupts + */ +void musb_platform_disable(struct musb *musb) +{ + /* because we don't set CTRLR.UINT, "important" to: + * - not read/write INTRUSB/INTRUSBE + * - (except during initial setup, as workaround) + * - use INTSETR/INTCLRR instead + */ + musb_writel(musb->ctrl_base, DAVINCI_USB_INT_MASK_CLR_REG, + DAVINCI_USB_USBINT_MASK + | DAVINCI_USB_TXINT_MASK + | DAVINCI_USB_RXINT_MASK); + musb_writeb(musb->mregs, MUSB_DEVCTL, 0); + musb_writel(musb->ctrl_base, DAVINCI_USB_EOI_REG, 0); + + if (is_dma_capable() && !dma_off) + WARNING("dma still active\n"); +} + + +/* REVISIT it's not clear whether DaVinci can support full OTG. */ + +static int vbus_state = -1; + +#ifdef CONFIG_USB_MUSB_HDRC_HCD +#define portstate(stmt) stmt +#else +#define portstate(stmt) +#endif + + +/* VBUS SWITCHING IS BOARD-SPECIFIC */ + +#ifdef CONFIG_MACH_DAVINCI_EVM +#ifndef CONFIG_MACH_DAVINCI_EVM_OTG + +/* I2C operations are always synchronous, and require a task context. + * With unloaded systems, using the shared workqueue seems to suffice + * to satisfy the 100msec A_WAIT_VRISE timeout... + */ +static void evm_deferred_drvvbus(struct work_struct *ignored) +{ + davinci_i2c_expander_op(0x3a, USB_DRVVBUS, vbus_state); + vbus_state = !vbus_state; +} +static DECLARE_WORK(evm_vbus_work, evm_deferred_drvvbus); + +#endif /* modified board */ +#endif /* EVM */ + +static void davinci_source_power(struct musb *musb, int is_on, int immediate) +{ + if (is_on) + is_on = 1; + + if (vbus_state == is_on) + return; + vbus_state = !is_on; /* 0/1 vs "-1 == unknown/init" */ + +#ifdef CONFIG_MACH_DAVINCI_EVM + if (machine_is_davinci_evm()) { +#ifdef CONFIG_MACH_DAVINCI_EVM_OTG + /* modified EVM board switching VBUS with GPIO(6) not I2C + * NOTE: PINMUX0.RGB888 (bit23) must be clear + */ + if (is_on) + gpio_set(GPIO(6)); + else + gpio_clear(GPIO(6)); + immediate = 1; +#else + if (immediate) + davinci_i2c_expander_op(0x3a, USB_DRVVBUS, !is_on); + else + schedule_work(&evm_vbus_work); +#endif + } +#endif + if (immediate) + vbus_state = is_on; +} + +static void davinci_set_vbus(struct musb *musb, int is_on) +{ + WARN_ON(is_on && is_peripheral_active(musb)); + davinci_source_power(musb, is_on, 0); +} + + +#define POLL_SECONDS 2 + +static struct timer_list otg_workaround; + +static void otg_timer(unsigned long _musb) +{ + struct musb *musb = (void *)_musb; + void __iomem *mregs = musb->mregs; + u8 devctl; + unsigned long flags; + + /* We poll because DaVinci's won't expose several OTG-critical + * status change events (from the transceiver) otherwise. + */ + devctl = musb_readb(mregs, MUSB_DEVCTL); + DBG(7, "poll devctl %02x (%s)\n", devctl, otg_state_string(musb)); + + spin_lock_irqsave(&musb->lock, flags); + switch (musb->xceiv.state) { + case OTG_STATE_A_WAIT_VFALL: + /* Wait till VBUS falls below SessionEnd (~0.2V); the 1.3 RTL + * seems to mis-handle session "start" otherwise (or in our + * case "recover"), in routine "VBUS was valid by the time + * VBUSERR got reported during enumeration" cases. + */ + if (devctl & MUSB_DEVCTL_VBUS) { + mod_timer(&otg_workaround, jiffies + POLL_SECONDS * HZ); + break; + } + musb->xceiv.state = OTG_STATE_A_WAIT_VRISE; + musb_writel(musb->ctrl_base, DAVINCI_USB_INT_SET_REG, + MUSB_INTR_VBUSERROR << DAVINCI_USB_USBINT_SHIFT); + break; + case OTG_STATE_B_IDLE: + if (!is_peripheral_enabled(musb)) + break; + + /* There's no ID-changed IRQ, so we have no good way to tell + * when to switch to the A-Default state machine (by setting + * the DEVCTL.SESSION flag). + * + * Workaround: whenever we're in B_IDLE, try setting the + * session flag every few seconds. If it works, ID was + * grounded and we're now in the A-Default state machine. + * + * NOTE setting the session flag is _supposed_ to trigger + * SRP, but clearly it doesn't. + */ + musb_writeb(mregs, MUSB_DEVCTL, + devctl | MUSB_DEVCTL_SESSION); + devctl = musb_readb(mregs, MUSB_DEVCTL); + if (devctl & MUSB_DEVCTL_BDEVICE) + mod_timer(&otg_workaround, jiffies + POLL_SECONDS * HZ); + else + musb->xceiv.state = OTG_STATE_A_IDLE; + break; + default: + break; + } + spin_unlock_irqrestore(&musb->lock, flags); +} + +static irqreturn_t davinci_interrupt(int irq, void *__hci) +{ + unsigned long flags; + irqreturn_t retval = IRQ_NONE; + struct musb *musb = __hci; + void __iomem *tibase = musb->ctrl_base; + u32 tmp; + + spin_lock_irqsave(&musb->lock, flags); + + /* NOTE: DaVinci shadows the Mentor IRQs. Don't manage them through + * the Mentor registers (except for setup), use the TI ones and EOI. + * + * Docs describe irq "vector" registers asociated with the CPPI and + * USB EOI registers. These hold a bitmask corresponding to the + * current IRQ, not an irq handler address. Would using those bits + * resolve some of the races observed in this dispatch code?? + */ + + /* CPPI interrupts share the same IRQ line, but have their own + * mask, state, "vector", and EOI registers. + */ + if (is_cppi_enabled()) { + u32 cppi_tx = musb_readl(tibase, DAVINCI_TXCPPI_MASKED_REG); + u32 cppi_rx = musb_readl(tibase, DAVINCI_RXCPPI_MASKED_REG); + + if (cppi_tx || cppi_rx) { + DBG(4, "CPPI IRQ t%x r%x\n", cppi_tx, cppi_rx); + cppi_completion(musb, cppi_rx, cppi_tx); + retval = IRQ_HANDLED; + } + } + + /* ack and handle non-CPPI interrupts */ + tmp = musb_readl(tibase, DAVINCI_USB_INT_SRC_MASKED_REG); + musb_writel(tibase, DAVINCI_USB_INT_SRC_CLR_REG, tmp); + DBG(4, "IRQ %08x\n", tmp); + + musb->int_rx = (tmp & DAVINCI_USB_RXINT_MASK) + >> DAVINCI_USB_RXINT_SHIFT; + musb->int_tx = (tmp & DAVINCI_USB_TXINT_MASK) + >> DAVINCI_USB_TXINT_SHIFT; + musb->int_usb = (tmp & DAVINCI_USB_USBINT_MASK) + >> DAVINCI_USB_USBINT_SHIFT; + + /* DRVVBUS irqs are the only proxy we have (a very poor one!) for + * DaVinci's missing ID change IRQ. We need an ID change IRQ to + * switch appropriately between halves of the OTG state machine. + * Managing DEVCTL.SESSION per Mentor docs requires we know its + * value, but DEVCTL.BDEVICE is invalid without DEVCTL.SESSION set. + * Also, DRVVBUS pulses for SRP (but not at 5V) ... + */ + if (tmp & (DAVINCI_INTR_DRVVBUS << DAVINCI_USB_USBINT_SHIFT)) { + int drvvbus = musb_readl(tibase, DAVINCI_USB_STAT_REG); + void __iomem *mregs = musb->mregs; + u8 devctl = musb_readb(mregs, MUSB_DEVCTL); + int err = musb->int_usb & MUSB_INTR_VBUSERROR; + + err = is_host_enabled(musb) + && (musb->int_usb & MUSB_INTR_VBUSERROR); + if (err) { + /* The Mentor core doesn't debounce VBUS as needed + * to cope with device connect current spikes. This + * means it's not uncommon for bus-powered devices + * to get VBUS errors during enumeration. + * + * This is a workaround, but newer RTL from Mentor + * seems to allow a better one: "re"starting sessions + * without waiting (on EVM, a **long** time) for VBUS + * to stop registering in devctl. + */ + musb->int_usb &= ~MUSB_INTR_VBUSERROR; + musb->xceiv.state = OTG_STATE_A_WAIT_VFALL; + mod_timer(&otg_workaround, jiffies + POLL_SECONDS * HZ); + WARNING("VBUS error workaround (delay coming)\n"); + } else if (is_host_enabled(musb) && drvvbus) { + musb->is_active = 1; + MUSB_HST_MODE(musb); + musb->xceiv.default_a = 1; + musb->xceiv.state = OTG_STATE_A_WAIT_VRISE; + portstate(musb->port1_status |= USB_PORT_STAT_POWER); + del_timer(&otg_workaround); + } else { + musb->is_active = 0; + MUSB_DEV_MODE(musb); + musb->xceiv.default_a = 0; + musb->xceiv.state = OTG_STATE_B_IDLE; + portstate(musb->port1_status &= ~USB_PORT_STAT_POWER); + } + + /* NOTE: this must complete poweron within 100 msec */ + davinci_source_power(musb, drvvbus, 0); + DBG(2, "VBUS %s (%s)%s, devctl %02x\n", + drvvbus ? "on" : "off", + otg_state_string(musb), + err ? " ERROR" : "", + devctl); + retval = IRQ_HANDLED; + } + + if (musb->int_tx || musb->int_rx || musb->int_usb) + retval |= musb_interrupt(musb); + + /* irq stays asserted until EOI is written */ + musb_writel(tibase, DAVINCI_USB_EOI_REG, 0); + + /* poll for ID change */ + if (is_otg_enabled(musb) + && musb->xceiv.state == OTG_STATE_B_IDLE) + mod_timer(&otg_workaround, jiffies + POLL_SECONDS * HZ); + + spin_unlock_irqrestore(&musb->lock, flags); + + /* REVISIT we sometimes get unhandled IRQs + * (e.g. ep0). not clear why... + */ + if (retval != IRQ_HANDLED) + DBG(5, "unhandled? %08x\n", tmp); + return IRQ_HANDLED; +} + +int __init musb_platform_init(struct musb *musb) +{ + void __iomem *tibase = musb->ctrl_base; + u32 revision; + + musb->mregs += DAVINCI_BASE_OFFSET; +#if 0 + /* REVISIT there's something odd about clocking, this + * didn't appear do the job ... + */ + musb->clock = clk_get(pDevice, "usb"); + if (IS_ERR(musb->clock)) + return PTR_ERR(musb->clock); + + status = clk_enable(musb->clock); + if (status < 0) + return -ENODEV; +#endif + + /* returns zero if e.g. not clocked */ + revision = musb_readl(tibase, DAVINCI_USB_VERSION_REG); + if (revision == 0) + return -ENODEV; + + if (is_host_enabled(musb)) + setup_timer(&otg_workaround, otg_timer, (unsigned long) musb); + + musb->board_set_vbus = davinci_set_vbus; + davinci_source_power(musb, 0, 1); + + /* reset the controller */ + musb_writel(tibase, DAVINCI_USB_CTRL_REG, 0x1); + + /* start the on-chip PHY and its PLL */ + phy_on(); + + msleep(5); + + /* NOTE: irqs are in mixed mode, not bypass to pure-musb */ + pr_debug("DaVinci OTG revision %08x phy %03x control %02x\n", + revision, __raw_readl((void __force __iomem *) + IO_ADDRESS(USBPHY_CTL_PADDR)), + musb_readb(tibase, DAVINCI_USB_CTRL_REG)); + + musb->isr = davinci_interrupt; + return 0; +} + +int musb_platform_exit(struct musb *musb) +{ + if (is_host_enabled(musb)) + del_timer_sync(&otg_workaround); + + davinci_source_power(musb, 0 /*off*/, 1); + + /* delay, to avoid problems with module reload */ + if (is_host_enabled(musb) && musb->xceiv.default_a) { + int maxdelay = 30; + u8 devctl, warn = 0; + + /* if there's no peripheral connected, this can take a + * long time to fall, especially on EVM with huge C133. + */ + do { + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + if (!(devctl & MUSB_DEVCTL_VBUS)) + break; + if ((devctl & MUSB_DEVCTL_VBUS) != warn) { + warn = devctl & MUSB_DEVCTL_VBUS; + DBG(1, "VBUS %d\n", + warn >> MUSB_DEVCTL_VBUS_SHIFT); + } + msleep(1000); + maxdelay--; + } while (maxdelay > 0); + + /* in OTG mode, another host might be connected */ + if (devctl & MUSB_DEVCTL_VBUS) + DBG(1, "VBUS off timeout (devctl %02x)\n", devctl); + } + + phy_off(); + return 0; +} diff --cc drivers/usb/musb/musb_core.c index 1404a10a3e6,00000000000..bcc92e69c42 mode 100644,000000..100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@@ -1,2269 -1,0 +1,2269 @@@ +/* + * MUSB OTG driver core code + * + * Copyright 2005 Mentor Graphics Corporation + * Copyright (C) 2005-2006 by Texas Instruments + * Copyright (C) 2006-2007 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * 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 AUTHORS 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. + * + */ + +/* + * Inventra (Multipoint) Dual-Role Controller Driver for Linux. + * + * This consists of a Host Controller Driver (HCD) and a peripheral + * controller driver implementing the "Gadget" API; OTG support is + * in the works. These are normal Linux-USB controller drivers which + * use IRQs and have no dedicated thread. + * + * This version of the driver has only been used with products from + * Texas Instruments. Those products integrate the Inventra logic + * with other DMA, IRQ, and bus modules, as well as other logic that + * needs to be reflected in this driver. + * + * + * NOTE: the original Mentor code here was pretty much a collection + * of mechanisms that don't seem to have been fully integrated/working + * for *any* Linux kernel version. This version aims at Linux 2.6.now, + * Key open issues include: + * + * - Lack of host-side transaction scheduling, for all transfer types. + * The hardware doesn't do it; instead, software must. + * + * This is not an issue for OTG devices that don't support external + * hubs, but for more "normal" USB hosts it's a user issue that the + * "multipoint" support doesn't scale in the expected ways. That + * includes DaVinci EVM in a common non-OTG mode. + * + * * Control and bulk use dedicated endpoints, and there's as + * yet no mechanism to either (a) reclaim the hardware when + * peripherals are NAKing, which gets complicated with bulk + * endpoints, or (b) use more than a single bulk endpoint in + * each direction. + * + * RESULT: one device may be perceived as blocking another one. + * + * * Interrupt and isochronous will dynamically allocate endpoint + * hardware, but (a) there's no record keeping for bandwidth; + * (b) in the common case that few endpoints are available, there + * is no mechanism to reuse endpoints to talk to multiple devices. + * + * RESULT: At one extreme, bandwidth can be overcommitted in + * some hardware configurations, no faults will be reported. + * At the other extreme, the bandwidth capabilities which do + * exist tend to be severely undercommitted. You can't yet hook + * up both a keyboard and a mouse to an external USB hub. + */ + +/* + * This gets many kinds of configuration information: + * - Kconfig for everything user-configurable - * - for SOC or family details ++ * - for SOC or family details + * - platform_device for addressing, irq, and platform_data + * - platform_data is mostly for board-specific informarion + * + * Most of the conditional compilation will (someday) vanish. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_ARM - #include - #include ++#include ++#include +#include +#endif + +#include "musb_core.h" + + +#ifdef CONFIG_ARCH_DAVINCI +#include "davinci.h" +#endif + + + +#if MUSB_DEBUG > 0 +unsigned debug = MUSB_DEBUG; +module_param(debug, uint, 0); +MODULE_PARM_DESC(debug, "initial debug message level"); + +#define MUSB_VERSION_SUFFIX "/dbg" +#endif + +#define DRIVER_AUTHOR "Mentor Graphics, Texas Instruments, Nokia" +#define DRIVER_DESC "Inventra Dual-Role USB Controller Driver" + +#define MUSB_VERSION_BASE "6.0" + +#ifndef MUSB_VERSION_SUFFIX +#define MUSB_VERSION_SUFFIX "" +#endif +#define MUSB_VERSION MUSB_VERSION_BASE MUSB_VERSION_SUFFIX + +#define DRIVER_INFO DRIVER_DESC ", v" MUSB_VERSION + +#define MUSB_DRIVER_NAME "musb_hdrc" +const char musb_driver_name[] = MUSB_DRIVER_NAME; + +MODULE_DESCRIPTION(DRIVER_INFO); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" MUSB_DRIVER_NAME); + + +/*-------------------------------------------------------------------------*/ + +static inline struct musb *dev_to_musb(struct device *dev) +{ +#ifdef CONFIG_USB_MUSB_HDRC_HCD + /* usbcore insists dev->driver_data is a "struct hcd *" */ + return hcd_to_musb(dev_get_drvdata(dev)); +#else + return dev_get_drvdata(dev); +#endif +} + +/*-------------------------------------------------------------------------*/ + +#ifndef CONFIG_USB_TUSB6010 +/* + * Load an endpoint's FIFO + */ +void musb_write_fifo(struct musb_hw_ep *hw_ep, u16 len, const u8 *src) +{ + void __iomem *fifo = hw_ep->fifo; + + prefetch((u8 *)src); + + DBG(4, "%cX ep%d fifo %p count %d buf %p\n", + 'T', hw_ep->epnum, fifo, len, src); + + /* we can't assume unaligned reads work */ + if (likely((0x01 & (unsigned long) src) == 0)) { + u16 index = 0; + + /* best case is 32bit-aligned source address */ + if ((0x02 & (unsigned long) src) == 0) { + if (len >= 4) { + writesl(fifo, src + index, len >> 2); + index += len & ~0x03; + } + if (len & 0x02) { + musb_writew(fifo, 0, *(u16 *)&src[index]); + index += 2; + } + } else { + if (len >= 2) { + writesw(fifo, src + index, len >> 1); + index += len & ~0x01; + } + } + if (len & 0x01) + musb_writeb(fifo, 0, src[index]); + } else { + /* byte aligned */ + writesb(fifo, src, len); + } +} + +/* + * Unload an endpoint's FIFO + */ +void musb_read_fifo(struct musb_hw_ep *hw_ep, u16 len, u8 *dst) +{ + void __iomem *fifo = hw_ep->fifo; + + DBG(4, "%cX ep%d fifo %p count %d buf %p\n", + 'R', hw_ep->epnum, fifo, len, dst); + + /* we can't assume unaligned writes work */ + if (likely((0x01 & (unsigned long) dst) == 0)) { + u16 index = 0; + + /* best case is 32bit-aligned destination address */ + if ((0x02 & (unsigned long) dst) == 0) { + if (len >= 4) { + readsl(fifo, dst, len >> 2); + index = len & ~0x03; + } + if (len & 0x02) { + *(u16 *)&dst[index] = musb_readw(fifo, 0); + index += 2; + } + } else { + if (len >= 2) { + readsw(fifo, dst, len >> 1); + index = len & ~0x01; + } + } + if (len & 0x01) + dst[index] = musb_readb(fifo, 0); + } else { + /* byte aligned */ + readsb(fifo, dst, len); + } +} + +#endif /* normal PIO */ + + +/*-------------------------------------------------------------------------*/ + +/* for high speed test mode; see USB 2.0 spec 7.1.20 */ +static const u8 musb_test_packet[53] = { + /* implicit SYNC then DATA0 to start */ + + /* JKJKJKJK x9 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* JJKKJJKK x8 */ + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + /* JJJJKKKK x8 */ + 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, + /* JJJJJJJKKKKKKK x8 */ + 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /* JJJJJJJK x8 */ + 0x7f, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xfd, + /* JKKKKKKK x10, JK */ + 0xfc, 0x7e, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xfd, 0x7e + + /* implicit CRC16 then EOP to end */ +}; + +void musb_load_testpacket(struct musb *musb) +{ + void __iomem *regs = musb->endpoints[0].regs; + + musb_ep_select(musb->mregs, 0); + musb_write_fifo(musb->control_ep, + sizeof(musb_test_packet), musb_test_packet); + musb_writew(regs, MUSB_CSR0, MUSB_CSR0_TXPKTRDY); +} + +/*-------------------------------------------------------------------------*/ + +const char *otg_state_string(struct musb *musb) +{ + switch (musb->xceiv.state) { + case OTG_STATE_A_IDLE: return "a_idle"; + case OTG_STATE_A_WAIT_VRISE: return "a_wait_vrise"; + case OTG_STATE_A_WAIT_BCON: return "a_wait_bcon"; + case OTG_STATE_A_HOST: return "a_host"; + case OTG_STATE_A_SUSPEND: return "a_suspend"; + case OTG_STATE_A_PERIPHERAL: return "a_peripheral"; + case OTG_STATE_A_WAIT_VFALL: return "a_wait_vfall"; + case OTG_STATE_A_VBUS_ERR: return "a_vbus_err"; + case OTG_STATE_B_IDLE: return "b_idle"; + case OTG_STATE_B_SRP_INIT: return "b_srp_init"; + case OTG_STATE_B_PERIPHERAL: return "b_peripheral"; + case OTG_STATE_B_WAIT_ACON: return "b_wait_acon"; + case OTG_STATE_B_HOST: return "b_host"; + default: return "UNDEFINED"; + } +} + +#ifdef CONFIG_USB_MUSB_OTG + +/* + * See also USB_OTG_1-3.pdf 6.6.5 Timers + * REVISIT: Are the other timers done in the hardware? + */ +#define TB_ASE0_BRST 100 /* Min 3.125 ms */ + +/* + * Handles OTG hnp timeouts, such as b_ase0_brst + */ +void musb_otg_timer_func(unsigned long data) +{ + struct musb *musb = (struct musb *)data; + unsigned long flags; + + spin_lock_irqsave(&musb->lock, flags); + switch (musb->xceiv.state) { + case OTG_STATE_B_WAIT_ACON: + DBG(1, "HNP: b_wait_acon timeout; back to b_peripheral\n"); + musb_g_disconnect(musb); + musb->xceiv.state = OTG_STATE_B_PERIPHERAL; + musb->is_active = 0; + break; + case OTG_STATE_A_WAIT_BCON: + DBG(1, "HNP: a_wait_bcon timeout; back to a_host\n"); + musb_hnp_stop(musb); + break; + default: + DBG(1, "HNP: Unhandled mode %s\n", otg_state_string(musb)); + } + musb->ignore_disconnect = 0; + spin_unlock_irqrestore(&musb->lock, flags); +} + +static DEFINE_TIMER(musb_otg_timer, musb_otg_timer_func, 0, 0); + +/* + * Stops the B-device HNP state. Caller must take care of locking. + */ +void musb_hnp_stop(struct musb *musb) +{ + struct usb_hcd *hcd = musb_to_hcd(musb); + void __iomem *mbase = musb->mregs; + u8 reg; + + switch (musb->xceiv.state) { + case OTG_STATE_A_PERIPHERAL: + case OTG_STATE_A_WAIT_VFALL: + case OTG_STATE_A_WAIT_BCON: + DBG(1, "HNP: Switching back to A-host\n"); + musb_g_disconnect(musb); + musb->xceiv.state = OTG_STATE_A_IDLE; + MUSB_HST_MODE(musb); + musb->is_active = 0; + break; + case OTG_STATE_B_HOST: + DBG(1, "HNP: Disabling HR\n"); + hcd->self.is_b_host = 0; + musb->xceiv.state = OTG_STATE_B_PERIPHERAL; + MUSB_DEV_MODE(musb); + reg = musb_readb(mbase, MUSB_POWER); + reg |= MUSB_POWER_SUSPENDM; + musb_writeb(mbase, MUSB_POWER, reg); + /* REVISIT: Start SESSION_REQUEST here? */ + break; + default: + DBG(1, "HNP: Stopping in unknown state %s\n", + otg_state_string(musb)); + } + + /* + * When returning to A state after HNP, avoid hub_port_rebounce(), + * which cause occasional OPT A "Did not receive reset after connect" + * errors. + */ + musb->port1_status &= + ~(1 << USB_PORT_FEAT_C_CONNECTION); +} + +#endif + +/* + * Interrupt Service Routine to record USB "global" interrupts. + * Since these do not happen often and signify things of + * paramount importance, it seems OK to check them individually; + * the order of the tests is specified in the manual + * + * @param musb instance pointer + * @param int_usb register contents + * @param devctl + * @param power + */ + +#define STAGE0_MASK (MUSB_INTR_RESUME | MUSB_INTR_SESSREQ \ + | MUSB_INTR_VBUSERROR | MUSB_INTR_CONNECT \ + | MUSB_INTR_RESET) + +static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, + u8 devctl, u8 power) +{ + irqreturn_t handled = IRQ_NONE; + void __iomem *mbase = musb->mregs; + + DBG(3, "<== Power=%02x, DevCtl=%02x, int_usb=0x%x\n", power, devctl, + int_usb); + + /* in host mode, the peripheral may issue remote wakeup. + * in peripheral mode, the host may resume the link. + * spurious RESUME irqs happen too, paired with SUSPEND. + */ + if (int_usb & MUSB_INTR_RESUME) { + handled = IRQ_HANDLED; + DBG(3, "RESUME (%s)\n", otg_state_string(musb)); + + if (devctl & MUSB_DEVCTL_HM) { +#ifdef CONFIG_USB_MUSB_HDRC_HCD + switch (musb->xceiv.state) { + case OTG_STATE_A_SUSPEND: + /* remote wakeup? later, GetPortStatus + * will stop RESUME signaling + */ + + if (power & MUSB_POWER_SUSPENDM) { + /* spurious */ + musb->int_usb &= ~MUSB_INTR_SUSPEND; + DBG(2, "Spurious SUSPENDM\n"); + break; + } + + power &= ~MUSB_POWER_SUSPENDM; + musb_writeb(mbase, MUSB_POWER, + power | MUSB_POWER_RESUME); + + musb->port1_status |= + (USB_PORT_STAT_C_SUSPEND << 16) + | MUSB_PORT_STAT_RESUME; + musb->rh_timer = jiffies + + msecs_to_jiffies(20); + + musb->xceiv.state = OTG_STATE_A_HOST; + musb->is_active = 1; + usb_hcd_resume_root_hub(musb_to_hcd(musb)); + break; + case OTG_STATE_B_WAIT_ACON: + musb->xceiv.state = OTG_STATE_B_PERIPHERAL; + musb->is_active = 1; + MUSB_DEV_MODE(musb); + break; + default: + WARNING("bogus %s RESUME (%s)\n", + "host", + otg_state_string(musb)); + } +#endif + } else { + switch (musb->xceiv.state) { +#ifdef CONFIG_USB_MUSB_HDRC_HCD + case OTG_STATE_A_SUSPEND: + /* possibly DISCONNECT is upcoming */ + musb->xceiv.state = OTG_STATE_A_HOST; + usb_hcd_resume_root_hub(musb_to_hcd(musb)); + break; +#endif +#ifdef CONFIG_USB_GADGET_MUSB_HDRC + case OTG_STATE_B_WAIT_ACON: + case OTG_STATE_B_PERIPHERAL: + /* disconnect while suspended? we may + * not get a disconnect irq... + */ + if ((devctl & MUSB_DEVCTL_VBUS) + != (3 << MUSB_DEVCTL_VBUS_SHIFT) + ) { + musb->int_usb |= MUSB_INTR_DISCONNECT; + musb->int_usb &= ~MUSB_INTR_SUSPEND; + break; + } + musb_g_resume(musb); + break; + case OTG_STATE_B_IDLE: + musb->int_usb &= ~MUSB_INTR_SUSPEND; + break; +#endif + default: + WARNING("bogus %s RESUME (%s)\n", + "peripheral", + otg_state_string(musb)); + } + } + } + +#ifdef CONFIG_USB_MUSB_HDRC_HCD + /* see manual for the order of the tests */ + if (int_usb & MUSB_INTR_SESSREQ) { + DBG(1, "SESSION_REQUEST (%s)\n", otg_state_string(musb)); + + /* IRQ arrives from ID pin sense or (later, if VBUS power + * is removed) SRP. responses are time critical: + * - turn on VBUS (with silicon-specific mechanism) + * - go through A_WAIT_VRISE + * - ... to A_WAIT_BCON. + * a_wait_vrise_tmout triggers VBUS_ERROR transitions + */ + musb_writeb(mbase, MUSB_DEVCTL, MUSB_DEVCTL_SESSION); + musb->ep0_stage = MUSB_EP0_START; + musb->xceiv.state = OTG_STATE_A_IDLE; + MUSB_HST_MODE(musb); + musb_set_vbus(musb, 1); + + handled = IRQ_HANDLED; + } + + if (int_usb & MUSB_INTR_VBUSERROR) { + int ignore = 0; + + /* During connection as an A-Device, we may see a short + * current spikes causing voltage drop, because of cable + * and peripheral capacitance combined with vbus draw. + * (So: less common with truly self-powered devices, where + * vbus doesn't act like a power supply.) + * + * Such spikes are short; usually less than ~500 usec, max + * of ~2 msec. That is, they're not sustained overcurrent + * errors, though they're reported using VBUSERROR irqs. + * + * Workarounds: (a) hardware: use self powered devices. + * (b) software: ignore non-repeated VBUS errors. + * + * REVISIT: do delays from lots of DEBUG_KERNEL checks + * make trouble here, keeping VBUS < 4.4V ? + */ + switch (musb->xceiv.state) { + case OTG_STATE_A_HOST: + /* recovery is dicey once we've gotten past the + * initial stages of enumeration, but if VBUS + * stayed ok at the other end of the link, and + * another reset is due (at least for high speed, + * to redo the chirp etc), it might work OK... + */ + case OTG_STATE_A_WAIT_BCON: + case OTG_STATE_A_WAIT_VRISE: + if (musb->vbuserr_retry) { + musb->vbuserr_retry--; + ignore = 1; + devctl |= MUSB_DEVCTL_SESSION; + musb_writeb(mbase, MUSB_DEVCTL, devctl); + } else { + musb->port1_status |= + (1 << USB_PORT_FEAT_OVER_CURRENT) + | (1 << USB_PORT_FEAT_C_OVER_CURRENT); + } + break; + default: + break; + } + + DBG(1, "VBUS_ERROR in %s (%02x, %s), retry #%d, port1 %08x\n", + otg_state_string(musb), + devctl, + ({ char *s; + switch (devctl & MUSB_DEVCTL_VBUS) { + case 0 << MUSB_DEVCTL_VBUS_SHIFT: + s = "vbuserr_retry, + musb->port1_status); + + /* go through A_WAIT_VFALL then start a new session */ + if (!ignore) + musb_set_vbus(musb, 0); + handled = IRQ_HANDLED; + } + + if (int_usb & MUSB_INTR_CONNECT) { + struct usb_hcd *hcd = musb_to_hcd(musb); + + handled = IRQ_HANDLED; + musb->is_active = 1; + set_bit(HCD_FLAG_SAW_IRQ, &hcd->flags); + + musb->ep0_stage = MUSB_EP0_START; + +#ifdef CONFIG_USB_MUSB_OTG + /* flush endpoints when transitioning from Device Mode */ + if (is_peripheral_active(musb)) { + /* REVISIT HNP; just force disconnect */ + } + musb_writew(mbase, MUSB_INTRTXE, musb->epmask); + musb_writew(mbase, MUSB_INTRRXE, musb->epmask & 0xfffe); + musb_writeb(mbase, MUSB_INTRUSBE, 0xf7); +#endif + musb->port1_status &= ~(USB_PORT_STAT_LOW_SPEED + |USB_PORT_STAT_HIGH_SPEED + |USB_PORT_STAT_ENABLE + ); + musb->port1_status |= USB_PORT_STAT_CONNECTION + |(USB_PORT_STAT_C_CONNECTION << 16); + + /* high vs full speed is just a guess until after reset */ + if (devctl & MUSB_DEVCTL_LSDEV) + musb->port1_status |= USB_PORT_STAT_LOW_SPEED; + + if (hcd->status_urb) + usb_hcd_poll_rh_status(hcd); + else + usb_hcd_resume_root_hub(hcd); + + MUSB_HST_MODE(musb); + + /* indicate new connection to OTG machine */ + switch (musb->xceiv.state) { + case OTG_STATE_B_PERIPHERAL: + if (int_usb & MUSB_INTR_SUSPEND) { + DBG(1, "HNP: SUSPEND+CONNECT, now b_host\n"); + musb->xceiv.state = OTG_STATE_B_HOST; + hcd->self.is_b_host = 1; + int_usb &= ~MUSB_INTR_SUSPEND; + } else + DBG(1, "CONNECT as b_peripheral???\n"); + break; + case OTG_STATE_B_WAIT_ACON: + DBG(1, "HNP: Waiting to switch to b_host state\n"); + musb->xceiv.state = OTG_STATE_B_HOST; + hcd->self.is_b_host = 1; + break; + default: + if ((devctl & MUSB_DEVCTL_VBUS) + == (3 << MUSB_DEVCTL_VBUS_SHIFT)) { + musb->xceiv.state = OTG_STATE_A_HOST; + hcd->self.is_b_host = 0; + } + break; + } + DBG(1, "CONNECT (%s) devctl %02x\n", + otg_state_string(musb), devctl); + } +#endif /* CONFIG_USB_MUSB_HDRC_HCD */ + + /* mentor saves a bit: bus reset and babble share the same irq. + * only host sees babble; only peripheral sees bus reset. + */ + if (int_usb & MUSB_INTR_RESET) { + if (is_host_capable() && (devctl & MUSB_DEVCTL_HM) != 0) { + /* + * Looks like non-HS BABBLE can be ignored, but + * HS BABBLE is an error condition. For HS the solution + * is to avoid babble in the first place and fix what + * caused BABBLE. When HS BABBLE happens we can only + * stop the session. + */ + if (devctl & (MUSB_DEVCTL_FSDEV | MUSB_DEVCTL_LSDEV)) + DBG(1, "BABBLE devctl: %02x\n", devctl); + else { + ERR("Stopping host session -- babble\n"); + musb_writeb(mbase, MUSB_DEVCTL, 0); + } + } else if (is_peripheral_capable()) { + DBG(1, "BUS RESET as %s\n", otg_state_string(musb)); + switch (musb->xceiv.state) { +#ifdef CONFIG_USB_OTG + case OTG_STATE_A_SUSPEND: + /* We need to ignore disconnect on suspend + * otherwise tusb 2.0 won't reconnect after a + * power cycle, which breaks otg compliance. + */ + musb->ignore_disconnect = 1; + musb_g_reset(musb); + /* FALLTHROUGH */ + case OTG_STATE_A_WAIT_BCON: /* OPT TD.4.7-900ms */ + DBG(1, "HNP: Setting timer as %s\n", + otg_state_string(musb)); + musb_otg_timer.data = (unsigned long)musb; + mod_timer(&musb_otg_timer, jiffies + + msecs_to_jiffies(100)); + break; + case OTG_STATE_A_PERIPHERAL: + musb_hnp_stop(musb); + break; + case OTG_STATE_B_WAIT_ACON: + DBG(1, "HNP: RESET (%s), back to b_peripheral\n", + otg_state_string(musb)); + musb->xceiv.state = OTG_STATE_B_PERIPHERAL; + musb_g_reset(musb); + break; +#endif + case OTG_STATE_B_IDLE: + musb->xceiv.state = OTG_STATE_B_PERIPHERAL; + /* FALLTHROUGH */ + case OTG_STATE_B_PERIPHERAL: + musb_g_reset(musb); + break; + default: + DBG(1, "Unhandled BUS RESET as %s\n", + otg_state_string(musb)); + } + } + + handled = IRQ_HANDLED; + } + schedule_work(&musb->irq_work); + + return handled; +} + +/* + * Interrupt Service Routine to record USB "global" interrupts. + * Since these do not happen often and signify things of + * paramount importance, it seems OK to check them individually; + * the order of the tests is specified in the manual + * + * @param musb instance pointer + * @param int_usb register contents + * @param devctl + * @param power + */ +static irqreturn_t musb_stage2_irq(struct musb *musb, u8 int_usb, + u8 devctl, u8 power) +{ + irqreturn_t handled = IRQ_NONE; + +#if 0 +/* REVISIT ... this would be for multiplexing periodic endpoints, or + * supporting transfer phasing to prevent exceeding ISO bandwidth + * limits of a given frame or microframe. + * + * It's not needed for peripheral side, which dedicates endpoints; + * though it _might_ use SOF irqs for other purposes. + * + * And it's not currently needed for host side, which also dedicates + * endpoints, relies on TX/RX interval registers, and isn't claimed + * to support ISO transfers yet. + */ + if (int_usb & MUSB_INTR_SOF) { + void __iomem *mbase = musb->mregs; + struct musb_hw_ep *ep; + u8 epnum; + u16 frame; + + DBG(6, "START_OF_FRAME\n"); + handled = IRQ_HANDLED; + + /* start any periodic Tx transfers waiting for current frame */ + frame = musb_readw(mbase, MUSB_FRAME); + ep = musb->endpoints; + for (epnum = 1; (epnum < musb->nr_endpoints) + && (musb->epmask >= (1 << epnum)); + epnum++, ep++) { + /* + * FIXME handle framecounter wraps (12 bits) + * eliminate duplicated StartUrb logic + */ + if (ep->dwWaitFrame >= frame) { + ep->dwWaitFrame = 0; + printk("SOF --> periodic TX%s on %d\n", + ep->tx_channel ? " DMA" : "", + epnum); + if (!ep->tx_channel) + musb_h_tx_start(musb, epnum); + else + cppi_hostdma_start(musb, epnum); + } + } /* end of for loop */ + } +#endif + + if ((int_usb & MUSB_INTR_DISCONNECT) && !musb->ignore_disconnect) { + DBG(1, "DISCONNECT (%s) as %s, devctl %02x\n", + otg_state_string(musb), + MUSB_MODE(musb), devctl); + handled = IRQ_HANDLED; + + switch (musb->xceiv.state) { +#ifdef CONFIG_USB_MUSB_HDRC_HCD + case OTG_STATE_A_HOST: + case OTG_STATE_A_SUSPEND: + musb_root_disconnect(musb); + if (musb->a_wait_bcon != 0) + musb_platform_try_idle(musb, jiffies + + msecs_to_jiffies(musb->a_wait_bcon)); + break; +#endif /* HOST */ +#ifdef CONFIG_USB_MUSB_OTG + case OTG_STATE_B_HOST: + musb_hnp_stop(musb); + break; + case OTG_STATE_A_PERIPHERAL: + musb_hnp_stop(musb); + musb_root_disconnect(musb); + /* FALLTHROUGH */ + case OTG_STATE_B_WAIT_ACON: + /* FALLTHROUGH */ +#endif /* OTG */ +#ifdef CONFIG_USB_GADGET_MUSB_HDRC + case OTG_STATE_B_PERIPHERAL: + case OTG_STATE_B_IDLE: + musb_g_disconnect(musb); + break; +#endif /* GADGET */ + default: + WARNING("unhandled DISCONNECT transition (%s)\n", + otg_state_string(musb)); + break; + } + + schedule_work(&musb->irq_work); + } + + if (int_usb & MUSB_INTR_SUSPEND) { + DBG(1, "SUSPEND (%s) devctl %02x power %02x\n", + otg_state_string(musb), devctl, power); + handled = IRQ_HANDLED; + + switch (musb->xceiv.state) { +#ifdef CONFIG_USB_MUSB_OTG + case OTG_STATE_A_PERIPHERAL: + /* + * We cannot stop HNP here, devctl BDEVICE might be + * still set. + */ + break; +#endif + case OTG_STATE_B_PERIPHERAL: + musb_g_suspend(musb); + musb->is_active = is_otg_enabled(musb) + && musb->xceiv.gadget->b_hnp_enable; + if (musb->is_active) { +#ifdef CONFIG_USB_MUSB_OTG + musb->xceiv.state = OTG_STATE_B_WAIT_ACON; + DBG(1, "HNP: Setting timer for b_ase0_brst\n"); + musb_otg_timer.data = (unsigned long)musb; + mod_timer(&musb_otg_timer, jiffies + + msecs_to_jiffies(TB_ASE0_BRST)); +#endif + } + break; + case OTG_STATE_A_WAIT_BCON: + if (musb->a_wait_bcon != 0) + musb_platform_try_idle(musb, jiffies + + msecs_to_jiffies(musb->a_wait_bcon)); + break; + case OTG_STATE_A_HOST: + musb->xceiv.state = OTG_STATE_A_SUSPEND; + musb->is_active = is_otg_enabled(musb) + && musb->xceiv.host->b_hnp_enable; + break; + case OTG_STATE_B_HOST: + /* Transition to B_PERIPHERAL, see 6.8.2.6 p 44 */ + DBG(1, "REVISIT: SUSPEND as B_HOST\n"); + break; + default: + /* "should not happen" */ + musb->is_active = 0; + break; + } + schedule_work(&musb->irq_work); + } + + + return handled; +} + +/*-------------------------------------------------------------------------*/ + +/* +* Program the HDRC to start (enable interrupts, dma, etc.). +*/ +void musb_start(struct musb *musb) +{ + void __iomem *regs = musb->mregs; + u8 devctl = musb_readb(regs, MUSB_DEVCTL); + + DBG(2, "<== devctl %02x\n", devctl); + + /* Set INT enable registers, enable interrupts */ + musb_writew(regs, MUSB_INTRTXE, musb->epmask); + musb_writew(regs, MUSB_INTRRXE, musb->epmask & 0xfffe); + musb_writeb(regs, MUSB_INTRUSBE, 0xf7); + + musb_writeb(regs, MUSB_TESTMODE, 0); + + /* put into basic highspeed mode and start session */ + musb_writeb(regs, MUSB_POWER, MUSB_POWER_ISOUPDATE + | MUSB_POWER_SOFTCONN + | MUSB_POWER_HSENAB + /* ENSUSPEND wedges tusb */ + /* | MUSB_POWER_ENSUSPEND */ + ); + + musb->is_active = 0; + devctl = musb_readb(regs, MUSB_DEVCTL); + devctl &= ~MUSB_DEVCTL_SESSION; + + if (is_otg_enabled(musb)) { + /* session started after: + * (a) ID-grounded irq, host mode; + * (b) vbus present/connect IRQ, peripheral mode; + * (c) peripheral initiates, using SRP + */ + if ((devctl & MUSB_DEVCTL_VBUS) == MUSB_DEVCTL_VBUS) + musb->is_active = 1; + else + devctl |= MUSB_DEVCTL_SESSION; + + } else if (is_host_enabled(musb)) { + /* assume ID pin is hard-wired to ground */ + devctl |= MUSB_DEVCTL_SESSION; + + } else /* peripheral is enabled */ { + if ((devctl & MUSB_DEVCTL_VBUS) == MUSB_DEVCTL_VBUS) + musb->is_active = 1; + } + musb_platform_enable(musb); + musb_writeb(regs, MUSB_DEVCTL, devctl); +} + + +static void musb_generic_disable(struct musb *musb) +{ + void __iomem *mbase = musb->mregs; + u16 temp; + + /* disable interrupts */ + musb_writeb(mbase, MUSB_INTRUSBE, 0); + musb_writew(mbase, MUSB_INTRTXE, 0); + musb_writew(mbase, MUSB_INTRRXE, 0); + + /* off */ + musb_writeb(mbase, MUSB_DEVCTL, 0); + + /* flush pending interrupts */ + temp = musb_readb(mbase, MUSB_INTRUSB); + temp = musb_readw(mbase, MUSB_INTRTX); + temp = musb_readw(mbase, MUSB_INTRRX); + +} + +/* + * Make the HDRC stop (disable interrupts, etc.); + * reversible by musb_start + * called on gadget driver unregister + * with controller locked, irqs blocked + * acts as a NOP unless some role activated the hardware + */ +void musb_stop(struct musb *musb) +{ + /* stop IRQs, timers, ... */ + musb_platform_disable(musb); + musb_generic_disable(musb); + DBG(3, "HDRC disabled\n"); + + /* FIXME + * - mark host and/or peripheral drivers unusable/inactive + * - disable DMA (and enable it in HdrcStart) + * - make sure we can musb_start() after musb_stop(); with + * OTG mode, gadget driver module rmmod/modprobe cycles that + * - ... + */ + musb_platform_try_idle(musb, 0); +} + +static void musb_shutdown(struct platform_device *pdev) +{ + struct musb *musb = dev_to_musb(&pdev->dev); + unsigned long flags; + + spin_lock_irqsave(&musb->lock, flags); + musb_platform_disable(musb); + musb_generic_disable(musb); + if (musb->clock) { + clk_put(musb->clock); + musb->clock = NULL; + } + spin_unlock_irqrestore(&musb->lock, flags); + + /* FIXME power down */ +} + + +/*-------------------------------------------------------------------------*/ + +/* + * The silicon either has hard-wired endpoint configurations, or else + * "dynamic fifo" sizing. The driver has support for both, though at this + * writing only the dynamic sizing is very well tested. We use normal + * idioms to so both modes are compile-tested, but dead code elimination + * leaves only the relevant one in the object file. + * + * We don't currently use dynamic fifo setup capability to do anything + * more than selecting one of a bunch of predefined configurations. + */ +#ifdef MUSB_C_DYNFIFO_DEF +#define can_dynfifo() 1 +#else +#define can_dynfifo() 0 +#endif + +#if defined(CONFIG_USB_TUSB6010) || \ + defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP34XX) +static ushort __initdata fifo_mode = 4; +#else +static ushort __initdata fifo_mode = 2; +#endif + +/* "modprobe ... fifo_mode=1" etc */ +module_param(fifo_mode, ushort, 0); +MODULE_PARM_DESC(fifo_mode, "initial endpoint configuration"); + + +#define DYN_FIFO_SIZE (1<<(MUSB_C_RAM_BITS+2)) + +enum fifo_style { FIFO_RXTX, FIFO_TX, FIFO_RX } __attribute__ ((packed)); +enum buf_mode { BUF_SINGLE, BUF_DOUBLE } __attribute__ ((packed)); + +struct fifo_cfg { + u8 hw_ep_num; + enum fifo_style style; + enum buf_mode mode; + u16 maxpacket; +}; + +/* + * tables defining fifo_mode values. define more if you like. + * for host side, make sure both halves of ep1 are set up. + */ + +/* mode 0 - fits in 2KB */ +static struct fifo_cfg __initdata mode_0_cfg[] = { +{ .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, }, +{ .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, }, +{ .hw_ep_num = 2, .style = FIFO_RXTX, .maxpacket = 512, }, +{ .hw_ep_num = 3, .style = FIFO_RXTX, .maxpacket = 256, }, +{ .hw_ep_num = 4, .style = FIFO_RXTX, .maxpacket = 256, }, +}; + +/* mode 1 - fits in 4KB */ +static struct fifo_cfg __initdata mode_1_cfg[] = { +{ .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, .mode = BUF_DOUBLE, }, +{ .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, .mode = BUF_DOUBLE, }, +{ .hw_ep_num = 2, .style = FIFO_RXTX, .maxpacket = 512, .mode = BUF_DOUBLE, }, +{ .hw_ep_num = 3, .style = FIFO_RXTX, .maxpacket = 256, }, +{ .hw_ep_num = 4, .style = FIFO_RXTX, .maxpacket = 256, }, +}; + +/* mode 2 - fits in 4KB */ +static struct fifo_cfg __initdata mode_2_cfg[] = { +{ .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, }, +{ .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, }, +{ .hw_ep_num = 2, .style = FIFO_TX, .maxpacket = 512, }, +{ .hw_ep_num = 2, .style = FIFO_RX, .maxpacket = 512, }, +{ .hw_ep_num = 3, .style = FIFO_RXTX, .maxpacket = 256, }, +{ .hw_ep_num = 4, .style = FIFO_RXTX, .maxpacket = 256, }, +}; + +/* mode 3 - fits in 4KB */ +static struct fifo_cfg __initdata mode_3_cfg[] = { +{ .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, .mode = BUF_DOUBLE, }, +{ .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, .mode = BUF_DOUBLE, }, +{ .hw_ep_num = 2, .style = FIFO_TX, .maxpacket = 512, }, +{ .hw_ep_num = 2, .style = FIFO_RX, .maxpacket = 512, }, +{ .hw_ep_num = 3, .style = FIFO_RXTX, .maxpacket = 256, }, +{ .hw_ep_num = 4, .style = FIFO_RXTX, .maxpacket = 256, }, +}; + +/* mode 4 - fits in 16KB */ +static struct fifo_cfg __initdata mode_4_cfg[] = { +{ .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, }, +{ .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, }, +{ .hw_ep_num = 2, .style = FIFO_TX, .maxpacket = 512, }, +{ .hw_ep_num = 2, .style = FIFO_RX, .maxpacket = 512, }, +{ .hw_ep_num = 3, .style = FIFO_TX, .maxpacket = 512, }, +{ .hw_ep_num = 3, .style = FIFO_RX, .maxpacket = 512, }, +{ .hw_ep_num = 4, .style = FIFO_TX, .maxpacket = 512, }, +{ .hw_ep_num = 4, .style = FIFO_RX, .maxpacket = 512, }, +{ .hw_ep_num = 5, .style = FIFO_TX, .maxpacket = 512, }, +{ .hw_ep_num = 5, .style = FIFO_RX, .maxpacket = 512, }, +{ .hw_ep_num = 6, .style = FIFO_TX, .maxpacket = 512, }, +{ .hw_ep_num = 6, .style = FIFO_RX, .maxpacket = 512, }, +{ .hw_ep_num = 7, .style = FIFO_TX, .maxpacket = 512, }, +{ .hw_ep_num = 7, .style = FIFO_RX, .maxpacket = 512, }, +{ .hw_ep_num = 8, .style = FIFO_TX, .maxpacket = 512, }, +{ .hw_ep_num = 8, .style = FIFO_RX, .maxpacket = 512, }, +{ .hw_ep_num = 9, .style = FIFO_TX, .maxpacket = 512, }, +{ .hw_ep_num = 9, .style = FIFO_RX, .maxpacket = 512, }, +{ .hw_ep_num = 10, .style = FIFO_TX, .maxpacket = 512, }, +{ .hw_ep_num = 10, .style = FIFO_RX, .maxpacket = 512, }, +{ .hw_ep_num = 11, .style = FIFO_TX, .maxpacket = 512, }, +{ .hw_ep_num = 11, .style = FIFO_RX, .maxpacket = 512, }, +{ .hw_ep_num = 12, .style = FIFO_TX, .maxpacket = 512, }, +{ .hw_ep_num = 12, .style = FIFO_RX, .maxpacket = 512, }, +{ .hw_ep_num = 13, .style = FIFO_TX, .maxpacket = 512, }, +{ .hw_ep_num = 13, .style = FIFO_RX, .maxpacket = 512, }, +{ .hw_ep_num = 14, .style = FIFO_RXTX, .maxpacket = 1024, }, +{ .hw_ep_num = 15, .style = FIFO_RXTX, .maxpacket = 1024, }, +}; + + +/* + * configure a fifo; for non-shared endpoints, this may be called + * once for a tx fifo and once for an rx fifo. + * + * returns negative errno or offset for next fifo. + */ +static int __init +fifo_setup(struct musb *musb, struct musb_hw_ep *hw_ep, + const struct fifo_cfg *cfg, u16 offset) +{ + void __iomem *mbase = musb->mregs; + int size = 0; + u16 maxpacket = cfg->maxpacket; + u16 c_off = offset >> 3; + u8 c_size; + + /* expect hw_ep has already been zero-initialized */ + + size = ffs(max(maxpacket, (u16) 8)) - 1; + maxpacket = 1 << size; + + c_size = size - 3; + if (cfg->mode == BUF_DOUBLE) { + if ((offset + (maxpacket << 1)) > DYN_FIFO_SIZE) + return -EMSGSIZE; + c_size |= MUSB_FIFOSZ_DPB; + } else { + if ((offset + maxpacket) > DYN_FIFO_SIZE) + return -EMSGSIZE; + } + + /* configure the FIFO */ + musb_writeb(mbase, MUSB_INDEX, hw_ep->epnum); + +#ifdef CONFIG_USB_MUSB_HDRC_HCD + /* EP0 reserved endpoint for control, bidirectional; + * EP1 reserved for bulk, two unidirection halves. + */ + if (hw_ep->epnum == 1) + musb->bulk_ep = hw_ep; + /* REVISIT error check: be sure ep0 can both rx and tx ... */ +#endif + switch (cfg->style) { + case FIFO_TX: + musb_writeb(mbase, MUSB_TXFIFOSZ, c_size); + musb_writew(mbase, MUSB_TXFIFOADD, c_off); + hw_ep->tx_double_buffered = !!(c_size & MUSB_FIFOSZ_DPB); + hw_ep->max_packet_sz_tx = maxpacket; + break; + case FIFO_RX: + musb_writeb(mbase, MUSB_RXFIFOSZ, c_size); + musb_writew(mbase, MUSB_RXFIFOADD, c_off); + hw_ep->rx_double_buffered = !!(c_size & MUSB_FIFOSZ_DPB); + hw_ep->max_packet_sz_rx = maxpacket; + break; + case FIFO_RXTX: + musb_writeb(mbase, MUSB_TXFIFOSZ, c_size); + musb_writew(mbase, MUSB_TXFIFOADD, c_off); + hw_ep->rx_double_buffered = !!(c_size & MUSB_FIFOSZ_DPB); + hw_ep->max_packet_sz_rx = maxpacket; + + musb_writeb(mbase, MUSB_RXFIFOSZ, c_size); + musb_writew(mbase, MUSB_RXFIFOADD, c_off); + hw_ep->tx_double_buffered = hw_ep->rx_double_buffered; + hw_ep->max_packet_sz_tx = maxpacket; + + hw_ep->is_shared_fifo = true; + break; + } + + /* NOTE rx and tx endpoint irqs aren't managed separately, + * which happens to be ok + */ + musb->epmask |= (1 << hw_ep->epnum); + + return offset + (maxpacket << ((c_size & MUSB_FIFOSZ_DPB) ? 1 : 0)); +} + +static struct fifo_cfg __initdata ep0_cfg = { + .style = FIFO_RXTX, .maxpacket = 64, +}; + +static int __init ep_config_from_table(struct musb *musb) +{ + const struct fifo_cfg *cfg; + unsigned i, n; + int offset; + struct musb_hw_ep *hw_ep = musb->endpoints; + + switch (fifo_mode) { + default: + fifo_mode = 0; + /* FALLTHROUGH */ + case 0: + cfg = mode_0_cfg; + n = ARRAY_SIZE(mode_0_cfg); + break; + case 1: + cfg = mode_1_cfg; + n = ARRAY_SIZE(mode_1_cfg); + break; + case 2: + cfg = mode_2_cfg; + n = ARRAY_SIZE(mode_2_cfg); + break; + case 3: + cfg = mode_3_cfg; + n = ARRAY_SIZE(mode_3_cfg); + break; + case 4: + cfg = mode_4_cfg; + n = ARRAY_SIZE(mode_4_cfg); + break; + } + + printk(KERN_DEBUG "%s: setup fifo_mode %d\n", + musb_driver_name, fifo_mode); + + + offset = fifo_setup(musb, hw_ep, &ep0_cfg, 0); + /* assert(offset > 0) */ + + /* NOTE: for RTL versions >= 1.400 EPINFO and RAMINFO would + * be better than static MUSB_C_NUM_EPS and DYN_FIFO_SIZE... + */ + + for (i = 0; i < n; i++) { + u8 epn = cfg->hw_ep_num; + + if (epn >= MUSB_C_NUM_EPS) { + pr_debug("%s: invalid ep %d\n", + musb_driver_name, epn); + continue; + } + offset = fifo_setup(musb, hw_ep + epn, cfg++, offset); + if (offset < 0) { + pr_debug("%s: mem overrun, ep %d\n", + musb_driver_name, epn); + return -EINVAL; + } + epn++; + musb->nr_endpoints = max(epn, musb->nr_endpoints); + } + + printk(KERN_DEBUG "%s: %d/%d max ep, %d/%d memory\n", + musb_driver_name, + n + 1, MUSB_C_NUM_EPS * 2 - 1, + offset, DYN_FIFO_SIZE); + +#ifdef CONFIG_USB_MUSB_HDRC_HCD + if (!musb->bulk_ep) { + pr_debug("%s: missing bulk\n", musb_driver_name); + return -EINVAL; + } +#endif + + return 0; +} + + +/* + * ep_config_from_hw - when MUSB_C_DYNFIFO_DEF is false + * @param musb the controller + */ +static int __init ep_config_from_hw(struct musb *musb) +{ + u8 epnum = 0, reg; + struct musb_hw_ep *hw_ep; + void *mbase = musb->mregs; + + DBG(2, "<== static silicon ep config\n"); + + /* FIXME pick up ep0 maxpacket size */ + + for (epnum = 1; epnum < MUSB_C_NUM_EPS; epnum++) { + musb_ep_select(mbase, epnum); + hw_ep = musb->endpoints + epnum; + + /* read from core using indexed model */ + reg = musb_readb(hw_ep->regs, 0x10 + MUSB_FIFOSIZE); + if (!reg) { + /* 0's returned when no more endpoints */ + break; + } + musb->nr_endpoints++; + musb->epmask |= (1 << epnum); + + hw_ep->max_packet_sz_tx = 1 << (reg & 0x0f); + + /* shared TX/RX FIFO? */ + if ((reg & 0xf0) == 0xf0) { + hw_ep->max_packet_sz_rx = hw_ep->max_packet_sz_tx; + hw_ep->is_shared_fifo = true; + continue; + } else { + hw_ep->max_packet_sz_rx = 1 << ((reg & 0xf0) >> 4); + hw_ep->is_shared_fifo = false; + } + + /* FIXME set up hw_ep->{rx,tx}_double_buffered */ + +#ifdef CONFIG_USB_MUSB_HDRC_HCD + /* pick an RX/TX endpoint for bulk */ + if (hw_ep->max_packet_sz_tx < 512 + || hw_ep->max_packet_sz_rx < 512) + continue; + + /* REVISIT: this algorithm is lazy, we should at least + * try to pick a double buffered endpoint. + */ + if (musb->bulk_ep) + continue; + musb->bulk_ep = hw_ep; +#endif + } + +#ifdef CONFIG_USB_MUSB_HDRC_HCD + if (!musb->bulk_ep) { + pr_debug("%s: missing bulk\n", musb_driver_name); + return -EINVAL; + } +#endif + + return 0; +} + +enum { MUSB_CONTROLLER_MHDRC, MUSB_CONTROLLER_HDRC, }; + +/* Initialize MUSB (M)HDRC part of the USB hardware subsystem; + * configure endpoints, or take their config from silicon + */ +static int __init musb_core_init(u16 musb_type, struct musb *musb) +{ +#ifdef MUSB_AHB_ID + u32 data; +#endif + u8 reg; + char *type; + u16 hwvers, rev_major, rev_minor; + char aInfo[78], aRevision[32], aDate[12]; + void __iomem *mbase = musb->mregs; + int status = 0; + int i; + + /* log core options (read using indexed model) */ + musb_ep_select(mbase, 0); + reg = musb_readb(mbase, 0x10 + MUSB_CONFIGDATA); + + strcpy(aInfo, (reg & MUSB_CONFIGDATA_UTMIDW) ? "UTMI-16" : "UTMI-8"); + if (reg & MUSB_CONFIGDATA_DYNFIFO) + strcat(aInfo, ", dyn FIFOs"); + if (reg & MUSB_CONFIGDATA_MPRXE) { + strcat(aInfo, ", bulk combine"); +#ifdef C_MP_RX + musb->bulk_combine = true; +#else + strcat(aInfo, " (X)"); /* no driver support */ +#endif + } + if (reg & MUSB_CONFIGDATA_MPTXE) { + strcat(aInfo, ", bulk split"); +#ifdef C_MP_TX + musb->bulk_split = true; +#else + strcat(aInfo, " (X)"); /* no driver support */ +#endif + } + if (reg & MUSB_CONFIGDATA_HBRXE) { + strcat(aInfo, ", HB-ISO Rx"); + strcat(aInfo, " (X)"); /* no driver support */ + } + if (reg & MUSB_CONFIGDATA_HBTXE) { + strcat(aInfo, ", HB-ISO Tx"); + strcat(aInfo, " (X)"); /* no driver support */ + } + if (reg & MUSB_CONFIGDATA_SOFTCONE) + strcat(aInfo, ", SoftConn"); + + printk(KERN_DEBUG "%s: ConfigData=0x%02x (%s)\n", + musb_driver_name, reg, aInfo); + +#ifdef MUSB_AHB_ID + data = musb_readl(mbase, 0x404); + sprintf(aDate, "%04d-%02x-%02x", (data & 0xffff), + (data >> 16) & 0xff, (data >> 24) & 0xff); + /* FIXME ID2 and ID3 are unused */ + data = musb_readl(mbase, 0x408); + printk("ID2=%lx\n", (long unsigned)data); + data = musb_readl(mbase, 0x40c); + printk("ID3=%lx\n", (long unsigned)data); + reg = musb_readb(mbase, 0x400); + musb_type = ('M' == reg) ? MUSB_CONTROLLER_MHDRC : MUSB_CONTROLLER_HDRC; +#else + aDate[0] = 0; +#endif + if (MUSB_CONTROLLER_MHDRC == musb_type) { + musb->is_multipoint = 1; + type = "M"; + } else { + musb->is_multipoint = 0; + type = ""; +#ifdef CONFIG_USB_MUSB_HDRC_HCD +#ifndef CONFIG_USB_OTG_BLACKLIST_HUB + printk(KERN_ERR + "%s: kernel must blacklist external hubs\n", + musb_driver_name); +#endif +#endif + } + + /* log release info */ + hwvers = musb_readw(mbase, MUSB_HWVERS); + rev_major = (hwvers >> 10) & 0x1f; + rev_minor = hwvers & 0x3ff; + snprintf(aRevision, 32, "%d.%d%s", rev_major, + rev_minor, (hwvers & 0x8000) ? "RC" : ""); + printk(KERN_DEBUG "%s: %sHDRC RTL version %s %s\n", + musb_driver_name, type, aRevision, aDate); + + /* configure ep0 */ + musb->endpoints[0].max_packet_sz_tx = MUSB_EP0_FIFOSIZE; + musb->endpoints[0].max_packet_sz_rx = MUSB_EP0_FIFOSIZE; + + /* discover endpoint configuration */ + musb->nr_endpoints = 1; + musb->epmask = 1; + + if (reg & MUSB_CONFIGDATA_DYNFIFO) { + if (can_dynfifo()) + status = ep_config_from_table(musb); + else { + ERR("reconfigure software for Dynamic FIFOs\n"); + status = -ENODEV; + } + } else { + if (!can_dynfifo()) + status = ep_config_from_hw(musb); + else { + ERR("reconfigure software for static FIFOs\n"); + return -ENODEV; + } + } + + if (status < 0) + return status; + + /* finish init, and print endpoint config */ + for (i = 0; i < musb->nr_endpoints; i++) { + struct musb_hw_ep *hw_ep = musb->endpoints + i; + + hw_ep->fifo = MUSB_FIFO_OFFSET(i) + mbase; +#ifdef CONFIG_USB_TUSB6010 + hw_ep->fifo_async = musb->async + 0x400 + MUSB_FIFO_OFFSET(i); + hw_ep->fifo_sync = musb->sync + 0x400 + MUSB_FIFO_OFFSET(i); + hw_ep->fifo_sync_va = + musb->sync_va + 0x400 + MUSB_FIFO_OFFSET(i); + + if (i == 0) + hw_ep->conf = mbase - 0x400 + TUSB_EP0_CONF; + else + hw_ep->conf = mbase + 0x400 + (((i - 1) & 0xf) << 2); +#endif + + hw_ep->regs = MUSB_EP_OFFSET(i, 0) + mbase; +#ifdef CONFIG_USB_MUSB_HDRC_HCD + hw_ep->target_regs = MUSB_BUSCTL_OFFSET(i, 0) + mbase; + hw_ep->rx_reinit = 1; + hw_ep->tx_reinit = 1; +#endif + + if (hw_ep->max_packet_sz_tx) { + printk(KERN_DEBUG + "%s: hw_ep %d%s, %smax %d\n", + musb_driver_name, i, + hw_ep->is_shared_fifo ? "shared" : "tx", + hw_ep->tx_double_buffered + ? "doublebuffer, " : "", + hw_ep->max_packet_sz_tx); + } + if (hw_ep->max_packet_sz_rx && !hw_ep->is_shared_fifo) { + printk(KERN_DEBUG + "%s: hw_ep %d%s, %smax %d\n", + musb_driver_name, i, + "rx", + hw_ep->rx_double_buffered + ? "doublebuffer, " : "", + hw_ep->max_packet_sz_rx); + } + if (!(hw_ep->max_packet_sz_tx || hw_ep->max_packet_sz_rx)) + DBG(1, "hw_ep %d not configured\n", i); + } + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +#if defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP3430) + +static irqreturn_t generic_interrupt(int irq, void *__hci) +{ + unsigned long flags; + irqreturn_t retval = IRQ_NONE; + struct musb *musb = __hci; + + spin_lock_irqsave(&musb->lock, flags); + + musb->int_usb = musb_readb(musb->mregs, MUSB_INTRUSB); + musb->int_tx = musb_readw(musb->mregs, MUSB_INTRTX); + musb->int_rx = musb_readw(musb->mregs, MUSB_INTRRX); + + if (musb->int_usb || musb->int_tx || musb->int_rx) + retval = musb_interrupt(musb); + + spin_unlock_irqrestore(&musb->lock, flags); + + /* REVISIT we sometimes get spurious IRQs on g_ep0 + * not clear why... + */ + if (retval != IRQ_HANDLED) + DBG(5, "spurious?\n"); + + return IRQ_HANDLED; +} + +#else +#define generic_interrupt NULL +#endif + +/* + * handle all the irqs defined by the HDRC core. for now we expect: other + * irq sources (phy, dma, etc) will be handled first, musb->int_* values + * will be assigned, and the irq will already have been acked. + * + * called in irq context with spinlock held, irqs blocked + */ +irqreturn_t musb_interrupt(struct musb *musb) +{ + irqreturn_t retval = IRQ_NONE; + u8 devctl, power; + int ep_num; + u32 reg; + + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + power = musb_readb(musb->mregs, MUSB_POWER); + + DBG(4, "** IRQ %s usb%04x tx%04x rx%04x\n", + (devctl & MUSB_DEVCTL_HM) ? "host" : "peripheral", + musb->int_usb, musb->int_tx, musb->int_rx); + + /* the core can interrupt us for multiple reasons; docs have + * a generic interrupt flowchart to follow + */ + if (musb->int_usb & STAGE0_MASK) + retval |= musb_stage0_irq(musb, musb->int_usb, + devctl, power); + + /* "stage 1" is handling endpoint irqs */ + + /* handle endpoint 0 first */ + if (musb->int_tx & 1) { + if (devctl & MUSB_DEVCTL_HM) + retval |= musb_h_ep0_irq(musb); + else + retval |= musb_g_ep0_irq(musb); + } + + /* RX on endpoints 1-15 */ + reg = musb->int_rx >> 1; + ep_num = 1; + while (reg) { + if (reg & 1) { + /* musb_ep_select(musb->mregs, ep_num); */ + /* REVISIT just retval = ep->rx_irq(...) */ + retval = IRQ_HANDLED; + if (devctl & MUSB_DEVCTL_HM) { + if (is_host_capable()) + musb_host_rx(musb, ep_num); + } else { + if (is_peripheral_capable()) + musb_g_rx(musb, ep_num); + } + } + + reg >>= 1; + ep_num++; + } + + /* TX on endpoints 1-15 */ + reg = musb->int_tx >> 1; + ep_num = 1; + while (reg) { + if (reg & 1) { + /* musb_ep_select(musb->mregs, ep_num); */ + /* REVISIT just retval |= ep->tx_irq(...) */ + retval = IRQ_HANDLED; + if (devctl & MUSB_DEVCTL_HM) { + if (is_host_capable()) + musb_host_tx(musb, ep_num); + } else { + if (is_peripheral_capable()) + musb_g_tx(musb, ep_num); + } + } + reg >>= 1; + ep_num++; + } + + /* finish handling "global" interrupts after handling fifos */ + if (musb->int_usb) + retval |= musb_stage2_irq(musb, + musb->int_usb, devctl, power); + + return retval; +} + + +#ifndef CONFIG_MUSB_PIO_ONLY +static int __initdata use_dma = 1; + +/* "modprobe ... use_dma=0" etc */ +module_param(use_dma, bool, 0); +MODULE_PARM_DESC(use_dma, "enable/disable use of DMA"); + +void musb_dma_completion(struct musb *musb, u8 epnum, u8 transmit) +{ + u8 devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + + /* called with controller lock already held */ + + if (!epnum) { +#ifndef CONFIG_USB_TUSB_OMAP_DMA + if (!is_cppi_enabled()) { + /* endpoint 0 */ + if (devctl & MUSB_DEVCTL_HM) + musb_h_ep0_irq(musb); + else + musb_g_ep0_irq(musb); + } +#endif + } else { + /* endpoints 1..15 */ + if (transmit) { + if (devctl & MUSB_DEVCTL_HM) { + if (is_host_capable()) + musb_host_tx(musb, epnum); + } else { + if (is_peripheral_capable()) + musb_g_tx(musb, epnum); + } + } else { + /* receive */ + if (devctl & MUSB_DEVCTL_HM) { + if (is_host_capable()) + musb_host_rx(musb, epnum); + } else { + if (is_peripheral_capable()) + musb_g_rx(musb, epnum); + } + } + } +} + +#else +#define use_dma 0 +#endif + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_SYSFS + +static ssize_t +musb_mode_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct musb *musb = dev_to_musb(dev); + unsigned long flags; + int ret = -EINVAL; + + spin_lock_irqsave(&musb->lock, flags); + ret = sprintf(buf, "%s\n", otg_state_string(musb)); + spin_unlock_irqrestore(&musb->lock, flags); + + return ret; +} + +static ssize_t +musb_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct musb *musb = dev_to_musb(dev); + unsigned long flags; + + spin_lock_irqsave(&musb->lock, flags); + if (!strncmp(buf, "host", 4)) + musb_platform_set_mode(musb, MUSB_HOST); + if (!strncmp(buf, "peripheral", 10)) + musb_platform_set_mode(musb, MUSB_PERIPHERAL); + if (!strncmp(buf, "otg", 3)) + musb_platform_set_mode(musb, MUSB_OTG); + spin_unlock_irqrestore(&musb->lock, flags); + + return n; +} +static DEVICE_ATTR(mode, 0644, musb_mode_show, musb_mode_store); + +static ssize_t +musb_vbus_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct musb *musb = dev_to_musb(dev); + unsigned long flags; + unsigned long val; + + if (sscanf(buf, "%lu", &val) < 1) { + printk(KERN_ERR "Invalid VBUS timeout ms value\n"); + return -EINVAL; + } + + spin_lock_irqsave(&musb->lock, flags); + musb->a_wait_bcon = val; + if (musb->xceiv.state == OTG_STATE_A_WAIT_BCON) + musb->is_active = 0; + musb_platform_try_idle(musb, jiffies + msecs_to_jiffies(val)); + spin_unlock_irqrestore(&musb->lock, flags); + + return n; +} + +static ssize_t +musb_vbus_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct musb *musb = dev_to_musb(dev); + unsigned long flags; + unsigned long val; + int vbus; + + spin_lock_irqsave(&musb->lock, flags); + val = musb->a_wait_bcon; + vbus = musb_platform_get_vbus_status(musb); + spin_unlock_irqrestore(&musb->lock, flags); + + return sprintf(buf, "Vbus %s, timeout %lu\n", + vbus ? "on" : "off", val); +} +static DEVICE_ATTR(vbus, 0644, musb_vbus_show, musb_vbus_store); + +#ifdef CONFIG_USB_GADGET_MUSB_HDRC + +/* Gadget drivers can't know that a host is connected so they might want + * to start SRP, but users can. This allows userspace to trigger SRP. + */ +static ssize_t +musb_srp_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct musb *musb = dev_to_musb(dev); + unsigned short srp; + + if (sscanf(buf, "%hu", &srp) != 1 + || (srp != 1)) { + printk(KERN_ERR "SRP: Value must be 1\n"); + return -EINVAL; + } + + if (srp == 1) + musb_g_wakeup(musb); + + return n; +} +static DEVICE_ATTR(srp, 0644, NULL, musb_srp_store); + +#endif /* CONFIG_USB_GADGET_MUSB_HDRC */ + +#endif /* sysfs */ + +/* Only used to provide driver mode change events */ +static void musb_irq_work(struct work_struct *data) +{ + struct musb *musb = container_of(data, struct musb, irq_work); + static int old_state; + + if (musb->xceiv.state != old_state) { + old_state = musb->xceiv.state; + sysfs_notify(&musb->controller->kobj, NULL, "mode"); + } +} + +/* -------------------------------------------------------------------------- + * Init support + */ + +static struct musb *__init +allocate_instance(struct device *dev, void __iomem *mbase) +{ + struct musb *musb; + struct musb_hw_ep *ep; + int epnum; +#ifdef CONFIG_USB_MUSB_HDRC_HCD + struct usb_hcd *hcd; + + hcd = usb_create_hcd(&musb_hc_driver, dev, dev->bus_id); + if (!hcd) + return NULL; + /* usbcore sets dev->driver_data to hcd, and sometimes uses that... */ + + musb = hcd_to_musb(hcd); + INIT_LIST_HEAD(&musb->control); + INIT_LIST_HEAD(&musb->in_bulk); + INIT_LIST_HEAD(&musb->out_bulk); + + hcd->uses_new_polling = 1; + + musb->vbuserr_retry = VBUSERR_RETRY_COUNT; +#else + musb = kzalloc(sizeof *musb, GFP_KERNEL); + if (!musb) + return NULL; + dev_set_drvdata(dev, musb); + +#endif + + musb->mregs = mbase; + musb->ctrl_base = mbase; + musb->nIrq = -ENODEV; + for (epnum = 0, ep = musb->endpoints; + epnum < MUSB_C_NUM_EPS; + epnum++, ep++) { + + ep->musb = musb; + ep->epnum = epnum; + } + +#ifdef CONFIG_USB_MUSB_OTG + otg_set_transceiver(&musb->xceiv); +#endif + musb->controller = dev; + return musb; +} + +static void musb_free(struct musb *musb) +{ + /* this has multiple entry modes. it handles fault cleanup after + * probe(), where things may be partially set up, as well as rmmod + * cleanup after everything's been de-activated. + */ + +#ifdef CONFIG_SYSFS + device_remove_file(musb->controller, &dev_attr_mode); + device_remove_file(musb->controller, &dev_attr_vbus); +#ifdef CONFIG_USB_MUSB_OTG + device_remove_file(musb->controller, &dev_attr_srp); +#endif +#endif + +#ifdef CONFIG_USB_GADGET_MUSB_HDRC + musb_gadget_cleanup(musb); +#endif + + if (musb->nIrq >= 0) { + disable_irq_wake(musb->nIrq); + free_irq(musb->nIrq, musb); + } + if (is_dma_capable() && musb->dma_controller) { + struct dma_controller *c = musb->dma_controller; + + (void) c->stop(c); + dma_controller_destroy(c); + } + + musb_writeb(musb->mregs, MUSB_DEVCTL, 0); + musb_platform_exit(musb); + musb_writeb(musb->mregs, MUSB_DEVCTL, 0); + + if (musb->clock) { + clk_disable(musb->clock); + clk_put(musb->clock); + } + +#ifdef CONFIG_USB_MUSB_OTG + put_device(musb->xceiv.dev); +#endif + +#ifdef CONFIG_USB_MUSB_HDRC_HCD + usb_put_hcd(musb_to_hcd(musb)); +#else + kfree(musb); +#endif +} + +/* + * Perform generic per-controller initialization. + * + * @pDevice: the controller (already clocked, etc) + * @nIrq: irq + * @mregs: virtual address of controller registers, + * not yet corrected for platform-specific offsets + */ +static int __init +musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl) +{ + int status; + struct musb *musb; + struct musb_hdrc_platform_data *plat = dev->platform_data; + + /* The driver might handle more features than the board; OK. + * Fail when the board needs a feature that's not enabled. + */ + if (!plat) { + dev_dbg(dev, "no platform_data?\n"); + return -ENODEV; + } + switch (plat->mode) { + case MUSB_HOST: +#ifdef CONFIG_USB_MUSB_HDRC_HCD + break; +#else + goto bad_config; +#endif + case MUSB_PERIPHERAL: +#ifdef CONFIG_USB_GADGET_MUSB_HDRC + break; +#else + goto bad_config; +#endif + case MUSB_OTG: +#ifdef CONFIG_USB_MUSB_OTG + break; +#else +bad_config: +#endif + default: + dev_err(dev, "incompatible Kconfig role setting\n"); + return -EINVAL; + } + + /* allocate */ + musb = allocate_instance(dev, ctrl); + if (!musb) + return -ENOMEM; + + spin_lock_init(&musb->lock); + musb->board_mode = plat->mode; + musb->board_set_power = plat->set_power; + musb->set_clock = plat->set_clock; + musb->min_power = plat->min_power; + + /* Clock usage is chip-specific ... functional clock (DaVinci, + * OMAP2430), or PHY ref (some TUSB6010 boards). All this core + * code does is make sure a clock handle is available; platform + * code manages it during start/stop and suspend/resume. + */ + if (plat->clock) { + musb->clock = clk_get(dev, plat->clock); + if (IS_ERR(musb->clock)) { + status = PTR_ERR(musb->clock); + musb->clock = NULL; + goto fail; + } + } + + /* assume vbus is off */ + + /* platform adjusts musb->mregs and musb->isr if needed, + * and activates clocks + */ + musb->isr = generic_interrupt; + status = musb_platform_init(musb); + + if (status < 0) + goto fail; + if (!musb->isr) { + status = -ENODEV; + goto fail2; + } + +#ifndef CONFIG_MUSB_PIO_ONLY + if (use_dma && dev->dma_mask) { + struct dma_controller *c; + + c = dma_controller_create(musb, musb->mregs); + musb->dma_controller = c; + if (c) + (void) c->start(c); + } +#endif + /* ideally this would be abstracted in platform setup */ + if (!is_dma_capable() || !musb->dma_controller) + dev->dma_mask = NULL; + + /* be sure interrupts are disabled before connecting ISR */ + musb_platform_disable(musb); + musb_generic_disable(musb); + + /* setup musb parts of the core (especially endpoints) */ + status = musb_core_init(plat->multipoint + ? MUSB_CONTROLLER_MHDRC + : MUSB_CONTROLLER_HDRC, musb); + if (status < 0) + goto fail2; + + /* Init IRQ workqueue before request_irq */ + INIT_WORK(&musb->irq_work, musb_irq_work); + + /* attach to the IRQ */ + if (request_irq(nIrq, musb->isr, 0, dev->bus_id, musb)) { + dev_err(dev, "request_irq %d failed!\n", nIrq); + status = -ENODEV; + goto fail2; + } + musb->nIrq = nIrq; +/* FIXME this handles wakeup irqs wrong */ + if (enable_irq_wake(nIrq) == 0) + device_init_wakeup(dev, 1); + + pr_info("%s: USB %s mode controller at %p using %s, IRQ %d\n", + musb_driver_name, + ({char *s; + switch (musb->board_mode) { + case MUSB_HOST: s = "Host"; break; + case MUSB_PERIPHERAL: s = "Peripheral"; break; + default: s = "OTG"; break; + }; s; }), + ctrl, + (is_dma_capable() && musb->dma_controller) + ? "DMA" : "PIO", + musb->nIrq); + +#ifdef CONFIG_USB_MUSB_HDRC_HCD + /* host side needs more setup, except for no-host modes */ + if (musb->board_mode != MUSB_PERIPHERAL) { + struct usb_hcd *hcd = musb_to_hcd(musb); + + if (musb->board_mode == MUSB_OTG) + hcd->self.otg_port = 1; + musb->xceiv.host = &hcd->self; + hcd->power_budget = 2 * (plat->power ? : 250); + } +#endif /* CONFIG_USB_MUSB_HDRC_HCD */ + + /* For the host-only role, we can activate right away. + * (We expect the ID pin to be forcibly grounded!!) + * Otherwise, wait till the gadget driver hooks up. + */ + if (!is_otg_enabled(musb) && is_host_enabled(musb)) { + MUSB_HST_MODE(musb); + musb->xceiv.default_a = 1; + musb->xceiv.state = OTG_STATE_A_IDLE; + + status = usb_add_hcd(musb_to_hcd(musb), -1, 0); + + DBG(1, "%s mode, status %d, devctl %02x %c\n", + "HOST", status, + musb_readb(musb->mregs, MUSB_DEVCTL), + (musb_readb(musb->mregs, MUSB_DEVCTL) + & MUSB_DEVCTL_BDEVICE + ? 'B' : 'A')); + + } else /* peripheral is enabled */ { + MUSB_DEV_MODE(musb); + musb->xceiv.default_a = 0; + musb->xceiv.state = OTG_STATE_B_IDLE; + + status = musb_gadget_setup(musb); + + DBG(1, "%s mode, status %d, dev%02x\n", + is_otg_enabled(musb) ? "OTG" : "PERIPHERAL", + status, + musb_readb(musb->mregs, MUSB_DEVCTL)); + + } + + if (status == 0) + musb_debug_create("driver/musb_hdrc", musb); + else { +fail: + if (musb->clock) + clk_put(musb->clock); + device_init_wakeup(dev, 0); + musb_free(musb); + return status; + } + +#ifdef CONFIG_SYSFS + status = device_create_file(dev, &dev_attr_mode); + status = device_create_file(dev, &dev_attr_vbus); +#ifdef CONFIG_USB_GADGET_MUSB_HDRC + status = device_create_file(dev, &dev_attr_srp); +#endif /* CONFIG_USB_GADGET_MUSB_HDRC */ + status = 0; +#endif + + return status; + +fail2: + musb_platform_exit(musb); + goto fail; +} + +/*-------------------------------------------------------------------------*/ + +/* all implementations (PCI bridge to FPGA, VLYNQ, etc) should just + * bridge to a platform device; this driver then suffices. + */ + +#ifndef CONFIG_MUSB_PIO_ONLY +static u64 *orig_dma_mask; +#endif + +static int __init musb_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int irq = platform_get_irq(pdev, 0); + struct resource *iomem; + void __iomem *base; + + iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!iomem || irq == 0) + return -ENODEV; + + base = ioremap(iomem->start, iomem->end - iomem->start + 1); + if (!base) { + dev_err(dev, "ioremap failed\n"); + return -ENOMEM; + } + +#ifndef CONFIG_MUSB_PIO_ONLY + /* clobbered by use_dma=n */ + orig_dma_mask = dev->dma_mask; +#endif + return musb_init_controller(dev, irq, base); +} + +static int __devexit musb_remove(struct platform_device *pdev) +{ + struct musb *musb = dev_to_musb(&pdev->dev); + void __iomem *ctrl_base = musb->ctrl_base; + + /* this gets called on rmmod. + * - Host mode: host may still be active + * - Peripheral mode: peripheral is deactivated (or never-activated) + * - OTG mode: both roles are deactivated (or never-activated) + */ + musb_shutdown(pdev); + musb_debug_delete("driver/musb_hdrc", musb); +#ifdef CONFIG_USB_MUSB_HDRC_HCD + if (musb->board_mode == MUSB_HOST) + usb_remove_hcd(musb_to_hcd(musb)); +#endif + musb_free(musb); + iounmap(ctrl_base); + device_init_wakeup(&pdev->dev, 0); +#ifndef CONFIG_MUSB_PIO_ONLY + pdev->dev.dma_mask = orig_dma_mask; +#endif + return 0; +} + +#ifdef CONFIG_PM + +static int musb_suspend(struct platform_device *pdev, pm_message_t message) +{ + unsigned long flags; + struct musb *musb = dev_to_musb(&pdev->dev); + + if (!musb->clock) + return 0; + + spin_lock_irqsave(&musb->lock, flags); + + if (is_peripheral_active(musb)) { + /* FIXME force disconnect unless we know USB will wake + * the system up quickly enough to respond ... + */ + } else if (is_host_active(musb)) { + /* we know all the children are suspended; sometimes + * they will even be wakeup-enabled. + */ + } + + if (musb->set_clock) + musb->set_clock(musb->clock, 0); + else + clk_disable(musb->clock); + spin_unlock_irqrestore(&musb->lock, flags); + return 0; +} + +static int musb_resume(struct platform_device *pdev) +{ + unsigned long flags; + struct musb *musb = dev_to_musb(&pdev->dev); + + if (!musb->clock) + return 0; + + spin_lock_irqsave(&musb->lock, flags); + + if (musb->set_clock) + musb->set_clock(musb->clock, 1); + else + clk_enable(musb->clock); + + /* for static cmos like DaVinci, register values were preserved + * unless for some reason the whole soc powered down and we're + * not treating that as a whole-system restart (e.g. swsusp) + */ + spin_unlock_irqrestore(&musb->lock, flags); + return 0; +} + +#else +#define musb_suspend NULL +#define musb_resume NULL +#endif + +static struct platform_driver musb_driver = { + .driver = { + .name = (char *)musb_driver_name, + .bus = &platform_bus_type, + .owner = THIS_MODULE, + }, + .remove = __devexit_p(musb_remove), + .shutdown = musb_shutdown, + .suspend = musb_suspend, + .resume = musb_resume, +}; + +/*-------------------------------------------------------------------------*/ + +static int __init musb_init(void) +{ +#ifdef CONFIG_USB_MUSB_HDRC_HCD + if (usb_disabled()) + return 0; +#endif + + pr_info("%s: version " MUSB_VERSION ", " +#ifdef CONFIG_MUSB_PIO_ONLY + "pio" +#elif defined(CONFIG_USB_TI_CPPI_DMA) + "cppi-dma" +#elif defined(CONFIG_USB_INVENTRA_DMA) + "musb-dma" +#elif defined(CONFIG_USB_TUSB_OMAP_DMA) + "tusb-omap-dma" +#else + "?dma?" +#endif + ", " +#ifdef CONFIG_USB_MUSB_OTG + "otg (peripheral+host)" +#elif defined(CONFIG_USB_GADGET_MUSB_HDRC) + "peripheral" +#elif defined(CONFIG_USB_MUSB_HDRC_HCD) + "host" +#endif + ", debug=%d\n", + musb_driver_name, debug); + return platform_driver_probe(&musb_driver, musb_probe); +} + +/* make us init after usbcore and before usb + * gadget and host-side drivers start to register + */ +subsys_initcall(musb_init); + +static void __exit musb_cleanup(void) +{ + platform_driver_unregister(&musb_driver); +} +module_exit(musb_cleanup); diff --cc drivers/usb/musb/musb_core.h index c2cd5a9c486,00000000000..2e42dc2210c mode 100644,000000..100644 --- a/drivers/usb/musb/musb_core.h +++ b/drivers/usb/musb/musb_core.h @@@ -1,516 -1,0 +1,516 @@@ +/* + * MUSB OTG driver defines + * + * Copyright 2005 Mentor Graphics Corporation + * Copyright (C) 2005-2006 by Texas Instruments + * Copyright (C) 2006-2007 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * 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 AUTHORS 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. + * + */ + +#ifndef __MUSB_CORE_H__ +#define __MUSB_CORE_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct musb; +struct musb_hw_ep; +struct musb_ep; + + +#include "musb_debug.h" +#include "musb_dma.h" + +#ifdef CONFIG_USB_MUSB_SOC +/* + * Get core configuration from a header converted (by cfg_conv) + * from the Verilog config file generated by the core config utility + * + * For now we assume that header is provided along with other + * arch-specific files. Discrete chips will need a build tweak. + * So will using AHB IDs from silicon that provides them. + */ - #include ++#include +#endif + +#include "musb_io.h" +#include "musb_regs.h" + +#include "musb_gadget.h" +#include "../core/hcd.h" +#include "musb_host.h" + + + +#ifdef CONFIG_USB_MUSB_OTG + +#define is_peripheral_enabled(musb) ((musb)->board_mode != MUSB_HOST) +#define is_host_enabled(musb) ((musb)->board_mode != MUSB_PERIPHERAL) +#define is_otg_enabled(musb) ((musb)->board_mode == MUSB_OTG) + +/* NOTE: otg and peripheral-only state machines start at B_IDLE. + * OTG or host-only go to A_IDLE when ID is sensed. + */ +#define is_peripheral_active(m) (!(m)->is_host) +#define is_host_active(m) ((m)->is_host) + +#else +#define is_peripheral_enabled(musb) is_peripheral_capable() +#define is_host_enabled(musb) is_host_capable() +#define is_otg_enabled(musb) 0 + +#define is_peripheral_active(musb) is_peripheral_capable() +#define is_host_active(musb) is_host_capable() +#endif + +#if defined(CONFIG_USB_MUSB_OTG) || defined(CONFIG_USB_MUSB_PERIPHERAL) +/* for some reason, the "select USB_GADGET_MUSB_HDRC" doesn't always + * override that choice selection (often USB_GADGET_DUMMY_HCD). + */ +#ifndef CONFIG_USB_GADGET_MUSB_HDRC +#error bogus Kconfig output ... select CONFIG_USB_GADGET_MUSB_HDRC +#endif +#endif /* need MUSB gadget selection */ + + +#ifdef CONFIG_PROC_FS +#include +#define MUSB_CONFIG_PROC_FS +#endif + +/****************************** PERIPHERAL ROLE *****************************/ + +#ifdef CONFIG_USB_GADGET_MUSB_HDRC + +#define is_peripheral_capable() (1) + +extern irqreturn_t musb_g_ep0_irq(struct musb *); +extern void musb_g_tx(struct musb *, u8); +extern void musb_g_rx(struct musb *, u8); +extern void musb_g_reset(struct musb *); +extern void musb_g_suspend(struct musb *); +extern void musb_g_resume(struct musb *); +extern void musb_g_wakeup(struct musb *); +extern void musb_g_disconnect(struct musb *); + +#else + +#define is_peripheral_capable() (0) + +static inline irqreturn_t musb_g_ep0_irq(struct musb *m) { return IRQ_NONE; } +static inline void musb_g_reset(struct musb *m) {} +static inline void musb_g_suspend(struct musb *m) {} +static inline void musb_g_resume(struct musb *m) {} +static inline void musb_g_wakeup(struct musb *m) {} +static inline void musb_g_disconnect(struct musb *m) {} + +#endif + +/****************************** HOST ROLE ***********************************/ + +#ifdef CONFIG_USB_MUSB_HDRC_HCD + +#define is_host_capable() (1) + +extern irqreturn_t musb_h_ep0_irq(struct musb *); +extern void musb_host_tx(struct musb *, u8); +extern void musb_host_rx(struct musb *, u8); + +#else + +#define is_host_capable() (0) + +static inline irqreturn_t musb_h_ep0_irq(struct musb *m) { return IRQ_NONE; } +static inline void musb_host_tx(struct musb *m, u8 e) {} +static inline void musb_host_rx(struct musb *m, u8 e) {} + +#endif + + +/****************************** CONSTANTS ********************************/ + +#ifndef MUSB_C_NUM_EPS +#define MUSB_C_NUM_EPS ((u8)16) +#endif + +#ifndef MUSB_MAX_END0_PACKET +#define MUSB_MAX_END0_PACKET ((u16)MUSB_EP0_FIFOSIZE) +#endif + +/* host side ep0 states */ +enum musb_h_ep0_state { + MUSB_EP0_IDLE, + MUSB_EP0_START, /* expect ack of setup */ + MUSB_EP0_IN, /* expect IN DATA */ + MUSB_EP0_OUT, /* expect ack of OUT DATA */ + MUSB_EP0_STATUS, /* expect ack of STATUS */ +} __attribute__ ((packed)); + +/* peripheral side ep0 states */ +enum musb_g_ep0_state { + MUSB_EP0_STAGE_SETUP, /* idle, waiting for setup */ + MUSB_EP0_STAGE_TX, /* IN data */ + MUSB_EP0_STAGE_RX, /* OUT data */ + MUSB_EP0_STAGE_STATUSIN, /* (after OUT data) */ + MUSB_EP0_STAGE_STATUSOUT, /* (after IN data) */ + MUSB_EP0_STAGE_ACKWAIT, /* after zlp, before statusin */ +} __attribute__ ((packed)); + +/* OTG protocol constants */ +#define OTG_TIME_A_WAIT_VRISE 100 /* msec (max) */ +#define OTG_TIME_A_WAIT_BCON 0 /* 0=infinite; min 1000 msec */ +#define OTG_TIME_A_IDLE_BDIS 200 /* msec (min) */ + +/*************************** REGISTER ACCESS ********************************/ + +/* Endpoint registers (other than dynfifo setup) can be accessed either + * directly with the "flat" model, or after setting up an index register. + */ + +#if defined(CONFIG_ARCH_DAVINCI) || defined(CONFIG_ARCH_OMAP2430) \ + || defined(CONFIG_ARCH_OMAP3430) +/* REVISIT indexed access seemed to + * misbehave (on DaVinci) for at least peripheral IN ... + */ +#define MUSB_FLAT_REG +#endif + +/* TUSB mapping: "flat" plus ep0 special cases */ +#if defined(CONFIG_USB_TUSB6010) +#define musb_ep_select(_mbase, _epnum) \ + musb_writeb((_mbase), MUSB_INDEX, (_epnum)) +#define MUSB_EP_OFFSET MUSB_TUSB_OFFSET + +/* "flat" mapping: each endpoint has its own i/o address */ +#elif defined(MUSB_FLAT_REG) +#define musb_ep_select(_mbase, _epnum) (((void)(_mbase)), ((void)(_epnum))) +#define MUSB_EP_OFFSET MUSB_FLAT_OFFSET + +/* "indexed" mapping: INDEX register controls register bank select */ +#else +#define musb_ep_select(_mbase, _epnum) \ + musb_writeb((_mbase), MUSB_INDEX, (_epnum)) +#define MUSB_EP_OFFSET MUSB_INDEXED_OFFSET +#endif + +/****************************** FUNCTIONS ********************************/ + +#define MUSB_HST_MODE(_musb)\ + { (_musb)->is_host = true; } +#define MUSB_DEV_MODE(_musb) \ + { (_musb)->is_host = false; } + +#define test_devctl_hst_mode(_x) \ + (musb_readb((_x)->mregs, MUSB_DEVCTL)&MUSB_DEVCTL_HM) + +#define MUSB_MODE(musb) ((musb)->is_host ? "Host" : "Peripheral") + +/******************************** TYPES *************************************/ + +/* + * struct musb_hw_ep - endpoint hardware (bidirectional) + * + * Ordered slightly for better cacheline locality. + */ +struct musb_hw_ep { + struct musb *musb; + void __iomem *fifo; + void __iomem *regs; + +#ifdef CONFIG_USB_TUSB6010 + void __iomem *conf; +#endif + + /* index in musb->endpoints[] */ + u8 epnum; + + /* hardware configuration, possibly dynamic */ + bool is_shared_fifo; + bool tx_double_buffered; + bool rx_double_buffered; + u16 max_packet_sz_tx; + u16 max_packet_sz_rx; + + struct dma_channel *tx_channel; + struct dma_channel *rx_channel; + +#ifdef CONFIG_USB_TUSB6010 + /* TUSB has "asynchronous" and "synchronous" dma modes */ + dma_addr_t fifo_async; + dma_addr_t fifo_sync; + void __iomem *fifo_sync_va; +#endif + +#ifdef CONFIG_USB_MUSB_HDRC_HCD + void __iomem *target_regs; + + /* currently scheduled peripheral endpoint */ + struct musb_qh *in_qh; + struct musb_qh *out_qh; + + u8 rx_reinit; + u8 tx_reinit; +#endif + +#ifdef CONFIG_USB_GADGET_MUSB_HDRC + /* peripheral side */ + struct musb_ep ep_in; /* TX */ + struct musb_ep ep_out; /* RX */ +#endif +}; + +static inline struct usb_request *next_in_request(struct musb_hw_ep *hw_ep) +{ +#ifdef CONFIG_USB_GADGET_MUSB_HDRC + return next_request(&hw_ep->ep_in); +#else + return NULL; +#endif +} + +static inline struct usb_request *next_out_request(struct musb_hw_ep *hw_ep) +{ +#ifdef CONFIG_USB_GADGET_MUSB_HDRC + return next_request(&hw_ep->ep_out); +#else + return NULL; +#endif +} + +/* + * struct musb - Driver instance data. + */ +struct musb { + spinlock_t lock; + struct clk *clock; + irqreturn_t (*isr)(int, void *); + struct work_struct irq_work; + +/* this hub status bit is reserved by USB 2.0 and not seen by usbcore */ +#define MUSB_PORT_STAT_RESUME (1 << 31) + + u32 port1_status; + +#ifdef CONFIG_USB_MUSB_HDRC_HCD + unsigned long rh_timer; + + enum musb_h_ep0_state ep0_stage; + + /* bulk traffic normally dedicates endpoint hardware, and each + * direction has its own ring of host side endpoints. + * we try to progress the transfer at the head of each endpoint's + * queue until it completes or NAKs too much; then we try the next + * endpoint. + */ + struct musb_hw_ep *bulk_ep; + + struct list_head control; /* of musb_qh */ + struct list_head in_bulk; /* of musb_qh */ + struct list_head out_bulk; /* of musb_qh */ + struct musb_qh *periodic[32]; /* tree of interrupt+iso */ +#endif + + /* called with IRQs blocked; ON/nonzero implies starting a session, + * and waiting at least a_wait_vrise_tmout. + */ + void (*board_set_vbus)(struct musb *, int is_on); + + struct dma_controller *dma_controller; + + struct device *controller; + void __iomem *ctrl_base; + void __iomem *mregs; + +#ifdef CONFIG_USB_TUSB6010 + dma_addr_t async; + dma_addr_t sync; + void __iomem *sync_va; +#endif + + /* passed down from chip/board specific irq handlers */ + u8 int_usb; + u16 int_rx; + u16 int_tx; + + struct otg_transceiver xceiv; + + int nIrq; + + struct musb_hw_ep endpoints[MUSB_C_NUM_EPS]; +#define control_ep endpoints + +#define VBUSERR_RETRY_COUNT 3 + u16 vbuserr_retry; + u16 epmask; + u8 nr_endpoints; + + u8 board_mode; /* enum musb_mode */ + int (*board_set_power)(int state); + + int (*set_clock)(struct clk *clk, int is_active); + + u8 min_power; /* vbus for periph, in mA/2 */ + + bool is_host; + + int a_wait_bcon; /* VBUS timeout in msecs */ + unsigned long idle_timeout; /* Next timeout in jiffies */ + + /* active means connected and not suspended */ + unsigned is_active:1; + + unsigned is_multipoint:1; + unsigned ignore_disconnect:1; /* during bus resets */ + +#ifdef C_MP_TX + unsigned bulk_split:1; +#define can_bulk_split(musb,type) \ + (((type) == USB_ENDPOINT_XFER_BULK) && (musb)->bulk_split) +#else +#define can_bulk_split(musb, type) 0 +#endif + +#ifdef C_MP_RX + unsigned bulk_combine:1; +#define can_bulk_combine(musb,type) \ + (((type) == USB_ENDPOINT_XFER_BULK) && (musb)->bulk_combine) +#else +#define can_bulk_combine(musb, type) 0 +#endif + +#ifdef CONFIG_USB_GADGET_MUSB_HDRC + /* is_suspended means USB B_PERIPHERAL suspend */ + unsigned is_suspended:1; + + /* may_wakeup means remote wakeup is enabled */ + unsigned may_wakeup:1; + + /* is_self_powered is reported in device status and the + * config descriptor. is_bus_powered means B_PERIPHERAL + * draws some VBUS current; both can be true. + */ + unsigned is_self_powered:1; + unsigned is_bus_powered:1; + + unsigned set_address:1; + unsigned test_mode:1; + unsigned softconnect:1; + + u8 address; + u8 test_mode_nr; + u16 ackpend; /* ep0 */ + enum musb_g_ep0_state ep0_state; + struct usb_gadget g; /* the gadget */ + struct usb_gadget_driver *gadget_driver; /* its driver */ +#endif + +#ifdef MUSB_CONFIG_PROC_FS + struct proc_dir_entry *proc_entry; +#endif +}; + +static inline void musb_set_vbus(struct musb *musb, int is_on) +{ + musb->board_set_vbus(musb, is_on); +} + +#ifdef CONFIG_USB_GADGET_MUSB_HDRC +static inline struct musb *gadget_to_musb(struct usb_gadget *g) +{ + return container_of(g, struct musb, g); +} +#endif + + +/***************************** Glue it together *****************************/ + +extern const char musb_driver_name[]; + +extern void musb_start(struct musb *musb); +extern void musb_stop(struct musb *musb); + +extern void musb_write_fifo(struct musb_hw_ep *ep, u16 len, const u8 *src); +extern void musb_read_fifo(struct musb_hw_ep *ep, u16 len, u8 *dst); + +extern void musb_load_testpacket(struct musb *); + +extern irqreturn_t musb_interrupt(struct musb *); + +extern void musb_platform_enable(struct musb *musb); +extern void musb_platform_disable(struct musb *musb); + +extern void musb_hnp_stop(struct musb *musb); + +extern void musb_platform_set_mode(struct musb *musb, u8 musb_mode); + +#if defined(CONFIG_USB_TUSB6010) || \ + defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP34XX) +extern void musb_platform_try_idle(struct musb *musb, unsigned long timeout); +#else +#define musb_platform_try_idle(x, y) do {} while (0) +#endif + +#ifdef CONFIG_USB_TUSB6010 +extern int musb_platform_get_vbus_status(struct musb *musb); +#else +#define musb_platform_get_vbus_status(x) 0 +#endif + +extern int __init musb_platform_init(struct musb *musb); +extern int musb_platform_exit(struct musb *musb); + +/*-------------------------- ProcFS definitions ---------------------*/ + +struct proc_dir_entry; + +#if (MUSB_DEBUG > 0) && defined(MUSB_CONFIG_PROC_FS) +extern struct proc_dir_entry *musb_debug_create(char *name, struct musb *data); +extern void musb_debug_delete(char *name, struct musb *data); + +#else +static inline struct proc_dir_entry * +musb_debug_create(char *name, struct musb *data) +{ + return NULL; +} +static inline void musb_debug_delete(char *name, struct musb *data) +{ +} +#endif + +#endif /* __MUSB_CORE_H__ */ diff --cc drivers/usb/musb/musb_procfs.c index 61849414413,00000000000..22c48d084a2 mode 100644,000000..100644 --- a/drivers/usb/musb/musb_procfs.c +++ b/drivers/usb/musb/musb_procfs.c @@@ -1,830 -1,0 +1,830 @@@ +/* + * MUSB OTG driver debug support + * + * Copyright 2005 Mentor Graphics Corporation + * Copyright (C) 2005-2006 by Texas Instruments + * Copyright (C) 2006-2007 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * 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 AUTHORS 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. + * + */ + +#include +#include +#include +#include /* FIXME remove procfs writes */ - #include ++#include + +#include "musb_core.h" + +#include "davinci.h" + +#ifdef CONFIG_USB_MUSB_HDRC_HCD + +static int dump_qh(struct musb_qh *qh, char *buf, unsigned max) +{ + int count; + int tmp; + struct usb_host_endpoint *hep = qh->hep; + struct urb *urb; + + count = snprintf(buf, max, " qh %p dev%d ep%d%s max%d\n", + qh, qh->dev->devnum, qh->epnum, + ({ char *s; switch (qh->type) { + case USB_ENDPOINT_XFER_BULK: + s = "-bulk"; break; + case USB_ENDPOINT_XFER_INT: + s = "-int"; break; + case USB_ENDPOINT_XFER_CONTROL: + s = ""; break; + default: + s = "iso"; break; + }; s; }), + qh->maxpacket); + if (count <= 0) + return 0; + buf += count; + max -= count; + + list_for_each_entry(urb, &hep->urb_list, urb_list) { + tmp = snprintf(buf, max, "\t%s urb %p %d/%d\n", + usb_pipein(urb->pipe) ? "in" : "out", + urb, urb->actual_length, + urb->transfer_buffer_length); + if (tmp <= 0) + break; + tmp = min(tmp, (int)max); + count += tmp; + buf += tmp; + max -= tmp; + } + return count; +} + +static int +dump_queue(struct list_head *q, char *buf, unsigned max) +{ + int count = 0; + struct musb_qh *qh; + + list_for_each_entry(qh, q, ring) { + int tmp; + + tmp = dump_qh(qh, buf, max); + if (tmp <= 0) + break; + tmp = min(tmp, (int)max); + count += tmp; + buf += tmp; + max -= tmp; + } + return count; +} + +#endif /* HCD */ + +#ifdef CONFIG_USB_GADGET_MUSB_HDRC +static int dump_ep(struct musb_ep *ep, char *buffer, unsigned max) +{ + char *buf = buffer; + int code = 0; + void __iomem *regs = ep->hw_ep->regs; + char *mode = "1buf"; + + if (ep->is_in) { + if (ep->hw_ep->tx_double_buffered) + mode = "2buf"; + } else { + if (ep->hw_ep->rx_double_buffered) + mode = "2buf"; + } + + do { + struct usb_request *req; + + code = snprintf(buf, max, + "\n%s (hw%d): %s%s, csr %04x maxp %04x\n", + ep->name, ep->current_epnum, + mode, ep->dma ? " dma" : "", + musb_readw(regs, + (ep->is_in || !ep->current_epnum) + ? MUSB_TXCSR + : MUSB_RXCSR), + musb_readw(regs, ep->is_in + ? MUSB_TXMAXP + : MUSB_RXMAXP) + ); + if (code <= 0) + break; + code = min(code, (int) max); + buf += code; + max -= code; + + if (is_cppi_enabled() && ep->current_epnum) { + unsigned cppi = ep->current_epnum - 1; + void __iomem *base = ep->musb->ctrl_base; + unsigned off1 = cppi << 2; + void __iomem *ram = base; + char tmp[16]; + + if (ep->is_in) { + ram += DAVINCI_TXCPPI_STATERAM_OFFSET(cppi); + tmp[0] = 0; + } else { + ram += DAVINCI_RXCPPI_STATERAM_OFFSET(cppi); + snprintf(tmp, sizeof tmp, "%d left, ", + musb_readl(base, + DAVINCI_RXCPPI_BUFCNT0_REG + off1)); + } + + code = snprintf(buf, max, "%cX DMA%d: %s" + "%08x %08x, %08x %08x; " + "%08x %08x %08x .. %08x\n", + ep->is_in ? 'T' : 'R', + ep->current_epnum - 1, tmp, + musb_readl(ram, 0 * 4), + musb_readl(ram, 1 * 4), + musb_readl(ram, 2 * 4), + musb_readl(ram, 3 * 4), + musb_readl(ram, 4 * 4), + musb_readl(ram, 5 * 4), + musb_readl(ram, 6 * 4), + musb_readl(ram, 7 * 4)); + if (code <= 0) + break; + code = min(code, (int) max); + buf += code; + max -= code; + } + + if (list_empty(&ep->req_list)) { + code = snprintf(buf, max, "\t(queue empty)\n"); + if (code <= 0) + break; + code = min(code, (int) max); + buf += code; + max -= code; + break; + } + list_for_each_entry(req, &ep->req_list, list) { + code = snprintf(buf, max, "\treq %p, %s%s%d/%d\n", + req, + req->zero ? "zero, " : "", + req->short_not_ok ? "!short, " : "", + req->actual, req->length); + if (code <= 0) + break; + code = min(code, (int) max); + buf += code; + max -= code; + } + } while (0); + return buf - buffer; +} +#endif + +static int +dump_end_info(struct musb *musb, u8 epnum, char *aBuffer, unsigned max) +{ + int code = 0; + char *buf = aBuffer; + struct musb_hw_ep *hw_ep = &musb->endpoints[epnum]; + + do { + musb_ep_select(musb->mregs, epnum); +#ifdef CONFIG_USB_MUSB_HDRC_HCD + if (is_host_active(musb)) { + int dump_rx, dump_tx; + void __iomem *regs = hw_ep->regs; + + /* TEMPORARY (!) until we have a real periodic + * schedule tree ... + */ + if (!epnum) { + /* control is shared, uses RX queue + * but (mostly) shadowed tx registers + */ + dump_tx = !list_empty(&musb->control); + dump_rx = 0; + } else if (hw_ep == musb->bulk_ep) { + dump_tx = !list_empty(&musb->out_bulk); + dump_rx = !list_empty(&musb->in_bulk); + } else if (musb->periodic[epnum]) { + struct usb_host_endpoint *hep; + + hep = musb->periodic[epnum]->hep; + dump_rx = hep->desc.bEndpointAddress + & USB_ENDPOINT_DIR_MASK; + dump_tx = !dump_rx; + } else + break; + /* END TEMPORARY */ + + + if (dump_rx) { + code = snprintf(buf, max, + "\nRX%d: %s rxcsr %04x interval %02x " + "max %04x type %02x; " + "dev %d hub %d port %d" + "\n", + epnum, + hw_ep->rx_double_buffered + ? "2buf" : "1buf", + musb_readw(regs, MUSB_RXCSR), + musb_readb(regs, MUSB_RXINTERVAL), + musb_readw(regs, MUSB_RXMAXP), + musb_readb(regs, MUSB_RXTYPE), + /* FIXME: assumes multipoint */ + musb_readb(musb->mregs, + MUSB_BUSCTL_OFFSET(epnum, + MUSB_RXFUNCADDR)), + musb_readb(musb->mregs, + MUSB_BUSCTL_OFFSET(epnum, + MUSB_RXHUBADDR)), + musb_readb(musb->mregs, + MUSB_BUSCTL_OFFSET(epnum, + MUSB_RXHUBPORT)) + ); + if (code <= 0) + break; + code = min(code, (int) max); + buf += code; + max -= code; + + if (is_cppi_enabled() + && epnum + && hw_ep->rx_channel) { + unsigned cppi = epnum - 1; + unsigned off1 = cppi << 2; + void __iomem *base; + void __iomem *ram; + char tmp[16]; + + base = musb->ctrl_base; + ram = DAVINCI_RXCPPI_STATERAM_OFFSET( + cppi) + base; + snprintf(tmp, sizeof tmp, "%d left, ", + musb_readl(base, + DAVINCI_RXCPPI_BUFCNT0_REG + + off1)); + + code = snprintf(buf, max, + " rx dma%d: %s" + "%08x %08x, %08x %08x; " + "%08x %08x %08x .. %08x\n", + cppi, tmp, + musb_readl(ram, 0 * 4), + musb_readl(ram, 1 * 4), + musb_readl(ram, 2 * 4), + musb_readl(ram, 3 * 4), + musb_readl(ram, 4 * 4), + musb_readl(ram, 5 * 4), + musb_readl(ram, 6 * 4), + musb_readl(ram, 7 * 4)); + if (code <= 0) + break; + code = min(code, (int) max); + buf += code; + max -= code; + } + + if (hw_ep == musb->bulk_ep + && !list_empty( + &musb->in_bulk)) { + code = dump_queue(&musb->in_bulk, + buf, max); + if (code <= 0) + break; + code = min(code, (int) max); + buf += code; + max -= code; + } else if (musb->periodic[epnum]) { + code = dump_qh(musb->periodic[epnum], + buf, max); + if (code <= 0) + break; + code = min(code, (int) max); + buf += code; + max -= code; + } + } + + if (dump_tx) { + code = snprintf(buf, max, + "\nTX%d: %s txcsr %04x interval %02x " + "max %04x type %02x; " + "dev %d hub %d port %d" + "\n", + epnum, + hw_ep->tx_double_buffered + ? "2buf" : "1buf", + musb_readw(regs, MUSB_TXCSR), + musb_readb(regs, MUSB_TXINTERVAL), + musb_readw(regs, MUSB_TXMAXP), + musb_readb(regs, MUSB_TXTYPE), + /* FIXME: assumes multipoint */ + musb_readb(musb->mregs, + MUSB_BUSCTL_OFFSET(epnum, + MUSB_TXFUNCADDR)), + musb_readb(musb->mregs, + MUSB_BUSCTL_OFFSET(epnum, + MUSB_TXHUBADDR)), + musb_readb(musb->mregs, + MUSB_BUSCTL_OFFSET(epnum, + MUSB_TXHUBPORT)) + ); + if (code <= 0) + break; + code = min(code, (int) max); + buf += code; + max -= code; + + if (is_cppi_enabled() + && epnum + && hw_ep->tx_channel) { + unsigned cppi = epnum - 1; + void __iomem *base; + void __iomem *ram; + + base = musb->ctrl_base; + ram = DAVINCI_RXCPPI_STATERAM_OFFSET( + cppi) + base; + code = snprintf(buf, max, + " tx dma%d: " + "%08x %08x, %08x %08x; " + "%08x %08x %08x .. %08x\n", + cppi, + musb_readl(ram, 0 * 4), + musb_readl(ram, 1 * 4), + musb_readl(ram, 2 * 4), + musb_readl(ram, 3 * 4), + musb_readl(ram, 4 * 4), + musb_readl(ram, 5 * 4), + musb_readl(ram, 6 * 4), + musb_readl(ram, 7 * 4)); + if (code <= 0) + break; + code = min(code, (int) max); + buf += code; + max -= code; + } + + if (hw_ep == musb->control_ep + && !list_empty( + &musb->control)) { + code = dump_queue(&musb->control, + buf, max); + if (code <= 0) + break; + code = min(code, (int) max); + buf += code; + max -= code; + } else if (hw_ep == musb->bulk_ep + && !list_empty( + &musb->out_bulk)) { + code = dump_queue(&musb->out_bulk, + buf, max); + if (code <= 0) + break; + code = min(code, (int) max); + buf += code; + max -= code; + } else if (musb->periodic[epnum]) { + code = dump_qh(musb->periodic[epnum], + buf, max); + if (code <= 0) + break; + code = min(code, (int) max); + buf += code; + max -= code; + } + } + } +#endif +#ifdef CONFIG_USB_GADGET_MUSB_HDRC + if (is_peripheral_active(musb)) { + code = 0; + + if (hw_ep->ep_in.desc || !epnum) { + code = dump_ep(&hw_ep->ep_in, buf, max); + if (code <= 0) + break; + code = min(code, (int) max); + buf += code; + max -= code; + } + if (hw_ep->ep_out.desc) { + code = dump_ep(&hw_ep->ep_out, buf, max); + if (code <= 0) + break; + code = min(code, (int) max); + buf += code; + max -= code; + } + } +#endif + } while (0); + + return buf - aBuffer; +} + +/* Dump the current status and compile options. + * @param musb the device driver instance + * @param buffer where to dump the status; it must be big enough to hold the + * result otherwise "BAD THINGS HAPPENS(TM)". + */ +static int dump_header_stats(struct musb *musb, char *buffer) +{ + int code, count = 0; + const void __iomem *mbase = musb->mregs; + + *buffer = 0; + count = sprintf(buffer, "Status: %sHDRC, Mode=%s " + "(Power=%02x, DevCtl=%02x)\n", + (musb->is_multipoint ? "M" : ""), MUSB_MODE(musb), + musb_readb(mbase, MUSB_POWER), + musb_readb(mbase, MUSB_DEVCTL)); + if (count <= 0) + return 0; + buffer += count; + + code = sprintf(buffer, "OTG state: %s; %sactive\n", + otg_state_string(musb), + musb->is_active ? "" : "in"); + if (code <= 0) + goto done; + buffer += code; + count += code; + + code = sprintf(buffer, + "Options: " +#ifdef CONFIG_MUSB_PIO_ONLY + "pio" +#elif defined(CONFIG_USB_TI_CPPI_DMA) + "cppi-dma" +#elif defined(CONFIG_USB_INVENTRA_DMA) + "musb-dma" +#elif defined(CONFIG_USB_TUSB_OMAP_DMA) + "tusb-omap-dma" +#else + "?dma?" +#endif + ", " +#ifdef CONFIG_USB_MUSB_OTG + "otg (peripheral+host)" +#elif defined(CONFIG_USB_GADGET_MUSB_HDRC) + "peripheral" +#elif defined(CONFIG_USB_MUSB_HDRC_HCD) + "host" +#endif + ", debug=%d [eps=%d]\n", + debug, + musb->nr_endpoints); + if (code <= 0) + goto done; + count += code; + buffer += code; + +#ifdef CONFIG_USB_GADGET_MUSB_HDRC + code = sprintf(buffer, "Peripheral address: %02x\n", + musb_readb(musb->ctrl_base, MUSB_FADDR)); + if (code <= 0) + goto done; + buffer += code; + count += code; +#endif + +#ifdef CONFIG_USB_MUSB_HDRC_HCD + code = sprintf(buffer, "Root port status: %08x\n", + musb->port1_status); + if (code <= 0) + goto done; + buffer += code; + count += code; +#endif + +#ifdef CONFIG_ARCH_DAVINCI + code = sprintf(buffer, + "DaVinci: ctrl=%02x stat=%1x phy=%03x\n" + "\trndis=%05x auto=%04x intsrc=%08x intmsk=%08x" + "\n", + musb_readl(musb->ctrl_base, DAVINCI_USB_CTRL_REG), + musb_readl(musb->ctrl_base, DAVINCI_USB_STAT_REG), + __raw_readl((void __force __iomem *) + IO_ADDRESS(USBPHY_CTL_PADDR)), + musb_readl(musb->ctrl_base, DAVINCI_RNDIS_REG), + musb_readl(musb->ctrl_base, DAVINCI_AUTOREQ_REG), + musb_readl(musb->ctrl_base, + DAVINCI_USB_INT_SOURCE_REG), + musb_readl(musb->ctrl_base, + DAVINCI_USB_INT_MASK_REG)); + if (code <= 0) + goto done; + count += code; + buffer += code; +#endif /* DAVINCI */ + +#ifdef CONFIG_USB_TUSB6010 + code = sprintf(buffer, + "TUSB6010: devconf %08x, phy enable %08x drive %08x" + "\n\totg %03x timer %08x" + "\n\tprcm conf %08x mgmt %08x; int src %08x mask %08x" + "\n", + musb_readl(musb->ctrl_base, TUSB_DEV_CONF), + musb_readl(musb->ctrl_base, TUSB_PHY_OTG_CTRL_ENABLE), + musb_readl(musb->ctrl_base, TUSB_PHY_OTG_CTRL), + musb_readl(musb->ctrl_base, TUSB_DEV_OTG_STAT), + musb_readl(musb->ctrl_base, TUSB_DEV_OTG_TIMER), + musb_readl(musb->ctrl_base, TUSB_PRCM_CONF), + musb_readl(musb->ctrl_base, TUSB_PRCM_MNGMT), + musb_readl(musb->ctrl_base, TUSB_INT_SRC), + musb_readl(musb->ctrl_base, TUSB_INT_MASK)); + if (code <= 0) + goto done; + count += code; + buffer += code; +#endif /* DAVINCI */ + + if (is_cppi_enabled() && musb->dma_controller) { + code = sprintf(buffer, + "CPPI: txcr=%d txsrc=%01x txena=%01x; " + "rxcr=%d rxsrc=%01x rxena=%01x " + "\n", + musb_readl(musb->ctrl_base, + DAVINCI_TXCPPI_CTRL_REG), + musb_readl(musb->ctrl_base, + DAVINCI_TXCPPI_RAW_REG), + musb_readl(musb->ctrl_base, + DAVINCI_TXCPPI_INTENAB_REG), + musb_readl(musb->ctrl_base, + DAVINCI_RXCPPI_CTRL_REG), + musb_readl(musb->ctrl_base, + DAVINCI_RXCPPI_RAW_REG), + musb_readl(musb->ctrl_base, + DAVINCI_RXCPPI_INTENAB_REG)); + if (code <= 0) + goto done; + count += code; + buffer += code; + } + +#ifdef CONFIG_USB_GADGET_MUSB_HDRC + if (is_peripheral_enabled(musb)) { + code = sprintf(buffer, "Gadget driver: %s\n", + musb->gadget_driver + ? musb->gadget_driver->driver.name + : "(none)"); + if (code <= 0) + goto done; + count += code; + buffer += code; + } +#endif + +done: + return count; +} + +/* Write to ProcFS + * + * C soft-connect + * c soft-disconnect + * I enable HS + * i disable HS + * s stop session + * F force session (OTG-unfriendly) + * E rElinquish bus (OTG) + * H request host mode + * h cancel host request + * T start sending TEST_PACKET + * D set/query the debug level + */ +static int musb_proc_write(struct file *file, const char __user *buffer, + unsigned long count, void *data) +{ + char cmd; + u8 reg; + struct musb *musb = (struct musb *)data; + void __iomem *mbase = musb->mregs; + + /* MOD_INC_USE_COUNT; */ + + if (unlikely(copy_from_user(&cmd, buffer, 1))) + return -EFAULT; + + switch (cmd) { + case 'C': + if (mbase) { + reg = musb_readb(mbase, MUSB_POWER) + | MUSB_POWER_SOFTCONN; + musb_writeb(mbase, MUSB_POWER, reg); + } + break; + + case 'c': + if (mbase) { + reg = musb_readb(mbase, MUSB_POWER) + & ~MUSB_POWER_SOFTCONN; + musb_writeb(mbase, MUSB_POWER, reg); + } + break; + + case 'I': + if (mbase) { + reg = musb_readb(mbase, MUSB_POWER) + | MUSB_POWER_HSENAB; + musb_writeb(mbase, MUSB_POWER, reg); + } + break; + + case 'i': + if (mbase) { + reg = musb_readb(mbase, MUSB_POWER) + & ~MUSB_POWER_HSENAB; + musb_writeb(mbase, MUSB_POWER, reg); + } + break; + + case 'F': + reg = musb_readb(mbase, MUSB_DEVCTL); + reg |= MUSB_DEVCTL_SESSION; + musb_writeb(mbase, MUSB_DEVCTL, reg); + break; + + case 'H': + if (mbase) { + reg = musb_readb(mbase, MUSB_DEVCTL); + reg |= MUSB_DEVCTL_HR; + musb_writeb(mbase, MUSB_DEVCTL, reg); + /* MUSB_HST_MODE( ((struct musb*)data) ); */ + /* WARNING("Host Mode\n"); */ + } + break; + + case 'h': + if (mbase) { + reg = musb_readb(mbase, MUSB_DEVCTL); + reg &= ~MUSB_DEVCTL_HR; + musb_writeb(mbase, MUSB_DEVCTL, reg); + } + break; + + case 'T': + if (mbase) { + musb_load_testpacket(musb); + musb_writeb(mbase, MUSB_TESTMODE, + MUSB_TEST_PACKET); + } + break; + +#if (MUSB_DEBUG > 0) + /* set/read debug level */ + case 'D':{ + if (count > 1) { + char digits[8], *p = digits; + int i = 0, level = 0, sign = 1; + int len = min(count - 1, (unsigned long)8); + + if (copy_from_user(&digits, &buffer[1], len)) + return -EFAULT; + + /* optional sign */ + if (*p == '-') { + len -= 1; + sign = -sign; + p++; + } + + /* read it */ + while (i++ < len && *p > '0' && *p < '9') { + level = level * 10 + (*p - '0'); + p++; + } + + level *= sign; + DBG(1, "debug level %d\n", level); + debug = level; + } + } + break; + + + case '?': + INFO("?: you are seeing it\n"); + INFO("C/c: soft connect enable/disable\n"); + INFO("I/i: hispeed enable/disable\n"); + INFO("F: force session start\n"); + INFO("H: host mode\n"); + INFO("T: start sending TEST_PACKET\n"); + INFO("D: set/read dbug level\n"); + break; +#endif + + default: + ERR("Command %c not implemented\n", cmd); + break; + } + + musb_platform_try_idle(musb, 0); + + return count; +} + +static int musb_proc_read(char *page, char **start, + off_t off, int count, int *eof, void *data) +{ + char *buffer = page; + int code = 0; + unsigned long flags; + struct musb *musb = data; + unsigned epnum; + + count -= off; + count -= 1; /* for NUL at end */ + if (count <= 0) + return -EINVAL; + + spin_lock_irqsave(&musb->lock, flags); + + code = dump_header_stats(musb, buffer); + if (code > 0) { + buffer += code; + count -= code; + } + + /* generate the report for the end points */ + /* REVISIT ... not unless something's connected! */ + for (epnum = 0; count >= 0 && epnum < musb->nr_endpoints; + epnum++) { + code = dump_end_info(musb, epnum, buffer, count); + if (code > 0) { + buffer += code; + count -= code; + } + } + + musb_platform_try_idle(musb, 0); + + spin_unlock_irqrestore(&musb->lock, flags); + *eof = 1; + + return buffer - page; +} + +void __devexit musb_debug_delete(char *name, struct musb *musb) +{ + if (musb->proc_entry) + remove_proc_entry(name, NULL); +} + +struct proc_dir_entry *__init +musb_debug_create(char *name, struct musb *data) +{ + struct proc_dir_entry *pde; + + /* FIXME convert everything to seq_file; then later, debugfs */ + + if (!name) + return NULL; + + data->proc_entry = pde = create_proc_entry(name, + S_IFREG | S_IRUGO | S_IWUSR, NULL); + if (pde) { + pde->data = data; + /* pde->owner = THIS_MODULE; */ + + pde->read_proc = musb_proc_read; + pde->write_proc = musb_proc_write; + + pde->size = 0; + + pr_debug("Registered /proc/%s\n", name); + } else { + pr_debug("Cannot create a valid proc file entry"); + } + + return pde; +} diff --cc drivers/usb/musb/omap2430.c index 472c30403c8,00000000000..ee010eca9a1 mode 100644,000000..100644 --- a/drivers/usb/musb/omap2430.c +++ b/drivers/usb/musb/omap2430.c @@@ -1,326 -1,0 +1,326 @@@ +/* + * Copyright (C) 2005-2007 by Texas Instruments + * Some code has been taken from tusb6010.c + * Copyrights for that are attributable to: + * Copyright (C) 2006 Nokia Corporation + * Jarkko Nikula + * Tony Lindgren + * + * This file is part of the Inventra Controller Driver for Linux. + * + * The Inventra Controller Driver for Linux 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. + * + * The Inventra Controller Driver for Linux 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 The Inventra Controller Driver for Linux ; if not, + * write to the Free Software Foundation, Inc., 59 Temple Place, + * Suite 330, Boston, MA 02111-1307 USA + * + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include - #include - #include ++#include ++#include + +#include "musb_core.h" +#include "omap2430.h" + +#ifdef CONFIG_ARCH_OMAP3430 +#define get_cpu_rev() 2 +#endif + +#define MUSB_TIMEOUT_A_WAIT_BCON 1100 + +static struct timer_list musb_idle_timer; + +static void musb_do_idle(unsigned long _musb) +{ + struct musb *musb = (void *)_musb; + unsigned long flags; + u8 power; + u8 devctl; + + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + + spin_lock_irqsave(&musb->lock, flags); + + switch (musb->xceiv.state) { + case OTG_STATE_A_WAIT_BCON: + devctl &= ~MUSB_DEVCTL_SESSION; + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); + + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + if (devctl & MUSB_DEVCTL_BDEVICE) { + musb->xceiv.state = OTG_STATE_B_IDLE; + MUSB_DEV_MODE(musb); + } else { + musb->xceiv.state = OTG_STATE_A_IDLE; + MUSB_HST_MODE(musb); + } + break; +#ifdef CONFIG_USB_MUSB_HDRC_HCD + case OTG_STATE_A_SUSPEND: + /* finish RESUME signaling? */ + if (musb->port1_status & MUSB_PORT_STAT_RESUME) { + power = musb_readb(musb->mregs, MUSB_POWER); + power &= ~MUSB_POWER_RESUME; + DBG(1, "root port resume stopped, power %02x\n", power); + musb_writeb(musb->mregs, MUSB_POWER, power); + musb->is_active = 1; + musb->port1_status &= ~(USB_PORT_STAT_SUSPEND + | MUSB_PORT_STAT_RESUME); + musb->port1_status |= USB_PORT_STAT_C_SUSPEND << 16; + usb_hcd_poll_rh_status(musb_to_hcd(musb)); + /* NOTE: it might really be A_WAIT_BCON ... */ + musb->xceiv.state = OTG_STATE_A_HOST; + } + break; +#endif +#ifdef CONFIG_USB_MUSB_HDRC_HCD + case OTG_STATE_A_HOST: + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + if (devctl & MUSB_DEVCTL_BDEVICE) + musb->xceiv.state = OTG_STATE_B_IDLE; + else + musb->xceiv.state = OTG_STATE_A_WAIT_BCON; +#endif + default: + break; + } + spin_unlock_irqrestore(&musb->lock, flags); +} + + +void musb_platform_try_idle(struct musb *musb, unsigned long timeout) +{ + unsigned long default_timeout = jiffies + msecs_to_jiffies(3); + static unsigned long last_timer; + + if (timeout == 0) + timeout = default_timeout; + + /* Never idle if active, or when VBUS timeout is not set as host */ + if (musb->is_active || ((musb->a_wait_bcon == 0) + && (musb->xceiv.state == OTG_STATE_A_WAIT_BCON))) { + DBG(4, "%s active, deleting timer\n", otg_state_string(musb)); + del_timer(&musb_idle_timer); + last_timer = jiffies; + return; + } + + if (time_after(last_timer, timeout)) { + if (!timer_pending(&musb_idle_timer)) + last_timer = timeout; + else { + DBG(4, "Longer idle timer already pending, ignoring\n"); + return; + } + } + last_timer = timeout; + + DBG(4, "%s inactive, for idle timer for %lu ms\n", + otg_state_string(musb), + (unsigned long)jiffies_to_msecs(timeout - jiffies)); + mod_timer(&musb_idle_timer, timeout); +} + +void musb_platform_enable(struct musb *musb) +{ +} +void musb_platform_disable(struct musb *musb) +{ +} +static void omap_vbus_power(struct musb *musb, int is_on, int sleeping) +{ +} + +static void omap_set_vbus(struct musb *musb, int is_on) +{ + u8 devctl; + /* HDRC controls CPEN, but beware current surges during device + * connect. They can trigger transient overcurrent conditions + * that must be ignored. + */ + + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + + if (is_on) { + musb->is_active = 1; + musb->xceiv.default_a = 1; + musb->xceiv.state = OTG_STATE_A_WAIT_VRISE; + devctl |= MUSB_DEVCTL_SESSION; + + MUSB_HST_MODE(musb); + } else { + musb->is_active = 0; + + /* NOTE: we're skipping A_WAIT_VFALL -> A_IDLE and + * jumping right to B_IDLE... + */ + + musb->xceiv.default_a = 0; + musb->xceiv.state = OTG_STATE_B_IDLE; + devctl &= ~MUSB_DEVCTL_SESSION; + + MUSB_DEV_MODE(musb); + } + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); + + DBG(1, "VBUS %s, devctl %02x " + /* otg %3x conf %08x prcm %08x */ "\n", + otg_state_string(musb), + musb_readb(musb->mregs, MUSB_DEVCTL)); +} +static int omap_set_power(struct otg_transceiver *x, unsigned mA) +{ + return 0; +} + +int musb_platform_resume(struct musb *musb); + +void musb_platform_set_mode(struct musb *musb, u8 musb_mode) +{ + u8 devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + + devctl |= MUSB_DEVCTL_SESSION; + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); + + switch (musb_mode) { + case MUSB_HOST: + otg_set_host(&musb->xceiv, musb->xceiv.host); + break; + case MUSB_PERIPHERAL: + otg_set_peripheral(&musb->xceiv, musb->xceiv.gadget); + break; + case MUSB_OTG: + break; + } +} + +int __init musb_platform_init(struct musb *musb) +{ + struct otg_transceiver *xceiv = otg_get_transceiver(); + u32 l; + +#if defined(CONFIG_ARCH_OMAP2430) + omap_cfg_reg(AE5_2430_USB0HS_STP); +#endif + + musb->xceiv = *xceiv; + musb_platform_resume(musb); + + l = omap_readl(OTG_SYSCONFIG); + l &= ~ENABLEWAKEUP; /* disable wakeup */ + l &= ~NOSTDBY; /* remove possible nostdby */ + l |= SMARTSTDBY; /* enable smart standby */ + l &= ~AUTOIDLE; /* disable auto idle */ + l &= ~NOIDLE; /* remove possible noidle */ + l |= SMARTIDLE; /* enable smart idle */ + l |= AUTOIDLE; /* enable auto idle */ + omap_writel(l, OTG_SYSCONFIG); + + l = omap_readl(OTG_INTERFSEL); + l |= ULPI_12PIN; + omap_writel(l, OTG_INTERFSEL); + + pr_debug("HS USB OTG: revision 0x%x, sysconfig 0x%02x, " + "sysstatus 0x%x, intrfsel 0x%x, simenable 0x%x\n", + omap_readl(OTG_REVISION), omap_readl(OTG_SYSCONFIG), + omap_readl(OTG_SYSSTATUS), omap_readl(OTG_INTERFSEL), + omap_readl(OTG_SIMENABLE)); + + omap_vbus_power(musb, musb->board_mode == MUSB_HOST, 1); + + if (is_host_enabled(musb)) + musb->board_set_vbus = omap_set_vbus; + if (is_peripheral_enabled(musb)) + musb->xceiv.set_power = omap_set_power; + musb->a_wait_bcon = MUSB_TIMEOUT_A_WAIT_BCON; + + setup_timer(&musb_idle_timer, musb_do_idle, (unsigned long) musb); + + return 0; +} + +int musb_platform_suspend(struct musb *musb) +{ + u32 l; + + if (!musb->clock) + return 0; + + /* in any role */ + l = omap_readl(OTG_FORCESTDBY); + l |= ENABLEFORCE; /* enable MSTANDBY */ + omap_writel(l, OTG_FORCESTDBY); + + l = omap_readl(OTG_SYSCONFIG); + l |= ENABLEWAKEUP; /* enable wakeup */ + omap_writel(l, OTG_SYSCONFIG); + + if (musb->xceiv.set_suspend) + musb->xceiv.set_suspend(&musb->xceiv, 1); + + if (musb->set_clock) + musb->set_clock(musb->clock, 0); + else + clk_disable(musb->clock); + + return 0; +} + +int musb_platform_resume(struct musb *musb) +{ + u32 l; + + if (!musb->clock) + return 0; + + if (musb->xceiv.set_suspend) + musb->xceiv.set_suspend(&musb->xceiv, 0); + + if (musb->set_clock) + musb->set_clock(musb->clock, 1); + else + clk_enable(musb->clock); + + l = omap_readl(OTG_SYSCONFIG); + l &= ~ENABLEWAKEUP; /* disable wakeup */ + omap_writel(l, OTG_SYSCONFIG); + + l = omap_readl(OTG_FORCESTDBY); + l &= ~ENABLEFORCE; /* disable MSTANDBY */ + omap_writel(l, OTG_FORCESTDBY); + + return 0; +} + + +int musb_platform_exit(struct musb *musb) +{ + + omap_vbus_power(musb, 0 /*off*/, 1); + + musb_platform_suspend(musb); + + clk_put(musb->clock); + musb->clock = 0; + + return 0; +} diff --cc drivers/usb/musb/omap2430.h index 786a62071f7,00000000000..dc7670718cd mode 100644,000000..100644 --- a/drivers/usb/musb/omap2430.h +++ b/drivers/usb/musb/omap2430.h @@@ -1,56 -1,0 +1,56 @@@ +/* + * Copyright (C) 2005-2006 by Texas Instruments + * + * The Inventra Controller Driver for Linux is free software; you + * can redistribute it and/or modify it under the terms of the GNU + * General Public License version 2 as published by the Free Software + * Foundation. + */ + +#ifndef __MUSB_OMAP243X_H__ +#define __MUSB_OMAP243X_H__ + +#if defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP3430) - #include - #include ++#include ++#include + +/* + * OMAP2430-specific definitions + */ + +#define MENTOR_BASE_OFFSET 0 +#if defined(CONFIG_ARCH_OMAP2430) +#define OMAP_HSOTG_BASE (OMAP243X_HS_BASE) +#elif defined(CONFIG_ARCH_OMAP3430) +#define OMAP_HSOTG_BASE (OMAP34XX_HSUSB_OTG_BASE) +#endif +#define OMAP_HSOTG(offset) (OMAP_HSOTG_BASE + 0x400 + (offset)) +#define OTG_REVISION OMAP_HSOTG(0x0) +#define OTG_SYSCONFIG OMAP_HSOTG(0x4) +# define MIDLEMODE 12 /* bit position */ +# define FORCESTDBY (0 << MIDLEMODE) +# define NOSTDBY (1 << MIDLEMODE) +# define SMARTSTDBY (2 << MIDLEMODE) +# define SIDLEMODE 3 /* bit position */ +# define FORCEIDLE (0 << SIDLEMODE) +# define NOIDLE (1 << SIDLEMODE) +# define SMARTIDLE (2 << SIDLEMODE) +# define ENABLEWAKEUP (1 << 2) +# define SOFTRST (1 << 1) +# define AUTOIDLE (1 << 0) +#define OTG_SYSSTATUS OMAP_HSOTG(0x8) +# define RESETDONE (1 << 0) +#define OTG_INTERFSEL OMAP_HSOTG(0xc) +# define EXTCP (1 << 2) +# define PHYSEL 0 /* bit position */ +# define UTMI_8BIT (0 << PHYSEL) +# define ULPI_12PIN (1 << PHYSEL) +# define ULPI_8PIN (2 << PHYSEL) +#define OTG_SIMENABLE OMAP_HSOTG(0x10) +# define TM1 (1 << 0) +#define OTG_FORCESTDBY OMAP_HSOTG(0x14) +# define ENABLEFORCE (1 << 0) + +#endif /* CONFIG_ARCH_OMAP2430 */ + +#endif /* __MUSB_OMAP243X_H__ */ diff --cc drivers/usb/musb/tusb6010_omap.c index f7a3ffe9bef,00000000000..1020d464ece mode 100644,000000..100644 --- a/drivers/usb/musb/tusb6010_omap.c +++ b/drivers/usb/musb/tusb6010_omap.c @@@ -1,719 -1,0 +1,719 @@@ +/* + * TUSB6010 USB 2.0 OTG Dual Role controller OMAP DMA interface + * + * Copyright (C) 2006 Nokia Corporation + * Tony Lindgren + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include - #include - #include ++#include ++#include + +#include "musb_core.h" + +#define to_chdat(c) (struct tusb_omap_dma_ch *)(c)->private_data + +#define MAX_DMAREQ 5 /* REVISIT: Really 6, but req5 not OK */ + +struct tusb_omap_dma_ch { + struct musb *musb; + void __iomem *tbase; + unsigned long phys_offset; + int epnum; + u8 tx; + struct musb_hw_ep *hw_ep; + + int ch; + s8 dmareq; + s8 sync_dev; + + struct tusb_omap_dma *tusb_dma; + + void __iomem *dma_addr; + + u32 len; + u16 packet_sz; + u16 transfer_packet_sz; + u32 transfer_len; + u32 completed_len; +}; + +struct tusb_omap_dma { + struct dma_controller controller; + struct musb *musb; + void __iomem *tbase; + + int ch; + s8 dmareq; + s8 sync_dev; + unsigned multichannel:1; +}; + +static int tusb_omap_dma_start(struct dma_controller *c) +{ + struct tusb_omap_dma *tusb_dma; + + tusb_dma = container_of(c, struct tusb_omap_dma, controller); + + /* DBG(3, "ep%i ch: %i\n", chdat->epnum, chdat->ch); */ + + return 0; +} + +static int tusb_omap_dma_stop(struct dma_controller *c) +{ + struct tusb_omap_dma *tusb_dma; + + tusb_dma = container_of(c, struct tusb_omap_dma, controller); + + /* DBG(3, "ep%i ch: %i\n", chdat->epnum, chdat->ch); */ + + return 0; +} + +/* + * Allocate dmareq0 to the current channel unless it's already taken + */ +static inline int tusb_omap_use_shared_dmareq(struct tusb_omap_dma_ch *chdat) +{ + u32 reg = musb_readl(chdat->tbase, TUSB_DMA_EP_MAP); + + if (reg != 0) { + DBG(3, "ep%i dmareq0 is busy for ep%i\n", + chdat->epnum, reg & 0xf); + return -EAGAIN; + } + + if (chdat->tx) + reg = (1 << 4) | chdat->epnum; + else + reg = chdat->epnum; + + musb_writel(chdat->tbase, TUSB_DMA_EP_MAP, reg); + + return 0; +} + +static inline void tusb_omap_free_shared_dmareq(struct tusb_omap_dma_ch *chdat) +{ + u32 reg = musb_readl(chdat->tbase, TUSB_DMA_EP_MAP); + + if ((reg & 0xf) != chdat->epnum) { + printk(KERN_ERR "ep%i trying to release dmareq0 for ep%i\n", + chdat->epnum, reg & 0xf); + return; + } + musb_writel(chdat->tbase, TUSB_DMA_EP_MAP, 0); +} + +/* + * See also musb_dma_completion in plat_uds.c and musb_g_[tx|rx]() in + * musb_gadget.c. + */ +static void tusb_omap_dma_cb(int lch, u16 ch_status, void *data) +{ + struct dma_channel *channel = (struct dma_channel *)data; + struct tusb_omap_dma_ch *chdat = to_chdat(channel); + struct tusb_omap_dma *tusb_dma = chdat->tusb_dma; + struct musb *musb = chdat->musb; + struct musb_hw_ep *hw_ep = chdat->hw_ep; + void __iomem *ep_conf = hw_ep->conf; + void __iomem *mbase = musb->mregs; + unsigned long remaining, flags, pio; + int ch; + + spin_lock_irqsave(&musb->lock, flags); + + if (tusb_dma->multichannel) + ch = chdat->ch; + else + ch = tusb_dma->ch; + + if (ch_status != OMAP_DMA_BLOCK_IRQ) + printk(KERN_ERR "TUSB DMA error status: %i\n", ch_status); + + DBG(3, "ep%i %s dma callback ch: %i status: %x\n", + chdat->epnum, chdat->tx ? "tx" : "rx", + ch, ch_status); + + if (chdat->tx) + remaining = musb_readl(ep_conf, TUSB_EP_TX_OFFSET); + else + remaining = musb_readl(ep_conf, TUSB_EP_RX_OFFSET); + + remaining = TUSB_EP_CONFIG_XFR_SIZE(remaining); + + /* HW issue #10: XFR_SIZE may get corrupt on DMA (both async & sync) */ + if (unlikely(remaining > chdat->transfer_len)) { + DBG(2, "Corrupt %s dma ch%i XFR_SIZE: 0x%08lx\n", + chdat->tx ? "tx" : "rx", chdat->ch, + remaining); + remaining = 0; + } + + channel->actual_len = chdat->transfer_len - remaining; + pio = chdat->len - channel->actual_len; + + DBG(3, "DMA remaining %lu/%u\n", remaining, chdat->transfer_len); + + /* Transfer remaining 1 - 31 bytes */ + if (pio > 0 && pio < 32) { + u8 *buf; + + DBG(3, "Using PIO for remaining %lu bytes\n", pio); + buf = phys_to_virt((u32)chdat->dma_addr) + chdat->transfer_len; + if (chdat->tx) { + dma_cache_maint(phys_to_virt((u32)chdat->dma_addr), + chdat->transfer_len, DMA_TO_DEVICE); + musb_write_fifo(hw_ep, pio, buf); + } else { + musb_read_fifo(hw_ep, pio, buf); + dma_cache_maint(phys_to_virt((u32)chdat->dma_addr), + chdat->transfer_len, DMA_FROM_DEVICE); + } + channel->actual_len += pio; + } + + if (!tusb_dma->multichannel) + tusb_omap_free_shared_dmareq(chdat); + + channel->status = MUSB_DMA_STATUS_FREE; + + /* Handle only RX callbacks here. TX callbacks must be handled based + * on the TUSB DMA status interrupt. + * REVISIT: Use both TUSB DMA status interrupt and OMAP DMA callback + * interrupt for RX and TX. + */ + if (!chdat->tx) + musb_dma_completion(musb, chdat->epnum, chdat->tx); + + /* We must terminate short tx transfers manually by setting TXPKTRDY. + * REVISIT: This same problem may occur with other MUSB dma as well. + * Easy to test with g_ether by pinging the MUSB board with ping -s54. + */ + if ((chdat->transfer_len < chdat->packet_sz) + || (chdat->transfer_len % chdat->packet_sz != 0)) { + u16 csr; + + if (chdat->tx) { + DBG(3, "terminating short tx packet\n"); + musb_ep_select(mbase, chdat->epnum); + csr = musb_readw(hw_ep->regs, MUSB_TXCSR); + csr |= MUSB_TXCSR_MODE | MUSB_TXCSR_TXPKTRDY + | MUSB_TXCSR_P_WZC_BITS; + musb_writew(hw_ep->regs, MUSB_TXCSR, csr); + } + } + + spin_unlock_irqrestore(&musb->lock, flags); +} + +static int tusb_omap_dma_program(struct dma_channel *channel, u16 packet_sz, + u8 rndis_mode, dma_addr_t dma_addr, u32 len) +{ + struct tusb_omap_dma_ch *chdat = to_chdat(channel); + struct tusb_omap_dma *tusb_dma = chdat->tusb_dma; + struct musb *musb = chdat->musb; + struct musb_hw_ep *hw_ep = chdat->hw_ep; + void __iomem *mbase = musb->mregs; + void __iomem *ep_conf = hw_ep->conf; + dma_addr_t fifo = hw_ep->fifo_sync; + struct omap_dma_channel_params dma_params; + u32 dma_remaining; + int src_burst, dst_burst; + u16 csr; + int ch; + s8 dmareq; + s8 sync_dev; + + if (unlikely(dma_addr & 0x1) || (len < 32) || (len > packet_sz)) + return false; + + /* + * HW issue #10: Async dma will eventually corrupt the XFR_SIZE + * register which will cause missed DMA interrupt. We could try to + * use a timer for the callback, but it is unsafe as the XFR_SIZE + * register is corrupt, and we won't know if the DMA worked. + */ + if (dma_addr & 0x2) + return false; + + /* + * Because of HW issue #10, it seems like mixing sync DMA and async + * PIO access can confuse the DMA. Make sure XFR_SIZE is reset before + * using the channel for DMA. + */ + if (chdat->tx) + dma_remaining = musb_readl(ep_conf, TUSB_EP_TX_OFFSET); + else + dma_remaining = musb_readl(ep_conf, TUSB_EP_RX_OFFSET); + + dma_remaining = TUSB_EP_CONFIG_XFR_SIZE(dma_remaining); + if (dma_remaining) { + DBG(2, "Busy %s dma ch%i, not using: %08x\n", + chdat->tx ? "tx" : "rx", chdat->ch, + dma_remaining); + return false; + } + + chdat->transfer_len = len & ~0x1f; + + if (len < packet_sz) + chdat->transfer_packet_sz = chdat->transfer_len; + else + chdat->transfer_packet_sz = packet_sz; + + if (tusb_dma->multichannel) { + ch = chdat->ch; + dmareq = chdat->dmareq; + sync_dev = chdat->sync_dev; + } else { + if (tusb_omap_use_shared_dmareq(chdat) != 0) { + DBG(3, "could not get dma for ep%i\n", chdat->epnum); + return false; + } + if (tusb_dma->ch < 0) { + /* REVISIT: This should get blocked earlier, happens + * with MSC ErrorRecoveryTest + */ + WARN_ON(1); + return false; + } + + ch = tusb_dma->ch; + dmareq = tusb_dma->dmareq; + sync_dev = tusb_dma->sync_dev; + omap_set_dma_callback(ch, tusb_omap_dma_cb, channel); + } + + chdat->packet_sz = packet_sz; + chdat->len = len; + channel->actual_len = 0; + chdat->dma_addr = (void __iomem *)dma_addr; + channel->status = MUSB_DMA_STATUS_BUSY; + + /* Since we're recycling dma areas, we need to clean or invalidate */ + if (chdat->tx) + dma_cache_maint(phys_to_virt(dma_addr), len, DMA_TO_DEVICE); + else + dma_cache_maint(phys_to_virt(dma_addr), len, DMA_FROM_DEVICE); + + /* Use 16-bit transfer if dma_addr is not 32-bit aligned */ + if ((dma_addr & 0x3) == 0) { + dma_params.data_type = OMAP_DMA_DATA_TYPE_S32; + dma_params.elem_count = 8; /* Elements in frame */ + } else { + dma_params.data_type = OMAP_DMA_DATA_TYPE_S16; + dma_params.elem_count = 16; /* Elements in frame */ + fifo = hw_ep->fifo_async; + } + + dma_params.frame_count = chdat->transfer_len / 32; /* Burst sz frame */ + + DBG(3, "ep%i %s dma ch%i dma: %08x len: %u(%u) packet_sz: %i(%i)\n", + chdat->epnum, chdat->tx ? "tx" : "rx", + ch, dma_addr, chdat->transfer_len, len, + chdat->transfer_packet_sz, packet_sz); + + /* + * Prepare omap DMA for transfer + */ + if (chdat->tx) { + dma_params.src_amode = OMAP_DMA_AMODE_POST_INC; + dma_params.src_start = (unsigned long)dma_addr; + dma_params.src_ei = 0; + dma_params.src_fi = 0; + + dma_params.dst_amode = OMAP_DMA_AMODE_DOUBLE_IDX; + dma_params.dst_start = (unsigned long)fifo; + dma_params.dst_ei = 1; + dma_params.dst_fi = -31; /* Loop 32 byte window */ + + dma_params.trigger = sync_dev; + dma_params.sync_mode = OMAP_DMA_SYNC_FRAME; + dma_params.src_or_dst_synch = 0; /* Dest sync */ + + src_burst = OMAP_DMA_DATA_BURST_16; /* 16x32 read */ + dst_burst = OMAP_DMA_DATA_BURST_8; /* 8x32 write */ + } else { + dma_params.src_amode = OMAP_DMA_AMODE_DOUBLE_IDX; + dma_params.src_start = (unsigned long)fifo; + dma_params.src_ei = 1; + dma_params.src_fi = -31; /* Loop 32 byte window */ + + dma_params.dst_amode = OMAP_DMA_AMODE_POST_INC; + dma_params.dst_start = (unsigned long)dma_addr; + dma_params.dst_ei = 0; + dma_params.dst_fi = 0; + + dma_params.trigger = sync_dev; + dma_params.sync_mode = OMAP_DMA_SYNC_FRAME; + dma_params.src_or_dst_synch = 1; /* Source sync */ + + src_burst = OMAP_DMA_DATA_BURST_8; /* 8x32 read */ + dst_burst = OMAP_DMA_DATA_BURST_16; /* 16x32 write */ + } + + DBG(3, "ep%i %s using %i-bit %s dma from 0x%08lx to 0x%08lx\n", + chdat->epnum, chdat->tx ? "tx" : "rx", + (dma_params.data_type == OMAP_DMA_DATA_TYPE_S32) ? 32 : 16, + ((dma_addr & 0x3) == 0) ? "sync" : "async", + dma_params.src_start, dma_params.dst_start); + + omap_set_dma_params(ch, &dma_params); + omap_set_dma_src_burst_mode(ch, src_burst); + omap_set_dma_dest_burst_mode(ch, dst_burst); + omap_set_dma_write_mode(ch, OMAP_DMA_WRITE_LAST_NON_POSTED); + + /* + * Prepare MUSB for DMA transfer + */ + if (chdat->tx) { + musb_ep_select(mbase, chdat->epnum); + csr = musb_readw(hw_ep->regs, MUSB_TXCSR); + csr |= (MUSB_TXCSR_AUTOSET | MUSB_TXCSR_DMAENAB + | MUSB_TXCSR_DMAMODE | MUSB_TXCSR_MODE); + csr &= ~MUSB_TXCSR_P_UNDERRUN; + musb_writew(hw_ep->regs, MUSB_TXCSR, csr); + } else { + musb_ep_select(mbase, chdat->epnum); + csr = musb_readw(hw_ep->regs, MUSB_RXCSR); + csr |= MUSB_RXCSR_DMAENAB; + csr &= ~(MUSB_RXCSR_AUTOCLEAR | MUSB_RXCSR_DMAMODE); + musb_writew(hw_ep->regs, MUSB_RXCSR, + csr | MUSB_RXCSR_P_WZC_BITS); + } + + /* + * Start DMA transfer + */ + omap_start_dma(ch); + + if (chdat->tx) { + /* Send transfer_packet_sz packets at a time */ + musb_writel(ep_conf, TUSB_EP_MAX_PACKET_SIZE_OFFSET, + chdat->transfer_packet_sz); + + musb_writel(ep_conf, TUSB_EP_TX_OFFSET, + TUSB_EP_CONFIG_XFR_SIZE(chdat->transfer_len)); + } else { + /* Receive transfer_packet_sz packets at a time */ + musb_writel(ep_conf, TUSB_EP_MAX_PACKET_SIZE_OFFSET, + chdat->transfer_packet_sz << 16); + + musb_writel(ep_conf, TUSB_EP_RX_OFFSET, + TUSB_EP_CONFIG_XFR_SIZE(chdat->transfer_len)); + } + + return true; +} + +static int tusb_omap_dma_abort(struct dma_channel *channel) +{ + struct tusb_omap_dma_ch *chdat = to_chdat(channel); + struct tusb_omap_dma *tusb_dma = chdat->tusb_dma; + + if (!tusb_dma->multichannel) { + if (tusb_dma->ch >= 0) { + omap_stop_dma(tusb_dma->ch); + omap_free_dma(tusb_dma->ch); + tusb_dma->ch = -1; + } + + tusb_dma->dmareq = -1; + tusb_dma->sync_dev = -1; + } + + channel->status = MUSB_DMA_STATUS_FREE; + + return 0; +} + +static inline int tusb_omap_dma_allocate_dmareq(struct tusb_omap_dma_ch *chdat) +{ + u32 reg = musb_readl(chdat->tbase, TUSB_DMA_EP_MAP); + int i, dmareq_nr = -1; + + const int sync_dev[6] = { + OMAP24XX_DMA_EXT_DMAREQ0, + OMAP24XX_DMA_EXT_DMAREQ1, + OMAP242X_DMA_EXT_DMAREQ2, + OMAP242X_DMA_EXT_DMAREQ3, + OMAP242X_DMA_EXT_DMAREQ4, + OMAP242X_DMA_EXT_DMAREQ5, + }; + + for (i = 0; i < MAX_DMAREQ; i++) { + int cur = (reg & (0xf << (i * 5))) >> (i * 5); + if (cur == 0) { + dmareq_nr = i; + break; + } + } + + if (dmareq_nr == -1) + return -EAGAIN; + + reg |= (chdat->epnum << (dmareq_nr * 5)); + if (chdat->tx) + reg |= ((1 << 4) << (dmareq_nr * 5)); + musb_writel(chdat->tbase, TUSB_DMA_EP_MAP, reg); + + chdat->dmareq = dmareq_nr; + chdat->sync_dev = sync_dev[chdat->dmareq]; + + return 0; +} + +static inline void tusb_omap_dma_free_dmareq(struct tusb_omap_dma_ch *chdat) +{ + u32 reg; + + if (!chdat || chdat->dmareq < 0) + return; + + reg = musb_readl(chdat->tbase, TUSB_DMA_EP_MAP); + reg &= ~(0x1f << (chdat->dmareq * 5)); + musb_writel(chdat->tbase, TUSB_DMA_EP_MAP, reg); + + chdat->dmareq = -1; + chdat->sync_dev = -1; +} + +static struct dma_channel *dma_channel_pool[MAX_DMAREQ]; + +static struct dma_channel * +tusb_omap_dma_allocate(struct dma_controller *c, + struct musb_hw_ep *hw_ep, + u8 tx) +{ + int ret, i; + const char *dev_name; + struct tusb_omap_dma *tusb_dma; + struct musb *musb; + void __iomem *tbase; + struct dma_channel *channel = NULL; + struct tusb_omap_dma_ch *chdat = NULL; + u32 reg; + + tusb_dma = container_of(c, struct tusb_omap_dma, controller); + musb = tusb_dma->musb; + tbase = musb->ctrl_base; + + reg = musb_readl(tbase, TUSB_DMA_INT_MASK); + if (tx) + reg &= ~(1 << hw_ep->epnum); + else + reg &= ~(1 << (hw_ep->epnum + 15)); + musb_writel(tbase, TUSB_DMA_INT_MASK, reg); + + /* REVISIT: Why does dmareq5 not work? */ + if (hw_ep->epnum == 0) { + DBG(3, "Not allowing DMA for ep0 %s\n", tx ? "tx" : "rx"); + return NULL; + } + + for (i = 0; i < MAX_DMAREQ; i++) { + struct dma_channel *ch = dma_channel_pool[i]; + if (ch->status == MUSB_DMA_STATUS_UNKNOWN) { + ch->status = MUSB_DMA_STATUS_FREE; + channel = ch; + chdat = ch->private_data; + break; + } + } + + if (!channel) + return NULL; + + if (tx) { + chdat->tx = 1; + dev_name = "TUSB transmit"; + } else { + chdat->tx = 0; + dev_name = "TUSB receive"; + } + + chdat->musb = tusb_dma->musb; + chdat->tbase = tusb_dma->tbase; + chdat->hw_ep = hw_ep; + chdat->epnum = hw_ep->epnum; + chdat->dmareq = -1; + chdat->completed_len = 0; + chdat->tusb_dma = tusb_dma; + + channel->max_len = 0x7fffffff; + channel->desired_mode = 0; + channel->actual_len = 0; + + if (tusb_dma->multichannel) { + ret = tusb_omap_dma_allocate_dmareq(chdat); + if (ret != 0) + goto free_dmareq; + + ret = omap_request_dma(chdat->sync_dev, dev_name, + tusb_omap_dma_cb, channel, &chdat->ch); + if (ret != 0) + goto free_dmareq; + } else if (tusb_dma->ch == -1) { + tusb_dma->dmareq = 0; + tusb_dma->sync_dev = OMAP24XX_DMA_EXT_DMAREQ0; + + /* Callback data gets set later in the shared dmareq case */ + ret = omap_request_dma(tusb_dma->sync_dev, "TUSB shared", + tusb_omap_dma_cb, NULL, &tusb_dma->ch); + if (ret != 0) + goto free_dmareq; + + chdat->dmareq = -1; + chdat->ch = -1; + } + + DBG(3, "ep%i %s dma: %s dma%i dmareq%i sync%i\n", + chdat->epnum, + chdat->tx ? "tx" : "rx", + chdat->ch >= 0 ? "dedicated" : "shared", + chdat->ch >= 0 ? chdat->ch : tusb_dma->ch, + chdat->dmareq >= 0 ? chdat->dmareq : tusb_dma->dmareq, + chdat->sync_dev >= 0 ? chdat->sync_dev : tusb_dma->sync_dev); + + return channel; + +free_dmareq: + tusb_omap_dma_free_dmareq(chdat); + + DBG(3, "ep%i: Could not get a DMA channel\n", chdat->epnum); + channel->status = MUSB_DMA_STATUS_UNKNOWN; + + return NULL; +} + +static void tusb_omap_dma_release(struct dma_channel *channel) +{ + struct tusb_omap_dma_ch *chdat = to_chdat(channel); + struct musb *musb = chdat->musb; + void __iomem *tbase = musb->ctrl_base; + u32 reg; + + DBG(3, "ep%i ch%i\n", chdat->epnum, chdat->ch); + + reg = musb_readl(tbase, TUSB_DMA_INT_MASK); + if (chdat->tx) + reg |= (1 << chdat->epnum); + else + reg |= (1 << (chdat->epnum + 15)); + musb_writel(tbase, TUSB_DMA_INT_MASK, reg); + + reg = musb_readl(tbase, TUSB_DMA_INT_CLEAR); + if (chdat->tx) + reg |= (1 << chdat->epnum); + else + reg |= (1 << (chdat->epnum + 15)); + musb_writel(tbase, TUSB_DMA_INT_CLEAR, reg); + + channel->status = MUSB_DMA_STATUS_UNKNOWN; + + if (chdat->ch >= 0) { + omap_stop_dma(chdat->ch); + omap_free_dma(chdat->ch); + chdat->ch = -1; + } + + if (chdat->dmareq >= 0) + tusb_omap_dma_free_dmareq(chdat); + + channel = NULL; +} + +void dma_controller_destroy(struct dma_controller *c) +{ + struct tusb_omap_dma *tusb_dma; + int i; + + tusb_dma = container_of(c, struct tusb_omap_dma, controller); + for (i = 0; i < MAX_DMAREQ; i++) { + struct dma_channel *ch = dma_channel_pool[i]; + if (ch) { + kfree(ch->private_data); + kfree(ch); + } + } + + if (!tusb_dma->multichannel && tusb_dma && tusb_dma->ch >= 0) + omap_free_dma(tusb_dma->ch); + + kfree(tusb_dma); +} + +struct dma_controller *__init +dma_controller_create(struct musb *musb, void __iomem *base) +{ + void __iomem *tbase = musb->ctrl_base; + struct tusb_omap_dma *tusb_dma; + int i; + + /* REVISIT: Get dmareq lines used from board-*.c */ + + musb_writel(musb->ctrl_base, TUSB_DMA_INT_MASK, 0x7fffffff); + musb_writel(musb->ctrl_base, TUSB_DMA_EP_MAP, 0); + + musb_writel(tbase, TUSB_DMA_REQ_CONF, + TUSB_DMA_REQ_CONF_BURST_SIZE(2) + | TUSB_DMA_REQ_CONF_DMA_REQ_EN(0x3f) + | TUSB_DMA_REQ_CONF_DMA_REQ_ASSER(2)); + + tusb_dma = kzalloc(sizeof(struct tusb_omap_dma), GFP_KERNEL); + if (!tusb_dma) + goto cleanup; + + tusb_dma->musb = musb; + tusb_dma->tbase = musb->ctrl_base; + + tusb_dma->ch = -1; + tusb_dma->dmareq = -1; + tusb_dma->sync_dev = -1; + + tusb_dma->controller.start = tusb_omap_dma_start; + tusb_dma->controller.stop = tusb_omap_dma_stop; + tusb_dma->controller.channel_alloc = tusb_omap_dma_allocate; + tusb_dma->controller.channel_release = tusb_omap_dma_release; + tusb_dma->controller.channel_program = tusb_omap_dma_program; + tusb_dma->controller.channel_abort = tusb_omap_dma_abort; + + if (tusb_get_revision(musb) >= TUSB_REV_30) + tusb_dma->multichannel = 1; + + for (i = 0; i < MAX_DMAREQ; i++) { + struct dma_channel *ch; + struct tusb_omap_dma_ch *chdat; + + ch = kzalloc(sizeof(struct dma_channel), GFP_KERNEL); + if (!ch) + goto cleanup; + + dma_channel_pool[i] = ch; + + chdat = kzalloc(sizeof(struct tusb_omap_dma_ch), GFP_KERNEL); + if (!chdat) + goto cleanup; + + ch->status = MUSB_DMA_STATUS_UNKNOWN; + ch->private_data = chdat; + } + + return &tusb_dma->controller; + +cleanup: + dma_controller_destroy(&tusb_dma->controller); + + return NULL; +} diff --cc drivers/video/backlight/omap_bl.c index 2072f28578f,00000000000..eb9b5ba38c1 mode 100644,000000..100644 --- a/drivers/video/backlight/omap_bl.c +++ b/drivers/video/backlight/omap_bl.c @@@ -1,218 -1,0 +1,218 @@@ +/* + * drivers/video/backlight/omap_bl.c + * + * Backlight driver for OMAP based boards. + * + * Copyright (c) 2006 Andrzej Zaborowski + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include + - #include - #include - #include ++#include ++#include ++#include + +#define OMAPBL_MAX_INTENSITY 0xff + +struct omap_backlight { + int powermode; + int current_intensity; + + struct device *dev; + struct omap_backlight_config *pdata; +}; + +static void inline omapbl_send_intensity(int intensity) +{ + omap_writeb(intensity, OMAP_PWL_ENABLE); +} + +static void inline omapbl_send_enable(int enable) +{ + omap_writeb(enable, OMAP_PWL_CLK_ENABLE); +} + +static void omapbl_blank(struct omap_backlight *bl, int mode) +{ + if (bl->pdata->set_power) + bl->pdata->set_power(bl->dev, mode); + + switch (mode) { + case FB_BLANK_NORMAL: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_POWERDOWN: + omapbl_send_intensity(0); + omapbl_send_enable(0); + break; + + case FB_BLANK_UNBLANK: + omapbl_send_intensity(bl->current_intensity); + omapbl_send_enable(1); + break; + } +} + +#ifdef CONFIG_PM +static int omapbl_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct backlight_device *dev = platform_get_drvdata(pdev); + struct omap_backlight *bl = dev_get_drvdata(&dev->dev); + + omapbl_blank(bl, FB_BLANK_POWERDOWN); + return 0; +} + +static int omapbl_resume(struct platform_device *pdev) +{ + struct backlight_device *dev = platform_get_drvdata(pdev); + struct omap_backlight *bl = dev_get_drvdata(&dev->dev); + + omapbl_blank(bl, bl->powermode); + return 0; +} +#else +#define omapbl_suspend NULL +#define omapbl_resume NULL +#endif + +static int omapbl_set_power(struct backlight_device *dev, int state) +{ + struct omap_backlight *bl = dev_get_drvdata(&dev->dev); + + omapbl_blank(bl, state); + bl->powermode = state; + + return 0; +} + +static int omapbl_update_status(struct backlight_device *dev) +{ + struct omap_backlight *bl = dev_get_drvdata(&dev->dev); + + if (bl->current_intensity != dev->props.brightness) { + if (dev->props.brightness < 0) + return -EPERM; /* Leave current_intensity untouched */ + + if (bl->powermode == FB_BLANK_UNBLANK) + omapbl_send_intensity(dev->props.brightness); + bl->current_intensity = dev->props.brightness; + } + + if (dev->props.fb_blank != bl->powermode) + omapbl_set_power(dev, dev->props.fb_blank); + + return 0; +} + + +static int omapbl_get_intensity(struct backlight_device *dev) +{ + struct omap_backlight *bl = dev_get_drvdata(&dev->dev); + return bl->current_intensity; +} + +static struct backlight_ops omapbl_ops = { + .get_brightness = omapbl_get_intensity, + .update_status = omapbl_update_status, +}; + + +static int omapbl_probe(struct platform_device *pdev) +{ + struct backlight_device *dev; + struct omap_backlight *bl; + struct omap_backlight_config *pdata = pdev->dev.platform_data; + + if (!pdata) + return -ENXIO; + + omapbl_ops.check_fb = pdata->check_fb; + + bl = kzalloc(sizeof(struct omap_backlight), GFP_KERNEL); + if (unlikely(!bl)) + return -ENOMEM; + + dev = backlight_device_register("omap-bl", &pdev->dev, + bl, &omapbl_ops); + if (IS_ERR(dev)) { + kfree(bl); + return PTR_ERR(dev); + } + + bl->powermode = FB_BLANK_POWERDOWN; + bl->current_intensity = 0; + + bl->pdata = pdata; + bl->dev = &pdev->dev; + + platform_set_drvdata(pdev, dev); + + omap_cfg_reg(PWL); /* Conflicts with UART3 */ + + dev->props.fb_blank = FB_BLANK_UNBLANK; + dev->props.max_brightness = OMAPBL_MAX_INTENSITY; + dev->props.brightness = pdata->default_intensity; + omapbl_update_status(dev); + + printk(KERN_INFO "OMAP LCD backlight initialised\n"); + + return 0; +} + +static int omapbl_remove(struct platform_device *pdev) +{ + struct backlight_device *dev = platform_get_drvdata(pdev); + struct omap_backlight *bl = dev_get_drvdata(&dev->dev); + + backlight_device_unregister(dev); + kfree(bl); + + return 0; +} + +static struct platform_driver omapbl_driver = { + .probe = omapbl_probe, + .remove = omapbl_remove, + .suspend = omapbl_suspend, + .resume = omapbl_resume, + .driver = { + .name = "omap-bl", + }, +}; + +static int __init omapbl_init(void) +{ + return platform_driver_register(&omapbl_driver); +} + +static void __exit omapbl_exit(void) +{ + platform_driver_unregister(&omapbl_driver); +} + +module_init(omapbl_init); +module_exit(omapbl_exit); + +MODULE_AUTHOR("Andrzej Zaborowski "); +MODULE_DESCRIPTION("OMAP LCD Backlight driver"); +MODULE_LICENSE("GPL"); diff --cc drivers/video/omap/lcd_2430sdp.c index 8ba15dceb92,00000000000..9af6cd0a963 mode 100644,000000..100644 --- a/drivers/video/omap/lcd_2430sdp.c +++ b/drivers/video/omap/lcd_2430sdp.c @@@ -1,182 -1,0 +1,182 @@@ +/* + * LCD panel support for the TI 2430SDP board + * + * Copyright (C) 2007 MontaVista + * Author: Hunyue Yau + * + * Derived from drivers/video/omap/lcd-apollon.c + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include + - #include - #include - #include ++#include ++#include ++#include +#include + +#define SDP2430_LCD_PANEL_BACKLIGHT_GPIO 91 +#define SDP2430_LCD_PANEL_ENABLE_GPIO 154 +#define SDP3430_LCD_PANEL_BACKLIGHT_GPIO 24 +#define SDP3430_LCD_PANEL_ENABLE_GPIO 28 + +static unsigned backlight_gpio; +static unsigned enable_gpio; + +#define LCD_PIXCLOCK_MAX 5400 /* freq 5.4 MHz */ +#define PM_RECEIVER TWL4030_MODULE_PM_RECEIVER +#define ENABLE_VAUX2_DEDICATED 0x09 +#define ENABLE_VAUX2_DEV_GRP 0x20 +#define ENABLE_VAUX3_DEDICATED 0x03 +#define ENABLE_VAUX3_DEV_GRP 0x20 + + +#define t2_out(c, r, v) twl4030_i2c_write_u8(c, r, v) + + +static int sdp2430_panel_init(struct lcd_panel *panel, + struct omapfb_device *fbdev) +{ + if (machine_is_omap_3430sdp()) { + enable_gpio = SDP3430_LCD_PANEL_ENABLE_GPIO; + backlight_gpio = SDP3430_LCD_PANEL_BACKLIGHT_GPIO; + } else { + enable_gpio = SDP2430_LCD_PANEL_ENABLE_GPIO; + backlight_gpio = SDP2430_LCD_PANEL_BACKLIGHT_GPIO; + } + + omap_request_gpio(enable_gpio); /* LCD panel */ + omap_request_gpio(backlight_gpio); /* LCD backlight */ + omap_set_gpio_direction(enable_gpio, 0); /* output */ + omap_set_gpio_direction(backlight_gpio, 0); /* output */ + + return 0; +} + +static void sdp2430_panel_cleanup(struct lcd_panel *panel) +{ +} + +static int sdp2430_panel_enable(struct lcd_panel *panel) +{ + u8 ded_val, ded_reg; + u8 grp_val, grp_reg; + + if (machine_is_omap_3430sdp()) { + ded_reg = TWL4030_VAUX3_DEDICATED; + ded_val = ENABLE_VAUX3_DEDICATED; + grp_reg = TWL4030_VAUX3_DEV_GRP; + grp_val = ENABLE_VAUX3_DEV_GRP; + } else { + ded_reg = TWL4030_VAUX2_DEDICATED; + ded_val = ENABLE_VAUX2_DEDICATED; + grp_reg = TWL4030_VAUX2_DEV_GRP; + grp_val = ENABLE_VAUX2_DEV_GRP; + } + + omap_set_gpio_dataout(enable_gpio, 1); + omap_set_gpio_dataout(backlight_gpio, 1); + + if (0 != t2_out(PM_RECEIVER, ded_val, ded_reg)) + return -EIO; + if (0 != t2_out(PM_RECEIVER, grp_val, grp_reg)) + return -EIO; + + return 0; +} + +static void sdp2430_panel_disable(struct lcd_panel *panel) +{ + omap_set_gpio_dataout(enable_gpio, 0); + omap_set_gpio_dataout(backlight_gpio, 0); +} + +static unsigned long sdp2430_panel_get_caps(struct lcd_panel *panel) +{ + return 0; +} + +struct lcd_panel sdp2430_panel = { + .name = "sdp2430", + .config = OMAP_LCDC_PANEL_TFT | OMAP_LCDC_INV_VSYNC | + OMAP_LCDC_INV_HSYNC, + + .bpp = 16, + .data_lines = 16, + .x_res = 240, + .y_res = 320, + .hsw = 3, /* hsync_len (4) - 1 */ + .hfp = 3, /* right_margin (4) - 1 */ + .hbp = 39, /* left_margin (40) - 1 */ + .vsw = 1, /* vsync_len (2) - 1 */ + .vfp = 2, /* lower_margin */ + .vbp = 7, /* upper_margin (8) - 1 */ + + .pixel_clock = LCD_PIXCLOCK_MAX, + + .init = sdp2430_panel_init, + .cleanup = sdp2430_panel_cleanup, + .enable = sdp2430_panel_enable, + .disable = sdp2430_panel_disable, + .get_caps = sdp2430_panel_get_caps, +}; + +static int sdp2430_panel_probe(struct platform_device *pdev) +{ + omapfb_register_panel(&sdp2430_panel); + return 0; +} + +static int sdp2430_panel_remove(struct platform_device *pdev) +{ + return 0; +} + +static int sdp2430_panel_suspend(struct platform_device *pdev, pm_message_t mesg) +{ + return 0; +} + +static int sdp2430_panel_resume(struct platform_device *pdev) +{ + return 0; +} + +struct platform_driver sdp2430_panel_driver = { + .probe = sdp2430_panel_probe, + .remove = sdp2430_panel_remove, + .suspend = sdp2430_panel_suspend, + .resume = sdp2430_panel_resume, + .driver = { + .name = "sdp2430_lcd", + .owner = THIS_MODULE, + }, +}; + +static int __init sdp2430_panel_drv_init(void) +{ + return platform_driver_register(&sdp2430_panel_driver); +} + +static void __exit sdp2430_panel_drv_exit(void) +{ + platform_driver_unregister(&sdp2430_panel_driver); +} + +module_init(sdp2430_panel_drv_init); +module_exit(sdp2430_panel_drv_exit); diff --cc drivers/video/omap/lcd_ams_delta.c index 3476689467d,00000000000..3fd5342d645 mode 100644,000000..100644 --- a/drivers/video/omap/lcd_ams_delta.c +++ b/drivers/video/omap/lcd_ams_delta.c @@@ -1,140 -1,0 +1,140 @@@ +/* + * File: drivers/video/omap/lcd_ams_delta.c + * + * Based on drivers/video/omap/lcd_inn1510.c + * + * LCD panel support for the Amstrad E3 (Delta) videophone. + * + * Copyright (C) 2006 Jonathan McDowell + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include + +#include +#include + - #include - #include - #include ++#include ++#include ++#include + +#define AMS_DELTA_DEFAULT_CONTRAST 112 + +static int ams_delta_panel_init(struct lcd_panel *panel, + struct omapfb_device *fbdev) +{ + return 0; +} + +static void ams_delta_panel_cleanup(struct lcd_panel *panel) +{ +} + +static int ams_delta_panel_enable(struct lcd_panel *panel) +{ + ams_delta_latch2_write(AMS_DELTA_LATCH2_LCD_NDISP, + AMS_DELTA_LATCH2_LCD_NDISP); + ams_delta_latch2_write(AMS_DELTA_LATCH2_LCD_VBLEN, + AMS_DELTA_LATCH2_LCD_VBLEN); + + omap_writeb(1, OMAP_PWL_CLK_ENABLE); + omap_writeb(AMS_DELTA_DEFAULT_CONTRAST, OMAP_PWL_ENABLE); + + return 0; +} + +static void ams_delta_panel_disable(struct lcd_panel *panel) +{ + ams_delta_latch2_write(AMS_DELTA_LATCH2_LCD_VBLEN, 0); + ams_delta_latch2_write(AMS_DELTA_LATCH2_LCD_NDISP, 0); +} + +static unsigned long ams_delta_panel_get_caps(struct lcd_panel *panel) +{ + return 0; +} + +static struct lcd_panel ams_delta_panel = { + .name = "ams-delta", + .config = 0, + + .bpp = 12, + .data_lines = 16, + .x_res = 480, + .y_res = 320, + .pixel_clock = 4687, + .hsw = 3, + .hfp = 1, + .hbp = 1, + .vsw = 1, + .vfp = 0, + .vbp = 0, + .pcd = 0, + .acb = 37, + + .init = ams_delta_panel_init, + .cleanup = ams_delta_panel_cleanup, + .enable = ams_delta_panel_enable, + .disable = ams_delta_panel_disable, + .get_caps = ams_delta_panel_get_caps, +}; + +static int ams_delta_panel_probe(struct platform_device *pdev) +{ + omapfb_register_panel(&ams_delta_panel); + return 0; +} + +static int ams_delta_panel_remove(struct platform_device *pdev) +{ + return 0; +} + +static int ams_delta_panel_suspend(struct platform_device *pdev, + pm_message_t mesg) +{ + return 0; +} + +static int ams_delta_panel_resume(struct platform_device *pdev) +{ + return 0; +} + +struct platform_driver ams_delta_panel_driver = { + .probe = ams_delta_panel_probe, + .remove = ams_delta_panel_remove, + .suspend = ams_delta_panel_suspend, + .resume = ams_delta_panel_resume, + .driver = { + .name = "lcd_ams_delta", + .owner = THIS_MODULE, + }, +}; + +static int ams_delta_panel_drv_init(void) +{ + return platform_driver_register(&ams_delta_panel_driver); +} + +static void ams_delta_panel_drv_cleanup(void) +{ + platform_driver_unregister(&ams_delta_panel_driver); +} + +module_init(ams_delta_panel_drv_init); +module_exit(ams_delta_panel_drv_cleanup); diff --cc drivers/video/omap/lcd_apollon.c index 179315f1a43,00000000000..beae5d9329a mode 100644,000000..100644 --- a/drivers/video/omap/lcd_apollon.c +++ b/drivers/video/omap/lcd_apollon.c @@@ -1,137 -1,0 +1,137 @@@ +/* + * LCD panel support for the Samsung OMAP2 Apollon board + * + * Copyright (C) 2005,2006 Samsung Electronics + * Author: Kyungmin Park + * + * Derived from drivers/video/omap/lcd-h4.c + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include + - #include - #include - #include ++#include ++#include ++#include + +/* #define USE_35INCH_LCD 1 */ + +static int apollon_panel_init(struct lcd_panel *panel, + struct omapfb_device *fbdev) +{ + /* configure LCD PWR_EN */ + omap_cfg_reg(M21_242X_GPIO11); + return 0; +} + +static void apollon_panel_cleanup(struct lcd_panel *panel) +{ +} + +static int apollon_panel_enable(struct lcd_panel *panel) +{ + return 0; +} + +static void apollon_panel_disable(struct lcd_panel *panel) +{ +} + +static unsigned long apollon_panel_get_caps(struct lcd_panel *panel) +{ + return 0; +} + +struct lcd_panel apollon_panel = { + .name = "apollon", + .config = OMAP_LCDC_PANEL_TFT | OMAP_LCDC_INV_VSYNC | + OMAP_LCDC_INV_HSYNC, + + .bpp = 16, + .data_lines = 18, +#ifdef USE_35INCH_LCD + .x_res = 240, + .y_res = 320, + .hsw = 2, + .hfp = 3, + .hbp = 9, + .vsw = 4, + .vfp = 3, + .vbp = 5, +#else + .x_res = 480, + .y_res = 272, + .hsw = 41, + .hfp = 2, + .hbp = 2, + .vsw = 10, + .vfp = 2, + .vbp = 2, +#endif + .pixel_clock = 6250, + + .init = apollon_panel_init, + .cleanup = apollon_panel_cleanup, + .enable = apollon_panel_enable, + .disable = apollon_panel_disable, + .get_caps = apollon_panel_get_caps, +}; + +static int apollon_panel_probe(struct platform_device *pdev) +{ + omapfb_register_panel(&apollon_panel); + return 0; +} + +static int apollon_panel_remove(struct platform_device *pdev) +{ + return 0; +} + +static int apollon_panel_suspend(struct platform_device *pdev, pm_message_t mesg) +{ + return 0; +} + +static int apollon_panel_resume(struct platform_device *pdev) +{ + return 0; +} + +struct platform_driver apollon_panel_driver = { + .probe = apollon_panel_probe, + .remove = apollon_panel_remove, + .suspend = apollon_panel_suspend, + .resume = apollon_panel_resume, + .driver = { + .name = "apollon_lcd", + .owner = THIS_MODULE, + }, +}; + +static int __init apollon_panel_drv_init(void) +{ + return platform_driver_register(&apollon_panel_driver); +} + +static void __exit apollon_panel_drv_exit(void) +{ + platform_driver_unregister(&apollon_panel_driver); +} + +module_init(apollon_panel_drv_init); +module_exit(apollon_panel_drv_exit); diff --cc drivers/video/omap/lcd_h2.c index 7d16b5703c9,00000000000..96b4816f648 mode 100644,000000..100644 --- a/drivers/video/omap/lcd_h2.c +++ b/drivers/video/omap/lcd_h2.c @@@ -1,155 -1,0 +1,155 @@@ +/* + * LCD panel support for the TI OMAP H2 board + * + * Copyright (C) 2004 Nokia Corporation + * Author: Imre Deak + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include + - #include - #include ++#include ++#include + +static struct { + struct platform_device *lcd_dev; + struct spi_device *tsc2101_dev; +} h2_panel_dev; + +static int h2_panel_init(struct lcd_panel *panel, struct omapfb_device *fbdev) +{ + return 0; +} + +static void h2_panel_cleanup(struct lcd_panel *panel) +{ +} + +static int h2_panel_enable(struct lcd_panel *panel) +{ + int r; + + /* + * Assert LCD_EN, BKLIGHT_EN pins on LCD panel + * page2, GPIO config reg, GPIO(0,1) to out and asserted + */ + r = tsc2101_write_sync(h2_panel_dev.tsc2101_dev, 2, 0x23, 0xcc00); + if (r < 0) + dev_err(&h2_panel_dev.lcd_dev->dev, + "failed to enable LCD panel\n"); + + return r; +} + +static void h2_panel_disable(struct lcd_panel *panel) +{ + /* + * Deassert LCD_EN and BKLIGHT_EN pins on LCD panel + * page2, GPIO config reg, GPIO(0,1) to out and deasserted + */ + if (tsc2101_write_sync(h2_panel_dev.tsc2101_dev, 2, 0x23, 0x8800)) + dev_err(&h2_panel_dev.lcd_dev->dev, + "failed to disable LCD panel\n"); +} + +static unsigned long h2_panel_get_caps(struct lcd_panel *panel) +{ + return 0; +} + +struct lcd_panel h2_panel = { + .name = "h2", + .config = OMAP_LCDC_PANEL_TFT, + + .bpp = 16, + .data_lines = 16, + .x_res = 240, + .y_res = 320, + .pixel_clock = 5000, + .hsw = 12, + .hfp = 12, + .hbp = 46, + .vsw = 1, + .vfp = 1, + .vbp = 0, + + .init = h2_panel_init, + .cleanup = h2_panel_cleanup, + .enable = h2_panel_enable, + .disable = h2_panel_disable, + .get_caps = h2_panel_get_caps, +}; + +static int h2_panel_probe(struct platform_device *pdev) +{ + struct spi_device *tsc2101; + + tsc2101 = pdev->dev.platform_data; + if (tsc2101 == NULL) { + dev_err(&pdev->dev, "no platform data\n"); + return -ENODEV; + } + if (strncmp(tsc2101->modalias, "tsc2101", 8) != 0) { + dev_err(&pdev->dev, "tsc2101 not found\n"); + return -EINVAL; + } + h2_panel_dev.lcd_dev = pdev; + h2_panel_dev.tsc2101_dev = tsc2101; + omapfb_register_panel(&h2_panel); + return 0; +} + +static int h2_panel_remove(struct platform_device *pdev) +{ + return 0; +} + +static int h2_panel_suspend(struct platform_device *pdev, pm_message_t mesg) +{ + return 0; +} + +static int h2_panel_resume(struct platform_device *pdev) +{ + return 0; +} + +struct platform_driver h2_panel_driver = { + .probe = h2_panel_probe, + .remove = h2_panel_remove, + .suspend = h2_panel_suspend, + .resume = h2_panel_resume, + .driver = { + .name = "lcd_h2", + .owner = THIS_MODULE, + }, +}; + +static int h2_panel_drv_init(void) +{ + return platform_driver_register(&h2_panel_driver); +} + +static void h2_panel_drv_cleanup(void) +{ + platform_driver_unregister(&h2_panel_driver); +} + +module_init(h2_panel_drv_init); +module_exit(h2_panel_drv_cleanup); + diff --cc drivers/video/omap/lcd_mipid.c index 3af8b2fd971,00000000000..1895997d8d8 mode 100644,000000..100644 --- a/drivers/video/omap/lcd_mipid.c +++ b/drivers/video/omap/lcd_mipid.c @@@ -1,617 -1,0 +1,617 @@@ +/* + * LCD driver for MIPI DBI-C / DCS compatible LCDs + * + * Copyright (C) 2006 Nokia Corporation + * Author: Imre Deak + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include +#include +#include +#include + - #include - #include ++#include ++#include + +#include "../../cbus/tahvo.h" + +#define MIPID_MODULE_NAME "lcd_mipid" + +#define MIPID_CMD_READ_DISP_ID 0x04 +#define MIPID_CMD_READ_RED 0x06 +#define MIPID_CMD_READ_GREEN 0x07 +#define MIPID_CMD_READ_BLUE 0x08 +#define MIPID_CMD_READ_DISP_STATUS 0x09 +#define MIPID_CMD_RDDSDR 0x0F +#define MIPID_CMD_SLEEP_IN 0x10 +#define MIPID_CMD_SLEEP_OUT 0x11 +#define MIPID_CMD_DISP_OFF 0x28 +#define MIPID_CMD_DISP_ON 0x29 + +#define MIPID_VER_LPH8923 3 +#define MIPID_VER_LS041Y3 4 + +#define MIPID_ESD_CHECK_PERIOD msecs_to_jiffies(5000) + +#define to_mipid_device(p) container_of(p, struct mipid_device, \ + panel) +struct mipid_device { + int enabled; + int model; + int revision; + u8 display_id[3]; + unsigned int saved_bklight_level; + unsigned long hw_guard_end; /* next value of jiffies + when we can issue the + next sleep in/out command */ + unsigned long hw_guard_wait; /* max guard time in jiffies */ + + struct omapfb_device *fbdev; + struct spi_device *spi; + struct mutex mutex; + struct lcd_panel panel; + + struct workqueue_struct *esd_wq; + struct delayed_work esd_work; + void (*esd_check)(struct mipid_device *m); +}; + +static void mipid_transfer(struct mipid_device *md, int cmd, const u8 *wbuf, + int wlen, u8 *rbuf, int rlen) +{ + struct spi_message m; + struct spi_transfer *x, xfer[4]; + u16 w; + int r; + + BUG_ON(md->spi == NULL); + + spi_message_init(&m); + + memset(xfer, 0, sizeof(xfer)); + x = &xfer[0]; + + cmd &= 0xff; + x->tx_buf = &cmd; + x->bits_per_word= 9; + x->len = 2; + spi_message_add_tail(x, &m); + + if (wlen) { + x++; + x->tx_buf = wbuf; + x->len = wlen; + x->bits_per_word= 9; + spi_message_add_tail(x, &m); + } + + if (rlen) { + x++; + x->rx_buf = &w; + x->len = 1; + spi_message_add_tail(x, &m); + + if (rlen > 1) { + /* Arrange for the extra clock before the first + * data bit. + */ + x->bits_per_word = 9; + x->len = 2; + + x++; + x->rx_buf = &rbuf[1]; + x->len = rlen - 1; + spi_message_add_tail(x, &m); + } + } + + r = spi_sync(md->spi, &m); + if (r < 0) + dev_dbg(&md->spi->dev, "spi_sync %d\n", r); + + if (rlen) + rbuf[0] = w & 0xff; +} + +static inline void mipid_cmd(struct mipid_device *md, int cmd) +{ + mipid_transfer(md, cmd, NULL, 0, NULL, 0); +} + +static inline void mipid_write(struct mipid_device *md, + int reg, const u8 *buf, int len) +{ + mipid_transfer(md, reg, buf, len, NULL, 0); +} + +static inline void mipid_read(struct mipid_device *md, + int reg, u8 *buf, int len) +{ + mipid_transfer(md, reg, NULL, 0, buf, len); +} + +static void set_data_lines(struct mipid_device *md, int data_lines) +{ + u16 par; + + switch (data_lines) { + case 16: + par = 0x150; + break; + case 18: + par = 0x160; + break; + case 24: + par = 0x170; + break; + } + mipid_write(md, 0x3a, (u8 *)&par, 2); +} + +static void send_init_string(struct mipid_device *md) +{ + u16 initpar[] = { 0x0102, 0x0100, 0x0100 }; + + mipid_write(md, 0xc2, (u8 *)initpar, sizeof(initpar)); + set_data_lines(md, md->panel.data_lines); +} + +static void hw_guard_start(struct mipid_device *md, int guard_msec) +{ + md->hw_guard_wait = msecs_to_jiffies(guard_msec); + md->hw_guard_end = jiffies + md->hw_guard_wait; +} + +static void hw_guard_wait(struct mipid_device *md) +{ + unsigned long wait = md->hw_guard_end - jiffies; + + if ((long)wait > 0 && wait <= md->hw_guard_wait) { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(wait); + } +} + +static void set_sleep_mode(struct mipid_device *md, int on) +{ + int cmd, sleep_time = 50; + + if (on) + cmd = MIPID_CMD_SLEEP_IN; + else + cmd = MIPID_CMD_SLEEP_OUT; + hw_guard_wait(md); + mipid_cmd(md, cmd); + hw_guard_start(md, 120); + /* + * When we enable the panel, it seems we _have_ to sleep + * 120 ms before sending the init string. When disabling the + * panel we'll sleep for the duration of 2 frames, so that the + * controller can still provide the PCLK,HS,VS signals. */ + if (!on) + sleep_time = 120; + msleep(sleep_time); +} + +static void set_display_state(struct mipid_device *md, int enabled) +{ + int cmd = enabled ? MIPID_CMD_DISP_ON : MIPID_CMD_DISP_OFF; + + mipid_cmd(md, cmd); +} + +static int mipid_set_bklight_level(struct lcd_panel *panel, unsigned int level) +{ + struct mipid_device *md = to_mipid_device(panel); + + if (level > tahvo_get_max_backlight_level()) + return -EINVAL; + if (!md->enabled) { + md->saved_bklight_level = level; + return 0; + } + tahvo_set_backlight_level(level); + + return 0; +} + +static unsigned int mipid_get_bklight_level(struct lcd_panel *panel) +{ + return tahvo_get_backlight_level(); +} + +static unsigned int mipid_get_bklight_max(struct lcd_panel *panel) +{ + return tahvo_get_max_backlight_level(); +} + + +static unsigned long mipid_get_caps(struct lcd_panel *panel) +{ + return OMAPFB_CAPS_SET_BACKLIGHT; +} + +static u16 read_first_pixel(struct mipid_device *md) +{ + u16 pixel; + u8 red, green, blue; + + mutex_lock(&md->mutex); + mipid_read(md, MIPID_CMD_READ_RED, &red, 1); + mipid_read(md, MIPID_CMD_READ_GREEN, &green, 1); + mipid_read(md, MIPID_CMD_READ_BLUE, &blue, 1); + mutex_unlock(&md->mutex); + + switch (md->panel.data_lines) { + case 16: + pixel = ((red >> 1) << 11) | (green << 5) | (blue >> 1); + break; + case 24: + /* 24 bit -> 16 bit */ + pixel = ((red >> 3) << 11) | ((green >> 2) << 5) | + (blue >> 3); + break; + default: + BUG(); + } + + return pixel; +} + +static int mipid_run_test(struct lcd_panel *panel, int test_num) +{ + struct mipid_device *md = to_mipid_device(panel); + static const u16 test_values[4] = { + 0x0000, 0xffff, 0xaaaa, 0x5555, + }; + int i; + + if (test_num != MIPID_TEST_RGB_LINES) + return MIPID_TEST_INVALID; + + for (i = 0; i < ARRAY_SIZE(test_values); i++) { + int delay; + unsigned long tmo; + + omapfb_write_first_pixel(md->fbdev, test_values[i]); + tmo = jiffies + msecs_to_jiffies(100); + delay = 25; + while (1) { + u16 pixel; + + msleep(delay); + pixel = read_first_pixel(md); + if (pixel == test_values[i]) + break; + if (time_after(jiffies, tmo)) { + dev_err(&md->spi->dev, + "MIPI LCD RGB I/F test failed: " + "expecting %04x, got %04x\n", + test_values[i], pixel); + return MIPID_TEST_FAILED; + } + delay = 10; + } + } + + return 0; +} + +static void ls041y3_esd_recover(struct mipid_device *md) +{ + dev_err(&md->spi->dev, "performing LCD ESD recovery\n"); + set_sleep_mode(md, 1); + set_sleep_mode(md, 0); +} + +static void ls041y3_esd_check_mode1(struct mipid_device *md) +{ + u8 state1, state2; + + mipid_read(md, MIPID_CMD_RDDSDR, &state1, 1); + set_sleep_mode(md, 0); + mipid_read(md, MIPID_CMD_RDDSDR, &state2, 1); + dev_dbg(&md->spi->dev, "ESD mode 1 state1 %02x state2 %02x\n", + state1, state2); + /* Each sleep out command will trigger a self diagnostic and flip + * Bit6 if the test passes. + */ + if (!((state1 ^ state2) & (1 << 6))) + ls041y3_esd_recover(md); +} + +static void ls041y3_esd_check_mode2(struct mipid_device *md) +{ + int i; + u8 rbuf[2]; + static const struct { + int cmd; + int wlen; + u16 wbuf[3]; + } *rd, rd_ctrl[7] = { + { 0xb0, 4, { 0x0101, 0x01fe, } }, + { 0xb1, 4, { 0x01de, 0x0121, } }, + { 0xc2, 4, { 0x0100, 0x0100, } }, + { 0xbd, 2, { 0x0100, } }, + { 0xc2, 4, { 0x01fc, 0x0103, } }, + { 0xb4, 0, }, + { 0x00, 0, }, + }; + + rd = rd_ctrl; + for (i = 0; i < 3; i++, rd++) + mipid_write(md, rd->cmd, (u8 *)rd->wbuf, rd->wlen); + + udelay(10); + mipid_read(md, rd->cmd, rbuf, 2); + rd++; + + for (i = 0; i < 3; i++, rd++) { + udelay(10); + mipid_write(md, rd->cmd, (u8 *)rd->wbuf, rd->wlen); + } + + dev_dbg(&md->spi->dev, "ESD mode 2 state %02x\n", rbuf[1]); + if (rbuf[1] == 0x00) + ls041y3_esd_recover(md); +} + +static void ls041y3_esd_check(struct mipid_device *md) +{ + ls041y3_esd_check_mode1(md); + if (md->revision >= 0x88) + ls041y3_esd_check_mode2(md); +} + +static void mipid_esd_start_check(struct mipid_device *md) +{ + if (md->esd_check != NULL) + queue_delayed_work(md->esd_wq, &md->esd_work, + MIPID_ESD_CHECK_PERIOD); +} + +static void mipid_esd_stop_check(struct mipid_device *md) +{ + if (md->esd_check != NULL) + cancel_rearming_delayed_workqueue(md->esd_wq, &md->esd_work); +} + +static void mipid_esd_work(struct work_struct *work) +{ + struct mipid_device *md = container_of(work, struct mipid_device, esd_work.work); + + mutex_lock(&md->mutex); + md->esd_check(md); + mutex_unlock(&md->mutex); + mipid_esd_start_check(md); +} + +static int mipid_enable(struct lcd_panel *panel) +{ + struct mipid_device *md = to_mipid_device(panel); + + mutex_lock(&md->mutex); + + if (md->enabled) { + mutex_unlock(&md->mutex); + return 0; + } + set_sleep_mode(md, 0); + md->enabled = 1; + send_init_string(md); + set_display_state(md, 1); + mipid_set_bklight_level(panel, md->saved_bklight_level); + mipid_esd_start_check(md); + + mutex_unlock(&md->mutex); + return 0; +} + +static void mipid_disable(struct lcd_panel *panel) +{ + struct mipid_device *md = to_mipid_device(panel); + + /* + * A final ESD work might be called before returning, + * so do this without holding the lock. + */ + mipid_esd_stop_check(md); + mutex_lock(&md->mutex); + + if (!md->enabled) { + mutex_unlock(&md->mutex); + return; + } + md->saved_bklight_level = mipid_get_bklight_level(panel); + mipid_set_bklight_level(panel, 0); + set_display_state(md, 0); + set_sleep_mode(md, 1); + md->enabled = 0; + + mutex_unlock(&md->mutex); +} + +static int panel_enabled(struct mipid_device *md) +{ + u32 disp_status; + int enabled; + + mipid_read(md, MIPID_CMD_READ_DISP_STATUS, (u8 *)&disp_status, 4); + disp_status = __be32_to_cpu(disp_status); + enabled = (disp_status & (1 << 17)) && (disp_status & (1 << 10)); + dev_dbg(&md->spi->dev, + "LCD panel %senabled by bootloader (status 0x%04x)\n", + enabled ? "" : "not ", disp_status); + return enabled; +} + +static int mipid_init(struct lcd_panel *panel, + struct omapfb_device *fbdev) +{ + struct mipid_device *md = to_mipid_device(panel); + + md->fbdev = fbdev; + md->esd_wq = create_singlethread_workqueue("mipid_esd"); + if (md->esd_wq == NULL) { + dev_err(&md->spi->dev, "can't create ESD workqueue\n"); + return -ENOMEM; + } + INIT_DELAYED_WORK(&md->esd_work, mipid_esd_work); + mutex_init(&md->mutex); + + md->enabled = panel_enabled(md); + + if (md->enabled) + mipid_esd_start_check(md); + else + md->saved_bklight_level = mipid_get_bklight_level(panel); + + return 0; +} + +static void mipid_cleanup(struct lcd_panel *panel) +{ + struct mipid_device *md = to_mipid_device(panel); + + if (md->enabled) + mipid_esd_stop_check(md); + destroy_workqueue(md->esd_wq); +} + +static struct lcd_panel mipid_panel = { + .config = OMAP_LCDC_PANEL_TFT, + + .bpp = 16, + .x_res = 800, + .y_res = 480, + .pixel_clock = 21940, + .hsw = 50, + .hfp = 20, + .hbp = 15, + .vsw = 2, + .vfp = 1, + .vbp = 3, + + .init = mipid_init, + .cleanup = mipid_cleanup, + .enable = mipid_enable, + .disable = mipid_disable, + .get_caps = mipid_get_caps, + .set_bklight_level= mipid_set_bklight_level, + .get_bklight_level= mipid_get_bklight_level, + .get_bklight_max= mipid_get_bklight_max, + .run_test = mipid_run_test, +}; + +static int mipid_detect(struct mipid_device *md) +{ + struct mipid_platform_data *pdata; + + pdata = md->spi->dev.platform_data; + if (pdata == NULL) { + dev_err(&md->spi->dev, "missing platform data\n"); + return -ENOENT; + } + + mipid_read(md, MIPID_CMD_READ_DISP_ID, md->display_id, 3); + dev_dbg(&md->spi->dev, "MIPI display ID: %02x%02x%02x\n", + md->display_id[0], md->display_id[1], md->display_id[2]); + + switch (md->display_id[0]) { + case 0x45: + md->model = MIPID_VER_LPH8923; + md->panel.name = "lph8923"; + break; + case 0x83: + md->model = MIPID_VER_LS041Y3; + md->panel.name = "ls041y3"; + md->esd_check = ls041y3_esd_check; + break; + default: + md->panel.name = "unknown"; + dev_err(&md->spi->dev, "invalid display ID\n"); + return -ENODEV; + } + + md->revision = md->display_id[1]; + md->panel.data_lines = pdata->data_lines; + pr_info("omapfb: %s rev %02x LCD detected\n", + md->panel.name, md->revision); + + return 0; +} + +static int mipid_spi_probe(struct spi_device *spi) +{ + struct mipid_device *md; + int r; + + md = kzalloc(sizeof(*md), GFP_KERNEL); + if (md == NULL) { + dev_err(&spi->dev, "out of memory\n"); + return -ENOMEM; + } + + spi->mode = SPI_MODE_0; + md->spi = spi; + dev_set_drvdata(&spi->dev, md); + md->panel = mipid_panel; + + r = mipid_detect(md); + if (r < 0) + return r; + + omapfb_register_panel(&md->panel); + + return 0; +} + +static int mipid_spi_remove(struct spi_device *spi) +{ + struct mipid_device *md = dev_get_drvdata(&spi->dev); + + mipid_disable(&md->panel); + kfree(md); + + return 0; +} + +static struct spi_driver mipid_spi_driver = { + .driver = { + .name = MIPID_MODULE_NAME, + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = mipid_spi_probe, + .remove = __devexit_p(mipid_spi_remove), +}; + +static int mipid_drv_init(void) +{ + spi_register_driver(&mipid_spi_driver); + + return 0; +} +module_init(mipid_drv_init); + +static void mipid_drv_cleanup(void) +{ + spi_unregister_driver(&mipid_spi_driver); +} +module_exit(mipid_drv_cleanup); + +MODULE_DESCRIPTION("MIPI display driver"); +MODULE_LICENSE("GPL"); diff --cc drivers/video/omap/lcd_omap2evm.c index 8311cac19e2,00000000000..8a0105e3520 mode 100644,000000..100644 --- a/drivers/video/omap/lcd_omap2evm.c +++ b/drivers/video/omap/lcd_omap2evm.c @@@ -1,195 -1,0 +1,195 @@@ +/* + * LCD panel support for the MISTRAL OMAP2EVM board + * + * Author: Arun C + * + * Derived from drivers/video/omap/lcd_omap3evm.c + * Derived from drivers/video/omap/lcd-apollon.c + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include + - #include - #include - #include ++#include ++#include ++#include +#include + +#define LCD_PANEL_ENABLE_GPIO 154 +#define LCD_PANEL_LR 128 +#define LCD_PANEL_UD 129 +#define LCD_PANEL_INI 152 +#define LCD_PANEL_QVGA 148 +#define LCD_PANEL_RESB 153 + +#define LCD_XRES 480 +#define LCD_YRES 640 +#define LCD_PIXCLOCK_MAX 20000 /* in kHz */ + +#define TWL_LED_LEDEN 0x00 +#define TWL_PWMA_PWMAON 0x00 +#define TWL_PWMA_PWMAOFF 0x01 + +static unsigned int bklight_level; + +static int omap2evm_panel_init(struct lcd_panel *panel, + struct omapfb_device *fbdev) +{ + omap_request_gpio(LCD_PANEL_ENABLE_GPIO); + omap_request_gpio(LCD_PANEL_LR); + omap_request_gpio(LCD_PANEL_UD); + omap_request_gpio(LCD_PANEL_INI); + omap_request_gpio(LCD_PANEL_QVGA); + omap_request_gpio(LCD_PANEL_RESB); + + omap_set_gpio_direction(LCD_PANEL_ENABLE_GPIO, 0); + omap_set_gpio_direction(LCD_PANEL_LR, 0); + omap_set_gpio_direction(LCD_PANEL_UD, 0); + omap_set_gpio_direction(LCD_PANEL_INI, 0); + omap_set_gpio_direction(LCD_PANEL_QVGA, 0); + omap_set_gpio_direction(LCD_PANEL_RESB, 0); + + omap_set_gpio_dataout(LCD_PANEL_RESB, 1); + omap_set_gpio_dataout(LCD_PANEL_INI, 1); + omap_set_gpio_dataout(LCD_PANEL_QVGA, 0); + omap_set_gpio_dataout(LCD_PANEL_LR, 1); + omap_set_gpio_dataout(LCD_PANEL_UD, 1); + + twl4030_i2c_write_u8(TWL4030_MODULE_LED, 0x11, TWL_LED_LEDEN); + twl4030_i2c_write_u8(TWL4030_MODULE_PWMA, 0x01, TWL_PWMA_PWMAON); + twl4030_i2c_write_u8(TWL4030_MODULE_PWMA, 0x02, TWL_PWMA_PWMAOFF); + bklight_level = 100; + + return 0; +} + +static void omap2evm_panel_cleanup(struct lcd_panel *panel) +{ +} + +static int omap2evm_panel_enable(struct lcd_panel *panel) +{ + omap_set_gpio_dataout(LCD_PANEL_ENABLE_GPIO, 0); + return 0; +} + +static void omap2evm_panel_disable(struct lcd_panel *panel) +{ + omap_set_gpio_dataout(LCD_PANEL_ENABLE_GPIO, 1); +} + +static unsigned long omap2evm_panel_get_caps(struct lcd_panel *panel) +{ + return 0; +} + +static int omap2evm_bklight_setlevel(struct lcd_panel *panel, + unsigned int level) +{ + u8 c; + if ((level >= 0) && (level <= 100)) { + c = (125 * (100 - level)) / 100 + 2; + twl4030_i2c_write_u8(TWL4030_MODULE_PWMA, c, TWL_PWMA_PWMAOFF); + bklight_level = level; + } + return 0; +} + +static unsigned int omap2evm_bklight_getlevel(struct lcd_panel *panel) +{ + return bklight_level; +} + +static unsigned int omap2evm_bklight_getmaxlevel(struct lcd_panel *panel) +{ + return 100; +} + +struct lcd_panel omap2evm_panel = { + .name = "omap2evm", + .config = OMAP_LCDC_PANEL_TFT | OMAP_LCDC_INV_VSYNC | + OMAP_LCDC_INV_HSYNC, + + .bpp = 16, + .data_lines = 18, + .x_res = LCD_XRES, + .y_res = LCD_YRES, + .hsw = 3, + .hfp = 0, + .hbp = 28, + .vsw = 2, + .vfp = 1, + .vbp = 0, + + .pixel_clock = LCD_PIXCLOCK_MAX, + + .init = omap2evm_panel_init, + .cleanup = omap2evm_panel_cleanup, + .enable = omap2evm_panel_enable, + .disable = omap2evm_panel_disable, + .get_caps = omap2evm_panel_get_caps, + .set_bklight_level = omap2evm_bklight_setlevel, + .get_bklight_level = omap2evm_bklight_getlevel, + .get_bklight_max = omap2evm_bklight_getmaxlevel, +}; + +static int omap2evm_panel_probe(struct platform_device *pdev) +{ + omapfb_register_panel(&omap2evm_panel); + return 0; +} + +static int omap2evm_panel_remove(struct platform_device *pdev) +{ + return 0; +} + +static int omap2evm_panel_suspend(struct platform_device *pdev, + pm_message_t mesg) +{ + return 0; +} + +static int omap2evm_panel_resume(struct platform_device *pdev) +{ + return 0; +} + +struct platform_driver omap2evm_panel_driver = { + .probe = omap2evm_panel_probe, + .remove = omap2evm_panel_remove, + .suspend = omap2evm_panel_suspend, + .resume = omap2evm_panel_resume, + .driver = { + .name = "omap2evm_lcd", + .owner = THIS_MODULE, + }, +}; + +static int __init omap2evm_panel_drv_init(void) +{ + return platform_driver_register(&omap2evm_panel_driver); +} + +static void __exit omap2evm_panel_drv_exit(void) +{ + platform_driver_unregister(&omap2evm_panel_driver); +} + +module_init(omap2evm_panel_drv_init); +module_exit(omap2evm_panel_drv_exit); diff --cc drivers/video/omap/lcd_omap3beagle.c index 69d4e06a943,00000000000..96377353b75 mode 100644,000000..100644 --- a/drivers/video/omap/lcd_omap3beagle.c +++ b/drivers/video/omap/lcd_omap3beagle.c @@@ -1,133 -1,0 +1,133 @@@ +/* + * LCD panel support for the TI OMAP3 Beagle board + * + * Author: Koen Kooi + * + * Derived from drivers/video/omap/lcd-omap3evm.c + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include + - #include - #include - #include ++#include ++#include ++#include +#include + +#define LCD_PANEL_ENABLE_GPIO 170 + +#define LCD_XRES 1024 +#define LCD_YRES 768 +#define LCD_PIXCLOCK 64000 /* in kHz */ + +static int omap3beagle_panel_init(struct lcd_panel *panel, + struct omapfb_device *fbdev) +{ + omap_request_gpio(LCD_PANEL_ENABLE_GPIO); + return 0; +} + +static void omap3beagle_panel_cleanup(struct lcd_panel *panel) +{ +} + +static int omap3beagle_panel_enable(struct lcd_panel *panel) +{ + omap_set_gpio_dataout(LCD_PANEL_ENABLE_GPIO, 1); + return 0; +} + +static void omap3beagle_panel_disable(struct lcd_panel *panel) +{ + omap_set_gpio_dataout(LCD_PANEL_ENABLE_GPIO, 0); +} + +static unsigned long omap3beagle_panel_get_caps(struct lcd_panel *panel) +{ + return 0; +} + +struct lcd_panel omap3beagle_panel = { + .name = "omap3beagle", + .config = OMAP_LCDC_PANEL_TFT, + + .bpp = 24, + .data_lines = 24, + .x_res = LCD_XRES, + .y_res = LCD_YRES, + .hsw = 3, /* hsync_len (4) - 1 */ + .hfp = 3, /* right_margin (4) - 1 */ + .hbp = 39, /* left_margin (40) - 1 */ + .vsw = 1, /* vsync_len (2) - 1 */ + .vfp = 2, /* lower_margin */ + .vbp = 7, /* upper_margin (8) - 1 */ + + .pixel_clock = LCD_PIXCLOCK, + + .init = omap3beagle_panel_init, + .cleanup = omap3beagle_panel_cleanup, + .enable = omap3beagle_panel_enable, + .disable = omap3beagle_panel_disable, + .get_caps = omap3beagle_panel_get_caps, +}; + +static int omap3beagle_panel_probe(struct platform_device *pdev) +{ + omapfb_register_panel(&omap3beagle_panel); + return 0; +} + +static int omap3beagle_panel_remove(struct platform_device *pdev) +{ + return 0; +} + +static int omap3beagle_panel_suspend(struct platform_device *pdev, + pm_message_t mesg) +{ + return 0; +} + +static int omap3beagle_panel_resume(struct platform_device *pdev) +{ + return 0; +} + +struct platform_driver omap3beagle_panel_driver = { + .probe = omap3beagle_panel_probe, + .remove = omap3beagle_panel_remove, + .suspend = omap3beagle_panel_suspend, + .resume = omap3beagle_panel_resume, + .driver = { + .name = "omap3beagle_lcd", + .owner = THIS_MODULE, + }, +}; + +static int __init omap3beagle_panel_drv_init(void) +{ + return platform_driver_register(&omap3beagle_panel_driver); +} + +static void __exit omap3beagle_panel_drv_exit(void) +{ + platform_driver_unregister(&omap3beagle_panel_driver); +} + +module_init(omap3beagle_panel_drv_init); +module_exit(omap3beagle_panel_drv_exit); diff --cc drivers/video/omap/lcd_omap3evm.c index f5a1477036b,00000000000..a564ca54b5c mode 100644,000000..100644 --- a/drivers/video/omap/lcd_omap3evm.c +++ b/drivers/video/omap/lcd_omap3evm.c @@@ -1,197 -1,0 +1,197 @@@ +/* + * LCD panel support for the TI OMAP3 EVM board + * + * Author: Steve Sakoman + * + * Derived from drivers/video/omap/lcd-apollon.c + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include + - #include - #include - #include ++#include ++#include ++#include +#include + +#define LCD_PANEL_ENABLE_GPIO 153 +#define LCD_PANEL_LR 2 +#define LCD_PANEL_UD 3 +#define LCD_PANEL_INI 152 +#define LCD_PANEL_QVGA 154 +#define LCD_PANEL_RESB 155 + +#define LCD_XRES 480 +#define LCD_YRES 640 +#define LCD_PIXCLOCK 26000 /* in kHz */ + +#define ENABLE_VDAC_DEDICATED 0x03 +#define ENABLE_VDAC_DEV_GRP 0x20 +#define ENABLE_VPLL2_DEDICATED 0x05 +#define ENABLE_VPLL2_DEV_GRP 0xE0 + +#define TWL_LED_LEDEN 0x00 +#define TWL_PWMA_PWMAON 0x00 +#define TWL_PWMA_PWMAOFF 0x01 + +static unsigned int bklight_level; + +static int omap3evm_panel_init(struct lcd_panel *panel, + struct omapfb_device *fbdev) +{ + omap_request_gpio(LCD_PANEL_LR); + omap_request_gpio(LCD_PANEL_UD); + omap_request_gpio(LCD_PANEL_INI); + omap_request_gpio(LCD_PANEL_RESB); + omap_request_gpio(LCD_PANEL_QVGA); + + omap_set_gpio_direction(LCD_PANEL_LR, 0); + omap_set_gpio_direction(LCD_PANEL_UD, 0); + omap_set_gpio_direction(LCD_PANEL_INI, 0); + omap_set_gpio_direction(LCD_PANEL_RESB, 0); + omap_set_gpio_direction(LCD_PANEL_QVGA, 0); + + twl4030_i2c_write_u8(TWL4030_MODULE_LED, 0x11, TWL_LED_LEDEN); + twl4030_i2c_write_u8(TWL4030_MODULE_PWMA, 0x01, TWL_PWMA_PWMAON); + twl4030_i2c_write_u8(TWL4030_MODULE_PWMA, 0x02, TWL_PWMA_PWMAOFF); + bklight_level = 100; + + omap_set_gpio_dataout(LCD_PANEL_RESB, 1); + omap_set_gpio_dataout(LCD_PANEL_INI, 1); + omap_set_gpio_dataout(LCD_PANEL_QVGA, 0); + omap_set_gpio_dataout(LCD_PANEL_LR, 1); + omap_set_gpio_dataout(LCD_PANEL_UD, 1); + + return 0; +} + +static void omap3evm_panel_cleanup(struct lcd_panel *panel) +{ +} + +static int omap3evm_panel_enable(struct lcd_panel *panel) +{ + omap_set_gpio_dataout(LCD_PANEL_ENABLE_GPIO, 0); + return 0; +} + +static void omap3evm_panel_disable(struct lcd_panel *panel) +{ + omap_set_gpio_dataout(LCD_PANEL_ENABLE_GPIO, 1); +} + +static unsigned long omap3evm_panel_get_caps(struct lcd_panel *panel) +{ + return 0; +} + +static int omap3evm_bklight_setlevel(struct lcd_panel *panel, + unsigned int level) +{ + u8 c; + if ((level >= 0) && (level <= 100)) { + c = (125 * (100 - level)) / 100 + 2; + twl4030_i2c_write_u8(TWL4030_MODULE_PWMA, c, TWL_PWMA_PWMAOFF); + bklight_level = level; + } + return 0; +} + +static unsigned int omap3evm_bklight_getlevel(struct lcd_panel *panel) +{ + return bklight_level; +} + +static unsigned int omap3evm_bklight_getmaxlevel(struct lcd_panel *panel) +{ + return 100; +} + +struct lcd_panel omap3evm_panel = { + .name = "omap3evm", + .config = OMAP_LCDC_PANEL_TFT | OMAP_LCDC_INV_VSYNC | + OMAP_LCDC_INV_HSYNC, + + .bpp = 16, + .data_lines = 18, + .x_res = LCD_XRES, + .y_res = LCD_YRES, + .hsw = 3, /* hsync_len (4) - 1 */ + .hfp = 3, /* right_margin (4) - 1 */ + .hbp = 39, /* left_margin (40) - 1 */ + .vsw = 1, /* vsync_len (2) - 1 */ + .vfp = 2, /* lower_margin */ + .vbp = 7, /* upper_margin (8) - 1 */ + + .pixel_clock = LCD_PIXCLOCK, + + .init = omap3evm_panel_init, + .cleanup = omap3evm_panel_cleanup, + .enable = omap3evm_panel_enable, + .disable = omap3evm_panel_disable, + .get_caps = omap3evm_panel_get_caps, + .set_bklight_level = omap3evm_bklight_setlevel, + .get_bklight_level = omap3evm_bklight_getlevel, + .get_bklight_max = omap3evm_bklight_getmaxlevel, +}; + +static int omap3evm_panel_probe(struct platform_device *pdev) +{ + omapfb_register_panel(&omap3evm_panel); + return 0; +} + +static int omap3evm_panel_remove(struct platform_device *pdev) +{ + return 0; +} + +static int omap3evm_panel_suspend(struct platform_device *pdev, + pm_message_t mesg) +{ + return 0; +} + +static int omap3evm_panel_resume(struct platform_device *pdev) +{ + return 0; +} + +struct platform_driver omap3evm_panel_driver = { + .probe = omap3evm_panel_probe, + .remove = omap3evm_panel_remove, + .suspend = omap3evm_panel_suspend, + .resume = omap3evm_panel_resume, + .driver = { + .name = "omap3evm_lcd", + .owner = THIS_MODULE, + }, +}; + +static int __init omap3evm_panel_drv_init(void) +{ + return platform_driver_register(&omap3evm_panel_driver); +} + +static void __exit omap3evm_panel_drv_exit(void) +{ + platform_driver_unregister(&omap3evm_panel_driver); +} + +module_init(omap3evm_panel_drv_init); +module_exit(omap3evm_panel_drv_exit); diff --cc drivers/video/omap/lcd_p2.c index b0a0af8bf20,00000000000..8c66bf635d6 mode 100644,000000..100644 --- a/drivers/video/omap/lcd_p2.c +++ b/drivers/video/omap/lcd_p2.c @@@ -1,342 -1,0 +1,342 @@@ +/* + * LCD panel support for the TI OMAP P2 board + * + * Authors: + * jekyll + * B Jp + * Brian Swetland + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include + - #include - #include - #include ++#include ++#include ++#include + +/* + * File: epson-md-tft.h + * + * This file contains definitions for Epsons MD-TF LCD Module + * + * Copyright (C) 2004 MPC-Data Limited (http://www.mpc-data.co.uk) + * Author: Dave Peverley + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Please report all bugs and problems to the author. + * + */ + +/* LCD uWire commands & params + * All values from Epson + */ +#define LCD_DISON 0xAF +#define LCD_DISOFF 0xAE +#define LCD_DISNOR 0xA6 +#define LCD_DISINV 0xA7 +#define LCD_DISCTL 0xCA +#define LCD_GCP64 0xCB +#define LCD_GCP16 0xCC +#define LCD_GSSET 0xCD +#define LCD_SLPIN 0x95 +#define LCD_SLPOUT 0x94 +#define LCD_SD_PSET 0x75 +#define LCD_MD_PSET 0x76 +#define LCD_SD_CSET 0x15 +#define LCD_MD_CSET 0x16 +#define LCD_DATCTL 0xBC +#define LCD_RAMWR 0x5C +#define LCD_RAMRD 0x5D +#define LCD_PTLIN 0xA8 +#define LCD_PTLOUT 0xA9 +#define LCD_ASCSET 0xAA +#define LCD_SCSTART 0xAB +#define LCD_VOLCTL 0xC6 +#define LCD_NOP 0x25 +#define LCD_OSCISEL 0x7 +#define LCD_3500KSET 0xD1 +#define LCD_3500KEND 0xD2 +#define LCD_14MSET 0xD3 +#define LCD_14MEND 0xD4 + +#define INIT_3500KSET 0x45 +#define INIT_14MSET 0x4B +#define INIT_DATCTL 0x08 /* 6.6.6 bits for D-Sample */ + +#define INIT_OSCISEL 0x05 + +#define INIT_VOLCTL 0x77 /* Nominel "volume" */ + +#define INIT_VOLCTL_Ton 0x98 /* Activate power-IC timer */ +#define INIT_GSSET 0x00 + +const unsigned short INIT_DISCTL[11] = +{ + 0xDE, 0x01, 0x64, 0x00, 0x1B, 0xF4, 0x00, 0xDC, 0x00, 0x02, 0x00 +}; + +const unsigned short INIT_GCP64[126] = +{ + 0x3B,0x00,0x42,0x00,0x4A,0x00,0x51,0x00, + 0x58,0x00,0x5F,0x00,0x66,0x00,0x6E,0x00, + 0x75,0x00,0x7C,0x00,0x83,0x00,0x8A,0x00, + 0x92,0x00,0x99,0x00,0xA0,0x00,0xA7,0x00, + 0xAE,0x00,0xB6,0x00,0xBD,0x00,0xC4,0x00, + 0xCB,0x00,0xD2,0x00,0xDA,0x00,0xE1,0x00, + 0xE8,0x00,0xEF,0x00,0xF6,0x00,0xFE,0x00, + 0x05,0x01,0x0C,0x01,0x13,0x01,0x1A,0x01, + 0x22,0x01,0x29,0x01,0x30,0x01,0x37,0x01, + 0x3E,0x01,0x46,0x01,0x4D,0x01,0x54,0x01, + 0x5B,0x01,0x62,0x01,0x6A,0x01,0x71,0x01, + 0x78,0x01,0x7F,0x01,0x86,0x01,0x8E,0x01, + 0x95,0x01,0x9C,0x01,0xA3,0x01,0xAA,0x01, + 0xB2,0x01,0xB9,0x01,0xC0,0x01,0xC7,0x01, + 0xCE,0x01,0xD6,0x01,0xDD,0x01,0xE4,0x01, + 0xEB,0x01,0xF2,0x01,0xFA,0x01 +}; + +const unsigned short INIT_GCP16[15] = +{ + 0x1A,0x31,0x48,0x54,0x5F,0x67,0x70,0x76,0x7C,0x80,0x83,0x84,0x85,0x87,0x96 +}; + +const unsigned short INIT_MD_PSET[4] = { 0, 0, 219, 0 }; +const unsigned short INIT_MD_CSET[4] = { 2, 0, 177, 0 }; + +const unsigned short INIT_SD_PSET[4] = { 0x00, 0x01, 0x00, 0x01 }; +const unsigned short INIT_SD_CSET[4] = { 0x00, 0x02, 0x00, 0x02 }; + +const unsigned short INIT_ASCSET[7] = { 0x00, 0x00, 0xDB, 0x00, 0xDC, 0x00, 0x01 }; +const unsigned short INIT_SCSTART[2] = { 0x00, 0x00 }; + +/* ----- end of epson_md_tft.h ----- */ + + +#include "../drivers/ssi/omap-uwire.h" + +#define LCD_UWIRE_CS 0 + +static int p2_panel_init(struct lcd_panel *panel, struct omapfb_device *fbdev) +{ + return 0; +} + +static void p2_panel_cleanup(struct lcd_panel *panel) +{ +} + +static int p2_panel_enable(struct lcd_panel *panel) +{ + int i; + unsigned long value; + + /* thwack the reset line */ + omap_set_gpio_direction(19, 0); + omap_set_gpio_dataout(19, 0); + mdelay(2); + omap_set_gpio_dataout(19, 1); + + /* bits 31:28 -> 0 LCD_PXL_15 .. 12 */ + value = omap_readl(OMAP730_IO_CONF_3) & 0x0FFFFFFF; + omap_writel(value, OMAP730_IO_CONF_3); + + /* bits 19:0 -> 0 LCD_VSYNC, AC, PXL_0, PCLK, HSYNC, + ** PXL_9..1, PXL_10, PXL_11 + */ + value = omap_readl(OMAP730_IO_CONF_4) & 0xFFF00000; + omap_writel(value, OMAP730_IO_CONF_4); + + omap_uwire_configure_mode(0,16); + + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_DISOFF, 9, 0,NULL,1); + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_SLPIN, 9, 0,NULL,1); + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_DISNOR, 9, 0,NULL,1); + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_GSSET, 9, 0,NULL,1); + omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_GSSET | 0x100), 9, 0,NULL,1); + + /* DISCTL */ + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_DISCTL, 9, 0,NULL,1); + for (i = 0; i < (sizeof(INIT_DISCTL)/sizeof(unsigned short)); i++) + omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_DISCTL[i] | 0x100), 9, 0,NULL,1); + + /* GCP64 */ + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_GCP64, 9, 0,NULL,1); + for (i = 0; i < (sizeof(INIT_GCP64)/sizeof(unsigned short)); i++) + omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_GCP64[i] | 0x100), 9, 0,NULL,1); + + /* GCP16 */ + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_GCP16, 9, 0,NULL,1); + for (i = 0; i < (sizeof(INIT_GCP16)/sizeof(unsigned short)); i++) + omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_GCP16[i] | 0x100), 9, 0,NULL,1); + + /* MD_CSET */ + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_MD_CSET, 9, 0,NULL,1); + for (i = 0; i < (sizeof(INIT_MD_CSET)/sizeof(unsigned short)); i++) + omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_MD_CSET[i] | 0x100), 9, 0,NULL,1); + + /* MD_PSET */ + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_MD_PSET, 9, 0,NULL,1); + for (i = 0; i < (sizeof(INIT_MD_PSET)/sizeof(unsigned short)); i++) + omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_MD_PSET[i] | 0x100), 9, 0,NULL,1); + + /* SD_CSET */ + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_SD_CSET, 9, 0,NULL,1); + for (i = 0; i < (sizeof(INIT_SD_CSET)/sizeof(unsigned short)); i++) + omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_SD_CSET[i] | 0x100), 9, 0,NULL,1); + + /* SD_PSET */ + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_SD_PSET, 9, 0,NULL,1); + for (i = 0; i < (sizeof(INIT_SD_PSET)/sizeof(unsigned short)); i++) + omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_SD_PSET[i] | 0x100), 9, 0,NULL,1); + + /* DATCTL */ + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_DATCTL, 9, 0,NULL,1); + omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_DATCTL | 0x100), 9, 0,NULL,1); + + /* OSSISEL = d'5 */ + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_OSCISEL, 9, 0,NULL,1); + omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_OSCISEL | 0x100), 9, 0,NULL,1); + + /* 14MSET = d'74 */ + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_14MSET, 9, 0,NULL,1); + omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_14MSET | 0x100), 9, 0,NULL,1); + + /* 14MEND */ + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_14MEND, 9, 0,NULL,1); + + /* 3500KSET = d'69 */ + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_3500KSET, 9, 0,NULL,1); + omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_3500KSET | 0x100), 9, 0,NULL,1); + + /* 3500KEND */ + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_3500KEND, 9, 0,NULL,1); + + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_SLPOUT, 9, 0,NULL,1); + + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_VOLCTL, 9, 0,NULL,1); + omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_VOLCTL_Ton | 0x100), 9, 0,NULL,1); + + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_VOLCTL, 9, 0,NULL,1); + + omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_VOLCTL | 0x100), 9, 0,NULL,1); + + omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_DISON, 9, 0,NULL,1); + + /* enable backlight */ + omap_set_gpio_direction(134, 0); + omap_set_gpio_dataout(134, 1); + + return 0; +} + +static void p2_panel_disable(struct lcd_panel *panel) +{ +} + +static unsigned long p2_panel_get_caps(struct lcd_panel *panel) +{ + return 0; +} + +struct lcd_panel p2_panel = { + .name = "p2", + .config = OMAP_LCDC_PANEL_TFT | OMAP_LCDC_INV_PIX_CLOCK, + + .bpp = 16, + .data_lines = 16, + .x_res = 176, + .y_res = 220, + .pixel_clock = 12500, + .hsw = 5, + .hfp = 1, + .hbp = 1, + .vsw = 2, + .vfp = 12, + .vbp = 1, + + .init = p2_panel_init, + .cleanup = p2_panel_cleanup, + .enable = p2_panel_enable, + .disable = p2_panel_disable, + .get_caps = p2_panel_get_caps, +}; + +static int p2_panel_probe(struct platform_device *pdev) +{ + omapfb_register_panel(&p2_panel); + return 0; +} + +static int p2_panel_remove(struct platform_device *pdev) +{ + return 0; +} + +static int p2_panel_suspend(struct platform_device *pdev, pm_message_t mesg) +{ + return 0; +} + +static int p2_panel_resume(struct platform_device *pdev) +{ + return 0; +} + +struct platform_driver p2_panel_driver = { + .probe = p2_panel_probe, + .remove = p2_panel_remove, + .suspend = p2_panel_suspend, + .resume = p2_panel_resume, + .driver = { + .name = "lcd_p2", + .owner = THIS_MODULE, + }, +}; + +static int p2_panel_drv_init(void) +{ + return platform_driver_register(&p2_panel_driver); +} + +static void p2_panel_drv_cleanup(void) +{ + platform_driver_unregister(&p2_panel_driver); +} + +module_init(p2_panel_drv_init); +module_exit(p2_panel_drv_cleanup); + diff --cc drivers/w1/masters/omap_hdq.c index 8cb6aa38d64,00000000000..880e282b75d mode 100644,000000..100644 --- a/drivers/w1/masters/omap_hdq.c +++ b/drivers/w1/masters/omap_hdq.c @@@ -1,704 -1,0 +1,704 @@@ +/* + * drivers/w1/masters/omap_hdq.c + * + * Copyright (C) 2007 Texas Instruments, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include - #include ++#include + +#include "../w1.h" +#include "../w1_int.h" + +#define MOD_NAME "OMAP_HDQ:" + +#define OMAP_HDQ_REVISION 0x00 +#define OMAP_HDQ_TX_DATA 0x04 +#define OMAP_HDQ_RX_DATA 0x08 +#define OMAP_HDQ_CTRL_STATUS 0x0c +#define OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK (1<<6) +#define OMAP_HDQ_CTRL_STATUS_CLOCKENABLE (1<<5) +#define OMAP_HDQ_CTRL_STATUS_GO (1<<4) +#define OMAP_HDQ_CTRL_STATUS_INITIALIZATION (1<<2) +#define OMAP_HDQ_CTRL_STATUS_DIR (1<<1) +#define OMAP_HDQ_CTRL_STATUS_MODE (1<<0) +#define OMAP_HDQ_INT_STATUS 0x10 +#define OMAP_HDQ_INT_STATUS_TXCOMPLETE (1<<2) +#define OMAP_HDQ_INT_STATUS_RXCOMPLETE (1<<1) +#define OMAP_HDQ_INT_STATUS_TIMEOUT (1<<0) +#define OMAP_HDQ_SYSCONFIG 0x14 +#define OMAP_HDQ_SYSCONFIG_SOFTRESET (1<<1) +#define OMAP_HDQ_SYSCONFIG_AUTOIDLE (1<<0) +#define OMAP_HDQ_SYSSTATUS 0x18 +#define OMAP_HDQ_SYSSTATUS_RESETDONE (1<<0) + +#define OMAP_HDQ_FLAG_CLEAR 0 +#define OMAP_HDQ_FLAG_SET 1 +#define OMAP_HDQ_TIMEOUT (HZ/5) + +#define OMAP_HDQ_MAX_USER 4 + +DECLARE_WAIT_QUEUE_HEAD(hdq_wait_queue); +int W1_ID; + +struct hdq_data { + resource_size_t hdq_base; + struct semaphore hdq_semlock; + int hdq_usecount; + struct clk *hdq_ick; + struct clk *hdq_fck; + u8 hdq_irqstatus; + spinlock_t hdq_spinlock; +}; + +static struct hdq_data *hdq_data; + +static int omap_hdq_get(void); +static int omap_hdq_put(void); +static int omap_hdq_break(void); + +static int __init omap_hdq_probe(struct platform_device *pdev); +static int omap_hdq_remove(struct platform_device *pdev); + +static struct platform_driver omap_hdq_driver = { + .probe = omap_hdq_probe, + .remove = omap_hdq_remove, + .suspend = NULL, + .resume = NULL, + .driver = { + .name = "omap_hdq", + }, +}; + +static u8 omap_w1_read_byte(void *data); +static void omap_w1_write_byte(void *data, u8 byte); +static u8 omap_w1_reset_bus(void *data); +static void omap_w1_search_bus(void *data, u8 search_type, + w1_slave_found_callback slave_found); + +static struct w1_bus_master omap_w1_master = { + .read_byte = omap_w1_read_byte, + .write_byte = omap_w1_write_byte, + .reset_bus = omap_w1_reset_bus, + .search = omap_w1_search_bus, +}; + +/* + * HDQ register I/O routines + */ +static inline u8 +hdq_reg_in(u32 offset) +{ + return omap_readb(hdq_data->hdq_base + offset); +} + +static inline u8 +hdq_reg_out(u32 offset, u8 val) +{ + omap_writeb(val, hdq_data->hdq_base + offset); + return val; +} + +static inline u8 +hdq_reg_merge(u32 offset, u8 val, u8 mask) +{ + u8 new_val = (omap_readb(hdq_data->hdq_base + offset) & ~mask) + | (val & mask); + omap_writeb(new_val, hdq_data->hdq_base + offset); + return new_val; +} + +/* + * Wait for one or more bits in flag change. + * HDQ_FLAG_SET: wait until any bit in the flag is set. + * HDQ_FLAG_CLEAR: wait until all bits in the flag are cleared. + * return 0 on success and -ETIMEDOUT in the case of timeout. + */ +static int +hdq_wait_for_flag(u32 offset, u8 flag, u8 flag_set, u8 *status) +{ + int ret = 0; + unsigned long timeout = jiffies + OMAP_HDQ_TIMEOUT; + + if (flag_set == OMAP_HDQ_FLAG_CLEAR) { + /* wait for the flag clear */ + while (((*status = hdq_reg_in(offset)) & flag) + && time_before(jiffies, timeout)) { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(1); + } + if (unlikely(*status & flag)) + ret = -ETIMEDOUT; + } else if (flag_set == OMAP_HDQ_FLAG_SET) { + /* wait for the flag set */ + while (!((*status = hdq_reg_in(offset)) & flag) + && time_before(jiffies, timeout)) { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(1); + } + if (unlikely(!(*status & flag))) + ret = -ETIMEDOUT; + } else + return -EINVAL; + + return ret; +} + +/* + * write out a byte and fill *status with HDQ_INT_STATUS + */ +static int +hdq_write_byte(u8 val, u8 *status) +{ + int ret; + u8 tmp_status; + unsigned long irqflags; + + *status = 0; + + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags); + /* clear interrupt flags via a dummy read */ + hdq_reg_in(OMAP_HDQ_INT_STATUS); + /* ISR loads it with new INT_STATUS */ + hdq_data->hdq_irqstatus = 0; + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags); + + hdq_reg_out(OMAP_HDQ_TX_DATA, val); + + /* set the GO bit */ + hdq_reg_merge(OMAP_HDQ_CTRL_STATUS, OMAP_HDQ_CTRL_STATUS_GO, + OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_GO); + /* wait for the TXCOMPLETE bit */ + ret = wait_event_interruptible_timeout(hdq_wait_queue, + hdq_data->hdq_irqstatus, OMAP_HDQ_TIMEOUT); + if (unlikely(ret < 0)) { + pr_debug("wait interrupted"); + return -EINTR; + } + + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags); + *status = hdq_data->hdq_irqstatus; + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags); + /* check irqstatus */ + if (!(*status & OMAP_HDQ_INT_STATUS_TXCOMPLETE)) { + pr_debug("timeout waiting for TXCOMPLETE/RXCOMPLETE, %x", + *status); + return -ETIMEDOUT; + } + + /* wait for the GO bit return to zero */ + ret = hdq_wait_for_flag(OMAP_HDQ_CTRL_STATUS, OMAP_HDQ_CTRL_STATUS_GO, + OMAP_HDQ_FLAG_CLEAR, &tmp_status); + if (ret) { + pr_debug("timeout waiting GO bit return to zero, %x", + tmp_status); + return ret; + } + + return ret; +} + +/* + * HDQ Interrupt service routine. + */ +static irqreturn_t +hdq_isr(int irq, void *arg) +{ + unsigned long irqflags; + + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags); + hdq_data->hdq_irqstatus = hdq_reg_in(OMAP_HDQ_INT_STATUS); + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags); + pr_debug("hdq_isr: %x", hdq_data->hdq_irqstatus); + + if (hdq_data->hdq_irqstatus & + (OMAP_HDQ_INT_STATUS_TXCOMPLETE | OMAP_HDQ_INT_STATUS_RXCOMPLETE + | OMAP_HDQ_INT_STATUS_TIMEOUT)) { + /* wake up sleeping process */ + wake_up_interruptible(&hdq_wait_queue); + } + + return IRQ_HANDLED; +} + +/* + * HDQ Mode: always return success. + */ +static u8 omap_w1_reset_bus(void *data) +{ + return 0; +} + +/* + * W1 search callback function. + */ +static void omap_w1_search_bus(void *data, u8 search_type, + w1_slave_found_callback slave_found) +{ + u64 module_id, rn_le, cs, id; + + if (W1_ID) + module_id = W1_ID; + else + module_id = 0x1; + + rn_le = cpu_to_le64(module_id); + /* + * HDQ might not obey truly the 1-wire spec. + * So calculate CRC based on module parameter. + */ + cs = w1_calc_crc8((u8 *)&rn_le, 7); + id = (cs << 56) | module_id; + + slave_found(data, id); +} + +static int +_omap_hdq_reset(void) +{ + int ret; + u8 tmp_status; + + hdq_reg_out(OMAP_HDQ_SYSCONFIG, OMAP_HDQ_SYSCONFIG_SOFTRESET); + /* + * Select HDQ mode & enable clocks. + * It is observed that INT flags can't be cleared via a read and GO/INIT + * won't return to zero if interrupt is disabled. So we always enable + * interrupt. + */ + hdq_reg_out(OMAP_HDQ_CTRL_STATUS, + OMAP_HDQ_CTRL_STATUS_CLOCKENABLE | + OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK); + + /* wait for reset to complete */ + ret = hdq_wait_for_flag(OMAP_HDQ_SYSSTATUS, + OMAP_HDQ_SYSSTATUS_RESETDONE, OMAP_HDQ_FLAG_SET, &tmp_status); + if (ret) + pr_debug("timeout waiting HDQ reset, %x", tmp_status); + else { + hdq_reg_out(OMAP_HDQ_CTRL_STATUS, + OMAP_HDQ_CTRL_STATUS_CLOCKENABLE | + OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK); + hdq_reg_out(OMAP_HDQ_SYSCONFIG, OMAP_HDQ_SYSCONFIG_AUTOIDLE); + } + + return ret; +} + +/* + * Issue break pulse to the device. + */ +static int +omap_hdq_break() +{ + int ret; + u8 tmp_status; + unsigned long irqflags; + + ret = down_interruptible(&hdq_data->hdq_semlock); + if (ret < 0) + return -EINTR; + + if (!hdq_data->hdq_usecount) { + up(&hdq_data->hdq_semlock); + return -EINVAL; + } + + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags); + /* clear interrupt flags via a dummy read */ + hdq_reg_in(OMAP_HDQ_INT_STATUS); + /* ISR loads it with new INT_STATUS */ + hdq_data->hdq_irqstatus = 0; + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags); + + /* set the INIT and GO bit */ + hdq_reg_merge(OMAP_HDQ_CTRL_STATUS, + OMAP_HDQ_CTRL_STATUS_INITIALIZATION | OMAP_HDQ_CTRL_STATUS_GO, + OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_INITIALIZATION | + OMAP_HDQ_CTRL_STATUS_GO); + + /* wait for the TIMEOUT bit */ + ret = wait_event_interruptible_timeout(hdq_wait_queue, + hdq_data->hdq_irqstatus, OMAP_HDQ_TIMEOUT); + if (unlikely(ret < 0)) { + pr_debug("wait interrupted"); + up(&hdq_data->hdq_semlock); + return -EINTR; + } + + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags); + tmp_status = hdq_data->hdq_irqstatus; + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags); + /* check irqstatus */ + if (!(tmp_status & OMAP_HDQ_INT_STATUS_TIMEOUT)) { + pr_debug("timeout waiting for TIMEOUT, %x", tmp_status); + up(&hdq_data->hdq_semlock); + return -ETIMEDOUT; + } + /* + * wait for both INIT and GO bits rerurn to zero. + * zero wait time expected for interrupt mode. + */ + ret = hdq_wait_for_flag(OMAP_HDQ_CTRL_STATUS, + OMAP_HDQ_CTRL_STATUS_INITIALIZATION | + OMAP_HDQ_CTRL_STATUS_GO, OMAP_HDQ_FLAG_CLEAR, + &tmp_status); + if (ret) + pr_debug("timeout waiting INIT&GO bits return to zero, %x", + tmp_status); + + up(&hdq_data->hdq_semlock); + return ret; +} + +static int hdq_read_byte(u8 *val) +{ + int ret; + u8 status; + unsigned long irqflags; + + ret = down_interruptible(&hdq_data->hdq_semlock); + if (ret < 0) + return -EINTR; + + if (!hdq_data->hdq_usecount) { + up(&hdq_data->hdq_semlock); + return -EINVAL; + } + + if (!(hdq_data->hdq_irqstatus & OMAP_HDQ_INT_STATUS_RXCOMPLETE)) { + hdq_reg_merge(OMAP_HDQ_CTRL_STATUS, + OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_GO, + OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_GO); + /* + * The RX comes immediately after TX. It + * triggers another interrupt before we + * sleep. So we have to wait for RXCOMPLETE bit. + */ + { + unsigned long timeout = jiffies + OMAP_HDQ_TIMEOUT; + while (!(hdq_data->hdq_irqstatus + & OMAP_HDQ_INT_STATUS_RXCOMPLETE) + && time_before(jiffies, timeout)) { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(1); + } + } + hdq_reg_merge(OMAP_HDQ_CTRL_STATUS, 0, + OMAP_HDQ_CTRL_STATUS_DIR); + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags); + status = hdq_data->hdq_irqstatus; + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags); + /* check irqstatus */ + if (!(status & OMAP_HDQ_INT_STATUS_RXCOMPLETE)) { + pr_debug("timeout waiting for RXCOMPLETE, %x", status); + up(&hdq_data->hdq_semlock); + return -ETIMEDOUT; + } + } + /* the data is ready. Read it in! */ + *val = hdq_reg_in(OMAP_HDQ_RX_DATA); + up(&hdq_data->hdq_semlock); + + return 0; + +} + +/* + * Enable clocks and set the controller to HDQ mode. + */ +static int +omap_hdq_get() +{ + int ret = 0; + + ret = down_interruptible(&hdq_data->hdq_semlock); + if (ret < 0) + return -EINTR; + + if (OMAP_HDQ_MAX_USER == hdq_data->hdq_usecount) { + pr_debug("attempt to exceed the max use count"); + up(&hdq_data->hdq_semlock); + ret = -EINVAL; + } else { + hdq_data->hdq_usecount++; + try_module_get(THIS_MODULE); + if (1 == hdq_data->hdq_usecount) { + if (clk_enable(hdq_data->hdq_ick)) { + pr_debug("Can not enable ick\n"); + clk_put(hdq_data->hdq_ick); + clk_put(hdq_data->hdq_fck); + up(&hdq_data->hdq_semlock); + return -ENODEV; + } + if (clk_enable(hdq_data->hdq_fck)) { + pr_debug("Can not enable fck\n"); + clk_put(hdq_data->hdq_ick); + clk_put(hdq_data->hdq_fck); + up(&hdq_data->hdq_semlock); + return -ENODEV; + } + + /* make sure HDQ is out of reset */ + if (!(hdq_reg_in(OMAP_HDQ_SYSSTATUS) & + OMAP_HDQ_SYSSTATUS_RESETDONE)) { + ret = _omap_hdq_reset(); + if (ret) + /* back up the count */ + hdq_data->hdq_usecount--; + } else { + /* select HDQ mode & enable clocks */ + hdq_reg_out(OMAP_HDQ_CTRL_STATUS, + OMAP_HDQ_CTRL_STATUS_CLOCKENABLE | + OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK); + hdq_reg_out(OMAP_HDQ_SYSCONFIG, + OMAP_HDQ_SYSCONFIG_AUTOIDLE); + hdq_reg_in(OMAP_HDQ_INT_STATUS); + } + } + } + up(&hdq_data->hdq_semlock); + return ret; +} + +/* + * Disable clocks to the module. + */ +static int +omap_hdq_put() +{ + int ret = 0; + + ret = down_interruptible(&hdq_data->hdq_semlock); + if (ret < 0) + return -EINTR; + + if (0 == hdq_data->hdq_usecount) { + pr_debug("attempt to decrement use count when it is zero"); + ret = -EINVAL; + } else { + hdq_data->hdq_usecount--; + module_put(THIS_MODULE); + if (0 == hdq_data->hdq_usecount) { + clk_disable(hdq_data->hdq_ick); + clk_disable(hdq_data->hdq_fck); + } + } + up(&hdq_data->hdq_semlock); + return ret; +} + +/* + * Used to control the call to omap_hdq_get and omap_hdq_put. + * HDQ Protocol: Write the CMD|REG_address first, followed by + * the data wrire or read. + */ +static int init_trans; + +/* + * Read a byte of data from the device. + */ +static u8 omap_w1_read_byte(void *data) +{ + u8 val; + int ret; + + ret = hdq_read_byte(&val); + if (ret) { + init_trans = 0; + omap_hdq_put(); + return -1; + } + + /* Write followed by a read, release the module */ + if (init_trans) { + init_trans = 0; + omap_hdq_put(); + } + + return val; +} + +/* + * Write a byte of data to the device. + */ +static void omap_w1_write_byte(void *data, u8 byte) +{ + u8 status; + + /* First write to initialize the transfer */ + if (init_trans == 0) + omap_hdq_get(); + + init_trans++; + + hdq_write_byte(byte, &status); + pr_debug("Ctrl status %x\n", status); + + /* Second write, data transfered. Release the module */ + if (init_trans > 1) { + omap_hdq_put(); + init_trans = 0; + } + + return; +} + +static int __init omap_hdq_probe(struct platform_device *pdev) +{ + struct resource *res; + int ret, irq; + u8 rev; + + if (!pdev) + return -ENODEV; + + hdq_data = kmalloc(sizeof(*hdq_data), GFP_KERNEL); + if (!hdq_data) + return -ENODEV; + + platform_set_drvdata(pdev, hdq_data); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + platform_set_drvdata(pdev, NULL); + kfree(hdq_data); + return -ENXIO; + } + + hdq_data->hdq_base = res->start; + + /* get interface & functional clock objects */ + hdq_data->hdq_ick = clk_get(&pdev->dev, "hdq_ick"); + hdq_data->hdq_fck = clk_get(&pdev->dev, "hdq_fck"); + + if (IS_ERR(hdq_data->hdq_ick) || IS_ERR(hdq_data->hdq_fck)) { + pr_debug("Can't get HDQ clock objects\n"); + if (IS_ERR(hdq_data->hdq_ick)) { + ret = PTR_ERR(hdq_data->hdq_ick); + platform_set_drvdata(pdev, NULL); + kfree(hdq_data); + return ret; + } + if (IS_ERR(hdq_data->hdq_fck)) { + ret = PTR_ERR(hdq_data->hdq_fck); + platform_set_drvdata(pdev, NULL); + kfree(hdq_data); + return ret; + } + } + + hdq_data->hdq_usecount = 0; + sema_init(&hdq_data->hdq_semlock, 1); + + if (clk_enable(hdq_data->hdq_ick)) { + pr_debug("Can not enable ick\n"); + clk_put(hdq_data->hdq_ick); + clk_put(hdq_data->hdq_fck); + platform_set_drvdata(pdev, NULL); + kfree(hdq_data); + return -ENODEV; + } + + if (clk_enable(hdq_data->hdq_fck)) { + pr_debug("Can not enable fck\n"); + clk_disable(hdq_data->hdq_ick); + clk_put(hdq_data->hdq_ick); + clk_put(hdq_data->hdq_fck); + platform_set_drvdata(pdev, NULL); + kfree(hdq_data); + return -ENODEV; + } + + rev = hdq_reg_in(OMAP_HDQ_REVISION); + pr_info("OMAP HDQ Hardware Revision %c.%c. Driver in %s mode.\n", + (rev >> 4) + '0', (rev & 0x0f) + '0', "Interrupt"); + + spin_lock_init(&hdq_data->hdq_spinlock); + omap_hdq_break(); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + platform_set_drvdata(pdev, NULL); + kfree(hdq_data); + return -ENXIO; + } + + if (request_irq(irq, hdq_isr, IRQF_DISABLED, "OMAP HDQ", + &hdq_data->hdq_semlock)) { + pr_debug("request_irq failed\n"); + clk_disable(hdq_data->hdq_ick); + clk_put(hdq_data->hdq_ick); + clk_put(hdq_data->hdq_fck); + platform_set_drvdata(pdev, NULL); + kfree(hdq_data); + return -ENODEV; + } + + /* don't clock the HDQ until it is needed */ + clk_disable(hdq_data->hdq_ick); + clk_disable(hdq_data->hdq_fck); + + ret = w1_add_master_device(&omap_w1_master); + if (ret) { + pr_debug("Failure in registering w1 master\n"); + clk_put(hdq_data->hdq_ick); + clk_put(hdq_data->hdq_fck); + platform_set_drvdata(pdev, NULL); + kfree(hdq_data); + return ret; + } + + return 0; +} + +static int omap_hdq_remove(struct platform_device *pdev) +{ + down_interruptible(&hdq_data->hdq_semlock); + if (0 != hdq_data->hdq_usecount) { + pr_debug("removed when use count is not zero\n"); + return -EBUSY; + } + up(&hdq_data->hdq_semlock); + + /* remove module dependency */ + clk_put(hdq_data->hdq_ick); + clk_put(hdq_data->hdq_fck); + free_irq(INT_24XX_HDQ_IRQ, &hdq_data->hdq_semlock); + platform_set_drvdata(pdev, NULL); + kfree(hdq_data); + + return 0; +} + +static int __init +omap_hdq_init(void) +{ + return platform_driver_register(&omap_hdq_driver); +} + +static void __exit +omap_hdq_exit(void) +{ + platform_driver_unregister(&omap_hdq_driver); +} + +module_init(omap_hdq_init); +module_exit(omap_hdq_exit); + +module_param(W1_ID, int, S_IRUSR); + +MODULE_AUTHOR("Texas Instruments"); +MODULE_DESCRIPTION("HDQ driver Library"); +MODULE_LICENSE("GPL"); diff --cc drivers/watchdog/omap_wdt.c index d4c7959cffd,3a11dadfd8e..b111dec8059 --- a/drivers/watchdog/omap_wdt.c +++ b/drivers/watchdog/omap_wdt.c @@@ -39,13 -39,11 +39,13 @@@ #include #include #include -#include -#include -#include + +#include +#include - #include + #include +#include + - #include + #include #include "omap_wdt.h" diff --cc include/linux/i2c-id.h index be88882cf7f,bf34c5f4c05..3ae0f766013 --- a/include/linux/i2c-id.h +++ b/include/linux/i2c-id.h @@@ -39,11 -38,7 +39,10 @@@ #define I2C_DRIVERID_TDA9840 7 /* stereo sound processor */ #define I2C_DRIVERID_SAA7111A 8 /* video input processor */ #define I2C_DRIVERID_SAA7185B 13 /* video encoder */ +#define I2C_DRIVERID_TEA6300 18 /* audio mixer */ +#define I2C_DRIVERID_TDA9850 20 /* audio mixer */ +#define I2C_DRIVERID_TDA9855 21 /* audio mixer */ #define I2C_DRIVERID_SAA7110 22 /* video decoder */ - #define I2C_DRIVERID_MGATVO 23 /* Matrox TVOut */ #define I2C_DRIVERID_SAA5249 24 /* SAA5249 and compatibles */ #define I2C_DRIVERID_PCF8583 25 /* real time clock */ #define I2C_DRIVERID_SAB3036 26 /* SAB3036 tuner */ diff --cc include/linux/i2c/twl4030.h index b141cc73c23,00000000000..00e14fc37f4 mode 100644,000000..100644 --- a/include/linux/i2c/twl4030.h +++ b/include/linux/i2c/twl4030.h @@@ -1,137 -1,0 +1,137 @@@ +/* + * twl4030.h - header for TWL4030 PM and audio CODEC device + * + * Copyright (C) 2005-2006 Texas Instruments, Inc. + * + * Based on tlv320aic23.c: + * Copyright (c) by Kai Svahn + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __TWL4030_H_ +#define __TWL4030_H_ + +/* USB ID */ +#define TWL4030_MODULE_USB 0x00 +/* AUD ID */ +#define TWL4030_MODULE_AUDIO_VOICE 0x01 +#define TWL4030_MODULE_GPIO 0x02 +#define TWL4030_MODULE_INTBR 0x03 +#define TWL4030_MODULE_PIH 0x04 +#define TWL4030_MODULE_TEST 0x05 +/* AUX ID */ +#define TWL4030_MODULE_KEYPAD 0x06 +#define TWL4030_MODULE_MADC 0x07 +#define TWL4030_MODULE_INTERRUPTS 0x08 +#define TWL4030_MODULE_LED 0x09 +#define TWL4030_MODULE_MAIN_CHARGE 0x0A +#define TWL4030_MODULE_PRECHARGE 0x0B +#define TWL4030_MODULE_PWM0 0x0C +#define TWL4030_MODULE_PWM1 0x0D +#define TWL4030_MODULE_PWMA 0x0E +#define TWL4030_MODULE_PWMB 0x0F +/* POWER ID */ +#define TWL4030_MODULE_BACKUP 0x10 +#define TWL4030_MODULE_INT 0x11 +#define TWL4030_MODULE_PM_MASTER 0x12 +#define TWL4030_MODULE_PM_RECEIVER 0x13 +#define TWL4030_MODULE_RTC 0x14 +#define TWL4030_MODULE_SECURED_REG 0x15 + +/* IRQ information-need base */ - #include ++#include +/* TWL4030 interrupts */ + +#define TWL4030_MODIRQ_GPIO (TWL4030_IRQ_BASE + 0) +#define TWL4030_MODIRQ_KEYPAD (TWL4030_IRQ_BASE + 1) +#define TWL4030_MODIRQ_BCI (TWL4030_IRQ_BASE + 2) +#define TWL4030_MODIRQ_MADC (TWL4030_IRQ_BASE + 3) +#define TWL4030_MODIRQ_USB (TWL4030_IRQ_BASE + 4) +#define TWL4030_MODIRQ_PWR (TWL4030_IRQ_BASE + 5) + +#define TWL4030_PWRIRQ_PWRBTN (TWL4030_PWR_IRQ_BASE + 0) +#define TWL4030_PWRIRQ_CHG_PRES (TWL4030_PWR_IRQ_BASE + 1) +#define TWL4030_PWRIRQ_USB_PRES (TWL4030_PWR_IRQ_BASE + 2) +#define TWL4030_PWRIRQ_RTC (TWL4030_PWR_IRQ_BASE + 3) +#define TWL4030_PWRIRQ_HOT_DIE (TWL4030_PWR_IRQ_BASE + 4) +#define TWL4030_PWRIRQ_PWROK_TIMEOUT (TWL4030_PWR_IRQ_BASE + 5) +#define TWL4030_PWRIRQ_MBCHG (TWL4030_PWR_IRQ_BASE + 6) +#define TWL4030_PWRIRQ_SC_DETECT (TWL4030_PWR_IRQ_BASE + 7) + +/* Rest are unsued currently*/ + +/* Offsets to Power Registers */ +#define TWL4030_VDAC_DEV_GRP 0x3B +#define TWL4030_VDAC_DEDICATED 0x3E +#define TWL4030_VAUX1_DEV_GRP 0x17 +#define TWL4030_VAUX1_DEDICATED 0x1A +#define TWL4030_VAUX2_DEV_GRP 0x1B +#define TWL4030_VAUX2_DEDICATED 0x1E +#define TWL4030_VAUX3_DEV_GRP 0x1F +#define TWL4030_VAUX3_DEDICATED 0x22 + +/* TWL4030 GPIO interrupt definitions */ + +#define TWL4030_GPIO_MIN 0 +#define TWL4030_GPIO_MAX 18 +#define TWL4030_GPIO_MAX_CD 2 +#define TWL4030_GPIO_IRQ_NO(n) (TWL4030_GPIO_IRQ_BASE + (n)) +#define TWL4030_GPIO_IS_INPUT 1 +#define TWL4030_GPIO_IS_OUTPUT 0 +#define TWL4030_GPIO_IS_ENABLE 1 +#define TWL4030_GPIO_IS_DISABLE 0 +#define TWL4030_GPIO_PULL_UP 0 +#define TWL4030_GPIO_PULL_DOWN 1 +#define TWL4030_GPIO_PULL_NONE 2 +#define TWL4030_GPIO_EDGE_NONE 0 +#define TWL4030_GPIO_EDGE_RISING 1 +#define TWL4030_GPIO_EDGE_FALLING 2 + +/* Functions to read and write from TWL4030 */ + +/* + * IMP NOTE: + * The base address of the module will be added by the triton driver + * It is the caller's responsibility to ensure sane values + */ +int twl4030_i2c_write_u8(u8 mod_no, u8 val, u8 reg); +int twl4030_i2c_read_u8(u8 mod_no, u8 *val, u8 reg); + + /* + * i2c_write: IMPORTANT - Allocate value num_bytes+1 and valid data starts at + * Offset 1. + */ +int twl4030_i2c_write(u8 mod_no, u8 *value, u8 reg, u8 num_bytes); +int twl4030_i2c_read(u8 mod_no, u8 *value, u8 reg, u8 num_bytes); + +/* + * Exported TWL4030 GPIO APIs + */ +int twl4030_get_gpio_datain(int gpio); +int twl4030_request_gpio(int gpio); +int twl4030_set_gpio_edge_ctrl(int gpio, int edge); +int twl4030_set_gpio_debounce(int gpio, int enable); +int twl4030_free_gpio(int gpio); + +#if defined(CONFIG_TWL4030_BCI_BATTERY) || \ + defined(CONFIG_TWL4030_BCI_BATTERY_MODULE) + extern int twl4030charger_usb_en(int enable); +#else + static inline int twl4030charger_usb_en(int enable) { return 0; } +#endif + +#endif /* End of __TWL4030_H */ diff --cc sound/arm/omap/eac.c index 6ae2677d653,00000000000..9fe8d7435c1 mode 100644,000000..100644 --- a/sound/arm/omap/eac.c +++ b/sound/arm/omap/eac.c @@@ -1,801 -1,0 +1,801 @@@ +/* + * linux/sound/arm/omap/omap-alsa-eac.c + * + * OMAP24xx Enhanced Audio Controller sound driver + * + * Copyright (C) 2006 Nokia Corporation + * + * Contact: Jarkko Nikula + * Juha Yrjölä + * + * Definitions: + * Copyright (C) 2004 Texas Instruments, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#define DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include - #include ++#include + +#include +#include + + +#define EAC_CPCFR1 0x0000 +#define EAC_CPCFR2 0x0004 +#define EAC_CPCFR3 0x0008 +#define EAC_CPCFR4 0x000C +#define EAC_CPTCTL 0x0010 +#define EAC_CPTTADR 0x0014 +#define EAC_CPTDATL 0x0018 +#define EAC_CPTDATH 0x001C +#define EAC_CPTVSLL 0x0020 +#define EAC_CPTVSLH 0x0024 +#define EAC_MPCTR 0x0040 +#define EAC_MPMCCFR 0x0044 +#define EAC_BPCTR 0x0060 +#define EAC_BPMCCFR 0x0064 +#define EAC_AMSCFR 0x0080 +#define EAC_AMVCTR 0x0084 +#define EAC_AM1VCTR 0x0088 +#define EAC_AM2VCTR 0x008C +#define EAC_AM3VCTR 0x0090 +#define EAC_ASTCTR 0x0094 +#define EAC_APD1LCR 0x0098 +#define EAC_APD1RCR 0x009C +#define EAC_APD2LCR 0x00A0 +#define EAC_APD2RCR 0x00A4 +#define EAC_APD3LCR 0x00A8 +#define EAC_APD3RCR 0x00AC +#define EAC_APD4R 0x00B0 +#define EAC_ADWR 0x00B4 +#define EAC_ADRDR 0x00B8 +#define EAC_AGCFR 0x00BC +#define EAC_AGCTR 0x00C0 +#define EAC_AGCFR2 0x00C4 +#define EAC_AGCFR3 0x00C8 +#define EAC_MBPDMACTR 0x00CC +#define EAC_MPDDMARR 0x00D0 +#define EAC_MPDDMAWR 0x00D4 +#define EAC_MPUDMARR 0x00D8 +#define EAC_MPUDMAWR 0x00E0 +#define EAC_BPDDMARR 0x00E4 +#define EAC_BPDDMAWR 0x00E8 +#define EAC_BPUDMARR 0x00EC +#define EAC_BPUDMAWR 0x00F0 +#define EAC_VERSION 0x0100 +#define EAC_SYSCONFIG 0x0104 +#define EAC_SYSSTATUS 0x0108 + +/* CPTCTL */ +#define CPTCTL_RXF (1 << 7) /* receive data register full */ +#define CPTCTL_RXIE (1 << 6) /* receive interrupt enable */ +#define CPTCTL_TXE (1 << 5) /* transmit register empty */ +#define CPTCTL_TXIE (1 << 4) /* transmit interrupt enable */ +#define CPTCTL_CPEN (1 << 3) /* codec port enable */ +#define CPTCTL_CRST (1 << 0) /* external codec reset */ + +/* CPCFR1 */ +#define CPCFR1_MTSL(val) ((val & 0x1f) << 3) /* number of time slots per frame */ +#define CPCFR1_MTSL_BITS (0x1f << 3) +#define CPCFR1_MODE(val) ((val & 0x7) << 0) /* codec port interface mode */ +#define CPCFR1_MODE_BITS (0x7 << 0) + +/* CPCFR2 */ +#define CPCFR2_TSLOL(val) ((val & 0x3) << 6) /* time slot 0 length in number of serial clock (CLK_BIT) cycles */ +#define CPCFR2_TSLOL_BITS (0x3 << 6) +#define CPCFR2_BPTSL(val) ((val & 0x7) << 3) /* number of data bits per audio time slot */ +#define CPCFR2_BPTSL_BITS (0x7 << 3) +#define CPCFR2_TSLL(val) ((val & 0x7) << 0) /* time slot lenght (except slot 0) in number of serial clock cycles */ +#define CPCFR2_TSLL_BITS (0x7 << 0) + +/* CPCFR3 */ +#define CPCFR3_DDLY (1 << 7) /* data delay: data bits start according to SYNC signal leading edge */ +#define CPCFR3_TRSEN (1 << 6) /* 3-state enable: data serial output state during nonvalid audio frames */ +#define CPCFR3_CLKBP (1 << 5) /* clock polarity */ +#define CPCFR3_CSYNCP (1 << 4) /* cp_sync(synchro) polarity */ +#define CPCFR3_CSYNCL (1 << 3) /* csync length */ +/* bit 2 reserved */ +#define CPCFR3_CSCLKD (1 << 1) /* cp_sclk port (serial clock) direction */ +#define CPCFR3_CSYNCD (1 << 0) /* cp_sync (synchro) direction */ + +/* CPCFR4 */ +#define CPCFR4_ATSL(val) ((val & 0xf) << 4) /* audio time slots for secondary communication address and data values */ +#define CPCFR4_ATSL_BITS (0xf << 4) +#define CPCFR4_CLKS (1 << 3) /* clock source */ +#define CPCFR4_DIVB(val) ((val & 0x7) << 0) /* cp_sclk driver value */ +#define CPCFR4_DIVB_BITS (0x7 << 0) + +/* AGCFR */ +#define AGCFR_MN_ST (1 << 10) /* mono/stereo audio file */ +#define AGCFR_B8_16 (1 << 9) /* 8 bits/16 bits audio file */ +#define AGCFR_LI_BI (1 << 8) /* audio file endianism */ +#define AGCFR_FSINT(val) ((val & 0x3) << 6) /* intermediate sample frequency for DMA read and write operations */ +#define AGCFR_FINST_BITS (0x3 << 6) + +#define AGCFR_FSINT_8000 (0) /* 8000 Hz */ +#define AGCFR_FSINT_11025 (1) /* 11025 Hz */ +#define AGCFR_FSINT_22050 (2) /* 22050 Hz */ +#define AGCFR_FSINT_44100 (3) /* 44100 Hz */ + +#define AGCFR_AUD_CKSRC(val)((val & 0x3) << 4) /* audio processing clock source */ +#define AGCFR_AUD_CKSRC_BITS (0x3 << 4) +#define AGCFR_M_CKSRC (1 << 3) /* modem interface clock source */ +#define AGCFR_MCLK_OUT (1 << 1) +#define AGCFR_MCLK (1 << 0) + + +/* AGCTR */ +#define AGCTR_AUDRD (1 << 15) /* audio ready */ +#define AGCTR_AUDRDI (1 << 14) /* audio ready interrupt status */ +#define AGCTR_AUDRDIEN (1 << 13) /* audio ready interrupt enable */ +#define AGCTR_DMAREN (1 << 12) /* audio files play operation */ +#define AGCTR_DMAWEN (1 << 11) /* audio file record operation */ +/* bits 10:4 reserved */ +#define AGCTR_MCLK_EN (1 << 3) /* internal MCLK enable */ +#define AGCTR_OSCMCLK_EN (1 << 2) /* OSCMCLK_EN output for MCLK oscillator control */ +#define AGCTR_AUDEN (1 << 1) /* audio processing enable/disable */ +#define AGCTR_EACPWD (1 << 0) /* EAC operation */ + +/* AGCFR2 */ +#define AGCFR2_BT_MD_WIDEBAND (1 << 5) /* the BT device and modem AuSPIs wide-band mode */ +#define AGCFR2_MCLK_I2S_N11M_12M (1 << 4) /* MCLK freq indicator for audio operations */ +#define AGCFR2_I2S_N44K_48K (1 << 3) /* Frame sample frecuency of I2S codec port, does not generate value */ +#define AGCFR2_FSINT2(val) ((val & 0x7) << 0) /* intermediate sample frequency for DMA channel read and write operations */ +#define AGCFR2_FSINT2_BITS (0x7 << 0) + +#define AGCFR2_FSINT2_8000 (0) /* 8000 Hz */ +#define AGCFR2_FSINT2_11025 (1) /* 11025 Hz */ +#define AGCFR2_FSINT2_22050 (2) /* 22050 Hz */ +#define AGCFR2_FSINT2_44100 (3) /* 44100 Hz */ +#define AGCFR2_FSINT2_48000 (4) /* 48000 Hz */ +#define AGCFR2_FSINT2_FSINT (7) /* based on AGCFR/FSINT */ + + +/* AGCFR3 */ +#define AGCFR3_CP_TR_DMA (1 << 15) /* codec port transparent DMA (to audio DMAs) */ +#define AGCFR3_BT_TR_DMA (1 << 14) /* BT transparent DMA (to BT UL write & DL read DMAs */ +#define AGCFR3_MD_TR_DMA (1 << 13) /* modem transparent DMA (to modem UL write and DL read DMAs) */ +#define AGCFR3_FSINT(val) ((val & 0xf) << 9) /* FSINT */ +#define AGCFR3_FSINT_BITS (0xf << 9) + +#define AGCFR3_FSINT_8000 (0) /* 8000 Hz */ +#define AGCFR3_FSINT_11025 (1) /* 11025 Hz */ +#define AGCFR3_FSINT_16000 (2) /* 16000 Hz */ +#define AGCFR3_FSINT_22050 (3) /* 22050 Hz */ +#define AGCFR3_FSINT_24000 (4) /* 24000 Hz */ +#define AGCFR3_FSINT_32000 (5) /* 32000 Hz */ +#define AGCFR3_FSINT_44100 (6) /* 44100 Hz */ +#define AGCFR3_FSINT_48000 (7) /* 48000 Hz */ +#define AGCFR3_FSINT_FSINT (15) /* based on AGCFR2/AGCFR */ + + +#define AGCFR3_BT_CKSRC(val) ((val & 0x3) << 7) /* BT port clock selection */ +#define AGCFR3_BT_CKSRC_BITS (0x3 << 7) +#define AGCFR3_MD_CKSRC(val) ((val & 0x3) << 5) /* modem port clock source */ +#define AGCFR3_MD_CKSRC_BITS (0x3 << 5) +#define AGCFR3_AUD_CKSRC(val) ((val & 0x7) << 2) /* audio and codec port clock source */ +#define AGCFR3_AUD_CKSRC_BITS (0x7 << 2) +#define AGCFR3_CLK12MINT_SEL (1 << 1) /* internal 12MHz clock source */ +#define AGCFR3_MCLKINT_SEL (1 << 0) /* internal codec master clock source */ + +/* AMSCFR */ +#define AMSCFR_K12 (1 << 11) /* K12 switch open/close */ +#define AMSCFR_K11 (1 << 10) +#define AMSCFR_K10 (1 << 9) +#define AMSCFR_K9 (1 << 8) +#define AMSCFR_K8 (1 << 7) +#define AMSCFR_K7 (1 << 6) +#define AMSCFR_K6 (1 << 5) +#define AMSCFR_K5 (1 << 4) +#define AMSCFR_K4 (1 << 3) +#define AMSCFR_K3 (1 << 2) +#define AMSCFR_K2 (1 << 1) +#define AMSCFR_K1 (1 << 0) + +/* AMVCTR */ +#define AMVCTR_GWO_BITS (0xff << 8) +#define AMVCTR_GWO(val) ((val & 0xff) << 8) /* Gain on write DMA operation */ +#define AMVCTR_GRO_BITS (0xff << 0) +#define AMVCTR_GRO(val) ((val & 0xff) << 0) /* Gain on read DMA operation */ + +/* AM1VCTR */ +#define AM1VCTR_MUTE (1 << 15) /* mute/no mute on mixer output */ +#define AM1VCTR_GINB(val) ((val & 0x7f) << 8) /* gain on input B */ +#define AM1VCTR_GINB_BITS (0x7f << 8) +#define AM1VCTR_GINA(val) ((val & 0x7f) << 0) /* gain on input A */ +#define AM1VCTR_GINA_BITS (0x7f << 0) + +/* AM2VCTR */ +#define AM2VCTR_MUTE (1 << 15) /* mute/no mute on mixer output */ +#define AM2VCTR_GINB(val) ((val & 0x7f) << 8) /* gain on input B */ +#define AM2VCTR_GINB_BITS (0x7f << 8) +#define AM2VCTR_GINA(val) ((val & 0x7f) << 0) /* gain on input A */ +#define AM2VCTR_GINA_BITS (0x7f << 0) + +/* AM3VCTR */ +#define AM3VCTR_MUTE (1 << 15) /* mute/no mute */ +#define AM3VCTR_GINB(val) ((val & 0x7f) << 8) /* gain on input B */ +#define AM3VCTR_GINB_BITS (0x7f << 8) +#define AM3VCTR_GINA(val) ((val & 0x7f) << 0) /* gain on input A */ +#define AM3VCTR_GINA_BITS (0x7f << 0) + +/* ASTCTR */ +#define ASTCTR_ATT(val) ((val & 0x7f) << 1) /* Attenuation of side tone */ +#define ASTCTR_ATT_BITS (0x7f << 1) +#define ASTCTR_ATTEN (1 << 0) /* side tone enabled/disabled */ + + +/* internal structure of the EAC driver */ +struct omap_eac { + struct mutex mutex; + void __iomem * base; + struct platform_device * pdev; + struct eac_platform_data * pdata; + struct snd_card * card; + struct clk * fck; + struct clk * ick; + struct eac_codec * codec; + + unsigned clocks_enabled:1; +}; + +static char *id = SNDRV_DEFAULT_STR1; +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for OMAP24xx EAC"); + + +#define MOD_REG_BIT(val, mask, set) do { \ + if (set) \ + val |= mask; \ + else \ + val &= ~mask; \ +} while(0) + +static inline void eac_write_reg(struct omap_eac *eac, int idx, u16 val) +{ + __raw_writew(val, eac->base + idx); +} + +static inline u16 eac_read_reg(struct omap_eac *eac, int idx) +{ + return __raw_readw(eac->base + idx); +} + +static int eac_get_clocks(struct omap_eac *eac) +{ + eac->ick = clk_get(NULL, "eac_ick"); + if (IS_ERR(eac->ick)) { + dev_err(&eac->pdev->dev, "Could not get eac_ick"); + return -ENODEV; + } + + eac->fck = clk_get(NULL, "eac_fck"); + if (IS_ERR(eac->fck)) { + dev_err(&eac->pdev->dev, "Could not get eac_fck"); + clk_put(eac->ick); + return -ENODEV; + } + + return 0; +} + +static void eac_put_clocks(struct omap_eac *eac) +{ + clk_put(eac->fck); + clk_put(eac->ick); +} + +static int eac_enable_clocks(struct omap_eac *eac) +{ + int err = 0; + + if (eac->clocks_enabled) + return 0; + + if (eac->pdata != NULL && eac->pdata->enable_ext_clocks != NULL) { + if ((err = eac->pdata->enable_ext_clocks(&eac->pdev->dev)) != 0) + return err; + } + clk_enable(eac->ick); + clk_enable(eac->fck); + eac->clocks_enabled = 1; + + return 0; +} + +static void eac_disable_clocks(struct omap_eac *eac) +{ + if (!eac->clocks_enabled) + return; + eac->clocks_enabled = 0; + + clk_disable(eac->fck); + clk_disable(eac->ick); + if (eac->pdata != NULL && eac->pdata->disable_ext_clocks != NULL) + eac->pdata->disable_ext_clocks(&eac->pdev->dev); +} + +static int eac_reset(struct omap_eac *eac) +{ + int i; + + /* step 1 (see TRM) */ + /* first, let's reset the EAC */ + eac_write_reg(eac, EAC_SYSCONFIG, 0x2); + /* step 2 (see TRM) */ + eac_write_reg(eac, EAC_AGCTR, AGCTR_MCLK_EN | AGCTR_AUDEN); + /* step 3 (see TRM) */ + /* wait until reset done */ + i = 10000; + while (!(eac_read_reg(eac, EAC_SYSSTATUS) & 1)) { + if (--i == 0) + return -ENODEV; + udelay(1); + } + + return 0; +} + +static int eac_calc_agcfr3_fsint(int rate) +{ + int fsint; + + if (rate >= 48000) + fsint = AGCFR3_FSINT_48000; + else if (rate >= 44100) + fsint = AGCFR3_FSINT_44100; + else if (rate >= 32000) + fsint = AGCFR3_FSINT_32000; + else if (rate >= 24000) + fsint = AGCFR3_FSINT_24000; + else if (rate >= 22050) + fsint = AGCFR3_FSINT_22050; + else if (rate >= 16000) + fsint = AGCFR3_FSINT_16000; + else if (rate >= 11025) + fsint = AGCFR3_FSINT_11025; + else + fsint = AGCFR3_FSINT_8000; + + return fsint; +} + +static int eac_configure_pcm(struct omap_eac *eac, struct eac_codec *conf) +{ + dev_err(&eac->pdev->dev, + "EAC codec port configuration for PCM not implemented\n"); + + return -ENODEV; +} + +static int eac_configure_ac97(struct omap_eac *eac, struct eac_codec *conf) +{ + dev_err(&eac->pdev->dev, + "EAC codec port configuration for AC97 not implemented\n"); + + return -ENODEV; +} + +static int eac_configure_i2s(struct omap_eac *eac, struct eac_codec *conf) +{ + u16 cpcfr1, cpcfr2, cpcfr3, cpcfr4; + + cpcfr1 = eac_read_reg(eac, EAC_CPCFR1); + cpcfr2 = eac_read_reg(eac, EAC_CPCFR2); + cpcfr3 = eac_read_reg(eac, EAC_CPCFR3); + cpcfr4 = eac_read_reg(eac, EAC_CPCFR4); + + cpcfr1 &= ~(CPCFR1_MODE_BITS | CPCFR1_MTSL_BITS); + cpcfr1 |= CPCFR1_MTSL(1); /* 2 timeslots per frame (I2S default) */ + + /* audio time slot configuration for I2S mode */ + cpcfr2 &= ~(CPCFR2_TSLL_BITS | CPCFR2_BPTSL_BITS | CPCFR2_TSLOL_BITS); + cpcfr2 |= CPCFR2_TSLOL(0); /* time slot 0 length same as TSLL */ + cpcfr2 |= CPCFR2_BPTSL(1); /* 16 data bits per time slot */ + cpcfr2 |= CPCFR2_TSLL(1); /* time slot length 16 serial clock cycles */ + + /* I2S link configuration */ + MOD_REG_BIT(cpcfr3, CPCFR3_DDLY, + conf->codec_conf.i2s.sync_delay_enable); /* 0/1 clk delay */ + /* data serial output enabled during nonvalid audio frames, clock + * polarity = falling edge, CSYNC lenght equal to time slot0 length */ + MOD_REG_BIT(cpcfr3, CPCFR3_TRSEN, 1); + MOD_REG_BIT(cpcfr3, CPCFR3_CLKBP, 1); + MOD_REG_BIT(cpcfr3, CPCFR3_CSYNCL, 1); + + cpcfr4 &= ~(CPCFR4_DIVB_BITS | CPCFR4_ATSL_BITS); + cpcfr4 |= CPCFR4_DIVB(7); /* CP_SCLK = MCLK / 8 */ + + /* configuration for normal I2S or polarity-changed I2S */ + if (!conf->codec_conf.i2s.polarity_changed_mode) { + cpcfr1 |= CPCFR1_MODE(4); /* I2S mode */ + MOD_REG_BIT(cpcfr3, CPCFR3_CSYNCP, 0); /* CP_SYNC active low */ + /* audio time slots configuration for I2S */ + cpcfr4 |= CPCFR4_ATSL(0); + } else { + cpcfr1 |= CPCFR1_MODE(1); /* PCM mode/polarity-changed I2S */ + MOD_REG_BIT(cpcfr3, CPCFR3_CSYNCP, 1); /* CP_SYNC active + high */ + /* audio time slots configuration for polarity-changed I2S */ + cpcfr4 |= CPCFR4_ATSL(0xf); + }; + + /* master/slave configuration */ + if (conf->codec_mode == EAC_CODEC_I2S_MASTER) { + /* EAC is master. Set CP_SCLK and CP_SYNC as outputs */ + MOD_REG_BIT(cpcfr3, CPCFR3_CSCLKD, 0); + MOD_REG_BIT(cpcfr3, CPCFR3_CSYNCD, 0); + } else { + /* EAC is slave. Set CP_SCLK and CP_SYNC as inputs */ + MOD_REG_BIT(cpcfr3, CPCFR3_CSCLKD, 1); + MOD_REG_BIT(cpcfr3, CPCFR3_CSYNCD, 1); + } + + eac_write_reg(eac, EAC_CPCFR1, cpcfr1); + eac_write_reg(eac, EAC_CPCFR2, cpcfr2); + eac_write_reg(eac, EAC_CPCFR3, cpcfr3); + eac_write_reg(eac, EAC_CPCFR4, cpcfr4); + + return 0; +} + +static int eac_codec_port_init(struct omap_eac *eac, struct eac_codec *conf) +{ + u16 agcfr, agcfr2, agcfr3, agctr; + u16 cpctl, reg; + int err = 0, i; + + /* use internal MCLK gating before doing full configuration for it. + * Partial or misconfigured MCLK will cause that access to some of the + * EAC registers causes "external abort on linefetch". Same happens + * also when using external clock as a MCLK source and if that clock is + * either missing or not having a right rate (e.g. half of it) */ + agcfr3 = eac_read_reg(eac, EAC_AGCFR3); + MOD_REG_BIT(agcfr3, AGCFR3_MCLKINT_SEL, 1); /* 96 Mhz / 8.5 */ + eac_write_reg(eac, EAC_AGCFR3, agcfr3); + + /* disable codec port, enable access to config registers */ + cpctl = eac_read_reg(eac, EAC_CPTCTL); + MOD_REG_BIT(cpctl, CPTCTL_CPEN, 0); + eac_write_reg(eac, EAC_CPTCTL, cpctl); + + agcfr = eac_read_reg(eac, EAC_AGCFR); + agctr = eac_read_reg(eac, EAC_AGCTR); + agcfr2 = eac_read_reg(eac, EAC_AGCFR2); + + /* MCLK source and frequency configuration */ + MOD_REG_BIT(agcfr, AGCFR_MCLK, 0); + switch (conf->mclk_src) { + case EAC_MCLK_EXT_2x11289600: + MOD_REG_BIT(agcfr, AGCFR_MCLK, 1); /* div by 2 path */ + MOD_REG_BIT(agcfr, AGCFR_MCLK_OUT, 1); /* div by 2 */ + case EAC_MCLK_EXT_11289600: + MOD_REG_BIT(agcfr, AGCFR_MCLK, 1); + MOD_REG_BIT(agcfr2, AGCFR2_I2S_N44K_48K, 0); /* 44.1 kHz */ + MOD_REG_BIT(agcfr2, AGCFR2_MCLK_I2S_N11M_12M, 0); /* 11.2896 */ + MOD_REG_BIT(agcfr3, AGCFR3_MCLKINT_SEL, 0); + break; + + case EAC_MCLK_EXT_2x12288000: + MOD_REG_BIT(agcfr, AGCFR_MCLK, 1); /* div by 2 path */ + MOD_REG_BIT(agcfr, AGCFR_MCLK_OUT, 1); /* div by 2 */ + case EAC_MCLK_EXT_12288000: + MOD_REG_BIT(agcfr2, AGCFR2_I2S_N44K_48K, 1); /* 48 kHz */ + MOD_REG_BIT(agcfr2, AGCFR2_MCLK_I2S_N11M_12M, 1); /* 12.288 */ + MOD_REG_BIT(agcfr3, AGCFR3_MCLKINT_SEL, 0); + break; + + default: + /* internal MCLK gating */ + break; + } + MOD_REG_BIT(agctr, AGCTR_MCLK_EN, 1); + MOD_REG_BIT(agctr, AGCTR_OSCMCLK_EN, 1); /* oscillator enabled? */ + /* use MCLK just configured above as audio & codec port clock source */ + agcfr3 &= ~AGCFR3_AUD_CKSRC_BITS; + agcfr3 |= AGCFR3_AUD_CKSRC(0); + + /* audio data format */ + MOD_REG_BIT(agcfr, AGCFR_MN_ST, 1); /* stereo file */ + MOD_REG_BIT(agcfr, AGCFR_B8_16, 1); /* 16 bit audio file */ + MOD_REG_BIT(agcfr, AGCFR_LI_BI, 0); /* little endian stream */ + + /* there are FSINT configuration bits in AGCFR, AGCFR2 and AGCFR3 + * registers but it seems that it is just enough to set in AGCFR3 + * only */ + agcfr3 &= ~AGCFR3_FSINT_BITS; + agcfr3 |= AGCFR3_FSINT(eac_calc_agcfr3_fsint(conf->default_rate)); + + /* transparent DMA enable bits */ + MOD_REG_BIT(agcfr3, AGCFR3_MD_TR_DMA, 1); /* modem */ + MOD_REG_BIT(agcfr3, AGCFR3_BT_TR_DMA, 1); /* BT */ + if (conf->codec_mode != EAC_CODEC_I2S_SLAVE) + MOD_REG_BIT(agcfr3, AGCFR3_CP_TR_DMA, 0); + else + MOD_REG_BIT(agcfr3, AGCFR3_CP_TR_DMA, 1); + + /* step 4 (see TRM) */ + eac_write_reg(eac, EAC_AGCFR3, agcfr3); + /* pre-write AGCTR now (finally in step 10) in order to get MCLK + * settings effective (especially when using external MCLK) */ + eac_write_reg(eac, EAC_AGCTR, agctr); + eac_write_reg(eac, EAC_AGCFR2, agcfr2); + + /* step 5 (see TRM) */ + eac_write_reg(eac, EAC_AGCFR, agcfr); + + /* step 6 (see TRM) */ + /* wait until audio reset done */ + i = 10000; + while (!(eac_read_reg(eac, EAC_SYSSTATUS) & (1 << 3))) { + if (--i == 0) + return -ETIMEDOUT; + udelay(1); + } + + /* step 7 (see TRM) */ + reg = eac_read_reg(eac, EAC_AMSCFR); + MOD_REG_BIT(reg, AMSCFR_K1, 1); /* K1 switch closed */ + MOD_REG_BIT(reg, AMSCFR_K5, 1); /* K5 switch closed */ + MOD_REG_BIT(reg, AMSCFR_K2, 0); /* K2 switch open */ + MOD_REG_BIT(reg, AMSCFR_K6, 0); /* K6 switch open */ + eac_write_reg(eac, EAC_AMSCFR, reg); + + /* step 8 (see TRM) */ + switch (conf->codec_mode) { + case EAC_CODEC_PCM: + err = eac_configure_pcm(eac, conf); + break; + case EAC_CODEC_AC97: + err = eac_configure_ac97(eac, conf); + break; + default: + err = eac_configure_i2s(eac, conf); + break; + } + + /* step 9 (see TRM) */ + MOD_REG_BIT(cpctl, CPTCTL_CPEN, 1); /* codec port enable */ + MOD_REG_BIT(cpctl, CPTCTL_RXIE, 1); /* receive int enable */ + MOD_REG_BIT(cpctl, CPTCTL_TXIE, 1); /* transmit int enable */ + eac_write_reg(eac, EAC_CPTCTL, cpctl); + + /* step 10 (see TRM) */ + /* enable playing & recording */ + MOD_REG_BIT(agctr, AGCTR_DMAREN, 1); /* playing enabled (DMA R) */ + MOD_REG_BIT(agctr, AGCTR_DMAWEN, 1); /* recording enabled (DMA W) */ + MOD_REG_BIT(agctr, AGCTR_AUDEN, 1); /* audio processing enabled */ + eac_write_reg(eac, EAC_AGCTR, agctr); + + /* audio mixer1, no mute on mixer output, gain = 0 dB */ + reg = eac_read_reg(eac, EAC_AM1VCTR); + MOD_REG_BIT(reg, AM1VCTR_MUTE, 0); + reg = ((reg & ~AM1VCTR_GINB_BITS) | (AM1VCTR_GINB(0x67))); + eac_write_reg(eac, EAC_AM1VCTR, reg); + + /* audio mixer3, no mute on mixer output, gain = 0 dB */ + reg = eac_read_reg(eac, EAC_AM3VCTR); + MOD_REG_BIT(reg, AM3VCTR_MUTE, 0); + reg = ((reg & ~AM3VCTR_GINB_BITS) | (AM3VCTR_GINB(0x67))); + eac_write_reg(eac, EAC_AM3VCTR, reg); + + /* audio side tone disabled */ + eac_write_reg(eac, EAC_ASTCTR, 0x0); + + return 0; +} + +int eac_set_mode(struct device *dev, int play, int rec) +{ + struct omap_eac *eac = dev_get_drvdata(dev); + +#ifdef DEBUG + printk(KERN_DEBUG "EAC mode: play %s, rec %s\n", + play ? "enabled" : "disabled", + rec ? "enabled" : "disabled"); +#endif + BUG_ON(eac == NULL); + mutex_lock(&eac->mutex); + if (play || rec) { + /* activate clocks */ + eac_enable_clocks(eac); + + /* power-up codec */ + if (eac->codec != NULL && eac->codec->set_power != NULL) + eac->codec->set_power(eac->codec->private_data, + play, rec); + } else { + /* shutdown codec */ + if (eac->codec != NULL && eac->codec->set_power != NULL) + eac->codec->set_power(eac->codec->private_data, 0, 0); + + /* de-activate clocks */ + eac_disable_clocks(eac); + } + mutex_unlock(&eac->mutex); + + return 0; +} + +int eac_register_codec(struct device *dev, struct eac_codec *codec) +{ + struct omap_eac *eac = dev_get_drvdata(dev); + struct snd_card *card = eac->card; + int err; + + BUG_ON(eac->codec != NULL); + + mutex_lock(&eac->mutex); + eac->codec = codec; + eac_enable_clocks(eac); + err = eac_codec_port_init(eac, codec); + eac_disable_clocks(eac); + mutex_unlock(&eac->mutex); + if (err) + return err; + + /* register mixer controls implemented by a codec driver */ + if (codec->register_controls != NULL) { + err = codec->register_controls(codec->private_data, card); + if (err) + return err; + } + + if (codec->short_name != NULL) { + sprintf(card->longname, "%s with codec %s", card->shortname, + codec->short_name); + strcpy(card->mixername, codec->short_name); + } + + err = snd_card_register(card); + return err; +} + +void eac_unregister_codec(struct device *dev) +{ + struct omap_eac *eac = dev_get_drvdata(dev); + + BUG_ON(eac->codec == NULL); + eac_set_mode(dev, 0, 0); + snd_card_disconnect(eac->card); + eac->codec = NULL; +} + +static int __devinit eac_probe(struct platform_device *pdev) +{ + struct eac_platform_data *pdata = pdev->dev.platform_data; + struct snd_card *card; + struct omap_eac *eac; + struct resource *res; + int err; + + eac = kzalloc(sizeof(*eac), GFP_KERNEL); + if (!eac) + return -ENOMEM; + + mutex_init(&eac->mutex); + eac->pdev = pdev; + platform_set_drvdata(pdev, eac); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + err = -ENODEV; + goto err1; + } + eac->base = (void __iomem *)io_p2v(res->start); + eac->pdata = pdata; + + /* pre-initialize EAC hw */ + err = eac_get_clocks(eac); + if (err) + goto err1; + err = eac_enable_clocks(eac); + if (err) + goto err2; + + err = eac_reset(eac); + if (err) + goto err3; + + dev_info(&pdev->dev, "EAC version: %d.%d\n", + eac_read_reg(eac, EAC_VERSION) >> 4, + eac_read_reg(eac, EAC_VERSION) & 0x0f); + eac_disable_clocks(eac); + + /* create soundcard instance */ + card = snd_card_new(-1, id, THIS_MODULE, 0); + if (card == NULL) { + err = -ENOMEM; + goto err3; + } + eac->card = card; + strcpy(card->driver, "EAC"); + strcpy(card->shortname, "OMAP24xx EAC"); + + sprintf(card->longname, "%s", card->shortname); + strcpy(card->mixername, "EAC Mixer"); + + if (eac->pdata->init) { + err = eac->pdata->init(&pdev->dev); + if (err < 0) { + printk("init %d\n", err); + goto err4; + } + } + + return 0; + +err4: + snd_card_free(card); +err3: + eac_disable_clocks(eac); +err2: + eac_put_clocks(eac); +err1: + kfree(eac); + return err; +} + +static int __devexit eac_remove(struct platform_device *pdev) +{ + struct omap_eac *eac = platform_get_drvdata(pdev); + struct snd_card *card = eac->card; + + snd_card_free(card); + + eac_disable_clocks(eac); + eac_put_clocks(eac); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver eac_driver = { + .driver = { + .name = "omap24xx-eac", + .bus = &platform_bus_type, + }, + .probe = eac_probe, + .remove = eac_remove, +}; + +int __init eac_init(void) +{ + return platform_driver_register(&eac_driver); +} + +void __exit eac_exit(void) +{ + platform_driver_unregister(&eac_driver); +} + +module_init(eac_init); +module_exit(eac_exit); +MODULE_AUTHOR("Jarkko Nikula "); +MODULE_LICENSE("GPL"); diff --cc sound/arm/omap/omap-alsa-aic23-mixer.c index 7f06582a462,00000000000..da2a875f417 mode 100644,000000..100644 --- a/sound/arm/omap/omap-alsa-aic23-mixer.c +++ b/sound/arm/omap/omap-alsa-aic23-mixer.c @@@ -1,523 -1,0 +1,523 @@@ +/* + * sound/arm/omap/omap-alsa-aic23-mixer.c + * + * Alsa Driver Mixer for generic codecs for omap boards + * + * Copyright (C) 2005 Instituto Nokia de Tecnologia - INdT - Manaus Brazil + * Written by David Cohen, Daniel Petrini + * {david.cohen, daniel.petrini}@indt.org.br + * + * Based on es1688_lib.c, + * Copyright (c) by Jaroslav Kysela + * + * 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. + * + * History: + * + * 2005-08-02 INdT Kernel Team - Alsa mixer driver for omap osk. + * Creation of new file omap-alsa-mixer.c. + * Initial version with aic23 codec for osk5912 + */ + +#include + - #include ++#include + - #include ++#include +#include "omap-alsa-aic23.h" +#include +#include + +MODULE_AUTHOR("David Cohen"); +MODULE_AUTHOR("Daniel Petrini"); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("OMAP Alsa mixer driver for ALSA"); + +/* + * Codec dependent region + */ + +/* Codec AIC23 */ +#if defined(CONFIG_SENSORS_TLV320AIC23) || \ + defined(CONFIG_SENSORS_TLV320AIC23_MODULE) + +#define MIXER_NAME "Mixer AIC23" +#define SND_OMAP_WRITE(reg, val) audio_aic23_write(reg, val) + +#endif + +/* Callback Functions */ +#define OMAP_BOOL(xname, xindex, reg, reg_index, mask, invert) \ +{ \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .info = snd_omap_info_bool, \ + .get = snd_omap_get_bool, \ + .put = snd_omap_put_bool, \ + .private_value = reg | (reg_index << 8) | (invert << 10) | \ + (mask << 12) \ +} + +#define OMAP_MUX(xname, reg, reg_index, mask) \ +{ \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .info = snd_omap_info_mux, \ + .get = snd_omap_get_mux, \ + .put = snd_omap_put_mux, \ + .private_value = reg | (reg_index << 8) | (mask << 10) \ +} + +#define OMAP_SINGLE(xname, xindex, reg, reg_index, reg_val, mask) \ +{\ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .info = snd_omap_info_single, \ + .get = snd_omap_get_single, \ + .put = snd_omap_put_single, \ + .private_value = reg | (reg_val << 8) | (reg_index << 16) |\ + (mask << 18) \ +} + +#define OMAP_DOUBLE(xname, xindex, left_reg, right_reg, reg_index, mask) \ +{\ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .info = snd_omap_info_double, \ + .get = snd_omap_get_double, \ + .put = snd_omap_put_double, \ + .private_value = left_reg | (right_reg << 8) | (reg_index << 16) | \ + (mask << 18) \ +} + +/* Local Registers */ +enum snd_device_index { + PCM_INDEX = 0, + LINE_INDEX, + AAC_INDEX, /* Analog Audio Control: reg = l_reg */ +}; + +struct { + u16 l_reg; + u16 r_reg; + u8 sw; +} omap_regs[3]; + +#ifdef CONFIG_PM +struct { + u16 l_reg; + u16 r_reg; + u8 sw; +} omap_pm_regs[3]; +#endif + +u16 snd_sidetone[6] = { + SIDETONE_18, + SIDETONE_12, + SIDETONE_9, + SIDETONE_6, + SIDETONE_0, + 0 +}; + +/* Begin Bool Functions */ + +static int snd_omap_info_bool(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + + return 0; +} + +static int snd_omap_get_bool(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int mic_index = (kcontrol->private_value >> 8) & 0x03; + u16 mask = (kcontrol->private_value >> 12) & 0xff; + int invert = (kcontrol->private_value >> 10) & 0x03; + + if (invert) + ucontrol->value.integer.value[0] = + (omap_regs[mic_index].l_reg & mask) ? 0 : 1; + else + ucontrol->value.integer.value[0] = + (omap_regs[mic_index].l_reg & mask) ? 1 : 0; + + return 0; +} + +static int snd_omap_put_bool(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int mic_index = (kcontrol->private_value >> 8) & 0x03; + u16 mask = (kcontrol->private_value >> 12) & 0xff; + u16 reg = kcontrol->private_value & 0xff; + int invert = (kcontrol->private_value >> 10) & 0x03; + + int changed = 1; + + if (ucontrol->value.integer.value[0]) /* XOR */ + if (invert) + omap_regs[mic_index].l_reg &= ~mask; + else + omap_regs[mic_index].l_reg |= mask; + else + if (invert) + omap_regs[mic_index].l_reg |= mask; + else + omap_regs[mic_index].l_reg &= ~mask; + + SND_OMAP_WRITE(reg, omap_regs[mic_index].l_reg); + + return changed; +} + +/* End Bool Functions */ + +/* Begin Mux Functions */ + +static int snd_omap_info_mux(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + /* Mic = 0 + * Line = 1 */ + static char *texts[2] = { "Mic", "Line" }; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = 2; + + if (uinfo->value.enumerated.item > 1) + uinfo->value.enumerated.item = 1; + + strcpy(uinfo->value.enumerated.name, + texts[uinfo->value.enumerated.item]); + + return 0; +} + +static int snd_omap_get_mux(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u16 mask = (kcontrol->private_value >> 10) & 0xff; + int mux_idx = (kcontrol->private_value >> 8) & 0x03; + + ucontrol->value.enumerated.item[0] = + (omap_regs[mux_idx].l_reg & mask) ? 0 /* Mic */ : 1 /* Line */; + + return 0; +} + +static int snd_omap_put_mux(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u16 reg = kcontrol->private_value & 0xff; + u16 mask = (kcontrol->private_value >> 10) & 0xff; + int mux_index = (kcontrol->private_value >> 8) & 0x03; + + int changed = 1; + + if (!ucontrol->value.integer.value[0]) + omap_regs[mux_index].l_reg |= mask; /* AIC23: Mic */ + else + omap_regs[mux_index].l_reg &= ~mask; /* AIC23: Line */ + + SND_OMAP_WRITE(reg, omap_regs[mux_index].l_reg); + + return changed; +} + +/* End Mux Functions */ + +/* Begin Single Functions */ + +static int snd_omap_info_single(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int mask = (kcontrol->private_value >> 18) & 0xff; + int reg_val = (kcontrol->private_value >> 8) & 0xff; + + uinfo->type = mask ? SNDRV_CTL_ELEM_TYPE_INTEGER : + SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = reg_val-1; + + return 0; +} + +static int snd_omap_get_single(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u16 reg_val = (kcontrol->private_value >> 8) & 0xff; + + ucontrol->value.integer.value[0] = snd_sidetone[reg_val]; + + return 0; +} + +static int snd_omap_put_single(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u16 reg_index = (kcontrol->private_value >> 16) & 0x03; + u16 mask = (kcontrol->private_value >> 18) & 0x1ff; + u16 reg = kcontrol->private_value & 0xff; + u16 reg_val = (kcontrol->private_value >> 8) & 0xff; + + int changed = 0; + + /* Volume */ + if ((omap_regs[reg_index].l_reg != + (ucontrol->value.integer.value[0] & mask))) { + changed = 1; + + omap_regs[reg_index].l_reg &= ~mask; + omap_regs[reg_index].l_reg |= + snd_sidetone[ucontrol->value.integer.value[0]]; + + snd_sidetone[reg_val] = ucontrol->value.integer.value[0]; + SND_OMAP_WRITE(reg, omap_regs[reg_index].l_reg); + } else { + changed = 0; + } + + return changed; +} + +/* End Single Functions */ + +/* Begin Double Functions */ + +static int snd_omap_info_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + /* + * mask == 0 : Switch + * mask != 0 : Volume + */ + int mask = (kcontrol->private_value >> 18) & 0xff; + + uinfo->type = mask ? SNDRV_CTL_ELEM_TYPE_INTEGER : + SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = mask ? 2 : 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mask ? mask : 1; + + return 0; +} + +static int snd_omap_get_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + /* + * mask == 0 : Switch + * mask != 0 : Volume + */ + int mask = (kcontrol->private_value >> 18) & 0xff; + int vol_index = (kcontrol->private_value >> 16) & 0x03; + + if (!mask) { + /* Switch */ + ucontrol->value.integer.value[0] = omap_regs[vol_index].sw; + } else { + /* Volume */ + ucontrol->value.integer.value[0] = omap_regs[vol_index].l_reg; + ucontrol->value.integer.value[1] = omap_regs[vol_index].r_reg; + } + + return 0; +} + +static int snd_omap_put_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + /* mask == 0 : Switch + * mask != 0 : Volume */ + int vol_index = (kcontrol->private_value >> 16) & 0x03; + int mask = (kcontrol->private_value >> 18) & 0xff; + int left_reg = kcontrol->private_value & 0xff; + int right_reg = (kcontrol->private_value >> 8) & 0xff; + + int changed = 0; + + if (!mask) { + /* Switch */ + if (!ucontrol->value.integer.value[0]) { + SND_OMAP_WRITE(left_reg, 0x00); + SND_OMAP_WRITE(right_reg, 0x00); + } else { + SND_OMAP_WRITE(left_reg, omap_regs[vol_index].l_reg); + SND_OMAP_WRITE(right_reg, omap_regs[vol_index].r_reg); + } + changed = 1; + omap_regs[vol_index].sw = ucontrol->value.integer.value[0]; + } else { + /* Volume */ + if ((omap_regs[vol_index].l_reg != + (ucontrol->value.integer.value[0] & mask)) || + (omap_regs[vol_index].r_reg != + (ucontrol->value.integer.value[1] & mask))) { + changed = 1; + + omap_regs[vol_index].l_reg &= ~mask; + omap_regs[vol_index].r_reg &= ~mask; + omap_regs[vol_index].l_reg |= + (ucontrol->value.integer.value[0] & mask); + omap_regs[vol_index].r_reg |= + (ucontrol->value.integer.value[1] & mask); + if (omap_regs[vol_index].sw) { + /* write to registers only if sw is actived */ + SND_OMAP_WRITE(left_reg, + omap_regs[vol_index].l_reg); + SND_OMAP_WRITE(right_reg, + omap_regs[vol_index].r_reg); + } + } else { + changed = 0; + } + } + + return changed; +} + +/* End Double Functions */ + +static struct snd_kcontrol_new snd_omap_controls[] = { + OMAP_DOUBLE("PCM Playback Switch", 0, LEFT_CHANNEL_VOLUME_ADDR, + RIGHT_CHANNEL_VOLUME_ADDR, PCM_INDEX, 0x00), + OMAP_DOUBLE("PCM Playback Volume", 0, LEFT_CHANNEL_VOLUME_ADDR, + RIGHT_CHANNEL_VOLUME_ADDR, PCM_INDEX, + OUTPUT_VOLUME_MASK), + OMAP_BOOL("Line Playback Switch", 0, ANALOG_AUDIO_CONTROL_ADDR, + AAC_INDEX, BYPASS_ON, 0), + OMAP_DOUBLE("Line Capture Switch", 0, LEFT_LINE_VOLUME_ADDR, + RIGHT_LINE_VOLUME_ADDR, LINE_INDEX, 0x00), + OMAP_DOUBLE("Line Capture Volume", 0, LEFT_LINE_VOLUME_ADDR, + RIGHT_LINE_VOLUME_ADDR, LINE_INDEX, INPUT_VOLUME_MASK), + OMAP_BOOL("Mic Playback Switch", 0, ANALOG_AUDIO_CONTROL_ADDR, + AAC_INDEX, STE_ENABLED, 0), + OMAP_SINGLE("Mic Playback Volume", 0, ANALOG_AUDIO_CONTROL_ADDR, + AAC_INDEX, 5, SIDETONE_MASK), + OMAP_BOOL("Mic Capture Switch", 0, ANALOG_AUDIO_CONTROL_ADDR, + AAC_INDEX, MICM_MUTED, 1), + OMAP_BOOL("Mic Booster Playback Switch", 0, ANALOG_AUDIO_CONTROL_ADDR, + AAC_INDEX, MICB_20DB, 0), + OMAP_MUX("Capture Source", ANALOG_AUDIO_CONTROL_ADDR, AAC_INDEX, + INSEL_MIC), +}; + +#ifdef CONFIG_PM + +void snd_omap_suspend_mixer(void) +{ + /* Saves current values to wake-up correctly */ + omap_pm_regs[LINE_INDEX].l_reg = omap_regs[LINE_INDEX].l_reg; + omap_pm_regs[LINE_INDEX].r_reg = omap_regs[LINE_INDEX].l_reg; + omap_pm_regs[LINE_INDEX].sw = omap_regs[LINE_INDEX].sw; + + omap_pm_regs[AAC_INDEX].l_reg = omap_regs[AAC_INDEX].l_reg; + + omap_pm_regs[PCM_INDEX].l_reg = omap_regs[PCM_INDEX].l_reg; + omap_pm_regs[PCM_INDEX].r_reg = omap_regs[PCM_INDEX].r_reg; + omap_pm_regs[PCM_INDEX].sw = omap_regs[PCM_INDEX].sw; +} + +void snd_omap_resume_mixer(void) +{ + /* Line's saved values */ + omap_regs[LINE_INDEX].l_reg = omap_pm_regs[LINE_INDEX].l_reg; + omap_regs[LINE_INDEX].r_reg = omap_pm_regs[LINE_INDEX].l_reg; + omap_regs[LINE_INDEX].sw = omap_pm_regs[LINE_INDEX].sw; + SND_OMAP_WRITE(LEFT_LINE_VOLUME_ADDR, omap_pm_regs[LINE_INDEX].l_reg); + SND_OMAP_WRITE(RIGHT_LINE_VOLUME_ADDR, omap_pm_regs[LINE_INDEX].l_reg); + + /* Analog Audio Control's saved values */ + omap_regs[AAC_INDEX].l_reg = omap_pm_regs[AAC_INDEX].l_reg; + SND_OMAP_WRITE(ANALOG_AUDIO_CONTROL_ADDR, omap_regs[AAC_INDEX].l_reg); + + /* Headphone's saved values */ + omap_regs[PCM_INDEX].l_reg = omap_pm_regs[PCM_INDEX].l_reg; + omap_regs[PCM_INDEX].r_reg = omap_pm_regs[PCM_INDEX].r_reg; + omap_regs[PCM_INDEX].sw = omap_pm_regs[PCM_INDEX].sw; + SND_OMAP_WRITE(LEFT_CHANNEL_VOLUME_ADDR, + omap_pm_regs[PCM_INDEX].l_reg); + SND_OMAP_WRITE(RIGHT_CHANNEL_VOLUME_ADDR, + omap_pm_regs[PCM_INDEX].r_reg); +} +#endif + +void snd_omap_init_mixer(void) +{ + u16 vol_reg; + + /* Line's default values */ + omap_regs[LINE_INDEX].l_reg = DEFAULT_INPUT_VOLUME & INPUT_VOLUME_MASK; + omap_regs[LINE_INDEX].r_reg = DEFAULT_INPUT_VOLUME & INPUT_VOLUME_MASK; + omap_regs[LINE_INDEX].sw = 0; + SND_OMAP_WRITE(LEFT_LINE_VOLUME_ADDR, + DEFAULT_INPUT_VOLUME & INPUT_VOLUME_MASK); + SND_OMAP_WRITE(RIGHT_LINE_VOLUME_ADDR, + DEFAULT_INPUT_VOLUME & INPUT_VOLUME_MASK); + + /* Analog Audio Control's default values */ + omap_regs[AAC_INDEX].l_reg = DEFAULT_ANALOG_AUDIO_CONTROL; + + /* Headphone's default values */ + vol_reg = LZC_ON; + vol_reg &= ~OUTPUT_VOLUME_MASK; + vol_reg |= DEFAULT_OUTPUT_VOLUME; + omap_regs[PCM_INDEX].l_reg = DEFAULT_OUTPUT_VOLUME; + omap_regs[PCM_INDEX].r_reg = DEFAULT_OUTPUT_VOLUME; + omap_regs[PCM_INDEX].sw = 1; + SND_OMAP_WRITE(LEFT_CHANNEL_VOLUME_ADDR, vol_reg); + SND_OMAP_WRITE(RIGHT_CHANNEL_VOLUME_ADDR, vol_reg); +} + +int snd_omap_mixer(struct snd_card_omap_codec *chip) +{ + struct snd_card *card; + unsigned int idx; + int err; + + snd_assert(chip != NULL && chip->card != NULL, return -EINVAL); + + card = chip->card; + + strcpy(card->mixername, MIXER_NAME); + + /* Registering alsa mixer controls */ + for (idx = 0; idx < ARRAY_SIZE(snd_omap_controls); idx++) { + err = snd_ctl_add(card, + snd_ctl_new1(&snd_omap_controls[idx], chip)); + if (err < 0) + return err; + } + + return 0; +} diff --cc sound/arm/omap/omap-alsa-aic23.c index 1e43608b68e,00000000000..9820e51c2e9 mode 100644,000000..100644 --- a/sound/arm/omap/omap-alsa-aic23.c +++ b/sound/arm/omap/omap-alsa-aic23.c @@@ -1,327 -1,0 +1,327 @@@ +/* + * arch/arm/mach-omap1/omap-alsa-aic23.c + * + * Alsa codec Driver for AIC23 chip on OSK5912 platform board + * + * Copyright (C) 2005 Instituto Nokia de Tecnologia - INdT - Manaus Brazil + * Written by Daniel Petrini, David Cohen, Anderson Briglia + * {daniel.petrini, david.cohen, anderson.briglia}@indt.org.br + * + * Copyright (C) 2006 Mika Laitio + * + * Based in former alsa driver for osk and oss driver + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include - #include - #include ++#include ++#include + - #include ++#include +#include "omap-alsa-aic23.h" + +static struct clk *aic23_mclk; + +/* aic23 related */ +static const struct aic23_samplerate_reg_info + rate_reg_info[NUMBER_SAMPLE_RATES_SUPPORTED] = { + {4000, 0x06, 1}, /* 4000 */ + {8000, 0x06, 0}, /* 8000 */ + {16000, 0x0C, 1}, /* 16000 */ + {22050, 0x11, 1}, /* 22050 */ + {24000, 0x00, 1}, /* 24000 */ + {32000, 0x0C, 0}, /* 32000 */ + {44100, 0x11, 0}, /* 44100 */ + {48000, 0x00, 0}, /* 48000 */ + {88200, 0x1F, 0}, /* 88200 */ + {96000, 0x0E, 0}, /* 96000 */ +}; + +/* + * Hardware capabilities + */ + + /* + * DAC USB-mode sampling rates (MCLK = 12 MHz) + * The rates and rate_reg_into MUST be in the same order + */ +static unsigned int rates[] = { + 4000, 8000, 16000, 22050, + 24000, 32000, 44100, + 48000, 88200, 96000, +}; + +static struct snd_pcm_hw_constraint_list aic23_hw_constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static struct snd_pcm_hardware aic23_snd_omap_alsa_playback = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID), + .formats = (SNDRV_PCM_FMTBIT_S16_LE), + .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_KNOT), + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 128 * 1024, + .period_bytes_min = 32, + .period_bytes_max = 8 * 1024, + .periods_min = 16, + .periods_max = 255, + .fifo_size = 0, +}; + +static struct snd_pcm_hardware aic23_snd_omap_alsa_capture = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID), + .formats = (SNDRV_PCM_FMTBIT_S16_LE), + .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_KNOT), + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 128 * 1024, + .period_bytes_min = 32, + .period_bytes_max = 8 * 1024, + .periods_min = 16, + .periods_max = 255, + .fifo_size = 0, +}; + +/* + * Codec/mcbsp init and configuration section + * codec dependent code. + */ + +/* TLV320AIC23 is a write only device */ +void audio_aic23_write(u8 address, u16 data) +{ + aic23_write_value(address, data); +} +EXPORT_SYMBOL_GPL(audio_aic23_write); + +/* + * Sample rate changing + */ +void aic23_set_samplerate(long rate) +{ + u8 count = 0; + u16 data = 0; + + /* Fix the rate if it has a wrong value */ + if (rate >= 96000) + rate = 96000; + else if (rate >= 88200) + rate = 88200; + else if (rate >= 48000) + rate = 48000; + else if (rate >= 44100) + rate = 44100; + else if (rate >= 32000) + rate = 32000; + else if (rate >= 24000) + rate = 24000; + else if (rate >= 22050) + rate = 22050; + else if (rate >= 16000) + rate = 16000; + else if (rate >= 8000) + rate = 8000; + else + rate = 4000; + + /* Search for the right sample rate */ + /* Verify what happens if the rate is not supported + * now it goes to 96Khz */ + while ((rate_reg_info[count].sample_rate != rate) && + (count < (NUMBER_SAMPLE_RATES_SUPPORTED - 1))) { + count++; + } + + data = (rate_reg_info[count].divider << CLKIN_SHIFT) | + (rate_reg_info[count].control << BOSR_SHIFT) | USB_CLK_ON; + + audio_aic23_write(SAMPLE_RATE_CONTROL_ADDR, data); +} + +inline void aic23_configure(void) +{ + /* Reset codec */ + audio_aic23_write(RESET_CONTROL_ADDR, 0); + + /* Initialize the AIC23 internal state */ + + /* + * Analog audio path control, DAC selected, + * delete INSEL_MIC for line-in + */ + audio_aic23_write(ANALOG_AUDIO_CONTROL_ADDR, + DEFAULT_ANALOG_AUDIO_CONTROL); + + /* Digital audio path control, de-emphasis control 44.1kHz */ + audio_aic23_write(DIGITAL_AUDIO_CONTROL_ADDR, DEEMP_44K); + + /* Digital audio interface, master/slave mode, I2S, 16 bit */ +#ifdef AIC23_MASTER + audio_aic23_write(DIGITAL_AUDIO_FORMAT_ADDR, + MS_MASTER | IWL_16 | FOR_DSP); +#else + audio_aic23_write(DIGITAL_AUDIO_FORMAT_ADDR, IWL_16 | FOR_DSP); +#endif + + /* Enable digital interface */ + audio_aic23_write(DIGITAL_INTERFACE_ACT_ADDR, ACT_ON); +} + +/* + * OMAP MCBSP clock configuration and Power Management + * + * Here we have some functions that allow clock to be enabled and + * disabled only when needed. Besides doing clock configuration + * it allows turn on/turn off audio when necessary. + */ +/* + * Do clock framework mclk search + */ +void aic23_clock_setup(void) +{ + aic23_mclk = clk_get(0, "mclk"); +} + +/* + * Do some sanity check, set clock rate, starts it and + * turn codec audio on + */ +int aic23_clock_on(void) +{ + uint curRate; + + if (clk_get_usecount(aic23_mclk) > 0) { + /* MCLK is already in use */ + printk(KERN_WARNING + "MCLK in use at %d Hz. We change it to %d Hz\n", + (uint) clk_get_rate(aic23_mclk), + CODEC_CLOCK); + } + curRate = (uint)clk_get_rate(aic23_mclk); + if (curRate != CODEC_CLOCK) { + if (clk_set_rate(aic23_mclk, CODEC_CLOCK)) { + printk(KERN_ERR + "Cannot set MCLK for AIC23 CODEC\n"); + return -ECANCELED; + } + } + clk_enable(aic23_mclk); + + printk(KERN_DEBUG + "MCLK = %d [%d], usecount = %d\n", + (uint) clk_get_rate(aic23_mclk), CODEC_CLOCK, + clk_get_usecount(aic23_mclk)); + + /* Now turn the audio on */ + audio_aic23_write(POWER_DOWN_CONTROL_ADDR, + ~DEVICE_POWER_OFF & ~OUT_OFF & ~DAC_OFF & + ~ADC_OFF & ~MIC_OFF & ~LINE_OFF); + return 0; +} + +/* + * Do some sanity check, turn clock off and then turn + * codec audio off + */ +int aic23_clock_off(void) +{ + if (clk_get_usecount(aic23_mclk) > 0) { + if (clk_get_rate(aic23_mclk) != CODEC_CLOCK) { + printk(KERN_WARNING + "MCLK for audio should be %d Hz. But is %d Hz\n", + (uint) clk_get_rate(aic23_mclk), + CODEC_CLOCK); + } + + clk_disable(aic23_mclk); + } + + audio_aic23_write(POWER_DOWN_CONTROL_ADDR, + DEVICE_POWER_OFF | OUT_OFF | DAC_OFF | + ADC_OFF | MIC_OFF | LINE_OFF); + return 0; +} + +int aic23_get_default_samplerate(void) +{ + return DEFAULT_SAMPLE_RATE; +} + +static int __devinit snd_omap_alsa_aic23_probe(struct platform_device *pdev) +{ + int ret; + struct omap_alsa_codec_config *codec_cfg; + + codec_cfg = pdev->dev.platform_data; + if (codec_cfg != NULL) { + codec_cfg->hw_constraints_rates = &aic23_hw_constraints_rates; + codec_cfg->snd_omap_alsa_playback = + &aic23_snd_omap_alsa_playback; + codec_cfg->snd_omap_alsa_capture = &aic23_snd_omap_alsa_capture; + codec_cfg->codec_configure_dev = aic23_configure; + codec_cfg->codec_set_samplerate = aic23_set_samplerate; + codec_cfg->codec_clock_setup = aic23_clock_setup; + codec_cfg->codec_clock_on = aic23_clock_on; + codec_cfg->codec_clock_off = aic23_clock_off; + codec_cfg->get_default_samplerate = + aic23_get_default_samplerate; + ret = snd_omap_alsa_post_probe(pdev, codec_cfg); + } else + ret = -ENODEV; + return ret; +} + +static struct platform_driver omap_alsa_driver = { + .probe = snd_omap_alsa_aic23_probe, + .remove = snd_omap_alsa_remove, + .suspend = snd_omap_alsa_suspend, + .resume = snd_omap_alsa_resume, + .driver = { + .name = "omap_alsa_mcbsp", + }, +}; + +static int __init omap_alsa_aic23_init(void) +{ + int err; + + ADEBUG(); + err = platform_driver_register(&omap_alsa_driver); + + return err; +} + +static void __exit omap_alsa_aic23_exit(void) +{ + ADEBUG(); + + platform_driver_unregister(&omap_alsa_driver); +} + +module_init(omap_alsa_aic23_init); +module_exit(omap_alsa_aic23_exit); diff --cc sound/arm/omap/omap-alsa-aic23.h index e7dfc155c0a,00000000000..5431b022699 mode 100644,000000..100644 --- a/sound/arm/omap/omap-alsa-aic23.h +++ b/sound/arm/omap/omap-alsa-aic23.h @@@ -1,86 -1,0 +1,86 @@@ +/* + * sound/arm/omap-alsa-aic23.h + * + * Alsa Driver for AIC23 codec on OSK5912 platform board + * + * Copyright (C) 2005 Instituto Nokia de Tecnologia - INdT - Manaus Brazil + * Written by Daniel Petrini, David Cohen, Anderson Briglia + * {daniel.petrini, david.cohen, anderson.briglia}@indt.org.br + * + * Copyright (C) 2006 Mika Laitio + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef __OMAP_ALSA_AIC23_H +#define __OMAP_ALSA_AIC23_H + - #include ++#include +#include +#include - #include ++#include + +/* Define to set the AIC23 as the master w.r.t McBSP */ +#define AIC23_MASTER + +#define NUMBER_SAMPLE_RATES_SUPPORTED 10 + +/* + * AUDIO related MACROS + */ +#ifndef DEFAULT_BITPERSAMPLE +#define DEFAULT_BITPERSAMPLE 16 +#endif + +#define DEFAULT_SAMPLE_RATE 44100 +#define CODEC_CLOCK 12000000 +#define AUDIO_MCBSP OMAP_MCBSP1 + +#define DEFAULT_OUTPUT_VOLUME 0x60 +#define DEFAULT_INPUT_VOLUME 0x00 /* 0 ==> mute line in */ + +#define OUTPUT_VOLUME_MIN LHV_MIN +#define OUTPUT_VOLUME_MAX LHV_MAX +#define OUTPUT_VOLUME_RANGE (OUTPUT_VOLUME_MAX - OUTPUT_VOLUME_MIN) +#define OUTPUT_VOLUME_MASK OUTPUT_VOLUME_MAX + +#define INPUT_VOLUME_MIN LIV_MIN +#define INPUT_VOLUME_MAX LIV_MAX +#define INPUT_VOLUME_RANGE (INPUT_VOLUME_MAX - INPUT_VOLUME_MIN) +#define INPUT_VOLUME_MASK INPUT_VOLUME_MAX + +#define SIDETONE_MASK 0x1c0 +#define SIDETONE_0 0x100 +#define SIDETONE_6 0x000 +#define SIDETONE_9 0x040 +#define SIDETONE_12 0x080 +#define SIDETONE_18 0x0c0 + +#define DEFAULT_ANALOG_AUDIO_CONTROL (DAC_SELECTED | STE_ENABLED | \ + BYPASS_ON | INSEL_MIC | MICB_20DB) + +struct aic23_samplerate_reg_info { + u32 sample_rate; + u8 control; /* SR3, SR2, SR1, SR0 and BOSR */ + u8 divider; /* if 0 CLKIN = MCLK, if 1 CLKIN = MCLK/2 */ +}; + +extern int aic23_write_value(u8 reg, u16 value); + +/* + * Defines codec specific function pointers that can be used from the + * common omap-alsa base driver for all omap codecs. (tsc2101 and aic23) + */ +void audio_aic23_write(u8 address, u16 data); +void define_codec_functions(struct omap_alsa_codec_config *codec_config); +inline void aic23_configure(void); +void aic23_set_samplerate(long rate); +void aic23_clock_setup(void); +int aic23_clock_on(void); +int aic23_clock_off(void); +int aic23_get_default_samplerate(void); + +#endif diff --cc sound/arm/omap/omap-alsa-dma.c index baafacf4b70,00000000000..b2623cbb53f mode 100644,000000..100644 --- a/sound/arm/omap/omap-alsa-dma.c +++ b/sound/arm/omap/omap-alsa-dma.c @@@ -1,437 -1,0 +1,437 @@@ +/* + * sound/arm/omap/omap-alsa-dma.c + * + * Common audio DMA handling for the OMAP processors + * + * Copyright (C) 2006 Mika Laitio + * + * Copyright (C) 2005 Instituto Nokia de Tecnologia - INdT - Manaus Brazil + * + * Copyright (C) 2004 Texas Instruments, Inc. + * + * Copyright (C) 2000, 2001 Nicolas Pitre + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * History: + * + * 2004-06-07 Sriram Kannan - Created new file from omap_audio_dma_intfc.c. + * This file will contain only the DMA interface + * and buffer handling of OMAP audio driver. + * + * 2004-06-22 Sriram Kannan - removed legacy code (auto-init). Self-linking + * of DMA logical channel. + * + * 2004-08-12 Nishanth Menon - Modified to integrate Audio requirements on + * 1610, 1710 platforms + * + * 2004-11-01 Nishanth Menon - 16xx platform code base modified to support + * multi channel chaining. + * + * 2004-12-15 Nishanth Menon - Improved 16xx platform channel logic + * introduced - tasklets, queue handling updated + * + * 2005-07-19 INdT Kernel Team - Alsa port. Creation of new file + * omap-alsa-dma.c based in omap-audio-dma-intfc.c + * oss file. Support for aic23 codec. Removal of + * buffer handling (Alsa does that), modifications + * in dma handling and port to alsa structures. + * + * 2005-12-18 Dirk Behme - Added L/R Channel Interchange fix as proposed + * by Ajaya Babu + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + - #include - #include - #include - #include ++#include ++#include ++#include ++#include + +#include "omap-alsa-dma.h" + +#undef DEBUG + +/* + * Channel Queue Handling macros + * tail always points to the current free entry + * Head always points to the current entry being used + * end is either head or tail + */ + +#define AUDIO_QUEUE_INIT(s) s->dma_q_head = s->dma_q_tail = s->dma_q_count = 0; +#define AUDIO_QUEUE_FULL(s) (nr_linked_channels == s->dma_q_count) +#define AUDIO_QUEUE_LAST(s) (1 == s->dma_q_count) +#define AUDIO_QUEUE_EMPTY(s) (0 == s->dma_q_count) +#define __AUDIO_INCREMENT_QUEUE(end) ((end) = ((end)+1) % nr_linked_channels) +#define AUDIO_INCREMENT_HEAD(s) \ + do { \ + __AUDIO_INCREMENT_QUEUE(s->dma_q_head); \ + s->dma_q_count--; \ + } while (0) +#define AUDIO_INCREMENT_TAIL(s) \ + do { \ + __AUDIO_INCREMENT_QUEUE(s->dma_q_tail); \ + s->dma_q_count++; \ + } while (0) + +/* DMA buffer fragmentation sizes */ +#define MAX_DMA_SIZE 0x1000000 /* todo: sync with alsa */ +/* #define CUT_DMA_SIZE 0x1000 */ +/* TODO: To be moved to more appropriate location */ +#define DCSR_ERROR 0x3 +#define DCSR_END_BLOCK (1 << 5) +#define DCSR_SYNC_SET (1 << 6) + +#define DCCR_FS (1 << 5) +#define DCCR_PRIO (1 << 6) +#define DCCR_AI (1 << 8) +#define DCCR_REPEAT (1 << 9) +/* if 0 the channel works in 3.1 compatible mode */ +#define DCCR_N31COMP (1 << 10) +#define DCCR_EP (1 << 11) +#define DCCR_SRC_AMODE_BIT 12 +#define DCCR_SRC_AMODE_MASK (0x3<<12) +#define DCCR_DST_AMODE_BIT 14 +#define DCCR_DST_AMODE_MASK (0x3<<14) +#define AMODE_CONST 0x0 +#define AMODE_POST_INC 0x1 +#define AMODE_SINGLE_INDEX 0x2 +#define AMODE_DOUBLE_INDEX 0x3 + +/* Data structures */ +DEFINE_SPINLOCK(dma_list_lock); +static char nr_linked_channels = 1; + +/* Module specific functions */ + +static void sound_dma_irq_handler(int lch, u16 ch_status, void *data); +static int audio_set_dma_params_play(int channel, dma_addr_t dma_ptr, + u_int dma_size); +static int audio_set_dma_params_capture(int channel, dma_addr_t dma_ptr, + u_int dma_size); +static int audio_start_dma_chain(struct audio_stream *s); + +/* + * DMA channel requests + */ +static void omap_sound_dma_link_lch(void *data) +{ + + struct audio_stream *s = (struct audio_stream *) data; + int *chan = s->lch; + int i; + + FN_IN; + if (s->linked) { + FN_OUT(1); + return; + } + for (i = 0; i < nr_linked_channels; i++) { + int cur_chan = chan[i]; + int nex_chan = + ((nr_linked_channels - 1 == + i) ? chan[0] : chan[i + 1]); + omap_dma_link_lch(cur_chan, nex_chan); + } + s->linked = 1; + FN_OUT(0); +} + +int omap_request_alsa_sound_dma(int device_id, const char *device_name, + void *data, int **channels) +{ + int i, err = 0; + int *chan = NULL; + FN_IN; + if (unlikely((NULL == channels) || (NULL == device_name))) { + BUG(); + return -EPERM; + } + /* Try allocate memory for the num channels */ + *channels = kmalloc(sizeof(int) * nr_linked_channels, GFP_KERNEL); + chan = *channels; + if (NULL == chan) { + ERR("No Memory for channel allocs!\n"); + FN_OUT(-ENOMEM); + return -ENOMEM; + } + spin_lock(&dma_list_lock); + for (i = 0; i < nr_linked_channels; i++) { + err = omap_request_dma(device_id, + device_name, + sound_dma_irq_handler, + data, + &chan[i]); + + /* Handle Failure condition here */ + if (err < 0) { + int j; + + for (j = 0; j < i; j++) + omap_free_dma(chan[j]); + + spin_unlock(&dma_list_lock); + kfree(chan); + *channels = NULL; + ERR("Error in requesting channel %d=0x%x\n", i, + err); + FN_OUT(err); + return err; + } + } + + /* Chain the channels together */ + if (!cpu_is_omap15xx()) + omap_sound_dma_link_lch(data); + + spin_unlock(&dma_list_lock); + FN_OUT(0); + return 0; +} +EXPORT_SYMBOL(omap_request_alsa_sound_dma); + +/* + * DMA channel requests Freeing + */ +static void omap_sound_dma_unlink_lch(void *data) +{ + struct audio_stream *s = (struct audio_stream *)data; + int *chan = s->lch; + int i; + + FN_IN; + if (!s->linked) { + FN_OUT(1); + return; + } + for (i = 0; i < nr_linked_channels; i++) { + int cur_chan = chan[i]; + int nex_chan = + ((nr_linked_channels - 1 == + i) ? chan[0] : chan[i + 1]); + omap_dma_unlink_lch(cur_chan, nex_chan); + } + s->linked = 0; + FN_OUT(0); +} + +int omap_free_alsa_sound_dma(void *data, int **channels) +{ + int i; + int *chan = NULL; + + FN_IN; + if (unlikely(NULL == channels)) { + BUG(); + return -EPERM; + } + if (unlikely(NULL == *channels)) { + BUG(); + return -EPERM; + } + chan = (*channels); + + if (!cpu_is_omap15xx()) + omap_sound_dma_unlink_lch(data); + for (i = 0; i < nr_linked_channels; i++) { + int cur_chan = chan[i]; + omap_stop_dma(cur_chan); + omap_free_dma(cur_chan); + } + kfree(*channels); + *channels = NULL; + FN_OUT(0); + return 0; +} +EXPORT_SYMBOL(omap_free_alsa_sound_dma); + +/* + * Stop all the DMA channels of the stream + */ +void omap_stop_alsa_sound_dma(struct audio_stream *s) +{ + int *chan = s->lch; + int i; + + FN_IN; + if (unlikely(NULL == chan)) { + BUG(); + return; + } + for (i = 0; i < nr_linked_channels; i++) { + int cur_chan = chan[i]; + omap_stop_dma(cur_chan); + } + s->started = 0; + FN_OUT(0); + return; +} +EXPORT_SYMBOL(omap_stop_alsa_sound_dma); + +/* + * Clear any pending transfers + */ +void omap_clear_alsa_sound_dma(struct audio_stream *s) +{ + FN_IN; + omap_clear_dma(s->lch[s->dma_q_head]); + FN_OUT(0); + return; +} +EXPORT_SYMBOL(omap_clear_alsa_sound_dma); + +/* + * DMA related functions + */ +static int audio_set_dma_params_play(int channel, dma_addr_t dma_ptr, + u_int dma_size) +{ + int dt = 0x1; /* data type 16 */ + int cen = 32; /* Stereo */ + int cfn = dma_size / (2 * cen); + + FN_IN; + omap_set_dma_dest_params(channel, 0x05, 0x00, + (OMAP1510_MCBSP1_BASE + 0x06), + 0, 0); + omap_set_dma_src_params(channel, 0x00, 0x01, dma_ptr, + 0, 0); + omap_set_dma_transfer_params(channel, dt, cen, cfn, 0x00, 0, 0); + FN_OUT(0); + return 0; +} + +static int audio_set_dma_params_capture(int channel, dma_addr_t dma_ptr, + u_int dma_size) +{ + int dt = 0x1; /* data type 16 */ + int cen = 32; /* stereo */ + int cfn = dma_size / (2 * cen); + + FN_IN; + omap_set_dma_src_params(channel, 0x05, 0x00, + (OMAP1510_MCBSP1_BASE + 0x02), + 0, 0); + omap_set_dma_dest_params(channel, 0x00, 0x01, dma_ptr, 0, 0); + omap_set_dma_transfer_params(channel, dt, cen, cfn, 0x00, 0, 0); + FN_OUT(0); + return 0; +} + +static int audio_start_dma_chain(struct audio_stream *s) +{ + int channel = s->lch[s->dma_q_head]; + FN_IN; + if (!s->started) { + s->hw_stop(); /* stops McBSP Interface */ + omap_start_dma(channel); + s->started = 1; + s->hw_start(); /* start McBSP interface */ + } else if (cpu_is_omap310()) + omap_start_dma(channel); + /* else the dma itself will progress forward with out our help */ + FN_OUT(0); + return 0; +} + +/* + * Start DMA - + * Do the initial set of work to initialize all the channels as required. + * We shall then initate a transfer + */ +int omap_start_alsa_sound_dma(struct audio_stream *s, + dma_addr_t dma_ptr, + u_int dma_size) +{ + int ret = -EPERM; + + FN_IN; + + if (unlikely(dma_size > MAX_DMA_SIZE)) { + ERR("DmaSoundDma: Start: overflowed %d-%d\n", dma_size, + MAX_DMA_SIZE); + return -EOVERFLOW; + } + + if (s->stream_id == SNDRV_PCM_STREAM_PLAYBACK) { + /* playback */ + ret = + audio_set_dma_params_play(s->lch[s->dma_q_tail], + dma_ptr, dma_size); + } else { + ret = + audio_set_dma_params_capture(s->lch[s->dma_q_tail], + dma_ptr, dma_size); + } + if (ret != 0) { + ret = -3; /* indicate queue full */ + goto sound_out; + } + AUDIO_INCREMENT_TAIL(s); + ret = audio_start_dma_chain(s); + if (ret) + ERR("dma start failed"); + +sound_out: + FN_OUT(ret); + return ret; + +} +EXPORT_SYMBOL(omap_start_alsa_sound_dma); + +/* + * ISRs have to be short and smart.. + * Here we call alsa handling, after some error checking + */ +static void sound_dma_irq_handler(int sound_curr_lch, u16 ch_status, + void *data) +{ + int dma_status = ch_status; + struct audio_stream *s = (struct audio_stream *) data; + FN_IN; + + /* some register checking */ + DPRINTK("lch=%d,status=0x%x, dma_status=%d, data=%p\n", + sound_curr_lch, ch_status, dma_status, data); + + if (dma_status & (DCSR_ERROR)) { + omap_stop_dma(sound_curr_lch); + ERR("DCSR_ERROR!\n"); + FN_OUT(-1); + return; + } + + if (ch_status & DCSR_END_BLOCK) + callback_omap_alsa_sound_dma(s); + FN_OUT(0); + return; +} + +MODULE_AUTHOR("Texas Instruments"); +MODULE_DESCRIPTION("Common DMA handling for Audio driver on OMAP processors"); +MODULE_LICENSE("GPL"); + diff --cc sound/arm/omap/omap-alsa-dma.h index 2f0e4e8a2f9,00000000000..0dcd3791418 mode 100644,000000..100644 --- a/sound/arm/omap/omap-alsa-dma.h +++ b/sound/arm/omap/omap-alsa-dma.h @@@ -1,53 -1,0 +1,53 @@@ +/* + * linux/sound/arm/omap/omap-alsa-dma.h + * + * Common audio DMA handling for the OMAP processors + * + * Copyright (C) 2006 Mika Laitio + * + * Copyright (C) 2005 Instituto Nokia de Tecnologia - INdT - Manaus Brazil + * + * Copyright (C) 2004 Texas Instruments, Inc. + * + * Copyright (C) 2000, 2001 Nicolas Pitre + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * History: + * + * + * 2004/08/12 Nishanth Menon - Modified to integrate Audio requirements on + * 1610, 1710 platforms + * + * 2005/07/25 INdT Kernel Team - Renamed to omap-alsa-dma.h. Ported to Alsa. + */ + +#ifndef __OMAP_AUDIO_ALSA_DMA_H +#define __OMAP_AUDIO_ALSA_DMA_H + - #include ++#include + +/* Global data structures */ + +typedef void (*dma_callback_t) (int lch, u16 ch_status, void *data); + +/* arch specific functions */ + +void omap_clear_alsa_sound_dma(struct audio_stream *s); + +int omap_request_alsa_sound_dma(int device_id, const char *device_name, + void *data, int **channels); +int omap_free_alsa_sound_dma(void *data, int **channels); + +int omap_start_alsa_sound_dma(struct audio_stream *s, dma_addr_t dma_ptr, + u_int dma_size); + +void omap_stop_alsa_sound_dma(struct audio_stream *s); + +#endif diff --cc sound/arm/omap/omap-alsa-sx1.c index efcabff4a22,00000000000..62530430fd9 mode 100644,000000..100644 --- a/sound/arm/omap/omap-alsa-sx1.c +++ b/sound/arm/omap/omap-alsa-sx1.c @@@ -1,306 -1,0 +1,306 @@@ +/* + * Alsa codec Driver for Siemens SX1 board. + * based on omap-alsa-tsc2101.c and cn_test.c example by Evgeniy Polyakov + * + * Copyright (C) 2006 Vladimir Ananiev (vovan888 at gmail com) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + - #include - #include - #include - #include - #include ++#include ++#include ++#include ++#include ++#include + +#include "omap-alsa-sx1.h" + +/* Connector implementation */ +static struct cb_id cn_sx1snd_id = { CN_IDX_SX1SND, CN_VAL_SX1SND }; +static char cn_sx1snd_name[] = "cn_sx1snd"; + +static void cn_sx1snd_callback(void *data) +{ + struct cn_msg *msg = (struct cn_msg *)data; + + printk(KERN_INFO + "%s: %lu: idx=%x, val=%x, seq=%u, ack=%u, len=%d: %s.\n", + __func__, jiffies, msg->id.idx, msg->id.val, + msg->seq, msg->ack, msg->len, (char *)msg->data); +} + +/* Send IPC message to sound server */ +int cn_sx1snd_send(unsigned int cmd, unsigned int arg1, unsigned int arg2) +{ + struct cn_msg *m; + unsigned short data[3]; + int err; + + m = kzalloc(sizeof(*m) + sizeof(data), gfp_any()); + if (!m) + return -1; + + memcpy(&m->id, &cn_sx1snd_id, sizeof(m->id)); + m->seq = 1; + m->len = sizeof(data); + + data[0] = (unsigned short)cmd; + data[1] = (unsigned short)arg1; + data[2] = (unsigned short)arg2; + + memcpy(m + 1, data, m->len); + + err = cn_netlink_send(m, CN_IDX_SX1SND, gfp_any()); + snd_printd("sent= %02X %02X %02X, err=%d\n", cmd, arg1, arg2, err); + kfree(m); + + if (err == -ESRCH) + return -1; /* there are no listeners on socket */ + return 0; +} + +/* Hardware capabilities + * + * DAC USB-mode sampling rates (MCLK = 12 MHz) + * The rates and rate_reg_into MUST be in the same order + */ +static unsigned int rates[] = { + 8000, 11025, 12000, + 16000, 22050, 24000, + 32000, 44100, 48000, +}; + +static struct snd_pcm_hw_constraint_list egold_hw_constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static struct snd_pcm_hardware egold_snd_omap_alsa_playback = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID), + .formats = (SNDRV_PCM_FMTBIT_S16_LE), + .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_KNOT), + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 128 * 1024, + .period_bytes_min = 32, + .period_bytes_max = 8 * 1024, + .periods_min = 16, + .periods_max = 255, + .fifo_size = 0, +}; + +static struct snd_pcm_hardware egold_snd_omap_alsa_capture = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID), + .formats = (SNDRV_PCM_FMTBIT_S16_LE), + .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_KNOT), + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 128 * 1024, + .period_bytes_min = 32, + .period_bytes_max = 8 * 1024, + .periods_min = 16, + .periods_max = 255, + .fifo_size = 0, +}; + +static long current_rate = -1; /* current rate in egold format 0..8 */ +/* + * ALSA operations according to board file + */ + +/* + * Sample rate changing + */ +static void egold_set_samplerate(long sample_rate) +{ + int egold_rate = 0; + int clkgdv = 0; + u16 srgr1, srgr2; + + /* Set the sample rate */ +#if 0 + /* fw15: 5005E490 - divs are different !!! */ + clkgdv = CODEC_CLOCK / (sample_rate * (DEFAULT_BITPERSAMPLE * 2 - 1)); +#endif + switch (sample_rate) { + case 8000: + clkgdv = 71; + egold_rate = FRQ_8000; + break; + case 11025: + clkgdv = 51; + egold_rate = FRQ_11025; + break; + case 12000: + clkgdv = 47; + egold_rate = FRQ_12000; + break; + case 16000: + clkgdv = 35; + egold_rate = FRQ_16000; + break; + case 22050: + clkgdv = 25; + egold_rate = FRQ_22050; + break; + case 24000: + clkgdv = 23; + egold_rate = FRQ_24000; + break; + case 32000: + clkgdv = 17; + egold_rate = FRQ_32000; + break; + case 44100: + clkgdv = 12; + egold_rate = FRQ_44100; + break; + case 48000: + clkgdv = 11; + egold_rate = FRQ_48000; + break; + } + + srgr1 = (FWID(DEFAULT_BITPERSAMPLE - 1) | CLKGDV(clkgdv)); + srgr2 = ((FSGM | FPER(DEFAULT_BITPERSAMPLE * 2 - 1))); + + OMAP_MCBSP_WRITE(OMAP1510_MCBSP1_BASE, SRGR2, srgr2); + OMAP_MCBSP_WRITE(OMAP1510_MCBSP1_BASE, SRGR1, srgr1); + current_rate = egold_rate; + snd_printd("set samplerate=%ld\n", sample_rate); + +} + +static void egold_configure(void) +{ +} + +/* + * Omap MCBSP clock and Power Management configuration + * + * Here we have some functions that allows clock to be enabled and + * disabled only when needed. Besides doing clock configuration + * it allows turn on/turn off audio when necessary. + */ + +/* + * Do clock framework mclk search + */ +static void egold_clock_setup(void) +{ + omap_request_gpio(OSC_EN); + omap_set_gpio_direction(OSC_EN, 0); /* output */ + snd_printd("\n"); +} + +/* + * Do some sanity check, set clock rate, starts it and turn codec audio on + */ +static int egold_clock_on(void) +{ + omap_set_gpio_dataout(OSC_EN, 1); + egold_set_samplerate(44100); /* TODO */ + cn_sx1snd_send(DAC_SETAUDIODEVICE, SX1_DEVICE_SPEAKER, 0); + cn_sx1snd_send(DAC_OPEN_DEFAULT, current_rate , 4); + snd_printd("\n"); + return 0; +} + +/* + * Do some sanity check, turn clock off and then turn codec audio off + */ +static int egold_clock_off(void) +{ + cn_sx1snd_send(DAC_CLOSE, 0 , 0); + cn_sx1snd_send(DAC_SETAUDIODEVICE, SX1_DEVICE_PHONE, 0); + omap_set_gpio_dataout(OSC_EN, 0); + snd_printd("\n"); + return 0; +} + +static int egold_get_default_samplerate(void) +{ + snd_printd("\n"); + return DEFAULT_SAMPLE_RATE; +} + +static int __init snd_omap_alsa_egold_probe(struct platform_device *pdev) +{ + int ret; + struct omap_alsa_codec_config *codec_cfg; + + codec_cfg = pdev->dev.platform_data; + if (!codec_cfg) + return -ENODEV; + + codec_cfg->hw_constraints_rates = &egold_hw_constraints_rates; + codec_cfg->snd_omap_alsa_playback = &egold_snd_omap_alsa_playback; + codec_cfg->snd_omap_alsa_capture = &egold_snd_omap_alsa_capture; + codec_cfg->codec_configure_dev = egold_configure; + codec_cfg->codec_set_samplerate = egold_set_samplerate; + codec_cfg->codec_clock_setup = egold_clock_setup; + codec_cfg->codec_clock_on = egold_clock_on; + codec_cfg->codec_clock_off = egold_clock_off; + codec_cfg->get_default_samplerate = egold_get_default_samplerate; + ret = snd_omap_alsa_post_probe(pdev, codec_cfg); + + snd_printd("\n"); + return ret; +} + +static struct platform_driver omap_alsa_driver = { + .probe = snd_omap_alsa_egold_probe, + .remove = snd_omap_alsa_remove, + .suspend = snd_omap_alsa_suspend, + .resume = snd_omap_alsa_resume, + .driver = { + .name = "omap_alsa_mcbsp", + }, +}; + +static int __init omap_alsa_egold_init(void) +{ + int retval; + + retval = cn_add_callback(&cn_sx1snd_id, cn_sx1snd_name, + cn_sx1snd_callback); + if (retval) + printk(KERN_WARNING "cn_sx1snd failed to register\n"); + return platform_driver_register(&omap_alsa_driver); +} + +static void __exit omap_alsa_egold_exit(void) +{ + cn_del_callback(&cn_sx1snd_id); + platform_driver_unregister(&omap_alsa_driver); +} + +module_init(omap_alsa_egold_init); +module_exit(omap_alsa_egold_exit); diff --cc sound/arm/omap/omap-alsa-tsc2101.c index 8a7e770a3d2,00000000000..6b0e1063346 mode 100644,000000..100644 --- a/sound/arm/omap/omap-alsa-tsc2101.c +++ b/sound/arm/omap/omap-alsa-tsc2101.c @@@ -1,543 -1,0 +1,543 @@@ +/* + * sound/arm/omap/omap-alsa-tsc2101.c + * + * Alsa codec Driver for TSC2101 chip for OMAP platform boards. + * Code obtained from oss omap drivers + * + * Copyright (C) 2004 Texas Instruments, Inc. + * Written by Nishanth Menon and Sriram Kannan + * + * Copyright (C) 2006 Instituto Nokia de Tecnologia - INdT - Manaus Brazil + * Alsa modularization by Daniel Petrini (d.pensator@gmail.com) + * + * Copyright (C) 2006 Mika Laitio + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_PM +#include +#endif + +#include - #include - #include - #include ++#include ++#include ++#include +#include - #include ++#include + +#include "omap-alsa-tsc2101.h" + +struct mcbsp_dev_info mcbsp_dev; + +static struct clk *tsc2101_mclk; + +/* #define DUMP_TSC2101_AUDIO_REGISTERS */ +#undef DUMP_TSC2101_AUDIO_REGISTERS + +/* + * Hardware capabilities + */ + +/* + * DAC USB-mode sampling rates (MCLK = 12 MHz) + * The rates and rate_reg_into MUST be in the same order + */ +static unsigned int rates[] = { + 7350, 8000, 8018, 8727, + 8820, 9600, 11025, 12000, + 14700, 16000, 22050, 24000, + 29400, 32000, 44100, 48000, +}; + +static struct snd_pcm_hw_constraint_list tsc2101_hw_constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static const struct tsc2101_samplerate_reg_info + rate_reg_info[NUMBER_SAMPLE_RATES_SUPPORTED] = { + /* Div 6 */ + {7350, 7, 1}, + {8000, 7, 0}, + /* Div 5.5 */ + {8018, 6, 1}, + {8727, 6, 0}, + /* Div 5 */ + {8820, 5, 1}, + {9600, 5, 0}, + /* Div 4 */ + {11025, 4, 1}, + {12000, 4, 0}, + /* Div 3 */ + {14700, 3, 1}, + {16000, 3, 0}, + /* Div 2 */ + {22050, 2, 1}, + {24000, 2, 0}, + /* Div 1.5 */ + {29400, 1, 1}, + {32000, 1, 0}, + /* Div 1 */ + {44100, 0, 1}, + {48000, 0, 0}, +}; + +static struct snd_pcm_hardware tsc2101_snd_omap_alsa_playback = { + .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID, +#ifdef CONFIG_MACH_OMAP_H6300 + .formats = SNDRV_PCM_FMTBIT_S8, +#else + .formats = SNDRV_PCM_FMTBIT_S16_LE, +#endif + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_KNOT, + .rate_min = 7350, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 128 * 1024, + .period_bytes_min = 32, + .period_bytes_max = 8 * 1024, + .periods_min = 16, + .periods_max = 255, + .fifo_size = 0, +}; + +static struct snd_pcm_hardware tsc2101_snd_omap_alsa_capture = { + .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_KNOT, + .rate_min = 7350, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 128 * 1024, + .period_bytes_min = 32, + .period_bytes_max = 8 * 1024, + .periods_min = 16, + .periods_max = 255, + .fifo_size = 0, +}; + +/* + * Simplified write for tsc2101 audio registers. + */ +inline void tsc2101_audio_write(u8 address, u16 data) +{ + tsc2101_write_sync(mcbsp_dev.tsc2101_dev, PAGE2_AUDIO_CODEC_REGISTERS, + address, data); +} + +/* + * Simplified read for tsc2101 audio registers. + */ +inline u16 tsc2101_audio_read(u8 address) +{ + return (tsc2101_read_sync(mcbsp_dev.tsc2101_dev, + PAGE2_AUDIO_CODEC_REGISTERS, address)); +} + +#ifdef DUMP_TSC2101_AUDIO_REGISTERS +void dump_tsc2101_audio_reg(void) +{ + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_AUDIO_CTRL_1 = 0x%04x\n", + tsc2101_audio_read(TSC2101_AUDIO_CTRL_1)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_HEADSET_GAIN_CTRL = 0x%04x\n", + tsc2101_audio_read(TSC2101_HEADSET_GAIN_CTRL)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_DAC_GAIN_CTRL = 0x%04x\n", + tsc2101_audio_read(TSC2101_DAC_GAIN_CTRL)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_MIXER_PGA_CTRL = 0x%04x\n", + tsc2101_audio_read(TSC2101_MIXER_PGA_CTRL)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_AUDIO_CTRL_2 = 0x%04x\n", + tsc2101_audio_read(TSC2101_AUDIO_CTRL_2)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_CODEC_POWER_CTRL = 0x%04x\n", + tsc2101_audio_read(TSC2101_CODEC_POWER_CTRL)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_AUDIO_CTRL_3 = 0x%04x\n", + tsc2101_audio_read(TSC2101_AUDIO_CTRL_3)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_LCH_BASS_BOOST_N0 = 0x%04x\n", + tsc2101_audio_read(TSC2101_LCH_BASS_BOOST_N0)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_LCH_BASS_BOOST_N1 = 0x%04x\n", + tsc2101_audio_read(TSC2101_LCH_BASS_BOOST_N1)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_LCH_BASS_BOOST_N2 = 0x%04x\n", + tsc2101_audio_read(TSC2101_LCH_BASS_BOOST_N2)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_LCH_BASS_BOOST_N3 = 0x%04x\n", + tsc2101_audio_read(TSC2101_LCH_BASS_BOOST_N3)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_LCH_BASS_BOOST_N4 = 0x%04x\n", + tsc2101_audio_read(TSC2101_LCH_BASS_BOOST_N4)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_LCH_BASS_BOOST_N5 = 0x%04x\n", + tsc2101_audio_read(TSC2101_LCH_BASS_BOOST_N5)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_LCH_BASS_BOOST_D1 = 0x%04x\n", + tsc2101_audio_read(TSC2101_LCH_BASS_BOOST_D1)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_LCH_BASS_BOOST_D2 = 0x%04x\n", + tsc2101_audio_read(TSC2101_LCH_BASS_BOOST_D2)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_LCH_BASS_BOOST_D4 = 0x%04x\n", + tsc2101_audio_read(TSC2101_LCH_BASS_BOOST_D4)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_LCH_BASS_BOOST_D5 = 0x%04x\n", + tsc2101_audio_read(TSC2101_LCH_BASS_BOOST_D5)); + + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_RCH_BASS_BOOST_N0 = 0x%04x\n", + tsc2101_audio_read(TSC2101_RCH_BASS_BOOST_N0)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_RCH_BASS_BOOST_N1 = 0x%04x\n", + tsc2101_audio_read(TSC2101_RCH_BASS_BOOST_N1)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_RCH_BASS_BOOST_N2 = 0x%04x\n", + tsc2101_audio_read(TSC2101_RCH_BASS_BOOST_N2)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_RCH_BASS_BOOST_N3 = 0x%04x\n", + tsc2101_audio_read(TSC2101_RCH_BASS_BOOST_N3)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_RCH_BASS_BOOST_N4 = 0x%04x\n", + tsc2101_audio_read(TSC2101_RCH_BASS_BOOST_N4)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_RCH_BASS_BOOST_N5 = 0x%04x\n", + tsc2101_audio_read(TSC2101_RCH_BASS_BOOST_N5)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_RCH_BASS_BOOST_D1 = 0x%04x\n", + tsc2101_audio_read(TSC2101_RCH_BASS_BOOST_D1)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_RCH_BASS_BOOST_D2 = 0x%04x\n", + tsc2101_audio_read(TSC2101_RCH_BASS_BOOST_D2)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_RCH_BASS_BOOST_D4 = 0x%04x\n", + tsc2101_audio_read(TSC2101_RCH_BASS_BOOST_D4)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_RCH_BASS_BOOST_D5 = 0x%04x\n", + tsc2101_audio_read(TSC2101_RCH_BASS_BOOST_D5)); + + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_PLL_PROG_1 = 0x%04x\n", + tsc2101_audio_read(TSC2101_PLL_PROG_1)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_PLL_PROG_1 = 0x%04x\n", + tsc2101_audio_read(TSC2101_PLL_PROG_2)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_AUDIO_CTRL_4 = 0x%04x\n", + tsc2101_audio_read(TSC2101_AUDIO_CTRL_4)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_HANDSET_GAIN_CTRL = 0x%04x\n", + tsc2101_audio_read(TSC2101_HANDSET_GAIN_CTRL)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_BUZZER_GAIN_CTRL = 0x%04x\n", + tsc2101_audio_read(TSC2101_BUZZER_GAIN_CTRL)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_AUDIO_CTRL_5 = 0x%04x\n", + tsc2101_audio_read(TSC2101_AUDIO_CTRL_5)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_AUDIO_CTRL_6 = 0x%04x\n", + tsc2101_audio_read(TSC2101_AUDIO_CTRL_6)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_AUDIO_CTRL_7 = 0x%04x\n", + tsc2101_audio_read(TSC2101_AUDIO_CTRL_7)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_GPIO_CTRL = 0x%04x\n", + tsc2101_audio_read(TSC2101_GPIO_CTRL)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_AGC_CTRL = 0x%04x\n", + tsc2101_audio_read(TSC2101_AGC_CTRL)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_POWERDOWN_STS = 0x%04x\n", + tsc2101_audio_read(TSC2101_POWERDOWN_STS)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_MIC_AGC_CONTROL = 0x%04x\n", + tsc2101_audio_read(TSC2101_MIC_AGC_CONTROL)); + dev_dbg(&mcbsp_dev.mcbsp_dev->dev, + "TSC2101_CELL_AGC_CONTROL = 0x%04x\n", + tsc2101_audio_read(TSC2101_CELL_AGC_CONTROL)); +} +#endif + +/* + * ALSA operations according to board file + */ + +/* + * Sample rate changing + */ +void tsc2101_set_samplerate(long sample_rate) +{ + u8 count = 0; + u16 data = 0; + int clkgdv = 0; + + u16 srgr1, srgr2; + /* wait for any frame to complete */ + udelay(125); + ADEBUG(); + + sample_rate = sample_rate; + /* Search for the right sample rate */ + while ((rate_reg_info[count].sample_rate != sample_rate) && + (count < NUMBER_SAMPLE_RATES_SUPPORTED)) { + count++; + } + if (count == NUMBER_SAMPLE_RATES_SUPPORTED) { + printk(KERN_ERR "Invalid Sample Rate %d requested\n", + (int) sample_rate); + return; + } + + /* Set AC1 */ + data = tsc2101_audio_read(TSC2101_AUDIO_CTRL_1); + /* Clear prev settings */ + data &= ~(AC1_DACFS(0x07) | AC1_ADCFS(0x07)); + data |= AC1_DACFS(rate_reg_info[count].divisor) | + AC1_ADCFS(rate_reg_info[count].divisor); + tsc2101_audio_write(TSC2101_AUDIO_CTRL_1, data); + + /* Set the AC3 */ + data = tsc2101_audio_read(TSC2101_AUDIO_CTRL_3); + /*Clear prev settings */ + data &= ~(AC3_REFFS | AC3_SLVMS); + data |= (rate_reg_info[count].fs_44kHz) ? AC3_REFFS : 0; +#ifdef TSC_MASTER + data |= AC3_SLVMS; +#endif /* #ifdef TSC_MASTER */ + tsc2101_audio_write(TSC2101_AUDIO_CTRL_3, data); + + /* + * Program the PLLs. This code assumes that the 12 Mhz MCLK is in use. + * If MCLK rate is something else, these values must be changed. + * See the tsc2101 specification for the details. + */ + if (rate_reg_info[count].fs_44kHz) { + /* samplerate = (44.1kHZ / x), where x is int. */ + tsc2101_audio_write(TSC2101_PLL_PROG_1, PLL1_PLLSEL | + /* PVAL 1; I_VAL 7 */ + PLL1_PVAL(1) | PLL1_I_VAL(7)); + /* D_VAL 5264 */ + tsc2101_audio_write(TSC2101_PLL_PROG_2, PLL2_D_VAL(0x1490)); + } else { + /* samplerate = (48.kHZ / x), where x is int. */ + tsc2101_audio_write(TSC2101_PLL_PROG_1, PLL1_PLLSEL | + /* PVAL 1; I_VAL 8 */ + PLL1_PVAL(1) | PLL1_I_VAL(8)); + /* D_VAL 1920 */ + tsc2101_audio_write(TSC2101_PLL_PROG_2, PLL2_D_VAL(0x780)); + } + + /* Set the sample rate */ +#ifndef TSC_MASTER + clkgdv = CODEC_CLOCK / (sample_rate * (DEFAULT_BITPERSAMPLE * 2 - 1)); + if (clkgdv) + srgr1 = (FWID(DEFAULT_BITPERSAMPLE - 1) | CLKGDV(clkgdv)); + else + return (1); + + /* Stereo Mode */ + srgr2 = (CLKSM | FSGM | FPER(DEFAULT_BITPERSAMPLE * 2 - 1)); +#else + srgr1 = (FWID(DEFAULT_BITPERSAMPLE - 1) | CLKGDV(clkgdv)); + srgr2 = ((GSYNC | CLKSP | FSGM | FPER(DEFAULT_BITPERSAMPLE * 2 - 1))); + +#endif /* end of #ifdef TSC_MASTER */ + OMAP_MCBSP_WRITE(OMAP1610_MCBSP1_BASE, SRGR2, srgr2); + OMAP_MCBSP_WRITE(OMAP1610_MCBSP1_BASE, SRGR1, srgr1); +} + +void tsc2101_configure(void) +{ +} + +/* + * Omap MCBSP clock and Power Management configuration + * + * Here we have some functions that allows clock to be enabled and + * disabled only when needed. Besides doing clock configuration + * it allows turn on/turn off audio when necessary. + */ + +/* + * Do clock framework mclk search + */ +void tsc2101_clock_setup(void) +{ + tsc2101_mclk = clk_get(0, "mclk"); +} + +/* + * Do some sanity check, set clock rate, starts it and turn codec audio on + */ +int tsc2101_clock_on(void) +{ + int curUseCount; + uint curRate; + int err; + + curUseCount = clk_get_usecount(tsc2101_mclk); + DPRINTK("clock use count = %d\n", curUseCount); + if (curUseCount > 0) { + /* MCLK is already in use */ + printk(KERN_WARNING + "MCLK already in use at %d Hz. We change it to %d Hz\n", + (uint) clk_get_rate(tsc2101_mclk), + CODEC_CLOCK); + } + curRate = (uint)clk_get_rate(tsc2101_mclk); + if (curRate != CODEC_CLOCK) { + err = clk_set_rate(tsc2101_mclk, CODEC_CLOCK); + if (err) { + printk(KERN_WARNING "Cannot set MCLK clock rate for " + "TSC2101 CODEC, error code = %d\n", err); + return -ECANCELED; + } + } + err = clk_enable(tsc2101_mclk); + curRate = (uint)clk_get_rate(tsc2101_mclk); + curUseCount = clk_get_usecount(tsc2101_mclk); + DPRINTK("MCLK = %d [%d], usecount = %d, clk_enable retval = %d\n", + curRate, + CODEC_CLOCK, + curUseCount, + err); + + /* Now turn the audio on */ + tsc2101_write_sync(mcbsp_dev.tsc2101_dev, PAGE2_AUDIO_CODEC_REGISTERS, + TSC2101_CODEC_POWER_CTRL, + 0x0000); + return 0; +} + +/* + * Do some sanity check, turn clock off and then turn codec audio off + */ +int tsc2101_clock_off(void) +{ + int curUseCount; + int curRate; + + curUseCount = clk_get_usecount(tsc2101_mclk); + DPRINTK("clock use count = %d\n", curUseCount); + if (curUseCount > 0) { + curRate = clk_get_rate(tsc2101_mclk); + DPRINTK("clock rate = %d\n", curRate); + if (curRate != CODEC_CLOCK) { + printk(KERN_WARNING + "MCLK for audio should be %d Hz. But is %d Hz\n", + (uint) clk_get_rate(tsc2101_mclk), + CODEC_CLOCK); + } + clk_disable(tsc2101_mclk); + DPRINTK("clock disabled\n"); + } + tsc2101_audio_write(TSC2101_CODEC_POWER_CTRL, + ~(CPC_SP1PWDN | CPC_SP2PWDN | CPC_BASSBC)); + DPRINTK("audio codec off\n"); + return 0; +} + +int tsc2101_get_default_samplerate(void) +{ + return DEFAULT_SAMPLE_RATE; +} + +static int __devinit snd_omap_alsa_tsc2101_probe(struct platform_device *pdev) +{ + struct spi_device *tsc2101; + int ret; + struct omap_alsa_codec_config *codec_cfg; + + tsc2101 = dev_get_drvdata(&pdev->dev); + if (tsc2101 == NULL) { + dev_err(&pdev->dev, "no platform data\n"); + return -ENODEV; + } + if (strncmp(tsc2101->modalias, "tsc2101", 8) != 0) { + dev_err(&pdev->dev, "tsc2101 not found\n"); + return -EINVAL; + } + mcbsp_dev.mcbsp_dev = pdev; + mcbsp_dev.tsc2101_dev = tsc2101; + + codec_cfg = pdev->dev.platform_data; + if (codec_cfg != NULL) { + codec_cfg->hw_constraints_rates = + &tsc2101_hw_constraints_rates; + codec_cfg->snd_omap_alsa_playback = + &tsc2101_snd_omap_alsa_playback; + codec_cfg->snd_omap_alsa_capture = + &tsc2101_snd_omap_alsa_capture; + codec_cfg->codec_configure_dev = tsc2101_configure; + codec_cfg->codec_set_samplerate = tsc2101_set_samplerate; + codec_cfg->codec_clock_setup = tsc2101_clock_setup; + codec_cfg->codec_clock_on = tsc2101_clock_on; + codec_cfg->codec_clock_off = tsc2101_clock_off; + codec_cfg->get_default_samplerate = + tsc2101_get_default_samplerate; + ret = snd_omap_alsa_post_probe(pdev, codec_cfg); + } else + ret = -ENODEV; + return ret; +} + +static struct platform_driver omap_alsa_driver = { + .probe = snd_omap_alsa_tsc2101_probe, + .remove = snd_omap_alsa_remove, + .suspend = snd_omap_alsa_suspend, + .resume = snd_omap_alsa_resume, + .driver = { + .name = "omap_alsa_mcbsp", + }, +}; + +static int __init omap_alsa_tsc2101_init(void) +{ + ADEBUG(); +#ifdef DUMP_TSC2101_AUDIO_REGISTERS + printk(KERN_INFO "omap_alsa_tsc2101_init()\n"); + dump_tsc2101_audio_reg(); +#endif + return platform_driver_register(&omap_alsa_driver); +} + +static void __exit omap_alsa_tsc2101_exit(void) +{ + ADEBUG(); +#ifdef DUMP_TSC2101_AUDIO_REGISTERS + printk(KERN_INFO "omap_alsa_tsc2101_exit()\n"); + dump_tsc2101_audio_reg(); +#endif + platform_driver_unregister(&omap_alsa_driver); +} + +module_init(omap_alsa_tsc2101_init); +module_exit(omap_alsa_tsc2101_exit); diff --cc sound/arm/omap/omap-alsa-tsc2102-mixer.c index cf1f8520575,00000000000..74c1fd6de0b mode 100644,000000..100644 --- a/sound/arm/omap/omap-alsa-tsc2102-mixer.c +++ b/sound/arm/omap/omap-alsa-tsc2102-mixer.c @@@ -1,284 -1,0 +1,284 @@@ +/* + * sound/arm/omap/omap-alsa-tsc2102-mixer.c + * + * Alsa mixer driver for TSC2102 chip for OMAP platforms. + * + * Copyright (c) 2006 Andrzej Zaborowski + * Code based on the TSC2101 ALSA driver. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include + - #include ++#include + +#include +#include + +#include "omap-alsa-tsc2102.h" +#include "omap-alsa-dma.h" + +static int vol[2], mute[2], filter[2]; + +/* + * Converts the Alsa mixer volume (0 - 100) to actual Digital + * Gain Control (DGC) value that can be written or read from the + * TSC2102 registers. + * + * Note that the number "OUTPUT_VOLUME_MAX" is smaller than + * OUTPUT_VOLUME_MIN because DGC works as a volume decreaser. (The + * higher the value sent to DAC, the more the volume of controlled + * channel is decreased) + */ +static void set_dac_gain_stereo(int left_ch, int right_ch) +{ + int lch, rch; + + if (left_ch > 100) + vol[0] = 100; + else if (left_ch < 0) + vol[0] = 0; + else + vol[0] = left_ch; + lch = OUTPUT_VOLUME_MIN - vol[0] * + (OUTPUT_VOLUME_MIN - OUTPUT_VOLUME_MAX) / 100; + + if (right_ch > 100) + vol[1] = 100; + else if (right_ch < 0) + vol[1] = 0; + else + vol[1] = right_ch; + rch = OUTPUT_VOLUME_MIN - vol[1] * + (OUTPUT_VOLUME_MIN - OUTPUT_VOLUME_MAX) / 100; + + tsc2102_set_volume(lch, rch); +} + +void init_playback_targets(void) +{ + set_dac_gain_stereo(DEFAULT_OUTPUT_VOLUME, DEFAULT_OUTPUT_VOLUME); + + /* Unmute */ + tsc2102_set_mute(0, 0); + + mute[0] = 0; + mute[1] = 0; + filter[0] = 0; + filter[1] = 0; +} + +/* + * Initializes TSC 2102 and playback target. + */ +void snd_omap_init_mixer(void) +{ + FN_IN; + + init_playback_targets(); + + FN_OUT(0); +} + +static int __pcm_playback_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 100; + return 0; +} + +static int __pcm_playback_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = vol[0]; /* L */ + ucontrol->value.integer.value[1] = vol[1]; /* R */ + + return 0; +} + +static int __pcm_playback_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + set_dac_gain_stereo( + ucontrol->value.integer.value[0], /* L */ + ucontrol->value.integer.value[1]); /* R */ + return 1; +} + +static int __pcm_playback_switch_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int __pcm_playback_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = !mute[0]; /* L */ + ucontrol->value.integer.value[1] = !mute[1]; /* R */ + + return 0; +} + +static int __pcm_playback_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + mute[0] = (ucontrol->value.integer.value[0] == 0); /* L */ + mute[1] = (ucontrol->value.integer.value[1] == 0); /* R */ + + tsc2102_set_mute(mute[0], mute[1]); + return 1; +} + +static int __pcm_playback_deemphasis_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int __pcm_playback_deemphasis_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = filter[0]; + return 0; +} + +static int __pcm_playback_deemphasis_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + filter[0] = (ucontrol->value.integer.value[0] > 0); + + tsc2102_set_deemphasis(filter[0]); + return 1; +} + +static int __pcm_playback_bassboost_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int __pcm_playback_bassboost_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = filter[1]; + return 0; +} + +static int __pcm_playback_bassboost_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + filter[1] = (ucontrol->value.integer.value[0] > 0); + + tsc2102_set_bassboost(filter[1]); + return 1; +} + +static struct snd_kcontrol_new tsc2102_controls[] __devinitdata = { + { + .name = "Master Playback Volume", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = __pcm_playback_volume_info, + .get = __pcm_playback_volume_get, + .put = __pcm_playback_volume_put, + }, + { + .name = "Master Playback Switch", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = __pcm_playback_switch_info, + .get = __pcm_playback_switch_get, + .put = __pcm_playback_switch_put, + }, + { + .name = "De-emphasis Filter Switch", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = __pcm_playback_deemphasis_info, + .get = __pcm_playback_deemphasis_get, + .put = __pcm_playback_deemphasis_put, + }, + { + .name = "Bass-boost Filter Switch", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = __pcm_playback_bassboost_info, + .get = __pcm_playback_bassboost_get, + .put = __pcm_playback_bassboost_put, + }, +}; + +#ifdef CONFIG_PM +void snd_omap_suspend_mixer(void) +{ + /* Nothing to do */ +} + +void snd_omap_resume_mixer(void) +{ + /* The chip was reset, restore the last used values */ + set_dac_gain_stereo(vol[0], vol[1]); + + tsc2102_set_mute(mute[0], mute[1]); + tsc2102_set_deemphasis(filter[0]); + tsc2102_set_bassboost(filter[1]); +} +#endif + +int snd_omap_mixer(struct snd_card_omap_codec *tsc2102) +{ + int i, err; + + if (!tsc2102) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(tsc2102_controls); i++) { + err = snd_ctl_add(tsc2102->card, + snd_ctl_new1(&tsc2102_controls[i], + tsc2102->card)); + + if (err < 0) + return err; + } + return 0; +} diff --cc sound/arm/omap/omap-alsa-tsc2102.c index 911e776ffc8,00000000000..4c34f7cf312 mode 100644,000000..100644 --- a/sound/arm/omap/omap-alsa-tsc2102.c +++ b/sound/arm/omap/omap-alsa-tsc2102.c @@@ -1,318 -1,0 +1,318 @@@ +/* + * sound/arm/omap/omap-alsa-tsc2102.c + * + * Alsa codec driver for TSC2102 chip for OMAP platforms. + * + * Copyright (c) 2006 Andrzej Zaborowski + * Code based on the TSC2101 ALSA driver. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include + - #include - #include - #include ++#include ++#include ++#include + +#include "omap-alsa-tsc2102.h" + +static struct clk *tsc2102_bclk; + +/* + * Hardware capabilities + */ + +/* DAC sampling rates (BCLK = 12 MHz) */ +static unsigned int rates[] = { + 7350, 8000, 8820, 9600, 11025, 12000, 14700, + 16000, 22050, 24000, 29400, 32000, 44100, 48000, +}; + +static struct snd_pcm_hw_constraint_list tsc2102_hw_constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static struct snd_pcm_hardware tsc2102_snd_omap_alsa_playback = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_KNOT, + .rate_min = 7350, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 128 * 1024, + .period_bytes_min = 32, + .period_bytes_max = 8 * 1024, + .periods_min = 16, + .periods_max = 255, + .fifo_size = 0, +}; + +#ifdef DUMP_TSC2102_AUDIO_REGISTERS +static void dump_tsc2102_audio_regs(void) +{ + printk(KERN_INFO "TSC2102_AUDIO1_CTRL = 0x%04x\n", + tsc2102_read_sync(TSC2102_AUDIO1_CTRL)); + printk(KERN_INFO "TSC2102_DAC_GAIN_CTRL = 0x%04x\n", + tsc2102_read_sync(TSC2102_DAC_GAIN_CTRL)); + printk(KERN_INFO "TSC2102_AUDIO2_CTRL = 0x%04x\n", + tsc2102_read_sync(TSC2102_AUDIO2_CTRL)); + printk(KERN_INFO "TSC2102_DAC_POWER_CTRL = 0x%04x\n", + tsc2102_read_sync(TSC2102_DAC_POWER_CTRL)); + printk(KERN_INFO "TSC2102_AUDIO3_CTRL = 0x%04x\n", + tsc2102_read_sync(TSC2102_AUDIO_CTRL_3)); + printk(KERN_INFO "TSC2102_LCH_BASS_BOOST_N0 = 0x%04x\n", + tsc2102_read_sync(TSC2102_LCH_BASS_BOOST_N0)); + printk(KERN_INFO "TSC2102_LCH_BASS_BOOST_N1 = 0x%04x\n", + tsc2102_read_sync(TSC2102_LCH_BASS_BOOST_N1)); + printk(KERN_INFO "TSC2102_LCH_BASS_BOOST_N2 = 0x%04x\n", + tsc2102_read_sync(TSC2102_LCH_BASS_BOOST_N2)); + printk(KERN_INFO "TSC2102_LCH_BASS_BOOST_N3 = 0x%04x\n", + tsc2102_read_sync(TSC2102_LCH_BASS_BOOST_N3)); + printk(KERN_INFO "TSC2102_LCH_BASS_BOOST_N4 = 0x%04x\n", + tsc2102_read_sync(TSC2102_LCH_BASS_BOOST_N4)); + printk(KERN_INFO "TSC2102_LCH_BASS_BOOST_N5 = 0x%04x\n", + tsc2102_read_sync(TSC2102_LCH_BASS_BOOST_N5)); + printk(KERN_INFO "TSC2102_LCH_BASS_BOOST_D1 = 0x%04x\n", + tsc2102_read_sync(TSC2102_LCH_BASS_BOOST_D1)); + printk(KERN_INFO "TSC2102_LCH_BASS_BOOST_D2 = 0x%04x\n", + tsc2102_read_sync(TSC2102_LCH_BASS_BOOST_D2)); + printk(KERN_INFO "TSC2102_LCH_BASS_BOOST_D4 = 0x%04x\n", + tsc2102_read_sync(TSC2102_LCH_BASS_BOOST_D4)); + printk(KERN_INFO "TSC2102_LCH_BASS_BOOST_D5 = 0x%04x\n", + tsc2102_read_sync(TSC2102_LCH_BASS_BOOST_D5)); + printk(KERN_INFO "TSC2102_RCH_BASS_BOOST_N0 = 0x%04x\n", + tsc2102_read_sync(TSC2102_RCH_BASS_BOOST_N0)); + printk(KERN_INFO "TSC2102_RCH_BASS_BOOST_N1 = 0x%04x\n", + tsc2102_read_sync(TSC2102_RCH_BASS_BOOST_N1)); + printk(KERN_INFO "TSC2102_RCH_BASS_BOOST_N2 = 0x%04x\n", + tsc2102_read_sync(TSC2102_RCH_BASS_BOOST_N2)); + printk(KERN_INFO "TSC2102_RCH_BASS_BOOST_N3 = 0x%04x\n", + tsc2102_read_sync(TSC2102_RCH_BASS_BOOST_N3)); + printk(KERN_INFO "TSC2102_RCH_BASS_BOOST_N4 = 0x%04x\n", + tsc2102_read_sync(TSC2102_RCH_BASS_BOOST_N4)); + printk(KERN_INFO "TSC2102_RCH_BASS_BOOST_N5 = 0x%04x\n", + tsc2102_read_sync(TSC2102_RCH_BASS_BOOST_N5)); + printk(KERN_INFO "TSC2102_RCH_BASS_BOOST_D1 = 0x%04x\n", + tsc2102_read_sync(TSC2102_RCH_BASS_BOOST_D1)); + printk(KERN_INFO "TSC2102_RCH_BASS_BOOST_D2 = 0x%04x\n", + tsc2102_read_sync(TSC2102_RCH_BASS_BOOST_D2)); + printk(KERN_INFO "TSC2102_RCH_BASS_BOOST_D4 = 0x%04x\n", + tsc2102_read_sync(TSC2102_RCH_BASS_BOOST_D4)); + printk(KERN_INFO "TSC2102_RCH_BASS_BOOST_D5 = 0x%04x\n", + tsc2102_read_sync(TSC2102_RCH_BASS_BOOST_D5)); + printk(KERN_INFO "TSC2102_PLL1_CTRL = 0x%04x\n", + tsc2102_read_sync(TSC2102_PLL1_CTRL)); + printk(KERN_INFO "TSC2102_PLL2_CTRL = 0x%04x\n", + tsc2102_read_sync(TSC2102_PLL2_CTRL)); + printk(KERN_INFO "TSC2102_AUDIO4_CTRL = 0x%04x\n", + tsc2102_read_sync(TSC2102_AUDIO4_CTRL)); +} +#endif + +/* + * ALSA operations according to board file + */ + +static long current_rate; + +/* + * Sample rate changing + */ +static void tsc2102_set_samplerate(long sample_rate) +{ + int clkgdv = 0; + u16 srgr1, srgr2; + + if (sample_rate == current_rate) + return; + current_rate = 0; + + if (tsc2102_set_rate(sample_rate)) + return; + + /* Set the sample rate */ +#ifndef TSC_MASTER + clkgdv = CODEC_CLOCK / (sample_rate * (DEFAULT_BITPERSAMPLE * 2 - 1)); + if (clkgdv) + srgr1 = (FWID(DEFAULT_BITPERSAMPLE - 1) | CLKGDV(clkgdv)); + else + return; + + /* Stereo Mode */ + srgr2 = CLKSM | FSGM | FPER(DEFAULT_BITPERSAMPLE * 2 - 1); +#else + srgr1 = FWID(DEFAULT_BITPERSAMPLE - 1) | CLKGDV(clkgdv); + srgr2 = GSYNC | CLKSP | FSGM | FPER(DEFAULT_BITPERSAMPLE * 2 - 1); +#endif + OMAP_MCBSP_WRITE(OMAP1510_MCBSP1_BASE, SRGR2, srgr2); + OMAP_MCBSP_WRITE(OMAP1510_MCBSP1_BASE, SRGR1, srgr1); + current_rate = sample_rate; +} + +static void tsc2102_configure(void) +{ + tsc2102_dac_power(1); + +#ifdef TSC_MASTER + tsc2102_set_i2s_master(1); +#else + tsc2102_set_i2s_master(0); +#endif +} + +/* + * Omap McBSP clock and Power Management configuration + * + * Here we have some functions that allow clock to be enabled and + * disabled only when needed. Besides doing clock configuration + * they allow turn audio on and off when necessary. + */ + +/* + * Do clock framework bclk search + */ +static void tsc2102_clock_setup(void) +{ + tsc2102_bclk = clk_get(0, "bclk"); +} + +/* + * Do some sanity checks, set clock rate, start it. + */ +static int tsc2102_clock_on(void) +{ + int err; + + if (clk_get_usecount(tsc2102_bclk) > 0 && + clk_get_rate(tsc2102_bclk) != CODEC_CLOCK) { + /* BCLK is already in use */ + printk(KERN_WARNING + "BCLK already in use at %d Hz. We change it to %d Hz\n", + (uint) clk_get_rate(tsc2102_bclk), CODEC_CLOCK); + + err = clk_set_rate(tsc2102_bclk, CODEC_CLOCK); + if (err) + printk(KERN_WARNING "Cannot set BCLK clock rate " + "for TSC2102 codec, error code = %d\n", err); + } + + clk_enable(tsc2102_bclk); + return 0; +} + +/* + * Turn off the audio codec and then stop the clock. + */ +static int tsc2102_clock_off(void) +{ + DPRINTK("clock use count = %d\n", clk_get_usecount(tsc2102_bclk)); + + clk_disable(tsc2102_bclk); + return 0; +} + +static int tsc2102_get_default_samplerate(void) +{ + return DEFAULT_SAMPLE_RATE; +} + +static int snd_omap_alsa_tsc2102_suspend( + struct platform_device *pdev, pm_message_t state) +{ + tsc2102_dac_power(0); + current_rate = 0; + + return snd_omap_alsa_suspend(pdev, state); +} + +static int snd_omap_alsa_tsc2102_resume(struct platform_device *pdev) +{ + tsc2102_dac_power(1); + +#ifdef TSC_MASTER + tsc2102_set_i2s_master(1); +#else + tsc2102_set_i2s_master(0); +#endif + + return snd_omap_alsa_resume(pdev); +} + +static int __init snd_omap_alsa_tsc2102_probe(struct platform_device *pdev) +{ + int ret; + struct omap_alsa_codec_config *codec_cfg = pdev->dev.platform_data; + + if (codec_cfg) { + codec_cfg->hw_constraints_rates = + &tsc2102_hw_constraints_rates; + codec_cfg->snd_omap_alsa_playback = + &tsc2102_snd_omap_alsa_playback; + codec_cfg->codec_configure_dev = tsc2102_configure; + codec_cfg->codec_set_samplerate = tsc2102_set_samplerate; + codec_cfg->codec_clock_setup = tsc2102_clock_setup; + codec_cfg->codec_clock_on = tsc2102_clock_on; + codec_cfg->codec_clock_off = tsc2102_clock_off; + codec_cfg->get_default_samplerate = + tsc2102_get_default_samplerate; + ret = snd_omap_alsa_post_probe(pdev, codec_cfg); + } else + ret = -ENODEV; + + return ret; +} + +static int snd_omap_alsa_tsc2102_remove(struct platform_device *pdev) +{ + tsc2102_dac_power(0); + + return snd_omap_alsa_remove(pdev); +} + +static struct platform_driver omap_alsa_driver = { + .probe = snd_omap_alsa_tsc2102_probe, + .remove = snd_omap_alsa_tsc2102_remove, + .suspend = snd_omap_alsa_tsc2102_suspend, + .resume = snd_omap_alsa_tsc2102_resume, + .driver = { + .name = "tsc2102-alsa", + .owner = THIS_MODULE, + }, +}; + +static int __init omap_alsa_tsc2102_init(void) +{ + int err; + + ADEBUG(); + err = platform_driver_register(&omap_alsa_driver); + + return err; +} + +static void __exit omap_alsa_tsc2102_exit(void) +{ + ADEBUG(); + platform_driver_unregister(&omap_alsa_driver); +} + +module_init(omap_alsa_tsc2102_init); +module_exit(omap_alsa_tsc2102_exit); diff --cc sound/arm/omap/omap-alsa.c index f9293cda59c,00000000000..45c46e2dfb1 mode 100644,000000..100644 --- a/sound/arm/omap/omap-alsa.c +++ b/sound/arm/omap/omap-alsa.c @@@ -1,591 -1,0 +1,591 @@@ +/* + * sound/arm/omap-alsa.c + * + * Alsa Driver for OMAP + * + * Copyright (C) 2005 Instituto Nokia de Tecnologia - INdT - Manaus Brazil + * Written by Daniel Petrini, David Cohen, Anderson Briglia + * {daniel.petrini, david.cohen, anderson.briglia}@indt.org.br + * + * Copyright (C) 2006 Mika Laitio + * + * Based on sa11xx-uda1341.c, + * Copyright (C) 2002 Tomas Kasparek + * + * 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. + * + * History: + * + * 2005-07-29 INdT Kernel Team - Alsa driver for omap osk. Creation of new + * file omap-aic23.c + * + * 2005-12-18 Dirk Behme - Added L/R Channel Interchange fix as proposed + * by Ajaya Babu + * + */ + +#include +#ifdef CONFIG_PM +#include +#endif +#include +#include + - #include ++#include +#include "omap-alsa-dma.h" + +MODULE_AUTHOR("Mika Laitio"); +MODULE_AUTHOR("Daniel Petrini"); +MODULE_AUTHOR("David Cohen"); +MODULE_AUTHOR("Anderson Briglia"); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("OMAP driver for ALSA"); +MODULE_ALIAS("omap_alsa_mcbsp.1"); + +static char *id; +static struct snd_card_omap_codec *alsa_codec; +static struct omap_alsa_codec_config *alsa_codec_config; + +/* FIXME: Please change to use omap asoc framework instead, this can be racy */ +static dma_addr_t dma_start_pos; + +/* + * HW interface start and stop helper functions + */ +static int audio_ifc_start(void) +{ + omap_mcbsp_start(AUDIO_MCBSP); + return 0; +} + +static int audio_ifc_stop(void) +{ + omap_mcbsp_stop(AUDIO_MCBSP); + return 0; +} + +static void omap_alsa_audio_init(struct snd_card_omap_codec *omap_alsa) +{ + /* Setup DMA stuff */ + omap_alsa->s[SNDRV_PCM_STREAM_PLAYBACK].id = "Alsa omap out"; + omap_alsa->s[SNDRV_PCM_STREAM_PLAYBACK].stream_id = + SNDRV_PCM_STREAM_PLAYBACK; + omap_alsa->s[SNDRV_PCM_STREAM_PLAYBACK].dma_dev = + OMAP_DMA_MCBSP1_TX; + omap_alsa->s[SNDRV_PCM_STREAM_PLAYBACK].hw_start = + audio_ifc_start; + omap_alsa->s[SNDRV_PCM_STREAM_PLAYBACK].hw_stop = + audio_ifc_stop; + + omap_alsa->s[SNDRV_PCM_STREAM_CAPTURE].id = "Alsa omap in"; + omap_alsa->s[SNDRV_PCM_STREAM_CAPTURE].stream_id = + SNDRV_PCM_STREAM_CAPTURE; + omap_alsa->s[SNDRV_PCM_STREAM_CAPTURE].dma_dev = + OMAP_DMA_MCBSP1_RX; + omap_alsa->s[SNDRV_PCM_STREAM_CAPTURE].hw_start = + audio_ifc_start; + omap_alsa->s[SNDRV_PCM_STREAM_CAPTURE].hw_stop = + audio_ifc_stop; +} + +/* + * DMA functions + * Depends on omap-alsa-dma.c functions and (omap) dma.c + */ +static int audio_dma_request(struct audio_stream *s, + void (*callback) (void *)) +{ + int err; + ADEBUG(); + + err = omap_request_alsa_sound_dma(s->dma_dev, s->id, s, &s->lch); + if (err < 0) + printk(KERN_ERR "Unable to grab audio dma 0x%x\n", s->dma_dev); + return err; +} + +static int audio_dma_free(struct audio_stream *s) +{ + int err = 0; + ADEBUG(); + + err = omap_free_alsa_sound_dma(s, &s->lch); + if (err < 0) + printk(KERN_ERR "Unable to free audio dma channels!\n"); + return err; +} + +/* + * This function should calculate the current position of the dma in the + * buffer. It will help alsa middle layer to continue update the buffer. + * Its correctness is crucial for good functioning. + */ +static u_int audio_get_dma_pos(struct audio_stream *s) +{ + struct snd_pcm_substream *substream = s->stream; + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int offset; + unsigned long flags; + dma_addr_t count; + ADEBUG(); + + /* this must be called w/ interrupts locked as requested in dma.c */ + spin_lock_irqsave(&s->dma_lock, flags); + + /* For the current period let's see where we are */ + count = omap_get_dma_src_pos(s->lch[s->dma_q_head]) - dma_start_pos; + + spin_unlock_irqrestore(&s->dma_lock, flags); + + /* Now, the position related to the end of that period */ + offset = bytes_to_frames(runtime, s->offset) - + bytes_to_frames(runtime, count); + + if (offset >= runtime->buffer_size) + offset = 0; + + return offset; +} + +/* + * this stops the dma and clears the dma ptrs + */ +static void audio_stop_dma(struct audio_stream *s) +{ + unsigned long flags; + ADEBUG(); + + spin_lock_irqsave(&s->dma_lock, flags); + s->active = 0; + s->period = 0; + s->periods = 0; + + /* this stops the dma channel and clears the buffer ptrs */ + omap_stop_alsa_sound_dma(s); + + omap_clear_alsa_sound_dma(s); + + spin_unlock_irqrestore(&s->dma_lock, flags); +} + +/* + * Main dma routine, requests dma according where you are in main alsa buffer + */ +static void audio_process_dma(struct audio_stream *s) +{ + struct snd_pcm_substream *substream = s->stream; + struct snd_pcm_runtime *runtime; + unsigned int dma_size; + unsigned int offset; + int ret; + + ADEBUG(); + runtime = substream->runtime; + if (s->active) { + dma_size = frames_to_bytes(runtime, runtime->period_size); + offset = dma_size * s->period; + snd_assert(dma_size <= DMA_BUF_SIZE, return); + /* + * On omap1510 based devices, we need to call the stop_dma + * before calling the start_dma or we will not receive the + * irq from DMA after the first transfered/played buffer. + * (invocation of callback_omap_alsa_sound_dma() method). + */ + if (cpu_is_omap1510()) + omap_stop_alsa_sound_dma(s); + + dma_start_pos = (dma_addr_t)runtime->dma_area + offset; + ret = omap_start_alsa_sound_dma(s, dma_start_pos, dma_size); + if (ret) { + printk(KERN_ERR "audio_process_dma: cannot" + " queue DMA buffer (%i)\n", ret); + return; + } + + s->period++; + s->period %= runtime->periods; + s->periods++; + s->offset = offset; + } +} + +/* + * This is called when dma IRQ occurs at the end of each transmited block + */ +void callback_omap_alsa_sound_dma(void *data) +{ + struct audio_stream *s = data; + + ADEBUG(); + /* + * If we are getting a callback for an active stream then we inform + * the PCM middle layer we've finished a period + */ + if (s->active) + snd_pcm_period_elapsed(s->stream); + + spin_lock(&s->dma_lock); + if (s->periods > 0) + s->periods--; + + audio_process_dma(s); + spin_unlock(&s->dma_lock); +} + +/* + * Alsa section + * PCM settings and callbacks + */ +static int snd_omap_alsa_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_card_omap_codec *chip = + snd_pcm_substream_chip(substream); + int stream_id = substream->pstr->stream; + struct audio_stream *s = &chip->s[stream_id]; + int err = 0; + + ADEBUG(); + /* note local interrupts are already disabled in the midlevel code */ + spin_lock(&s->dma_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* requested stream startup */ + s->active = 1; + audio_process_dma(s); + break; + case SNDRV_PCM_TRIGGER_STOP: + /* requested stream shutdown */ + audio_stop_dma(s); + break; + default: + err = -EINVAL; + break; + } + spin_unlock(&s->dma_lock); + + return err; +} + +static int snd_omap_alsa_prepare(struct snd_pcm_substream *substream) +{ + struct snd_card_omap_codec *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_stream *s = &chip->s[substream->pstr->stream]; + + ADEBUG(); + /* set requested samplerate */ + alsa_codec_config->codec_set_samplerate(runtime->rate); + chip->samplerate = runtime->rate; + + s->period = 0; + s->periods = 0; + + return 0; +} + +static snd_pcm_uframes_t +snd_omap_alsa_pointer(struct snd_pcm_substream *substream) +{ + struct snd_card_omap_codec *chip = snd_pcm_substream_chip(substream); + + ADEBUG(); + return audio_get_dma_pos(&chip->s[substream->pstr->stream]); +} + +static int snd_card_omap_alsa_open(struct snd_pcm_substream *substream) +{ + struct snd_card_omap_codec *chip = + snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int stream_id = substream->pstr->stream; + int err; + + ADEBUG(); + chip->s[stream_id].stream = substream; + alsa_codec_config->codec_clock_on(); + if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) + runtime->hw = *(alsa_codec_config->snd_omap_alsa_playback); + else + runtime->hw = *(alsa_codec_config->snd_omap_alsa_capture); + + err = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + alsa_codec_config->hw_constraints_rates); + if (err < 0) + return err; + + return 0; +} + +static int snd_card_omap_alsa_close(struct snd_pcm_substream *substream) +{ + struct snd_card_omap_codec *chip = snd_pcm_substream_chip(substream); + + ADEBUG(); + alsa_codec_config->codec_clock_off(); + chip->s[substream->pstr->stream].stream = NULL; + + return 0; +} + +/* HW params & free */ +static int snd_omap_alsa_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static int snd_omap_alsa_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +/* pcm operations */ +static struct snd_pcm_ops snd_card_omap_alsa_playback_ops = { + .open = snd_card_omap_alsa_open, + .close = snd_card_omap_alsa_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_omap_alsa_hw_params, + .hw_free = snd_omap_alsa_hw_free, + .prepare = snd_omap_alsa_prepare, + .trigger = snd_omap_alsa_trigger, + .pointer = snd_omap_alsa_pointer, +}; + +static struct snd_pcm_ops snd_card_omap_alsa_capture_ops = { + .open = snd_card_omap_alsa_open, + .close = snd_card_omap_alsa_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_omap_alsa_hw_params, + .hw_free = snd_omap_alsa_hw_free, + .prepare = snd_omap_alsa_prepare, + .trigger = snd_omap_alsa_trigger, + .pointer = snd_omap_alsa_pointer, +}; + +/* + * Alsa init and exit section + * Inits pcm alsa structures, allocate the alsa buffer, suspend, resume + */ +static int __init snd_card_omap_alsa_pcm(struct snd_card_omap_codec *omap_alsa, + int device) +{ + struct snd_pcm *pcm; + int err; + + ADEBUG(); + err = snd_pcm_new(omap_alsa->card, "OMAP PCM", device, 1, 1, &pcm); + if (err < 0) + return err; + + /* sets up initial buffer with continuous allocation */ + snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data + (GFP_KERNEL), + 128 * 1024, 128 * 1024); + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_card_omap_alsa_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_card_omap_alsa_capture_ops); + pcm->private_data = omap_alsa; + pcm->info_flags = 0; + strcpy(pcm->name, "omap alsa pcm"); + + omap_alsa_audio_init(omap_alsa); + + /* setup DMA controller */ + audio_dma_request(&omap_alsa->s[SNDRV_PCM_STREAM_PLAYBACK], + callback_omap_alsa_sound_dma); + audio_dma_request(&omap_alsa->s[SNDRV_PCM_STREAM_CAPTURE], + callback_omap_alsa_sound_dma); + + omap_alsa->pcm = pcm; + + return 0; +} + + +#ifdef CONFIG_PM +/* + * Driver suspend/resume - calls alsa functions. Some hints from aaci.c + */ +int snd_omap_alsa_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_card_omap_codec *chip; + struct snd_card *card = platform_get_drvdata(pdev); + + if (card->power_state != SNDRV_CTL_POWER_D3hot) { + chip = card->private_data; + if (chip->card->power_state != SNDRV_CTL_POWER_D3hot) { + snd_power_change_state(chip->card, + SNDRV_CTL_POWER_D3hot); + snd_pcm_suspend_all(chip->pcm); + /* Mutes and turn clock off */ + alsa_codec_config->codec_clock_off(); + snd_omap_suspend_mixer(); + } + } + return 0; +} + +int snd_omap_alsa_resume(struct platform_device *pdev) +{ + struct snd_card_omap_codec *chip; + struct snd_card *card = platform_get_drvdata(pdev); + + if (card->power_state != SNDRV_CTL_POWER_D0) { + chip = card->private_data; + if (chip->card->power_state != SNDRV_CTL_POWER_D0) { + snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0); + alsa_codec_config->codec_clock_on(); + snd_omap_resume_mixer(); + } + } + return 0; +} + +#endif /* CONFIG_PM */ + +void snd_omap_alsa_free(struct snd_card *card) +{ + struct snd_card_omap_codec *chip = card->private_data; + ADEBUG(); + + /* + * Turn off codec after it is done. + * Can't do it immediately, since it may still have + * buffered data. + */ + schedule_timeout_interruptible(2); + + omap_mcbsp_stop(AUDIO_MCBSP); + omap_mcbsp_free(AUDIO_MCBSP); + + audio_dma_free(&chip->s[SNDRV_PCM_STREAM_PLAYBACK]); + audio_dma_free(&chip->s[SNDRV_PCM_STREAM_CAPTURE]); +} + +/* module init & exit */ + +/* + * Inits alsa soudcard structure. + * Called by the probe method in codec after function pointers has been set. + */ +int snd_omap_alsa_post_probe(struct platform_device *pdev, + struct omap_alsa_codec_config *config) +{ + int err = 0; + int def_rate; + struct snd_card *card; + + ADEBUG(); + alsa_codec_config = config; + + alsa_codec_config->codec_clock_setup(); + alsa_codec_config->codec_clock_on(); + + omap_mcbsp_request(AUDIO_MCBSP); + omap_mcbsp_stop(AUDIO_MCBSP); + omap_mcbsp_config(AUDIO_MCBSP, alsa_codec_config->mcbsp_regs_alsa); + omap_mcbsp_start(AUDIO_MCBSP); + + if (alsa_codec_config && alsa_codec_config->codec_configure_dev) + alsa_codec_config->codec_configure_dev(); + + alsa_codec_config->codec_clock_off(); + + /* register the soundcard */ + card = snd_card_new(-1, id, THIS_MODULE, sizeof(alsa_codec)); + if (card == NULL) + goto nodev1; + + alsa_codec = kcalloc(1, sizeof(*alsa_codec), GFP_KERNEL); + if (alsa_codec == NULL) + goto nodev2; + + card->private_data = (void *)alsa_codec; + card->private_free = snd_omap_alsa_free; + + alsa_codec->card = card; + def_rate = alsa_codec_config->get_default_samplerate(); + alsa_codec->samplerate = def_rate; + + spin_lock_init(&alsa_codec->s[0].dma_lock); + spin_lock_init(&alsa_codec->s[1].dma_lock); + + /* mixer */ + err = snd_omap_mixer(alsa_codec); + if (err < 0) + goto nodev3; + + /* PCM */ + err = snd_card_omap_alsa_pcm(alsa_codec, 0); + if (err < 0) + goto nodev3; + + strcpy(card->driver, "OMAP_ALSA"); + strcpy(card->shortname, alsa_codec_config->name); + sprintf(card->longname, alsa_codec_config->name); + + snd_omap_init_mixer(); + snd_card_set_dev(card, &pdev->dev); + + err = snd_card_register(card); + if (err == 0) { + printk(KERN_INFO "audio support initialized\n"); + platform_set_drvdata(pdev, card); + return 0; + } + +nodev3: + kfree(alsa_codec); +nodev2: + snd_card_free(card); +nodev1: + omap_mcbsp_stop(AUDIO_MCBSP); + omap_mcbsp_free(AUDIO_MCBSP); + + return err; +} + +int snd_omap_alsa_remove(struct platform_device *pdev) +{ + struct snd_card *card = platform_get_drvdata(pdev); + struct snd_card_omap_codec *chip = card->private_data; + + snd_card_free(card); + + alsa_codec = NULL; + card->private_data = NULL; + kfree(chip); + + platform_set_drvdata(pdev, NULL); + + return 0; +} diff --cc sound/oss/omap-audio-aic23.c index 0ecb2f77c67,00000000000..c603aaa18d5 mode 100644,000000..100644 --- a/sound/oss/omap-audio-aic23.c +++ b/sound/oss/omap-audio-aic23.c @@@ -1,754 -1,0 +1,754 @@@ +/* + * linux/sound/oss/omap-audio-aic23.c + * + * Glue audio driver for TI TLV320AIC23 codec + * + * Copyright (c) 2000 Nicolas Pitre + * Copyright (C) 2001, Steve Johnson + * Copyright (C) 2004 Texas Instruments, Inc. + * Copyright (C) 2005 Dirk Behme + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include - #include ++#include +#include +#include + - #include - #include - #include - #include ++#include ++#include ++#include ++#include + +#include "omap-audio.h" +#include "omap-audio-dma-intfc.h" + +#ifdef CONFIG_PROC_FS +#include +#define PROC_START_FILE "driver/aic23-audio-start" +#define PROC_STOP_FILE "driver/aic23-audio-stop" +#endif + +//#define DEBUG + +#ifdef DEBUG +#define DPRINTK(ARGS...) printk("<%s>: ",__FUNCTION__);printk(ARGS) +#else +#define DPRINTK( x... ) +#endif + +#define CODEC_NAME "AIC23" + +#if CONFIG_MACH_OMAP_OSK +#define PLATFORM_NAME "OMAP OSK" +#elif CONFIG_MACH_OMAP_INNOVATOR +#define PLATFORM_NAME "OMAP INNOVATOR" +#else +#error "Unsupported plattform" +#endif + +/* Define to set the AIC23 as the master w.r.t McBSP */ +#define AIC23_MASTER + +#define CODEC_CLOCK 12000000 + +/* + * AUDIO related MACROS + */ +#define DEFAULT_BITPERSAMPLE 16 +#define AUDIO_RATE_DEFAULT 44100 + +/* Select the McBSP For Audio */ +#define AUDIO_MCBSP OMAP_MCBSP1 + +#define REC_MASK (SOUND_MASK_LINE | SOUND_MASK_MIC) +#define DEV_MASK (REC_MASK | SOUND_MASK_VOLUME) + +#define SET_VOLUME 1 +#define SET_LINE 2 + +#define DEFAULT_OUTPUT_VOLUME 93 +#define DEFAULT_INPUT_VOLUME 0 /* 0 ==> mute line in */ + +#define OUTPUT_VOLUME_MIN LHV_MIN +#define OUTPUT_VOLUME_MAX LHV_MAX +#define OUTPUT_VOLUME_RANGE (OUTPUT_VOLUME_MAX - OUTPUT_VOLUME_MIN) +#define OUTPUT_VOLUME_MASK OUTPUT_VOLUME_MAX + +#define INPUT_VOLUME_MIN LIV_MIN +#define INPUT_VOLUME_MAX LIV_MAX +#define INPUT_VOLUME_RANGE (INPUT_VOLUME_MAX - INPUT_VOLUME_MIN) +#define INPUT_VOLUME_MASK INPUT_VOLUME_MAX + +#define NUMBER_SAMPLE_RATES_SUPPORTED 9 + +/* + * HW interface start and stop helper functions + */ +static int audio_ifc_start(void) +{ + omap_mcbsp_start(AUDIO_MCBSP); + return 0; +} + +static int audio_ifc_stop(void) +{ + omap_mcbsp_stop(AUDIO_MCBSP); + return 0; +} + +static audio_stream_t output_stream = { + .id = "AIC23 out", + .dma_dev = OMAP_DMA_MCBSP1_TX, + .input_or_output = FMODE_WRITE, + .hw_start = audio_ifc_start, + .hw_stop = audio_ifc_stop +}; + +static audio_stream_t input_stream = { + .id = "AIC23 in", + .dma_dev = OMAP_DMA_MCBSP1_RX, + .input_or_output = FMODE_READ, + .hw_start = audio_ifc_start, + .hw_stop = audio_ifc_stop +}; + +static struct clk *aic23_mclk = 0; + +static int audio_dev_id, mixer_dev_id; + +static struct aic23_local_info { + u8 volume; + u16 volume_reg; + u8 line; + u8 mic; + u16 input_volume_reg; + int mod_cnt; +} aic23_local; + +struct sample_rate_reg_info { + u32 sample_rate; + u8 control; /* SR3, SR2, SR1, SR0 and BOSR */ + u8 divider; /* if 0 CLKIN = MCLK, if 1 CLKIN = MCLK/2 */ +}; + +/* To Store the default sample rate */ +static long audio_samplerate = AUDIO_RATE_DEFAULT; + +/* DAC USB-mode sampling rates (MCLK = 12 MHz) */ +static const struct sample_rate_reg_info +reg_info[NUMBER_SAMPLE_RATES_SUPPORTED] = { + {96000, 0x0E, 0}, + {88200, 0x1F, 0}, + {48000, 0x00, 0}, + {44100, 0x11, 0}, + {32000, 0x0C, 0}, + {24000, 0x00, 1}, + {16000, 0x0C, 1}, + { 8000, 0x06, 0}, + { 4000, 0x06, 1}, +}; + +static struct omap_mcbsp_reg_cfg initial_config = { + .spcr2 = FREE | FRST | GRST | XRST | XINTM(3), + .spcr1 = RINTM(3) | RRST, + .rcr2 = RPHASE | RFRLEN2(OMAP_MCBSP_WORD_8) | + RWDLEN2(OMAP_MCBSP_WORD_16) | RDATDLY(0), + .rcr1 = RFRLEN1(OMAP_MCBSP_WORD_8) | RWDLEN1(OMAP_MCBSP_WORD_16), + .xcr2 = XPHASE | XFRLEN2(OMAP_MCBSP_WORD_8) | + XWDLEN2(OMAP_MCBSP_WORD_16) | XDATDLY(0) | XFIG, + .xcr1 = XFRLEN1(OMAP_MCBSP_WORD_8) | XWDLEN1(OMAP_MCBSP_WORD_16), + .srgr1 = FWID(DEFAULT_BITPERSAMPLE - 1), + .srgr2 = GSYNC | CLKSP | FSGM | FPER(DEFAULT_BITPERSAMPLE * 2 - 1), +#ifndef AIC23_MASTER + /* configure McBSP to be the I2S master */ + .pcr0 = FSXM | FSRM | CLKXM | CLKRM | CLKXP | CLKRP, +#else + /* configure McBSP to be the I2S slave */ + .pcr0 = CLKXP | CLKRP, +#endif /* AIC23_MASTER */ +}; + +static void omap_aic23_initialize(void *dummy); +static void omap_aic23_shutdown(void *dummy); +static int omap_aic23_ioctl(struct inode *inode, struct file *file, + uint cmd, ulong arg); +static int omap_aic23_probe(void); +#ifdef MODULE +static void omap_aic23_remove(void); +#endif +static int omap_aic23_suspend(void); +static int omap_aic23_resume(void); +static inline void aic23_configure(void); +static int mixer_open(struct inode *inode, struct file *file); +static int mixer_release(struct inode *inode, struct file *file); +static int mixer_ioctl(struct inode *inode, struct file *file, uint cmd, + ulong arg); + +#ifdef CONFIG_PROC_FS +static int codec_start(char *buf, char **start, off_t offset, int count, + int *eof, void *data); +static int codec_stop(char *buf, char **start, off_t offset, int count, + int *eof, void *data); +#endif + + +/* File Op structure for mixer */ +static struct file_operations omap_mixer_fops = { + .open = mixer_open, + .release = mixer_release, + .ioctl = mixer_ioctl, + .owner = THIS_MODULE +}; + +/* To store characteristic info regarding the codec for the audio driver */ +static audio_state_t aic23_state = { + .output_stream = &output_stream, + .input_stream = &input_stream, +/* .need_tx_for_rx = 1, //Once the Full Duplex works */ + .need_tx_for_rx = 0, + .hw_init = omap_aic23_initialize, + .hw_shutdown = omap_aic23_shutdown, + .client_ioctl = omap_aic23_ioctl, + .hw_probe = omap_aic23_probe, + .hw_remove = __exit_p(omap_aic23_remove), + .hw_suspend = omap_aic23_suspend, + .hw_resume = omap_aic23_resume, +}; + +/* This will be defined in the audio.h */ +static struct file_operations *omap_audio_fops; + +extern int aic23_write_value(u8 reg, u16 value); + +/* TLV320AIC23 is a write only device */ +static __inline__ void audio_aic23_write(u8 address, u16 data) +{ + aic23_write_value(address, data); +} + +static int aic23_update(int flag, int val) +{ + u16 volume; + + /* Ignore separate left/right channel for now, + even the codec does support it. */ + val &= 0xff; + + if (val < 0 || val > 100) { + printk(KERN_ERR "Trying a bad volume value(%d)!\n",val); + return -EPERM; + } + + switch (flag) { + case SET_VOLUME: + // Convert 0 -> 100 volume to 0x00 (LHV_MIN) -> 0x7f (LHV_MAX) + // volume range + volume = ((val * OUTPUT_VOLUME_RANGE) / 100) + OUTPUT_VOLUME_MIN; + + // R/LHV[6:0] 1111111 (+6dB) to 0000000 (-73dB) in 1db steps, + // default 1111001 (0dB) + aic23_local.volume_reg &= ~OUTPUT_VOLUME_MASK; + aic23_local.volume_reg |= volume; + audio_aic23_write(LEFT_CHANNEL_VOLUME_ADDR, aic23_local.volume_reg); + audio_aic23_write(RIGHT_CHANNEL_VOLUME_ADDR, aic23_local.volume_reg); + break; + + case SET_LINE: + // Convert 0 -> 100 volume to 0x0 (LIV_MIN) -> 0x1f (LIV_MAX) + // volume range + volume = ((val * INPUT_VOLUME_RANGE) / 100) + INPUT_VOLUME_MIN; + + // R/LIV[4:0] 11111 (+12dB) to 00000 (-34.5dB) in 1.5dB steps, + // default 10111 (0dB) + aic23_local.input_volume_reg &= ~INPUT_VOLUME_MASK; + aic23_local.input_volume_reg |= volume; + audio_aic23_write(LEFT_LINE_VOLUME_ADDR, aic23_local.input_volume_reg); + audio_aic23_write(RIGHT_LINE_VOLUME_ADDR, aic23_local.input_volume_reg); + break; + } + return 0; +} + +static int mixer_open(struct inode *inode, struct file *file) +{ + /* Any mixer specific initialization */ + + return 0; +} + +static int mixer_release(struct inode *inode, struct file *file) +{ + /* Any mixer specific Un-initialization */ + + return 0; +} + +static int +mixer_ioctl(struct inode *inode, struct file *file, uint cmd, ulong arg) +{ + int val; + int ret = 0; + int nr = _IOC_NR(cmd); + + /* + * We only accept mixer (type 'M') ioctls. + */ + if (_IOC_TYPE(cmd) != 'M') + return -EINVAL; + + DPRINTK(" 0x%08x\n", cmd); + + if (cmd == SOUND_MIXER_INFO) { + struct mixer_info mi; + + strncpy(mi.id, "AIC23", sizeof(mi.id)); + strncpy(mi.name, "TI AIC23", sizeof(mi.name)); + mi.modify_counter = aic23_local.mod_cnt; + return copy_to_user((void *)arg, &mi, sizeof(mi)); + } + + if (_IOC_DIR(cmd) & _IOC_WRITE) { + ret = get_user(val, (int *)arg); + if (ret) + goto out; + + + switch (nr) { + case SOUND_MIXER_VOLUME: + aic23_local.volume = val; + aic23_local.mod_cnt++; + ret = aic23_update(SET_VOLUME, val); + break; + + case SOUND_MIXER_LINE: + aic23_local.line = val; + aic23_local.mod_cnt++; + ret = aic23_update(SET_LINE, val); + break; + + case SOUND_MIXER_MIC: + aic23_local.mic = val; + aic23_local.mod_cnt++; + ret = aic23_update(SET_LINE, val); + break; + + case SOUND_MIXER_RECSRC: + break; + + default: + ret = -EINVAL; + } + } + + if (ret == 0 && _IOC_DIR(cmd) & _IOC_READ) { + ret = 0; + + switch (nr) { + case SOUND_MIXER_VOLUME: + val = aic23_local.volume; + break; + case SOUND_MIXER_LINE: + val = aic23_local.line; + break; + case SOUND_MIXER_MIC: + val = aic23_local.mic; + break; + case SOUND_MIXER_RECSRC: + val = REC_MASK; + break; + case SOUND_MIXER_RECMASK: + val = REC_MASK; + break; + case SOUND_MIXER_DEVMASK: + val = DEV_MASK; + break; + case SOUND_MIXER_CAPS: + val = 0; + break; + case SOUND_MIXER_STEREODEVS: + val = 0; + break; + default: + val = 0; + ret = -EINVAL; + break; + } + + if (ret == 0) + ret = put_user(val, (int *)arg); + } +out: + return ret; + +} + +int omap_set_samplerate(long sample_rate) +{ + u8 count = 0; + u16 data = 0; + /* wait for any frame to complete */ + udelay(125); + + /* Search for the right sample rate */ + while ((reg_info[count].sample_rate != sample_rate) && + (count < NUMBER_SAMPLE_RATES_SUPPORTED)) { + count++; + } + if (count == NUMBER_SAMPLE_RATES_SUPPORTED) { + printk(KERN_ERR "Invalid Sample Rate %d requested\n", + (int)sample_rate); + return -EPERM; + } + + if (machine_is_omap_innovator()) { + /* set the CODEC clock input source to 12.000MHz */ + fpga_write(fpga_read(OMAP1510_FPGA_POWER) & ~0x01, + OMAP1510_FPGA_POWER); + } + + data = (reg_info[count].divider << CLKIN_SHIFT) | + (reg_info[count].control << BOSR_SHIFT) | USB_CLK_ON; + + audio_aic23_write(SAMPLE_RATE_CONTROL_ADDR, data); + + audio_samplerate = sample_rate; + +#ifndef AIC23_MASTER + { + int clkgdv = 0; + /* + Set Sample Rate at McBSP + + Formula : + Codec System Clock = CODEC_CLOCK, or half if clock_divider = 1; + clkgdv = ((Codec System Clock / (SampleRate * BitsPerSample * 2)) - 1); + + FWID = BitsPerSample - 1; + FPER = (BitsPerSample * 2) - 1; + */ + if (reg_info[count].divider) + clkgdv = CODEC_CLOCK / 2; + else + clkgdv = CODEC_CLOCK; + + clkgdv = (clkgdv / (sample_rate * DEFAULT_BITPERSAMPLE * 2)) - 1; + + initial_config.srgr1 = (FWID(DEFAULT_BITPERSAMPLE - 1) | CLKGDV(clkgdv)); + + initial_config.srgr2 = + (CLKSM | FSGM | FPER(DEFAULT_BITPERSAMPLE * 2 - 1)); + + omap_mcbsp_config(AUDIO_MCBSP, &initial_config); + } +#endif /* AIC23_MASTER */ + + return 0; +} + +static void omap_aic23_initialize(void *dummy) +{ + DPRINTK("entry\n"); + + /* initialize with default sample rate */ + audio_samplerate = AUDIO_RATE_DEFAULT; + + omap_mcbsp_request(AUDIO_MCBSP); + + /* if configured, then stop mcbsp */ + omap_mcbsp_stop(AUDIO_MCBSP); + + omap_mcbsp_config(AUDIO_MCBSP, &initial_config); + omap_mcbsp_start(AUDIO_MCBSP); + aic23_configure(); + + DPRINTK("exit\n"); +} + +static void omap_aic23_shutdown(void *dummy) +{ + /* + Turn off codec after it is done. + Can't do it immediately, since it may still have + buffered data. + + Wait 20ms (arbitrary value) and then turn it off. + */ + + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(2); + + omap_mcbsp_stop(AUDIO_MCBSP); + omap_mcbsp_free(AUDIO_MCBSP); + + audio_aic23_write(RESET_CONTROL_ADDR, 0); + audio_aic23_write(POWER_DOWN_CONTROL_ADDR, 0xff); +} + +static inline void aic23_configure() +{ + /* Reset codec */ + audio_aic23_write(RESET_CONTROL_ADDR, 0); + + /* Initialize the AIC23 internal state */ + + /* Left/Right line input volume control */ + aic23_local.line = DEFAULT_INPUT_VOLUME; + aic23_local.mic = DEFAULT_INPUT_VOLUME; + aic23_update(SET_LINE, DEFAULT_INPUT_VOLUME); + + /* Left/Right headphone channel volume control */ + /* Zero-cross detect on */ + aic23_local.volume_reg = LZC_ON; + aic23_update(SET_VOLUME, aic23_local.volume); + + /* Analog audio path control, DAC selected, delete INSEL_MIC for line in */ + audio_aic23_write(ANALOG_AUDIO_CONTROL_ADDR, DAC_SELECTED | INSEL_MIC); + + /* Digital audio path control, de-emphasis control 44.1kHz */ + audio_aic23_write(DIGITAL_AUDIO_CONTROL_ADDR, DEEMP_44K); + + /* Power control, everything is on */ + audio_aic23_write(POWER_DOWN_CONTROL_ADDR, 0); + + /* Digital audio interface, master/slave mode, I2S, 16 bit */ +#ifdef AIC23_MASTER + audio_aic23_write(DIGITAL_AUDIO_FORMAT_ADDR, MS_MASTER | IWL_16 | FOR_DSP); +#else + audio_aic23_write(DIGITAL_AUDIO_FORMAT_ADDR, IWL_16 | FOR_DSP); +#endif /* AIC23_MASTER */ + + /* Enable digital interface */ + audio_aic23_write(DIGITAL_INTERFACE_ACT_ADDR, ACT_ON); + + /* clock configuration */ + omap_set_samplerate(audio_samplerate); +} + +static int +omap_aic23_ioctl(struct inode *inode, struct file *file, uint cmd, ulong arg) +{ + long val; + int ret = 0; + + DPRINTK(" 0x%08x\n", cmd); + + /* + * These are platform dependent ioctls which are not handled by the + * generic omap-audio module. + */ + switch (cmd) { + case SNDCTL_DSP_STEREO: + ret = get_user(val, (int *)arg); + if (ret) + return ret; + /* the AIC23 is stereo only */ + ret = (val == 0) ? -EINVAL : 1; + return put_user(ret, (int *)arg); + + case SNDCTL_DSP_CHANNELS: + case SOUND_PCM_READ_CHANNELS: + /* the AIC23 is stereo only */ + return put_user(2, (long *)arg); + + case SNDCTL_DSP_SPEED: + ret = get_user(val, (long *)arg); + if (ret) + break; + ret = omap_set_samplerate(val); + if (ret) + break; + /* fall through */ + + case SOUND_PCM_READ_RATE: + return put_user(audio_samplerate, (long *)arg); + + case SOUND_PCM_READ_BITS: + case SNDCTL_DSP_SETFMT: + case SNDCTL_DSP_GETFMTS: + /* we can do 16-bit only */ + return put_user(AFMT_S16_LE, (long *)arg); + + default: + /* Maybe this is meant for the mixer (As per OSS Docs) */ + return mixer_ioctl(inode, file, cmd, arg); + } + + return ret; +} + +static int omap_aic23_probe(void) +{ + /* Get the fops from audio oss driver */ + if (!(omap_audio_fops = audio_get_fops())) { + printk(KERN_ERR "Unable to get the file operations for AIC23 OSS driver\n"); + audio_unregister_codec(&aic23_state); + return -EPERM; + } + + aic23_local.volume = DEFAULT_OUTPUT_VOLUME; + + /* register devices */ + audio_dev_id = register_sound_dsp(omap_audio_fops, -1); + mixer_dev_id = register_sound_mixer(&omap_mixer_fops, -1); + +#ifdef CONFIG_PROC_FS + create_proc_read_entry(PROC_START_FILE, 0 /* default mode */ , + NULL /* parent dir */ , + codec_start, NULL /* client data */ ); + + create_proc_read_entry(PROC_STOP_FILE, 0 /* default mode */ , + NULL /* parent dir */ , + codec_stop, NULL /* client data */ ); +#endif + + /* Announcement Time */ + printk(KERN_INFO PLATFORM_NAME " " CODEC_NAME + " audio support initialized\n"); + return 0; +} + +#ifdef MODULE +static void __exit omap_aic23_remove(void) +{ + /* Un-Register the codec with the audio driver */ + unregister_sound_dsp(audio_dev_id); + unregister_sound_mixer(mixer_dev_id); + +#ifdef CONFIG_PROC_FS + remove_proc_entry(PROC_START_FILE, NULL); + remove_proc_entry(PROC_STOP_FILE, NULL); +#endif +} +#endif /* MODULE */ + +static int omap_aic23_suspend(void) +{ + /* Empty for the moment */ + return 0; +} + +static int omap_aic23_resume(void) +{ + /* Empty for the moment */ + return 0; +} + +static int __init audio_aic23_init(void) +{ + + int err = 0; + + if (machine_is_omap_h2() || machine_is_omap_h3()) + return -ENODEV; + + mutex_init(&aic23_state.mutex); + + if (machine_is_omap_osk()) { + /* Set MCLK to be clock input for AIC23 */ + aic23_mclk = clk_get(0, "mclk"); + + if(clk_get_rate( aic23_mclk) != CODEC_CLOCK){ + /* MCLK ist not at CODEC_CLOCK */ + if( clk_get_usecount(aic23_mclk) > 0 ){ + /* MCLK is already in use */ + printk(KERN_WARNING "MCLK in use at %d Hz. We change it to %d Hz\n", + (uint)clk_get_rate( aic23_mclk), CODEC_CLOCK); + } + if( clk_set_rate( aic23_mclk, CODEC_CLOCK ) ){ + printk(KERN_ERR "Cannot set MCLK for AIC23 CODEC\n"); + return -ECANCELED; + } + } + + clk_enable( aic23_mclk ); + + DPRINTK("MCLK = %d [%d], usecount = %d\n",(uint)clk_get_rate( aic23_mclk ), + CODEC_CLOCK, clk_get_usecount( aic23_mclk)); + } + + if (machine_is_omap_innovator()) { + u8 fpga; + /* + Turn on chip select for CODEC (shared with touchscreen). + Don't turn it back off, in case touch screen needs it. + */ + fpga = fpga_read(OMAP1510_FPGA_TOUCHSCREEN); + fpga |= 0x4; + fpga_write(fpga, OMAP1510_FPGA_TOUCHSCREEN); + } + + /* register the codec with the audio driver */ + if ((err = audio_register_codec(&aic23_state))) { + printk(KERN_ERR + "Failed to register AIC23 driver with Audio OSS Driver\n"); + } + + return err; +} + +static void __exit audio_aic23_exit(void) +{ + (void)audio_unregister_codec(&aic23_state); + return; +} + +#ifdef CONFIG_PROC_FS +static int codec_start(char *buf, char **start, off_t offset, int count, + int *eof, void *data) +{ + void *foo = NULL; + + omap_aic23_initialize(foo); + + printk("AIC23 codec initialization done.\n"); + return 0; +} +static int codec_stop(char *buf, char **start, off_t offset, int count, + int *eof, void *data) +{ + void *foo = NULL; + + omap_aic23_shutdown(foo); + + printk("AIC23 codec shutdown.\n"); + return 0; +} +#endif /* CONFIG_PROC_FS */ + +module_init(audio_aic23_init); +module_exit(audio_aic23_exit); + +MODULE_AUTHOR("Dirk Behme "); +MODULE_DESCRIPTION("Glue audio driver for the TI AIC23 codec."); +MODULE_LICENSE("GPL"); diff --cc sound/oss/omap-audio-dma-intfc.c index d568582604b,00000000000..c6a61369eab mode 100644,000000..100644 --- a/sound/oss/omap-audio-dma-intfc.c +++ b/sound/oss/omap-audio-dma-intfc.c @@@ -1,985 -1,0 +1,985 @@@ +/* + * linux/sound/oss/omap-audio-dma-intfc.c + * + * Common audio DMA handling for the OMAP processors + * + * Copyright (C) 2004 Texas Instruments, Inc. + * + * Copyright (C) 2000, 2001 Nicolas Pitre + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * History: + * + * 2004-06-07 Sriram Kannan - Created new file from omap_audio_dma_intfc.c. This file + * will contain only the DMA interface and buffer handling of OMAP + * audio driver. + * + * 2004-06-22 Sriram Kannan - removed legacy code (auto-init). Self-linking of DMA logical channel. + * + * 2004-08-12 Nishanth Menon - Modified to integrate Audio requirements on 1610,1710 platforms + * + * 2004-11-01 Nishanth Menon - 16xx platform code base modified to support multi channel chaining. + * + * 2004-12-15 Nishanth Menon - Improved 16xx platform channel logic introduced - tasklets, queue handling updated + * + * 2005-12-10 Dirk Behme - Added L/R Channel Interchange fix as proposed by Ajaya Babu + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include - #include ++#include +#include + - #include ++#include +#include "omap-audio-dma-intfc.h" + - #include ++#include + +#include "omap-audio.h" + +#undef DEBUG +//#define DEBUG +#ifdef DEBUG +#define DPRINTK(ARGS...) printk(KERN_INFO "<%s>: ",__FUNCTION__);printk(ARGS) +#define FN_IN printk(KERN_INFO "[%s]: start\n", __FUNCTION__) +#define FN_OUT(n) printk(KERN_INFO "[%s]: end(%u)\n",__FUNCTION__, n) +#else + +#define DPRINTK( x... ) +#define FN_IN +#define FN_OUT(x) +#endif + +#define ERR(ARGS...) printk(KERN_ERR "{%s}-ERROR: ", __FUNCTION__);printk(ARGS); + +#define AUDIO_NAME "omap-audio" +#define AUDIO_NBFRAGS_DEFAULT 8 +#define AUDIO_FRAGSIZE_DEFAULT 8192 + +#define AUDIO_ACTIVE(state) ((state)->rd_ref || (state)->wr_ref) + +#define SPIN_ADDR (dma_addr_t)0 +#define SPIN_SIZE 2048 + +/* Channel Queue Handling macros + * tail always points to the current free entry + * Head always points to the current entry being used + * end is either head or tail + */ + +#define AUDIO_QUEUE_INIT(s) s->dma_q_head = s->dma_q_tail = s->dma_q_count = 0; +#define AUDIO_QUEUE_FULL(s) (nr_linked_channels == s->dma_q_count) +#define AUDIO_QUEUE_LAST(s) (1 == s->dma_q_count) +#define AUDIO_QUEUE_EMPTY(s) (0 == s->dma_q_count) +#define __AUDIO_INCREMENT_QUEUE(end) ((end)=((end)+1) % nr_linked_channels) +#define AUDIO_INCREMENT_HEAD(s) __AUDIO_INCREMENT_QUEUE(s->dma_q_head); s->dma_q_count--; +#define AUDIO_INCREMENT_TAIL(s) __AUDIO_INCREMENT_QUEUE(s->dma_q_tail); s->dma_q_count++; + +/* DMA buffer fragmentation sizes */ +#define MAX_DMA_SIZE 0x1000000 +#define CUT_DMA_SIZE 0x1000 +/* TODO: To be moved to more appropriate location */ +#define DCSR_ERROR 0x3 +#define DCSR_SYNC_SET (1 << 6) + +#define DCCR_FS (1 << 5) +#define DCCR_PRIO (1 << 6) +#define DCCR_AI (1 << 8) +#define DCCR_REPEAT (1 << 9) +/* if 0 the channel works in 3.1 compatible mode*/ +#define DCCR_N31COMP (1 << 10) +#define DCCR_EP (1 << 11) +#define DCCR_SRC_AMODE_BIT 12 +#define DCCR_SRC_AMODE_MASK (0x3<<12) +#define DCCR_DST_AMODE_BIT 14 +#define DCCR_DST_AMODE_MASK (0x3<<14) +#define AMODE_CONST 0x0 +#define AMODE_POST_INC 0x1 +#define AMODE_SINGLE_INDEX 0x2 +#define AMODE_DOUBLE_INDEX 0x3 + +/**************************** DATA STRUCTURES *****************************************/ + +static spinlock_t dma_list_lock = SPIN_LOCK_UNLOCKED; + +struct audio_isr_work_item { + int current_lch; + u16 ch_status; + audio_stream_t *s; +}; + +static char work_item_running = 0; +static char nr_linked_channels = 1; +static struct audio_isr_work_item work1, work2; + + +/*********************************** MODULE SPECIFIC FUNCTIONS PROTOTYPES *************/ + +static void audio_dsr_handler(unsigned long); +static DECLARE_TASKLET(audio_isr_work1, audio_dsr_handler, + (unsigned long)&work1); +static DECLARE_TASKLET(audio_isr_work2, audio_dsr_handler, + (unsigned long)&work2); + +static void sound_dma_irq_handler(int lch, u16 ch_status, void *data); +static void audio_dma_callback(int lch, u16 ch_status, void *data); +static int omap_start_sound_dma(audio_stream_t * s, dma_addr_t dma_ptr, + u_int size); +static int audio_set_dma_params_play(int channel, dma_addr_t dma_ptr, + u_int dma_size); +static int audio_set_dma_params_capture(int channel, dma_addr_t dma_ptr, + u_int dma_size); +static int audio_start_dma_chain(audio_stream_t * s); + +/*********************************** GLOBAL FUNCTIONS DEFINTIONS ***********************/ + +/*************************************************************************************** + * + * Buffer creation/destruction + * + **************************************************************************************/ +int audio_setup_buf(audio_stream_t * s) +{ + int frag; + int dmasize = 0; + char *dmabuf = NULL; + dma_addr_t dmaphys = 0; + FN_IN; + if (s->buffers) { + FN_OUT(1); + return -EBUSY; + } + s->buffers = kmalloc(sizeof(audio_buf_t) * s->nbfrags, GFP_KERNEL); + if (!s->buffers) + goto err; + memset(s->buffers, 0, sizeof(audio_buf_t) * s->nbfrags); + for (frag = 0; frag < s->nbfrags; frag++) { + audio_buf_t *b = &s->buffers[frag]; + /* + * Let's allocate non-cached memory for DMA buffers. + * We try to allocate all memory at once. + * If this fails (a common reason is memory fragmentation), + * then we allocate more smaller buffers. + */ + if (!dmasize) { + dmasize = (s->nbfrags - frag) * s->fragsize; + do { + dmabuf = + dma_alloc_coherent(NULL, dmasize, &dmaphys, + 0); + if (!dmabuf) + dmasize -= s->fragsize; + } + while (!dmabuf && dmasize); + if (!dmabuf) + goto err; + b->master = dmasize; + memzero(dmabuf, dmasize); + } + b->data = dmabuf; + b->dma_addr = dmaphys; + dmabuf += s->fragsize; + dmaphys += s->fragsize; + dmasize -= s->fragsize; + } + s->usr_head = s->dma_head = s->dma_tail = 0; + AUDIO_QUEUE_INIT(s); + s->started = 0; + s->bytecount = 0; + s->fragcount = 0; + init_completion(&s->wfc); + s->wfc.done = s->nbfrags; + FN_OUT(0); + return 0; + err: + audio_discard_buf(s); + FN_OUT(1); + return -ENOMEM; +} + +void audio_discard_buf(audio_stream_t * s) +{ + FN_IN; + /* ensure DMA isn't using those buffers */ + audio_reset(s); + if (s->buffers) { + int frag; + for (frag = 0; frag < s->nbfrags; frag++) { + if (!s->buffers[frag].master) + continue; + dma_free_coherent(NULL, + s->buffers[frag].master, + s->buffers[frag].data, + s->buffers[frag].dma_addr); + } + kfree(s->buffers); + s->buffers = NULL; + } + FN_OUT(0); +} + +/*************************************************************************************** + * + * DMA channel requests + * + **************************************************************************************/ +static void omap_sound_dma_link_lch(void *data) +{ + audio_stream_t *s = (audio_stream_t *) data; + int *chan = s->lch; + int i; + + FN_IN; + if (s->linked) { + FN_OUT(1); + return; + } + for (i = 0; i < nr_linked_channels; i++) { + int cur_chan = chan[i]; + int nex_chan = + ((nr_linked_channels - 1 == + i) ? chan[0] : chan[i + 1]); + omap_dma_link_lch(cur_chan, nex_chan); + } + s->linked = 1; + FN_OUT(0); +} + +int +omap_request_sound_dma(int device_id, const char *device_name, void *data, + int **channels) +{ + int i, err = 0; + int *chan = NULL; + FN_IN; + if (unlikely((NULL == channels) || (NULL == device_name))) { + BUG(); + return -EPERM; + } + /* Try allocate memory for the num channels */ + *channels = + (int *)kmalloc(sizeof(int) * nr_linked_channels, + GFP_KERNEL); + chan = *channels; + if (NULL == chan) { + ERR("No Memory for channel allocs!\n"); + FN_OUT(-ENOMEM); + return -ENOMEM; + } + spin_lock(&dma_list_lock); + for (i = 0; i < nr_linked_channels; i++) { + err = + omap_request_dma(device_id, device_name, + sound_dma_irq_handler, data, &chan[i]); + /* Handle Failure condition here */ + if (err < 0) { + int j; + for (j = 0; j < i; j++) { + omap_free_dma(chan[j]); + } + spin_unlock(&dma_list_lock); + kfree(chan); + *channels = NULL; + ERR("Error in requesting channel %d=0x%x\n", i, err); + FN_OUT(err); + return err; + } + } + + /* Chain the channels together */ + if (!cpu_is_omap15xx()) + omap_sound_dma_link_lch(data); + + spin_unlock(&dma_list_lock); + FN_OUT(0); + return 0; +} + +/*************************************************************************************** + * + * DMA channel requests Freeing + * + **************************************************************************************/ +static void omap_sound_dma_unlink_lch(void *data) +{ + audio_stream_t *s = (audio_stream_t *) data; + int *chan = s->lch; + int i; + + FN_IN; + if (!s->linked) { + FN_OUT(1); + return; + } + for (i = 0; i < nr_linked_channels; i++) { + int cur_chan = chan[i]; + int nex_chan = + ((nr_linked_channels - 1 == + i) ? chan[0] : chan[i + 1]); + omap_dma_unlink_lch(cur_chan, nex_chan); + } + s->linked = 0; + FN_OUT(0); +} + +int omap_free_sound_dma(void *data, int **channels) +{ + int i; + int *chan = NULL; + FN_IN; + if (unlikely(NULL == channels)) { + BUG(); + return -EPERM; + } + if (unlikely(NULL == *channels)) { + BUG(); + return -EPERM; + } + chan = (*channels); + + if (!cpu_is_omap15xx()) + omap_sound_dma_unlink_lch(data); + for (i = 0; i < nr_linked_channels; i++) { + int cur_chan = chan[i]; + omap_stop_dma(cur_chan); + omap_free_dma(cur_chan); + } + kfree(*channels); + *channels = NULL; + FN_OUT(0); + return 0; +} + +/*************************************************************************************** + * + * Process DMA requests - This will end up starting the transfer. Proper fragments of + * Transfers will be initiated. + * + **************************************************************************************/ +int audio_process_dma(audio_stream_t * s) +{ + int ret = 0; + unsigned long flags; + FN_IN; + + /* Dont let the ISR over ride touching the in_use flag */ + local_irq_save(flags); + if (1 == s->in_use) { + local_irq_restore(flags); + ERR("Called again while In Use\n"); + return 0; + } + s->in_use = 1; + local_irq_restore(flags); + + if (s->stopped) + goto spin; + + if (s->dma_spinref > 0 && s->pending_frags) { + s->dma_spinref = 0; + DMA_CLEAR(s); + } + while (s->pending_frags) { + audio_buf_t *b = &s->buffers[s->dma_head]; + u_int dma_size = s->fragsize - b->offset; + if (dma_size > MAX_DMA_SIZE) + dma_size = CUT_DMA_SIZE; + ret = + omap_start_sound_dma(s, b->dma_addr + b->offset, dma_size); + if (ret) { + goto process_out; + } + b->dma_ref++; + b->offset += dma_size; + if (b->offset >= s->fragsize) { + s->pending_frags--; + if (++s->dma_head >= s->nbfrags) + s->dma_head = 0; + } + } + spin: + if (s->spin_idle) { + int spincnt = 0; + ERR("we are spinning\n"); + while (omap_start_sound_dma(s, SPIN_ADDR, SPIN_SIZE) == 0) + spincnt++; + /* + * Note: if there is still a data buffer being + * processed then the ref count is negative. This + * allows for the DMA termination to be accounted in + * the proper order. Of course dma_spinref can't be + * greater than 0 if dma_ref is not 0 since we kill + * the spinning above as soon as there is real data to process. + */ + if (s->buffers && s->buffers[s->dma_tail].dma_ref) + spincnt = -spincnt; + s->dma_spinref += spincnt; + } + + process_out: + s->in_use = 0; + + FN_OUT(ret); + return ret; +} + +/*************************************************************************************** + * + * Prime Rx - Since the recieve buffer has no time limit as to when it would arrive, + * we need to prime it + * + **************************************************************************************/ +void audio_prime_rx(audio_state_t * state) +{ + audio_stream_t *is = state->input_stream; + + FN_IN; + if (state->need_tx_for_rx) { + /* + * With some codecs like the Philips UDA1341 we must ensure + * there is an output stream at any time while recording since + * this is how the UDA1341 gets its clock from the SA1100. + * So while there is no playback data to send, the output DMA + * will spin with all zeroes. We use the cache flush special + * area for that. + */ + state->output_stream->spin_idle = 1; + audio_process_dma(state->output_stream); + } + is->pending_frags = is->nbfrags; + init_completion(&is->wfc); + is->wfc.done = 0; + + is->active = 1; + audio_process_dma(is); + + FN_OUT(0); + return; +} + +/*************************************************************************************** + * + * set the fragment size + * + **************************************************************************************/ +int audio_set_fragments(audio_stream_t * s, int val) +{ + FN_IN; + if (s->active) + return -EBUSY; + if (s->buffers) + audio_discard_buf(s); + s->nbfrags = (val >> 16) & 0x7FFF; + val &= 0xFFFF; + if (val < 4) + val = 4; + if (val > 15) + val = 15; + s->fragsize = 1 << val; + if (s->nbfrags < 2) + s->nbfrags = 2; + if (s->nbfrags * s->fragsize > 128 * 1024) + s->nbfrags = 128 * 1024 / s->fragsize; + FN_OUT(0); + if (audio_setup_buf(s)) + return -ENOMEM; + return val | (s->nbfrags << 16); + +} + +/*************************************************************************************** + * + * Sync up the buffers before we shutdown, else under-run errors will happen + * + **************************************************************************************/ +int audio_sync(struct file *file) +{ + audio_state_t *state = file->private_data; + audio_stream_t *s = state->output_stream; + audio_buf_t *b; + u_int shiftval = 0; + unsigned long flags; + + DECLARE_WAITQUEUE(wait, current); + + FN_IN; + + if (!(file->f_mode & FMODE_WRITE) || !s->buffers || s->mapped) { + FN_OUT(1); + return 0; + } + + /* + * Send current buffer if it contains data. Be sure to send + * a full sample count. + */ + b = &s->buffers[s->usr_head]; + if (b->offset &= ~3) { + /* Wait for a buffer to become free */ + if (wait_for_completion_interruptible(&s->wfc)) + return 0; + /* + * HACK ALERT ! + * To avoid increased complexity in the rest of the code + * where full fragment sizes are assumed, we cheat a little + * with the start pointer here and don't forget to restore + * it later. + */ + + /* As this is a last frag we need only one dma channel + * to complete. So it's need to unlink dma channels + * to avoid empty dma work. + */ + if (!cpu_is_omap15xx() && AUDIO_QUEUE_EMPTY(s)) + omap_sound_dma_unlink_lch(s); + + shiftval = s->fragsize - b->offset; + b->offset = shiftval; + b->dma_addr -= shiftval; + b->data -= shiftval; + local_irq_save(flags); + s->bytecount -= shiftval; + if (++s->usr_head >= s->nbfrags) + s->usr_head = 0; + + s->pending_frags++; + audio_process_dma(s); + local_irq_restore(flags); + } + + /* Let's wait for all buffers to complete */ + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&s->wq, &wait); + while ((s->pending_frags || (s->wfc.done < s->nbfrags)) + && !signal_pending(current)) { + schedule(); + set_current_state(TASK_INTERRUPTIBLE); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&s->wq, &wait); + + /* undo the pointer hack above */ + if (shiftval) { + local_irq_save(flags); + b->dma_addr += shiftval; + b->data += shiftval; + /* ensure sane DMA code behavior if not yet processed */ + if (b->offset != 0) + b->offset = s->fragsize; + local_irq_restore(flags); + } + + FN_OUT(0); + return 0; +} + +/*************************************************************************************** + * + * Stop all the DMA channels of the stream + * + **************************************************************************************/ +void audio_stop_dma(audio_stream_t * s) +{ + int *chan = s->lch; + int i; + FN_IN; + if (unlikely(NULL == chan)) { + BUG(); + return; + } + for (i = 0; i < nr_linked_channels; i++) { + int cur_chan = chan[i]; + omap_stop_dma(cur_chan); + } + s->started = 0; + FN_OUT(0); + return; +} + +/*************************************************************************************** + * + * Get the dma posn + * + **************************************************************************************/ +u_int audio_get_dma_pos(audio_stream_t * s) +{ + audio_buf_t *b = &s->buffers[s->dma_tail]; + u_int offset; + + FN_IN; + if (b->dma_ref) { + offset = omap_get_dma_src_pos(s->lch[s->dma_q_head]) - b->dma_addr; + if (offset >= s->fragsize) + offset = s->fragsize - 4; + } else if (s->pending_frags) { + offset = b->offset; + } else { + offset = 0; + } + FN_OUT(offset); + return offset; +} + +/*************************************************************************************** + * + * Reset the audio buffers + * + **************************************************************************************/ +void audio_reset(audio_stream_t * s) +{ + FN_IN; + if (s->buffers) { + audio_stop_dma(s); + s->buffers[s->dma_head].offset = 0; + s->buffers[s->usr_head].offset = 0; + s->usr_head = s->dma_head; + s->pending_frags = 0; + init_completion(&s->wfc); + s->wfc.done = s->nbfrags; + } + s->active = 0; + s->stopped = 0; + s->started = 0; + FN_OUT(0); + return; +} + +/*************************************************************************************** + * + * Clear any pending transfers + * + **************************************************************************************/ +void omap_clear_sound_dma(audio_stream_t * s) +{ + FN_IN; + omap_clear_dma(s->lch[s->dma_q_head]); + FN_OUT(0); + return; +} + +/*************************************************************************************** + * + * DMA related functions + * + **************************************************************************************/ +static int audio_set_dma_params_play(int channel, dma_addr_t dma_ptr, + u_int dma_size) +{ + int dt = 0x1; /* data type 16 */ + int cen = 32; /* Stereo */ + int cfn = dma_size / (2 * cen); + unsigned long dest_start; + int dest_port = 0; + int sync_dev = 0; + + FN_IN; + + if (cpu_is_omap15xx() || cpu_is_omap16xx()) { + dest_start = AUDIO_MCBSP_DATAWRITE; + dest_port = OMAP_DMA_PORT_MPUI; + } + if (cpu_is_omap24xx()) { + dest_start = AUDIO_MCBSP_DATAWRITE; + sync_dev = AUDIO_DMA_TX; + } + + omap_set_dma_dest_params(channel, dest_port, OMAP_DMA_AMODE_CONSTANT, dest_start, 0, 0); + omap_set_dma_src_params(channel, 0, OMAP_DMA_AMODE_POST_INC, dma_ptr, 0, 0); + omap_set_dma_transfer_params(channel, dt, cen, cfn, OMAP_DMA_SYNC_ELEMENT, sync_dev, 0); + + FN_OUT(0); + return 0; +} + +static int audio_set_dma_params_capture(int channel, dma_addr_t dma_ptr, + u_int dma_size) +{ + int dt = 0x1; /* data type 16 */ + int cen = 16; /* mono */ + int cfn = dma_size / (2 * cen); + unsigned long src_start; + int src_port = 0; + int sync_dev = 0; + int src_sync = 0; + + FN_IN; + + if (cpu_is_omap15xx() || cpu_is_omap16xx()) { + src_start = AUDIO_MCBSP_DATAREAD; + src_port = OMAP_DMA_PORT_MPUI; + } + if (cpu_is_omap24xx()) { + src_start = AUDIO_MCBSP_DATAREAD; + sync_dev = AUDIO_DMA_RX; + src_sync = 1; + } + + omap_set_dma_src_params(channel, src_port, OMAP_DMA_AMODE_CONSTANT, src_start, 0, 0); + omap_set_dma_dest_params(channel, 0, OMAP_DMA_AMODE_POST_INC, dma_ptr, 0, 0); + omap_set_dma_transfer_params(channel, dt, cen, cfn, OMAP_DMA_SYNC_ELEMENT, sync_dev, src_sync); + + FN_OUT(0); + return 0; +} + +static int audio_start_dma_chain(audio_stream_t * s) +{ + int channel = s->lch[s->dma_q_head]; + FN_IN; + if (!s->started) { + s->hw_stop(); /* stops McBSP Interface */ + omap_start_dma(channel); + s->started = 1; + s->hw_start(); /* start McBSP interface */ + } + /* else the dma itself will progress forward with out our help */ + FN_OUT(0); + return 0; +} + +/* Start DMA - + * Do the initial set of work to initialize all the channels as required. + * We shall then initate a transfer + */ +static int omap_start_sound_dma(audio_stream_t * s, dma_addr_t dma_ptr, + u_int dma_size) +{ + int ret = -EPERM; + + FN_IN; + if (unlikely(dma_size > MAX_DMA_SIZE)) { + ERR("DmaSoundDma: Start: overflowed %d-%d\n", dma_size, + MAX_DMA_SIZE); + return -EOVERFLOW; + } + + if (AUDIO_QUEUE_FULL(s)) { + ret = -2; + goto sound_out; + } + + if (s->input_or_output == FMODE_WRITE) + /*playback */ + { + ret = + audio_set_dma_params_play(s->lch[s->dma_q_tail], dma_ptr, + dma_size); + } else { + ret = + audio_set_dma_params_capture(s->lch[s->dma_q_tail], dma_ptr, + dma_size); + } + if (ret != 0) { + ret = -2; /* indicate queue full */ + goto sound_out; + } + AUDIO_INCREMENT_TAIL(s); + ret = audio_start_dma_chain(s); + if (ret) { + ERR("dma start failed"); + } + sound_out: + FN_OUT(ret); + return ret; + +} + +/*************************************************************************************** + * + * ISR related functions + * + **************************************************************************************/ +/* The work item handler */ +static void audio_dsr_handler(unsigned long inData) +{ + void *data = (void *)inData; + struct audio_isr_work_item *work = data; + audio_stream_t *s = (work->s); + int sound_curr_lch = work->current_lch; + u16 ch_status = work->ch_status; + + FN_IN; + DPRINTK("lch=%d,status=0x%x, data=%p as=%p\n", sound_curr_lch, + ch_status, data, s); + if (AUDIO_QUEUE_EMPTY(s)) { + ERR("Interrupt(%d) for empty queue(h=%d, T=%d)???\n", + sound_curr_lch, s->dma_q_head, s->dma_q_tail); + ERR("nbfrag=%d,pendfrags=%d,USR-H=%d, QH-%d QT-%d\n", + s->nbfrags, s->pending_frags, s->usr_head, s->dma_head, + s->dma_tail); + FN_OUT(-1); + return; + } + + AUDIO_INCREMENT_HEAD(s); /* Empty the queue */ + + /* Try to fill again */ + audio_dma_callback(sound_curr_lch, ch_status, s); + FN_OUT(0); + +} + +/* Macro to trace the IRQ calls - checks for multi-channel irqs */ +//#define IRQ_TRACE +#ifdef IRQ_TRACE +#define MAX_UP 10 +static char xyz[MAX_UP] = { 0 }; +static int h = 0; +#endif + +/* ISRs have to be short and smart.. So we transfer every heavy duty stuff to the + * work item + */ +static void sound_dma_irq_handler(int sound_curr_lch, u16 ch_status, void *data) +{ + int dma_status = ch_status; + audio_stream_t *s = (audio_stream_t *) data; + FN_IN; +#ifdef IRQ_TRACE + xyz[h++] = '0' + sound_curr_lch; + if (h == MAX_UP - 1) { + printk("%s-", xyz); + h = 0; + } +#endif + DPRINTK("lch=%d,status=0x%x, dma_status=%d, data=%p\n", sound_curr_lch, + ch_status, dma_status, data); + + if (dma_status & (DCSR_ERROR)) { + if (cpu_is_omap15xx() || cpu_is_omap16xx()) + omap_stop_dma(sound_curr_lch); + ERR("DCSR_ERROR!\n"); + FN_OUT(-1); + return; + } + + if (AUDIO_QUEUE_LAST(s)) + audio_stop_dma(s); + + /* Start the work item - we ping pong the work items */ + if (!work_item_running) { + work1.current_lch = sound_curr_lch; + work1.ch_status = ch_status; + work1.s = s; + /* schedule tasklet 1 */ + tasklet_schedule(&audio_isr_work1); + work_item_running = 1; + } else { + work2.current_lch = sound_curr_lch; + work2.ch_status = ch_status; + work2.s = s; + /* schedule tasklet 2 */ + tasklet_schedule(&audio_isr_work2); + work_item_running = 0; + } + FN_OUT(0); + return; +} + +/* The call back that handles buffer stuff */ +static void audio_dma_callback(int lch, u16 ch_status, void *data) +{ + audio_stream_t *s = data; + audio_buf_t *b = &s->buffers[s->dma_tail]; + FN_IN; + + if (s->dma_spinref > 0) { + s->dma_spinref--; + } else if (!s->buffers) { + printk(KERN_CRIT + "omap_audio: received DMA IRQ for non existent buffers!\n"); + return; + } else if (b->dma_ref && --b->dma_ref == 0 && b->offset >= s->fragsize) { + /* This fragment is done */ + b->offset = 0; + s->bytecount += s->fragsize; + s->fragcount++; + s->dma_spinref = -s->dma_spinref; + + if (++s->dma_tail >= s->nbfrags) + s->dma_tail = 0; + + if (!s->mapped) + complete(&s->wfc); + else + s->pending_frags++; + + wake_up(&s->wq); + } + + audio_process_dma(s); + + FN_OUT(0); + return; +} + +/********************************************************************************* + * + * audio_get_dma_callback(): return the dma interface call back function + * + *********************************************************************************/ +dma_callback_t audio_get_dma_callback(void) +{ + FN_IN; + FN_OUT(0); + return audio_dma_callback; +} + +static int __init audio_dma_init(void) +{ + if (!cpu_is_omap15xx()) + nr_linked_channels = 2; + + return 0; +} + +static void __exit audio_dma_exit(void) +{ + /* Nothing */ +} + +module_init(audio_dma_init); +module_exit(audio_dma_exit); + +MODULE_AUTHOR("Texas Instruments"); +MODULE_DESCRIPTION("Common DMA handling for Audio driver on OMAP processors"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(omap_clear_sound_dma); +EXPORT_SYMBOL(omap_request_sound_dma); +EXPORT_SYMBOL(omap_free_sound_dma); + +EXPORT_SYMBOL(audio_get_dma_callback); +EXPORT_SYMBOL(audio_setup_buf); +EXPORT_SYMBOL(audio_process_dma); +EXPORT_SYMBOL(audio_prime_rx); +EXPORT_SYMBOL(audio_set_fragments); +EXPORT_SYMBOL(audio_sync); +EXPORT_SYMBOL(audio_stop_dma); +EXPORT_SYMBOL(audio_get_dma_pos); +EXPORT_SYMBOL(audio_reset); +EXPORT_SYMBOL(audio_discard_buf); diff --cc sound/oss/omap-audio-tsc2101.c index cbabcf59ad9,00000000000..9cbb0e82139 mode 100644,000000..100644 --- a/sound/oss/omap-audio-tsc2101.c +++ b/sound/oss/omap-audio-tsc2101.c @@@ -1,1238 -1,0 +1,1238 @@@ +/* + * linux/sound/oss/omap-audio-tsc2101.c + * + * Glue driver for TSC2101 for OMAP processors + * + * Copyright (C) 2004 Texas Instruments, Inc. + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * History: + * ------- + * 2004-08-12 Nishanth Menon - Modified to integrate Audio requirements on 1610,1710 platforms. + * 2004-09-14 Sriram Kannan - Added /proc support for asynchronous starting/stopping the codec + * (without affecting the normal driver flow). + * 2004-11-04 Nishanth Menon - Support for power management + * 2004-11-07 Nishanth Menon - Support for Common TSC access b/w Touchscreen and audio drivers + */ + +/***************************** INCLUDES ************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include - #include - #include ++#include ++#include +#include - #include ++#include + - #include - #include ++#include ++#include +#include + +#include "omap-audio.h" +#include "omap-audio-dma-intfc.h" - #include ++#include +#ifdef CONFIG_ARCH_OMAP16XX +#include <../drivers/ssi/omap-uwire.h> - #include ++#include +#elif defined(CONFIG_ARCH_OMAP24XX) +#else +#error "Unsupported configuration" +#endif + +#include +#include <../drivers/ssi/omap-tsc2101.h> + +/***************************** MACROS ************************************/ + +#define PROC_SUPPORT + +#ifdef PROC_SUPPORT +#include +#define PROC_START_FILE "driver/tsc2101-audio-start" +#define PROC_STOP_FILE "driver/tsc2101-audio-stop" +#endif + +#define CODEC_NAME "TSC2101" + +#ifdef CONFIG_ARCH_OMAP16XX +#define PLATFORM_NAME "OMAP16XX" +#elif defined(CONFIG_ARCH_OMAP24XX) +#define PLATFORM_NAME "OMAP2" +#endif + +/* Define to set the tsc as the master w.r.t McBSP */ +#define TSC_MASTER + +/* + * AUDIO related MACROS + */ +#define DEFAULT_BITPERSAMPLE 16 +#define AUDIO_RATE_DEFAULT 44100 +#define PAGE2_AUDIO_CODEC_REGISTERS (2) +#define LEAVE_CS 0x80 + +/* Select the McBSP For Audio */ +/* 16XX is MCBSP1 and 24XX is MCBSP2*/ +/* see include/asm-arm/arch-omap/mcbsp.h */ +#ifndef AUDIO_MCBSP +#error "UnSupported Configuration" +#endif + +#define REC_MASK (SOUND_MASK_LINE | SOUND_MASK_MIC) +#define DEV_MASK (REC_MASK | SOUND_MASK_VOLUME) + +#define SET_VOLUME 1 +#define SET_LINE 2 +#define SET_MIC 3 +#define SET_RECSRC 4 + +#define DEFAULT_VOLUME 93 +#define DEFAULT_INPUT_VOLUME 20 /* An minimal volume */ + +/* Tsc Audio Specific */ +#define NUMBER_SAMPLE_RATES_SUPPORTED 16 +#define OUTPUT_VOLUME_MIN 0x7F +#define OUTPUT_VOLUME_MAX 0x32 +#define OUTPUT_VOLUME_RANGE (OUTPUT_VOLUME_MIN - OUTPUT_VOLUME_MAX) +#define OUTPUT_VOLUME_MASK OUTPUT_VOLUME_MIN +#define DEFAULT_VOLUME_LEVEL OUTPUT_VOLUME_MAX + +/* use input vol of 75 for 0dB gain */ +#define INPUT_VOLUME_MIN 0x0 +#define INPUT_VOLUME_MAX 0x7D +#define INPUT_VOLUME_RANGE (INPUT_VOLUME_MAX - INPUT_VOLUME_MIN) +#define INPUT_VOLUME_MASK INPUT_VOLUME_MAX + +/*********** Debug Macros ********/ +/* To Generate a rather shrill tone -test the entire path */ +//#define TONE_GEN +/* To Generate a tone for each keyclick - test the tsc,spi paths*/ +//#define TEST_KEYCLICK +/* To dump the tsc registers for debug */ +//#define TSC_DUMP_REGISTERS + +#ifdef DPRINTK +#undef DPRINTK +#endif +#undef DEBUG + +//#define DEBUG +#ifdef DEBUG +#define DPRINTK(ARGS...) printk(KERN_INFO "<%s>: ",__FUNCTION__);printk(ARGS) +#define FN_IN printk(KERN_INFO "[%s]: start\n", __FUNCTION__) +#define FN_OUT(n) printk(KERN_INFO "[%s]: end(%u)\n",__FUNCTION__, n) +#else +#define DPRINTK( x... ) +#define FN_IN +#define FN_OUT(n) +#endif + +/***************************** Data Structures **********************************/ + +static int audio_ifc_start(void) +{ + omap_mcbsp_start(AUDIO_MCBSP); + return 0; +} + +static int audio_ifc_stop(void) +{ + omap_mcbsp_stop(AUDIO_MCBSP); + return 0; +} + +static audio_stream_t output_stream = { + .id = "TSC2101 out", + .dma_dev = AUDIO_DMA_TX, + .input_or_output = FMODE_WRITE, + .hw_start = audio_ifc_start, + .hw_stop = audio_ifc_stop, +}; + +static audio_stream_t input_stream = { + .id = "TSC2101 in", + .dma_dev = AUDIO_DMA_RX, + .input_or_output = FMODE_READ, + .hw_start = audio_ifc_start, + .hw_stop = audio_ifc_stop, +}; + +static int audio_dev_id, mixer_dev_id; + +typedef struct { + u8 volume; + u8 line; + u8 mic; + int recsrc; + int mod_cnt; +} tsc2101_local_info; + +static tsc2101_local_info tsc2101_local = { + volume: DEFAULT_VOLUME, + line: DEFAULT_INPUT_VOLUME, + mic: DEFAULT_INPUT_VOLUME, + recsrc: SOUND_MASK_LINE, + mod_cnt: 0 +}; + +struct sample_rate_reg_info { + u16 sample_rate; + u8 divisor; + u8 fs_44kHz; /* if 0 48 khz, if 1 44.1 khz fsref */ +}; + +/* To Store the default sample rate */ +static long audio_samplerate = AUDIO_RATE_DEFAULT; + +static const struct sample_rate_reg_info + reg_info[NUMBER_SAMPLE_RATES_SUPPORTED] = { + /* Div 1 */ + {48000, 0, 0}, + {44100, 0, 1}, + /* Div 1.5 */ + {32000, 1, 0}, + {29400, 1, 1}, + /* Div 2 */ + {24000, 2, 0}, + {22050, 2, 1}, + /* Div 3 */ + {16000, 3, 0}, + {14700, 3, 1}, + /* Div 4 */ + {12000, 4, 0}, + {11025, 4, 1}, + /* Div 5 */ + {9600, 5, 0}, + {8820, 5, 1}, + /* Div 5.5 */ + {8727, 6, 0}, + {8018, 6, 1}, + /* Div 6 */ + {8000, 7, 0}, + {7350, 7, 1}, +}; + +static struct omap_mcbsp_reg_cfg initial_config = { + .spcr2 = FREE | FRST | GRST | XRST | XINTM(3), + .spcr1 = RINTM(3) | RRST, + .rcr2 = RPHASE | RFRLEN2(OMAP_MCBSP_WORD_8) | + RWDLEN2(OMAP_MCBSP_WORD_16) | RDATDLY(1), + .rcr1 = RFRLEN1(OMAP_MCBSP_WORD_8) | RWDLEN1(OMAP_MCBSP_WORD_16), + .xcr2 = XPHASE | XFRLEN2(OMAP_MCBSP_WORD_8) | + XWDLEN2(OMAP_MCBSP_WORD_16) | XDATDLY(1) | XFIG, + .xcr1 = XFRLEN1(OMAP_MCBSP_WORD_8) | XWDLEN1(OMAP_MCBSP_WORD_16), + .srgr1 = FWID(15), + .srgr2 = GSYNC | CLKSP | FSGM | FPER(31), + + /* platform specific initialization */ +#ifdef CONFIG_MACH_OMAP_H2 + .pcr0 = CLKXM | CLKRM | FSXP | FSRP | CLKXP | CLKRP, +#elif defined(CONFIG_MACH_OMAP_H3) || defined(CONFIG_MACH_OMAP_H4) || defined(CONFIG_MACH_OMAP_APOLLON) + +#ifndef TSC_MASTER + .pcr0 = FSXM | FSRM | CLKXM | CLKRM | CLKXP | CLKRP, +#else + .pcr0 = CLKRM | SCLKME | FSXP | FSRP | CLKXP | CLKRP, +#endif /* tsc Master defs */ + +#endif /* platform specific inits */ +}; + +/***************************** MODULES SPECIFIC FUNCTION PROTOTYPES ********************/ + +static void omap_tsc2101_initialize(void *dummy); + +static void omap_tsc2101_shutdown(void *dummy); + +static int omap_tsc2101_ioctl(struct inode *inode, struct file *file, + uint cmd, ulong arg); + +static int omap_tsc2101_probe(void); + +static void omap_tsc2101_remove(void); + +static int omap_tsc2101_suspend(void); + +static int omap_tsc2101_resume(void); + +static void tsc2101_configure(void); + +static int mixer_open(struct inode *inode, struct file *file); + +static int mixer_release(struct inode *inode, struct file *file); + +static int mixer_ioctl(struct inode *inode, struct file *file, uint cmd, + ulong arg); + +#ifdef TEST_KEYCLICK +void tsc2101_testkeyclick(void); +#endif + +#ifdef TONE_GEN +void toneGen(void); +#endif + +#ifdef TSC_DUMP_REGISTERS +static void tsc2101_dumpRegisters(void); +#endif + +#ifdef PROC_SUPPORT +static int codec_start(char *buf, char **start, off_t offset, int count, + int *eof, void *data); + +static int codec_stop(char *buf, char **start, off_t offset, int count, + int *eof, void *data); + +static void tsc2101_start(void); +#endif + +/******************** DATA STRUCTURES USING FUNCTION POINTERS **************************/ + +/* File Op structure for mixer */ +static struct file_operations omap_mixer_fops = { + .open = mixer_open, + .release = mixer_release, + .ioctl = mixer_ioctl, + .owner = THIS_MODULE +}; + +/* To store characteristic info regarding the codec for the audio driver */ +static audio_state_t tsc2101_state = { + .output_stream = &output_stream, + .input_stream = &input_stream, +/* .need_tx_for_rx = 1, //Once the Full Duplex works */ + .need_tx_for_rx = 0, + .hw_init = omap_tsc2101_initialize, + .hw_shutdown = omap_tsc2101_shutdown, + .client_ioctl = omap_tsc2101_ioctl, + .hw_probe = omap_tsc2101_probe, + .hw_remove = omap_tsc2101_remove, + .hw_suspend = omap_tsc2101_suspend, + .hw_resume = omap_tsc2101_resume, +}; + +/* This will be defined in the Audio.h */ +static struct file_operations *omap_audio_fops; + +/***************************** MODULES SPECIFIC FUNCTIONs *******************************/ + +/********************************************************************************* + * + * Simplified write for tsc Audio + * + *********************************************************************************/ +static __inline__ void audio_tsc2101_write(u8 address, u16 data) +{ + omap_tsc2101_write(PAGE2_AUDIO_CODEC_REGISTERS, address, data); +} + +/********************************************************************************* + * + * Simplified read for tsc Audio + * + *********************************************************************************/ +static __inline__ u16 audio_tsc2101_read(u8 address) +{ + return (omap_tsc2101_read(PAGE2_AUDIO_CODEC_REGISTERS, address)); +} + +/********************************************************************************* + * + * tsc2101_update() + * Volume Adj etc + * + ********************************************************************************/ +static int tsc2101_update(int flag, int val) +{ + u16 volume; + u16 data; + + FN_IN; + switch (flag) { + case SET_VOLUME: + if (val < 0 || val > 100) { + printk(KERN_ERR "Trying a bad volume value(%d)!\n", val); + return -EPERM; + } + /* Convert 0 -> 100 volume to 0x7F(min) -> y(max) volume range */ + volume = + ((val * OUTPUT_VOLUME_RANGE) / 100) + OUTPUT_VOLUME_MAX; + /* invert the value for getting the proper range 0 min and 100 max */ + volume = OUTPUT_VOLUME_MIN - volume; + data = audio_tsc2101_read(TSC2101_DAC_GAIN_CTRL); + data &= + ~(DGC_DALVL(OUTPUT_VOLUME_MIN) | + DGC_DARVL(OUTPUT_VOLUME_MIN)); + data |= DGC_DALVL(volume) | DGC_DARVL(volume); + audio_tsc2101_write(TSC2101_DAC_GAIN_CTRL, data); + data = audio_tsc2101_read(TSC2101_DAC_GAIN_CTRL); + + break; + + case SET_LINE: + if (val < 0 || val > 100) { + printk(KERN_ERR "Trying a bad volume value(%d)!\n", val); + return -EPERM; + } + /* Convert 0 -> 100 volume to 0x0(min) -> 0x7D(max) volume range */ + /* NOTE: 0 is minimum volume and not mute */ + volume = ((val * INPUT_VOLUME_RANGE) / 100) + INPUT_VOLUME_MIN; + /* Handset Input not muted, AGC for Handset In off */ + audio_tsc2101_write(TSC2101_HEADSET_GAIN_CTRL, + HGC_ADPGA_HED(volume)); + break; + + case SET_MIC: + if (val < 0 || val > 100) { + printk(KERN_ERR "Trying a bad volume value(%d)!\n", val); + return -EPERM; + } + /* Convert 0 -> 100 volume to 0x0(min) -> 0x7D(max) volume range */ + /* NOTE: 0 is minimum volume and not mute */ + volume = ((val * INPUT_VOLUME_RANGE) / 100) + INPUT_VOLUME_MIN; + /* Handset Input not muted, AGC for Handset In off */ + audio_tsc2101_write(TSC2101_HANDSET_GAIN_CTRL, + HNGC_ADPGA_HND(volume)); + break; + + case SET_RECSRC: + /* + * If more than one recording device selected, + * disable the device that is currently in use. + */ + if (hweight32(val) > 1) + val &= ~tsc2101_local.recsrc; + + data = audio_tsc2101_read(TSC2101_MIXER_PGA_CTRL); + data &= ~MPC_MICSEL(7); /* clear all MICSEL bits */ + + if (val == SOUND_MASK_MIC) { + data |= MPC_MICSEL(1); + audio_tsc2101_write(TSC2101_MIXER_PGA_CTRL, data); + } + else if (val == SOUND_MASK_LINE) { + data |= MPC_MICSEL(0); + audio_tsc2101_write(TSC2101_MIXER_PGA_CTRL, data); + } + else { + printk(KERN_WARNING "omap1610-tsc2101: Wrong RECSRC" + " value specified\n"); + return -EINVAL; + } + tsc2101_local.recsrc = val; + break; + default: + printk(KERN_WARNING "omap1610-tsc2101: Wrong tsc2101_update " + "flag specified\n"); + break; + } + + FN_OUT(0); + return 0; +} + +/********************************************************************************* + * + * mixer_open() + * + ********************************************************************************/ +static int mixer_open(struct inode *inode, struct file *file) +{ + /* Any mixer specific initialization */ + + /* Initalize the tsc2101 */ + omap_tsc2101_enable(); + + return 0; +} + +/********************************************************************************* + * + * mixer_release() + * + ********************************************************************************/ +static int mixer_release(struct inode *inode, struct file *file) +{ + /* Any mixer specific Un-initialization */ + omap_tsc2101_disable(); + + return 0; +} + +/********************************************************************************* + * + * mixer_ioctl() + * + ********************************************************************************/ +static int +mixer_ioctl(struct inode *inode, struct file *file, uint cmd, ulong arg) +{ + int val; + int gain; + int ret = 0; + int nr = _IOC_NR(cmd); + + /* + * We only accept mixer (type 'M') ioctls. + */ + FN_IN; + if (_IOC_TYPE(cmd) != 'M') + return -EINVAL; + + DPRINTK(" 0x%08x\n", cmd); + + if (cmd == SOUND_MIXER_INFO) { + struct mixer_info mi; + + strncpy(mi.id, "TSC2101", sizeof(mi.id)); + strncpy(mi.name, "TI TSC2101", sizeof(mi.name)); + mi.modify_counter = tsc2101_local.mod_cnt; + FN_OUT(1); + return copy_to_user((void __user *)arg, &mi, sizeof(mi)); + } + + if (_IOC_DIR(cmd) & _IOC_WRITE) { + ret = get_user(val, (int __user *)arg); + if (ret) + goto out; + + /* Ignore separate left/right channel for now, + * even the codec does support it. + */ + gain = val & 255; + + switch (nr) { + case SOUND_MIXER_VOLUME: + tsc2101_local.volume = val; + tsc2101_local.mod_cnt++; + ret = tsc2101_update(SET_VOLUME, gain); + break; + + case SOUND_MIXER_LINE: + tsc2101_local.line = val; + tsc2101_local.mod_cnt++; + ret = tsc2101_update(SET_LINE, gain); + break; + + case SOUND_MIXER_MIC: + tsc2101_local.mic = val; + tsc2101_local.mod_cnt++; + ret = tsc2101_update(SET_MIC, gain); + break; + + case SOUND_MIXER_RECSRC: + if ((val & SOUND_MASK_LINE) || + (val & SOUND_MASK_MIC)) { + if (tsc2101_local.recsrc != val) { + tsc2101_local.mod_cnt++; + tsc2101_update(SET_RECSRC, val); + } + } + else { + ret = -EINVAL; + } + break; + + default: + ret = -EINVAL; + } + } + + if (ret == 0 && _IOC_DIR(cmd) & _IOC_READ) { + ret = 0; + + switch (nr) { + case SOUND_MIXER_VOLUME: + val = tsc2101_local.volume; + val = (tsc2101_local.volume << 8) | + tsc2101_local.volume; + break; + case SOUND_MIXER_LINE: + val = (tsc2101_local.line << 8) | + tsc2101_local.line; + break; + case SOUND_MIXER_MIC: + val = (tsc2101_local.mic << 8) | + tsc2101_local.mic; + break; + case SOUND_MIXER_RECSRC: + val = tsc2101_local.recsrc; + break; + case SOUND_MIXER_RECMASK: + val = REC_MASK; + break; + case SOUND_MIXER_DEVMASK: + val = DEV_MASK; + break; + case SOUND_MIXER_CAPS: + val = 0; + break; + case SOUND_MIXER_STEREODEVS: + val = SOUND_MASK_VOLUME; + break; + default: + val = 0; + printk(KERN_WARNING "omap1610-tsc2101: unknown mixer " + "read ioctl flag specified\n"); + ret = -EINVAL; + break; + } + + if (ret == 0) + ret = put_user(val, (int __user *)arg); + } + out: + FN_OUT(0); + return ret; + +} + +/********************************************************************************* + * + * omap_set_samplerate() + * + ********************************************************************************/ +static int omap_set_samplerate(long sample_rate) +{ + u8 count = 0; + u16 data = 0; + int clkgdv = 0; + /* wait for any frame to complete */ + udelay(125); + + /* Search for the right sample rate */ + while ((reg_info[count].sample_rate != sample_rate) && + (count < NUMBER_SAMPLE_RATES_SUPPORTED)) { + count++; + } + if (count == NUMBER_SAMPLE_RATES_SUPPORTED) { + printk(KERN_ERR "Invalid Sample Rate %d requested\n", + (int)sample_rate); + return -EPERM; + } + + /* Set AC1 */ + data = audio_tsc2101_read(TSC2101_AUDIO_CTRL_1); + /*Clear prev settings */ + data &= ~(AC1_DACFS(0x07) | AC1_ADCFS(0x07)); + data |= + AC1_DACFS(reg_info[count].divisor) | AC1_ADCFS(reg_info[count]. + divisor); + audio_tsc2101_write(TSC2101_AUDIO_CTRL_1, data); + + /* Set the AC3 */ + data = audio_tsc2101_read(TSC2101_AUDIO_CTRL_3); + /*Clear prev settings */ + data &= ~(AC3_REFFS | AC3_SLVMS); + data |= (reg_info[count].fs_44kHz) ? AC3_REFFS : 0; +#ifdef TSC_MASTER + data |= AC3_SLVMS; +#endif /* #ifdef TSC_MASTER */ + audio_tsc2101_write(TSC2101_AUDIO_CTRL_3, data); + + /* program the PLLs */ + if (reg_info[count].fs_44kHz) { + /* 44.1 khz - 12 MHz Mclk */ + audio_tsc2101_write(TSC2101_PLL_PROG_1, PLL1_PLLSEL | PLL1_PVAL(1) | PLL1_I_VAL(7)); /* PVAL 1; I_VAL 7 */ + audio_tsc2101_write(TSC2101_PLL_PROG_2, PLL2_D_VAL(0x1490)); /* D_VAL 5264 */ + } else { + /* 48 khz - 12 Mhz Mclk */ + audio_tsc2101_write(TSC2101_PLL_PROG_1, PLL1_PLLSEL | PLL1_PVAL(1) | PLL1_I_VAL(8)); /* PVAL 1; I_VAL 8 */ + audio_tsc2101_write(TSC2101_PLL_PROG_2, PLL2_D_VAL(0x780)); /* D_VAL 1920 */ + } + + audio_samplerate = sample_rate; + + /* Set the sample rate */ +#ifndef TSC_MASTER + clkgdv = + DEFAULT_MCBSP_CLOCK / (sample_rate * + (DEFAULT_BITPERSAMPLE * 2 - 1)); + if (clkgdv) + initial_config.srgr1 = + (FWID(DEFAULT_BITPERSAMPLE - 1) | CLKGDV(clkgdv)); + else + return (1); + + /* Stereo Mode */ + initial_config.srgr2 = + (CLKSM | FSGM | FPER(DEFAULT_BITPERSAMPLE * 2 - 1)); +#else + initial_config.srgr1 = + (FWID(DEFAULT_BITPERSAMPLE - 1) | CLKGDV(clkgdv)); + initial_config.srgr2 = + ((GSYNC | CLKSP | FSGM | FPER(DEFAULT_BITPERSAMPLE * 2 - 1))); + +#endif /* end of #ifdef TSC_MASTER */ + omap_mcbsp_config(AUDIO_MCBSP, &initial_config); + + return 0; +} + +/********************************************************************************* + * + * omap_tsc2101_initialize() [hw_init() ] + * + ********************************************************************************/ +static void omap_tsc2101_initialize(void *dummy) +{ + + DPRINTK("omap_tsc2101_initialize entry\n"); + + /* initialize with default sample rate */ + audio_samplerate = AUDIO_RATE_DEFAULT; + + omap_mcbsp_request(AUDIO_MCBSP); + + /* if configured, then stop mcbsp */ + omap_mcbsp_stop(AUDIO_MCBSP); + + omap_tsc2101_enable(); + + omap_mcbsp_config(AUDIO_MCBSP, &initial_config); + omap_mcbsp_start(AUDIO_MCBSP); + tsc2101_configure(); + +#ifdef TEST_KEYCLICK + tsc2101_testkeyclick(); +#endif + +#ifdef TONE_GEN + toneGen(); +#endif + + DPRINTK("omap_tsc2101_initialize exit\n"); +} + +/********************************************************************************* + * + * omap_tsc2101_shutdown() [hw_shutdown() ] + * + ********************************************************************************/ +static void omap_tsc2101_shutdown(void *dummy) +{ + /* + Turn off codec after it is done. + Can't do it immediately, since it may still have + buffered data. + + Wait 20ms (arbitrary value) and then turn it off. + */ + + FN_IN; + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(2); + + omap_mcbsp_stop(AUDIO_MCBSP); + omap_mcbsp_free(AUDIO_MCBSP); + + audio_tsc2101_write(TSC2101_CODEC_POWER_CTRL, + ~(CPC_SP1PWDN | CPC_SP2PWDN | CPC_BASSBC)); + + omap_tsc2101_disable(); + + FN_OUT(0); +} + +/********************************************************************************* + * + * tsc2101_configure + * + ********************************************************************************/ +static void tsc2101_configure(void) +{ + FN_IN; + + audio_tsc2101_write(TSC2101_CODEC_POWER_CTRL, 0x0000); + + /*Mute Analog Sidetone */ + /*Select MIC_INHED input for headset */ + /*Cell Phone In not connected */ + audio_tsc2101_write(TSC2101_MIXER_PGA_CTRL, + MPC_ASTMU | MPC_ASTG(0x40) | MPC_MICADC); + + /* Set record source */ + tsc2101_update(SET_RECSRC, tsc2101_local.recsrc); + + /* ADC, DAC, Analog Sidetone, cellphone, buzzer softstepping enabled */ + /* 1dB AGC hysteresis */ + /* MICes bias 2V */ + audio_tsc2101_write(TSC2101_AUDIO_CTRL_4, AC4_MB_HED(0)); + + /* Set codec output volume */ + audio_tsc2101_write(TSC2101_DAC_GAIN_CTRL, 0x0000); + + /* DAC left and right routed to SPK2 */ + /* SPK1/2 unmuted */ + audio_tsc2101_write(TSC2101_AUDIO_CTRL_5, + AC5_DAC2SPK1(3) | AC5_AST2SPK1 | AC5_KCL2SPK1 | + AC5_DAC2SPK2(3) | AC5_AST2SPK2 | AC5_KCL2SPK2 | + AC5_HDSCPTC); + + /* OUT8P/N muted, CPOUT muted */ + + audio_tsc2101_write(TSC2101_AUDIO_CTRL_6, + AC6_MUTLSPK | AC6_MUTSPK2 | AC6_LDSCPTC | + AC6_VGNDSCPTC); + + /* Headset/Hook switch detect disabled */ + audio_tsc2101_write(TSC2101_AUDIO_CTRL_7, 0x0000); + + /* Left line input volume control */ + tsc2101_update(SET_LINE, tsc2101_local.line); + + /* mic input volume control */ + tsc2101_update(SET_MIC, tsc2101_local.mic); + + /* Left/Right headphone channel volume control */ + /* Zero-cross detect on */ + tsc2101_update(SET_VOLUME, tsc2101_local.volume); + + /* clock configuration */ + omap_set_samplerate(audio_samplerate); + +#ifdef TSC_DUMP_REGISTERS + tsc2101_dumpRegisters(); +#endif + + FN_OUT(0); +} + +#ifdef PROC_SUPPORT +static void tsc2101_start(void) +{ + FN_IN; + + audio_tsc2101_write(TSC2101_CODEC_POWER_CTRL, 0x0000); + + /*Mute Analog Sidetone */ + /*Select MIC_INHED input for headset */ + /*Cell Phone In not connected */ + audio_tsc2101_write(TSC2101_MIXER_PGA_CTRL, + MPC_ASTMU | MPC_ASTG(0x40) | MPC_MICADC); + + /* Set record source */ + tsc2101_update(SET_RECSRC, tsc2101_local.recsrc); + + /* ADC, DAC, Analog Sidetone, cellphone, buzzer softstepping enabled */ + /* 1dB AGC hysteresis */ + /* MICes bias 2V */ + audio_tsc2101_write(TSC2101_AUDIO_CTRL_4, AC4_MB_HED(0)); + + /* Set codec output volume */ + audio_tsc2101_write(TSC2101_DAC_GAIN_CTRL, 0x0000); + + /* DAC left and right routed to SPK2 */ + /* SPK1/2 unmuted */ + audio_tsc2101_write(TSC2101_AUDIO_CTRL_5, + AC5_DAC2SPK1(3) | AC5_AST2SPK1 | AC5_KCL2SPK1 | + AC5_DAC2SPK2(3) | AC5_AST2SPK2 | AC5_KCL2SPK2 | + AC5_HDSCPTC); + + /* OUT8P/N muted, CPOUT muted */ + + audio_tsc2101_write(TSC2101_AUDIO_CTRL_6, + AC6_MUTLSPK | AC6_MUTSPK2 | AC6_LDSCPTC | + AC6_VGNDSCPTC); + + /* Headset/Hook switch detect disabled */ + audio_tsc2101_write(TSC2101_AUDIO_CTRL_7, 0x0000); + + /* Left line input volume control */ + tsc2101_update(SET_LINE, tsc2101_local.line); + + /* mic input volume control */ + tsc2101_update(SET_MIC, tsc2101_local.mic); + + /* Left/Right headphone channel volume control */ + /* Zero-cross detect on */ + tsc2101_update(SET_VOLUME, tsc2101_local.volume); + + FN_OUT(0); + +} +#endif + +/****************************************************************************************** + * + * All generic ioctl's are handled by audio_ioctl() [File: omap-audio.c]. This + * routine handles some platform specific ioctl's + * + ******************************************************************************************/ +static int +omap_tsc2101_ioctl(struct inode *inode, struct file *file, uint cmd, ulong arg) +{ + long val; + int ret = 0; + + DPRINTK(" 0x%08x\n", cmd); + + /* + * These are platform dependent ioctls which are not handled by the + * generic omap-audio module. + */ + switch (cmd) { + case SNDCTL_DSP_STEREO: + ret = get_user(val, (int __user *)arg); + if (ret) + return ret; + /* the AIC23 is stereo only */ + ret = (val == 0) ? -EINVAL : 1; + FN_OUT(1); + return put_user(ret, (int __user *)arg); + + case SNDCTL_DSP_CHANNELS: + case SOUND_PCM_READ_CHANNELS: + /* the AIC23 is stereo only */ + FN_OUT(2); + return put_user(2, (long __user *)arg); + + case SNDCTL_DSP_SPEED: + ret = get_user(val, (long __user *)arg); + if (ret) + break; + ret = omap_set_samplerate(val); + if (ret) + break; + /* fall through */ + + case SOUND_PCM_READ_RATE: + FN_OUT(3); + return put_user(audio_samplerate, (long __user *)arg); + + case SOUND_PCM_READ_BITS: + case SNDCTL_DSP_SETFMT: + case SNDCTL_DSP_GETFMTS: + /* we can do 16-bit only */ + FN_OUT(4); + return put_user(AFMT_S16_LE, (long __user *)arg); + + default: + /* Maybe this is meant for the mixer (As per OSS Docs) */ + FN_OUT(5); + return mixer_ioctl(inode, file, cmd, arg); + } + + FN_OUT(0); + return ret; +} + +/********************************************************************************* + * + * module_probe for TSC2101 + * + ********************************************************************************/ +static int omap_tsc2101_probe(void) +{ + FN_IN; + + /* Get the fops from audio oss driver */ + if (!(omap_audio_fops = audio_get_fops())) { + printk(KERN_ERR "Unable to Get the FOPs of Audio OSS driver\n"); + audio_unregister_codec(&tsc2101_state); + return -EPERM; + } + + /* register devices */ + audio_dev_id = register_sound_dsp(omap_audio_fops, -1); + mixer_dev_id = register_sound_mixer(&omap_mixer_fops, -1); + +#ifdef PROC_SUPPORT + create_proc_read_entry(PROC_START_FILE, 0 /* default mode */ , + NULL /* parent dir */ , + codec_start, NULL /* client data */ ); + + create_proc_read_entry(PROC_STOP_FILE, 0 /* default mode */ , + NULL /* parent dir */ , + codec_stop, NULL /* client data */ ); +#endif + + /* Announcement Time */ + printk(KERN_INFO PLATFORM_NAME " " CODEC_NAME + " Audio support initialized\n"); + + FN_OUT(0); + return 0; +} + +/********************************************************************************* + * + * Module Remove for TSC2101 + * + ********************************************************************************/ +static void omap_tsc2101_remove(void) +{ + FN_IN; + /* Un-Register the codec with the audio driver */ + unregister_sound_dsp(audio_dev_id); + unregister_sound_mixer(mixer_dev_id); + +#ifdef PROC_SUPPORT + remove_proc_entry(PROC_START_FILE, NULL); + remove_proc_entry(PROC_STOP_FILE, NULL); +#endif + FN_OUT(0); + +} + +/********************************************************************************* + * + * Module Suspend for TSC2101 + * + ********************************************************************************/ +static int omap_tsc2101_suspend(void) +{ + + FN_OUT(0); + return 0; +} + +/********************************************************************************* + * + * Module Resume for TSC2101 + * + ********************************************************************************/ +static int omap_tsc2101_resume(void) +{ + + FN_OUT(0); + return 0; +} + +/********************************************************************************* + * + * module_init for TSC2101 + * + ********************************************************************************/ +static int __init audio_tsc2101_init(void) +{ + + int err = 0; + FN_IN; + + if (machine_is_omap_osk() || machine_is_omap_innovator()) + return -ENODEV; + + mutex_init(&tsc2101_state.mutex); + + /* register the codec with the audio driver */ + if ((err = audio_register_codec(&tsc2101_state))) { + printk(KERN_ERR + "Failed to register TSC driver with Audio OSS Driver\n"); + } + FN_OUT(err); + return err; +} + +/********************************************************************************* + * + * module_exit for TSC2101 + * + ********************************************************************************/ +static void __exit audio_tsc2101_exit(void) +{ + + FN_IN; + (void)audio_unregister_codec(&tsc2101_state); + FN_OUT(0); + return; +} + +/**************************** DEBUG FUNCTIONS ***********************************/ + +/********************************************************************************* + * TEST_KEYCLICK: + * This is a test to generate various keyclick sound on tsc. + * verifies if the tsc and the spi interfaces are operational. + * + ********************************************************************************/ +#ifdef TEST_KEYCLICK +void tsc2101_testkeyclick(void) +{ + u8 freq = 0; + u16 old_reg_val, reg_val; + u32 uDummyVal = 0; + u32 uTryVal = 0; + + old_reg_val = audio_tsc2101_read(TSC2101_AUDIO_CTRL_2); + + /* Keyclick active, max amplitude and longest key click len(32 period) */ + printk(KERN_INFO " TESTING KEYCLICK\n Listen carefully NOW....\n"); + printk(KERN_INFO " OLD REG VAL=0x%x\n", old_reg_val); + /* try all frequencies */ + for (; freq < 8; freq++) { + /* Keyclick active, max amplitude and longest key click len(32 period) */ + reg_val = old_reg_val | AC2_KCLAC(0x7) | AC2_KCLLN(0xF); + uDummyVal = 0; + uTryVal = 0; + printk(KERN_INFO "\n\nTrying frequency %d reg val= 0x%x\n", + freq, reg_val | AC2_KCLFRQ(freq) | AC2_KCLEN); + audio_tsc2101_write(TSC2101_AUDIO_CTRL_2, + reg_val | AC2_KCLFRQ(freq) | AC2_KCLEN); + printk("DONE. Wait 10 ms ...\n"); + /* wait till the kclk bit is auto cleared! time out also to be considered. */ + while (audio_tsc2101_read(TSC2101_AUDIO_CTRL_2) & AC2_KCLEN) { + udelay(3); + uTryVal++; + if (uTryVal > 2000) { + printk(KERN_ERR + "KEYCLICK TIMED OUT! freq val=%d, POSSIBLE ERROR!\n", + freq); + printk(KERN_INFO + "uTryVal == %d: Read back new reg val= 0x%x\n", + uTryVal, + audio_tsc2101_read + (TSC2101_AUDIO_CTRL_2)); + /* clear */ + audio_tsc2101_write(TSC2101_AUDIO_CTRL_2, 0x00); + break; + } + } + } + /* put the old value back */ + audio_tsc2101_write(TSC2101_AUDIO_CTRL_2, old_reg_val); + printk(KERN_INFO " KEYCLICK TEST COMPLETE\n"); + +} /* End of tsc2101_testkeyclick */ + +#endif /* TEST_KEYCLICK */ + +/********************************************************************************* + * TONEGEN: + * This is a test to generate a rather unpleasant sound.. + * verifies if the mcbsp is active (requires MCBSP_DIRECT_RW to be active on McBSP) + * + ********************************************************************************/ +#ifdef TONE_GEN +/* Generates a shrill tone */ +u16 tone[] = { + 0x0ce4, 0x0ce4, 0x1985, 0x1985, 0x25A1, 0x25A1, 0x30FD, 0x30FE, + 0x3B56, 0x3B55, 0x447A, 0x447A, 0x4C3B, 0x4C3C, 0x526D, 0x526C, + 0x56F1, 0x56F1, 0x59B1, 0x59B1, 0x5A9E, 0x5A9D, 0x59B1, 0x59B2, + 0x56F3, 0x56F2, 0x526D, 0x526D, 0x4C3B, 0x4C3B, 0x447C, 0x447C, + 0x3B5A, 0x3B59, 0x30FE, 0x30FE, 0x25A5, 0x25A6, 0x1989, 0x198A, + 0x0CE5, 0x0CE3, 0x0000, 0x0000, 0xF31C, 0xF31C, 0xE677, 0xE676, + 0xDA5B, 0xDA5B, 0xCF03, 0xCF03, 0xC4AA, 0xC4AA, 0xBB83, 0xBB83, + 0xB3C5, 0xB3C5, 0xAD94, 0xAD94, 0xA90D, 0xA90E, 0xA64F, 0xA64E, + 0xA562, 0xA563, 0xA64F, 0xA64F, 0xA910, 0xA90F, 0xAD93, 0xAD94, + 0xB3C4, 0xB3C4, 0xBB87, 0xBB86, 0xC4AB, 0xC4AB, 0xCF03, 0xCF03, + 0xDA5B, 0xDA5A, 0xE67B, 0xE67B, 0xF31B, 0xF3AC, 0x0000, 0x0000, + 0x0CE4, 0x0CE4, 0x1985, 0x1985, 0x25A1, 0x25A1, 0x30FD, 0x30FE, + 0x3B56, 0x3B55, 0x447A, 0x447A, 0x4C3B, 0x4C3C, 0x526D, 0x526C, + 0x56F1, 0x56F1, 0x59B1, 0x59B1, 0x5A9E, 0x5A9D, 0x59B1, 0x59B2, + 0x56F3, 0x56F2, 0x526D, 0x526D, 0x4C3B, 0x4C3B, 0x447C, 0x447C, + 0x3B5A, 0x3B59, 0x30FE, 0x30FE, 0x25A5, 0x25A6, 0x1989, 0x198A, + 0x0CE5, 0x0CE3, 0x0000, 0x0000, 0xF31C, 0xF31C, 0xE677, 0xE676, + 0xDA5B, 0xDA5B, 0xCF03, 0xCF03, 0xC4AA, 0xC4AA, 0xBB83, 0xBB83, + 0xB3C5, 0xB3C5, 0xAD94, 0xAD94, 0xA90D, 0xA90E, 0xA64F, 0xA64E, + 0xA562, 0xA563, 0xA64F, 0xA64F, 0xA910, 0xA90F, 0xAD93, 0xAD94, + 0xB3C4, 0xB3C4, 0xBB87, 0xBB86, 0xC4AB, 0xC4AB, 0xCF03, 0xCF03, + 0xDA5B, 0xDA5A, 0xE67B, 0xE67B, 0xF31B, 0xF3AC, 0x0000, 0x0000, + 0x0CE4, 0x0CE4, 0x1985, 0x1985, 0x25A1, 0x25A1, 0x30FD, 0x30FE, + 0x3B56, 0x3B55, 0x447A, 0x447A, 0x4C3B, 0x4C3C, 0x526D, 0x526C, + 0x56F1, 0x56F1, 0x59B1, 0x59B1, 0x5A9E, 0x5A9D, 0x59B1, 0x59B2, + 0x56F3, 0x56F2, 0x526D, 0x526D, 0x4C3B, 0x4C3B, 0x447C, 0x447C, + 0x3B5A, 0x3B59, 0x30FE, 0x30FE, 0x25A5, 0x25A6, 0x1989, 0x198A, + 0x0CE5, 0x0CE3, 0x0000, 0x0000, 0xF31C, 0xF31C, 0xE677, 0xE676, + 0xDA5B, 0xDA5B, 0xCF03, 0xCF03, 0xC4AA, 0xC4AA, 0xBB83, 0xBB83, + 0xB3C5, 0xB3C5, 0xAD94, 0xAD94, 0xA90D, 0xA90E, 0xA64F, 0xA64E, + 0xA562, 0xA563, 0xA64F, 0xA64F, 0xA910, 0xA90F, 0xAD93, 0xAD94, + 0xB3C4, 0xB3C4, 0xBB87, 0xBB86, 0xC4AB, 0xC4AB, 0xCF03, 0xCF03, + 0xDA5B, 0xDA5A, 0xE67B, 0xE67B, 0xF31B, 0xF3AC, 0x0000, 0x0000 +}; + +void toneGen(void) +{ + int count = 0; + int ret = 0; + printk(KERN_INFO "TONE GEN TEST :"); + + for (count = 0; count < 5000; count++) { + int bytes; + for (bytes = 0; bytes < sizeof(tone) / 2; bytes++) { + ret = omap_mcbsp_pollwrite(AUDIO_MCBSP, tone[bytes]); + if (ret == -1) { + /* retry */ + bytes--; + } else if (ret == -2) { + printk(KERN_INFO "ERROR:bytes=%d\n", bytes); + return; + } + } + } + printk(KERN_INFO "SUCCESS\n"); +} + +#endif /* End of TONE_GEN */ + +/********************************************************************************* + * + * TSC_DUMP_REGISTERS: + * This will dump the entire register set of Page 2 tsc2101. + * Useful for major goof ups + * + ********************************************************************************/ +#ifdef TSC_DUMP_REGISTERS +static void tsc2101_dumpRegisters(void) +{ + int i = 0; + u16 data = 0; + printk("TSC 2101 Register dump for Page 2 \n"); + for (i = 0; i < 0x27; i++) { + data = audio_tsc2101_read(i); + printk(KERN_INFO "Register[%x]=0x%04x\n", i, data); + + } +} +#endif /* End of #ifdef TSC_DUMP_REGISTERS */ + +#ifdef PROC_SUPPORT +static int codec_start(char *buf, char **start, off_t offset, int count, + int *eof, void *data) +{ + omap_tsc2101_enable(); + tsc2101_start(); + printk("Codec initialization done.\n"); + return 0; +} +static int codec_stop(char *buf, char **start, off_t offset, int count, + int *eof, void *data) +{ + + omap_tsc2101_disable(); + audio_tsc2101_write(TSC2101_CODEC_POWER_CTRL, + ~(CPC_SP1PWDN | CPC_SP2PWDN | CPC_BASSBC)); + printk("Codec shutdown.\n"); + return 0; +} +#endif + +/********************************************************************************* + * + * Other misc management, registration etc + * + ********************************************************************************/ +module_init(audio_tsc2101_init); +module_exit(audio_tsc2101_exit); + +MODULE_AUTHOR("Texas Instruments"); +MODULE_DESCRIPTION + ("Glue audio driver for the TI OMAP1610/OMAP1710 TSC2101 codec."); +MODULE_LICENSE("GPL"); diff --cc sound/oss/omap-audio.c index 84f544598ef,00000000000..95b5eef1184 mode 100644,000000..100644 --- a/sound/oss/omap-audio.c +++ b/sound/oss/omap-audio.c @@@ -1,1162 -1,0 +1,1162 @@@ +/* + * linux/sound/oss/omap-audio.c + * + * Common audio handling for the OMAP processors + * + * Copyright (C) 2004 Texas Instruments, Inc. + * + * Copyright (C) 2000, 2001 Nicolas Pitre + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * History: + * + * 2004/08/12 Nishanth Menon - Modified to integrate Audio requirements on 1610,1710 platforms + * + * 2004-11-01 Nishanth Menon - modified to support 16xx and 17xx + * platform multi channel chaining. + * + * 2004-11-04 Nishanth Menon - Added support for power management + * + * 2004-12-17 Nishanth Menon - Provided proper module handling support + */ + +/***************************** INCLUDES ************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include - #include ++#include + +#include "omap-audio-dma-intfc.h" +#include "omap-audio.h" + +/***************************** MACROS ************************************/ + +#undef DEBUG +//#define DEBUG +#ifdef DEBUG +#define DPRINTK printk +#define FN_IN printk("[omap_audio.c:[%s] start\n", __FUNCTION__) +#define FN_OUT(n) printk("[omap_audio.c:[%s] end(%d)\n", __FUNCTION__ , n) +#else +#define DPRINTK( x... ) +#define FN_IN +#define FN_OUT(x) +#endif + +#define OMAP_AUDIO_NAME "omap-audio" +#define AUDIO_NBFRAGS_DEFAULT 8 +#define AUDIO_FRAGSIZE_DEFAULT 8192 + +/* HACK ALERT!: These values will bave to be tuned as this is a trade off b/w + * Sampling Rate vs buffer size and delay we are prepared to do before giving up + */ +#define MAX_QUEUE_FULL_RETRIES 1000000 +#define QUEUE_WAIT_TIME 10 + +#define AUDIO_ACTIVE(state) ((state)->rd_ref || (state)->wr_ref) + +#define SPIN_ADDR (dma_addr_t)0 +#define SPIN_SIZE 2048 + +/***************************** MODULES SPECIFIC FUNCTION PROTOTYPES ********************/ + +static int audio_write(struct file *file, const char __user *buffer, + size_t count, loff_t * ppos); + +static int audio_read(struct file *file, char __user *buffer, size_t count, + loff_t * ppos); + +static int audio_mmap(struct file *file, struct vm_area_struct *vma); + +static unsigned int audio_poll(struct file *file, + struct poll_table_struct *wait); + +static loff_t audio_llseek(struct file *file, loff_t offset, int origin); + +static int audio_ioctl(struct inode *inode, struct file *file, uint cmd, + ulong arg); + +static int audio_open(struct inode *inode, struct file *file); + +static int audio_release(struct inode *inode, struct file *file); + +static int audio_probe(struct platform_device *pdev); + +static int audio_remove(struct platform_device *pdev); + +static void audio_shutdown(struct platform_device *pdev); + +static int audio_suspend(struct platform_device *pdev, pm_message_t mesg); + +static int audio_resume(struct platform_device *pdev); + +static void audio_free(struct device *dev); + +/***************************** Data Structures **********************************/ + +/* + * The function pointer set to be registered by the codec. + */ +static audio_state_t audio_state = { NULL }; + +/* DMA Call back function */ +static dma_callback_t audio_dma_callback = NULL; + +/* File Ops structure */ +static struct file_operations omap_audio_fops = { + .open = audio_open, + .release = audio_release, + .write = audio_write, + .read = audio_read, + .mmap = audio_mmap, + .poll = audio_poll, + .ioctl = audio_ioctl, + .llseek = audio_llseek, + .owner = THIS_MODULE +}; + +/* Driver information */ +static struct platform_driver omap_audio_driver = { + .probe = audio_probe, + .remove = audio_remove, + .suspend = audio_suspend, + .shutdown = audio_shutdown, + .resume = audio_resume, + .driver = { + .name = OMAP_AUDIO_NAME, + }, +}; + +/* Device Information */ +static struct platform_device omap_audio_device = { + .name = OMAP_AUDIO_NAME, + .dev = { + .driver_data = &audio_state, + .release = audio_free, + }, + .id = 0, +}; + +/***************************** GLOBAL FUNCTIONs **********************************/ + +/* Power Management Functions for Linux Device Model */ +/* DEBUG PUPOSES ONLY! */ +#ifdef CONFIG_PM +//#undef CONFIG_PM +#endif + +#ifdef CONFIG_PM +/********************************************************************************* + * + * audio_ldm_suspend(): Suspend operation + * + *********************************************************************************/ +static int audio_ldm_suspend(void *data) +{ + audio_state_t *state = data; + + FN_IN; + + /* + * Reject the suspend request if we are already actively transmitting data + * Rationale: We dont want to be suspended while in the middle of a call! + */ + if (AUDIO_ACTIVE(state) && state->hw_init) { + printk(KERN_ERR "Audio device Active, Cannot Suspend"); + return -EPERM; +#if 0 + /* NOTE: + * This Piece of code is commented out in hope + * That one day we would need to suspend the device while + * audio operations are in progress and resume the operations + * once the resume is done. + * This is just a sample implementation of how it could be done. + * Currently NOT SUPPORTED + */ + audio_stream_t *is = state->input_stream; + audio_stream_t *os = state->output_stream; + int stopstate; + if (is && is->buffers) { + printk("IS Suspend\n"); + stopstate = is->stopped; + audio_stop_dma(is); + DMA_CLEAR(is); + is->dma_spinref = 0; + is->stopped = stopstate; + } + if (os && os->buffers) { + printk("OS Suspend\n"); + stopstate = os->stopped; + audio_stop_dma(os); + DMA_CLEAR(os); + os->dma_spinref = 0; + os->stopped = stopstate; + } +#endif + } + + FN_OUT(0); + return 0; +} + +/********************************************************************************* + * + * audio_ldm_resume(): Resume Operations + * + *********************************************************************************/ +static int audio_ldm_resume(void *data) +{ + audio_state_t *state = data; + + FN_IN; + if (AUDIO_ACTIVE(state) && state->hw_init) { + /* Should never occur - since we never suspend with active state */ + BUG(); + return -EPERM; +#if 0 + /* NOTE: + * This Piece of code is commented out in hope + * That one day we would need to suspend the device while + * audio operations are in progress and resume the operations + * once the resume is done. + * This is just a sample implementation of how it could be done. + * Currently NOT SUPPORTED + */ + audio_stream_t *is = state->input_stream; + audio_stream_t *os = state->output_stream; + if (os && os->buffers) { + printk("OS Resume\n"); + audio_reset(os); + audio_process_dma(os); + } + if (is && is->buffers) { + printk("IS Resume\n"); + audio_reset(is); + audio_process_dma(is); + } +#endif + } + FN_OUT(0); + return 0; +} +#endif /* End of #ifdef CONFIG_PM */ + +/********************************************************************************* + * + * audio_free(): The Audio driver release function + * This is a dummy function required by the platform driver + * + *********************************************************************************/ +static void audio_free(struct device *dev) +{ + /* Nothing to Release! */ +} + +/********************************************************************************* + * + * audio_probe(): The Audio driver probe function + * WARNING!!!! : It is expected that the codec would have registered with us by now + * + *********************************************************************************/ +static int audio_probe(struct platform_device *pdev) +{ + int ret; + FN_IN; + if (!audio_state.hw_probe) { + printk(KERN_ERR "Probe Function Not Registered\n"); + return -ENODEV; + } + ret = audio_state.hw_probe(); + FN_OUT(ret); + return ret; +} + +/********************************************************************************* + * + * audio_remove() Function to handle removal operations + * + *********************************************************************************/ +static int audio_remove(struct platform_device *pdev) +{ + FN_IN; + if (audio_state.hw_remove) { + audio_state.hw_remove(); + } + FN_OUT(0); + return 0; +} + +/********************************************************************************* + * + * audio_shutdown(): Function to handle shutdown operations + * + *********************************************************************************/ +static void audio_shutdown(struct platform_device *pdev) +{ + FN_IN; + if (audio_state.hw_cleanup) { + audio_state.hw_cleanup(); + } + FN_OUT(0); + return; +} + +/********************************************************************************* + * + * audio_suspend(): Function to handle suspend operations + * + *********************************************************************************/ +static int audio_suspend(struct platform_device *pdev, pm_message_t mesg) +{ + int ret = 0; + +#ifdef CONFIG_PM + void *data = pdev->dev.driver_data; + FN_IN; + if (audio_state.hw_suspend) { + ret = audio_ldm_suspend(data); + if (ret == 0) + ret = audio_state.hw_suspend(); + } + if (ret) { + printk(KERN_INFO "Audio Suspend Failed \n"); + } else { + printk(KERN_INFO "Audio Suspend Success \n"); + } +#endif /* CONFIG_PM */ + + FN_OUT(ret); + return ret; +} + +/********************************************************************************* + * + * audio_resume(): Function to handle resume operations + * + *********************************************************************************/ +static int audio_resume(struct platform_device *pdev) +{ + int ret = 0; + +#ifdef CONFIG_PM + void *data = pdev->dev.driver_data; + FN_IN; + if (audio_state.hw_resume) { + ret = audio_ldm_resume(data); + if (ret == 0) + ret = audio_state.hw_resume(); + } + if (ret) { + printk(KERN_INFO " Audio Resume Failed \n"); + } else { + printk(KERN_INFO " Audio Resume Success \n"); + } +#endif /* CONFIG_PM */ + + FN_OUT(ret); + return ret; +} + +/********************************************************************************* + * + * audio_get_fops(): Return the fops required to get the function pointers of + * OMAP Audio Driver + * + *********************************************************************************/ +struct file_operations *audio_get_fops(void) +{ + FN_IN; + FN_OUT(0); + return &omap_audio_fops; +} + +/********************************************************************************* + * + * audio_register_codec(): Register a Codec fn points using this function + * WARNING!!!!! : Codecs should ensure that they do so! no sanity checks + * during runtime is done due to obvious performance + * penalties. + * + *********************************************************************************/ +int audio_register_codec(audio_state_t * codec_state) +{ + int ret; + FN_IN; + + /* We dont handle multiple codecs now */ + if (audio_state.hw_init) { + printk(KERN_ERR " Codec Already registered\n"); + return -EPERM; + } + + /* Grab the dma Callback */ + audio_dma_callback = audio_get_dma_callback(); + if (!audio_dma_callback) { + printk(KERN_ERR "Unable to get call back function\n"); + return -EPERM; + } + + /* Sanity checks */ + if (!codec_state) { + printk(KERN_ERR "NULL ARGUMENT!\n"); + return -EPERM; + } + + if (!codec_state->hw_probe || !codec_state->hw_init + || !codec_state->hw_shutdown || !codec_state->client_ioctl) { + printk(KERN_ERR + "Required Fn Entry point Missing probe=%p init=%p,down=%p,ioctl=%p!\n", + codec_state->hw_probe, codec_state->hw_init, + codec_state->hw_shutdown, codec_state->client_ioctl); + return -EPERM; + } + + memcpy(&audio_state, codec_state, sizeof(audio_state_t)); + mutex_init(&audio_state.mutex); + + ret = platform_device_register(&omap_audio_device); + if (ret != 0) { + printk(KERN_ERR "Platform dev_register failed =%d\n", ret); + ret = -ENODEV; + goto register_out; + } + + ret = platform_driver_register(&omap_audio_driver); + if (ret != 0) { + printk(KERN_ERR "Device Register failed =%d\n", ret); + ret = -ENODEV; + platform_device_unregister(&omap_audio_device); + goto register_out; + } + + register_out: + + FN_OUT(ret); + return ret; +} + +/********************************************************************************* + * + * audio_unregister_codec(): Un-Register a Codec using this function + * + *********************************************************************************/ +int audio_unregister_codec(audio_state_t * codec_state) +{ + FN_IN; + + /* We dont handle multiple codecs now */ + if (!audio_state.hw_init) { + printk(KERN_ERR " No Codec registered\n"); + return -EPERM; + } + /* Security check */ + if (audio_state.hw_init != codec_state->hw_init) { + printk(KERN_ERR + " Attempt to unregister codec which was not registered with us\n"); + return -EPERM; + } + + platform_driver_unregister(&omap_audio_driver); + platform_device_unregister(&omap_audio_device); + + memset(&audio_state, 0, sizeof(audio_state_t)); + + FN_OUT(0); + return 0; +} + +/***************************** MODULES SPECIFIC FUNCTION *************************/ + +/********************************************************************************* + * + * audio_write(): Exposed to write() call + * + *********************************************************************************/ +static int +audio_write(struct file *file, const char __user *buffer, + size_t count, loff_t * ppos) +{ + const char __user *buffer0 = buffer; + audio_state_t *state = file->private_data; + audio_stream_t *s = state->output_stream; + int chunksize, ret = 0; + + DPRINTK("audio_write: count=%d\n", count); + if (*ppos != file->f_pos) { + printk("FPOS not ppos ppos=0x%x fpos =0x%x\n", (u32) * ppos, + (u32) file->f_pos); + return -ESPIPE; + } + if (s->mapped) { + printk("s already mapped\n"); + return -ENXIO; + } + if (!s->buffers && audio_setup_buf(s)) { + printk("NO MEMORY\n"); + return -ENOMEM; + } + + while (count > 0) { + audio_buf_t *b = &s->buffers[s->usr_head]; + + /* Wait for a buffer to become free */ + if (file->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + if (!s->wfc.done) + break; + } + ret = -ERESTARTSYS; + if (wait_for_completion_interruptible(&s->wfc)) + break; + + /* Feed the current buffer */ + chunksize = s->fragsize - b->offset; + if (chunksize > count) + chunksize = count; + DPRINTK("write %d to %d\n", chunksize, s->usr_head); + if (copy_from_user(b->data + b->offset, buffer, chunksize)) { + printk(KERN_ERR "Audio: CopyFrom User failed \n"); + complete(&s->wfc); + return -EFAULT; + } + + buffer += chunksize; + count -= chunksize; + b->offset += chunksize; + + if (b->offset < s->fragsize) { + complete(&s->wfc); + break; + } + + /* Update pointers and send current fragment to DMA */ + b->offset = 0; + if (++s->usr_head >= s->nbfrags) + s->usr_head = 0; + /* Add the num of frags pending */ + s->pending_frags++; + s->active = 1; + + audio_process_dma(s); + + } + + if ((buffer - buffer0)) + ret = buffer - buffer0; + DPRINTK("audio_write: return=%d\n", ret); + return ret; +} + +/********************************************************************************* + * + * audio_read(): Exposed as read() function + * + *********************************************************************************/ +static int +audio_read(struct file *file, char __user *buffer, size_t count, loff_t * ppos) +{ + char __user *buffer0 = buffer; + audio_state_t *state = file->private_data; + audio_stream_t *s = state->input_stream; + int chunksize, ret = 0; + unsigned long flags; + + DPRINTK("audio_read: count=%d\n", count); + + if (*ppos != file->f_pos) { + printk("AudioRead - FPOS not ppos ppos=0x%x fpos =0x%x\n", + (u32) * ppos, (u32) file->f_pos); + return -ESPIPE; + } + if (s->mapped) { + printk("AudioRead - s already mapped\n"); + return -ENXIO; + } + + if (!s->active) { + if (!s->buffers && audio_setup_buf(s)) { + printk("AudioRead - No Memory\n"); + return -ENOMEM; + } + audio_prime_rx(state); + } + + while (count > 0) { + audio_buf_t *b = &s->buffers[s->usr_head]; + + /* Wait for a buffer to become full */ + if (file->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + if (!s->wfc.done) + break; + } + ret = -ERESTARTSYS; + if (wait_for_completion_interruptible(&s->wfc)) + break; + + /* Grab data from the current buffer */ + chunksize = s->fragsize - b->offset; + if (chunksize > count) + chunksize = count; + DPRINTK("read %d from %d\n", chunksize, s->usr_head); + if (copy_to_user(buffer, b->data + b->offset, chunksize)) { + complete(&s->wfc); + return -EFAULT; + } + buffer += chunksize; + count -= chunksize; + b->offset += chunksize; + if (b->offset < s->fragsize) { + complete(&s->wfc); + break; + } + + /* Update pointers and return current fragment to DMA */ + local_irq_save(flags); + b->offset = 0; + if (++s->usr_head >= s->nbfrags) + s->usr_head = 0; + + s->pending_frags++; + local_irq_restore(flags); + audio_process_dma(s); + + } + + if ((buffer - buffer0)) + ret = buffer - buffer0; + DPRINTK("audio_read: return=%d\n", ret); + return ret; +} + +/********************************************************************************* + * + * audio_mmap(): Exposed as mmap Function + * !!WARNING: Still under development + * + *********************************************************************************/ +static int audio_mmap(struct file *file, struct vm_area_struct *vma) +{ + audio_state_t *state = file->private_data; + audio_stream_t *s; + unsigned long size, vma_addr; + int i, ret; + + FN_IN; + if (vma->vm_pgoff != 0) + return -EINVAL; + + if (vma->vm_flags & VM_WRITE) { + if (!state->wr_ref) + return -EINVAL;; + s = state->output_stream; + } else if (vma->vm_flags & VM_READ) { + if (!state->rd_ref) + return -EINVAL; + s = state->input_stream; + } else + return -EINVAL; + + if (s->mapped) + return -EINVAL; + size = vma->vm_end - vma->vm_start; + if (size != s->fragsize * s->nbfrags) + return -EINVAL; + if (!s->buffers && audio_setup_buf(s)) + return -ENOMEM; + vma_addr = vma->vm_start; + for (i = 0; i < s->nbfrags; i++) { + audio_buf_t *buf = &s->buffers[i]; + if (!buf->master) + continue; + ret = + remap_pfn_range(vma, vma_addr, buf->dma_addr >> PAGE_SHIFT, + buf->master, vma->vm_page_prot); + if (ret) + return ret; + vma_addr += buf->master; + } + s->mapped = 1; + + FN_OUT(0); + return 0; +} + +/********************************************************************************* + * + * audio_poll(): Exposed as poll function + * + *********************************************************************************/ +static unsigned int +audio_poll(struct file *file, struct poll_table_struct *wait) +{ + audio_state_t *state = file->private_data; + audio_stream_t *is = state->input_stream; + audio_stream_t *os = state->output_stream; + unsigned int mask = 0; + + DPRINTK("audio_poll(): mode=%s%s\n", + (file->f_mode & FMODE_READ) ? "r" : "", + (file->f_mode & FMODE_WRITE) ? "w" : ""); + + if (file->f_mode & FMODE_READ) { + /* Start audio input if not already active */ + if (!is->active) { + if (!is->buffers && audio_setup_buf(is)) + return -ENOMEM; + audio_prime_rx(state); + } + poll_wait(file, &is->wq, wait); + } + + if (file->f_mode & FMODE_WRITE) { + if (!os->buffers && audio_setup_buf(os)) + return -ENOMEM; + poll_wait(file, &os->wq, wait); + } + + if (file->f_mode & FMODE_READ) + if ((is->mapped && is->bytecount > 0) || + (!is->mapped && is->wfc.done > 0)) + mask |= POLLIN | POLLRDNORM; + + if (file->f_mode & FMODE_WRITE) + if ((os->mapped && os->bytecount > 0) || + (!os->mapped && os->wfc.done > 0)) + mask |= POLLOUT | POLLWRNORM; + + DPRINTK("audio_poll() returned mask of %s%s\n", + (mask & POLLIN) ? "r" : "", (mask & POLLOUT) ? "w" : ""); + + FN_OUT(mask); + return mask; +} + +/********************************************************************************* + * + * audio_llseek(): Exposed as lseek() function. + * + *********************************************************************************/ +static loff_t audio_llseek(struct file *file, loff_t offset, int origin) +{ + FN_IN; + FN_OUT(0); + return -ESPIPE; +} + +/********************************************************************************* + * + * audio_ioctl(): Handles generic ioctls. If there is a request for something this + * fn cannot handle, its then given to client specific ioctl routine, that will take + * up platform specific requests + * + *********************************************************************************/ +static int +audio_ioctl(struct inode *inode, struct file *file, uint cmd, ulong arg) +{ + audio_state_t *state = file->private_data; + audio_stream_t *os = state->output_stream; + audio_stream_t *is = state->input_stream; + long val; + + DPRINTK(__FILE__ " audio_ioctl 0x%08x\n", cmd); + + /* dispatch based on command */ + switch (cmd) { + case OSS_GETVERSION: + return put_user(SOUND_VERSION, (int __user *)arg); + + case SNDCTL_DSP_GETBLKSIZE: + if (file->f_mode & FMODE_WRITE) + return put_user(os->fragsize, (int __user *)arg); + else + return put_user(is->fragsize, (int __user *)arg); + + case SNDCTL_DSP_GETCAPS: + val = DSP_CAP_REALTIME | DSP_CAP_TRIGGER | DSP_CAP_MMAP; + if (is && os) + val |= DSP_CAP_DUPLEX; + FN_OUT(1); + return put_user(val, (int __user *)arg); + + case SNDCTL_DSP_SETFRAGMENT: + if (get_user(val, (long __user *)arg)) { + FN_OUT(2); + return -EFAULT; + } + if (file->f_mode & FMODE_READ) { + int ret = audio_set_fragments(is, val); + if (ret < 0) { + FN_OUT(3); + return ret; + } + ret = put_user(ret, (int __user *)arg); + if (ret) { + FN_OUT(4); + return ret; + } + } + if (file->f_mode & FMODE_WRITE) { + int ret = audio_set_fragments(os, val); + if (ret < 0) { + FN_OUT(5); + return ret; + } + ret = put_user(ret, (int __user *)arg); + if (ret) { + FN_OUT(6); + return ret; + } + } + FN_OUT(7); + return 0; + + case SNDCTL_DSP_SYNC: + FN_OUT(8); + return audio_sync(file); + + case SNDCTL_DSP_SETDUPLEX: + FN_OUT(9); + return 0; + + case SNDCTL_DSP_POST: + FN_OUT(10); + return 0; + + case SNDCTL_DSP_GETTRIGGER: + val = 0; + if (file->f_mode & FMODE_READ && is->active && !is->stopped) + val |= PCM_ENABLE_INPUT; + if (file->f_mode & FMODE_WRITE && os->active && !os->stopped) + val |= PCM_ENABLE_OUTPUT; + FN_OUT(11); + return put_user(val, (int __user *)arg); + + case SNDCTL_DSP_SETTRIGGER: + if (get_user(val, (int __user *)arg)) { + FN_OUT(12); + return -EFAULT; + } + if (file->f_mode & FMODE_READ) { + if (val & PCM_ENABLE_INPUT) { + unsigned long flags; + if (!is->active) { + if (!is->buffers && audio_setup_buf(is)) { + FN_OUT(13); + return -ENOMEM; + } + audio_prime_rx(state); + } + local_irq_save(flags); + is->stopped = 0; + local_irq_restore(flags); + audio_process_dma(is); + + } else { + audio_stop_dma(is); + } + } + if (file->f_mode & FMODE_WRITE) { + if (val & PCM_ENABLE_OUTPUT) { + unsigned long flags; + if (!os->buffers && audio_setup_buf(os)) { + FN_OUT(14); + return -ENOMEM; + } + local_irq_save(flags); + if (os->mapped && !os->pending_frags) { + os->pending_frags = os->nbfrags; + init_completion(&os->wfc); + os->wfc.done = 0; + os->active = 1; + } + os->stopped = 0; + local_irq_restore(flags); + audio_process_dma(os); + + } else { + audio_stop_dma(os); + } + } + FN_OUT(15); + return 0; + + case SNDCTL_DSP_GETOPTR: + case SNDCTL_DSP_GETIPTR: + { + count_info inf = { 0, }; + audio_stream_t *s = + (cmd == SNDCTL_DSP_GETOPTR) ? os : is; + int bytecount, offset; + unsigned long flags; + + if ((s == is && !(file->f_mode & FMODE_READ)) || + (s == os && !(file->f_mode & FMODE_WRITE))) { + FN_OUT(16); + return -EINVAL; + } + if (s->active) { + local_irq_save(flags); + offset = audio_get_dma_pos(s); + inf.ptr = s->dma_tail * s->fragsize + offset; + bytecount = s->bytecount + offset; + s->bytecount = -offset; + inf.blocks = s->fragcount; + s->fragcount = 0; + local_irq_restore(flags); + if (bytecount < 0) + bytecount = 0; + inf.bytes = bytecount; + } + FN_OUT(17); + return copy_to_user((void __user *)arg, &inf, sizeof(inf)); + } + + case SNDCTL_DSP_GETOSPACE: + case SNDCTL_DSP_GETISPACE: + { + audio_buf_info inf = { 0, }; + audio_stream_t *s = + (cmd == SNDCTL_DSP_GETOSPACE) ? os : is; + + if ((s == is && !(file->f_mode & FMODE_READ)) || + (s == os && !(file->f_mode & FMODE_WRITE))) { + FN_OUT(18); + return -EINVAL; + } + if (!s->buffers && audio_setup_buf(s)) { + FN_OUT(19); + return -ENOMEM; + } + inf.bytes = s->wfc.done * s->fragsize; + + inf.fragments = inf.bytes / s->fragsize; + inf.fragsize = s->fragsize; + inf.fragstotal = s->nbfrags; + FN_OUT(20); + return copy_to_user((void __user *)arg, &inf, sizeof(inf)); + } + + case SNDCTL_DSP_NONBLOCK: + file->f_flags |= O_NONBLOCK; + FN_OUT(21); + return 0; + + case SNDCTL_DSP_RESET: + if (file->f_mode & FMODE_READ) { + audio_reset(is); + if (state->need_tx_for_rx) { + unsigned long flags; + local_irq_save(flags); + os->spin_idle = 0; + local_irq_restore(flags); + } + } + if (file->f_mode & FMODE_WRITE) { + audio_reset(os); + } + FN_OUT(22); + return 0; + + default: + /* + * Let the client of this module handle the + * non generic ioctls + */ + FN_OUT(23); + return state->client_ioctl(inode, file, cmd, arg); + } + + FN_OUT(0); + return 0; +} + +/********************************************************************************* + * + * audio_open(): Exposed as open() function + * + *********************************************************************************/ +static int audio_open(struct inode *inode, struct file *file) +{ + audio_state_t *state = (&audio_state); + audio_stream_t *os = state->output_stream; + audio_stream_t *is = state->input_stream; + int err, need_tx_dma; + static unsigned char tsc2101_init_flag = 0; + + FN_IN; + + /* Lock the module */ + if (!try_module_get(THIS_MODULE)) { + printk(KERN_CRIT "Failed to get module\n"); + return -ESTALE; + } + /* Lock the codec module */ + if (!try_module_get(state->owner)) { + printk(KERN_CRIT "Failed to get codec module\n"); + module_put(THIS_MODULE); + return -ESTALE; + } + + mutex_lock(&state->mutex); + + /* access control */ + err = -ENODEV; + if ((file->f_mode & FMODE_WRITE) && !os) + goto out; + if ((file->f_mode & FMODE_READ) && !is) + goto out; + err = -EBUSY; + if ((file->f_mode & FMODE_WRITE) && state->wr_ref) + goto out; + if ((file->f_mode & FMODE_READ) && state->rd_ref) + goto out; + err = -EINVAL; + if ((file->f_mode & FMODE_READ) && state->need_tx_for_rx && !os) + goto out; + + /* request DMA channels */ + need_tx_dma = ((file->f_mode & FMODE_WRITE) || + ((file->f_mode & FMODE_READ) && state->need_tx_for_rx)); + if (state->wr_ref || (state->rd_ref && state->need_tx_for_rx)) + need_tx_dma = 0; + if (need_tx_dma) { + DMA_REQUEST(err, os, audio_dma_callback); + if (err < 0) + goto out; + } + if (file->f_mode & FMODE_READ) { + DMA_REQUEST(err, is, audio_dma_callback); + if (err < 0) { + if (need_tx_dma) + DMA_FREE(os); + goto out; + } + } + + /* now complete initialisation */ + if (!AUDIO_ACTIVE(state)) { + if (state->hw_init && !tsc2101_init_flag) { + state->hw_init(state->data); + tsc2101_init_flag = 0; + + } + + } + + if ((file->f_mode & FMODE_WRITE)) { + state->wr_ref = 1; + audio_reset(os); + os->fragsize = AUDIO_FRAGSIZE_DEFAULT; + os->nbfrags = AUDIO_NBFRAGS_DEFAULT; + os->mapped = 0; + init_waitqueue_head(&os->wq); + } + + if (file->f_mode & FMODE_READ) { + state->rd_ref = 1; + audio_reset(is); + is->fragsize = AUDIO_FRAGSIZE_DEFAULT; + is->nbfrags = AUDIO_NBFRAGS_DEFAULT; + is->mapped = 0; + init_waitqueue_head(&is->wq); + } + + file->private_data = state; + err = 0; + + out: + mutex_unlock(&state->mutex); + if (err) { + module_put(state->owner); + module_put(THIS_MODULE); + } + FN_OUT(err); + return err; +} + +/********************************************************************************* + * + * audio_release(): Exposed as release function() + * + *********************************************************************************/ +static int audio_release(struct inode *inode, struct file *file) +{ + audio_state_t *state = file->private_data; + audio_stream_t *os = state->output_stream; + audio_stream_t *is = state->input_stream; + + FN_IN; + + mutex_lock(&state->mutex); + + if (file->f_mode & FMODE_READ) { + audio_discard_buf(is); + DMA_FREE(is); + is->dma_spinref = 0; + if (state->need_tx_for_rx) { + os->spin_idle = 0; + if (!state->wr_ref) { + DMA_FREE(os); + os->dma_spinref = 0; + } + } + state->rd_ref = 0; + } + + if (file->f_mode & FMODE_WRITE) { + audio_sync(file); + audio_discard_buf(os); + if (!state->need_tx_for_rx || !state->rd_ref) { + DMA_FREE(os); + os->dma_spinref = 0; + } + state->wr_ref = 0; + } + + if (!AUDIO_ACTIVE(state)) { + if (state->hw_shutdown) + state->hw_shutdown(state->data); + } + + mutex_unlock(&state->mutex); + + module_put(state->owner); + module_put(THIS_MODULE); + + FN_OUT(0); + return 0; +} + +EXPORT_SYMBOL(audio_register_codec); +EXPORT_SYMBOL(audio_unregister_codec); +EXPORT_SYMBOL(audio_get_fops); + +MODULE_AUTHOR("Texas Instruments"); +MODULE_DESCRIPTION("Common audio handling for OMAP processors"); +MODULE_LICENSE("GPL"); diff --cc sound/oss/omap-audio.h index c039dee3b14,00000000000..895d8031d7c mode 100644,000000..100644 --- a/sound/oss/omap-audio.h +++ b/sound/oss/omap-audio.h @@@ -1,125 -1,0 +1,125 @@@ +/* + * linux/sound/oss/omap-audio.h + * + * Common audio handling for the OMAP processors + * + * Copyright (C) 2004 Texas Instruments, Inc. + * + * Copyright (C) 2000, 2001 Nicolas Pitre + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * History + * ------- + * 2004/08/12 Nishanth Menon - Modified to integrate Audio requirements on 1610,1710 platforms + * + * 2004/04/04 Nishanth menon - Added hooks for power management + * + * 2005/12/10 Dirk Behme - Added L/R Channel Interchange fix as proposed by Ajaya Babu + */ + +#ifndef __OMAP_AUDIO_H +#define __OMAP_AUDIO_H + +/* Requires dma.h */ - #include ++#include + +/* + * Buffer Management + */ +typedef struct { + int offset; /* current offset */ + char *data; /* points to actual buffer */ + dma_addr_t dma_addr; /* physical buffer address */ + int dma_ref; /* DMA refcount */ + int master; /* owner for buffer allocation, contain size when true */ +} audio_buf_t; + +/* + * Structure describing the data stream related information + */ +typedef struct { + char *id; /* identification string */ + audio_buf_t *buffers; /* pointer to audio buffer structures */ + u_int usr_head; /* user fragment index */ + u_int dma_head; /* DMA fragment index to go */ + u_int dma_tail; /* DMA fragment index to complete */ + u_int fragsize; /* fragment i.e. buffer size */ + u_int nbfrags; /* nbr of fragments i.e. buffers */ + u_int pending_frags; /* Fragments sent to DMA */ + int dma_dev; /* device identifier for DMA */ + +#ifdef OMAP_DMA_CHAINING_SUPPORT + lch_chain *dma_chain; + dma_regs_t *dma_regs; /* points to our DMA registers */ +#else + char started; /* to store if the chain was started or not */ + int dma_q_head; /* DMA Channel Q Head */ + int dma_q_tail; /* DMA Channel Q Tail */ + char dma_q_count; /* DMA Channel Q Count */ + char in_use; /* Is this is use? */ + int *lch; /* Chain of channels this stream is linked to */ +#endif + int input_or_output; /* Direction of this data stream */ + int bytecount; /* nbr of processed bytes */ + int fragcount; /* nbr of fragment transitions */ + struct completion wfc; /* wait for "nbfrags" fragment completion */ + wait_queue_head_t wq; /* for poll */ + int dma_spinref; /* DMA is spinning */ + unsigned mapped:1; /* mmap()'ed buffers */ + unsigned active:1; /* actually in progress */ + unsigned stopped:1; /* might be active but stopped */ + unsigned spin_idle:1; /* have DMA spin on zeros when idle */ + unsigned linked:1; /* dma channels linked */ + int (*hw_start)(void); /* interface to start HW interface, e.g. McBSP */ + int (*hw_stop)(void); /* interface to stop HW interface, e.g. McBSP */ +} audio_stream_t; + +/* + * State structure for one instance + */ +typedef struct { + struct module *owner; /* Codec module ID */ + audio_stream_t *output_stream; + audio_stream_t *input_stream; + unsigned rd_ref:1; /* open reference for recording */ + unsigned wr_ref:1; /* open reference for playback */ + unsigned need_tx_for_rx:1; /* if data must be sent while receiving */ + void *data; + void (*hw_init) (void *); + void (*hw_shutdown) (void *); + int (*client_ioctl) (struct inode *, struct file *, uint, ulong); + int (*hw_probe) (void); + void (*hw_remove) (void); + void (*hw_cleanup) (void); + int (*hw_suspend) (void); + int (*hw_resume) (void); + struct pm_dev *pm_dev; + struct mutex mutex; /* to protect against races in attach() */ +} audio_state_t; + +#ifdef AUDIO_PM +void audio_ldm_suspend(void *data); + +void audio_ldm_resume(void *data); + +#endif + +/* Register a Codec using this function */ +extern int audio_register_codec(audio_state_t * codec_state); +/* Un-Register a Codec using this function */ +extern int audio_unregister_codec(audio_state_t * codec_state); +/* Function to provide fops of omap audio driver */ +extern struct file_operations *audio_get_fops(void); +/* Function to initialize the device info for audio driver */ +extern int audio_dev_init(void); +/* Function to un-initialize the device info for audio driver */ +void audio_dev_uninit(void); + +#endif /* End of #ifndef __OMAP_AUDIO_H */