/* sound/soc/samsung/lpeffwork.c * * Exynos Seiren DMA driver for Exynos Audio Subsystem * * Copyright (c) 2015 Samsung Electronics * http://www.samsungsemi.com/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../lpass.h" #include "seiren.h" #include "seiren_ioctl.h" #include "lpeffwork.h" #include "../esa_sa_effect.h" #define MAX_EFFCMD_NUM 10 #define LPEFF_DEBUG_HEADER "[lpeff]" #define lpeff_prinfo(s, ...) pr_info( LPEFF_DEBUG_HEADER s, ##__VA_ARGS__ ) #define lpeff_prerr(s, ...) pr_err( LPEFF_DEBUG_HEADER s, ##__VA_ARGS__ ) extern struct mutex esa_mutex; struct lpeff_workstruct { struct task_struct *g_th_id; wait_queue_head_t msg_wait; spinlock_t slock; int msg_rdidx; int msg_wridx; int msg_count; enum exynos_effwork_cmd msg_queue[MAX_EFFCMD_NUM]; }; static struct lpeff_workstruct g_worker_data; void __iomem *g_effect_addr; char *lpeff_fw_lib_entry = NULL; static struct seiren_info g_si; int lpeff_log(char *str) { lpeff_prinfo("[LIB] %s", str); return 0; } int lpeff_memcpy(void * dest, const void *src, size_t n) { memcpy(dest, src, n); return 0; } int lpeff_alloc(char *str) { lpeff_prinfo("[LIB] %s", str); return 0; } static ssize_t lpeff_show(struct device *dev, struct device_attribute *attr, char *buf) { u32 elpe_cmd; u32 arg0, arg1, arg2, arg3, arg4, arg5, arg6; u32 arg7, arg8, arg9, arg10, arg11, arg12; mutex_lock(&esa_mutex); pm_runtime_get_sync(&g_si.pdev->dev); elpe_cmd = readl(g_effect_addr + ELPE_BASE + ELPE_CMD); arg0 = readl(g_effect_addr + ELPE_BASE + ELPE_ARG0); arg1 = readl(g_effect_addr + ELPE_BASE + ELPE_ARG1); arg2 = readl(g_effect_addr + ELPE_BASE + ELPE_ARG2); arg3 = readl(g_effect_addr + ELPE_BASE + ELPE_ARG3); arg4 = readl(g_effect_addr + ELPE_BASE + ELPE_ARG4); arg5 = readl(g_effect_addr + ELPE_BASE + ELPE_ARG5); arg6 = readl(g_effect_addr + ELPE_BASE + ELPE_ARG6); arg7 = readl(g_effect_addr + ELPE_BASE + ELPE_ARG7); arg8 = readl(g_effect_addr + ELPE_BASE + ELPE_ARG8); arg9 = readl(g_effect_addr + ELPE_BASE + ELPE_ARG9); arg10 = readl(g_effect_addr + ELPE_BASE + ELPE_ARG10); arg11 = readl(g_effect_addr + ELPE_BASE + ELPE_ARG11); arg12 = readl(g_effect_addr + ELPE_BASE + ELPE_ARG12); /* change src, dst address to offset value */ #if 0 src = (src & 0xFFFFFF) - 0x400000; dst = (dst & 0xFFFFFF) - 0x400000; #endif pm_runtime_mark_last_busy(&g_si.pdev->dev); pm_runtime_put_sync_autosuspend(&g_si.pdev->dev); mutex_unlock(&esa_mutex); // flush_cache_all(); return scnprintf(buf, PAGE_SIZE, "%d %d %d %d %d %d %d %d %d %d %d %d %d %d\n", elpe_cmd, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12); } static DEVICE_ATTR(lpeff, S_IRUGO, lpeff_show, NULL); int lpeff_init(struct seiren_info si) { int ret = 0; memcpy(&g_si, &si, sizeof(struct seiren_info)); g_effect_addr = si.effect_ram; printk("g_effect_addr = 0x%p \n", g_effect_addr); ret = device_create_file(&g_si.pdev->dev, &dev_attr_lpeff); if (ret) { lpeff_prerr("failed to create lpeff file\n"); return ret; } return 0; } void queue_lpeff_cmd(enum exynos_effwork_cmd msg) { spin_lock(&g_worker_data.slock); g_worker_data.msg_queue[g_worker_data.msg_wridx] = msg; g_worker_data.msg_wridx++; g_worker_data.msg_wridx = g_worker_data.msg_wridx % MAX_EFFCMD_NUM; g_worker_data.msg_count++; spin_unlock(&g_worker_data.slock); wake_up(&g_worker_data.msg_wait); } static enum exynos_effwork_cmd lpeff_worker_dequeue(void) { unsigned long flags; enum exynos_effwork_cmd msg = -1; spin_lock_irqsave(&g_worker_data.slock, flags); if(g_worker_data.msg_count == 0) { spin_unlock_irqrestore(&g_worker_data.slock, flags); wait_event(g_worker_data.msg_wait,(g_worker_data.msg_count>0)); spin_lock_irqsave(&g_worker_data.slock, flags); } msg = g_worker_data.msg_queue[g_worker_data.msg_rdidx]; g_worker_data.msg_rdidx++; g_worker_data.msg_rdidx = g_worker_data.msg_rdidx % MAX_EFFCMD_NUM; g_worker_data.msg_count--; spin_unlock_irqrestore(&g_worker_data.slock, flags); return msg; } void lpeff_send_effect_cmd(void) { sysfs_notify(&g_si.pdev->dev.kobj, NULL, "lpeff"); } static int lpeff_worker_func(void *arg) { while(!kthread_should_stop()) { enum exynos_effwork_cmd msg = lpeff_worker_dequeue(); switch(msg) { case LPEFF_EFFECT_CMD: lpeff_send_effect_cmd(); break; case LPEFF_REVERB_CMD: break; case LPEFF_DEBUG_CMD: break; case LPEFF_EXIT_CMD: break; default: lpeff_prinfo("[lpeff] invalid command (%d)\n", msg); } } return 0; } void exynos_init_lpeffworker(void) { memset(&g_worker_data, 0, sizeof(g_worker_data)); g_worker_data.g_th_id = NULL; g_worker_data.msg_rdidx = 0; g_worker_data.msg_wridx = 0; g_worker_data.msg_count = 0; init_waitqueue_head(&g_worker_data.msg_wait); spin_lock_init(&g_worker_data.slock); if(g_worker_data.g_th_id == NULL) g_worker_data.g_th_id = (struct task_struct *)kthread_run(lpeff_worker_func, NULL, "lpeff_worker"); } void exynos_lpeff_finalize(void) { queue_lpeff_cmd(LPEFF_EXIT_CMD); if(g_worker_data.g_th_id){ kthread_stop(g_worker_data.g_th_id); g_worker_data.g_th_id = NULL; } device_remove_file(&g_si.pdev->dev, &dev_attr_lpeff); } MODULE_AUTHOR("Donggyun Ko "); MODULE_AUTHOR("Hyoungmin Seo "); MODULE_DESCRIPTION("Exynos Seiren LPEFF Driver"); MODULE_LICENSE("GPL");