From: Toshihiro Kobayashi Date: Mon, 9 May 2005 20:16:48 +0000 (-0700) Subject: ARM: OMAP: Add DSP gateway X-Git-Tag: v2.6.13-omap1~181 X-Git-Url: http://pilppa.com/gitweb/?a=commitdiff_plain;h=091726595413932049664d4b472b4fd60fb1913c;p=linux-2.6-omap-h63xx.git ARM: OMAP: Add DSP gateway Adds support for integrated DSP on OMAP processors. Signed-off-by: Toshihiro Kobayashi Signed-off-by: Tony Lindgren --- diff --git a/arch/arm/mach-omap/Makefile b/arch/arm/mach-omap/Makefile index bcbbfb1fe47..503345f2ee3 100644 --- a/arch/arm/mach-omap/Makefile +++ b/arch/arm/mach-omap/Makefile @@ -21,3 +21,6 @@ obj-$(CONFIG_CPU_FREQ) += cpu-omap.o obj-$(CONFIG_OMAP_DM_TIMER) += dmtimer.o obj-$(CONFIG_OMAP_BOOT_REASON) += bootreason.o obj-$(CONFIG_OMAP_GPIO_SWITCH) += gpio-switch.o + +# DSP subsystem +obj-y += dsp/ diff --git a/arch/arm/mach-omap/dsp/Kconfig b/arch/arm/mach-omap/dsp/Kconfig new file mode 100644 index 00000000000..fc439089c92 --- /dev/null +++ b/arch/arm/mach-omap/dsp/Kconfig @@ -0,0 +1,29 @@ + +config OMAP_DSP + tristate "OMAP DSP driver (DSP Gateway)" + depends on ARCH_OMAP1510 || ARCH_OMAP16XX + help + This enables OMAP DSP driver, DSP Gateway. + +config OMAP_DSP_MBCMD_VERBOSE + bool "Mailbox Command Verbose LOG" + depends on OMAP_DSP + help + This enables kernel log output in the Mailbox command exchanges + in the DSP Gateway driver. + +config OMAP_DSP_TASK_MULTIOPEN + bool "DSP Task Multiopen Capability" + depends on OMAP_DSP + help + This enables DSP tasks to be opened by multiple times at a time. + Otherwise, they can be opened only once at a time. + +config OMAP_DSP_FBEXPORT + bool "Framebuffer export to DSP" + depends on OMAP_DSP + help + This enables to map the frame buffer to DSP. + By doing this, DSP can access the frame buffer directly without + bothering ARM. + diff --git a/arch/arm/mach-omap/dsp/Makefile b/arch/arm/mach-omap/dsp/Makefile new file mode 100644 index 00000000000..c7d86f358b5 --- /dev/null +++ b/arch/arm/mach-omap/dsp/Makefile @@ -0,0 +1,15 @@ +# +# Makefile for the OMAP DSP driver. +# + +# The target object and module list name. + +obj-y := dsp_common.o + +obj-$(CONFIG_OMAP_DSP) += dsp.o + +# Declare multi-part drivers + +dsp-objs := dsp_core.o ipbuf.o mblog.o task.o \ + dsp_ctl_core.o dsp_ctl.o taskwatch.o error.o dsp_mem.o \ + uaccess_dsp.o diff --git a/arch/arm/mach-omap/dsp/dsp.h b/arch/arm/mach-omap/dsp/dsp.h new file mode 100644 index 00000000000..6282a281503 --- /dev/null +++ b/arch/arm/mach-omap/dsp/dsp.h @@ -0,0 +1,184 @@ +/* + * linux/arch/arm/mach-omap/dsp/dsp.h + * + * Header for OMAP DSP driver + * + * Copyright (C) 2002-2005 Nokia Corporation + * + * Written by Toshihiro Kobayashi + * + * 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 + * + * 2005/01/21: DSP Gateway version 3.2 + */ + +#include "hardware_dsp.h" +#include "dsp_common.h" + +#define OLD_BINARY_SUPPORT y + +#ifdef OLD_BINARY_SUPPORT +#define MBREV_3_0 0x0017 +#endif + +#define DSP_INIT_PAGE 0xfff000 +/* idle program will be placed at IDLEPG_BASE. */ +#define IDLEPG_BASE 0xfffe00 +#define IDLEPG_SIZE 0x100 + +/* + * INT_D2A_MB value definition + * INT_DSP_MAILBOX1: use Mailbox 1 (INT 10) for DSP->ARM mailbox + * INT_DSP_MAILBOX2: use Mailbox 2 (INT 11) for DSP->ARM mailbox + */ +#define INT_D2A_MB1 INT_DSP_MAILBOX1 + +/* keep 2 entries for OMAP_DSP_TID_FREE and OMAP_DSP_TID_ANON */ +#define TASKDEV_MAX 254 + +#define MKLONG(uw,lw) (((unsigned long)(uw)) << 16 | (lw)) +#define MBCMD(nm) OMAP_DSP_MBCMD_##nm + +/* struct mbcmd and struct mbcmd_hw must be compatible */ +struct mbcmd { + unsigned short cmd_l:8; + unsigned short cmd_h:7; + unsigned short seq:1; + unsigned short data; +}; + +struct mbcmd_hw { + unsigned short cmd; + unsigned short data; +}; + +#define mbcmd_set(mb, h, l, d) \ + do { \ + (mb).cmd_h = (h); \ + (mb).cmd_l = (l); \ + (mb).data = (d); \ + } while(0) + +struct mb_exarg { + unsigned char tid; + int argc; + unsigned short *argv; +}; + +extern void dsp_mb_start(void); +extern void dsp_mb_stop(void); +extern void dsp_mb_config(void *sync_seq_adr); +extern int sync_with_dsp(unsigned short *syncwd, unsigned short tid, + int try_cnt); +extern int __mbsend(struct mbcmd *mb); +extern int __dsp_mbsend(struct mbcmd *mb, struct mb_exarg *arg, + int recovery_flag); +#define dsp_mbsend(mb) __dsp_mbsend(mb, NULL, 0) +#define dsp_mbsend_recovery(mb) __dsp_mbsend(mb, NULL, 1) +#define dsp_mbsend_exarg(mb, arg) __dsp_mbsend(mb, arg, 0) +extern int __dsp_mbsend_and_wait(struct mbcmd *mb, struct mb_exarg *arg, + wait_queue_head_t *q); +#define dsp_mbsend_and_wait(mb, q) \ + __dsp_mbsend_and_wait(mb, NULL, q) +#define dsp_mbsend_and_wait_exarg(mb, arg, q) \ + __dsp_mbsend_and_wait(mb, arg, q) + +extern void ipbuf_start(void); +extern void ipbuf_stop(void); +extern int ipbuf_config(unsigned short ln, unsigned short lsz, + unsigned long adr); +extern unsigned short get_free_ipbuf(unsigned char tid); +extern void unuse_ipbuf_nowait(unsigned short bid); +extern void unuse_ipbuf(unsigned short bid); +extern void release_ipbuf(unsigned short bid); +extern void balance_ipbuf(void); + +#define release_ipbuf_pvt(ipbuf_pvt) \ + do { \ + (ipbuf_pvt)->s = OMAP_DSP_TID_FREE; \ + } while(0) + +extern int dsp_is_ready(void); +extern int dspuncfg(void); +extern void dsp_runlevel(unsigned char level); +extern int dsp_suspend(void); +extern int dsp_resume(void); + +extern int dsp_task_config_all(unsigned char n); +extern void dsp_task_unconfig_all(void); +extern unsigned char dsp_task_count(void); +extern int dsp_taskmod_busy(void); +extern int dsp_mkdev(char *name); +extern int dsp_rmdev(char *name); +extern int dsp_tadd(unsigned char minor, unsigned long adr); +extern int dsp_tdel(unsigned char minor); +extern int dsp_tkill(unsigned char minor); +extern long taskdev_state(unsigned char minor); + +extern int ipbuf_is_held(unsigned char tid, unsigned short bid); + +extern int dsp_mem_enable(void *adr); +extern int dsp_mem_disable(void *adr); +extern int __dsp_mem_enable(void *adr); +extern int __dsp_mem_disable(void *adr); +extern unsigned long dsp_virt_to_phys(void *vadr, size_t *len); +extern void dsp_mem_start(void); + +extern void dsp_twch_start(void); +extern void dsp_twch_stop(void); +extern void dsp_twch_touch(void); + +extern void dsp_err_start(void); +extern void dsp_err_stop(void); +extern void dsp_err_mmu_set(unsigned long adr); +extern void dsp_err_mmu_clear(void); +extern int dsp_err_mmu_isset(void); +extern void dsp_err_wdt_clear(void); +extern int dsp_err_wdt_isset(void); + +enum cmd_l_type { + CMD_L_TYPE_NULL, + CMD_L_TYPE_TID, + CMD_L_TYPE_SUBCMD, +}; + +struct cmdinfo { + char *name; + enum cmd_l_type cmd_l_type; + void (*handler)(struct mbcmd *mb); +}; + +extern const struct cmdinfo *cmdinfo[]; + +#define cmd_name(mb) (cmdinfo[(mb).cmd_h]->name) +extern char *subcmd_name(struct mbcmd *mb); + +enum mblog_dir { + MBLOG_DIR_AD, + MBLOG_DIR_DA, +}; + +extern void mblog_add(struct mbcmd *mb, enum mblog_dir dir); +#ifdef CONFIG_OMAP_DSP_MBCMD_VERBOSE +extern void mblog_printcmd(struct mbcmd *mb, enum mblog_dir dir); +#else /* CONFIG_OMAP_DSP_MBCMD_VERBOSE */ +#define mblog_printcmd(mb, dir) do {} while(0) +#endif /* CONFIG_OMAP_DSP_MBCMD_VERBOSE */ + +#ifdef CONFIG_PROC_FS +extern struct proc_dir_entry *procdir_dsp; +#endif /* CONFIG_PROC_FS */ + +extern struct platform_device dsp_device; diff --git a/arch/arm/mach-omap/dsp/dsp_common.c b/arch/arm/mach-omap/dsp/dsp_common.c new file mode 100644 index 00000000000..175cb987702 --- /dev/null +++ b/arch/arm/mach-omap/dsp/dsp_common.c @@ -0,0 +1,233 @@ +/* + * linux/arch/arm/mach-omap/dsp/dsp_common.c + * + * OMAP DSP driver static part + * + * Copyright (C) 2002-2005 Nokia Corporation + * + * Written by Toshihiro Kobayashi + * + * 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 + * + * 2004/11/19: DSP Gateway version 3.2 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dsp_common.h" + +struct clk *dsp_ck_handle; +struct clk *api_ck_handle; +unsigned long dspmem_base, dspmem_size; +int dsp_runstat = RUNSTAT_RESET; +unsigned short dsp_icrmask = DSPREG_ICR_EMIF_IDLE_DOMAIN | + DSPREG_ICR_DPLL_IDLE_DOMAIN | + DSPREG_ICR_PER_IDLE_DOMAIN | + DSPREG_ICR_CACHE_IDLE_DOMAIN | + DSPREG_ICR_DMA_IDLE_DOMAIN | + DSPREG_ICR_CPU_IDLE_DOMAIN; + +int dsp_set_rstvect(unsigned long adr) +{ + unsigned long *dst_adr; + + if (adr >= DSPSPACE_SIZE) + return -EINVAL; + + dst_adr = dspbyte_to_virt(DSP_BOOT_ADR_DIRECT); + /* word swap */ + *dst_adr = ((adr & 0xffff) << 16) | (adr >> 16); + /* fill 8 bytes! */ + *(dst_adr+1) = 0; + /* direct boot */ + omap_writew(MPUI_DSP_BOOT_CONFIG_DIRECT, MPUI_DSP_BOOT_CONFIG); + + return 0; +} + +static void simple_load_code(unsigned char *src_c, unsigned short *dst, int len) +{ + int i; + unsigned short *src = (unsigned short *)src_c; + int len_w; + + /* len must be multiple of 2. */ + if (len & 1) + BUG(); + + len_w = len / 2; + for (i = 0; i < len_w; i++) { + /* byte swap copy */ + *dst = ((*src & 0x00ff) << 8) | + ((*src & 0xff00) >> 8); + src++; + dst++; + } +} + +/* program size must be multiple of 2 */ +#define IDLE_TEXT_SIZE 28 +#define IDLE_TEXT(icr) { \ + /* disable WDT */ \ + 0x76, 0x34, 0x04, 0xb8, /* 0x763404b8: mov AR3 0x3404 */ \ + 0xfb, 0x61, 0x00, 0xf5, /* 0xfb6100f5: mov *AR3 0x00f5 */ \ + 0x9a, /* 0x9a: port */ \ + 0xfb, 0x61, 0x00, 0xa0, /* 0xfb6100a0: mov *AR3 0x00a0 */ \ + 0x9a, /* 0x9a: port */ \ + /* set ICR = icr */ \ + 0x3c, 0x1b, /* 0x3c1b: mov AR3 0x1 */ \ + 0xe6, 0x61, (icr), /* 0xe661**: mov *AR3, icr */ \ + 0x9a, /* 0x9a: port */ \ + /* idle and loop forever */ \ + 0x7a, 0x00, 0x00, 0x0c, /* 0x7a00000c: idle */ \ + 0x4a, 0x7a, /* 0x4a7a: b -6 (infinite loop) */ \ + 0x20, 0x20 /* 0x20: nop */ \ +} + +/* + * idle_boot base: + * Initialized with DSP_BOOT_ADR_MPUI (=0x010000). + * This value is used before DSP Gateway driver is initialized. + * DSP Gateway driver will overwrite this value with other value, + * to avoid confliction with the user program. + */ +static unsigned long idle_boot_base = DSP_BOOT_ADR_MPUI; + +void dsp_idle(void) +{ + unsigned char icr; + + disable_irq(INT_DSP_MMU); + preempt_disable(); + __dsp_reset(); + clk_use(api_ck_handle); + + /* + * icr settings: + * DMA should not sleep for DARAM/SARAM access + * DPLL should not sleep for DMA. + */ + icr = dsp_icrmask & + ~(DSPREG_ICR_DMA_IDLE_DOMAIN | DSPREG_ICR_DPLL_IDLE_DOMAIN) & + 0xff; + { + unsigned char idle_text[IDLE_TEXT_SIZE] = IDLE_TEXT(icr); + simple_load_code(idle_text, dspbyte_to_virt(idle_boot_base), + IDLE_TEXT_SIZE); + } + if (idle_boot_base == DSP_BOOT_ADR_MPUI) + omap_writew(MPUI_DSP_BOOT_CONFIG_MPUI, MPUI_DSP_BOOT_CONFIG); + else + dsp_set_rstvect(idle_boot_base); + clk_unuse(api_ck_handle); + udelay(10); /* to make things stable */ + __dsp_run(); + dsp_runstat = RUNSTAT_IDLE; + preempt_enable(); + enable_irq(INT_DSP_MMU); +} + +void dsp_set_idle_boot_base(unsigned long adr, size_t size) +{ + if (adr == idle_boot_base) + return; + idle_boot_base = adr; + if (size < IDLE_TEXT_SIZE) { + printk(KERN_ERR + "omapdsp: size for idle program is not enough!\n"); + BUG(); + } + if (dsp_runstat == RUNSTAT_IDLE) + dsp_idle(); +} + +static int init_done; + +static int __init omap_dsp_init(void) +{ + dspmem_size = 0; +#ifdef CONFIG_ARCH_OMAP1510 + if (cpu_is_omap1510()) { + dspmem_base = OMAP1510_DSP_BASE; + dspmem_size = OMAP1510_DSP_SIZE; + } +#endif +#ifdef CONFIG_ARCH_OMAP16XX + if (cpu_is_omap16xx()) { + dspmem_base = OMAP16XX_DSP_BASE; + dspmem_size = OMAP16XX_DSP_SIZE; + } +#endif + if (dspmem_size == 0) { + printk(KERN_ERR "omapdsp: unsupported omap architecture.\n"); + return -ENODEV; + } + + dsp_ck_handle = clk_get(0, "dsp_ck"); + if (IS_ERR(dsp_ck_handle)) { + printk(KERN_ERR "omapdsp: could not acquire dsp_ck handle.\n"); + return PTR_ERR(dsp_ck_handle); + } + + api_ck_handle = clk_get(0, "api_ck"); + if (IS_ERR(api_ck_handle)) { + printk(KERN_ERR "omapdsp: could not acquire api_ck handle.\n"); + return PTR_ERR(api_ck_handle); + } + + __dsp_enable(); + mpui_byteswap_off(); + mpui_wordswap_on(); + tc_wordswap(); + + init_done = 1; + return 0; +} + +void omap_dsp_request_idle(void) +{ + if (dsp_runstat == RUNSTAT_RESET) { + if (!init_done) + omap_dsp_init(); + dsp_idle(); + } +} + +arch_initcall(omap_dsp_init); + +EXPORT_SYMBOL(omap_dsp_request_idle); + +#ifdef CONFIG_OMAP_DSP_MODULE +EXPORT_SYMBOL(dsp_ck_handle); +EXPORT_SYMBOL(api_ck_handle); +EXPORT_SYMBOL(dspmem_base); +EXPORT_SYMBOL(dspmem_size); +EXPORT_SYMBOL(dsp_runstat); +EXPORT_SYMBOL(dsp_icrmask); +EXPORT_SYMBOL(dsp_set_rstvect); +EXPORT_SYMBOL(dsp_idle); +EXPORT_SYMBOL(dsp_set_idle_boot_base); + +EXPORT_SYMBOL(__cpu_flush_kern_tlb_range); +#endif diff --git a/arch/arm/mach-omap/dsp/dsp_common.h b/arch/arm/mach-omap/dsp/dsp_common.h new file mode 100644 index 00000000000..a4b2610a39d --- /dev/null +++ b/arch/arm/mach-omap/dsp/dsp_common.h @@ -0,0 +1,117 @@ +/* + * linux/arch/arm/mach-omap/dsp/dsp_common.h + * + * Header for OMAP DSP driver static part + * + * Copyright (C) 2002-2005 Nokia Corporation + * + * Written by Toshihiro Kobayashi + * + * 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 + * + * 2004/11/16: DSP Gateway version 3.2 + */ + +#include "hardware_dsp.h" + +#define DSPSPACE_SIZE 0x1000000 + +#define omap_set_bit_regw(b,r) \ + do { omap_writew(omap_readw(r) | (b), (r)); } while(0) +#define omap_clr_bit_regw(b,r) \ + do { omap_writew(omap_readw(r) & ~(b), (r)); } while(0) +#define omap_set_bit_regl(b,r) \ + do { omap_writel(omap_readl(r) | (b), (r)); } while(0) +#define omap_clr_bit_regl(b,r) \ + do { omap_writel(omap_readl(r) & ~(b), (r)); } while(0) + +#define dspword_to_virt(dw) ((void *)(dspmem_base + ((dw) << 1))) +#define dspbyte_to_virt(db) ((void *)(dspmem_base + (db))) +#define virt_to_dspword(va) (((unsigned long)(va) - dspmem_base) >> 1) +#define virt_to_dspbyte(va) ((unsigned long)(va) - dspmem_base) +#define is_dsp_internal_mem(va) \ + (((unsigned long)(va) >= dspmem_base) && \ + ((unsigned long)(va) < dspmem_base + dspmem_size)) +#define is_dspbyte_internal_mem(db) ((db) < dspmem_size) +#define is_dspword_internal_mem(dw) (((dw) << 1) < dspmem_size) + +/* + * MPUI byteswap/wordswap on/off + * default setting: wordswap = all, byteswap = APIMEM only + */ +#define mpui_wordswap_on() \ + { \ + omap_writel( \ + (omap_readl(MPUI_CTRL) & ~MPUI_CTRL_WORDSWAP_MASK) | \ + MPUI_CTRL_WORDSWAP_ALL, MPUI_CTRL); \ + } while(0) + +#define mpui_wordswap_off() \ + { \ + omap_writel( \ + (omap_readl(MPUI_CTRL) & ~MPUI_CTRL_WORDSWAP_MASK) | \ + MPUI_CTRL_WORDSWAP_NONE, MPUI_CTRL); \ + } while(0) + +#define mpui_byteswap_on() \ + { \ + omap_writel( \ + (omap_readl(MPUI_CTRL) & ~MPUI_CTRL_BYTESWAP_MASK) | \ + MPUI_CTRL_BYTESWAP_API, MPUI_CTRL); \ + } while(0) + +#define mpui_byteswap_off() \ + { \ + omap_writel( \ + (omap_readl(MPUI_CTRL) & ~MPUI_CTRL_BYTESWAP_MASK) | \ + MPUI_CTRL_BYTESWAP_NONE, MPUI_CTRL); \ + } while(0) + +/* + * TC wordswap on / off + */ +#define tc_wordswap() \ + { \ + omap_writel(TC_ENDIANISM_SWAP_WORD | TC_ENDIANISM_EN, \ + TC_ENDIANISM); \ + } while(0) + +#define tc_noswap() \ + { \ + omap_writel(omap_readl(TC_ENDIANISM) & ~TC_ENDIANISM_EN, \ + TC_ENDIANISM); \ + } while(0) + +/* + * enable priority registers, EMIF, MPUI control logic + */ +#define __dsp_enable() omap_set_bit_regw(ARM_RSTCT1_DSP_RST, ARM_RSTCT1) +#define __dsp_disable() omap_clr_bit_regw(ARM_RSTCT1_DSP_RST, ARM_RSTCT1) +#define __dsp_run() omap_set_bit_regw(ARM_RSTCT1_DSP_EN, ARM_RSTCT1) +#define __dsp_reset() omap_clr_bit_regw(ARM_RSTCT1_DSP_EN, ARM_RSTCT1) + +#define RUNSTAT_RESET 0 +#define RUNSTAT_IDLE 1 +#define RUNSTAT_RUN 2 + +extern struct clk *dsp_ck_handle; +extern struct clk *api_ck_handle; +extern unsigned long dspmem_base, dspmem_size; +extern int dsp_runstat; +extern unsigned short dsp_icrmask; + +int dsp_set_rstvect(unsigned long adr); +void dsp_idle(void); +void dsp_set_idle_boot_base(unsigned long adr, size_t size); diff --git a/arch/arm/mach-omap/dsp/dsp_core.c b/arch/arm/mach-omap/dsp/dsp_core.c new file mode 100644 index 00000000000..e86d3599a95 --- /dev/null +++ b/arch/arm/mach-omap/dsp/dsp_core.c @@ -0,0 +1,741 @@ +/* + * linux/arch/arm/mach-omap/dsp/dsp_core.c + * + * OMAP DSP driver + * + * Copyright (C) 2002-2005 Nokia Corporation + * + * Written by Toshihiro Kobayashi + * + * 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 + * + * 2005/02/15: DSP Gateway version 3.2 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "hardware_dsp.h" +#include "dsp.h" +#include "ipbuf.h" + + +MODULE_AUTHOR("Toshihiro Kobayashi "); +MODULE_DESCRIPTION("OMAP DSP driver module"); +MODULE_LICENSE("GPL"); + +enum mbseq_check_level { + MBSEQ_CHECK_NONE, /* no check */ + MBSEQ_CHECK_VERBOSE, /* discard the illegal command and + error report */ + MBSEQ_CHECK_SILENT, /* discard the illegal command */ +}; + +static enum mbseq_check_level mbseq_check_level = MBSEQ_CHECK_VERBOSE; +static unsigned short mbseq_send; +static unsigned short mbseq_expect; + +struct sync_seq { + unsigned short da_dsp; + unsigned short da_arm; + unsigned short ad_dsp; + unsigned short ad_arm; +}; + +static struct sync_seq *sync_seq; + +/* + * mailbox commands + */ +extern void mbx1_wdsnd(struct mbcmd *mb); +extern void mbx1_wdreq(struct mbcmd *mb); +extern void mbx1_bksnd(struct mbcmd *mb); +extern void mbx1_bkreq(struct mbcmd *mb); +extern void mbx1_bkyld(struct mbcmd *mb); +extern void mbx1_bksndp(struct mbcmd *mb); +extern void mbx1_bkreqp(struct mbcmd *mb); +extern void mbx1_tctl(struct mbcmd *mb); +extern void mbx1_wdt(struct mbcmd *mb); +extern void mbx1_suspend(struct mbcmd *mb); +static void mbx1_kfunc(struct mbcmd *mb); +extern void mbx1_tcfg(struct mbcmd *mb); +extern void mbx1_tadd(struct mbcmd *mb); +extern void mbx1_tdel(struct mbcmd *mb); +extern void mbx1_dspcfg(struct mbcmd *mb); +extern void mbx1_regrw(struct mbcmd *mb); +extern void mbx1_getvar(struct mbcmd *mb); +extern void mbx1_err(struct mbcmd *mb); +extern void mbx1_dbg(struct mbcmd *mb); + +static const struct cmdinfo + cif_null = { "Unknown", CMD_L_TYPE_NULL, NULL }, + cif_wdsnd = { "WDSND", CMD_L_TYPE_TID, mbx1_wdsnd }, + cif_wdreq = { "WDREQ", CMD_L_TYPE_TID, mbx1_wdreq }, + cif_bksnd = { "BKSND", CMD_L_TYPE_TID, mbx1_bksnd }, + cif_bkreq = { "BKREQ", CMD_L_TYPE_TID, mbx1_bkreq }, + cif_bkyld = { "BKYLD", CMD_L_TYPE_NULL, mbx1_bkyld }, + cif_bksndp = { "BKSNDP", CMD_L_TYPE_TID, mbx1_bksndp }, + cif_bkreqp = { "BKREQP", CMD_L_TYPE_TID, mbx1_bkreqp }, + cif_tctl = { "TCTL", CMD_L_TYPE_TID, mbx1_tctl }, + cif_wdt = { "WDT", CMD_L_TYPE_NULL, mbx1_wdt }, + cif_runlevel = { "RUNLEVEL", CMD_L_TYPE_SUBCMD, NULL }, + cif_pm = { "PM", CMD_L_TYPE_SUBCMD, NULL }, + cif_suspend = { "SUSPEND", CMD_L_TYPE_NULL, mbx1_suspend }, + cif_kfunc = { "KFUNC", CMD_L_TYPE_SUBCMD, mbx1_kfunc }, + cif_tcfg = { "TCFG", CMD_L_TYPE_TID, mbx1_tcfg }, + cif_tadd = { "TADD", CMD_L_TYPE_TID, mbx1_tadd }, + cif_tdel = { "TDEL", CMD_L_TYPE_TID, mbx1_tdel }, + cif_tstop = { "TSTOP", CMD_L_TYPE_TID, NULL }, + cif_dspcfg = { "DSPCFG", CMD_L_TYPE_SUBCMD, mbx1_dspcfg }, + cif_regrw = { "REGRW", CMD_L_TYPE_SUBCMD, mbx1_regrw }, + cif_getvar = { "GETVAR", CMD_L_TYPE_SUBCMD, mbx1_getvar }, + cif_setvar = { "SETVAR", CMD_L_TYPE_SUBCMD, NULL }, + cif_err = { "ERR", CMD_L_TYPE_SUBCMD, mbx1_err }, + cif_dbg = { "DBG", CMD_L_TYPE_NULL, mbx1_dbg }; + +const struct cmdinfo *cmdinfo[128] = { +/*00*/ &cif_null, &cif_null, &cif_null, &cif_null, + &cif_null, &cif_null, &cif_null, &cif_null, + &cif_null, &cif_null, &cif_null, &cif_null, + &cif_null, &cif_null, &cif_null, &cif_null, +/*10*/ &cif_wdsnd, &cif_wdreq, &cif_null, &cif_null, + &cif_null, &cif_null, &cif_null, &cif_null, + &cif_null, &cif_null, &cif_null, &cif_null, + &cif_null, &cif_null, &cif_null, &cif_null, +/*20*/ &cif_bksnd, &cif_bkreq, &cif_null, &cif_bkyld, + &cif_bksndp, &cif_bkreqp, &cif_null, &cif_null, + &cif_null, &cif_null, &cif_null, &cif_null, + &cif_null, &cif_null, &cif_null, &cif_null, +/*30*/ &cif_tctl, &cif_null, &cif_null, &cif_null, + &cif_null, &cif_null, &cif_null, &cif_null, + &cif_null, &cif_null, &cif_null, &cif_null, + &cif_null, &cif_null, &cif_null, &cif_null, +/*40*/ &cif_null, &cif_null, &cif_null, &cif_null, + &cif_null, &cif_null, &cif_null, &cif_null, + &cif_null, &cif_null, &cif_null, &cif_null, + &cif_null, &cif_null, &cif_null, &cif_null, +/*50*/ &cif_wdt, &cif_runlevel, &cif_pm, &cif_suspend, + &cif_kfunc, &cif_null, &cif_null, &cif_null, + &cif_null, &cif_null, &cif_null, &cif_null, + &cif_null, &cif_null, &cif_null, &cif_null, +/*60*/ &cif_tcfg, &cif_null, &cif_tadd, &cif_tdel, + &cif_null, &cif_tstop, &cif_null, &cif_null, + &cif_null, &cif_null, &cif_null, &cif_null, + &cif_null, &cif_null, &cif_null, &cif_null, +/*70*/ &cif_dspcfg, &cif_null, &cif_regrw, &cif_null, + &cif_getvar, &cif_setvar, &cif_null, &cif_null, + &cif_err, &cif_dbg, &cif_null, &cif_null, + &cif_null, &cif_null, &cif_null, &cif_null +}; + +int sync_with_dsp(unsigned short *syncwd, unsigned short tid, int try_cnt) +{ + int try; + + if (*(volatile unsigned short *)syncwd == tid) + return 0; + + for (try = 0; try < try_cnt; try++) { + udelay(1); + if (*(volatile unsigned short *)syncwd == tid) { + /* success! */ + printk(KERN_INFO + "omapdsp: sync_with_dsp(): try = %d\n", try); + return 0; + } + } + + /* fail! */ + return -1; +} + +static __inline__ int mbsync_irq_save(unsigned long *flags, int try_cnt) +{ + int cnt; + + local_irq_save(*flags); + if (omap_readw(MAILBOX_ARM2DSP1_Flag) == 0) + return 0; + /* + * mailbox is busy. wait for some usecs... + */ + local_irq_restore(*flags); + for (cnt = 0; cnt < try_cnt; cnt++) { + udelay(1); + local_irq_save(*flags); + if (omap_readw(MAILBOX_ARM2DSP1_Flag) == 0) /* success! */ + return 0; + local_irq_restore(*flags); + } + + /* fail! */ + return -1; +} + +#ifdef CONFIG_OMAP_DSP_MBCMD_VERBOSE +#define print_mb_busy_abort(mb) \ + printk(KERN_DEBUG \ + "mbx: mailbox is busy. %s is aborting.\n", cmd_name(*mb)) +#define print_mb_mmu_abort(mb) \ + printk(KERN_DEBUG \ + "mbx: mmu interrupt is set. %s is aborting.\n", cmd_name(*mb)) +#else /* CONFIG_OMAP_DSP_MBCMD_VERBOSE */ +#define print_mb_busy_abort(mb) do {} while(0) +#define print_mb_mmu_abort(mb) do {} while(0) +#endif /* !CONFIG_OMAP_DSP_MBCMD_VERBOSE */ + +int __mbsend(struct mbcmd *mb) +{ + struct mbcmd_hw *mb_hw = (struct mbcmd_hw *)mb; + unsigned long flags; + + /* + * DSP mailbox interrupt latency must be less than 1ms. + */ + if (mbsync_irq_save(&flags, 1000) < 0) { + print_mb_busy_abort(mb); + return -1; + } + + mb->seq = mbseq_send & 1; + mbseq_send++; + if (sync_seq) + sync_seq->ad_arm = mbseq_send; + mblog_add(mb, MBLOG_DIR_AD); + mblog_printcmd(mb, MBLOG_DIR_AD); + + omap_writew(mb_hw->data, MAILBOX_ARM2DSP1); + omap_writew(mb_hw->cmd, MAILBOX_ARM2DSP1b); + + local_irq_restore(flags); + return 0; +} + +/* + * __dsp_mbsend(): mailbox dispatcher + */ +int __dsp_mbsend(struct mbcmd *mb, struct mb_exarg *arg, int recovery_flag) +{ + static DECLARE_MUTEX(mbsend_sem); + int ret = 0; + + /* + * while MMU fault is set, + * only recovery command can be executed + */ + if (dsp_err_mmu_isset() && !recovery_flag) { + print_mb_mmu_abort(mb); + return -1; + } + + if (down_interruptible(&mbsend_sem) < 0) + return -1; + + if (arg) { /* we have extra argument */ + int i; + + if (__dsp_mem_enable(ipbuf_sys_ad) < 0) + goto out; + if (sync_with_dsp(&ipbuf_sys_ad->s, OMAP_DSP_TID_FREE, 10) < 0) { + printk(KERN_ERR "omapdsp: ipbuf_sys_ad is busy.\n"); + ret = -EBUSY; + goto out; + } + for (i = 0; i < arg->argc; i++) { + ipbuf_sys_ad->d[i] = arg->argv[i]; + } + ipbuf_sys_ad->s = arg->tid; + if (__dsp_mem_disable(ipbuf_sys_ad) < 0) + goto out; + } + + ret = __mbsend(mb); + +out: + up(&mbsend_sem); + return ret; +} + +int __dsp_mbsend_and_wait(struct mbcmd *mb, struct mb_exarg *arg, + wait_queue_head_t *q) +{ + long current_state; + DECLARE_WAITQUEUE(wait, current); + + add_wait_queue(q, &wait); + current_state = current->state; + set_current_state(TASK_INTERRUPTIBLE); + if (dsp_mbsend_exarg(mb, arg) < 0) { + set_current_state(current_state); + remove_wait_queue(q, &wait); + return -1; + } + schedule(); + set_current_state(current_state); + remove_wait_queue(q, &wait); + + return 0; +} + +void dsp_mb_start(void) +{ + mbseq_send = 0; + mbseq_expect = 0; +} + +void dsp_mb_stop(void) +{ + sync_seq = NULL; +} + +/* + * dsp_mb_config() is called from mbx1 workqueue + */ +void dsp_mb_config(void *sync_seq_adr) +{ + sync_seq = sync_seq_adr; + sync_seq->da_arm = mbseq_expect; +} + +/* + * mbq: mailbox queue + */ +#define MBQ_DEPTH 16 +struct mbq { + struct mbcmd mb[MBQ_DEPTH]; + int rp, wp, full; +} mbq = { + .rp = 0, + .wp = 0, +}; + +#define mbq_inc(p) do { if (++(p) == MBQ_DEPTH) (p) = 0; } while(0) + +/* + * workqueue for mbx1 + */ +static void do_mbx1(void) +{ + int empty = 0; + + disable_irq(INT_D2A_MB1); + if ((mbq.rp == mbq.wp) && !mbq.full) + empty = 1; + enable_irq(INT_D2A_MB1); + + while (!empty) { + struct mbcmd *mb; + + mb = &mbq.mb[mbq.rp]; + + mblog_add(mb, MBLOG_DIR_DA); + mblog_printcmd(mb, MBLOG_DIR_DA); + + /* + * call handler for each command + */ + if (cmdinfo[mb->cmd_h]->handler) + cmdinfo[mb->cmd_h]->handler(mb); + else if (cmdinfo[mb->cmd_h] != &cif_null) + printk(KERN_ERR "mbx: %s is not allowed from DSP.\n", + cmd_name(*mb)); + else + printk(KERN_ERR + "mbx: Unrecognized command: " + "cmd=0x%04x, data=0x%04x\n", + ((struct mbcmd_hw *)mb)->cmd & 0x7fff, mb->data); + + disable_irq(INT_D2A_MB1); + mbq_inc(mbq.rp); + if (mbq.rp == mbq.wp) + empty = 1; + /* if mbq has been full, now we have a room. */ + if (mbq.full) { + mbq.full = 0; + enable_irq(INT_D2A_MB1); + } + enable_irq(INT_D2A_MB1); + } +} + +static DECLARE_WORK(mbx1_work, (void (*)(void *))do_mbx1, NULL); + +/* + * kernel function dispatcher + */ +#ifdef CONFIG_FB_OMAP_EXTERNAL_LCDC +extern void mbx1_fbctl_disable(void); + +static void mbx1_kfunc_fbctl(unsigned short data) +{ + switch (data) { + case OMAP_DSP_MBCMD_FBCTL_DISABLE: + mbx1_fbctl_disable(); + break; + default: + printk(KERN_ERR + "mailbox: Unknown FBCTL from DSP: 0x%04x\n", data); + } +} +#endif + +static void mbx1_kfunc(struct mbcmd *mb) +{ + switch (mb->cmd_l) { +#ifdef CONFIG_FB_OMAP_EXTERNAL_LCDC + case OMAP_DSP_MBCMD_KFUNC_FBCTL: + mbx1_kfunc_fbctl(mb->data); + break; +#endif + + default: + printk(KERN_ERR + "mailbox: Unknown kfunc from DSP: 0x%02x\n", mb->cmd_l); + } +} + +/* + * mailbox interrupt handler + */ +static irqreturn_t mbx1_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + union { + struct mbcmd sw; + struct mbcmd_hw hw; + } *mb = (void *)&mbq.mb[mbq.wp]; + +#if (INT_D2A_MB1 == INT_DSP_MAILBOX1) + mb->hw.data = omap_readw(MAILBOX_DSP2ARM1); + mb->hw.cmd = omap_readw(MAILBOX_DSP2ARM1b); +#elif (INT_D2A_MB1 == INT_DSP_MAILBOX2) + mb->hw.data = omap_readw(MAILBOX_DSP2ARM2); + mb->hw.cmd = omap_readw(MAILBOX_DSP2ARM2b); +#endif + + if (mb->sw.seq != (mbseq_expect & 1)) { + switch (mbseq_check_level) { + case MBSEQ_CHECK_NONE: + break; + case MBSEQ_CHECK_VERBOSE: + printk(KERN_INFO + "mbx: illegal seq bit!!! ignoring this command." + " (%04x:%04x)\n", mb->hw.cmd, mb->hw.data); + return IRQ_HANDLED; + case MBSEQ_CHECK_SILENT: + return IRQ_HANDLED; + } + } + + mbseq_expect++; + if (sync_seq) + sync_seq->da_arm = mbseq_expect; + + mbq_inc(mbq.wp); + if (mbq.wp == mbq.rp) { /* mbq is full */ + mbq.full = 1; + disable_irq(INT_D2A_MB1); + } + schedule_work(&mbx1_work); + + return IRQ_HANDLED; +} + +static irqreturn_t mbx2_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + unsigned short cmd, data; + +#if (INT_D2A_MB1 == INT_DSP_MAILBOX1) + data = omap_readw(MAILBOX_DSP2ARM2); + cmd = omap_readw(MAILBOX_DSP2ARM2b); +#elif (INT_D2A_MB1 == INT_DSP_MAILBOX2) + data = omap_readw(MAILBOX_DSP2ARM1); + cmd = omap_readw(MAILBOX_DSP2ARM1b); +#endif + printk(KERN_DEBUG + "mailbox2 interrupt! cmd=%04x, data=%04x\n", cmd, data); + + return IRQ_HANDLED; +} + +#if 0 +static void mpuio_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + printk(KERN_INFO "MPUIO interrupt!\n"); +} +#endif + +#ifdef CONFIG_PROC_FS +struct proc_dir_entry *procdir_dsp = NULL; + +static void dsp_create_procdir_dsp(void) +{ + procdir_dsp = proc_mkdir("dsp", 0); + if (procdir_dsp == NULL) { + printk(KERN_ERR + "omapdsp: failed to register proc directory: dsp\n"); + } +} + +static void dsp_remove_procdir_dsp(void) +{ + procdir_dsp = NULL; + remove_proc_entry("dsp", 0); +} +#else /* CONFIG_PROC_FS */ +#define dsp_create_procdir_dsp() do { } while (0) +#define dsp_remove_procdir_dsp() do { } while (0) +#endif /* CONFIG_PROC_FS */ + +extern irqreturn_t dsp_mmu_interrupt(int irq, void *dev_id, + struct pt_regs *regs); + +extern int dsp_ctl_core_init(void); +extern void dsp_ctl_core_exit(void); +extern void dsp_ctl_init(void); +extern void dsp_ctl_exit(void); +extern int dsp_mem_init(void); +extern void dsp_mem_exit(void); +extern void mblog_init(void); +extern void mblog_exit(void); +extern int dsp_taskmod_init(void); +extern void dsp_taskmod_exit(void); + +/* + * device functions + */ +static void dsp_dev_release(struct device *dev) +{ +} + +/* + * driver functions + */ +#if (INT_D2A_MB1 == INT_DSP_MAILBOX1) +# define INT_D2A_MB2 INT_DSP_MAILBOX2 +#elif(INT_D2A_MB1 == INT_DSP_MAILBOX2) /* swap MB1 and MB2 */ +# define INT_D2A_MB2 INT_DSP_MAILBOX1 +#endif + +static int __init dsp_drv_probe(struct device *dev) +{ + int ret; + + printk(KERN_INFO "OMAP DSP driver initialization\n"); + + //__dsp_enable(); // XXX + + dsp_create_procdir_dsp(); + + if ((ret = dsp_ctl_core_init()) < 0) + goto fail1; + if ((ret = dsp_mem_init()) < 0) + goto fail2; + dsp_ctl_init(); + mblog_init(); + if ((ret = dsp_taskmod_init()) < 0) + goto fail3; + + /* + * mailbox interrupt handlers registration + */ + ret = request_irq(INT_D2A_MB1, mbx1_interrupt, SA_INTERRUPT, "dsp", + dev); + if (ret) { + printk(KERN_ERR + "failed to register mailbox1 interrupt: %d\n", ret); + goto fail4; + } + + ret = request_irq(INT_D2A_MB2, mbx2_interrupt, SA_INTERRUPT, "dsp", + dev); + if (ret) { + printk(KERN_ERR + "failed to register mailbox2 interrupt: %d\n", ret); + goto fail5; + } + + ret = request_irq(INT_DSP_MMU, dsp_mmu_interrupt, SA_INTERRUPT, "dsp", + dev); + if (ret) { + printk(KERN_ERR + "failed to register DSP MMU interrupt: %d\n", ret); + goto fail6; + } + +#if 0 + ret = request_irq(INT_MPUIO, mpuio_interrupt, SA_INTERRUPT, "dsp", dev); + if (ret) { + printk(KERN_ERR + "failed to register MPUIO interrupt: %d\n", ret); + goto fail7; + } +#endif + + return 0; + +fail6: + free_irq(INT_D2A_MB2, dev); +fail5: + free_irq(INT_D2A_MB1, dev); +fail4: + dsp_taskmod_exit(); +fail3: + mblog_exit(); + dsp_ctl_exit(); + dsp_mem_exit(); +fail2: + dsp_ctl_core_exit(); +fail1: + dsp_remove_procdir_dsp(); + + //__dsp_disable(); // XXX + return ret; +} + +static int dsp_drv_remove(struct device *dev) +{ + __dsp_reset(); + +#if 0 + free_irq(INT_MPUIO, dev); +#endif + free_irq(INT_DSP_MMU, dev); + free_irq(INT_D2A_MB2, dev); + free_irq(INT_D2A_MB1, dev); + + dspuncfg(); + dsp_taskmod_exit(); + mblog_exit(); + dsp_ctl_exit(); + dsp_mem_exit(); + + dsp_ctl_core_exit(); + dsp_remove_procdir_dsp(); + + //__dsp_disable(); // XXX + + return 0; +} + +#ifdef CONFIG_PM +static int dsp_drv_suspend(struct device *dev, u32 state, u32 level) +{ + switch(level) { + case SUSPEND_NOTIFY: + case SUSPEND_DISABLE: + case SUSPEND_SAVE_STATE: + break; + case SUSPEND_POWER_DOWN: + dsp_suspend(); + break; + } + + return 0; +} + +static int dsp_drv_resume(struct device *dev, u32 level) +{ + switch(level) { + case RESUME_POWER_ON: + dsp_resume(); + break; + case RESUME_RESTORE_STATE: + case RESUME_ENABLE: + break; + } + + return 0; +} +#endif /* CONFIG_PM */ + +static struct resource dsp_resources[] = { + { + .start = INT_DSP_MAILBOX1, + .flags = IORESOURCE_IRQ, + }, + { + .start = INT_DSP_MAILBOX2, + .flags = IORESOURCE_IRQ, + }, + { + .start = INT_DSP_MMU, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device dsp_device = { + .name = "dsp", + .id = -1, + .dev = { + .release = dsp_dev_release, + }, + .num_resources = ARRAY_SIZE(&dsp_resources), + .resource = dsp_resources, +}; + +static struct device_driver dsp_driver = { + .name = "dsp", + .bus = &platform_bus_type, + .probe = dsp_drv_probe, + .remove = dsp_drv_remove, +#ifdef CONFIG_PM + .suspend = dsp_drv_suspend, + .resume = dsp_drv_resume, +#endif +}; + +static int __init omap_dsp_mod_init(void) +{ + int ret; + + ret = platform_device_register(&dsp_device); + if (ret) { + printk(KERN_ERR "failed to register the DSP device: %d\n", ret); + goto fail1; + } + + ret = driver_register(&dsp_driver); + if (ret) { + printk(KERN_ERR "failed to register the DSP driver: %d\n", ret); + goto fail2; + } + + return 0; + +fail2: + platform_device_unregister(&dsp_device); +fail1: + return -ENODEV; +} + +static void __exit omap_dsp_mod_exit(void) +{ + driver_unregister(&dsp_driver); + platform_device_unregister(&dsp_device); +} + +module_init(omap_dsp_mod_init); +module_exit(omap_dsp_mod_exit); diff --git a/arch/arm/mach-omap/dsp/dsp_ctl.c b/arch/arm/mach-omap/dsp/dsp_ctl.c new file mode 100644 index 00000000000..3c65bf06b85 --- /dev/null +++ b/arch/arm/mach-omap/dsp/dsp_ctl.c @@ -0,0 +1,913 @@ +/* + * linux/arch/arm/mach-omap/dsp/dsp_ctl.c + * + * OMAP DSP control device driver + * + * Copyright (C) 2002-2005 Nokia Corporation + * + * Written by Toshihiro Kobayashi + * + * 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 + * + * 2005/02/17: DSP Gateway version 3.2 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "hardware_dsp.h" +#include "dsp.h" +#include "ipbuf.h" + +static ssize_t loadinfo_show(struct device *dev, char *buf); +static struct device_attribute dev_attr_loadinfo = __ATTR_RO(loadinfo); +extern struct device_attribute dev_attr_ipbuf; + +static enum { + CFG_ERR, + CFG_READY, + CFG_SUSPEND +} cfgstat; +static int mbx_revision; +static DECLARE_WAIT_QUEUE_HEAD(ioctl_wait_q); +static unsigned short ioctl_wait_cmd; +static DECLARE_MUTEX(ioctl_sem); + +static unsigned char n_stask; + +/* + * control functions + */ +static void dsp_run(void) +{ + disable_irq(INT_DSP_MMU); + preempt_disable(); + if (dsp_runstat == RUNSTAT_RESET) { + clk_use(api_ck_handle); + __dsp_run(); + dsp_runstat = RUNSTAT_RUN; + } + preempt_enable(); + enable_irq(INT_DSP_MMU); +} + +static void dsp_reset(void) +{ + disable_irq(INT_DSP_MMU); + preempt_disable(); + if (dsp_runstat > RUNSTAT_RESET) { + __dsp_reset(); + if (dsp_runstat == RUNSTAT_RUN) + clk_unuse(api_ck_handle); + dsp_runstat = RUNSTAT_RESET; + } + preempt_enable(); + enable_irq(INT_DSP_MMU); +} + +static short varread_val[5]; /* maximum */ + +static int dsp_regread(unsigned short cmd_l, unsigned short adr, + unsigned short *val) +{ + struct mbcmd mb; + int ret = 0; + + if (down_interruptible(&ioctl_sem)) + return -ERESTARTSYS; + + ioctl_wait_cmd = MBCMD(REGRW); + mbcmd_set(mb, MBCMD(REGRW), cmd_l, adr); + dsp_mbsend_and_wait(&mb, &ioctl_wait_q); + + if (ioctl_wait_cmd != 0) { + printk(KERN_ERR "omapdsp: register read error!\n"); + ret = -EINVAL; + goto up_out; + } + + *val = varread_val[0]; + +up_out: + up(&ioctl_sem); + return ret; +} + +static int dsp_regwrite(unsigned short cmd_l, unsigned short adr, + unsigned short val) +{ + struct mbcmd mb; + struct mb_exarg arg = { + .tid = OMAP_DSP_TID_ANON, + .argc = 1, + .argv = &val, + }; + + mbcmd_set(mb, MBCMD(REGRW), cmd_l, adr); + dsp_mbsend_exarg(&mb, &arg); + return 0; +} + +static int dsp_getvar(unsigned char varid, unsigned short *val, int sz) +{ + struct mbcmd mb; + int ret = 0; + + if (down_interruptible(&ioctl_sem)) + return -ERESTARTSYS; + + ioctl_wait_cmd = MBCMD(GETVAR); + mbcmd_set(mb, MBCMD(GETVAR), varid, 0); + dsp_mbsend_and_wait(&mb, &ioctl_wait_q); + + if (ioctl_wait_cmd != 0) { + printk(KERN_ERR "omapdsp: variable read error!\n"); + ret = -EINVAL; + goto up_out; + } + + memcpy(val, varread_val, sz * sizeof(short)); + +up_out: + up(&ioctl_sem); + return ret; +} + +static int dsp_setvar(unsigned char varid, unsigned short val) +{ + struct mbcmd mb; + + mbcmd_set(mb, MBCMD(SETVAR), varid, val); + dsp_mbsend(&mb); + return 0; +} + +static int dspcfg(void) +{ + struct mbcmd mb; + int ret = 0; + + if (down_interruptible(&ioctl_sem)) + return -ERESTARTSYS; + + if (cfgstat != CFG_ERR) { + printk(KERN_ERR + "omapdsp: DSP has been already configured. " + "do unconfig!\n"); + ret = -EBUSY; + goto up_out; + } + + dsp_mb_start(); + dsp_twch_start(); + dsp_mem_start(); + dsp_err_start(); + + mbx_revision = -1; + ioctl_wait_cmd = MBCMD(DSPCFG); + mbcmd_set(mb, MBCMD(DSPCFG), OMAP_DSP_MBCMD_DSPCFG_REQ, 0); + dsp_mbsend_and_wait(&mb, &ioctl_wait_q); + + if (ioctl_wait_cmd != 0) { + printk(KERN_ERR "omapdsp: configuration error!\n"); + ret = -EINVAL; + cfgstat = CFG_ERR; + goto up_out; + } + + if ((ret = dsp_task_config_all(n_stask)) < 0) { + up(&ioctl_sem); + dspuncfg(); + return -EINVAL; + } + + cfgstat = CFG_READY; + + /* send parameter */ + if ((ret = dsp_setvar(OMAP_DSP_MBCMD_VARID_ICRMASK, dsp_icrmask)) < 0) + goto up_out; + + /* create runtime sysfs entries */ + device_create_file(&dsp_device.dev, &dev_attr_loadinfo); + device_create_file(&dsp_device.dev, &dev_attr_ipbuf); + +up_out: + up(&ioctl_sem); + return ret; +} + +int dspuncfg(void) +{ + if (dsp_taskmod_busy()) { + printk(KERN_WARNING "omapdsp: tasks are busy.\n"); + return -EBUSY; + } + + if (down_interruptible(&ioctl_sem)) + return -ERESTARTSYS; + + /* FIXME: lock task module */ + + /* remove runtime sysfs entries */ + device_remove_file(&dsp_device.dev, &dev_attr_loadinfo); + device_remove_file(&dsp_device.dev, &dev_attr_ipbuf); + + dsp_mb_stop(); + dsp_twch_stop(); + dsp_err_stop(); + dsp_task_unconfig_all(); + ipbuf_stop(); + cfgstat = CFG_ERR; + + up(&ioctl_sem); + return 0; +} + +int dsp_is_ready(void) +{ + return (cfgstat == CFG_READY) ? 1 : 0; +} + +void dsp_runlevel(unsigned char level) +{ + struct mbcmd mb; + + mbcmd_set(mb, MBCMD(RUNLEVEL), level, 0); + if (level == OMAP_DSP_MBCMD_RUNLEVEL_RECOVERY) + dsp_mbsend_recovery(&mb); + else + dsp_mbsend(&mb); +} + +int dsp_suspend(void) +{ + struct mbcmd mb; + int ret = 0; + + if (down_interruptible(&ioctl_sem)) + return -ERESTARTSYS; + + if (!dsp_is_ready()) { + printk(KERN_WARNING + "omapdsp: DSP is not ready. suspend failed.\n"); + ret = -EINVAL; + goto up_out; + } + + ioctl_wait_cmd = MBCMD(SUSPEND); + mbcmd_set(mb, MBCMD(SUSPEND), 0, 0); + dsp_mbsend_and_wait(&mb, &ioctl_wait_q); + + if (ioctl_wait_cmd != 0) { + printk(KERN_ERR "omapdsp: DSP suspend error!\n"); + ret = -EINVAL; + goto up_out; + } + + udelay(100); + dsp_reset(); + cfgstat = CFG_SUSPEND; +up_out: + up(&ioctl_sem); + return ret; +} + +int dsp_resume(void) +{ + if (cfgstat != CFG_SUSPEND) + return 0; + + cfgstat = CFG_READY; + dsp_run(); + return 0; +} + +static void dsp_fbctl_enable(void) +{ +#ifdef CONFIG_FB_OMAP_EXTERNAL_LCDC + struct mbcmd mb; + + mbcmd_set(mb, MBCMD(KFUNC), OMAP_DSP_MBCMD_KFUNC_FBCTL, + OMAP_DSP_MBCMD_FBCTL_ENABLE); + dsp_mbsend(&mb); +#endif +} + +static int dsp_fbctl_disable(void) +{ + int ret = 0; + +#ifdef CONFIG_FB_OMAP_EXTERNAL_LCDC + struct mbcmd mb; + + if (down_interruptible(&ioctl_sem)) + return -ERESTARTSYS; + + ioctl_wait_cmd = MBCMD(KFUNC); + mbcmd_set(mb, MBCMD(KFUNC), OMAP_DSP_MBCMD_KFUNC_FBCTL, + OMAP_DSP_MBCMD_FBCTL_DISABLE); + dsp_mbsend_and_wait(&mb, &ioctl_wait_q); + if (ioctl_wait_cmd != 0) { + printk(KERN_ERR "omapdsp: fb disable error!\n"); + ret = -EINVAL; + } + up(&ioctl_sem); +#endif + + return ret; +} + +/* + * DSP control device file operations + */ +static int dsp_ctl_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int ret = 0; + + switch (cmd) { + /* + * command level 1: commands which don't need lock + */ + case OMAP_DSP_IOCTL_RUN: + dsp_run(); + break; + + case OMAP_DSP_IOCTL_RESET: + dsp_reset(); + break; + + case OMAP_DSP_IOCTL_SETRSTVECT: + ret = dsp_set_rstvect((unsigned long)arg); + break; + + case OMAP_DSP_IOCTL_IDLE: + dsp_idle(); + break; + + case OMAP_DSP_IOCTL_MPUI_WORDSWAP_ON: + mpui_wordswap_on(); + break; + + case OMAP_DSP_IOCTL_MPUI_WORDSWAP_OFF: + mpui_wordswap_off(); + break; + + case OMAP_DSP_IOCTL_MPUI_BYTESWAP_ON: + mpui_byteswap_on(); + break; + + case OMAP_DSP_IOCTL_MPUI_BYTESWAP_OFF: + mpui_byteswap_off(); + break; + + case OMAP_DSP_IOCTL_MBSEND: + { + struct omap_dsp_mailbox_cmd u_cmd; + struct mbcmd_hw mb; + if (copy_from_user(&u_cmd, (void *)arg, sizeof(u_cmd))) + return -EFAULT; + mb.cmd = u_cmd.cmd; + mb.data = u_cmd.data; + ret = dsp_mbsend((struct mbcmd *)&mb); + break; + } + + case OMAP_DSP_IOCTL_SETVAR: + { + struct omap_dsp_varinfo var; + if (copy_from_user(&var, (void *)arg, sizeof(var))) + return -EFAULT; + ret = dsp_setvar(var.varid, var.val[0]); + break; + } + + case OMAP_DSP_IOCTL_RUNLEVEL: + dsp_runlevel(arg); + break; + + case OMAP_DSP_IOCTL_FBEN: + dsp_fbctl_enable(); + return 0; + + /* + * command level 2: commands which need lock + */ + case OMAP_DSP_IOCTL_DSPCFG: + ret = dspcfg(); + break; + + case OMAP_DSP_IOCTL_DSPUNCFG: + ret = dspuncfg(); + break; + + case OMAP_DSP_IOCTL_TASKCNT: + ret = dsp_task_count(); + break; + + case OMAP_DSP_IOCTL_FBDIS: + ret = dsp_fbctl_disable(); + break; + + case OMAP_DSP_IOCTL_SUSPEND: + ret = dsp_suspend(); + break; + + case OMAP_DSP_IOCTL_RESUME: + ret = dsp_resume(); + break; + + case OMAP_DSP_IOCTL_REGMEMR: + { + struct omap_dsp_reginfo *u_reg = (void *)arg; + unsigned short adr, val; + + if (copy_from_user(&adr, &u_reg->adr, sizeof(short))) + return -EFAULT; + if ((ret = dsp_regread(OMAP_DSP_MBCMD_REGRW_MEMR, + adr, &val)) < 0) + return ret; + if (copy_to_user(&u_reg->val, &val, sizeof(short))) + return -EFAULT; + break; + } + + case OMAP_DSP_IOCTL_REGMEMW: + { + struct omap_dsp_reginfo reg; + + if (copy_from_user(®, (void *)arg, sizeof(reg))) + return -EFAULT; + ret = dsp_regwrite(OMAP_DSP_MBCMD_REGRW_MEMW, + reg.adr, reg.val); + break; + } + + case OMAP_DSP_IOCTL_REGIOR: + { + struct omap_dsp_reginfo *u_reg = (void *)arg; + unsigned short adr, val; + + if (copy_from_user(&adr, &u_reg->adr, sizeof(short))) + return -EFAULT; + if ((ret = dsp_regread(OMAP_DSP_MBCMD_REGRW_IOR, + adr, &val)) < 0) + return ret; + if (copy_to_user(&u_reg->val, &val, sizeof(short))) + return -EFAULT; + break; + } + + case OMAP_DSP_IOCTL_REGIOW: + { + struct omap_dsp_reginfo reg; + + if (copy_from_user(®, (void *)arg, sizeof(reg))) + return -EFAULT; + ret = dsp_regwrite(OMAP_DSP_MBCMD_REGRW_IOW, + reg.adr, reg.val); + break; + } + + case OMAP_DSP_IOCTL_GETVAR: + { + struct omap_dsp_varinfo *u_var = (void *)arg; + unsigned char varid; + unsigned short val[5]; /* maximum */ + int argc; + + if (copy_from_user(&varid, &u_var->varid, sizeof(char))) + return -EFAULT; + switch (varid) { + case OMAP_DSP_MBCMD_VARID_ICRMASK: + argc = 1; + break; + case OMAP_DSP_MBCMD_VARID_LOADINFO: + argc = 5; + break; + default: + return -EINVAL; + } + if ((ret = dsp_getvar(varid, val, argc)) < 0) + return ret; + if (copy_to_user(&u_var->val, val, sizeof(short) * argc)) + return -EFAULT; + break; + } + + default: + return -ENOIOCTLCMD; + } + + return ret; +} + +/* + * functions called from mailbox1 interrupt routine + */ +void mbx1_suspend(struct mbcmd *mb) +{ + if (!waitqueue_active(&ioctl_wait_q) || + (ioctl_wait_cmd != MBCMD(SUSPEND))) { + printk(KERN_WARNING + "mbx: SUSPEND command received, " + "but nobody is waiting for it...\n"); + return; + } + + ioctl_wait_cmd = 0; + wake_up_interruptible(&ioctl_wait_q); +} + +void mbx1_dspcfg(struct mbcmd *mb) +{ + unsigned char last = mb->cmd_l & 0x80; + unsigned char cfgcmd = mb->cmd_l & 0x7f; + static unsigned long tmp_ipbuf_sys_da; + + /* mailbox protocol check */ + if (cfgcmd == OMAP_DSP_MBCMD_DSPCFG_PROTREV) { + if (!waitqueue_active(&ioctl_wait_q) || + (ioctl_wait_cmd != MBCMD(DSPCFG))) { + printk(KERN_WARNING + "mbx: DSPCFG command received, " + "but nobody is waiting for it...\n"); + return; + } + + mbx_revision = mb->data; + if (mbx_revision == OMAP_DSP_MBPROT_REVISION) + return; +#ifdef OLD_BINARY_SUPPORT + else if (mbx_revision == MBREV_3_0) { + printk(KERN_WARNING + "mbx: ***** old DSP binary *****\n" + " Please update your DSP application.\n"); + return; + } +#endif + else { + printk(KERN_ERR + "mbx: protocol revision check error!\n" + " expected=0x%04x, received=0x%04x\n", + OMAP_DSP_MBPROT_REVISION, mb->data); + mbx_revision = -1; + goto abort; + } + } + + /* + * following commands are accepted only after + * revision check has been passed. + */ + if (!mbx_revision < 0) { + printk(KERN_INFO + "mbx: DSPCFG command received, " + "but revision check has not been passed.\n"); + return; + } + + if (!waitqueue_active(&ioctl_wait_q) || + (ioctl_wait_cmd != MBCMD(DSPCFG))) { + printk(KERN_WARNING + "mbx: DSPCFG command received, " + "but nobody is waiting for it...\n"); + return; + } + + switch (cfgcmd) { + case OMAP_DSP_MBCMD_DSPCFG_SYSADRH: + tmp_ipbuf_sys_da = (unsigned long)mb->data << 16; + break; + + case OMAP_DSP_MBCMD_DSPCFG_SYSADRL: + tmp_ipbuf_sys_da |= mb->data; + break; + + case OMAP_DSP_MBCMD_DSPCFG_ABORT: + goto abort; + + default: + printk(KERN_ERR + "mbx: Unknown CFG command: cmd_l=0x%02x, data=0x%04x\n", + mb->cmd_l, mb->data); + return; + } + + if (last) { + unsigned long badr; + unsigned short bln; + unsigned short bsz; + volatile unsigned short *buf; + void *sync_seq; + + /* system IPBUF initialization */ + if (tmp_ipbuf_sys_da & 0x1) { + printk(KERN_ERR + "mbx: system ipbuf address (0x%lx) " + "is odd number!\n", tmp_ipbuf_sys_da); + goto abort; + } + ipbuf_sys_da = dspword_to_virt(tmp_ipbuf_sys_da); + + if (sync_with_dsp(&ipbuf_sys_da->s, OMAP_DSP_TID_ANON, 10) < 0) { + printk(KERN_ERR "mbx: DSPCFG - IPBUF sync failed!\n"); + return; + } + /* + * read configuration data on system IPBUF + * we must read with 16bit-access + */ +#ifdef OLD_BINARY_SUPPORT + if (mbx_revision == OMAP_DSP_MBPROT_REVISION) { +#endif + buf = ipbuf_sys_da->d; + n_stask = buf[0]; + bln = buf[1]; + bsz = buf[2]; + badr = MKLONG(buf[3], buf[4]); + /*ipbuf_sys_da = dspword_to_virt(MKLONG(buf[5], buf[6])); */ + ipbuf_sys_ad = dspword_to_virt(MKLONG(buf[7], buf[8])); + sync_seq = dspword_to_virt(MKLONG(buf[9], buf[10])); +#ifdef OLD_BINARY_SUPPORT + } else if (mbx_revision == MBREV_3_0) { + buf = ipbuf_sys_da->d; + n_stask = buf[0]; + bln = buf[1]; + bsz = buf[2]; + badr = MKLONG(buf[3], buf[4]); + /* bkeep = buf[5]; */ + /*ipbuf_sys_da = dspword_to_virt(MKLONG(buf[6], buf[67)); */ + ipbuf_sys_ad = dspword_to_virt(MKLONG(buf[8], buf[9])); + sync_seq = dspword_to_virt(MKLONG(buf[10], buf[11])); + } else /* should not occur */ + goto abort; +#endif + + /* ipbuf_config() should be done in interrupt routine. */ + if (ipbuf_config(bln, bsz, badr) < 0) + goto abort; + + ipbuf_sys_da->s = OMAP_DSP_TID_FREE; + + /* mb_config() should be done in interrupt routine. */ + dsp_mb_config(sync_seq); + + ioctl_wait_cmd = 0; + wake_up_interruptible(&ioctl_wait_q); + } + return; + +abort: + wake_up_interruptible(&ioctl_wait_q); + return; +} + +void mbx1_regrw(struct mbcmd *mb) +{ + if (!waitqueue_active(&ioctl_wait_q) || + (ioctl_wait_cmd != MBCMD(REGRW))) { + printk(KERN_WARNING + "mbx: REGRW command received, " + "but nobody is waiting for it...\n"); + return; + } + + switch (mb->cmd_l) { + case OMAP_DSP_MBCMD_REGRW_DATA: + ioctl_wait_cmd = 0; + varread_val[0] = mb->data; + wake_up_interruptible(&ioctl_wait_q); + return; + + default: + printk(KERN_ERR + "mbx: Illegal REGRW command: " + "cmd_l=0x%02x, data=0x%04x\n", mb->cmd_l, mb->data); + return; + } +} + +void mbx1_getvar(struct mbcmd *mb) +{ + unsigned char varid = mb->cmd_l; + int i; + volatile unsigned short *buf; + + if (!waitqueue_active(&ioctl_wait_q) || + (ioctl_wait_cmd != MBCMD(GETVAR))) { + printk(KERN_WARNING + "mbx: GETVAR command received, " + "but nobody is waiting for it...\n"); + return; + } + + ioctl_wait_cmd = 0; + switch (varid) { + case OMAP_DSP_MBCMD_VARID_ICRMASK: + varread_val[0] = mb->data; + break; + case OMAP_DSP_MBCMD_VARID_LOADINFO: + { + if (sync_with_dsp(&ipbuf_sys_da->s, OMAP_DSP_TID_ANON, 10) < 0) { + printk(KERN_ERR + "mbx: GETVAR - IPBUF sync failed!\n"); + return; + } + /* need word access. do not use memcpy. */ + buf = ipbuf_sys_da->d; + for (i = 0; i < 5; i++) { + varread_val[i] = buf[i]; + } + ipbuf_sys_da->s = OMAP_DSP_TID_FREE; + break; + } + } + wake_up_interruptible(&ioctl_wait_q); + + return; +} + +/* + * sysfs files + */ +static ssize_t ifver_show(struct device *dev, char *buf) +{ + int len = 0; + + /* + * I/F VERSION descriptions: + * + * 3.2: sysfs / udev support + * KMEM_RESERVE / KMEM_RELEASE ioctls for mem device + */ + + /* + * print all supporting I/F VERSIONs, like followings. + * + * len += sprintf(buf, "3.1\n"); + * len += sprintf(buf, "3.2\n"); + */ + len += sprintf(buf + len, "3.2\n"); + + return len; +} + +static struct device_attribute dev_attr_ifver = __ATTR_RO(ifver); + +static ssize_t icrmask_show(struct device *dev, char *buf) +{ +#if 0 + if (dsp_is_ready()) { + int ret; + unsigned short val; + + if ((ret = dsp_getvar(OMAP_DSP_MBCMD_VARID_ICRMASK, &val, 1)) < 0) + return ret; + if (val != dsp_icrmask) + printk(KERN_WARNING + "omapdsp: icrmask value is inconsistent!\n"); + } +#endif + return sprintf(buf, "0x%04x\n", dsp_icrmask); +} + +static ssize_t icrmask_store(struct device *dev, const char *buf, size_t count) +{ + int ret; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + dsp_icrmask = simple_strtol(buf, NULL, 16); + + if (dsp_is_ready()) { + ret = dsp_setvar(OMAP_DSP_MBCMD_VARID_ICRMASK, dsp_icrmask); + if (ret < 0) + return ret; + } + + return strlen(buf); +} + +static struct device_attribute dev_attr_icrmask = + __ATTR(icrmask, S_IWUSR | S_IRUGO, icrmask_show, icrmask_store); + +static ssize_t loadinfo_show(struct device *dev, char *buf) +{ + int len; + int ret; + static unsigned short val[5]; + + if ((ret = dsp_getvar(OMAP_DSP_MBCMD_VARID_LOADINFO, val, 5)) < 0) + return ret; + + /* load info value range is 0(free) - 10000(busy) */ + len = sprintf(buf, + "DSP load info:\n" + " 10ms average = %3d.%02d%%\n" + " 1sec average = %3d.%02d%% busiest 10ms = %3d.%02d%%\n" + " 1min average = %3d.%02d%% busiest 1s = %3d.%02d%%\n", + val[0]/100, val[0]%100, + val[1]/100, val[1]%100, val[2]/100, val[2]%100, + val[3]/100, val[3]%100, val[4]/100, val[4]%100); + return len; +} + +/* + * This is declared at the top of this file. + * + * static struct device_attribute dev_attr_loadinfo = __ATTR_RO(loadinfo); + */ + +#ifdef CONFIG_FB_OMAP_EXTERNAL_LCDC +void mbx1_fbctl_disable(void) +{ + if (!waitqueue_active(&ioctl_wait_q) || + (ioctl_wait_cmd != MBCMD(KFUNC))) { + printk(KERN_WARNING + "mbx: KFUNC:FBCTL command received, " + "but nobody is waiting for it...\n"); + return; + } + ioctl_wait_cmd = 0; + wake_up_interruptible(&ioctl_wait_q); +} +#endif + +#ifdef CONFIG_PROC_FS +/* for backward compatibility */ +static int version_read_proc(char *page, char **start, off_t off, int count, + int *eof, void *data) +{ + /* + * This entry is read by 3.1 tools only, so leave it as is. + * 3.2 and later will read from sysfs file. + */ + return sprintf(page, "3.1\n"); +} + +static void __init dsp_ctl_create_proc(void) +{ + struct proc_dir_entry *ent; + + /* version */ + ent = create_proc_read_entry("version", 0, procdir_dsp, + version_read_proc, NULL); + if (ent == NULL) { + printk(KERN_ERR + "omapdsp: failed to register proc device: version\n"); + } +} + +static void dsp_ctl_remove_proc(void) +{ + remove_proc_entry("version", procdir_dsp); +} +#endif /* CONFIG_PROC_FS */ + +struct file_operations dsp_ctl_fops = { + .owner = THIS_MODULE, + .ioctl = dsp_ctl_ioctl, +}; + +void __init dsp_ctl_init(void) +{ + device_create_file(&dsp_device.dev, &dev_attr_ifver); + device_create_file(&dsp_device.dev, &dev_attr_icrmask); +#ifdef CONFIG_PROC_FS + dsp_ctl_create_proc(); +#endif +} + +void dsp_ctl_exit(void) +{ + device_remove_file(&dsp_device.dev, &dev_attr_ifver); + device_remove_file(&dsp_device.dev, &dev_attr_icrmask); +#ifdef CONFIG_PROC_FS + dsp_ctl_remove_proc(); +#endif +} diff --git a/arch/arm/mach-omap/dsp/dsp_ctl_core.c b/arch/arm/mach-omap/dsp/dsp_ctl_core.c new file mode 100644 index 00000000000..729ad9fcf0b --- /dev/null +++ b/arch/arm/mach-omap/dsp/dsp_ctl_core.c @@ -0,0 +1,129 @@ +/* + * linux/arch/arm/mach-omap/dsp/dsp_ctl_core.c + * + * OMAP DSP control devices core driver + * + * Copyright (C) 2004,2005 Nokia Corporation + * + * Written by Toshihiro Kobayashi + * + * 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 + * + * 2005/02/10: DSP Gateway version 3.2 + */ + +#include +#include +#include +#include +#include +#include +#include +#include "hardware_dsp.h" + +#define CTL_MINOR 0 +#define MEM_MINOR 1 +#define TWCH_MINOR 2 +#define ERR_MINOR 3 + +static struct class_simple *dsp_ctl_class; +extern struct file_operations dsp_ctl_fops, + dsp_mem_fops, + dsp_twch_fops, + dsp_err_fops; + +static int dsp_ctl_core_open(struct inode *inode, struct file *file) +{ + switch (iminor(inode)) { + case CTL_MINOR: + file->f_op = &dsp_ctl_fops; + break; + case MEM_MINOR: + file->f_op = &dsp_mem_fops; + break; + case TWCH_MINOR: + file->f_op = &dsp_twch_fops; + break; + case ERR_MINOR: + file->f_op = &dsp_err_fops; + break; + default: + return -ENXIO; + } + if (file->f_op && file->f_op->open) + return file->f_op->open(inode, file); + return 0; +} + +static struct file_operations dsp_ctl_core_fops = { + .owner = THIS_MODULE, + .open = dsp_ctl_core_open, +}; + +static const struct dev_list { + unsigned int minor; + char *devname; + char *devfs_name; + umode_t mode; +} dev_list[] = { + {CTL_MINOR, "dspctl", "dspctl/ctl", S_IRUSR | S_IWUSR}, + {MEM_MINOR, "dspmem", "dspctl/mem", S_IRUSR | S_IWUSR | S_IRGRP}, + {TWCH_MINOR, "dsptwch", "dspctl/twch", S_IRUSR | S_IWUSR | S_IRGRP}, + {ERR_MINOR, "dsperr", "dspctl/err", S_IRUSR | S_IRGRP}, +}; + +int __init dsp_ctl_core_init(void) +{ + int retval; + int i; + + retval = register_chrdev(OMAP_DSP_CTL_MAJOR, "dspctl", + &dsp_ctl_core_fops); + if (retval < 0) { + printk(KERN_ERR + "omapdsp: failed to register dspctl device: %d\n", + retval); + return retval; + } + + dsp_ctl_class = class_simple_create(THIS_MODULE, "dspctl"); + devfs_mk_dir("dspctl"); + for (i = 0; i < ARRAY_SIZE(dev_list); i++) { + class_simple_device_add(dsp_ctl_class, + MKDEV(OMAP_DSP_CTL_MAJOR, + dev_list[i].minor), + NULL, dev_list[i].devname); + devfs_mk_cdev(MKDEV(OMAP_DSP_CTL_MAJOR, dev_list[i].minor), + S_IFCHR | dev_list[i].mode, + dev_list[i].devfs_name); + } + + return 0; +} + +void dsp_ctl_core_exit(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dev_list); i++) { + devfs_remove(dev_list[i].devfs_name); + class_simple_device_remove(MKDEV(OMAP_DSP_CTL_MAJOR, + dev_list[i].minor)); + } + devfs_remove("dspctl"); + class_simple_destroy(dsp_ctl_class); + + unregister_chrdev(OMAP_DSP_CTL_MAJOR, "dspctl"); +} diff --git a/arch/arm/mach-omap/dsp/dsp_mem.c b/arch/arm/mach-omap/dsp/dsp_mem.c new file mode 100644 index 00000000000..107417941c2 --- /dev/null +++ b/arch/arm/mach-omap/dsp/dsp_mem.c @@ -0,0 +1,1597 @@ +/* + * linux/arch/arm/mach-omap/dsp/dsp_mem.c + * + * OMAP DSP memory driver + * + * Copyright (C) 2002-2005 Nokia Corporation + * + * Written by Toshihiro Kobayashi + * + * 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 + * + * Toshihiro Kobayashi + * 2005/02/17: DSP Gateway version 3.2 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "uaccess_dsp.h" +#include "ipbuf.h" +#include "dsp.h" + +#define SZ_1MB 0x100000 +#define SZ_64KB 0x10000 +#define SZ_4KB 0x1000 +#define SZ_1KB 0x400 +#define is_aligned(adr,align) (!((adr)&((align)-1))) +#define ORDER_1MB (20 - PAGE_SHIFT) +#define ORDER_64KB (16 - PAGE_SHIFT) +#define ORDER_4KB (12 - PAGE_SHIFT) + +#define PGDIR_MASK (~(PGDIR_SIZE-1)) +#define PGDIR_ALIGN(addr) (((addr)+PGDIR_SIZE-1)&(PGDIR_MASK)) + +#define dsp_mmu_enable() \ + do { \ + omap_writew(DSPMMU_CNTL_MMU_EN | DSPMMU_CNTL_RESET_SW, \ + DSPMMU_CNTL); \ + } while(0) +#define dsp_mmu_disable() \ + do { omap_writew(0, DSPMMU_CNTL); } while(0) +#define dsp_mmu_flush() \ + do { \ + omap_writew(DSPMMU_FLUSH_ENTRY_FLUSH_ENTRY, \ + DSPMMU_FLUSH_ENTRY); \ + } while(0) +#define __dsp_mmu_gflush() \ + do { omap_writew(DSPMMU_GFLUSH_GFLUSH, DSPMMU_GFLUSH); } while(0) +#define __dsp_mmu_itack() \ + do { omap_writew(DSPMMU_IT_ACK_IT_ACK, DSPMMU_IT_ACK); } while(0) + +#define EMIF_PRIO_LB_MASK 0x0000f000 +#define EMIF_PRIO_LB_SHIFT 12 +#define EMIF_PRIO_DMA_MASK 0x00000f00 +#define EMIF_PRIO_DMA_SHIFT 8 +#define EMIF_PRIO_DSP_MASK 0x00000070 +#define EMIF_PRIO_DSP_SHIFT 4 +#define EMIF_PRIO_MPU_MASK 0x00000007 +#define EMIF_PRIO_MPU_SHIFT 0 +#define set_emiff_dma_prio(prio) \ + do { \ + omap_writel((omap_readl(OMAP_TC_OCPT1_PRIOR) & \ + ~EMIF_PRIO_DMA_MASK) | \ + ((prio) << EMIF_PRIO_DMA_SHIFT), \ + OMAP_TC_OCPT1_PRIOR); \ + } while(0) + +enum exmap_type { + EXMAP_TYPE_MEM, + EXMAP_TYPE_FB +}; + +struct exmap_tbl { + unsigned int valid:1; + unsigned int cntnu:1; /* grouping */ + enum exmap_type type; + void *buf; + void *vadr; + unsigned int order; +}; +#define DSPMMU_TLB_LINES 32 +static struct exmap_tbl exmap_tbl[DSPMMU_TLB_LINES]; +static DECLARE_RWSEM(exmap_sem); + +static int dsp_exunmap(unsigned long dspadr); + +static void *dspvect_page; +static unsigned long dsp_fault_adr; + +static __inline__ unsigned long lineup_offset(unsigned long adr, + unsigned long ref, + unsigned long mask) +{ + unsigned long newadr; + + newadr = (adr & ~mask) | (ref & mask); + if (newadr < adr) + newadr += mask + 1; + return newadr; +} + +/* + * kmem_reserve(), kmem_release(): + * reserve or release kernel memory for exmap(). + * + * exmap() might request consecutive 1MB or 64kB, + * but it will be difficult after memory pages are fragmented. + * So, user can reserve such memory blocks in the early phase + * through kmem_reserve(). + */ +struct kmem_pool { + struct semaphore sem; + unsigned long buf[16]; + int count; +}; + +#define KMEM_POOL_INIT(name) \ +{ \ + .sem = __MUTEX_INITIALIZER((name).sem), \ +} +#define DECLARE_KMEM_POOL(name) \ + struct kmem_pool name = KMEM_POOL_INIT(name) + +DECLARE_KMEM_POOL(kmem_pool_1M); +DECLARE_KMEM_POOL(kmem_pool_64K); + +static void dsp_kmem_release(void) +{ + int i; + + down(&kmem_pool_1M.sem); + for (i = 0; i < kmem_pool_1M.count; i++) { + if (kmem_pool_1M.buf[i]) + free_pages(kmem_pool_1M.buf[i], ORDER_1MB); + } + kmem_pool_1M.count = 0; + up(&kmem_pool_1M.sem); + + down(&kmem_pool_64K.sem); + for (i = 0; i < kmem_pool_64K.count; i++) { + if (kmem_pool_64K.buf[i]) + free_pages(kmem_pool_64K.buf[i], ORDER_64KB); + } + kmem_pool_64K.count = 0; + up(&kmem_pool_1M.sem); +} + +static int dsp_kmem_reserve(unsigned long size) +{ + unsigned long buf; + unsigned int order; + unsigned long unit; + unsigned long _size; + struct kmem_pool *pool; + int i; + + /* alignment check */ + if (!is_aligned(size, SZ_64KB)) { + printk(KERN_ERR + "omapdsp: size(0x%lx) is not multiple of 64KB.\n", size); + return -EINVAL; + } + if (size > DSPSPACE_SIZE) { + printk(KERN_ERR + "omapdsp: size(0x%lx) is larger than DSP memory space " + "size (0x%x.\n", size, DSPSPACE_SIZE); + return -EINVAL; + } + + for (_size = size; _size; _size -= unit) { + if (_size >= SZ_1MB) { + unit = SZ_1MB; + order = ORDER_1MB; + pool = &kmem_pool_1M; + } else { + unit = SZ_64KB; + order = ORDER_64KB; + pool = &kmem_pool_64K; + } + + buf = __get_dma_pages(GFP_KERNEL, order); + if (!buf) + return size - _size; + down(&pool->sem); + for (i = 0; i < 16; i++) { + if (!pool->buf[i]) { + pool->buf[i] = buf; + pool->count++; + buf = 0; + break; + } + } + up(&pool->sem); + + if (buf) { /* pool is full */ + free_pages(buf, order); + return size - _size; + } + } + + return size; +} + +static unsigned long dsp_mem_get_dma_pages(unsigned int order) +{ + struct kmem_pool *pool; + unsigned long buf = 0; + int i; + + switch (order) { + case ORDER_1MB: + pool = &kmem_pool_1M; + break; + case ORDER_64KB: + pool = &kmem_pool_64K; + break; + default: + pool = NULL; + } + + if (pool) { + down(&pool->sem); + for (i = 0; i < pool->count; i++) { + if (pool->buf[i]) { + buf = pool->buf[i]; + pool->buf[i] = 0; + break; + } + } + up(&pool->sem); + if (buf) + return buf; + } + + /* other size or not found in pool */ + return __get_dma_pages(GFP_KERNEL, order); +} + +static void dsp_mem_free_pages(unsigned int buf, unsigned int order) +{ + struct kmem_pool *pool; + int i; + + switch (order) { + case ORDER_1MB: + pool = &kmem_pool_1M; + break; + case ORDER_64KB: + pool = &kmem_pool_64K; + break; + default: + pool = NULL; + } + + if (pool) { + down(&pool->sem); + for (i = 0; i < pool->count; i++) { + if (!pool->buf[i]) { + pool->buf[i] = buf; + buf = 0; + } + } + up(&pool->sem); + } + + /* other size or pool is filled */ + if (buf) + free_pages(buf, order); +} + +/* + * ARM MMU operations + */ +static int exmap_set_armmmu(unsigned long virt, unsigned long phys, + unsigned long size) +{ + long off; + unsigned long sz_left; + pmd_t *pmdp; + pte_t *ptep; + int prot_pmd, prot_pte; + + printk(KERN_DEBUG + "omapdsp: mapping in ARM MMU, v=0x%08lx, p=0x%08lx, sz=0x%lx\n", + virt, phys, size); + + prot_pmd = PMD_TYPE_TABLE | PMD_DOMAIN(DOMAIN_IO); + prot_pte = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY | L_PTE_WRITE; + + pmdp = pmd_offset(pgd_offset_k(virt), virt); + if (pmd_none(*pmdp)) { + ptep = pte_alloc_one_kernel(&init_mm, 0); + if (ptep == NULL) + return -ENOMEM; + /* note: two PMDs will be set */ + pmd_populate_kernel(&init_mm, pmdp, ptep); + } + + off = phys - virt; + for (sz_left = size; + sz_left >= PAGE_SIZE; + sz_left -= PAGE_SIZE, virt += PAGE_SIZE) { + ptep = pte_offset_kernel(pmdp, virt); + set_pte(ptep, __pte((virt + off) | prot_pte)); + } + if (sz_left) + BUG(); + + return 0; +} + +static void exmap_clear_armmmu(unsigned long virt, unsigned long size) +{ + unsigned long sz_left; + pmd_t *pmdp; + pte_t *ptep; + + printk(KERN_DEBUG + "omapdsp: unmapping in ARM MMU, v=0x%08lx, sz=0x%lx\n", + virt, size); + + for (sz_left = size; + sz_left >= PAGE_SIZE; + sz_left -= PAGE_SIZE, virt += PAGE_SIZE) { + pmdp = pmd_offset(pgd_offset_k(virt), virt); + ptep = pte_offset_kernel(pmdp, virt); + pte_clear(&init_mm, virt, ptep); + } + if (sz_left) + BUG(); +} + +static int exmap_valid(void *vadr, size_t len) +{ + int i; + +start: + for (i = 0; i < DSPMMU_TLB_LINES; i++) { + void *mapadr; + unsigned long mapsize; + struct exmap_tbl *ent = &exmap_tbl[i]; + + if (!ent->valid) + continue; + mapadr = (void *)ent->vadr; + mapsize = 1 << (ent->order + PAGE_SHIFT); + if ((vadr >= mapadr) && (vadr < mapadr + mapsize)) { + if (vadr + len <= mapadr + mapsize) { + /* this map covers whole address. */ + return 1; + } else { + /* + * this map covers partially. + * check rest portion. + */ + len -= mapadr + mapsize - vadr; + vadr = mapadr + mapsize; + goto start; + } + } + } + + return 0; +} + +/* + * dsp_virt_to_phys() + * returns physical address, and sets len to valid length + */ +unsigned long dsp_virt_to_phys(void *vadr, size_t *len) +{ + int i; + + if (is_dsp_internal_mem(vadr)) { + /* DSRAM or SARAM */ + *len = dspmem_base + dspmem_size - (unsigned long)vadr; + return (unsigned long)vadr; + } + + /* EXRAM */ + for (i = 0; i < DSPMMU_TLB_LINES; i++) { + void *mapadr; + unsigned long mapsize; + struct exmap_tbl *ent = &exmap_tbl[i]; + + if (!ent->valid) + continue; + mapadr = (void *)ent->vadr; + mapsize = 1 << (ent->order + PAGE_SHIFT); + if ((vadr >= mapadr) && (vadr < mapadr + mapsize)) { + *len = mapadr + mapsize - vadr; + return __pa(ent->buf) + vadr - mapadr; + } + } + + /* valid mapping not found */ + return 0; +} + +/* + * DSP MMU operations + */ +static __inline__ unsigned short get_cam_l_va_mask(unsigned short slst) +{ + switch (slst) { + case DSPMMU_CAM_L_SLST_1MB: + return DSPMMU_CAM_L_VA_TAG_L1_MASK | + DSPMMU_CAM_L_VA_TAG_L2_MASK_1MB; + case DSPMMU_CAM_L_SLST_64KB: + return DSPMMU_CAM_L_VA_TAG_L1_MASK | + DSPMMU_CAM_L_VA_TAG_L2_MASK_64KB; + case DSPMMU_CAM_L_SLST_4KB: + return DSPMMU_CAM_L_VA_TAG_L1_MASK | + DSPMMU_CAM_L_VA_TAG_L2_MASK_4KB; + case DSPMMU_CAM_L_SLST_1KB: + return DSPMMU_CAM_L_VA_TAG_L1_MASK | + DSPMMU_CAM_L_VA_TAG_L2_MASK_1KB; + } + return 0; +} + +static __inline__ void get_tlb_lock(int *base, int *victim) +{ + unsigned short lock = omap_readw(DSPMMU_LOCK); + if (base != NULL) + *base = (lock & DSPMMU_LOCK_BASE_MASK) + >> DSPMMU_LOCK_BASE_SHIFT; + if (victim != NULL) + *victim = (lock & DSPMMU_LOCK_VICTIM_MASK) + >> DSPMMU_LOCK_VICTIM_SHIFT; +} + +static __inline__ void set_tlb_lock(int base, int victim) +{ + omap_writew((base << DSPMMU_LOCK_BASE_SHIFT) | + (victim << DSPMMU_LOCK_VICTIM_SHIFT), DSPMMU_LOCK); +} + +static __inline__ void __read_tlb(unsigned short lbase, unsigned short victim, + unsigned short *cam_h, unsigned short *cam_l, + unsigned short *ram_h, unsigned short *ram_l) +{ + /* set victim */ + set_tlb_lock(lbase, victim); + + /* read a TLB entry */ + omap_writew(DSPMMU_LD_TLB_RD, DSPMMU_LD_TLB); + + if (cam_h != NULL) + *cam_h = omap_readw(DSPMMU_READ_CAM_H); + if (cam_l != NULL) + *cam_l = omap_readw(DSPMMU_READ_CAM_L); + if (ram_h != NULL) + *ram_h = omap_readw(DSPMMU_READ_RAM_H); + if (ram_l != NULL) + *ram_l = omap_readw(DSPMMU_READ_RAM_L); +} + +static __inline__ void __load_tlb(unsigned short cam_h, unsigned short cam_l, + unsigned short ram_h, unsigned short ram_l) +{ + omap_writew(cam_h, DSPMMU_CAM_H); + omap_writew(cam_l, DSPMMU_CAM_L); + omap_writew(ram_h, DSPMMU_RAM_H); + omap_writew(ram_l, DSPMMU_RAM_L); + + /* flush the entry */ + dsp_mmu_flush(); + + /* load a TLB entry */ + omap_writew(DSPMMU_LD_TLB_LD, DSPMMU_LD_TLB); +} + +static int dsp_mmu_load_tlb(unsigned long vadr, unsigned long padr, + unsigned short slst, unsigned short prsvd, + unsigned short ap) +{ + int lbase, victim; + unsigned short cam_l_va_mask; + + clk_use(dsp_ck_handle); + + get_tlb_lock(&lbase, NULL); + for (victim = 0; victim < lbase; victim++) { + unsigned short cam_l; + + /* read a TLB entry */ + __read_tlb(lbase, victim, NULL, &cam_l, NULL, NULL); + if (!(cam_l & DSPMMU_CAM_L_V)) + goto found_victim; + } + set_tlb_lock(lbase, victim); + +found_victim: + /* The last (31st) entry cannot be locked? */ + if (victim == 31) { + printk(KERN_ERR "omapdsp: TLB is full.\n"); + return -EBUSY; + } + + cam_l_va_mask = get_cam_l_va_mask(slst); + if (vadr & + ~(DSPMMU_CAM_H_VA_TAG_H_MASK << 22 | + (unsigned long)cam_l_va_mask << 6)) { + printk(KERN_ERR + "omapdsp: mapping vadr (0x%06lx) is not " + "aligned boundary\n", vadr); + return -EINVAL; + } + + __load_tlb(vadr >> 22, (vadr >> 6 & cam_l_va_mask) | prsvd | slst, + padr >> 16, (padr & DSPMMU_RAM_L_RAM_LSB_MASK) | ap); + + /* update lock base */ + if (victim == lbase) + lbase++; + set_tlb_lock(lbase, lbase); + + clk_unuse(dsp_ck_handle); + return 0; +} + +static int dsp_mmu_clear_tlb(unsigned long vadr) +{ + int lbase; + int i; + int max_valid = 0; + + clk_use(dsp_ck_handle); + + get_tlb_lock(&lbase, NULL); + for (i = 0; i < lbase; i++) { + unsigned short cam_h, cam_l; + unsigned short cam_l_va_mask, cam_vld, slst; + unsigned long cam_va; + + /* read a TLB entry */ + __read_tlb(lbase, i, &cam_h, &cam_l, NULL, NULL); + + cam_vld = cam_l & DSPMMU_CAM_L_V; + if (!cam_vld) + continue; + + slst = cam_l & DSPMMU_CAM_L_SLST_MASK; + cam_l_va_mask = get_cam_l_va_mask(slst); + cam_va = (unsigned long)(cam_h & DSPMMU_CAM_H_VA_TAG_H_MASK) << 22 | + (unsigned long)(cam_l & cam_l_va_mask) << 6; + + if (cam_va == vadr) + /* flush the entry */ + dsp_mmu_flush(); + else + max_valid = i; + } + + /* set new lock base */ + set_tlb_lock(max_valid+1, max_valid+1); + + clk_unuse(dsp_ck_handle); + return 0; +} + +static void dsp_mmu_gflush(void) +{ + clk_use(dsp_ck_handle); + + __dsp_mmu_gflush(); + set_tlb_lock(1, 1); + + clk_unuse(dsp_ck_handle); +} + +/* + * dsp_exmap() + * + * OMAP_DSP_MEM_IOCTL_EXMAP ioctl calls this function with padr=0. + * In this case, the buffer for DSP is allocated in this routine, + * then it is mapped. + * On the other hand, for example - frame buffer sharing, calls + * this function with padr set. It means some known address space + * pointed with padr is going to be shared with DSP. + */ +static int dsp_exmap(unsigned long dspadr, unsigned long padr, + unsigned long size, enum exmap_type type) +{ + unsigned short slst; + void *buf; + unsigned int order = 0; + unsigned long unit; + unsigned int cntnu = 0; + unsigned long _dspadr = dspadr; + unsigned long _padr = padr; + void *_vadr = dspbyte_to_virt(dspadr); + unsigned long _size = size; + struct exmap_tbl *exmap_ent; + int status; + int i; + +#define MINIMUM_PAGESZ SZ_4KB + /* + * alignment check + */ + if (!is_aligned(size, MINIMUM_PAGESZ)) { + printk(KERN_ERR + "omapdsp: size(0x%lx) is not multiple of 4KB.\n", size); + return -EINVAL; + } + if (!is_aligned(dspadr, MINIMUM_PAGESZ)) { + printk(KERN_ERR + "omapdsp: DSP address(0x%lx) is not aligned.\n", dspadr); + return -EINVAL; + } + if (!is_aligned(padr, MINIMUM_PAGESZ)) { + printk(KERN_ERR + "omapdsp: physical address(0x%lx) is not aligned.\n", + padr); + return -EINVAL; + } + + /* address validity check */ + if ((dspadr < dspmem_size) || + (dspadr >= DSPSPACE_SIZE) || + ((dspadr + size > DSP_INIT_PAGE) && + (dspadr < DSP_INIT_PAGE + PAGE_SIZE))) { + printk(KERN_ERR + "omapdsp: illegal address/size for dsp_exmap().\n"); + return -EINVAL; + } + + down_write(&exmap_sem); + + /* overlap check */ + for (i = 0; i < DSPMMU_TLB_LINES; i++) { + unsigned long mapsize; + struct exmap_tbl *tmp_ent = &exmap_tbl[i]; + + if (!tmp_ent->valid) + continue; + mapsize = 1 << (tmp_ent->order + PAGE_SHIFT); + if ((_vadr + size > tmp_ent->vadr) && + (_vadr < tmp_ent->vadr + mapsize)) { + printk(KERN_ERR "omapdsp: exmap page overlap!\n"); + up_write(&exmap_sem); + return -EINVAL; + } + } + +start: + buf = NULL; + /* Are there any free TLB lines? */ + for (i = 0; i < DSPMMU_TLB_LINES; i++) { + if (!exmap_tbl[i].valid) + goto found_free; + } + printk(KERN_ERR "omapdsp: DSP TLB is full.\n"); + status = -EBUSY; + goto fail; + +found_free: + exmap_ent = &exmap_tbl[i]; + + if ((_size >= SZ_1MB) && + (is_aligned(_padr, SZ_1MB) || (padr == 0)) && + is_aligned(_dspadr, SZ_1MB)) { + unit = SZ_1MB; + slst = DSPMMU_CAM_L_SLST_1MB; + order = ORDER_1MB; + } else if ((_size >= SZ_64KB) && + (is_aligned(_padr, SZ_64KB) || (padr == 0)) && + is_aligned(_dspadr, SZ_64KB)) { + unit = SZ_64KB; + slst = DSPMMU_CAM_L_SLST_64KB; + order = ORDER_64KB; + } else /* if (_size >= SZ_4KB) */ { + unit = SZ_4KB; + slst = DSPMMU_CAM_L_SLST_4KB; + order = ORDER_4KB; + } +#if 0 /* 1KB is not enabled */ + else if (_size >= SZ_1KB) { + unit = SZ_1KB; + slst = DSPMMU_CAM_L_SLST_1KB; + order = XXX; + } +#endif + + /* buffer allocation */ + if (type == EXMAP_TYPE_MEM) { + struct page *page, *ps, *pe; + + buf = (void *)dsp_mem_get_dma_pages(order); + if (buf == NULL) { + status = -ENOMEM; + goto fail; + } + /* mark the pages as reserved; this is needed for mmap */ + ps = virt_to_page(buf); + pe = virt_to_page(buf + unit); + for (page = ps; page < pe; page++) { + SetPageReserved(page); + } + _padr = __pa(buf); + } + + /* + * mapping for ARM MMU: + * we should not access to the allocated memory through 'buf' + * since this area should not be cashed. + */ + status = exmap_set_armmmu((unsigned long)_vadr, _padr, unit); + if (status < 0) + goto fail; + + /* loading DSP TLB entry */ + status = dsp_mmu_load_tlb(_dspadr, _padr, slst, 0, DSPMMU_RAM_L_AP_FA); + if (status < 0) { + exmap_clear_armmmu((unsigned long)_vadr, unit); + goto fail; + } + + exmap_ent->buf = buf; + exmap_ent->vadr = _vadr; + exmap_ent->order = order; + exmap_ent->valid = 1; + exmap_ent->cntnu = cntnu; + exmap_ent->type = type; + + if ((_size -= unit) == 0) { /* normal completion */ + up_write(&exmap_sem); + return size; + } + + _dspadr += unit; + _vadr += unit; + _padr = padr ? _padr + unit : 0; + cntnu = 1; + goto start; + +fail: + up_write(&exmap_sem); + if (buf) + dsp_mem_free_pages((unsigned long)buf, order); + dsp_exunmap(dspadr); + return status; +} + +static unsigned long unmap_free_arm(struct exmap_tbl *ent) +{ + unsigned long size; + + /* clearing ARM MMU */ + size = 1 << (ent->order + PAGE_SHIFT); + exmap_clear_armmmu((unsigned long)ent->vadr, size); + + /* freeing allocated memory */ + if (ent->type == EXMAP_TYPE_MEM) { + dsp_mem_free_pages((unsigned long)ent->buf, ent->order); + printk(KERN_DEBUG + "omapdsp: freeing 0x%lx bytes @ adr 0x%8p\n", + size, ent->buf); + } + + return size; +} + +static int dsp_exunmap(unsigned long dspadr) +{ + void *vadr; + unsigned long size; + int total = 0; + struct exmap_tbl *ent; + int idx; + + vadr = dspbyte_to_virt(dspadr); + down_write(&exmap_sem); + for (idx = 0; idx < DSPMMU_TLB_LINES; idx++) { + ent = &exmap_tbl[idx]; + if (!ent->valid) + continue; + if (ent->vadr == vadr) + goto found_map; + } + up_write(&exmap_sem); + printk(KERN_WARNING + "omapdsp: address %06lx not found in exmap_tbl.\n", dspadr); + return -EINVAL; + +found_map: + /* clearing DSP TLB entry */ + dsp_mmu_clear_tlb(dspadr); + + /* clear ARM MMU and free buffer */ + size = unmap_free_arm(ent); + ent->valid = 0; + total += size; + + /* we don't free PTEs */ + + /* flush TLB */ + flush_tlb_kernel_range((unsigned long)vadr, (unsigned long)vadr + size); + + /* check if next mapping is in same group */ + if (++idx == DSPMMU_TLB_LINES) + goto up_out; /* normal completion */ + ent = &exmap_tbl[idx]; + if (!ent->valid || !ent->cntnu) + goto up_out; /* normal completion */ + + dspadr += size; + vadr += size; + if (ent->vadr == vadr) + goto found_map; /* continue */ + + printk(KERN_ERR + "omapdsp: illegal exmap_tbl grouping!\n" + "expected vadr = %p, exmap_tbl[%d].vadr = %p\n", + vadr, idx, ent->vadr); + up_write(&exmap_sem); + return -EINVAL; + +up_out: + up_write(&exmap_sem); + return total; +} + +static void exmap_flush(void) +{ + struct exmap_tbl *ent; + int i; + + down_write(&exmap_sem); + + /* clearing DSP TLB entry */ + dsp_mmu_gflush(); + + /* exmap_tbl[0] should be preserved */ + for (i = 1; i < DSPMMU_TLB_LINES; i++) { + ent = &exmap_tbl[i]; + if (ent->valid) { + unmap_free_arm(ent); + ent->valid = 0; + } + } + + /* flush TLB */ + flush_tlb_kernel_range(dspmem_base + dspmem_size, + dspmem_base + DSPSPACE_SIZE); + /* + * we should clear processes' mm as well, + * because processes might had accessed to those spaces + * with old table in the past. + */ + up_write(&exmap_sem); +} + +#ifdef CONFIG_OMAP_DSP_FBEXPORT +#ifndef CONFIG_FB +#error You configured OMAP_DSP_FBEXPORT, but FB was not configured! +#endif /* CONFIG_FB */ + +static int dsp_fbexport(unsigned long *dspadr) +{ + unsigned long dspadr_actual; + unsigned long padr_sys, padr, fbsz_sys, fbsz; + int cnt; + + printk(KERN_DEBUG "omapdsp: frame buffer export\n"); + + if (num_registered_fb == 0) { + printk(KERN_INFO "omapdsp: frame buffer not registered.\n"); + return -EINVAL; + } + if (num_registered_fb != 1) { + printk(KERN_INFO + "omapdsp: %d frame buffers found. we use first one.\n", + num_registered_fb); + } + padr_sys = registered_fb[0]->fix.smem_start; + fbsz_sys = registered_fb[0]->fix.smem_len; + if (fbsz_sys == 0) { + printk(KERN_ERR + "omapdsp: framebuffer doesn't seem to be configured " + "correctly! (size=0)\n"); + return -EINVAL; + } + + /* + * align padr and fbsz to 4kB boundary + * (should be noted to the user afterwards!) + */ + padr = padr_sys & ~(SZ_4KB-1); + fbsz = (fbsz_sys + padr_sys - padr + SZ_4KB-1) & ~(SZ_4KB-1); + + /* line up dspadr offset with padr */ + dspadr_actual = + (fbsz > SZ_1MB) ? lineup_offset(*dspadr, padr, SZ_1MB-1) : + (fbsz > SZ_64KB) ? lineup_offset(*dspadr, padr, SZ_64KB-1) : + /* (fbsz > SZ_4KB) ? */ *dspadr; + if (dspadr_actual != *dspadr) + printk(KERN_DEBUG + "omapdsp: actual dspadr for FBEXPORT = %08lx\n", + dspadr_actual); + *dspadr = dspadr_actual; + + cnt = dsp_exmap(dspadr_actual, padr, fbsz, EXMAP_TYPE_FB); + if (cnt < 0) { + printk(KERN_ERR "omapdsp: exmap failure.\n"); + return cnt; + } + + if ((padr != padr_sys) || (fbsz != fbsz_sys)) { + printk(KERN_WARNING +" !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" +" !! screen base address or size is not aligned in 4kB: !!\n" +" !! actual screen adr = %08lx, size = %08lx !!\n" +" !! exporting adr = %08lx, size = %08lx !!\n" +" !! Make sure that the framebuffer is allocated with 4kB-order! !!\n" +" !! Otherwise DSP can corrupt the kernel memory. !!\n" +" !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n", + padr_sys, fbsz_sys, padr, fbsz); + } + + /* increase the DMA priority */ + set_emiff_dma_prio(15); + + return cnt; +} + +#else /* CONFIG_OMAP_DSP_FBEXPORT */ + +static int dsp_fbexport(unsigned long *dspadr) +{ + printk(KERN_ERR "omapdsp: FBEXPORT function is not enabled.\n"); + return -EINVAL; +} + +#endif /* CONFIG_OMAP_DSP_FBEXPORT */ + +static int dsp_mmu_itack(void) +{ + unsigned long dspadr; + + printk(KERN_INFO "omapdsp: sending DSP MMU interrupt ack.\n"); + if (!dsp_err_mmu_isset()) { + printk(KERN_ERR "omapdsp: DSP MMU error has not been set.\n"); + return -EINVAL; + } + dspadr = dsp_fault_adr & ~(SZ_4K-1); + dsp_exmap(dspadr, 0, SZ_4K, EXMAP_TYPE_MEM); /* FIXME: reserve TLB entry for this */ + printk(KERN_INFO "omapdsp: falling into recovery runlevel...\n"); + dsp_runlevel(OMAP_DSP_MBCMD_RUNLEVEL_RECOVERY); + __dsp_mmu_itack(); + udelay(100); + dsp_exunmap(dspadr); + dsp_err_mmu_clear(); + return 0; +} + +static void dsp_mmu_init(void) +{ + unsigned long phys; + void *virt; + + clk_use(dsp_ck_handle); + down_write(&exmap_sem); + + dsp_mmu_disable(); /* clear all */ + udelay(100); + dsp_mmu_enable(); + + /* mapping for ARM MMU */ + phys = __pa(dspvect_page); + virt = dspbyte_to_virt(DSP_INIT_PAGE); /* 0xe0fff000 */ + exmap_set_armmmu((unsigned long)virt, phys, PAGE_SIZE); + exmap_tbl[0].buf = dspvect_page; + exmap_tbl[0].vadr = virt; + exmap_tbl[0].order = 0; + exmap_tbl[0].valid = 1; + exmap_tbl[0].cntnu = 0; + + /* DSP TLB initialization */ + set_tlb_lock(0, 0); + /* preserved, full access */ + dsp_mmu_load_tlb(DSP_INIT_PAGE, phys, DSPMMU_CAM_L_SLST_4KB, + DSPMMU_CAM_L_P, DSPMMU_RAM_L_AP_FA); + up_write(&exmap_sem); + clk_unuse(dsp_ck_handle); +} + +static void dsp_mmu_shutdown(void) +{ + exmap_flush(); + dsp_mmu_disable(); /* clear all */ +} + +/* + * dsp_mem_enable() / disable(): + * if the address is in DSP internal memories, + * we send PM mailbox commands so that DSP DMA domain won't go in idle + * when ARM is accessing to those memories. + * if the address is in external memory, acquire exmap_sem. + * + * __dsp_mem_enable() / disable() should be called only from __dsp_mbsend(). + */ +static int dsp_mem_en_count; + +int dsp_mem_enable(void *adr) +{ + struct mbcmd mb; + int ret; + + if (is_dsp_internal_mem(adr)) { + if (dsp_is_ready() && (!dsp_mem_en_count) && + (dsp_icrmask & DSPREG_ICR_DMA_IDLE_DOMAIN)) { + mbcmd_set(mb, MBCMD(PM), OMAP_DSP_MBCMD_PM_ENABLE, + DSPREG_ICR_DMA_IDLE_DOMAIN); + if ((ret = dsp_mbsend(&mb)) < 0) + return ret; + dsp_mem_en_count++; + } + } else + down_read(&exmap_sem); + return 0; +} + +int dsp_mem_disable(void *adr) +{ + struct mbcmd mb; + int ret; + + if (is_dsp_internal_mem(adr)) { + if (dsp_is_ready() && dsp_mem_en_count) { + mbcmd_set(mb, MBCMD(PM), OMAP_DSP_MBCMD_PM_DISABLE, + DSPREG_ICR_DMA_IDLE_DOMAIN); + if ((ret = dsp_mbsend(&mb)) < 0) + return ret; + dsp_mem_en_count--; + } + } else + up_read(&exmap_sem); + return 0; +} + +int __dsp_mem_enable(void *adr) +{ + struct mbcmd mb; + int ret; + + if (is_dsp_internal_mem(adr)) { + if (dsp_is_ready() && (!dsp_mem_en_count) && + (dsp_icrmask & DSPREG_ICR_DMA_IDLE_DOMAIN)) { + mbcmd_set(mb, MBCMD(PM), OMAP_DSP_MBCMD_PM_ENABLE, + DSPREG_ICR_DMA_IDLE_DOMAIN); + if ((ret = __mbsend(&mb)) < 0) + return ret; + dsp_mem_en_count++; + } + } else + down_read(&exmap_sem); + return 0; +} + +int __dsp_mem_disable(void *adr) +{ + struct mbcmd mb; + int ret; + + if (is_dsp_internal_mem(adr)) { + if (dsp_is_ready() && dsp_mem_en_count) { + mbcmd_set(mb, MBCMD(PM), OMAP_DSP_MBCMD_PM_DISABLE, + DSPREG_ICR_DMA_IDLE_DOMAIN); + if ((ret = __mbsend(&mb)) < 0) + return ret; + dsp_mem_en_count--; + } + } else + up_read(&exmap_sem); + return 0; +} + +/* + * dsp_mem file operations + */ +static loff_t dsp_mem_lseek(struct file *file, loff_t offset, int orig) +{ + loff_t ret; + + down(&file->f_dentry->d_inode->i_sem); + switch (orig) { + case 0: + file->f_pos = offset; + ret = file->f_pos; + break; + case 1: + file->f_pos += offset; + ret = file->f_pos; + break; + default: + ret = -EINVAL; + } + up(&file->f_dentry->d_inode->i_sem); + return ret; +} + +static ssize_t intmem_read(struct file *file, char *buf, size_t count, + loff_t *ppos) +{ + unsigned long p = *ppos; + void *vadr = dspbyte_to_virt(p); + ssize_t size = dspmem_size; + ssize_t read; + + if (p >= size) + return 0; + clk_use(api_ck_handle); + read = count; + if (count > size - p) + read = size - p; + if (copy_to_user(buf, vadr, read)) { + read = -EFAULT; + goto finish; + } + *ppos += read; +finish: + clk_unuse(api_ck_handle); + return read; +} + +static ssize_t exmem_read(struct file *file, char *buf, size_t count, + loff_t *ppos) +{ + unsigned long p = *ppos; + void *vadr = dspbyte_to_virt(p); + ssize_t ret; + + down_read(&exmap_sem); + if (!exmap_valid(vadr, count)) { + printk(KERN_ERR + "omapdsp: DSP address %08lx / size %08x " + "is not valid!\n", p, count); + ret = -EFAULT; + goto up_out; + } + if (count > DSPSPACE_SIZE - p) + count = DSPSPACE_SIZE - p; + if (copy_to_user(buf, vadr, count)) { + ret = -EFAULT; + goto up_out; + } + *ppos += count; + + up_read(&exmap_sem); + return count; + +up_out: + up_read(&exmap_sem); + return ret; +} + +static ssize_t dsp_mem_read(struct file *file, char *buf, size_t count, + loff_t *ppos) +{ + if (is_dspbyte_internal_mem(*ppos)) + return intmem_read(file, buf, count, ppos); + else + return exmem_read(file, buf, count, ppos); +} + +static ssize_t intmem_write(struct file *file, const char *buf, size_t count, + loff_t *ppos) +{ + unsigned long p = *ppos; + void *vadr = dspbyte_to_virt(p); + ssize_t size = dspmem_size; + ssize_t written; + + if (p >= size) + return 0; + clk_use(api_ck_handle); + written = count; + if (count > size - p) + written = size - p; + if (copy_from_user(vadr, buf, written)) { + written = -EFAULT; + goto finish; + } + *ppos += written; +finish: + clk_unuse(api_ck_handle); + return written; +} + +static ssize_t exmem_write(struct file *file, const char *buf, size_t count, + loff_t *ppos) +{ + unsigned long p = *ppos; + void *vadr = dspbyte_to_virt(p); + ssize_t ret; + + down_read(&exmap_sem); + if (!exmap_valid(vadr, count)) { + printk(KERN_ERR + "omapdsp: DSP address %08lx / size %08x " + "is not valid!\n", p, count); + ret = -EFAULT; + goto up_out; + } + if (count > DSPSPACE_SIZE - p) + count = DSPSPACE_SIZE - p; + if (copy_from_user(vadr, buf, count)) { + ret = -EFAULT; + goto up_out; + } + *ppos += count; + + up_read(&exmap_sem); + return count; + +up_out: + up_read(&exmap_sem); + return ret; +} + +static ssize_t dsp_mem_write(struct file *file, const char *buf, size_t count, + loff_t *ppos) +{ + if (is_dspbyte_internal_mem(*ppos)) + return intmem_write(file, buf, count, ppos); + else + return exmem_write(file, buf, count, ppos); +} + +static int dsp_mem_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case OMAP_DSP_MEM_IOCTL_MMUINIT: + dsp_mmu_init(); + return 0; + + case OMAP_DSP_MEM_IOCTL_EXMAP: + { + struct omap_dsp_mapinfo mapinfo; + if (copy_from_user(&mapinfo, (void *)arg, + sizeof(mapinfo))) + return -EFAULT; + return dsp_exmap(mapinfo.dspadr, 0, mapinfo.size, + EXMAP_TYPE_MEM); + } + + case OMAP_DSP_MEM_IOCTL_EXUNMAP: + return dsp_exunmap((unsigned long)arg); + + case OMAP_DSP_MEM_IOCTL_EXMAP_FLUSH: + exmap_flush(); + return 0; + + case OMAP_DSP_MEM_IOCTL_FBEXPORT: + { + unsigned long dspadr; + int ret; + if (copy_from_user(&dspadr, (void *)arg, sizeof(long))) + return -EFAULT; + ret = dsp_fbexport(&dspadr); + if (copy_to_user((void *)arg, &dspadr, sizeof(long))) + return -EFAULT; + return ret; + } + + case OMAP_DSP_MEM_IOCTL_MMUITACK: + return dsp_mmu_itack(); + + case OMAP_DSP_MEM_IOCTL_KMEM_RESERVE: + { + unsigned long size; + if (copy_from_user(&size, (void *)arg, sizeof(long))) + return -EFAULT; + return dsp_kmem_reserve(size); + } + + case OMAP_DSP_MEM_IOCTL_KMEM_RELEASE: + dsp_kmem_release(); + return 0; + + default: + return -ENOIOCTLCMD; + } +} + +static int dsp_mem_mmap(struct file *file, struct vm_area_struct *vma) +{ + /* + * FIXME + */ + return -ENOSYS; +} + +static int dsp_mem_open(struct inode *inode, struct file *file) +{ + if (!capable(CAP_SYS_RAWIO)) + return -EPERM; + if (dsp_mem_enable((void *)dspmem_base) < 0) + return -EBUSY; + + return 0; +} + +static int dsp_mem_release(struct inode *inode, struct file *file) +{ + dsp_mem_disable((void *)dspmem_base); + return 0; +} + +/* + * sysfs files + */ +static ssize_t mmu_show(struct device *dev, char *buf) +{ + int len; + int lbase, victim; + int i; + + clk_use(dsp_ck_handle); + down_read(&exmap_sem); + + get_tlb_lock(&lbase, &victim); + + len = sprintf(buf, "p: preserved, v: valid\n" + "ety cam_va ram_pa sz ap\n"); + /* 00: p v 0x300000 0x10171800 64KB FA */ + for (i = 0; i < 32; i++) { + unsigned short cam_h, cam_l, ram_h, ram_l; + unsigned short cam_l_va_mask, prsvd, cam_vld, slst; + unsigned long cam_va; + unsigned short ram_l_ap; + unsigned long ram_pa; + char *pgsz_str, *ap_str; + + /* read a TLB entry */ + __read_tlb(lbase, i, &cam_h, &cam_l, &ram_h, &ram_l); + + slst = cam_l & DSPMMU_CAM_L_SLST_MASK; + cam_l_va_mask = get_cam_l_va_mask(slst); + pgsz_str = (slst == DSPMMU_CAM_L_SLST_1MB) ? " 1MB": + (slst == DSPMMU_CAM_L_SLST_64KB)? "64KB": + (slst == DSPMMU_CAM_L_SLST_4KB) ? " 4KB": + " 1KB"; + prsvd = cam_l & DSPMMU_CAM_L_P; + cam_vld = cam_l & DSPMMU_CAM_L_V; + ram_l_ap = ram_l & DSPMMU_RAM_L_AP_MASK; + ap_str = (ram_l_ap == DSPMMU_RAM_L_AP_RO) ? "RO": + (ram_l_ap == DSPMMU_RAM_L_AP_FA) ? "FA": + "NA"; + cam_va = (unsigned long)(cam_h & DSPMMU_CAM_H_VA_TAG_H_MASK) << 22 | + (unsigned long)(cam_l & cam_l_va_mask) << 6; + ram_pa = (unsigned long)ram_h << 16 | + (ram_l & DSPMMU_RAM_L_RAM_LSB_MASK); + + if (i == lbase) + len += sprintf(buf + len, "lock base = %d\n", lbase); + if (i == victim) + len += sprintf(buf + len, "victim = %d\n", victim); + /* 00: p v 0x300000 0x10171800 64KB FA */ + len += sprintf(buf + len, + "%02d: %c %c 0x%06lx 0x%08lx %s %s\n", + i, + prsvd ? 'p' : ' ', + cam_vld ? 'v' : ' ', + cam_va, ram_pa, pgsz_str, ap_str); + } + + /* restore victim entry */ + set_tlb_lock(lbase, victim); + + up_read(&exmap_sem); + clk_unuse(dsp_ck_handle); + return len; +} + +static struct device_attribute dev_attr_mmu = __ATTR_RO(mmu); + +static ssize_t exmap_show(struct device *dev, char *buf) +{ + int len; + int i; + + down_read(&exmap_sem); + len = sprintf(buf, "v: valid, c: cntnu\n" + "ety vadr buf od\n"); + /* 00: v c 0xe0300000 0xc0171800 0 */ + for (i = 0; i < DSPMMU_TLB_LINES; i++) { + struct exmap_tbl *ent = &exmap_tbl[i]; + /* 00: v c 0xe0300000 0xc0171800 0 */ + len += sprintf(buf + len, "%02d: %c %c 0x%8p 0x%8p %2d\n", + i, + ent->valid ? 'v' : ' ', + ent->cntnu ? 'c' : ' ', + ent->vadr, ent->buf, ent->order); + } + + up_read(&exmap_sem); + return len; +} + +static struct device_attribute dev_attr_exmap = __ATTR_RO(exmap); + +/* + * DSP MMU interrupt handler + */ + +/* + * MMU fault mask: + * We ignore prefetch err. + */ +#define MMUFAULT_MASK \ + (DSPMMU_FAULT_ST_PERM |\ + DSPMMU_FAULT_ST_TLB_MISS |\ + DSPMMU_FAULT_ST_TRANS) +irqreturn_t dsp_mmu_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + unsigned short status; + unsigned short adh, adl; + unsigned short dp; + + status = omap_readw(DSPMMU_FAULT_ST); + adh = omap_readw(DSPMMU_FAULT_AD_H); + adl = omap_readw(DSPMMU_FAULT_AD_L); + dp = adh & DSPMMU_FAULT_AD_H_DP; + dsp_fault_adr = MKLONG(adh & DSPMMU_FAULT_AD_H_ADR_MASK, adl); + /* if the fault is masked, nothing to do */ + if ((status & MMUFAULT_MASK) == 0) { + printk(KERN_DEBUG "DSP MMU interrupt, but ignoring.\n"); + /* + * note: in OMAP1710, + * when CACHE + DMA domain gets out of idle in DSP, + * MMU interrupt occurs but DSPMMU_FAULT_ST is not set. + * in this case, we just ignore the interrupt. + */ + if (status) { + printk(KERN_DEBUG "%s%s%s%s\n", + (status & DSPMMU_FAULT_ST_PREF)? + " (prefetch err)" : "", + (status & DSPMMU_FAULT_ST_PERM)? + " (permission fault)" : "", + (status & DSPMMU_FAULT_ST_TLB_MISS)? + " (TLB miss)" : "", + (status & DSPMMU_FAULT_ST_TRANS) ? + " (translation fault)": ""); + printk(KERN_DEBUG + "fault address = %s: 0x%06lx\n", + dp ? "DATA" : "PROGRAM", + dsp_fault_adr); + } + return IRQ_HANDLED; + } + + printk(KERN_INFO "DSP MMU interrupt!\n"); + printk(KERN_INFO "%s%s%s%s\n", + (status & DSPMMU_FAULT_ST_PREF)? + (MMUFAULT_MASK & DSPMMU_FAULT_ST_PREF)? + " prefetch err": + " (prefetch err)": + "", + (status & DSPMMU_FAULT_ST_PERM)? + (MMUFAULT_MASK & DSPMMU_FAULT_ST_PERM)? + " permission fault": + " (permission fault)": + "", + (status & DSPMMU_FAULT_ST_TLB_MISS)? + (MMUFAULT_MASK & DSPMMU_FAULT_ST_TLB_MISS)? + " TLB miss": + " (TLB miss)": + "", + (status & DSPMMU_FAULT_ST_TRANS)? + (MMUFAULT_MASK & DSPMMU_FAULT_ST_TRANS)? + " translation fault": + " (translation fault)": + ""); + printk(KERN_INFO "fault address = %s: 0x%06lx\n", + dp ? "DATA" : "PROGRAM", + dsp_fault_adr); + + if (dsp_is_ready()) { + /* + * If we call dsp_exmap() here, + * "kernel BUG at slab.c" occurs. + */ + /* FIXME */ + dsp_err_mmu_set(dsp_fault_adr); + } else { + printk(KERN_INFO "Resetting DSP...\n"); + __dsp_reset(); + clk_unuse(api_ck_handle); + /* + * if we enable followings, semaphore lock should be avoided. + * + printk(KERN_INFO "Flushing DSP MMU...\n"); + exmap_flush(); + dsp_mmu_init(); + */ + } + + return IRQ_HANDLED; +} + +/* + * + */ +struct file_operations dsp_mem_fops = { + .owner = THIS_MODULE, + .llseek = dsp_mem_lseek, + .read = dsp_mem_read, + .write = dsp_mem_write, + .ioctl = dsp_mem_ioctl, + .mmap = dsp_mem_mmap, + .open = dsp_mem_open, + .release = dsp_mem_release, +}; + +void dsp_mem_start(void) +{ + dsp_mem_en_count = 0; +} + +int __init dsp_mem_init(void) +{ + int i; + + for (i = 0; i < DSPMMU_TLB_LINES; i++) { + exmap_tbl[i].valid = 0; + } + + dspvect_page = (void *)__get_dma_pages(GFP_KERNEL, 0); + if (dspvect_page == NULL) { + printk(KERN_ERR + "omapdsp: failed to allocate memory " + "for dsp vector table\n"); + return -ENOMEM; + } + dsp_mmu_init(); + dsp_set_idle_boot_base(IDLEPG_BASE, IDLEPG_SIZE); + + device_create_file(&dsp_device.dev, &dev_attr_mmu); + device_create_file(&dsp_device.dev, &dev_attr_exmap); + + return 0; +} + +void dsp_mem_exit(void) +{ + dsp_mmu_shutdown(); + dsp_kmem_release(); + + if (dspvect_page != NULL) { + unsigned long virt; + pmd_t *pmdp; + pte_t *ptep; + + free_page((unsigned long)dspvect_page); + dspvect_page = NULL; + + virt = (unsigned long)dspbyte_to_virt(DSP_INIT_PAGE); + pmdp = pmd_offset(pgd_offset_k(virt), virt); + ptep = pte_offset_kernel(pmdp, 0); + pmd_clear(pmdp); + pte_free_kernel(ptep); + } + + device_remove_file(&dsp_device.dev, &dev_attr_mmu); + device_remove_file(&dsp_device.dev, &dev_attr_exmap); +} diff --git a/arch/arm/mach-omap/dsp/error.c b/arch/arm/mach-omap/dsp/error.c new file mode 100644 index 00000000000..ac6d8d84d9b --- /dev/null +++ b/arch/arm/mach-omap/dsp/error.c @@ -0,0 +1,195 @@ +/* + * linux/arch/arm/mach-omap/dsp/error.c + * + * OMAP DSP error detection I/F device driver + * + * Copyright (C) 2002-2005 Nokia Corporation + * + * Written by Toshihiro Kobayashi + * + * 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 + * + * 2004/11/22: DSP Gateway version 3.2 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dsp.h" + +static DECLARE_WAIT_QUEUE_HEAD(err_wait_q); +static unsigned long errcode; +static int errcnt; +static unsigned short wdtval; /* FIXME: read through ioctl */ +static unsigned long mmu_fadr; /* FIXME: read through ioctl */ + +/* + * DSP error detection device file operations + */ +static ssize_t dsp_err_read(struct file *file, char *buf, size_t count, + loff_t *ppos) +{ + unsigned long flags; + int status; + + if (count < 4) + return 0; + + if (errcnt == 0) { + long current_state; + DECLARE_WAITQUEUE(wait, current); + + add_wait_queue(&err_wait_q, &wait); + current_state = current->state; + set_current_state(TASK_INTERRUPTIBLE); + if (errcnt == 0) /* last check */ + schedule(); + set_current_state(current_state); + remove_wait_queue(&err_wait_q, &wait); + if (signal_pending(current)) + return -EINTR; + } + + local_irq_save(flags); + status = copy_to_user(buf, &errcode, 4); + if (status) { + local_irq_restore(flags); + return -EFAULT; + } + errcnt = 0; + local_irq_restore(flags); + + return 4; +} + +static unsigned int dsp_err_poll(struct file *file, poll_table *wait) +{ + unsigned int mask = 0; + + poll_wait(file, &err_wait_q, wait); + if (errcnt != 0) + mask |= POLLIN | POLLRDNORM; + + return mask; +} + +struct file_operations dsp_err_fops = { + .owner = THIS_MODULE, + .poll = dsp_err_poll, + .read = dsp_err_read, +}; + +/* + * DSP MMU + */ +void dsp_err_mmu_set(unsigned long adr) +{ + disable_irq(INT_DSP_MMU); + errcode |= OMAP_DSP_ERRDT_MMU; + errcnt++; + mmu_fadr = adr; + wake_up_interruptible(&err_wait_q); +} + +void dsp_err_mmu_clear(void) +{ + errcode &= ~OMAP_DSP_ERRDT_MMU; + enable_irq(INT_DSP_MMU); +} + +int dsp_err_mmu_isset(void) +{ + return (errcode & OMAP_DSP_ERRDT_MMU) ? 1 : 0; +} + +/* + * WDT + */ +void dsp_err_wdt_clear(void) +{ + errcode &= ~OMAP_DSP_ERRDT_WDT; +} + +int dsp_err_wdt_isset(void) +{ + return (errcode & OMAP_DSP_ERRDT_WDT) ? 1 : 0; +} + +/* + * functions called from mailbox1 interrupt routine + */ +void mbx1_wdt(struct mbcmd *mb) +{ + printk(KERN_WARNING "omapdsp: DSP WDT expired!\n"); + errcode |= OMAP_DSP_ERRDT_WDT; + errcnt++; + wdtval = mb->data; + wake_up_interruptible(&err_wait_q); +} + +extern void mbx1_err_ipbfull(void); +extern void mbx1_err_fatal(unsigned char tid); + +void mbx1_err(struct mbcmd *mb) +{ + unsigned char eid = mb->cmd_l; + char *eidnm = subcmd_name(mb); + unsigned char tid; + + if (eidnm) { + printk(KERN_WARNING + "mbx: ERR from DSP (%s): 0x%04x\n", eidnm, mb->data); + } else { + printk(KERN_WARNING + "mbx: ERR from DSP (unknown EID=%02x): %04x\n", + eid, mb->data); + } + + switch (eid) { + case OMAP_DSP_EID_IPBFULL: + mbx1_err_ipbfull(); + break; + + case OMAP_DSP_EID_FATAL: + tid = mb->data & 0x00ff; + mbx1_err_fatal(tid); + break; + } +} + +/* + * + */ +void dsp_err_start(void) +{ + errcnt = 0; + if (dsp_err_wdt_isset()) + dsp_err_wdt_clear(); + if (dsp_err_mmu_isset()) + dsp_err_mmu_clear(); +} + +void dsp_err_stop(void) +{ + wake_up_interruptible(&err_wait_q); +} diff --git a/arch/arm/mach-omap/dsp/fifo.h b/arch/arm/mach-omap/dsp/fifo.h new file mode 100644 index 00000000000..0f890697810 --- /dev/null +++ b/arch/arm/mach-omap/dsp/fifo.h @@ -0,0 +1,179 @@ +/* + * linux/arch/arm/mach-omap/dsp/fifo.h + * + * FIFO buffer operators + * + * Copyright (C) 2002-2005 Nokia Corporation + * + * Written by Toshihiro Kobayashi + * + * 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 + * + * 2005/02/24: DSP Gateway version 3.2 + */ + +struct fifo_struct { + spinlock_t lock; + char *buf; + size_t sz; + size_t cnt; + unsigned int wp; +}; + +static inline int alloc_fifo(struct fifo_struct *fifo, size_t sz) +{ + if ((fifo->buf = kmalloc(sz, GFP_KERNEL)) == NULL) { + fifo->sz = 0; + return -ENOMEM; + } + fifo->sz = sz; + fifo->cnt = 0; + fifo->wp = 0; + return 0; +} + +static inline int init_fifo(struct fifo_struct *fifo, size_t sz) +{ + spin_lock_init(&fifo->lock); + return alloc_fifo(fifo, sz); +} + +static inline void free_fifo(struct fifo_struct *fifo) +{ + spin_lock(&fifo->lock); + if (fifo->buf == NULL) + return; + + kfree(fifo->buf); + fifo->buf = NULL; + fifo->sz = 0; + spin_unlock(&fifo->lock); +} + +static inline void flush_fifo(struct fifo_struct *fifo) +{ + spin_lock(&fifo->lock); + fifo->cnt = 0; + fifo->wp = 0; + spin_unlock(&fifo->lock); +} + +#define fifo_empty(fifo) ((fifo)->cnt == 0) + +static inline int realloc_fifo(struct fifo_struct *fifo, size_t sz) +{ + int ret = sz; + + spin_lock(&fifo->lock); + if (!fifo_empty(fifo)) { + ret = -EBUSY; + goto out; + } + + /* free */ + if (fifo->buf) + kfree(fifo->buf); + + /* alloc */ + if ((fifo->buf = kmalloc(sz, GFP_KERNEL)) == NULL) { + fifo->sz = 0; + ret = -ENOMEM; + goto out; + } + fifo->sz = sz; + fifo->cnt = 0; + fifo->wp = 0; + +out: + spin_unlock(&fifo->lock); + return ret; +} + +static inline void write_word_to_fifo(struct fifo_struct *fifo, + unsigned short word) +{ + spin_lock(&fifo->lock); + *(unsigned short *)&fifo->buf[fifo->wp] = word; + if ((fifo->wp += 2) == fifo->sz) + fifo->wp = 0; + if ((fifo->cnt += 2) > fifo->sz) + fifo->cnt = fifo->sz; + spin_unlock(&fifo->lock); +} + +/* + * (before) + * + * [*******----------*************] + * ^wp + * <----------------------------> sz = 30 + * <-----> <-----------> cnt = 20 + * + * (read: count=16) + * <-> <-----------> count = 16 + * <-----------> cnt1 = 13 + * ^rp + * + * (after) + * [---****-----------------------] + * ^wp + */ +static inline ssize_t copy_to_user_fm_fifo(char *dst, struct fifo_struct *fifo, + size_t count) +{ + int rp; + ssize_t ret; + + /* fifo size can be zero */ + if (fifo->sz == 0) + return 0; + + spin_lock(&fifo->lock); + if (count > fifo->cnt) + count = fifo->cnt; + + if ((rp = fifo->wp - fifo->cnt) >= 0) { + /* valid area is straight */ + if (copy_to_user(dst, &fifo->buf[rp], count)) { + ret = -EFAULT; + goto out; + } + } else { + int cnt1 = -rp; + rp += fifo->sz; + if (cnt1 >= count) { + /* requested area is straight */ + if (copy_to_user(dst, &fifo->buf[rp], count)) { + ret = -EFAULT; + goto out; + } + } else { + if (copy_to_user(dst, &fifo->buf[rp], cnt1)) { + ret = -EFAULT; + goto out; + } + if (copy_to_user(dst+cnt1, fifo->buf, count-cnt1)) { + ret = -EFAULT; + goto out; + } + } + } + fifo->cnt -= count; + ret = count; + +out: + spin_unlock(&fifo->lock); + return ret; +} diff --git a/arch/arm/mach-omap/dsp/hardware_dsp.h b/arch/arm/mach-omap/dsp/hardware_dsp.h new file mode 100644 index 00000000000..4c70dd1ccf8 --- /dev/null +++ b/arch/arm/mach-omap/dsp/hardware_dsp.h @@ -0,0 +1,196 @@ +/* + * linux/arch/arm/mach-omap/dsp/hardware_dsp.h + * + * Register bit definitions for DSP driver + * + * Copyright (C) 2002-2005 Nokia Corporation + * + * Written by Toshihiro Kobayashi + * + * 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 + * + * 2004/09/30: DSP Gateway version 3.2 + */ + +#ifndef __OMAP_DSP_HARDWARE_DSP_H +#define __OMAP_DSP_HARDWARE_DSP_H + +/* + * MAJOR device number: !! allocated arbitrary !! + */ +#define OMAP_DSP_CTL_MAJOR 96 +#define OMAP_DSP_TASK_MAJOR 97 + +/* + * Reset Control + */ +#define ARM_RSTCT1_SW_RST 0x0008 +#define ARM_RSTCT1_DSP_RST 0x0004 +#define ARM_RSTCT1_DSP_EN 0x0002 +#define ARM_RSTCT1_ARM_RST 0x0001 + +/* + * MPUI + */ +#define MPUI_CTRL_WORDSWAP_MASK 0x00600000 +#define MPUI_CTRL_WORDSWAP_ALL 0x00000000 +#define MPUI_CTRL_WORDSWAP_NONAPI 0x00200000 +#define MPUI_CTRL_WORDSWAP_API 0x00400000 +#define MPUI_CTRL_WORDSWAP_NONE 0x00600000 +#define MPUI_CTRL_AP_MASK 0x001c0000 +#define MPUI_CTRL_AP_MDH 0x00000000 +#define MPUI_CTRL_AP_MHD 0x00040000 +#define MPUI_CTRL_AP_DMH 0x00080000 +#define MPUI_CTRL_AP_HMD 0x000c0000 +#define MPUI_CTRL_AP_DHM 0x00100000 +#define MPUI_CTRL_AP_HDM 0x00140000 +#define MPUI_CTRL_BYTESWAP_MASK 0x00030000 +#define MPUI_CTRL_BYTESWAP_NONE 0x00000000 +#define MPUI_CTRL_BYTESWAP_NONAPI 0x00010000 +#define MPUI_CTRL_BYTESWAP_ALL 0x00020000 +#define MPUI_CTRL_BYTESWAP_API 0x00030000 +#define MPUI_CTRL_TIMEOUT_MASK 0x0000ff00 +#define MPUI_CTRL_APIF_HNSTB_DIV_MASK 0x000000f0 +#define MPUI_CTRL_S_NABORT_GL 0x00000008 +#define MPUI_CTRL_S_NABORT_32BIT 0x00000004 +#define MPUI_CTRL_EN_TIMEOUT 0x00000002 +#define MPUI_CTRL_HF_MCUCLK 0x00000001 +#define MPUI_DSP_BOOT_CONFIG_DIRECT 0x00000000 +#define MPUI_DSP_BOOT_CONFIG_PSD_DIRECT 0x00000001 +#define MPUI_DSP_BOOT_CONFIG_IDLE 0x00000002 +#define MPUI_DSP_BOOT_CONFIG_DL16 0x00000003 +#define MPUI_DSP_BOOT_CONFIG_DL32 0x00000004 +#define MPUI_DSP_BOOT_CONFIG_MPUI 0x00000005 +#define MPUI_DSP_BOOT_CONFIG_INTERNAL 0x00000006 + +/* + * DSP boot mode + * direct: 0xffff00 + * pseudo direct: 0x080000 + * MPUI: branch 0x010000 + * internel: branch 0x024000 + */ +#define DSP_BOOT_ADR_DIRECT 0xffff00 +#define DSP_BOOT_ADR_PSD_DIRECT 0x080000 +#define DSP_BOOT_ADR_MPUI 0x010000 +#define DSP_BOOT_ADR_INTERNAL 0x024000 + +/* + * TC + */ +#define TC_ENDIANISM_SWAP 0x00000002 +#define TC_ENDIANISM_SWAP_WORD 0x00000002 +#define TC_ENDIANISM_SWAP_BYTE 0x00000000 +#define TC_ENDIANISM_EN 0x00000001 + +/* + * DSP MMU + */ +#define DSPMMU_BASE (0xfffed200) +#define DSPMMU_PREFETCH (DSPMMU_BASE + 0x00) +#define DSPMMU_WALKING_ST (DSPMMU_BASE + 0x04) +#define DSPMMU_CNTL (DSPMMU_BASE + 0x08) +#define DSPMMU_FAULT_AD_H (DSPMMU_BASE + 0x0c) +#define DSPMMU_FAULT_AD_L (DSPMMU_BASE + 0x10) +#define DSPMMU_FAULT_ST (DSPMMU_BASE + 0x14) +#define DSPMMU_IT_ACK (DSPMMU_BASE + 0x18) +#define DSPMMU_TTB_H (DSPMMU_BASE + 0x1c) +#define DSPMMU_TTB_L (DSPMMU_BASE + 0x20) +#define DSPMMU_LOCK (DSPMMU_BASE + 0x24) +#define DSPMMU_LD_TLB (DSPMMU_BASE + 0x28) +#define DSPMMU_CAM_H (DSPMMU_BASE + 0x2c) +#define DSPMMU_CAM_L (DSPMMU_BASE + 0x30) +#define DSPMMU_RAM_H (DSPMMU_BASE + 0x34) +#define DSPMMU_RAM_L (DSPMMU_BASE + 0x38) +#define DSPMMU_GFLUSH (DSPMMU_BASE + 0x3c) +#define DSPMMU_FLUSH_ENTRY (DSPMMU_BASE + 0x40) +#define DSPMMU_READ_CAM_H (DSPMMU_BASE + 0x44) +#define DSPMMU_READ_CAM_L (DSPMMU_BASE + 0x48) +#define DSPMMU_READ_RAM_H (DSPMMU_BASE + 0x4c) +#define DSPMMU_READ_RAM_L (DSPMMU_BASE + 0x50) + +#define DSPMMU_CNTL_BURST_16MNGT_EN 0x0020 +#define DSPMMU_CNTL_WTL_EN 0x0004 +#define DSPMMU_CNTL_MMU_EN 0x0002 +#define DSPMMU_CNTL_RESET_SW 0x0001 + +#define DSPMMU_FAULT_AD_H_DP 0x0100 +#define DSPMMU_FAULT_AD_H_ADR_MASK 0x00ff + +#define DSPMMU_FAULT_ST_PREF 0x0008 +#define DSPMMU_FAULT_ST_PERM 0x0004 +#define DSPMMU_FAULT_ST_TLB_MISS 0x0002 +#define DSPMMU_FAULT_ST_TRANS 0x0001 + +#define DSPMMU_IT_ACK_IT_ACK 0x0001 + +#define DSPMMU_LOCK_BASE_MASK 0xfc00 +#define DSPMMU_LOCK_BASE_SHIFT 10 +#define DSPMMU_LOCK_VICTIM_MASK 0x03f0 +#define DSPMMU_LOCK_VICTIM_SHIFT 4 + +#define DSPMMU_CAM_H_VA_TAG_H_MASK 0x0003 + +#define DSPMMU_CAM_L_VA_TAG_L1_MASK 0xc000 +#define DSPMMU_CAM_L_VA_TAG_L2_MASK_1MB 0x0000 +#define DSPMMU_CAM_L_VA_TAG_L2_MASK_64KB 0x3c00 +#define DSPMMU_CAM_L_VA_TAG_L2_MASK_4KB 0x3fc0 +#define DSPMMU_CAM_L_VA_TAG_L2_MASK_1KB 0x3ff0 +#define DSPMMU_CAM_L_P 0x0008 +#define DSPMMU_CAM_L_V 0x0004 +#define DSPMMU_CAM_L_SLST_MASK 0x0003 +#define DSPMMU_CAM_L_SLST_1MB 0x0000 +#define DSPMMU_CAM_L_SLST_64KB 0x0001 +#define DSPMMU_CAM_L_SLST_4KB 0x0002 +#define DSPMMU_CAM_L_SLST_1KB 0x0003 + +#define DSPMMU_RAM_L_RAM_LSB_MASK 0xfc00 +#define DSPMMU_RAM_L_AP_MASK 0x0300 +#define DSPMMU_RAM_L_AP_NA 0x0000 +#define DSPMMU_RAM_L_AP_RO 0x0200 +#define DSPMMU_RAM_L_AP_FA 0x0300 + +#define DSPMMU_GFLUSH_GFLUSH 0x0001 + +#define DSPMMU_FLUSH_ENTRY_FLUSH_ENTRY 0x0001 + +#define DSPMMU_LD_TLB_RD 0x0002 +#define DSPMMU_LD_TLB_LD 0x0001 + +/* + * Mailbox + */ +#define MAILBOX_BASE (0xfffcf000) +#define MAILBOX_ARM2DSP1 (MAILBOX_BASE + 0x00) +#define MAILBOX_ARM2DSP1b (MAILBOX_BASE + 0x04) +#define MAILBOX_DSP2ARM1 (MAILBOX_BASE + 0x08) +#define MAILBOX_DSP2ARM1b (MAILBOX_BASE + 0x0c) +#define MAILBOX_DSP2ARM2 (MAILBOX_BASE + 0x10) +#define MAILBOX_DSP2ARM2b (MAILBOX_BASE + 0x14) +#define MAILBOX_ARM2DSP1_Flag (MAILBOX_BASE + 0x18) +#define MAILBOX_DSP2ARM1_Flag (MAILBOX_BASE + 0x1c) +#define MAILBOX_DSP2ARM2_Flag (MAILBOX_BASE + 0x20) + +/* + * DSP ICR + */ +#define DSPREG_ICR_EMIF_IDLE_DOMAIN 0x0020 +#define DSPREG_ICR_DPLL_IDLE_DOMAIN 0x0010 +#define DSPREG_ICR_PER_IDLE_DOMAIN 0x0008 +#define DSPREG_ICR_CACHE_IDLE_DOMAIN 0x0004 +#define DSPREG_ICR_DMA_IDLE_DOMAIN 0x0002 +#define DSPREG_ICR_CPU_IDLE_DOMAIN 0x0001 + +#endif /* __OMAP_DSP_HARDWARE_DSP_H */ diff --git a/arch/arm/mach-omap/dsp/ipbuf.c b/arch/arm/mach-omap/dsp/ipbuf.c new file mode 100644 index 00000000000..9926dadc0b6 --- /dev/null +++ b/arch/arm/mach-omap/dsp/ipbuf.c @@ -0,0 +1,291 @@ +/* + * linux/arch/arm/mach-omap/dsp/ipbuf.c + * + * IPBUF handler + * + * Copyright (C) 2002-2005 Nokia Corporation + * + * Written by Toshihiro Kobayashi + * + * 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 + * + * 2005/02/17: DSP Gateway version 3.2 + */ + +#include +#include +#include +#include +#include +#include +#include +#include "dsp.h" +#include "ipbuf.h" + +struct ipbuf **ipbuf; +struct ipbcfg ipbcfg; +struct ipbuf_sys *ipbuf_sys_da, *ipbuf_sys_ad; +static struct ipblink ipb_free = IPBLINK_INIT; + +void ipbuf_stop(void) +{ + ipbcfg.ln = 0; + if (ipbuf) { + kfree(ipbuf); + ipbuf = NULL; + } +} + +/* + * ipbuf_config() is called by mailbox workqueue + */ +int ipbuf_config(unsigned short ln, unsigned short lsz, unsigned long adr) +{ + void *base; + unsigned long lsz_byte = ((unsigned long)lsz) << 1; + size_t size; + int ret = 0; + int i; + + spin_lock(&ipb_free.lock); + INIT_IPBLINK(&ipb_free); + spin_unlock(&ipb_free.lock); + + /* + * global IPBUF + */ + if (adr & 0x1) { + printk(KERN_ERR + "mbx: global ipbuf address (0x%08lx) is odd number!\n", + adr); + return -EINVAL; + } + size = lsz_byte * ln; + if (adr + size > DSPSPACE_SIZE) { + printk(KERN_ERR + "mbx: ipbuf address (0x%08lx) and size (0x%08x) is " + "illegal!\n", adr, size); + return -EINVAL; + } + base = dspword_to_virt(adr); + ipbuf = kmalloc(sizeof(void *) * ln, GFP_KERNEL); + if (ipbuf == NULL) { + printk(KERN_ERR "mbx: memory allocation for ipbuf failed.\n"); + return -ENOMEM; + } + for (i = 0; i < ln; i++) { + void *top, *btm; + + top = base + (sizeof(struct ipbuf) + lsz_byte) * i; + btm = base + (sizeof(struct ipbuf) + lsz_byte) * (i+1) - 1; + ipbuf[i] = (struct ipbuf *)top; + if (((unsigned long)top & 0xfffe0000) != + ((unsigned long)btm & 0xfffe0000)) { + /* + * an ipbuf line should not cross + * 64k-word boundary. + */ + printk(KERN_ERR + "omapdsp: ipbuf[%d] crosses 64k-word boundary!\n" + " @0x%p, size=0x%08lx\n", i, top, lsz_byte); + ret = -EINVAL; + } + } + ipbcfg.ln = ln; + ipbcfg.lsz = lsz; + ipbcfg.adr = adr; + ipbcfg.bsycnt = ln; /* DSP holds all ipbufs initially. */ + ipbcfg.cnt_full = 0; + + printk(KERN_INFO + "omapdsp: IPBUF configuration\n" + " %d words * %d lines at 0x%p.\n", + ipbcfg.lsz, ipbcfg.ln, dspword_to_virt(ipbcfg.adr)); + + return ret; +} + +/* + * Global IPBUF operations + */ +unsigned short get_free_ipbuf(unsigned char tid) +{ + unsigned short bid; + + if (ipblink_empty(&ipb_free)) { + /* FIXME: wait on queue when not available. */ + return OMAP_DSP_BID_NULL; + } + + /* + * FIXME: dsp_enable_dspmem! + */ + spin_lock(&ipb_free.lock); + bid = ipb_free.top; + ipbuf[bid]->la = tid; /* lock */ + ipblink_del_top(&ipb_free, ipbuf); + spin_unlock(&ipb_free.lock); + + return bid; +} + +void release_ipbuf(unsigned short bid) +{ + if (ipbuf[bid]->la == OMAP_DSP_TID_FREE) { + printk(KERN_WARNING + "omapdsp: attempt to release unlocked IPBUF[%d].\n", + bid); + /* + * FIXME: re-calc bsycnt + */ + return; + } + ipbuf[bid]->la = OMAP_DSP_TID_FREE; + ipbuf[bid]->sa = OMAP_DSP_TID_FREE; + spin_lock(&ipb_free.lock); + ipblink_add_tail(&ipb_free, bid, ipbuf); + spin_unlock(&ipb_free.lock); +} + +static int try_yld(unsigned short bid) +{ + struct mbcmd mb; + int status; + + ipbuf[bid]->sa = OMAP_DSP_TID_ANON; + mbcmd_set(mb, MBCMD(BKYLD), 0, bid); + status = dsp_mbsend(&mb); + if (status < 0) { + /* DSP is busy and ARM keeps this line. */ + release_ipbuf(bid); + return status; + } + + ipb_bsycnt_inc(&ipbcfg); + return 0; +} + +/* + * balancing ipbuf lines with DSP + */ +static void do_balance_ipbuf(void) +{ + while (ipbcfg.bsycnt <= ipbcfg.ln / 4) { + unsigned short bid; + + bid = get_free_ipbuf(OMAP_DSP_TID_ANON); + if (bid == OMAP_DSP_BID_NULL) + return; + if (try_yld(bid) < 0) + return; + } +} + +static DECLARE_WORK(balance_ipbuf_work, (void (*)(void *))do_balance_ipbuf, + NULL); + +void balance_ipbuf(void) +{ + schedule_work(&balance_ipbuf_work); +} + +/* for process context */ +void unuse_ipbuf(unsigned short bid) +{ + if (ipbcfg.bsycnt > ipbcfg.ln / 4) { + /* we don't have enough IPBUF lines. let's keep it. */ + release_ipbuf(bid); + } else { + /* we have enough IPBUF lines. let's return this line to DSP. */ + ipbuf[bid]->la = OMAP_DSP_TID_ANON; + try_yld(bid); + balance_ipbuf(); + } +} + +/* for interrupt context */ +void unuse_ipbuf_nowait(unsigned short bid) +{ + release_ipbuf(bid); + balance_ipbuf(); +} + +/* + * functions called from mailbox1 interrupt routine + */ + +void mbx1_err_ipbfull(void) +{ + ipbcfg.cnt_full++; +} + +/* + * sysfs files + */ +static ssize_t ipbuf_show(struct device *dev, char *buf) +{ + int len = 0; + unsigned short bid; + + for (bid = 0; bid < ipbcfg.ln; bid++) { + unsigned short la = ipbuf[bid]->la; + unsigned short ld = ipbuf[bid]->ld; + unsigned short c = ipbuf[bid]->c; + + if (len > PAGE_SIZE - 100) { + len += sprintf(buf + len, "out of buffer.\n"); + goto finish; + } + + len += sprintf(buf + len, "ipbuf[%d]: adr = 0x%p\n", + bid, ipbuf[bid]); + if (la == OMAP_DSP_TID_FREE) { + len += sprintf(buf + len, + " DSPtask[%d]->Linux " + "(already read and now free for Linux)\n", + ld); + } else if (ld == OMAP_DSP_TID_FREE) { + len += sprintf(buf + len, + " Linux->DSPtask[%d] " + "(already read and now free for DSP)\n", + la); + } else if (ipbuf_is_held(ld, bid)) { + len += sprintf(buf + len, + " DSPtask[%d]->Linux " + "(waiting to be read)\n" + " count = %d\n", ld, c); + } else { + len += sprintf(buf + len, + " Linux->DSPtask[%d] " + "(waiting to be read)\n" + " count = %d\n", la, c); + } + } + + len += sprintf(buf + len, "\nFree IPBUF link: "); + spin_lock(&ipb_free.lock); + ipblink_for_each(bid, &ipb_free, ipbuf) { + len += sprintf(buf + len, "%d ", bid); + } + spin_unlock(&ipb_free.lock); + len += sprintf(buf + len, "\n"); + len += sprintf(buf + len, "IPBFULL error count: %ld\n", + ipbcfg.cnt_full); + +finish: + return len; +} + +struct device_attribute dev_attr_ipbuf = __ATTR_RO(ipbuf); diff --git a/arch/arm/mach-omap/dsp/ipbuf.h b/arch/arm/mach-omap/dsp/ipbuf.h new file mode 100644 index 00000000000..c70f7964a29 --- /dev/null +++ b/arch/arm/mach-omap/dsp/ipbuf.h @@ -0,0 +1,133 @@ +/* + * linux/arch/arm/mach-omap/dsp/ipbuf.h + * + * Header for IPBUF + * + * Copyright (C) 2002-2005 Nokia Corporation + * + * Written by Toshihiro Kobayashi + * + * 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 + * + * 2005/02/17: DSP Gateway version 3.2 + */ + +struct ipbuf { + unsigned short c; /* count */ + unsigned short next; /* link */ + unsigned short la; /* lock owner (ARM side) */ + unsigned short sa; /* sync word (ARM->DSP) */ + unsigned short ld; /* lock owner (DSP side) */ + unsigned short sd; /* sync word (DSP->ARM) */ + unsigned char d[0]; /* data */ +}; + +struct ipbuf_p { + unsigned short c; /* count */ + unsigned short s; /* sync word */ + unsigned short al; /* data address lower */ + unsigned short ah; /* data address upper */ +}; + +struct ipbuf_sys { + unsigned short s; /* sync word */ + unsigned short d[15]; /* data */ +}; + +struct ipbcfg { + unsigned short ln; + unsigned short lsz; + unsigned long adr; + unsigned short bsycnt; + unsigned long cnt_full; /* count of IPBFULL error */ +}; + +extern struct ipbuf **ipbuf; +extern struct ipbcfg ipbcfg; +extern struct ipbuf_sys *ipbuf_sys_da, *ipbuf_sys_ad; + +#define ipb_bsycnt_inc(ipbcfg) \ + do { \ + disable_irq(INT_D2A_MB1); \ + (ipbcfg)->bsycnt++; \ + enable_irq(INT_D2A_MB1); \ + } while(0) + +#define ipb_bsycnt_dec(ipbcfg) \ + do { \ + disable_irq(INT_D2A_MB1); \ + (ipbcfg)->bsycnt--; \ + enable_irq(INT_D2A_MB1); \ + } while(0) + +#define dsp_mem_enable_ipbuf() dsp_mem_enable(dspword_to_virt(ipbcfg.adr)) +#define dsp_mem_disable_ipbuf() dsp_mem_disable(dspword_to_virt(ipbcfg.adr)) + +struct ipblink { + spinlock_t lock; + unsigned short top; + unsigned short tail; +}; + +#define IPBLINK_INIT { \ + .lock = SPIN_LOCK_UNLOCKED, \ + .top = OMAP_DSP_BID_NULL, \ + .tail = OMAP_DSP_BID_NULL, \ + } + +#define INIT_IPBLINK(link) \ + do { \ + (link)->top = OMAP_DSP_BID_NULL; \ + (link)->tail = OMAP_DSP_BID_NULL; \ + } while(0) + +#define ipblink_empty(link) ((link)->top == OMAP_DSP_BID_NULL) + +static __inline__ void ipblink_del_top(struct ipblink *link, + struct ipbuf **ipbuf) +{ + struct ipbuf *bufp = ipbuf[link->top]; + + if ((link->top = bufp->next) == OMAP_DSP_BID_NULL) + link->tail = OMAP_DSP_BID_NULL; + else + bufp->next = OMAP_DSP_BID_NULL; +} + +static __inline__ void ipblink_add_tail(struct ipblink *link, + unsigned short bid, + struct ipbuf **ipbuf) +{ + if (ipblink_empty(link)) + link->top = bid; + else + ipbuf[link->tail]->next = bid; + link->tail = bid; +} + +static __inline__ void ipblink_add_pvt(struct ipblink *link) +{ + link->top = OMAP_DSP_BID_PVT; + link->tail = OMAP_DSP_BID_PVT; +} + +static __inline__ void ipblink_del_pvt(struct ipblink *link) +{ + link->top = OMAP_DSP_BID_NULL; + link->tail = OMAP_DSP_BID_NULL; +} + +#define ipblink_for_each(bid, link, ipbuf) \ + for (bid = (link)->top; bid != OMAP_DSP_BID_NULL; bid = ipbuf[bid]->next) diff --git a/arch/arm/mach-omap/dsp/mblog.c b/arch/arm/mach-omap/dsp/mblog.c new file mode 100644 index 00000000000..3671c436c54 --- /dev/null +++ b/arch/arm/mach-omap/dsp/mblog.c @@ -0,0 +1,273 @@ +/* + * linux/arch/arm/mach-omap/dsp/mblog.c + * + * OMAP DSP driver Mailbox log module + * + * Copyright (C) 2003-2005 Nokia Corporation + * + * Written by Toshihiro Kobayashi + * + * 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 + * + * 2005/02/17: DSP Gateway version 3.2 + */ + +#include +#include +#include +#include +#include +#include +#include "dsp.h" + +#define RLCMD(nm) OMAP_DSP_MBCMD_RUNLEVEL_##nm +#define KFUNCCMD(nm) OMAP_DSP_MBCMD_KFUNC_##nm +#define PMCMD(nm) OMAP_DSP_MBCMD_PM_##nm +#define CFGCMD(nm) OMAP_DSP_MBCMD_DSPCFG_##nm +#define REGCMD(nm) OMAP_DSP_MBCMD_REGRW_##nm +#define VICMD(nm) OMAP_DSP_MBCMD_VARID_##nm +#define EID(nm) OMAP_DSP_EID_##nm + +char *subcmd_name(struct mbcmd *mb) +{ + unsigned char cmd_h = mb->cmd_h; + unsigned char cmd_l = mb->cmd_l; + char *s; + + switch (cmd_h) { + case MBCMD(RUNLEVEL): + s = (cmd_l == RLCMD(USER)) ? "USER": + (cmd_l == RLCMD(SUPER)) ? "SUPER": + (cmd_l == RLCMD(RECOVERY)) ? "RECOVERY": + NULL; + break; + case MBCMD(PM): + s = (cmd_l == PMCMD(DISABLE)) ? "DISABLE": + (cmd_l == PMCMD(ENABLE)) ? "ENABLE": + NULL; + break; + case MBCMD(KFUNC): + s = (cmd_l == KFUNCCMD(FBCTL)) ? "FBCTL": + NULL; + break; + case MBCMD(DSPCFG): + { + unsigned char cfgc = cmd_l & 0x7f; + s = (cfgc == CFGCMD(REQ)) ? "REQ": + (cfgc == CFGCMD(SYSADRH)) ? "SYSADRH": + (cfgc == CFGCMD(SYSADRL)) ? "SYSADRL": + (cfgc == CFGCMD(ABORT)) ? "ABORT": + (cfgc == CFGCMD(PROTREV)) ? "PROTREV": + NULL; + break; + } + case MBCMD(REGRW): + s = (cmd_l == REGCMD(MEMR)) ? "MEMR": + (cmd_l == REGCMD(MEMW)) ? "MEMW": + (cmd_l == REGCMD(IOR)) ? "IOR": + (cmd_l == REGCMD(IOW)) ? "IOW": + (cmd_l == REGCMD(DATA)) ? "DATA": + NULL; + break; + case MBCMD(GETVAR): + case MBCMD(SETVAR): + s = (cmd_l == VICMD(ICRMASK)) ? "ICRMASK": + (cmd_l == VICMD(LOADINFO)) ? "LOADINFO": + NULL; + break; + case MBCMD(ERR): + s = (cmd_l == EID(BADTID)) ? "BADTID": + (cmd_l == EID(BADTCN)) ? "BADTCN": + (cmd_l == EID(BADBID)) ? "BADBID": + (cmd_l == EID(BADCNT)) ? "BADCNT": + (cmd_l == EID(NOTLOCKED)) ? "NOTLOCKED": + (cmd_l == EID(STVBUF)) ? "STVBUF": + (cmd_l == EID(BADADR)) ? "BADADR": + (cmd_l == EID(BADTCTL)) ? "BADTCTL": + (cmd_l == EID(BADPARAM)) ? "BADPARAM": + (cmd_l == EID(FATAL)) ? "FATAL": + (cmd_l == EID(NOMEM)) ? "NOMEM": + (cmd_l == EID(NORES)) ? "NORES": + (cmd_l == EID(IPBFULL)) ? "IPBFULL": + (cmd_l == EID(TASKNOTRDY)) ? "TASKNOTRDY": + (cmd_l == EID(TASKBSY)) ? "TASKBSY": + (cmd_l == EID(TASKERR)) ? "TASKERR": + (cmd_l == EID(BADCFGTYP)) ? "BADCFGTYP": + (cmd_l == EID(DEBUG)) ? "DEBUG": + (cmd_l == EID(BADSEQ)) ? "BADSEQ": + (cmd_l == EID(BADCMD)) ? "BADCMD": + NULL; + break; + default: + s = NULL; + } + + return s; +} + +/* output of show() method should fit to PAGE_SIZE */ +#define MBLOG_DEPTH 64 + +struct mblogent { + unsigned long jiffies; + unsigned short cmd; + unsigned short data; + enum mblog_dir dir; +}; + +static struct { + spinlock_t lock; + int wp; + unsigned long cnt, cnt_ad, cnt_da; + struct mblogent ent[MBLOG_DEPTH]; +} mblog; + +void mblog_add(struct mbcmd *mb, enum mblog_dir dir) +{ + struct mbcmd_hw *mb_hw = (struct mbcmd_hw *)mb; + struct mblogent *ent; + + spin_lock(&mblog.lock); + ent = &mblog.ent[mblog.wp]; + ent->jiffies = jiffies; + ent->cmd = mb_hw->cmd; + ent->data = mb_hw->data; + ent->dir = dir; + if (mblog.cnt < 0xffffffff) + mblog.cnt++; + switch (dir) { + case MBLOG_DIR_AD: + if (mblog.cnt_ad < 0xffffffff) + mblog.cnt_ad++; + break; + case MBLOG_DIR_DA: + if (mblog.cnt_da < 0xffffffff) + mblog.cnt_da++; + break; + } + if (++mblog.wp == MBLOG_DEPTH) + mblog.wp = 0; + spin_unlock(&mblog.lock); +} + +/* + * sysfs file + */ +static ssize_t mblog_show(struct device *dev, char *buf) +{ + int len = 0; + int wp; + int i; + + spin_lock(&mblog.lock); + + wp = mblog.wp; + len += sprintf(buf + len, + "log count:%ld / ARM->DSP:%ld, DSP->ARM:%ld\n", + mblog.cnt, mblog.cnt_ad, mblog.cnt_da); + if (mblog.cnt == 0) + goto done; + + len += sprintf(buf + len, " ARM -> DSP ARM <- DSP\n"); + len += sprintf(buf + len, "jiffies q cmd data q cmd data\n"); + i = (mblog.cnt >= MBLOG_DEPTH) ? wp : 0; + do { + struct mblogent *ent = &mblog.ent[i]; + union { + struct mbcmd sw; + struct mbcmd_hw hw; + } mb = { + .hw.cmd = ent->cmd, + .hw.data = ent->data + }; + char *subname; + const struct cmdinfo *ci = cmdinfo[mb.sw.cmd_h]; + + len += sprintf(buf + len, + (ent->dir == MBLOG_DIR_AD) ? + "%08lx %d %04x %04x ": + "%08lx %d %04x %04x ", + ent->jiffies, mb.sw.seq, ent->cmd, ent->data); + switch (ci->cmd_l_type) { + case CMD_L_TYPE_SUBCMD: + if ((subname = subcmd_name(&mb.sw)) == NULL) + subname = "Unknown"; + len += sprintf(buf + len, "%s:%s\n", + ci->name, subname); + break; + case CMD_L_TYPE_TID: + len += sprintf(buf + len, "%s:task %d\n", + ci->name, mb.sw.cmd_l); + break; + case CMD_L_TYPE_NULL: + len += sprintf(buf + len, "%s\n", ci->name); + break; + } + + if (++i == MBLOG_DEPTH) + i = 0; + } while (i != wp); + +done: + spin_unlock(&mblog.lock); + + return len; +} + +static struct device_attribute dev_attr_mblog = __ATTR_RO(mblog); + +#ifdef CONFIG_OMAP_DSP_MBCMD_VERBOSE +void mblog_printcmd(struct mbcmd *mb, enum mblog_dir dir) +{ + const struct cmdinfo *ci = cmdinfo[mb->cmd_h]; + char *dir_str; + char *subname; + + dir_str = (dir == MBLOG_DIR_AD) ? "sending" : "receiving"; + switch (ci->cmd_l_type) { + case CMD_L_TYPE_SUBCMD: + if ((subname = subcmd_name(mb)) == NULL) + subname = "Unknown"; + printk(KERN_DEBUG + "mbx: %s seq=%d, cmd=%02x:%02x(%s:%s), data=%04x\n", + dir_str, mb->seq, mb->cmd_h, mb->cmd_l, + ci->name, subname, mb->data); + break; + case CMD_L_TYPE_TID: + printk(KERN_DEBUG + "mbx: %s seq=%d, cmd=%02x:%02x(%s:task %d), data=%04x\n", + dir_str, mb->seq, mb->cmd_h, mb->cmd_l, + ci->name, mb->cmd_l, mb->data); + break; + case CMD_L_TYPE_NULL: + printk(KERN_DEBUG + "mbx: %s seq=%d, cmd=%02x:%02x(%s), data=%04x\n", + dir_str, mb->seq, mb->cmd_h, mb->cmd_l, + ci->name, mb->data); + break; + } +} +#endif /* CONFIG_OMAP_DSP_MBCMD_VERBOSE */ + +void __init mblog_init(void) +{ + spin_lock_init(&mblog.lock); + device_create_file(&dsp_device.dev, &dev_attr_mblog); +} + +void mblog_exit(void) +{ + device_remove_file(&dsp_device.dev, &dev_attr_mblog); +} diff --git a/arch/arm/mach-omap/dsp/proclist.h b/arch/arm/mach-omap/dsp/proclist.h new file mode 100644 index 00000000000..723e5b09d27 --- /dev/null +++ b/arch/arm/mach-omap/dsp/proclist.h @@ -0,0 +1,84 @@ +/* + * linux/arch/arm/mach-omap/dsp/proclist.h + * + * Linux task list handler + * + * Copyright (C) 2004,2005 Nokia Corporation + * + * Written by Toshihiro Kobayashi + * + * 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 + * + * 2004/11/22: DSP Gateway version 3.2 + */ + +struct proc_list { + struct list_head list_head; + struct task_struct *tsk; + unsigned int cnt; +}; + +static __inline__ void proc_list_add(struct list_head *list, + struct task_struct *tsk) +{ + struct list_head *ptr; + struct proc_list *pl; + struct proc_list *new; + + list_for_each(ptr, list) { + pl = list_entry(ptr, struct proc_list, list_head); + if (pl->tsk == tsk) { + /* + * this process has opened DSP devices multi time + */ + pl->cnt++; + return; + } + } + + new = kmalloc(sizeof(struct proc_list), GFP_KERNEL); + new->tsk = tsk; + new->cnt = 1; + list_add_tail(&new->list_head, list); +} + +static __inline__ void proc_list_del(struct list_head *list, + struct task_struct *tsk) +{ + struct list_head *ptr; + struct proc_list *pl; + + list_for_each(ptr, list) { + pl = list_entry(ptr, struct proc_list, list_head); + if (pl->tsk == tsk) { + if (--pl->cnt == 0) { + list_del(&pl->list_head); + kfree(pl); + } + return; + } + } +} + +static __inline__ void proc_list_flush(struct list_head *list) +{ + struct proc_list *pl; + + while (!list_empty(list)) { + pl = list_entry(list->next, struct proc_list, list_head); + list_del(&pl->list_head); + kfree(pl); + } +} diff --git a/arch/arm/mach-omap/dsp/task.c b/arch/arm/mach-omap/dsp/task.c new file mode 100644 index 00000000000..118f1a974cf --- /dev/null +++ b/arch/arm/mach-omap/dsp/task.c @@ -0,0 +1,2615 @@ +/* + * linux/arch/arm/mach-omap/dsp/task.c + * + * OMAP DSP task device driver + * + * Copyright (C) 2002-2005 Nokia Corporation + * + * Written by Toshihiro Kobayashi + * mmap function by Hiroo Ishikawa + * + * 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 + * + * 2005/02/22: DSP Gateway version 3.2 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "uaccess_dsp.h" +#include "dsp.h" +#include "ipbuf.h" +#include "fifo.h" +#include "proclist.h" + +/* + * 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. + * KILLREQ: requesting for tkill. + * ADDFAIL: tadd failed. + */ + +struct taskdev { + struct bus_type *bus; +// struct device_driver *driver; + struct device dev; /* Generic device interface */ + + long state; + spinlock_t state_lock; + wait_queue_head_t state_wait_q; + unsigned int usecount; + char name[OMAP_DSP_TNM_LEN]; + struct file_operations fops; + struct list_head proc_list; + struct dsptask *task; + + /* read stuff */ + wait_queue_head_t read_wait_q; + struct semaphore read_sem; + + /* write stuff */ + wait_queue_head_t write_wait_q; + struct semaphore write_sem; + + /* ioctl stuff */ + wait_queue_head_t ioctl_wait_q; + struct semaphore ioctl_sem; + + /* device lock */ + struct semaphore lock_sem; + pid_t lock_pid; +}; + +#define to_taskdev(n) container_of(n, struct taskdev, dev) + +struct rcvdt_bk_struct { + struct ipblink link; + unsigned int rp; + struct ipbuf_p *ipbuf_pvt_r; +}; + +struct dsptask { + enum { + TASK_STATE_ERR = 0, + TASK_STATE_READY, + TASK_STATE_CFGREQ + } state; + unsigned char tid; + char name[OMAP_DSP_TNM_LEN]; + unsigned short ttyp; + struct taskdev *dev; + + /* read stuff */ + union { + struct fifo_struct fifo; /* for active word */ + struct rcvdt_bk_struct bk; + } rcvdt; + + /* write stuff */ + size_t wsz; + spinlock_t wsz_lock; + struct ipbuf_p *ipbuf_pvt_w; /* for private block */ + + /* tctl stuff */ + int tctl_stat; + + /* mmap stuff */ + void *map_base; + size_t map_length; +}; + +#define sndtyp_acv(ttyp) ((ttyp) & OMAP_DSP_TTYP_ASND) +#define sndtyp_psv(ttyp) (!((ttyp) & OMAP_DSP_TTYP_ASND)) +#define sndtyp_bk(ttyp) ((ttyp) & OMAP_DSP_TTYP_BKDM) +#define sndtyp_wd(ttyp) (!((ttyp) & OMAP_DSP_TTYP_BKDM)) +#define sndtyp_pvt(ttyp) ((ttyp) & OMAP_DSP_TTYP_PVDM) +#define sndtyp_gbl(ttyp) (!((ttyp) & OMAP_DSP_TTYP_PVDM)) +#define rcvtyp_acv(ttyp) ((ttyp) & OMAP_DSP_TTYP_ARCV) +#define rcvtyp_psv(ttyp) (!((ttyp) & OMAP_DSP_TTYP_ARCV)) +#define rcvtyp_bk(ttyp) ((ttyp) & OMAP_DSP_TTYP_BKMD) +#define rcvtyp_wd(ttyp) (!((ttyp) & OMAP_DSP_TTYP_BKMD)) +#define rcvtyp_pvt(ttyp) ((ttyp) & OMAP_DSP_TTYP_PVMD) +#define rcvtyp_gbl(ttyp) (!((ttyp) & OMAP_DSP_TTYP_PVMD)) + +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 void taskdev_attach_task(struct taskdev *dev, struct dsptask *task); +static void taskdev_detach_task(struct taskdev *dev); + +static ssize_t devname_show(struct device *d, char *buf); +static ssize_t devstate_show(struct device *d, char *buf); +static ssize_t proc_list_show(struct device *d, char *buf); +static ssize_t taskname_show(struct device *d, char *buf); +static ssize_t ttyp_show(struct device *d, char *buf); +static ssize_t fifosz_show(struct device *d, char *buf); +static int fifosz_store(struct device *d, const char *buf, size_t count); +static ssize_t fifocnt_show(struct device *d, char *buf); +static ssize_t ipblink_show(struct device *d, char *buf); +static ssize_t wsz_show(struct device *d, char *buf); +static ssize_t mmap_show(struct device *d, char *buf); + +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_fifosz = + __ATTR(fifosz, S_IWUGO | S_IRUGO, fifosz_show, fifosz_store); +static struct device_attribute dev_attr_fifocnt = __ATTR_RO(fifocnt); +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_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 struct bus_type dsptask_bus = { + .name = "dsptask", +}; + +static struct class_simple *dsp_task_class; +static struct taskdev *taskdev[TASKDEV_MAX]; +static struct dsptask *dsptask[TASKDEV_MAX]; +static DECLARE_MUTEX(cfg_sem); +static unsigned short cfg_cmd; +static unsigned char cfg_tid; +static DECLARE_WAIT_QUEUE_HEAD(cfg_wait_q); +static unsigned char n_task; +static void *heap; + +#define devstate_lock(dev, devstate) devstate_lock_timeout(dev, devstate, 0) + +/* + * devstate_lock_timeout(): + * when called with timeout > 0, dev->state can be diffeent from what you want. + */ +static int devstate_lock_timeout(struct taskdev *dev, long devstate, + int timeout) +{ + DECLARE_WAITQUEUE(wait, current); + long current_state = current->state; + int ret = 0; + + spin_lock(&dev->state_lock); + add_wait_queue(&dev->state_wait_q, &wait); + while (!(dev->state & devstate)) { + set_current_state(TASK_INTERRUPTIBLE); + spin_unlock(&dev->state_lock); + if (timeout) { + if ((timeout = schedule_timeout(timeout)) == 0) { + /* timeout */ + spin_lock(&dev->state_lock); + break; + } + } + else + schedule(); + if (signal_pending(current)) { + ret = -ERESTARTSYS; + break; + } + spin_lock(&dev->state_lock); + } + remove_wait_queue(&dev->state_wait_q, &wait); + set_current_state(current_state); + return ret; +} + +static __inline__ void devstate_unlock(struct taskdev *dev) +{ + spin_unlock(&dev->state_lock); +} + +static __inline__ int down_tasksem_interruptible(struct taskdev *dev, + struct semaphore *sem) +{ + int ret; + + if (dev->lock_pid == current->pid) { + /* this process has lock */ + ret = down_interruptible(sem); + } else { + if ((ret = down_interruptible(&dev->lock_sem)) != 0) + return ret; + ret = down_interruptible(sem); + up(&dev->lock_sem); + } + return ret; +} + +static int dsp_task_flush_buf(struct dsptask *task) +{ + unsigned short ttyp = task->ttyp; + + if (sndtyp_wd(ttyp)) { + /* word receiving */ + flush_fifo(&task->rcvdt.fifo); + } else { + /* block receiving */ + struct rcvdt_bk_struct *rcvdt = &task->rcvdt.bk; + + spin_lock(&rcvdt->link.lock); + if (sndtyp_gbl(ttyp)) { + /* global IPBUF */ + while (!ipblink_empty(&rcvdt->link)) { + unsigned short bid = rcvdt->link.top; + ipblink_del_top(&rcvdt->link, ipbuf); + unuse_ipbuf(bid); + } + } else { + /* private IPBUF */ + if (!ipblink_empty(&rcvdt->link)) { + ipblink_del_pvt(&rcvdt->link); + release_ipbuf_pvt(rcvdt->ipbuf_pvt_r); + } + } + spin_unlock(&rcvdt->link.lock); + } + + return 0; +} + +static int dsp_task_set_fifosz(struct dsptask *task, unsigned long sz) +{ + unsigned short ttyp = task->ttyp; + int stat; + + 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; + } + + stat = realloc_fifo(&task->rcvdt.fifo, sz); + if (stat == -EBUSY) { + printk(KERN_ERR "omapdsp: buffer is not empty!\n"); + return stat; + } else if (stat < 0) { + printk(KERN_ERR + "omapdsp: unable to change receive buffer size. " + "(%ld bytes for %s)\n", sz, task->name); + return stat; + } + + return 0; +} + +static int taskdev_lock(struct taskdev *dev) +{ + if (down_interruptible(&dev->lock_sem)) + return -ERESTARTSYS; + dev->lock_pid = current->pid; + return 0; +} + +static int taskdev_unlock(struct taskdev *dev) +{ + if (dev->lock_pid != current->pid) { + printk(KERN_ERR + "omapdsp: an illegal process attempted to " + "unlock the dsptask lock!\n"); + return -EINVAL; + } + dev->lock_pid = 0; + up(&dev->lock_sem); + return 0; +} + +static int dsp_task_config(struct dsptask *task, unsigned char tid) +{ + unsigned short ttyp; + struct mbcmd mb; + + dsptask[tid] = task; + task->tid = tid; + + /* TCFG request */ + task->state = TASK_STATE_CFGREQ; + if (down_interruptible(&cfg_sem)) + return -ERESTARTSYS; + cfg_cmd = MBCMD(TCFG); + mbcmd_set(mb, MBCMD(TCFG), tid, 0); + dsp_mbsend_and_wait(&mb, &cfg_wait_q); + cfg_cmd = 0; + up(&cfg_sem); + + if (task->state != TASK_STATE_READY) { + printk(KERN_ERR "omapdsp: task %d configuration error!\n", tid); + return -EINVAL; + } + + if (strlen(task->name) <= 1) + sprintf(task->name, "%d", tid); + printk(KERN_INFO "omapdsp: task %d: name %s\n", tid, task->name); + + ttyp = task->ttyp; + + /* task type check */ + if (rcvtyp_psv(ttyp) && rcvtyp_pvt(ttyp)) { + printk(KERN_ERR "mbx: illegal task type(0x%04x), tid=%d\n", + tid, ttyp); + } + + /* private buffer address check */ + if (sndtyp_pvt(ttyp)) { + void *p = task->rcvdt.bk.ipbuf_pvt_r; + + if ((unsigned long)p & 0x1) { + printk(KERN_ERR + "mbx: private ipbuf (DSP->ARM) address (0x%p) " + "is odd number!\n", p); + return -EINVAL; + } + } + + if (rcvtyp_pvt(ttyp)) { + void *p = task->ipbuf_pvt_w; + + if ((unsigned long)p & 0x1) { + printk(KERN_ERR + "mbx: private ipbuf (ARM->DSP) address (0x%p) " + "is odd number!\n", p); + return -EINVAL; + } + } + + /* read initialization */ + if (sndtyp_wd(ttyp)) { + /* word */ + size_t fifosz; + + fifosz = sndtyp_psv(ttyp) ? 2 : /* passive */ + 32; /* active */ + if (init_fifo(&task->rcvdt.fifo, fifosz) < 0) { + printk(KERN_ERR + "omapdsp: unable to allocate receive buffer. " + "(%d bytes for %s)\n", fifosz, task->name); + return -ENOMEM; + } + } else { + /* block */ + spin_lock_init(&task->rcvdt.bk.link.lock); + INIT_IPBLINK(&task->rcvdt.bk.link); + task->rcvdt.bk.rp = 0; + } + + /* write initialization */ + spin_lock_init(&task->wsz_lock); + task->wsz = rcvtyp_acv(ttyp) ? 0 : /* active */ + rcvtyp_wd(ttyp) ? 2 : /* passive word */ + ipbcfg.lsz*2; /* passive block */ + + return 0; +} + +static void dsp_task_init(struct dsptask *task) +{ + struct mbcmd mb; + + mbcmd_set(mb, MBCMD(TCTL), task->tid, OMAP_DSP_MBCMD_TCTL_TINIT); + dsp_mbsend(&mb); +} + +int dsp_task_config_all(unsigned char n) +{ + int i, ret; + struct taskdev *devheap; + struct dsptask *taskheap; + size_t devheapsz, taskheapsz; + + memset(taskdev, 0, sizeof(void *) * TASKDEV_MAX); + memset(dsptask, 0, sizeof(void *) * TASKDEV_MAX); + + n_task = n; + printk(KERN_INFO "omapdsp: found %d task(s)\n", n_task); + if (n_task == 0) + return 0; + + /* + * reducing kmalloc! + */ + devheapsz = sizeof(struct taskdev) * n_task; + taskheapsz = sizeof(struct dsptask) * n_task; + heap = kmalloc(devheapsz + taskheapsz, GFP_KERNEL); + if (heap == NULL) { + n_task = 0; + return -ENOMEM; + } + memset(heap, 0, devheapsz + taskheapsz); + devheap = heap; + taskheap = heap + devheapsz; + + for (i = 0; i < n_task; 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; + taskdev_attach_task(dev, task); + dsp_task_init(task); + printk(KERN_INFO "omapdsp: taskdev %s enabled.\n", dev->name); + } + + return 0; +} + +static void dsp_task_unconfig(struct dsptask *task) +{ + unsigned char tid = task->tid; + + preempt_disable(); + dsp_task_flush_buf(task); + if (sndtyp_wd(task->ttyp) && (task->state == TASK_STATE_READY)) + free_fifo(&task->rcvdt.fifo); + dsptask[tid] = NULL; + preempt_enable(); +} + +void dsp_task_unconfig_all(void) +{ + unsigned char minor; + unsigned char 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, +}; + +unsigned char dsp_task_count(void) +{ + return n_task; +} + +int dsp_taskmod_busy(void) +{ + struct taskdev *dev; + unsigned char minor; + + for (minor = 0; minor < TASKDEV_MAX; minor++) { + dev = taskdev[minor]; + if (dev && + ((dev->usecount > 0) || + (dev->state == OMAP_DSP_DEVSTATE_ADDREQ) || + (dev->state == OMAP_DSP_DEVSTATE_DELREQ))) + return 1; + } + return 0; +} + +/* + * DSP task device file operations + */ +static ssize_t dsp_task_read_wd_acv(struct file *file, char *buf, size_t count, + loff_t *ppos) +{ + unsigned int minor = MINOR(file->f_dentry->d_inode->i_rdev); + struct taskdev *dev = taskdev[minor]; + int have_devstate_lock = 0; + 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; + } + + if (down_tasksem_interruptible(dev, &dev->read_sem)) + return -ERESTARTSYS; + if (devstate_lock(dev, OMAP_DSP_DEVSTATE_ATTACHED) < 0) { + ret = -ERESTARTSYS; + goto up_out; + } + have_devstate_lock = 1; + + if (fifo_empty(&dev->task->rcvdt.fifo)) { + long current_state = current->state; + DECLARE_WAITQUEUE(wait, current); + + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&dev->read_wait_q, &wait); + if (fifo_empty(&dev->task->rcvdt.fifo)) { /* last check */ + devstate_unlock(dev); + have_devstate_lock = 0; + schedule(); + } + set_current_state(current_state); + remove_wait_queue(&dev->read_wait_q, &wait); + if (signal_pending(current)) { + ret = -ERESTARTSYS; + goto up_out; + } + if (!have_devstate_lock) { + if (devstate_lock(dev, OMAP_DSP_DEVSTATE_ATTACHED) < 0) { + ret = -ERESTARTSYS; + goto up_out; + } + have_devstate_lock = 1; + } + if (fifo_empty(&dev->task->rcvdt.fifo)) /* should not occur */ + goto up_out; + } + + ret = copy_to_user_fm_fifo(buf, &dev->task->rcvdt.fifo, count); + +up_out: + if (have_devstate_lock) + devstate_unlock(dev); + up(&dev->read_sem); + return ret; +} + +static ssize_t dsp_task_read_bk_acv(struct file *file, char *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; + int have_devstate_lock = 0; + ssize_t 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 (down_tasksem_interruptible(dev, &dev->read_sem)) + return -ERESTARTSYS; + if (devstate_lock(dev, OMAP_DSP_DEVSTATE_ATTACHED) < 0) { + ret = -ERESTARTSYS; + goto up_out; + } + have_devstate_lock = 1; + + if (ipblink_empty(&dev->task->rcvdt.bk.link)) { + long current_state; + DECLARE_WAITQUEUE(wait, current); + + add_wait_queue(&dev->read_wait_q, &wait); + current_state = current->state; + set_current_state(TASK_INTERRUPTIBLE); + if (ipblink_empty(&dev->task->rcvdt.bk.link)) { /* last check */ + devstate_unlock(dev); + have_devstate_lock = 0; + schedule(); + } + set_current_state(current_state); + remove_wait_queue(&dev->read_wait_q, &wait); + if (signal_pending(current)) { + ret = -ERESTARTSYS; + goto up_out; + } + if (!have_devstate_lock) { + if (devstate_lock(dev, OMAP_DSP_DEVSTATE_ATTACHED) < 0) { + ret = -ERESTARTSYS; + goto up_out; + } + have_devstate_lock = 1; + } + /* signal or 0-byte send from DSP */ + if (ipblink_empty(&dev->task->rcvdt.bk.link)) + goto up_out; + } + + rcvdt = &dev->task->rcvdt.bk; + /* copy from delayed IPBUF */ + if (sndtyp_pvt(dev->task->ttyp)) { + /* private */ + if (!ipblink_empty(&rcvdt->link)) { + struct ipbuf_p *ipbp = rcvdt->ipbuf_pvt_r; + unsigned char *base, *src; + size_t bkcnt; + + if (dsp_mem_enable(ipbp) < 0) { + ret = -ERESTARTSYS; + goto up_out; + } + base = dspword_to_virt(MKLONG(ipbp->ah, ipbp->al)); + src = base + rcvdt->rp; + if (dsp_mem_enable(base) < 0) { + ret = -ERESTARTSYS; + goto pv_out1; + } + bkcnt = ((unsigned long)ipbp->c) * 2 - 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; + spin_lock(&rcvdt->link.lock); + ipblink_del_pvt(&rcvdt->link); + spin_unlock(&rcvdt->link.lock); + 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 = -ERESTARTSYS; + goto up_out; + } + while (!ipblink_empty(&rcvdt->link)) { + unsigned char *src; + size_t bkcnt; + unsigned short bid = rcvdt->link.top; + struct ipbuf *ipbp = ipbuf[bid]; + + src = ipbp->d + rcvdt->rp; + bkcnt = ((unsigned long)ipbp->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; + spin_lock(&rcvdt->link.lock); + ipblink_del_top(&rcvdt->link, ipbuf); + spin_unlock(&rcvdt->link.lock); + unuse_ipbuf(bid); + rcvdt->rp = 0; + } + } +gb_out: + dsp_mem_disable_ipbuf(); + } + +up_out: + if (have_devstate_lock) + devstate_unlock(dev); + up(&dev->read_sem); + return ret; +} + +static ssize_t dsp_task_read_wd_psv(struct file *file, char *buf, size_t count, + loff_t *ppos) +{ + unsigned int minor = MINOR(file->f_dentry->d_inode->i_rdev); + struct taskdev *dev = taskdev[minor]; + struct mbcmd mb; + unsigned char tid; + 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 (down_tasksem_interruptible(dev, &dev->read_sem)) + return -ERESTARTSYS; + if (devstate_lock(dev, OMAP_DSP_DEVSTATE_ATTACHED) < 0) { + ret = -ERESTARTSYS; + goto up_out; + } + tid = dev->task->tid; + devstate_unlock(dev); + + mbcmd_set(mb, MBCMD(WDREQ), tid, 0); + dsp_mbsend_and_wait(&mb, &dev->read_wait_q); + + if (signal_pending(current)) { + ret = -ERESTARTSYS; + goto up_out; + } + if (devstate_lock(dev, OMAP_DSP_DEVSTATE_ATTACHED) < 0) { + ret = -ERESTARTSYS; + goto up_out; + } + if (fifo_empty(&dev->task->rcvdt.fifo)) /* should not occur */ + goto unlock_out; + + ret = copy_to_user_fm_fifo(buf, &dev->task->rcvdt.fifo, count); + +unlock_out: + devstate_unlock(dev); +up_out: + up(&dev->read_sem); + return ret; +} + +static ssize_t dsp_task_read_bk_psv(struct file *file, char *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; + struct mbcmd mb; + unsigned char tid; + 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 (down_tasksem_interruptible(dev, &dev->read_sem)) + return -ERESTARTSYS; + if (devstate_lock(dev, OMAP_DSP_DEVSTATE_ATTACHED) < 0) { + ret = -ERESTARTSYS; + goto up_out; + } + tid = dev->task->tid; + devstate_unlock(dev); + + mbcmd_set(mb, MBCMD(BKREQ), tid, count/2); + dsp_mbsend_and_wait(&mb, &dev->read_wait_q); + + if (signal_pending(current)) { + ret = -ERESTARTSYS; + goto up_out; + } + if (devstate_lock(dev, OMAP_DSP_DEVSTATE_ATTACHED) < 0) { + ret = -ERESTARTSYS; + goto up_out; + } + rcvdt = &dev->task->rcvdt.bk; + /* signal or 0-byte send from DSP */ + if (ipblink_empty(&rcvdt->link)) + goto unlock_out; + + /* + * We will not receive more than requested count. + */ + if (sndtyp_pvt(dev->task->ttyp)) { + /* private */ + struct ipbuf_p *ipbp = rcvdt->ipbuf_pvt_r; + size_t rcvcnt; + void *src; + + if (dsp_mem_enable(ipbp) < 0) { + ret = -ERESTARTSYS; + goto unlock_out; + } + src = dspword_to_virt(MKLONG(ipbp->ah, ipbp->al)); + if (dsp_mem_enable(src) < 0) { + ret = -ERESTARTSYS; + goto pv_out1; + } + rcvcnt = ((unsigned long)ipbp->c) * 2; + if (count > rcvcnt) + count = rcvcnt; + if (copy_to_user_dsp(buf, src, count)) { + ret = -EFAULT; + goto pv_out2; + } + spin_lock(&rcvdt->link.lock); + ipblink_del_pvt(&rcvdt->link); + spin_unlock(&rcvdt->link.lock); + release_ipbuf_pvt(ipbp); + ret = count; +pv_out2: + dsp_mem_disable(src); +pv_out1: + dsp_mem_disable(ipbp); + } else { + /* global */ + unsigned short bid = rcvdt->link.top; + struct ipbuf *ipbp = ipbuf[bid]; + size_t rcvcnt; + + if (dsp_mem_enable_ipbuf() < 0) { + ret = -ERESTARTSYS; + goto unlock_out; + } + rcvcnt = ((unsigned long)ipbp->c) * 2; + if (count > rcvcnt) + count = rcvcnt; + if (copy_to_user_dsp(buf, ipbp->d, count)) { + ret = -EFAULT; + goto gb_out; + } + spin_lock(&rcvdt->link.lock); + ipblink_del_top(&rcvdt->link, ipbuf); + spin_unlock(&rcvdt->link.lock); + unuse_ipbuf(bid); + ret = count; +gb_out: + dsp_mem_disable_ipbuf(); + } + +unlock_out: + devstate_unlock(dev); +up_out: + up(&dev->read_sem); + return ret; +} + +static ssize_t dsp_task_write_wd(struct file *file, const char *buf, + size_t count, loff_t *ppos) +{ + unsigned int minor = MINOR(file->f_dentry->d_inode->i_rdev); + struct taskdev *dev = taskdev[minor]; + struct mbcmd mb; + unsigned short wd; + int have_devstate_lock = 0; + 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 (down_tasksem_interruptible(dev, &dev->write_sem)) + return -ERESTARTSYS; + if (devstate_lock(dev, OMAP_DSP_DEVSTATE_ATTACHED) < 0) { + ret = -ERESTARTSYS; + goto up_out; + } + have_devstate_lock = 1; + + if (dev->task->wsz == 0) { + long current_state; + DECLARE_WAITQUEUE(wait, current); + + add_wait_queue(&dev->write_wait_q, &wait); + current_state = current->state; + set_current_state(TASK_INTERRUPTIBLE); + if (dev->task->wsz == 0) { /* last check */ + devstate_unlock(dev); + have_devstate_lock = 0; + schedule(); + } + set_current_state(current_state); + remove_wait_queue(&dev->write_wait_q, &wait); + if (signal_pending(current)) { + ret = -ERESTARTSYS; + goto up_out; + } + if (!have_devstate_lock) { + if (devstate_lock(dev, OMAP_DSP_DEVSTATE_ATTACHED) < 0) { + ret = -ERESTARTSYS; + goto up_out; + } + have_devstate_lock = 1; + } + if (dev->task->wsz == 0) /* should not occur */ + goto up_out; + } + + if (copy_from_user(&wd, buf, count)) { + ret = -EFAULT; + goto up_out; + } + + mbcmd_set(mb, MBCMD(WDSND), dev->task->tid, wd); + spin_lock(&dev->task->wsz_lock); + if (dsp_mbsend(&mb) < 0) { + spin_unlock(&dev->task->wsz_lock); + goto up_out; + } + ret = count; + if (rcvtyp_acv(dev->task->ttyp)) + dev->task->wsz = 0; + spin_unlock(&dev->task->wsz_lock); + +up_out: + if (have_devstate_lock) + devstate_unlock(dev); + up(&dev->write_sem); + return ret; +} + +static ssize_t dsp_task_write_bk(struct file *file, const char *buf, + size_t count, loff_t *ppos) +{ + unsigned int minor = MINOR(file->f_dentry->d_inode->i_rdev); + struct taskdev *dev = taskdev[minor]; + struct mbcmd mb; + int have_devstate_lock = 0; + 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_write().\n"); + return -EINVAL; + } + + if (down_tasksem_interruptible(dev, &dev->write_sem)) + return -ERESTARTSYS; + if (devstate_lock(dev, OMAP_DSP_DEVSTATE_ATTACHED) < 0) { + ret = -ERESTARTSYS; + goto up_out; + } + have_devstate_lock = 1; + + if (dev->task->wsz == 0) { + long current_state; + DECLARE_WAITQUEUE(wait, current); + + add_wait_queue(&dev->write_wait_q, &wait); + current_state = current->state; + set_current_state(TASK_INTERRUPTIBLE); + if (dev->task->wsz == 0) { /* last check */ + devstate_unlock(dev); + have_devstate_lock = 0; + schedule(); + } + set_current_state(current_state); + remove_wait_queue(&dev->write_wait_q, &wait); + if (signal_pending(current)) { + ret = -ERESTARTSYS; + goto up_out; + } + if (!have_devstate_lock) { + if (devstate_lock(dev, OMAP_DSP_DEVSTATE_ATTACHED) < 0) { + ret = -ERESTARTSYS; + goto up_out; + } + have_devstate_lock = 1; + } + if (dev->task->wsz == 0) /* should not occur */ + goto up_out; + } + + if (count > dev->task->wsz) + count = dev->task->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 = -ERESTARTSYS; + goto up_out; + } + dst = dspword_to_virt(MKLONG(ipbp->ah, ipbp->al)); + if (dsp_mem_enable(dst) < 0) { + ret = -ERESTARTSYS; + 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; + mbcmd_set(mb, MBCMD(BKSNDP), dev->task->tid, 0); + spin_lock(&dev->task->wsz_lock); + if (dsp_mbsend(&mb) == 0) { + if (rcvtyp_acv(dev->task->ttyp)) + dev->task->wsz = 0; + ret = count; + } + spin_unlock(&dev->task->wsz_lock); +pv_out2: + dsp_mem_disable(dst); +pv_out1: + dsp_mem_disable(ipbp); + } else { + /* global */ + struct ipbuf *ipbp; + unsigned short bid; + + if (dsp_mem_enable_ipbuf() < 0) { + ret = -ERESTARTSYS; + goto up_out; + } + bid = get_free_ipbuf(dev->task->tid); + if (bid == OMAP_DSP_BID_NULL) + goto gb_out; + ipbp = ipbuf[bid]; + if (copy_from_user_dsp(ipbp->d, buf, count)) { + release_ipbuf(bid); + ret = -EFAULT; + goto gb_out; + } + ipbp->c = count/2; + ipbp->sa = dev->task->tid; + mbcmd_set(mb, MBCMD(BKSND), dev->task->tid, bid); + spin_lock(&dev->task->wsz_lock); + if (dsp_mbsend(&mb) == 0) { + if (rcvtyp_acv(dev->task->ttyp)) + dev->task->wsz = 0; + ret = count; + ipb_bsycnt_inc(&ipbcfg); + } else + release_ipbuf(bid); + spin_unlock(&dev->task->wsz_lock); +gb_out: + dsp_mem_disable_ipbuf(); + } + +up_out: + if (have_devstate_lock) + devstate_unlock(dev); + up(&dev->write_sem); + 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; + + poll_wait(file, &dev->read_wait_q, wait); + poll_wait(file, &dev->write_wait_q, wait); + if (devstate_lock(dev, OMAP_DSP_DEVSTATE_ATTACHED) < 0) + return 0; + if (sndtyp_psv(task->ttyp) || + (sndtyp_wd(task->ttyp) && !fifo_empty(&task->rcvdt.fifo)) || + (sndtyp_bk(task->ttyp) && !ipblink_empty(&task->rcvdt.bk.link))) + mask |= POLLIN | POLLRDNORM; + if (task->wsz) + mask |= POLLOUT | POLLWRNORM; + devstate_unlock(dev); + + return mask; +} + +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]; + struct mbcmd mb; + unsigned char tid; + struct mb_exarg mbarg, *mbargp; + int mbargc; + unsigned short mbargv[1]; + int interactive; + int ret; + + /* LOCK / UNLOCK operations */ + switch (cmd) { + case OMAP_DSP_TASK_IOCTL_LOCK: + return taskdev_lock(dev); + case OMAP_DSP_TASK_IOCTL_UNLOCK: + return taskdev_unlock(dev); + } + + /* + * actually only interractive commands need to lock + * the semaphore, but here all commands do it for simplicity. + */ + if (down_tasksem_interruptible(dev, &dev->ioctl_sem)) + return -ERESTARTSYS; + if (devstate_lock(dev, OMAP_DSP_DEVSTATE_ATTACHED) < 0) { + ret = -ERESTARTSYS; + goto up_out; + } + + if ((cmd >= 0x0080) && (cmd < 0x0100)) { + /* + * 0x0080 - 0x00ff + * reserved for backward compatibility + * user-defined TCTL commands: no arg, non-interactive + */ + mbargc = 0; + interactive = 0; + } else if (cmd < 0x8000) { + /* + * 0x0000 - 0x7fff (except 0x0080 - 0x00ff) + * system reserved TCTL commands + */ + switch (cmd) { + case OMAP_DSP_MBCMD_TCTL_TEN: + case OMAP_DSP_MBCMD_TCTL_TDIS: + mbargc = 0; + interactive = 0; + break; + default: + ret = -ENOIOCTLCMD; + goto unlock_out; + } + } + /* + * 0x8000 - 0xffff + * user-defined TCTL commands + */ + else if (cmd < 0x8100) { + /* 0x8000-0x80ff: no arg, non-interactive */ + mbargc = 0; + interactive = 0; + } else if (cmd < 0x8200) { + /* 0x8100-0x81ff: 1 arg, non-interactive */ + mbargc = 1; + mbargv[0] = arg & 0xffff; + interactive = 0; + } else if (cmd < 0x9000) { + /* 0x8200-0x8fff: reserved */ + ret = -ENOIOCTLCMD; + goto unlock_out; + } else if (cmd < 0x9100) { + /* 0x9000-0x90ff: no arg, interactive */ + mbargc = 0; + interactive = 1; + } else if (cmd < 0x9200) { + /* 0x9100-0x91ff: 1 arg, interactive */ + mbargc = 1; + mbargv[0] = arg & 0xffff; + interactive = 1; + } else if (cmd < 0x10000) { + /* 0x9200-0xffff: reserved */ + ret = -ENOIOCTLCMD; + goto unlock_out; + } else { + /* + * 0x10000 - + * non TCTL ioctls + */ + switch (cmd) { + case OMAP_DSP_TASK_IOCTL_BFLSH: + ret = dsp_task_flush_buf(dev->task); + break; + case OMAP_DSP_TASK_IOCTL_SETBSZ: + ret = dsp_task_set_fifosz(dev->task, arg); + break; + case OMAP_DSP_TASK_IOCTL_GETNAME: + ret = 0; + if (copy_to_user((void *)arg, dev->name, + strlen(dev->name) + 1)) + ret = -EFAULT; + break; + default: + ret = -ENOIOCTLCMD; + } + goto unlock_out; + } + + /* + * issue TCTL + */ + tid = dev->task->tid; + mbcmd_set(mb, MBCMD(TCTL), tid, cmd); + if (mbargc > 0) { + mbarg.argc = mbargc; + mbarg.tid = tid; + mbarg.argv = mbargv; + mbargp = &mbarg; + } else + mbargp = NULL; + + if (interactive) { + dev->task->tctl_stat = -ERESTARTSYS; + devstate_unlock(dev); + + dsp_mbsend_and_wait_exarg(&mb, mbargp, &dev->ioctl_wait_q); + if (signal_pending(current)) { + ret = -ERESTARTSYS; + goto up_out; + } + if (devstate_lock(dev, OMAP_DSP_DEVSTATE_ATTACHED) < 0) { + ret = -ERESTARTSYS; + goto up_out; + } + ret = dev->task->tctl_stat; + if (ret < 0) { + printk(KERN_ERR "omapdsp: TCTL not responding.\n"); + goto unlock_out; + } + } else { + dsp_mbsend_exarg(&mb, mbargp); + ret = 0; + } + +unlock_out: + devstate_unlock(dev); +up_out: + up(&dev->ioctl_sem); + return ret; +} + +/** + * On demand page allocation is not allowed. The mapping area is defined by + * corresponding DSP tasks. + */ +static struct page *dsp_task_nopage_mmap(struct vm_area_struct *vma, + unsigned long address, int *type) +{ + return NOPAGE_SIGBUS; +} + +static struct vm_operations_struct dsp_task_vm_ops = { + .nopage = dsp_task_nopage_mmap, +}; + +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_lock(dev, OMAP_DSP_DEVSTATE_ATTACHED) < 0) + return -ERESTARTSYS; + task = dev->task; + if (task->map_length == 0) { + printk(KERN_ERR + "omapdsp: task %s doesn't have mmap buffer.\n", + task->name); + ret = -EINVAL; + goto unlock_out; + } + if (is_dsp_internal_mem(task->map_base)) { + printk(KERN_ERR + "omapdsp: task %s: map_base = %p\n" + " DARAM/SARAM can't be used as mmap buffer.\n", + task->name, task->map_base); + ret = -EINVAL; + goto unlock_out; + } + + /* + * 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 = dsp_virt_to_phys(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; + + printk(KERN_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; +unlock_out: + devstate_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) + return -ENODEV; + dev = taskdev[minor]; + if (dev == NULL) + return -ENODEV; + + if (devstate_lock(dev, OMAP_DSP_DEVSTATE_NOTASK | + OMAP_DSP_DEVSTATE_ATTACHED) < 0) + return -ERESTARTSYS; +#ifndef CONFIG_OMAP_DSP_TASK_MULTIOPEN + if (dev->usecount > 0) { + ret = -EBUSY; + goto unlock_out; + } +#endif + + if (dev->state == OMAP_DSP_DEVSTATE_NOTASK) { + dev->state = OMAP_DSP_DEVSTATE_ADDREQ; + /* wake up twch daemon for tadd */ + dsp_twch_touch(); + devstate_unlock(dev); + if (devstate_lock(dev, OMAP_DSP_DEVSTATE_ATTACHED | + OMAP_DSP_DEVSTATE_ADDFAIL) < 0) + return -ERESTARTSYS; + if (dev->state == OMAP_DSP_DEVSTATE_ADDFAIL) { + printk(KERN_ERR "omapdsp: task attach failed for %s!\n", + dev->name); + ret = -EBUSY; + dev->state = OMAP_DSP_DEVSTATE_NOTASK; + wake_up_interruptible_all(&dev->state_wait_q); + goto unlock_out; + } + } + + /* state_lock covers usecount, proc_list as well. */ + dev->usecount++; + proc_list_add(&dev->proc_list, current); + file->f_op = &dev->fops; + devstate_unlock(dev); + + return 0; + +unlock_out: + devstate_unlock(dev); + 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]; + + /* state_lock covers usecount, proc_list as well. */ + spin_lock(&dev->state_lock); + + /* state can be ATTACHED, KILLREQ or GARBAGE here. */ + switch (dev->state) { + + case OMAP_DSP_DEVSTATE_KILLREQ: + dev->usecount--; + break; + + case OMAP_DSP_DEVSTATE_GARBAGE: + if(--dev->usecount == 0) { + dev->state = OMAP_DSP_DEVSTATE_NOTASK; + wake_up_interruptible_all(&dev->state_wait_q); + } + break; + + case OMAP_DSP_DEVSTATE_ATTACHED: + if (dev->lock_pid == current->pid) + taskdev_unlock(dev); + proc_list_del(&dev->proc_list, current); + if (--dev->usecount == 0) { + if (minor >= n_task) { /* dynamic task */ + dev->state = OMAP_DSP_DEVSTATE_DELREQ; + /* wake up twch daemon for tdel */ + dsp_twch_touch(); + } + } + break; + + } + + spin_unlock(&dev->state_lock); + return 0; +} + +/* + * mkdev / rmdev + */ +int dsp_mkdev(char *name) +{ + struct taskdev *dev; + int status; + unsigned char minor; + + if (!dsp_is_ready()) { + printk(KERN_ERR "omapdsp: dsp has not been configured.\n"); + return -EINVAL; + } + for (minor = n_task; minor < TASKDEV_MAX; minor++) { + if (taskdev[minor] == NULL) + goto do_make; + } + printk(KERN_ERR "omapdsp: Too many task devices.\n"); + return -EBUSY; + +do_make: + if ((dev = kmalloc(sizeof(struct taskdev), GFP_KERNEL)) == NULL) + return -ENOMEM; + memset(dev, 0, sizeof(struct taskdev)); + if ((status = taskdev_init(dev, name, minor)) < 0) { + kfree(dev); + return status; + } + return minor; +} + +int dsp_rmdev(char *name) +{ + unsigned char minor; + int ret; + + if (!dsp_is_ready()) { + printk(KERN_ERR "omapdsp: dsp has not been configured.\n"); + return -EINVAL; + } + for (minor = n_task; minor < TASKDEV_MAX; minor++) { + if (taskdev[minor] && !strcmp(taskdev[minor]->name, name)) { + if ((ret = dsp_rmdev_minor(minor)) < 0) + return ret; + return minor; + } + } + return -EINVAL; +} + +static int dsp_rmdev_minor(unsigned char minor) +{ + struct taskdev *dev = taskdev[minor]; + struct dsptask *task = dev->task; + + spin_lock(&dev->state_lock); + + switch (dev->state) { + + case OMAP_DSP_DEVSTATE_NOTASK: + /* fine */ + break; + + case OMAP_DSP_DEVSTATE_ATTACHED: + /* task is working. kill it. */ + { + siginfo_t info; + struct proc_list *pl; + + info.si_signo = SIGBUS; + info.si_errno = 0; + info.si_code = SI_KERNEL; + info._sifields._sigfault._addr = NULL; + list_for_each_entry(pl, &dev->proc_list, list_head) { + send_sig_info(SIGBUS, &info, pl->tsk); + } + taskdev_detach_task(dev); + dsp_task_unconfig(task); + kfree(task); + dev->state = OMAP_DSP_DEVSTATE_GARBAGE; + } + break; + + case OMAP_DSP_DEVSTATE_ADDREQ: + /* open() is waiting. drain it. */ + dev->state = OMAP_DSP_DEVSTATE_ADDFAIL; + wake_up_interruptible_all(&dev->state_wait_q); + break; + + case OMAP_DSP_DEVSTATE_DELREQ: + /* nobody is waiting. */ + dev->state = OMAP_DSP_DEVSTATE_NOTASK; + wake_up_interruptible_all(&dev->state_wait_q); + break; + + case OMAP_DSP_DEVSTATE_KILLREQ: + case OMAP_DSP_DEVSTATE_GARBAGE: + case OMAP_DSP_DEVSTATE_ADDFAIL: + /* transient state. wait for a moment. */ + break; + + } + + spin_unlock(&dev->state_lock); + + /* wait for some time and hope the state is settled */ + devstate_lock_timeout(dev, OMAP_DSP_DEVSTATE_NOTASK, HZ); + if (dev->state != OMAP_DSP_DEVSTATE_NOTASK) { + printk(KERN_WARNING + "omapdsp: illegal device state on rmdev %s.\n", + dev->name); + } + dev->state = OMAP_DSP_DEVSTATE_INVALID; + devstate_unlock(dev); + + taskdev_delete(minor); + kfree(dev); + + return 0; +} + +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, + .mmap = dsp_task_mmap, +}; + +static void dsptask_dev_release(struct device *dev) +{ +} + +static int taskdev_init(struct taskdev *dev, char *name, unsigned char minor) +{ + taskdev[minor] = dev; + + INIT_LIST_HEAD(&dev->proc_list); + init_waitqueue_head(&dev->read_wait_q); + init_waitqueue_head(&dev->write_wait_q); + init_waitqueue_head(&dev->ioctl_wait_q); + init_MUTEX(&dev->read_sem); + init_MUTEX(&dev->write_sem); + init_MUTEX(&dev->ioctl_sem); + init_MUTEX(&dev->lock_sem); + dev->lock_pid = 0; + + strncpy(dev->name, name, OMAP_DSP_TNM_LEN); + dev->name[OMAP_DSP_TNM_LEN-1] = '\0'; + dev->state = (minor < n_task) ? OMAP_DSP_DEVSTATE_ATTACHED : + OMAP_DSP_DEVSTATE_NOTASK; + dev->usecount = 0; + memcpy(&dev->fops, &dsp_task_fops, sizeof(struct file_operations)); + + dev->dev.parent = &dsp_device.dev; + dev->dev.bus = &dsptask_bus; + sprintf(dev->dev.bus_id, "dsptask%d", minor); + dev->dev.release = dsptask_dev_release; + device_register(&dev->dev); + device_create_file(&dev->dev, &dev_attr_devname); + device_create_file(&dev->dev, &dev_attr_devstate); + device_create_file(&dev->dev, &dev_attr_proc_list); + class_simple_device_add(dsp_task_class, + MKDEV(OMAP_DSP_TASK_MAJOR, minor), NULL, + "dsptask%d", minor); + devfs_mk_cdev(MKDEV(OMAP_DSP_TASK_MAJOR, minor), + S_IFCHR | S_IRUGO | S_IWUGO, "dsptask%d", minor); + + init_waitqueue_head(&dev->state_wait_q); + spin_lock_init(&dev->state_lock); + + return 0; +} + +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); + + devfs_remove("dsptask%d", minor); + device_unregister(&dev->dev); + proc_list_flush(&dev->proc_list); + taskdev[minor] = NULL; +} + +static void taskdev_attach_task(struct taskdev *dev, struct dsptask *task) +{ + unsigned short ttyp = task->ttyp; + + dev->task = task; + task->dev = dev; + 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; + dev->fops.write = + rcvtyp_wd(ttyp) ? dsp_task_write_wd: + /* rcvbyp_bk */ dsp_task_write_bk; + + device_create_file(&dev->dev, &dev_attr_taskname); + device_create_file(&dev->dev, &dev_attr_ttyp); + if (sndtyp_wd(ttyp)) { + device_create_file(&dev->dev, &dev_attr_fifosz); + device_create_file(&dev->dev, &dev_attr_fifocnt); + } else + device_create_file(&dev->dev, &dev_attr_ipblink); + device_create_file(&dev->dev, &dev_attr_wsz); + if (task->map_length) + device_create_file(&dev->dev, &dev_attr_mmap); +} + +static void taskdev_detach_task(struct taskdev *dev) +{ + unsigned short 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); + + if (dev->task) { + dev->task = NULL; + dev->fops.read = NULL; + dev->fops.write = NULL; + printk(KERN_INFO "omapdsp: taskdev %s disabled.\n", dev->name); + } +} + +/* + * tadd / tdel / tkill + */ +int dsp_tadd(unsigned char minor, unsigned long adr) +{ + struct taskdev *dev; + struct dsptask *task; + struct mbcmd mb; + struct mb_exarg arg; + unsigned char tid; + unsigned short argv[2]; + int ret = minor; + + if ((minor >= TASKDEV_MAX) || ((dev = taskdev[minor]) == NULL)) { + printk(KERN_ERR + "omapdsp: no task device with minor %d\n", minor); + return -EINVAL; + } + /* + * we don't need to lock state_lock because + * only tadd is allowed when devstate is ADDREQ. + */ + if (dev->state != OMAP_DSP_DEVSTATE_ADDREQ) { + printk(KERN_ERR + "omapdsp: taskdev %s is not requesting for tadd.\n", + dev->name); + return -EINVAL; + } + + if (adr == OMAP_DSP_TADD_ABORTADR) { + /* aborting tadd intentionally */ + printk(KERN_INFO "omapdsp: tadd address is ABORTADR.\n"); + goto fail_out; + } + if (adr >= DSPSPACE_SIZE) { + printk(KERN_ERR + "omapdsp: illegal address 0x%08lx for tadd\n", adr); + ret = -EINVAL; + goto fail_out; + } + + adr >>= 1; /* word address */ + argv[0] = adr >> 16; /* addrh */ + argv[1] = adr & 0xffff; /* addrl */ + + if (down_interruptible(&cfg_sem)) { + ret = -ERESTARTSYS; + goto fail_out; + } + cfg_tid = OMAP_DSP_TID_ANON; + cfg_cmd = MBCMD(TADD); + mbcmd_set(mb, MBCMD(TADD), 0, 0); + arg.tid = OMAP_DSP_TID_ANON; + arg.argc = 2; + arg.argv = argv; + + dsp_mbsend_and_wait_exarg(&mb, &arg, &cfg_wait_q); + + tid = cfg_tid; + cfg_tid = OMAP_DSP_TID_ANON; + cfg_cmd = 0; + up(&cfg_sem); + + if (tid == OMAP_DSP_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 = kmalloc(sizeof(struct dsptask), GFP_KERNEL)) == NULL) { + ret = -ENOMEM; + goto fail_out; + } + memset(task, 0, sizeof(struct dsptask)); + + if ((ret = dsp_task_config(task, tid)) < 0) + goto free_out; + taskdev_attach_task(dev, task); + + 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; + dev->state = OMAP_DSP_DEVSTATE_DELREQ; + dsp_twch_touch(); + return -EINVAL; + } + + dsp_task_init(task); + printk(KERN_INFO "omapdsp: taskdev %s enabled.\n", dev->name); + dev->state = OMAP_DSP_DEVSTATE_ATTACHED; + wake_up_interruptible_all(&dev->state_wait_q); + return minor; + +free_out: + kfree(task); +fail_out: + dev->state = OMAP_DSP_DEVSTATE_ADDFAIL; + wake_up_interruptible_all(&dev->state_wait_q); + return ret; +} + +int dsp_tdel(unsigned char minor) +{ + struct taskdev *dev; + struct dsptask *task; + struct mbcmd mb; + unsigned char tid, tid_response; + int ret = minor; + + if ((minor >= TASKDEV_MAX) || ((dev = taskdev[minor]) == NULL)) { + printk(KERN_ERR + "omapdsp: no task device with minor %d\n", minor); + return -EINVAL; + } + /* + * we don't need to lock state_lock because + * only tdel is allowed when devstate is DELREQ. + */ + if (dev->state != OMAP_DSP_DEVSTATE_DELREQ) { + printk(KERN_ERR + "omapdsp: taskdev %s is not requesting for tdel.\n", + dev->name); + return -EINVAL; + } + + task = dev->task; + tid = task->tid; + if (down_interruptible(&cfg_sem)) { + return -ERESTARTSYS; + } + cfg_tid = OMAP_DSP_TID_ANON; + cfg_cmd = MBCMD(TDEL); + mbcmd_set(mb, MBCMD(TDEL), tid, OMAP_DSP_MBCMD_TDEL_SAFE); + dsp_mbsend_and_wait(&mb, &cfg_wait_q); + tid_response = cfg_tid; + cfg_tid = OMAP_DSP_TID_ANON; + cfg_cmd = 0; + up(&cfg_sem); + + taskdev_detach_task(dev); + dsp_task_unconfig(task); + kfree(task); + dev->state = OMAP_DSP_DEVSTATE_NOTASK; + wake_up_interruptible_all(&dev->state_wait_q); + + if (tid_response != tid) { + printk(KERN_ERR "omapdsp: tdel failed!\n"); + ret = -EINVAL; + } + + return ret; +} + +int dsp_tkill(unsigned char minor) +{ + struct taskdev *dev; + struct dsptask *task; + struct mbcmd mb; + unsigned char tid, tid_response; + siginfo_t info; + struct proc_list *pl; + int ret = minor; + + if ((minor >= TASKDEV_MAX) || ((dev = taskdev[minor]) == NULL)) { + printk(KERN_ERR + "omapdsp: no task device with minor %d\n", minor); + return -EINVAL; + } + spin_lock(&dev->state_lock); + if (dev->state != OMAP_DSP_DEVSTATE_ATTACHED) { + printk(KERN_ERR + "omapdsp: task has not been attached for taskdev %s\n", + dev->name); + spin_unlock(&dev->state_lock); + return -EINVAL; + } + dev->state = OMAP_DSP_DEVSTATE_KILLREQ; + info.si_signo = SIGBUS; + info.si_errno = 0; + info.si_code = SI_KERNEL; + info._sifields._sigfault._addr = NULL; + list_for_each_entry(pl, &dev->proc_list, list_head) { + send_sig_info(SIGBUS, &info, pl->tsk); + } + spin_unlock(&dev->state_lock); + + task = dev->task; + tid = task->tid; + if (down_interruptible(&cfg_sem)) { + tid_response = OMAP_DSP_TID_ANON; + ret = -ERESTARTSYS; + goto detach_out; + } + cfg_tid = OMAP_DSP_TID_ANON; + cfg_cmd = MBCMD(TDEL); + mbcmd_set(mb, MBCMD(TDEL), tid, OMAP_DSP_MBCMD_TDEL_KILL); + dsp_mbsend_and_wait(&mb, &cfg_wait_q); + tid_response = cfg_tid; + cfg_tid = OMAP_DSP_TID_ANON; + cfg_cmd = 0; + up(&cfg_sem); + +detach_out: + taskdev_detach_task(dev); + dsp_task_unconfig(task); + kfree(task); + + if (tid_response != tid) + printk(KERN_ERR "omapdsp: tkill failed!\n"); + + spin_lock(&dev->state_lock); + dev->state = (dev->usecount > 0) ? OMAP_DSP_DEVSTATE_GARBAGE : + OMAP_DSP_DEVSTATE_NOTASK; + wake_up_interruptible_all(&dev->state_wait_q); + spin_unlock(&dev->state_lock); + + return ret; +} + +/* + * state inquiry + */ +long taskdev_state(unsigned char minor) +{ + return taskdev[minor] ? taskdev[minor]->state : + OMAP_DSP_DEVSTATE_NOTASK; +} + +/* + * functions called from mailbox1 interrupt routine + */ +void mbx1_wdsnd(struct mbcmd *mb) +{ + unsigned char tid = mb->cmd_l; + struct dsptask *task = dsptask[tid]; + + if ((tid >= TASKDEV_MAX) || (task == NULL)) { + printk(KERN_ERR "mbx: WDSND with illegal tid! %d\n", tid); + return; + } + if (sndtyp_bk(task->ttyp)) { + printk(KERN_ERR + "mbx: 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 + "mbx: WDSND from passive sending task (task%d) " + "without request!\n", tid); + return; + } + + write_word_to_fifo(&task->rcvdt.fifo, mb->data); + wake_up_interruptible(&task->dev->read_wait_q); +} + +void mbx1_wdreq(struct mbcmd *mb) +{ + unsigned char tid = mb->cmd_l; + struct dsptask *task = dsptask[tid]; + + if ((tid >= TASKDEV_MAX) || (task == NULL)) { + printk(KERN_ERR "mbx: WDREQ with illegal tid! %d\n", tid); + return; + } + if (rcvtyp_psv(task->ttyp)) { + printk(KERN_ERR + "mbx: WDREQ from passive receiving task! (task%d)\n", + tid); + return; + } + + spin_lock(&task->wsz_lock); + task->wsz = 2; + spin_unlock(&task->wsz_lock); + wake_up_interruptible(&task->dev->write_wait_q); +} + +void mbx1_bksnd(struct mbcmd *mb) +{ + unsigned char tid = mb->cmd_l; + unsigned short bid = mb->data; + struct dsptask *task = dsptask[tid]; + unsigned short cnt; + + if (bid >= ipbcfg.ln) { + printk(KERN_ERR "mbx: BKSND with illegal bid! %d\n", bid); + return; + } + ipb_bsycnt_dec(&ipbcfg); + if ((tid >= TASKDEV_MAX) || (task == NULL)) { + printk(KERN_ERR "mbx: BKSND with illegal tid! %d\n", tid); + goto unuse_ipbuf_out; + } + if (sndtyp_wd(task->ttyp)) { + printk(KERN_ERR + "mbx: BKSND from word sending task! (task%d)\n", tid); + goto unuse_ipbuf_out; + } + if (sndtyp_pvt(task->ttyp)) { + printk(KERN_ERR + "mbx: BKSND from private sending task! (task%d)\n", tid); + goto unuse_ipbuf_out; + } + if (sync_with_dsp(&ipbuf[bid]->sd, tid, 10) < 0) { + printk(KERN_ERR "mbx: BKSND - IPBUF sync failed!\n"); + return; + } + + /* should be done in DSP, but just in case. */ + ipbuf[bid]->next = OMAP_DSP_BID_NULL; + + cnt = ipbuf[bid]->c; + if (cnt > ipbcfg.lsz) { + printk(KERN_ERR "mbx: 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(bid); + goto done; + } + spin_lock(&task->rcvdt.bk.link.lock); + ipblink_add_tail(&task->rcvdt.bk.link, bid, ipbuf); + spin_unlock(&task->rcvdt.bk.link.lock); + /* 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(bid); + return; +} + +void mbx1_bkreq(struct mbcmd *mb) +{ + unsigned char tid = mb->cmd_l; + unsigned short cnt = mb->data; + struct dsptask *task = dsptask[tid]; + + if ((tid >= TASKDEV_MAX) || (task == NULL)) { + printk(KERN_ERR "mbx: BKREQ with illegal tid! %d\n", tid); + return; + } + if (rcvtyp_wd(task->ttyp)) { + printk(KERN_ERR + "mbx: BKREQ from word receiving task! (task%d)\n", tid); + return; + } + if (rcvtyp_pvt(task->ttyp)) { + printk(KERN_ERR + "mbx: BKREQ from private receiving task! (task%d)\n", + tid); + return; + } + if (rcvtyp_psv(task->ttyp)) { + printk(KERN_ERR + "mbx: BKREQ from passive receiving task! (task%d)\n", + tid); + return; + } + + spin_lock(&task->wsz_lock); + task->wsz = cnt*2; + spin_unlock(&task->wsz_lock); + wake_up_interruptible(&task->dev->write_wait_q); +} + +void mbx1_bkyld(struct mbcmd *mb) +{ + unsigned short bid = mb->data; + + if (bid >= ipbcfg.ln) { + printk(KERN_ERR "mbx: BKYLD with illegal bid! %d\n", bid); + return; + } + + /* should be done in DSP, but just in case. */ + ipbuf[bid]->next = OMAP_DSP_BID_NULL; + + /* we don't need to sync with DSP */ + ipb_bsycnt_dec(&ipbcfg); + release_ipbuf(bid); +} + +void mbx1_bksndp(struct mbcmd *mb) +{ + unsigned char tid = mb->cmd_l; + struct dsptask *task = dsptask[tid]; + struct rcvdt_bk_struct *rcvdt = &task->rcvdt.bk; + struct ipbuf_p *ipbp = rcvdt->ipbuf_pvt_r; + + if ((tid >= TASKDEV_MAX) || (task == NULL)) { + printk(KERN_ERR "mbx: BKSNDP with illegal tid! %d\n", tid); + return; + } + if (sndtyp_wd(task->ttyp)) { + printk(KERN_ERR + "mbx: BKSNDP from word sending task! (task%d)\n", tid); + return; + } + if (sndtyp_gbl(task->ttyp)) { + printk(KERN_ERR + "mbx: 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. + */ + + if (sync_with_dsp(&ipbp->s, tid, 10) < 0) { + printk(KERN_ERR "mbx: BKSNDP - IPBUF sync failed!\n"); + return; + } + printk(KERN_DEBUG "mbx: ipbuf_pvt_r->a = 0x%08lx\n", + MKLONG(ipbp->ah, ipbp->al)); + spin_lock(&rcvdt->link.lock); + ipblink_add_pvt(&rcvdt->link); + spin_unlock(&rcvdt->link.lock); + wake_up_interruptible(&task->dev->read_wait_q); +} + +void mbx1_bkreqp(struct mbcmd *mb) +{ + unsigned char tid = mb->cmd_l; + struct dsptask *task = dsptask[tid]; + struct ipbuf_p *ipbp = task->ipbuf_pvt_w; + + if ((tid >= TASKDEV_MAX) || (task == NULL)) { + printk(KERN_ERR "mbx: BKREQP with illegal tid! %d\n", tid); + return; + } + if (rcvtyp_wd(task->ttyp)) { + printk(KERN_ERR + "mbx: BKREQP from word receiving task! (task%d)\n", tid); + return; + } + if (rcvtyp_gbl(task->ttyp)) { + printk(KERN_ERR + "mbx: BKREQP from non-private receiving task! (task%d)\n", tid); + return; + } + if (rcvtyp_psv(task->ttyp)) { + printk(KERN_ERR + "mbx: BKREQP from passive receiving task! (task%d)\n", tid); + return; + } + + if (sync_with_dsp(&ipbp->s, OMAP_DSP_TID_FREE, 10) < 0) { + printk(KERN_ERR "mbx: BKREQP - IPBUF sync failed!\n"); + return; + } + printk(KERN_DEBUG "mbx: ipbuf_pvt_w->a = 0x%08lx\n", + MKLONG(ipbp->ah, ipbp->al)); + spin_lock(&task->wsz_lock); + task->wsz = ipbp->c*2; + spin_unlock(&task->wsz_lock); + wake_up_interruptible(&task->dev->write_wait_q); +} + +void mbx1_tctl(struct mbcmd *mb) +{ + unsigned char tid = mb->cmd_l; + struct dsptask *task = dsptask[tid]; + + if ((tid >= TASKDEV_MAX) || (task == NULL)) { + printk(KERN_ERR "mbx: TCTL with illegal tid! %d\n", tid); + return; + } + + if (!waitqueue_active(&task->dev->ioctl_wait_q)) { + printk(KERN_WARNING "mbx: unexpected TCTL from DSP!\n"); + return; + } + + task->tctl_stat = mb->data; + wake_up_interruptible(&task->dev->ioctl_wait_q); +} + +void mbx1_tcfg(struct mbcmd *mb) +{ + unsigned char tid = mb->cmd_l; + struct dsptask *task = dsptask[tid]; + unsigned long tmp_ipbp_r, tmp_ipbp_w; + unsigned long tmp_mapstart, tmp_maplen; + unsigned long tmp_tnm; + unsigned short *tnm; + volatile unsigned short *buf; + int i; + + if ((tid >= TASKDEV_MAX) || (task == NULL)) { + printk(KERN_ERR "mbx: TCFG with illegal tid! %d\n", tid); + return; + } + if ((task->state != TASK_STATE_CFGREQ) || (cfg_cmd != MBCMD(TCFG))) { + printk(KERN_WARNING "mbx: unexpected TCFG from DSP!\n"); + return; + } + + if (sync_with_dsp(&ipbuf_sys_da->s, tid, 10) < 0) { + printk(KERN_ERR "mbx: TCFG - IPBUF sync failed!\n"); + return; + } + + /* + * read configuration data on system IPBUF + */ + buf = ipbuf_sys_da->d; + task->ttyp = buf[0]; + tmp_ipbp_r = MKLONG(buf[1], buf[2]); + tmp_ipbp_w = MKLONG(buf[3], buf[4]); + tmp_mapstart = MKLONG(buf[5], buf[6]); + tmp_maplen = MKLONG(buf[7], buf[8]); + tmp_tnm = MKLONG(buf[9], buf[10]); + + task->rcvdt.bk.ipbuf_pvt_r = dspword_to_virt(tmp_ipbp_r); + task->ipbuf_pvt_w = dspword_to_virt(tmp_ipbp_w); + task->map_base = dspword_to_virt(tmp_mapstart); + task->map_length = tmp_maplen << 1; /* word -> byte */ + tnm = dspword_to_virt(tmp_tnm); + for (i = 0; i < OMAP_DSP_TNM_LEN-1; i++) { + /* avoiding byte access */ + unsigned short tmp = tnm[i]; + task->name[i] = tmp & 0x00ff; + if (!tmp) + break; + } + task->name[OMAP_DSP_TNM_LEN-1] = '\0'; + + release_ipbuf_pvt(ipbuf_sys_da); + task->state = TASK_STATE_READY; + wake_up_interruptible(&cfg_wait_q); +} + +void mbx1_tadd(struct mbcmd *mb) +{ + unsigned char tid = mb->cmd_l; + + if ((!waitqueue_active(&cfg_wait_q)) || (cfg_cmd != MBCMD(TADD))) { + printk(KERN_WARNING "mbx: unexpected TADD from DSP!\n"); + return; + } + cfg_tid = tid; + wake_up_interruptible(&cfg_wait_q); +} + +void mbx1_tdel(struct mbcmd *mb) +{ + unsigned char tid = mb->cmd_l; + + if ((!waitqueue_active(&cfg_wait_q)) || (cfg_cmd != MBCMD(TDEL))) { + printk(KERN_WARNING "mbx: unexpected TDEL from DSP!\n"); + return; + } + cfg_tid = tid; + wake_up_interruptible(&cfg_wait_q); +} + +void mbx1_err_fatal(unsigned char tid) +{ + struct dsptask *task = dsptask[tid]; + struct proc_list *pl; + siginfo_t info; + + if ((tid >= TASKDEV_MAX) || (task == NULL)) { + printk(KERN_ERR "mbx: FATAL ERR with illegal tid! %d\n", tid); + return; + } + + info.si_signo = SIGBUS; + info.si_errno = 0; + info.si_code = SI_KERNEL; + info._sifields._sigfault._addr = NULL; + spin_lock(&task->dev->state_lock); + list_for_each_entry(pl, &task->dev->proc_list, list_head) { + send_sig_info(SIGBUS, &info, pl->tsk); + } + spin_unlock(&task->dev->state_lock); +} + +void mbx1_dbg(struct mbcmd *mb) +{ + unsigned char tid = mb->cmd_l; + char s[80], *s_end = &s[79], *p; + unsigned short *src; + volatile unsigned short *buf; + int cnt; + int i; + + if (((tid >= TASKDEV_MAX) || (dsptask[tid] == NULL)) && + (tid != OMAP_DSP_TID_ANON)) { + printk(KERN_ERR "mbx: DBG with illegal tid! %d\n", tid); + return; + } + if (sync_with_dsp(&ipbuf_sys_da->s, tid, 10) < 0) { + printk(KERN_ERR "mbx: DBG - IPBUF sync failed!\n"); + return; + } + buf = ipbuf_sys_da->d; + cnt = buf[0]; + src = dspword_to_virt(MKLONG(buf[1], buf[2])); + p = s; + for (i = 0; i < cnt; i++) { + unsigned short 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'; + printk(KERN_INFO "%s", s); + p = s; + continue; + } + if (p == s_end) { + *p = '\0'; + printk(KERN_INFO "%s\n", s); + p = s; + continue; + } + } + if (p > s) { + *p = '\0'; + printk(KERN_INFO "%s\n", s); + } + + release_ipbuf_pvt(ipbuf_sys_da); +} + + +/* + * sysfs files + */ +static ssize_t devname_show(struct device *d, char *buf) +{ + struct taskdev *dev = to_taskdev(d); + return sprintf(buf, "%s\n", dev->name); +} + +#define devstate_name(stat) (\ + ((stat) == OMAP_DSP_DEVSTATE_NOTASK) ? "NOTASK" :\ + ((stat) == OMAP_DSP_DEVSTATE_ATTACHED) ? "ATTACHED" :\ + ((stat) == OMAP_DSP_DEVSTATE_GARBAGE) ? "GARBAGE" :\ + ((stat) == OMAP_DSP_DEVSTATE_INVALID) ? "INVALID" :\ + ((stat) == OMAP_DSP_DEVSTATE_ADDREQ) ? "ADDREQ" :\ + ((stat) == OMAP_DSP_DEVSTATE_DELREQ) ? "DELREQ" :\ + ((stat) == OMAP_DSP_DEVSTATE_KILLREQ) ? "KILLREQ" :\ + ((stat) == OMAP_DSP_DEVSTATE_ADDFAIL) ? "ADDFAIL" :\ + "unknown") + +static ssize_t devstate_show(struct device *d, char *buf) +{ + struct taskdev *dev = to_taskdev(d); + return sprintf(buf, "%s\n", devstate_name(dev->state)); +} + +static ssize_t proc_list_show(struct device *d, char *buf) +{ + struct taskdev *dev; + struct proc_list *pl; + int len = 0; + + dev = to_taskdev(d); + spin_lock(&dev->state_lock); + list_for_each_entry(pl, &dev->proc_list, list_head) { + len += sprintf(buf + len, "%d\n", pl->tsk->pid); + } + spin_unlock(&dev->state_lock); + + return len; +} + +static ssize_t taskname_show(struct device *d, char *buf) +{ + struct taskdev *dev = to_taskdev(d); + int len; + + len = sprintf(buf, "%s\n", dev->task->name); + + return len; +} + +static ssize_t ttyp_show(struct device *d, char *buf) +{ + unsigned short ttyp = to_taskdev(d)->task->ttyp; + int len = 0; + + 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"); + + return len; +} + +static ssize_t fifosz_show(struct device *d, char *buf) +{ + struct fifo_struct *fifo = &to_taskdev(d)->task->rcvdt.fifo; + return sprintf(buf, "%d\n", fifo->sz); +} + +static int fifosz_store(struct device *d, const char *buf, size_t count) +{ + struct dsptask *task = to_taskdev(d)->task; + unsigned long fifosz; + int ret; + + fifosz = simple_strtol(buf, NULL, 10); + ret = dsp_task_set_fifosz(task, fifosz); + + return (ret < 0) ? ret : strlen(buf); +} + +static ssize_t fifocnt_show(struct device *d, char *buf) +{ + struct fifo_struct *fifo = &to_taskdev(d)->task->rcvdt.fifo; + return sprintf(buf, "%d\n", fifo->cnt); +} + +static __inline__ char *bid_name(unsigned short bid) +{ + static char s[6]; + + switch (bid) { + case OMAP_DSP_BID_NULL: + return "NULL"; + case OMAP_DSP_BID_PVT: + return "PRIVATE"; + default: + sprintf(s, "%d", bid); + return s; + } +} + +static ssize_t ipblink_show(struct device *d, char *buf) +{ + struct rcvdt_bk_struct *rcvdt = &to_taskdev(d)->task->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; +} + +static ssize_t wsz_show(struct device *d, char *buf) +{ + return sprintf(buf, "%d\n", to_taskdev(d)->task->wsz); +} + +static ssize_t mmap_show(struct device *d, 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_read_proc() + */ +int ipbuf_is_held(unsigned char tid, unsigned short bid) +{ + struct dsptask *task = dsptask[tid]; + unsigned short b; + int ret = 0; + + if (task == NULL) + return 0; + + spin_lock(&task->rcvdt.bk.link.lock); + ipblink_for_each(b, &task->rcvdt.bk.link, ipbuf) { + if (b == bid) { /* found */ + ret = 1; + break; + } + } + spin_unlock(&task->rcvdt.bk.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; + } + + bus_register(&dsptask_bus); + 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_simple_create(THIS_MODULE, "dsptask"); + devfs_mk_dir("dsptask"); + + return 0; +} + +void dsp_taskmod_exit(void) +{ + devfs_remove("dsptask"); + class_simple_destroy(dsp_task_class); + driver_unregister(&dsptask_driver); + bus_unregister(&dsptask_bus); + unregister_chrdev(OMAP_DSP_TASK_MAJOR, "dsptask"); +} diff --git a/arch/arm/mach-omap/dsp/taskwatch.c b/arch/arm/mach-omap/dsp/taskwatch.c new file mode 100644 index 00000000000..fb96c784946 --- /dev/null +++ b/arch/arm/mach-omap/dsp/taskwatch.c @@ -0,0 +1,183 @@ +/* + * linux/arch/arm/mach-omap/dsp/taskwatch.c + * + * OMAP DSP task watch device driver + * + * Copyright (C) 2002-2005 Nokia Corporation + * + * Written by Toshihiro Kobayashi + * + * 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 + * + * 2004/12/01: DSP Gateway version 3.2 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dsp.h" + +static DECLARE_WAIT_QUEUE_HEAD(read_wait_q); +static unsigned int change_cnt; + +void dsp_twch_touch(void) +{ + change_cnt++; + wake_up_interruptible(&read_wait_q); +} + +/* + * @count: represents the device counts of the user's interst + */ +static ssize_t dsp_twch_read(struct file *file, char *buf, size_t count, + loff_t *ppos) +{ + long taskstat[TASKDEV_MAX]; + int devcount = count / sizeof(long); + int i; + + if (!dsp_is_ready()) { + printk(KERN_ERR "omapdsp: dsp has not been configured.\n"); + return -EINVAL; + } + + if (change_cnt == 0) { + long current_state; + DECLARE_WAITQUEUE(wait, current); + + add_wait_queue(&read_wait_q, &wait); + current_state = current->state; + set_current_state(TASK_INTERRUPTIBLE); + if (change_cnt == 0) /* last check */ + schedule(); + set_current_state(current_state); + remove_wait_queue(&read_wait_q, &wait); + + /* unconfigured while waiting ;-( */ + if (dsp_is_ready()) + return -EINVAL; + } + + if (devcount > TASKDEV_MAX) + devcount = TASKDEV_MAX; + + count = devcount * sizeof(long); + change_cnt = 0; + for (i = 0; i < devcount; i++) { + taskstat[i] = taskdev_state(i); + } + + if (copy_to_user(buf, taskstat, count)) + return -EFAULT; + + return count; +} + +static unsigned int dsp_twch_poll(struct file *file, poll_table *wait) +{ + unsigned int mask = 0; + + poll_wait(file, &read_wait_q, wait); + if (change_cnt) + mask |= POLLIN | POLLRDNORM; + + return mask; +} + +static int dsp_twch_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + static DECLARE_MUTEX(ioctl_sem); + int ret; + + if (down_interruptible(&ioctl_sem)) + return -ERESTARTSYS; + + switch (cmd) { + case OMAP_DSP_TWCH_IOCTL_MKDEV: + { + char name[OMAP_DSP_TNM_LEN]; + if (copy_from_user(name, (void *)arg, OMAP_DSP_TNM_LEN)) { + ret = -EFAULT; + goto up_out; + } + name[OMAP_DSP_TNM_LEN-1] = '\0'; + ret = dsp_mkdev(name); + break; + } + + case OMAP_DSP_TWCH_IOCTL_RMDEV: + { + char name[OMAP_DSP_TNM_LEN]; + if (copy_from_user(name, (void *)arg, OMAP_DSP_TNM_LEN)) { + ret = -EFAULT; + goto up_out; + } + name[OMAP_DSP_TNM_LEN-1] = '\0'; + ret = dsp_rmdev(name); + break; + } + + case OMAP_DSP_TWCH_IOCTL_TADD: + { + struct omap_dsp_taddinfo ti; + if (copy_from_user(&ti, (void *)arg, sizeof(ti))) { + ret = -EFAULT; + goto up_out; + } + ret = dsp_tadd(ti.minor, ti.taskadr); + break; + } + + case OMAP_DSP_TWCH_IOCTL_TDEL: + ret = dsp_tdel(arg); + break; + + case OMAP_DSP_TWCH_IOCTL_TKILL: + ret = dsp_tkill(arg); + break; + + default: + ret = -ENOIOCTLCMD; + } + +up_out: + up(&ioctl_sem); + return ret; +} + +struct file_operations dsp_twch_fops = { + .owner = THIS_MODULE, + .read = dsp_twch_read, + .poll = dsp_twch_poll, + .ioctl = dsp_twch_ioctl, +}; + +void dsp_twch_start(void) +{ + change_cnt = 1; /* first read will not wait */ +} + +void dsp_twch_stop(void) +{ + wake_up_interruptible(&read_wait_q); +} diff --git a/arch/arm/mach-omap/dsp/uaccess_dsp.S b/arch/arm/mach-omap/dsp/uaccess_dsp.S new file mode 100644 index 00000000000..07639038d29 --- /dev/null +++ b/arch/arm/mach-omap/dsp/uaccess_dsp.S @@ -0,0 +1,80 @@ +/* + * linux/arch/arm/mach-omap/dsp/uaccess_dsp.S + * + * user memory access functions for DSP driver + * + * Copyright (C) 2004,2005 Nokia Corporation + * + * Written by Toshihiro Kobayashi + * + * 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 + * + * 2004/06/29: DSP Gateway version 3.2 + */ + +#include +#include + + .text + +/* Prototype: int __arch_copy_to_user_dsp_2b(void *to, const char *from) + * Purpose : copy 2 bytes to user memory from kernel(DSP) memory + * escaping from unexpected byte swap using __arch_copy_to_user() + * in OMAP architecture. + * Params : to - user memory + * : from - kernel(DSP) memory + * Returns : success = 0, failure = 2 + */ + +ENTRY(__arch_copy_to_user_dsp_2b) + stmfd sp!, {r4, lr} + ldrb r3, [r1], #1 + ldrb r4, [r1], #1 +USER( strbt r4, [r0], #1) @ May fault +USER( strbt r3, [r0], #1) @ May fault + mov r0, #0 + LOADREGS(fd,sp!,{r4, pc}) + + .section .fixup,"ax" + .align 0 +9001: mov r0, #2 + LOADREGS(fd,sp!, {r4, pc}) + .previous + +/* Prototype: unsigned long __arch_copy_from_user_dsp_2b(void *to, const void *from); + * Purpose : copy 2 bytes from user memory to kernel(DSP) memory + * escaping from unexpected byte swap using __arch_copy_to_user() + * in OMAP architecture. + * Params : to - kernel (DSP) memory + * : from - user memory + * Returns : success = 0, failure = 2 + */ + +ENTRY(__arch_copy_from_user_dsp_2b) + stmfd sp!, {r4, lr} +USER( ldrbt r3, [r1], #1) @ May fault +USER( ldrbt r4, [r1], #1) @ May fault + strb r4, [r0], #1 + strb r3, [r0], #1 + mov r0, #0 + LOADREGS(fd,sp!,{r4, pc}) + + .section .fixup,"ax" + .align 0 +9001: mov r3, #0 + strh r3, [r0], #2 + mov r0, #2 + LOADREGS(fd,sp!, {r4, pc}) + .previous diff --git a/arch/arm/mach-omap/dsp/uaccess_dsp.h b/arch/arm/mach-omap/dsp/uaccess_dsp.h new file mode 100644 index 00000000000..d1f445340bd --- /dev/null +++ b/arch/arm/mach-omap/dsp/uaccess_dsp.h @@ -0,0 +1,186 @@ +/* + * linux/arch/arm/mach-omap/dsp/uaccess_dsp.h + * + * Header for user access functions for DSP driver + * + * Copyright (C) 2002-2005 Nokia Corporation + * + * Modified from linux/include/asm-arm/uaccess.h + * by Toshihiro Kobayashi + * + * 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 + * + * 2004/06/29: DSP Gateway version 3.2 + */ + +#ifndef _OMAP_DSP_UACCESS_DSP_H +#define _OMAP_DSP_UACCESS_DSP_H + +#include + +#define HAVE_ASM_COPY_FROM_USER_DSP_2B + +#ifdef HAVE_ASM_COPY_FROM_USER_DSP_2B +extern unsigned long __arch_copy_from_user_dsp_2b(void *to, + const void __user *from); +extern unsigned long __arch_copy_to_user_dsp_2b(void __user *to, + const void *from); +#endif + +extern unsigned long dspmem_base, dspmem_size; +#define is_dsp_internal_mem(va) \ + (((unsigned long)(va) >= dspmem_base) && \ + ((unsigned long)(va) < dspmem_base + dspmem_size)) + + +#ifndef HAVE_ASM_COPY_FROM_USER_DSP_2B +static __inline__ unsigned long copy_from_user_dsp_2b(void *to, + const void *from) +{ + unsigned short tmp; + + if (__arch_copy_from_user(&tmp, from, 2)) + return 2; + /* expecting compiler to generate "strh" instruction */ + *((unsigned short *)to) = tmp; + return 0; +} +#endif + +/* + * @n must be multiple of 2 + */ +static __inline__ unsigned long copy_from_user_dsp(void *to, const void *from, + unsigned long n) +{ + if (access_ok(VERIFY_READ, from, n)) { + if ((is_dsp_internal_mem(to)) && + (((unsigned long)to & 2) || (n & 2))) { + /* + * DARAM/SARAM with odd word alignment + */ + unsigned long n4; + unsigned long last_n; + + /* dest not aligned -- copy 2 bytes */ + if (((unsigned long)to & 2) && (n >= 2)) { +#ifdef HAVE_ASM_COPY_FROM_USER_DSP_2B + if (__arch_copy_from_user_dsp_2b(to, from)) +#else + if (copy_from_user_dsp_2b(to, from)) +#endif + return n; + to += 2; + from += 2; + n -= 2; + } + /* middle 4*n bytes */ + last_n = n & 2; + n4 = n - last_n; + if ((n = __arch_copy_from_user(to, from, n4)) != 0) + return n + last_n; + /* last 2 bytes */ + if (last_n) { + to += n4; + from += n4; +#ifdef HAVE_ASM_COPY_FROM_USER_DSP_2B + if (__arch_copy_from_user_dsp_2b(to, from)) +#else + if (copy_from_user_dsp_2b(to, from)) +#endif + return 2; + n = 0; + } + } else { + /* + * DARAM/SARAM with 4-byte alignment or + * external memory + */ + n = __arch_copy_from_user(to, from, n); + } + } + else /* security hole - plug it */ + memzero(to, n); + return n; +} + +#ifndef HAVE_ASM_COPY_FROM_USER_DSP_2B +static __inline__ unsigned long copy_to_user_dsp_2b(void *to, const void *from) +{ + /* expecting compiler to generate "strh" instruction */ + unsigned short tmp = *(unsigned short *)from; + + return __arch_copy_to_user(to, &tmp, 2); +} +#endif + +/* + * @n must be multiple of 2 + */ +static __inline__ unsigned long copy_to_user_dsp(void *to, const void *from, + unsigned long n) +{ + if (access_ok(VERIFY_WRITE, to, n)) { + if ((is_dsp_internal_mem(from)) && + (((unsigned long)to & 2) || (n & 2))) { + /* + * DARAM/SARAM with odd word alignment + */ + unsigned long n4; + unsigned long last_n; + + /* dest not aligned -- copy 2 bytes */ + if (((unsigned long)to & 2) && (n >= 2)) { +#ifdef HAVE_ASM_COPY_FROM_USER_DSP_2B + if (__arch_copy_to_user_dsp_2b(to, from)) +#else + if (copy_to_user_dsp_2b(to, from)) +#endif + return n; + to += 2; + from += 2; + n -= 2; + } + /* middle 4*n bytes */ + last_n = n & 2; + n4 = n - last_n; + if ((n = __arch_copy_to_user(to, from, n4)) != 0) + return n + last_n; + /* last 2 bytes */ + if (last_n) { + to += n4; + from += n4; +#ifdef HAVE_ASM_COPY_FROM_USER_DSP_2B + if (__arch_copy_to_user_dsp_2b(to, from)) +#else + if (copy_to_user_dsp_2b(to, from)) +#endif + return 2; + n = 0; + } + } else { + /* + * DARAM/SARAM with 4-byte alignment or + * external memory + */ + n = __arch_copy_to_user(to, from, n); + } + } + return n; +} + +#undef is_dsp_internal_mem + +#endif /* _OMAP_DSP_UACCESS_DSP_H */ diff --git a/arch/arm/mach-omap/omap1/Kconfig b/arch/arm/mach-omap/omap1/Kconfig index 7408ac94f77..478e0d6a51d 100644 --- a/arch/arm/mach-omap/omap1/Kconfig +++ b/arch/arm/mach-omap/omap1/Kconfig @@ -142,3 +142,5 @@ config OMAP_ARM_30MHZ help Enable 30MHz clock for OMAP CPU. If unsure, say N. +source "arch/arm/mach-omap/dsp/Kconfig" + diff --git a/include/asm-arm/arch-omap/dsp.h b/include/asm-arm/arch-omap/dsp.h new file mode 100644 index 00000000000..a4a17ba1514 --- /dev/null +++ b/include/asm-arm/arch-omap/dsp.h @@ -0,0 +1,236 @@ +/* + * linux/include/asm-arm/arch-omap/dsp.h + * + * Header for OMAP DSP driver + * + * Copyright (C) 2002-2005 Nokia Corporation + * + * Written by Toshihiro Kobayashi + * + * 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 + * + * 2005/01/17: DSP Gateway version 3.2 + */ + +#ifndef ASM_ARCH_DSP_H +#define ASM_ARCH_DSP_H + + +/* + * for /dev/dspctl/ctl + */ +#define OMAP_DSP_IOCTL_RESET 1 +#define OMAP_DSP_IOCTL_RUN 2 +#define OMAP_DSP_IOCTL_SETRSTVECT 3 +#define OMAP_DSP_IOCTL_IDLE 4 +#define OMAP_DSP_IOCTL_MPUI_WORDSWAP_ON 5 +#define OMAP_DSP_IOCTL_MPUI_WORDSWAP_OFF 6 +#define OMAP_DSP_IOCTL_MPUI_BYTESWAP_ON 7 +#define OMAP_DSP_IOCTL_MPUI_BYTESWAP_OFF 8 +#define OMAP_DSP_IOCTL_DSPCFG 10 +#define OMAP_DSP_IOCTL_DSPUNCFG 11 +#define OMAP_DSP_IOCTL_TASKCNT 12 +#define OMAP_DSP_IOCTL_REGMEMR 40 +#define OMAP_DSP_IOCTL_REGMEMW 41 +#define OMAP_DSP_IOCTL_REGIOR 42 +#define OMAP_DSP_IOCTL_REGIOW 43 +#define OMAP_DSP_IOCTL_GETVAR 44 +#define OMAP_DSP_IOCTL_SETVAR 45 +#define OMAP_DSP_IOCTL_RUNLEVEL 50 +#define OMAP_DSP_IOCTL_SUSPEND 51 +#define OMAP_DSP_IOCTL_RESUME 52 +#define OMAP_DSP_IOCTL_FBEN 53 +#define OMAP_DSP_IOCTL_FBDIS 54 +#define OMAP_DSP_IOCTL_MBSEND 99 + +/* + * for taskdev + * (ioctls below should be >= 0x10000) + */ +#define OMAP_DSP_TASK_IOCTL_BFLSH 0x10000 +#define OMAP_DSP_TASK_IOCTL_SETBSZ 0x10001 +#define OMAP_DSP_TASK_IOCTL_LOCK 0x10002 +#define OMAP_DSP_TASK_IOCTL_UNLOCK 0x10003 +#define OMAP_DSP_TASK_IOCTL_GETNAME 0x10004 + +/* + * for /dev/dspctl/mem + */ +#define OMAP_DSP_MEM_IOCTL_EXMAP 1 +#define OMAP_DSP_MEM_IOCTL_EXUNMAP 2 +#define OMAP_DSP_MEM_IOCTL_EXMAP_FLUSH 3 +#define OMAP_DSP_MEM_IOCTL_FBEXPORT 5 +#define OMAP_DSP_MEM_IOCTL_MMUITACK 7 +#define OMAP_DSP_MEM_IOCTL_MMUINIT 9 +#define OMAP_DSP_MEM_IOCTL_KMEM_RESERVE 11 +#define OMAP_DSP_MEM_IOCTL_KMEM_RELEASE 12 + +struct omap_dsp_mapinfo { + unsigned long dspadr; + unsigned long size; +}; + +/* + * for /dev/dspctl/twch + */ +#define OMAP_DSP_TWCH_IOCTL_MKDEV 1 +#define OMAP_DSP_TWCH_IOCTL_RMDEV 2 +#define OMAP_DSP_TWCH_IOCTL_TADD 11 +#define OMAP_DSP_TWCH_IOCTL_TDEL 12 +#define OMAP_DSP_TWCH_IOCTL_TKILL 13 + +#define OMAP_DSP_DEVSTATE_NOTASK 0x00000001 +#define OMAP_DSP_DEVSTATE_ATTACHED 0x00000002 +#define OMAP_DSP_DEVSTATE_GARBAGE 0x00000004 +#define OMAP_DSP_DEVSTATE_INVALID 0x00000008 +#define OMAP_DSP_DEVSTATE_ADDREQ 0x00000100 +#define OMAP_DSP_DEVSTATE_DELREQ 0x00000200 +#define OMAP_DSP_DEVSTATE_KILLREQ 0x00000400 +#define OMAP_DSP_DEVSTATE_ADDFAIL 0x00001000 + +struct omap_dsp_taddinfo { + unsigned char minor; + unsigned long taskadr; +}; +#define OMAP_DSP_TADD_ABORTADR 0xffffffff + + +/* + * error cause definition (for error detection device) + */ +#define OMAP_DSP_ERRDT_WDT 0x00000001 +#define OMAP_DSP_ERRDT_MMU 0x00000002 + + +/* + * mailbox protocol definitions + */ + +struct omap_dsp_mailbox_cmd { + unsigned short cmd; + unsigned short data; +}; + +struct omap_dsp_reginfo { + unsigned short adr; + unsigned short val; +}; + +struct omap_dsp_varinfo { + unsigned char varid; + unsigned short val[0]; +}; + +#define OMAP_DSP_MBPROT_REVISION 0x0018 + +#define OMAP_DSP_MBCMD_WDSND 0x10 +#define OMAP_DSP_MBCMD_WDREQ 0x11 +#define OMAP_DSP_MBCMD_BKSND 0x20 +#define OMAP_DSP_MBCMD_BKREQ 0x21 +#define OMAP_DSP_MBCMD_BKYLD 0x23 +#define OMAP_DSP_MBCMD_BKSNDP 0x24 +#define OMAP_DSP_MBCMD_BKREQP 0x25 +#define OMAP_DSP_MBCMD_TCTL 0x30 +#define OMAP_DSP_MBCMD_TCTLDATA 0x31 +#define OMAP_DSP_MBCMD_WDT 0x50 +#define OMAP_DSP_MBCMD_RUNLEVEL 0x51 +#define OMAP_DSP_MBCMD_PM 0x52 +#define OMAP_DSP_MBCMD_SUSPEND 0x53 +#define OMAP_DSP_MBCMD_KFUNC 0x54 +#define OMAP_DSP_MBCMD_TCFG 0x60 +#define OMAP_DSP_MBCMD_TADD 0x62 +#define OMAP_DSP_MBCMD_TDEL 0x63 +#define OMAP_DSP_MBCMD_TSTOP 0x65 +#define OMAP_DSP_MBCMD_DSPCFG 0x70 +#define OMAP_DSP_MBCMD_REGRW 0x72 +#define OMAP_DSP_MBCMD_GETVAR 0x74 +#define OMAP_DSP_MBCMD_SETVAR 0x75 +#define OMAP_DSP_MBCMD_ERR 0x78 +#define OMAP_DSP_MBCMD_DBG 0x79 + +#define OMAP_DSP_MBCMD_TCTL_TINIT 0x0000 +#define OMAP_DSP_MBCMD_TCTL_TEN 0x0001 +#define OMAP_DSP_MBCMD_TCTL_TDIS 0x0002 +#define OMAP_DSP_MBCMD_TCTL_TCLR 0x0003 +#define OMAP_DSP_MBCMD_TCTL_TCLR_FORCE 0x0004 + +#define OMAP_DSP_MBCMD_RUNLEVEL_USER 0x01 +#define OMAP_DSP_MBCMD_RUNLEVEL_SUPER 0x0e +#define OMAP_DSP_MBCMD_RUNLEVEL_RECOVERY 0x10 + +#define OMAP_DSP_MBCMD_PM_DISABLE 0x00 +#define OMAP_DSP_MBCMD_PM_ENABLE 0x01 + +#define OMAP_DSP_MBCMD_KFUNC_FBCTL 0x00 + +#define OMAP_DSP_MBCMD_FBCTL_ENABLE 0x0002 +#define OMAP_DSP_MBCMD_FBCTL_DISABLE 0x0003 + +#define OMAP_DSP_MBCMD_TDEL_SAFE 0x0000 +#define OMAP_DSP_MBCMD_TDEL_KILL 0x0001 + +#define OMAP_DSP_MBCMD_DSPCFG_REQ 0x00 +#define OMAP_DSP_MBCMD_DSPCFG_SYSADRH 0x28 +#define OMAP_DSP_MBCMD_DSPCFG_SYSADRL 0x29 +#define OMAP_DSP_MBCMD_DSPCFG_PROTREV 0x70 +#define OMAP_DSP_MBCMD_DSPCFG_ABORT 0x78 +#define OMAP_DSP_MBCMD_DSPCFG_LAST 0x80 + +#define OMAP_DSP_MBCMD_REGRW_MEMR 0x00 +#define OMAP_DSP_MBCMD_REGRW_MEMW 0x01 +#define OMAP_DSP_MBCMD_REGRW_IOR 0x02 +#define OMAP_DSP_MBCMD_REGRW_IOW 0x03 +#define OMAP_DSP_MBCMD_REGRW_DATA 0x04 + +#define OMAP_DSP_MBCMD_VARID_ICRMASK 0x00 +#define OMAP_DSP_MBCMD_VARID_LOADINFO 0x01 + +#define OMAP_DSP_TTYP_ARCV 0x0001 +#define OMAP_DSP_TTYP_ASND 0x0002 +#define OMAP_DSP_TTYP_BKMD 0x0004 +#define OMAP_DSP_TTYP_BKDM 0x0008 +#define OMAP_DSP_TTYP_PVMD 0x0010 +#define OMAP_DSP_TTYP_PVDM 0x0020 + +#define OMAP_DSP_EID_BADTID 0x10 +#define OMAP_DSP_EID_BADTCN 0x11 +#define OMAP_DSP_EID_BADBID 0x20 +#define OMAP_DSP_EID_BADCNT 0x21 +#define OMAP_DSP_EID_NOTLOCKED 0x22 +#define OMAP_DSP_EID_STVBUF 0x23 +#define OMAP_DSP_EID_BADADR 0x24 +#define OMAP_DSP_EID_BADTCTL 0x30 +#define OMAP_DSP_EID_BADPARAM 0x50 +#define OMAP_DSP_EID_FATAL 0x58 +#define OMAP_DSP_EID_NOMEM 0xc0 +#define OMAP_DSP_EID_NORES 0xc1 +#define OMAP_DSP_EID_IPBFULL 0xc2 +#define OMAP_DSP_EID_TASKNOTRDY 0xe0 +#define OMAP_DSP_EID_TASKBSY 0xe1 +#define OMAP_DSP_EID_TASKERR 0xef +#define OMAP_DSP_EID_BADCFGTYP 0xf0 +#define OMAP_DSP_EID_DEBUG 0xf8 +#define OMAP_DSP_EID_BADSEQ 0xfe +#define OMAP_DSP_EID_BADCMD 0xff + +#define OMAP_DSP_TNM_LEN 16 + +#define OMAP_DSP_TID_FREE 0xff +#define OMAP_DSP_TID_ANON 0xfe + +#define OMAP_DSP_BID_NULL 0xffff +#define OMAP_DSP_BID_PVT 0xfffe + +#endif /* ASM_ARCH_DSP_H */ diff --git a/include/asm-arm/arch-omap/dsp_common.h b/include/asm-arm/arch-omap/dsp_common.h new file mode 100644 index 00000000000..2cad37e1ae8 --- /dev/null +++ b/include/asm-arm/arch-omap/dsp_common.h @@ -0,0 +1,32 @@ +/* + * linux/include/asm-arm/arch-omap/dsp_common.h + * + * Header for OMAP DSP subsystem control + * + * Copyright (C) 2004,2005 Nokia Corporation + * + * Written by Toshihiro Kobayashi + * + * 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 + * + * 2004/09/22: DSP Gateway version 3.2 + */ + +#ifndef ASM_ARCH_DSP_COMMON_H +#define ASM_ARCH_DSP_COMMON_H + +void omap_dsp_request_idle(void); + +#endif /* ASM_ARCH_DSP_COMMON_H */