/* * This file is part of OMAP DSP driver (DSP Gateway version 3.3.1) * * Copyright (C) 2002-2006 Nokia Corporation. All rights reserved. * * Contact: Toshihiro Kobayashi * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include #include #include #include #include #include #include #include #include #include #include "dsp_mbcmd.h" #include "dsp.h" #include "ipbuf.h" #include "dsp_common.h" MODULE_AUTHOR("Toshihiro Kobayashi "); MODULE_DESCRIPTION("OMAP DSP driver module"); MODULE_LICENSE("GPL"); static struct sync_seq *mbseq; static u16 mbseq_expect_tmp; static u16 *mbseq_expect = &mbseq_expect_tmp; extern 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);