/* Copyright (c) 2012-2013, The Linux Foundation. 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "q6core.h" #include "audio_ocmem.h" #define AUDIO_OCMEM_BUF_SIZE (512 * SZ_1K) /** * Exercise OCMEM Dump if audio OCMEM state is * one of the following. All other states indicate * audio data is not mapped from DDR to OCMEM and * therefore no need of dump. */ #define _DO_OCMEM_DUMP_BIT_MASK_\ ((1 << OCMEM_STATE_MAP_COMPL) |\ (1 << OCMEM_STATE_MAP_TRANSITION) |\ (1 << OCMEM_STATE_UNMAP_TRANSITION) |\ (1 << OCMEM_STATE_SHRINK) |\ (1 << OCMEM_STATE_GROW)) /** * Wait for OCMEM driver to process and respond for * ongoing map/unmap request before calling OCMEM dump. */ #define _WAIT_BFR_DUMP_BIT_MASK_\ ((1 << OCMEM_STATE_MAP_COMPL) |\ (1 << OCMEM_STATE_UNMAP_COMPL) |\ (1 << OCMEM_STATE_MAP_FAIL) |\ (1 << OCMEM_STATE_UNMAP_FAIL)) #define _MAP_RESPONSE_BIT_MASK_\ ((1 << OCMEM_STATE_MAP_COMPL) |\ (1 << OCMEM_STATE_MAP_FAIL)) #define _UNMAP_RESPONSE_BIT_MASK_\ ((1 << OCMEM_STATE_UNMAP_COMPL) |\ (1 << OCMEM_STATE_UNMAP_FAIL)) #define _BIT_MASK_\ ((1 << OCMEM_STATE_SSR) |\ (1 << OCMEM_STATE_EXIT) |\ (1 << OCMEM_STATE_GROW) |\ (1 << OCMEM_STATE_SHRINK)) #define set_bit_pos(x, y) (atomic_set(&x, (atomic_read(&x) | (1 << y)))) #define clear_bit_pos(x, y) (atomic_set(&x, (atomic_read(&x) & (~(1 << y))))) #define test_bit_pos(x, y) ((atomic_read(&x)) & (1 << y)) static int enable_ocmem_audio_voice = 1; module_param(enable_ocmem_audio_voice, int, S_IRUGO | S_IWUSR | S_IWGRP); MODULE_PARM_DESC(enable_ocmem_audio_voice, "control OCMEM usage for audio/voice"); enum { OCMEM_STATE_DEFAULT = 0, 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, OCMEM_STATE_SSR, OCMEM_STATE_DISABLE, }; 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 mutex state_process_lock; struct workqueue_struct *audio_ocmem_workqueue; struct workqueue_struct *voice_ocmem_workqueue; bool ocmem_en; bool audio_ocmem_running; void *ocmem_ramdump_dev; struct ramdump_segment ocmem_ramdump_segment; unsigned long ocmem_dump_addr; }; 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; struct ocmem_buf *rbuf; int vwait = 0; 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__); clear_bit_pos(audio_ocmem_lcl.audio_state, OCMEM_STATE_MAP_TRANSITION); set_bit_pos(audio_ocmem_lcl.audio_state, OCMEM_STATE_MAP_COMPL); break; case OCMEM_MAP_FAIL: pr_debug("%s: map fail\n", __func__); clear_bit_pos(audio_ocmem_lcl.audio_state, OCMEM_STATE_MAP_TRANSITION); set_bit_pos(audio_ocmem_lcl.audio_state, OCMEM_STATE_MAP_FAIL); break; case OCMEM_UNMAP_DONE: pr_debug("%s: unmap done\n", __func__); clear_bit_pos(audio_ocmem_lcl.audio_state, OCMEM_STATE_UNMAP_TRANSITION); set_bit_pos(audio_ocmem_lcl.audio_state, OCMEM_STATE_UNMAP_COMPL); break; case OCMEM_UNMAP_FAIL: pr_debug("%s: unmap fail\n", __func__); clear_bit_pos(audio_ocmem_lcl.audio_state, OCMEM_STATE_UNMAP_TRANSITION); set_bit_pos(audio_ocmem_lcl.audio_state, OCMEM_STATE_UNMAP_FAIL); break; case OCMEM_ALLOC_GROW: rbuf = data; if ((rbuf->len == AUDIO_OCMEM_BUF_SIZE)) { audio_ocmem_lcl.buf = data; pr_debug("%s: Alloc grow request received buf->addr: 0x%08lx\n", __func__, (audio_ocmem_lcl.buf)->addr); set_bit_pos(audio_ocmem_lcl.audio_state, OCMEM_STATE_GROW); } else { pr_debug("%s: Alloc grow request with size: %ld", __func__, rbuf->len); vwait = 1; } break; case OCMEM_ALLOC_SHRINK: pr_debug("%s: Alloc shrink request received\n", __func__); set_bit_pos(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); atomic_set(&audio_ocmem_lcl.audio_cond, 0); wake_up(&audio_ocmem_lcl.audio_wait); return rc; } int get_state_to_process(atomic_t *state) { if (test_bit_pos((*state), OCMEM_STATE_SHRINK)) { pr_debug("%s: returning shrink state\n", __func__); return OCMEM_STATE_SHRINK; } else if (test_bit_pos((*state), OCMEM_STATE_GROW)) { pr_debug("%s: returning grow state\n", __func__); return OCMEM_STATE_GROW; } else if (test_bit_pos((*state), OCMEM_STATE_EXIT)) { pr_debug("%s: returning exit state\n", __func__); return OCMEM_STATE_EXIT; } else if (test_bit_pos((*state), OCMEM_STATE_SSR)) { pr_debug("%s: returning ssr state\n", __func__); return OCMEM_STATE_SSR; } else return -EINVAL; } /** * 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; int state_bit; struct ocmem_buf *buf = NULL; struct avcs_cmd_rsp_get_low_power_segments_info_t *lp_segptr; pr_debug("%s, %p\n", __func__, &audio_ocmem_lcl); atomic_set(&audio_ocmem_lcl.audio_state, OCMEM_STATE_DEFAULT); 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); mutex_unlock(&audio_ocmem_lcl.state_process_lock); 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[0x%08x], size[0x%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); } /* 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); ret = -ENOMEM; mutex_unlock(&audio_ocmem_lcl.state_process_lock); goto fail_cmd; } set_bit_pos(audio_ocmem_lcl.audio_state, OCMEM_STATE_ALLOC); audio_ocmem_lcl.buf = buf; atomic_set(&audio_ocmem_lcl.audio_exit, 0); atomic_set(&audio_ocmem_lcl.audio_cond, 1); pr_debug("%s: buf->len: %ld\n", __func__, buf->len); if (!buf->len) { pr_debug("%s: buf.len is 0, waiting for ocmem region\n", __func__); mutex_unlock(&audio_ocmem_lcl.state_process_lock); 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)) { 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)); } pr_info("%s: audio playback ended while waiting for ocmem\n", __func__); ret = 0; goto fail_cmd; } clear_bit_pos(audio_ocmem_lcl.audio_state, OCMEM_STATE_GROW); mutex_trylock(&audio_ocmem_lcl.state_process_lock); } pr_debug("%s: buf->len: %ld\n", __func__, (audio_ocmem_lcl.buf)->len); /* vote for ocmem bus bandwidth */ ret = msm_bus_scale_client_update_request( audio_ocmem_lcl.audio_ocmem_bus_client, 1); if (ret) pr_err("%s: failed to vote for bus bandwidth\n", __func__); set_bit_pos(audio_ocmem_lcl.audio_state, OCMEM_STATE_MAP_TRANSITION); pr_debug("%s: buf->addr: 0x%08lx, len: %ld, audio_state[0x%x]\n", __func__, audio_ocmem_lcl.buf->addr, audio_ocmem_lcl.buf->len, atomic_read(&audio_ocmem_lcl.audio_state)); 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\n", __func__); atomic_set(&audio_ocmem_lcl.audio_state, OCMEM_STATE_MAP_FAIL); goto fail_cmd1; } wait_event_interruptible(audio_ocmem_lcl.audio_wait, (atomic_read(&audio_ocmem_lcl.audio_state) & _MAP_RESPONSE_BIT_MASK_) != 0); atomic_set(&audio_ocmem_lcl.audio_cond, 1); mutex_unlock(&audio_ocmem_lcl.state_process_lock); pr_debug("%s: audio_cond[%d] audio_state[0x%x]\n", __func__, atomic_read(&audio_ocmem_lcl.audio_cond), atomic_read(&audio_ocmem_lcl.audio_state)); while ((test_bit_pos(audio_ocmem_lcl.audio_state, OCMEM_STATE_DISABLE)) == 0) { wait_event_interruptible(audio_ocmem_lcl.audio_wait, (atomic_read(&audio_ocmem_lcl.audio_state) & _BIT_MASK_) != 0); mutex_lock(&audio_ocmem_lcl.state_process_lock); state_bit = get_state_to_process(&audio_ocmem_lcl.audio_state); switch (state_bit) { 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: pr_debug("%s: ocmem shrink request process\n", __func__); atomic_set(&audio_ocmem_lcl.audio_cond, 1); clear_bit_pos(audio_ocmem_lcl.audio_state, OCMEM_STATE_MAP_COMPL); set_bit_pos(audio_ocmem_lcl.audio_state, OCMEM_STATE_UNMAP_TRANSITION); 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_cmd1; } wait_event_interruptible(audio_ocmem_lcl.audio_wait, (atomic_read(&audio_ocmem_lcl.audio_state) & _UNMAP_RESPONSE_BIT_MASK_) != 0); 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_cmd1; } atomic_set(&audio_ocmem_lcl.audio_cond, 1); clear_bit_pos(audio_ocmem_lcl.audio_state, OCMEM_STATE_SHRINK); pr_debug("%s:shrink process complete\n", __func__); break; case OCMEM_STATE_GROW: pr_debug("%s: ocmem grow request process\n", __func__); atomic_set(&audio_ocmem_lcl.audio_cond, 1); clear_bit_pos(audio_ocmem_lcl.audio_state, OCMEM_STATE_UNMAP_COMPL); set_bit_pos(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, state[%d]\n", __func__, atomic_read(&audio_ocmem_lcl.audio_state)); goto fail_cmd1; } wait_event_interruptible(audio_ocmem_lcl.audio_wait, (atomic_read(&audio_ocmem_lcl.audio_state) & _MAP_RESPONSE_BIT_MASK_) != 0); clear_bit_pos(audio_ocmem_lcl.audio_state, OCMEM_STATE_GROW); atomic_set(&audio_ocmem_lcl.audio_cond, 1); break; case OCMEM_STATE_EXIT: if (test_bit_pos(audio_ocmem_lcl.audio_state, OCMEM_STATE_MAP_COMPL)) { clear_bit_pos(audio_ocmem_lcl.audio_state, OCMEM_STATE_MAP_COMPL); set_bit_pos(audio_ocmem_lcl.audio_state, OCMEM_STATE_UNMAP_TRANSITION); ret = ocmem_unmap(cid, audio_ocmem_lcl.buf, &audio_ocmem_lcl.mlist); if (ret) { pr_err("%s: ocmem_unmap failed, state[0x%x]\n", __func__, atomic_read(&audio_ocmem_lcl.audio_state)); goto fail_cmd1; } wait_event_interruptible( audio_ocmem_lcl.audio_wait, (atomic_read(&audio_ocmem_lcl.audio_state) & _UNMAP_RESPONSE_BIT_MASK_) != 0); } if (test_bit_pos(audio_ocmem_lcl.audio_state, OCMEM_STATE_SHRINK)) { pr_debug("%s: SHRINK while exiting\n", __func__); ret = ocmem_shrink(cid, audio_ocmem_lcl.buf, 0); if (ret) { pr_err("%s: ocmem_shrink failed, state[0x%x]\n", __func__, atomic_read(&audio_ocmem_lcl.audio_state)); goto fail_cmd1; } clear_bit_pos(audio_ocmem_lcl.audio_state, OCMEM_STATE_SHRINK); } pr_debug("%s: calling ocmem free, state:0x%x\n", __func__, atomic_read(&audio_ocmem_lcl.audio_state)); ret = ocmem_free(OCMEM_LP_AUDIO, audio_ocmem_lcl.buf); if (ret == -EAGAIN) { pr_debug("%s: received EAGAIN\n", __func__); if (test_bit_pos(audio_ocmem_lcl.audio_state, OCMEM_STATE_SHRINK)) { ret = ocmem_shrink(cid, audio_ocmem_lcl.buf, 0); if (ret) { pr_err("%s: ocmem_shrink failed, state[0x%x]\n", __func__, atomic_read(&audio_ocmem_lcl.audio_state)); goto fail_cmd1; } pr_debug("calling free after EAGAIN"); ret = ocmem_free(OCMEM_LP_AUDIO, audio_ocmem_lcl.buf); if (ret) { pr_err("%s: ocmem_free failed\n", __func__); goto fail_cmd2; } } else { pr_debug("%s: shrink callback already processed\n", __func__); goto fail_cmd1; } } else if (ret) { pr_err("%s: ocmem_free failed, state[0x%x], ret:%d\n", __func__, atomic_read(&audio_ocmem_lcl.audio_state), ret); goto fail_cmd2; } pr_debug("%s: ocmem_free success\n", __func__); /* Fall through */ case OCMEM_STATE_SSR: msm_bus_scale_client_update_request( audio_ocmem_lcl.audio_ocmem_bus_client, 0); set_bit_pos(audio_ocmem_lcl.audio_state, OCMEM_STATE_DISABLE); break; case -EINVAL: pr_info("%s: audio_cond[%d] audio_state[0x%x]\n", __func__, atomic_read(&audio_ocmem_lcl.audio_cond), atomic_read(&audio_ocmem_lcl.audio_state)); break; } mutex_unlock(&audio_ocmem_lcl.state_process_lock); } ret = 0; goto fail_cmd; fail_cmd1: ret = ocmem_free(OCMEM_LP_AUDIO, audio_ocmem_lcl.buf); if (ret) pr_err("%s: ocmem_free failed\n", __func__); fail_cmd2: mutex_unlock(&audio_ocmem_lcl.state_process_lock); fail_cmd: pr_debug("%s: exit\n", __func__); audio_ocmem_lcl.audio_ocmem_running = false; 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) { 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)); if (!test_bit_pos(audio_ocmem_lcl.audio_state, OCMEM_STATE_SSR)) set_bit_pos(audio_ocmem_lcl.audio_state, OCMEM_STATE_EXIT); wake_up(&audio_ocmem_lcl.audio_wait); mutex_unlock(&audio_ocmem_lcl.state_process_lock); pr_debug("%s: exit\n", __func__); return 0; } 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; } kfree(voice_ocm_work); return; } /** * 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 (enable) { if (!enable_ocmem_audio_voice) audio_ocmem_lcl.ocmem_en = false; else audio_ocmem_lcl.ocmem_en = true; } if (audio_ocmem_lcl.ocmem_en) { 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; mutex_lock(&audio_ocmem_lcl.state_process_lock); /* if previous work waiting for ocmem - signal it to exit */ atomic_set(&audio_ocmem_lcl.audio_exit, 1); pr_debug("%s: acquired mutex for %d\n", __func__, 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; } kfree(audio_ocm_work); return; } /** * 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 (enable) { if (!enable_ocmem_audio_voice) audio_ocmem_lcl.ocmem_en = false; else audio_ocmem_lcl.ocmem_en = true; } if (audio_ocmem_lcl.ocmem_en) { 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; audio_ocmem_lcl.audio_ocmem_running = true; 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) { struct msm_bus_scale_pdata *audio_ocmem_adata = NULL; if (!pdev->dev.of_node) { pr_err("%s: device tree information missing\n", __func__); return -ENODEV; } audio_ocmem_adata = msm_bus_cl_get_pdata(pdev); if (!audio_ocmem_adata) { pr_err("%s: bus device tree allocation failed\n", __func__); return -EINVAL; } dev_set_drvdata(&pdev->dev, audio_ocmem_adata); return 0; } static void do_ocmem_ramdump(void) { int ret = 0; void *virt = NULL; virt = ioremap(audio_ocmem_lcl.ocmem_dump_addr, AUDIO_OCMEM_BUF_SIZE); ret = ocmem_dump(OCMEM_LP_AUDIO, audio_ocmem_lcl.buf, (unsigned long)virt); iounmap(virt); if (ret) pr_err("%s: ocmem_dump failed\n", __func__); audio_ocmem_lcl.ocmem_ramdump_segment.address = (unsigned long)audio_ocmem_lcl.ocmem_dump_addr; audio_ocmem_lcl.ocmem_ramdump_segment.size = AUDIO_OCMEM_BUF_SIZE; ret = do_ramdump(audio_ocmem_lcl.ocmem_ramdump_dev, &audio_ocmem_lcl.ocmem_ramdump_segment, 1); if (ret < 0) pr_err("%s: do_ramdump failed\n", __func__); } static void process_ocmem_dump(void) { int ret = 0; set_bit_pos(audio_ocmem_lcl.audio_state, OCMEM_STATE_SSR); if (atomic_read(&audio_ocmem_lcl.audio_state) & _DO_OCMEM_DUMP_BIT_MASK_) { wait_event_interruptible(audio_ocmem_lcl.audio_wait, (atomic_read(&audio_ocmem_lcl.audio_state) & _WAIT_BFR_DUMP_BIT_MASK_) != 0); if (test_bit_pos(audio_ocmem_lcl.audio_state, OCMEM_STATE_MAP_COMPL) || test_bit_pos(audio_ocmem_lcl.audio_state, OCMEM_STATE_UNMAP_FAIL)) { if (audio_ocmem_lcl.ocmem_dump_addr && audio_ocmem_lcl.ocmem_ramdump_dev) do_ocmem_ramdump(); else pr_err("%s: Error calling ocmem ramdump\n", __func__); ret = ocmem_drop(OCMEM_LP_AUDIO, audio_ocmem_lcl.buf, &audio_ocmem_lcl.mlist); if (ret) pr_err("%s: ocmem_drop failed\n", __func__); } } ret = ocmem_free(OCMEM_LP_AUDIO, audio_ocmem_lcl.buf); if (ret) pr_err("%s: ocmem_free failed\n", __func__); } static int lpass_notifier_cb(struct notifier_block *this, unsigned long code, void *_cmd) { int ret = NOTIFY_DONE; switch (code) { case SUBSYS_BEFORE_SHUTDOWN: pr_debug("AO-Notify: Shutdown started\n"); break; case SUBSYS_AFTER_SHUTDOWN: pr_debug("AO-Notify: Shutdown Completed\n"); break; case SUBSYS_RAMDUMP_NOTIFICATION: pr_debug("AO-Notify: OCMEM dump\n"); if (audio_ocmem_lcl.ocmem_en && audio_ocmem_lcl.audio_ocmem_running) process_ocmem_dump(); pr_debug("AO-Notify: OCMEM dump done\n"); break; case SUBSYS_BEFORE_POWERUP: pr_debug("AO-Notify: Powerup started\n"); break; case SUBSYS_AFTER_POWERUP: pr_debug("AO-Notify: Powerup completed\n"); break; default: pr_err("AO-Notify: Generel: %lu\n", code); break; } return ret; } static struct notifier_block anb = { .notifier_call = lpass_notifier_cb, }; 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__); subsys_notif_register_notifier("adsp", &anb); audio_ocmem_lcl.ocmem_dump_addr = allocate_contiguous_memory_nomap(AUDIO_OCMEM_BUF_SIZE, MEMTYPE_EBI1, AUDIO_OCMEM_BUF_SIZE); if (audio_ocmem_lcl.ocmem_dump_addr) { audio_ocmem_lcl.ocmem_ramdump_dev = create_ramdump_device("audio-ocmem", &pdev->dev); if (!audio_ocmem_lcl.ocmem_ramdump_dev) pr_err("%s: audio-ocmem ramdump device failed\n", __func__); } else { pr_err("%s: ocmem dump memory alloc failed\n", __func__); } audio_ocmem_lcl.audio_ocmem_workqueue = alloc_workqueue("ocmem_audio_client_driver_audio", WQ_NON_REENTRANT | WQ_UNBOUND, 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_state, OCMEM_STATE_DEFAULT); atomic_set(&audio_ocmem_lcl.audio_exit, 0); spin_lock_init(&audio_ocmem_lcl.audio_lock); mutex_init(&audio_ocmem_lcl.state_process_lock); audio_ocmem_lcl.ocmem_en = true; audio_ocmem_lcl.audio_ocmem_running = false; /* 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); msm_bus_cl_clear_pdata(audio_ocmem_bus_scale_pdata); ocmem_notifier_unregister(audio_ocmem_lcl.audio_hdl, &audio_ocmem_client_nb); free_contiguous_memory_by_paddr(audio_ocmem_lcl.ocmem_dump_addr); 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);