diff options
Diffstat (limited to 'drivers/staging/dream/qdsp5/adsp.c')
-rw-r--r-- | drivers/staging/dream/qdsp5/adsp.c | 1163 |
1 files changed, 1163 insertions, 0 deletions
diff --git a/drivers/staging/dream/qdsp5/adsp.c b/drivers/staging/dream/qdsp5/adsp.c new file mode 100644 index 00000000000..d096456688d --- /dev/null +++ b/drivers/staging/dream/qdsp5/adsp.c @@ -0,0 +1,1163 @@ +/* arch/arm/mach-msm/qdsp5/adsp.c + * + * Register/Interrupt access for userspace aDSP library. + * + * Copyright (c) 2008 QUALCOMM Incorporated + * Copyright (C) 2008 Google, Inc. + * Author: Iliyan Malchev <ibm@android.com> + * + * 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. + * + */ + +/* TODO: + * - move shareable rpc code outside of adsp.c + * - general solution for virt->phys patchup + * - queue IDs should be relative to modules + * - disallow access to non-associated queues + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/kthread.h> +#include <linux/module.h> +#include <linux/uaccess.h> +#include <linux/wait.h> +#include <linux/wakelock.h> + +static struct wake_lock adsp_wake_lock; +static inline void prevent_suspend(void) +{ + wake_lock(&adsp_wake_lock); +} +static inline void allow_suspend(void) +{ + wake_unlock(&adsp_wake_lock); +} + +#include <linux/io.h> +#include <mach/msm_iomap.h> +#include "adsp.h" + +#define INT_ADSP INT_ADSP_A9_A11 + +static struct adsp_info adsp_info; +static struct msm_rpc_endpoint *rpc_cb_server_client; +static struct msm_adsp_module *adsp_modules; +static int adsp_open_count; +static DEFINE_MUTEX(adsp_open_lock); + +/* protect interactions with the ADSP command/message queue */ +static spinlock_t adsp_cmd_lock; + +static uint32_t current_image = -1; + +void adsp_set_image(struct adsp_info *info, uint32_t image) +{ + current_image = image; +} + +/* + * Checks whether the module_id is available in the + * module_entries table.If module_id is available returns `0`. + * If module_id is not available returns `-ENXIO`. + */ +#if CONFIG_MSM_AMSS_VERSION >= 6350 +static int32_t adsp_validate_module(uint32_t module_id) +{ + uint32_t *ptr; + uint32_t module_index; + uint32_t num_mod_entries; + + ptr = adsp_info.init_info_ptr->module_entries; + num_mod_entries = adsp_info.init_info_ptr->module_table_size; + + for (module_index = 0; module_index < num_mod_entries; module_index++) + if (module_id == ptr[module_index]) + return 0; + + return -ENXIO; +} +#else +static inline int32_t adsp_validate_module(uint32_t module_id) { return 0; } +#endif + +uint32_t adsp_get_module(struct adsp_info *info, uint32_t task) +{ + BUG_ON(current_image == -1UL); + return info->task_to_module[current_image][task]; +} + +uint32_t adsp_get_queue_offset(struct adsp_info *info, uint32_t queue_id) +{ + BUG_ON(current_image == -1UL); + return info->queue_offset[current_image][queue_id]; +} + +static int rpc_adsp_rtos_app_to_modem(uint32_t cmd, uint32_t module, + struct msm_adsp_module *adsp_module) +{ + int rc; + struct rpc_adsp_rtos_app_to_modem_args_t rpc_req; + struct rpc_reply_hdr *rpc_rsp; + + msm_rpc_setup_req(&rpc_req.hdr, + RPC_ADSP_RTOS_ATOM_PROG, + msm_rpc_get_vers(adsp_module->rpc_client), + RPC_ADSP_RTOS_APP_TO_MODEM_PROC); + + rpc_req.gotit = cpu_to_be32(1); + rpc_req.cmd = cpu_to_be32(cmd); + rpc_req.proc_id = cpu_to_be32(RPC_ADSP_RTOS_PROC_APPS); + rpc_req.module = cpu_to_be32(module); + rc = msm_rpc_write(adsp_module->rpc_client, &rpc_req, sizeof(rpc_req)); + if (rc < 0) { + pr_err("adsp: could not send RPC request: %d\n", rc); + return rc; + } + + rc = msm_rpc_read(adsp_module->rpc_client, + (void **)&rpc_rsp, -1, (5*HZ)); + if (rc < 0) { + pr_err("adsp: error receiving RPC reply: %d (%d)\n", + rc, -ERESTARTSYS); + return rc; + } + + if (be32_to_cpu(rpc_rsp->reply_stat) != RPCMSG_REPLYSTAT_ACCEPTED) { + pr_err("adsp: RPC call was denied!\n"); + kfree(rpc_rsp); + return -EPERM; + } + + if (be32_to_cpu(rpc_rsp->data.acc_hdr.accept_stat) != + RPC_ACCEPTSTAT_SUCCESS) { + pr_err("adsp error: RPC call was not successful (%d)\n", + be32_to_cpu(rpc_rsp->data.acc_hdr.accept_stat)); + kfree(rpc_rsp); + return -EINVAL; + } + + kfree(rpc_rsp); + return 0; +} + +#if CONFIG_MSM_AMSS_VERSION >= 6350 +static int get_module_index(uint32_t id) +{ + int mod_idx; + for (mod_idx = 0; mod_idx < adsp_info.module_count; mod_idx++) + if (adsp_info.module[mod_idx].id == id) + return mod_idx; + + return -ENXIO; +} +#endif + +static struct msm_adsp_module *find_adsp_module_by_id( + struct adsp_info *info, uint32_t id) +{ + if (id > info->max_module_id) { + return NULL; + } else { +#if CONFIG_MSM_AMSS_VERSION >= 6350 + id = get_module_index(id); + if (id < 0) + return NULL; +#endif + return info->id_to_module[id]; + } +} + +static struct msm_adsp_module *find_adsp_module_by_name( + struct adsp_info *info, const char *name) +{ + unsigned n; + for (n = 0; n < info->module_count; n++) + if (!strcmp(name, adsp_modules[n].name)) + return adsp_modules + n; + return NULL; +} + +static int adsp_rpc_init(struct msm_adsp_module *adsp_module) +{ + /* remove the original connect once compatible support is complete */ + adsp_module->rpc_client = msm_rpc_connect( + RPC_ADSP_RTOS_ATOM_PROG, + RPC_ADSP_RTOS_ATOM_VERS, + MSM_RPC_UNINTERRUPTIBLE); + + if (IS_ERR(adsp_module->rpc_client)) { + int rc = PTR_ERR(adsp_module->rpc_client); + adsp_module->rpc_client = 0; + pr_err("adsp: could not open rpc client: %d\n", rc); + return rc; + } + + return 0; +} + +#if CONFIG_MSM_AMSS_VERSION >= 6350 +/* + * Send RPC_ADSP_RTOS_CMD_GET_INIT_INFO cmd to ARM9 and get + * queue offsets and module entries (init info) as part of the event. + */ +static void msm_get_init_info(void) +{ + int rc; + struct rpc_adsp_rtos_app_to_modem_args_t rpc_req; + + adsp_info.init_info_rpc_client = msm_rpc_connect( + RPC_ADSP_RTOS_ATOM_PROG, + RPC_ADSP_RTOS_ATOM_VERS, + MSM_RPC_UNINTERRUPTIBLE); + if (IS_ERR(adsp_info.init_info_rpc_client)) { + rc = PTR_ERR(adsp_info.init_info_rpc_client); + adsp_info.init_info_rpc_client = 0; + pr_err("adsp: could not open rpc client: %d\n", rc); + return; + } + + msm_rpc_setup_req(&rpc_req.hdr, + RPC_ADSP_RTOS_ATOM_PROG, + msm_rpc_get_vers(adsp_info.init_info_rpc_client), + RPC_ADSP_RTOS_APP_TO_MODEM_PROC); + + rpc_req.gotit = cpu_to_be32(1); + rpc_req.cmd = cpu_to_be32(RPC_ADSP_RTOS_CMD_GET_INIT_INFO); + rpc_req.proc_id = cpu_to_be32(RPC_ADSP_RTOS_PROC_APPS); + rpc_req.module = 0; + + rc = msm_rpc_write(adsp_info.init_info_rpc_client, + &rpc_req, sizeof(rpc_req)); + if (rc < 0) + pr_err("adsp: could not send RPC request: %d\n", rc); +} +#endif + +int msm_adsp_get(const char *name, struct msm_adsp_module **out, + struct msm_adsp_ops *ops, void *driver_data) +{ + struct msm_adsp_module *module; + int rc = 0; + +#if CONFIG_MSM_AMSS_VERSION >= 6350 + static uint32_t init_info_cmd_sent; + if (!init_info_cmd_sent) { + msm_get_init_info(); + init_waitqueue_head(&adsp_info.init_info_wait); + rc = wait_event_timeout(adsp_info.init_info_wait, + adsp_info.init_info_state == ADSP_STATE_INIT_INFO, + 5 * HZ); + if (!rc) { + pr_info("adsp: INIT_INFO failed\n"); + return -ETIMEDOUT; + } + init_info_cmd_sent++; + } +#endif + + module = find_adsp_module_by_name(&adsp_info, name); + if (!module) + return -ENODEV; + + mutex_lock(&module->lock); + pr_info("adsp: opening module %s\n", module->name); + if (module->open_count++ == 0 && module->clk) + clk_enable(module->clk); + + mutex_lock(&adsp_open_lock); + if (adsp_open_count++ == 0) { + enable_irq(INT_ADSP); + prevent_suspend(); + } + mutex_unlock(&adsp_open_lock); + + if (module->ops) { + rc = -EBUSY; + goto done; + } + + rc = adsp_rpc_init(module); + if (rc) + goto done; + + module->ops = ops; + module->driver_data = driver_data; + *out = module; + rc = rpc_adsp_rtos_app_to_modem(RPC_ADSP_RTOS_CMD_REGISTER_APP, + module->id, module); + if (rc) { + module->ops = NULL; + module->driver_data = NULL; + *out = NULL; + pr_err("adsp: REGISTER_APP failed\n"); + goto done; + } + + pr_info("adsp: module %s has been registered\n", module->name); + +done: + mutex_lock(&adsp_open_lock); + if (rc && --adsp_open_count == 0) { + disable_irq(INT_ADSP); + allow_suspend(); + } + if (rc && --module->open_count == 0 && module->clk) + clk_disable(module->clk); + mutex_unlock(&adsp_open_lock); + mutex_unlock(&module->lock); + return rc; +} +EXPORT_SYMBOL(msm_adsp_get); + +static int msm_adsp_disable_locked(struct msm_adsp_module *module); + +void msm_adsp_put(struct msm_adsp_module *module) +{ + unsigned long flags; + + mutex_lock(&module->lock); + if (--module->open_count == 0 && module->clk) + clk_disable(module->clk); + if (module->ops) { + pr_info("adsp: closing module %s\n", module->name); + + /* lock to ensure a dsp event cannot be delivered + * during or after removal of the ops and driver_data + */ + spin_lock_irqsave(&adsp_cmd_lock, flags); + module->ops = NULL; + module->driver_data = NULL; + spin_unlock_irqrestore(&adsp_cmd_lock, flags); + + if (module->state != ADSP_STATE_DISABLED) { + pr_info("adsp: disabling module %s\n", module->name); + msm_adsp_disable_locked(module); + } + + msm_rpc_close(module->rpc_client); + module->rpc_client = 0; + if (--adsp_open_count == 0) { + disable_irq(INT_ADSP); + allow_suspend(); + pr_info("adsp: disable interrupt\n"); + } + } else { + pr_info("adsp: module %s is already closed\n", module->name); + } + mutex_unlock(&module->lock); +} +EXPORT_SYMBOL(msm_adsp_put); + +/* this should be common code with rpc_servers.c */ +static int rpc_send_accepted_void_reply(struct msm_rpc_endpoint *client, + uint32_t xid, uint32_t accept_status) +{ + int rc = 0; + uint8_t reply_buf[sizeof(struct rpc_reply_hdr)]; + struct rpc_reply_hdr *reply = (struct rpc_reply_hdr *)reply_buf; + + reply->xid = cpu_to_be32(xid); + reply->type = cpu_to_be32(1); /* reply */ + reply->reply_stat = cpu_to_be32(RPCMSG_REPLYSTAT_ACCEPTED); + + reply->data.acc_hdr.accept_stat = cpu_to_be32(accept_status); + reply->data.acc_hdr.verf_flavor = 0; + reply->data.acc_hdr.verf_length = 0; + + rc = msm_rpc_write(rpc_cb_server_client, reply_buf, sizeof(reply_buf)); + if (rc < 0) + pr_err("adsp: could not write RPC response: %d\n", rc); + return rc; +} + +int __msm_adsp_write(struct msm_adsp_module *module, unsigned dsp_queue_addr, + void *cmd_buf, size_t cmd_size) +{ + uint32_t ctrl_word; + uint32_t dsp_q_addr; + uint32_t dsp_addr; + uint32_t cmd_id = 0; + int cnt = 0; + int ret_status = 0; + unsigned long flags; + struct adsp_info *info = module->info; + + spin_lock_irqsave(&adsp_cmd_lock, flags); + + if (module->state != ADSP_STATE_ENABLED) { + spin_unlock_irqrestore(&adsp_cmd_lock, flags); + pr_err("adsp: module %s not enabled before write\n", + module->name); + return -ENODEV; + } + if (adsp_validate_module(module->id)) { + spin_unlock_irqrestore(&adsp_cmd_lock, flags); + pr_info("adsp: module id validation failed %s %d\n", + module->name, module->id); + return -ENXIO; + } + dsp_q_addr = adsp_get_queue_offset(info, dsp_queue_addr); + dsp_q_addr &= ADSP_RTOS_WRITE_CTRL_WORD_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 (((ctrl_word = readl(info->write_ctrl)) & + ADSP_RTOS_WRITE_CTRL_WORD_READY_M) != + ADSP_RTOS_WRITE_CTRL_WORD_READY_V) { + if (cnt > 100) { + pr_err("adsp: timeout waiting for DSP write ready\n"); + ret_status = -EIO; + goto fail; + } + pr_warning("adsp: waiting for DSP write ready\n"); + udelay(1); + cnt++; + } + + /* Set the mutex bits */ + ctrl_word &= ~(ADSP_RTOS_WRITE_CTRL_WORD_MUTEX_M); + ctrl_word |= ADSP_RTOS_WRITE_CTRL_WORD_MUTEX_NAVAIL_V; + + /* Clear the command bits */ + ctrl_word &= ~(ADSP_RTOS_WRITE_CTRL_WORD_CMD_M); + + /* Set the queue address bits */ + ctrl_word &= ~(ADSP_RTOS_WRITE_CTRL_WORD_DSP_ADDR_M); + ctrl_word |= dsp_q_addr; + + writel(ctrl_word, info->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, info->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(info->write_ctrl) & + ADSP_RTOS_WRITE_CTRL_WORD_MUTEX_M) == + ADSP_RTOS_WRITE_CTRL_WORD_MUTEX_NAVAIL_V) { + if (cnt > 5000) { + pr_err("adsp: timeout waiting for adsp ack\n"); + ret_status = -EIO; + goto fail; + } + udelay(1); + cnt++; + } + + /* Read the ctrl word */ + ctrl_word = readl(info->write_ctrl); + + if ((ctrl_word & ADSP_RTOS_WRITE_CTRL_WORD_STATUS_M) != + ADSP_RTOS_WRITE_CTRL_WORD_NO_ERR_V) { + ret_status = -EAGAIN; + goto fail; + } + + /* Ctrl word status bits were 00, no error in the ctrl word */ + + /* Get the DSP buffer address */ + dsp_addr = (ctrl_word & ADSP_RTOS_WRITE_CTRL_WORD_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 */ + ctrl_word &= ~(ADSP_RTOS_WRITE_CTRL_WORD_MUTEX_M); + ctrl_word |= ADSP_RTOS_WRITE_CTRL_WORD_MUTEX_NAVAIL_V; + + /* Set the command bits to write done */ + ctrl_word &= ~(ADSP_RTOS_WRITE_CTRL_WORD_CMD_M); + ctrl_word |= ADSP_RTOS_WRITE_CTRL_WORD_CMD_WRITE_DONE_V; + + /* Set the queue address bits */ + ctrl_word &= ~(ADSP_RTOS_WRITE_CTRL_WORD_DSP_ADDR_M); + ctrl_word |= dsp_q_addr; + + writel(ctrl_word, info->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, info->send_irq); + + module->num_commands++; + +fail: + spin_unlock_irqrestore(&adsp_cmd_lock, flags); + return ret_status; +} +EXPORT_SYMBOL(msm_adsp_write); + +int msm_adsp_write(struct msm_adsp_module *module, unsigned dsp_queue_addr, + void *cmd_buf, size_t cmd_size) +{ + int rc, retries = 0; + do { + rc = __msm_adsp_write(module, dsp_queue_addr, cmd_buf, cmd_size); + if (rc == -EAGAIN) + udelay(10); + } while(rc == -EAGAIN && retries++ < 100); + if (retries > 50) + pr_warning("adsp: %s command took %d attempts: rc %d\n", + module->name, retries, rc); + return rc; +} + +#ifdef CONFIG_MSM_ADSP_REPORT_EVENTS +static void *modem_event_addr; +#if CONFIG_MSM_AMSS_VERSION >= 6350 +static void read_modem_event(void *buf, size_t len) +{ + uint32_t *dptr = buf; + struct rpc_adsp_rtos_modem_to_app_args_t *sptr; + struct adsp_rtos_mp_mtoa_type *pkt_ptr; + + sptr = modem_event_addr; + pkt_ptr = &sptr->mtoa_pkt.adsp_rtos_mp_mtoa_data.mp_mtoa_packet; + + dptr[0] = be32_to_cpu(sptr->mtoa_pkt.mp_mtoa_header.event); + dptr[1] = be32_to_cpu(pkt_ptr->module); + dptr[2] = be32_to_cpu(pkt_ptr->image); +} +#else +static void read_modem_event(void *buf, size_t len) +{ + uint32_t *dptr = buf; + struct rpc_adsp_rtos_modem_to_app_args_t *sptr = + modem_event_addr; + dptr[0] = be32_to_cpu(sptr->event); + dptr[1] = be32_to_cpu(sptr->module); + dptr[2] = be32_to_cpu(sptr->image); +} +#endif /* CONFIG_MSM_AMSS_VERSION >= 6350 */ +#endif /* CONFIG_MSM_ADSP_REPORT_EVENTS */ + +static void handle_adsp_rtos_mtoa_app(struct rpc_request_hdr *req) +{ + struct rpc_adsp_rtos_modem_to_app_args_t *args = + (struct rpc_adsp_rtos_modem_to_app_args_t *)req; + uint32_t event; + uint32_t proc_id; + uint32_t module_id; + uint32_t image; + struct msm_adsp_module *module; +#if CONFIG_MSM_AMSS_VERSION >= 6350 + struct adsp_rtos_mp_mtoa_type *pkt_ptr = + &args->mtoa_pkt.adsp_rtos_mp_mtoa_data.mp_mtoa_packet; + + event = be32_to_cpu(args->mtoa_pkt.mp_mtoa_header.event); + proc_id = be32_to_cpu(args->mtoa_pkt.mp_mtoa_header.proc_id); + module_id = be32_to_cpu(pkt_ptr->module); + image = be32_to_cpu(pkt_ptr->image); + + if (be32_to_cpu(args->mtoa_pkt.desc_field) == RPC_ADSP_RTOS_INIT_INFO) { + struct queue_to_offset_type *qptr; + struct queue_to_offset_type *qtbl; + uint32_t *mptr; + uint32_t *mtbl; + uint32_t q_idx; + uint32_t num_entries; + uint32_t entries_per_image; + struct adsp_rtos_mp_mtoa_init_info_type *iptr; + struct adsp_rtos_mp_mtoa_init_info_type *sptr; + int32_t i_no, e_idx; + + pr_info("adsp:INIT_INFO Event\n"); + sptr = &args->mtoa_pkt.adsp_rtos_mp_mtoa_data. + mp_mtoa_init_packet; + + iptr = adsp_info.init_info_ptr; + iptr->image_count = be32_to_cpu(sptr->image_count); + iptr->num_queue_offsets = be32_to_cpu(sptr->num_queue_offsets); + num_entries = iptr->num_queue_offsets; + qptr = &sptr->queue_offsets_tbl[0][0]; + for (i_no = 0; i_no < iptr->image_count; i_no++) { + qtbl = &iptr->queue_offsets_tbl[i_no][0]; + for (e_idx = 0; e_idx < num_entries; e_idx++) { + qtbl[e_idx].offset = be32_to_cpu(qptr->offset); + qtbl[e_idx].queue = be32_to_cpu(qptr->queue); + q_idx = be32_to_cpu(qptr->queue); + iptr->queue_offsets[i_no][q_idx] = + qtbl[e_idx].offset; + qptr++; + } + } + + num_entries = be32_to_cpu(sptr->num_task_module_entries); + iptr->num_task_module_entries = num_entries; + entries_per_image = num_entries / iptr->image_count; + mptr = &sptr->task_to_module_tbl[0][0]; + for (i_no = 0; i_no < iptr->image_count; i_no++) { + mtbl = &iptr->task_to_module_tbl[i_no][0]; + for (e_idx = 0; e_idx < entries_per_image; e_idx++) { + mtbl[e_idx] = be32_to_cpu(*mptr); + mptr++; + } + } + + iptr->module_table_size = be32_to_cpu(sptr->module_table_size); + mptr = &sptr->module_entries[0]; + for (i_no = 0; i_no < iptr->module_table_size; i_no++) + iptr->module_entries[i_no] = be32_to_cpu(mptr[i_no]); + adsp_info.init_info_state = ADSP_STATE_INIT_INFO; + rpc_send_accepted_void_reply(rpc_cb_server_client, req->xid, + RPC_ACCEPTSTAT_SUCCESS); + wake_up(&adsp_info.init_info_wait); + + return; + } +#else + event = be32_to_cpu(args->event); + proc_id = be32_to_cpu(args->proc_id); + module_id = be32_to_cpu(args->module); + image = be32_to_cpu(args->image); +#endif + + pr_info("adsp: rpc event=%d, proc_id=%d, module=%d, image=%d\n", + event, proc_id, module_id, image); + + module = find_adsp_module_by_id(&adsp_info, module_id); + if (!module) { + pr_err("adsp: module %d is not supported!\n", module_id); + rpc_send_accepted_void_reply(rpc_cb_server_client, req->xid, + RPC_ACCEPTSTAT_GARBAGE_ARGS); + return; + } + + mutex_lock(&module->lock); + switch (event) { + case RPC_ADSP_RTOS_MOD_READY: + pr_info("adsp: module %s: READY\n", module->name); + module->state = ADSP_STATE_ENABLED; + wake_up(&module->state_wait); + adsp_set_image(module->info, image); + break; + case RPC_ADSP_RTOS_MOD_DISABLE: + pr_info("adsp: module %s: DISABLED\n", module->name); + module->state = ADSP_STATE_DISABLED; + wake_up(&module->state_wait); + break; + case RPC_ADSP_RTOS_SERVICE_RESET: + pr_info("adsp: module %s: SERVICE_RESET\n", module->name); + module->state = ADSP_STATE_DISABLED; + wake_up(&module->state_wait); + break; + case RPC_ADSP_RTOS_CMD_SUCCESS: + pr_info("adsp: module %s: CMD_SUCCESS\n", module->name); + break; + case RPC_ADSP_RTOS_CMD_FAIL: + pr_info("adsp: module %s: CMD_FAIL\n", module->name); + break; +#if CONFIG_MSM_AMSS_VERSION >= 6350 + case RPC_ADSP_RTOS_DISABLE_FAIL: + pr_info("adsp: module %s: DISABLE_FAIL\n", module->name); + break; +#endif + default: + pr_info("adsp: unknown event %d\n", event); + rpc_send_accepted_void_reply(rpc_cb_server_client, req->xid, + RPC_ACCEPTSTAT_GARBAGE_ARGS); + mutex_unlock(&module->lock); + return; + } + rpc_send_accepted_void_reply(rpc_cb_server_client, req->xid, + RPC_ACCEPTSTAT_SUCCESS); + mutex_unlock(&module->lock); +#ifdef CONFIG_MSM_ADSP_REPORT_EVENTS + modem_event_addr = (uint32_t *)req; + module->ops->event(module->driver_data, EVENT_MSG_ID, + EVENT_LEN, read_modem_event); +#endif +} + +static int handle_adsp_rtos_mtoa(struct rpc_request_hdr *req) +{ + switch (req->procedure) { + case RPC_ADSP_RTOS_MTOA_NULL_PROC: + rpc_send_accepted_void_reply(rpc_cb_server_client, + req->xid, + RPC_ACCEPTSTAT_SUCCESS); + break; + case RPC_ADSP_RTOS_MODEM_TO_APP_PROC: + handle_adsp_rtos_mtoa_app(req); + break; + default: + pr_err("adsp: unknowned proc %d\n", req->procedure); + rpc_send_accepted_void_reply( + rpc_cb_server_client, req->xid, + RPC_ACCEPTSTAT_PROC_UNAVAIL); + break; + } + return 0; +} + +/* this should be common code with rpc_servers.c */ +static int adsp_rpc_thread(void *data) +{ + void *buffer; + struct rpc_request_hdr *req; + int rc; + + do { + rc = msm_rpc_read(rpc_cb_server_client, &buffer, -1, -1); + if (rc < 0) { + pr_err("adsp: could not read rpc: %d\n", rc); + break; + } + req = (struct rpc_request_hdr *)buffer; + + req->type = be32_to_cpu(req->type); + req->xid = be32_to_cpu(req->xid); + req->rpc_vers = be32_to_cpu(req->rpc_vers); + req->prog = be32_to_cpu(req->prog); + req->vers = be32_to_cpu(req->vers); + req->procedure = be32_to_cpu(req->procedure); + + if (req->type != 0) + goto bad_rpc; + if (req->rpc_vers != 2) + goto bad_rpc; + if (req->prog != RPC_ADSP_RTOS_MTOA_PROG) + goto bad_rpc; + if (req->vers != RPC_ADSP_RTOS_MTOA_VERS) + goto bad_rpc; + + handle_adsp_rtos_mtoa(req); + kfree(buffer); + continue; + +bad_rpc: + pr_err("adsp: bogus rpc from modem\n"); + kfree(buffer); + } while (1); + + do_exit(0); +} + +static size_t read_event_size; +static void *read_event_addr; + +static void read_event_16(void *buf, size_t len) +{ + uint16_t *dst = buf; + uint16_t *src = read_event_addr; + len /= 2; + if (len > read_event_size) + len = read_event_size; + while (len--) + *dst++ = *src++; +} + +static void read_event_32(void *buf, size_t len) +{ + uint32_t *dst = buf; + uint32_t *src = read_event_addr; + len /= 2; + if (len > read_event_size) + len = read_event_size; + while (len--) + *dst++ = *src++; +} + +static int adsp_rtos_read_ctrl_word_cmd_tast_to_h_v( + struct adsp_info *info, void *dsp_addr) +{ + struct msm_adsp_module *module; + unsigned rtos_task_id; + unsigned msg_id; + unsigned msg_length; + void (*func)(void *, size_t); + + if (dsp_addr >= (void *)(MSM_AD5_BASE + QDSP_RAMC_OFFSET)) { + uint32_t *dsp_addr32 = dsp_addr; + uint32_t tmp = *dsp_addr32++; + rtos_task_id = (tmp & ADSP_RTOS_READ_CTRL_WORD_TASK_ID_M) >> 8; + msg_id = (tmp & ADSP_RTOS_READ_CTRL_WORD_MSG_ID_M); + read_event_size = tmp >> 16; + read_event_addr = dsp_addr32; + msg_length = read_event_size * sizeof(uint32_t); + func = read_event_32; + } else { + uint16_t *dsp_addr16 = dsp_addr; + uint16_t tmp = *dsp_addr16++; + rtos_task_id = (tmp & ADSP_RTOS_READ_CTRL_WORD_TASK_ID_M) >> 8; + msg_id = tmp & ADSP_RTOS_READ_CTRL_WORD_MSG_ID_M; + read_event_size = *dsp_addr16++; + read_event_addr = dsp_addr16; + msg_length = read_event_size * sizeof(uint16_t); + func = read_event_16; + } + + if (rtos_task_id > info->max_task_id) { + pr_err("adsp: bogus task id %d\n", rtos_task_id); + return 0; + } + module = find_adsp_module_by_id(info, + adsp_get_module(info, rtos_task_id)); + + if (!module) { + pr_err("adsp: no module for task id %d\n", rtos_task_id); + return 0; + } + + module->num_events++; + + if (!module->ops) { + pr_err("adsp: module %s is not open\n", module->name); + return 0; + } + + module->ops->event(module->driver_data, msg_id, msg_length, func); + return 0; +} + +static int adsp_get_event(struct adsp_info *info) +{ + uint32_t ctrl_word; + uint32_t ready; + void *dsp_addr; + uint32_t cmd_type; + int cnt; + unsigned long flags; + int rc = 0; + + spin_lock_irqsave(&adsp_cmd_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 < 10; cnt++) { + ctrl_word = readl(info->read_ctrl); + + if ((ctrl_word & ADSP_RTOS_READ_CTRL_WORD_FLAG_M) == + ADSP_RTOS_READ_CTRL_WORD_FLAG_UP_CONT_V) + goto ready; + + udelay(10); + } + pr_warning("adsp: 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 = ctrl_word & ADSP_RTOS_READ_CTRL_WORD_READY_M; + if ((ready != ADSP_RTOS_READ_CTRL_WORD_READY_V) && + (ready != ADSP_RTOS_READ_CTRL_WORD_CONT_V)) { + rc = -EAGAIN; + goto done; + } + + /* DSP says that there are messages waiting for the host to read */ + + /* Get the Command Type */ + cmd_type = ctrl_word & ADSP_RTOS_READ_CTRL_WORD_CMD_TYPE_M; + + /* Get the DSP buffer address */ + dsp_addr = (void *)((ctrl_word & + ADSP_RTOS_READ_CTRL_WORD_DSP_ADDR_M) + + (uint32_t)MSM_AD5_BASE); + + /* We can only handle Task-to-Host messages */ + if (cmd_type != ADSP_RTOS_READ_CTRL_WORD_CMD_TASK_TO_H_V) { + pr_err("adsp: unknown dsp cmd_type %d\n", cmd_type); + rc = -EIO; + goto done; + } + + adsp_rtos_read_ctrl_word_cmd_tast_to_h_v(info, dsp_addr); + + ctrl_word = readl(info->read_ctrl); + ctrl_word &= ~ADSP_RTOS_READ_CTRL_WORD_READY_M; + + /* Write ctrl word to the DSP */ + writel(ctrl_word, info->read_ctrl); + + /* Generate an interrupt to the DSP */ + writel(1, info->send_irq); + +done: + spin_unlock_irqrestore(&adsp_cmd_lock, flags); + return rc; +} + +static irqreturn_t adsp_irq_handler(int irq, void *data) +{ + struct adsp_info *info = &adsp_info; + int cnt = 0; + for (cnt = 0; cnt < 10; cnt++) + if (adsp_get_event(info) < 0) + break; + if (cnt > info->event_backlog_max) + info->event_backlog_max = cnt; + info->events_received += cnt; + if (cnt == 10) + pr_err("adsp: too many (%d) events for single irq!\n", cnt); + return IRQ_HANDLED; +} + +int adsp_set_clkrate(struct msm_adsp_module *module, unsigned long clk_rate) +{ + if (module->clk && clk_rate) + return clk_set_rate(module->clk, clk_rate); + + return -EINVAL; +} + +int msm_adsp_enable(struct msm_adsp_module *module) +{ + int rc = 0; + + pr_info("msm_adsp_enable() '%s'state[%d] id[%d]\n", + module->name, module->state, module->id); + + mutex_lock(&module->lock); + switch (module->state) { + case ADSP_STATE_DISABLED: + rc = rpc_adsp_rtos_app_to_modem(RPC_ADSP_RTOS_CMD_ENABLE, + module->id, module); + if (rc) + break; + module->state = ADSP_STATE_ENABLING; + mutex_unlock(&module->lock); + rc = wait_event_timeout(module->state_wait, + module->state != ADSP_STATE_ENABLING, + 1 * HZ); + mutex_lock(&module->lock); + if (module->state == ADSP_STATE_ENABLED) { + rc = 0; + } else { + pr_err("adsp: module '%s' enable timed out\n", + module->name); + rc = -ETIMEDOUT; + } + break; + case ADSP_STATE_ENABLING: + pr_warning("adsp: module '%s' enable in progress\n", + module->name); + break; + case ADSP_STATE_ENABLED: + pr_warning("adsp: module '%s' already enabled\n", + module->name); + break; + case ADSP_STATE_DISABLING: + pr_err("adsp: module '%s' disable in progress\n", + module->name); + rc = -EBUSY; + break; + } + mutex_unlock(&module->lock); + return rc; +} +EXPORT_SYMBOL(msm_adsp_enable); + +static int msm_adsp_disable_locked(struct msm_adsp_module *module) +{ + int rc = 0; + + switch (module->state) { + case ADSP_STATE_DISABLED: + pr_warning("adsp: module '%s' already disabled\n", + module->name); + break; + case ADSP_STATE_ENABLING: + case ADSP_STATE_ENABLED: + rc = rpc_adsp_rtos_app_to_modem(RPC_ADSP_RTOS_CMD_DISABLE, + module->id, module); + module->state = ADSP_STATE_DISABLED; + } + return rc; +} + +int msm_adsp_disable(struct msm_adsp_module *module) +{ + int rc; + pr_info("msm_adsp_disable() '%s'\n", module->name); + mutex_lock(&module->lock); + rc = msm_adsp_disable_locked(module); + mutex_unlock(&module->lock); + return rc; +} +EXPORT_SYMBOL(msm_adsp_disable); + +static int msm_adsp_probe(struct platform_device *pdev) +{ + unsigned count; + int rc, i; + int max_module_id; + + pr_info("adsp: probe\n"); + + wake_lock_init(&adsp_wake_lock, WAKE_LOCK_SUSPEND, "adsp"); +#if CONFIG_MSM_AMSS_VERSION >= 6350 + adsp_info.init_info_ptr = kzalloc( + (sizeof(struct adsp_rtos_mp_mtoa_init_info_type)), GFP_KERNEL); + if (!adsp_info.init_info_ptr) + return -ENOMEM; +#endif + + rc = adsp_init_info(&adsp_info); + if (rc) + return rc; + adsp_info.send_irq += (uint32_t) MSM_AD5_BASE; + adsp_info.read_ctrl += (uint32_t) MSM_AD5_BASE; + adsp_info.write_ctrl += (uint32_t) MSM_AD5_BASE; + count = adsp_info.module_count; + +#if CONFIG_MSM_AMSS_VERSION >= 6350 + max_module_id = count; +#else + max_module_id = adsp_info.max_module_id + 1; +#endif + + adsp_modules = kzalloc( + sizeof(struct msm_adsp_module) * count + + sizeof(void *) * max_module_id, GFP_KERNEL); + if (!adsp_modules) + return -ENOMEM; + + adsp_info.id_to_module = (void *) (adsp_modules + count); + + spin_lock_init(&adsp_cmd_lock); + + rc = request_irq(INT_ADSP, adsp_irq_handler, IRQF_TRIGGER_RISING, + "adsp", 0); + if (rc < 0) + goto fail_request_irq; + disable_irq(INT_ADSP); + + rpc_cb_server_client = msm_rpc_open(); + if (IS_ERR(rpc_cb_server_client)) { + rpc_cb_server_client = NULL; + rc = PTR_ERR(rpc_cb_server_client); + pr_err("adsp: could not create rpc server (%d)\n", rc); + goto fail_rpc_open; + } + + rc = msm_rpc_register_server(rpc_cb_server_client, + RPC_ADSP_RTOS_MTOA_PROG, + RPC_ADSP_RTOS_MTOA_VERS); + if (rc) { + pr_err("adsp: could not register callback server (%d)\n", rc); + goto fail_rpc_register; + } + + /* start the kernel thread to process the callbacks */ + kthread_run(adsp_rpc_thread, NULL, "kadspd"); + + for (i = 0; i < count; i++) { + struct msm_adsp_module *mod = adsp_modules + i; + mutex_init(&mod->lock); + init_waitqueue_head(&mod->state_wait); + mod->info = &adsp_info; + mod->name = adsp_info.module[i].name; + mod->id = adsp_info.module[i].id; + if (adsp_info.module[i].clk_name) + mod->clk = clk_get(NULL, adsp_info.module[i].clk_name); + else + mod->clk = NULL; + if (mod->clk && adsp_info.module[i].clk_rate) + clk_set_rate(mod->clk, adsp_info.module[i].clk_rate); + mod->verify_cmd = adsp_info.module[i].verify_cmd; + mod->patch_event = adsp_info.module[i].patch_event; + INIT_HLIST_HEAD(&mod->pmem_regions); + mod->pdev.name = adsp_info.module[i].pdev_name; + mod->pdev.id = -1; +#if CONFIG_MSM_AMSS_VERSION >= 6350 + adsp_info.id_to_module[i] = mod; +#else + adsp_info.id_to_module[mod->id] = mod; +#endif + platform_device_register(&mod->pdev); + } + + msm_adsp_publish_cdevs(adsp_modules, count); + + return 0; + +fail_rpc_register: + msm_rpc_close(rpc_cb_server_client); + rpc_cb_server_client = NULL; +fail_rpc_open: + enable_irq(INT_ADSP); + free_irq(INT_ADSP, 0); +fail_request_irq: + kfree(adsp_modules); +#if CONFIG_MSM_AMSS_VERSION >= 6350 + kfree(adsp_info.init_info_ptr); +#endif + return rc; +} + +static struct platform_driver msm_adsp_driver = { + .probe = msm_adsp_probe, + .driver = { + .name = MSM_ADSP_DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init adsp_init(void) +{ + return platform_driver_register(&msm_adsp_driver); +} + +device_initcall(adsp_init); |