/* arch/arm/mach-msm/qdsp5/audio_voicememo.c * * Voice Memo device * * Copyright (C) 2008 Google, Inc. * Copyright (C) 2008 HTC Corporation * Copyright (c) 2009-2012, The Linux Foundation. All rights reserved. * * This code is based in part on arch/arm/mach-msm/qdsp5/audio_mp3.c * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * 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. * You should have received a copy of the GNU General Public License * along with this program; if not, you can find it at http://www.fsf.org. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "audmgr.h" #define SND_PROG_VERS "rs30000002:0x00020001" #define SND_PROG 0x30000002 #define SND_VERS_COMP 0x00020001 #define SND_VERS2_COMP 0x00030001 #define SND_VOC_REC_START_PROC 19 #define SND_VOC_REC_STOP_PROC 20 #define SND_VOC_REC_PAUSE_PROC 21 #define SND_VOC_REC_RESUME_PROC 22 #define SND_VOC_REC_PUT_BUF_PROC 23 #define SND_VOC_REC_AV_SYNC_CB_PTR_PROC 9 #define SND_VOC_REC_CB_FUNC_TYPE_PROC 10 #define REC_CLIENT_DATA 0x11223344 #define DATA_CB_FUNC_ID 0x12345678 #define AV_SYNC_CB_FUNC_ID 0x87654321 #define CLIENT_DATA 0xaabbccdd #define RPC_TYPE_REQUEST 0 #define RPC_TYPE_REPLY 1 #define RPC_STATUS_FAILURE 0 #define RPC_STATUS_SUCCESS 1 #define RPC_VERSION 2 #define RPC_COMMON_HDR_SZ (sizeof(uint32_t) * 2) #define RPC_REQUEST_HDR_SZ (sizeof(struct rpc_request_hdr)) #define RPC_REPLY_HDR_SZ (sizeof(uint32_t) * 3) #define RPC_REPLY_SZ (sizeof(uint32_t) * 6) #define MAX_FRAME_SIZE 36 /* QCELP - 36, AMRNB - 32, EVRC - 24 */ #define MAX_REC_BUF_COUNT 5 /* Maximum supported voc rec buffers */ #define MAX_REC_BUF_SIZE (MAX_FRAME_SIZE * 10) #define MAX_VOICEMEMO_BUF_SIZE \ ((MAX_REC_BUF_SIZE)*MAX_REC_BUF_COUNT) /* 5 buffers for 200ms frame */ #define MSM_AUD_BUFFER_UPDATE_WAIT_MS 2000 enum rpc_voc_rec_status_type { RPC_VOC_REC_STAT_SUCCESS = 1, RPC_VOC_REC_STAT_DONE = 2, RPC_VOC_REC_STAT_AUTO_STOP = 4, RPC_VOC_REC_STAT_PAUSED = 8, RPC_VOC_REC_STAT_RESUMED = 16, RPC_VOC_REC_STAT_ERROR = 32, RPC_VOC_REC_STAT_BUFFER_ERROR = 64, RPC_VOC_REC_STAT_INVALID_PARAM = 128, RPC_VOC_REC_STAT_INT_TIME = 256, RPC_VOC_REC_STAT_DATA = 512, RPC_VOC_REC_STAT_NOT_READY = 1024, RPC_VOC_REC_STAT_INFORM_EVRC = 2048, RPC_VOC_REC_STAT_INFORM_13K = 4096, RPC_VOC_REC_STAT_INFORM_AMR = 8192, RPC_VOC_REC_STAT_INFORM_MAX = 65535 }; struct rpc_snd_voc_rec_start_args { uint32_t param_status; /* 1 = valid, 0 = not valid */ uint32_t rec_type; uint32_t rec_interval_ms; uint32_t auto_stop_ms; uint32_t capability; uint32_t max_rate; uint32_t min_rate; uint32_t frame_format; uint32_t dtx_enable; uint32_t data_req_ms; uint32_t rec_client_data; uint32_t cb_func_id; uint32_t sync_cb_func_id; uint32_t client_data; }; struct rpc_snd_voc_rec_put_buf_args { uint32_t buf; uint32_t num_bytes; }; struct snd_voc_rec_start_msg { struct rpc_request_hdr hdr; struct rpc_snd_voc_rec_start_args args; }; struct snd_voc_rec_put_buf_msg { struct rpc_request_hdr hdr; struct rpc_snd_voc_rec_put_buf_args args; }; struct snd_voc_rec_av_sync_cb_func_data { uint32_t sync_cb_func_id; uint32_t status; /* Pointer status (1 = valid, 0 = invalid) */ uint32_t num_samples; uint32_t time_stamp[2]; uint32_t lost_samples; uint32_t frame_index; uint32_t client_data; }; struct snd_voc_rec_cb_func_fw_data { uint32_t fw_ptr_status; /* FW Pointer status (1=valid,0=invalid) */ uint32_t rec_buffer_size; uint32_t data[MAX_REC_BUF_SIZE/4]; uint32_t rec_buffer_size_copy; uint32_t rec_num_frames; /* Number of voice frames */ uint32_t rec_length; /* Valid data in record buffer = * data_req_ms amount of data */ uint32_t client_data; /* A11 rec buffer pointer */ uint32_t rw_ptr_status; /* RW Pointer status (1=valid,0=invalid) */ }; struct snd_voc_rec_cb_func_rw_data { uint32_t fw_ptr_status; /* FW Pointer status (1=valid,0=invalid) */ uint32_t rw_ptr_status; /* RW Pointer status (1=valid,0=invalid) */ uint32_t rec_buffer_size; uint32_t data[MAX_REC_BUF_SIZE/4]; uint32_t rec_buffer_size_copy; uint32_t rec_num_frames; /* Number of voice frames */ uint32_t rec_length; /* Valid data in record buffer = * data_req_ms amount of data */ uint32_t client_data; /* A11 rec buffer pointer */ }; struct snd_voc_rec_data_cb_func_data { uint32_t cb_func_id; uint32_t status; /* Pointer status (1 = valid, 0 = invalid) */ uint32_t rec_status; union { struct snd_voc_rec_cb_func_fw_data fw_data; struct snd_voc_rec_cb_func_rw_data rw_data; } pkt; }; struct buffer { void *data; unsigned size; unsigned used; /* Usage actual recorded data */ unsigned addr; unsigned numframes; }; struct audio_voicememo { uint32_t byte_count; /* Pass statistics to user space for * time stamping */ uint32_t frame_count; int opened; int enabled; int running; int stopped; int pause_resume; uint32_t rpc_prog; uint32_t rpc_ver; uint32_t rpc_xid; uint32_t rpc_status; struct mutex lock; struct mutex read_lock; struct mutex dsp_lock; wait_queue_head_t read_wait; wait_queue_head_t wait; struct buffer in[MAX_REC_BUF_COUNT]; char *rec_buf_ptr; dma_addr_t phys; uint32_t rec_buf_size; uint8_t read_next; /* index to input buffers to be read next */ uint8_t fill_next; /* index to buffer that should be filled as * data comes from A9 */ struct audmgr audmgr; struct msm_audio_voicememo_config voicememo_cfg; struct msm_rpc_endpoint *sndept; struct task_struct *task; }; static struct audio_voicememo the_audio_voicememo; static int audvoicememo_validate_usr_config( struct msm_audio_voicememo_config *config) { int rc = -1; /* error */ if (config->rec_type != RPC_VOC_REC_FORWARD && config->rec_type != RPC_VOC_REC_REVERSE && config->rec_type != RPC_VOC_REC_BOTH) goto done; /* QCELP, EVRC, AMR-NB only */ if (config->capability != RPC_VOC_CAP_IS733 && config->capability != RPC_VOC_CAP_IS127 && config->capability != RPC_VOC_CAP_AMR) goto done; /* QCP, AMR format supported */ if ((config->frame_format != RPC_VOC_PB_NATIVE_QCP) && (config->frame_format != RPC_VOC_PB_AMR)) goto done; if ((config->frame_format == RPC_VOC_PB_AMR) && (config->capability != RPC_VOC_CAP_AMR)) goto done; /* To make sure, max kernel buf size matches * with max data request time */ if (config->data_req_ms > ((MAX_REC_BUF_SIZE/MAX_FRAME_SIZE)*20)) goto done; rc = 0; done: return rc; } static void audvoicememo_flush_buf(struct audio_voicememo *audio) { uint8_t index; for (index = 0; index < MAX_REC_BUF_COUNT; index++) audio->in[index].used = 0; audio->read_next = 0; mutex_lock(&audio->dsp_lock); audio->fill_next = 0; mutex_unlock(&audio->dsp_lock); } static void audvoicememo_ioport_reset(struct audio_voicememo *audio) { /* Make sure read/write thread are free from * sleep and knowing that system is not able * to process io request at the moment */ wake_up(&audio->read_wait); mutex_lock(&audio->read_lock); audvoicememo_flush_buf(audio); mutex_unlock(&audio->read_lock); } /* must be called with audio->lock held */ static int audvoicememo_enable(struct audio_voicememo *audio) { struct audmgr_config cfg; struct snd_voc_rec_put_buf_msg bmsg; struct snd_voc_rec_start_msg msg; uint8_t index; uint32_t offset = 0; int rc; if (audio->enabled) return 0; /* Codec / method configure to audmgr client */ cfg.tx_rate = RPC_AUD_DEF_SAMPLE_RATE_8000; cfg.rx_rate = RPC_AUD_DEF_SAMPLE_RATE_NONE; cfg.def_method = RPC_AUD_DEF_METHOD_RECORD; if (audio->voicememo_cfg.capability == RPC_VOC_CAP_IS733) cfg.codec = RPC_AUD_DEF_CODEC_VOC_13K; else if (audio->voicememo_cfg.capability == RPC_VOC_CAP_IS127) cfg.codec = RPC_AUD_DEF_CODEC_VOC_EVRC; else cfg.codec = RPC_AUD_DEF_CODEC_VOC_AMR; /* RPC_VOC_CAP_AMR */ cfg.snd_method = RPC_SND_METHOD_VOICE; rc = audmgr_enable(&audio->audmgr, &cfg); if (rc < 0) return rc; /* Configure VOC Rec buffer */ for (index = 0; index < MAX_REC_BUF_COUNT; index++) { audio->in[index].data = audio->rec_buf_ptr + offset; audio->in[index].addr = audio->phys + offset; audio->in[index].size = audio->rec_buf_size; audio->in[index].used = 0; audio->in[index].numframes = 0; offset += audio->rec_buf_size; bmsg.args.buf = (uint32_t) audio->in[index].data; bmsg.args.num_bytes = cpu_to_be32(audio->in[index].size); MM_DBG("rec_buf_ptr=0x%8x, rec_buf_size = 0x%8x\n", bmsg.args.buf, bmsg.args.num_bytes); msm_rpc_setup_req(&bmsg.hdr, audio->rpc_prog, audio->rpc_ver, SND_VOC_REC_PUT_BUF_PROC); audio->rpc_xid = bmsg.hdr.xid; audio->rpc_status = RPC_STATUS_FAILURE; msm_rpc_write(audio->sndept, &bmsg, sizeof(bmsg)); rc = wait_event_timeout(audio->wait, audio->rpc_status != RPC_STATUS_FAILURE, 1 * HZ); if (rc == 0) goto err; } /* Start Recording */ msg.args.param_status = cpu_to_be32(0x00000001); msg.args.rec_type = cpu_to_be32(audio->voicememo_cfg.rec_type); msg.args.rec_interval_ms = cpu_to_be32(audio->voicememo_cfg.rec_interval_ms); msg.args.auto_stop_ms = cpu_to_be32(audio->voicememo_cfg.auto_stop_ms); msg.args.capability = cpu_to_be32(audio->voicememo_cfg.capability); msg.args.max_rate = cpu_to_be32(audio->voicememo_cfg.max_rate); msg.args.min_rate = cpu_to_be32(audio->voicememo_cfg.min_rate); msg.args.frame_format = cpu_to_be32(audio->voicememo_cfg.frame_format); msg.args.dtx_enable = cpu_to_be32(audio->voicememo_cfg.dtx_enable); msg.args.data_req_ms = cpu_to_be32(audio->voicememo_cfg.data_req_ms); msg.args.rec_client_data = cpu_to_be32(REC_CLIENT_DATA); msg.args.cb_func_id = cpu_to_be32(DATA_CB_FUNC_ID); msg.args.sync_cb_func_id = cpu_to_be32(AV_SYNC_CB_FUNC_ID); msg.args.client_data = cpu_to_be32(CLIENT_DATA); msm_rpc_setup_req(&msg.hdr, audio->rpc_prog, audio->rpc_ver, SND_VOC_REC_START_PROC); audio->rpc_xid = msg.hdr.xid; audio->rpc_status = RPC_STATUS_FAILURE; msm_rpc_write(audio->sndept, &msg, sizeof(msg)); rc = wait_event_timeout(audio->wait, audio->rpc_status != RPC_STATUS_FAILURE, 1 * HZ); if (rc == 0) goto err; audio->rpc_xid = 0; audio->enabled = 1; return 0; err: audio->rpc_xid = 0; audmgr_disable(&audio->audmgr); MM_ERR("Fail\n"); return -1; } /* must be called with audio->lock held */ static int audvoicememo_disable(struct audio_voicememo *audio) { struct rpc_request_hdr rhdr; int rc = 0; if (audio->enabled) { msm_rpc_setup_req(&rhdr, audio->rpc_prog, audio->rpc_ver, SND_VOC_REC_STOP_PROC); rc = msm_rpc_write(audio->sndept, &rhdr, sizeof(rhdr)); rc = wait_event_timeout(audio->wait, audio->stopped == 1, 1 * HZ); if (rc == 0) audio->stopped = 1; wake_up(&audio->read_wait); audmgr_disable(&audio->audmgr); audio->enabled = 0; } return 0; } /* RPC Reply Generator */ static void rpc_reply(struct msm_rpc_endpoint *ept, uint32_t xid) { int rc = 0; uint8_t reply_buf[sizeof(struct rpc_reply_hdr)]; struct rpc_reply_hdr *reply = (struct rpc_reply_hdr *)reply_buf; MM_DBG("inside\n"); reply->xid = cpu_to_be32(xid); reply->type = cpu_to_be32(RPC_TYPE_REPLY); /* reply */ reply->reply_stat = cpu_to_be32(RPCMSG_REPLYSTAT_ACCEPTED); reply->data.acc_hdr.accept_stat = cpu_to_be32(RPC_ACCEPTSTAT_SUCCESS); reply->data.acc_hdr.verf_flavor = 0; reply->data.acc_hdr.verf_length = 0; rc = msm_rpc_write(ept, reply_buf, sizeof(reply_buf)); if (rc < 0) MM_ERR("could not write RPC response: %d\n", rc); } static void process_rpc_request(uint32_t proc, uint32_t xid, void *data, int len, void *private) { struct audio_voicememo *audio = private; MM_DBG("inside\n"); /* Sending Ack before processing the request * to make sure A9 get response immediate * However, if there is validation of request planned * may be move this reply Ack at the end */ rpc_reply(audio->sndept, xid); switch (proc) { case SND_VOC_REC_AV_SYNC_CB_PTR_PROC: { MM_DBG("AV Sync CB:func_id=0x%8x,status=0x%x\n", be32_to_cpu(( \ (struct snd_voc_rec_av_sync_cb_func_data *)\ data)->sync_cb_func_id),\ be32_to_cpu(( \ (struct snd_voc_rec_av_sync_cb_func_data *)\ data)->status)); break; } case SND_VOC_REC_CB_FUNC_TYPE_PROC: { struct snd_voc_rec_data_cb_func_data *datacb_data = (void *)(data); struct snd_voc_rec_put_buf_msg bmsg; uint32_t rec_status = be32_to_cpu(datacb_data->rec_status); MM_DBG("Data CB:func_id=0x%8x,status=0x%x,\ rec_status=0x%x\n", be32_to_cpu(datacb_data->cb_func_id),\ be32_to_cpu(datacb_data->status),\ be32_to_cpu(datacb_data->rec_status)); /* Data recorded */ if ((rec_status == RPC_VOC_REC_STAT_DATA) || (rec_status == RPC_VOC_REC_STAT_DONE)) { if (datacb_data->pkt.fw_data.fw_ptr_status && be32_to_cpu(datacb_data->pkt.fw_data.rec_length) && be32_to_cpu(datacb_data->pkt.fw_data.rec_length) <= MAX_REC_BUF_SIZE) { MM_DBG("Copy FW link:rec_buf_size \ = 0x%08x, rec_length=0x%08x\n", be32_to_cpu( \ datacb_data->pkt.fw_data. \ rec_buffer_size_copy),\ be32_to_cpu(datacb_data->pkt.fw_data. \ rec_length)); mutex_lock(&audio->dsp_lock); memcpy(audio->in[audio->fill_next].data, \ &(datacb_data->pkt.fw_data.data[0]), \ be32_to_cpu( datacb_data->pkt.fw_data.rec_length)); audio->in[audio->fill_next].used = be32_to_cpu( datacb_data->pkt.fw_data.rec_length); audio->in[audio->fill_next].numframes = be32_to_cpu( datacb_data->pkt.fw_data.rec_num_frames); mutex_unlock(&audio->dsp_lock); } else if (datacb_data->pkt.rw_data.rw_ptr_status && be32_to_cpu(datacb_data->pkt.rw_data.rec_length) && be32_to_cpu(datacb_data->pkt.rw_data.rec_length) <= MAX_REC_BUF_SIZE) { MM_DBG("Copy RW link:rec_buf_size \ =0x%08x, rec_length=0x%08x\n", be32_to_cpu( \ datacb_data->pkt.rw_data. \ rec_buffer_size_copy),\ be32_to_cpu(datacb_data->pkt.rw_data. \ rec_length)); mutex_lock(&audio->dsp_lock); memcpy(audio->in[audio->fill_next].data, \ &(datacb_data->pkt.rw_data.data[0]), \ be32_to_cpu( datacb_data->pkt.rw_data.rec_length)); audio->in[audio->fill_next].used = be32_to_cpu( datacb_data->pkt.rw_data.rec_length); audio->in[audio->fill_next].numframes = be32_to_cpu( datacb_data->pkt.rw_data.rec_num_frames); mutex_unlock(&audio->dsp_lock); } else { MM_ERR("FW: ptr_status %d, rec_length=0x%08x," "RW: ptr_status %d, rec_length=0x%08x\n", datacb_data->pkt.fw_data.fw_ptr_status, \ be32_to_cpu( \ datacb_data->pkt.fw_data.rec_length), \ datacb_data->pkt.rw_data.rw_ptr_status, \ be32_to_cpu( \ datacb_data->pkt.rw_data.rec_length)); } if (rec_status != RPC_VOC_REC_STAT_DONE) { /* Not end of record */ bmsg.args.buf = \ (uint32_t) audio->in[audio->fill_next].data; bmsg.args.num_bytes = \ be32_to_cpu(audio->in[audio->fill_next].size); if (++audio->fill_next == MAX_REC_BUF_COUNT) audio->fill_next = 0; msm_rpc_setup_req(&bmsg.hdr, audio->rpc_prog, audio->rpc_ver, SND_VOC_REC_PUT_BUF_PROC); msm_rpc_write(audio->sndept, &bmsg, sizeof(bmsg)); wake_up(&audio->read_wait); } else { /* Indication record stopped gracefully */ MM_DBG("End Of Voice Record\n"); audio->stopped = 1; wake_up(&audio->wait); } } else if (rec_status == RPC_VOC_REC_STAT_PAUSED) { MM_DBG(" Voice Record PAUSED\n"); audio->pause_resume = 1; } else if (rec_status == RPC_VOC_REC_STAT_RESUMED) { MM_DBG(" Voice Record RESUMED\n"); audio->pause_resume = 0; } else if ((rec_status == RPC_VOC_REC_STAT_ERROR) || (rec_status == RPC_VOC_REC_STAT_INVALID_PARAM) || (rec_status == RPC_VOC_REC_STAT_BUFFER_ERROR)) MM_ERR("error recording =0x%8x\n", rec_status); else if (rec_status == RPC_VOC_REC_STAT_INT_TIME) MM_DBG("Frames recorded matches interval \ callback time\n"); else if (rec_status == RPC_VOC_REC_STAT_AUTO_STOP) { MM_DBG(" Voice Record AUTO STOP\n"); mutex_lock(&audio->lock); audio->stopped = 1; wake_up(&audio->read_wait); audmgr_disable(&audio->audmgr); audvoicememo_ioport_reset(audio); audio->stopped = 0; audio->enabled = 0; mutex_unlock(&audio->lock); } break; } default: MM_ERR("UNKNOWN PROC , proc = 0x%8x \n", proc); } } static int voicememo_rpc_thread(void *data) { struct audio_voicememo *audio = data; struct rpc_request_hdr *hdr = NULL; uint32_t type; int len; MM_DBG("start\n"); while (!kthread_should_stop()) { kfree(hdr); hdr = NULL; len = msm_rpc_read(audio->sndept, (void **) &hdr, -1, -1); MM_DBG("rpc_read len = 0x%x\n", len); if (len < 0) { MM_ERR("rpc read failed (%d)\n", len); break; } if (len < RPC_COMMON_HDR_SZ) continue; type = be32_to_cpu(hdr->type); if (type == RPC_TYPE_REPLY) { struct rpc_reply_hdr *rep = (void *) hdr; uint32_t status; if (len < RPC_REPLY_HDR_SZ) continue; status = be32_to_cpu(rep->reply_stat); if (status == RPCMSG_REPLYSTAT_ACCEPTED) { status = be32_to_cpu(rep->data.acc_hdr.accept_stat); /* Confirm major RPC success during open*/ if ((audio->enabled == 0) && (status == RPC_ACCEPTSTAT_SUCCESS) && (audio->rpc_xid == rep->xid)) { audio->rpc_status = \ RPC_STATUS_SUCCESS; wake_up(&audio->wait); } MM_DBG("rpc_reply status 0x%8x\n", status); } else { MM_ERR("rpc_reply denied!\n"); } /* process reply */ continue; } else if (type == RPC_TYPE_REQUEST) { if (len < RPC_REQUEST_HDR_SZ) continue; process_rpc_request(be32_to_cpu(hdr->procedure), be32_to_cpu(hdr->xid), (void *) (hdr + 1), len - sizeof(*hdr), audio); } else MM_ERR("Unexpected type (%d)\n", type); } MM_DBG("stop\n"); kfree(hdr); hdr = NULL; return 0; } /* ------------------- device --------------------- */ static long audio_voicememo_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct audio_voicememo *audio = file->private_data; int rc = 0; if (cmd == AUDIO_GET_STATS) { struct msm_audio_stats stats; mutex_lock(&audio->dsp_lock); stats.byte_count = audio->byte_count; stats.sample_count = audio->frame_count; mutex_unlock(&audio->dsp_lock); if (copy_to_user((void *) arg, &stats, sizeof(stats))) return -EFAULT; return 0; } mutex_lock(&audio->lock); switch (cmd) { case AUDIO_START: { MM_DBG("AUDIO_START\n"); audio->byte_count = 0; audio->frame_count = 0; if (audio->voicememo_cfg.rec_type != RPC_VOC_REC_NONE) rc = audvoicememo_enable(audio); else rc = -EINVAL; MM_DBG("AUDIO_START rc %d\n", rc); break; } case AUDIO_STOP: { MM_DBG("AUDIO_STOP\n"); rc = audvoicememo_disable(audio); audvoicememo_ioport_reset(audio); audio->stopped = 0; MM_DBG("AUDIO_STOP rc %d\n", rc); break; } case AUDIO_GET_CONFIG: { struct msm_audio_config cfg; MM_DBG("AUDIO_GET_CONFIG\n"); cfg.buffer_size = audio->rec_buf_size; cfg.buffer_count = MAX_REC_BUF_COUNT; cfg.sample_rate = 8000; /* Voice Encoder works on 8k, * Mono */ cfg.channel_count = 1; cfg.type = 0; cfg.unused[0] = 0; cfg.unused[1] = 0; cfg.unused[2] = 0; if (copy_to_user((void *) arg, &cfg, sizeof(cfg))) rc = -EFAULT; else rc = 0; MM_DBG("AUDIO_GET_CONFIG rc %d\n", rc); break; } case AUDIO_GET_VOICEMEMO_CONFIG: { MM_DBG("AUDIO_GET_VOICEMEMO_CONFIG\n"); if (copy_to_user((void *)arg, &audio->voicememo_cfg, sizeof(audio->voicememo_cfg))) rc = -EFAULT; else rc = 0; MM_DBG("AUDIO_GET_VOICEMEMO_CONFIG rc %d\n", rc); break; } case AUDIO_SET_VOICEMEMO_CONFIG: { struct msm_audio_voicememo_config usr_config; MM_DBG("AUDIO_SET_VOICEMEMO_CONFIG\n"); if (copy_from_user (&usr_config, (void *)arg, sizeof(usr_config))) { rc = -EFAULT; break; } if (audvoicememo_validate_usr_config(&usr_config) == 0) { audio->voicememo_cfg = usr_config; rc = 0; } else rc = -EINVAL; MM_DBG("AUDIO_SET_VOICEMEMO_CONFIG rc %d\n", rc); break; } case AUDIO_PAUSE: { struct rpc_request_hdr rhdr; MM_DBG("AUDIO_PAUSE\n"); if (arg == 1) msm_rpc_setup_req(&rhdr, audio->rpc_prog, audio->rpc_ver, SND_VOC_REC_PAUSE_PROC); else msm_rpc_setup_req(&rhdr, audio->rpc_prog, audio->rpc_ver, SND_VOC_REC_RESUME_PROC); rc = msm_rpc_write(audio->sndept, &rhdr, sizeof(rhdr)); MM_DBG("AUDIO_PAUSE exit %d\n", rc); break; } default: MM_ERR("IOCTL %d not supported\n", cmd); rc = -EINVAL; } mutex_unlock(&audio->lock); return rc; } static ssize_t audio_voicememo_read(struct file *file, char __user *buf, size_t count, loff_t *pos) { struct audio_voicememo *audio = file->private_data; const char __user *start = buf; int rc = 0; mutex_lock(&audio->read_lock); MM_DBG("buff read =0x%8x \n", count); while (count > 0) { rc = wait_event_interruptible_timeout(audio->read_wait, (audio->in[audio->read_next].used > 0) || (audio->stopped), msecs_to_jiffies(MSM_AUD_BUFFER_UPDATE_WAIT_MS)); if (rc == 0) { rc = -ETIMEDOUT; break; } else if (rc < 0) break; if (audio->stopped) { rc = -EBUSY; break; } if (count < audio->in[audio->read_next].used) { /* Read must happen in frame boundary. Since driver does * not split frames, read count must be greater or * equal to size of existing frames to copy */ MM_DBG("read not in frame boundary\n"); break; } else { mutex_lock(&audio->dsp_lock); dma_coherent_post_ops(); if (copy_to_user (buf, audio->in[audio->read_next].data, audio->in[audio->read_next].used)) { MM_ERR("invalid addr %x \n", (unsigned int)buf); rc = -EFAULT; mutex_unlock(&audio->dsp_lock); break; } count -= audio->in[audio->read_next].used; audio->byte_count += audio->in[audio->read_next].used; audio->frame_count += audio->in[audio->read_next].numframes; buf += audio->in[audio->read_next].used; audio->in[audio->read_next].used = 0; mutex_unlock(&audio->dsp_lock); if ((++audio->read_next) == MAX_REC_BUF_COUNT) audio->read_next = 0; if (audio->in[audio->read_next].used == 0) break; /* No data ready at this moment * Exit while loop to prevent * output thread sleep too long */ } } mutex_unlock(&audio->read_lock); if (buf > start) rc = buf - start; MM_DBG("exit return =0x%8x\n", rc); return rc; } static ssize_t audio_voicememo_write(struct file *file, const char __user *buf, size_t count, loff_t *pos) { return -EINVAL; } static int audio_voicememo_release(struct inode *inode, struct file *file) { struct audio_voicememo *audio = file->private_data; mutex_lock(&audio->lock); audvoicememo_disable(audio); audvoicememo_flush_buf(audio); audio->opened = 0; mutex_unlock(&audio->lock); return 0; } static int audio_voicememo_open(struct inode *inode, struct file *file) { struct audio_voicememo *audio = &the_audio_voicememo; int rc; mutex_lock(&audio->lock); if (audio->opened) { rc = -EBUSY; goto done; } rc = audmgr_open(&audio->audmgr); if (rc) goto done; /*Set default param to None*/ memset(&audio->voicememo_cfg, 0, sizeof(audio->voicememo_cfg)); file->private_data = audio; audio->opened = 1; audio->stopped = 0; rc = 0; done: mutex_unlock(&audio->lock); return rc; } static const struct file_operations audio_fops = { .owner = THIS_MODULE, .open = audio_voicememo_open, .release = audio_voicememo_release, .read = audio_voicememo_read, .write = audio_voicememo_write, .unlocked_ioctl = audio_voicememo_ioctl, }; struct miscdevice audio_voicememo_misc = { .minor = MISC_DYNAMIC_MINOR, .name = "msm_voicememo", .fops = &audio_fops, }; static int audio_voicememo_probe(struct platform_device *pdev) { int rc; if ((pdev->id != (SND_VERS_COMP & RPC_VERSION_MAJOR_MASK)) && (pdev->id != (SND_VERS2_COMP & RPC_VERSION_MAJOR_MASK))) return -EINVAL; mutex_init(&the_audio_voicememo.lock); mutex_init(&the_audio_voicememo.read_lock); mutex_init(&the_audio_voicememo.dsp_lock); init_waitqueue_head(&the_audio_voicememo.read_wait); init_waitqueue_head(&the_audio_voicememo.wait); the_audio_voicememo.rec_buf_ptr = dma_alloc_coherent(NULL, MAX_VOICEMEMO_BUF_SIZE, &the_audio_voicememo.phys, GFP_KERNEL); if (the_audio_voicememo.rec_buf_ptr == NULL) { MM_ERR("error allocating memory\n"); rc = -ENOMEM; return rc; } the_audio_voicememo.rec_buf_size = MAX_REC_BUF_SIZE; MM_DBG("rec_buf_ptr = 0x%8x, phys = 0x%8x \n", (uint32_t) the_audio_voicememo.rec_buf_ptr, \ the_audio_voicememo.phys); the_audio_voicememo.sndept = msm_rpc_connect_compatible(SND_PROG, SND_VERS_COMP, MSM_RPC_UNINTERRUPTIBLE); if (IS_ERR(the_audio_voicememo.sndept)) { MM_DBG("connect failed with VERS \ = %x, trying again with another API\n", SND_VERS_COMP); the_audio_voicememo.sndept = msm_rpc_connect_compatible( SND_PROG, SND_VERS2_COMP, MSM_RPC_UNINTERRUPTIBLE); if (IS_ERR(the_audio_voicememo.sndept)) { rc = PTR_ERR(the_audio_voicememo.sndept); the_audio_voicememo.sndept = NULL; MM_ERR("Failed to connect to snd svc\n"); goto err; } the_audio_voicememo.rpc_ver = SND_VERS2_COMP; } else the_audio_voicememo.rpc_ver = SND_VERS_COMP; the_audio_voicememo.task = kthread_run(voicememo_rpc_thread, &the_audio_voicememo, "voicememo_rpc"); if (IS_ERR(the_audio_voicememo.task)) { rc = PTR_ERR(the_audio_voicememo.task); the_audio_voicememo.task = NULL; msm_rpc_close(the_audio_voicememo.sndept); the_audio_voicememo.sndept = NULL; MM_ERR("Failed to create voicememo_rpc task\n"); goto err; } the_audio_voicememo.rpc_prog = SND_PROG; return misc_register(&audio_voicememo_misc); err: dma_free_coherent(NULL, MAX_VOICEMEMO_BUF_SIZE, the_audio_voicememo.rec_buf_ptr, the_audio_voicememo.phys); the_audio_voicememo.rec_buf_ptr = NULL; return rc; } static void __exit audio_voicememo_exit(void) { /* Close the RPC connection to make thread to comeout */ msm_rpc_close(the_audio_voicememo.sndept); the_audio_voicememo.sndept = NULL; kthread_stop(the_audio_voicememo.task); the_audio_voicememo.task = NULL; if (the_audio_voicememo.rec_buf_ptr) dma_free_coherent(NULL, MAX_VOICEMEMO_BUF_SIZE, the_audio_voicememo.rec_buf_ptr, the_audio_voicememo.phys); the_audio_voicememo.rec_buf_ptr = NULL; misc_deregister(&audio_voicememo_misc); } static char audio_voicememo_rpc_name[] = "rs00000000"; static struct platform_driver audio_voicememo_driver = { .probe = audio_voicememo_probe, .driver = { .owner = THIS_MODULE, }, }; static int __init audio_voicememo_init(void) { snprintf(audio_voicememo_rpc_name, sizeof(audio_voicememo_rpc_name), "rs%08x", SND_PROG); audio_voicememo_driver.driver.name = audio_voicememo_rpc_name; return platform_driver_register(&audio_voicememo_driver); } module_init(audio_voicememo_init); module_exit(audio_voicememo_exit); MODULE_DESCRIPTION("MSM Voice Memo driver"); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("QUALCOMM");