/* Copyright (c) 2010-2012, 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef MAX #define MAX(x, y) (((x) > (y)) ? (x) : (y)) #endif static DEFINE_MUTEX(session_lock); static struct workqueue_struct *msm_reset_device_work_queue; static void reset_device_work(struct work_struct *work); static DECLARE_WORK(msm_reset_device_work, reset_device_work); struct audio_dev_ctrl_state { struct msm_snddev_info *devs[AUDIO_DEV_CTL_MAX_DEV]; u32 num_dev; atomic_t opened; struct msm_snddev_info *voice_rx_dev; struct msm_snddev_info *voice_tx_dev; wait_queue_head_t wait; }; static struct audio_dev_ctrl_state audio_dev_ctrl; struct event_listner event; struct session_freq { int freq; int evt; }; struct audio_routing_info { unsigned short mixer_mask[MAX_SESSIONS]; unsigned short audrec_mixer_mask[MAX_SESSIONS]; struct session_freq dec_freq[MAX_SESSIONS]; struct session_freq enc_freq[MAX_SESSIONS]; unsigned int copp_list[MAX_SESSIONS][AFE_MAX_PORTS]; int voice_tx_dev_id; int voice_rx_dev_id; int voice_tx_sample_rate; int voice_rx_sample_rate; signed int voice_tx_vol; signed int voice_rx_vol; int tx_mute; int rx_mute; int voice_state; struct mutex copp_list_mutex; struct mutex adm_mutex; }; static struct audio_routing_info routing_info; struct audio_copp_topology { struct mutex lock; int session_cnt; int session_id[MAX_SESSIONS]; int topolog_id[MAX_SESSIONS]; }; static struct audio_copp_topology adm_tx_topology_tbl; int msm_reset_all_device(void) { int rc = 0; int dev_id = 0; struct msm_snddev_info *dev_info = NULL; for (dev_id = 0; dev_id < audio_dev_ctrl.num_dev; dev_id++) { dev_info = audio_dev_ctrl_find_dev(dev_id); if (IS_ERR(dev_info)) { pr_err("%s:pass invalid dev_id\n", __func__); rc = PTR_ERR(dev_info); return rc; } if (!dev_info->opened) continue; pr_debug("%s:Resetting device %d active on COPP %d" "with %lld as routing\n", __func__, dev_id, dev_info->copp_id, dev_info->sessions); broadcast_event(AUDDEV_EVT_REL_PENDING, dev_id, SESSION_IGNORE); rc = dev_info->dev_ops.close(dev_info); if (rc < 0) { pr_err("%s:Snd device failed close!\n", __func__); return rc; } else { dev_info->opened = 0; broadcast_event(AUDDEV_EVT_DEV_RLS, dev_id, SESSION_IGNORE); if (dev_info->copp_id == VOICE_PLAYBACK_TX) voice_start_playback(0); } dev_info->sessions = 0; } msm_clear_all_session(); return 0; } EXPORT_SYMBOL(msm_reset_all_device); static void reset_device_work(struct work_struct *work) { msm_reset_all_device(); } int reset_device(void) { queue_work(msm_reset_device_work_queue, &msm_reset_device_work); return 0; } EXPORT_SYMBOL(reset_device); int msm_set_copp_id(int session_id, int copp_id) { int rc = 0; int index; if (session_id < 1 || session_id > 8) return -EINVAL; if (afe_validate_port(copp_id) < 0) return -EINVAL; index = afe_get_port_index(copp_id); if (index < 0 || index > AFE_MAX_PORTS) return -EINVAL; pr_debug("%s: session[%d] copp_id[%d] index[%d]\n", __func__, session_id, copp_id, index); mutex_lock(&routing_info.copp_list_mutex); if (routing_info.copp_list[session_id][index] == COPP_IGNORE) routing_info.copp_list[session_id][index] = copp_id; mutex_unlock(&routing_info.copp_list_mutex); return rc; } EXPORT_SYMBOL(msm_set_copp_id); int msm_clear_copp_id(int session_id, int copp_id) { int rc = 0; int index = afe_get_port_index(copp_id); if (session_id < 1 || session_id > 8) return -EINVAL; pr_debug("%s: session[%d] copp_id[%d] index[%d]\n", __func__, session_id, copp_id, index); mutex_lock(&routing_info.copp_list_mutex); if (routing_info.copp_list[session_id][index] == copp_id) routing_info.copp_list[session_id][index] = COPP_IGNORE; #ifdef CONFIG_MSM8X60_RTAC rtac_remove_adm_device(copp_id, session_id); #endif mutex_unlock(&routing_info.copp_list_mutex); return rc; } EXPORT_SYMBOL(msm_clear_copp_id); int msm_clear_session_id(int session_id) { int rc = 0; int i = 0; if (session_id < 1 || session_id > 8) return -EINVAL; pr_debug("%s: session[%d]\n", __func__, session_id); mutex_lock(&routing_info.adm_mutex); mutex_lock(&routing_info.copp_list_mutex); for (i = 0; i < AFE_MAX_PORTS; i++) { if (routing_info.copp_list[session_id][i] != COPP_IGNORE) { rc = adm_close(routing_info.copp_list[session_id][i]); if (rc < 0) { pr_err("%s: adm close fail port[%d] rc[%d]\n", __func__, routing_info.copp_list[session_id][i], rc); continue; } #ifdef CONFIG_MSM8X60_RTAC rtac_remove_adm_device( routing_info.copp_list[session_id][i], session_id); #endif routing_info.copp_list[session_id][i] = COPP_IGNORE; rc = 0; } } mutex_unlock(&routing_info.copp_list_mutex); mutex_unlock(&routing_info.adm_mutex); return rc; } EXPORT_SYMBOL(msm_clear_session_id); int msm_clear_all_session() { int rc = 0; int i = 0, j = 0; pr_info("%s:\n", __func__); mutex_lock(&routing_info.adm_mutex); mutex_lock(&routing_info.copp_list_mutex); for (j = 1; j < MAX_SESSIONS; j++) { for (i = 0; i < AFE_MAX_PORTS; i++) { if (routing_info.copp_list[j][i] != COPP_IGNORE) { rc = adm_close( routing_info.copp_list[j][i]); if (rc < 0) { pr_err("%s: adm close fail copp[%d]" "session[%d] rc[%d]\n", __func__, routing_info.copp_list[j][i], j, rc); continue; } routing_info.copp_list[j][i] = COPP_IGNORE; rc = 0; } } } mutex_unlock(&routing_info.copp_list_mutex); mutex_unlock(&routing_info.adm_mutex); return rc; } EXPORT_SYMBOL(msm_clear_all_session); int msm_get_voice_state(void) { pr_debug("voice state %d\n", routing_info.voice_state); return routing_info.voice_state; } EXPORT_SYMBOL(msm_get_voice_state); int msm_set_voice_mute(int dir, int mute, u32 session_id) { pr_debug("dir %x mute %x\n", dir, mute); if (dir == DIR_TX) { routing_info.tx_mute = mute; broadcast_event(AUDDEV_EVT_DEVICE_VOL_MUTE_CHG, routing_info.voice_tx_dev_id, session_id); } else return -EPERM; return 0; } EXPORT_SYMBOL(msm_set_voice_mute); int msm_set_voice_vol(int dir, s32 volume, u32 session_id) { if (dir == DIR_TX) { routing_info.voice_tx_vol = volume; broadcast_event(AUDDEV_EVT_DEVICE_VOL_MUTE_CHG, routing_info.voice_tx_dev_id, session_id); } else if (dir == DIR_RX) { routing_info.voice_rx_vol = volume; broadcast_event(AUDDEV_EVT_DEVICE_VOL_MUTE_CHG, routing_info.voice_rx_dev_id, session_id); } else return -EINVAL; return 0; } EXPORT_SYMBOL(msm_set_voice_vol); void msm_snddev_register(struct msm_snddev_info *dev_info) { mutex_lock(&session_lock); if (audio_dev_ctrl.num_dev < AUDIO_DEV_CTL_MAX_DEV) { audio_dev_ctrl.devs[audio_dev_ctrl.num_dev] = dev_info; /* roughly 0 DB for digital gain * If default gain is not desirable, it is expected that * application sets desired gain before activating sound * device */ dev_info->dev_volume = 75; dev_info->sessions = 0x0; dev_info->usage_count = 0; audio_dev_ctrl.num_dev++; } else pr_err("%s: device registry max out\n", __func__); mutex_unlock(&session_lock); } EXPORT_SYMBOL(msm_snddev_register); int msm_snddev_devcount(void) { return audio_dev_ctrl.num_dev; } EXPORT_SYMBOL(msm_snddev_devcount); int msm_snddev_query(int dev_id) { if (dev_id <= audio_dev_ctrl.num_dev) return 0; return -ENODEV; } EXPORT_SYMBOL(msm_snddev_query); int msm_snddev_is_set(int popp_id, int copp_id) { return routing_info.mixer_mask[popp_id] & (0x1 << copp_id); } EXPORT_SYMBOL(msm_snddev_is_set); unsigned short msm_snddev_route_enc(int enc_id) { if (enc_id >= MAX_SESSIONS) return -EINVAL; return routing_info.audrec_mixer_mask[enc_id]; } EXPORT_SYMBOL(msm_snddev_route_enc); unsigned short msm_snddev_route_dec(int popp_id) { if (popp_id >= MAX_SESSIONS) return -EINVAL; return routing_info.mixer_mask[popp_id]; } EXPORT_SYMBOL(msm_snddev_route_dec); /*To check one->many case*/ int msm_check_multicopp_per_stream(int session_id, struct route_payload *payload) { int i = 0; int flag = 0; pr_debug("%s: session_id=%d\n", __func__, session_id); mutex_lock(&routing_info.copp_list_mutex); for (i = 0; i < AFE_MAX_PORTS; i++) { if (routing_info.copp_list[session_id][i] == COPP_IGNORE) continue; else { pr_debug("Device enabled\n"); payload->copp_ids[flag++] = routing_info.copp_list[session_id][i]; } } mutex_unlock(&routing_info.copp_list_mutex); if (flag > 1) { pr_debug("Multiple copp per stream case num_copps=%d\n", flag); } else { pr_debug("Stream routed to single copp\n"); } payload->num_copps = flag; return flag; } int msm_snddev_set_dec(int popp_id, int copp_id, int set, int rate, int mode) { int rc = 0, i = 0, num_copps; struct route_payload payload; if ((popp_id >= MAX_SESSIONS) || (popp_id <= 0)) { pr_err("%s: Invalid session id %d\n", __func__, popp_id); return 0; } mutex_lock(&routing_info.adm_mutex); if (set) { rc = adm_open(copp_id, ADM_PATH_PLAYBACK, rate, mode, DEFAULT_COPP_TOPOLOGY); if (rc < 0) { pr_err("%s: adm open fail rc[%d]\n", __func__, rc); rc = -EINVAL; mutex_unlock(&routing_info.adm_mutex); return rc; } msm_set_copp_id(popp_id, copp_id); pr_debug("%s:Session id=%d copp_id=%d\n", __func__, popp_id, copp_id); memset(payload.copp_ids, COPP_IGNORE, (sizeof(unsigned int) * AFE_MAX_PORTS)); num_copps = msm_check_multicopp_per_stream(popp_id, &payload); /* Multiple streams per copp is handled, one stream at a time */ rc = adm_matrix_map(popp_id, ADM_PATH_PLAYBACK, num_copps, payload.copp_ids, copp_id); if (rc < 0) { pr_err("%s: matrix map failed rc[%d]\n", __func__, rc); adm_close(copp_id); rc = -EINVAL; mutex_unlock(&routing_info.adm_mutex); return rc; } #ifdef CONFIG_MSM8X60_RTAC for (i = 0; i < num_copps; i++) rtac_add_adm_device(payload.copp_ids[i], popp_id); #endif } else { for (i = 0; i < AFE_MAX_PORTS; i++) { if (routing_info.copp_list[popp_id][i] == copp_id) { rc = adm_close(copp_id); if (rc < 0) { pr_err("%s: adm close fail copp[%d]" "rc[%d]\n", __func__, copp_id, rc); rc = -EINVAL; mutex_unlock(&routing_info.adm_mutex); return rc; } msm_clear_copp_id(popp_id, copp_id); break; } } } if (copp_id == VOICE_PLAYBACK_TX) { /* Signal uplink playback. */ rc = voice_start_playback(set); } mutex_unlock(&routing_info.adm_mutex); return rc; } EXPORT_SYMBOL(msm_snddev_set_dec); static int check_tx_copp_topology(int session_id) { int cnt; int ret_val = -ENOENT; cnt = adm_tx_topology_tbl.session_cnt; if (cnt) { do { if (adm_tx_topology_tbl.session_id[cnt-1] == session_id) ret_val = cnt-1; } while (--cnt); } return ret_val; } static int add_to_tx_topology_lists(int session_id, int topology) { int idx = 0, tbl_idx; int ret_val = -ENOSPC; mutex_lock(&adm_tx_topology_tbl.lock); tbl_idx = check_tx_copp_topology(session_id); if (tbl_idx == -ENOENT) { while (adm_tx_topology_tbl.session_id[idx++]) ; tbl_idx = idx-1; } if (tbl_idx < MAX_SESSIONS) { adm_tx_topology_tbl.session_id[tbl_idx] = session_id; adm_tx_topology_tbl.topolog_id[tbl_idx] = topology; adm_tx_topology_tbl.session_cnt++; ret_val = 0; } mutex_unlock(&adm_tx_topology_tbl.lock); return ret_val; } static void remove_from_tx_topology_lists(int session_id) { int tbl_idx; mutex_lock(&adm_tx_topology_tbl.lock); tbl_idx = check_tx_copp_topology(session_id); if (tbl_idx != -ENOENT) { adm_tx_topology_tbl.session_cnt--; adm_tx_topology_tbl.session_id[tbl_idx] = 0; adm_tx_topology_tbl.topolog_id[tbl_idx] = 0; } mutex_unlock(&adm_tx_topology_tbl.lock); } int auddev_cfg_tx_copp_topology(int session_id, int cfg) { int ret = 0; if (cfg == DEFAULT_COPP_TOPOLOGY) remove_from_tx_topology_lists(session_id); else { switch (cfg) { case VPM_TX_SM_ECNS_COPP_TOPOLOGY: case VPM_TX_DM_FLUENCE_COPP_TOPOLOGY: ret = add_to_tx_topology_lists(session_id, cfg); break; default: ret = -ENODEV; break; } } return ret; } int msm_snddev_set_enc(int popp_id, int copp_id, int set, int rate, int mode) { int topology; int tbl_idx; int rc = 0, i = 0; mutex_lock(&routing_info.adm_mutex); if (set) { mutex_lock(&adm_tx_topology_tbl.lock); tbl_idx = check_tx_copp_topology(popp_id); if (tbl_idx == -ENOENT) topology = DEFAULT_COPP_TOPOLOGY; else { topology = adm_tx_topology_tbl.topolog_id[tbl_idx]; rate = 16000; } mutex_unlock(&adm_tx_topology_tbl.lock); rc = adm_open(copp_id, ADM_PATH_LIVE_REC, rate, mode, topology); if (rc < 0) { pr_err("%s: adm open fail rc[%d]\n", __func__, rc); rc = -EINVAL; goto fail_cmd; } rc = adm_matrix_map(popp_id, ADM_PATH_LIVE_REC, 1, (unsigned int *)&copp_id, copp_id); if (rc < 0) { pr_err("%s: matrix map failed rc[%d]\n", __func__, rc); adm_close(copp_id); rc = -EINVAL; goto fail_cmd; } msm_set_copp_id(popp_id, copp_id); #ifdef CONFIG_MSM8X60_RTAC rtac_add_adm_device(copp_id, popp_id); #endif } else { for (i = 0; i < AFE_MAX_PORTS; i++) { if (routing_info.copp_list[popp_id][i] == copp_id) { rc = adm_close(copp_id); if (rc < 0) { pr_err("%s: adm close fail copp[%d]" "rc[%d]\n", __func__, copp_id, rc); rc = -EINVAL; goto fail_cmd; } msm_clear_copp_id(popp_id, copp_id); break; } } } fail_cmd: mutex_unlock(&routing_info.adm_mutex); return rc; } EXPORT_SYMBOL(msm_snddev_set_enc); int msm_device_is_voice(int dev_id) { if ((dev_id == routing_info.voice_rx_dev_id) || (dev_id == routing_info.voice_tx_dev_id)) return 0; else return -EINVAL; } EXPORT_SYMBOL(msm_device_is_voice); int msm_set_voc_route(struct msm_snddev_info *dev_info, int stream_type, int dev_id) { int rc = 0; u64 session_mask = 0; mutex_lock(&session_lock); switch (stream_type) { case AUDIO_ROUTE_STREAM_VOICE_RX: if (audio_dev_ctrl.voice_rx_dev) audio_dev_ctrl.voice_rx_dev->sessions &= ~0xFFFF; if (!(dev_info->capability & SNDDEV_CAP_RX) | !(dev_info->capability & SNDDEV_CAP_VOICE)) { rc = -EINVAL; break; } audio_dev_ctrl.voice_rx_dev = dev_info; if (audio_dev_ctrl.voice_rx_dev) { session_mask = ((u64)0x1) << (MAX_BIT_PER_CLIENT * \ ((int)AUDDEV_CLNT_VOC-1)); audio_dev_ctrl.voice_rx_dev->sessions |= session_mask; } routing_info.voice_rx_dev_id = dev_id; break; case AUDIO_ROUTE_STREAM_VOICE_TX: if (audio_dev_ctrl.voice_tx_dev) audio_dev_ctrl.voice_tx_dev->sessions &= ~0xFFFF; if (!(dev_info->capability & SNDDEV_CAP_TX) | !(dev_info->capability & SNDDEV_CAP_VOICE)) { rc = -EINVAL; break; } audio_dev_ctrl.voice_tx_dev = dev_info; if (audio_dev_ctrl.voice_rx_dev) { session_mask = ((u64)0x1) << (MAX_BIT_PER_CLIENT * \ ((int)AUDDEV_CLNT_VOC-1)); audio_dev_ctrl.voice_tx_dev->sessions |= session_mask; } routing_info.voice_tx_dev_id = dev_id; break; default: rc = -EINVAL; } mutex_unlock(&session_lock); return rc; } EXPORT_SYMBOL(msm_set_voc_route); void msm_release_voc_thread(void) { wake_up(&audio_dev_ctrl.wait); } EXPORT_SYMBOL(msm_release_voc_thread); int msm_snddev_get_enc_freq(session_id) { return routing_info.enc_freq[session_id].freq; } EXPORT_SYMBOL(msm_snddev_get_enc_freq); int msm_get_voc_freq(int *tx_freq, int *rx_freq) { *tx_freq = routing_info.voice_tx_sample_rate; *rx_freq = routing_info.voice_rx_sample_rate; return 0; } EXPORT_SYMBOL(msm_get_voc_freq); int msm_get_voc_route(u32 *rx_id, u32 *tx_id) { int rc = 0; if (!rx_id || !tx_id) return -EINVAL; mutex_lock(&session_lock); if (!audio_dev_ctrl.voice_rx_dev || !audio_dev_ctrl.voice_tx_dev) { rc = -ENODEV; mutex_unlock(&session_lock); return rc; } *rx_id = audio_dev_ctrl.voice_rx_dev->acdb_id; *tx_id = audio_dev_ctrl.voice_tx_dev->acdb_id; mutex_unlock(&session_lock); return rc; } EXPORT_SYMBOL(msm_get_voc_route); struct msm_snddev_info *audio_dev_ctrl_find_dev(u32 dev_id) { struct msm_snddev_info *info; if ((audio_dev_ctrl.num_dev - 1) < dev_id) { info = ERR_PTR(-ENODEV); goto error; } info = audio_dev_ctrl.devs[dev_id]; error: return info; } EXPORT_SYMBOL(audio_dev_ctrl_find_dev); int snddev_voice_set_volume(int vol, int path) { if (audio_dev_ctrl.voice_rx_dev && audio_dev_ctrl.voice_tx_dev) { if (path) audio_dev_ctrl.voice_tx_dev->dev_volume = vol; else audio_dev_ctrl.voice_rx_dev->dev_volume = vol; } else return -ENODEV; return 0; } EXPORT_SYMBOL(snddev_voice_set_volume); static int audio_dev_ctrl_get_devices(struct audio_dev_ctrl_state *dev_ctrl, void __user *arg) { int rc = 0; u32 index; struct msm_snd_device_list work_list; struct msm_snd_device_info *work_tbl; if (copy_from_user(&work_list, arg, sizeof(work_list))) { rc = -EFAULT; goto error; } if (work_list.num_dev > dev_ctrl->num_dev) { rc = -EINVAL; goto error; } work_tbl = kmalloc(work_list.num_dev * sizeof(struct msm_snd_device_info), GFP_KERNEL); if (!work_tbl) { rc = -ENOMEM; goto error; } for (index = 0; index < dev_ctrl->num_dev; index++) { work_tbl[index].dev_id = index; work_tbl[index].dev_cap = dev_ctrl->devs[index]->capability; strlcpy(work_tbl[index].dev_name, dev_ctrl->devs[index]->name, 64); } if (copy_to_user((void *) (work_list.list), work_tbl, work_list.num_dev * sizeof(struct msm_snd_device_info))) rc = -EFAULT; kfree(work_tbl); error: return rc; } int auddev_register_evt_listner(u32 evt_id, u32 clnt_type, u32 clnt_id, void (*listner)(u32 evt_id, union auddev_evt_data *evt_payload, void *private_data), void *private_data) { int rc; struct msm_snd_evt_listner *callback = NULL; struct msm_snd_evt_listner *new_cb; new_cb = kzalloc(sizeof(struct msm_snd_evt_listner), GFP_KERNEL); if (!new_cb) { pr_err("No memory to add new listener node\n"); return -ENOMEM; } mutex_lock(&session_lock); new_cb->cb_next = NULL; new_cb->auddev_evt_listener = listner; new_cb->evt_id = evt_id; new_cb->clnt_type = clnt_type; new_cb->clnt_id = clnt_id; new_cb->private_data = private_data; if (event.cb == NULL) { event.cb = new_cb; new_cb->cb_prev = NULL; } else { callback = event.cb; for (; ;) { if (callback->cb_next == NULL) break; else { callback = callback->cb_next; continue; } } callback->cb_next = new_cb; new_cb->cb_prev = callback; } event.num_listner++; mutex_unlock(&session_lock); rc = 0; return rc; } EXPORT_SYMBOL(auddev_register_evt_listner); int auddev_unregister_evt_listner(u32 clnt_type, u32 clnt_id) { struct msm_snd_evt_listner *callback = event.cb; struct msm_snddev_info *info; u64 session_mask = 0; int i = 0; mutex_lock(&session_lock); while (callback != NULL) { if ((callback->clnt_type == clnt_type) && (callback->clnt_id == clnt_id)) break; callback = callback->cb_next; } if (callback == NULL) { mutex_unlock(&session_lock); return -EINVAL; } if ((callback->cb_next == NULL) && (callback->cb_prev == NULL)) event.cb = NULL; else if (callback->cb_next == NULL) callback->cb_prev->cb_next = NULL; else if (callback->cb_prev == NULL) { callback->cb_next->cb_prev = NULL; event.cb = callback->cb_next; } else { callback->cb_prev->cb_next = callback->cb_next; callback->cb_next->cb_prev = callback->cb_prev; } kfree(callback); session_mask = (((u64)0x1) << clnt_id) << (MAX_BIT_PER_CLIENT * \ ((int)clnt_type-1)); for (i = 0; i < audio_dev_ctrl.num_dev; i++) { info = audio_dev_ctrl.devs[i]; info->sessions &= ~session_mask; } mutex_unlock(&session_lock); return 0; } EXPORT_SYMBOL(auddev_unregister_evt_listner); int msm_snddev_withdraw_freq(u32 session_id, u32 capability, u32 clnt_type) { int i = 0; struct msm_snddev_info *info; u64 session_mask = 0; if ((clnt_type == AUDDEV_CLNT_VOC) && (session_id != 0)) return -EINVAL; if ((clnt_type == AUDDEV_CLNT_DEC) && (session_id >= MAX_SESSIONS)) return -EINVAL; if ((clnt_type == AUDDEV_CLNT_ENC) && (session_id >= MAX_SESSIONS)) return -EINVAL; session_mask = (((u64)0x1) << session_id) << (MAX_BIT_PER_CLIENT * \ ((int)clnt_type-1)); for (i = 0; i < audio_dev_ctrl.num_dev; i++) { info = audio_dev_ctrl.devs[i]; if ((info->sessions & session_mask) && (info->capability & capability)) { if (!(info->sessions & ~(session_mask))) info->set_sample_rate = 0; } } if (clnt_type == AUDDEV_CLNT_DEC) routing_info.dec_freq[session_id].freq = 0; else if (clnt_type == AUDDEV_CLNT_ENC) routing_info.enc_freq[session_id].freq = 0; else if (capability == SNDDEV_CAP_TX) routing_info.voice_tx_sample_rate = 0; else routing_info.voice_rx_sample_rate = 48000; return 0; } int msm_snddev_request_freq(int *freq, u32 session_id, u32 capability, u32 clnt_type) { int i = 0; int rc = 0; struct msm_snddev_info *info; u32 set_freq; u64 session_mask = 0; u64 clnt_type_mask = 0; pr_debug(": clnt_type 0x%08x\n", clnt_type); if ((clnt_type == AUDDEV_CLNT_VOC) && (session_id != 0)) return -EINVAL; if ((clnt_type == AUDDEV_CLNT_DEC) && (session_id >= MAX_SESSIONS)) return -EINVAL; if ((clnt_type == AUDDEV_CLNT_ENC) && (session_id >= MAX_SESSIONS)) return -EINVAL; session_mask = (((u64)0x1) << session_id) << (MAX_BIT_PER_CLIENT * \ ((int)clnt_type-1)); clnt_type_mask = (0xFFFF << (MAX_BIT_PER_CLIENT * (clnt_type-1))); if (!(*freq == 8000) && !(*freq == 11025) && !(*freq == 12000) && !(*freq == 16000) && !(*freq == 22050) && !(*freq == 24000) && !(*freq == 32000) && !(*freq == 44100) && !(*freq == 48000)) return -EINVAL; for (i = 0; i < audio_dev_ctrl.num_dev; i++) { info = audio_dev_ctrl.devs[i]; if ((info->sessions & session_mask) && (info->capability & capability)) { rc = 0; if ((info->sessions & ~clnt_type_mask) && ((*freq != 8000) && (*freq != 16000) && (*freq != 48000))) { if (clnt_type == AUDDEV_CLNT_ENC) { routing_info.enc_freq[session_id].freq = 0; return -EPERM; } else if (clnt_type == AUDDEV_CLNT_DEC) { routing_info.dec_freq[session_id].freq = 0; return -EPERM; } } if (*freq == info->set_sample_rate) { rc = info->set_sample_rate; continue; } set_freq = MAX(*freq, info->set_sample_rate); if (clnt_type == AUDDEV_CLNT_DEC) { routing_info.dec_freq[session_id].evt = 1; routing_info.dec_freq[session_id].freq = set_freq; } else if (clnt_type == AUDDEV_CLNT_ENC) { routing_info.enc_freq[session_id].evt = 1; routing_info.enc_freq[session_id].freq = set_freq; } else if (capability == SNDDEV_CAP_TX) routing_info.voice_tx_sample_rate = set_freq; rc = set_freq; info->set_sample_rate = set_freq; *freq = info->set_sample_rate; if (info->opened) { broadcast_event(AUDDEV_EVT_FREQ_CHG, i, SESSION_IGNORE); set_freq = info->dev_ops.set_freq(info, set_freq); broadcast_event(AUDDEV_EVT_DEV_RDY, i, SESSION_IGNORE); } } pr_debug("info->set_sample_rate = %d\n", info->set_sample_rate); pr_debug("routing_info.enc_freq.freq = %d\n", routing_info.enc_freq[session_id].freq); } return rc; } EXPORT_SYMBOL(msm_snddev_request_freq); int msm_snddev_enable_sidetone(u32 dev_id, u32 enable, uint16_t gain) { int rc; struct msm_snddev_info *dev_info; pr_debug("dev_id %d enable %d\n", dev_id, enable); dev_info = audio_dev_ctrl_find_dev(dev_id); if (IS_ERR(dev_info)) { pr_err("bad dev_id %d\n", dev_id); rc = -EINVAL; } else if (!dev_info->dev_ops.enable_sidetone) { pr_debug("dev %d no sidetone support\n", dev_id); rc = -EPERM; } else rc = dev_info->dev_ops.enable_sidetone(dev_info, enable, gain); return rc; } EXPORT_SYMBOL(msm_snddev_enable_sidetone); int msm_enable_incall_recording(int popp_id, int rec_mode, int rate, int channel_mode) { int rc = 0; unsigned int port_id[2]; port_id[0] = VOICE_RECORD_TX; port_id[1] = VOICE_RECORD_RX; pr_debug("%s: popp_id %d, rec_mode %d, rate %d, channel_mode %d\n", __func__, popp_id, rec_mode, rate, channel_mode); mutex_lock(&routing_info.adm_mutex); if (rec_mode == VOC_REC_UPLINK) { rc = afe_start_pseudo_port(port_id[0]); if (rc < 0) { pr_err("%s: Error %d in Tx pseudo port start\n", __func__, rc); goto fail_cmd; } rc = adm_open(port_id[0], ADM_PATH_LIVE_REC, rate, channel_mode, DEFAULT_COPP_TOPOLOGY); if (rc < 0) { pr_err("%s: Error %d in ADM open %d\n", __func__, rc, port_id[0]); goto fail_cmd; } rc = adm_matrix_map(popp_id, ADM_PATH_LIVE_REC, 1, &port_id[0], port_id[0]); if (rc < 0) { pr_err("%s: Error %d in ADM matrix map %d\n", __func__, rc, port_id[0]); goto fail_cmd; } msm_set_copp_id(popp_id, port_id[0]); } else if (rec_mode == VOC_REC_DOWNLINK) { rc = afe_start_pseudo_port(port_id[1]); if (rc < 0) { pr_err("%s: Error %d in Rx pseudo port start\n", __func__, rc); goto fail_cmd; } rc = adm_open(port_id[1], ADM_PATH_LIVE_REC, rate, channel_mode, DEFAULT_COPP_TOPOLOGY); if (rc < 0) { pr_err("%s: Error %d in ADM open %d\n", __func__, rc, port_id[1]); goto fail_cmd; } rc = adm_matrix_map(popp_id, ADM_PATH_LIVE_REC, 1, &port_id[1], port_id[1]); if (rc < 0) { pr_err("%s: Error %d in ADM matrix map %d\n", __func__, rc, port_id[1]); goto fail_cmd; } msm_set_copp_id(popp_id, port_id[1]); } else if (rec_mode == VOC_REC_BOTH) { rc = afe_start_pseudo_port(port_id[0]); if (rc < 0) { pr_err("%s: Error %d in Tx pseudo port start\n", __func__, rc); goto fail_cmd; } rc = adm_open(port_id[0], ADM_PATH_LIVE_REC, rate, channel_mode, DEFAULT_COPP_TOPOLOGY); if (rc < 0) { pr_err("%s: Error %d in ADM open %d\n", __func__, rc, port_id[0]); goto fail_cmd; } msm_set_copp_id(popp_id, port_id[0]); rc = afe_start_pseudo_port(port_id[1]); if (rc < 0) { pr_err("%s: Error %d in Rx pseudo port start\n", __func__, rc); goto fail_cmd; } rc = adm_open(port_id[1], ADM_PATH_LIVE_REC, rate, channel_mode, DEFAULT_COPP_TOPOLOGY); if (rc < 0) { pr_err("%s: Error %d in ADM open %d\n", __func__, rc, port_id[0]); goto fail_cmd; } rc = adm_matrix_map(popp_id, ADM_PATH_LIVE_REC, 2, &port_id[0], port_id[1]); if (rc < 0) { pr_err("%s: Error %d in ADM matrix map\n", __func__, rc); goto fail_cmd; } msm_set_copp_id(popp_id, port_id[1]); } else { pr_err("%s Unknown rec_mode %d\n", __func__, rec_mode); goto fail_cmd; } rc = voice_start_record(rec_mode, 1); fail_cmd: mutex_unlock(&routing_info.adm_mutex); return rc; } int msm_disable_incall_recording(uint32_t popp_id, uint32_t rec_mode) { int rc = 0; uint32_t port_id[2]; port_id[0] = VOICE_RECORD_TX; port_id[1] = VOICE_RECORD_RX; pr_debug("%s: popp_id %d, rec_mode %d\n", __func__, popp_id, rec_mode); mutex_lock(&routing_info.adm_mutex); rc = voice_start_record(rec_mode, 0); if (rc < 0) { pr_err("%s: Error %d stopping record\n", __func__, rc); goto fail_cmd; } if (rec_mode == VOC_REC_UPLINK) { rc = adm_close(port_id[0]); if (rc < 0) { pr_err("%s: Error %d in ADM close %d\n", __func__, rc, port_id[0]); goto fail_cmd; } msm_clear_copp_id(popp_id, port_id[0]); rc = afe_stop_pseudo_port(port_id[0]); if (rc < 0) { pr_err("%s: Error %d in Tx pseudo port stop\n", __func__, rc); goto fail_cmd; } } else if (rec_mode == VOC_REC_DOWNLINK) { rc = adm_close(port_id[1]); if (rc < 0) { pr_err("%s: Error %d in ADM close %d\n", __func__, rc, port_id[1]); goto fail_cmd; } msm_clear_copp_id(popp_id, port_id[1]); rc = afe_stop_pseudo_port(port_id[1]); if (rc < 0) { pr_err("%s: Error %d in Rx pseudo port stop\n", __func__, rc); goto fail_cmd; } } else if (rec_mode == VOC_REC_BOTH) { rc = adm_close(port_id[0]); if (rc < 0) { pr_err("%s: Error %d in ADM close %d\n", __func__, rc, port_id[0]); goto fail_cmd; } msm_clear_copp_id(popp_id, port_id[0]); rc = afe_stop_pseudo_port(port_id[0]); if (rc < 0) { pr_err("%s: Error %d in Tx pseudo port stop\n", __func__, rc); goto fail_cmd; } rc = adm_close(port_id[1]); if (rc < 0) { pr_err("%s: Error %d in ADM close %d\n", __func__, rc, port_id[1]); goto fail_cmd; } msm_clear_copp_id(popp_id, port_id[1]); rc = afe_stop_pseudo_port(port_id[1]); if (rc < 0) { pr_err("%s: Error %d in Rx pseudo port stop\n", __func__, rc); goto fail_cmd; } } else { pr_err("%s Unknown rec_mode %d\n", __func__, rec_mode); goto fail_cmd; } fail_cmd: mutex_unlock(&routing_info.adm_mutex); return rc; } static long audio_dev_ctrl_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int rc = 0; struct audio_dev_ctrl_state *dev_ctrl = file->private_data; mutex_lock(&session_lock); switch (cmd) { case AUDIO_GET_NUM_SND_DEVICE: rc = put_user(dev_ctrl->num_dev, (uint32_t __user *) arg); break; case AUDIO_GET_SND_DEVICES: rc = audio_dev_ctrl_get_devices(dev_ctrl, (void __user *) arg); break; case AUDIO_ENABLE_SND_DEVICE: { struct msm_snddev_info *dev_info; u32 dev_id; if (get_user(dev_id, (u32 __user *) arg)) { rc = -EFAULT; break; } dev_info = audio_dev_ctrl_find_dev(dev_id); if (IS_ERR(dev_info)) rc = PTR_ERR(dev_info); else { rc = dev_info->dev_ops.open(dev_info); if (!rc) dev_info->opened = 1; wake_up(&audio_dev_ctrl.wait); } break; } case AUDIO_DISABLE_SND_DEVICE: { struct msm_snddev_info *dev_info; u32 dev_id; if (get_user(dev_id, (u32 __user *) arg)) { rc = -EFAULT; break; } dev_info = audio_dev_ctrl_find_dev(dev_id); if (IS_ERR(dev_info)) rc = PTR_ERR(dev_info); else { rc = dev_info->dev_ops.close(dev_info); dev_info->opened = 0; } break; } case AUDIO_ROUTE_STREAM: { struct msm_audio_route_config route_cfg; struct msm_snddev_info *dev_info; if (copy_from_user(&route_cfg, (void __user *) arg, sizeof(struct msm_audio_route_config))) { rc = -EFAULT; break; } pr_debug("%s: route cfg %d %d type\n", __func__, route_cfg.dev_id, route_cfg.stream_type); dev_info = audio_dev_ctrl_find_dev(route_cfg.dev_id); if (IS_ERR(dev_info)) { pr_err("%s: pass invalid dev_id\n", __func__); rc = PTR_ERR(dev_info); break; } switch (route_cfg.stream_type) { case AUDIO_ROUTE_STREAM_VOICE_RX: if (!(dev_info->capability & SNDDEV_CAP_RX) | !(dev_info->capability & SNDDEV_CAP_VOICE)) { rc = -EINVAL; break; } dev_ctrl->voice_rx_dev = dev_info; break; case AUDIO_ROUTE_STREAM_VOICE_TX: if (!(dev_info->capability & SNDDEV_CAP_TX) | !(dev_info->capability & SNDDEV_CAP_VOICE)) { rc = -EINVAL; break; } dev_ctrl->voice_tx_dev = dev_info; break; } break; } default: rc = -EINVAL; } mutex_unlock(&session_lock); return rc; } static int audio_dev_ctrl_open(struct inode *inode, struct file *file) { pr_debug("open audio_dev_ctrl\n"); atomic_inc(&audio_dev_ctrl.opened); file->private_data = &audio_dev_ctrl; return 0; } static int audio_dev_ctrl_release(struct inode *inode, struct file *file) { pr_debug("release audio_dev_ctrl\n"); atomic_dec(&audio_dev_ctrl.opened); return 0; } static const struct file_operations audio_dev_ctrl_fops = { .owner = THIS_MODULE, .open = audio_dev_ctrl_open, .release = audio_dev_ctrl_release, .unlocked_ioctl = audio_dev_ctrl_ioctl, }; struct miscdevice audio_dev_ctrl_misc = { .minor = MISC_DYNAMIC_MINOR, .name = "msm_audio_dev_ctrl", .fops = &audio_dev_ctrl_fops, }; /* session id is 64 bit routing mask per device * 0-15 for voice clients * 16-31 for Decoder clients * 32-47 for Encoder clients * 48-63 Do not care */ void broadcast_event(u32 evt_id, u32 dev_id, u64 session_id) { int clnt_id = 0, i; union auddev_evt_data *evt_payload; struct msm_snd_evt_listner *callback; struct msm_snddev_info *dev_info = NULL; u64 session_mask = 0; static int pending_sent; pr_debug(": evt_id = %d\n", evt_id); if ((evt_id != AUDDEV_EVT_START_VOICE) && (evt_id != AUDDEV_EVT_END_VOICE) && (evt_id != AUDDEV_EVT_STREAM_VOL_CHG) && (evt_id != AUDDEV_EVT_VOICE_STATE_CHG)) { dev_info = audio_dev_ctrl_find_dev(dev_id); if (IS_ERR(dev_info)) { pr_err("%s: pass invalid dev_id(%d)\n", __func__, dev_id); return; } } if (event.cb != NULL) callback = event.cb; else return; mutex_lock(&session_lock); if (evt_id == AUDDEV_EVT_VOICE_STATE_CHG) routing_info.voice_state = dev_id; evt_payload = kzalloc(sizeof(union auddev_evt_data), GFP_KERNEL); if (evt_payload == NULL) { pr_err("broadcast_event: cannot allocate memory\n"); mutex_unlock(&session_lock); return; } for (; ;) { if (!(evt_id & callback->evt_id)) { if (callback->cb_next == NULL) break; else { callback = callback->cb_next; continue; } } clnt_id = callback->clnt_id; memset(evt_payload, 0, sizeof(union auddev_evt_data)); if ((evt_id == AUDDEV_EVT_START_VOICE) || (evt_id == AUDDEV_EVT_END_VOICE) || evt_id == AUDDEV_EVT_DEVICE_VOL_MUTE_CHG) goto skip_check; if (callback->clnt_type == AUDDEV_CLNT_AUDIOCAL) goto aud_cal; session_mask = (((u64)0x1) << clnt_id) << (MAX_BIT_PER_CLIENT * \ ((int)callback->clnt_type-1)); if ((evt_id == AUDDEV_EVT_STREAM_VOL_CHG) || \ (evt_id == AUDDEV_EVT_VOICE_STATE_CHG)) { pr_debug("AUDDEV_EVT_STREAM_VOL_CHG or\ AUDDEV_EVT_VOICE_STATE_CHG\n"); goto volume_strm; } pr_debug("dev_info->sessions = %llu\n", dev_info->sessions); if ((!session_id && !(dev_info->sessions & session_mask)) || (session_id && ((dev_info->sessions & session_mask) != session_id))) { if (callback->cb_next == NULL) break; else { callback = callback->cb_next; continue; } } if (evt_id == AUDDEV_EVT_DEV_CHG_VOICE) goto voc_events; volume_strm: if (callback->clnt_type == AUDDEV_CLNT_DEC) { pr_debug("AUDDEV_CLNT_DEC\n"); if (evt_id == AUDDEV_EVT_STREAM_VOL_CHG) { pr_debug("clnt_id = %d, session_id = %llu\n", clnt_id, session_id); if (session_mask != session_id) goto sent_dec; else evt_payload->session_vol = msm_vol_ctl.volume; } else if (evt_id == AUDDEV_EVT_FREQ_CHG) { if (routing_info.dec_freq[clnt_id].evt) { routing_info.dec_freq[clnt_id].evt = 0; goto sent_dec; } else if (routing_info.dec_freq[clnt_id].freq == dev_info->set_sample_rate) goto sent_dec; else { evt_payload->freq_info.sample_rate = dev_info->set_sample_rate; evt_payload->freq_info.dev_type = dev_info->capability; evt_payload->freq_info.acdb_dev_id = dev_info->acdb_id; } } else if (evt_id == AUDDEV_EVT_VOICE_STATE_CHG) evt_payload->voice_state = routing_info.voice_state; else evt_payload->routing_id = dev_info->copp_id; callback->auddev_evt_listener( evt_id, evt_payload, callback->private_data); sent_dec: if ((evt_id != AUDDEV_EVT_STREAM_VOL_CHG) && (evt_id != AUDDEV_EVT_VOICE_STATE_CHG)) routing_info.dec_freq[clnt_id].freq = dev_info->set_sample_rate; if (callback->cb_next == NULL) break; else { callback = callback->cb_next; continue; } } if (callback->clnt_type == AUDDEV_CLNT_ENC) { pr_debug("AUDDEV_CLNT_ENC\n"); if (evt_id == AUDDEV_EVT_FREQ_CHG) { if (routing_info.enc_freq[clnt_id].evt) { routing_info.enc_freq[clnt_id].evt = 0; goto sent_enc; } else { evt_payload->freq_info.sample_rate = dev_info->set_sample_rate; evt_payload->freq_info.dev_type = dev_info->capability; evt_payload->freq_info.acdb_dev_id = dev_info->acdb_id; } } else if (evt_id == AUDDEV_EVT_VOICE_STATE_CHG) evt_payload->voice_state = routing_info.voice_state; else evt_payload->routing_id = dev_info->copp_id; callback->auddev_evt_listener( evt_id, evt_payload, callback->private_data); sent_enc: if (callback->cb_next == NULL) break; else { callback = callback->cb_next; continue; } } aud_cal: if (callback->clnt_type == AUDDEV_CLNT_AUDIOCAL) { pr_debug("AUDDEV_CLNT_AUDIOCAL\n"); if (evt_id == AUDDEV_EVT_VOICE_STATE_CHG) evt_payload->voice_state = routing_info.voice_state; else if (!dev_info->sessions) goto sent_aud_cal; else { evt_payload->audcal_info.dev_id = dev_info->copp_id; evt_payload->audcal_info.acdb_id = dev_info->acdb_id; evt_payload->audcal_info.dev_type = (dev_info->capability & SNDDEV_CAP_TX) ? SNDDEV_CAP_TX : SNDDEV_CAP_RX; evt_payload->audcal_info.sample_rate = dev_info->set_sample_rate ? dev_info->set_sample_rate : dev_info->sample_rate; } callback->auddev_evt_listener( evt_id, evt_payload, callback->private_data); sent_aud_cal: if (callback->cb_next == NULL) break; else { callback = callback->cb_next; continue; } } skip_check: voc_events: if (callback->clnt_type == AUDDEV_CLNT_VOC) { pr_debug("AUDDEV_CLNT_VOC\n"); if (evt_id == AUDDEV_EVT_DEV_RLS) { if (!pending_sent) goto sent_voc; else pending_sent = 0; } if (evt_id == AUDDEV_EVT_REL_PENDING) pending_sent = 1; if (evt_id == AUDDEV_EVT_DEVICE_VOL_MUTE_CHG) { evt_payload->voc_vm_info.voice_session_id = session_id; if (dev_info->capability & SNDDEV_CAP_TX) { evt_payload->voc_vm_info.dev_type = SNDDEV_CAP_TX; evt_payload->voc_vm_info.acdb_dev_id = dev_info->acdb_id; evt_payload-> voc_vm_info.dev_vm_val.mute = routing_info.tx_mute; } else { evt_payload->voc_vm_info.dev_type = SNDDEV_CAP_RX; evt_payload->voc_vm_info.acdb_dev_id = dev_info->acdb_id; evt_payload-> voc_vm_info.dev_vm_val.vol = routing_info.voice_rx_vol; } } else if ((evt_id == AUDDEV_EVT_START_VOICE) || (evt_id == AUDDEV_EVT_END_VOICE)) { memset(evt_payload, 0, sizeof(union auddev_evt_data)); evt_payload->voice_session_id = session_id; } else if (evt_id == AUDDEV_EVT_FREQ_CHG) { if (routing_info.voice_tx_sample_rate != dev_info->set_sample_rate) { routing_info.voice_tx_sample_rate = dev_info->set_sample_rate; evt_payload->freq_info.sample_rate = dev_info->set_sample_rate; evt_payload->freq_info.dev_type = dev_info->capability; evt_payload->freq_info.acdb_dev_id = dev_info->acdb_id; } else goto sent_voc; } else if (evt_id == AUDDEV_EVT_VOICE_STATE_CHG) evt_payload->voice_state = routing_info.voice_state; else { evt_payload->voc_devinfo.dev_type = (dev_info->capability & SNDDEV_CAP_TX) ? SNDDEV_CAP_TX : SNDDEV_CAP_RX; evt_payload->voc_devinfo.acdb_dev_id = dev_info->acdb_id; evt_payload->voc_devinfo.dev_port_id = dev_info->copp_id; evt_payload->voc_devinfo.dev_sample = dev_info->set_sample_rate ? dev_info->set_sample_rate : dev_info->sample_rate; evt_payload->voc_devinfo.dev_id = dev_id; if (dev_info->capability & SNDDEV_CAP_RX) { for (i = 0; i < VOC_RX_VOL_ARRAY_NUM; i++) { evt_payload-> voc_devinfo.max_rx_vol[i] = dev_info->max_voc_rx_vol[i]; evt_payload ->voc_devinfo.min_rx_vol[i] = dev_info->min_voc_rx_vol[i]; } } } callback->auddev_evt_listener( evt_id, evt_payload, callback->private_data); if (evt_id == AUDDEV_EVT_DEV_RLS) dev_info->sessions &= ~(0xFFFF); sent_voc: if (callback->cb_next == NULL) break; else { callback = callback->cb_next; continue; } } } kfree(evt_payload); mutex_unlock(&session_lock); } EXPORT_SYMBOL(broadcast_event); void mixer_post_event(u32 evt_id, u32 id) { pr_debug("evt_id = %d\n", evt_id); switch (evt_id) { case AUDDEV_EVT_DEV_CHG_VOICE: /* Called from Voice_route */ broadcast_event(AUDDEV_EVT_DEV_CHG_VOICE, id, SESSION_IGNORE); break; case AUDDEV_EVT_DEV_RDY: broadcast_event(AUDDEV_EVT_DEV_RDY, id, SESSION_IGNORE); break; case AUDDEV_EVT_DEV_RLS: broadcast_event(AUDDEV_EVT_DEV_RLS, id, SESSION_IGNORE); break; case AUDDEV_EVT_REL_PENDING: broadcast_event(AUDDEV_EVT_REL_PENDING, id, SESSION_IGNORE); break; case AUDDEV_EVT_DEVICE_VOL_MUTE_CHG: broadcast_event(AUDDEV_EVT_DEVICE_VOL_MUTE_CHG, id, SESSION_IGNORE); break; case AUDDEV_EVT_STREAM_VOL_CHG: broadcast_event(AUDDEV_EVT_STREAM_VOL_CHG, id, SESSION_IGNORE); break; case AUDDEV_EVT_START_VOICE: broadcast_event(AUDDEV_EVT_START_VOICE, id, SESSION_IGNORE); break; case AUDDEV_EVT_END_VOICE: broadcast_event(AUDDEV_EVT_END_VOICE, id, SESSION_IGNORE); break; case AUDDEV_EVT_FREQ_CHG: broadcast_event(AUDDEV_EVT_FREQ_CHG, id, SESSION_IGNORE); break; default: break; } } EXPORT_SYMBOL(mixer_post_event); static int __init audio_dev_ctrl_init(void) { init_waitqueue_head(&audio_dev_ctrl.wait); event.cb = NULL; msm_reset_device_work_queue = create_workqueue("reset_device"); if (msm_reset_device_work_queue == NULL) return -ENOMEM; atomic_set(&audio_dev_ctrl.opened, 0); audio_dev_ctrl.num_dev = 0; audio_dev_ctrl.voice_tx_dev = NULL; audio_dev_ctrl.voice_rx_dev = NULL; routing_info.voice_state = VOICE_STATE_INVALID; mutex_init(&adm_tx_topology_tbl.lock); mutex_init(&routing_info.copp_list_mutex); mutex_init(&routing_info.adm_mutex); memset(routing_info.copp_list, COPP_IGNORE, (sizeof(unsigned int) * MAX_SESSIONS * AFE_MAX_PORTS)); return misc_register(&audio_dev_ctrl_misc); } static void __exit audio_dev_ctrl_exit(void) { destroy_workqueue(msm_reset_device_work_queue); } module_init(audio_dev_ctrl_init); module_exit(audio_dev_ctrl_exit); MODULE_DESCRIPTION("MSM 8K Audio Device Control driver"); MODULE_LICENSE("GPL v2");