blob: 8610b00dbd9fec5d15511f37fa253b45ba1a8cbd [file] [log] [blame]
/* sound/soc/samsung/seiren/seiren.c
*
* Exynos Seiren Audio driver for Exynos5430
*
* Copyright (c) 2013 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 <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/serio.h>
#include <linux/time.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/clk.h>
#include <linux/mutex.h>
#include <linux/vmalloc.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/pm_runtime.h>
#include <linux/pm_qos.h>
#include <linux/iommu.h>
#include <linux/dma-mapping.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/firmware.h>
#include <linux/kthread.h>
#include <linux/exynos_ion.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/syscalls.h>
#include <linux/uaccess.h>
#include <linux/file.h>
#include <asm/cacheflush.h>
#include <asm/cachetype.h>
#include <asm/tlbflush.h>
#include <sound/exynos.h>
#include <sound/soc.h>
#if 0
#include <plat/map-base.h>
#include <plat/map-s5p.h>
#endif
#include "../lpass.h"
#include "../eax.h"
#include "seiren.h"
#include "seiren-dma.h"
#include "seiren_ioctl.h"
#include "seiren_error.h"
#ifdef CONFIG_SND_SAMSUNG_ELPE
#include "lpeffwork.h"
#endif
#ifdef CONFIG_SND_ESA_SA_EFFECT
#include "../esa_sa_effect.h"
#endif
#ifdef CONFIG_PM_DEVFREQ
#ifdef CONFIG_SOC_EXYNOS5430
#define CA5_MIF_FREQ_NORM (0)
#define CA5_MIF_FREQ_HIGH (317000)
#else
#define CA5_MIF_FREQ_NORM (0)
#define CA5_MIF_FREQ_HIGH (317000)
#endif
#define CA5_MIF_FREQ_BOOST (413000)
#define CA5_INT_FREQ_BOOST (200000)
#endif
#ifdef CONFIG_SOC_EXYNOS8890
#define OFFLOAD_INT_LOCK_FREQ 255000
#endif
#define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t)
void __iomem *dma_reg = NULL;
DEFINE_MUTEX(esa_mutex);
static DECLARE_WAIT_QUEUE_HEAD(esa_wq);
static DECLARE_WAIT_QUEUE_HEAD(esa_fx_wq);
#ifdef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
static DECLARE_WAIT_QUEUE_HEAD(esa_cpu_lock_wq);
#endif
static struct seiren_info si;
static int esa_send_cmd_(u32 cmd_code, bool sram_only);
static irqreturn_t esa_isr(int irqno, void *id);
static DEFINE_RAW_SPINLOCK(esa_logbuf_lock);
int header_printed;
int start, end;
int end_index;
#define esa_send_cmd_sram(cmd) esa_send_cmd_(cmd, true)
#define esa_send_cmd(cmd) esa_send_cmd_(cmd, false)
#ifdef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
int esa_compr_running(void);
static void esa_fw_snapshot(void);
#endif
int check_esa_status(void)
{
#ifndef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
return si.fw_use_dram ? 1 : 0;
#else
return esa_compr_running();
#endif
}
void esa_memset_mailbox(void)
{
int i;
for (i = 0; i < 0x80; i += 0x4)
writel(0x0, si.mailbox + i);
}
void esa_memcpy_mailbox(bool save)
{
unsigned int *src;
unsigned int *dst;
int i;
if (save) {
src = si.mailbox;
dst = si.mailbox_bak;
for (i = 0; i < 0x80; i += 0x4, dst++)
*dst = readl(src + i);
} else {
src = si.mailbox_bak;
dst = si.mailbox;
for (i = 0; i < 0x80; i += 0x4, src++)
writel(*src, dst + i);
}
}
#ifdef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
static int esa_set_cpu_lock_kthread(void *arg)
{
while (!kthread_should_stop()) {
wait_event_interruptible(esa_cpu_lock_wq, si.set_cpu_lock);
si.set_cpu_lock = false;
lpass_set_cpu_lock(si.cpu_lock_level);
}
return 0;
}
static struct audio_processor *ptr_ap;
static void esa_dump_fw_log(void);
int esa_compr_send_direct_cmd(int32_t cmd)
{
int n, ack;
switch (cmd) {
case CMD_DMA_START:
esa_info("%s: CMD_COMPR_DMA_START %d\n", __func__, cmd);
break;
case CMD_DMA_STOP:
esa_info("%s: CMD_COMPR_DMA_STOP %d\n", __func__, cmd);
break;
default:
esa_err("%s: unknown cmd %d\n", __func__, cmd);
return -EIO;
}
spin_lock(&si.cmd_lock);
writel(2, si.mailbox + COMPR_HANDLE_ID);
writel(cmd, si.mailbox + COMPR_CMD_CODE); /* command */
writel(1, si.regs + SW_INTR_CA5); /* trigger ca5 */
for (n = 0, ack = 0; n < 2000; n++) {
if (readl(si.mailbox + COMPR_ACK)) { /* Wait for ACK */
ack = 1;
break;
}
udelay(100);
}
writel(0, si.mailbox + COMPR_ACK); /* clear ACK */
spin_unlock(&si.cmd_lock);
if (!ack) {
esa_err("%s: No ack error!(%x)", __func__, cmd);
esa_dump_fw_log();
return -EFAULT;
}
return 0;
}
int esa_compr_send_cmd(int32_t cmd, struct audio_processor* ap)
{
int n, ack;
switch (cmd) {
case CMD_COMPR_CREATE:
esa_info("%s: CMD_COMPR_CREATE %d\n", __func__, cmd);
break;
case CMD_COMPR_DESTROY:
esa_info("%s: CMD_COMPR_DESTROY %d\n", __func__, cmd);
break;
case CMD_COMPR_SET_PARAM:
esa_info("%s: CMD_COMPR_SET_PARAM %d\n", __func__, cmd);
#ifdef CONFIG_SND_ESA_SA_EFFECT
spin_lock(&si.cmd_lock);
writel(si.out_sample_rate, si.mailbox + COMPR_PARAM_RATE);
/* MySpace effect is set before opening offload node.
seiren driver keeps the myspace configuration.
Then, it transfer that value to audio firmware.
*/
if (si.effect_ram && atomic_read(&si.update_myspace)) {
writel(si.myspace, si.effect_ram + MYSPACE_BASE + 0x10);
writel(CHANGE_BIT, si.effect_ram + MYSPACE_BASE);
atomic_set(&si.update_myspace, 0);
}
spin_unlock(&si.cmd_lock);
#endif
break;
case CMD_COMPR_WRITE:
esa_debug("%s: CMD_COMPR_WRITE %d\n", __func__, cmd);
break;
case CMD_COMPR_READ:
esa_debug("%s: CMD_COMPR_READ %d\n", __func__, cmd);
break;
case CMD_COMPR_START:
esa_info("%s: CMD_COMPR_START %d\n", __func__, cmd);
break;
case CMD_COMPR_STOP:
esa_info("%s: CMD_COMPR_STOP %d\n", __func__, cmd);
break;
case CMD_COMPR_PAUSE:
esa_info("%s: CMD_COMPR_PAUSE %d\n", __func__, cmd);
break;
case CMD_COMPR_EOS:
esa_info("%s: CMD_COMPR_EOS %d\n", __func__, cmd);
break;
case CMD_COMPR_GET_VOLUME:
esa_debug("%s: CMD_COMPR_GET_VOLUME %d\n", __func__, cmd);
break;
case CMD_COMPR_SET_VOLUME:
esa_debug("%s: CMD_COMPR_SET_VOLUME %d\n", __func__, cmd);
break;
case CMD_COMPR_CA5_WAKEUP:
esa_debug("%s: CMD_COMPR_CA5_WAKEUP %d\n", __func__, cmd);
break;
case CMD_COMPR_HPDET_NOTIFY:
esa_debug("%s: CMD_COMPR_HPDET_NOTIFY %d\n", __func__, cmd);
break;
default:
esa_err("%s: unknown cmd %d\n", __func__, cmd);
return -EIO;
}
spin_lock(&si.cmd_lock);
writel(ap->handle_id, si.mailbox + COMPR_HANDLE_ID);
writel(cmd, si.mailbox + COMPR_CMD_CODE); /* command */
writel(1, si.regs + SW_INTR_CA5); /* trigger ca5 */
for (n = 0, ack = 0; n < 2000; n++) {
if (readl(ap->reg_ack)) { /* Wait for ACK */
ack = 1;
break;
}
udelay(100);
}
writel(0, ap->reg_ack); /* clear ACK */
spin_unlock(&si.cmd_lock);
if (!ack) {
esa_err("%s: No ack error!(%x)", __func__, cmd);
esa_dump_fw_log();
return -EFAULT;
}
return 0;
}
int esa_compr_send_buffer(const size_t copy_size, struct audio_processor *ap)
{
int ret;
/* write mp3 data to firmware */
spin_lock(&si.compr_lock);
writel(copy_size, si.mailbox + COMPR_SIZE_OF_FRAGMENT);
ret = esa_compr_send_cmd(CMD_COMPR_WRITE, ap);
if (ret) {
esa_err("%s: can't send CMD_COMPR_WRITE (%d)\n",
__func__, ret);
spin_unlock(&si.compr_lock);
#ifdef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
esa_fw_snapshot();
#endif
return ret;
}
spin_unlock(&si.compr_lock);
return 0;
}
void __iomem *esa_compr_get_mem(void)
{
return si.mailbox;
}
u32 esa_compr_pcm_size(void)
{
/* update frame count(dma buffer) */
return readl(si.mailbox + COMPR_RENDERED_PCM_SIZE);
}
int esa_compr_set_param(struct audio_processor* ap, uint8_t **buffer)
{
unsigned int ip_type;
unsigned char *ibuf;
u32 ibuf_ca5_pa;
u32 ibuf_offset;
int ret;
ptr_ap = ap;
/* initialize in buffer */
/* use free area in dram */
ibuf = si.fwarea[1];
ap->block_num = 1;
/* calculate the physical address */
ibuf_offset = ibuf - si.fwarea[ap->block_num];
ibuf_ca5_pa = ibuf_offset + FWAREA_SIZE * ap->block_num;
/* set buffer information at mailbox */
spin_lock(&si.lock);
writel(ap->buffer_size, si.mailbox + COMPR_SIZE_OF_INBUF);
writel(ibuf_ca5_pa, si.mailbox + COMPR_PHY_ADDR_INBUF);
writel(ap->sample_rate, si.mailbox + COMPR_PARAM_SAMPLE);
writel(ap->num_channels, si.mailbox + COMPR_PARAM_CH);
ip_type = ap->codec_id << 16;
writel(ip_type, si.mailbox + COMPR_IP_TYPE);
spin_unlock(&si.lock);
si.isr_compr_created = 0;
ret = esa_compr_send_cmd(CMD_COMPR_SET_PARAM, ap);
if (ret) {
esa_err("%s: can't send CMD_COMPR_SET_PARAM (%d)\n",
__func__, ret);
#ifdef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
esa_fw_snapshot();
#endif
return ret;
}
/* wait until the parameter is set up */
ret = wait_event_interruptible_timeout(esa_wq, si.isr_compr_created, HZ * 2);
if (!ret) {
esa_err("%s: compress set param timed out!!! (%d)\n",
__func__, ret);
writel(0, si.mailbox + COMPR_INTR_ACK);
esa_dump_fw_log();
si.fw_use_dram = true;
ptr_ap = NULL;
return -EBUSY;
}
/* created instance */
ap->handle_id = readl(si.mailbox + COMPR_IP_ID);
esa_info("%s: codec id:0x%x, ret_val:0x%x, handle_id:0x%x\n",
__func__, (unsigned int)ap->codec_id,
readl(si.mailbox + COMPR_RETURN_CMD),
(unsigned int)ap->handle_id);
/* return the buffer address for caller */
*buffer = ibuf;
esa_info("%s: allocated buffer address (0x%p), size(0x%x)\n",
__func__, *buffer, ap->buffer_size);
#ifdef CONFIG_SND_ESA_SA_EFFECT
si.effect_on = false;
#endif
return 0;
}
int esa_compr_running(void)
{
return (si.isr_compr_created ? true : false);
}
void esa_compr_set_state(bool flag)
{
si.is_compr_open = flag;
esa_debug("%s Compress-State %s\n", __func__,
(si.is_compr_open ? "OPENED" : "CLOSED"));
return;
}
int check_esa_compr_state(void)
{
return (si.is_compr_open ? true : false);
}
/* Notify firmware when alpa is enter and exit
* through mailbox interface */
void esa_compr_alpa_notifier(bool on)
{
if (si.mailbox == NULL)
return;
writel(on ? 1 : 0, si.mailbox + COMPR_ALPA_NOTI);
esa_debug("%s ALPA %s\n", __func__, (on ? "Enter" : "Exit"));
if (!on) {
/* Send software interrupt to CA5 to wakeup */
if (ptr_ap) {
if (esa_compr_send_cmd(CMD_COMPR_CA5_WAKEUP, ptr_ap))
esa_err("%s Unable to Send CA5 Wakeup command\n",
__func__);
}
}
return;
}
/* HP Detect notification command to FW */
void esa_compr_hpdet_notifier(bool on)
{
esa_info("%s %s\n", __func__, (on ? "Jack Detected" : "Jack Removed"));
if (on) {
/* Send HP detect notification command */
if (ptr_ap) {
if (esa_compr_send_cmd(CMD_COMPR_HPDET_NOTIFY, ptr_ap))
esa_err("%s Unable to Send HPDET_NOTIFY command\n",
__func__);
}
}
return;
}
void esa_compr_ctrl_fxintr(bool fxon)
{
writel(fxon ? 1 : 0, si.mailbox + EFFECT_EXT_ON);
return;
}
#ifdef CONFIG_SND_ESA_SA_EFFECT
void esa_compr_set_sample_rate(u32 rate)
{
esa_debug("%s output sample_rate %d \n", __func__, rate);
si.out_sample_rate = rate;
return;
}
u32 esa_compr_get_sample_rate(void)
{
return si.out_sample_rate;
}
void esa_compr_set_myspace(u32 ms)
{
atomic_set(&si.update_myspace, 1);
si.myspace = ms;
return;
}
#endif
void esa_fw_start(void)
{
pm_runtime_get_sync(&si.pdev->dev);
#ifdef ESA_DATA_DUMP
if (!sram_filp)
sram_filp =
filp_open("/data/sram.bin", O_RDWR|O_TRUNC|O_CREAT, S_IRUSR|S_IWUSR);
#endif
}
void esa_fw_stop(void)
{
pm_runtime_mark_last_busy(&si.pdev->dev);
pm_runtime_put_sync_autosuspend(&si.pdev->dev);
}
void esa_compr_open(void)
{
void __iomem *cmu_reg;
u32 cfg;
pm_qos_update_request(&si.ca5_int_qos, OFFLOAD_INT_LOCK_FREQ);
pm_runtime_get_sync(&si.pdev->dev);
cmu_reg = ioremap(0x114C0000, SZ_4K);
cfg = readl(cmu_reg + 0x404) & ~(0xF);
cfg |= 0x2; /* ACLK_AUD: 102.5MHz */
writel(cfg, cmu_reg + 0x404);
cfg = readl(cmu_reg + 0x400) & ~(0xF); /* CA5: 410MHz */
writel(cfg, cmu_reg + 0x400);
iounmap(cmu_reg);
}
void esa_compr_close(void)
{
void __iomem *cmu_reg;
u32 cfg;
#ifdef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
lpass_set_cpu_lock(0);
#endif
cmu_reg = ioremap(0x114C0000, SZ_4K);
cfg = readl(cmu_reg + 0x400) & ~(0xF);
cfg |= 0x1; /* CA5: 205MHz */
writel(cfg, cmu_reg + 0x400);
cfg = readl(cmu_reg + 0x404) & ~(0xF);
cfg |= 0x1; /* ACLK_AUD: 102.5MHz */
writel(cfg, cmu_reg + 0x404);
iounmap(cmu_reg);
pm_runtime_mark_last_busy(&si.pdev->dev);
pm_runtime_put_sync_autosuspend(&si.pdev->dev);
pm_qos_update_request(&si.ca5_int_qos, 0);
ptr_ap = NULL;
#ifdef CONFIG_SND_ESA_SA_EFFECT
si.out_sample_rate = 0;
#endif
}
#endif
#ifdef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
static void esa_fw_snapshot(void)
{
struct file *sram_filp;
struct file *dram_filp;
mm_segment_t old_fs;
old_fs = get_fs();
set_fs(KERNEL_DS);
if (si.sram) {
memcpy(si.fwmem_sram_bak, si.sram, SRAM_FW_MAX);
} else {
esa_err("%s : Can't store seiren firmware\n", __func__);
goto out;
}
sram_filp = filp_open("/data/seiren_fw_sram_snapsot.bin",
O_RDWR|O_TRUNC|O_CREAT, S_IRUSR|S_IWUSR);
vfs_write(sram_filp, si.fwmem_sram_bak, SRAM_FW_MAX, &sram_filp->f_pos);
vfs_fsync(sram_filp, 0);
filp_close(sram_filp, NULL);
dram_filp = filp_open("/data/seiren_fw_dram_snapsot.bin",
O_RDWR|O_TRUNC|O_CREAT, S_IRUSR|S_IWUSR);
vfs_write(dram_filp, si.fwarea[0], SZ_4M, &dram_filp->f_pos);
vfs_fsync(dram_filp, 0);
filp_close(dram_filp, NULL);
out:
set_fs(old_fs);
}
struct kobject *seiren_fw_snapshot_kobj = NULL;
static ssize_t snapshot_seiren_fw(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
pr_info("%s: %d seiren snapshot is called\n", __func__, __LINE__);
esa_fw_snapshot();
return count;
}
static struct kobj_attribute seiren_fw_dump_attribute =
__ATTR(seiren_fw_dump, S_IRUGO | S_IWUSR, NULL, snapshot_seiren_fw);
#endif
static void esa_dump_fw_log(void)
{
char log[256];
char *addr = si.fw_log_buf;
int n, fwver;
esa_info("fw log:\n");
esa_info("running cnt:%d\n", readl(si.mailbox + COMPR_CHECK_RUNNING));
fwver = readl(si.mailbox + VIRSION_ID);
esa_info("Firmware Version: %c%c%c-%c\n",
(fwver >> 24), ((fwver >> 16) & 0xFF),
((fwver >> 8) & 0xFF), fwver & 0xFF);
esa_info("ack status: lpcm(%d) compr(%d:%x)\n",
readl(si.mailbox + COMPR_INTR_DMA_ACK),
readl(si.mailbox + COMPR_INTR_ACK),
readl(si.mailbox + COMPR_CHECK_CMD));
if (addr) {
for (n = 0; n < FW_LOG_LINE; n++, addr += 128) {
memcpy(log, addr, 128);
esa_info("%s", log);
}
}
#ifdef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
if (si.sram) {
memcpy(si.fwmem_sram_bak, si.sram, SRAM_FW_MAX);
} else {
esa_err("%s: Can't store seiren firmware\n", __func__);
}
#endif
}
#ifndef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
static struct esa_rtd *esa_alloc_rtd(void)
{
struct esa_rtd *rtd = NULL;
int idx;
rtd = kzalloc(sizeof(struct esa_rtd), GFP_KERNEL);
if (rtd) {
spin_lock(&si.lock);
for (idx = 0; idx < INSTANCE_MAX; idx++) {
if (!si.rtd_pool[idx]) {
si.rtd_cnt++;
si.rtd_pool[idx] = rtd;
rtd->idx = idx;
break;
}
}
spin_unlock(&si.lock);
if (idx == INSTANCE_MAX) {
kfree(rtd);
rtd = NULL;
}
}
return rtd;
}
static void esa_free_rtd(struct esa_rtd *rtd)
{
spin_lock(&si.lock);
si.rtd_cnt--;
si.rtd_pool[rtd->idx] = NULL;
spin_unlock(&si.lock);
kfree(rtd);
}
static int esa_send_cmd_exe(struct esa_rtd *rtd, unsigned char *ibuf,
unsigned char *obuf, size_t size)
{
u32 ibuf_ca5_pa, obuf_ca5_pa;
u32 ibuf_offset, obuf_offset;
int out_size;
int response;
if (rtd->use_sram) { /* SRAM buffer */
ibuf_offset = SRAM_IO_BUF + SRAM_IBUF_OFFSET;
obuf_offset = SRAM_IO_BUF + SRAM_OBUF_OFFSET;
ibuf_ca5_pa = ibuf_offset;
obuf_ca5_pa = obuf_offset;
if (si.sram) {
memcpy(si.sram + ibuf_offset, ibuf, size);
} else {
esa_err("%s : there is no sram input buffer", __func__);
return -ENOMEM;
}
} else { /* DRAM buffer */
ibuf_offset = ibuf - si.fwarea[rtd->block_num];
obuf_offset = obuf - si.fwarea[rtd->block_num];
ibuf_ca5_pa = ibuf_offset + FWAREA_SIZE * rtd->block_num;
obuf_ca5_pa = obuf_offset + FWAREA_SIZE * rtd->block_num;
}
writel(rtd->handle_id, si.mailbox + HANDLE_ID);
writel(size, si.mailbox + SIZE_OF_INDATA);
writel(ibuf_ca5_pa, si.mailbox + PHY_ADDR_INBUF);
writel(obuf_ca5_pa, si.mailbox + PHY_ADDR_OUTBUF);
if (rtd->use_sram)
esa_send_cmd_sram(CMD_EXE);
else
esa_send_cmd(CMD_EXE);
/* check response of FW */
response = readl(si.mailbox + RETURN_CMD);
if (rtd->use_sram) {
out_size = readl(si.mailbox + SIZE_OUT_DATA);
if ((response == 0) && (out_size > 0)) {
if (si.sram) {
memcpy(obuf, si.sram + obuf_offset, out_size);
} else {
esa_err("%s : there is no sram output buffer", __func__);
return -ENOMEM;
}
}
}
return response;
}
#endif
static void esa_fw_download(void)
{
int n;
esa_info("%s: fw size = sram(%d) dram(%d)\n", __func__,
si.fw_sbin_size, si.fw_dbin_size);
lpass_reset(LPASS_IP_CA5, LPASS_OP_RESET);
udelay(20);
#ifndef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
memset(si.mailbox, 0, 128);
#endif
if (si.fw_suspended) {
esa_info("%s: resume\n", __func__);
/* Restore SRAM */
if (si.sram) {
memcpy(si.sram, si.fwmem_sram_bak, SRAM_FW_MEMSET_SIZE);
#ifndef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
memset(si.sram + FW_ZERO_SET_BASE, 0, FW_ZERO_SET_SIZE);
#else
esa_memcpy_mailbox(false);
#endif
} else {
esa_err("%s: sram address is empty (%d)\n",
__func__, __LINE__);
}
} else {
esa_info("%s: intialize\n", __func__);
for (n = 0; n < FWAREA_NUM; n++)
memset(si.fwarea[n], 0, FWAREA_SIZE);
esa_memset_mailbox();
if (si.sram) {
memset(si.sram, 0, SRAM_FW_MEMSET_SIZE); /* for ZI area */
memcpy(si.sram, si.fwmem, si.fw_sbin_size);
} else {
esa_err("%s: sram address is empty (%d)\n",
__func__, __LINE__);
}
memcpy(si.fwarea[0], si.fwmem + si.fw_sbin_size,
si.fw_dbin_size);
}
lpass_reset(LPASS_IP_CA5, LPASS_OP_NORMAL);
esa_info("%s: CA5 startup...\n", __func__);
}
static int esa_fw_startup(void)
{
#ifndef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
unsigned int dec_ver;
#endif
int ret;
if (si.fw_ready)
return 0;
if (!si.fwmem_loaded) {
lpass_update_lpclock(LPCLK_CTRLID_OFFLOAD, false);
return -EAGAIN;
}
/* Not to enter SICD_AUD */
lpass_update_lpclock(LPCLK_CTRLID_LEGACY, true);
/* power on */
si.fw_use_dram = true;
esa_debug("Turn on CA5...\n");
esa_fw_download();
/* wait for fw ready */
ret = wait_event_interruptible_timeout(esa_wq, si.fw_ready, HZ / 2);
if (!ret) {
pr_info("%s: Retry CA5 Initialization\n", __func__);
lpass_reset(LPASS_IP_CA5, LPASS_OP_RESET);
udelay(20);
lpass_reset(LPASS_IP_CA5, LPASS_OP_NORMAL);
ret = wait_event_interruptible_timeout(esa_wq, si.fw_ready, HZ / 2);
if (!ret) {
#ifdef CONFIG_SOC_EXYNOS8890
u32 cfg;
void __iomem *cmu_reg;
cmu_reg = ioremap(0x114C0000, SZ_4K);
cfg = readl(cmu_reg + 0x800); /* Check CA5 clock */
iounmap(cmu_reg);
esa_err("%s: fw not ready!!! (%d), clk = %x\n", __func__,
readl(si.mailbox + LAST_CHECKPT), cfg);
#else
esa_err("%s: fw not ready!!! (%d)\n", __func__,
readl(si.mailbox + LAST_CHECKPT));
#endif
lpass_reset(LPASS_IP_CA5, LPASS_OP_RESET);
if (!check_esa_compr_state())
lpass_update_lpclock(LPCLK_CTRLID_OFFLOAD, false);
lpass_update_lpclock(LPCLK_CTRLID_LEGACY, false);
si.fw_use_dram = false;
return -EBUSY;
}
}
#ifndef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
/* check decoder version */
esa_send_cmd(SYS_GET_STATUS);
dec_ver = readl(si.mailbox) & 0xFF00;
dec_ver = dec_ver >> 8;
esa_debug("Decoder version : %x\n", dec_ver);
#endif
lpass_update_lpclock(LPCLK_CTRLID_LEGACY, false);
return 0;
}
static void esa_fw_shutdown(void)
{
u32 cnt, val;
if (!si.fw_ready)
return;
if (!si.fwmem_loaded)
return;
/* Not to enter SICD_AUD */
lpass_update_lpclock(LPCLK_CTRLID_LEGACY, true);
/* SUSPEND & IDLE */
esa_send_cmd(SYS_SUSPEND);
si.fw_suspended = false;
cnt = msecs_to_loops(100);
while (--cnt) {
val = readl(si.regs + CA5_STATUS);
if (val & CA5_STATUS_WFI) {
si.fw_suspended = true;
break;
}
cpu_relax();
}
esa_debug("CA5_STATUS: %X\n", val);
/* Backup SRAM */
if (si.sram)
memcpy(si.fwmem_sram_bak, si.sram, SRAM_FW_MEMSET_SIZE);
else
esa_err("%s : Failed to save seiren firmware\n", __func__);
#ifdef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
esa_memcpy_mailbox(true);
#endif
/* power off */
esa_debug("Turn off CA5...\n");
lpass_reset(LPASS_IP_CA5, LPASS_OP_RESET);
si.fw_ready = false;
si.fw_use_dram = false;
lpass_update_lpclock(LPCLK_CTRLID_LEGACY, false);
}
#if !defined(CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD) && defined(CONFIG_PM_DEVFREQ)
static bool esa_check_ip_exist(unsigned int ip_type)
{
int idx;
for (idx = 0; idx < INSTANCE_MAX; idx++) {
if (si.rtd_pool[idx]) {
if (si.rtd_pool[idx]->ip_type == ip_type)
return true;
}
}
return false;
}
#endif
static void esa_update_qos(void)
{
#if !defined(CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD) && defined(CONFIG_PM_DEVFREQ)
int mif_qos_new;
#ifdef CONFIG_SND_ESA_SA_EFFECT
int int_qos_new = 0;
#endif
if (!si.fw_ready)
mif_qos_new = 0;
else if (esa_check_ip_exist(ADEC_AAC))
mif_qos_new = CA5_MIF_FREQ_HIGH;
else
mif_qos_new = CA5_MIF_FREQ_NORM;
#ifdef CONFIG_SND_ESA_SA_EFFECT
if (si.effect_on) {
mif_qos_new = CA5_MIF_FREQ_BOOST;
int_qos_new = CA5_INT_FREQ_BOOST;
}
if (si.int_qos != int_qos_new) {
si.int_qos = int_qos_new;
pm_qos_update_request(&si.ca5_int_qos, si.int_qos);
pr_debug("%s: int_qos = %d\n", __func__, si.int_qos);
}
#endif
if (si.mif_qos != mif_qos_new) {
si.mif_qos = mif_qos_new;
pm_qos_update_request(&si.ca5_mif_qos, si.mif_qos);
pr_debug("%s: mif_qos = %d\n", __func__, si.mif_qos);
}
#endif
return;
}
#ifdef CONFIG_SND_ESA_SA_EFFECT
int esa_effect_write(int type, int *value, int count)
{
int effect_count = count;
void __iomem *effect_addr;
int i, *effect_value;
int ret = 0;
if (!check_esa_compr_state())
return 0;
pm_runtime_get_sync(&si.pdev->dev);
effect_value = value;
if (!si.effect_ram) {
esa_err("%s: memory for effect parameters isn't ready\n",
__func__);
goto out;
} else if (!si.fw_ready) {
esa_err("%s: audio firmware isn't ready\n", __func__);
goto out;
}
switch (type) {
case SOUNDALIVE:
effect_addr = si.effect_ram + SA_BASE;
break;
case MYSOUND:
effect_addr = si.effect_ram + MYSOUND_BASE;
break;
case PLAYSPEED:
effect_addr = si.effect_ram + VSP_BASE;
break;
case SOUNDBALANCE:
effect_addr = si.effect_ram + LRSM_BASE;
break;
case MYSPACE:
effect_addr = si.effect_ram + MYSPACE_BASE;
break;
case BASSBOOST:
effect_addr = si.effect_ram + BB_BASE;
pr_info("effect type : %d {BassBoost}\n", type);
break;
case EQUALIZER:
effect_addr = si.effect_ram + EQ_BASE;
pr_info("effect type : %d {Equalizer}\n", type);
break;
case NXPBUNDLE:
effect_addr = si.effect_ram + NXPBDL_BASE;
pr_info("effect type : %d {NXP Bundle Effect}\n", type);
break;
case NXPREVERB_CTX:
effect_addr = si.effect_ram + NXPRVB_CTX_BASE;
pr_info("effect type : %d {NXP Reverb Context}\n", type);
break;
case NXPREVERB_PARAM:
effect_addr = si.effect_ram + NXPRVB_PARAM_BASE;
pr_info("effect type : %d {NXP Reverb Control params}\n", type);
break;
default :
pr_err("Not support effect type : %d\n", type);
ret = -EINVAL;
goto out;
}
for (i = 0; i < effect_count; i++) {
pr_debug("effect_value[%d] = %d\n", i, effect_value[i]);
writel(effect_value[i], effect_addr + 0x10 + (i * 4));
}
writel(CHANGE_BIT, effect_addr);
esa_update_qos();
out:
pm_runtime_mark_last_busy(&si.pdev->dev);
pm_runtime_put_sync_autosuspend(&si.pdev->dev);
return ret;
}
#endif
#ifndef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
static void esa_buffer_init(struct file *file)
{
struct esa_rtd *rtd = file->private_data;
unsigned long ibuf_size, obuf_size;
unsigned int ibuf_count, obuf_count;
unsigned char *buf;
bool use_sram = false;
esa_debug("iptype: %d", rtd->ip_type);
switch (rtd->ip_type) {
case ADEC_MP3:
ibuf_size = DEC_IBUF_SIZE;
obuf_size = DEC_OBUF_SIZE;
ibuf_count = DEC_IBUF_NUM;
obuf_count = DEC_OBUF_NUM;
use_sram = true;
break;
case ADEC_AAC:
ibuf_size = DEC_AAC_IBUF_SIZE;
obuf_size = DEC_AAC_OBUF_SIZE;
ibuf_count = DEC_IBUF_NUM;
obuf_count = DEC_OBUF_NUM;
break;
case ADEC_FLAC:
ibuf_size = DEC_FLAC_IBUF_SIZE;
obuf_size = DEC_FLAC_OBUF_SIZE;
ibuf_count = DEC_IBUF_NUM;
obuf_count = DEC_OBUF_NUM;
break;
case SOUND_EQ:
ibuf_size = SP_IBUF_SIZE;
obuf_size = SP_OBUF_SIZE;
ibuf_count = SP_IBUF_NUM;
obuf_count = SP_OBUF_NUM;
break;
default:
ibuf_size = 0x10000;
obuf_size = 0x10000;
ibuf_count = 1;
obuf_count = 1;
break;
}
rtd->ibuf_size = ibuf_size;
rtd->obuf_size = obuf_size;
rtd->ibuf_count = ibuf_count;
rtd->obuf_count = obuf_count;
rtd->use_sram = use_sram;
/* You must consider seiren's module arrangement. */
if (rtd->idx < 3) {
buf = si.fwarea[0] + BASEMEM_OFFSET + rtd->idx * BUF_SIZE_MAX;
rtd->block_num = 0;
} else if (rtd->idx < 15) {
buf = si.fwarea[1] + (rtd->idx - 3) * BUF_SIZE_MAX;
rtd->block_num = 1;
} else {
buf = si.fwarea[2] + (rtd->idx - 15) * BUF_SIZE_MAX;
rtd->block_num = 2;
}
rtd->ibuf0 = buf;
buf += ibuf_count == 2 ? ibuf_size : 0;
rtd->ibuf1 = buf;
buf += ibuf_size;
rtd->obuf0 = buf;
buf += obuf_count == 2 ? obuf_size : 0;
rtd->obuf1 = buf;
}
#endif
static int esa_send_cmd_(u32 cmd_code, bool sram_only)
{
u32 cnt, val;
int ret;
si.isr_done = 0;
writel(cmd_code, si.mailbox + CMD_CODE); /* command */
writel(1, si.regs + SW_INTR_CA5); /* trigger ca5 */
si.fw_use_dram = sram_only ? false : true;
ret = wait_event_interruptible_timeout(esa_wq, si.isr_done, HZ / 2);
if (!ret) {
esa_err("%s: CMD(%08X) timed out!!!\n",
__func__, cmd_code);
esa_dump_fw_log();
si.fw_use_dram = true;
return -EBUSY;
}
cnt = msecs_to_loops(10);
while (--cnt) {
val = readl(si.regs + CA5_STATUS);
if (val & CA5_STATUS_WFI)
break;
cpu_relax();
}
si.fw_use_dram = false;
return 0;
}
#ifndef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
static ssize_t esa_write(struct file *file, const char *buffer,
size_t size, loff_t *pos)
{
struct esa_rtd *rtd = file->private_data;
unsigned char *ibuf;
unsigned char *obuf;
unsigned int *obuf_filled_size;
bool *obuf_filled;
int response, consumed_size = 0;
mutex_lock(&esa_mutex);
pm_runtime_get_sync(&si.pdev->dev);
if (rtd->obuf0_filled && rtd->obuf1_filled) {
esa_err("%s: There is no unfilled obuf\n", __func__);
goto err;
}
/* select IBUF0 or IBUF1 */
if (rtd->select_ibuf == 0) {
ibuf = rtd->ibuf0;
obuf = rtd->obuf0;
obuf_filled = &rtd->obuf0_filled;
obuf_filled_size = &rtd->obuf0_filled_size;
esa_debug("%s: use ibuf0\n", __func__);
} else {
ibuf = rtd->ibuf1;
obuf = rtd->obuf1;
obuf_filled = &rtd->obuf1_filled;
obuf_filled_size = &rtd->obuf1_filled_size;
esa_debug("%s: use ibuf1\n", __func__);
}
if (size > rtd->ibuf_size) {
esa_err("%s: copy size is bigger than buffer size\n",
__func__);
goto err;
}
/* receive stream data from user */
if (copy_from_user(ibuf, buffer, size)) {
esa_err("%s: failed to copy_from_user\n", __func__);
goto err;
}
/* select IBUF0 or IBUF1 for next writing */
rtd->select_ibuf = !rtd->select_ibuf;
/* send execute command to FW for decoding */
response = esa_send_cmd_exe(rtd, ibuf, obuf, size);
/* filled size in OBUF */
*obuf_filled_size = readl(si.mailbox + SIZE_OUT_DATA);
/* consumed size */
consumed_size = readl(si.mailbox + CONSUMED_BYTE_IN);
if (response == 0 && *obuf_filled_size > 0) {
*obuf_filled = true;
} else {
if (consumed_size <= 0)
consumed_size = response;
if (rtd->need_config)
rtd->need_config = false;
else if (size != 0)
esa_debug("%s: No output? response:%x\n", __func__, response);
}
pm_runtime_mark_last_busy(&si.pdev->dev);
pm_runtime_put_sync_autosuspend(&si.pdev->dev);
mutex_unlock(&esa_mutex);
esa_debug("%s: handle_id[%x], idx:[%d], consumed:[%d], filled_size:[%d], ibuf:[%d]\n",
__func__, rtd->handle_id, rtd->idx, consumed_size,
*obuf_filled_size, !rtd->select_ibuf);
return consumed_size;
err:
pm_runtime_mark_last_busy(&si.pdev->dev);
pm_runtime_put_sync_autosuspend(&si.pdev->dev);
mutex_unlock(&esa_mutex);
return -EFAULT;
}
static ssize_t esa_read(struct file *file, char *buffer,
size_t size, loff_t *pos)
{
struct esa_rtd *rtd = file->private_data;
unsigned char *obuf;
unsigned int *obuf_filled_size;
bool *obuf_filled;
unsigned char *obuf_;
unsigned int *obuf_filled_size_;
bool *obuf_filled_;
mutex_lock(&esa_mutex);
pm_runtime_get_sync(&si.pdev->dev);
/* select OBUF0 or OBUF1 */
if (rtd->select_obuf == 0) {
obuf = rtd->obuf0;
obuf_filled = &rtd->obuf0_filled;
obuf_filled_size = &rtd->obuf0_filled_size;
obuf_ = rtd->obuf1;
obuf_filled_ = &rtd->obuf1_filled;
obuf_filled_size_ = &rtd->obuf1_filled_size;
esa_debug("%s: use obuf0\n", __func__);
} else {
obuf = rtd->obuf1;
obuf_filled = &rtd->obuf1_filled;
obuf_filled_size = &rtd->obuf1_filled_size;
obuf_ = rtd->obuf0;
obuf_filled_ = &rtd->obuf0_filled;
obuf_filled_size_ = &rtd->obuf0_filled_size;
esa_debug("%s: use obuf1\n", __func__);
}
/* select OBUF0 or OBUF1 for next reading */
rtd->select_obuf = !rtd->select_obuf;
/* later... invalidate obuf cache */
/* send pcm data to user */
if (copy_to_user((void *)buffer, obuf, *obuf_filled_size)) {
esa_err("%s: failed to copy_to_user\n", __func__);
goto err;
}
/* if meet eos, it sholud also collect data of another buff */
if (rtd->get_eos && !*obuf_filled_) {
rtd->get_eos = EOS_FINAL;
}
esa_debug("%s: handle_id[%x], idx:[%d], obuf:[%d], obuf_filled_size:[%d]\n",
__func__, rtd->handle_id, rtd->idx, !rtd->select_obuf,
(u32)*obuf_filled_size);
*obuf_filled = false;
pm_runtime_mark_last_busy(&si.pdev->dev);
pm_runtime_put_sync_autosuspend(&si.pdev->dev);
mutex_unlock(&esa_mutex);
return *obuf_filled_size;
err:
pm_runtime_mark_last_busy(&si.pdev->dev);
pm_runtime_put_sync_autosuspend(&si.pdev->dev);
mutex_unlock(&esa_mutex);
return -EFAULT;
}
static int esa_exe(struct file *file, unsigned int param,
unsigned long arg)
{
struct esa_rtd *rtd = file->private_data;
struct audio_mem_info_t ibuf_info, obuf_info;
int response, obuf_filled_size;
unsigned char *ibuf = rtd->ibuf0;
unsigned char *obuf = rtd->obuf0;
int ret = 0;
mutex_lock(&esa_mutex);
/* receive ibuf_info from user */
if (copy_from_user(&ibuf_info, (struct audio_mem_info_t *)arg,
sizeof(struct audio_mem_info_t))) {
esa_err("%s: failed to copy_from_user ibuf_info\n", __func__);
goto err;
}
/* receive obuf_info from user */
arg += sizeof(struct audio_mem_info_t);
if (copy_from_user(&obuf_info, (struct audio_mem_info_t *)arg,
sizeof(struct audio_mem_info_t))) {
esa_err("%s: failed to copy_from_user obuf_info\n", __func__);
goto err;
}
/* prevent test alarmed about tainted data */
if (ibuf_info.mem_size > rtd->ibuf_size) {
ibuf_info.mem_size = rtd->ibuf_size;
esa_err("%s: There is too much input data", __func__);
}
/* receive pcm data from user */
if (copy_from_user(ibuf, (void *)(u64)ibuf_info.virt_addr,
ibuf_info.mem_size)) {
esa_err("%s: failed to copy_from_user\n", __func__);
goto err;
}
/* send execute command to FW for decoding */
response = esa_send_cmd_exe(rtd, ibuf, obuf, ibuf_info.mem_size);
/* filled size in OBUF */
obuf_filled_size = readl(si.mailbox + SIZE_OUT_DATA);
/* later... invalidate obuf cache */
if (!response && obuf_filled_size > 0) {
esa_debug("%s: cmd_exe OK!\n", __func__);
/* send pcm data to user */
if (copy_to_user((void *)(u64)obuf_info.virt_addr,
obuf, obuf_filled_size)) {
esa_err("%s: failed to copy_to_user obuf pcm\n",
__func__);
goto err;
}
} else {
esa_debug("%s: cmd_exe Fail: %d\n", __func__, response);
}
esa_debug("%s: handle_id[%x], idx:[%d], filled_size:[%d]\n",
__func__, rtd->handle_id, rtd->idx, obuf_filled_size);
return ret;
err:
mutex_unlock(&esa_mutex);
return -EFAULT;
}
static int esa_set_params(struct file *file, unsigned int param,
unsigned long arg)
{
struct esa_rtd *rtd = file->private_data;
int ret = 0;
switch (param) {
case PCM_PARAM_MAX_SAMPLE_RATE:
esa_debug("PCM_PARAM_MAX_SAMPLE_RATE: arg:%ld\n", arg);
break;
case PCM_PARAM_MAX_NUM_OF_CH:
esa_debug("PCM_PARAM_MAX_NUM_OF_CH: arg:%ld\n", arg);
break;
case PCM_PARAM_MAX_BIT_PER_SAMPLE:
esa_debug("PCM_PARAM_MAX_BIT_PER_SAMPLE: arg:%ld\n", arg);
break;
case PCM_PARAM_SAMPLE_RATE:
esa_debug("%s: sampling rate: %ld\n", __func__, arg);
writel(rtd->handle_id, si.mailbox + HANDLE_ID);
writel((unsigned int)arg, si.mailbox + PARAMS_VAL1);
esa_send_cmd((param << 16) | CMD_SET_PARAMS);
break;
case PCM_PARAM_NUM_OF_CH:
esa_debug("%s: channel: %ld\n", __func__, arg);
writel(rtd->handle_id, si.mailbox + HANDLE_ID);
writel((unsigned int)arg, si.mailbox + PARAMS_VAL1);
esa_send_cmd((param << 16) | CMD_SET_PARAMS);
break;
case PCM_PARAM_BIT_PER_SAMPLE:
esa_debug("PCM_PARAM_BIT_PER_SAMPLE: arg:%ld\n", arg);
break;
case PCM_MAX_CONFIG_INFO:
esa_debug("PCM_MAX_CONFIG_INFO: arg:%ld\n", arg);
break;
case PCM_CONFIG_INFO:
esa_debug("PCM_CONFIG_INFO: arg:%ld\n", arg);
break;
case EQ_PARAM_NUM_OF_PRESETS:
esa_debug("EQ_PARAM_NUM_OF_PRESETS: arg:%ld\n", arg);
break;
case EQ_PARAM_MAX_NUM_OF_BANDS:
esa_debug("EQ_PARAM_MAX_NUM_OF_BANDS: arg:%ld\n", arg);
break;
case EQ_PARAM_RANGE_OF_BANDLEVEL:
esa_debug("EQ_PARAM_RANGE_OF_BANDLEVEL: arg:%ld\n", arg);
break;
case EQ_PARAM_RANGE_OF_FREQ:
esa_debug("EQ_PARAM_RANGE_OF_FREQ: arg:%ld\n", arg);
break;
case EQ_PARAM_PRESET_ID:
esa_debug("%s: eq preset: %ld\n", __func__, arg);
writel(rtd->handle_id, si.mailbox + HANDLE_ID);
writel((unsigned int)arg, si.mailbox + PARAMS_VAL1);
esa_send_cmd((param << 16) | CMD_SET_PARAMS);
break;
case EQ_PARAM_NUM_OF_BANDS:
esa_debug("EQ_PARAM_NUM_OF_BANDS: arg:%ld\n", arg);
break;
case EQ_PARAM_CENTER_FREQ:
esa_debug("EQ_PARAM_CENTER_FREQ: arg:%ld\n", arg);
break;
case EQ_PARAM_BANDLEVEL:
esa_debug("EQ_PARAM_BANDLEVEL: arg:%ld\n", arg);
break;
case EQ_PARAM_BANDWIDTH:
esa_debug("EQ_PARAM_BANDWIDTH: arg:%ld\n", arg);
break;
case EQ_MAX_CONFIG_INFO:
esa_debug("EQ_MAX_CONFIG_INFO: arg:%ld\n", arg);
break;
case EQ_CONFIG_INFO:
esa_debug("EQ_CONFIG_INFO: arg:%ld\n", arg);
break;
case EQ_BAND_INFO:
esa_debug("EQ_BAND_INFO: arg:%ld\n", arg);
break;
case ADEC_PARAM_SET_EOS:
writel(rtd->handle_id, si.mailbox + HANDLE_ID);
esa_send_cmd((param << 16) | CMD_SET_PARAMS);
esa_debug("ADEC_PARAM_SET_EOS: handle_id:%x\n", rtd->handle_id);
rtd->get_eos = EOS_YET;
break;
case SET_IBUF_POOL_INFO:
esa_debug("SET_IBUF_POOL_INFO: arg:%ld\n", arg);
break;
case SET_OBUF_POOL_INFO:
esa_debug("SET_OBUF_POOL_INFO: arg:%ld\n", arg);
break;
default:
esa_err("%s: Unknown %ld\n", __func__, arg);
break;
}
return ret;
}
static int esa_get_params(struct file *file, unsigned int param,
unsigned long arg)
{
struct esa_rtd *rtd = file->private_data;
struct audio_mem_info_t *argp = (struct audio_mem_info_t *)arg;
struct audio_pcm_config_info_t *argp_pcm_info;
unsigned long val;
int ret = 0;
switch (param) {
case PCM_PARAM_MAX_SAMPLE_RATE:
esa_debug("PCM_PARAM_MAX_SAMPLE_RATE: arg:%ld\n", arg);
break;
case PCM_PARAM_MAX_NUM_OF_CH:
esa_debug("PCM_PARAM_MAX_NUM_OF_CH: arg:%ld\n", arg);
break;
case PCM_PARAM_MAX_BIT_PER_SAMPLE:
esa_debug("PCM_PARAM_MAX_BIT_PER_SAMPLE: arg:%ld\n", arg);
break;
case PCM_PARAM_SAMPLE_RATE:
writel(rtd->handle_id, si.mailbox + HANDLE_ID);
esa_send_cmd((param << 16) | CMD_GET_PARAMS);
val = readl(si.mailbox + PARAMS_VAL1);
esa_debug("%s: sampling rate:%ld\n", __func__, val);
if (val >= SAMPLE_RATE_MIN) {
esa_debug("SAMPLE_RATE: SUCCESS: arg:0x%p\n", (void*)arg);
ret = copy_to_user((unsigned long *)arg, &val,
sizeof(unsigned long));
} else
esa_debug("SAMPLE_RATE: FAILED: arg:0x%p\n", (void*)arg);
break;
case PCM_PARAM_NUM_OF_CH:
writel(rtd->handle_id, si.mailbox + HANDLE_ID);
esa_send_cmd((param << 16) | CMD_GET_PARAMS);
val = readl(si.mailbox + PARAMS_VAL1);
esa_debug("%s: Channel:%ld\n", __func__, val);
if (val >= CH_NUM_MIN) {
esa_debug("PCM_CONFIG_NUM_CH: SUCCESS: arg:0x%p\n", (void*)arg);
ret = copy_to_user((unsigned long *)arg, &val,
sizeof(unsigned long));
} else
esa_debug("PCM_PARAM_NUM_CH: FAILED: arg:0x%p\n", (void*)arg);
break;
case PCM_PARAM_BIT_PER_SAMPLE:
esa_debug("PCM_PARAM_BIT_PER_SAMPLE: arg:%ld\n", arg);
break;
case PCM_MAX_CONFIG_INFO:
esa_debug("PCM_MAX_CONFIG_INFO: arg:%ld\n", arg);
break;
case PCM_CONFIG_INFO:
argp_pcm_info = (struct audio_pcm_config_info_t *)arg;
rtd->pcm_info.sample_rate = readl(si.mailbox + FREQ_SAMPLE);
rtd->pcm_info.num_of_channel = readl(si.mailbox + CH_NUM);
esa_debug("%s: rate:%d, ch:%d\n", __func__,
rtd->pcm_info.sample_rate,
rtd->pcm_info.num_of_channel);
if (rtd->pcm_info.sample_rate >= 8000 &&
rtd->pcm_info.num_of_channel > 0) {
esa_debug("PCM_CONFIG_INFO: SUCCESS: arg:0x%p\n", (void*)arg);
ret = copy_to_user(argp_pcm_info, &rtd->ibuf_info,
sizeof(struct audio_pcm_config_info_t));
} else
esa_debug("PCM_CONFIG_INFO: FAILED: arg:0x%p\n", (void*)arg);
break;
case ADEC_PARAM_GET_OUTPUT_STATUS:
writel(rtd->handle_id, si.mailbox + HANDLE_ID);
esa_send_cmd((param << 16) | CMD_GET_PARAMS);
val = readl(si.mailbox + PARAMS_VAL1);
esa_debug("OUTPUT_STATUS:%ld, handle_id:%x\n", val, rtd->handle_id);
if (val && rtd->get_eos == EOS_YET)
val = 0;
ret = copy_to_user((unsigned long *)arg, &val,
sizeof(unsigned long));
break;
case GET_IBUF_POOL_INFO:
esa_debug("GET_IBUF_POOL_INFO: arg:0x%p\n", (void*)arg);
rtd->ibuf_info.mem_size = rtd->ibuf_size;
rtd->ibuf_info.block_count = rtd->ibuf_count;
ret = copy_to_user(argp, &rtd->ibuf_info,
sizeof(struct audio_mem_info_t));
break;
case GET_OBUF_POOL_INFO:
esa_debug("GET_OBUF_POOL_INFO: arg:0x%p\n", (void*)arg);
rtd->obuf_info.mem_size = rtd->obuf_size;
rtd->obuf_info.block_count = rtd->obuf_count;
ret = copy_to_user(argp, &rtd->obuf_info,
sizeof(struct audio_mem_info_t));
break;
default:
esa_err("%s: Unknown %ld\n", __func__, arg);
break;
}
if (ret)
esa_err("%s: failed to copy_to_user\n", __func__);
return ret;
}
#endif
static irqreturn_t esa_isr(int irqno, void *id)
{
unsigned int fw_stat, log_size, val;
int index;
bool wakeup = false;
#ifdef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
unsigned int size;
int ret;
#endif
writel(0, si.regs + SW_INTR_CPU);
val = readl(si.mailbox + RETURN_CMD);
if (val == 1)
esa_err("%s: There is possibility of firmware CMD fail %u\n",
__func__, val);
fw_stat = val >> 16;
esa_debug("fw_stat(%08x), val(%08x)\n", fw_stat, val);
switch (fw_stat) {
case INTR_WAKEUP: /* handle wakeup interrupt from firmware */
si.isr_done = 1;
wakeup = true;
break;
case INTR_READY:
pr_debug("FW is ready!\n");
si.fw_ready = true;
wakeup = true;
break;
case INTR_DMA:
index = readl(si.mailbox + COMPR_DMA_IDX);
#ifdef CONFIG_SND_SOC_EAX_SLOWPATH
pr_debug("ADMA INTR index = %d !!!!\n", index);
eax_slowpath_wakeup_buf_wq(index);
#endif
writel(0, si.mailbox + COMPR_INTR_ACK);
break;
case INTR_DMA_INDEX:
index = readl(si.mailbox + RETURN_CMD) & 0xF;
#ifdef CONFIG_SND_SOC_EAX_SLOWPATH
pr_debug("ADMA INTR index = %d , dma_ptr = %x!!!!\n",
index, readl(dma_reg + 0x440));
eax_slowpath_wakeup_buf_wq(index);
#endif
writel(0, si.mailbox + COMPR_INTR_ACK);
break;
#ifdef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
case INTR_CREATED:
esa_info("INTR_CREATED\n");
si.isr_compr_created = 1;
wakeup = true;
writel(0, si.mailbox + COMPR_INTR_ACK);
break;
case INTR_DECODED:
/* check the error */
val &= 0xFF;
if (val) {
esa_err("INTR_DECODED err(%d)\n", val);
esa_dump_fw_log();
writel(0, si.mailbox + COMPR_INTR_ACK);
break;
}
/* update copied total bytes */
size = readl(si.mailbox + COMPR_SIZE_OUT_DATA);
esa_debug("INTR_DECODED(%d)\n", size);
if (ptr_ap) {
ret = ptr_ap->ops(fw_stat, size, ptr_ap->priv);
if (ret)
esa_err("INTR_DECODED handler err(%d)\n", ret);
}
writel(0, si.mailbox + COMPR_INTR_ACK);
break;
case INTR_FLUSH:
/* check the error */
val &= 0xFF;
if (val) {
esa_err("INTR_FLUSH err(%d)\n", val);
esa_dump_fw_log();
writel(0, si.mailbox + COMPR_INTR_ACK);
break;
}
/* flush done */
if (ptr_ap) {
ret = ptr_ap->ops(fw_stat, 0, ptr_ap->priv);
if (ret)
esa_err("INTR_FLUSH handler err(%d)\n", ret);
}
writel(0, si.mailbox + COMPR_INTR_ACK);
break;
case INTR_PAUSED:
/* check the error */
val &= 0xFF;
if (val) {
esa_err("INTR_PAUSED err(%d)\n", val);
esa_dump_fw_log();
writel(0, si.mailbox + COMPR_INTR_ACK);
break;
}
/* paused */
if (ptr_ap) {
ret = ptr_ap->ops(fw_stat, 0, ptr_ap->priv);
if (ret)
esa_err("INTR_PAUSED handler err(%d)\n", ret);
}
writel(0, si.mailbox + COMPR_INTR_ACK);
break;
case INTR_EOS:
if (ptr_ap) {
ret = ptr_ap->ops(fw_stat, 0, ptr_ap->priv);
if (ret)
esa_err("INTR_EOS handler err(%d)\n", ret);
}
writel(0, si.mailbox + COMPR_INTR_ACK);
break;
case INTR_DESTROY:
/* check the error */
val &= 0xFF;
if (val) {
esa_err("INTR_DESTROY err(%d)\n", val);
esa_dump_fw_log();
writel(0, si.mailbox + COMPR_INTR_ACK);
break;
}
/* destroied */
if (ptr_ap) {
ret = ptr_ap->ops(fw_stat, 0, ptr_ap->priv);
if (ret)
esa_err("INTR_DESTROY handler err(%d)\n", ret);
}
writel(0, si.mailbox + COMPR_INTR_ACK);
si.isr_compr_created = 0;
break;
case INTR_FX_EXT:
esa_debug("INTR_FX_EXT: fw_stat(%08x), val(%08x)\n",
fw_stat, val);
si.fx_next_idx = val & 0xF;
si.fx_irq_done = true;
if (waitqueue_active(&esa_fx_wq))
wake_up_interruptible(&esa_fx_wq);
writel(0, si.mailbox + COMPR_INTR_ACK);
break;
#ifdef CONFIG_SND_SAMSUNG_ELPE
case INTR_EFF_REQUEST:
queue_lpeff_cmd(LPEFF_EFFECT_CMD);
writel(0, si.mailbox + COMPR_INTR_ACK);
break;
#endif
#endif
case INTR_FW_LOG: /* print debug message from firmware */
log_size = readl(si.mailbox + RETURN_CMD) & 0x00FF;
if (!log_size) {
esa_debug("FW: %s", si.fw_log);
si.fw_log_pos = 0;
} else {
val = readl(si.mailbox + FW_LOG_VAL1);
memcpy(si.fw_log + si.fw_log_pos, &val, 4);
val = readl(si.mailbox + FW_LOG_VAL2);
memcpy(si.fw_log + si.fw_log_pos + 4, &val, 4);
si.fw_log_pos += log_size;
si.fw_log[si.fw_log_pos] = '\0';
}
writel(0, si.mailbox + RETURN_CMD);
break;
#ifdef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
case INTR_SET_CPU_LOCK: /* Set CPU LOCK for OFFLOAD EFFECT */
si.cpu_lock_level = readl(si.mailbox + COMPR_CPU_LOCK_LV);
si.set_cpu_lock = true;
if (waitqueue_active(&esa_cpu_lock_wq))
wake_up_interruptible(&esa_cpu_lock_wq);
writel(0, si.mailbox + COMPR_INTR_ACK);
break;
#endif
default:
esa_err("%s: unknown intr_stat = 0x%08X\n",
__func__, fw_stat);
writel(0, si.mailbox + COMPR_INTR_ACK);
break;
}
if (wakeup && waitqueue_active(&esa_wq))
wake_up_interruptible(&esa_wq);
return IRQ_HANDLED;
}
#ifndef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
static long esa_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct esa_rtd *rtd = file->private_data;
int ret = 0;
unsigned int param = cmd >> 16;
cmd = cmd & 0xffff;
esa_debug("%s: idx:%d, param:%x, cmd:%x\n", __func__,
rtd->idx, param, cmd);
mutex_lock(&esa_mutex);
pm_runtime_get_sync(&si.pdev->dev);
switch (cmd) {
case SEIREN_IOCTL_CH_CREATE:
rtd->ip_type = (unsigned int) arg;
arg = arg << 16;
writel(arg, si.mailbox + IP_TYPE);
ret = esa_send_cmd(CMD_CREATE);
if (ret == -EBUSY)
break;
ret = readl(si.mailbox + RETURN_CMD);
if (ret != 0)
break;
rtd->handle_id = readl(si.mailbox + IP_ID);
esa_buffer_init(file);
esa_debug("CH_CREATE: ret_val:%x, handle_id:%x\n",
readl(si.mailbox + RETURN_CMD),
rtd->handle_id);
esa_update_qos();
break;
case SEIREN_IOCTL_CH_DESTROY:
writel(rtd->handle_id, si.mailbox + HANDLE_ID);
ret = esa_send_cmd(CMD_DESTROY);
if (ret == -EBUSY)
break;
ret = readl(si.mailbox + RETURN_CMD);
if (ret != 0)
break;
esa_debug("CH_DESTROY: ret_val:%x, handle_id:%x\n",
readl(si.mailbox + RETURN_CMD),
rtd->handle_id);
break;
case SEIREN_IOCTL_CH_EXE:
esa_debug("CH_EXE\n");
ret = esa_exe(file, param, arg);
break;
case SEIREN_IOCTL_CH_SET_PARAMS:
esa_debug("CH_SET_PARAMS\n");
ret = esa_set_params(file, param, arg);
break;
case SEIREN_IOCTL_CH_GET_PARAMS:
esa_debug("CH_GET_PARAMS\n");
ret = esa_get_params(file, param, arg);
break;
case SEIREN_IOCTL_CH_RESET:
esa_debug("CH_RESET\n");
break;
case SEIREN_IOCTL_CH_FLUSH:
arg = arg << 16;
writel(rtd->handle_id, si.mailbox + HANDLE_ID);
esa_send_cmd(CMD_RESET);
esa_debug("CH_FLUSH: val: %x, handle_id : %x\n",
readl(si.mailbox + RETURN_CMD),
rtd->handle_id);
rtd->get_eos = EOS_NO;
rtd->select_ibuf = 0;
rtd->select_obuf = 0;
rtd->obuf0_filled = false;
rtd->obuf1_filled = false;
break;
case SEIREN_IOCTL_CH_CONFIG:
esa_debug("CH_CONFIG\n");
rtd->need_config = true;
break;
}
pm_runtime_mark_last_busy(&si.pdev->dev);
pm_runtime_put_sync_autosuspend(&si.pdev->dev);
mutex_unlock(&esa_mutex);
return ret;
}
static int esa_open(struct inode *inode, struct file *file)
{
struct esa_rtd *rtd;
if (!si.fwmem_loaded) {
esa_err("Firmware not ready\n");
return -ENXIO;
}
/* alloc rtd */
rtd = esa_alloc_rtd();
if (!rtd) {
esa_debug("%s: Not enough memory\n", __func__);
return -EFAULT;
}
esa_debug("%s: idx:%d\n", __func__, rtd->idx);
/* initialize */
file->private_data = rtd;
rtd->get_eos = EOS_NO;
rtd->need_config = false;
rtd->select_ibuf = 0;
rtd->select_obuf = 0;
rtd->obuf0_filled = false;
rtd->obuf1_filled = false;
return 0;
}
static int esa_release(struct inode *inode, struct file *file)
{
struct esa_rtd *rtd = file->private_data;
esa_debug("%s: idx:%d\n", __func__, rtd->idx);
/* de-initialize */
file->private_data = NULL;
esa_free_rtd(rtd);
return 0;
}
#endif
#ifdef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
static int esa_open(struct inode *inode, struct file *file)
{
esa_info("%s\n", __func__);
return 0;
}
static int esa_release(struct inode *inode, struct file *file)
{
esa_info("%s\n", __func__);
return 0;
}
ssize_t esa_copy(unsigned long hwbuf, ssize_t size)
{
int i, cnt, ret;
mutex_lock(&esa_mutex);
pm_runtime_get_sync(&si.pdev->dev);
cnt = size / FX_BUF_SIZE;
if (cnt && (size - (FX_BUF_SIZE * cnt))) {
cnt++;
} else {
cnt = 1;
}
for (i = 0; i < cnt; i++) {
size_t copy_size;
ret = wait_event_interruptible_timeout(esa_fx_wq,
si.fx_irq_done, HZ);
if (!ret) {
esa_err("%s: fx irq timeout\n", __func__);
ret = -EBUSY;
goto out;
}
si.fx_irq_done = false;
if (si.sram)
si.fx_work_buf = (unsigned char *)(si.sram + FX_BUF_OFFSET);
else
esa_err("%s: sram address is empty (%d)\n", __func__, __LINE__);
si.fx_work_buf += si.fx_next_idx * FX_BUF_SIZE;
esa_debug("%s: buf_idx = %d\n", __func__, si.fx_next_idx);
copy_size = (size > FX_BUF_SIZE) ? FX_BUF_SIZE : size;
memcpy((char *)hwbuf, si.fx_work_buf, copy_size);
hwbuf += (unsigned long)copy_size;
size -= copy_size;
}
out:
pm_runtime_mark_last_busy(&si.pdev->dev);
pm_runtime_put_sync_autosuspend(&si.pdev->dev);
mutex_unlock(&esa_mutex);
return ret;
}
static ssize_t esa_read(struct file *file, char *buffer,
size_t size, loff_t *pos)
{
int ret;
mutex_lock(&esa_mutex);
pm_runtime_get_sync(&si.pdev->dev);
if (!si.fx_ext_on) {
esa_debug("%s: fx ext not enabled\n", __func__);
ret = -EINVAL;
goto out;
}
ret = wait_event_interruptible_timeout(esa_fx_wq, si.fx_irq_done, HZ);
if (!ret) {
esa_err("%s: fx irq timeout\n", __func__);
ret = -EBUSY;
goto out;
}
si.fx_irq_done = false;
if (si.sram)
si.fx_work_buf = (unsigned char *)(si.sram + FX_BUF_OFFSET);
else
esa_err("%s: sram address is empty (%d)\n", __func__, __LINE__);
si.fx_work_buf += si.fx_next_idx * FX_BUF_SIZE;
esa_debug("%s: buf_idx = %d\n", __func__, si.fx_next_idx);
if (copy_to_user((void *)buffer, si.fx_work_buf, FX_BUF_SIZE)) {
esa_err("%s: failed to copy_to_user\n", __func__);
ret = -EFAULT;
} else {
ret = FX_BUF_SIZE;
}
out:
pm_runtime_mark_last_busy(&si.pdev->dev);
pm_runtime_put_sync_autosuspend(&si.pdev->dev);
mutex_unlock(&esa_mutex);
return ret;
}
static ssize_t esa_write(struct file *file, const char *buffer,
size_t size, loff_t *pos)
{
int ret;
mutex_lock(&esa_mutex);
pm_runtime_get_sync(&si.pdev->dev);
if (!si.fx_ext_on) {
esa_debug("%s: fx ext not enabled\n", __func__);
ret = -EINVAL;
goto out;
}
if (!si.fx_work_buf) {
esa_debug("%s: fx buf not ready\n", __func__);
ret = -EBUSY;
goto out;
}
if (size > FX_BUF_SIZE) {
esa_err("%s: copy size is bigger than buffer size\n",
__func__);
goto out;
}
if (copy_from_user(si.fx_work_buf, buffer, size)) {
esa_err("%s: failed to copy_from_user\n", __func__);
ret = -EFAULT;
} else {
esa_debug("%s: %lu bytes\n", __func__, size);
ret = FX_BUF_SIZE;
}
out:
pm_runtime_mark_last_busy(&si.pdev->dev);
pm_runtime_put_sync_autosuspend(&si.pdev->dev);
mutex_unlock(&esa_mutex);
return ret;
}
static long esa_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int ret = 0;
mutex_lock(&esa_mutex);
pm_runtime_get_sync(&si.pdev->dev);
switch (cmd) {
case SEIREN_IOCTL_FX_EXT:
si.fx_ext_on = arg ? true : false;
writel(si.fx_ext_on ? 1 : 0, si.mailbox + EFFECT_EXT_ON);
break;
#ifdef CONFIG_SND_SAMSUNG_ELPE
case SEIREN_IOCTL_ELPE_DONE:
if (si.effect_ram) {
writel(arg, si.effect_ram + ELPE_BASE + ELPE_RET);
writel(0, si.effect_ram + ELPE_BASE + ELPE_DONE);
} else {
esa_err("%s: elpe effect ram is empty\n", __func__);
}
break;
#endif
default:
esa_err("%s: unknown cmd:%08X, arg:%08X\n",
__func__, cmd, (unsigned int)arg);
break;
}
pm_runtime_mark_last_busy(&si.pdev->dev);
pm_runtime_put_sync_autosuspend(&si.pdev->dev);
mutex_unlock(&esa_mutex);
return ret;
}
#endif
static int esa_mmap(struct file *filep, struct vm_area_struct *vmarea)
{
unsigned int pfn;
unsigned long len = vmarea->vm_end - vmarea->vm_start;
int ret = 0;
esa_info("%s: start=0x%p, size=%ld, offset=%ld, phys=0x%llX",
__func__,
(void *)vmarea->vm_start, len, vmarea->vm_pgoff,
(u64)si.fwarea_pa[1]);
if (len > FWAREA_SIZE) {
esa_err("%s: failed to mmap\n", __func__);
return -EINVAL;
}
vmarea->vm_flags |= VM_IO;
pfn = (unsigned int)(si.fwarea_pa[1] >> PAGE_SHIFT);
ret = (int)remap_pfn_range(vmarea, vmarea->vm_start,
pfn, len, pgprot_noncached(vmarea->vm_page_prot));
esa_info("%s: ret = 0x%x\n", __func__, ret);
return ret;
}
static const struct file_operations esa_fops = {
.owner = THIS_MODULE,
.open = esa_open,
.release = esa_release,
.read = esa_read,
.write = esa_write,
.unlocked_ioctl = esa_ioctl,
.compat_ioctl = esa_ioctl,
.mmap = esa_mmap,
};
static struct miscdevice esa_miscdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "seiren",
.fops = &esa_fops,
};
static int esa_proc_show(struct seq_file *m, void *v)
{
return 0;
}
static int esa_proc_open(struct inode *inode, struct file *file)
{
header_printed = 0;
end_index = 0;
return single_open(file, esa_proc_show, NULL);
}
static ssize_t esa_proc_read(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
ssize_t len, ret = 0;
char *buf;
int fwver;
int n;
char log[256];
char *addr = si.fw_log_buf;
int next;
int i;
int index, prev_index, start_index;
int is_skip = 0;
if (*ppos < 0 || !count)
return -EINVAL;
buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!buf)
return -ENOMEM;
if (header_printed == 0) {
len = snprintf(buf + ret, PAGE_SIZE - ret,
"fw is %s\n", si.fw_ready ? "ready" : "off");
if (len > 0) ret += len;
len = snprintf(buf + ret, PAGE_SIZE - ret,
"rtd cnt: %d\n", si.rtd_cnt);
if (len > 0) ret += len;
#ifdef CONFIG_PM_DEVFREQ
len = snprintf(buf + ret, PAGE_SIZE - ret,
"mif: %d\n", si.mif_qos / 1000);
if (len > 0) ret += len;
#endif
if (si.fw_ready) {
fwver = readl(si.mailbox + VIRSION_ID);
len = snprintf(buf + ret, PAGE_SIZE - ret,
"Firmware Version: %c%c%c-%c\n",
(fwver >> 24), ((fwver >> 16) & 0xFF),
((fwver >> 8) & 0xFF), fwver & 0xFF);
if (len > 0) ret += len;
len = snprintf(buf + ret, PAGE_SIZE - ret,
"fw_log:\n");
if (len > 0) ret += len;
len = snprintf(buf + ret, PAGE_SIZE - ret,
"running cnt:%d\n",
readl(si.mailbox + COMPR_CHECK_RUNNING));
if (len > 0) ret += len;
len = snprintf(buf + ret, PAGE_SIZE - ret,
"ack status: lpcm(%d) compr(%d:%x)\n",
readl(si.mailbox + COMPR_INTR_DMA_ACK),
readl(si.mailbox + COMPR_INTR_ACK),
readl(si.mailbox + COMPR_CHECK_CMD));
if (len > 0) ret += len;
}
if (si.fw_ready) {
/* sorting */
prev_index = 0;
start = 0;
for (n = 0; n < FW_LOG_LINE; n++, addr += 128) {
memcpy(log, addr, 128);
index = 0;
for (i = 0; i < 8; i++)
index += (hex_to_bin(log[i]) << ((7-i)*4));
if (index < 0)
is_skip = 1;
else if (prev_index <= index) {
prev_index = index;
end_index = index;
end = n;
} else {
start_index = index;
start = n;
break;
}
}
/* log print */
if (!is_skip) {
addr = si.fw_log_buf + start * 128;
for (n = start; n < FW_LOG_LINE; n++, addr += 128) {
memcpy(log, addr, 128);
len = snprintf(buf + ret, PAGE_SIZE - ret,
"%s", log);
if (len > 0) ret += len;
}
}
addr = si.fw_log_buf;
for (n = 0; n <= end; n++, addr += 128) {
memcpy(log, addr, 128);
len = snprintf(buf + ret, PAGE_SIZE - ret,
"%s", log);
if (len > 0) ret += len;
}
}
if (ret > PAGE_SIZE) ret = PAGE_SIZE;
if (copy_to_user(user_buf, buf, ret)) {
ret = -EFAULT;
}
header_printed = 1;
} else {
ret = 0;
while (si.fw_ready) {
udelay(500);
raw_spin_lock_irq(&esa_logbuf_lock);
/* Check prev printed index */
addr = si.fw_log_buf;
next = (end+1)%FW_LOG_LINE;
memcpy(log, addr+(next*128), 128);
index = 0;
for (i = 0; i < 8; i++)
index += (hex_to_bin(log[i]) << ((7-i)*4));
if (index > end_index) {
end_index = index;
end = next;
len = snprintf(buf + ret, PAGE_SIZE - ret,
"%s", log);
if (len > 0) {
ret += len;
if (ret > PAGE_SIZE) {
raw_spin_unlock_irq(&esa_logbuf_lock);
break;
}
if (copy_to_user(user_buf, buf, ret)) {
printk("copy_to_user error \n");
ret = -EFAULT;
}
}
} else {
if (ret != 0) {
raw_spin_unlock_irq(&esa_logbuf_lock);
break;
}
}
raw_spin_unlock_irq(&esa_logbuf_lock);
}
}
kfree(buf);
return ret;
}
static const struct file_operations esa_proc_fops = {
.owner = THIS_MODULE,
.open = esa_proc_open,
.read = esa_proc_read,
};
#ifdef CONFIG_PM
static int esa_do_suspend(struct device *dev)
{
esa_fw_shutdown();
clk_disable_unprepare(si.clk_ca5);
lpass_put_sync(dev);
esa_update_qos();
si.pm_on = false;
#ifdef CONFIG_SOC_EXYNOS8890
si.sram = NULL;
si.fw_log_buf = NULL;
si.effect_ram = NULL;
lpeff_set_effect_addr(NULL);
#endif
lpass_update_lpclock(LPCLK_CTRLID_OFFLOAD, false);
return 0;
}
#endif
static int esa_do_resume(struct device *dev)
{
si.pm_on = true;
lpass_update_lpclock(LPCLK_CTRLID_OFFLOAD, true);
lpass_get_sync(dev);
#ifdef CONFIG_SOC_EXYNOS8890
si.sram = lpass_get_mem();
if (!si.sram) {
esa_err("Failed to get lpass sram\n");
return -ENOMEM;
} else {
si.fw_log_buf = si.sram + FW_LOG_ADDR;
si.effect_ram = si.sram + EFFECT_OFFSET;
lpeff_set_effect_addr(si.effect_ram);
}
#endif
clk_prepare_enable(si.clk_ca5);
esa_fw_startup();
esa_update_qos();
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int esa_suspend(struct device *dev)
{
esa_debug("%s entered\n", __func__);
if (!si.pm_on)
return 0;
#ifdef CONFIG_SOC_EXYNOS8890
si.sram = lpass_get_mem();
if (!si.sram) {
esa_err("Failed to get lpass sram %s\n", __func__);
return -ENOMEM;
} else {
si.fw_log_buf = si.sram + FW_LOG_ADDR;
si.effect_ram = si.sram + EFFECT_OFFSET;
lpeff_set_effect_addr(si.effect_ram);
}
#endif
esa_fw_shutdown();
clk_disable_unprepare(si.clk_ca5);
si.pm_suspended = true;
return 0;
}
static int esa_resume(struct device *dev)
{
#ifdef CONFIG_SOC_EXYNOS8890
void __iomem *cmu_reg;
#endif
esa_debug("%s entered\n", __func__);
if (!si.pm_suspended)
return 0;
si.pm_suspended = false;
#ifdef CONFIG_SOC_EXYNOS8890
si.sram = lpass_get_mem();
if (!si.sram) {
esa_err("Failed to get lpass sram %s\n", __func__);
return -ENOMEM;
} else {
si.fw_log_buf = si.sram + FW_LOG_ADDR;
si.effect_ram = si.sram + EFFECT_OFFSET;
lpeff_set_effect_addr(si.effect_ram);
}
cmu_reg = ioremap(0x114C0000, SZ_4K);
writel(0x1, cmu_reg + 0x800); /* Enable CA5 clock */
iounmap(cmu_reg);
#endif
clk_prepare_enable(si.clk_ca5);
esa_fw_startup();
return 0;
}
#else
#define esa_suspend NULL
#define esa_resume NULL
#endif
#ifdef CONFIG_SND_SAMSUNG_IOMMU
static int esa_prepare_buffer(struct device *dev)
{
unsigned long iova;
int n, ret;
/* Original firmware */
si.fwmem = devm_kzalloc(dev, FWMEM_SIZE, GFP_KERNEL);
if (!si.fwmem) {
esa_err("Failed to alloc fwmem\n");
goto err;
}
si.fwmem_pa = virt_to_phys(si.fwmem);
/* Firmware backup for SRAM */
si.fwmem_sram_bak = devm_kzalloc(dev, SRAM_FW_MAX, GFP_KERNEL);
if (!si.fwmem_sram_bak) {
esa_err("Failed to alloc fwmem_sram_bak\n");
goto err;
}
/* Firmware for DRAM */
for (n = 0; n < FWAREA_NUM; n++) {
si.fwarea[n] = dma_alloc_coherent(dev, FWAREA_SIZE,
&si.fwarea_pa[n], GFP_KERNEL);
esa_info("si.fwarea[%d] v_address : 0x%p p_address : 0x%x\n",
n, si.fwarea[n], (int)si.fwarea_pa[n]);
if (!si.fwarea[n]) {
esa_err("Failed to alloc fwarea\n");
goto err0;
}
}
for (n = 0, iova = FWAREA_IOVA;
n < FWAREA_NUM; n++, iova += FWAREA_SIZE) {
ret = iommu_map(si.domain, iova,
si.fwarea_pa[n], FWAREA_SIZE, 0);
if (ret) {
esa_err("Failed to map iommu\n");
goto err1;
}
}
/* Base address for IBUF, OBUF and FW LOG */
si.bufmem = si.fwarea[0] + BASEMEM_OFFSET;
si.bufmem_pa = si.fwarea_pa[0];
#ifndef CONFIG_SOC_EXYNOS8890
si.fw_log_buf = si.sram + FW_LOG_ADDR;
#endif
return 0;
err1:
for (n = 0, iova = FWAREA_IOVA;
n < FWAREA_NUM; n++, iova += FWAREA_SIZE) {
iommu_unmap(si.domain, iova, FWAREA_SIZE);
}
err0:
for (n = 0; n < FWAREA_NUM; n++) {
if (si.fwarea[n])
dma_free_coherent(dev, FWAREA_SIZE,
si.fwarea[n], si.fwarea_pa[n]);
}
err:
return -ENOMEM;
}
#endif
static void esa_fw_request_complete(const struct firmware *fw_sram, void *ctx)
{
const struct firmware *fw_dram;
struct device *dev = ctx;
if (!fw_sram) {
esa_err("Failed to requset firmware[%s]\n", FW_SRAM_NAME);
return;
}
if (request_firmware(&fw_dram, FW_DRAM_NAME, dev)) {
esa_err("Failed to requset firmware[%s]\n", FW_DRAM_NAME);
return;
}
si.fwmem_loaded = true;
si.fw_sbin_size = fw_sram->size;
si.fw_dbin_size = fw_dram->size;
memcpy(si.fwmem, fw_sram->data, si.fw_sbin_size);
memcpy(si.fwmem + si.fw_sbin_size, fw_dram->data, si.fw_dbin_size);
esa_info("FW Loaded (SRAM = %d, DRAM = %d)\n",
si.fw_sbin_size, si.fw_dbin_size);
return;
}
static const char banner[] =
KERN_INFO "Exynos Seiren Audio driver, (c)2013 Samsung Electronics\n";
static int esa_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = pdev->dev.of_node;
struct resource *res;
#ifdef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
struct sched_param param = { .sched_priority = 0 };
#endif
int ret = 0;
printk(banner);
spin_lock_init(&si.lock);
#if defined(CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD)
spin_lock_init(&si.cmd_lock);
spin_lock_init(&si.compr_lock);
si.is_compr_open = false;
#endif
si.pdev = pdev;
ret = misc_register(&esa_miscdev);
if (ret) {
esa_err("Cannot register miscdev\n");
return -ENODEV;
}
si.regs = lpass_get_regs();
if (!si.regs) {
esa_err("Failed to get lpass regs\n");
return -ENODEV;
}
#ifndef CONFIG_SOC_EXYNOS8890
si.sram = lpass_get_mem();
if (!si.sram) {
esa_err("Failed to get lpass sram\n");
return -ENODEV;
}
#endif
#ifdef CONFIG_SND_ESA_SA_EFFECT
#ifndef CONFIG_SOC_EXYNOS8890
si.effect_ram = si.sram + EFFECT_OFFSET;
#endif
si.out_sample_rate = 0;
#endif
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!res) {
dev_err(dev, "Failed to get irq resource\n");
return -ENXIO;
}
si.irq_ca5 = res->start;
si.mailbox = si.regs + 0x80;
si.clk_ca5 = clk_get(dev, "ca5");
if (IS_ERR(si.clk_ca5)) {
dev_err(dev, "ca5 clk not found\n");
return -ENODEV;
}
if (np) {
if (of_find_property(np, "samsung,lpass-subip", NULL))
lpass_register_subip(dev, "ca5");
}
#ifdef CONFIG_SND_SAMSUNG_IOMMU
si.domain = lpass_get_iommu_domain();
if (!si.domain) {
dev_err(dev, "iommu not available\n");
goto err;
}
/* prepare buffer */
if (esa_prepare_buffer(dev))
goto err;
#else
dev_err(dev, "iommu not available\n");
goto err;
#endif
ret = request_firmware_nowait(THIS_MODULE,
FW_ACTION_HOTPLUG,
FW_SRAM_NAME,
dev,
GFP_KERNEL,
dev,
esa_fw_request_complete);
if (ret) {
dev_err(dev, "could not load firmware\n");
goto err;
} else {
dev_err(dev, "load firmware success\n");
}
ret = request_irq(si.irq_ca5, esa_isr, 0, "lpass-ca5", 0);
if (ret < 0) {
esa_err("Failed to claim CA5 irq\n");
goto err;
}
#ifdef CONFIG_PROC_FS
si.proc_file = proc_create("driver/seiren", 0, NULL, &esa_proc_fops);
if (!si.proc_file)
esa_info("Failed to register /proc/driver/seiren\n");
#endif
/* hold reset */
lpass_reset(LPASS_IP_CA5, LPASS_OP_RESET);
esa_debug("regs = %p\n", si.regs);
esa_debug("mailbox = %p\n", si.mailbox);
esa_debug("bufmem_pa = %p\n", (void*)si.bufmem_pa);
esa_debug("fwmem_pa = %p\n", (void*)si.fwmem_pa);
#ifdef CONFIG_PM
pm_runtime_use_autosuspend(dev);
pm_runtime_set_autosuspend_delay(dev, 300);
pm_runtime_enable(dev);
pm_runtime_get_sync(dev);
pm_runtime_put_sync(dev);
#else
esa_do_resume(dev);
#endif
#ifdef CONFIG_SND_SAMSUNG_ELPE
lpeff_init(si);
exynos_init_lpeffworker();
#endif
#ifdef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
si.aud_cpu_lock_thrd = (struct task_struct *)
kthread_run(esa_set_cpu_lock_kthread, NULL,
"esa-set-cpu-lock");
sched_setscheduler_nocheck(si.aud_cpu_lock_thrd, SCHED_NORMAL, &param);
if (!seiren_fw_snapshot_kobj) {
seiren_fw_snapshot_kobj =
kobject_create_and_add("snapshot-seiren-fw", NULL);
if (sysfs_create_file(seiren_fw_snapshot_kobj, &seiren_fw_dump_attribute.attr))
pr_err("%s: failed to create sysfs to control PCM dump\n", __func__);
}
#endif
#ifdef CONFIG_PM_DEVFREQ
si.mif_qos = 0;
si.int_qos = 0;
pm_qos_add_request(&si.ca5_mif_qos, PM_QOS_BUS_THROUGHPUT, 0);
pm_qos_add_request(&si.ca5_int_qos, PM_QOS_DEVICE_THROUGHPUT, 0);
#endif
if (!dma_reg)
dma_reg = ioremap(0x11420000, SZ_4K);
return 0;
err:
clk_put(si.clk_ca5);
return -ENODEV;
}
static int esa_remove(struct platform_device *pdev)
{
int ret = 0;
free_irq(si.irq_ca5, 0);
//ret = misc_deregister(&esa_miscdev);
if (ret)
esa_err("Cannot deregister miscdev\n");
#ifndef CONFIG_PM
clk_disable_unprepare(si.clk_ca5);
lpass_put_sync(&pdev->dev);
#endif
clk_put(si.clk_ca5);
return ret;
}
#ifdef CONFIG_PM
static int esa_runtime_suspend(struct device *dev)
{
esa_debug("%s entered\n", __func__);
return esa_do_suspend(dev);
}
static int esa_runtime_resume(struct device *dev)
{
esa_debug("%s entered\n", __func__);
return esa_do_resume(dev);
}
#endif
static struct platform_device_id esa_driver_ids[] = {
{
.name = "samsung-seiren",
},
{},
};
MODULE_DEVICE_TABLE(platform, esa_driver_ids);
#ifdef CONFIG_OF
static const struct of_device_id exynos_esa_match[] = {
{
.compatible = "samsung,exynos5430-seiren",
},
{},
};
MODULE_DEVICE_TABLE(of, exynos_esa_match);
#endif
static const struct dev_pm_ops esa_pmops = {
SET_SYSTEM_SLEEP_PM_OPS(
esa_suspend,
esa_resume
)
SET_RUNTIME_PM_OPS(
esa_runtime_suspend,
esa_runtime_resume,
NULL
)
};
static struct platform_driver esa_driver = {
.probe = esa_probe,
.remove = esa_remove,
.id_table = esa_driver_ids,
.driver = {
.name = "samsung-seiren",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(exynos_esa_match),
.pm = &esa_pmops,
},
};
module_platform_driver(esa_driver);
MODULE_AUTHOR("Yongjin Jo <yjin.jo@samsung.com>, "
"Yeongman Seo <yman.seo@samsung.com>");
MODULE_DESCRIPTION("Exynos Seiren Audio driver");
MODULE_LICENSE("GPL");