blob: ceeb5e5c97c4cf006978418bae5ae2eeafb7cf6b [file] [log] [blame]
/*
* This file is part of OMAP DSP driver (DSP Gateway version 3.3.1)
*
* Copyright (C) 2002-2006 Nokia Corporation. All rights reserved.
*
* Contact: Toshihiro Kobayashi <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
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/mutex.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <asm/delay.h>
#include <asm/arch/mailbox.h>
#include <asm/arch/dsp_common.h>
#include "dsp_mbcmd.h"
#include "dsp.h"
#include "ipbuf.h"
#include "dsp_common.h"
MODULE_AUTHOR("Toshihiro Kobayashi <toshihiro.kobayashi@nokia.com>");
MODULE_DESCRIPTION("OMAP DSP driver module");
MODULE_LICENSE("GPL");
static struct sync_seq *mbseq;
static u16 mbseq_expect_tmp;
static u16 *mbseq_expect = &mbseq_expect_tmp;
extern void dsp_mem_late_init(void);
/*
* mailbox commands
*/
extern void mbox_wdsnd(struct mbcmd *mb);
extern void mbox_wdreq(struct mbcmd *mb);
extern void mbox_bksnd(struct mbcmd *mb);
extern void mbox_bkreq(struct mbcmd *mb);
extern void mbox_bkyld(struct mbcmd *mb);
extern void mbox_bksndp(struct mbcmd *mb);
extern void mbox_bkreqp(struct mbcmd *mb);
extern void mbox_tctl(struct mbcmd *mb);
extern void mbox_poll(struct mbcmd *mb);
#ifdef OLD_BINARY_SUPPORT
/* v3.3 obsolete */
extern void mbox_wdt(struct mbcmd *mb);
#endif
extern void mbox_suspend(struct mbcmd *mb);
static void mbox_kfunc(struct mbcmd *mb);
extern void mbox_tcfg(struct mbcmd *mb);
extern void mbox_tadd(struct mbcmd *mb);
extern void mbox_tdel(struct mbcmd *mb);
extern void mbox_dspcfg(struct mbcmd *mb);
extern void mbox_regrw(struct mbcmd *mb);
extern void mbox_getvar(struct mbcmd *mb);
extern void mbox_err(struct mbcmd *mb);
extern void mbox_dbg(struct mbcmd *mb);
static const struct cmdinfo
cif_wdsnd = { "WDSND", CMD_L_TYPE_TID, mbox_wdsnd },
cif_wdreq = { "WDREQ", CMD_L_TYPE_TID, mbox_wdreq },
cif_bksnd = { "BKSND", CMD_L_TYPE_TID, mbox_bksnd },
cif_bkreq = { "BKREQ", CMD_L_TYPE_TID, mbox_bkreq },
cif_bkyld = { "BKYLD", CMD_L_TYPE_NULL, mbox_bkyld },
cif_bksndp = { "BKSNDP", CMD_L_TYPE_TID, mbox_bksndp },
cif_bkreqp = { "BKREQP", CMD_L_TYPE_TID, mbox_bkreqp },
cif_tctl = { "TCTL", CMD_L_TYPE_TID, mbox_tctl },
cif_poll = { "POLL", CMD_L_TYPE_NULL, mbox_poll },
#ifdef OLD_BINARY_SUPPORT
/* v3.3 obsolete */
cif_wdt = { "WDT", CMD_L_TYPE_NULL, mbox_wdt },
#endif
cif_runlevel = { "RUNLEVEL", CMD_L_TYPE_SUBCMD, NULL },
cif_pm = { "PM", CMD_L_TYPE_SUBCMD, NULL },
cif_suspend = { "SUSPEND", CMD_L_TYPE_NULL, mbox_suspend },
cif_kfunc = { "KFUNC", CMD_L_TYPE_SUBCMD, mbox_kfunc },
cif_tcfg = { "TCFG", CMD_L_TYPE_TID, mbox_tcfg },
cif_tadd = { "TADD", CMD_L_TYPE_TID, mbox_tadd },
cif_tdel = { "TDEL", CMD_L_TYPE_TID, mbox_tdel },
cif_tstop = { "TSTOP", CMD_L_TYPE_TID, NULL },
cif_dspcfg = { "DSPCFG", CMD_L_TYPE_SUBCMD, mbox_dspcfg },
cif_regrw = { "REGRW", CMD_L_TYPE_SUBCMD, mbox_regrw },
cif_getvar = { "GETVAR", CMD_L_TYPE_SUBCMD, mbox_getvar },
cif_setvar = { "SETVAR", CMD_L_TYPE_SUBCMD, NULL },
cif_err = { "ERR", CMD_L_TYPE_SUBCMD, mbox_err },
cif_dbg = { "DBG", CMD_L_TYPE_NULL, mbox_dbg };
#define MBOX_CMD_MAX 0x80
const struct cmdinfo *cmdinfo[MBOX_CMD_MAX] = {
[MBOX_CMD_DSP_WDSND] = &cif_wdsnd,
[MBOX_CMD_DSP_WDREQ] = &cif_wdreq,
[MBOX_CMD_DSP_BKSND] = &cif_bksnd,
[MBOX_CMD_DSP_BKREQ] = &cif_bkreq,
[MBOX_CMD_DSP_BKYLD] = &cif_bkyld,
[MBOX_CMD_DSP_BKSNDP] = &cif_bksndp,
[MBOX_CMD_DSP_BKREQP] = &cif_bkreqp,
[MBOX_CMD_DSP_TCTL] = &cif_tctl,
[MBOX_CMD_DSP_POLL] = &cif_poll,
#ifdef OLD_BINARY_SUPPORT
[MBOX_CMD_DSP_WDT] = &cif_wdt, /* v3.3 obsolete */
#endif
[MBOX_CMD_DSP_RUNLEVEL] = &cif_runlevel,
[MBOX_CMD_DSP_PM] = &cif_pm,
[MBOX_CMD_DSP_SUSPEND] = &cif_suspend,
[MBOX_CMD_DSP_KFUNC] = &cif_kfunc,
[MBOX_CMD_DSP_TCFG] = &cif_tcfg,
[MBOX_CMD_DSP_TADD] = &cif_tadd,
[MBOX_CMD_DSP_TDEL] = &cif_tdel,
[MBOX_CMD_DSP_TSTOP] = &cif_tstop,
[MBOX_CMD_DSP_DSPCFG] = &cif_dspcfg,
[MBOX_CMD_DSP_REGRW] = &cif_regrw,
[MBOX_CMD_DSP_GETVAR] = &cif_getvar,
[MBOX_CMD_DSP_SETVAR] = &cif_setvar,
[MBOX_CMD_DSP_ERR] = &cif_err,
[MBOX_CMD_DSP_DBG] = &cif_dbg,
};
static int dsp_kfunc_probe_devices(struct omap_dsp *dsp)
{
struct dsp_kfunc_device *p;
int ret, fail = 0;
mutex_lock(&dsp->lock);
list_for_each_entry(p, dsp->kdev_list, entry) {
if (p->probe == NULL)
continue;
ret = p->probe(p);
if (ret) {
printk(KERN_ERR
"probing %s failed\n", p->name);
fail++;
}
}
mutex_unlock(&dsp->lock);
pr_debug("%s() fail:%d\n", __FUNCTION__, fail);
return fail;
}
static int dsp_kfunc_remove_devices(struct omap_dsp *dsp)
{
struct dsp_kfunc_device *p;
int ret, fail = 0;
mutex_lock(&dsp->lock);
list_for_each_entry_reverse(p, dsp->kdev_list, entry) {
if (p->remove == NULL)
continue;
ret = p->remove(p);
if (ret) {
printk(KERN_ERR
"removing %s failed\n", p->name);
fail++;
}
}
mutex_unlock(&dsp->lock);
pr_debug("%s() fail:%d\n", __FUNCTION__, fail);
return fail;
}
static int dsp_kfunc_enable_devices(struct omap_dsp *dsp, int type, int stage)
{
struct dsp_kfunc_device *p;
int ret, fail = 0;
mutex_lock(&dsp->lock);
list_for_each_entry(p, dsp->kdev_list, entry) {
if ((p->type != type) || (p->enable == NULL))
continue;
ret = p->enable(p, stage);
if (ret) {
printk(KERN_ERR
"enabling %s failed\n", p->name);
fail++;
}
}
mutex_unlock(&dsp->lock);
pr_debug("%s(%d) fail:%d\n", __FUNCTION__, type, fail);
return fail;
}
static int dsp_kfunc_disable_devices(struct omap_dsp *dsp, int type, int stage)
{
struct dsp_kfunc_device *p;
int ret, fail = 0;
mutex_lock(&dsp->lock);
list_for_each_entry_reverse(p, omap_dsp->kdev_list, entry) {
if ((p->type != type) || (p->disable == NULL))
continue;
ret = p->disable(p, stage);
if (ret) {
printk(KERN_ERR
"disabling %s failed\n", p->name);
fail++;
}
}
mutex_unlock(&dsp->lock);
pr_debug("%s(%d) fail:%d\n", __FUNCTION__, type, fail);
return fail;
}
int sync_with_dsp(u16 *adr, u16 val, int try_cnt)
{
int try;
if (*(volatile u16 *)adr == val)
return 0;
for (try = 0; try < try_cnt; try++) {
udelay(1);
if (*(volatile u16 *)adr == val) {
/* success! */
pr_info("omapdsp: sync_with_dsp(): try = %d\n", try);
return 0;
}
}
/* fail! */
return -1;
}
static int mbcmd_sender_prepare(void *data)
{
struct mb_exarg *arg = data;
int i, ret = 0;
/*
* even if ipbuf_sys_ad is in DSP internal memory,
* dsp_mem_enable() never cause to call PM mailbox command
* because in that case DSP memory should be always enabled.
* (see ipbuf_sys_hold_mem_active in ipbuf.c)
*
* Therefore, we can call this function here safely.
*/
if (sync_with_dsp(&ipbuf_sys_ad->s, TID_FREE, 10) < 0) {
printk(KERN_ERR "omapdsp: ipbuf_sys_ad is busy.\n");
ret = -EBUSY;
goto out;
}
for (i = 0; i < arg->argc; i++) {
ipbuf_sys_ad->d[i] = arg->argv[i];
}
ipbuf_sys_ad->s = arg->tid;
out:
return ret;
}
/*
* __dsp_mbcmd_send_exarg(): mailbox dispatcher
*/
int __dsp_mbcmd_send_exarg(struct mbcmd *mb, struct mb_exarg *arg,
int recovery_flag)
{
int ret = 0;
if (unlikely(omap_dsp->enabled == 0)) {
ret = dsp_kfunc_enable_devices(omap_dsp,
DSP_KFUNC_DEV_TYPE_COMMON, 0);
if (ret == 0)
omap_dsp->enabled = 1;
}
/*
* while MMU fault is set,
* only recovery command can be executed
*/
if (dsp_err_isset(ERRCODE_MMU) && !recovery_flag) {
printk(KERN_ERR
"mbox: mmu interrupt is set. %s is aborting.\n",
cmd_name(*mb));
goto out;
}
if (arg)
dsp_mem_enable(ipbuf_sys_ad);
ret = omap_mbox_msg_send(omap_dsp->mbox,
*(mbox_msg_t *)mb, (void*)arg);
if (ret)
goto out;
if (mbseq)
mbseq->ad_arm++;
mblog_add(mb, DIR_A2D);
out:
if (arg)
dsp_mem_disable(ipbuf_sys_ad);
return ret;
}
int dsp_mbcmd_send_and_wait_exarg(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_mbcmd_send_exarg(mb, arg) < 0) {
set_current_state(current_state);
remove_wait_queue(q, &wait);
return -1;
}
schedule_timeout(DSP_TIMEOUT);
set_current_state(current_state);
remove_wait_queue(q, &wait);
return 0;
}
/*
* mbcmd receiver
*/
static void mbcmd_receiver(mbox_msg_t msg)
{
struct mbcmd *mb = (struct mbcmd *)&msg;
if (cmdinfo[mb->cmd_h] == NULL) {
printk(KERN_ERR
"invalid message (%08x) for mbcmd_receiver().\n", msg);
return;
}
(*mbseq_expect)++;
mblog_add(mb, DIR_D2A);
/* call handler for the command */
if (cmdinfo[mb->cmd_h]->handler)
cmdinfo[mb->cmd_h]->handler(mb);
else
printk(KERN_ERR "mbox: %s is not allowed from DSP.\n",
cmd_name(*mb));
}
static int mbsync_hold_mem_active;
void dsp_mbox_start(void)
{
omap_mbox_init_seq(omap_dsp->mbox);
mbseq_expect_tmp = 0;
}
void dsp_mbox_stop(void)
{
mbseq = NULL;
mbseq_expect = &mbseq_expect_tmp;
}
int dsp_mbox_config(void *p)
{
unsigned long flags;
if (dsp_address_validate(p, sizeof(struct sync_seq), "mbseq") < 0)
return -1;
if (dsp_mem_type(p, sizeof(struct sync_seq)) != MEM_TYPE_EXTERN) {
printk(KERN_WARNING
"omapdsp: mbseq is placed in DSP internal memory.\n"
" It will prevent DSP from idling.\n");
mbsync_hold_mem_active = 1;
/*
* dsp_mem_enable() never fails because
* it has been already enabled in dspcfg process and
* this will just increment the usecount.
*/
dsp_mem_enable((void *)daram_base);
}
local_irq_save(flags);
mbseq = p;
mbseq->da_arm = mbseq_expect_tmp;
mbseq_expect = &mbseq->da_arm;
local_irq_restore(flags);
return 0;
}
static int __init dsp_mbox_init(void)
{
omap_dsp->mbox = omap_mbox_get("dsp");
if (IS_ERR(omap_dsp->mbox)) {
printk(KERN_ERR "failed to get mailbox handler for DSP.\n");
return -ENODEV;
}
omap_dsp->mbox->msg_receive_cb = mbcmd_receiver;
omap_dsp->mbox->msg_sender_cb = mbcmd_sender_prepare;
return 0;
}
static void dsp_mbox_exit(void)
{
omap_dsp->mbox->msg_sender_cb = NULL;
omap_dsp->mbox->msg_receive_cb = NULL;
if (mbsync_hold_mem_active) {
dsp_mem_disable((void *)daram_base);
mbsync_hold_mem_active = 0;
}
}
/*
* kernel function dispatcher
*/
extern void mbox_fbctl_upd(void);
extern void mbox_fbctl_disable(struct mbcmd *mb);
static void mbox_kfunc_fbctl(struct mbcmd *mb)
{
switch (mb->data) {
case FBCTL_UPD:
mbox_fbctl_upd();
break;
case FBCTL_DISABLE:
mbox_fbctl_disable(mb);
break;
default:
printk(KERN_ERR
"mbox: Unknown FBCTL from DSP: 0x%04x\n", mb->data);
}
}
/*
* dspgw: KFUNC message handler
*/
static void mbox_kfunc_power(unsigned short data)
{
int ret = -1;
switch (data) {
case DVFS_START: /* ACK from DSP */
/* TBD */
break;
case AUDIO_PWR_UP:
ret = dsp_kfunc_enable_devices(omap_dsp,
DSP_KFUNC_DEV_TYPE_AUDIO, 0);
if (ret == 0)
ret++;
break;
case AUDIO_PWR_DOWN: /* == AUDIO_PWR_DOWN1 */
ret = dsp_kfunc_disable_devices(omap_dsp,
DSP_KFUNC_DEV_TYPE_AUDIO, 1);
break;
case AUDIO_PWR_DOWN2:
ret = dsp_kfunc_disable_devices(omap_dsp,
DSP_KFUNC_DEV_TYPE_AUDIO, 2);
break;
case DSP_PWR_DOWN:
ret = dsp_kfunc_disable_devices(omap_dsp,
DSP_KFUNC_DEV_TYPE_COMMON, 0);
if (ret == 0)
omap_dsp->enabled = 0;
break;
default:
printk(KERN_ERR
"mailbox: Unknown PWR from DSP: 0x%04x\n", data);
break;
}
if (unlikely(ret < 0)) {
printk(KERN_ERR "mailbox: PWR(0x%04x) failed\n", data);
return;
}
if (likely(ret == 0))
return;
mbcompose_send(KFUNC, KFUNC_POWER, data);
}
static void mbox_kfunc(struct mbcmd *mb)
{
switch (mb->cmd_l) {
case KFUNC_FBCTL:
mbox_kfunc_fbctl(mb);
break;
case KFUNC_POWER:
mbox_kfunc_power(mb->data);
break;
default:
printk(KERN_ERR
"mbox: Unknown KFUNC from DSP: 0x%02x\n", mb->cmd_l);
}
}
int dsp_late_init(void)
{
int ret;
dsp_clk_autoidle();
#ifdef CONFIG_ARCH_OMAP2
clk_enable(dsp_fck_handle);
clk_enable(dsp_ick_handle);
__dsp_per_enable();
#endif
dsp_mem_late_init();
#ifdef CONFIG_ARCH_OMAP1
dsp_set_idle_boot_base(IDLEPG_BASE, IDLEPG_SIZE);
#endif
ret = dsp_kfunc_enable_devices(omap_dsp,
DSP_KFUNC_DEV_TYPE_COMMON, 0);
if (ret == 0)
omap_dsp->enabled = 0;
return 0;
}
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);
/*
* driver functions
*/
static int __init dsp_drv_probe(struct platform_device *pdev)
{
int ret;
struct omap_dsp *info;
struct dsp_platform_data *pdata = pdev->dev.platform_data;
dev_info(&pdev->dev, "OMAP DSP driver initialization\n");
info = kzalloc(sizeof(struct omap_dsp), GFP_KERNEL);
if (unlikely(info == NULL)) {
dev_dbg(&pdev->dev, "no memory for info\n");
return -ENOMEM;
}
platform_set_drvdata(pdev, info);
omap_dsp = info;
mutex_init(&info->lock);
info->dev = &pdev->dev;
info->kdev_list = &pdata->kdev_list;
ret = dsp_kfunc_probe_devices(info);
if (ret) {
ret = -ENXIO;
goto fail0;
}
info->mmu_irq = platform_get_irq_byname(pdev, "dsp_mmu");
if (unlikely(info->mmu_irq) < 0) {
ret = -ENXIO;
goto fail1;
}
if ((ret = dsp_ctl_core_init()) < 0)
goto fail2;
if ((ret = dsp_mem_init()) < 0)
goto fail3;
dsp_ctl_init();
mblog_init();
if ((ret = dsp_taskmod_init()) < 0)
goto fail4;
if ((ret = dsp_mbox_init()) < 0)
goto fail5;
return 0;
fail5:
dsp_taskmod_exit();
fail4:
mblog_exit();
dsp_ctl_exit();
dsp_mem_exit();
fail3:
dsp_ctl_core_exit();
fail2:
fail1:
dsp_kfunc_remove_devices(info);
fail0:
kfree(info);
return ret;
}
static int dsp_drv_remove(struct platform_device *pdev)
{
struct omap_dsp *info = platform_get_drvdata(pdev);
dsp_cpustat_request(CPUSTAT_RESET);
dsp_cfgstat_request(CFGSTAT_CLEAN);
dsp_mbox_exit();
dsp_taskmod_exit();
mblog_exit();
dsp_ctl_exit();
dsp_mem_exit();
dsp_ctl_core_exit();
#ifdef CONFIG_ARCH_OMAP2
__dsp_per_disable();
clk_disable(dsp_ick_handle);
clk_disable(dsp_fck_handle);
#endif
dsp_kfunc_remove_devices(info);
kfree(info);
return 0;
}
#ifdef CONFIG_PM
static int dsp_drv_suspend(struct platform_device *pdev, pm_message_t state)
{
dsp_cfgstat_request(CFGSTAT_SUSPEND);
return 0;
}
static int dsp_drv_resume(struct platform_device *pdev)
{
dsp_cfgstat_request(CFGSTAT_RESUME);
return 0;
}
#else
#define dsp_drv_suspend NULL
#define dsp_drv_resume NULL
#endif /* CONFIG_PM */
static struct platform_driver dsp_driver = {
.probe = dsp_drv_probe,
.remove = dsp_drv_remove,
.suspend = dsp_drv_suspend,
.resume = dsp_drv_resume,
.driver = {
.name = "dsp",
},
};
static int __init omap_dsp_mod_init(void)
{
return platform_driver_register(&dsp_driver);
}
static void __exit omap_dsp_mod_exit(void)
{
platform_driver_unregister(&dsp_driver);
}
module_init(omap_dsp_mod_init);
module_exit(omap_dsp_mod_exit);