Initial Contribution
msm-2.6.38: tag AU_LINUX_ANDROID_GINGERBREAD.02.03.04.00.142
Signed-off-by: Bryan Huntsman <bryanh@codeaurora.org>
diff --git a/arch/arm/mach-msm/qdsp6v2/audio_multi_aac.c b/arch/arm/mach-msm/qdsp6v2/audio_multi_aac.c
new file mode 100644
index 0000000..7423950
--- /dev/null
+++ b/arch/arm/mach-msm/qdsp6v2/audio_multi_aac.c
@@ -0,0 +1,1673 @@
+/* aac audio output device
+ *
+ * Copyright (C) 2008 Google, Inc.
+ * Copyright (C) 2008 HTC Corporation
+ * Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/fs.h>
+#include <linux/module.h>
+#include <linux/miscdevice.h>
+#include <linux/mutex.h>
+#include <linux/sched.h>
+#include <linux/uaccess.h>
+#include <linux/wait.h>
+#include <linux/msm_audio.h>
+#include <linux/msm_audio_aac.h>
+#include <linux/debugfs.h>
+#include <linux/list.h>
+#include <linux/android_pmem.h>
+#include <linux/slab.h>
+#include <asm/ioctls.h>
+#include <asm/atomic.h>
+#include <sound/apr_audio.h>
+#include <sound/q6asm.h>
+
+#define ADRV_STATUS_AIO_INTF 0x00000001 /* AIO interface */
+#define ADRV_STATUS_FSYNC 0x00000008
+#define ADRV_STATUS_PAUSE 0x00000010
+
+#define TUNNEL_MODE 0x0000
+#define NON_TUNNEL_MODE 0x0001
+#define AUDAAC_EOS_SET 0x00000001
+
+/* Default number of pre-allocated event packets */
+#define AUDAAC_EVENT_NUM 10
+
+#define __CONTAINS(r, v, l) ({ \
+ typeof(r) __r = r; \
+ typeof(v) __v = v; \
+ typeof(v) __e = __v + l; \
+ int res = ((__v >= __r->vaddr) && \
+ (__e <= __r->vaddr + __r->len)); \
+ res; \
+})
+
+#define CONTAINS(r1, r2) ({ \
+ typeof(r2) __r2 = r2; \
+ __CONTAINS(r1, __r2->vaddr, __r2->len); \
+})
+
+#define IN_RANGE(r, v) ({ \
+ typeof(r) __r = r; \
+ typeof(v) __vv = v; \
+ int res = ((__vv >= __r->vaddr) && \
+ (__vv < (__r->vaddr + __r->len))); \
+ res; \
+})
+
+#define OVERLAPS(r1, r2) ({ \
+ typeof(r1) __r1 = r1; \
+ typeof(r2) __r2 = r2; \
+ typeof(__r2->vaddr) __v = __r2->vaddr; \
+ typeof(__v) __e = __v + __r2->len - 1; \
+ int res = (IN_RANGE(__r1, __v) || IN_RANGE(__r1, __e)); \
+ res; \
+})
+
+struct timestamp {
+ unsigned long lowpart;
+ unsigned long highpart;
+} __packed;
+
+struct meta_in {
+ unsigned char reserved[18];
+ unsigned short offset;
+ struct timestamp ntimestamp;
+ unsigned int nflags;
+} __packed;
+
+struct meta_out_dsp {
+ u32 offset_to_frame;
+ u32 frame_size;
+ u32 encoded_pcm_samples;
+ u32 msw_ts;
+ u32 lsw_ts;
+ u32 nflags;
+} __packed;
+
+struct dec_meta_out {
+ unsigned int reserved[7];
+ unsigned int num_of_frames;
+ struct meta_out_dsp meta_out_dsp[];
+} __packed;
+
+/* General meta field to store meta info
+locally */
+union meta_data {
+ struct dec_meta_out meta_out;
+ struct meta_in meta_in;
+} __packed;
+
+struct audaac_event {
+ struct list_head list;
+ int event_type;
+ union msm_audio_event_payload payload;
+};
+
+struct audaac_pmem_region {
+ struct list_head list;
+ struct file *file;
+ int fd;
+ void *vaddr;
+ unsigned long paddr;
+ unsigned long kvaddr;
+ unsigned long len;
+ unsigned ref_cnt;
+};
+
+struct audaac_buffer_node {
+ struct list_head list;
+ struct msm_audio_aio_buf buf;
+ unsigned long paddr;
+ unsigned long token;
+ void *kvaddr;
+ union meta_data meta_info;
+};
+
+struct q6audio;
+
+struct audaac_drv_operations {
+ void (*out_flush) (struct q6audio *);
+ void (*in_flush) (struct q6audio *);
+ int (*fsync)(struct q6audio *);
+};
+
+#define PCM_BUF_COUNT (2)
+/* Buffer with meta */
+#define PCM_BUFSZ_MIN ((8192) + sizeof(struct dec_meta_out))
+
+/* FRAME_NUM must be a power of two */
+#define FRAME_NUM (2)
+#define FRAME_SIZE ((4*1536) + sizeof(struct meta_in))
+
+struct q6audio {
+ atomic_t in_bytes;
+ atomic_t in_samples;
+
+ struct msm_audio_stream_config str_cfg;
+ struct msm_audio_buf_cfg buf_cfg;
+ struct msm_audio_config pcm_cfg;
+ struct msm_audio_aac_config aac_config;
+
+ struct audio_client *ac;
+
+ struct mutex lock;
+ struct mutex read_lock;
+ struct mutex write_lock;
+ struct mutex get_event_lock;
+ wait_queue_head_t cmd_wait;
+ wait_queue_head_t write_wait;
+ wait_queue_head_t event_wait;
+ spinlock_t dsp_lock;
+ spinlock_t event_queue_lock;
+
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *dentry;
+#endif
+ struct list_head out_queue; /* queue to retain output buffers */
+ struct list_head in_queue; /* queue to retain input buffers */
+ struct list_head free_event_queue;
+ struct list_head event_queue;
+ struct list_head pmem_region_queue; /* protected by lock */
+ struct audaac_drv_operations drv_ops;
+ union msm_audio_event_payload eos_write_payload;
+
+ uint32_t drv_status;
+ int event_abort;
+ int eos_rsp;
+ int eos_flag;
+ int opened;
+ int enabled;
+ int stopped;
+ int feedback;
+ int rflush; /* Read flush */
+ int wflush; /* Write flush */
+};
+
+static int insert_eos_buf(struct q6audio *audio,
+ struct audaac_buffer_node *buf_node) {
+ struct dec_meta_out *eos_buf = buf_node->kvaddr;
+ eos_buf->num_of_frames = 0xFFFFFFFF;
+ eos_buf->meta_out_dsp[0].offset_to_frame = 0x0;
+ eos_buf->meta_out_dsp[0].nflags = AUDAAC_EOS_SET;
+ return sizeof(struct dec_meta_out) +
+ sizeof(eos_buf->meta_out_dsp[0]);
+}
+
+/* Routine which updates read buffers of driver/dsp,
+ for flush operation as DSP output might not have proper
+ value set */
+static int insert_meta_data(struct q6audio *audio,
+ struct audaac_buffer_node *buf_node) {
+ struct dec_meta_out *meta_data = buf_node->kvaddr;
+ meta_data->num_of_frames = 0x0;
+ meta_data->meta_out_dsp[0].offset_to_frame = 0x0;
+ meta_data->meta_out_dsp[0].nflags = 0x0;
+ return sizeof(struct dec_meta_out) +
+ sizeof(meta_data->meta_out_dsp[0]);
+}
+
+static void extract_meta_info(struct q6audio *audio,
+ struct audaac_buffer_node *buf_node, int dir)
+{
+ if (dir) { /* input buffer - Write */
+ if (audio->buf_cfg.meta_info_enable)
+ memcpy(&buf_node->meta_info.meta_in,
+ (char *)buf_node->kvaddr, sizeof(struct meta_in));
+ else
+ memset(&buf_node->meta_info.meta_in,
+ 0, sizeof(struct meta_in));
+ pr_debug("i/p: msw_ts 0x%lx lsw_ts 0x%lx nflags 0x%8x\n",
+ buf_node->meta_info.meta_in.ntimestamp.highpart,
+ buf_node->meta_info.meta_in.ntimestamp.lowpart,
+ buf_node->meta_info.meta_in.nflags);
+ } else { /* output buffer - Read */
+ memcpy((char *)buf_node->kvaddr,
+ &buf_node->meta_info.meta_out,
+ sizeof(struct dec_meta_out));
+ pr_debug("o/p: msw_ts 0x%8x lsw_ts 0x%8x nflags 0x%8x,"
+ "num_frames = %d\n",
+ ((struct dec_meta_out *)buf_node->kvaddr)->\
+ meta_out_dsp[0].msw_ts,
+ ((struct dec_meta_out *)buf_node->kvaddr)->\
+ meta_out_dsp[0].lsw_ts,
+ ((struct dec_meta_out *)buf_node->kvaddr)->\
+ meta_out_dsp[0].nflags,
+ ((struct dec_meta_out *)buf_node->kvaddr)->num_of_frames);
+ }
+}
+
+static int audaac_pmem_lookup_vaddr(struct q6audio *audio, void *addr,
+ unsigned long len, struct audaac_pmem_region **region)
+{
+ struct audaac_pmem_region *region_elt;
+
+ int match_count = 0;
+
+ *region = NULL;
+
+ /* returns physical address or zero */
+ list_for_each_entry(region_elt, &audio->pmem_region_queue, list) {
+ if (addr >= region_elt->vaddr &&
+ addr < region_elt->vaddr + region_elt->len &&
+ addr + len <= region_elt->vaddr + region_elt->len) {
+ /* offset since we could pass vaddr inside a registerd
+ * pmem buffer
+ */
+
+ match_count++;
+ if (!*region)
+ *region = region_elt;
+ }
+ }
+
+ if (match_count > 1) {
+ pr_err("multiple hits for vaddr %p, len %ld\n", addr, len);
+ list_for_each_entry(region_elt, &audio->pmem_region_queue,
+ list) {
+ if (addr >= region_elt->vaddr &&
+ addr < region_elt->vaddr + region_elt->len &&
+ addr + len <= region_elt->vaddr + region_elt->len)
+ pr_err("\t%p, %ld --> %p\n", region_elt->vaddr,
+ region_elt->len,
+ (void *)region_elt->paddr);
+ }
+ }
+
+ return *region ? 0 : -1;
+}
+
+static unsigned long audaac_pmem_fixup(struct q6audio *audio, void *addr,
+ unsigned long len, int ref_up, void **kvaddr)
+{
+ struct audaac_pmem_region *region;
+ unsigned long paddr;
+ int ret;
+
+ ret = audaac_pmem_lookup_vaddr(audio, addr, len, ®ion);
+ if (ret) {
+ pr_err("lookup (%p, %ld) failed\n", addr, len);
+ return 0;
+ }
+ if (ref_up)
+ region->ref_cnt++;
+ else
+ region->ref_cnt--;
+ pr_debug("found region %p ref_cnt %d\n", region, region->ref_cnt);
+ paddr = region->paddr + (addr - region->vaddr);
+ /* provide kernel virtual address for accessing meta information */
+ if (kvaddr)
+ *kvaddr = (void *) (region->kvaddr + (addr - region->vaddr));
+ return paddr;
+}
+
+static void audaac_post_event(struct q6audio *audio, int type,
+ union msm_audio_event_payload payload)
+{
+ struct audaac_event *e_node = NULL;
+ unsigned long flags;
+
+ spin_lock_irqsave(&audio->event_queue_lock, flags);
+
+ if (!list_empty(&audio->free_event_queue)) {
+ e_node = list_first_entry(&audio->free_event_queue,
+ struct audaac_event, list);
+ list_del(&e_node->list);
+ } else {
+ e_node = kmalloc(sizeof(struct audaac_event), GFP_ATOMIC);
+ if (!e_node) {
+ pr_err("No mem to post event %d\n", type);
+ spin_unlock_irqrestore(&audio->event_queue_lock, flags);
+ return;
+ }
+ }
+
+ e_node->event_type = type;
+ e_node->payload = payload;
+
+ list_add_tail(&e_node->list, &audio->event_queue);
+ spin_unlock_irqrestore(&audio->event_queue_lock, flags);
+ wake_up(&audio->event_wait);
+}
+
+static int audaac_enable(struct q6audio *audio)
+{
+ /* 2nd arg: 0 -> run immediately
+ 3rd arg: 0 -> msw_ts, 4th arg: 0 ->lsw_ts */
+ return q6asm_run(audio->ac, 0x00, 0x00, 0x00);
+}
+
+static int audaac_disable(struct q6audio *audio)
+{
+ int rc = 0;
+ if (audio->opened) {
+ audio->enabled = 0;
+ audio->opened = 0;
+ pr_debug("%s: inbytes[%d] insamples[%d]\n", __func__,
+ atomic_read(&audio->in_bytes),
+ atomic_read(&audio->in_samples));
+ /* Close the session */
+ rc = q6asm_cmd(audio->ac, CMD_CLOSE);
+ if (rc < 0)
+ pr_err("Failed to close the session rc=%d\n", rc);
+ audio->stopped = 1;
+ wake_up(&audio->write_wait);
+ wake_up(&audio->cmd_wait);
+ }
+ pr_debug("enabled[%d]\n", audio->enabled);
+ return rc;
+}
+
+static int audaac_pause(struct q6audio *audio)
+{
+ int rc = 0;
+
+ pr_debug("%s, enabled = %d\n", __func__,
+ audio->enabled);
+ if (audio->enabled) {
+ rc = q6asm_cmd(audio->ac, CMD_PAUSE);
+ if (rc < 0)
+ pr_err("%s: pause cmd failed rc=%d\n", __func__, rc);
+
+ } else
+ pr_err("%s: Driver not enabled\n", __func__);
+ return rc;
+}
+
+static int audaac_flush(struct q6audio *audio)
+{
+ int rc;
+
+ if (audio->enabled) {
+ /* Implicitly issue a pause to the decoder before flushing if
+ it is not in pause state */
+ if (!(audio->drv_status & ADRV_STATUS_PAUSE)) {
+ rc = audaac_pause(audio);
+ if (rc < 0)
+ pr_err("%s: pause cmd failed rc=%d\n", __func__,
+ rc);
+ else
+ audio->drv_status |= ADRV_STATUS_PAUSE;
+ }
+ rc = q6asm_cmd(audio->ac, CMD_FLUSH);
+ if (rc < 0)
+ pr_err("%s: flush cmd failed rc=%d\n", __func__, rc);
+ /* Not in stop state, reenable the stream */
+ if (audio->stopped == 0) {
+ rc = audaac_enable(audio);
+ if (rc)
+ pr_err("%s:audio re-enable failed\n", __func__);
+ else {
+ audio->enabled = 1;
+ if (audio->drv_status & ADRV_STATUS_PAUSE)
+ audio->drv_status &= ~ADRV_STATUS_PAUSE;
+ }
+ }
+ }
+ pr_debug("in_bytes %d\n", atomic_read(&audio->in_bytes));
+ pr_debug("in_samples %d\n", atomic_read(&audio->in_samples));
+ atomic_set(&audio->in_bytes, 0);
+ atomic_set(&audio->in_samples, 0);
+ return 0;
+}
+
+static int audaac_outport_flush(struct q6audio *audio)
+{
+ int rc;
+
+ rc = q6asm_cmd(audio->ac, CMD_OUT_FLUSH);
+ if (rc < 0)
+ pr_err("%s: output port flush cmd failed rc=%d\n", __func__,
+ rc);
+ return rc;
+}
+
+static void audaac_async_read(struct q6audio *audio,
+ struct audaac_buffer_node *buf_node)
+{
+ struct audio_client *ac;
+ struct audio_aio_read_param param;
+ int rc;
+
+ pr_debug("%s: Send read buff %p phy %lx len %d\n", __func__, buf_node,
+ buf_node->paddr, buf_node->buf.buf_len);
+ ac = audio->ac;
+ /* Provide address so driver can append nr frames information */
+ param.paddr = buf_node->paddr +
+ sizeof(struct dec_meta_out);
+ param.len = buf_node->buf.buf_len -
+ sizeof(struct dec_meta_out);
+ param.uid = param.paddr;
+ /* Write command will populate paddr as token */
+ buf_node->token = param.paddr;
+ rc = q6asm_async_read(ac, ¶m);
+ if (rc < 0)
+ pr_err("%s:failed\n", __func__);
+}
+
+static void audaac_async_write(struct q6audio *audio,
+ struct audaac_buffer_node *buf_node)
+{
+ int rc;
+ struct audio_client *ac;
+ struct audio_aio_write_param param;
+
+ pr_debug("%s: Send write buff %p phy %lx len %d, meta_enable = %d\n",
+ __func__, buf_node, buf_node->paddr, buf_node->buf.data_len,
+ audio->buf_cfg.meta_info_enable);
+
+ ac = audio->ac;
+ /* Offset with appropriate meta */
+ param.paddr = buf_node->paddr + sizeof(struct meta_in);
+ param.len = buf_node->buf.data_len - sizeof(struct meta_in);
+ param.msw_ts = buf_node->meta_info.meta_in.ntimestamp.highpart;
+ param.lsw_ts = buf_node->meta_info.meta_in.ntimestamp.lowpart;
+ /* If no meta_info enaled, indicate no time stamp valid */
+ if (audio->buf_cfg.meta_info_enable)
+ param.flags = 0;
+ else
+ param.flags = 0xFF00;
+ param.uid = param.paddr;
+ /* Read command will populate paddr as token */
+ buf_node->token = param.paddr;
+ rc = q6asm_async_write(ac, ¶m);
+ if (rc < 0)
+ pr_err("%s:failed\n", __func__);
+}
+
+/* Write buffer to DSP / Handle Ack from DSP */
+static void audaac_async_write_ack(struct q6audio *audio, uint32_t token,
+ uint32_t *payload)
+{
+ unsigned long flags;
+ union msm_audio_event_payload event_payload;
+ struct audaac_buffer_node *used_buf;
+
+ /* No active flush in progress */
+ if (audio->wflush)
+ return;
+
+ spin_lock_irqsave(&audio->dsp_lock, flags);
+ BUG_ON(list_empty(&audio->out_queue));
+ used_buf = list_first_entry(&audio->out_queue,
+ struct audaac_buffer_node, list);
+ if (token == used_buf->token) {
+ list_del(&used_buf->list);
+ spin_unlock_irqrestore(&audio->dsp_lock, flags);
+ pr_debug("consumed buffer\n");
+ event_payload.aio_buf = used_buf->buf;
+ audaac_post_event(audio, AUDIO_EVENT_WRITE_DONE,
+ event_payload);
+ kfree(used_buf);
+ if (list_empty(&audio->out_queue) &&
+ (audio->drv_status & ADRV_STATUS_FSYNC)) {
+ pr_debug("%s: list is empty, reached EOS in\
+ Tunnel\n", __func__);
+ wake_up(&audio->write_wait);
+ }
+ } else {
+ pr_err("expected=%lx ret=%x\n", used_buf->token, token);
+ spin_unlock_irqrestore(&audio->dsp_lock, flags);
+ }
+}
+
+/* Read buffer from DSP / Handle Ack from DSP */
+static void audaac_async_read_ack(struct q6audio *audio, uint32_t token,
+ uint32_t *payload)
+{
+ unsigned long flags;
+ union msm_audio_event_payload event_payload;
+ struct audaac_buffer_node *filled_buf;
+
+ /* No active flush in progress */
+ if (audio->rflush)
+ return;
+
+ /* Statistics of read */
+ atomic_add(payload[2], &audio->in_bytes);
+ atomic_add(payload[7], &audio->in_samples);
+
+ spin_lock_irqsave(&audio->dsp_lock, flags);
+ BUG_ON(list_empty(&audio->in_queue));
+ filled_buf = list_first_entry(&audio->in_queue,
+ struct audaac_buffer_node, list);
+ if (token == (filled_buf->token)) {
+ list_del(&filled_buf->list);
+ spin_unlock_irqrestore(&audio->dsp_lock, flags);
+ event_payload.aio_buf = filled_buf->buf;
+ /* Read done Buffer due to flush/normal condition
+ after EOS event, so append EOS buffer */
+ if (audio->eos_rsp == 0x1) {
+ event_payload.aio_buf.data_len =
+ insert_eos_buf(audio, filled_buf);
+ /* Reset flag back to indicate eos intimated */
+ audio->eos_rsp = 0;
+ } else {
+ filled_buf->meta_info.meta_out.num_of_frames =
+ payload[7];
+ event_payload.aio_buf.data_len = payload[2] + \
+ payload[3] + \
+ sizeof(struct dec_meta_out);
+ pr_debug("nr of frames 0x%8x len=%d\n",
+ filled_buf->meta_info.meta_out.num_of_frames,
+ event_payload.aio_buf.data_len);
+ extract_meta_info(audio, filled_buf, 0);
+ audio->eos_rsp = 0;
+ }
+ audaac_post_event(audio, AUDIO_EVENT_READ_DONE, event_payload);
+ kfree(filled_buf);
+ } else {
+ pr_err("expected=%lx ret=%x\n", filled_buf->token, token);
+ spin_unlock_irqrestore(&audio->dsp_lock, flags);
+ }
+}
+
+static void q6_audaac_cb(uint32_t opcode, uint32_t token,
+ uint32_t *payload, void *priv)
+{
+ struct q6audio *audio = (struct q6audio *)priv;
+ union msm_audio_event_payload e_payload;
+
+ switch (opcode) {
+ case ASM_DATA_EVENT_WRITE_DONE:
+ pr_debug("%s:ASM_DATA_EVENT_WRITE_DONE token = 0x%x\n",
+ __func__, token);
+ audaac_async_write_ack(audio, token, payload);
+ break;
+ case ASM_DATA_EVENT_READ_DONE:
+ pr_debug("%s:ASM_DATA_EVENT_READ_DONE token = 0x%x\n",
+ __func__, token);
+ audaac_async_read_ack(audio, token, payload);
+ break;
+ case ASM_DATA_CMDRSP_EOS:
+ /* EOS Handle */
+ pr_debug("%s:ASM_DATA_CMDRSP_EOS\n", __func__);
+ if (audio->feedback) { /* Non-Tunnel mode */
+ audio->eos_rsp = 1;
+ /* propagate input EOS i/p buffer,
+ after receiving DSP acknowledgement */
+ if (audio->eos_flag &&
+ (audio->eos_write_payload.aio_buf.buf_addr)) {
+ audaac_post_event(audio, AUDIO_EVENT_WRITE_DONE,
+ audio->eos_write_payload);
+ memset(&audio->eos_write_payload , 0,
+ sizeof(union msm_audio_event_payload));
+ audio->eos_flag = 0;
+ }
+ } else { /* Tunnel mode */
+ audio->eos_rsp = 1;
+ wake_up(&audio->write_wait);
+ wake_up(&audio->cmd_wait);
+ }
+ break;
+ case ASM_DATA_CMD_MEDIA_FORMAT_UPDATE:
+ case ASM_STREAM_CMD_SET_ENCDEC_PARAM:
+ pr_debug("%s:payload0[%x] payloa1d[%x]opcode= 0x%x\n",
+ __func__, payload[0], payload[1], opcode);
+ break;
+
+ case ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY:
+ pr_debug("%s: ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY, "
+ "payload[0]-sr = %d, payload[1]-chl = %d, "
+ "payload[2] = %d, payload[3] = %d\n", __func__,
+ payload[0], payload[1], payload[2],
+ payload[3]);
+ pr_debug("%s: ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY, "
+ "sr(prev) = %d, chl(prev) = %d,",
+ __func__, audio->pcm_cfg.sample_rate,
+ audio->pcm_cfg.channel_count);
+ audio->pcm_cfg.sample_rate = payload[0];
+ audio->pcm_cfg.channel_count = payload[1] & 0xFFFF;
+ e_payload.stream_info.chan_info = audio->pcm_cfg.channel_count;
+ e_payload.stream_info.sample_rate = audio->pcm_cfg.sample_rate;
+ audaac_post_event(audio, AUDIO_EVENT_STREAM_INFO, e_payload);
+ break;
+ default:
+ pr_debug("%s:Unhandled event = 0x%8x\n", __func__, opcode);
+ break;
+ }
+}
+
+/* ------------------- device --------------------- */
+static void audaac_async_out_flush(struct q6audio *audio)
+{
+ struct audaac_buffer_node *buf_node;
+ struct list_head *ptr, *next;
+ union msm_audio_event_payload payload;
+ unsigned long flags;
+
+ pr_debug("%s\n", __func__);
+ /* EOS followed by flush, EOS response not guranteed, free EOS i/p
+ buffer */
+ spin_lock_irqsave(&audio->dsp_lock, flags);
+ if (audio->eos_flag && (audio->eos_write_payload.aio_buf.buf_addr)) {
+ pr_debug("%s: EOS followed by flush received,acknowledge eos"\
+ " i/p buffer immediately\n", __func__);
+ audaac_post_event(audio, AUDIO_EVENT_WRITE_DONE,
+ audio->eos_write_payload);
+ memset(&audio->eos_write_payload , 0,
+ sizeof(union msm_audio_event_payload));
+ }
+ spin_unlock_irqrestore(&audio->dsp_lock, flags);
+
+ list_for_each_safe(ptr, next, &audio->out_queue) {
+ buf_node = list_entry(ptr, struct audaac_buffer_node, list);
+ list_del(&buf_node->list);
+ payload.aio_buf = buf_node->buf;
+ audaac_post_event(audio, AUDIO_EVENT_WRITE_DONE, payload);
+ kfree(buf_node);
+ pr_debug("%s: Propagate WRITE_DONE during flush\n", __func__);
+ }
+}
+
+static void audaac_async_in_flush(struct q6audio *audio)
+{
+ struct audaac_buffer_node *buf_node;
+ struct list_head *ptr, *next;
+ union msm_audio_event_payload payload;
+
+ pr_debug("%s\n", __func__);
+ list_for_each_safe(ptr, next, &audio->in_queue) {
+ buf_node = list_entry(ptr, struct audaac_buffer_node, list);
+ list_del(&buf_node->list);
+ /* Forcefull send o/p eos buffer after flush, if no eos response
+ * received by dsp even after sending eos command */
+ if ((audio->eos_rsp != 1) && audio->eos_flag) {
+ pr_debug("%s: send eos on o/p buffer during flush\n",\
+ __func__);
+ payload.aio_buf = buf_node->buf;
+ payload.aio_buf.data_len =
+ insert_eos_buf(audio, buf_node);
+ audio->eos_flag = 0;
+ } else {
+ payload.aio_buf = buf_node->buf;
+ payload.aio_buf.data_len =
+ insert_meta_data(audio, buf_node);
+ }
+ audaac_post_event(audio, AUDIO_EVENT_READ_DONE, payload);
+ kfree(buf_node);
+ pr_debug("%s: Propagate READ_DONE during flush\n", __func__);
+ }
+}
+
+static void audaac_ioport_reset(struct q6audio *audio)
+{
+ if (audio->drv_status & ADRV_STATUS_AIO_INTF) {
+ /* If fsync is in progress, make sure
+ * return value of fsync indicates
+ * abort due to flush
+ */
+ if (audio->drv_status & ADRV_STATUS_FSYNC) {
+ pr_debug("fsync in progress\n");
+ audio->drv_ops.out_flush(audio);
+ } else
+ audio->drv_ops.out_flush(audio);
+ audio->drv_ops.in_flush(audio);
+ }
+}
+
+static int audaac_events_pending(struct q6audio *audio)
+{
+ unsigned long flags;
+ int empty;
+
+ spin_lock_irqsave(&audio->event_queue_lock, flags);
+ empty = !list_empty(&audio->event_queue);
+ spin_unlock_irqrestore(&audio->event_queue_lock, flags);
+ return empty || audio->event_abort;
+}
+
+static void audaac_reset_event_queue(struct q6audio *audio)
+{
+ unsigned long flags;
+ struct audaac_event *drv_evt;
+ struct list_head *ptr, *next;
+
+ spin_lock_irqsave(&audio->event_queue_lock, flags);
+ list_for_each_safe(ptr, next, &audio->event_queue) {
+ drv_evt = list_first_entry(&audio->event_queue,
+ struct audaac_event, list);
+ list_del(&drv_evt->list);
+ kfree(drv_evt);
+ }
+ list_for_each_safe(ptr, next, &audio->free_event_queue) {
+ drv_evt = list_first_entry(&audio->free_event_queue,
+ struct audaac_event, list);
+ list_del(&drv_evt->list);
+ kfree(drv_evt);
+ }
+ spin_unlock_irqrestore(&audio->event_queue_lock, flags);
+
+ return;
+}
+
+static long audaac_process_event_req(struct q6audio *audio, void __user * arg)
+{
+ long rc;
+ struct msm_audio_event usr_evt;
+ struct audaac_event *drv_evt = NULL;
+ int timeout;
+ unsigned long flags;
+
+ if (copy_from_user(&usr_evt, arg, sizeof(struct msm_audio_event)))
+ return -EFAULT;
+
+ timeout = (int)usr_evt.timeout_ms;
+
+ if (timeout > 0) {
+ rc = wait_event_interruptible_timeout(audio->event_wait,
+ audaac_events_pending
+ (audio),
+ msecs_to_jiffies
+ (timeout));
+ if (rc == 0)
+ return -ETIMEDOUT;
+ } else {
+ rc = wait_event_interruptible(audio->event_wait,
+ audaac_events_pending(audio));
+ }
+
+ if (rc < 0)
+ return rc;
+
+ if (audio->event_abort) {
+ audio->event_abort = 0;
+ return -ENODEV;
+ }
+
+ rc = 0;
+
+ spin_lock_irqsave(&audio->event_queue_lock, flags);
+ if (!list_empty(&audio->event_queue)) {
+ drv_evt = list_first_entry(&audio->event_queue,
+ struct audaac_event, list);
+ list_del(&drv_evt->list);
+ }
+ if (drv_evt) {
+ usr_evt.event_type = drv_evt->event_type;
+ usr_evt.event_payload = drv_evt->payload;
+ list_add_tail(&drv_evt->list, &audio->free_event_queue);
+ } else {
+ pr_err("Unexpected path\n");
+ spin_unlock_irqrestore(&audio->event_queue_lock, flags);
+ return -EPERM;
+ }
+ spin_unlock_irqrestore(&audio->event_queue_lock, flags);
+
+ if (drv_evt->event_type == AUDIO_EVENT_WRITE_DONE) {
+ pr_debug("posted AUDIO_EVENT_WRITE_DONE to user\n");
+ mutex_lock(&audio->write_lock);
+ audaac_pmem_fixup(audio, drv_evt->payload.aio_buf.buf_addr,
+ drv_evt->payload.aio_buf.buf_len, 0, 0);
+ mutex_unlock(&audio->write_lock);
+ } else if (drv_evt->event_type == AUDIO_EVENT_READ_DONE) {
+ pr_debug("posted AUDIO_EVENT_READ_DONE to user\n");
+ mutex_lock(&audio->read_lock);
+ audaac_pmem_fixup(audio, drv_evt->payload.aio_buf.buf_addr,
+ drv_evt->payload.aio_buf.buf_len, 0, 0);
+ mutex_unlock(&audio->read_lock);
+ }
+
+ /* Some read buffer might be held up in DSP, release all
+ * once EOS indicated
+ */
+ if (audio->eos_rsp && !list_empty(&audio->in_queue)) {
+ pr_debug("Send flush command to release read buffers"\
+ "held up in DSP\n");
+ audaac_flush(audio);
+ }
+
+ if (copy_to_user(arg, &usr_evt, sizeof(usr_evt)))
+ rc = -EFAULT;
+
+ return rc;
+}
+
+static int audaac_pmem_check(struct q6audio *audio,
+ void *vaddr, unsigned long len)
+{
+ struct audaac_pmem_region *region_elt;
+ struct audaac_pmem_region t = {.vaddr = vaddr, .len = len };
+
+ list_for_each_entry(region_elt, &audio->pmem_region_queue, list) {
+ if (CONTAINS(region_elt, &t) || CONTAINS(&t, region_elt) ||
+ OVERLAPS(region_elt, &t)) {
+ pr_err("region (vaddr %p len %ld)"
+ " clashes with registered region"
+ " (vaddr %p paddr %p len %ld)\n",
+ vaddr, len,
+ region_elt->vaddr,
+ (void *)region_elt->paddr, region_elt->len);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int audaac_pmem_add(struct q6audio *audio,
+ struct msm_audio_pmem_info *info)
+{
+ unsigned long paddr, kvaddr, len;
+ struct file *file;
+ struct audaac_pmem_region *region;
+ int rc = -EINVAL;
+
+ pr_debug("%s:\n", __func__);
+ region = kmalloc(sizeof(*region), GFP_KERNEL);
+
+ if (!region) {
+ rc = -ENOMEM;
+ goto end;
+ }
+
+ if (get_pmem_file(info->fd, &paddr, &kvaddr, &len, &file)) {
+ kfree(region);
+ goto end;
+ }
+
+ rc = audaac_pmem_check(audio, info->vaddr, len);
+ if (rc < 0) {
+ put_pmem_file(file);
+ kfree(region);
+ goto end;
+ }
+
+ region->vaddr = info->vaddr;
+ region->fd = info->fd;
+ region->paddr = paddr;
+ region->kvaddr = kvaddr;
+ region->len = len;
+ region->file = file;
+ region->ref_cnt = 0;
+ pr_debug("add region paddr %lx vaddr %p, len %lu kvaddr %lx\n",
+ region->paddr, region->vaddr, region->len, region->kvaddr);
+ list_add_tail(®ion->list, &audio->pmem_region_queue);
+
+ rc = q6asm_memory_map(audio->ac, (uint32_t) paddr, IN, (uint32_t) len,
+ 1);
+ if (rc < 0)
+ pr_err("%s: memory map failed\n", __func__);
+end:
+ return rc;
+}
+
+static int audaac_pmem_remove(struct q6audio *audio,
+ struct msm_audio_pmem_info *info)
+{
+ struct audaac_pmem_region *region;
+ struct list_head *ptr, *next;
+ int rc = -EINVAL;
+
+ pr_debug("info fd %d vaddr %p\n", info->fd, info->vaddr);
+
+ list_for_each_safe(ptr, next, &audio->pmem_region_queue) {
+ region = list_entry(ptr, struct audaac_pmem_region, list);
+
+ if ((region->fd == info->fd) &&
+ (region->vaddr == info->vaddr)) {
+ if (region->ref_cnt) {
+ pr_debug("region %p in use ref_cnt %d\n",
+ region, region->ref_cnt);
+ break;
+ }
+ pr_debug("remove region fd %d vaddr %p\n",
+ info->fd, info->vaddr);
+ rc = q6asm_memory_unmap(audio->ac,
+ (uint32_t) region->paddr, IN);
+ if (rc < 0)
+ pr_err("%s: memory unmap failed\n", __func__);
+
+ list_del(®ion->list);
+ put_pmem_file(region->file);
+ kfree(region);
+ rc = 0;
+ break;
+ }
+ }
+
+ return rc;
+}
+
+/* audio -> lock must be held at this point */
+static int audaac_aio_buf_add(struct q6audio *audio, unsigned dir,
+ void __user *arg)
+{
+ unsigned long flags;
+ struct audaac_buffer_node *buf_node;
+
+ buf_node = kzalloc(sizeof(*buf_node), GFP_KERNEL);
+
+ if (!buf_node)
+ return -ENOMEM;
+
+ if (copy_from_user(&buf_node->buf, arg, sizeof(buf_node->buf))) {
+ kfree(buf_node);
+ return -EFAULT;
+ }
+
+ pr_debug("node %p dir %x buf_addr %p buf_len %d data_len \
+ %d\n", buf_node, dir, buf_node->buf.buf_addr,
+ buf_node->buf.buf_len, buf_node->buf.data_len);
+
+ buf_node->paddr = audaac_pmem_fixup(audio, buf_node->buf.buf_addr,
+ buf_node->buf.buf_len, 1,
+ &buf_node->kvaddr);
+ if (dir) {
+ /* write */
+ if (!buf_node->paddr ||
+ (buf_node->paddr & 0x1) ||
+ (!audio->feedback && !buf_node->buf.data_len)) {
+ kfree(buf_node);
+ return -EINVAL;
+ }
+ extract_meta_info(audio, buf_node, 1);
+ /* Not a EOS buffer */
+ if (!(buf_node->meta_info.meta_in.nflags & AUDAAC_EOS_SET)) {
+ spin_lock_irqsave(&audio->dsp_lock, flags);
+ audaac_async_write(audio, buf_node);
+ /* EOS buffer handled in driver */
+ list_add_tail(&buf_node->list, &audio->out_queue);
+ spin_unlock_irqrestore(&audio->dsp_lock, flags);
+ }
+ if (buf_node->meta_info.meta_in.nflags & AUDAAC_EOS_SET) {
+ if (!audio->wflush) {
+ pr_debug("%s:Send EOS cmd at i/p\n", __func__);
+ /* Driver will forcefully post writedone event
+ * once eos ack recived from DSP
+ */
+ audio->eos_write_payload.aio_buf =
+ buf_node->buf;
+ audio->eos_flag = 1;
+ audio->eos_rsp = 0;
+ q6asm_cmd(audio->ac, CMD_EOS);
+ kfree(buf_node);
+ } else {/* Flush in progress, send back i/p
+ * EOS buffer as is
+ */
+ union msm_audio_event_payload event_payload;
+ event_payload.aio_buf = buf_node->buf;
+ audaac_post_event(audio, AUDIO_EVENT_WRITE_DONE,
+ event_payload);
+ kfree(buf_node);
+ }
+
+ }
+ } else {
+ /* read */
+ if (!buf_node->paddr ||
+ (buf_node->paddr & 0x1) ||
+ (buf_node->buf.buf_len < PCM_BUFSZ_MIN)) {
+ kfree(buf_node);
+ return -EINVAL;
+ }
+ /* No EOS reached */
+ if (!audio->eos_rsp) {
+ spin_lock_irqsave(&audio->dsp_lock, flags);
+ audaac_async_read(audio, buf_node);
+ /* EOS buffer handled in driver */
+ list_add_tail(&buf_node->list, &audio->in_queue);
+ spin_unlock_irqrestore(&audio->dsp_lock, flags);
+ } else {
+ /* EOS reached at input side fake all upcoming read buffer to
+ * indicate the same
+ */
+ union msm_audio_event_payload event_payload;
+ event_payload.aio_buf = buf_node->buf;
+ event_payload.aio_buf.data_len =
+ insert_eos_buf(audio, buf_node);
+ pr_debug("%s: propagate READ_DONE as EOS done\n",
+ __func__);
+ audaac_post_event(audio, AUDIO_EVENT_READ_DONE,
+ event_payload);
+ kfree(buf_node);
+ }
+ }
+ return 0;
+}
+
+/* TBD: Only useful in tunnel-mode */
+static int audmultiaac_async_fsync(struct q6audio *audio)
+{
+ int rc = 0;
+
+ /* Blocking client sends more data */
+ mutex_lock(&audio->lock);
+ audio->drv_status |= ADRV_STATUS_FSYNC;
+ mutex_unlock(&audio->lock);
+
+ pr_debug("%s:\n", __func__);
+
+ mutex_lock(&audio->write_lock);
+ audio->eos_rsp = 0;
+
+ rc = wait_event_interruptible(audio->write_wait,
+ (list_empty(&audio->out_queue)) ||
+ audio->wflush || audio->stopped);
+
+ if (rc < 0) {
+ pr_err("%s: wait event for list_empty failed, rc = %d\n",
+ __func__, rc);
+ goto done;
+ }
+
+ rc = q6asm_cmd(audio->ac, CMD_EOS);
+
+ if (rc < 0)
+ pr_err("%s: q6asm_cmd failed, rc = %d", __func__, rc);
+
+ rc = wait_event_interruptible(audio->write_wait,
+ (audio->eos_rsp || audio->wflush ||
+ audio->stopped));
+
+ if (rc < 0) {
+ pr_err("%s: wait event for eos_rsp failed, rc = %d\n", __func__,
+ rc);
+ goto done;
+ }
+
+ if (audio->eos_rsp == 1) {
+ rc = audaac_enable(audio);
+ if (rc)
+ pr_err("%s: audio enable failed\n", __func__);
+ else {
+ audio->drv_status &= ~ADRV_STATUS_PAUSE;
+ audio->enabled = 1;
+ }
+ }
+
+ if (audio->stopped || audio->wflush)
+ rc = -EBUSY;
+
+done:
+ mutex_unlock(&audio->write_lock);
+ mutex_lock(&audio->lock);
+ audio->drv_status &= ~ADRV_STATUS_FSYNC;
+ mutex_unlock(&audio->lock);
+
+ return rc;
+}
+
+static int audmultiaac_fsync(struct file *file, int datasync)
+{
+ struct q6audio *audio = file->private_data;
+
+ if (!audio->enabled || audio->feedback)
+ return -EINVAL;
+
+ return audio->drv_ops.fsync(audio);
+}
+
+static void audaac_reset_pmem_region(struct q6audio *audio)
+{
+ struct audaac_pmem_region *region;
+ struct list_head *ptr, *next;
+
+ list_for_each_safe(ptr, next, &audio->pmem_region_queue) {
+ region = list_entry(ptr, struct audaac_pmem_region, list);
+ list_del(®ion->list);
+ put_pmem_file(region->file);
+ kfree(region);
+ }
+
+ return;
+}
+
+#ifdef CONFIG_DEBUG_FS
+static ssize_t audaac_debug_open(struct inode *inode, struct file *file)
+{
+ file->private_data = inode->i_private;
+ return 0;
+}
+
+static ssize_t audaac_debug_read(struct file *file, char __user * buf,
+ size_t count, loff_t *ppos)
+{
+ const int debug_bufmax = 4096;
+ static char buffer[4096];
+ int n = 0;
+ struct q6audio *audio = file->private_data;
+
+ mutex_lock(&audio->lock);
+ n = scnprintf(buffer, debug_bufmax, "opened %d\n", audio->opened);
+ n += scnprintf(buffer + n, debug_bufmax - n,
+ "enabled %d\n", audio->enabled);
+ n += scnprintf(buffer + n, debug_bufmax - n,
+ "stopped %d\n", audio->stopped);
+ n += scnprintf(buffer + n, debug_bufmax - n,
+ "feedback %d\n", audio->feedback);
+ mutex_unlock(&audio->lock);
+ /* Following variables are only useful for debugging when
+ * when playback halts unexpectedly. Thus, no mutual exclusion
+ * enforced
+ */
+ n += scnprintf(buffer + n, debug_bufmax - n,
+ "wflush %d\n", audio->wflush);
+ n += scnprintf(buffer + n, debug_bufmax - n,
+ "rflush %d\n", audio->rflush);
+ n += scnprintf(buffer + n, debug_bufmax - n,
+ "inqueue empty %d\n", list_empty(&audio->in_queue));
+ n += scnprintf(buffer + n, debug_bufmax - n,
+ "outqueue empty %d\n", list_empty(&audio->out_queue));
+ buffer[n] = 0;
+ return simple_read_from_buffer(buf, count, ppos, buffer, n);
+}
+
+static const struct file_operations audaac_debug_fops = {
+ .read = audaac_debug_read,
+ .open = audaac_debug_open,
+};
+#endif
+
+static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct q6audio *audio = file->private_data;
+ int rc = 0;
+
+ if (cmd == AUDIO_GET_STATS) {
+ struct msm_audio_stats stats;
+ stats.byte_count = atomic_read(&audio->in_bytes);
+ stats.sample_count = atomic_read(&audio->in_samples);
+ if (copy_to_user((void *)arg, &stats, sizeof(stats)))
+ return -EFAULT;
+ return rc;
+ }
+
+ if (cmd == AUDIO_GET_EVENT) {
+ pr_debug("AUDIO_GET_EVENT\n");
+ if (mutex_trylock(&audio->get_event_lock)) {
+ rc = audaac_process_event_req(audio,
+ (void __user *)arg);
+ mutex_unlock(&audio->get_event_lock);
+ } else
+ rc = -EBUSY;
+ return rc;
+ }
+
+ if (cmd == AUDIO_ASYNC_WRITE) {
+ mutex_lock(&audio->write_lock);
+ if (audio->drv_status & ADRV_STATUS_FSYNC)
+ rc = -EBUSY;
+ else {
+ if (audio->enabled)
+ rc = audaac_aio_buf_add(audio, 1,
+ (void __user *)arg);
+ else
+ rc = -EPERM;
+ }
+ mutex_unlock(&audio->write_lock);
+ return rc;
+ }
+
+ if (cmd == AUDIO_ASYNC_READ) {
+ mutex_lock(&audio->read_lock);
+ if ((audio->feedback) && (audio->enabled))
+ rc = audaac_aio_buf_add(audio, 0,
+ (void __user *)arg);
+ else
+ rc = -EPERM;
+ mutex_unlock(&audio->read_lock);
+ return rc;
+ }
+
+ if (cmd == AUDIO_ABORT_GET_EVENT) {
+ audio->event_abort = 1;
+ wake_up(&audio->event_wait);
+ return 0;
+ }
+
+ mutex_lock(&audio->lock);
+ switch (cmd) {
+ case AUDIO_START: {
+ struct asm_aac_cfg aac_cfg;
+ uint32_t sbr_ps = 0x00;
+ if (audio->feedback == NON_TUNNEL_MODE) {
+ /* Configure PCM output block */
+ rc = q6asm_enc_cfg_blk_pcm(audio->ac,
+ audio->pcm_cfg.sample_rate,
+ audio->pcm_cfg.channel_count);
+ if (rc < 0) {
+ pr_err("pcm output block config failed\n");
+ break;
+ }
+ }
+ /* turn on both sbr and ps */
+ rc = q6asm_enable_sbrps(audio->ac, sbr_ps);
+ if (rc < 0)
+ pr_err("sbr-ps enable failed\n");
+ if (audio->aac_config.sbr_ps_on_flag)
+ aac_cfg.aot = AAC_ENC_MODE_EAAC_P;
+ else if (audio->aac_config.sbr_on_flag)
+ aac_cfg.aot = AAC_ENC_MODE_AAC_P;
+ else
+ aac_cfg.aot = AAC_ENC_MODE_AAC_LC;
+
+ switch (audio->aac_config.format) {
+ case AUDIO_AAC_FORMAT_ADTS:
+ aac_cfg.format = 0x00;
+ break;
+ case AUDIO_AAC_FORMAT_LOAS:
+ aac_cfg.format = 0x01;
+ break;
+ /* ADIF, use it as RAW */
+ default:
+ case AUDIO_AAC_FORMAT_RAW:
+ aac_cfg.format = 0x03;
+ }
+ aac_cfg.ep_config = audio->aac_config.ep_config;
+ aac_cfg.section_data_resilience =
+ audio->aac_config.aac_section_data_resilience_flag;
+ aac_cfg.scalefactor_data_resilience =
+ audio->aac_config.aac_scalefactor_data_resilience_flag;
+ aac_cfg.spectral_data_resilience =
+ audio->aac_config.aac_spectral_data_resilience_flag;
+ aac_cfg.ch_cfg = audio->aac_config.channel_configuration;
+ aac_cfg.sample_rate = audio->pcm_cfg.sample_rate;
+
+ pr_debug("%s:format=%x aot=%d ch=%d sr=%d\n",
+ __func__, aac_cfg.format,
+ aac_cfg.aot, aac_cfg.ch_cfg,
+ aac_cfg.sample_rate);
+
+ /* Configure Media format block */
+ rc = q6asm_media_format_block_aac(audio->ac, &aac_cfg);
+ if (rc < 0) {
+ pr_err("cmd media format block failed\n");
+ break;
+ }
+ rc = audaac_enable(audio);
+ audio->eos_rsp = 0;
+ audio->eos_flag = 0;
+ if (!rc) {
+ audio->enabled = 1;
+ } else {
+ audio->enabled = 0;
+ pr_err("Audio Start procedure failed rc=%d\n", rc);
+ break;
+ }
+ pr_info("%s: AUDIO_START sessionid[%d]enable[%d]\n", __func__,
+ audio->ac->session,
+ audio->enabled);
+ if (audio->stopped == 1)
+ audio->stopped = 0;
+ break;
+ }
+ case AUDIO_STOP: {
+ pr_debug("%s: AUDIO_STOP sessionid[%d]\n", __func__,
+ audio->ac->session);
+ audio->stopped = 1;
+ audaac_flush(audio);
+ audio->enabled = 0;
+ audio->drv_status &= ~ADRV_STATUS_PAUSE;
+ if (rc < 0) {
+ pr_err("Audio Stop procedure failed rc=%d\n", rc);
+ break;
+ }
+ break;
+ }
+ case AUDIO_PAUSE: {
+ pr_debug("AUDIO_PAUSE %ld\n", arg);
+ if (arg == 1) {
+ rc = audaac_pause(audio);
+ if (rc < 0)
+ pr_err("%s: pause FAILED rc=%d\n", __func__,
+ rc);
+ audio->drv_status |= ADRV_STATUS_PAUSE;
+ } else if (arg == 0) {
+ if (audio->drv_status & ADRV_STATUS_PAUSE) {
+ rc = audaac_enable(audio);
+ if (rc)
+ pr_err("%s: audio enable failed\n",
+ __func__);
+ else {
+ audio->drv_status &= ~ADRV_STATUS_PAUSE;
+ audio->enabled = 1;
+ }
+ }
+ }
+ break;
+ }
+ case AUDIO_FLUSH: {
+ pr_debug("%s: AUDIO_FLUSH sessionid[%d]\n", __func__,
+ audio->ac->session);
+ audio->rflush = 1;
+ audio->wflush = 1;
+ /* Flush DSP */
+ rc = audaac_flush(audio);
+ /* Flush input / Output buffer in software*/
+ audaac_ioport_reset(audio);
+ if (rc < 0) {
+ pr_err("AUDIO_FLUSH interrupted\n");
+ rc = -EINTR;
+ } else {
+ audio->rflush = 0;
+ audio->wflush = 0;
+ }
+ audio->eos_flag = 0;
+ audio->eos_rsp = 0;
+ break;
+ }
+ case AUDIO_OUTPORT_FLUSH:
+ {
+ pr_debug("AUDIO_OUTPORT_FLUSH\n");
+ rc = audaac_outport_flush(audio);
+ if (rc < 0) {
+ pr_err("%s: AUDIO_OUTPORT_FLUSH failed\n", __func__);
+ rc = -EINTR;
+ }
+ break;
+ }
+ case AUDIO_REGISTER_PMEM: {
+ struct msm_audio_pmem_info info;
+ pr_debug("AUDIO_REGISTER_PMEM\n");
+ if (copy_from_user(&info, (void *)arg, sizeof(info)))
+ rc = -EFAULT;
+ else
+ rc = audaac_pmem_add(audio, &info);
+ break;
+ }
+ case AUDIO_DEREGISTER_PMEM: {
+ struct msm_audio_pmem_info info;
+ pr_debug("AUDIO_DEREGISTER_PMEM\n");
+ if (copy_from_user(&info, (void *)arg, sizeof(info)))
+ rc = -EFAULT;
+ else
+ rc = audaac_pmem_remove(audio, &info);
+ break;
+ }
+ case AUDIO_GET_AAC_CONFIG: {
+ if (copy_to_user((void *)arg, &audio->aac_config,
+ sizeof(struct msm_audio_aac_config))) {
+ rc = -EFAULT;
+ break;
+ }
+ break;
+ }
+ case AUDIO_SET_AAC_CONFIG: {
+ if (copy_from_user(&audio->aac_config, (void *)arg,
+ sizeof(struct msm_audio_aac_config))) {
+ rc = -EFAULT;
+ break;
+ }
+ break;
+ }
+ case AUDIO_GET_STREAM_CONFIG: {
+ struct msm_audio_stream_config cfg;
+ memset(&cfg, 0, sizeof(cfg));
+ cfg.buffer_size = audio->str_cfg.buffer_size;
+ cfg.buffer_count = audio->str_cfg.buffer_count;
+ pr_debug("GET STREAM CFG %d %d\n", cfg.buffer_size,
+ cfg.buffer_count);
+ if (copy_to_user((void *)arg, &cfg, sizeof(cfg)))
+ rc = -EFAULT;
+ break;
+ }
+ case AUDIO_SET_STREAM_CONFIG: {
+ struct msm_audio_stream_config cfg;
+ pr_debug("SET STREAM CONFIG\n");
+ if (copy_from_user(&cfg, (void *)arg, sizeof(cfg))) {
+ rc = -EFAULT;
+ break;
+ }
+ audio->str_cfg.buffer_size = FRAME_SIZE;
+ audio->str_cfg.buffer_count = FRAME_NUM;
+ rc = 0;
+ break;
+ }
+ case AUDIO_GET_CONFIG: {
+ struct msm_audio_config cfg;
+ if (copy_to_user((void *)arg, &audio->pcm_cfg, sizeof(cfg)))
+ rc = -EFAULT;
+ break;
+ }
+ case AUDIO_SET_CONFIG: {
+ struct msm_audio_config config;
+ if (copy_from_user(&config, (void *)arg, sizeof(config))) {
+ rc = -EFAULT;
+ break;
+ }
+ if (audio->feedback != NON_TUNNEL_MODE) {
+ pr_err("Not sufficient permission to"
+ "change the playback mode\n");
+ rc = -EACCES;
+ break;
+ }
+ if ((config.buffer_count > PCM_BUF_COUNT) ||
+ (config.buffer_count == 1))
+ config.buffer_count = PCM_BUF_COUNT;
+
+ if (config.buffer_size < PCM_BUFSZ_MIN)
+ config.buffer_size = PCM_BUFSZ_MIN;
+
+ audio->pcm_cfg.buffer_count = config.buffer_count;
+ audio->pcm_cfg.buffer_size = config.buffer_size;
+ audio->pcm_cfg.channel_count = config.channel_count;
+ audio->pcm_cfg.sample_rate = config.sample_rate;
+ rc = 0;
+ break;
+ }
+ case AUDIO_SET_BUF_CFG: {
+ struct msm_audio_buf_cfg cfg;
+ if (copy_from_user(&cfg, (void *)arg, sizeof(cfg))) {
+ rc = -EFAULT;
+ break;
+ }
+ if ((audio->feedback == NON_TUNNEL_MODE) &&
+ !cfg.meta_info_enable) {
+ rc = -EFAULT;
+ break;
+ }
+
+ audio->buf_cfg.meta_info_enable = cfg.meta_info_enable;
+ pr_debug("%s:session id %d: Set-buf-cfg: meta[%d]", __func__,
+ audio->ac->session, cfg.meta_info_enable);
+ break;
+ }
+ case AUDIO_GET_BUF_CFG: {
+ pr_debug("%s:session id %d: Get-buf-cfg: meta[%d]\
+ framesperbuf[%d]\n", __func__,
+ audio->ac->session, audio->buf_cfg.meta_info_enable,
+ audio->buf_cfg.frames_per_buf);
+
+ if (copy_to_user((void *)arg, &audio->buf_cfg,
+ sizeof(struct msm_audio_buf_cfg)))
+ rc = -EFAULT;
+ break;
+ }
+ case AUDIO_GET_SESSION_ID: {
+ if (copy_to_user((void *)arg, &audio->ac->session,
+ sizeof(unsigned short))) {
+ rc = -EFAULT;
+ }
+ break;
+ }
+ default:
+ rc = -EINVAL;
+ }
+ mutex_unlock(&audio->lock);
+ return rc;
+}
+
+static int audio_release(struct inode *inode, struct file *file)
+{
+ struct q6audio *audio = file->private_data;
+ pr_debug("%s: multi_aac dec\n", __func__);
+ mutex_lock(&audio->lock);
+ audaac_disable(audio);
+ audio->drv_ops.out_flush(audio);
+ audio->drv_ops.in_flush(audio);
+ audaac_reset_pmem_region(audio);
+ audio->event_abort = 1;
+ wake_up(&audio->event_wait);
+ audaac_reset_event_queue(audio);
+ q6asm_audio_client_free(audio->ac);
+ mutex_unlock(&audio->lock);
+ mutex_destroy(&audio->lock);
+ mutex_destroy(&audio->read_lock);
+ mutex_destroy(&audio->write_lock);
+ mutex_destroy(&audio->get_event_lock);
+#ifdef CONFIG_DEBUG_FS
+ if (audio->dentry)
+ debugfs_remove(audio->dentry);
+#endif
+ kfree(audio);
+ pr_info("%s:multi_aac dec success\n", __func__);
+ return 0;
+}
+
+static int audio_open(struct inode *inode, struct file *file)
+{
+ struct q6audio *audio = NULL;
+ int rc = 0;
+ int i;
+ struct audaac_event *e_node = NULL;
+
+#ifdef CONFIG_DEBUG_FS
+ /* 4 bytes represents decoder number, 1 byte for terminate string */
+ char name[sizeof "msm_multi_aac_" + 5];
+#endif
+ audio = kzalloc(sizeof(struct q6audio), GFP_KERNEL);
+
+ if (audio == NULL) {
+ pr_err("Could not allocate memory for aac decode driver\n");
+ return -ENOMEM;
+ }
+
+ /* Settings will be re-config at AUDIO_SET_CONFIG,
+ * but at least we need to have initial config
+ */
+ audio->str_cfg.buffer_size = FRAME_SIZE;
+ audio->str_cfg.buffer_count = FRAME_NUM;
+ audio->pcm_cfg.buffer_size = PCM_BUFSZ_MIN;
+ audio->pcm_cfg.buffer_count = PCM_BUF_COUNT;
+ audio->pcm_cfg.sample_rate = 48000;
+ audio->pcm_cfg.channel_count = 2;
+
+ audio->ac = q6asm_audio_client_alloc((app_cb) q6_audaac_cb,
+ (void *)audio);
+
+ if (!audio->ac) {
+ pr_err("Could not allocate memory for audio client\n");
+ kfree(audio);
+ return -ENOMEM;
+ }
+ /* Only AIO interface */
+ if (file->f_flags & O_NONBLOCK) {
+ pr_debug("set to aio interface\n");
+ audio->drv_status |= ADRV_STATUS_AIO_INTF;
+ audio->drv_ops.out_flush = audaac_async_out_flush;
+ audio->drv_ops.in_flush = audaac_async_in_flush;
+ audio->drv_ops.fsync = audmultiaac_async_fsync;
+ q6asm_set_io_mode(audio->ac, ASYNC_IO_MODE);
+ } else {
+ pr_err("SIO interface not supported\n");
+ rc = -EACCES;
+ goto fail;
+ }
+
+ /* open in T/NT mode */
+ if ((file->f_mode & FMODE_WRITE) && (file->f_mode & FMODE_READ)) {
+ rc = q6asm_open_read_write(audio->ac, FORMAT_LINEAR_PCM,
+ FORMAT_MPEG4_AAC);
+ if (rc < 0) {
+ pr_err("NT mode Open failed rc=%d\n", rc);
+ rc = -ENODEV;
+ goto fail;
+ }
+ audio->feedback = NON_TUNNEL_MODE;
+ /* open AAC decoder, expected frames is always 1
+ audio->buf_cfg.frames_per_buf = 0x01;*/
+ audio->buf_cfg.meta_info_enable = 0x01;
+ } else if ((file->f_mode & FMODE_WRITE) &&
+ !(file->f_mode & FMODE_READ)) {
+ rc = q6asm_open_write(audio->ac, FORMAT_MPEG4_AAC);
+ if (rc < 0) {
+ pr_err("T mode Open failed rc=%d\n", rc);
+ rc = -ENODEV;
+ goto fail;
+ }
+ audio->feedback = TUNNEL_MODE;
+ audio->buf_cfg.meta_info_enable = 0x00;
+ } else {
+ pr_err("Not supported mode\n");
+ rc = -EACCES;
+ goto fail;
+ }
+ /* Initialize all locks of audio instance */
+ mutex_init(&audio->lock);
+ mutex_init(&audio->read_lock);
+ mutex_init(&audio->write_lock);
+ mutex_init(&audio->get_event_lock);
+ spin_lock_init(&audio->dsp_lock);
+ spin_lock_init(&audio->event_queue_lock);
+ init_waitqueue_head(&audio->cmd_wait);
+ init_waitqueue_head(&audio->write_wait);
+ init_waitqueue_head(&audio->event_wait);
+ INIT_LIST_HEAD(&audio->out_queue);
+ INIT_LIST_HEAD(&audio->in_queue);
+ INIT_LIST_HEAD(&audio->pmem_region_queue);
+ INIT_LIST_HEAD(&audio->free_event_queue);
+ INIT_LIST_HEAD(&audio->event_queue);
+
+ audio->drv_ops.out_flush(audio);
+ audio->opened = 1;
+ file->private_data = audio;
+
+#ifdef CONFIG_DEBUG_FS
+ snprintf(name, sizeof name, "msm_multi_aac_%04x", audio->ac->session);
+ audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO,
+ NULL, (void *)audio,
+ &audaac_debug_fops);
+
+ if (IS_ERR(audio->dentry))
+ pr_debug("debugfs_create_file failed\n");
+#endif
+ for (i = 0; i < AUDAAC_EVENT_NUM; i++) {
+ e_node = kmalloc(sizeof(struct audaac_event), GFP_KERNEL);
+ if (e_node)
+ list_add_tail(&e_node->list, &audio->free_event_queue);
+ else {
+ pr_err("event pkt alloc failed\n");
+ break;
+ }
+ }
+ pr_info("%s:AAC 5.1 Decoder OPEN success mode[%d]session[%d]\n",
+ __func__, audio->feedback, audio->ac->session);
+ return 0;
+fail:
+ q6asm_audio_client_free(audio->ac);
+ kfree(audio);
+ return rc;
+}
+
+static const struct file_operations audio_aac_fops = {
+ .owner = THIS_MODULE,
+ .open = audio_open,
+ .release = audio_release,
+ .unlocked_ioctl = audio_ioctl,
+ .fsync = audmultiaac_fsync,
+};
+
+struct miscdevice audmultiaac_misc = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "msm_multi_aac",
+ .fops = &audio_aac_fops,
+};
+
+static int __init audio_aac_init(void)
+{
+ return misc_register(&audmultiaac_misc);
+}
+
+device_initcall(audio_aac_init);