| /* arch/arm/mach-msm/qdsp5v2/adsp.c |
| * |
| * Copyright (C) 2010 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/fs.h> |
| #include <linux/module.h> |
| #include <linux/sched.h> |
| #include <linux/wait.h> |
| #include <linux/platform_device.h> |
| #include <linux/slab.h> |
| #include <linux/io.h> |
| #include <linux/delay.h> |
| #include <linux/interrupt.h> |
| |
| #include <mach/msm_iomap.h> |
| |
| #include "../dal.h" |
| |
| #include "adsp.h" |
| #include "adsp_private.h" |
| |
| struct msm_adsp_queue { |
| const char *name; |
| uint32_t offset; |
| uint32_t max_size; |
| uint32_t flags; |
| }; |
| |
| struct msm_adsp_module { |
| msm_adsp_callback func; |
| void *cookie; |
| |
| wait_queue_head_t wait; |
| struct msm_adsp *adsp; |
| uint32_t id; |
| |
| unsigned active; |
| |
| const char *name; |
| struct msm_adsp_module *next; |
| struct msm_adsp_queue queue[ADSP_QUEUES_MAX]; |
| }; |
| |
| struct msm_adsp { |
| /* DSP "registers" */ |
| void *read_ctrl; |
| void *write_ctrl; |
| void *send_irq; |
| void *base; |
| |
| /* DAL client handle for DSP control service */ |
| struct dal_client *client; |
| |
| spinlock_t callback_lock; |
| spinlock_t write_lock; |
| spinlock_t event_lock; |
| |
| wait_queue_head_t callback_wq; |
| |
| /* list of all existing dsp modules */ |
| struct msm_adsp_module *all_modules; |
| |
| /* map from dsp rtos task IDs to modules */ |
| struct msm_adsp_module *task_to_module[ADSP_TASKS_MAX]; |
| |
| /* used during initialization */ |
| struct adsp_module_info tmpmodule; |
| |
| }; |
| |
| static struct msm_adsp the_adsp; |
| |
| static struct msm_adsp_module *id_to_module(struct msm_adsp *adsp, unsigned id) |
| { |
| struct msm_adsp_module *module; |
| |
| for (module = adsp->all_modules; module; module = module->next) |
| if (module->id == id) |
| return module; |
| return NULL; |
| } |
| |
| int msm_adsp_get(const char *name, struct msm_adsp_module **module, |
| msm_adsp_callback func, void *cookie) |
| { |
| struct msm_adsp *adsp = &the_adsp; |
| unsigned long flags; |
| int ret = -ENODEV; |
| struct msm_adsp_module *m; |
| |
| for (m = adsp->all_modules; m; m = m->next) { |
| if (!strcmp(m->name, name)) { |
| spin_lock_irqsave(&m->adsp->callback_lock, flags); |
| if (m->func == 0) { |
| m->func = func; |
| m->cookie = cookie; |
| *module = m; |
| ret = 0; |
| } else { |
| ret = -EBUSY; |
| } |
| spin_unlock_irqrestore(&m->adsp->callback_lock, flags); |
| break; |
| } |
| } |
| return ret; |
| } |
| |
| void msm_adsp_put(struct msm_adsp_module *m) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&m->adsp->callback_lock, flags); |
| m->func = 0; |
| m->cookie = 0; |
| spin_unlock_irqrestore(&m->adsp->callback_lock, flags); |
| } |
| |
| |
| int msm_adsp_lookup_queue(struct msm_adsp_module *module, const char *name) |
| { |
| int n; |
| for (n = 0; n < ADSP_QUEUES_MAX; n++) { |
| if (!module->queue[n].name) |
| break; |
| if (!strcmp(name, module->queue[n].name)) |
| return n; |
| } |
| return -ENODEV; |
| } |
| |
| static int msm_adsp_command(struct msm_adsp_module *module, unsigned cmd_id) |
| { |
| struct adsp_dal_cmd cmd; |
| int ret; |
| |
| cmd.cmd = cmd_id; |
| cmd.proc_id = ADSP_PROC_APPS; |
| cmd.module = module->id; |
| cmd.cookie = 0; |
| |
| ret = dal_call_f5(module->adsp->client, ADSP_DAL_COMMAND, |
| &cmd, sizeof(cmd)); |
| if (ret) |
| return -EIO; |
| |
| return 0; |
| } |
| |
| int msm_adsp_enable(struct msm_adsp_module *module) |
| { |
| int ret; |
| /* XXX interlock? */ |
| |
| ret = msm_adsp_command(module, ADSP_CMD_ENABLE); |
| if (ret < 0) { |
| pr_err("msm_adsp_enable: error enabling %s %d\n", |
| module->name, ret); |
| return -EIO; |
| } |
| ret = wait_event_timeout(module->adsp->callback_wq, |
| module->active, 5 * HZ); |
| if (!ret) { |
| pr_err("msm_adsp_enable: timeout enabling %s\n", |
| module->name); |
| return -ETIMEDOUT; |
| } |
| |
| printk("msm_adsp_enable: %s enabled.\n", module->name); |
| return 0; |
| } |
| |
| int msm_adsp_disable(struct msm_adsp_module *module) |
| { |
| /* XXX interlock? */ |
| return msm_adsp_command(module, ADSP_CMD_DISABLE); |
| } |
| |
| int msm_adsp_write(struct msm_adsp_module *module, unsigned queue_idx, |
| void *cmd_buf, size_t cmd_size) |
| { |
| struct msm_adsp *adsp; |
| uint32_t val; |
| uint32_t dsp_q_addr; |
| uint32_t dsp_addr; |
| uint32_t cmd_id = 0; |
| int cnt = 0; |
| int ret = 0; |
| unsigned long flags; |
| |
| if (!module || !cmd_size || (queue_idx >= ADSP_QUEUES_MAX)) |
| return -EINVAL; |
| |
| if (module->queue[queue_idx].name == NULL) |
| return -EINVAL; |
| |
| adsp = module->adsp; |
| |
| spin_lock_irqsave(&adsp->write_lock, flags); |
| |
| #if 0 |
| if (module->state != ADSP_STATE_ENABLED) { |
| ret = -ENODEV; |
| goto done; |
| } |
| #endif |
| |
| dsp_q_addr = module->queue[queue_idx].offset; |
| dsp_q_addr &= ADSP_WRITE_CTRL_DSP_ADDR_M; |
| |
| /* Poll until the ADSP is ready to accept a command. |
| * Wait for 100us, return error if it's not responding. |
| * If this returns an error, we need to disable ALL modules and |
| * then retry. |
| */ |
| while (((val = readl(adsp->write_ctrl)) & |
| ADSP_WRITE_CTRL_READY_M) != |
| ADSP_WRITE_CTRL_READY_V) { |
| if (cnt > 50) { |
| pr_err("timeout waiting for DSP write ready\n"); |
| ret = -EIO; |
| goto done; |
| } |
| udelay(2); |
| cnt++; |
| } |
| |
| /* Set the mutex bits */ |
| val &= ~(ADSP_WRITE_CTRL_MUTEX_M); |
| val |= ADSP_WRITE_CTRL_MUTEX_NAVAIL_V; |
| |
| /* Clear the command bits */ |
| val &= ~(ADSP_WRITE_CTRL_CMD_M); |
| |
| /* Set the queue address bits */ |
| val &= ~(ADSP_WRITE_CTRL_DSP_ADDR_M); |
| val |= dsp_q_addr; |
| |
| writel(val, adsp->write_ctrl); |
| |
| /* Generate an interrupt to the DSP. This notifies the DSP that |
| * we are about to send a command on this particular queue. The |
| * DSP will in response change its state. |
| */ |
| writel(1, adsp->send_irq); |
| |
| /* Poll until the adsp responds to the interrupt; this does not |
| * generate an interrupt from the adsp. This should happen within |
| * 5ms. |
| */ |
| cnt = 0; |
| while ((readl(adsp->write_ctrl) & |
| ADSP_WRITE_CTRL_MUTEX_M) == |
| ADSP_WRITE_CTRL_MUTEX_NAVAIL_V) { |
| if (cnt > 2500) { |
| pr_err("timeout waiting for adsp ack\n"); |
| ret = -EIO; |
| goto done; |
| } |
| udelay(2); |
| cnt++; |
| } |
| |
| /* Read the ctrl word */ |
| val = readl(adsp->write_ctrl); |
| |
| if ((val & ADSP_WRITE_CTRL_STATUS_M) != |
| ADSP_WRITE_CTRL_NO_ERR_V) { |
| ret = -EIO; |
| pr_err("failed to write queue %x, retry\n", dsp_q_addr); |
| goto done; |
| } |
| |
| /* No error */ |
| /* Get the DSP buffer address */ |
| dsp_addr = (val & ADSP_WRITE_CTRL_DSP_ADDR_M) + |
| (uint32_t)MSM_AD5_BASE; |
| |
| if (dsp_addr < (uint32_t)(MSM_AD5_BASE + QDSP_RAMC_OFFSET)) { |
| uint16_t *buf_ptr = (uint16_t *) cmd_buf; |
| uint16_t *dsp_addr16 = (uint16_t *)dsp_addr; |
| cmd_size /= sizeof(uint16_t); |
| |
| /* Save the command ID */ |
| cmd_id = (uint32_t) buf_ptr[0]; |
| |
| /* Copy the command to DSP memory */ |
| cmd_size++; |
| while (--cmd_size) |
| *dsp_addr16++ = *buf_ptr++; |
| } else { |
| uint32_t *buf_ptr = (uint32_t *) cmd_buf; |
| uint32_t *dsp_addr32 = (uint32_t *)dsp_addr; |
| cmd_size /= sizeof(uint32_t); |
| |
| /* Save the command ID */ |
| cmd_id = buf_ptr[0]; |
| |
| cmd_size++; |
| while (--cmd_size) |
| *dsp_addr32++ = *buf_ptr++; |
| } |
| |
| /* Set the mutex bits */ |
| val &= ~(ADSP_WRITE_CTRL_MUTEX_M); |
| val |= ADSP_WRITE_CTRL_MUTEX_NAVAIL_V; |
| |
| /* Set the command bits to write done */ |
| val &= ~(ADSP_WRITE_CTRL_CMD_M); |
| val |= ADSP_WRITE_CTRL_CMD_WRITE_DONE_V; |
| |
| /* Set the queue address bits */ |
| val &= ~(ADSP_WRITE_CTRL_DSP_ADDR_M); |
| val |= dsp_q_addr; |
| |
| writel(val, adsp->write_ctrl); |
| |
| /* Generate an interrupt to the DSP. It does not respond with |
| * an interrupt, and we do not need to wait for it to |
| * acknowledge, because it will hold the mutex lock until it's |
| * ready to receive more commands again. |
| */ |
| writel(1, adsp->send_irq); |
| |
| // module->num_commands++; |
| |
| done: |
| spin_unlock_irqrestore(&adsp->write_lock, flags); |
| return ret; |
| } |
| |
| static int adsp_read_task_to_host(struct msm_adsp *adsp, void *dsp_addr) |
| { |
| struct msm_adsp_module *module; |
| unsigned task_id; |
| unsigned msg_id; |
| unsigned msg_length; |
| unsigned n; |
| unsigned tmp; |
| union { |
| u32 data32[16]; |
| u16 data16[32]; |
| } u; |
| |
| if (dsp_addr >= (void *)(MSM_AD5_BASE + QDSP_RAMC_OFFSET)) { |
| uint32_t *dsp_addr32 = dsp_addr; |
| tmp = *dsp_addr32++; |
| task_id = (tmp & ADSP_READ_CTRL_TASK_ID_M) >> 8; |
| msg_id = (tmp & ADSP_READ_CTRL_MSG_ID_M); |
| tmp >>= 16; |
| if (tmp > 16) { |
| pr_err("adsp: message too large (%d x 32)\n", tmp); |
| tmp = 16; |
| } |
| msg_length = tmp * sizeof(uint32_t); |
| for (n = 0; n < tmp; n++) |
| u.data32[n] = *dsp_addr32++; |
| } else { |
| uint16_t *dsp_addr16 = dsp_addr; |
| tmp = *dsp_addr16++; |
| task_id = (tmp & ADSP_READ_CTRL_TASK_ID_M) >> 8; |
| msg_id = tmp & ADSP_READ_CTRL_MSG_ID_M; |
| tmp = *dsp_addr16++; |
| if (tmp > 32) { |
| pr_err("adsp: message too large (%d x 16)\n", tmp); |
| tmp = 32; |
| } |
| msg_length = tmp * sizeof(uint16_t); |
| for (n = 0; n < tmp; n++) |
| u.data16[n] = *dsp_addr16++; |
| } |
| |
| #if 0 |
| pr_info("ADSP EVENT TASK %d MSG %d SIZE %d\n", |
| task_id, msg_id, msg_length); |
| #endif |
| if (task_id > ADSP_TASKS_MAX) { |
| pr_err("adsp: bogus task id %d\n", task_id); |
| return 0; |
| } |
| module = adsp->task_to_module[task_id]; |
| |
| if (!module) { |
| pr_err("adsp: no module for task id %d\n", task_id); |
| return 0; |
| } |
| |
| if (!module->func) { |
| pr_err("module %s is not open\n", module->name); |
| return 0; |
| } |
| |
| module->func(msg_id, u.data32, msg_length, module->cookie); |
| return 0; |
| } |
| |
| static int adsp_get_event(struct msm_adsp *adsp) |
| { |
| uint32_t val; |
| uint32_t ready; |
| void *dsp_addr; |
| uint32_t cmd_type; |
| int cnt; |
| unsigned long flags; |
| int rc = 0; |
| |
| spin_lock_irqsave(&adsp->event_lock, flags); |
| |
| /* Whenever the DSP has a message, it updates this control word |
| * and generates an interrupt. When we receive the interrupt, we |
| * read this register to find out what ADSP task the command is |
| * comming from. |
| * |
| * The ADSP should *always* be ready on the first call, but the |
| * irq handler calls us in a loop (to handle back-to-back command |
| * processing), so we give the DSP some time to return to the |
| * ready state. The DSP will not issue another IRQ for events |
| * pending between the first IRQ and the event queue being drained, |
| * unfortunately. |
| */ |
| |
| for (cnt = 0; cnt < 50; cnt++) { |
| val = readl(adsp->read_ctrl); |
| |
| if ((val & ADSP_READ_CTRL_FLAG_M) == |
| ADSP_READ_CTRL_FLAG_UP_CONT_V) |
| goto ready; |
| |
| udelay(2); |
| } |
| pr_err("adsp_get_event: not ready after 100uS\n"); |
| rc = -EBUSY; |
| goto done; |
| |
| ready: |
| /* Here we check to see if there are pending messages. If there are |
| * none, we siply return -EAGAIN to indicate that there are no more |
| * messages pending. |
| */ |
| ready = val & ADSP_READ_CTRL_READY_M; |
| if ((ready != ADSP_READ_CTRL_READY_V) && |
| (ready != ADSP_READ_CTRL_CONT_V)) { |
| rc = -EAGAIN; |
| goto done; |
| } |
| |
| /* DSP says that there are messages waiting for the host to read */ |
| |
| /* Get the Command Type */ |
| cmd_type = val & ADSP_READ_CTRL_CMD_TYPE_M; |
| |
| /* Get the DSP buffer address */ |
| dsp_addr = (void *)((val & |
| ADSP_READ_CTRL_DSP_ADDR_M) + |
| (uint32_t)MSM_AD5_BASE); |
| |
| /* We can only handle Task-to-Host messages */ |
| if (cmd_type != ADSP_READ_CTRL_CMD_TASK_TO_H_V) { |
| rc = -EIO; |
| goto done; |
| } |
| |
| adsp_read_task_to_host(adsp, dsp_addr); |
| |
| val = readl(adsp->read_ctrl); |
| val &= ~ADSP_READ_CTRL_READY_M; |
| |
| /* Write ctrl word to the DSP */ |
| writel(val, adsp->read_ctrl); |
| |
| /* Generate an interrupt to the DSP */ |
| writel(1, adsp->send_irq); |
| |
| done: |
| spin_unlock_irqrestore(&adsp->event_lock, flags); |
| return rc; |
| } |
| |
| static irqreturn_t adsp_irq_handler(int irq, void *data) |
| { |
| struct msm_adsp *adsp = &the_adsp; |
| int count = 0; |
| for (count = 0; count < 15; count++) |
| if (adsp_get_event(adsp) < 0) |
| break; |
| #if 0 |
| if (count > adsp->event_backlog_max) |
| adsp->event_backlog_max = count; |
| adsp->events_received += count; |
| #endif |
| if (count == 15) |
| pr_err("too many (%d) events for single irq!\n", count); |
| return IRQ_HANDLED; |
| } |
| |
| static void adsp_dal_callback(void *data, int len, void *cookie) |
| { |
| struct msm_adsp *adsp = cookie; |
| struct adsp_dal_event *e = data; |
| struct msm_adsp_module *m; |
| #if 0 |
| pr_info("adsp: h %08x c %08x l %08x\n", |
| e->evt_handle, e->evt_cookie, e->evt_length); |
| pr_info(" : e %08x v %08x p %08x\n", |
| e->event, e->version, e->proc_id); |
| pr_info(" : m %08x i %08x a %08x\n", |
| e->u.info.module, e->u.info.image, e->u.info.apps_okts); |
| #endif |
| |
| switch (e->event) { |
| case ADSP_EVT_INIT_INFO: |
| memcpy(&adsp->tmpmodule, &e->u.module, |
| sizeof(adsp->tmpmodule)); |
| break; |
| case ADSP_EVT_MOD_READY: |
| m = id_to_module(adsp, e->u.info.module); |
| if (m) { |
| pr_info("adsp: %s READY\n", m->name); |
| m->active = 1; |
| } |
| break; |
| case ADSP_EVT_MOD_DISABLE: |
| /* does not actually happen in adsp5v2 */ |
| m = id_to_module(adsp, e->u.info.module); |
| if (m) |
| pr_info("adsp: %s DISABLED\n", m->name); |
| break; |
| case ADSP_EVT_DISABLE_FAIL: |
| m = id_to_module(adsp, e->u.info.module); |
| if (m) |
| pr_info("adsp: %s DISABLE FAILED\n", m->name); |
| break; |
| default: |
| pr_err("adsp_dal_callback: unknown event %d\n", e->event); |
| } |
| wake_up(&adsp->callback_wq); |
| } |
| |
| static void adsp_add_module(struct msm_adsp *adsp, struct adsp_module_info *mi) |
| { |
| struct msm_adsp_module *module; |
| int n; |
| |
| if (mi->task_id >= ADSP_TASKS_MAX) { |
| pr_err("adsp: module '%s' task id %d is invalid\n", |
| mi->name, mi->task_id); |
| return; |
| } |
| if (mi->q_cnt > ADSP_QUEUES_MAX) { |
| pr_err("adsp: module '%s' q_cnt %d is invalid\n", |
| mi->name, mi->q_cnt); |
| return; |
| } |
| |
| module = kzalloc(sizeof(*module), GFP_KERNEL); |
| if (!module) |
| return; |
| |
| module->name = kstrdup(mi->name, GFP_KERNEL); |
| if (!module->name) |
| goto fail_module_name; |
| |
| for (n = 0; n < mi->q_cnt; n++) { |
| struct msm_adsp_queue *queue = module->queue + n; |
| queue->name = kstrdup(mi->queue[n].name, GFP_KERNEL); |
| if (!queue->name) |
| goto fail_queue_name; |
| queue->offset = mi->queue[n].offset; |
| queue->max_size = mi->queue[n].max_size; |
| queue->flags = mi->queue[n].flag; |
| } |
| |
| init_waitqueue_head(&module->wait); |
| module->id = mi->uuid; |
| module->adsp = adsp; |
| |
| module->next = adsp->all_modules; |
| adsp->all_modules = module; |
| |
| adsp->task_to_module[mi->task_id] = module; |
| #if 0 |
| pr_info("adsp: module '%s' id 0x%x task %d\n", |
| module->name, module->id, mi->task_id); |
| for (n = 0; (n < ADSP_TASKS_MAX) && module->queue[n].name; n++) |
| pr_info(" queue '%s' off 0x%x size %d flags %x", |
| module->queue[n].name, module->queue[n].offset, |
| module->queue[n].max_size, module->queue[n].flags); |
| #endif |
| return; |
| |
| fail_queue_name: |
| for (n = 0; n < mi->q_cnt; n++) |
| if (module->queue[n].name) |
| kfree(module->queue[n].name); |
| fail_module_name: |
| kfree(module); |
| } |
| |
| static int adsp_probe(struct platform_device *pdev) { |
| struct msm_adsp *adsp = &the_adsp; |
| struct adsp_dal_cmd cmd; |
| int ret, n; |
| |
| pr_info("*** adsp_probe() ***\n"); |
| |
| adsp->base = MSM_AD5_BASE; |
| adsp->read_ctrl = adsp->base + ADSP_READ_CTRL_OFFSET; |
| adsp->write_ctrl = adsp->base + ADSP_WRITE_CTRL_OFFSET; |
| adsp->send_irq = adsp->base + ADSP_SEND_IRQ_OFFSET; |
| |
| adsp->client = dal_attach(ADSP_DAL_DEVICE, ADSP_DAL_PORT, |
| adsp_dal_callback, adsp); |
| if (!adsp->client) { |
| pr_err("adsp_probe: cannot attach to dal device\n"); |
| return -ENODEV; |
| } |
| |
| cmd.cmd = ADSP_CMD_GET_INIT_INFO; |
| cmd.proc_id = ADSP_PROC_APPS; |
| cmd.module = 0; |
| cmd.cookie = 0; |
| |
| for (n = 0; n < 64; n++) { |
| adsp->tmpmodule.uuid = 0xffffffff; |
| ret = dal_call_f5(adsp->client, ADSP_DAL_COMMAND, |
| &cmd, sizeof(cmd)); |
| if (ret) { |
| pr_err("adsp_probe() get info dal call failed\n"); |
| break; |
| } |
| ret = wait_event_timeout(adsp->callback_wq, |
| (adsp->tmpmodule.uuid != 0xffffffff), |
| 5*HZ); |
| if (ret == 0) { |
| pr_err("adsp_probe() timed out getting module info\n"); |
| break; |
| } |
| if (adsp->tmpmodule.uuid == 0x7fffffff) |
| break; |
| if (adsp->tmpmodule.task_id == 0xffff) |
| continue; |
| // adsp_print_module(&adsp->tmpmodule); |
| adsp_add_module(adsp, &adsp->tmpmodule); |
| } |
| |
| ret = request_irq(INT_AD5A_MPROC_APPS_0, adsp_irq_handler, |
| IRQF_TRIGGER_RISING, "adsp", 0); |
| if (ret < 0) |
| return ret; |
| |
| pr_info("*** adsp_probe() done ***\n"); |
| return 0; |
| } |
| |
| static struct platform_driver adsp_driver = { |
| .probe = adsp_probe, |
| .driver = { |
| .name = "SMD_DAL00", |
| .owner = THIS_MODULE, |
| }, |
| }; |
| |
| extern int msm_codec_init(void); |
| |
| static int __init adsp_init(void) |
| { |
| struct msm_adsp *adsp = &the_adsp; |
| |
| pr_info("*** adsp_init() ***\n"); |
| |
| init_waitqueue_head(&adsp->callback_wq); |
| spin_lock_init(&adsp->callback_lock); |
| spin_lock_init(&adsp->write_lock); |
| spin_lock_init(&adsp->event_lock); |
| |
| msm_codec_init(); |
| |
| return platform_driver_register(&adsp_driver); |
| } |
| |
| module_init(adsp_init); |