kernel/msm: 8974: Add audio OCMEM driver support
Add audio OCMEM driver support to exercise On-Chip
Memory (OCMEM) for low power audio and voice. The
driver is implemented as standalone and it gets
exercised based on the usecase. Also, this design
reduces the latency associated with OCMEM handshaking
protocol. The audio OCMEM driver is enabled by default
with a Kconfig option using select. Add device tree node
for audio OCMEM to retrieve platform data.
Change-Id: Iba46ce675fc03843d88cd7cf2aa9bc92fe70a955
Signed-off-by: Phani Kumar Uppalapati <phanik@codeaurora.org>
diff --git a/Documentation/devicetree/bindings/sound/qcom-audio-dev.txt b/Documentation/devicetree/bindings/sound/qcom-audio-dev.txt
index 50894fb..84f0c24 100644
--- a/Documentation/devicetree/bindings/sound/qcom-audio-dev.txt
+++ b/Documentation/devicetree/bindings/sound/qcom-audio-dev.txt
@@ -103,6 +103,22 @@
- compatible : "qcom,msm-pcm-hostless"
+* msm-ocmem-audio
+
+Required properties:
+
+ - compatible : "qcom,msm-ocmem-audio"
+
+ - qcom,msm-ocmem-audio-src-id: Master port id
+
+ - qcom,msm-ocmem-audio-dst-id: Slave port id
+
+ - qcom,msm-ocmem-audio-ab: arbitrated bandwidth
+ in Bytes/s
+
+ - qcom,msm-ocmem-audio-ib: instantaneous bandwidth
+ in Bytes/s
+
Example:
qcom,msm-pcm {
@@ -172,3 +188,11 @@
compatible = "qcom,msm-pcm-hostless";
};
+ qcom,msm-ocmem-audio {
+ compatible = "qcom,msm-ocmem-audio";
+ qcom,msm-ocmem-audio-src-id = <11>;
+ qcom,msm-ocmem-audio-dst-id = <604>;
+ qcom,msm-ocmem-audio-ab = <209715200>;
+ qcom,msm-ocmem-audio-ib = <471859200>;
+ };
+
diff --git a/arch/arm/boot/dts/msm8974.dtsi b/arch/arm/boot/dts/msm8974.dtsi
index 3a0f0b9..f71f74c 100644
--- a/arch/arm/boot/dts/msm8974.dtsi
+++ b/arch/arm/boot/dts/msm8974.dtsi
@@ -439,6 +439,14 @@
compatible = "qcom,msm-pcm-hostless";
};
+ qcom,msm-ocmem-audio {
+ compatible = "qcom,msm-ocmem-audio";
+ qcom,msm-ocmem-audio-src-id = <11>;
+ qcom,msm-ocmem-audio-dst-id = <604>;
+ qcom,msm-ocmem-audio-ab = <32505856>;
+ qcom,msm-ocmem-audio-ib = <32505856>;
+ };
+
qcom,mss@fc880000 {
compatible = "qcom,pil-q6v5-mss";
reg = <0xfc880000 0x100>,
diff --git a/sound/soc/msm/Kconfig b/sound/soc/msm/Kconfig
index e56b1da..894e114 100644
--- a/sound/soc/msm/Kconfig
+++ b/sound/soc/msm/Kconfig
@@ -153,6 +153,16 @@
help
To add support for SoC audio on MSM8960 and APQ8064 boards
+config AUDIO_OCMEM
+ bool "Enable OCMEM for audio/voice usecase"
+ depends on MSM_OCMEM
+ default n
+ help
+ To add support for on-chip memory use
+ for audio use cases on MSM8974.
+ OCMEM gets exercised for low-power
+ audio and voice use cases.
+
config SND_SOC_MSM8974
tristate "SoC Machine driver for MSM8974 boards"
depends on ARCH_MSM8974
@@ -161,6 +171,7 @@
select SND_SOC_MSM_HOSTLESS_PCM
select SND_SOC_WCD9320
select SND_DYNAMIC_MINORS
+ select AUDIO_OCMEM
help
To add support for SoC audio on MSM8974.
This will enable sound soc drivers which
diff --git a/sound/soc/msm/qdsp6v2/Makefile b/sound/soc/msm/qdsp6v2/Makefile
index ff2cc8d..acb073d 100644
--- a/sound/soc/msm/qdsp6v2/Makefile
+++ b/sound/soc/msm/qdsp6v2/Makefile
@@ -1,4 +1,6 @@
snd-soc-qdsp6v2-objs += msm-dai-q6-v2.o msm-pcm-q6-v2.o msm-pcm-routing-v2.o msm-compr-q6-v2.o msm-multi-ch-pcm-q6-v2.o
snd-soc-qdsp6v2-objs += msm-pcm-lpa-v2.o msm-pcm-afe-v2.o msm-pcm-voip-v2.o msm-pcm-voice-v2.o
obj-$(CONFIG_SND_SOC_QDSP6V2) += snd-soc-qdsp6v2.o
-obj-y += q6adm.o q6afe.o q6asm.o q6audio-v2.o q6voice.o
+obj-y += q6adm.o q6afe.o q6asm.o q6audio-v2.o q6voice.o q6core.o
+ocmem-audio-objs += audio_ocmem.o
+obj-$(CONFIG_AUDIO_OCMEM) += ocmem-audio.o
diff --git a/sound/soc/msm/qdsp6v2/audio_ocmem.c b/sound/soc/msm/qdsp6v2/audio_ocmem.c
new file mode 100644
index 0000000..86a82e2
--- /dev/null
+++ b/sound/soc/msm/qdsp6v2/audio_ocmem.c
@@ -0,0 +1,672 @@
+/* Copyright (c) 2012, 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/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+#include <linux/sched.h>
+#include <linux/platform_device.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/of_device.h>
+#include <asm/mach-types.h>
+#include <mach/msm_bus.h>
+#include <mach/msm_bus_board.h>
+#include <mach/ocmem.h>
+#include "q6core.h"
+#include "audio_ocmem.h"
+
+#define AUDIO_OCMEM_BUF_SIZE (512 * SZ_1K)
+
+enum {
+ OCMEM_STATE_ALLOC = 1,
+ OCMEM_STATE_MAP_TRANSITION,
+ OCMEM_STATE_MAP_COMPL,
+ OCMEM_STATE_UNMAP_TRANSITION,
+ OCMEM_STATE_UNMAP_COMPL,
+ OCMEM_STATE_SHRINK,
+ OCMEM_STATE_GROW,
+ OCMEM_STATE_FREE,
+ OCMEM_STATE_MAP_FAIL,
+ OCMEM_STATE_UNMAP_FAIL,
+ OCMEM_STATE_EXIT,
+};
+static void audio_ocmem_process_workdata(struct work_struct *work);
+
+struct audio_ocmem_workdata {
+ int id;
+ bool en;
+ struct work_struct work;
+};
+
+struct voice_ocmem_workdata {
+ int id;
+ bool en;
+ struct work_struct work;
+};
+
+struct audio_ocmem_prv {
+ atomic_t audio_state;
+ struct ocmem_notifier *audio_hdl;
+ struct ocmem_buf *buf;
+ uint32_t audio_ocmem_bus_client;
+ struct ocmem_map_list mlist;
+ struct avcs_cmd_rsp_get_low_power_segments_info_t *lp_memseg_ptr;
+ wait_queue_head_t audio_wait;
+ atomic_t audio_cond;
+ atomic_t audio_exit;
+ spinlock_t audio_lock;
+ struct workqueue_struct *audio_ocmem_workqueue;
+ struct workqueue_struct *voice_ocmem_workqueue;
+};
+
+static struct audio_ocmem_prv audio_ocmem_lcl;
+
+
+static int audio_ocmem_client_cb(struct notifier_block *this,
+ unsigned long event1, void *data)
+{
+ int rc = NOTIFY_DONE;
+ unsigned long flags;
+
+ pr_debug("%s: event[%ld] cur state[%x]\n", __func__,
+ event1, atomic_read(&audio_ocmem_lcl.audio_state));
+
+ spin_lock_irqsave(&audio_ocmem_lcl.audio_lock, flags);
+ switch (event1) {
+ case OCMEM_MAP_DONE:
+ pr_debug("%s: map done\n", __func__);
+ atomic_set(&audio_ocmem_lcl.audio_state, OCMEM_STATE_MAP_COMPL);
+ break;
+ case OCMEM_MAP_FAIL:
+ pr_debug("%s: map fail\n", __func__);
+ atomic_set(&audio_ocmem_lcl.audio_state, OCMEM_STATE_MAP_FAIL);
+ break;
+ case OCMEM_UNMAP_DONE:
+ pr_debug("%s: unmap done\n", __func__);
+ atomic_set(&audio_ocmem_lcl.audio_state,
+ OCMEM_STATE_UNMAP_COMPL);
+ break;
+ case OCMEM_UNMAP_FAIL:
+ pr_debug("%s: unmap fail\n", __func__);
+ atomic_set(&audio_ocmem_lcl.audio_state,
+ OCMEM_STATE_UNMAP_FAIL);
+ break;
+ case OCMEM_ALLOC_GROW:
+ audio_ocmem_lcl.buf = data;
+ atomic_set(&audio_ocmem_lcl.audio_state, OCMEM_STATE_GROW);
+ break;
+ case OCMEM_ALLOC_SHRINK:
+ atomic_set(&audio_ocmem_lcl.audio_state, OCMEM_STATE_SHRINK);
+ break;
+ default:
+ pr_err("%s: Invalid event[%ld]\n", __func__, event1);
+ break;
+ }
+ spin_unlock_irqrestore(&audio_ocmem_lcl.audio_lock, flags);
+ if (atomic_read(&audio_ocmem_lcl.audio_cond)) {
+ atomic_set(&audio_ocmem_lcl.audio_cond, 0);
+ wake_up(&audio_ocmem_lcl.audio_wait);
+ }
+ return rc;
+}
+
+/**
+ * audio_ocmem_enable() - Exercise OCMEM for audio
+ * @cid: client id - OCMEM_LP_AUDIO
+ *
+ * OCMEM gets allocated for audio usecase and the low power
+ * segments obtained from the DSP will be moved from/to main
+ * memory to OCMEM. Shrink and grow requests will be received
+ * and processed accordingly based on the current audio state.
+ */
+int audio_ocmem_enable(int cid)
+{
+ int ret;
+ int i, j;
+ struct ocmem_buf *buf = NULL;
+ struct avcs_cmd_rsp_get_low_power_segments_info_t *lp_segptr;
+
+ pr_debug("%s\n", __func__);
+ /* Non-blocking ocmem allocate (asynchronous) */
+ buf = ocmem_allocate_nb(cid, AUDIO_OCMEM_BUF_SIZE);
+ if (IS_ERR_OR_NULL(buf)) {
+ pr_err("%s: failed: %d\n", __func__, cid);
+ return -ENOMEM;
+ }
+ atomic_set(&audio_ocmem_lcl.audio_state, OCMEM_STATE_ALLOC);
+
+ audio_ocmem_lcl.buf = buf;
+ atomic_set(&audio_ocmem_lcl.audio_exit, 0);
+ if (!buf->len) {
+ wait_event_interruptible(audio_ocmem_lcl.audio_wait,
+ (atomic_read(&audio_ocmem_lcl.audio_cond) == 0) ||
+ (atomic_read(&audio_ocmem_lcl.audio_exit) == 1));
+
+ if (atomic_read(&audio_ocmem_lcl.audio_exit)) {
+ pr_err("%s: audio playback ended while waiting for ocmem\n",
+ __func__);
+ ret = -EINVAL;
+ goto fail_cmd;
+ }
+ }
+ if (audio_ocmem_lcl.lp_memseg_ptr == NULL) {
+ /* Retrieve low power segments */
+ ret = core_get_low_power_segments(
+ &audio_ocmem_lcl.lp_memseg_ptr);
+ if (ret != 0) {
+ pr_err("%s: get low power segments from DSP failed, rc=%d\n",
+ __func__, ret);
+ goto fail_cmd;
+ }
+ }
+ lp_segptr = audio_ocmem_lcl.lp_memseg_ptr;
+ audio_ocmem_lcl.mlist.num_chunks = lp_segptr->num_segments;
+ for (i = 0, j = 0; j < audio_ocmem_lcl.mlist.num_chunks; j++, i++) {
+ audio_ocmem_lcl.mlist.chunks[j].ro =
+ (lp_segptr->mem_segment[i].type == READ_ONLY_SEGMENT);
+ audio_ocmem_lcl.mlist.chunks[j].ddr_paddr =
+ lp_segptr->mem_segment[i].start_address_lsw;
+ audio_ocmem_lcl.mlist.chunks[j].size =
+ lp_segptr->mem_segment[i].size;
+ pr_debug("%s: ro:%d, ddr_paddr[%x], size[%x]\n", __func__,
+ audio_ocmem_lcl.mlist.chunks[j].ro,
+ (uint32_t)audio_ocmem_lcl.mlist.chunks[j].ddr_paddr,
+ (uint32_t)audio_ocmem_lcl.mlist.chunks[j].size);
+ }
+
+ /* vote for ocmem bus bandwidth */
+ ret = msm_bus_scale_client_update_request(
+ audio_ocmem_lcl.audio_ocmem_bus_client,
+ 0);
+ if (ret)
+ pr_err("%s: failed to vote for bus bandwidth\n", __func__);
+
+ atomic_set(&audio_ocmem_lcl.audio_state, OCMEM_STATE_MAP_TRANSITION);
+
+ ret = ocmem_map(cid, audio_ocmem_lcl.buf, &audio_ocmem_lcl.mlist);
+ if (ret) {
+ pr_err("%s: ocmem_map failed\n", __func__);
+ goto fail_cmd;
+ }
+
+
+ while ((atomic_read(&audio_ocmem_lcl.audio_state) !=
+ OCMEM_STATE_EXIT)) {
+
+ wait_event_interruptible(audio_ocmem_lcl.audio_wait,
+ atomic_read(&audio_ocmem_lcl.audio_cond) == 0);
+
+ switch (atomic_read(&audio_ocmem_lcl.audio_state)) {
+ case OCMEM_STATE_MAP_COMPL:
+ pr_debug("%s: audio_cond[0x%x], audio_state[0x%x]\n",
+ __func__, atomic_read(&audio_ocmem_lcl.audio_cond),
+ atomic_read(&audio_ocmem_lcl.audio_state));
+ atomic_set(&audio_ocmem_lcl.audio_state,
+ OCMEM_STATE_MAP_COMPL);
+ atomic_set(&audio_ocmem_lcl.audio_cond, 1);
+ break;
+ case OCMEM_STATE_SHRINK:
+ atomic_set(&audio_ocmem_lcl.audio_cond, 1);
+ ret = ocmem_unmap(cid, audio_ocmem_lcl.buf,
+ &audio_ocmem_lcl.mlist);
+ if (ret) {
+ pr_err("%s: ocmem_unmap failed, state[%d]\n",
+ __func__,
+ atomic_read(&audio_ocmem_lcl.audio_state));
+ goto fail_cmd;
+ }
+
+ atomic_set(&audio_ocmem_lcl.audio_state,
+ OCMEM_STATE_UNMAP_TRANSITION);
+ wait_event_interruptible(audio_ocmem_lcl.audio_wait,
+ atomic_read(&audio_ocmem_lcl.audio_cond) == 0);
+ atomic_set(&audio_ocmem_lcl.audio_state,
+ OCMEM_STATE_UNMAP_COMPL);
+ ret = ocmem_shrink(cid, audio_ocmem_lcl.buf, 0);
+ if (ret) {
+ pr_err("%s: ocmem_shrink failed, state[%d]\n",
+ __func__,
+ atomic_read(&audio_ocmem_lcl.audio_state));
+ goto fail_cmd;
+ }
+
+ break;
+ case OCMEM_STATE_GROW:
+ atomic_set(&audio_ocmem_lcl.audio_cond, 1);
+ ret = ocmem_map(cid, audio_ocmem_lcl.buf,
+ &audio_ocmem_lcl.mlist);
+ if (ret) {
+ pr_err("%s: ocmem_map failed, state[%d]\n",
+ __func__,
+ atomic_read(&audio_ocmem_lcl.audio_state));
+ goto fail_cmd;
+ }
+ atomic_set(&audio_ocmem_lcl.audio_state,
+ OCMEM_STATE_MAP_TRANSITION);
+ wait_event_interruptible(audio_ocmem_lcl.audio_wait,
+ atomic_read(&audio_ocmem_lcl.audio_cond) == 0);
+ atomic_set(&audio_ocmem_lcl.audio_state,
+ OCMEM_STATE_MAP_COMPL);
+ break;
+ }
+ }
+fail_cmd:
+ pr_debug("%s: exit\n", __func__);
+ return ret;
+}
+
+/**
+ * audio_ocmem_disable() - Disable OCMEM for audio
+ * @cid: client id - OCMEM_LP_AUDIO
+ *
+ * OCMEM gets deallocated for audio usecase. Depending on
+ * current audio state, OCMEM will be freed from using audio
+ * segments.
+ */
+int audio_ocmem_disable(int cid)
+{
+ int ret;
+
+ if (atomic_read(&audio_ocmem_lcl.audio_cond))
+ atomic_set(&audio_ocmem_lcl.audio_cond, 0);
+ pr_debug("%s: audio_cond[0x%x], audio_state[0x%x]\n", __func__,
+ atomic_read(&audio_ocmem_lcl.audio_cond),
+ atomic_read(&audio_ocmem_lcl.audio_state));
+ switch (atomic_read(&audio_ocmem_lcl.audio_state)) {
+ case OCMEM_STATE_MAP_COMPL:
+ atomic_set(&audio_ocmem_lcl.audio_cond, 1);
+ ret = ocmem_unmap(cid, audio_ocmem_lcl.buf,
+ &audio_ocmem_lcl.mlist);
+ if (ret) {
+ pr_err("%s: ocmem_unmap failed, state[%d]\n",
+ __func__,
+ atomic_read(&audio_ocmem_lcl.audio_state));
+ goto fail_cmd;
+ }
+
+ atomic_set(&audio_ocmem_lcl.audio_state, OCMEM_STATE_EXIT);
+
+ wait_event_interruptible(audio_ocmem_lcl.audio_wait,
+ atomic_read(&audio_ocmem_lcl.audio_cond) == 0);
+ case OCMEM_STATE_UNMAP_COMPL:
+ ret = ocmem_free(OCMEM_LP_AUDIO, audio_ocmem_lcl.buf);
+ if (ret) {
+ pr_err("%s: ocmem_free failed, state[%d]\n",
+ __func__,
+ atomic_read(&audio_ocmem_lcl.audio_state));
+ goto fail_cmd;
+ }
+ pr_debug("%s: ocmem_free success\n", __func__);
+ default:
+ pr_debug("%s: state=%d", __func__,
+ atomic_read(&audio_ocmem_lcl.audio_state));
+ break;
+
+ }
+ return 0;
+fail_cmd:
+ return ret;
+}
+
+static void voice_ocmem_process_workdata(struct work_struct *work)
+{
+ int cid;
+ bool en;
+ int rc = 0;
+
+ struct voice_ocmem_workdata *voice_ocm_work =
+ container_of(work, struct voice_ocmem_workdata, work);
+
+ en = voice_ocm_work->en;
+ switch (voice_ocm_work->id) {
+ case VOICE:
+ cid = OCMEM_VOICE;
+ if (en)
+ disable_ocmem_for_voice(cid);
+ else
+ enable_ocmem_after_voice(cid);
+ break;
+ default:
+ pr_err("%s: Invalid client id[%d]\n", __func__,
+ voice_ocm_work->id);
+ rc = -EINVAL;
+ }
+
+}
+/**
+ * voice_ocmem_process_req() - disable/enable OCMEM during voice call
+ * @cid: client id - VOICE
+ * @enable: 1 - enable
+ * 0 - disable
+ *
+ * This configures OCMEM during start of voice call. If any
+ * audio clients are already using OCMEM, they will be evicted
+ * out of OCMEM during voice call and get restored after voice
+ * call.
+ */
+int voice_ocmem_process_req(int cid, bool enable)
+{
+
+ struct voice_ocmem_workdata *workdata = NULL;
+
+ if (audio_ocmem_lcl.voice_ocmem_workqueue == NULL) {
+ pr_err("%s: voice ocmem workqueue is NULL\n", __func__);
+ return -EINVAL;
+ }
+ workdata = kzalloc(sizeof(struct voice_ocmem_workdata),
+ GFP_ATOMIC);
+ if (workdata == NULL) {
+ pr_err("%s: mem failure\n", __func__);
+ return -ENOMEM;
+ }
+ workdata->id = cid;
+ workdata->en = enable;
+
+ INIT_WORK(&workdata->work, voice_ocmem_process_workdata);
+ queue_work(audio_ocmem_lcl.voice_ocmem_workqueue, &workdata->work);
+
+ return 0;
+}
+
+/**
+ * disable_ocmem_for_voice() - disable OCMEM during voice call
+ * @cid: client id - OCMEM_VOICE
+ *
+ * This configures OCMEM during start of voice call. If any
+ * audio clients are already using OCMEM, they will be evicted
+ */
+int disable_ocmem_for_voice(int cid)
+{
+ int ret;
+
+ ret = ocmem_evict(cid);
+ if (ret)
+ pr_err("%s: ocmem_evict is not successful\n", __func__);
+ return ret;
+}
+
+/**
+ * enable_ocmem_for_voice() - To enable OCMEM after voice call
+ * @cid: client id - OCMEM_VOICE
+ *
+ * OCMEM gets re-enabled after OCMEM voice call. If other client
+ * is evicted out of OCMEM, that gets restored and remapped in
+ * OCMEM after the voice call.
+ */
+int enable_ocmem_after_voice(int cid)
+{
+ int ret;
+
+ ret = ocmem_restore(cid);
+ if (ret)
+ pr_err("%s: ocmem_restore is not successful\n", __func__);
+ return ret;
+}
+
+
+static void audio_ocmem_process_workdata(struct work_struct *work)
+{
+ int cid;
+ bool en;
+ int rc = 0;
+
+ struct audio_ocmem_workdata *audio_ocm_work =
+ container_of(work, struct audio_ocmem_workdata, work);
+
+ en = audio_ocm_work->en;
+ switch (audio_ocm_work->id) {
+ case AUDIO:
+ cid = OCMEM_LP_AUDIO;
+ if (en)
+ audio_ocmem_enable(cid);
+ else
+ audio_ocmem_disable(cid);
+ break;
+ default:
+ pr_err("%s: Invalid client id[%d]\n", __func__,
+ audio_ocm_work->id);
+ rc = -EINVAL;
+ }
+
+}
+
+/**
+ * audio_ocmem_process_req() - process audio request to use OCMEM
+ * @id: client id - OCMEM_LP_AUDIO
+ * @enable: enable or disable OCMEM
+ *
+ * A workqueue gets created and initialized to use OCMEM for
+ * audio clients.
+ */
+int audio_ocmem_process_req(int id, bool enable)
+{
+ struct audio_ocmem_workdata *workdata = NULL;
+
+ if (audio_ocmem_lcl.audio_ocmem_workqueue == NULL) {
+ pr_err("%s: audio ocmem workqueue is NULL\n", __func__);
+ return -EINVAL;
+ }
+ workdata = kzalloc(sizeof(struct audio_ocmem_workdata),
+ GFP_ATOMIC);
+ if (workdata == NULL) {
+ pr_err("%s: mem failure\n", __func__);
+ return -ENOMEM;
+ }
+ workdata->id = id;
+ workdata->en = enable;
+
+ /* if previous work waiting for ocmem - signal it to exit */
+ atomic_set(&audio_ocmem_lcl.audio_exit, 1);
+
+ INIT_WORK(&workdata->work, audio_ocmem_process_workdata);
+ queue_work(audio_ocmem_lcl.audio_ocmem_workqueue, &workdata->work);
+
+ return 0;
+}
+
+
+static struct notifier_block audio_ocmem_client_nb = {
+ .notifier_call = audio_ocmem_client_cb,
+};
+
+static int audio_ocmem_platform_data_populate(struct platform_device *pdev)
+{
+ int ret;
+ struct msm_bus_scale_pdata *audio_ocmem_bus_scale_pdata = NULL;
+ struct msm_bus_vectors *audio_ocmem_bus_vectors = NULL;
+ struct msm_bus_paths *ocmem_audio_bus_paths = NULL;
+ u32 val;
+
+ if (!pdev->dev.of_node) {
+ pr_err("%s: device tree information missing\n", __func__);
+ return -ENODEV;
+ }
+
+ audio_ocmem_bus_vectors = kzalloc(sizeof(struct msm_bus_vectors),
+ GFP_KERNEL);
+ if (!audio_ocmem_bus_vectors) {
+ dev_err(&pdev->dev, "Failed to allocate memory for platform data\n");
+ return -ENOMEM;
+ }
+
+ ret = of_property_read_u32(pdev->dev.of_node,
+ "qcom,msm-ocmem-audio-src-id", &val);
+ if (ret) {
+ dev_err(&pdev->dev, "%s: qcom,msm-ocmem-audio-src-id missing in DT node\n",
+ __func__);
+ goto fail1;
+ }
+ audio_ocmem_bus_vectors->src = val;
+ ret = of_property_read_u32(pdev->dev.of_node,
+ "qcom,msm-ocmem-audio-dst-id", &val);
+ if (ret) {
+ dev_err(&pdev->dev, "%s: qcom,msm-ocmem-audio-dst-id missing in DT node\n",
+ __func__);
+ goto fail1;
+ }
+ audio_ocmem_bus_vectors->dst = val;
+ ret = of_property_read_u32(pdev->dev.of_node,
+ "qcom,msm-ocmem-audio-ab", &val);
+ if (ret) {
+ dev_err(&pdev->dev, "%s: qcom,msm-ocmem-audio-ab missing in DT node\n",
+ __func__);
+ goto fail1;
+ }
+ audio_ocmem_bus_vectors->ab = val;
+ ret = of_property_read_u32(pdev->dev.of_node,
+ "qcom,msm-ocmem-audio-ib", &val);
+ if (ret) {
+ dev_err(&pdev->dev, "%s: qcom,msm-ocmem-audio-ib missing in DT node\n",
+ __func__);
+ goto fail1;
+ }
+ audio_ocmem_bus_vectors->ib = val;
+
+ ocmem_audio_bus_paths = kzalloc(sizeof(struct msm_bus_paths),
+ GFP_KERNEL);
+ if (!ocmem_audio_bus_paths) {
+ dev_err(&pdev->dev, "Failed to allocate memory for platform data\n");
+ goto fail1;
+ }
+ ocmem_audio_bus_paths->num_paths = 1;
+ ocmem_audio_bus_paths->vectors = audio_ocmem_bus_vectors;
+
+ audio_ocmem_bus_scale_pdata =
+ kzalloc(sizeof(struct msm_bus_scale_pdata), GFP_KERNEL);
+
+ if (!audio_ocmem_bus_scale_pdata) {
+ dev_err(&pdev->dev, "Failed to allocate memory for platform data\n");
+ goto fail2;
+ }
+
+ audio_ocmem_bus_scale_pdata->usecase = ocmem_audio_bus_paths;
+ audio_ocmem_bus_scale_pdata->num_usecases = 1;
+ audio_ocmem_bus_scale_pdata->name = "audio-ocmem";
+
+ dev_set_drvdata(&pdev->dev, audio_ocmem_bus_scale_pdata);
+ return ret;
+
+fail2:
+ kfree(ocmem_audio_bus_paths);
+fail1:
+ kfree(audio_ocmem_bus_vectors);
+ return ret;
+}
+static int ocmem_audio_client_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct msm_bus_scale_pdata *audio_ocmem_bus_scale_pdata = NULL;
+
+ pr_debug("%s\n", __func__);
+ audio_ocmem_lcl.audio_ocmem_workqueue =
+ alloc_workqueue("ocmem_audio_client_driver_audio",
+ WQ_NON_REENTRANT, 0);
+ if (!audio_ocmem_lcl.audio_ocmem_workqueue) {
+ pr_err("%s: Failed to create ocmem audio work queue\n",
+ __func__);
+ return -ENOMEM;
+ }
+
+ audio_ocmem_lcl.voice_ocmem_workqueue =
+ alloc_workqueue("ocmem_audio_client_driver_voice",
+ WQ_NON_REENTRANT, 0);
+ if (!audio_ocmem_lcl.voice_ocmem_workqueue) {
+ pr_err("%s: Failed to create ocmem voice work queue\n",
+ __func__);
+ return -ENOMEM;
+ }
+
+ init_waitqueue_head(&audio_ocmem_lcl.audio_wait);
+ atomic_set(&audio_ocmem_lcl.audio_cond, 1);
+ atomic_set(&audio_ocmem_lcl.audio_exit, 0);
+ spin_lock_init(&audio_ocmem_lcl.audio_lock);
+
+ /* populate platform data */
+ ret = audio_ocmem_platform_data_populate(pdev);
+ if (ret) {
+ dev_err(&pdev->dev, "%s: failed to populate platform data, rc = %d\n",
+ __func__, ret);
+ return -ENODEV;
+ }
+ audio_ocmem_bus_scale_pdata = dev_get_drvdata(&pdev->dev);
+
+ audio_ocmem_lcl.audio_ocmem_bus_client =
+ msm_bus_scale_register_client(audio_ocmem_bus_scale_pdata);
+
+ if (!audio_ocmem_lcl.audio_ocmem_bus_client) {
+ pr_err("%s: msm_bus_scale_register_client() failed\n",
+ __func__);
+ return -EFAULT;
+ }
+ audio_ocmem_lcl.audio_hdl = ocmem_notifier_register(OCMEM_LP_AUDIO,
+ &audio_ocmem_client_nb);
+ if (audio_ocmem_lcl.audio_hdl == NULL) {
+ pr_err("%s: Failed to get ocmem handle %d\n", __func__,
+ OCMEM_LP_AUDIO);
+ }
+ audio_ocmem_lcl.lp_memseg_ptr = NULL;
+ return 0;
+}
+
+static int ocmem_audio_client_remove(struct platform_device *pdev)
+{
+ struct msm_bus_scale_pdata *audio_ocmem_bus_scale_pdata = NULL;
+
+ audio_ocmem_bus_scale_pdata = (struct msm_bus_scale_pdata *)
+ dev_get_drvdata(&pdev->dev);
+
+ kfree(audio_ocmem_bus_scale_pdata->usecase->vectors);
+ kfree(audio_ocmem_bus_scale_pdata->usecase);
+ kfree(audio_ocmem_bus_scale_pdata);
+ ocmem_notifier_unregister(audio_ocmem_lcl.audio_hdl,
+ &audio_ocmem_client_nb);
+ return 0;
+}
+static const struct of_device_id msm_ocmem_audio_dt_match[] = {
+ {.compatible = "qcom,msm-ocmem-audio"},
+ {}
+};
+MODULE_DEVICE_TABLE(of, msm_ocmem_audio_dt_match);
+
+static struct platform_driver audio_ocmem_driver = {
+ .driver = {
+ .name = "audio-ocmem",
+ .owner = THIS_MODULE,
+ .of_match_table = msm_ocmem_audio_dt_match,
+ },
+ .probe = ocmem_audio_client_probe,
+ .remove = ocmem_audio_client_remove,
+};
+
+
+static int __init ocmem_audio_client_init(void)
+{
+ int rc;
+
+ rc = platform_driver_register(&audio_ocmem_driver);
+
+ if (rc)
+ pr_err("%s: Failed to register audio ocmem driver\n", __func__);
+ return rc;
+}
+module_init(ocmem_audio_client_init);
+
+static void __exit ocmem_audio_client_exit(void)
+{
+ platform_driver_unregister(&audio_ocmem_driver);
+}
+
+module_exit(ocmem_audio_client_exit);
diff --git a/sound/soc/msm/qdsp6v2/audio_ocmem.h b/sound/soc/msm/qdsp6v2/audio_ocmem.h
new file mode 100644
index 0000000..e915516
--- /dev/null
+++ b/sound/soc/msm/qdsp6v2/audio_ocmem.h
@@ -0,0 +1,38 @@
+/* Copyright (c) 2012, 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.
+ */
+
+#ifndef _AUDIO_OCMEM_H_
+#define _AUDIO_OCMEM_H_
+
+#include <linux/module.h>
+#include <linux/notifier.h>
+
+#include <mach/ocmem.h>
+
+#define AUDIO 0
+#define VOICE 1
+
+#ifdef CONFIG_AUDIO_OCMEM
+int audio_ocmem_process_req(int id, bool enable);
+int voice_ocmem_process_req(int cid, bool enable);
+int enable_ocmem_after_voice(int cid);
+int disable_ocmem_for_voice(int cid);
+#else
+static inline int audio_ocmem_process_req(int id, bool enable)\
+ { return 0; }
+static inline int voice_ocmem_process_req(int cid, bool enable)\
+ { return 0; }
+static inline int enable_ocmem_after_voice(int cid) { return 0; }
+static inline int disable_ocmem_for_voice(int cid) { return 0; }
+#endif
+
+#endif
diff --git a/sound/soc/msm/qdsp6v2/msm-pcm-lpa-v2.c b/sound/soc/msm/qdsp6v2/msm-pcm-lpa-v2.c
index 1ac872d..047e0f0 100644
--- a/sound/soc/msm/qdsp6v2/msm-pcm-lpa-v2.c
+++ b/sound/soc/msm/qdsp6v2/msm-pcm-lpa-v2.c
@@ -35,6 +35,7 @@
#include "msm-pcm-q6-v2.h"
#include "msm-pcm-routing-v2.h"
+#include "audio_ocmem.h"
static struct audio_locks the_locks;
@@ -223,6 +224,7 @@
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
prtd->pcm_irq_pos = 0;
+ audio_ocmem_process_req(AUDIO, true);
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
pr_debug("SNDRV_PCM_TRIGGER_START\n");
@@ -231,6 +233,7 @@
break;
case SNDRV_PCM_TRIGGER_STOP:
pr_debug("SNDRV_PCM_TRIGGER_STOP\n");
+ audio_ocmem_process_req(AUDIO, false);
atomic_set(&prtd->start, 0);
if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK)
break;
diff --git a/sound/soc/msm/qdsp6v2/msm-pcm-voip-v2.c b/sound/soc/msm/qdsp6v2/msm-pcm-voip-v2.c
index 630405a..492569b 100644
--- a/sound/soc/msm/qdsp6v2/msm-pcm-voip-v2.c
+++ b/sound/soc/msm/qdsp6v2/msm-pcm-voip-v2.c
@@ -30,6 +30,7 @@
#include "msm-pcm-q6-v2.h"
#include "msm-pcm-routing-v2.h"
#include "q6voice.h"
+#include "audio_ocmem.h"
#define VOIP_MAX_Q_LEN 10
#define VOIP_MAX_VOC_PKT_SIZE 640
@@ -452,6 +453,8 @@
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
pr_debug("%s: Trigger start\n", __func__);
+ if ((!prtd->capture_start) && (!prtd->playback_start))
+ voice_ocmem_process_req(VOICE, true);
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
prtd->capture_start = 1;
else
@@ -459,6 +462,8 @@
break;
case SNDRV_PCM_TRIGGER_STOP:
pr_debug("SNDRV_PCM_TRIGGER_STOP\n");
+ if (prtd->capture_start && prtd->playback_start)
+ voice_ocmem_process_req(VOICE, false);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
prtd->playback_start = 0;
else
diff --git a/sound/soc/msm/qdsp6v2/q6core.c b/sound/soc/msm/qdsp6v2/q6core.c
new file mode 100644
index 0000000..2c31d39
--- /dev/null
+++ b/sound/soc/msm/qdsp6v2/q6core.c
@@ -0,0 +1,211 @@
+/* Copyright (c) 2012, 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/kernel.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <mach/msm_smd.h>
+#include <mach/qdsp6v2/apr.h>
+#include "q6core.h"
+#include <mach/ocmem.h>
+
+#define TIMEOUT_MS 1000
+
+struct q6core_str {
+ struct apr_svc *core_handle_q;
+ wait_queue_head_t bus_bw_req_wait;
+ u32 bus_bw_resp_received;
+ struct avcs_cmd_rsp_get_low_power_segments_info_t *lp_ocm_payload;
+};
+
+struct q6core_str q6core_lcl;
+
+static int32_t aprv2_core_fn_q(struct apr_client_data *data, void *priv)
+{
+ uint32_t *payload1;
+ uint32_t nseg;
+ int i, j;
+
+ pr_info("core msg: payload len = %u, apr resp opcode = 0x%X\n",
+ data->payload_size, data->opcode);
+
+ switch (data->opcode) {
+
+ case APR_BASIC_RSP_RESULT:{
+
+ if (data->payload_size == 0) {
+ pr_err("%s: APR_BASIC_RSP_RESULT No Payload ",
+ __func__);
+ return 0;
+ }
+
+ payload1 = data->payload;
+
+ switch (payload1[0]) {
+
+ case AVCS_CMD_GET_LOW_POWER_SEGMENTS_INFO:
+ pr_info("%s: Cmd = AVCS_CMD_GET_LOW_POWER_SEGMENTS_INFO status[0x%x]\n",
+ __func__, payload1[1]);
+ break;
+ default:
+ pr_err("Invalid cmd rsp[0x%x][0x%x]\n",
+ payload1[0], payload1[1]);
+ break;
+ }
+ break;
+ }
+
+ case AVCS_CMDRSP_GET_LOW_POWER_SEGMENTS_INFO:
+ payload1 = data->payload;
+ pr_info("%s: cmd = AVCS_CMDRSP_GET_LOW_POWER_SEGMENTS_INFO num_segments = 0x%x\n",
+ __func__, payload1[0]);
+ nseg = payload1[0];
+ q6core_lcl.lp_ocm_payload->num_segments = nseg;
+ q6core_lcl.lp_ocm_payload->bandwidth = payload1[1];
+ for (i = 0, j = 2; i < nseg; i++) {
+ q6core_lcl.lp_ocm_payload->mem_segment[i].type =
+ (payload1[j] & 0xffff);
+ q6core_lcl.lp_ocm_payload->mem_segment[i].category =
+ ((payload1[j++] >> 16) & 0xffff);
+ q6core_lcl.lp_ocm_payload->mem_segment[i].size =
+ payload1[j++];
+ q6core_lcl.lp_ocm_payload->
+ mem_segment[i].start_address_lsw =
+ payload1[j++];
+ q6core_lcl.lp_ocm_payload->
+ mem_segment[i].start_address_msw =
+ payload1[j++];
+ }
+
+ q6core_lcl.bus_bw_resp_received = 1;
+ wake_up(&q6core_lcl.bus_bw_req_wait);
+ break;
+
+ case RESET_EVENTS:{
+ pr_debug("Reset event received in Core service");
+ apr_reset(q6core_lcl.core_handle_q);
+ q6core_lcl.core_handle_q = NULL;
+ break;
+ }
+
+ default:
+ pr_err("Message id from adsp core svc: %d\n", data->opcode);
+ break;
+ }
+
+ return 0;
+}
+
+
+void ocm_core_open(void)
+{
+ if (q6core_lcl.core_handle_q == NULL)
+ q6core_lcl.core_handle_q = apr_register("ADSP", "CORE",
+ aprv2_core_fn_q, 0xFFFFFFFF, NULL);
+ pr_debug("Open_q %p\n", q6core_lcl.core_handle_q);
+ if (q6core_lcl.core_handle_q == NULL)
+ pr_err("%s: Unable to register CORE\n", __func__);
+}
+
+int core_get_low_power_segments(
+ struct avcs_cmd_rsp_get_low_power_segments_info_t **lp_memseg)
+{
+ struct avcs_cmd_get_low_power_segments_info lp_ocm_cmd;
+ u8 *cptr = NULL;
+ int ret = 0;
+
+ pr_debug("%s: ", __func__);
+
+ ocm_core_open();
+ if (q6core_lcl.core_handle_q == NULL) {
+ pr_info("%s: apr registration for CORE failed\n", __func__);
+ return -ENODEV;
+ }
+
+ cptr = kzalloc(
+ sizeof(struct avcs_cmd_rsp_get_low_power_segments_info_t),
+ GFP_KERNEL);
+ if (!cptr) {
+ pr_err("%s: Failed to allocate memory for low power segment struct\n",
+ __func__);
+ return -ENOMEM;
+ }
+ q6core_lcl.lp_ocm_payload =
+ (struct avcs_cmd_rsp_get_low_power_segments_info_t *) cptr;
+
+ lp_ocm_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
+ APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER);
+ lp_ocm_cmd.hdr.pkt_size =
+ sizeof(struct avcs_cmd_get_low_power_segments_info);
+
+ lp_ocm_cmd.hdr.src_port = 0;
+ lp_ocm_cmd.hdr.dest_port = 0;
+ lp_ocm_cmd.hdr.token = 0;
+ lp_ocm_cmd.hdr.opcode = AVCS_CMD_GET_LOW_POWER_SEGMENTS_INFO;
+
+
+ ret = apr_send_pkt(q6core_lcl.core_handle_q, (uint32_t *) &lp_ocm_cmd);
+ if (ret < 0) {
+ pr_err("%s: CORE low power segment request failed\n", __func__);
+ goto fail_cmd;
+ }
+
+ ret = wait_event_timeout(q6core_lcl.bus_bw_req_wait,
+ (q6core_lcl.bus_bw_resp_received == 1),
+ msecs_to_jiffies(TIMEOUT_MS));
+ if (!ret) {
+ pr_err("%s: wait_event timeout for GET_LOW_POWER_SEGMENTS\n",
+ __func__);
+ ret = -ETIME;
+ goto fail_cmd;
+ }
+
+ *lp_memseg = q6core_lcl.lp_ocm_payload;
+ return 0;
+
+fail_cmd:
+ return ret;
+}
+
+
+static int __init core_init(void)
+{
+ init_waitqueue_head(&q6core_lcl.bus_bw_req_wait);
+ q6core_lcl.bus_bw_resp_received = 0;
+
+ q6core_lcl.core_handle_q = NULL;
+ q6core_lcl.lp_ocm_payload = kzalloc(
+ sizeof(struct avcs_cmd_rsp_get_low_power_segments_info_t), GFP_KERNEL);
+
+ if (!q6core_lcl.lp_ocm_payload) {
+ pr_err("%s: Failed to allocate memory for low power segment struct\n",
+ __func__);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+module_init(core_init);
+
+static void __exit core_exit(void)
+{
+ kfree(q6core_lcl.lp_ocm_payload);
+}
+module_exit(core_exit);
+MODULE_DESCRIPTION("ADSP core driver");
+MODULE_LICENSE("GPL v2");
+
diff --git a/sound/soc/msm/qdsp6v2/q6core.h b/sound/soc/msm/qdsp6v2/q6core.h
new file mode 100644
index 0000000..5cb6098
--- /dev/null
+++ b/sound/soc/msm/qdsp6v2/q6core.h
@@ -0,0 +1,93 @@
+/* Copyright (c) 2012, 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.
+ */
+
+#ifndef __Q6CORE_H__
+#define __Q6CORE_H__
+#include <mach/qdsp6v2/apr.h>
+#include <mach/ocmem.h>
+
+
+#define AVCS_CMD_GET_LOW_POWER_SEGMENTS_INFO 0x00012903
+
+struct avcs_cmd_get_low_power_segments_info {
+ struct apr_hdr hdr;
+} __packed;
+
+
+#define AVCS_CMDRSP_GET_LOW_POWER_SEGMENTS_INFO 0x00012904
+
+/* @brief AVCS_CMDRSP_GET_LOW_POWER_SEGMENTS_INFO payload
+ * structure. Payload for this event comprises one instance of
+ * avcs_cmd_rsp_get_low_power_segments_info_t, followed
+ * immediately by num_segments number of instances of the
+ * avcs_mem_segment_t structure.
+ */
+
+/* Types of Low Power Memory Segments. */
+#define READ_ONLY_SEGMENT 1
+/*< Read Only memory segment. */
+#define READ_WRITE_SEGMENT 2
+/*< Read Write memory segment. */
+/*Category indicates whether audio/os/sensor segments. */
+#define AUDIO_SEGMENT 1
+/*< Audio memory segment. */
+#define OS_SEGMENT 2
+/*< QDSP6's OS memory segment. */
+
+/* @brief Payload structure for AVS low power memory segment
+ * structure.
+ */
+struct avcs_mem_segment_t {
+ uint16_t type;
+/*< Indicates which type of memory this segment is.
+ *Allowed values: READ_ONLY_SEGMENT or READ_WRITE_SEGMENT only.
+ */
+ uint16_t category;
+/*< Indicates whether audio or OS segments.
+ *Allowed values: AUDIO_SEGMENT or OS_SEGMENT only.
+ */
+ uint32_t size;
+/*< Size (in bytes) of this segment.
+ * Will be a non-zero value.
+ */
+ uint32_t start_address_lsw;
+/*< Lower 32 bits of the 64-bit physical start address
+ * of this segment.
+ */
+ uint32_t start_address_msw;
+/*< Upper 32 bits of the 64-bit physical start address
+ * of this segment.
+ */
+};
+
+struct avcs_cmd_rsp_get_low_power_segments_info_t {
+ uint32_t num_segments;
+/*< Number of segments in this response.
+ * 0: there are no known sections that should be mapped
+ * from DDR to OCMEM.
+ * >0: the number of memory segments in the following list.
+ */
+
+ uint32_t bandwidth;
+/*< Required OCMEM read/write bandwidth (in bytes per second)
+ * if OCMEM is granted.
+ * 0 if num_segments = 0
+ * >0 if num_segments > 0.
+ */
+ struct avcs_mem_segment_t mem_segment[OCMEM_MAX_CHUNKS];
+};
+
+
+int core_get_low_power_segments(
+ struct avcs_cmd_rsp_get_low_power_segments_info_t **);
+
+#endif /* __Q6CORE_H__ */