| /* Copyright (c) 2010-2011, Code Aurora Forum. 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/list.h> |
| #include <linux/uaccess.h> |
| #include <linux/mutex.h> |
| #include <linux/wakelock.h> |
| #include <linux/slab.h> |
| #include <linux/wait.h> |
| #include <linux/sched.h> |
| #include <linux/spinlock.h> |
| #include <linux/msm_audio_mvs.h> |
| #include <mach/qdsp6v2/q6voice.h> |
| |
| /* Each buffer is 20 ms, queue holds 200 ms of data. */ |
| #define MVS_MAX_Q_LEN 10 |
| |
| /* Length of the DSP frame info header added to the voc packet. */ |
| #define DSP_FRAME_HDR_LEN 1 |
| |
| enum audio_mvs_state_type { |
| AUDIO_MVS_CLOSED, |
| AUDIO_MVS_STARTED, |
| AUDIO_MVS_STOPPED |
| }; |
| |
| struct audio_mvs_buf_node { |
| struct list_head list; |
| struct q6_msm_audio_mvs_frame frame; |
| }; |
| |
| struct audio_mvs_info_type { |
| enum audio_mvs_state_type state; |
| |
| uint32_t mvs_mode; |
| uint32_t rate_type; |
| uint32_t dtx_mode; |
| struct q_min_max_rate min_max_rate; |
| |
| struct list_head in_queue; |
| struct list_head free_in_queue; |
| |
| struct list_head out_queue; |
| struct list_head free_out_queue; |
| |
| wait_queue_head_t in_wait; |
| wait_queue_head_t out_wait; |
| |
| struct mutex lock; |
| struct mutex in_lock; |
| struct mutex out_lock; |
| |
| spinlock_t dsp_lock; |
| |
| struct wake_lock suspend_lock; |
| struct wake_lock idle_lock; |
| |
| void *memory_chunk; |
| }; |
| |
| static struct audio_mvs_info_type audio_mvs_info; |
| |
| static uint32_t audio_mvs_get_rate(uint32_t mvs_mode, uint32_t rate_type) |
| { |
| uint32_t cvs_rate; |
| |
| if (mvs_mode == MVS_MODE_AMR_WB) |
| cvs_rate = rate_type - MVS_AMR_MODE_0660; |
| else |
| cvs_rate = rate_type; |
| |
| pr_debug("%s: CVS rate is %d for MVS mode %d\n", |
| __func__, cvs_rate, mvs_mode); |
| |
| return cvs_rate; |
| } |
| |
| static void audio_mvs_process_ul_pkt(uint8_t *voc_pkt, |
| uint32_t pkt_len, |
| void *private_data) |
| { |
| struct audio_mvs_buf_node *buf_node = NULL; |
| struct audio_mvs_info_type *audio = private_data; |
| unsigned long dsp_flags; |
| |
| /* Copy up-link packet into out_queue. */ |
| spin_lock_irqsave(&audio->dsp_lock, dsp_flags); |
| |
| if (!list_empty(&audio->free_out_queue)) { |
| buf_node = list_first_entry(&audio->free_out_queue, |
| struct audio_mvs_buf_node, |
| list); |
| list_del(&buf_node->list); |
| |
| switch (audio->mvs_mode) { |
| case MVS_MODE_AMR: |
| case MVS_MODE_AMR_WB: { |
| /* Remove the DSP frame info header. Header format: |
| * Bits 0-3: Frame rate |
| * Bits 4-7: Frame type |
| */ |
| buf_node->frame.header.frame_type = |
| ((*voc_pkt) & 0xF0) >> 4; |
| voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; |
| |
| buf_node->frame.len = pkt_len - DSP_FRAME_HDR_LEN; |
| |
| memcpy(&buf_node->frame.voc_pkt[0], |
| voc_pkt, |
| buf_node->frame.len); |
| |
| list_add_tail(&buf_node->list, &audio->out_queue); |
| break; |
| } |
| |
| case MVS_MODE_IS127: { |
| buf_node->frame.header.packet_rate = (*voc_pkt) & 0x0F; |
| voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; |
| |
| buf_node->frame.len = pkt_len - DSP_FRAME_HDR_LEN; |
| |
| memcpy(&buf_node->frame.voc_pkt[0], |
| voc_pkt, |
| buf_node->frame.len); |
| |
| list_add_tail(&buf_node->list, &audio->out_queue); |
| break; |
| } |
| |
| case MVS_MODE_G729A: { |
| /* G729 frames are 10ms each, but the DSP works with |
| * 20ms frames and sends two 10ms frames per buffer. |
| * Extract the two frames and put them in separate |
| * buffers. |
| */ |
| /* Remove the first DSP frame info header. |
| * Header format: |
| * Bits 0-1: Frame type |
| */ |
| buf_node->frame.header.frame_type = (*voc_pkt) & 0x03; |
| voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; |
| |
| /* There are two frames in the buffer. Length of the |
| * first frame: |
| */ |
| buf_node->frame.len = (pkt_len - |
| 2 * DSP_FRAME_HDR_LEN) / 2; |
| |
| memcpy(&buf_node->frame.voc_pkt[0], |
| voc_pkt, |
| buf_node->frame.len); |
| voc_pkt = voc_pkt + buf_node->frame.len; |
| |
| list_add_tail(&buf_node->list, &audio->out_queue); |
| |
| /* Get another buffer from the free Q and fill in the |
| * second frame. |
| */ |
| if (!list_empty(&audio->free_out_queue)) { |
| buf_node = |
| list_first_entry(&audio->free_out_queue, |
| struct audio_mvs_buf_node, |
| list); |
| list_del(&buf_node->list); |
| |
| /* Remove the second DSP frame info header. |
| * Header format: |
| * Bits 0-1: Frame type |
| */ |
| buf_node->frame.header.frame_type = |
| (*voc_pkt) & 0x03; |
| voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; |
| |
| /* There are two frames in the buffer. Length |
| * of the first frame: |
| */ |
| buf_node->frame.len = (pkt_len - |
| 2 * DSP_FRAME_HDR_LEN) / 2; |
| |
| memcpy(&buf_node->frame.voc_pkt[0], |
| voc_pkt, |
| buf_node->frame.len); |
| |
| list_add_tail(&buf_node->list, |
| &audio->out_queue); |
| |
| } else { |
| /* Drop the second frame. */ |
| pr_err("%s: UL data dropped, read is slow\n", |
| __func__); |
| } |
| |
| break; |
| } |
| |
| case MVS_MODE_G711: |
| case MVS_MODE_G711A: { |
| /* G711 frames are 10ms each, but the DSP works with |
| * 20ms frames and sends two 10ms frames per buffer. |
| * Extract the two frames and put them in separate |
| * buffers. |
| */ |
| /* Remove the first DSP frame info header. |
| * Header format: G711A |
| * Bits 0-1: Frame type |
| * Bits 2-3: Frame rate |
| * |
| * Header format: G711 |
| * Bits 2-3: Frame rate |
| */ |
| if (audio->mvs_mode == MVS_MODE_G711A) |
| buf_node->frame.header.frame_type = |
| (*voc_pkt) & 0x03; |
| voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; |
| |
| /* There are two frames in the buffer. Length of the |
| * first frame: |
| */ |
| buf_node->frame.len = (pkt_len - |
| 2 * DSP_FRAME_HDR_LEN) / 2; |
| |
| memcpy(&buf_node->frame.voc_pkt[0], |
| voc_pkt, |
| buf_node->frame.len); |
| voc_pkt = voc_pkt + buf_node->frame.len; |
| |
| list_add_tail(&buf_node->list, &audio->out_queue); |
| |
| /* Get another buffer from the free Q and fill in the |
| * second frame. |
| */ |
| if (!list_empty(&audio->free_out_queue)) { |
| buf_node = |
| list_first_entry(&audio->free_out_queue, |
| struct audio_mvs_buf_node, |
| list); |
| list_del(&buf_node->list); |
| |
| /* Remove the second DSP frame info header. |
| * Header format: |
| * Bits 0-1: Frame type |
| * Bits 2-3: Frame rate |
| */ |
| if (audio->mvs_mode == MVS_MODE_G711A) |
| buf_node->frame.header.frame_type = |
| (*voc_pkt) & 0x03; |
| voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; |
| |
| /* There are two frames in the buffer. Length |
| * of the second frame: |
| */ |
| buf_node->frame.len = (pkt_len - |
| 2 * DSP_FRAME_HDR_LEN) / 2; |
| |
| memcpy(&buf_node->frame.voc_pkt[0], |
| voc_pkt, |
| buf_node->frame.len); |
| |
| list_add_tail(&buf_node->list, |
| &audio->out_queue); |
| } else { |
| /* Drop the second frame. */ |
| pr_err("%s: UL data dropped, read is slow\n", |
| __func__); |
| } |
| break; |
| } |
| |
| case MVS_MODE_IS733: |
| case MVS_MODE_4GV_NB: |
| case MVS_MODE_4GV_WB: { |
| /* Remove the DSP frame info header. |
| * Header format: |
| * Bits 0-3: frame rate |
| */ |
| buf_node->frame.header.packet_rate = (*voc_pkt) & 0x0F; |
| voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; |
| buf_node->frame.len = pkt_len - DSP_FRAME_HDR_LEN; |
| |
| memcpy(&buf_node->frame.voc_pkt[0], |
| voc_pkt, |
| buf_node->frame.len); |
| |
| list_add_tail(&buf_node->list, &audio->out_queue); |
| break; |
| } |
| |
| case MVS_MODE_EFR: |
| case MVS_MODE_FR: |
| case MVS_MODE_HR: { |
| /* |
| * Remove the DSP frame info header |
| * Header Format |
| * Bit 0: bfi unused for uplink |
| * Bit 1-2: sid applies to both uplink and downlink |
| * Bit 3: taf unused for uplink |
| * MVS_MODE_HR |
| * Bit 4: ufi unused for uplink |
| */ |
| buf_node->frame.header.gsm_frame_type.sid = |
| ((*voc_pkt) & 0x06) >> 1; |
| voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; |
| buf_node->frame.len = pkt_len - DSP_FRAME_HDR_LEN; |
| |
| memcpy(&buf_node->frame.voc_pkt[0], |
| voc_pkt, |
| buf_node->frame.len); |
| |
| list_add_tail(&buf_node->list, &audio->out_queue); |
| break; |
| } |
| |
| default: { |
| buf_node->frame.header.frame_type = 0; |
| |
| buf_node->frame.len = pkt_len; |
| |
| memcpy(&buf_node->frame.voc_pkt[0], |
| voc_pkt, |
| buf_node->frame.len); |
| |
| list_add_tail(&buf_node->list, &audio->out_queue); |
| } |
| } |
| } else { |
| pr_err("%s: UL data dropped, read is slow\n", __func__); |
| } |
| |
| spin_unlock_irqrestore(&audio->dsp_lock, dsp_flags); |
| |
| wake_up(&audio->out_wait); |
| } |
| |
| static void audio_mvs_process_dl_pkt(uint8_t *voc_pkt, |
| uint32_t *pkt_len, |
| void *private_data) |
| { |
| struct audio_mvs_buf_node *buf_node = NULL; |
| struct audio_mvs_info_type *audio = private_data; |
| unsigned long dsp_flags; |
| |
| spin_lock_irqsave(&audio->dsp_lock, dsp_flags); |
| |
| if (!list_empty(&audio->in_queue)) { |
| uint32_t rate_type = audio_mvs_get_rate(audio->mvs_mode, |
| audio->rate_type); |
| |
| buf_node = list_first_entry(&audio->in_queue, |
| struct audio_mvs_buf_node, |
| list); |
| list_del(&buf_node->list); |
| |
| switch (audio->mvs_mode) { |
| case MVS_MODE_AMR: |
| case MVS_MODE_AMR_WB: { |
| /* Add the DSP frame info header. Header format: |
| * Bits 0-3: Frame rate |
| * Bits 4-7: Frame type |
| */ |
| *voc_pkt = |
| ((buf_node->frame.header.frame_type & 0x0F) << 4) | |
| (rate_type & 0x0F); |
| voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; |
| |
| *pkt_len = buf_node->frame.len + DSP_FRAME_HDR_LEN; |
| |
| memcpy(voc_pkt, |
| &buf_node->frame.voc_pkt[0], |
| buf_node->frame.len); |
| |
| list_add_tail(&buf_node->list, &audio->free_in_queue); |
| break; |
| } |
| |
| case MVS_MODE_IS127: { |
| /* Add the DSP frame info header. Header format: |
| * Bits 0-3: Frame rate |
| */ |
| *voc_pkt = buf_node->frame.header.packet_rate & 0x0F; |
| voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; |
| |
| *pkt_len = buf_node->frame.len + DSP_FRAME_HDR_LEN; |
| |
| memcpy(voc_pkt, |
| &buf_node->frame.voc_pkt[0], |
| buf_node->frame.len); |
| |
| list_add_tail(&buf_node->list, &audio->free_in_queue); |
| break; |
| } |
| |
| case MVS_MODE_G729A: { |
| /* G729 frames are 10ms each but the DSP expects 20ms |
| * worth of data, so send two 10ms frames per buffer. |
| */ |
| /* Add the first DSP frame info header. Header format: |
| * Bits 0-1: Frame type |
| */ |
| *voc_pkt = buf_node->frame.header.frame_type & 0x03; |
| voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; |
| |
| *pkt_len = buf_node->frame.len + DSP_FRAME_HDR_LEN; |
| |
| memcpy(voc_pkt, |
| &buf_node->frame.voc_pkt[0], |
| buf_node->frame.len); |
| voc_pkt = voc_pkt + buf_node->frame.len; |
| |
| list_add_tail(&buf_node->list, &audio->free_in_queue); |
| |
| if (!list_empty(&audio->in_queue)) { |
| /* Get the second buffer. */ |
| buf_node = list_first_entry(&audio->in_queue, |
| struct audio_mvs_buf_node, |
| list); |
| list_del(&buf_node->list); |
| |
| /* Add the second DSP frame info header. |
| * Header format: |
| * Bits 0-1: Frame type |
| */ |
| *voc_pkt = buf_node->frame.header.frame_type |
| & 0x03; |
| voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; |
| |
| *pkt_len = *pkt_len + |
| buf_node->frame.len + DSP_FRAME_HDR_LEN; |
| |
| memcpy(voc_pkt, |
| &buf_node->frame.voc_pkt[0], |
| buf_node->frame.len); |
| |
| list_add_tail(&buf_node->list, |
| &audio->free_in_queue); |
| } else { |
| /* Only 10ms worth of data is available, signal |
| * erasure frame. |
| */ |
| *voc_pkt = MVS_G729A_ERASURE & 0x03; |
| |
| *pkt_len = *pkt_len + DSP_FRAME_HDR_LEN; |
| } |
| |
| break; |
| } |
| |
| case MVS_MODE_G711: |
| case MVS_MODE_G711A: { |
| /* G711 frames are 10ms each but the DSP expects 20ms |
| * worth of data, so send two 10ms frames per buffer. |
| */ |
| /* Add the first DSP frame info header. Header format: |
| * Bits 0-1: Frame type |
| * Bits 2-3: Frame rate |
| */ |
| *voc_pkt = ((rate_type & 0x0F) << 2) | |
| (buf_node->frame.header.frame_type & 0x03); |
| voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; |
| |
| *pkt_len = buf_node->frame.len + DSP_FRAME_HDR_LEN; |
| |
| memcpy(voc_pkt, |
| &buf_node->frame.voc_pkt[0], |
| buf_node->frame.len); |
| voc_pkt = voc_pkt + buf_node->frame.len; |
| |
| list_add_tail(&buf_node->list, &audio->free_in_queue); |
| |
| if (!list_empty(&audio->in_queue)) { |
| /* Get the second buffer. */ |
| buf_node = list_first_entry(&audio->in_queue, |
| struct audio_mvs_buf_node, |
| list); |
| list_del(&buf_node->list); |
| |
| /* Add the second DSP frame info header. |
| * Header format: |
| * Bits 0-1: Frame type |
| * Bits 2-3: Frame rate |
| */ |
| *voc_pkt = ((rate_type & 0x0F) << 2) | |
| (buf_node->frame.header.frame_type & 0x03); |
| voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; |
| |
| *pkt_len = *pkt_len + |
| buf_node->frame.len + DSP_FRAME_HDR_LEN; |
| |
| memcpy(voc_pkt, |
| &buf_node->frame.voc_pkt[0], |
| buf_node->frame.len); |
| |
| list_add_tail(&buf_node->list, |
| &audio->free_in_queue); |
| } else { |
| /* Only 10ms worth of data is available, signal |
| * erasure frame. |
| */ |
| *voc_pkt = ((rate_type & 0x0F) << 2) | |
| (MVS_G711A_ERASURE & 0x03); |
| |
| *pkt_len = *pkt_len + DSP_FRAME_HDR_LEN; |
| } |
| break; |
| } |
| |
| case MVS_MODE_IS733: |
| case MVS_MODE_4GV_NB: |
| case MVS_MODE_4GV_WB: { |
| /* Add the DSP frame info header. Header format: |
| * Bits 0-3 : Frame rate |
| */ |
| *voc_pkt = buf_node->frame.header.packet_rate & 0x0F; |
| voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; |
| *pkt_len = buf_node->frame.len + DSP_FRAME_HDR_LEN; |
| |
| memcpy(voc_pkt, |
| &buf_node->frame.voc_pkt[0], |
| buf_node->frame.len); |
| |
| list_add_tail(&buf_node->list, &audio->free_in_queue); |
| break; |
| } |
| |
| case MVS_MODE_EFR: |
| case MVS_MODE_FR: |
| case MVS_MODE_HR: { |
| /* |
| * Remove the DSP frame info header |
| * Header Format |
| * Bit 0: bfi applies only for downlink |
| * Bit 1-2: sid applies for downlink and uplink |
| * Bit 3: taf applies only for downlink |
| * MVS_MODE_HR |
| * Bit 4: ufi applies only for downlink |
| */ |
| *voc_pkt = |
| ((buf_node->frame.header.gsm_frame_type.bfi |
| & 0x01) | |
| ((buf_node->frame.header.gsm_frame_type.sid |
| & 0x03) << 1) | |
| ((buf_node->frame.header.gsm_frame_type.taf |
| & 0x01) << 3)); |
| |
| if (audio->mvs_mode == MVS_MODE_HR) { |
| *voc_pkt = (*voc_pkt | |
| ((buf_node->frame.header.gsm_frame_type.ufi |
| & 0x01) << 4) | |
| ((0 & 0x07) << 5)); |
| } else { |
| *voc_pkt = (*voc_pkt | |
| ((0 & 0x0F) << 4)); |
| } |
| |
| voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; |
| *pkt_len = buf_node->frame.len + DSP_FRAME_HDR_LEN; |
| |
| memcpy(voc_pkt, |
| &buf_node->frame.voc_pkt[0], |
| buf_node->frame.len); |
| |
| list_add_tail(&buf_node->list, &audio->free_in_queue); |
| |
| break; |
| } |
| |
| default: { |
| *pkt_len = buf_node->frame.len; |
| |
| memcpy(voc_pkt, |
| &buf_node->frame.voc_pkt[0], |
| buf_node->frame.len); |
| |
| list_add_tail(&buf_node->list, &audio->free_in_queue); |
| } |
| } |
| } else { |
| *pkt_len = 0; |
| |
| pr_info("%s: No DL data available to send to MVS\n", __func__); |
| } |
| |
| spin_unlock_irqrestore(&audio->dsp_lock, dsp_flags); |
| wake_up(&audio->in_wait); |
| } |
| |
| static uint32_t audio_mvs_get_media_type(uint32_t mvs_mode, uint32_t rate_type) |
| { |
| uint32_t media_type; |
| |
| switch (mvs_mode) { |
| case MVS_MODE_IS733: |
| media_type = VSS_MEDIA_ID_13K_MODEM; |
| break; |
| |
| case MVS_MODE_IS127: |
| media_type = VSS_MEDIA_ID_EVRC_MODEM; |
| break; |
| |
| case MVS_MODE_4GV_NB: |
| media_type = VSS_MEDIA_ID_4GV_NB_MODEM; |
| break; |
| |
| case MVS_MODE_4GV_WB: |
| media_type = VSS_MEDIA_ID_4GV_WB_MODEM; |
| break; |
| |
| case MVS_MODE_AMR: |
| media_type = VSS_MEDIA_ID_AMR_NB_MODEM; |
| break; |
| |
| case MVS_MODE_EFR: |
| media_type = VSS_MEDIA_ID_EFR_MODEM; |
| break; |
| |
| case MVS_MODE_FR: |
| media_type = VSS_MEDIA_ID_FR_MODEM; |
| break; |
| |
| case MVS_MODE_HR: |
| media_type = VSS_MEDIA_ID_HR_MODEM; |
| break; |
| |
| case MVS_MODE_LINEAR_PCM: |
| media_type = VSS_MEDIA_ID_PCM_NB; |
| break; |
| |
| case MVS_MODE_PCM: |
| media_type = VSS_MEDIA_ID_PCM_NB; |
| break; |
| |
| case MVS_MODE_AMR_WB: |
| media_type = VSS_MEDIA_ID_AMR_WB_MODEM; |
| break; |
| |
| case MVS_MODE_G729A: |
| media_type = VSS_MEDIA_ID_G729; |
| break; |
| |
| case MVS_MODE_G711: |
| case MVS_MODE_G711A: |
| if (rate_type == MVS_G711A_MODE_MULAW) |
| media_type = VSS_MEDIA_ID_G711_MULAW; |
| else |
| media_type = VSS_MEDIA_ID_G711_ALAW; |
| break; |
| |
| case MVS_MODE_PCM_WB: |
| media_type = VSS_MEDIA_ID_PCM_WB; |
| break; |
| |
| default: |
| media_type = VSS_MEDIA_ID_PCM_NB; |
| } |
| |
| pr_debug("%s: media_type is 0x%x\n", __func__, media_type); |
| |
| return media_type; |
| } |
| |
| static uint32_t audio_mvs_get_network_type(uint32_t mvs_mode) |
| { |
| uint32_t network_type; |
| |
| switch (mvs_mode) { |
| case MVS_MODE_IS733: |
| case MVS_MODE_IS127: |
| case MVS_MODE_4GV_NB: |
| case MVS_MODE_AMR: |
| case MVS_MODE_EFR: |
| case MVS_MODE_FR: |
| case MVS_MODE_HR: |
| case MVS_MODE_LINEAR_PCM: |
| case MVS_MODE_G711: |
| case MVS_MODE_PCM: |
| case MVS_MODE_G729A: |
| case MVS_MODE_G711A: |
| network_type = VSS_NETWORK_ID_VOIP_NB; |
| break; |
| |
| case MVS_MODE_4GV_WB: |
| case MVS_MODE_AMR_WB: |
| case MVS_MODE_PCM_WB: |
| network_type = VSS_NETWORK_ID_VOIP_WB; |
| break; |
| |
| default: |
| network_type = VSS_NETWORK_ID_DEFAULT; |
| } |
| |
| pr_debug("%s: network_type is 0x%x\n", __func__, network_type); |
| |
| return network_type; |
| } |
| |
| static int audio_mvs_start(struct audio_mvs_info_type *audio) |
| { |
| int rc = 0; |
| |
| pr_info("%s\n", __func__); |
| |
| /* Prevent sleep. */ |
| wake_lock(&audio->suspend_lock); |
| wake_lock(&audio->idle_lock); |
| |
| rc = voice_set_voc_path_full(1); |
| |
| if (rc == 0) { |
| voice_register_mvs_cb(audio_mvs_process_ul_pkt, |
| audio_mvs_process_dl_pkt, |
| audio); |
| |
| voice_config_vocoder( |
| audio_mvs_get_media_type(audio->mvs_mode, audio->rate_type), |
| audio_mvs_get_rate(audio->mvs_mode, audio->rate_type), |
| audio_mvs_get_network_type(audio->mvs_mode), |
| audio->dtx_mode, |
| audio->min_max_rate); |
| |
| audio->state = AUDIO_MVS_STARTED; |
| } else { |
| pr_err("%s: Error %d setting voc path to full\n", __func__, rc); |
| } |
| |
| return rc; |
| } |
| |
| static int audio_mvs_stop(struct audio_mvs_info_type *audio) |
| { |
| int rc = 0; |
| |
| pr_info("%s\n", __func__); |
| |
| voice_set_voc_path_full(0); |
| |
| audio->state = AUDIO_MVS_STOPPED; |
| |
| /* Allow sleep. */ |
| wake_unlock(&audio->suspend_lock); |
| wake_unlock(&audio->idle_lock); |
| |
| return rc; |
| } |
| |
| static int audio_mvs_open(struct inode *inode, struct file *file) |
| { |
| int rc = 0; |
| int i; |
| int offset = 0; |
| struct audio_mvs_buf_node *buf_node = NULL; |
| |
| pr_info("%s\n", __func__); |
| |
| mutex_lock(&audio_mvs_info.lock); |
| |
| /* Allocate input and output buffers. */ |
| audio_mvs_info.memory_chunk = kmalloc(2 * MVS_MAX_Q_LEN * |
| sizeof(struct audio_mvs_buf_node), |
| GFP_KERNEL); |
| |
| if (audio_mvs_info.memory_chunk != NULL) { |
| for (i = 0; i < MVS_MAX_Q_LEN; i++) { |
| buf_node = audio_mvs_info.memory_chunk + offset; |
| |
| list_add_tail(&buf_node->list, |
| &audio_mvs_info.free_in_queue); |
| |
| offset = offset + sizeof(struct audio_mvs_buf_node); |
| } |
| |
| for (i = 0; i < MVS_MAX_Q_LEN; i++) { |
| buf_node = audio_mvs_info.memory_chunk + offset; |
| |
| list_add_tail(&buf_node->list, |
| &audio_mvs_info.free_out_queue); |
| |
| offset = offset + sizeof(struct audio_mvs_buf_node); |
| } |
| |
| audio_mvs_info.state = AUDIO_MVS_STOPPED; |
| |
| file->private_data = &audio_mvs_info; |
| |
| } else { |
| pr_err("%s: No memory for IO buffers\n", __func__); |
| |
| rc = -ENOMEM; |
| } |
| |
| mutex_unlock(&audio_mvs_info.lock); |
| |
| return rc; |
| } |
| |
| static int audio_mvs_release(struct inode *inode, struct file *file) |
| { |
| struct list_head *ptr = NULL; |
| struct list_head *next = NULL; |
| struct audio_mvs_buf_node *buf_node = NULL; |
| struct audio_mvs_info_type *audio = file->private_data; |
| |
| pr_info("%s\n", __func__); |
| |
| mutex_lock(&audio->lock); |
| |
| if (audio->state == AUDIO_MVS_STARTED) |
| audio_mvs_stop(audio); |
| |
| /* Free input and output memory. */ |
| mutex_lock(&audio->in_lock); |
| |
| list_for_each_safe(ptr, next, &audio->in_queue) { |
| buf_node = list_entry(ptr, struct audio_mvs_buf_node, list); |
| list_del(&buf_node->list); |
| } |
| |
| list_for_each_safe(ptr, next, &audio->free_in_queue) { |
| buf_node = list_entry(ptr, struct audio_mvs_buf_node, list); |
| list_del(&buf_node->list); |
| } |
| |
| mutex_unlock(&audio->in_lock); |
| |
| |
| mutex_lock(&audio->out_lock); |
| |
| list_for_each_safe(ptr, next, &audio->out_queue) { |
| buf_node = list_entry(ptr, struct audio_mvs_buf_node, list); |
| list_del(&buf_node->list); |
| } |
| |
| list_for_each_safe(ptr, next, &audio->free_out_queue) { |
| buf_node = list_entry(ptr, struct audio_mvs_buf_node, list); |
| list_del(&buf_node->list); |
| } |
| |
| mutex_unlock(&audio->out_lock); |
| |
| kfree(audio->memory_chunk); |
| audio->memory_chunk = NULL; |
| |
| audio->state = AUDIO_MVS_CLOSED; |
| |
| mutex_unlock(&audio->lock); |
| |
| return 0; |
| } |
| |
| static ssize_t audio_mvs_read(struct file *file, |
| char __user *buf, |
| size_t count, |
| loff_t *pos) |
| { |
| int rc = 0; |
| struct audio_mvs_buf_node *buf_node = NULL; |
| struct audio_mvs_info_type *audio = file->private_data; |
| |
| pr_debug("%s:\n", __func__); |
| |
| rc = wait_event_interruptible_timeout(audio->out_wait, |
| (!list_empty(&audio->out_queue) || |
| audio->state == AUDIO_MVS_STOPPED), |
| 1 * HZ); |
| |
| if (rc > 0) { |
| mutex_lock(&audio->out_lock); |
| |
| if ((audio->state == AUDIO_MVS_STARTED) && |
| (!list_empty(&audio->out_queue))) { |
| |
| if (count >= sizeof(struct q6_msm_audio_mvs_frame)) { |
| buf_node = list_first_entry(&audio->out_queue, |
| struct audio_mvs_buf_node, |
| list); |
| list_del(&buf_node->list); |
| |
| rc = copy_to_user(buf, |
| &buf_node->frame, |
| sizeof(struct q6_msm_audio_mvs_frame)); |
| |
| if (rc == 0) { |
| rc = buf_node->frame.len + |
| sizeof(buf_node->frame.header) + |
| sizeof(buf_node->frame.len); |
| } else { |
| pr_err("%s: Copy to user retuned %d", |
| __func__, rc); |
| |
| rc = -EFAULT; |
| } |
| |
| list_add_tail(&buf_node->list, |
| &audio->free_out_queue); |
| } else { |
| pr_err("%s: Read count %d < sizeof(frame) %d", |
| __func__, count, |
| sizeof(struct q6_msm_audio_mvs_frame)); |
| |
| rc = -ENOMEM; |
| } |
| } else { |
| pr_err("%s: Read performed in state %d\n", |
| __func__, audio->state); |
| |
| rc = -EPERM; |
| } |
| |
| mutex_unlock(&audio->out_lock); |
| |
| } else if (rc == 0) { |
| pr_err("%s: No UL data available\n", __func__); |
| |
| rc = -ETIMEDOUT; |
| } else { |
| pr_err("%s: Read was interrupted\n", __func__); |
| |
| rc = -ERESTARTSYS; |
| } |
| |
| return rc; |
| } |
| |
| static ssize_t audio_mvs_write(struct file *file, |
| const char __user *buf, |
| size_t count, |
| loff_t *pos) |
| { |
| int rc = 0; |
| struct audio_mvs_buf_node *buf_node = NULL; |
| struct audio_mvs_info_type *audio = file->private_data; |
| |
| pr_debug("%s:\n", __func__); |
| |
| rc = wait_event_interruptible_timeout(audio->in_wait, |
| (!list_empty(&audio->free_in_queue) || |
| audio->state == AUDIO_MVS_STOPPED), 1 * HZ); |
| if (rc > 0) { |
| mutex_lock(&audio->in_lock); |
| |
| if (audio->state == AUDIO_MVS_STARTED) { |
| if (count <= sizeof(struct q6_msm_audio_mvs_frame)) { |
| if (!list_empty(&audio->free_in_queue)) { |
| buf_node = |
| list_first_entry(&audio->free_in_queue, |
| struct audio_mvs_buf_node, list); |
| list_del(&buf_node->list); |
| rc = copy_from_user(&buf_node->frame, |
| buf, |
| count); |
| |
| list_add_tail(&buf_node->list, |
| &audio->in_queue); |
| } else { |
| pr_err("%s: No free DL buffs\n", |
| __func__); |
| } |
| } else { |
| pr_err("%s: Write count %d < sizeof(frame) %d", |
| __func__, count, |
| sizeof(struct q6_msm_audio_mvs_frame)); |
| |
| rc = -ENOMEM; |
| } |
| } else { |
| pr_err("%s: Write performed in invalid state %d\n", |
| __func__, audio->state); |
| |
| rc = -EPERM; |
| } |
| |
| mutex_unlock(&audio->in_lock); |
| } else if (rc == 0) { |
| pr_err("%s: No free DL buffs\n", __func__); |
| |
| rc = -ETIMEDOUT; |
| } else { |
| pr_err("%s: write was interrupted\n", __func__); |
| |
| rc = -ERESTARTSYS; |
| } |
| |
| return rc; |
| } |
| |
| static long audio_mvs_ioctl(struct file *file, |
| unsigned int cmd, |
| unsigned long arg) |
| { |
| int rc = 0; |
| struct audio_mvs_info_type *audio = file->private_data; |
| |
| pr_info("%s:\n", __func__); |
| |
| switch (cmd) { |
| case AUDIO_GET_MVS_CONFIG: { |
| struct msm_audio_mvs_config config; |
| |
| pr_info("%s: IOCTL GET_MVS_CONFIG\n", __func__); |
| |
| mutex_lock(&audio->lock); |
| |
| config.mvs_mode = audio->mvs_mode; |
| config.rate_type = audio->rate_type; |
| config.dtx_mode = audio->dtx_mode; |
| config.min_max_rate.min_rate = audio->min_max_rate.min_rate; |
| config.min_max_rate.max_rate = audio->min_max_rate.max_rate; |
| mutex_unlock(&audio->lock); |
| |
| rc = copy_to_user((void *)arg, &config, sizeof(config)); |
| if (rc == 0) |
| rc = sizeof(config); |
| else |
| pr_err("%s: Config copy failed %d\n", __func__, rc); |
| |
| break; |
| } |
| |
| case AUDIO_SET_MVS_CONFIG: { |
| struct msm_audio_mvs_config config; |
| |
| pr_info("%s: IOCTL SET_MVS_CONFIG\n", __func__); |
| |
| rc = copy_from_user(&config, (void *)arg, sizeof(config)); |
| if (rc == 0) { |
| mutex_lock(&audio->lock); |
| |
| if (audio->state == AUDIO_MVS_STOPPED) { |
| audio->mvs_mode = config.mvs_mode; |
| audio->rate_type = config.rate_type; |
| audio->dtx_mode = config.dtx_mode; |
| audio->min_max_rate.min_rate = |
| config.min_max_rate.min_rate; |
| audio->min_max_rate.max_rate = |
| config.min_max_rate.max_rate; |
| } else { |
| pr_err("%s: Set confg called in state %d\n", |
| __func__, audio->state); |
| |
| rc = -EPERM; |
| } |
| |
| mutex_unlock(&audio->lock); |
| } else { |
| pr_err("%s: Config copy failed %d\n", __func__, rc); |
| } |
| |
| break; |
| } |
| |
| case AUDIO_START: { |
| pr_info("%s: IOCTL START\n", __func__); |
| |
| mutex_lock(&audio->lock); |
| |
| if (audio->state == AUDIO_MVS_STOPPED) { |
| rc = audio_mvs_start(audio); |
| |
| if (rc != 0) |
| audio_mvs_stop(audio); |
| } else { |
| pr_err("%s: Start called in invalid state %d\n", |
| __func__, audio->state); |
| |
| rc = -EPERM; |
| } |
| |
| mutex_unlock(&audio->lock); |
| |
| break; |
| } |
| |
| case AUDIO_STOP: { |
| pr_info("%s: IOCTL STOP\n", __func__); |
| |
| mutex_lock(&audio->lock); |
| |
| if (audio->state == AUDIO_MVS_STARTED) { |
| rc = audio_mvs_stop(audio); |
| } else { |
| pr_err("%s: Stop called in invalid state %d\n", |
| __func__, audio->state); |
| |
| rc = -EPERM; |
| } |
| |
| mutex_unlock(&audio->lock); |
| |
| break; |
| } |
| |
| default: { |
| pr_err("%s: Unknown IOCTL %d\n", __func__, cmd); |
| } |
| } |
| |
| return rc; |
| } |
| |
| static const struct file_operations audio_mvs_fops = { |
| .owner = THIS_MODULE, |
| .open = audio_mvs_open, |
| .release = audio_mvs_release, |
| .read = audio_mvs_read, |
| .write = audio_mvs_write, |
| .unlocked_ioctl = audio_mvs_ioctl |
| }; |
| |
| struct miscdevice audio_mvs_misc = { |
| .minor = MISC_DYNAMIC_MINOR, |
| .name = "msm_mvs", |
| .fops = &audio_mvs_fops |
| }; |
| |
| static int __init audio_mvs_init(void) |
| { |
| int rc = 0; |
| |
| memset(&audio_mvs_info, 0, sizeof(audio_mvs_info)); |
| |
| init_waitqueue_head(&audio_mvs_info.in_wait); |
| init_waitqueue_head(&audio_mvs_info.out_wait); |
| |
| mutex_init(&audio_mvs_info.lock); |
| mutex_init(&audio_mvs_info.in_lock); |
| mutex_init(&audio_mvs_info.out_lock); |
| |
| spin_lock_init(&audio_mvs_info.dsp_lock); |
| |
| INIT_LIST_HEAD(&audio_mvs_info.in_queue); |
| INIT_LIST_HEAD(&audio_mvs_info.free_in_queue); |
| INIT_LIST_HEAD(&audio_mvs_info.out_queue); |
| INIT_LIST_HEAD(&audio_mvs_info.free_out_queue); |
| |
| wake_lock_init(&audio_mvs_info.suspend_lock, |
| WAKE_LOCK_SUSPEND, |
| "audio_mvs_suspend"); |
| wake_lock_init(&audio_mvs_info.idle_lock, |
| WAKE_LOCK_IDLE, |
| "audio_mvs_idle"); |
| |
| rc = misc_register(&audio_mvs_misc); |
| |
| return rc; |
| } |
| |
| static void __exit audio_mvs_exit(void){ |
| pr_info("%s:\n", __func__); |
| |
| misc_deregister(&audio_mvs_misc); |
| } |
| |
| module_init(audio_mvs_init); |
| module_exit(audio_mvs_exit); |
| |
| MODULE_DESCRIPTION("MSM MVS driver"); |
| MODULE_LICENSE("GPL v2"); |