blob: d748304634d955545c60da90b6dcc144c151d2cc [file] [log] [blame]
/* 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 <linux/pm_qos.h>
#include <mach/qdsp6v2/q6voice.h>
#include <mach/cpuidle.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 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");