/* Copyright (c) 2010-2011, 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 /* 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 pm_qos_request pm_qos_req; 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); pm_qos_update_request(&audio->pm_qos_req, msm_cpuidle_get_deep_idle_latency()); 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. */ pm_qos_update_request(&audio->pm_qos_req, PM_QOS_DEFAULT_VALUE); wake_unlock(&audio->suspend_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"); pm_qos_add_request(&audio_mvs_info.pm_qos_req, PM_QOS_CPU_DMA_LATENCY, PM_QOS_DEFAULT_VALUE); 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");