| /* 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 <linux/module.h> |
| #include <linux/fs.h> |
| #include <linux/miscdevice.h> |
| #include <linux/msm_audio.h> |
| #include <linux/slab.h> |
| #include <linux/wait.h> |
| #include <linux/sched.h> |
| #include <linux/workqueue.h> |
| #include <asm/uaccess.h> |
| #include <asm/atomic.h> |
| #include <mach/qdsp6v2/audio_dev_ctl.h> |
| #include <mach/debug_mm.h> |
| #include <mach/qdsp6v2/q6voice.h> |
| #include <sound/apr_audio.h> |
| #include <sound/q6adm.h> |
| |
| #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"); |