| /* Copyright (c) 2014, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only 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. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/slab.h> |
| #include <linux/sched.h> |
| #include <linux/completion.h> |
| #include <linux/kthread.h> |
| #include <linux/mfd/wcd9xxx/core.h> |
| #include <sound/cpe_cmi.h> |
| #include <sound/soc.h> |
| #include <linux/mfd/wcd9xxx/wcd9330_registers.h> |
| #include "wcd_cpe_services.h" |
| #include "wcd_cmi_api.h" |
| |
| #define CPE_MSG_BUFFER_SIZE 132 |
| #define CPE_NO_SERVICE 0 |
| |
| #define CMI_DRIVER_SUPPORTED_VERSION 0 |
| #define CMI_API_SUCCESS 0 |
| #define CMI_MSG_TRANSPORT (0x0002) |
| |
| #define TOMTOM_A_SVASS_SPE_DRAM_OFFSET 0x50000 |
| #define TOMTOM_A_SVASS_SPE_DRAM_SIZE 0x30000 |
| #define TOMTOM_A_SVASS_SPE_IRAM_OFFSET 0x80000 |
| #define TOMTOM_A_SVASS_SPE_IRAM_SIZE 0xC000 |
| #define TOMTOM_A_SVASS_SPE_INBOX_SIZE 12 |
| #define TOMTOM_A_SVASS_SPE_OUTBOX_SIZE 12 |
| |
| #define MEM_ACCESS_NONE_VAL 0x0 |
| #define MEM_ACCESS_IRAM_VAL 0x1 |
| #define MEM_ACCESS_DRAM_VAL 0x2 |
| #define LISTEN_CTL_SPE_VAL 0x0 |
| #define LISTEN_CTL_MSM_VAL 0x1 |
| |
| #define TOMTOM_A_SVASS_SPE_INBOX(N) (TOMTOM_A_SVASS_SPE_INBOX_0 + (N)) |
| #define TOMTOM_A_SVASS_SPE_OUTBOX(N) (TOMTOM_A_SVASS_SPE_OUTBOX_0 + (N)) |
| #define CHUNK_SIZE 16 |
| |
| static const struct cpe_svc_hw_cfg cpe_svc_tomtom_info = { |
| TOMTOM_A_SVASS_SPE_DRAM_SIZE, |
| TOMTOM_A_SVASS_SPE_DRAM_OFFSET, |
| TOMTOM_A_SVASS_SPE_IRAM_SIZE, |
| TOMTOM_A_SVASS_SPE_IRAM_OFFSET, |
| TOMTOM_A_SVASS_SPE_INBOX_SIZE, |
| TOMTOM_A_SVASS_SPE_OUTBOX_SIZE |
| }; |
| |
| enum cpe_state { |
| CPE_STATE_UNINITIALIZED, |
| CPE_STATE_INITIALIZED, |
| CPE_STATE_IDLE, |
| CPE_STATE_DOWNLOADING, |
| CPE_STATE_BOOTING, |
| CPE_STATE_SENDING_MSG, |
| CPE_STATE_OFFLINE, |
| CPE_STATE_BUFFERING, |
| CPE_STATE_BUFFERING_CANCELLED |
| }; |
| |
| enum cpe_substate { |
| CPE_SS_IDLE, |
| CPE_SS_MSG_REQUEST_ACCESS, |
| CPE_SS_MSG_SEND_INBOX, |
| CPE_SS_MSG_SENT, |
| CPE_SS_DL_DOWNLOADING, |
| CPE_SS_DL_COMPLETED, |
| CPE_SS_BOOT, |
| CPE_SS_BOOT_INIT, |
| CPE_SS_ONLINE |
| }; |
| |
| enum cpe_command { |
| CPE_CMD_KILL_THREAD, |
| CPE_CMD_BOOT, |
| CPE_CMD_BOOT_INITIALIZE, |
| CPE_CMD_BOOT_COMPLETE, |
| CPE_CMD_SEND_MSG, |
| CPE_CMD_SEND_MSG_COMPLETE, |
| CPE_CMD_PROCESS_IRQ, |
| CPE_CMD_RAMDUMP, |
| CPE_CMD_DL_SEGMENT, |
| CPE_CMD_SHUTDOWN, |
| CPE_CMD_RESET, |
| CPE_CMD_DEINITIALIZE, |
| CPE_CMD_READ, |
| CPE_CMD_ENABLE_LAB, |
| CPE_CMD_DISABLE_LAB, |
| CPE_CMD_SWAP_BUFFER, |
| CPE_LAB_CFG_SB |
| }; |
| |
| struct cpe_command_node { |
| enum cpe_command command; |
| enum cpe_svc_result result; |
| void *data; |
| struct list_head list; |
| }; |
| |
| struct cpe_info { |
| struct list_head main_queue; |
| struct completion cmd_complete; |
| void *thread_handler; |
| enum cpe_state state; |
| enum cpe_substate substate; |
| struct list_head client_list; |
| bool (*cpe_process_command) |
| (struct cpe_command_node *command_node); |
| enum cpe_svc_result (*cpe_cmd_validate) |
| (const struct cpe_info *i, |
| enum cpe_command command); |
| enum cpe_svc_result (*cpe_start_notification) |
| (const struct cpe_info *i); |
| u32 initialized; |
| struct cpe_svc_tgt_abstraction *tgt; |
| void *pending; |
| void *data; |
| }; |
| |
| struct cpe_svc_tgt_abstraction { |
| enum cpe_svc_result (*tgt_boot) (int debug_mode); |
| |
| u32 (*tgt_cpar_init_done) (void); |
| |
| u32 (*tgt_is_active) (void); |
| |
| enum cpe_svc_result (*tgt_reset) (void); |
| |
| enum cpe_svc_result (*tgt_read_mailbox) |
| (u8 *buffer, size_t size); |
| |
| enum cpe_svc_result (*tgt_write_mailbox) |
| (u8 *buffer, size_t size); |
| |
| enum cpe_svc_result (*tgt_read_ram) |
| (struct cpe_info *c, |
| struct cpe_svc_mem_segment *data); |
| |
| enum cpe_svc_result (*tgt_write_ram) |
| (struct cpe_info *c, |
| const struct cpe_svc_mem_segment *data); |
| |
| enum cpe_svc_result (*tgt_route_notification) |
| (enum cpe_svc_module module, |
| enum cpe_svc_route_dest dest); |
| |
| enum cpe_svc_result (*tgt_set_debug_mode) (u32 enable); |
| const struct cpe_svc_hw_cfg *(*tgt_get_cpe_info) (void); |
| enum cpe_svc_result (*tgt_deinit) |
| (struct cpe_svc_tgt_abstraction *param); |
| enum cpe_svc_result (*tgt_voice_tx_lab) |
| (bool); |
| u8 *inbox; |
| u8 *outbox; |
| }; |
| |
| static enum cpe_svc_result cpe_tgt_tomtom_init( |
| struct cpe_svc_codec_info_v1 *codec_info, |
| struct cpe_svc_tgt_abstraction *param); |
| |
| struct cpe_send_msg { |
| u8 *payload; |
| u32 isobm; |
| u32 address; |
| size_t size; |
| }; |
| |
| struct cpe_read_handle { |
| void *registration; |
| struct cpe_info t_info; |
| struct list_head buffers; |
| }; |
| |
| struct generic_notification { |
| void (*notification) |
| (const struct cpe_svc_notification *parameter); |
| void (*cmi_notification) |
| (const struct cmi_api_notification *parameter); |
| }; |
| |
| struct cpe_notif_node { |
| struct generic_notification notif; |
| u32 mask; |
| u32 service; |
| const struct cpe_info *context; |
| const char *name; |
| struct list_head list; |
| }; |
| |
| static struct cpe_info *cpe_default_handle; |
| static void (*cpe_irq_control_callback)(u32 enable); |
| static u32 cpe_msg_buffer; |
| static struct mutex cpe_api_mutex; |
| |
| static enum cpe_svc_result |
| cpe_is_command_valid(const struct cpe_info *t_info, |
| enum cpe_command command); |
| |
| static void *cdc_priv; |
| |
| static int cpe_register_read(u32 reg, u8 *val) |
| { |
| *(val) = snd_soc_read(cdc_priv, reg); |
| return 0; |
| } |
| |
| static enum cpe_svc_result cpe_update_bits(u32 reg, |
| u32 mask, u32 value) |
| { |
| int ret = 0; |
| ret = snd_soc_update_bits(cdc_priv, reg, |
| mask, value); |
| if (ret < 0) |
| return CPE_SVC_FAILED; |
| |
| return CPE_SVC_SUCCESS; |
| } |
| |
| static int cpe_register_write(u32 reg, u32 val) |
| { |
| int ret = 0; |
| |
| if (reg != TOMTOM_A_SVASS_MEM_BANK) |
| pr_debug("%s: reg = 0x%x, value = 0x%x\n", |
| __func__, reg, val); |
| |
| ret = snd_soc_write(cdc_priv, reg, val); |
| if (ret < 0) |
| return CPE_SVC_FAILED; |
| |
| return CPE_SVC_SUCCESS; |
| } |
| |
| static int cpe_register_write_repeat(u32 reg, u8 *ptr, u32 to_write) |
| { |
| struct snd_soc_codec *codec = cdc_priv; |
| struct wcd9xxx *wcd9xxx = codec->control_data; |
| int ret = 0; |
| |
| ret = wcd9xxx_slim_write_repeat(wcd9xxx, reg, to_write, ptr); |
| if (ret != 0) |
| pr_err("%s: slim_write_repeat failed\n", __func__); |
| |
| if (ret < 0) |
| return CPE_SVC_FAILED; |
| |
| return CPE_SVC_SUCCESS; |
| } |
| |
| static bool cpe_register_read_autoinc_supported(void) |
| { |
| return true; |
| } |
| |
| static void cpe_cmd_received(struct cpe_info *t_info) |
| { |
| struct cpe_command_node *node = NULL; |
| bool check_next = true; |
| if (!t_info) { |
| pr_err("%s: Invalid thread info\n", |
| __func__); |
| return; |
| } |
| |
| while (!list_empty(&t_info->main_queue)) { |
| if (!check_next) |
| break; |
| node = list_first_entry(&t_info->main_queue, |
| struct cpe_command_node, list); |
| if (!node) |
| break; |
| list_del(&node->list); |
| check_next = t_info->cpe_process_command(node); |
| if (check_next) |
| kfree(node); |
| else |
| list_add(&node->list, &(t_info->main_queue)); |
| } |
| } |
| |
| static int cpe_worker_thread(void *context) |
| { |
| struct cpe_info *t_info = (struct cpe_info *)context; |
| |
| if (t_info->cpe_start_notification) |
| t_info->cpe_start_notification(t_info); |
| else |
| pr_debug("%s: no start notification\n", |
| __func__); |
| |
| while (!kthread_should_stop()) { |
| wait_for_completion(&t_info->cmd_complete); |
| cpe_cmd_received(t_info); |
| INIT_COMPLETION(t_info->cmd_complete); |
| }; |
| |
| return 0; |
| } |
| |
| static void cpe_create_worker_thread(struct cpe_info *t_info) |
| { |
| INIT_LIST_HEAD(&t_info->main_queue); |
| init_completion(&t_info->cmd_complete); |
| t_info->thread_handler = kthread_run(cpe_worker_thread, |
| (void *)t_info, "cpe-worker-thread"); |
| } |
| |
| static void cpe_cleanup_worker_thread(struct cpe_info *t_info) |
| { |
| if (t_info->thread_handler != NULL) |
| kthread_stop(t_info->thread_handler); |
| |
| t_info->thread_handler = NULL; |
| } |
| |
| static enum cpe_svc_result |
| cpe_send_cmd_to_thread(struct cpe_info *t_info, |
| enum cpe_command command, void *data) |
| { |
| enum cpe_svc_result rc = CPE_SVC_SUCCESS; |
| struct cpe_command_node *cmd = NULL; |
| |
| rc = cpe_is_command_valid(t_info, command); |
| if (rc != CPE_SVC_SUCCESS) { |
| pr_err("%s: Invalid command %d\n", |
| __func__, command); |
| return rc; |
| } |
| |
| cmd = kzalloc(sizeof(struct cpe_command_node), |
| GFP_ATOMIC); |
| if (!cmd) { |
| pr_err("%s: No memory for cmd node, size = %zu\n", |
| __func__, sizeof(struct cpe_command_node)); |
| return CPE_SVC_NO_MEMORY; |
| } |
| |
| cmd->command = command; |
| cmd->data = data; |
| list_add_tail(&(cmd->list), &(t_info->main_queue)); |
| |
| complete(&t_info->cmd_complete); |
| return rc; |
| } |
| |
| static enum cpe_svc_result |
| cpe_is_command_valid(const struct cpe_info *t_info, |
| enum cpe_command command) |
| { |
| enum cpe_svc_result rc = CPE_SVC_INVALID_HANDLE; |
| if (t_info && t_info->cpe_cmd_validate) |
| rc = t_info->cpe_cmd_validate(t_info, command); |
| else |
| pr_err("%s: invalid handle or callback\n", |
| __func__); |
| return rc; |
| } |
| |
| static void cpe_notify_client(struct cpe_notif_node *client, |
| struct cpe_svc_notification *payload) |
| { |
| if (!client || !payload) { |
| pr_err("%s: invalid client or payload\n", |
| __func__); |
| return; |
| } |
| |
| if (!(client->mask & payload->event)) { |
| pr_debug("%s: client mask 0x%x not registered for event 0x%x\n", |
| __func__, client->mask, payload->event); |
| return; |
| } |
| |
| if (client->notif.notification) |
| client->notif.notification(payload); |
| |
| if ((client->mask & CPE_SVC_CMI_MSG) && |
| client->notif.cmi_notification) |
| client->notif.cmi_notification( |
| (const struct cmi_api_notification *)payload); |
| } |
| |
| static void cpe_broadcast_notification(const struct cpe_info *t_info, |
| struct cpe_svc_notification *payload) |
| { |
| struct cpe_notif_node *n = NULL; |
| |
| if (!t_info || !payload) { |
| pr_err("%s: invalid handle\n", __func__); |
| return; |
| } |
| |
| pr_debug("%s: notify clients, event = %d\n", |
| __func__, payload->event); |
| payload->private_data = cdc_priv; |
| |
| list_for_each_entry(n, &t_info->client_list, list) { |
| if (!(n->mask & CPE_SVC_CMI_MSG)) { |
| cpe_notify_client(n, payload); |
| } |
| } |
| } |
| |
| static void *cpe_register_generic(struct cpe_info *t_info, |
| void notification_callback( |
| const struct cpe_svc_notification *parameter), |
| void cmi_callback( |
| const struct cmi_api_notification *parameter), |
| u32 mask, u32 service, const char *name) |
| { |
| struct cpe_notif_node *n = NULL; |
| |
| n = kzalloc(sizeof(struct cpe_notif_node), |
| GFP_KERNEL); |
| if (!n) { |
| pr_err("%s: No memory for notification, size = %zu\n", |
| __func__, sizeof(struct cpe_notif_node)); |
| return NULL; |
| } |
| n->mask = mask; |
| n->service = service; |
| n->notif.notification = notification_callback; |
| n->notif.cmi_notification = cmi_callback; |
| n->context = t_info; |
| n->name = name; |
| list_add_tail(&n->list, &t_info->client_list); |
| return n; |
| } |
| |
| static enum cpe_svc_result cpe_deregister_generic(struct cpe_info *t_info, |
| void *reg_handle) |
| { |
| struct cpe_notif_node *n = (struct cpe_notif_node *)reg_handle; |
| |
| if (!t_info || !reg_handle) { |
| pr_err("%s: invalid handle\n", __func__); |
| return CPE_SVC_INVALID_HANDLE; |
| } |
| |
| list_del(&(n->list)); |
| kfree(reg_handle); |
| |
| return CPE_SVC_SUCCESS; |
| } |
| |
| static enum cpe_svc_result cpe_svc_tgt_init(struct cpe_svc_codec_info_v1 *i, |
| struct cpe_svc_tgt_abstraction *abs) |
| { |
| if (!i || !abs) { |
| pr_err("%s: Incorrect information provided\n", |
| __func__); |
| return CPE_SVC_FAILED; |
| } |
| |
| switch (i->id) { |
| case CPE_SVC_CODEC_TOMTOM: |
| return cpe_tgt_tomtom_init(i, abs); |
| default: |
| pr_err("%s: Codec type %d not supported\n", |
| __func__, i->id); |
| return CPE_SVC_FAILED; |
| } |
| |
| return CPE_SVC_SUCCESS; |
| } |
| |
| static void cpe_notify_cmi_client(struct cpe_info *t_info, u8 *payload, |
| enum cpe_svc_result result) |
| { |
| struct cpe_notif_node *n = NULL; |
| struct cpe_svc_notification notif; |
| struct cmi_hdr *hdr; |
| u8 service = 0; |
| |
| if (!t_info || !payload) { |
| pr_err("%s: invalid payload/handle\n", |
| __func__); |
| return; |
| } |
| |
| hdr = CMI_GET_HEADER(payload); |
| service = CMI_HDR_GET_SERVICE(hdr); |
| |
| notif.event = CPE_SVC_CMI_MSG; |
| notif.result = result; |
| notif.payload = payload; |
| |
| list_for_each_entry(n, &t_info->client_list, list) { |
| if ((n->mask & CPE_SVC_CMI_MSG) && n->service == service) |
| cpe_notify_client(n, ¬if); |
| } |
| } |
| |
| static void cpe_toggle_irq_notification(struct cpe_info *t_info, u32 value) |
| { |
| if (cpe_irq_control_callback) |
| cpe_irq_control_callback(value); |
| } |
| |
| static void cpe_command_cleanup(struct cpe_command_node *command_node) |
| { |
| switch (command_node->command) { |
| case CPE_CMD_SEND_MSG: |
| case CPE_CMD_SEND_MSG_COMPLETE: |
| case CPE_CMD_SHUTDOWN: |
| case CPE_CMD_READ: |
| kfree(command_node->data); |
| command_node->data = NULL; |
| break; |
| default: |
| pr_err("%s: unhandled command\n", |
| __func__); |
| break; |
| } |
| } |
| |
| static void cpe_send_msg_to_inbox(struct cpe_info *t_info, |
| struct cpe_send_msg *msg) |
| { |
| size_t bytes = 0; |
| size_t inbox_size = |
| t_info->tgt->tgt_get_cpe_info()->inbox_size; |
| struct cmi_hdr *hdr; |
| |
| memset(t_info->tgt->inbox, 0, inbox_size); |
| hdr = CMI_GET_HEADER(t_info->tgt->inbox); |
| CMI_HDR_SET_SESSION(hdr, 1); |
| CMI_HDR_SET_SERVICE(hdr, CMI_CPE_CORE_SERVICE_ID); |
| CMI_HDR_SET_VERSION(hdr, CMI_DRIVER_SUPPORTED_VERSION); |
| CMI_HDR_SET_OBM(hdr, CMI_OBM_FLAG_IN_BAND); |
| |
| switch (t_info->substate) { |
| case CPE_SS_BOOT_INIT: { |
| struct cmi_core_svc_cmd_shared_mem_alloc *m; |
| hdr = CMI_GET_HEADER(t_info->tgt->inbox); |
| CMI_HDR_SET_OPCODE(hdr, |
| CPE_CORE_SVC_CMD_SHARED_MEM_ALLOC); |
| CMI_HDR_SET_PAYLOAD_SIZE(hdr, |
| sizeof(struct cmi_core_svc_cmd_shared_mem_alloc)); |
| m = (struct cmi_core_svc_cmd_shared_mem_alloc *) |
| CMI_GET_PAYLOAD(t_info->tgt->inbox); |
| m->size = CPE_MSG_BUFFER_SIZE; |
| pr_debug("send shared mem alloc msg to cpe inbox\n"); |
| } |
| break; |
| |
| case CPE_SS_DL_COMPLETED: |
| case CPE_SS_MSG_SEND_INBOX: |
| if (msg->address != 0) { |
| struct cmi_msg_transport *m = NULL; |
| struct cpe_svc_mem_segment mem_seg; |
| |
| mem_seg.type = CPE_SVC_DATA_MEM; |
| if (msg->isobm) { |
| struct cmi_obm *obm = (struct cmi_obm *) |
| CMI_GET_PAYLOAD(msg->payload); |
| mem_seg.cpe_addr = obm->mem_handle; |
| mem_seg.data = (u8 *)obm->data_ptr.kvaddr; |
| mem_seg.size = obm->size; |
| t_info->tgt->tgt_write_ram(t_info, &mem_seg); |
| } |
| |
| mem_seg.cpe_addr = msg->address; |
| mem_seg.data = msg->payload; |
| mem_seg.size = msg->size; |
| t_info->tgt->tgt_write_ram(t_info, &mem_seg); |
| |
| hdr = CMI_GET_HEADER(t_info->tgt->inbox); |
| CMI_HDR_SET_OPCODE(hdr, CMI_MSG_TRANSPORT); |
| m = (struct cmi_msg_transport *) |
| CMI_GET_PAYLOAD(t_info->tgt->inbox); |
| m->addr = msg->address; |
| m->size = msg->size; |
| CMI_HDR_SET_PAYLOAD_SIZE(hdr, |
| sizeof(struct cmi_msg_transport)); |
| } else { |
| memcpy(t_info->tgt->inbox, msg->payload, |
| msg->size); |
| } |
| |
| break; |
| case CPE_SS_MSG_REQUEST_ACCESS: |
| hdr = CMI_GET_HEADER(t_info->tgt->inbox); |
| CMI_HDR_SET_OPCODE(hdr, |
| CPE_CORE_SVC_CMD_DRAM_ACCESS_REQ); |
| CMI_HDR_SET_PAYLOAD_SIZE(hdr, 0); |
| pr_debug("%s: Creating DRAM acces request message\n", |
| __func__); |
| break; |
| |
| default: |
| pr_err("%s: unhandled substate %d\n", |
| __func__, t_info->substate); |
| break; |
| } |
| |
| pr_debug("%s: sending message to cpe inbox\n", |
| __func__); |
| bytes = sizeof(struct cmi_hdr); |
| hdr = CMI_GET_HEADER(t_info->tgt->inbox); |
| bytes += CMI_HDR_GET_PAYLOAD_SIZE(hdr); |
| t_info->tgt->tgt_write_mailbox(t_info->tgt->inbox, bytes); |
| } |
| |
| static void cpe_process_irq_int(u32 irq, |
| struct cpe_info *t_info) |
| { |
| struct cpe_command_node temp_node; |
| struct cpe_send_msg *m; |
| u8 size = 0; |
| bool err_irq = false; |
| |
| pr_debug("%s: irq = %u\n", __func__, irq); |
| |
| if (!t_info) { |
| pr_err("%s: Invalid handle\n", |
| __func__); |
| return; |
| } |
| |
| mutex_lock(&cpe_api_mutex); |
| switch (irq) { |
| case CPE_IRQ_OUTBOX_IRQ: |
| size = t_info->tgt->tgt_get_cpe_info()->outbox_size; |
| t_info->tgt->tgt_read_mailbox(t_info->tgt->outbox, size); |
| break; |
| |
| case CPE_IRQ_MEM_ACCESS_ERROR: |
| err_irq = true; |
| t_info->state = CPE_STATE_OFFLINE; |
| t_info->substate = CPE_SS_IDLE; |
| break; |
| |
| case CPE_IRQ_WDOG_BITE: |
| case CPE_IRQ_RCO_WDOG_INT: |
| err_irq = true; |
| mutex_unlock(&cpe_api_mutex); |
| cpe_svc_shutdown(t_info); |
| mutex_lock(&cpe_api_mutex); |
| break; |
| |
| case CPE_IRQ_FLL_LOCK_LOST: |
| default: |
| err_irq = true; |
| break; |
| } |
| |
| if (err_irq) { |
| pr_err("%s: CPE error IRQ %u occured\n", |
| __func__, irq); |
| mutex_unlock(&cpe_api_mutex); |
| return; |
| } |
| |
| switch (t_info->state) { |
| case CPE_STATE_BOOTING: |
| |
| switch (t_info->substate) { |
| case CPE_SS_BOOT: |
| temp_node.command = CPE_CMD_BOOT_INITIALIZE; |
| temp_node.result = CPE_SVC_SUCCESS; |
| t_info->substate = CPE_SS_BOOT_INIT; |
| t_info->cpe_process_command(&temp_node); |
| break; |
| |
| case CPE_SS_BOOT_INIT: |
| temp_node.command = CPE_CMD_BOOT_COMPLETE; |
| temp_node.result = CPE_SVC_SUCCESS; |
| t_info->substate = CPE_SS_ONLINE; |
| t_info->cpe_process_command(&temp_node); |
| break; |
| |
| default: |
| pr_debug("%s: unhandled substate %d for state %d\n", |
| __func__, t_info->state, t_info->substate); |
| break; |
| } |
| break; |
| |
| case CPE_STATE_SENDING_MSG: |
| m = (struct cpe_send_msg *)t_info->pending; |
| |
| switch (t_info->substate) { |
| case CPE_SS_MSG_REQUEST_ACCESS: |
| t_info->substate = CPE_SS_MSG_SEND_INBOX; |
| cpe_send_msg_to_inbox(t_info, m); |
| break; |
| |
| case CPE_SS_MSG_SEND_INBOX: |
| temp_node.command = CPE_CMD_SEND_MSG_COMPLETE; |
| temp_node.data = m->payload; |
| t_info->substate = CPE_SS_MSG_SENT; |
| t_info->cpe_process_command(&temp_node); |
| kfree(m); |
| t_info->pending = NULL; |
| break; |
| |
| default: |
| pr_debug("%s: unhandled substate %d for state %d\n", |
| __func__, t_info->state, t_info->substate); |
| break; |
| } |
| break; |
| |
| case CPE_STATE_IDLE: |
| pr_debug("%s: Message received, notifying client\n", |
| __func__); |
| cpe_notify_cmi_client(t_info, |
| t_info->tgt->outbox, CPE_SVC_SUCCESS); |
| break; |
| |
| default: |
| pr_debug("%s: unhandled state %d\n", |
| __func__, t_info->state); |
| break; |
| } |
| |
| mutex_unlock(&cpe_api_mutex); |
| } |
| |
| |
| static void broacast_boot_failed(void) |
| { |
| struct cpe_info *t_info = cpe_default_handle; |
| struct cpe_svc_notification payload; |
| |
| payload.event = CPE_SVC_BOOT_FAILED; |
| payload.result = CPE_SVC_FAILED; |
| payload.payload = NULL; |
| cpe_broadcast_notification(t_info, &payload); |
| } |
| |
| static enum cpe_svc_result broadcast_boot_event( |
| const struct cpe_info *t_info) |
| { |
| struct cpe_svc_notification payload; |
| |
| payload.event = CPE_SVC_ONLINE; |
| payload.result = CPE_SVC_SUCCESS; |
| payload.payload = NULL; |
| cpe_broadcast_notification(t_info, &payload); |
| |
| return CPE_SVC_SUCCESS; |
| } |
| |
| static bool cpe_mt_process_cmd(struct cpe_command_node *command_node) |
| { |
| bool rc = true; |
| struct cpe_info *t_info = cpe_default_handle; |
| struct cpe_svc_notification payload; |
| struct cmi_core_svc_event_system_boot *ev_boot; |
| struct cmi_core_svc_cmdrsp_shared_mem_alloc *rsp_shmem_alloc; |
| enum cpe_svc_result cpe_rc; |
| struct cpe_send_msg *m; |
| struct cmi_hdr *hdr; |
| u32 size; |
| u8 service = 0; |
| |
| if (!t_info || !command_node) { |
| pr_err("%s: Invalid handle/command node\n", |
| __func__); |
| return false; |
| } |
| |
| pr_debug("%s: cmd = %u\n", __func__, command_node->command); |
| |
| cpe_rc = cpe_is_command_valid(t_info, command_node->command); |
| |
| if (cpe_rc != CPE_SVC_SUCCESS) { |
| pr_err("%s: Invalid command %d, err = %d\n", |
| __func__, command_node->command, cpe_rc); |
| return false; |
| } |
| |
| switch (command_node->command) { |
| |
| case CPE_CMD_BOOT_INITIALIZE: |
| if (CMI_GET_OPCODE(t_info->tgt->outbox) != |
| CPE_CORE_SVC_EVENT_SYSTEM_BOOT) { |
| broacast_boot_failed(); |
| break; |
| } |
| |
| ev_boot = (struct cmi_core_svc_event_system_boot *) |
| CMI_GET_PAYLOAD(t_info->tgt->outbox); |
| |
| if (ev_boot->status != CPE_BOOT_SUCCESS) { |
| pr_err("%s: cpe boot failed, status = %d\n", |
| __func__, ev_boot->status); |
| broacast_boot_failed(); |
| break; |
| } |
| |
| t_info->substate = CPE_SS_BOOT_INIT; |
| cpe_send_msg_to_inbox(t_info, NULL); |
| break; |
| |
| case CPE_CMD_BOOT_COMPLETE: |
| if (CMI_GET_OPCODE(t_info->tgt->outbox) != |
| CPE_CORE_SVC_CMDRSP_SHARED_MEM_ALLOC) { |
| broacast_boot_failed(); |
| break; |
| } |
| |
| rsp_shmem_alloc = |
| (struct cmi_core_svc_cmdrsp_shared_mem_alloc *) |
| CMI_GET_PAYLOAD(t_info->tgt->outbox); |
| cpe_msg_buffer = rsp_shmem_alloc->addr; |
| |
| if (cpe_msg_buffer == 0) { |
| pr_err("%s: Invalid cpe buffer for message\n", |
| __func__); |
| broacast_boot_failed(); |
| } else { |
| pr_debug("%s: boot complete\n", __func__); |
| cpe_create_worker_thread(t_info); |
| } |
| |
| t_info->state = CPE_STATE_IDLE; |
| t_info->substate = CPE_SS_IDLE; |
| break; |
| |
| case CPE_CMD_SEND_MSG: |
| m = (struct cpe_send_msg *)command_node->data; |
| size = m->size; |
| t_info->state = CPE_STATE_SENDING_MSG; |
| |
| if (size <= t_info->tgt->tgt_get_cpe_info()->inbox_size) { |
| t_info->substate = CPE_SS_MSG_SEND_INBOX; |
| pr_debug("%s: msg fits mailbox, size %u\n", |
| __func__, size); |
| } else if (size < CPE_MSG_BUFFER_SIZE) { |
| t_info->substate = CPE_SS_MSG_REQUEST_ACCESS; |
| m->address = cpe_msg_buffer; |
| pr_debug("%s: msg is inband\n", __func__); |
| } else { |
| pr_err("%s: Wrong msg size %u\n", |
| __func__, size); |
| t_info->state = CPE_STATE_IDLE; |
| cpe_command_cleanup(command_node); |
| return CPE_SVC_FAILED; |
| } |
| |
| t_info->pending = m; |
| cpe_send_msg_to_inbox(t_info, m); |
| break; |
| |
| case CPE_CMD_SEND_MSG_COMPLETE: |
| hdr = CMI_GET_HEADER(t_info->tgt->outbox); |
| service = CMI_HDR_GET_SERVICE(hdr); |
| pr_debug("%s: msg send success, notifying clients\n", |
| __func__); |
| cpe_notify_cmi_client(t_info, |
| t_info->tgt->outbox, CPE_SVC_SUCCESS); |
| cpe_command_cleanup(command_node); |
| t_info->state = CPE_STATE_IDLE; |
| break; |
| |
| case CPE_CMD_KILL_THREAD: |
| |
| if (t_info->pending) { |
| struct cpe_send_msg *m = |
| (struct cpe_send_msg *)t_info->pending; |
| cpe_notify_cmi_client(t_info, m->payload, |
| CPE_SVC_SHUTTING_DOWN); |
| kfree(t_info->pending); |
| t_info->pending = NULL; |
| } |
| |
| cpe_command_cleanup(command_node); |
| kfree(command_node); |
| payload.result = CPE_SVC_SHUTTING_DOWN; |
| payload.event = CPE_SVC_OFFLINE; |
| payload.payload = NULL; |
| /* |
| * Make state as offline before broadcasting |
| * the message to clients. |
| */ |
| t_info->state = CPE_STATE_OFFLINE; |
| t_info->substate = CPE_SS_IDLE; |
| cpe_broadcast_notification(t_info, &payload); |
| cpe_cleanup_worker_thread(t_info); |
| break; |
| |
| default: |
| pr_err("%s: unhandled cpe cmd = %d\n", |
| __func__, command_node->command); |
| break; |
| } |
| |
| return rc; |
| } |
| |
| static enum cpe_svc_result cpe_mt_validate_cmd( |
| const struct cpe_info *t_info, |
| enum cpe_command command) |
| { |
| enum cpe_svc_result rc = CPE_SVC_SUCCESS; |
| |
| if (t_info && t_info->initialized == false) { |
| pr_err("%s: cpe service is not ready\n", |
| __func__); |
| return CPE_SVC_NOT_READY; |
| } |
| |
| switch (t_info->state) { |
| case CPE_STATE_UNINITIALIZED: |
| case CPE_STATE_INITIALIZED: |
| switch (command) { |
| case CPE_CMD_RESET: |
| case CPE_CMD_DL_SEGMENT: |
| case CPE_CMD_PROCESS_IRQ: |
| case CPE_CMD_KILL_THREAD: |
| case CPE_CMD_DEINITIALIZE: |
| rc = CPE_SVC_SUCCESS; |
| break; |
| default: |
| rc = CPE_SVC_NOT_READY; |
| break; |
| } |
| break; |
| |
| case CPE_STATE_DOWNLOADING: |
| switch (command) { |
| case CPE_CMD_RESET: |
| case CPE_CMD_DL_SEGMENT: |
| case CPE_CMD_BOOT: |
| rc = CPE_SVC_SUCCESS; |
| break; |
| default: |
| rc = CPE_SVC_NOT_READY; |
| break; |
| } |
| break; |
| |
| case CPE_STATE_BOOTING: |
| switch (command) { |
| case CPE_CMD_PROCESS_IRQ: |
| case CPE_CMD_BOOT_INITIALIZE: |
| case CPE_CMD_BOOT_COMPLETE: |
| case CPE_CMD_SHUTDOWN: |
| rc = CPE_SVC_SUCCESS; |
| break; |
| default: |
| rc = CPE_SVC_NOT_READY; |
| break; |
| } |
| break; |
| |
| case CPE_STATE_IDLE: |
| case CPE_STATE_SENDING_MSG: |
| switch (command) { |
| case CPE_CMD_SEND_MSG: |
| case CPE_CMD_SEND_MSG_COMPLETE: |
| case CPE_CMD_PROCESS_IRQ: |
| case CPE_CMD_SHUTDOWN: |
| case CPE_CMD_KILL_THREAD: |
| break; |
| default: |
| rc = CPE_SVC_FAILED; |
| break; |
| } |
| break; |
| |
| case CPE_STATE_OFFLINE: |
| switch (command) { |
| case CPE_CMD_RESET: |
| case CPE_CMD_RAMDUMP: |
| case CPE_CMD_KILL_THREAD: |
| rc = CPE_SVC_SUCCESS; |
| break; |
| default: |
| rc = CPE_SVC_NOT_READY; |
| break; |
| } |
| break; |
| |
| default: |
| pr_debug("%s: unhandled state %d\n", |
| __func__, t_info->state); |
| break; |
| } |
| |
| if (rc != CPE_SVC_SUCCESS) |
| pr_err("%s: invalid command %d, state = %d\n", |
| __func__, command, t_info->state); |
| return rc; |
| } |
| |
| void *cpe_svc_initialize( |
| void irq_control_callback(u32 enable), |
| const void *codec_info, void *context) |
| { |
| struct cpe_info *t_info = NULL; |
| const struct cpe_svc_hw_cfg *cap = NULL; |
| enum cpe_svc_result rc = CPE_SVC_SUCCESS; |
| |
| if (cpe_default_handle && cpe_default_handle->initialized == true) |
| return (void *)cpe_default_handle; |
| |
| if (!cpe_default_handle) { |
| cpe_default_handle = kzalloc(sizeof(struct cpe_info), |
| GFP_KERNEL); |
| if (!cpe_default_handle) { |
| pr_err("%s: no memory for cpe handle, size = %zu\n", |
| __func__, sizeof(struct cpe_info)); |
| goto err_register; |
| } |
| |
| memset(cpe_default_handle, 0, |
| sizeof(struct cpe_info)); |
| } |
| |
| t_info = cpe_default_handle; |
| |
| INIT_LIST_HEAD(&t_info->client_list); |
| cdc_priv = context; |
| |
| t_info->tgt = kzalloc(sizeof(struct cpe_svc_tgt_abstraction), |
| GFP_KERNEL); |
| if (!t_info->tgt) { |
| pr_err("%s: target allocation failed, size = %zu\n", |
| __func__, |
| sizeof(struct cpe_svc_tgt_abstraction)); |
| goto err_tgt_alloc; |
| } |
| |
| rc = cpe_svc_tgt_init((struct cpe_svc_codec_info_v1 *)codec_info, |
| t_info->tgt); |
| |
| if (rc != CPE_SVC_SUCCESS) { |
| pr_err("%s: target initialization failed, err = %d\n", |
| __func__, rc); |
| goto err_tgt_init; |
| } |
| |
| cap = t_info->tgt->tgt_get_cpe_info(); |
| |
| memset(t_info->tgt->outbox, 0, cap->outbox_size); |
| memset(t_info->tgt->inbox, 0, cap->inbox_size); |
| cpe_irq_control_callback = irq_control_callback; |
| t_info->cpe_process_command = cpe_mt_process_cmd; |
| t_info->cpe_cmd_validate = cpe_mt_validate_cmd; |
| t_info->cpe_start_notification = broadcast_boot_event; |
| mutex_init(&cpe_api_mutex); |
| pr_debug("%s: cpe services initialized\n", __func__); |
| t_info->state = CPE_STATE_INITIALIZED; |
| t_info->initialized = true; |
| |
| return t_info; |
| |
| err_tgt_init: |
| kfree(t_info->tgt); |
| |
| err_tgt_alloc: |
| kfree(cpe_default_handle); |
| cpe_default_handle = NULL; |
| |
| err_register: |
| return NULL; |
| } |
| |
| enum cpe_svc_result cpe_svc_deinitialize(void *cpe_handle) |
| { |
| enum cpe_svc_result rc = CPE_SVC_SUCCESS; |
| struct cpe_info *t_info = (struct cpe_info *)cpe_handle; |
| |
| if (!t_info) |
| t_info = cpe_default_handle; |
| |
| rc = cpe_is_command_valid(t_info, CPE_CMD_DEINITIALIZE); |
| |
| if (rc != CPE_SVC_SUCCESS) { |
| pr_err("%s: Invalid command %d\n", |
| __func__, CPE_CMD_DEINITIALIZE); |
| return rc; |
| } |
| |
| if (cpe_default_handle == t_info) |
| cpe_default_handle = NULL; |
| |
| t_info->tgt->tgt_deinit(t_info->tgt); |
| kfree(t_info->tgt); |
| kfree(t_info); |
| mutex_destroy(&cpe_api_mutex); |
| |
| return rc; |
| } |
| |
| void *cpe_svc_register(void *cpe_handle, |
| void (*notification_callback) |
| (const struct cpe_svc_notification *parameter), |
| u32 mask, const char *name) |
| { |
| if (!cpe_default_handle) { |
| cpe_default_handle = kzalloc(sizeof(struct cpe_info), |
| GFP_KERNEL); |
| if (!cpe_default_handle) { |
| pr_err("%s: no_mem for cpe handle, sz = %zu\n", |
| __func__, sizeof(struct cpe_info)); |
| return NULL; |
| } |
| |
| memset(cpe_default_handle, 0, |
| sizeof(struct cpe_info)); |
| } |
| |
| if (!cpe_handle) |
| cpe_handle = cpe_default_handle; |
| |
| return cpe_register_generic((struct cpe_info *)cpe_handle, |
| notification_callback, |
| NULL, |
| mask, CPE_NO_SERVICE, name); |
| } |
| |
| enum cpe_svc_result cpe_svc_deregister(void *cpe_handle, void *reg_handle) |
| { |
| if (!cpe_handle) |
| cpe_handle = cpe_default_handle; |
| |
| return cpe_deregister_generic((struct cpe_info *)cpe_handle, |
| reg_handle); |
| } |
| |
| enum cpe_svc_result cpe_svc_download_segment(void *cpe_handle, |
| const struct cpe_svc_mem_segment *segment) |
| { |
| enum cpe_svc_result rc = CPE_SVC_SUCCESS; |
| struct cpe_info *t_info = (struct cpe_info *)cpe_handle; |
| |
| if (!t_info) |
| t_info = cpe_default_handle; |
| |
| rc = cpe_is_command_valid(t_info, CPE_CMD_DL_SEGMENT); |
| |
| if (rc != CPE_SVC_SUCCESS) { |
| pr_err("%s: cmd validation fail, cmd = %d\n", |
| __func__, CPE_CMD_DL_SEGMENT); |
| return rc; |
| } |
| |
| mutex_lock(&cpe_api_mutex); |
| cpe_toggle_irq_notification(t_info, false); |
| t_info->state = CPE_STATE_DOWNLOADING; |
| t_info->substate = CPE_SS_DL_DOWNLOADING; |
| rc = t_info->tgt->tgt_write_ram(t_info, segment); |
| cpe_toggle_irq_notification(t_info, true); |
| mutex_unlock(&cpe_api_mutex); |
| |
| return rc; |
| } |
| |
| enum cpe_svc_result cpe_svc_boot(void *cpe_handle, int debug_mode) |
| { |
| enum cpe_svc_result rc = CPE_SVC_SUCCESS; |
| struct cpe_info *t_info = (struct cpe_info *)cpe_handle; |
| |
| if (!t_info) |
| t_info = cpe_default_handle; |
| |
| rc = cpe_is_command_valid(t_info, CPE_CMD_BOOT); |
| |
| if (rc != CPE_SVC_SUCCESS) { |
| pr_err("%s: cmd validation fail, cmd = %d\n", |
| __func__, CPE_CMD_BOOT); |
| return rc; |
| } |
| |
| mutex_lock(&cpe_api_mutex); |
| |
| if (rc == CPE_SVC_SUCCESS) { |
| t_info->tgt->tgt_boot(debug_mode); |
| t_info->state = CPE_STATE_BOOTING; |
| t_info->substate = CPE_SS_BOOT; |
| pr_debug("%s: cpe service booting\n", |
| __func__); |
| } |
| |
| mutex_unlock(&cpe_api_mutex); |
| return rc; |
| } |
| |
| enum cpe_svc_result cpe_svc_process_irq(void *cpe_handle, u32 cpe_irq) |
| { |
| enum cpe_svc_result rc = CPE_SVC_SUCCESS; |
| struct cpe_info *t_info = (struct cpe_info *)cpe_handle; |
| |
| if (!t_info) |
| t_info = cpe_default_handle; |
| |
| cpe_toggle_irq_notification(t_info, false); |
| cpe_process_irq_int(cpe_irq, t_info); |
| cpe_toggle_irq_notification(t_info, true); |
| |
| return rc; |
| } |
| |
| enum cpe_svc_result cpe_svc_route_notification(void *cpe_handle, |
| enum cpe_svc_module module, enum cpe_svc_route_dest dest) |
| { |
| struct cpe_info *t_info = (struct cpe_info *)cpe_handle; |
| |
| if (!t_info) |
| t_info = cpe_default_handle; |
| |
| if (t_info->tgt) |
| return t_info->tgt->tgt_route_notification(module, dest); |
| |
| return CPE_SVC_NOT_READY; |
| } |
| |
| enum cpe_svc_result cpe_svc_shutdown(void *cpe_handle) |
| { |
| enum cpe_svc_result rc = CPE_SVC_SUCCESS; |
| struct cpe_info *t_info = (struct cpe_info *)cpe_handle; |
| struct cpe_command_node *n = NULL; |
| |
| if (!t_info) |
| t_info = cpe_default_handle; |
| |
| rc = cpe_is_command_valid(t_info, CPE_CMD_SHUTDOWN); |
| |
| if (rc != CPE_SVC_SUCCESS) { |
| pr_err("%s: cmd validation fail, cmd = %d\n", |
| __func__, CPE_CMD_SHUTDOWN); |
| return rc; |
| } |
| |
| mutex_lock(&cpe_api_mutex); |
| |
| while (!list_empty(&t_info->main_queue)) { |
| n = list_first_entry(&t_info->main_queue, |
| struct cpe_command_node, list); |
| |
| if (n->command == CPE_CMD_SEND_MSG) { |
| cpe_notify_cmi_client(t_info, (u8 *)n->data, |
| CPE_SVC_SHUTTING_DOWN); |
| } |
| |
| cpe_command_cleanup(n); |
| kfree(n); |
| } |
| |
| pr_debug("%s: cpe service OFFLINE state\n", __func__); |
| |
| t_info->state = CPE_STATE_OFFLINE; |
| t_info->substate = CPE_SS_IDLE; |
| |
| rc = cpe_send_cmd_to_thread(t_info, CPE_CMD_KILL_THREAD, NULL); |
| mutex_unlock(&cpe_api_mutex); |
| |
| return rc; |
| } |
| |
| enum cpe_svc_result cpe_svc_reset(void *cpe_handle) |
| { |
| enum cpe_svc_result rc = CPE_SVC_SUCCESS; |
| struct cpe_info *t_info = (struct cpe_info *)cpe_handle; |
| |
| if (!t_info) |
| t_info = cpe_default_handle; |
| |
| rc = cpe_is_command_valid(t_info, CPE_CMD_RESET); |
| |
| if (rc != CPE_SVC_SUCCESS) { |
| pr_err("%s: cmd validation fail, cmd = %d\n", |
| __func__, CPE_CMD_RESET); |
| return rc; |
| } |
| |
| if (t_info && t_info->tgt) { |
| mutex_lock(&cpe_api_mutex); |
| rc = t_info->tgt->tgt_reset(); |
| pr_debug("%s: cpe services in INITIALIZED state\n", |
| __func__); |
| t_info->state = CPE_STATE_INITIALIZED; |
| t_info->substate = CPE_SS_IDLE; |
| mutex_unlock(&cpe_api_mutex); |
| } |
| |
| return rc; |
| } |
| |
| enum cpe_svc_result cpe_svc_ramdump(void *cpe_handle, |
| struct cpe_svc_mem_segment *buffer) |
| { |
| enum cpe_svc_result rc = CPE_SVC_SUCCESS; |
| struct cpe_info *t_info = (struct cpe_info *)cpe_handle; |
| |
| if (!t_info) |
| t_info = cpe_default_handle; |
| |
| rc = cpe_is_command_valid(t_info, CPE_CMD_RAMDUMP); |
| if (rc != CPE_SVC_SUCCESS) { |
| pr_err("%s: cmd validation fail, cmd = %d\n", |
| __func__, CPE_CMD_RAMDUMP); |
| return rc; |
| } |
| |
| if (t_info->tgt) { |
| rc = t_info->tgt->tgt_read_ram(t_info, buffer); |
| } else { |
| pr_err("%s: cpe service not ready\n", __func__); |
| return CPE_SVC_NOT_READY; |
| } |
| |
| return rc; |
| } |
| |
| enum cpe_svc_result cpe_svc_set_debug_mode(void *cpe_handle, u32 mode) |
| { |
| struct cpe_info *t_info = (struct cpe_info *)cpe_handle; |
| |
| if (!t_info) |
| t_info = cpe_default_handle; |
| |
| if (t_info->tgt) |
| return t_info->tgt->tgt_set_debug_mode(mode); |
| |
| else |
| return CPE_SVC_INVALID_HANDLE; |
| } |
| |
| const struct cpe_svc_hw_cfg *cpe_svc_get_hw_cfg(void *cpe_handle) |
| { |
| struct cpe_info *t_info = (struct cpe_info *)cpe_handle; |
| |
| if (!t_info) |
| t_info = cpe_default_handle; |
| |
| if (t_info->tgt) |
| return t_info->tgt->tgt_get_cpe_info(); |
| else |
| return NULL; |
| } |
| |
| void *cmi_register( |
| void notification_callback( |
| const struct cmi_api_notification *parameter), |
| u32 service) |
| { |
| return cpe_register_generic(cpe_default_handle, |
| NULL, |
| notification_callback, |
| CPE_SVC_CMI_MSG | CPE_SVC_OFFLINE | CPE_SVC_ONLINE, |
| service, |
| "CMI_CLIENT"); |
| } |
| |
| enum cmi_api_result cmi_deregister(void *reg_handle) |
| { |
| u32 clients = 0; |
| struct cpe_notif_node *n = NULL; |
| enum cmi_api_result rc = CMI_API_SUCCESS; |
| struct cpe_svc_notification payload; |
| |
| rc = (enum cmi_api_result) cpe_deregister_generic( |
| cpe_default_handle, reg_handle); |
| |
| list_for_each_entry(n, &cpe_default_handle->client_list, list) { |
| if (n->mask & CPE_SVC_CMI_MSG) |
| clients++; |
| } |
| |
| if (clients == 0) { |
| payload.event = CPE_SVC_CMI_CLIENTS_DEREG; |
| payload.payload = NULL; |
| payload.result = CPE_SVC_SUCCESS; |
| cpe_broadcast_notification(cpe_default_handle, &payload); |
| } |
| |
| return rc; |
| } |
| |
| enum cmi_api_result cmi_send_msg(void *message) |
| { |
| enum cmi_api_result rc = CMI_API_SUCCESS; |
| struct cpe_send_msg *msg = NULL; |
| struct cmi_hdr *hdr; |
| |
| hdr = CMI_GET_HEADER(message); |
| msg = kzalloc(sizeof(struct cpe_send_msg), |
| GFP_ATOMIC); |
| if (!msg) { |
| pr_err("%s: no memory for cmi msg, sz = %zu\n", |
| __func__, sizeof(struct cpe_send_msg)); |
| return CPE_SVC_NO_MEMORY; |
| } |
| |
| if (CMI_HDR_GET_OBM_FLAG(hdr) == CMI_OBM_FLAG_OUT_BAND) |
| msg->isobm = 1; |
| else |
| msg->isobm = 0; |
| |
| msg->size = sizeof(struct cmi_hdr) + |
| CMI_HDR_GET_PAYLOAD_SIZE(hdr); |
| |
| msg->payload = kzalloc(msg->size, GFP_ATOMIC); |
| if (!msg->payload) { |
| pr_err("%s: no memory for cmi payload, sz = %d\n", |
| __func__, msg->size); |
| kfree(msg); |
| return CPE_SVC_NO_MEMORY; |
| } |
| |
| msg->address = 0; |
| memcpy((void *)msg->payload, message, msg->size); |
| |
| rc = (enum cmi_api_result)cpe_send_cmd_to_thread(cpe_default_handle, |
| CPE_CMD_SEND_MSG, (void *)msg); |
| |
| if (rc != 0) { |
| pr_err("%s: Failed to queue message\n", __func__); |
| kfree(msg->payload); |
| kfree(msg); |
| } |
| |
| return rc; |
| } |
| |
| static enum cpe_svc_result cpe_tgt_tomtom_boot(int debug_mode) |
| { |
| enum cpe_svc_result rc = CPE_SVC_SUCCESS; |
| |
| if (!debug_mode) |
| rc = cpe_update_bits(TOMTOM_A_SVASS_CPAR_WDOG_CFG, |
| 0x3F, 0x31); |
| else |
| pr_info("%s: CPE in debug mode, WDOG disabled\n", |
| __func__); |
| |
| rc = cpe_update_bits(TOMTOM_A_SVASS_CLKRST_CTL, |
| 0x0C, 0x04); |
| rc = cpe_update_bits(TOMTOM_A_SVASS_CPAR_CFG, |
| 0x01, 0x01); |
| |
| return rc; |
| } |
| |
| static u32 cpe_tgt_tomtom_is_cpar_init_done(void) |
| { |
| u8 status = 0; |
| cpe_register_read(TOMTOM_A_SVASS_STATUS, &status); |
| status = status & 0x01; |
| return status & 0x01; |
| } |
| |
| static u32 cpe_tgt_tomtom_is_active(void) |
| { |
| u8 status = 0; |
| cpe_register_read(TOMTOM_A_SVASS_STATUS, &status); |
| status = status & 0x04; |
| return status; |
| } |
| |
| static enum cpe_svc_result cpe_tgt_tomtom_reset(void) |
| { |
| enum cpe_svc_result rc = CPE_SVC_SUCCESS; |
| |
| rc = cpe_update_bits(TOMTOM_A_SVASS_CPAR_CFG, |
| 0x01, 0x00); |
| rc = cpe_update_bits(TOMTOM_A_MEM_LEAKAGE_CTL, |
| 0x07, 0x03); |
| rc = cpe_update_bits(TOMTOM_A_SVASS_CLKRST_CTL, |
| 0x08, 0x08); |
| return rc; |
| } |
| |
| enum cpe_svc_result cpe_tgt_tomtom_voicetx(bool enable) |
| { |
| enum cpe_svc_result rc = CPE_SVC_SUCCESS; |
| u8 val = 0; |
| |
| if (enable) |
| val = 0x02; |
| else |
| val = 0x00; |
| rc = cpe_update_bits(TOMTOM_A_SVASS_CFG, |
| 0x02, val); |
| val = 0; |
| cpe_register_read(TOMTOM_A_SVASS_CFG, &val); |
| return rc; |
| } |
| |
| enum cpe_svc_result cpe_svc_toggle_lab(void *cpe_handle, bool enable) |
| { |
| |
| struct cpe_info *t_info = (struct cpe_info *)cpe_handle; |
| |
| if (!t_info) |
| t_info = cpe_default_handle; |
| |
| if (t_info->tgt) |
| return t_info->tgt->tgt_voice_tx_lab(enable); |
| else |
| return CPE_SVC_INVALID_HANDLE; |
| } |
| |
| static enum cpe_svc_result cpe_tgt_tomtom_read_mailbox(u8 *buffer, |
| size_t size) |
| { |
| enum cpe_svc_result rc = CPE_SVC_SUCCESS; |
| u32 cnt = 0; |
| |
| if (size >= TOMTOM_A_SVASS_SPE_OUTBOX_SIZE) |
| size = TOMTOM_A_SVASS_SPE_OUTBOX_SIZE - 1; |
| for (cnt = 0; (cnt < size) && (rc == CPE_SVC_SUCCESS); cnt++) { |
| rc = cpe_register_read(TOMTOM_A_SVASS_SPE_OUTBOX(cnt), |
| &(buffer[cnt])); |
| } |
| return rc; |
| } |
| |
| static enum cpe_svc_result cpe_tgt_tomtom_write_mailbox(u8 *buffer, |
| size_t size) |
| { |
| enum cpe_svc_result rc = CPE_SVC_SUCCESS; |
| u32 cnt = 0; |
| |
| if (size >= TOMTOM_A_SVASS_SPE_INBOX_SIZE) |
| size = TOMTOM_A_SVASS_SPE_INBOX_SIZE - 1; |
| for (cnt = 0; (cnt < size) && (rc == CPE_SVC_SUCCESS); cnt++) { |
| rc = cpe_register_write(TOMTOM_A_SVASS_SPE_INBOX(cnt), |
| buffer[cnt]); |
| } |
| |
| if (rc == CPE_SVC_SUCCESS) |
| rc = cpe_register_write(TOMTOM_A_SVASS_SPE_INBOX_TRG, 1); |
| |
| return rc; |
| } |
| |
| static enum cpe_svc_result cpe_get_mem_addr(struct cpe_info *t_info, |
| const struct cpe_svc_mem_segment *mem_seg, |
| u32 *addr, u8 *mem) |
| { |
| enum cpe_svc_result rc = CPE_SVC_SUCCESS; |
| u32 offset, mem_sz, address; |
| u8 mem_type; |
| |
| switch (mem_seg->type) { |
| |
| case CPE_SVC_DATA_MEM: |
| mem_type = MEM_ACCESS_DRAM_VAL; |
| offset = TOMTOM_A_SVASS_SPE_DRAM_OFFSET; |
| mem_sz = TOMTOM_A_SVASS_SPE_DRAM_SIZE; |
| break; |
| |
| case CPE_SVC_INSTRUCTION_MEM: |
| mem_type = MEM_ACCESS_IRAM_VAL; |
| offset = TOMTOM_A_SVASS_SPE_IRAM_OFFSET; |
| mem_sz = TOMTOM_A_SVASS_SPE_IRAM_SIZE; |
| break; |
| |
| default: |
| pr_err("%s: Invalid mem type = %u\n", |
| __func__, mem_seg->type); |
| return CPE_SVC_INVALID_HANDLE; |
| } |
| |
| if (mem_seg->cpe_addr < offset) { |
| pr_err("%s: Invalid addr %x for mem type %u\n", |
| __func__, mem_seg->cpe_addr, mem_type); |
| return CPE_SVC_INVALID_HANDLE; |
| } |
| |
| address = mem_seg->cpe_addr - offset; |
| if (address + mem_seg->size > mem_sz) { |
| pr_err("%s: wrong size %u, start adress %x, mem_type %u\n", |
| __func__, mem_seg->size, address, mem_type); |
| return CPE_SVC_INVALID_HANDLE; |
| } |
| |
| (*addr) = address; |
| (*mem) = mem_type; |
| |
| return rc; |
| } |
| |
| static enum cpe_svc_result cpe_tgt_tomtom_read_RAM(struct cpe_info *t_info, |
| struct cpe_svc_mem_segment *mem_seg) |
| { |
| enum cpe_svc_result rc = CPE_SVC_SUCCESS; |
| u8 mem_reg_val = 0; |
| u32 cnt = 0; |
| bool autoinc; |
| u8 mem = MEM_ACCESS_NONE_VAL; |
| u32 addr = 0; |
| u32 ptr_update = true; |
| |
| if (!mem_seg) { |
| pr_err("%s: Invalid mem segment\n", |
| __func__); |
| return CPE_SVC_INVALID_HANDLE; |
| } |
| |
| rc = cpe_get_mem_addr(t_info, mem_seg, &addr, &mem); |
| |
| if (rc != CPE_SVC_SUCCESS) { |
| pr_err("%s: Cannot obtain address, mem_type %u\n", |
| __func__, mem_seg->type); |
| return rc; |
| } |
| |
| rc = cpe_register_write(TOMTOM_A_SVASS_MEM_CTL, 0); |
| autoinc = cpe_register_read_autoinc_supported(); |
| if (autoinc) |
| mem_reg_val |= 0x04; |
| |
| mem_reg_val |= 0x08; |
| mem_reg_val |= mem; |
| |
| do { |
| if (autoinc || ptr_update) { |
| rc = cpe_register_write(TOMTOM_A_SVASS_MEM_PTR0, |
| (addr & 0xFF)); |
| rc = cpe_register_write(TOMTOM_A_SVASS_MEM_PTR1, |
| ((addr >> 8) & 0xFF)); |
| rc = cpe_register_write(TOMTOM_A_SVASS_MEM_PTR2, |
| ((addr >> 16) & 0xFF)); |
| |
| rc = cpe_register_write(TOMTOM_A_SVASS_MEM_CTL, |
| mem_reg_val); |
| |
| ptr_update = false; |
| } |
| rc = cpe_register_read(TOMTOM_A_SVASS_MEM_BANK, |
| &mem_seg->data[cnt]); |
| |
| if (!autoinc) |
| rc = cpe_register_write(TOMTOM_A_SVASS_MEM_CTL, 0); |
| } while (++cnt < mem_seg->size); |
| |
| rc = cpe_register_write(TOMTOM_A_SVASS_MEM_CTL, 0); |
| |
| return rc; |
| } |
| |
| static enum cpe_svc_result cpe_tgt_tomtom_write_RAM(struct cpe_info *t_info, |
| const struct cpe_svc_mem_segment *mem_seg) |
| { |
| enum cpe_svc_result rc = CPE_SVC_SUCCESS; |
| u8 mem_reg_val = 0; |
| u8 mem = MEM_ACCESS_NONE_VAL; |
| u32 addr = 0; |
| u8 *temp_ptr = NULL; |
| u32 temp_size = 0; |
| bool autoinc; |
| |
| if (!mem_seg) { |
| pr_err("%s: Invalid mem segment\n", |
| __func__); |
| return CPE_SVC_INVALID_HANDLE; |
| } |
| |
| rc = cpe_get_mem_addr(t_info, mem_seg, &addr, &mem); |
| |
| if (rc != CPE_SVC_SUCCESS) { |
| pr_err("%s: Cannot obtain address, mem_type %u\n", |
| __func__, mem_seg->type); |
| return rc; |
| } |
| |
| autoinc = cpe_register_read_autoinc_supported(); |
| if (autoinc) |
| mem_reg_val |= 0x04; |
| mem_reg_val |= mem; |
| |
| rc = cpe_update_bits(TOMTOM_A_SVASS_MEM_CTL, |
| 0x0F, mem_reg_val); |
| |
| rc = cpe_register_write(TOMTOM_A_SVASS_MEM_PTR0, |
| (addr & 0xFF)); |
| rc = cpe_register_write(TOMTOM_A_SVASS_MEM_PTR1, |
| ((addr >> 8) & 0xFF)); |
| |
| rc = cpe_register_write(TOMTOM_A_SVASS_MEM_PTR2, |
| ((addr >> 16) & 0xFF)); |
| |
| temp_size = 0; |
| temp_ptr = mem_seg->data; |
| |
| while (temp_size <= mem_seg->size) { |
| u32 to_write = (mem_seg->size >= temp_size+CHUNK_SIZE) |
| ? CHUNK_SIZE : (mem_seg->size-temp_size); |
| |
| if (t_info->state == CPE_STATE_OFFLINE) { |
| pr_err("%s: CPE is offline\n", __func__); |
| return CPE_SVC_FAILED; |
| } |
| |
| cpe_register_write_repeat(TOMTOM_A_SVASS_MEM_BANK, |
| temp_ptr, to_write); |
| temp_size += CHUNK_SIZE; |
| temp_ptr += CHUNK_SIZE; |
| } |
| |
| rc = cpe_register_write(TOMTOM_A_SVASS_MEM_CTL, 0); |
| return rc; |
| } |
| |
| static enum cpe_svc_result cpe_tgt_tomtom_route_notification( |
| enum cpe_svc_module module, |
| enum cpe_svc_route_dest dest) |
| { |
| enum cpe_svc_result rc = CPE_SVC_SUCCESS; |
| u8 ctl_reg_val = 0; |
| |
| switch (module) { |
| case CPE_SVC_LISTEN_PROC: |
| switch (dest) { |
| case CPE_SVC_EXTERNAL: |
| ctl_reg_val = LISTEN_CTL_MSM_VAL; |
| break; |
| case CPE_SVC_INTERNAL: |
| ctl_reg_val = LISTEN_CTL_SPE_VAL; |
| break; |
| default: |
| pr_err("%s: Invalid dest %d\n", |
| __func__, dest); |
| return CPE_SVC_FAILED; |
| } |
| |
| rc = cpe_update_bits(TOMTOM_A_SVASS_CFG, |
| 0x01, ctl_reg_val); |
| break; |
| default: |
| pr_err("%s: Invalid module %d\n", |
| __func__, module); |
| rc = CPE_SVC_FAILED; |
| break; |
| } |
| |
| return rc; |
| } |
| |
| static enum cpe_svc_result cpe_tgt_tomtom_set_debug_mode(u32 enable) |
| { |
| enum cpe_svc_result rc = CPE_SVC_SUCCESS; |
| u8 dbg_reg_val = 0x00; |
| if (enable) |
| dbg_reg_val = 0x08; |
| rc = cpe_update_bits(TOMTOM_A_SVASS_DEBUG, |
| 0x08, dbg_reg_val); |
| return rc; |
| } |
| |
| static const struct cpe_svc_hw_cfg *cpe_tgt_tomtom_get_cpe_info(void) |
| { |
| return &cpe_svc_tomtom_info; |
| } |
| |
| static enum cpe_svc_result cpe_tgt_tomtom_deinit( |
| struct cpe_svc_tgt_abstraction *param) |
| { |
| kfree(param->inbox); |
| param->inbox = NULL; |
| kfree(param->outbox); |
| param->outbox = NULL; |
| memset(param, 0, sizeof(struct cpe_svc_tgt_abstraction)); |
| return CPE_SVC_SUCCESS; |
| } |
| |
| static enum cpe_svc_result cpe_tgt_tomtom_init( |
| struct cpe_svc_codec_info_v1 *codec_info, |
| struct cpe_svc_tgt_abstraction *param) |
| { |
| if (!codec_info) |
| return CPE_SVC_INVALID_HANDLE; |
| if (!param) |
| return CPE_SVC_INVALID_HANDLE; |
| |
| if (codec_info->id == CPE_SVC_CODEC_TOMTOM) { |
| param->tgt_boot = cpe_tgt_tomtom_boot; |
| param->tgt_cpar_init_done = cpe_tgt_tomtom_is_cpar_init_done; |
| param->tgt_is_active = cpe_tgt_tomtom_is_active; |
| param->tgt_reset = cpe_tgt_tomtom_reset; |
| param->tgt_read_mailbox = cpe_tgt_tomtom_read_mailbox; |
| param->tgt_write_mailbox = cpe_tgt_tomtom_write_mailbox; |
| param->tgt_read_ram = cpe_tgt_tomtom_read_RAM; |
| param->tgt_write_ram = cpe_tgt_tomtom_write_RAM; |
| param->tgt_route_notification = |
| cpe_tgt_tomtom_route_notification; |
| param->tgt_set_debug_mode = cpe_tgt_tomtom_set_debug_mode; |
| param->tgt_get_cpe_info = cpe_tgt_tomtom_get_cpe_info; |
| param->tgt_deinit = cpe_tgt_tomtom_deinit; |
| param->tgt_voice_tx_lab = cpe_tgt_tomtom_voicetx; |
| |
| param->inbox = kzalloc(TOMTOM_A_SVASS_SPE_INBOX_SIZE, |
| GFP_KERNEL); |
| if (!param->inbox) { |
| pr_err("%s: no memory for inbox, sz = %d\n", |
| __func__, TOMTOM_A_SVASS_SPE_INBOX_SIZE); |
| return CPE_SVC_NO_MEMORY; |
| } |
| |
| param->outbox = kzalloc(TOMTOM_A_SVASS_SPE_OUTBOX_SIZE, |
| GFP_KERNEL); |
| if (!param->outbox) { |
| kfree(param->inbox); |
| pr_err("%s: no memory for inbox, sz = %d\n", |
| __func__, TOMTOM_A_SVASS_SPE_OUTBOX_SIZE); |
| return CPE_SVC_NO_MEMORY; |
| } |
| } |
| |
| return CPE_SVC_SUCCESS; |
| } |
| |
| MODULE_DESCRIPTION("WCD CPE Services"); |
| MODULE_LICENSE("GPL v2"); |