diff options
Diffstat (limited to 'drivers/staging/dream/qdsp5/audpp.c')
-rw-r--r-- | drivers/staging/dream/qdsp5/audpp.c | 429 |
1 files changed, 429 insertions, 0 deletions
diff --git a/drivers/staging/dream/qdsp5/audpp.c b/drivers/staging/dream/qdsp5/audpp.c new file mode 100644 index 00000000000..d06556eda31 --- /dev/null +++ b/drivers/staging/dream/qdsp5/audpp.c @@ -0,0 +1,429 @@ + +/* arch/arm/mach-msm/qdsp5/audpp.c + * + * common code to deal with the AUDPP dsp task (audio postproc) + * + * Copyright (C) 2008 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/wait.h> +#include <linux/delay.h> + +#include <asm/atomic.h> +#include <asm/ioctls.h> +#include <mach/msm_adsp.h> + +#include "audmgr.h" + +#include <mach/qdsp5/qdsp5audppcmdi.h> +#include <mach/qdsp5/qdsp5audppmsg.h> + +/* for queue ids - should be relative to module number*/ +#include "adsp.h" + +#include "evlog.h" + + +enum { + EV_NULL, + EV_ENABLE, + EV_DISABLE, + EV_EVENT, + EV_DATA, +}; + +static const char *dsp_log_strings[] = { + "NULL", + "ENABLE", + "DISABLE", + "EVENT", + "DATA", +}; + +DECLARE_LOG(dsp_log, 64, dsp_log_strings); + +static int __init _dsp_log_init(void) +{ + return ev_log_init(&dsp_log); +} +module_init(_dsp_log_init); +#define LOG(id,arg) ev_log_write(&dsp_log, id, arg) + +static DEFINE_MUTEX(audpp_lock); + +#define CH_COUNT 5 +#define AUDPP_CLNT_MAX_COUNT 6 +#define AUDPP_AVSYNC_INFO_SIZE 7 + +struct audpp_state { + struct msm_adsp_module *mod; + audpp_event_func func[AUDPP_CLNT_MAX_COUNT]; + void *private[AUDPP_CLNT_MAX_COUNT]; + struct mutex *lock; + unsigned open_count; + unsigned enabled; + + /* which channels are actually enabled */ + unsigned avsync_mask; + + /* flags, 48 bits sample/bytes counter per channel */ + uint16_t avsync[CH_COUNT * AUDPP_CLNT_MAX_COUNT + 1]; +}; + +struct audpp_state the_audpp_state = { + .lock = &audpp_lock, +}; + +int audpp_send_queue1(void *cmd, unsigned len) +{ + return msm_adsp_write(the_audpp_state.mod, + QDSP_uPAudPPCmd1Queue, cmd, len); +} +EXPORT_SYMBOL(audpp_send_queue1); + +int audpp_send_queue2(void *cmd, unsigned len) +{ + return msm_adsp_write(the_audpp_state.mod, + QDSP_uPAudPPCmd2Queue, cmd, len); +} +EXPORT_SYMBOL(audpp_send_queue2); + +int audpp_send_queue3(void *cmd, unsigned len) +{ + return msm_adsp_write(the_audpp_state.mod, + QDSP_uPAudPPCmd3Queue, cmd, len); +} +EXPORT_SYMBOL(audpp_send_queue3); + +static int audpp_dsp_config(int enable) +{ + audpp_cmd_cfg cmd; + + cmd.cmd_id = AUDPP_CMD_CFG; + cmd.cfg = enable ? AUDPP_CMD_CFG_ENABLE : AUDPP_CMD_CFG_SLEEP; + + return audpp_send_queue1(&cmd, sizeof(cmd)); +} + +static void audpp_broadcast(struct audpp_state *audpp, unsigned id, + uint16_t *msg) +{ + unsigned n; + for (n = 0; n < AUDPP_CLNT_MAX_COUNT; n++) { + if (audpp->func[n]) + audpp->func[n] (audpp->private[n], id, msg); + } +} + +static void audpp_notify_clnt(struct audpp_state *audpp, unsigned clnt_id, + unsigned id, uint16_t *msg) +{ + if (clnt_id < AUDPP_CLNT_MAX_COUNT && audpp->func[clnt_id]) + audpp->func[clnt_id] (audpp->private[clnt_id], id, msg); +} + +static void audpp_dsp_event(void *data, unsigned id, size_t len, + void (*getevent)(void *ptr, size_t len)) +{ + struct audpp_state *audpp = data; + uint16_t msg[8]; + + if (id == AUDPP_MSG_AVSYNC_MSG) { + getevent(audpp->avsync, sizeof(audpp->avsync)); + + /* mask off any channels we're not watching to avoid + * cases where we might get one last update after + * disabling avsync and end up in an odd state when + * we next read... + */ + audpp->avsync[0] &= audpp->avsync_mask; + return; + } + + getevent(msg, sizeof(msg)); + + LOG(EV_EVENT, (id << 16) | msg[0]); + LOG(EV_DATA, (msg[1] << 16) | msg[2]); + + switch (id) { + case AUDPP_MSG_STATUS_MSG:{ + unsigned cid = msg[0]; + pr_info("audpp: status %d %d %d\n", cid, msg[1], + msg[2]); + if ((cid < 5) && audpp->func[cid]) + audpp->func[cid] (audpp->private[cid], id, msg); + break; + } + case AUDPP_MSG_HOST_PCM_INTF_MSG: + if (audpp->func[5]) + audpp->func[5] (audpp->private[5], id, msg); + break; + case AUDPP_MSG_PCMDMAMISSED: + pr_err("audpp: DMA missed obj=%x\n", msg[0]); + break; + case AUDPP_MSG_CFG_MSG: + if (msg[0] == AUDPP_MSG_ENA_ENA) { + pr_info("audpp: ENABLE\n"); + audpp->enabled = 1; + audpp_broadcast(audpp, id, msg); + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + pr_info("audpp: DISABLE\n"); + audpp->enabled = 0; + audpp_broadcast(audpp, id, msg); + } else { + pr_err("audpp: invalid config msg %d\n", msg[0]); + } + break; + case AUDPP_MSG_ROUTING_ACK: + audpp_broadcast(audpp, id, msg); + break; + case AUDPP_MSG_FLUSH_ACK: + audpp_notify_clnt(audpp, msg[0], id, msg); + break; + default: + pr_info("audpp: unhandled msg id %x\n", id); + } +} + +static struct msm_adsp_ops adsp_ops = { + .event = audpp_dsp_event, +}; + +static void audpp_fake_event(struct audpp_state *audpp, int id, + unsigned event, unsigned arg) +{ + uint16_t msg[1]; + msg[0] = arg; + audpp->func[id] (audpp->private[id], event, msg); +} + +int audpp_enable(int id, audpp_event_func func, void *private) +{ + struct audpp_state *audpp = &the_audpp_state; + int res = 0; + + if (id < -1 || id > 4) + return -EINVAL; + + if (id == -1) + id = 5; + + mutex_lock(audpp->lock); + if (audpp->func[id]) { + res = -EBUSY; + goto out; + } + + audpp->func[id] = func; + audpp->private[id] = private; + + LOG(EV_ENABLE, 1); + if (audpp->open_count++ == 0) { + pr_info("audpp: enable\n"); + res = msm_adsp_get("AUDPPTASK", &audpp->mod, &adsp_ops, audpp); + if (res < 0) { + pr_err("audpp: cannot open AUDPPTASK\n"); + audpp->open_count = 0; + audpp->func[id] = NULL; + audpp->private[id] = NULL; + goto out; + } + LOG(EV_ENABLE, 2); + msm_adsp_enable(audpp->mod); + audpp_dsp_config(1); + } else { + unsigned long flags; + local_irq_save(flags); + if (audpp->enabled) + audpp_fake_event(audpp, id, + AUDPP_MSG_CFG_MSG, AUDPP_MSG_ENA_ENA); + local_irq_restore(flags); + } + + res = 0; +out: + mutex_unlock(audpp->lock); + return res; +} +EXPORT_SYMBOL(audpp_enable); + +void audpp_disable(int id, void *private) +{ + struct audpp_state *audpp = &the_audpp_state; + unsigned long flags; + + if (id < -1 || id > 4) + return; + + if (id == -1) + id = 5; + + mutex_lock(audpp->lock); + LOG(EV_DISABLE, 1); + if (!audpp->func[id]) + goto out; + if (audpp->private[id] != private) + goto out; + + local_irq_save(flags); + audpp_fake_event(audpp, id, AUDPP_MSG_CFG_MSG, AUDPP_MSG_ENA_DIS); + audpp->func[id] = NULL; + audpp->private[id] = NULL; + local_irq_restore(flags); + + if (--audpp->open_count == 0) { + pr_info("audpp: disable\n"); + LOG(EV_DISABLE, 2); + audpp_dsp_config(0); + msm_adsp_disable(audpp->mod); + msm_adsp_put(audpp->mod); + audpp->mod = NULL; + } +out: + mutex_unlock(audpp->lock); +} +EXPORT_SYMBOL(audpp_disable); + +#define BAD_ID(id) ((id < 0) || (id >= CH_COUNT)) + +void audpp_avsync(int id, unsigned rate) +{ + unsigned long flags; + audpp_cmd_avsync cmd; + + if (BAD_ID(id)) + return; + + local_irq_save(flags); + if (rate) + the_audpp_state.avsync_mask |= (1 << id); + else + the_audpp_state.avsync_mask &= (~(1 << id)); + the_audpp_state.avsync[0] &= the_audpp_state.avsync_mask; + local_irq_restore(flags); + + cmd.cmd_id = AUDPP_CMD_AVSYNC; + cmd.object_number = id; + cmd.interrupt_interval_lsw = rate; + cmd.interrupt_interval_msw = rate >> 16; + audpp_send_queue1(&cmd, sizeof(cmd)); +} +EXPORT_SYMBOL(audpp_avsync); + +unsigned audpp_avsync_sample_count(int id) +{ + uint16_t *avsync = the_audpp_state.avsync; + unsigned val; + unsigned long flags; + unsigned mask; + + if (BAD_ID(id)) + return 0; + + mask = 1 << id; + id = id * AUDPP_AVSYNC_INFO_SIZE + 2; + local_irq_save(flags); + if (avsync[0] & mask) + val = (avsync[id] << 16) | avsync[id + 1]; + else + val = 0; + local_irq_restore(flags); + + return val; +} +EXPORT_SYMBOL(audpp_avsync_sample_count); + +unsigned audpp_avsync_byte_count(int id) +{ + uint16_t *avsync = the_audpp_state.avsync; + unsigned val; + unsigned long flags; + unsigned mask; + + if (BAD_ID(id)) + return 0; + + mask = 1 << id; + id = id * AUDPP_AVSYNC_INFO_SIZE + 5; + local_irq_save(flags); + if (avsync[0] & mask) + val = (avsync[id] << 16) | avsync[id + 1]; + else + val = 0; + local_irq_restore(flags); + + return val; +} +EXPORT_SYMBOL(audpp_avsync_byte_count); + +#define AUDPP_CMD_CFG_OBJ_UPDATE 0x8000 +#define AUDPP_CMD_VOLUME_PAN 0 + +int audpp_set_volume_and_pan(unsigned id, unsigned volume, int pan) +{ + /* cmd, obj_cfg[7], cmd_type, volume, pan */ + uint16_t cmd[11]; + + if (id > 6) + return -EINVAL; + + memset(cmd, 0, sizeof(cmd)); + cmd[0] = AUDPP_CMD_CFG_OBJECT_PARAMS; + cmd[1 + id] = AUDPP_CMD_CFG_OBJ_UPDATE; + cmd[8] = AUDPP_CMD_VOLUME_PAN; + cmd[9] = volume; + cmd[10] = pan; + + return audpp_send_queue3(cmd, sizeof(cmd)); +} +EXPORT_SYMBOL(audpp_set_volume_and_pan); + +int audpp_pause(unsigned id, int pause) +{ + /* pause 1 = pause 0 = resume */ + u16 pause_cmd[AUDPP_CMD_DEC_CTRL_LEN / sizeof(unsigned short)]; + + if (id >= CH_COUNT) + return -EINVAL; + + memset(pause_cmd, 0, sizeof(pause_cmd)); + + pause_cmd[0] = AUDPP_CMD_DEC_CTRL; + if (pause == 1) + pause_cmd[1 + id] = AUDPP_CMD_UPDATE_V | AUDPP_CMD_PAUSE_V; + else if (pause == 0) + pause_cmd[1 + id] = AUDPP_CMD_UPDATE_V | AUDPP_CMD_RESUME_V; + else + return -EINVAL; + + return audpp_send_queue1(pause_cmd, sizeof(pause_cmd)); +} +EXPORT_SYMBOL(audpp_pause); + +int audpp_flush(unsigned id) +{ + u16 flush_cmd[AUDPP_CMD_DEC_CTRL_LEN / sizeof(unsigned short)]; + + if (id >= CH_COUNT) + return -EINVAL; + + memset(flush_cmd, 0, sizeof(flush_cmd)); + + flush_cmd[0] = AUDPP_CMD_DEC_CTRL; + flush_cmd[1 + id] = AUDPP_CMD_UPDATE_V | AUDPP_CMD_FLUSH_V; + + return audpp_send_queue1(flush_cmd, sizeof(flush_cmd)); +} +EXPORT_SYMBOL(audpp_flush); |