From: Tony Lindgren Date: Mon, 11 Aug 2008 14:16:24 +0000 (+0300) Subject: Merge mainline v2.6.27-rc2 tree into linux-omap tree X-Git-Tag: v2.6.27-omap1~329 X-Git-Url: http://www.pilppa.org/gitweb/gitweb.cgi?a=commitdiff_plain;h=5061bcd119547453b32c847d2b9490a052bc1755;p=linux-2.6-omap-h63xx.git Merge mainline v2.6.27-rc2 tree into linux-omap tree Merge branch 'master'; commit 'linus' Conflicts: arch/arm/mach-omap1/board-nokia770.c arch/arm/mach-omap1/mcbsp.c arch/arm/mach-omap2/Makefile arch/arm/mach-omap2/board-apollon.c arch/arm/mach-omap2/clock.c arch/arm/mach-omap2/clock.h arch/arm/mach-omap2/clock24xx.c arch/arm/mach-omap2/clock24xx.h arch/arm/mach-omap2/clock34xx.c arch/arm/mach-omap2/clock34xx.h arch/arm/mach-omap2/id.c arch/arm/mach-omap2/mcbsp.c arch/arm/mach-omap2/memory.c arch/arm/mach-omap2/pm.c arch/arm/mach-omap2/prm.h arch/arm/mach-omap2/sram242x.S arch/arm/mach-omap2/sram243x.S arch/arm/plat-omap/common.c arch/arm/plat-omap/devices.c arch/arm/plat-omap/mcbsp.c arch/arm/plat-omap/sram.c drivers/i2c/busses/Kconfig drivers/i2c/chips/isp1301_omap.c drivers/input/touchscreen/Kconfig drivers/misc/Makefile drivers/mtd/nand/Makefile drivers/net/Kconfig drivers/net/smc911x.h drivers/power/Kconfig drivers/power/Makefile drivers/usb/gadget/omap_udc.c include/asm-arm/arch-omap/board-2430sdp.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/hardware.h include/asm-arm/arch-omap/io.h include/asm-arm/arch-omap/mcbsp.h include/asm-arm/arch-omap/omap34xx.h include/asm-arm/arch-omap/sram.h include/asm-arm/cpu-multi32.h include/asm-arm/pgtable.h include/asm-arm/setup.h include/linux/i2c-id.h kernel/printk.c security/Makefile sound/arm/Kconfig --- 5061bcd119547453b32c847d2b9490a052bc1755 diff --cc arch/arm/Kconfig index 28f15c4a725,4b8acd2851f..cbc8406eaf2 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@@ -1179,13 -1225,10 +1225,15 @@@ source "drivers/dma/Kconfig source "drivers/dca/Kconfig" + source "drivers/regulator/Kconfig" + source "drivers/uio/Kconfig" +if ARCH_OMAP +source "drivers/cbus/Kconfig" +source "drivers/dsp/dspgateway/Kconfig" +endif + endmenu source "fs/Kconfig" diff --cc arch/arm/include/asm/pgtable.h index 00000000000,8ab060a53ab..53f7acb1719 mode 000000,100644..100644 --- a/arch/arm/include/asm/pgtable.h +++ b/arch/arm/include/asm/pgtable.h @@@ -1,0 -1,401 +1,402 @@@ + /* + * arch/arm/include/asm/pgtable.h + * + * Copyright (C) 1995-2002 Russell King + * + * 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 _ASMARM_PGTABLE_H + #define _ASMARM_PGTABLE_H + + #include + #include + + #ifndef CONFIG_MMU + + #include "pgtable-nommu.h" + + #else + + #include + #include + #include + + /* + * Just any arbitrary offset to the start of the vmalloc VM area: the + * current 8MB value just means that there will be a 8MB "hole" after the + * physical memory until the kernel virtual memory starts. That means that + * any out-of-bounds memory accesses will hopefully be caught. + * The vmalloc() routines leaves a hole of 4kB between each vmalloced + * area for the same reason. ;) + * + * Note that platforms may override VMALLOC_START, but they must provide + * VMALLOC_END. VMALLOC_END defines the (exclusive) limit of this space, + * which may not overlap IO space. + */ + #ifndef VMALLOC_START + #define VMALLOC_OFFSET (8*1024*1024) + #define VMALLOC_START (((unsigned long)high_memory + VMALLOC_OFFSET) & ~(VMALLOC_OFFSET-1)) + #endif + + /* + * Hardware-wise, we have a two level page table structure, where the first + * level has 4096 entries, and the second level has 256 entries. Each entry + * is one 32-bit word. Most of the bits in the second level entry are used + * by hardware, and there aren't any "accessed" and "dirty" bits. + * + * Linux on the other hand has a three level page table structure, which can + * be wrapped to fit a two level page table structure easily - using the PGD + * and PTE only. However, Linux also expects one "PTE" table per page, and + * at least a "dirty" bit. + * + * Therefore, we tweak the implementation slightly - we tell Linux that we + * have 2048 entries in the first level, each of which is 8 bytes (iow, two + * hardware pointers to the second level.) The second level contains two + * hardware PTE tables arranged contiguously, followed by Linux versions + * which contain the state information Linux needs. We, therefore, end up + * with 512 entries in the "PTE" level. + * + * This leads to the page tables having the following layout: + * + * pgd pte + * | | + * +--------+ +0 + * | |-----> +------------+ +0 + * +- - - - + +4 | h/w pt 0 | + * | |-----> +------------+ +1024 + * +--------+ +8 | h/w pt 1 | + * | | +------------+ +2048 + * +- - - - + | Linux pt 0 | + * | | +------------+ +3072 + * +--------+ | Linux pt 1 | + * | | +------------+ +4096 + * + * See L_PTE_xxx below for definitions of bits in the "Linux pt", and + * PTE_xxx for definitions of bits appearing in the "h/w pt". + * + * PMD_xxx definitions refer to bits in the first level page table. + * + * The "dirty" bit is emulated by only granting hardware write permission + * iff the page is marked "writable" and "dirty" in the Linux PTE. This + * means that a write to a clean page will cause a permission fault, and + * the Linux MM layer will mark the page dirty via handle_pte_fault(). + * For the hardware to notice the permission change, the TLB entry must + * be flushed, and ptep_set_access_flags() does that for us. + * + * The "accessed" or "young" bit is emulated by a similar method; we only + * allow accesses to the page if the "young" bit is set. Accesses to the + * page will cause a fault, and handle_pte_fault() will set the young bit + * for us as long as the page is marked present in the corresponding Linux + * PTE entry. Again, ptep_set_access_flags() will ensure that the TLB is + * up to date. + * + * However, when the "young" bit is cleared, we deny access to the page + * by clearing the hardware PTE. Currently Linux does not flush the TLB + * for us in this case, which means the TLB will retain the transation + * until either the TLB entry is evicted under pressure, or a context + * switch which changes the user space mapping occurs. + */ + #define PTRS_PER_PTE 512 + #define PTRS_PER_PMD 1 + #define PTRS_PER_PGD 2048 + + /* + * PMD_SHIFT determines the size of the area a second-level page table can map + * PGDIR_SHIFT determines what a third-level page table entry can map + */ + #define PMD_SHIFT 21 + #define PGDIR_SHIFT 21 + + #define LIBRARY_TEXT_START 0x0c000000 + + #ifndef __ASSEMBLY__ + extern void __pte_error(const char *file, int line, unsigned long val); + extern void __pmd_error(const char *file, int line, unsigned long val); + extern void __pgd_error(const char *file, int line, unsigned long val); + + #define pte_ERROR(pte) __pte_error(__FILE__, __LINE__, pte_val(pte)) + #define pmd_ERROR(pmd) __pmd_error(__FILE__, __LINE__, pmd_val(pmd)) + #define pgd_ERROR(pgd) __pgd_error(__FILE__, __LINE__, pgd_val(pgd)) + #endif /* !__ASSEMBLY__ */ + + #define PMD_SIZE (1UL << PMD_SHIFT) + #define PMD_MASK (~(PMD_SIZE-1)) + #define PGDIR_SIZE (1UL << PGDIR_SHIFT) + #define PGDIR_MASK (~(PGDIR_SIZE-1)) + + /* + * This is the lowest virtual address we can permit any user space + * mapping to be mapped at. This is particularly important for + * non-high vector CPUs. + */ + #define FIRST_USER_ADDRESS PAGE_SIZE + + #define FIRST_USER_PGD_NR 1 + #define USER_PTRS_PER_PGD ((TASK_SIZE/PGDIR_SIZE) - FIRST_USER_PGD_NR) + + /* + * section address mask and size definitions. + */ + #define SECTION_SHIFT 20 + #define SECTION_SIZE (1UL << SECTION_SHIFT) + #define SECTION_MASK (~(SECTION_SIZE-1)) + + /* + * ARMv6 supersection address mask and size definitions. + */ + #define SUPERSECTION_SHIFT 24 + #define SUPERSECTION_SIZE (1UL << SUPERSECTION_SHIFT) + #define SUPERSECTION_MASK (~(SUPERSECTION_SIZE-1)) + + /* + * "Linux" PTE definitions. + * + * We keep two sets of PTEs - the hardware and the linux version. + * This allows greater flexibility in the way we map the Linux bits + * onto the hardware tables, and allows us to have YOUNG and DIRTY + * bits. + * + * The PTE table pointer refers to the hardware entries; the "Linux" + * entries are stored 1024 bytes below. + */ + #define L_PTE_PRESENT (1 << 0) + #define L_PTE_FILE (1 << 1) /* only when !PRESENT */ + #define L_PTE_YOUNG (1 << 1) + #define L_PTE_BUFFERABLE (1 << 2) /* matches PTE */ + #define L_PTE_CACHEABLE (1 << 3) /* matches PTE */ + #define L_PTE_USER (1 << 4) + #define L_PTE_WRITE (1 << 5) + #define L_PTE_EXEC (1 << 6) + #define L_PTE_DIRTY (1 << 7) + #define L_PTE_SHARED (1 << 10) /* shared(v6), coherent(xsc3) */ + + #ifndef __ASSEMBLY__ + + /* + * The pgprot_* and protection_map entries will be fixed up in runtime + * to include the cachable and bufferable bits based on memory policy, + * as well as any architecture dependent bits like global/ASID and SMP + * shared mapping bits. + */ + #define _L_PTE_DEFAULT L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_CACHEABLE | L_PTE_BUFFERABLE + #define _L_PTE_READ L_PTE_USER | L_PTE_EXEC + + extern pgprot_t pgprot_user; + extern pgprot_t pgprot_kernel; + + #define PAGE_NONE pgprot_user + #define PAGE_COPY __pgprot(pgprot_val(pgprot_user) | _L_PTE_READ) + #define PAGE_SHARED __pgprot(pgprot_val(pgprot_user) | _L_PTE_READ | \ + L_PTE_WRITE) + #define PAGE_READONLY __pgprot(pgprot_val(pgprot_user) | _L_PTE_READ) + #define PAGE_KERNEL pgprot_kernel + + #define __PAGE_NONE __pgprot(_L_PTE_DEFAULT) + #define __PAGE_COPY __pgprot(_L_PTE_DEFAULT | _L_PTE_READ) + #define __PAGE_SHARED __pgprot(_L_PTE_DEFAULT | _L_PTE_READ | L_PTE_WRITE) + #define __PAGE_READONLY __pgprot(_L_PTE_DEFAULT | _L_PTE_READ) + + #endif /* __ASSEMBLY__ */ + + /* + * The table below defines the page protection levels that we insert into our + * Linux page table version. These get translated into the best that the + * architecture can perform. Note that on most ARM hardware: + * 1) We cannot do execute protection + * 2) If we could do execute protection, then read is implied + * 3) write implies read permissions + */ + #define __P000 __PAGE_NONE + #define __P001 __PAGE_READONLY + #define __P010 __PAGE_COPY + #define __P011 __PAGE_COPY + #define __P100 __PAGE_READONLY + #define __P101 __PAGE_READONLY + #define __P110 __PAGE_COPY + #define __P111 __PAGE_COPY + + #define __S000 __PAGE_NONE + #define __S001 __PAGE_READONLY + #define __S010 __PAGE_SHARED + #define __S011 __PAGE_SHARED + #define __S100 __PAGE_READONLY + #define __S101 __PAGE_READONLY + #define __S110 __PAGE_SHARED + #define __S111 __PAGE_SHARED + + #ifndef __ASSEMBLY__ + /* + * ZERO_PAGE is a global shared page that is always zero: used + * for zero-mapped memory areas etc.. + */ + extern struct page *empty_zero_page; + #define ZERO_PAGE(vaddr) (empty_zero_page) + + #define pte_pfn(pte) (pte_val(pte) >> PAGE_SHIFT) + #define pfn_pte(pfn,prot) (__pte(((pfn) << PAGE_SHIFT) | pgprot_val(prot))) + + #define pte_none(pte) (!pte_val(pte)) + #define pte_clear(mm,addr,ptep) set_pte_ext(ptep, __pte(0), 0) + #define pte_page(pte) (pfn_to_page(pte_pfn(pte))) + #define pte_offset_kernel(dir,addr) (pmd_page_vaddr(*(dir)) + __pte_index(addr)) + #define pte_offset_map(dir,addr) (pmd_page_vaddr(*(dir)) + __pte_index(addr)) + #define pte_offset_map_nested(dir,addr) (pmd_page_vaddr(*(dir)) + __pte_index(addr)) + #define pte_unmap(pte) do { } while (0) + #define pte_unmap_nested(pte) do { } while (0) + + #define set_pte_ext(ptep,pte,ext) cpu_set_pte_ext(ptep,pte,ext) + + #define set_pte_at(mm,addr,ptep,pteval) do { \ + set_pte_ext(ptep, pteval, (addr) >= TASK_SIZE ? 0 : PTE_EXT_NG); \ + } while (0) + + /* + * The following only work if pte_present() is true. + * Undefined behaviour if not.. + */ + #define pte_present(pte) (pte_val(pte) & L_PTE_PRESENT) + #define pte_write(pte) (pte_val(pte) & L_PTE_WRITE) + #define pte_dirty(pte) (pte_val(pte) & L_PTE_DIRTY) + #define pte_young(pte) (pte_val(pte) & L_PTE_YOUNG) + #define pte_special(pte) (0) + + /* + * The following only works if pte_present() is not true. + */ + #define pte_file(pte) (pte_val(pte) & L_PTE_FILE) + #define pte_to_pgoff(x) (pte_val(x) >> 2) + #define pgoff_to_pte(x) __pte(((x) << 2) | L_PTE_FILE) + + #define PTE_FILE_MAX_BITS 30 + + #define PTE_BIT_FUNC(fn,op) \ + static inline pte_t pte_##fn(pte_t pte) { pte_val(pte) op; return pte; } + + PTE_BIT_FUNC(wrprotect, &= ~L_PTE_WRITE); + PTE_BIT_FUNC(mkwrite, |= L_PTE_WRITE); + PTE_BIT_FUNC(mkclean, &= ~L_PTE_DIRTY); + PTE_BIT_FUNC(mkdirty, |= L_PTE_DIRTY); + PTE_BIT_FUNC(mkold, &= ~L_PTE_YOUNG); + PTE_BIT_FUNC(mkyoung, |= L_PTE_YOUNG); + + static inline pte_t pte_mkspecial(pte_t pte) { return pte; } + + /* + * Mark the prot value as uncacheable and unbufferable. + */ + #define pgprot_noncached(prot) __pgprot(pgprot_val(prot) & ~(L_PTE_CACHEABLE | L_PTE_BUFFERABLE)) + #define pgprot_writecombine(prot) __pgprot(pgprot_val(prot) & ~L_PTE_CACHEABLE) + + #define pmd_none(pmd) (!pmd_val(pmd)) + #define pmd_present(pmd) (pmd_val(pmd)) + #define pmd_bad(pmd) (pmd_val(pmd) & 2) ++#define pmd_table(pmd) ((pmd_val(pmd) & PMD_TYPE_MASK) == PMD_TYPE_TABLE) + + #define copy_pmd(pmdpd,pmdps) \ + do { \ + pmdpd[0] = pmdps[0]; \ + pmdpd[1] = pmdps[1]; \ + flush_pmd_entry(pmdpd); \ + } while (0) + + #define pmd_clear(pmdp) \ + do { \ + pmdp[0] = __pmd(0); \ + pmdp[1] = __pmd(0); \ + clean_pmd_entry(pmdp); \ + } while (0) + + static inline pte_t *pmd_page_vaddr(pmd_t pmd) + { + unsigned long ptr; + + ptr = pmd_val(pmd) & ~(PTRS_PER_PTE * sizeof(void *) - 1); + ptr += PTRS_PER_PTE * sizeof(void *); + + return __va(ptr); + } + + #define pmd_page(pmd) virt_to_page(__va(pmd_val(pmd))) + + /* + * Permanent address of a page. We never have highmem, so this is trivial. + */ + #define pages_to_mb(x) ((x) >> (20 - PAGE_SHIFT)) + + /* + * Conversion functions: convert a page and protection to a page entry, + * and a page entry and page directory to the page they refer to. + */ + #define mk_pte(page,prot) pfn_pte(page_to_pfn(page),prot) + + /* + * The "pgd_xxx()" functions here are trivial for a folded two-level + * setup: the pgd is never bad, and a pmd always exists (as it's folded + * into the pgd entry) + */ + #define pgd_none(pgd) (0) + #define pgd_bad(pgd) (0) + #define pgd_present(pgd) (1) + #define pgd_clear(pgdp) do { } while (0) + #define set_pgd(pgd,pgdp) do { } while (0) + + /* to find an entry in a page-table-directory */ + #define pgd_index(addr) ((addr) >> PGDIR_SHIFT) + + #define pgd_offset(mm, addr) ((mm)->pgd+pgd_index(addr)) + + /* to find an entry in a kernel page-table-directory */ + #define pgd_offset_k(addr) pgd_offset(&init_mm, addr) + + /* Find an entry in the second-level page table.. */ + #define pmd_offset(dir, addr) ((pmd_t *)(dir)) + + /* Find an entry in the third-level page table.. */ + #define __pte_index(addr) (((addr) >> PAGE_SHIFT) & (PTRS_PER_PTE - 1)) + + static inline pte_t pte_modify(pte_t pte, pgprot_t newprot) + { + const unsigned long mask = L_PTE_EXEC | L_PTE_WRITE | L_PTE_USER; + pte_val(pte) = (pte_val(pte) & ~mask) | (pgprot_val(newprot) & mask); + return pte; + } + + extern pgd_t swapper_pg_dir[PTRS_PER_PGD]; + + /* Encode and decode a swap entry. + * + * We support up to 32GB of swap on 4k machines + */ + #define __swp_type(x) (((x).val >> 2) & 0x7f) + #define __swp_offset(x) ((x).val >> 9) + #define __swp_entry(type,offset) ((swp_entry_t) { ((type) << 2) | ((offset) << 9) }) + #define __pte_to_swp_entry(pte) ((swp_entry_t) { pte_val(pte) }) + #define __swp_entry_to_pte(swp) ((pte_t) { (swp).val }) + + /* Needs to be defined here and not in linux/mm.h, as it is arch dependent */ + /* FIXME: this is not correct */ + #define kern_addr_valid(addr) (1) + + #include + + /* + * We provide our own arch_get_unmapped_area to cope with VIPT caches. + */ + #define HAVE_ARCH_UNMAPPED_AREA + + /* + * remap a physical page `pfn' of size `size' with page protection `prot' + * into virtual address `from' + */ + #define io_remap_pfn_range(vma,from,pfn,size,prot) \ + remap_pfn_range(vma, from, pfn, size, prot) + + #define pgtable_cache_init() do { } while (0) + + #endif /* !__ASSEMBLY__ */ + + #endif /* CONFIG_MMU */ + + #endif /* _ASMARM_PGTABLE_H */ diff --cc arch/arm/mach-omap2/board-n800.c index 433bd8e6b5a,00000000000..90909630b92 mode 100644,000000..100644 --- a/arch/arm/mach-omap2/board-n800.c +++ b/arch/arm/mach-omap2/board-n800.c @@@ -1,736 -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 <../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/plat-omap/gpio-switch.c index 7a61d0e185a,00000000000..7c94fc3c080 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 + +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), IRQT_FALLING); ++ set_irq_type(OMAP_GPIO_IRQ(sw->gpio), IRQ_TYPE_EDGE_FALLING); + else - set_irq_type(OMAP_GPIO_IRQ(sw->gpio), IRQT_RISING); ++ 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 + * + * 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 "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), IRQT_NOEDGE); ++ 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), IRQT_BOTHEDGE); ++ 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), IRQT_NOEDGE); ++ 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), IRQT_NOEDGE); ++ 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/cbus/retu.c index ebb86b363de,00000000000..ceb93d03e2d 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 "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), IRQT_RISING); ++ 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.c index edf7082c60a,00000000000..bc7a4b6c69a 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 "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), IRQT_RISING); ++ 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/crypto/Makefile index 6549d4503a4,73557b2968d..5da41a156fc --- a/drivers/crypto/Makefile +++ b/drivers/crypto/Makefile @@@ -1,5 -1,6 +1,7 @@@ obj-$(CONFIG_CRYPTO_DEV_PADLOCK_AES) += padlock-aes.o obj-$(CONFIG_CRYPTO_DEV_PADLOCK_SHA) += padlock-sha.o obj-$(CONFIG_CRYPTO_DEV_GEODE) += geode-aes.o +obj-$(CONFIG_OMAP_SHA1_MD5) += omap-sha1-md5.o obj-$(CONFIG_CRYPTO_DEV_HIFN_795X) += hifn_795x.o + obj-$(CONFIG_CRYPTO_DEV_TALITOS) += talitos.o + obj-$(CONFIG_CRYPTO_DEV_IXP4XX) += ixp4xx_crypto.o diff --cc drivers/dsp/dspgateway/task.c index e3a0a02a103,00000000000..4d7dcdd2658 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 "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), + "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(pl->pid) != NULL) ++ 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/i2c/busses/Kconfig index 9204f711fd5,6ee997b2817..eb94378cab7 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@@ -332,74 -403,10 +403,26 @@@ config I2C_OMA default y if MACH_OMAP_H3 || MACH_OMAP_OSK help If you say yes to this option, support will be included for the - I2C interface on the Texas Instruments OMAP1/2 family of processors. - Like OMAP1510/1610/1710/5912 and OMAP242x. + I2C interface on the Texas Instruments OMAP1/2/3 family of + processors. + Like OMAP1510/1610/1710/5912, OMAP242x, OMAP34x and OMAP35x. For details see http://www.ti.com/omap. +config I2C2_OMAP_BEAGLE + bool "Enable I2C2 for OMAP3 BeagleBoard" + depends on ARCH_OMAP && MACH_OMAP3_BEAGLE + select OMAP_MUX + default n + help + Say Y here if you want to enable I2C bus 2 at OMAP3 based + BeagleBoard. + I2C2 at BeagleBoard is connected to expansion connector, i.e. unused + if nothing is connected to this connector. As internal OMAP3 pull up + resistors are not strong enough, enabled but unused I2C2 bus results + in error messages (e.g. I2C timeouts). Enable this only if you have + something connected to I2C2 at board's expansion connector and this + extension has additional pull up resistors for I2C2 bus. + - - config I2C_PARPORT - tristate "Parallel port adapter" - depends on PARPORT - select I2C_ALGOBIT - help - This supports parallel port I2C adapters such as the ones made by - Philips or Velleman, Analog Devices evaluation boards, and more. - Basically any adapter using the parallel port as an I2C bus with - no extra chipset is supported by this driver, or could be. - - This driver is a replacement for (and was inspired by) an older - driver named i2c-philips-par. The new driver supports more devices, - and makes it easier to add support for new devices. - - An adapter type parameter is now mandatory. Please read the file - Documentation/i2c/busses/i2c-parport for details. - - Another driver exists, named i2c-parport-light, which doesn't depend - on the parport driver. This is meant for embedded systems. Don't say - Y here if you intend to say Y or M there. - - This support is also available as a module. If so, the module - will be called i2c-parport. - - config I2C_PARPORT_LIGHT - tristate "Parallel port adapter (light)" - select I2C_ALGOBIT - help - This supports parallel port I2C adapters such as the ones made by - Philips or Velleman, Analog Devices evaluation boards, and more. - Basically any adapter using the parallel port as an I2C bus with - no extra chipset is supported by this driver, or could be. - - This driver is a light version of i2c-parport. It doesn't depend - on the parport driver, and uses direct I/O access instead. This - might be preferred on embedded systems where wasting memory for - the clean but heavy parport handling is not an option. The - drawback is a reduced portability and the impossibility to - daisy-chain other parallel port devices. - - Don't say Y here if you said Y or M to i2c-parport. Saying M to - both is possible but both modules should not be loaded at the same - time. - - This support is also available as a module. If so, the module - will be called i2c-parport-light. - config I2C_PASEMI tristate "PA Semi SMBus interface" depends on PPC_PASEMI && PCI diff --cc drivers/i2c/busses/Makefile index 889fcc0520a,97dbfa2107f..d4dc22da8cc --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@@ -17,45 -34,38 +34,39 @@@ obj-$(CONFIG_I2C_GPIO) += i2c-gpio. obj-$(CONFIG_I2C_IBM_IIC) += i2c-ibm_iic.o obj-$(CONFIG_I2C_IOP3XX) += i2c-iop3xx.o obj-$(CONFIG_I2C_IXP2000) += i2c-ixp2000.o - obj-$(CONFIG_I2C_POWERMAC) += i2c-powermac.o obj-$(CONFIG_I2C_MPC) += i2c-mpc.o obj-$(CONFIG_I2C_MV64XXX) += i2c-mv64xxx.o - obj-$(CONFIG_I2C_NFORCE2) += i2c-nforce2.o obj-$(CONFIG_I2C_OCORES) += i2c-ocores.o obj-$(CONFIG_I2C_OMAP) += i2c-omap.o - obj-$(CONFIG_I2C_PARPORT) += i2c-parport.o - obj-$(CONFIG_I2C_PARPORT_LIGHT) += i2c-parport-light.o obj-$(CONFIG_I2C_PASEMI) += i2c-pasemi.o - obj-$(CONFIG_I2C_PCA_ISA) += i2c-pca-isa.o - obj-$(CONFIG_I2C_PCA_PLATFORM) += i2c-pca-platform.o - obj-$(CONFIG_I2C_PIIX4) += i2c-piix4.o - obj-$(CONFIG_I2C_PMCMSP) += i2c-pmcmsp.o obj-$(CONFIG_I2C_PNX) += i2c-pnx.o - obj-$(CONFIG_I2C_PROSAVAGE) += i2c-prosavage.o obj-$(CONFIG_I2C_PXA) += i2c-pxa.o obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o - obj-$(CONFIG_I2C_SAVAGE4) += i2c-savage4.o obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o - obj-$(CONFIG_I2C_SIBYTE) += i2c-sibyte.o obj-$(CONFIG_I2C_SIMTEC) += i2c-simtec.o - obj-$(CONFIG_I2C_SIS5595) += i2c-sis5595.o - obj-$(CONFIG_I2C_SIS630) += i2c-sis630.o - obj-$(CONFIG_I2C_SIS96X) += i2c-sis96x.o - obj-$(CONFIG_I2C_STUB) += i2c-stub.o + obj-$(CONFIG_I2C_VERSATILE) += i2c-versatile.o + + # External I2C/SMBus adapter drivers + obj-$(CONFIG_I2C_PARPORT) += i2c-parport.o + obj-$(CONFIG_I2C_PARPORT_LIGHT) += i2c-parport-light.o obj-$(CONFIG_I2C_TAOS_EVM) += i2c-taos-evm.o obj-$(CONFIG_I2C_TINY_USB) += i2c-tiny-usb.o - obj-$(CONFIG_I2C_VERSATILE) += i2c-versatile.o - obj-$(CONFIG_I2C_ACORN) += i2c-acorn.o - obj-$(CONFIG_I2C_VIA) += i2c-via.o - obj-$(CONFIG_I2C_VIAPRO) += i2c-viapro.o + + # Graphics adapter I2C/DDC channel drivers obj-$(CONFIG_I2C_VOODOO3) += i2c-voodoo3.o + + # Other I2C/SMBus bus drivers + obj-$(CONFIG_I2C_ACORN) += i2c-acorn.o + obj-$(CONFIG_I2C_ELEKTOR) += i2c-elektor.o + obj-$(CONFIG_I2C_PCA_ISA) += i2c-pca-isa.o + obj-$(CONFIG_I2C_PCA_PLATFORM) += i2c-pca-platform.o + obj-$(CONFIG_I2C_PMCMSP) += i2c-pmcmsp.o + obj-$(CONFIG_I2C_SIBYTE) += i2c-sibyte.o + obj-$(CONFIG_I2C_STUB) += i2c-stub.o obj-$(CONFIG_SCx200_ACB) += scx200_acb.o obj-$(CONFIG_SCx200_I2C) += scx200_i2c.o +obj-$(CONFIG_I2C_OMAP) += i2c-omap.o ifeq ($(CONFIG_I2C_DEBUG_BUS),y) EXTRA_CFLAGS += -DDEBUG diff --cc drivers/i2c/chips/twl4030-core.c index 632431f435a,00000000000..1a8e4137c05 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 + +#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, IRQT_FALLING); ++ 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/input/keyboard/tsc2301_kp.c index 58669f93f73,00000000000..a974a5fdc2e mode 100644,000000..100644 --- a/drivers/input/keyboard/tsc2301_kp.c +++ b/drivers/input/keyboard/tsc2301_kp.c @@@ -1,475 -1,0 +1,475 @@@ +/* + * TSC2301 keypad driver + * + * Copyright (C) 2005-2006 Nokia Corporation + * + * Written by Jarkko Oikarinen + * Rewritten by Juha Yrjola + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define TSC2301_KEYBOARD_PRODUCT_ID 0x0051 +#define TSC2301_KEYBOARD_PRODUCT_VERSION 0x0001 +#define TSC2301_DEBOUNCE_TIME_2MS 0x0000 +#define TSC2301_DEBOUNCE_TIME_10MS 0x0800 +#define TSC2301_DEBOUNCE_TIME_20MS 0x1000 +#define TSC2301_DEBOUNCE_TIME_50MS 0x1800 +#define TSC2301_DEBOUNCE_TIME_60MS 0x2000 +#define TSC2301_DEBOUNCE_TIME_80MS 0x2800 +#define TSC2301_DEBOUNCE_TIME_100MS 0x3000 +#define TSC2301_DEBOUNCE_TIME_120MS 0x3800 + +#define TSC2301_DEBOUNCE_TIME TSC2301_DEBOUNCE_TIME_20MS + +#define TSC2301_RELEASE_TIMEOUT 50 + +struct tsc2301_kp { + struct input_dev *idev; + char phys[32]; + spinlock_t lock; + struct mutex mutex; + struct timer_list timer; + u16 keys_pressed; + unsigned pending:1; + unsigned user_disabled:1; + unsigned disable_depth; + + struct spi_transfer read_xfer[4]; + struct spi_message read_msg; + + u16 data; + u16 mask; + + int irq; + s16 keymap[16]; +}; + +static inline int tsc2301_kp_disabled(struct tsc2301 *tsc) +{ + return tsc->kp->disable_depth != 0; +} + +static void tsc2301_kp_send_key_events(struct tsc2301 *tsc, + u16 prev_state, + u16 new_state) +{ + struct tsc2301_kp *kp = tsc->kp; + u16 common, released, pressed; + int i; + + common = prev_state & new_state; + released = common ^ prev_state; + pressed = common ^ new_state; + if (!released && !pressed) + return; + for (i = 0; i < 16 && (released || pressed); i++) { + if (released & 1) { + dev_dbg(&tsc->spi->dev, "key %d released\n", i); + input_report_key(kp->idev, kp->keymap[i], 0); + } + released >>= 1; + if (pressed & 1) { + dev_dbg(&tsc->spi->dev, "key %d pressed\n", i); + input_report_key(kp->idev, kp->keymap[i], 1); + } + pressed >>= 1; + } + input_sync(kp->idev); +} + +static inline void _filter_out(struct tsc2301 *tsc, u16 prev_state, + u16 *new_state, int row1, int row2, u8 rect_pat) +{ + u16 mask; + + mask = (rect_pat << (row1 * 4)) | (rect_pat << (row2 * 4)); + mask &= ~prev_state; + *new_state &= ~mask; + dev_dbg(&tsc->spi->dev, "filtering ghost keys %02x\n", mask); +} + +static void tsc2301_filter_ghost_keys(struct tsc2301 *tsc, u16 prev_state, + u16 *new_state) +{ + int row1, row2; + u16 key_map; + u16 row1_map; + static const u8 rect_pat[] = { + 0x3, 0x5, 0x9, 0x6, 0xa, 0xc, 0, + }; + + key_map = *new_state; + for (row1 = 0; row1 < 4; row1++) { + row1_map = (key_map >> (row1 * 4)) & 0xf; + if (!row1_map) + continue; + for (row2 = row1 + 1; row2 < 4; row2++) { + u16 rect_map = (key_map >> (row2 * 4)) & 0xf; + const u8 *rp; + + rect_map &= row1_map; + if (!rect_map) + continue; + for (rp = rect_pat; *rp; rp++) + if ((rect_map & *rp) == *rp) + _filter_out(tsc, prev_state, new_state, + row1, row2, *rp); + } + } +} + +static void tsc2301_kp_timer(unsigned long arg) +{ + struct tsc2301 *tsc = (void *) arg; + struct tsc2301_kp *kp = tsc->kp; + unsigned long flags; + + tsc2301_kp_send_key_events(tsc, kp->keys_pressed, 0); + spin_lock_irqsave(&kp->lock, flags); + kp->keys_pressed = 0; + spin_unlock_irqrestore(&kp->lock, flags); +} + +static void tsc2301_kp_rx(void *arg) +{ + struct tsc2301 *tsc = arg; + struct tsc2301_kp *kp = tsc->kp; + unsigned long flags; + u16 kp_data; + + kp_data = kp->data; + dev_dbg(&tsc->spi->dev, "KP data %04x\n", kp_data); + + tsc2301_filter_ghost_keys(tsc, kp->keys_pressed, &kp_data); + tsc2301_kp_send_key_events(tsc, kp->keys_pressed, kp_data); + spin_lock_irqsave(&kp->lock, flags); + kp->keys_pressed = kp_data; + kp->pending = 0; + spin_unlock_irqrestore(&kp->lock, flags); +} + +static irqreturn_t tsc2301_kp_irq_handler(int irq, void *dev_id) +{ + struct tsc2301 *tsc = dev_id; + struct tsc2301_kp *kp = tsc->kp; + unsigned long flags; + int r; + + spin_lock_irqsave(&kp->lock, flags); + if (tsc2301_kp_disabled(tsc)) { + spin_unlock_irqrestore(&kp->lock, flags); + return IRQ_HANDLED; + } + kp->pending = 1; + spin_unlock_irqrestore(&kp->lock, flags); + mod_timer(&kp->timer, + jiffies + msecs_to_jiffies(TSC2301_RELEASE_TIMEOUT)); + r = spi_async(tsc->spi, &tsc->kp->read_msg); + if (r) + dev_err(&tsc->spi->dev, "kp: spi_async() failed"); + return IRQ_HANDLED; +} + +static void tsc2301_kp_start_scan(struct tsc2301 *tsc) +{ + tsc2301_write_reg(tsc, TSC2301_REG_KPMASK, tsc->kp->mask); + tsc2301_write_reg(tsc, TSC2301_REG_KEY, TSC2301_DEBOUNCE_TIME); +} + +static void tsc2301_kp_stop_scan(struct tsc2301 *tsc) +{ + tsc2301_write_reg(tsc, TSC2301_REG_KEY, 1 << 14); +} + +/* Must be called with the mutex held */ +static void tsc2301_kp_enable(struct tsc2301 *tsc) +{ + struct tsc2301_kp *kp = tsc->kp; + unsigned long flags; + + spin_lock_irqsave(&kp->lock, flags); + BUG_ON(!tsc2301_kp_disabled(tsc)); + if (--kp->disable_depth != 0) { + spin_unlock_irqrestore(&kp->lock, flags); + return; + } + spin_unlock_irqrestore(&kp->lock, flags); + - set_irq_type(kp->irq, IRQT_FALLING); ++ set_irq_type(kp->irq, IRQ_TYPE_EDGE_FALLING); + tsc2301_kp_start_scan(tsc); + enable_irq(kp->irq); +} + +/* Must be called with the mutex held */ +static int tsc2301_kp_disable(struct tsc2301 *tsc, int release_keys) +{ + struct tsc2301_kp *kp = tsc->kp; + unsigned long flags; + + spin_lock_irqsave(&kp->lock, flags); + if (kp->disable_depth++ != 0) { + spin_unlock_irqrestore(&kp->lock, flags); + goto out; + } + disable_irq_nosync(kp->irq); - set_irq_type(kp->irq, IRQT_NOEDGE); ++ set_irq_type(kp->irq, IRQ_TYPE_NONE); + spin_unlock_irqrestore(&kp->lock, flags); + + while (kp->pending) { + msleep(1); + } + + tsc2301_kp_stop_scan(tsc); +out: + if (!release_keys) + del_timer(&kp->timer); /* let timeout release keys */ + + return 0; +} + +/* The following workaround is needed for a HW bug triggered by the + * following: + * 1. keep any key pressed + * 2. disable keypad + * 3. release all keys + * 4. reenable keypad + * 5. disable touch screen controller + * + * After this the keypad scanner will get stuck in busy state and won't + * report any interrupts for further keypresses. One way to recover is to + * restart the keypad scanner whenever we enable / disable the + * touchscreen controller. + */ +void tsc2301_kp_restart(struct tsc2301 *tsc) +{ + if (!tsc2301_kp_disabled(tsc)) { + tsc2301_kp_start_scan(tsc); + } +} + +static ssize_t tsc2301_kp_disable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tsc2301 *tsc = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", tsc2301_kp_disabled(tsc) ? 1 : 0); +} + +static ssize_t tsc2301_kp_disable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct tsc2301 *tsc = dev_get_drvdata(dev); + struct tsc2301_kp *kp = tsc->kp; + char *endp; + int i; + + i = simple_strtoul(buf, &endp, 10); + i = i ? 1 : 0; + + mutex_lock(&kp->mutex); + if (i == kp->user_disabled) { + mutex_unlock(&kp->mutex); + return count; + } + kp->user_disabled = i; + + if (i) + tsc2301_kp_disable(tsc, 1); + else + tsc2301_kp_enable(tsc); + mutex_unlock(&kp->mutex); + + return count; +} + +static DEVICE_ATTR(disable_kp, 0664, tsc2301_kp_disable_show, + tsc2301_kp_disable_store); + +static const u16 tsc2301_kp_read_data = 0x8000 | TSC2301_REG_KPDATA; + +static void tsc2301_kp_setup_spi_xfer(struct tsc2301 *tsc) +{ + struct tsc2301_kp *kp = tsc->kp; + struct spi_message *m = &kp->read_msg; + struct spi_transfer *x = &kp->read_xfer[0]; + + spi_message_init(&kp->read_msg); + + x->tx_buf = &tsc2301_kp_read_data; + x->len = 2; + spi_message_add_tail(x, m); + x++; + + x->rx_buf = &kp->data; + x->len = 2; + spi_message_add_tail(x, m); + + m->complete = tsc2301_kp_rx; + m->context = tsc; +} + +#ifdef CONFIG_PM +int tsc2301_kp_suspend(struct tsc2301 *tsc) +{ + struct tsc2301_kp *kp = tsc->kp; + + mutex_lock(&kp->mutex); + tsc2301_kp_disable(tsc, 1); + mutex_unlock(&kp->mutex); + return 0; +} + +void tsc2301_kp_resume(struct tsc2301 *tsc) +{ + struct tsc2301_kp *kp = tsc->kp; + + mutex_lock(&kp->mutex); + tsc2301_kp_enable(tsc); + mutex_unlock(&kp->mutex); +} +#endif + +int __devinit tsc2301_kp_init(struct tsc2301 *tsc, + struct tsc2301_platform_data *pdata) +{ + struct input_dev *idev; + struct tsc2301_kp *kp; + int r, i; + u16 mask; + + if (pdata->keyb_int < 0) { + dev_err(&tsc->spi->dev, "need kbirq"); + return -EINVAL; + } + + kp = kzalloc(sizeof(*kp), GFP_KERNEL); + if (kp == NULL) + return -ENOMEM; + tsc->kp = kp; + + kp->irq = pdata->keyb_int; + spin_lock_init(&kp->lock); + mutex_init(&kp->mutex); + + init_timer(&kp->timer); + kp->timer.data = (unsigned long) tsc; + kp->timer.function = tsc2301_kp_timer; + + idev = input_allocate_device(); + if (idev == NULL) { + r = -ENOMEM; + goto err1; + } + if (pdata->keyb_name) + idev->name = pdata->keyb_name; + else + idev->name = "TSC2301 keypad"; + snprintf(kp->phys, sizeof(kp->phys), "%s/input-kp", tsc->spi->dev.bus_id); + idev->phys = kp->phys; + + mask = 0; + idev->evbit[0] = BIT(EV_KEY); + for (i = 0; i < 16; i++) { + if (pdata->keymap[i] > 0) { + set_bit(pdata->keymap[i], idev->keybit); + kp->keymap[i] = pdata->keymap[i]; + } else { + kp->keymap[i] = -1; + mask |= 1 << i; + } + } + + if (pdata->kp_rep) + set_bit(EV_REP, idev->evbit); + + kp->idev = idev; + + tsc2301_kp_setup_spi_xfer(tsc); + + r = device_create_file(&tsc->spi->dev, &dev_attr_disable_kp); + if (r < 0) + goto err2; + + tsc2301_kp_start_scan(tsc); + + /* IRQ mode 0 is faulty, it can cause the KBIRQ to get stuck. + * Mode 2 deasserts the IRQ at: + * - HW or SW reset + * - Setting SCS flag in REG_KEY register + * - Releasing all keys + * - Reading the REG_KPDATA + */ + tsc2301_write_kbc(tsc, 2); + + tsc2301_write_reg(tsc, TSC2301_REG_KPMASK, mask); + kp->mask = mask; + - set_irq_type(kp->irq, IRQT_FALLING); ++ set_irq_type(kp->irq, IRQ_TYPE_EDGE_FALLING); + + r = request_irq(kp->irq, tsc2301_kp_irq_handler, IRQF_SAMPLE_RANDOM, + "tsc2301-kp", tsc); + if (r < 0) { + dev_err(&tsc->spi->dev, "unable to get kbirq IRQ"); + goto err3; + } + set_irq_wake(kp->irq, 1); + + /* We need to read the register once..? */ + tsc2301_read_reg(tsc, TSC2301_REG_KPDATA); + + r = input_register_device(idev); + if (r < 0) { + dev_err(&tsc->spi->dev, "can't register keypad device\n"); + goto err4; + } + + return 0; + +err4: + free_irq(kp->irq, tsc); +err3: + tsc2301_kp_stop_scan(tsc); + device_remove_file(&tsc->spi->dev, &dev_attr_disable_kp); +err2: + input_free_device(kp->idev); +err1: + kfree(kp); + return r; +} + +void __devexit tsc2301_kp_exit(struct tsc2301 *tsc) +{ + struct tsc2301_kp *kp = tsc->kp; + + tsc2301_kp_disable(tsc, 1); + input_unregister_device(kp->idev); + free_irq(kp->irq, tsc); + device_remove_file(&tsc->spi->dev, &dev_attr_disable_kp); + + kfree(kp); +} diff --cc drivers/input/touchscreen/Kconfig index 8b5fd642389,6e60a97a234..f32361fac87 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@@ -170,46 -205,18 +205,58 @@@ config TOUCHSCREEN_TOUCHWI To compile this driver as a module, choose M here: the module will be called touchwin. + config TOUCHSCREEN_ATMEL_TSADCC + tristate "Atmel Touchscreen Interface" + depends on ARCH_AT91SAM9RL + help + Say Y here if you have a 4-wire touchscreen connected to the + ADC Controller on your Atmel SoC (such as the AT91SAM9RL). + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called atmel_tsadcc. + +config TOUCHSCREEN_TSC2005 + tristate "TSC2005 touchscreen support" + help + Say Y here for if you are using the touchscreen features of TSC2301. + +config TOUCHSCREEN_TSC2102 + tristate "TSC 2102 based touchscreens" + depends on SPI_MASTER + select SPI_TSC2102 + help + Say Y here if you have a touchscreen interface using the + TI TSC 2102 controller, and your board-specific initialization + code includes that in its table of SPI devices. Also make + sure the proper SPI controller is selected. + + If unsure, say N (but it's safe to say "Y"). + + To compile this driver as a module, choose M here: the + module will be called tsc2102_ts. + +config TOUCHSCREEN_TSC210X + tristate "TI TSC210x based touchscreens" + depends on SPI_MASTER + select SPI_TSC210X + help + Say Y here if you have a touchscreen interface using a + TI TSC210x controller, and your board-specific initialisation + code includes that in its table of SPI devices. + + If unsure, say N (but it's safe to say "Y"). + + To compile this driver as a module, choose M here: the + module will be called tsc210x_ts. + +config TOUCHSCREEN_TSC2301 + tristate "TSC2301 touchscreen support" + depends on SPI_TSC2301 + help + Say Y here for if you are using the touchscreen features of TSC2301. + config TOUCHSCREEN_UCB1400 tristate "Philips UCB1400 touchscreen" select AC97_BUS diff --cc drivers/media/video/omap24xxcam.c index dfd34796d04,00000000000..6793869fcf5 mode 100644,000000..100644 --- a/drivers/media/video/omap24xxcam.c +++ b/drivers/media/video/omap24xxcam.c @@@ -1,1905 -1,0 +1,1905 @@@ +/* + * drivers/media/video/omap24xxcam.c + * + * OMAP 2 camera block driver. + * + * Copyright (C) 2004 MontaVista Software, Inc. + * Copyright (C) 2004 Texas Instruments. + * Copyright (C) 2007 Nokia Corporation. + * + * Contact: Sakari Ailus + * + * Based on code from Andy Lowe + * + * 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 /* needed for videobufs */ +#include +#include +#include +#include + +#include ++#include + +#include "omap24xxcam.h" + +#define OMAP24XXCAM_VERSION KERNEL_VERSION(0, 0, 0) + +#define RESET_TIMEOUT_NS 10000 + +static void omap24xxcam_reset(struct omap24xxcam_device *cam); +static int omap24xxcam_sensor_if_enable(struct omap24xxcam_device *cam); +static void omap24xxcam_device_unregister(struct v4l2_int_device *s); +static int omap24xxcam_remove(struct platform_device *pdev); + +/* module parameters */ +static int video_nr = -1; /* video device minor (-1 ==> auto assign) */ +/* + * Maximum amount of memory to use for capture buffers. + * Default is 4800KB, enough to double-buffer SXGA. + */ +static int capture_mem = 1280 * 960 * 2 * 2; + +static struct v4l2_int_device omap24xxcam; + +/* + * + * Clocks. + * + */ + +static void omap24xxcam_clock_put(struct omap24xxcam_device *cam) +{ + if (cam->ick != NULL && !IS_ERR(cam->ick)) + clk_put(cam->ick); + if (cam->fck != NULL && !IS_ERR(cam->fck)) + clk_put(cam->fck); + + cam->ick = cam->fck = NULL; +} + +static int omap24xxcam_clock_get(struct omap24xxcam_device *cam) +{ + int rval = 0; + + cam->fck = clk_get(cam->dev, "cam_fck"); + if (IS_ERR(cam->fck)) { + dev_err(cam->dev, "can't get cam_fck"); + rval = PTR_ERR(cam->fck); + omap24xxcam_clock_put(cam); + return rval; + } + + cam->ick = clk_get(cam->dev, "cam_ick"); + if (IS_ERR(cam->ick)) { + dev_err(cam->dev, "can't get cam_ick"); + rval = PTR_ERR(cam->ick); + omap24xxcam_clock_put(cam); + } + + return rval; +} + +static void omap24xxcam_clock_on(struct omap24xxcam_device *cam) +{ + clk_enable(cam->fck); + clk_enable(cam->ick); +} + +static void omap24xxcam_clock_off(struct omap24xxcam_device *cam) +{ + clk_disable(cam->fck); + clk_disable(cam->ick); +} + +/* + * + * Camera core + * + */ + +/* + * Set xclk. + * + * To disable xclk, use value zero. + */ +static void omap24xxcam_core_xclk_set(const struct omap24xxcam_device *cam, + u32 xclk) +{ + if (xclk) { + u32 divisor = CAM_MCLK / xclk; + + if (divisor == 1) + omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, + CC_CTRL_XCLK, + CC_CTRL_XCLK_DIV_BYPASS); + else + omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, + CC_CTRL_XCLK, divisor); + } else + omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, + CC_CTRL_XCLK, CC_CTRL_XCLK_DIV_STABLE_LOW); +} + +static void omap24xxcam_core_hwinit(const struct omap24xxcam_device *cam) +{ + /* + * Setting the camera core AUTOIDLE bit causes problems with frame + * synchronization, so we will clear the AUTOIDLE bit instead. + */ + omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, CC_SYSCONFIG, + CC_SYSCONFIG_AUTOIDLE); + + /* program the camera interface DMA packet size */ + omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, CC_CTRL_DMA, + CC_CTRL_DMA_EN | (DMA_THRESHOLD / 4 - 1)); + + /* enable camera core error interrupts */ + omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, CC_IRQENABLE, + CC_IRQENABLE_FW_ERR_IRQ + | CC_IRQENABLE_FSC_ERR_IRQ + | CC_IRQENABLE_SSC_ERR_IRQ + | CC_IRQENABLE_FIFO_OF_IRQ); +} + +/* + * Enable the camera core. + * + * Data transfer to the camera DMA starts from next starting frame. + */ +static void omap24xxcam_core_enable(const struct omap24xxcam_device *cam) +{ + + omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, CC_CTRL, + cam->cc_ctrl); +} + +/* + * Disable camera core. + * + * The data transfer will be stopped immediately (CC_CTRL_CC_RST). The + * core internal state machines will be reset. Use + * CC_CTRL_CC_FRAME_TRIG instead if you want to transfer the current + * frame completely. + */ +static void omap24xxcam_core_disable(const struct omap24xxcam_device *cam) +{ + omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, CC_CTRL, + CC_CTRL_CC_RST); +} + +/* Interrupt service routine for camera core interrupts. */ +static void omap24xxcam_core_isr(struct omap24xxcam_device *cam) +{ + u32 cc_irqstatus; + const u32 cc_irqstatus_err = + CC_IRQSTATUS_FW_ERR_IRQ + | CC_IRQSTATUS_FSC_ERR_IRQ + | CC_IRQSTATUS_SSC_ERR_IRQ + | CC_IRQSTATUS_FIFO_UF_IRQ + | CC_IRQSTATUS_FIFO_OF_IRQ; + + cc_irqstatus = omap24xxcam_reg_in(cam->mmio_base + CC_REG_OFFSET, + CC_IRQSTATUS); + omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, CC_IRQSTATUS, + cc_irqstatus); + + if (cc_irqstatus & cc_irqstatus_err + && !atomic_read(&cam->in_reset)) { + dev_dbg(cam->dev, "resetting camera, cc_irqstatus 0x%x\n", + cc_irqstatus); + omap24xxcam_reset(cam); + } +} + +/* + * + * videobuf_buffer handling. + * + * Memory for mmapped videobuf_buffers is not allocated + * conventionally, but by several kmalloc allocations and then + * creating the scatterlist on our own. User-space buffers are handled + * normally. + * + */ + +/* + * Free the memory-mapped buffer memory allocated for a + * videobuf_buffer and the associated scatterlist. + */ +static void omap24xxcam_vbq_free_mmap_buffer(struct videobuf_buffer *vb) +{ + struct videobuf_dmabuf *dma = videobuf_to_dma(vb); + size_t alloc_size; + struct page *page; + int i; + + if (dma->sglist == NULL) + return; + + i = dma->sglen; + while (i) { + i--; + alloc_size = sg_dma_len(&dma->sglist[i]); + page = sg_page(&dma->sglist[i]); + do { + ClearPageReserved(page++); + } while (alloc_size -= PAGE_SIZE); + __free_pages(sg_page(&dma->sglist[i]), + get_order(sg_dma_len(&dma->sglist[i]))); + } + + kfree(dma->sglist); + dma->sglist = NULL; +} + +/* Release all memory related to the videobuf_queue. */ +static void omap24xxcam_vbq_free_mmap_buffers(struct videobuf_queue *vbq) +{ + int i; + + mutex_lock(&vbq->vb_lock); + + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + if (NULL == vbq->bufs[i]) + continue; + if (V4L2_MEMORY_MMAP != vbq->bufs[i]->memory) + continue; + vbq->ops->buf_release(vbq, vbq->bufs[i]); + omap24xxcam_vbq_free_mmap_buffer(vbq->bufs[i]); + kfree(vbq->bufs[i]); + vbq->bufs[i] = NULL; + } + + mutex_unlock(&vbq->vb_lock); + + videobuf_mmap_free(vbq); +} + +/* + * Allocate physically as contiguous as possible buffer for video + * frame and allocate and build DMA scatter-gather list for it. + */ +static int omap24xxcam_vbq_alloc_mmap_buffer(struct videobuf_buffer *vb) +{ + unsigned int order; + size_t alloc_size, size = vb->bsize; /* vb->bsize is page aligned */ + struct page *page; + int max_pages, err = 0, i = 0; + struct videobuf_dmabuf *dma = videobuf_to_dma(vb); + + /* + * allocate maximum size scatter-gather list. Note this is + * overhead. We may not use as many entries as we allocate + */ + max_pages = vb->bsize >> PAGE_SHIFT; + dma->sglist = kcalloc(max_pages, sizeof(*dma->sglist), GFP_KERNEL); + if (dma->sglist == NULL) { + err = -ENOMEM; + goto out; + } + + while (size) { + order = get_order(size); + /* + * do not over-allocate even if we would get larger + * contiguous chunk that way + */ + if ((PAGE_SIZE << order) > size) + order--; + + /* try to allocate as many contiguous pages as possible */ + page = alloc_pages(GFP_KERNEL | GFP_DMA, order); + /* if allocation fails, try to allocate smaller amount */ + while (page == NULL) { + order--; + page = alloc_pages(GFP_KERNEL | GFP_DMA, order); + if (page == NULL && !order) { + err = -ENOMEM; + goto out; + } + } + size -= (PAGE_SIZE << order); + + /* append allocated chunk of pages into scatter-gather list */ + sg_set_page(&dma->sglist[i], page, PAGE_SIZE << order, 0); + dma->sglen++; + i++; + + alloc_size = (PAGE_SIZE << order); + + /* clear pages before giving them to user space */ + memset(page_address(page), 0, alloc_size); + + /* mark allocated pages reserved */ + do { + SetPageReserved(page++); + } while (alloc_size -= PAGE_SIZE); + } + /* + * REVISIT: not fully correct to assign nr_pages == sglen but + * video-buf is passing nr_pages for e.g. unmap_sg calls + */ + dma->nr_pages = dma->sglen; + dma->direction = PCI_DMA_FROMDEVICE; + + return 0; + +out: + omap24xxcam_vbq_free_mmap_buffer(vb); + return err; +} + +static int omap24xxcam_vbq_alloc_mmap_buffers(struct videobuf_queue *vbq, + unsigned int count) +{ + int i, err = 0; + struct omap24xxcam_fh *fh = + container_of(vbq, struct omap24xxcam_fh, vbq); + + mutex_lock(&vbq->vb_lock); + + for (i = 0; i < count; i++) { + err = omap24xxcam_vbq_alloc_mmap_buffer(vbq->bufs[i]); + if (err) + goto out; + dev_dbg(fh->cam->dev, "sglen is %d for buffer %d\n", + videobuf_to_dma(vbq->bufs[i])->sglen, i); + } + + mutex_unlock(&vbq->vb_lock); + + return 0; +out: + while (i) { + i--; + omap24xxcam_vbq_free_mmap_buffer(vbq->bufs[i]); + } + + mutex_unlock(&vbq->vb_lock); + + return err; +} + +/* + * This routine is called from interrupt context when a scatter-gather DMA + * transfer of a videobuf_buffer completes. + */ +static void omap24xxcam_vbq_complete(struct omap24xxcam_sgdma *sgdma, + u32 csr, void *arg) +{ + struct omap24xxcam_device *cam = + container_of(sgdma, struct omap24xxcam_device, sgdma); + struct omap24xxcam_fh *fh = cam->streaming->private_data; + struct videobuf_buffer *vb = (struct videobuf_buffer *)arg; + const u32 csr_error = CAMDMA_CSR_MISALIGNED_ERR + | CAMDMA_CSR_SUPERVISOR_ERR | CAMDMA_CSR_SECURE_ERR + | CAMDMA_CSR_TRANS_ERR | CAMDMA_CSR_DROP; + unsigned long flags; + + spin_lock_irqsave(&cam->core_enable_disable_lock, flags); + if (--cam->sgdma_in_queue == 0) + omap24xxcam_core_disable(cam); + spin_unlock_irqrestore(&cam->core_enable_disable_lock, flags); + + do_gettimeofday(&vb->ts); + vb->field_count = atomic_add_return(2, &fh->field_count); + if (csr & csr_error) { + vb->state = VIDEOBUF_ERROR; + if (!atomic_read(&fh->cam->in_reset)) { + dev_dbg(cam->dev, "resetting camera, csr 0x%x\n", csr); + omap24xxcam_reset(cam); + } + } else + vb->state = VIDEOBUF_DONE; + wake_up(&vb->done); +} + +static void omap24xxcam_vbq_release(struct videobuf_queue *vbq, + struct videobuf_buffer *vb) +{ + struct videobuf_dmabuf *dma = videobuf_to_dma(vb); + + /* wait for buffer, especially to get out of the sgdma queue */ + videobuf_waiton(vb, 0, 0); + if (vb->memory == V4L2_MEMORY_MMAP) { + dma_unmap_sg(vbq->dev, dma->sglist, dma->sglen, + dma->direction); + dma->direction = DMA_NONE; + } else { + videobuf_dma_unmap(vbq, videobuf_to_dma(vb)); + videobuf_dma_free(videobuf_to_dma(vb)); + } + + vb->state = VIDEOBUF_NEEDS_INIT; +} + +/* + * Limit the number of available kernel image capture buffers based on the + * number requested, the currently selected image size, and the maximum + * amount of memory permitted for kernel capture buffers. + */ +static int omap24xxcam_vbq_setup(struct videobuf_queue *vbq, unsigned int *cnt, + unsigned int *size) +{ + struct omap24xxcam_fh *fh = vbq->priv_data; + + if (*cnt <= 0) + *cnt = VIDEO_MAX_FRAME; /* supply a default number of buffers */ + + if (*cnt > VIDEO_MAX_FRAME) + *cnt = VIDEO_MAX_FRAME; + + *size = fh->pix.sizeimage; + + /* accessing fh->cam->capture_mem is ok, it's constant */ + while (*size * *cnt > fh->cam->capture_mem) + (*cnt)--; + + return 0; +} + +static int omap24xxcam_dma_iolock(struct videobuf_queue *vbq, + struct videobuf_dmabuf *dma) +{ + int err = 0; + + dma->direction = PCI_DMA_FROMDEVICE; + if (!dma_map_sg(vbq->dev, dma->sglist, dma->sglen, dma->direction)) { + kfree(dma->sglist); + dma->sglist = NULL; + dma->sglen = 0; + err = -EIO; + } + + return err; +} + +static int omap24xxcam_vbq_prepare(struct videobuf_queue *vbq, + struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct omap24xxcam_fh *fh = vbq->priv_data; + int err = 0; + + /* + * Accessing pix here is okay since it's constant while + * streaming is on (and we only get called then). + */ + if (vb->baddr) { + /* This is a userspace buffer. */ + if (fh->pix.sizeimage > vb->bsize) { + /* The buffer isn't big enough. */ + err = -EINVAL; + } else + vb->size = fh->pix.sizeimage; + } else { + if (vb->state != VIDEOBUF_NEEDS_INIT) { + /* + * We have a kernel bounce buffer that has + * already been allocated. + */ + if (fh->pix.sizeimage > vb->size) { + /* + * The image size has been changed to + * a larger size since this buffer was + * allocated, so we need to free and + * reallocate it. + */ + omap24xxcam_vbq_release(vbq, vb); + vb->size = fh->pix.sizeimage; + } + } else { + /* We need to allocate a new kernel bounce buffer. */ + vb->size = fh->pix.sizeimage; + } + } + + if (err) + return err; + + vb->width = fh->pix.width; + vb->height = fh->pix.height; + vb->field = field; + + if (vb->state == VIDEOBUF_NEEDS_INIT) { + if (vb->memory == V4L2_MEMORY_MMAP) + /* + * we have built the scatter-gather list by ourself so + * do the scatter-gather mapping as well + */ + err = omap24xxcam_dma_iolock(vbq, videobuf_to_dma(vb)); + else + err = videobuf_iolock(vbq, vb, NULL); + } + + if (!err) + vb->state = VIDEOBUF_PREPARED; + else + omap24xxcam_vbq_release(vbq, vb); + + return err; +} + +static void omap24xxcam_vbq_queue(struct videobuf_queue *vbq, + struct videobuf_buffer *vb) +{ + struct omap24xxcam_fh *fh = vbq->priv_data; + struct omap24xxcam_device *cam = fh->cam; + enum videobuf_state state = vb->state; + unsigned long flags; + int err; + + /* + * FIXME: We're marking the buffer active since we have no + * pretty way of marking it active exactly when the + * scatter-gather transfer starts. + */ + vb->state = VIDEOBUF_ACTIVE; + + err = omap24xxcam_sgdma_queue(&fh->cam->sgdma, + videobuf_to_dma(vb)->sglist, + videobuf_to_dma(vb)->sglen, vb->size, + omap24xxcam_vbq_complete, vb); + + if (!err) { + spin_lock_irqsave(&cam->core_enable_disable_lock, flags); + if (++cam->sgdma_in_queue == 1 + && !atomic_read(&cam->in_reset)) + omap24xxcam_core_enable(cam); + spin_unlock_irqrestore(&cam->core_enable_disable_lock, flags); + } else { + /* + * Oops. We're not supposed to get any errors here. + * The only way we could get an error is if we ran out + * of scatter-gather DMA slots, but we are supposed to + * have at least as many scatter-gather DMA slots as + * video buffers so that can't happen. + */ + dev_err(cam->dev, "failed to queue a video buffer for dma!\n"); + dev_err(cam->dev, "likely a bug in the driver!\n"); + vb->state = state; + } +} + +static struct videobuf_queue_ops omap24xxcam_vbq_ops = { + .buf_setup = omap24xxcam_vbq_setup, + .buf_prepare = omap24xxcam_vbq_prepare, + .buf_queue = omap24xxcam_vbq_queue, + .buf_release = omap24xxcam_vbq_release, +}; + +/* + * + * OMAP main camera system + * + */ + +/* + * Reset camera block to power-on state. + */ +static void omap24xxcam_poweron_reset(const struct omap24xxcam_device *cam) +{ + int max_loop = RESET_TIMEOUT_NS; + + /* Reset whole camera subsystem */ + omap24xxcam_reg_out(cam->mmio_base, + CAM_SYSCONFIG, + CAM_SYSCONFIG_SOFTRESET); + + /* Wait till it's finished */ + while (!(omap24xxcam_reg_in(cam->mmio_base, CAM_SYSSTATUS) + & CAM_SYSSTATUS_RESETDONE) + && --max_loop) { + ndelay(1); + } + + if (!(omap24xxcam_reg_in(cam->mmio_base, CAM_SYSSTATUS) + & CAM_SYSSTATUS_RESETDONE)) + dev_err(cam->dev, "camera soft reset timeout\n"); +} + +/* + * (Re)initialise the camera block. + */ +static void omap24xxcam_hwinit(const struct omap24xxcam_device *cam) +{ + omap24xxcam_poweron_reset(cam); + + /* set the camera subsystem autoidle bit */ + omap24xxcam_reg_out(cam->mmio_base, CAM_SYSCONFIG, + CAM_SYSCONFIG_AUTOIDLE); + + /* set the camera MMU autoidle bit */ + omap24xxcam_reg_out(cam->mmio_base, + CAMMMU_REG_OFFSET + CAMMMU_SYSCONFIG, + CAMMMU_SYSCONFIG_AUTOIDLE); + + omap24xxcam_core_hwinit(cam); + + omap24xxcam_dma_hwinit(&cam->sgdma.dma); +} + +/* + * Callback for dma transfer stalling. + */ +static void omap24xxcam_stalled_dma_reset(unsigned long data) +{ + struct omap24xxcam_device *cam = (struct omap24xxcam_device *)data; + + if (!atomic_read(&cam->in_reset)) { + dev_dbg(cam->dev, "dma stalled, resetting camera\n"); + omap24xxcam_reset(cam); + } +} + +/* + * Stop capture. Mark we're doing a reset, stop DMA transfers and + * core. (No new scatter-gather transfers will be queued whilst + * in_reset is non-zero.) + * + * If omap24xxcam_capture_stop is called from several places at + * once, only the first call will have an effect. Similarly, the last + * call omap24xxcam_streaming_cont will have effect. + * + * Serialisation is ensured by using cam->core_enable_disable_lock. + */ +static void omap24xxcam_capture_stop(struct omap24xxcam_device *cam) +{ + unsigned long flags; + + spin_lock_irqsave(&cam->core_enable_disable_lock, flags); + + if (atomic_inc_return(&cam->in_reset) != 1) { + spin_unlock_irqrestore(&cam->core_enable_disable_lock, flags); + return; + } + + omap24xxcam_core_disable(cam); + + spin_unlock_irqrestore(&cam->core_enable_disable_lock, flags); + + omap24xxcam_sgdma_sync(&cam->sgdma); +} + +/* + * Reset and continue streaming. + * + * Note: Resetting the camera FIFO via the CC_RST bit in the CC_CTRL + * register is supposed to be sufficient to recover from a camera + * interface error, but it doesn't seem to be enough. If we only do + * that then subsequent image captures are out of sync by either one + * or two times DMA_THRESHOLD bytes. Resetting and re-initializing the + * entire camera subsystem prevents the problem with frame + * synchronization. + */ +static void omap24xxcam_capture_cont(struct omap24xxcam_device *cam) +{ + unsigned long flags; + + spin_lock_irqsave(&cam->core_enable_disable_lock, flags); + + if (atomic_read(&cam->in_reset) != 1) + goto out; + + omap24xxcam_hwinit(cam); + + omap24xxcam_sensor_if_enable(cam); + + omap24xxcam_sgdma_process(&cam->sgdma); + + if (cam->sgdma_in_queue) + omap24xxcam_core_enable(cam); + +out: + atomic_dec(&cam->in_reset); + spin_unlock_irqrestore(&cam->core_enable_disable_lock, flags); +} + +static ssize_t +omap24xxcam_streaming_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct omap24xxcam_device *cam = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", cam->streaming ? "active" : "inactive"); +} +static DEVICE_ATTR(streaming, S_IRUGO, omap24xxcam_streaming_show, NULL); + +/* + * Stop capture and restart it. I.e. reset the camera during use. + */ +static void omap24xxcam_reset(struct omap24xxcam_device *cam) +{ + omap24xxcam_capture_stop(cam); + omap24xxcam_capture_cont(cam); +} + +/* + * The main interrupt handler. + */ +static irqreturn_t omap24xxcam_isr(int irq, void *arg) +{ + struct omap24xxcam_device *cam = (struct omap24xxcam_device *)arg; + u32 irqstatus; + unsigned int irqhandled = 0; + + irqstatus = omap24xxcam_reg_in(cam->mmio_base, CAM_IRQSTATUS); + + if (irqstatus & + (CAM_IRQSTATUS_DMA_IRQ2 | CAM_IRQSTATUS_DMA_IRQ1 + | CAM_IRQSTATUS_DMA_IRQ0)) { + omap24xxcam_dma_isr(&cam->sgdma.dma); + irqhandled = 1; + } + if (irqstatus & CAM_IRQSTATUS_CC_IRQ) { + omap24xxcam_core_isr(cam); + irqhandled = 1; + } + if (irqstatus & CAM_IRQSTATUS_MMU_IRQ) + dev_err(cam->dev, "unhandled camera MMU interrupt!\n"); + + return IRQ_RETVAL(irqhandled); +} + +/* + * + * Sensor handling. + * + */ + +/* + * Enable the external sensor interface. Try to negotiate interface + * parameters with the sensor and start using the new ones. The calls + * to sensor_if_enable and sensor_if_disable need not to be balanced. + */ +static int omap24xxcam_sensor_if_enable(struct omap24xxcam_device *cam) +{ + int rval; + struct v4l2_ifparm p; + + rval = vidioc_int_g_ifparm(cam->sdev, &p); + if (rval) { + dev_err(cam->dev, "vidioc_int_g_ifparm failed with %d\n", rval); + return rval; + } + + cam->if_type = p.if_type; + + cam->cc_ctrl = CC_CTRL_CC_EN; + + switch (p.if_type) { + case V4L2_IF_TYPE_BT656: + if (p.u.bt656.frame_start_on_rising_vs) + cam->cc_ctrl |= CC_CTRL_NOBT_SYNCHRO; + if (p.u.bt656.bt_sync_correct) + cam->cc_ctrl |= CC_CTRL_BT_CORRECT; + if (p.u.bt656.swap) + cam->cc_ctrl |= CC_CTRL_PAR_ORDERCAM; + if (p.u.bt656.latch_clk_inv) + cam->cc_ctrl |= CC_CTRL_PAR_CLK_POL; + if (p.u.bt656.nobt_hs_inv) + cam->cc_ctrl |= CC_CTRL_NOBT_HS_POL; + if (p.u.bt656.nobt_vs_inv) + cam->cc_ctrl |= CC_CTRL_NOBT_VS_POL; + + switch (p.u.bt656.mode) { + case V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT: + cam->cc_ctrl |= CC_CTRL_PAR_MODE_NOBT8; + break; + case V4L2_IF_TYPE_BT656_MODE_NOBT_10BIT: + cam->cc_ctrl |= CC_CTRL_PAR_MODE_NOBT10; + break; + case V4L2_IF_TYPE_BT656_MODE_NOBT_12BIT: + cam->cc_ctrl |= CC_CTRL_PAR_MODE_NOBT12; + break; + case V4L2_IF_TYPE_BT656_MODE_BT_8BIT: + cam->cc_ctrl |= CC_CTRL_PAR_MODE_BT8; + break; + case V4L2_IF_TYPE_BT656_MODE_BT_10BIT: + cam->cc_ctrl |= CC_CTRL_PAR_MODE_BT10; + break; + default: + dev_err(cam->dev, + "bt656 interface mode %d not supported\n", + p.u.bt656.mode); + return -EINVAL; + } + /* + * The clock rate that the sensor wants has changed. + * We have to adjust the xclk from OMAP 2 side to + * match the sensor's wish as closely as possible. + */ + if (p.u.bt656.clock_curr != cam->if_u.bt656.xclk) { + u32 xclk = p.u.bt656.clock_curr; + u32 divisor; + + if (xclk == 0) + return -EINVAL; + + if (xclk > CAM_MCLK) + xclk = CAM_MCLK; + + divisor = CAM_MCLK / xclk; + if (divisor * xclk < CAM_MCLK) + divisor++; + if (CAM_MCLK / divisor < p.u.bt656.clock_min + && divisor > 1) + divisor--; + if (divisor > 30) + divisor = 30; + + xclk = CAM_MCLK / divisor; + + if (xclk < p.u.bt656.clock_min + || xclk > p.u.bt656.clock_max) + return -EINVAL; + + cam->if_u.bt656.xclk = xclk; + } + omap24xxcam_core_xclk_set(cam, cam->if_u.bt656.xclk); + break; + default: + /* FIXME: how about other interfaces? */ + dev_err(cam->dev, "interface type %d not supported\n", + p.if_type); + return -EINVAL; + } + + return 0; +} + +static void omap24xxcam_sensor_if_disable(const struct omap24xxcam_device *cam) +{ + switch (cam->if_type) { + case V4L2_IF_TYPE_BT656: + omap24xxcam_core_xclk_set(cam, 0); + break; + } +} + +/* + * Initialise the sensor hardware. + */ +static int omap24xxcam_sensor_init(struct omap24xxcam_device *cam) +{ + int err = 0; + struct v4l2_int_device *sdev = cam->sdev; + + omap24xxcam_clock_on(cam); + err = omap24xxcam_sensor_if_enable(cam); + if (err) { + dev_err(cam->dev, "sensor interface could not be enabled at " + "initialisation, %d\n", err); + cam->sdev = NULL; + goto out; + } + + /* power up sensor during sensor initialization */ + vidioc_int_s_power(sdev, 1); + + err = vidioc_int_dev_init(sdev); + if (err) { + dev_err(cam->dev, "cannot initialize sensor, error %d\n", err); + /* Sensor init failed --- it's nonexistent to us! */ + cam->sdev = NULL; + goto out; + } + + dev_info(cam->dev, "sensor is %s\n", sdev->name); + +out: + omap24xxcam_sensor_if_disable(cam); + omap24xxcam_clock_off(cam); + + vidioc_int_s_power(sdev, 0); + + return err; +} + +static void omap24xxcam_sensor_exit(struct omap24xxcam_device *cam) +{ + if (cam->sdev) + vidioc_int_dev_exit(cam->sdev); +} + +static void omap24xxcam_sensor_disable(struct omap24xxcam_device *cam) +{ + omap24xxcam_sensor_if_disable(cam); + omap24xxcam_clock_off(cam); + vidioc_int_s_power(cam->sdev, 0); +} + +/* + * Power-up and configure camera sensor. It's ready for capturing now. + */ +static int omap24xxcam_sensor_enable(struct omap24xxcam_device *cam) +{ + int rval; + + omap24xxcam_clock_on(cam); + + omap24xxcam_sensor_if_enable(cam); + + rval = vidioc_int_s_power(cam->sdev, 1); + if (rval) + goto out; + + rval = vidioc_int_init(cam->sdev); + if (rval) + goto out; + + return 0; + +out: + omap24xxcam_sensor_disable(cam); + + return rval; +} + +static void omap24xxcam_sensor_reset_work(struct work_struct *work) +{ + struct omap24xxcam_device *cam = + container_of(work, struct omap24xxcam_device, + sensor_reset_work); + + if (atomic_read(&cam->reset_disable)) + return; + + omap24xxcam_capture_stop(cam); + + if (vidioc_int_reset(cam->sdev) == 0) { + vidioc_int_init(cam->sdev); + } else { + /* Can't reset it by vidioc_int_reset. */ + omap24xxcam_sensor_disable(cam); + omap24xxcam_sensor_enable(cam); + } + + omap24xxcam_capture_cont(cam); +} + +/* + * + * IOCTL interface. + * + */ + +static int vidioc_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + + strlcpy(cap->driver, CAM_NAME, sizeof(cap->driver)); + strlcpy(cap->card, cam->vfd->name, sizeof(cap->card)); + cap->version = OMAP24XXCAM_VERSION; + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + + return 0; +} + +static int vidioc_enum_fmt_cap(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + int rval; + + rval = vidioc_int_enum_fmt_cap(cam->sdev, f); + + return rval; +} + +static int vidioc_g_fmt_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + int rval; + + mutex_lock(&cam->mutex); + rval = vidioc_int_g_fmt_cap(cam->sdev, f); + mutex_unlock(&cam->mutex); + + return rval; +} + +static int vidioc_s_fmt_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + int rval; + + mutex_lock(&cam->mutex); + if (cam->streaming) { + rval = -EBUSY; + goto out; + } + + rval = vidioc_int_s_fmt_cap(cam->sdev, f); + +out: + mutex_unlock(&cam->mutex); + + if (!rval) { + mutex_lock(&ofh->vbq.vb_lock); + ofh->pix = f->fmt.pix; + mutex_unlock(&ofh->vbq.vb_lock); + } + + memset(f, 0, sizeof(*f)); + vidioc_g_fmt_cap(file, fh, f); + + return rval; +} + +static int vidioc_try_fmt_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + int rval; + + mutex_lock(&cam->mutex); + rval = vidioc_int_try_fmt_cap(cam->sdev, f); + mutex_unlock(&cam->mutex); + + return rval; +} + +static int vidioc_reqbufs(struct file *file, void *fh, + struct v4l2_requestbuffers *b) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + int rval; + + mutex_lock(&cam->mutex); + if (cam->streaming) { + mutex_unlock(&cam->mutex); + return -EBUSY; + } + + omap24xxcam_vbq_free_mmap_buffers(&ofh->vbq); + mutex_unlock(&cam->mutex); + + rval = videobuf_reqbufs(&ofh->vbq, b); + + /* + * Either videobuf_reqbufs failed or the buffers are not + * memory-mapped (which would need special attention). + */ + if (rval < 0 || b->memory != V4L2_MEMORY_MMAP) + goto out; + + rval = omap24xxcam_vbq_alloc_mmap_buffers(&ofh->vbq, rval); + if (rval) + omap24xxcam_vbq_free_mmap_buffers(&ofh->vbq); + +out: + return rval; +} + +static int vidioc_querybuf(struct file *file, void *fh, + struct v4l2_buffer *b) +{ + struct omap24xxcam_fh *ofh = fh; + + return videobuf_querybuf(&ofh->vbq, b); +} + +static int vidioc_qbuf(struct file *file, void *fh, struct v4l2_buffer *b) +{ + struct omap24xxcam_fh *ofh = fh; + + return videobuf_qbuf(&ofh->vbq, b); +} + +static int vidioc_dqbuf(struct file *file, void *fh, struct v4l2_buffer *b) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + struct videobuf_buffer *vb; + int rval; + +videobuf_dqbuf_again: + rval = videobuf_dqbuf(&ofh->vbq, b, file->f_flags & O_NONBLOCK); + if (rval) + goto out; + + vb = ofh->vbq.bufs[b->index]; + + mutex_lock(&cam->mutex); + /* _needs_reset returns -EIO if reset is required. */ + rval = vidioc_int_g_needs_reset(cam->sdev, (void *)vb->baddr); + mutex_unlock(&cam->mutex); + if (rval == -EIO) + schedule_work(&cam->sensor_reset_work); + else + rval = 0; + +out: + /* + * This is a hack. We don't want to show -EIO to the user + * space. Requeue the buffer and try again if we're not doing + * this in non-blocking mode. + */ + if (rval == -EIO) { + videobuf_qbuf(&ofh->vbq, b); + if (!(file->f_flags & O_NONBLOCK)) + goto videobuf_dqbuf_again; + /* + * We don't have a videobuf_buffer now --- maybe next + * time... + */ + rval = -EAGAIN; + } + + return rval; +} + +static int vidioc_streamon(struct file *file, void *fh, enum v4l2_buf_type i) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + int rval; + + mutex_lock(&cam->mutex); + if (cam->streaming) { + rval = -EBUSY; + goto out; + } + + rval = omap24xxcam_sensor_if_enable(cam); + if (rval) { + dev_dbg(cam->dev, "vidioc_int_g_ifparm failed\n"); + goto out; + } + + rval = videobuf_streamon(&ofh->vbq); + if (!rval) { + cam->streaming = file; + sysfs_notify(&cam->dev->kobj, NULL, "streaming"); + } + +out: + mutex_unlock(&cam->mutex); + + return rval; +} + +static int vidioc_streamoff(struct file *file, void *fh, enum v4l2_buf_type i) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + struct videobuf_queue *q = &ofh->vbq; + int rval; + + atomic_inc(&cam->reset_disable); + + flush_scheduled_work(); + + rval = videobuf_streamoff(q); + if (!rval) { + mutex_lock(&cam->mutex); + cam->streaming = NULL; + mutex_unlock(&cam->mutex); + sysfs_notify(&cam->dev->kobj, NULL, "streaming"); + } + + atomic_dec(&cam->reset_disable); + + return rval; +} + +static int vidioc_enum_input(struct file *file, void *fh, + struct v4l2_input *inp) +{ + if (inp->index > 0) + return -EINVAL; + + strlcpy(inp->name, "camera", sizeof(inp->name)); + inp->type = V4L2_INPUT_TYPE_CAMERA; + + return 0; +} + +static int vidioc_g_input(struct file *file, void *fh, unsigned int *i) +{ + *i = 0; + + return 0; +} + +static int vidioc_s_input(struct file *file, void *fh, unsigned int i) +{ + if (i > 0) + return -EINVAL; + + return 0; +} + +static int vidioc_queryctrl(struct file *file, void *fh, + struct v4l2_queryctrl *a) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + int rval; + + rval = vidioc_int_queryctrl(cam->sdev, a); + + return rval; +} + +static int vidioc_g_ctrl(struct file *file, void *fh, + struct v4l2_control *a) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + int rval; + + mutex_lock(&cam->mutex); + rval = vidioc_int_g_ctrl(cam->sdev, a); + mutex_unlock(&cam->mutex); + + return rval; +} + +static int vidioc_s_ctrl(struct file *file, void *fh, + struct v4l2_control *a) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + int rval; + + mutex_lock(&cam->mutex); + rval = vidioc_int_s_ctrl(cam->sdev, a); + mutex_unlock(&cam->mutex); + + return rval; +} + +static int vidioc_g_parm(struct file *file, void *fh, + struct v4l2_streamparm *a) { + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + int rval; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + mutex_lock(&cam->mutex); + rval = vidioc_int_g_parm(cam->sdev, a); + mutex_unlock(&cam->mutex); + + return rval; +} + +static int vidioc_s_parm(struct file *file, void *fh, + struct v4l2_streamparm *a) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + struct v4l2_streamparm old_streamparm; + int rval; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + mutex_lock(&cam->mutex); + if (cam->streaming) { + rval = -EBUSY; + goto out; + } + + old_streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + rval = vidioc_int_g_parm(cam->sdev, &old_streamparm); + if (rval) + goto out; + + rval = vidioc_int_s_parm(cam->sdev, a); + if (rval) + goto out; + + rval = omap24xxcam_sensor_if_enable(cam); + /* + * Revert to old streaming parameters if enabling sensor + * interface with the new ones failed. + */ + if (rval) + vidioc_int_s_parm(cam->sdev, &old_streamparm); + +out: + mutex_unlock(&cam->mutex); + + return rval; +} + +/* + * + * File operations. + * + */ + +static unsigned int omap24xxcam_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct omap24xxcam_fh *fh = file->private_data; + struct omap24xxcam_device *cam = fh->cam; + struct videobuf_buffer *vb; + + mutex_lock(&cam->mutex); + if (cam->streaming != file) { + mutex_unlock(&cam->mutex); + return POLLERR; + } + mutex_unlock(&cam->mutex); + + mutex_lock(&fh->vbq.vb_lock); + if (list_empty(&fh->vbq.stream)) { + mutex_unlock(&fh->vbq.vb_lock); + return POLLERR; + } + vb = list_entry(fh->vbq.stream.next, struct videobuf_buffer, stream); + mutex_unlock(&fh->vbq.vb_lock); + + poll_wait(file, &vb->done, wait); + + if (vb->state == VIDEOBUF_DONE || vb->state == VIDEOBUF_ERROR) + return POLLIN | POLLRDNORM; + + return 0; +} + +static int omap24xxcam_mmap_buffers(struct file *file, + struct vm_area_struct *vma) +{ + struct omap24xxcam_fh *fh = file->private_data; + struct omap24xxcam_device *cam = fh->cam; + struct videobuf_queue *vbq = &fh->vbq; + unsigned int first, last, size, i, j; + int err = 0; + + mutex_lock(&cam->mutex); + if (cam->streaming) { + mutex_unlock(&cam->mutex); + return -EBUSY; + } + mutex_unlock(&cam->mutex); + mutex_lock(&vbq->vb_lock); + + /* look for first buffer to map */ + for (first = 0; first < VIDEO_MAX_FRAME; first++) { + if (NULL == vbq->bufs[first]) + continue; + if (V4L2_MEMORY_MMAP != vbq->bufs[first]->memory) + continue; + if (vbq->bufs[first]->boff == (vma->vm_pgoff << PAGE_SHIFT)) + break; + } + + /* look for last buffer to map */ + for (size = 0, last = first; last < VIDEO_MAX_FRAME; last++) { + if (NULL == vbq->bufs[last]) + continue; + if (V4L2_MEMORY_MMAP != vbq->bufs[last]->memory) + continue; + size += vbq->bufs[last]->bsize; + if (size == (vma->vm_end - vma->vm_start)) + break; + } + + size = 0; + for (i = first; i <= last; i++) { + struct videobuf_dmabuf *dma = videobuf_to_dma(vbq->bufs[i]); + + for (j = 0; j < dma->sglen; j++) { + err = remap_pfn_range( + vma, vma->vm_start + size, + page_to_pfn(sg_page(&dma->sglist[j])), + sg_dma_len(&dma->sglist[j]), vma->vm_page_prot); + if (err) + goto out; + size += sg_dma_len(&dma->sglist[j]); + } + } + +out: + mutex_unlock(&vbq->vb_lock); + + return err; +} + +static int omap24xxcam_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct omap24xxcam_fh *fh = file->private_data; + int rval; + + /* let the video-buf mapper check arguments and set-up structures */ + rval = videobuf_mmap_mapper(&fh->vbq, vma); + if (rval) + return rval; + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + /* do mapping to our allocated buffers */ + rval = omap24xxcam_mmap_buffers(file, vma); + /* + * In case of error, free vma->vm_private_data allocated by + * videobuf_mmap_mapper. + */ + if (rval) + kfree(vma->vm_private_data); + + return rval; +} + +static int omap24xxcam_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct omap24xxcam_device *cam = omap24xxcam.priv; + struct omap24xxcam_fh *fh; + struct v4l2_format format; + + if (!cam || !cam->vfd || (cam->vfd->minor != minor)) + return -ENODEV; + + fh = kzalloc(sizeof(*fh), GFP_KERNEL); + if (fh == NULL) + return -ENOMEM; + + mutex_lock(&cam->mutex); + if (cam->sdev == NULL || !try_module_get(cam->sdev->module)) { + mutex_unlock(&cam->mutex); + goto out_try_module_get; + } + + if (atomic_inc_return(&cam->users) == 1) { + omap24xxcam_hwinit(cam); + if (omap24xxcam_sensor_enable(cam)) { + mutex_unlock(&cam->mutex); + goto out_omap24xxcam_sensor_enable; + } + } + mutex_unlock(&cam->mutex); + + fh->cam = cam; + mutex_lock(&cam->mutex); + vidioc_int_g_fmt_cap(cam->sdev, &format); + mutex_unlock(&cam->mutex); + /* FIXME: how about fh->pix when there are more users? */ + fh->pix = format.fmt.pix; + + file->private_data = fh; + + spin_lock_init(&fh->vbq_lock); + + videobuf_queue_sg_init(&fh->vbq, &omap24xxcam_vbq_ops, NULL, + &fh->vbq_lock, V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_NONE, + sizeof(struct videobuf_buffer), fh); + + return 0; + +out_omap24xxcam_sensor_enable: + omap24xxcam_poweron_reset(cam); + module_put(cam->sdev->module); + +out_try_module_get: + kfree(fh); + + return -ENODEV; +} + +static int omap24xxcam_release(struct inode *inode, struct file *file) +{ + struct omap24xxcam_fh *fh = file->private_data; + struct omap24xxcam_device *cam = fh->cam; + + atomic_inc(&cam->reset_disable); + + flush_scheduled_work(); + + /* stop streaming capture */ + videobuf_streamoff(&fh->vbq); + + mutex_lock(&cam->mutex); + if (cam->streaming == file) { + cam->streaming = NULL; + mutex_unlock(&cam->mutex); + sysfs_notify(&cam->dev->kobj, NULL, "streaming"); + } else { + mutex_unlock(&cam->mutex); + } + + atomic_dec(&cam->reset_disable); + + omap24xxcam_vbq_free_mmap_buffers(&fh->vbq); + + /* + * Make sure the reset work we might have scheduled is not + * pending! It may be run *only* if we have users. (And it may + * not be scheduled anymore since streaming is already + * disabled.) + */ + flush_scheduled_work(); + + mutex_lock(&cam->mutex); + if (atomic_dec_return(&cam->users) == 0) { + omap24xxcam_sensor_disable(cam); + omap24xxcam_poweron_reset(cam); + } + mutex_unlock(&cam->mutex); + + file->private_data = NULL; + + module_put(cam->sdev->module); + kfree(fh); + + return 0; +} + +static struct file_operations omap24xxcam_fops = { - .owner = THIS_MODULE, + .llseek = no_llseek, + .ioctl = video_ioctl2, + .poll = omap24xxcam_poll, + .mmap = omap24xxcam_mmap, + .open = omap24xxcam_open, + .release = omap24xxcam_release, +}; + +/* + * + * Power management. + * + */ + +#ifdef CONFIG_PM +static int omap24xxcam_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct omap24xxcam_device *cam = platform_get_drvdata(pdev); + + if (atomic_read(&cam->users) == 0) + return 0; + + if (!atomic_read(&cam->reset_disable)) + omap24xxcam_capture_stop(cam); + + omap24xxcam_sensor_disable(cam); + omap24xxcam_poweron_reset(cam); + + return 0; +} + +static int omap24xxcam_resume(struct platform_device *pdev) +{ + struct omap24xxcam_device *cam = platform_get_drvdata(pdev); + + if (atomic_read(&cam->users) == 0) + return 0; + + omap24xxcam_hwinit(cam); + omap24xxcam_sensor_enable(cam); + + if (!atomic_read(&cam->reset_disable)) + omap24xxcam_capture_cont(cam); + + return 0; +} +#endif /* CONFIG_PM */ + +/* + * + * Camera device (i.e. /dev/video). + * + */ + +static int omap24xxcam_device_register(struct v4l2_int_device *s) +{ + struct omap24xxcam_device *cam = s->u.slave->master->priv; + struct video_device *vfd; + int rval; + + /* We already have a slave. */ + if (cam->sdev) + return -EBUSY; + + cam->sdev = s; + + if (device_create_file(cam->dev, &dev_attr_streaming) != 0) { + dev_err(cam->dev, "could not register sysfs entry\n"); + rval = -EBUSY; + goto err; + } + + /* initialize the video_device struct */ + vfd = cam->vfd = video_device_alloc(); + if (!vfd) { + dev_err(cam->dev, "could not allocate video device struct\n"); + rval = -ENOMEM; + goto err; + } + vfd->release = video_device_release; + + vfd->dev = cam->dev; + + strlcpy(vfd->name, CAM_NAME, sizeof(vfd->name)); + vfd->type = VID_TYPE_CAPTURE | VID_TYPE_CHROMAKEY; + vfd->fops = &omap24xxcam_fops; + vfd->priv = cam; + vfd->minor = -1; + + vfd->vidioc_querycap = vidioc_querycap; + vfd->vidioc_enum_fmt_cap = vidioc_enum_fmt_cap; + vfd->vidioc_g_fmt_cap = vidioc_g_fmt_cap; + vfd->vidioc_s_fmt_cap = vidioc_s_fmt_cap; + vfd->vidioc_try_fmt_cap = vidioc_try_fmt_cap; + vfd->vidioc_reqbufs = vidioc_reqbufs; + vfd->vidioc_querybuf = vidioc_querybuf; + vfd->vidioc_qbuf = vidioc_qbuf; + vfd->vidioc_dqbuf = vidioc_dqbuf; + vfd->vidioc_streamon = vidioc_streamon; + vfd->vidioc_streamoff = vidioc_streamoff; + vfd->vidioc_enum_input = vidioc_enum_input; + vfd->vidioc_g_input = vidioc_g_input; + vfd->vidioc_s_input = vidioc_s_input; + vfd->vidioc_queryctrl = vidioc_queryctrl; + vfd->vidioc_g_ctrl = vidioc_g_ctrl; + vfd->vidioc_s_ctrl = vidioc_s_ctrl; + vfd->vidioc_g_parm = vidioc_g_parm; + vfd->vidioc_s_parm = vidioc_s_parm; + + omap24xxcam_hwinit(cam); + + rval = omap24xxcam_sensor_init(cam); + if (rval) + goto err; + + if (video_register_device(vfd, VFL_TYPE_GRABBER, video_nr) < 0) { + dev_err(cam->dev, "could not register V4L device\n"); + vfd->minor = -1; + rval = -EBUSY; + goto err; + } + + omap24xxcam_poweron_reset(cam); + + dev_info(cam->dev, "registered device video%d\n", vfd->minor); + + return 0; + +err: + omap24xxcam_device_unregister(s); + + return rval; +} + +static void omap24xxcam_device_unregister(struct v4l2_int_device *s) +{ + struct omap24xxcam_device *cam = s->u.slave->master->priv; + + omap24xxcam_sensor_exit(cam); + + if (cam->vfd) { + if (cam->vfd->minor == -1) { + /* + * The device was never registered, so release the + * video_device struct directly. + */ + video_device_release(cam->vfd); + } else { + /* + * The unregister function will release the + * video_device struct as well as + * unregistering it. + */ + video_unregister_device(cam->vfd); + } + cam->vfd = NULL; + } + + device_remove_file(cam->dev, &dev_attr_streaming); + + cam->sdev = NULL; +} + +static struct v4l2_int_master omap24xxcam_master = { + .attach = omap24xxcam_device_register, + .detach = omap24xxcam_device_unregister, +}; + +static struct v4l2_int_device omap24xxcam = { + .module = THIS_MODULE, + .name = CAM_NAME, + .type = v4l2_int_type_master, + .u = { + .master = &omap24xxcam_master + }, +}; + +/* + * + * Driver initialisation and deinitialisation. + * + */ + +static int __init omap24xxcam_probe(struct platform_device *pdev) +{ + struct omap24xxcam_device *cam; + struct resource *mem; + int irq; + + cam = kzalloc(sizeof(*cam), GFP_KERNEL); + if (!cam) { + dev_err(&pdev->dev, "could not allocate memory\n"); + goto err; + } + + platform_set_drvdata(pdev, cam); + + cam->dev = &pdev->dev; + + /* + * Impose a lower limit on the amount of memory allocated for + * capture. We require at least enough memory to double-buffer + * QVGA (300KB). + */ + if (capture_mem < 320 * 240 * 2 * 2) + capture_mem = 320 * 240 * 2 * 2; + cam->capture_mem = capture_mem; + + /* request the mem region for the camera registers */ + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(cam->dev, "no mem resource?\n"); + goto err; + } + if (!request_mem_region(mem->start, (mem->end - mem->start) + 1, + pdev->name)) { + dev_err(cam->dev, + "cannot reserve camera register I/O region\n"); + goto err; + } + cam->mmio_base_phys = mem->start; + cam->mmio_size = (mem->end - mem->start) + 1; + + /* map the region */ + cam->mmio_base = (unsigned long) + ioremap_nocache(cam->mmio_base_phys, cam->mmio_size); + if (!cam->mmio_base) { + dev_err(cam->dev, "cannot map camera register I/O region\n"); + goto err; + } + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) { + dev_err(cam->dev, "no irq for camera?\n"); + goto err; + } + + /* install the interrupt service routine */ + if (request_irq(irq, omap24xxcam_isr, 0, CAM_NAME, cam)) { + dev_err(cam->dev, + "could not install interrupt service routine\n"); + goto err; + } + cam->irq = irq; + + if (omap24xxcam_clock_get(cam)) + goto err; + + INIT_WORK(&cam->sensor_reset_work, omap24xxcam_sensor_reset_work); + + mutex_init(&cam->mutex); + spin_lock_init(&cam->core_enable_disable_lock); + + omap24xxcam_sgdma_init(&cam->sgdma, + cam->mmio_base + CAMDMA_REG_OFFSET, + omap24xxcam_stalled_dma_reset, + (unsigned long)cam); + + omap24xxcam.priv = cam; + + if (v4l2_int_device_register(&omap24xxcam)) + goto err; + + return 0; + +err: + omap24xxcam_remove(pdev); + return -ENODEV; +} + +static int omap24xxcam_remove(struct platform_device *pdev) +{ + struct omap24xxcam_device *cam = platform_get_drvdata(pdev); + + if (!cam) + return 0; + + if (omap24xxcam.priv != NULL) + v4l2_int_device_unregister(&omap24xxcam); + omap24xxcam.priv = NULL; + + omap24xxcam_clock_put(cam); + + if (cam->irq) { + free_irq(cam->irq, cam); + cam->irq = 0; + } + + if (cam->mmio_base) { + iounmap((void *)cam->mmio_base); + cam->mmio_base = 0; + } + + if (cam->mmio_base_phys) { + release_mem_region(cam->mmio_base_phys, cam->mmio_size); + cam->mmio_base_phys = 0; + } + + kfree(cam); + + return 0; +} + +static struct platform_driver omap24xxcam_driver = { + .probe = omap24xxcam_probe, + .remove = omap24xxcam_remove, +#ifdef CONFIG_PM + .suspend = omap24xxcam_suspend, + .resume = omap24xxcam_resume, +#endif + .driver = { + .name = CAM_NAME, + }, +}; + +/* + * + * Module initialisation and deinitialisation + * + */ + +static int __init omap24xxcam_init(void) +{ + return platform_driver_register(&omap24xxcam_driver); +} + +static void __exit omap24xxcam_cleanup(void) +{ + platform_driver_unregister(&omap24xxcam_driver); +} + +MODULE_AUTHOR("Sakari Ailus "); +MODULE_DESCRIPTION("OMAP24xx Video for Linux camera driver"); +MODULE_LICENSE("GPL"); +module_param(video_nr, int, 0); +MODULE_PARM_DESC(video_nr, + "Minor number for video device (-1 ==> auto assign)"); +module_param(capture_mem, int, 0); +MODULE_PARM_DESC(capture_mem, "Maximum amount of memory for capture " + "buffers (default 4800kiB)"); + +module_init(omap24xxcam_init); +module_exit(omap24xxcam_cleanup); diff --cc drivers/misc/Makefile index 2649528f06d,c6c13f60b45..b6167e79df7 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@@ -5,11 -5,11 +5,12 @@@ obj- := misc.o # Dummy rule to force bu obj-$(CONFIG_IBM_ASM) += ibmasm/ obj-$(CONFIG_HDPU_FEATURES) += hdpuftrs/ +obj-$(CONFIG_OMAP_STI) += sti/ - obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o - obj-$(CONFIG_ACER_WMI) += acer-wmi.o obj-$(CONFIG_ASUS_LAPTOP) += asus-laptop.o obj-$(CONFIG_EEEPC_LAPTOP) += eeepc-laptop.o + obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o + obj-$(CONFIG_COMPAL_LAPTOP) += compal-laptop.o + obj-$(CONFIG_ACER_WMI) += acer-wmi.o obj-$(CONFIG_ATMEL_PWM) += atmel_pwm.o obj-$(CONFIG_ATMEL_SSC) += atmel-ssc.o obj-$(CONFIG_ATMEL_TCLIB) += atmel_tclib.o diff --cc drivers/mmc/host/Makefile index d4d72f56ab7,db52eebfb50..6a10b43583b --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@@ -14,8 -15,10 +15,11 @@@ obj-$(CONFIG_MMC_RICOH_MMC) += ricoh_mm obj-$(CONFIG_MMC_WBSD) += wbsd.o obj-$(CONFIG_MMC_AU1X) += au1xmmc.o obj-$(CONFIG_MMC_OMAP) += omap.o +obj-$(CONFIG_MMC_OMAP_HS) += omap_hsmmc.o obj-$(CONFIG_MMC_AT91) += at91_mci.o + obj-$(CONFIG_MMC_ATMELMCI) += atmel-mci.o obj-$(CONFIG_MMC_TIFM_SD) += tifm_sd.o obj-$(CONFIG_MMC_SPI) += mmc_spi.o + obj-$(CONFIG_MMC_S3C) += s3cmci.o + obj-$(CONFIG_MMC_SDRICOH_CS) += sdricoh_cs.o diff --cc drivers/mtd/nand/Makefile index c8be0dad517,d772581de57..521cf078c6f --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@@ -24,10 -23,7 +23,10 @@@ obj-$(CONFIG_MTD_NAND_TS7250) += ts725 obj-$(CONFIG_MTD_NAND_NANDSIM) += nandsim.o obj-$(CONFIG_MTD_NAND_CS553X) += cs553x_nand.o obj-$(CONFIG_MTD_NAND_NDFC) += ndfc.o + obj-$(CONFIG_MTD_NAND_ATMEL) += atmel_nand.o +obj-$(CONFIG_MTD_NAND_OMAP) += omap-nand-flash.o +obj-$(CONFIG_MTD_NAND_OMAP2) += omap2.o +obj-$(CONFIG_MTD_NAND_OMAP_HW) += omap-hw.o - obj-$(CONFIG_MTD_NAND_AT91) += at91_nand.o obj-$(CONFIG_MTD_NAND_CM_X270) += cmx270_nand.o obj-$(CONFIG_MTD_NAND_BASLER_EXCITE) += excite_nandflash.o obj-$(CONFIG_MTD_NAND_PXA3xx) += pxa3xx_nand.o diff --cc drivers/mtd/onenand/omap2.c index 43060bee500,00000000000..f279d896eed 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 + +#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(dma_dst)) { ++ 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(dma_dst)) { ++ 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(dma_dst)) { ++ 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(dma_dst)) { ++ 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/Kconfig index ecda0b6eafd,8a03875ec87..fef138b4a63 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@@ -955,7 -959,7 +959,7 @@@ config SMC911 tristate "SMSC LAN911[5678] support" select CRC32 select MII - depends on ARCH_PXA || SH_MAGIC_PANEL_R2 || ARCH_OMAP24XX || ARCH_OMAP34XX - depends on ARCH_PXA || SUPERH ++ depends on ARCH_PXA || SUPERH || SH_MAGIC_PANEL_R2 || ARCH_OMAP24XX || ARCH_OMAP34XX help This is a driver for SMSC's LAN911x series of Ethernet chipsets including the new LAN9115, LAN9116, LAN9117, and LAN9118. diff --cc drivers/net/smc911x.h index 0f406a0d111,76c17c28fab..07e7af89fa0 --- a/drivers/net/smc911x.h +++ b/drivers/net/smc911x.h @@@ -41,19 -42,65 +42,75 @@@ #define SMC_USE_16BIT 0 #define SMC_USE_32BIT 1 #define SMC_IRQ_SENSE IRQF_TRIGGER_LOW +#elif defined(CONFIG_ARCH_OMAP34XX) + #define SMC_USE_16BIT 0 + #define SMC_USE_32BIT 1 + #define SMC_IRQ_SENSE IRQF_TRIGGER_LOW + #define SMC_MEM_RESERVED 1 +#elif defined(CONFIG_ARCH_OMAP24XX) + #define SMC_USE_16BIT 0 + #define SMC_USE_32BIT 1 + #define SMC_IRQ_SENSE IRQF_TRIGGER_LOW + #define SMC_MEM_RESERVED 1 + #else + /* + * Default configuration + */ + + #define SMC_DYNAMIC_BUS_CONFIG #endif + /* store this information for the driver.. */ + struct smc911x_local { + /* + * If I have to wait until the DMA is finished and ready to reload a + * packet, I will store the skbuff here. Then, the DMA will send it + * out and free it. + */ + struct sk_buff *pending_tx_skb; + + /* version/revision of the SMC911x chip */ + u16 version; + u16 revision; + + /* FIFO sizes */ + int tx_fifo_kb; + int tx_fifo_size; + int rx_fifo_size; + int afc_cfg; + + /* Contains the current active receive/phy mode */ + int ctl_rfduplx; + int ctl_rspeed; + + u32 msg_enable; + u32 phy_type; + struct mii_if_info mii; + + /* work queue */ + struct work_struct phy_configure; + + int tx_throttle; + spinlock_t lock; + + struct net_device *netdev; + + #ifdef SMC_USE_DMA + /* DMA needs the physical address of the chip */ + u_long physaddr; + int rxdma; + int txdma; + int rxdma_active; + int txdma_active; + struct sk_buff *current_rx_skb; + struct sk_buff *current_tx_skb; + struct device *dev; + #endif + void __iomem *base; + #ifdef SMC_DYNAMIC_BUS_CONFIG + struct smc911x_platdata cfg; + #endif + }; /* * Define the bus width specific IO macros diff --cc drivers/power/Kconfig index 80f45cd3f9f,9ce55850271..7bd8c2b4157 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@@ -49,33 -49,17 +49,46 @@@ config BATTERY_OLP help Say Y to enable support for the battery on the OLPC laptop. +config BATTERY_BQ27x00 + tristate "BQ27x00 battery driver" + help + Say Y here to enable support for batteries with BQ27000 or BQ27200 chip. + +config BATTERY_BQ27000 + bool "BQ27000 battery driver" + depends on BATTERY_BQ27x00 + select W1 + select W1_SLAVE_BQ27000 + help + Say Y here to enable support for batteries with BQ27000(HDQ) chip. + +config BATTERY_BQ27200 + bool "BQ27200 battery driver" + depends on BATTERY_BQ27x00 + select I2C + select I2C_OMAP + help + Say Y here to enable support for batteries with BQ27200(I2C) chip. + +config TWL4030_BCI_BATTERY + tristate "OMAP TWL4030 BCI Battery driver" + depends on (MACH_OMAP_2430SDP || MACH_OMAP_3430SDP) && TWL4030_MADC + default y + help + Support for OMAP TWL4030 BCI Battery driver. + This driver can give support for TWL4030 Battery Charge Interface. + + config BATTERY_TOSA + tristate "Sharp SL-6000 (tosa) battery" + depends on MACH_TOSA && MFD_TC6393XB + help + Say Y to enable support for the battery on the Sharp Zaurus + SL-6000 (tosa) models. + + config BATTERY_PALMTX + tristate "Palm T|X battery" + depends on MACH_PALMTX + help + Say Y to enable support for the battery in Palm T|X. + endif # POWER_SUPPLY diff --cc drivers/power/Makefile index a877c9077ad,4706bf8ff45..8da941a8090 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@@ -20,5 -20,5 +20,7 @@@ obj-$(CONFIG_APM_POWER) += apm_power. obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o +obj-$(CONFIG_BATTERY_BQ27x00) += bq27x00_battery.o +obj-$(CONFIG_TWL4030_BCI_BATTERY) += twl4030_bci_battery.o + obj-$(CONFIG_BATTERY_TOSA) += tosa_battery.o + obj-$(CONFIG_BATTERY_PALMTX) += palmtx_battery.o diff --cc drivers/spi/Kconfig index 5b26a7c50b1,b9d0efb6803..c04029a8598 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@@ -220,65 -225,9 +225,65 @@@ config SPI_AT2 This driver can also be built as a module. If so, the module will be called at25. +config SPI_TSC2101 + depends on SPI_MASTER + tristate "TSC2101 chip support" + ---help--- + Say Y here if you want support for the TSC2101 chip. + At the moment it provides basic register read / write interface + as well as a way to enable the MCLK clock. + +config SPI_TSC2102 + depends on SPI_MASTER + tristate "TSC2102 codec support" + ---help--- + Say Y here if you want support for the TSC2102 chip. It + will be needed for the touchscreen driver on some boards. + +config SPI_TSC210X + depends on SPI_MASTER && EXPERIMENTAL + tristate "TI TSC210x (TSC2101/TSC2102) support" + help + Say Y here if you want support for the TSC210x chips. Some + boards use these for touchscreen and audio support. + + These are members of a family of highly integrated PDA analog + interface circuit. They include a 12-bit ADC used for battery, + temperature, touchscreen, and other sensors. They also have + an audio DAC and amplifier, and in some models an audio ADC. + The audio support is highly chip-specific, but most of the + sensor support works the same. + + Note that the device has to be present in the board's SPI + devices table for this driver to load. This driver doesn't + automatically enable touchscreen, sensors or audio + functionality - enable these in their respective menus. + +config SPI_TSC2301 + tristate "TSC2301 driver" + depends on SPI_MASTER + help + Say Y here if you have a TSC2301 chip connected to an SPI + bus on your board. + + The TSC2301 is a highly integrated PDA analog interface circuit. + It contains a complete 12-bit A/D resistive touch screen + converter (ADC) including drivers, touch pressure measurement + capability, keypad controller, and 8-bit D/A converter (DAC) output + for LCD contrast control. + + To compile this driver as a module, choose M here: the + module will be called tsc2301. + +config SPI_TSC2301_AUDIO + boolean "TSC2301 audio support" + depends on SPI_TSC2301 && SND + help + Say Y here for if you are using the audio features of TSC2301. + config SPI_SPIDEV tristate "User mode SPI device driver support" - depends on SPI_MASTER && EXPERIMENTAL + depends on EXPERIMENTAL help This supports user mode SPI protocol drivers. diff --cc include/linux/connector.h index fd722f3cb6b,5c7f9468f75..4c3cb6d77de --- a/include/linux/connector.h +++ b/include/linux/connector.h @@@ -36,12 -36,11 +36,13 @@@ #define CN_VAL_CIFS 0x1 #define CN_W1_IDX 0x3 /* w1 communication */ #define CN_W1_VAL 0x1 +#define CN_IDX_SX1SND 0x4 +#define CN_VAL_SX1SND 0x1 #define CN_IDX_V86D 0x4 #define CN_VAL_V86D_UVESAFB 0x1 + #define CN_IDX_BB 0x5 /* BlackBoard, from the TSP GPL sampling framework */ - #define CN_NETLINK_USERS 5 + #define CN_NETLINK_USERS 6 /* * Maximum connector's message size. diff --cc net/ipv4/netfilter/Kconfig index 05952173b57,90eb7cb47e7..37e44705ff9 --- a/net/ipv4/netfilter/Kconfig +++ b/net/ipv4/netfilter/Kconfig @@@ -213,22 -213,8 +213,21 @@@ config IP_NF_TARGET_NETMA help NETMAP is an implementation of static 1:1 NAT mapping of network addresses. It maps the network address part, while keeping the host - address part intact. It is similar to Fast NAT, except that - Netfilter's connection tracking doesn't work well with Fast NAT. + address part intact. + To compile it as a module, choose M here. If unsure, say N. + +config IP_NF_TARGET_IDLETIMER + tristate "IDLETIMER target support" + depends on IP_NF_IPTABLES + help + This option adds a `IDLETIMER' target. Each matching packet resets + the timer associated with input and/or output interfaces. Timer + expiry causes kobject uevent. Idle timer can be read via sysfs. + + To compile it as a module, choose M here. If unsure, say N. + + To compile it as a module, choose M here. If unsure, say N. config NF_NAT_SNMP_BASIC diff --cc security/Makefile index 7111cf1e0d6,f65426099aa..22f84562058 --- a/security/Makefile +++ b/security/Makefile @@@ -6,17 -6,13 +6,14 @@@ obj-$(CONFIG_KEYS) += keys subdir-$(CONFIG_SECURITY_SELINUX) += selinux subdir-$(CONFIG_SECURITY_SMACK) += smack - # if we don't select a security model, use the default capabilities - ifneq ($(CONFIG_SECURITY),y) + # always enable default capabilities obj-y += commoncap.o - endif # Object file lists - obj-$(CONFIG_SECURITY) += security.o dummy.o inode.o + obj-$(CONFIG_SECURITY) += security.o capability.o inode.o # Must precede capability.o in order to stack properly. obj-$(CONFIG_SECURITY_SELINUX) += selinux/built-in.o - obj-$(CONFIG_SECURITY_SMACK) += commoncap.o smack/built-in.o - obj-$(CONFIG_SECURITY_CAPABILITIES) += commoncap.o capability.o - obj-$(CONFIG_SECURITY_ROOTPLUG) += commoncap.o root_plug.o + obj-$(CONFIG_SECURITY_SMACK) += smack/built-in.o + obj-$(CONFIG_SECURITY_ROOTPLUG) += root_plug.o +obj-$(CONFIG_SECURITY_LOWMEM) += commoncap.o lowmem.o obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o diff --cc sound/arm/Kconfig index c685a9cd9ae,351e19ea378..06a24df059f --- a/sound/arm/Kconfig +++ b/sound/arm/Kconfig @@@ -33,68 -41,5 +41,68 @@@ config SND_PXA2XX_AC9 Say Y or M if you want to support any AC97 codec attached to the PXA2xx AC97 interface. -endif # SND_ARM +config SND_OMAP_AIC23 + tristate "OMAP AIC23 alsa driver (osk5912)" + depends on ARCH_OMAP && SND + select SND_PCM + select I2C + select I2C_OMAP if ARCH_OMAP + select SENSORS_TLV320AIC23 + help + Say Y here if you have a OSK platform board + and want to use its AIC23 audio chip. + + To compile this driver as a module, choose M here: the module + will be called snd-omap-aic23. + +config SND_OMAP_TSC2101 + tristate "OMAP TSC2101 alsa driver" + depends on ARCH_OMAP && SND + select SND_PCM + select SPI_TSC2101 + help + Say Y here if you have a OMAP platform board + and want to use its TSC2101 audio chip. Driver has + been tested with H2 and iPAQ h6300. + + To compile this driver as a module, choose M here: the module + will be called snd-omap-tsc2101. + +config SND_SX1 + tristate "Siemens SX1 Egold alsa driver" + depends on ARCH_OMAP && SND + select SND_PCM + help + Say Y here if you have a OMAP310 based Siemens SX1. + + To compile this driver as a module, choose M here: the module + will be called snd-omap-sx1. + +config SND_OMAP_TSC2102 + tristate "OMAP TSC2102 alsa driver" + depends on ARCH_OMAP && SND + select SND_PCM + select SPI_TSC2102 + help + Say Y here if you have an OMAP platform board + and want to use its TSC2102 audio chip. + To compile this driver as a module, choose M here: the module + will be called snd-omap-tsc2102. + +config SND_OMAP24XX_EAC + tristate "Audio driver for OMAP24xx EAC" + depends on SND + help + Audio driver for Enhanced Audio Controller found in TI's OMAP24xx + processors. + + Currently contains only low-level support functions for + initializing EAC HW, creating ALSA sound card instance for it + and registering mixer controls implemented by a codec driver. + PCM stream is expected to be under DSP co-processor control. + + To compile this driver as a module, choose M here: the module + will be called snd-omap24xx-eac. + - endmenu ++endif # SND_ARM