blob: b58d3a2b6c2ed2869a08a6b9de99d0be65dd642c [file] [log] [blame]
/* Copyright (c) 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/debugfs.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/kthread.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/msm_audio.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <mach/debug_mm.h>
#include <mach/msm_rpcrouter.h>
#define SND_VOC_PCM_INTERFACE_PROG 0x30000002
#define SND_VOC_PCM_INTERFACE_VERS 0x00020004
/* Supply always 160 words of PCM samples (20ms data) */
#define MAX_VOC_FRAME_SIZE 160
#define VOC_FRAME_DURATION 20
/* Buffering for Maximum 8 frames between userspace and driver */
#define MAX_VOC_FRAMES 8
#define BUFSZ ((MAX_VOC_FRAME_SIZE*2)*MAX_VOC_FRAMES)
#define SND_VOC_PCM_CLIENT_INPUT_FN_TYPE_PROC 3
#define SND_VOC_REGISTER_PCM_INPUT_CLIENT_PROC 24
#define START_CALLBACK_ID 0x12345678
#define STOP_CALLBACK_ID 0xffffffff
#define MAX_WAIT_CONSUME (MAX_VOC_FRAMES * VOC_FRAME_DURATION)
/* PCM Interfaces */
enum voice_pcm_interface_type {
VOICE_PCM_INTERFACE_TX_INPUT = 3, /* PCM Inject input to PreProc */
};
enum voice_pcm_interface_reg_status_type {
SUCCESS = 0, /* Success 0, else failure */
};
/* status used by PCM input callbacks to indicate availability of PCM Data */
enum voice_pcm_data_status_type {
VOICE_PCM_DATA_STATUS_AVAILABLE, /* Data available for PCM input */
VOICE_PCM_DATA_STATUS_UNAVAILABLE, /* Data not available */
VOICE_PCM_DATA_STATUS_MAX
};
/* Argument needed to register PCM input client */
struct snd_voice_pcm_interface_ipclnt_reg_args {
/* Interface number specifies the PCM inject point */
enum voice_pcm_interface_type interface;
/* Non-NULL indicates start,NULL indicates stop */
uint32_t callback_id;
};
struct snd_voice_pcm_interface_ipclnt_reg_status {
enum voice_pcm_interface_reg_status_type status;
};
struct snd_voice_pcm_interface_ipclnt_fn_type_args {
uint32_t callback_id;
uint32_t pcm_data_ptr_not_null;
uint32_t pcm_data_max_length;
};
struct snd_voice_pcm_interface_ipclnt_fn_type_reply {
enum voice_pcm_data_status_type status;
struct {
uint32_t pcm_data_len;
struct {
uint16_t pcm_data_ignore;
uint16_t pcm_data_valid;
} pcm_data_val[MAX_VOC_FRAME_SIZE];
} pcm_data;
};
struct buffer {
void *data;
unsigned size;
unsigned used;
};
struct audio {
struct buffer out[MAX_VOC_FRAMES];
uint8_t out_head;
uint8_t out_tail;
atomic_t out_bytes;
/* data allocated for various buffers */
char *data;
struct mutex lock;
struct mutex write_lock;
wait_queue_head_t wait;
wait_queue_head_t stop_wait;
int buffer_finished;
int opened;
int enabled;
int running;
int stopped; /* set when stopped */
struct msm_rpc_client *client;
};
static struct audio the_audio;
static int snd_voice_pcm_interface_ipclnt_reg_args(
struct msm_rpc_client *client, void *buf, void *data)
{
struct snd_voice_pcm_interface_ipclnt_reg_args *arg;
int size = 0;
arg = (struct snd_voice_pcm_interface_ipclnt_reg_args *)data;
*((int *)buf) = cpu_to_be32(arg->interface);
size += sizeof(int);
buf += sizeof(int);
*((int *)buf) = cpu_to_be32(arg->callback_id);
size += sizeof(int);
return size;
}
static int snd_voice_pcm_interface_ipclnt_reg_status(
struct msm_rpc_client *client, void *buf, void *data)
{
struct snd_voice_pcm_interface_ipclnt_reg_status *result =
(struct snd_voice_pcm_interface_ipclnt_reg_status *)buf;
*((int *)data) = be32_to_cpu(result->status);
return 0;
}
static void process_callback(struct audio *audio,
void *buffer, int in_size)
{
uint32_t accept_status = RPC_ACCEPTSTAT_SUCCESS;
struct rpc_request_hdr *req;
struct snd_voice_pcm_interface_ipclnt_fn_type_args arg, *buf_ptr;
struct snd_voice_pcm_interface_ipclnt_fn_type_reply *reply;
struct buffer *frame;
uint32_t status;
uint32_t pcm_data_len;
req = (struct rpc_request_hdr *)buffer;
buf_ptr = (struct snd_voice_pcm_interface_ipclnt_fn_type_args *)\
(req + 1);
arg.callback_id = be32_to_cpu(buf_ptr->callback_id);
arg.pcm_data_ptr_not_null = be32_to_cpu(buf_ptr->pcm_data_ptr_not_null);
arg.pcm_data_max_length = be32_to_cpu(buf_ptr->pcm_data_max_length);
MM_DBG("callback_id = 0x%8x pcm_data_ptr_not_null = 0x%8x"\
"pcm_data_max_length = 0x%8x\n", arg.callback_id,\
arg.pcm_data_ptr_not_null, arg.pcm_data_max_length);
/* Flag interface as running */
if (!audio->running)
audio->running = 1;
if (!arg.pcm_data_ptr_not_null) {
accept_status = RPC_ACCEPTSTAT_SYSTEM_ERR;
msm_rpc_start_accepted_reply(audio->client,
be32_to_cpu(req->xid), accept_status);
msm_rpc_send_accepted_reply(audio->client, 0);
return;
}
reply = (struct snd_voice_pcm_interface_ipclnt_fn_type_reply *)
msm_rpc_start_accepted_reply(audio->client,
be32_to_cpu(req->xid), accept_status);
frame = audio->out + audio->out_tail;
/* If Data available, send data */
if (frame->used) {
int i;
unsigned short *src = frame->data;
atomic_add(frame->used, &audio->out_bytes);
status = VOICE_PCM_DATA_STATUS_AVAILABLE;
pcm_data_len = MAX_VOC_FRAME_SIZE;
xdr_send_int32(&audio->client->cb_xdr, &status);
xdr_send_int32(&audio->client->cb_xdr, &pcm_data_len);
/* Expected cb_xdr buffer size is more than PCM buffer size */
for (i = 0; i < MAX_VOC_FRAME_SIZE; i++, ++src)
xdr_send_int16(&audio->client->cb_xdr, src);
frame->used = 0;
audio->out_tail = ((++audio->out_tail) % MAX_VOC_FRAMES);
wake_up(&audio->wait);
} else {
status = VOICE_PCM_DATA_STATUS_UNAVAILABLE;
pcm_data_len = 0;
xdr_send_int32(&audio->client->cb_xdr, &status);
xdr_send_int32(&audio->client->cb_xdr, &pcm_data_len);
wake_up(&audio->wait);
/* Flag all buffer completed */
if (audio->stopped) {
audio->buffer_finished = 1;
wake_up(&audio->stop_wait);
}
}
MM_DBG("Provided PCM data = 0x%8x\n", reply->status);
msm_rpc_send_accepted_reply(audio->client, 0);
return;
}
static int pcm_interface_process_callback_routine(struct msm_rpc_client *client,
void *buffer, int in_size)
{
struct rpc_request_hdr *req;
struct audio *audio = &the_audio;
int rc = 0;
req = (struct rpc_request_hdr *)buffer;
MM_DBG("proc id = 0x%8x xid = 0x%8x size = 0x%8x\n",
be32_to_cpu(req->procedure), be32_to_cpu(req->xid), in_size);
switch (be32_to_cpu(req->procedure)) {
/* Procedure which called every 20ms for PCM samples request*/
case SND_VOC_PCM_CLIENT_INPUT_FN_TYPE_PROC:
process_callback(audio, buffer, in_size);
break;
default:
MM_ERR("Not supported proceudure 0x%8x\n",
be32_to_cpu(req->procedure));
/* Not supported RPC Procedure, send nagative code */
msm_rpc_start_accepted_reply(client, be32_to_cpu(req->xid),
RPC_ACCEPTSTAT_PROC_UNAVAIL);
msm_rpc_send_accepted_reply(client, 0);
}
return rc;
}
static void audio_flush(struct audio *audio)
{
int cnt;
for (cnt = 0; cnt < MAX_VOC_FRAMES; cnt++)
audio->out[cnt].used = 0;
audio->out_head = 0;
audio->out_tail = 0;
audio->stopped = 0;
audio->running = 0;
audio->buffer_finished = 0;
}
/* must be called with audio->lock held */
static int audio_enable(struct audio *audio)
{
int rc;
struct snd_voice_pcm_interface_ipclnt_reg_args arg;
struct snd_voice_pcm_interface_ipclnt_reg_status result;
/* voice_pcm_interface_type */
arg.interface = VOICE_PCM_INTERFACE_TX_INPUT;
/* Should be non-zero, unique */
arg.callback_id = START_CALLBACK_ID;
/* Start Voice PCM interface */
rc = msm_rpc_client_req(audio->client,
SND_VOC_REGISTER_PCM_INPUT_CLIENT_PROC,
snd_voice_pcm_interface_ipclnt_reg_args, &arg,
snd_voice_pcm_interface_ipclnt_reg_status,
&result, -1);
MM_DBG("input client registration status rc 0x%8x result 0x%8x\n",
rc, result.status);
/* If error in server side */
if (rc == 0)
if (result.status != SUCCESS)
rc = -ENODEV;
return rc;
}
static int audio_disable(struct audio *audio)
{
int rc;
struct snd_voice_pcm_interface_ipclnt_reg_args arg;
struct snd_voice_pcm_interface_ipclnt_reg_status result;
/* Wait till all buffers consumed to prevent data loss
Also ensure if client stops due to vocoder disable
do not loop forever */
rc = wait_event_interruptible_timeout(audio->stop_wait,
!(audio->running) || (audio->buffer_finished == 1),
msecs_to_jiffies(MAX_WAIT_CONSUME));
if (rc < 0)
return 0;
/* voice_pcm_interface_type */
arg.interface = VOICE_PCM_INTERFACE_TX_INPUT;
arg.callback_id = STOP_CALLBACK_ID; /* Should be zero */
/* Stop Voice PCM interface */
rc = msm_rpc_client_req(audio->client,
SND_VOC_REGISTER_PCM_INPUT_CLIENT_PROC,
snd_voice_pcm_interface_ipclnt_reg_args, &arg,
snd_voice_pcm_interface_ipclnt_reg_status,
&result, -1);
MM_DBG("input client de-registration status rc 0x%8x result 0x%8x\n",
rc, result.status);
/* If error in server side */
if (rc == 0)
if (result.status != SUCCESS)
rc = -ENODEV;
return rc;
}
static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct audio *audio = file->private_data;
int rc = -EINVAL;
if (cmd == AUDIO_GET_STATS) {
struct msm_audio_stats stats;
stats.byte_count = atomic_read(&audio->out_bytes);
if (copy_to_user((void *) arg, &stats, sizeof(stats)))
return -EFAULT;
return 0;
}
mutex_lock(&audio->lock);
switch (cmd) {
case AUDIO_START:
rc = audio_enable(audio);
if (rc == 0)
audio->enabled = 1;
break;
case AUDIO_STOP:
if (audio->enabled) {
audio->stopped = 1;
rc = audio_disable(audio);
if (rc == 0) {
audio->enabled = 0;
audio->running = 0;
wake_up(&audio->wait);
} else
audio->stopped = 0;
}
break;
case AUDIO_SET_CONFIG: {
struct msm_audio_config config;
if (copy_from_user(&config, (void *) arg, sizeof(config))) {
rc = -EFAULT;
break;
}
if (config.type == 0) {
/* Selection for different PCM intect point */
} else {
rc = -EINVAL;
break;
}
rc = 0;
break;
}
case AUDIO_GET_CONFIG: {
struct msm_audio_config config;
config.buffer_size = MAX_VOC_FRAME_SIZE * 2;
config.buffer_count = MAX_VOC_FRAMES;
config.sample_rate = 8000;
config.channel_count = 1;
config.type = 0;
config.unused[0] = 0;
config.unused[1] = 0;
config.unused[2] = 0;
if (copy_to_user((void *) arg, &config, sizeof(config)))
rc = -EFAULT;
else
rc = 0;
break;
}
default: {
rc = -EINVAL;
MM_ERR(" Unsupported ioctl 0x%8x\n", cmd);
}
}
mutex_unlock(&audio->lock);
return rc;
}
static ssize_t audio_read(struct file *file, char __user *buf,
size_t count, loff_t *pos)
{
return -EINVAL;
}
static ssize_t audio_write(struct file *file, const char __user *buf,
size_t count, loff_t *pos)
{
struct audio *audio = file->private_data;
const char __user *start = buf;
struct buffer *frame;
size_t xfer;
int rc = 0;
mutex_lock(&audio->write_lock);
/* Ensure to copy only till frame boundary */
while (count >= (MAX_VOC_FRAME_SIZE*2)) {
frame = audio->out + audio->out_head;
rc = wait_event_interruptible_timeout(audio->wait,\
(frame->used == 0) || (audio->stopped),
msecs_to_jiffies(MAX_WAIT_CONSUME));
if (rc < 0)
break;
if (audio->stopped) {
rc = -EBUSY;
break;
}
if (rc == 0) {
rc = -ETIMEDOUT;
break;
}
xfer = count > frame->size ? frame->size : count;
if (copy_from_user(frame->data, buf, xfer)) {
rc = -EFAULT;
break;
}
frame->used = xfer;
audio->out_head = ((++audio->out_head) % MAX_VOC_FRAMES);
count -= xfer;
buf += xfer;
}
mutex_unlock(&audio->write_lock);
MM_DBG("write done 0x%8x\n", (unsigned int)(buf - start));
if (rc < 0)
return rc;
return buf - start;
}
static int audio_open(struct inode *inode, struct file *file)
{
struct audio *audio = &the_audio;
int rc, cnt;
mutex_lock(&audio->lock);
if (audio->opened) {
MM_ERR("busy as driver already in open state\n");
rc = -EBUSY;
goto done;
}
if (!audio->data) {
audio->data = kmalloc(BUFSZ, GFP_KERNEL);
if (!audio->data) {
MM_ERR("could not allocate buffers\n");
rc = -ENOMEM;
goto done;
}
}
audio->client = msm_rpc_register_client("voice_pcm_interface_client",
SND_VOC_PCM_INTERFACE_PROG,
SND_VOC_PCM_INTERFACE_VERS, 1,
pcm_interface_process_callback_routine);
if (IS_ERR(audio->client)) {
MM_ERR("Failed to register voice pcm interface client"\
"to 0x%8x\n", SND_VOC_PCM_INTERFACE_PROG);
kfree(audio->data);
audio->data = NULL;
rc = -ENODEV;
goto done;
}
MM_INFO("voice pcm client registred %p\n", audio->client);
for (cnt = 0; cnt < MAX_VOC_FRAMES; cnt++) {
audio->out[cnt].data = (audio->data +\
((MAX_VOC_FRAME_SIZE * 2) * cnt));
audio->out[cnt].size = MAX_VOC_FRAME_SIZE * 2;
MM_DBG("data ptr = %p\n", audio->out[cnt].data);
}
file->private_data = audio;
audio_flush(audio);
audio->opened = 1;
rc = 0;
done:
mutex_unlock(&audio->lock);
return rc;
}
static int audio_release(struct inode *inode, struct file *file)
{
struct audio *audio = file->private_data;
mutex_lock(&audio->lock);
if (audio->enabled) {
audio->stopped = 1;
audio_disable(audio);
audio->running = 0;
audio->enabled = 0;
wake_up(&audio->wait);
}
msm_rpc_unregister_client(audio->client);
kfree(audio->data);
audio->data = NULL;
audio->opened = 0;
mutex_unlock(&audio->lock);
return 0;
}
static const struct file_operations audio_fops = {
.owner = THIS_MODULE,
.open = audio_open,
.release = audio_release,
.read = audio_read,
.write = audio_write,
.unlocked_ioctl = audio_ioctl,
};
static struct miscdevice audio_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "snd_pcm_client",
.fops = &audio_fops,
};
static int __init audio_init(void)
{
mutex_init(&the_audio.lock);
mutex_init(&the_audio.write_lock);
init_waitqueue_head(&the_audio.wait);
init_waitqueue_head(&the_audio.stop_wait);
return misc_register(&audio_misc);
}
device_initcall(audio_init);