/* arch/arm/mach-msm/qdsp5/audmgr.c * * interface to "audmgr" service on the baseband cpu * * Copyright (C) 2008 Google, Inc. * Copyright (c) 2009, 2012, 2013 The Linux Foundation. All rights reserved. * * 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 #include #include #include #include #include #include #include #include "audmgr.h" #include #define STATE_CLOSED 0 #define STATE_DISABLED 1 #define STATE_ENABLING 2 #define STATE_ENABLED 3 #define STATE_DISABLING 4 #define STATE_ERROR 5 #define MAX_DEVICE_INFO_CALLBACK 1 #define SESSION_VOICE 0 #define SESSION_PLAYBACK 1 #define SESSION_RECORDING 2 /* store information used across complete audmgr sessions */ struct audmgr_global { struct mutex *lock; struct msm_rpc_endpoint *ept; struct task_struct *task; uint32_t rpc_version; uint32_t rx_device; uint32_t tx_device; int cad; struct device_info_callback *device_cb[MAX_DEVICE_INFO_CALLBACK]; }; static DEFINE_MUTEX(audmgr_lock); static struct audmgr_global the_audmgr_state = { .lock = &audmgr_lock, }; static void audmgr_rpc_connect(struct audmgr_global *amg) { amg->cad = 0; amg->ept = msm_rpc_connect_compatible(AUDMGR_PROG, AUDMGR_VERS_COMP_VER3, MSM_RPC_UNINTERRUPTIBLE); if (IS_ERR(amg->ept)) { MM_DBG("connect failed with current VERS"\ "= %x, trying again with Cad API\n", AUDMGR_VERS_COMP_VER3); amg->ept = msm_rpc_connect_compatible(AUDMGR_PROG, AUDMGR_VERS_COMP_VER4, MSM_RPC_UNINTERRUPTIBLE); if (IS_ERR(amg->ept)) { amg->ept = msm_rpc_connect_compatible(AUDMGR_PROG, AUDMGR_VERS_COMP_VER2, MSM_RPC_UNINTERRUPTIBLE); if (IS_ERR(amg->ept)) { MM_ERR("connect failed with current VERS" \ "= %x, trying again with another API\n", AUDMGR_VERS_COMP_VER2); amg->ept = msm_rpc_connect_compatible( AUDMGR_PROG, AUDMGR_VERS_COMP, MSM_RPC_UNINTERRUPTIBLE); if (IS_ERR(amg->ept)) { MM_ERR("connect failed with current" \ "VERS=%x, trying again with" \ "another API\n", AUDMGR_VERS_COMP); amg->ept = msm_rpc_connect(AUDMGR_PROG, AUDMGR_VERS, MSM_RPC_UNINTERRUPTIBLE); amg->rpc_version = AUDMGR_VERS; } else amg->rpc_version = AUDMGR_VERS_COMP; } else amg->rpc_version = AUDMGR_VERS_COMP_VER2; } else { amg->rpc_version = AUDMGR_VERS_COMP_VER4; amg->cad = 1; } } else amg->rpc_version = AUDMGR_VERS_COMP_VER3; if (IS_ERR(amg->ept)) { amg->ept = NULL; MM_ERR("failed to connect to audmgr svc\n"); } return; } static void rpc_ack(struct msm_rpc_endpoint *ept, uint32_t xid) { uint32_t rep[6]; rep[0] = cpu_to_be32(xid); rep[1] = cpu_to_be32(1); rep[2] = cpu_to_be32(RPCMSG_REPLYSTAT_ACCEPTED); rep[3] = cpu_to_be32(RPC_ACCEPTSTAT_SUCCESS); rep[4] = 0; rep[5] = 0; msm_rpc_write(ept, rep, sizeof(rep)); } static void process_audmgr_callback(struct audmgr_global *amg, void *args, int len) { struct audmgr *am; int i = 0; struct rpc_audmgr_cb_device_info *temp; /* Allow only if complete arguments recevied*/ if (len < MIN_RPC_DATA_LENGTH) return; /* Allow only if valid argument */ if (be32_to_cpu(((struct rpc_audmgr_cb_common *)args)->set_to_one) != 1) return; switch (be32_to_cpu(((struct rpc_audmgr_cb_common *)args)->status)) { case RPC_AUDMGR_STATUS_READY: am = (struct audmgr *) be32_to_cpu( ((struct rpc_audmgr_cb_ready *)args)->client_data); if (!am) return; am->handle = be32_to_cpu( ((struct rpc_audmgr_cb_ready *)args)->u.handle); MM_INFO("rpc READY handle=0x%08x\n", am->handle); break; case RPC_AUDMGR_STATUS_CODEC_CONFIG: { MM_INFO("rpc CODEC_CONFIG\n"); am = (struct audmgr *) be32_to_cpu( ((struct rpc_audmgr_cb_ready *)args)->client_data); if (!am) return; if (am->state != STATE_ENABLED) am->state = STATE_ENABLED; if (!amg->cad) { wake_up(&am->wait); break; } if (am->evt.session_info == SESSION_PLAYBACK && am->evt.dev_type.rx_device != amg->rx_device) { am->evt.dev_type.rx_device = amg->rx_device; am->evt.dev_type.tx_device = 0; am->evt.acdb_id = am->evt.dev_type.rx_device; } if (am->evt.session_info == SESSION_RECORDING && am->evt.dev_type.tx_device != amg->tx_device) { am->evt.dev_type.rx_device = 0; am->evt.dev_type.tx_device = amg->tx_device; am->evt.acdb_id = am->evt.dev_type.tx_device; } while ((amg->device_cb[i] != NULL) && (i < MAX_DEVICE_INFO_CALLBACK) && (amg->cad)) { amg->device_cb[i]->func(&(am->evt), amg->device_cb[i]->private); i++; } wake_up(&am->wait); break; } case RPC_AUDMGR_STATUS_PENDING: MM_ERR("PENDING?\n"); break; case RPC_AUDMGR_STATUS_SUSPEND: MM_ERR("SUSPEND?\n"); break; case RPC_AUDMGR_STATUS_FAILURE: MM_ERR("FAILURE\n"); break; case RPC_AUDMGR_STATUS_VOLUME_CHANGE: MM_ERR("VOLUME_CHANGE?\n"); break; case RPC_AUDMGR_STATUS_DISABLED: MM_ERR("DISABLED\n"); am = (struct audmgr *) be32_to_cpu( ((struct rpc_audmgr_cb_ready *)args)->client_data); if (!am) return; am->state = STATE_DISABLED; wake_up(&am->wait); break; case RPC_AUDMGR_STATUS_ERROR: MM_ERR("ERROR?\n"); am = (struct audmgr *) be32_to_cpu( ((struct rpc_audmgr_cb_ready *)args)->client_data); if (!am) return; am->state = STATE_ERROR; wake_up(&am->wait); break; case RPC_AUDMGR_STATUS_DEVICE_INFO: MM_INFO("rpc DEVICE_INFO\n"); if (!amg->cad) break; temp = (struct rpc_audmgr_cb_device_info *)args; am = (struct audmgr *) be32_to_cpu(temp->client_data); if (!am) return; if (am->evt.session_info == SESSION_PLAYBACK) { am->evt.dev_type.rx_device = be32_to_cpu(temp->d.rx_device); am->evt.dev_type.tx_device = 0; am->evt.acdb_id = am->evt.dev_type.rx_device; amg->rx_device = am->evt.dev_type.rx_device; } else if (am->evt.session_info == SESSION_RECORDING) { am->evt.dev_type.rx_device = 0; am->evt.dev_type.tx_device = be32_to_cpu(temp->d.tx_device); am->evt.acdb_id = am->evt.dev_type.tx_device; amg->tx_device = am->evt.dev_type.tx_device; } am->evt.dev_type.ear_mute = be32_to_cpu(temp->d.ear_mute); am->evt.dev_type.mic_mute = be32_to_cpu(temp->d.mic_mute); am->evt.dev_type.volume = be32_to_cpu(temp->d.volume); break; case RPC_AUDMGR_STATUS_DEVICE_CONFIG: MM_ERR("rpc DEVICE_CONFIG\n"); break; default: break; } } static void process_rpc_request(uint32_t proc, uint32_t xid, void *data, int len, void *private) { struct audmgr_global *amg = private; if (proc == AUDMGR_CB_FUNC_PTR) process_audmgr_callback(amg, data, len); else MM_ERR("unknown rpc proc %d\n", proc); rpc_ack(amg->ept, xid); } #define RPC_TYPE_REQUEST 0 #define RPC_TYPE_REPLY 1 #define RPC_VERSION 2 #define RPC_COMMON_HDR_SZ (sizeof(uint32_t) * 2) #define RPC_REQUEST_HDR_SZ (sizeof(struct rpc_request_hdr)) #define RPC_REPLY_HDR_SZ (sizeof(uint32_t) * 3) #define RPC_REPLY_SZ (sizeof(uint32_t) * 6) static int audmgr_rpc_thread(void *data) { struct audmgr_global *amg = data; struct rpc_request_hdr *hdr = NULL; uint32_t type; int len; MM_INFO("start\n"); while (!kthread_should_stop()) { if (hdr) { kfree(hdr); hdr = NULL; } len = msm_rpc_read(amg->ept, (void **) &hdr, -1, -1); if (len < 0) { MM_ERR("rpc read failed (%d)\n", len); break; } if (len < RPC_COMMON_HDR_SZ) continue; type = be32_to_cpu(hdr->type); if (type == RPC_TYPE_REPLY) { struct rpc_reply_hdr *rep = (void *) hdr; uint32_t status; if (len < RPC_REPLY_HDR_SZ) continue; status = be32_to_cpu(rep->reply_stat); if (status == RPCMSG_REPLYSTAT_ACCEPTED) { status = be32_to_cpu(rep->data.acc_hdr.accept_stat); MM_INFO("rpc_reply status %d\n", status); } else { MM_INFO("rpc_reply denied!\n"); } /* process reply */ continue; } if (len < RPC_REQUEST_HDR_SZ) continue; process_rpc_request(be32_to_cpu(hdr->procedure), be32_to_cpu(hdr->xid), (void *) (hdr + 1), len - sizeof(*hdr), data); } MM_INFO("exit\n"); if (hdr) { kfree(hdr); hdr = NULL; } amg->task = NULL; return 0; } static unsigned convert_samp_index(unsigned index) { switch (index) { case RPC_AUD_DEF_SAMPLE_RATE_48000: return 48000; case RPC_AUD_DEF_SAMPLE_RATE_44100: return 44100; case RPC_AUD_DEF_SAMPLE_RATE_32000: return 32000; case RPC_AUD_DEF_SAMPLE_RATE_24000: return 24000; case RPC_AUD_DEF_SAMPLE_RATE_22050: return 22050; case RPC_AUD_DEF_SAMPLE_RATE_16000: return 16000; case RPC_AUD_DEF_SAMPLE_RATE_12000: return 12000; case RPC_AUD_DEF_SAMPLE_RATE_11025: return 11025; case RPC_AUD_DEF_SAMPLE_RATE_8000: return 8000; default: return 11025; } } static void get_current_session_info(struct audmgr *am, struct audmgr_config *cfg) { if (cfg->def_method == RPC_AUD_DEF_METHOD_PLAYBACK || (cfg->def_method == RPC_AUD_DEF_METHOD_HOST_PCM && cfg->rx_rate)) { am->evt.session_info = SESSION_PLAYBACK; /* playback */ am->evt.sample_rate = convert_samp_index(cfg->rx_rate); } else if (cfg->def_method == RPC_AUD_DEF_METHOD_RECORD) { am->evt.session_info = SESSION_RECORDING; /* recording */ am->evt.sample_rate = convert_samp_index(cfg->tx_rate); } else am->evt.session_info = SESSION_VOICE; } struct audmgr_enable_msg { struct rpc_request_hdr hdr; struct rpc_audmgr_enable_client_args args; }; struct audmgr_disable_msg { struct rpc_request_hdr hdr; uint32_t handle; }; int audmgr_open(struct audmgr *am) { struct audmgr_global *amg = &the_audmgr_state; int rc; if (am->state != STATE_CLOSED) return 0; mutex_lock(amg->lock); /* connect to audmgr end point and polling thread only once */ if (amg->ept == NULL) { audmgr_rpc_connect(amg); if (IS_ERR(amg->ept)) { rc = PTR_ERR(amg->ept); amg->ept = NULL; MM_ERR("failed to connect to audmgr svc\n"); goto done; } amg->task = kthread_run(audmgr_rpc_thread, amg, "audmgr_rpc"); if (IS_ERR(amg->task)) { rc = PTR_ERR(amg->task); amg->task = NULL; msm_rpc_close(amg->ept); amg->ept = NULL; goto done; } } /* Initialize session parameters */ init_waitqueue_head(&am->wait); am->state = STATE_DISABLED; rc = 0; done: mutex_unlock(amg->lock); return rc; } EXPORT_SYMBOL(audmgr_open); int audmgr_close(struct audmgr *am) { return -EBUSY; } EXPORT_SYMBOL(audmgr_close); int audmgr_enable(struct audmgr *am, struct audmgr_config *cfg) { struct audmgr_global *amg = &the_audmgr_state; struct audmgr_enable_msg msg; int rc; if (am->state == STATE_ENABLED) return 0; if (am->state == STATE_DISABLING) MM_ERR("state is DISABLING in enable?\n"); am->state = STATE_ENABLING; MM_INFO("session 0x%08x\n", (int) am); msg.args.set_to_one = cpu_to_be32(1); msg.args.tx_sample_rate = cpu_to_be32(cfg->tx_rate); msg.args.rx_sample_rate = cpu_to_be32(cfg->rx_rate); msg.args.def_method = cpu_to_be32(cfg->def_method); msg.args.codec_type = cpu_to_be32(cfg->codec); msg.args.snd_method = cpu_to_be32(cfg->snd_method); msg.args.cb_func = cpu_to_be32(0x11111111); msg.args.client_data = cpu_to_be32((int)am); get_current_session_info(am, cfg); msm_rpc_setup_req(&msg.hdr, AUDMGR_PROG, amg->rpc_version, AUDMGR_ENABLE_CLIENT); rc = msm_rpc_write(amg->ept, &msg, sizeof(msg)); if (rc < 0) return rc; rc = wait_event_timeout(am->wait, am->state != STATE_ENABLING, 15 * HZ); if (rc == 0) { MM_ERR("ARM9 did not reply to RPC am->state = %d\n", am->state); } if (am->state == STATE_ENABLED) return 0; am->evt.session_info = -1; MM_ERR("unexpected state %d while enabling?!\n", am->state); return -ENODEV; } EXPORT_SYMBOL(audmgr_enable); int audmgr_disable(struct audmgr *am) { struct audmgr_global *amg = &the_audmgr_state; struct audmgr_disable_msg msg; int rc; if (am->state == STATE_DISABLED) return 0; MM_INFO("session 0x%08x\n", (int) am); am->evt.session_info = -1; msg.handle = cpu_to_be32(am->handle); msm_rpc_setup_req(&msg.hdr, AUDMGR_PROG, amg->rpc_version, AUDMGR_DISABLE_CLIENT); am->state = STATE_DISABLING; rc = msm_rpc_write(amg->ept, &msg, sizeof(msg)); if (rc < 0) return rc; rc = wait_event_timeout(am->wait, am->state != STATE_DISABLING, 15 * HZ); if (rc == 0) { MM_ERR("ARM9 did not reply to RPC am->state = %d\n", am->state); } if (am->state == STATE_DISABLED) return 0; MM_ERR("unexpected state %d while disabling?!\n", am->state); return -ENODEV; } EXPORT_SYMBOL(audmgr_disable); int audmgr_register_device_info_callback(struct device_info_callback *dcb) { struct audmgr_global *amg = &the_audmgr_state; int i; for (i = 0; i < MAX_DEVICE_INFO_CALLBACK; i++) { if (NULL == amg->device_cb[i]) { amg->device_cb[i] = dcb; return 0; } } return -EINVAL; } EXPORT_SYMBOL(audmgr_register_device_info_callback); int audmgr_deregister_device_info_callback(struct device_info_callback *dcb) { struct audmgr_global *amg = &the_audmgr_state; int i; for (i = 0; i < MAX_DEVICE_INFO_CALLBACK; i++) { if (dcb == amg->device_cb[i]) { amg->device_cb[i] = NULL; return 0; } } return -EINVAL; } EXPORT_SYMBOL(audmgr_deregister_device_info_callback);