Commit 09172659 authored by Toshihiro Kobayashi's avatar Toshihiro Kobayashi Committed by Tony Lindgren

ARM: OMAP: Add DSP gateway

Adds support for integrated DSP on OMAP processors.
Signed-off-by: default avatarToshihiro Kobayashi <toshihiro.kobayashi@nokia.com>
Signed-off-by: default avatarTony Lindgren <tony@atomide.com>
parent e24e6af6
......@@ -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/
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.
#
# 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
/*
* linux/arch/arm/mach-omap/dsp/dsp.h
*
* Header for OMAP DSP driver
*
* Copyright (C) 2002-2005 Nokia Corporation
*
* Written by Toshihiro Kobayashi <toshihiro.kobayashi@nokia.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* 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;
/*
* linux/arch/arm/mach-omap/dsp/dsp_common.c
*
* OMAP DSP driver static part
*
* Copyright (C) 2002-2005 Nokia Corporation
*
* Written by Toshihiro Kobayashi <toshihiro.kobayashi@nokia.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* 2004/11/19: DSP Gateway version 3.2
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/mm.h>
#include <asm/tlbflush.h>
#include <asm/irq.h>
#include <asm/arch/dsp.h>
#include <asm/arch/tc.h>
#include <asm/hardware/clock.h>
#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
/*
* 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 <toshihiro.kobayashi@nokia.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* 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);
/*
* linux/arch/arm/mach-omap/dsp/dsp_core.c
*
* OMAP DSP driver
*
* Copyright (C) 2002-2005 Nokia Corporation
*
* Written by Toshihiro Kobayashi <toshihiro.kobayashi@nokia.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* 2005/02/15: DSP Gateway version 3.2
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/devfs_fs_kernel.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/signal.h>
#include <asm/delay.h>
#include <asm/irq.h>
#include <asm/arch/dsp.h>
#include "hardware_dsp.h"
#include "dsp.h"
#include "ipbuf.h"
MODULE_AUTHOR("Toshihiro Kobayashi <toshihiro.kobayashi@nokia.com>");
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);
/*
* linux/arch/arm/mach-omap/dsp/dsp_ctl.c
*
* OMAP DSP control device driver
*
* Copyright (C) 2002-2005 Nokia Corporation
*
* Written by Toshihiro Kobayashi <toshihiro.kobayashi@nokia.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* 2005/02/17: DSP Gateway version 3.2
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/major.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/proc_fs.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/ioctls.h>
#include <asm/hardware/clock.h>
#include <asm/arch/dsp.h>
#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(&reg, (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(&reg, (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
}
/*
* 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 <toshihiro.kobayashi@nokia.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* 2005/02/10: DSP Gateway version 3.2
*/
#include <linux/module.h>
#include <linux/major.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/devfs_fs_kernel.h>
#include <linux/init.h>
#include <asm/arch/dsp.h>
#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");
}
/*
* linux/arch/arm/mach-omap/dsp/dsp_mem.c
*
* OMAP DSP memory driver
*
* Copyright (C) 2002-2005 Nokia Corporation
*
* Written by Toshihiro Kobayashi <toshihiro.kobayashi@nokia.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Toshihiro Kobayashi <toshihiro.kobayashi@nokia.com>
* 2005/02/17: DSP Gateway version 3.2
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/major.h>
#include <linux/fs.h>
#include <linux/bootmem.h>
#include <linux/fb.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/ioctls.h>
#include <asm/irq.h>
#include <asm/pgalloc.h>
#include <asm/pgtable.h>
#include <asm/hardware/clock.h>
#include <asm/arch/tc.h>
#include <asm/arch/dsp.h>
#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);
}
/*
* 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 <toshihiro.kobayashi@nokia.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* 2004/11/22: DSP Gateway version 3.2
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/major.h>
#include <linux/fs.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/ioctls.h>
#include <asm/arch/dsp.h>
#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);
}
/*
* linux/arch/arm/mach-omap/dsp/fifo.h
*
* FIFO buffer operators
*
* Copyright (C) 2002-2005 Nokia Corporation
*
* Written by Toshihiro Kobayashi <toshihiro.kobayashi@nokia.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* 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;
}
/*
* 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 <toshihiro.kobayashi@nokia.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* 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 */
/*
* linux/arch/arm/mach-omap/dsp/ipbuf.c
*
* IPBUF handler
*
* Copyright (C) 2002-2005 Nokia Corporation
*
* Written by Toshihiro Kobayashi <toshihiro.kobayashi@nokia.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* 2005/02/17: DSP Gateway version 3.2
*/
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/device.h>
#include <asm/signal.h>
#include <asm/arch/dsp.h>
#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);
/*
* linux/arch/arm/mach-omap/dsp/ipbuf.h
*
* Header for IPBUF
*
* Copyright (C) 2002-2005 Nokia Corporation
*
* Written by Toshihiro Kobayashi <toshihiro.kobayashi@nokia.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* 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)
/*
* linux/arch/arm/mach-omap/dsp/mblog.c
*
* OMAP DSP driver Mailbox log module
*
* Copyright (C) 2003-2005 Nokia Corporation
*
* Written by Toshihiro Kobayashi <toshihiro.kobayashi@nokia.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* 2005/02/17: DSP Gateway version 3.2
*/
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/device.h>
#include <linux/init.h>
#include <asm/irq.h>
#include <asm/arch/dsp.h>
#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);
}
/*
* linux/arch/arm/mach-omap/dsp/proclist.h
*
* Linux task list handler
*
* Copyright (C) 2004,2005 Nokia Corporation
*
* Written by Toshihiro Kobayashi <toshihiro.kobayashi@nokia.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* 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);
}
}
/*
* linux/arch/arm/mach-omap/dsp/task.c
*
* OMAP DSP task device driver
*
* Copyright (C) 2002-2005 Nokia Corporation
*
* Written by Toshihiro Kobayashi <toshihiro.kobayashi@nokia.com>
* mmap function by Hiroo Ishikawa <ext-hiroo.ishikawa@nokia.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* 2005/02/22: DSP Gateway version 3.2
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/major.h>
#include <linux/fs.h>
#include <linux/poll.h>
#include <linux/device.h>
#include <linux/devfs_fs_kernel.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/signal.h>
#include <asm/irq.h>
#include <asm/ioctls.h>
#include <asm/arch/dsp.h>
#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");
}
/*
* linux/arch/arm/mach-omap/dsp/taskwatch.c
*
* OMAP DSP task watch device driver
*
* Copyright (C) 2002-2005 Nokia Corporation
*
* Written by Toshihiro Kobayashi <toshihiro.kobayashi@nokia.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* 2004/12/01: DSP Gateway version 3.2
*/
#include <linux/module.h>
#include <linux/major.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/poll.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/arch/dsp.h>
#include "dsp.h"
static DECLARE_WAIT_QUEUE_HEAD(read_wait_q);
static unsigned int change_cnt;
void dsp_twch_touch(void)
{
change_cnt++;
wake_up_interruptible(&read_wait_q);
}
/*
* @count: represents the device counts of the user's interst
*/
static ssize_t dsp_twch_read(struct file *file, char *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);
}
/*
* 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 <toshihiro.kobayashi@nokia.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* 2004/06/29: DSP Gateway version 3.2
*/
#include <linux/linkage.h>
#include <asm/assembler.h>
.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
/*
* 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 <toshihiro.kobayashi@nokia.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* 2004/06/29: DSP Gateway version 3.2
*/
#ifndef _OMAP_DSP_UACCESS_DSP_H
#define _OMAP_DSP_UACCESS_DSP_H
#include <asm/uaccess.h>
#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 */
......@@ -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"
/*
* linux/include/asm-arm/arch-omap/dsp.h
*
* Header for OMAP DSP driver
*
* Copyright (C) 2002-2005 Nokia Corporation
*
* Written by Toshihiro Kobayashi <toshihiro.kobayashi@nokia.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* 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 */
/*
* 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 <toshihiro.kobayashi@nokia.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* 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 */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment