blob: ac4d52f22c8a6c5a935119ac99b2b4cd0f2bda1a [file] [log] [blame]
/* sound/soc/samsung/eax-dma.c
*
* Exynos Audio Mixer DMA driver
*
* Copyright (c) 2014 Samsung Electronics Co. Ltd.
* Yeongman Seo <yman.seo@samsung.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/slab.h>
#include <linux/dma-mapping.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/kthread.h>
#include <linux/iommu.h>
#include <linux/sched.h>
#include <linux/sched/rt.h>
#include <linux/dma/dma-pl330.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include <sound/exynos.h>
#include "lpass.h"
#include "dma.h"
#include "eax.h"
#undef EAX_DMA_PCM_DUMP
#ifdef EAX_DMA_PCM_DUMP
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/syscalls.h>
#include <linux/uaccess.h>
#include <linux/file.h>
#include <trace/events/sched.h>
static int dump_count = 0;
static struct file *mfilp_uhqa = NULL;
static struct file *mfilp_normal = NULL;
#endif
#ifdef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
#define NMIXBUF_441_110_SIZE 110 /* PCM 16bit 2ch */
#define NMIXBUF_441_110_BYTE (NMIXBUF_441_110_SIZE * 4) /* PCM 16bit 2ch */
#define NMIXBUF_441_441_SIZE 441 /* PCM 16bit 2ch */
#define NMIXBUF_441_441_BYTE (NMIXBUF_441_441_SIZE * 4) /* PCM 16bit 2ch */
#endif
#define NMIXBUF_SIZE 120 /* PCM 16bit 2ch */
#define NMIXBUF_BYTE (NMIXBUF_SIZE * 4) /* PCM 16bit 2ch */
#ifndef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
#define UMIXBUF_SIZE 512 /* Total 16384 byte / 2ch / 4byte / 4 periods */
#else
#define UMIXBUF_SIZE 480 /* Total 15360 byte / 2ch / 4byte / 4 periods */
#endif
#define UMIXBUF_BYTE (UMIXBUF_SIZE * 8) /* PCM 32bit 2ch */
#define DMA_PERIOD_CNT 4
#define DMA_START_THRESHOLD (DMA_PERIOD_CNT - 1)
#define SOUNDCAMP_HIGH_PERIOD_BYTE (480 * 4) /* PCM 16bit 2ch */
static const struct snd_pcm_hardware dma_hardware = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE,
.channels_min = 1,
.channels_max = 8,
.buffer_bytes_max = 128 * 1024,
.period_bytes_min = 128,
.period_bytes_max = 64 * 1024,
.periods_min = 2,
.periods_max = 128,
.fifo_size = 32,
};
struct runtime_data {
spinlock_t lock;
bool running;
struct snd_pcm_substream *substream;
struct snd_soc_dai *cpu_dai;
#ifdef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
snd_pcm_format_t format;
unsigned int rate;
#endif
unsigned int dma_period;
dma_addr_t dma_start;
dma_addr_t dma_pos;
dma_addr_t dma_end;
u32 *dma_buf;
short *normal_dma_mono;
int *uhqa_dma_mono;
unsigned long dma_bytes;
#ifdef EAX_DMA_PCM_DUMP
struct file *filp;
mm_segment_t old_fs;
char name[50];
#endif
};
struct mixer_info {
spinlock_t lock;
struct task_struct *thread_id;
struct snd_soc_dai *cpu_dai;
short *nmix_buf;
int *umix_buf;
unsigned long mixbuf_size;
unsigned long mixbuf_byte;
bool is_uhqa;
bool buf_fill;
bool running;
} mi;
struct buf_info {
struct runtime_data *prtd;
struct list_head node;
};
static LIST_HEAD(buf_list);
static DECLARE_WAIT_QUEUE_HEAD(mixer_run_wq);
static DECLARE_WAIT_QUEUE_HEAD(mixer_buf_wq);
static struct dma_info {
spinlock_t lock;
struct mutex mutex;
struct snd_soc_dai *cpu_dai;
struct s3c_dma_params *params;
unsigned int set_params_cnt;
bool params_init;
bool params_done;
bool prepare_done;
bool running;
bool buf_done;
bool buf_fill[DMA_PERIOD_CNT];
unsigned char *buf_wr_p[DMA_PERIOD_CNT];
int buf_wr_idx;
int buf_rd_idx;
u32 *dma_buf;
unsigned int dma_period;
dma_addr_t dma_start;
dma_addr_t dma_pos;
dma_addr_t dma_end;
} di;
static int eax_mixer_add(struct runtime_data *prtd);
static int eax_mixer_remove(struct runtime_data *prtd);
static void eax_mixer_trigger(bool on);
static int eax_mixer_kthread(void *arg);
#ifdef CONFIG_SND_SAMSUNG_SEIREN_DMA
extern void *samsung_esa_dma_get_ops(void);
#endif
#ifdef EAX_DMA_PCM_DUMP
struct kobject *eax_mixer_kobj = NULL;
static int dump_enabled = 0;
static ssize_t show_dump_enabled(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return snprintf(buf, 3, "%d\n", dump_enabled);
}
static ssize_t store_dump_enabled(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
int input;
if (!sscanf(buf, "%1d", &input))
return -EINVAL;
dump_enabled = !!input;
return count;
}
static struct kobj_attribute pcm_dump_attribute =
__ATTR(pcm_dump, S_IRUGO | S_IWUSR, show_dump_enabled, store_dump_enabled);
static void open_file(struct runtime_data *prtd, char *filename)
{
/* kernel memory access setting */
prtd->old_fs = get_fs();
set_fs(KERNEL_DS);
/* open a file */
prtd->filp = filp_open(filename, O_RDWR|O_APPEND|O_CREAT, S_IRUSR|S_IWUSR);
printk("Audio dump open %s\n",filename);
if (!mfilp_uhqa)
mfilp_uhqa = filp_open("/data/pcm/mix_buf_uhqa.raw",
O_RDWR|O_APPEND|O_CREAT, S_IRUSR|S_IWUSR);
if (!mfilp_normal)
mfilp_normal = filp_open("/data/pcm/mix_buf_normal.raw",
O_RDWR|O_APPEND|O_CREAT, S_IRUSR|S_IWUSR);
if (IS_ERR(prtd->filp)) {
printk("open error\n");
return;
}
else {
printk("open success\n");
}
}
static void close_file(struct runtime_data *prtd)
{
printk("Audio dump close %s\n", prtd->name);
vfs_fsync(prtd->filp, 0);
filp_close(prtd->filp, NULL); /* filp_close(filp, current->files) ? */
/* restore kernel memory setting */
set_fs(prtd->old_fs);
}
#endif
/* check_eax_dma_status
*
* EAX-DMA status is checked for AP Power mode.
* return 1 : EAX-DMA is running.
* return 0 : EAX-DMA is idle.
*/
int check_eax_dma_status(void)
{
return di.running;
}
#ifndef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
static int eax_dma_is_uhqa(unsigned long dma_bytes)
{
return (dma_bytes > (SOUNDCAMP_HIGH_PERIOD_BYTE * DMA_PERIOD_CNT));
}
#else
static int eax_dma_is_uhqa(snd_pcm_format_t format)
{
return (SNDRV_PCM_FORMAT_S24_LE == format);
}
#endif
static inline bool eax_mixer_any_buf_running(void)
{
struct buf_info *bi;
if (!list_empty(&buf_list)) {
list_for_each_entry(bi, &buf_list, node) {
if (bi->prtd && bi->prtd->running)
return true;
}
}
return false;
}
static void eax_adma_alloc_buf(void)
{
#ifdef CONFIG_SND_SAMSUNG_IOMMU
size_t size = 128 * 1024;
struct iommu_domain *domain = lpass_get_iommu_domain();
di.dma_buf = dma_alloc_coherent(di.cpu_dai->dev,
size, &di.dma_start, GFP_KERNEL);
iommu_map(domain, 0x48000000, di.dma_start, size, 0);
di.dma_start = 0x48000000;
#else
size_t size = 480 * DMA_PERIOD_CNT; /* default: 16bit 2ch */
di.dma_buf = dma_alloc_coherent(di.cpu_dai->dev,
size, &di.dma_start, GFP_KERNEL);
#endif
memset(di.buf_wr_p, 0, sizeof(unsigned char *) * DMA_PERIOD_CNT);
}
static void eax_adma_free_buf(void)
{
#ifdef CONFIG_SND_SAMSUNG_IOMMU
size_t size = 128 * 1024;
struct iommu_domain *domain = lpass_get_iommu_domain();
dma_free_coherent(di.cpu_dai->dev, size, (void *)di.dma_buf, di.dma_start);
iommu_unmap(domain, 0x48000000, size);
di.dma_start = 0;
#else
size_t size = 480 * DMA_PERIOD_CNT; /* default: 16bit 2ch */
dma_free_coherent(di.cpu_dai->dev, size, (void *)di.dma_buf, di.dma_start);
#endif
memset(di.buf_wr_p, 0, sizeof(unsigned char *) * DMA_PERIOD_CNT);
}
int eax_dma_dai_register(struct snd_soc_dai *dai)
{
spin_lock_init(&di.lock);
mutex_init(&di.mutex);
di.cpu_dai = dai;
di.running = false;
di.params_init = false;
di.params_done = false;
di.prepare_done = false;
spin_lock_init(&mi.lock);
mi.cpu_dai = dai;
mi.running = false;
mi.thread_id = (struct task_struct *)
kthread_run(eax_mixer_kthread, NULL, "eax-mixer");
eax_adma_alloc_buf();
return 0;
}
int eax_dma_dai_unregister(void)
{
mutex_destroy(&di.mutex);
di.cpu_dai = NULL;
di.running = false;
di.params_init = false;
di.params_done = false;
di.prepare_done = false;
mi.cpu_dai = NULL;
mi.running = false;
mi.thread_id = NULL;
eax_adma_free_buf();
return 0;
}
int eax_dma_params_register(struct s3c_dma_params *dma)
{
di.params = dma;
return 0;
}
static void eax_dma_elapsed(int buf_idx)
{
int n;
di.buf_rd_idx = buf_idx;
for (n = 0; n < DMA_PERIOD_CNT; n++) {
if (--buf_idx < 0)
buf_idx += DMA_PERIOD_CNT;
di.buf_fill[buf_idx] = false;
if (buf_idx == di.buf_wr_idx)
break;
}
di.buf_done = true;
if (waitqueue_active(&mixer_buf_wq))
wake_up_interruptible(&mixer_buf_wq);
}
static void eax_adma_buffdone(void *data)
{
dma_addr_t src, dst, pos;
int buf_idx;
if (!di.running || !di.params->ch)
return;
di.params->ops->getposition(di.params->ch, &src, &dst);
pos = src - di.dma_start;
pos /= di.dma_period;
buf_idx = pos;
pos = di.dma_start + (pos * di.dma_period);
if (pos >= di.dma_end)
pos = di.dma_start;
di.dma_pos = pos;
eax_dma_elapsed(buf_idx);
}
static void eax_adma_hw_params(unsigned long dma_period_bytes)
{
struct samsung_dma_req req;
struct samsung_dma_config config;
int n;
mutex_lock(&di.mutex);
if (di.params_done)
goto out;
di.params_done = true;
if (!di.params_init) {
di.params_init = true;
di.params->ops = samsung_dma_get_ops();
req.cap = DMA_CYCLIC;
req.client = di.params->client;
config.direction = DMA_MEM_TO_DEV;
config.width = di.params->dma_size;
config.fifo = di.params->dma_addr;
di.params->ch = di.params->ops->request(di.params->channel,
&req, di.cpu_dai->dev, di.params->ch_name);
if (!di.params->ch) {
pr_err("EAXDMA: Failed to request DMA channel %s\n",
di.params->ch_name);
return;
}
di.params->ops->config(di.params->ch, &config);
}
di.dma_period = dma_period_bytes;
di.dma_pos = di.dma_start;
di.dma_end = di.dma_start + di.dma_period * DMA_PERIOD_CNT;
for (n = 0; n < DMA_PERIOD_CNT; n++) {
di.buf_wr_p[n] = (unsigned char *)di.dma_buf;
di.buf_wr_p[n] += dma_period_bytes * n;
}
pr_info("EAXDMA:DmaAddr=@%x Total=%d PrdSz=%d #Prds=%d dma_area=0x%p\n",
(u32)di.dma_start, (u32)(di.dma_end - di.dma_start),
di.dma_period, DMA_PERIOD_CNT, di.dma_buf);
out:
mutex_unlock(&di.mutex);
}
static void eax_adma_hw_free(void)
{
mutex_lock(&di.mutex);
pr_info("Entered %s ++\n", __func__);
if (di.running || eax_mixer_any_buf_running()) {
pr_info("EAXADMA: some mixer channel is running, (%d), (%d)\n",
di.running, eax_mixer_any_buf_running());
goto out;
}
if (di.params_init && (di.set_params_cnt == 1)) {
pr_info("EAXADMA: release dma channel : %s\n", di.params->ch_name);
di.params_init = false;
if (di.params->ch) {
di.params->ops->flush(di.params->ch);
di.params->ops->release(di.params->ch, di.params->client);
}
while (!waitqueue_active(&mixer_run_wq)) {
if (mi.running)
break;
usleep_range(50, 100);
};
}
di.params_done = false;
di.prepare_done = false;
out:
di.set_params_cnt--;
pr_info("Entered %s --\n", __func__);
mutex_unlock(&di.mutex);
}
static void eax_adma_prepare(unsigned long dma_period_bytes)
{
struct samsung_dma_prep dma_info;
int n;
mutex_lock(&di.mutex);
if (di.prepare_done)
goto out;
if (!di.params_init || !di.params_done) {
pr_err("EAXADMA: hw_params are not set. init = %d, done = %d\n",
di.params_init, di.params_done);
goto out;
}
di.prepare_done = true;
/* zero fill */
mi.buf_fill = false;
di.buf_wr_idx = 0;
di.buf_rd_idx = DMA_PERIOD_CNT;
memset(di.dma_buf, 0, dma_period_bytes * DMA_PERIOD_CNT);
for (n = 0; n < DMA_PERIOD_CNT; n++)
di.buf_fill[n] = true;
/* prepare */
if (di.params->ch)
di.params->ops->flush(di.params->ch);
di.dma_pos = di.dma_start;
/* enqueue */
dma_info.cap = DMA_CYCLIC;
dma_info.direction = DMA_MEM_TO_DEV;
dma_info.fp = eax_adma_buffdone;
dma_info.fp_param = NULL;
dma_info.period = di.dma_period;
dma_info.len = di.dma_period * DMA_PERIOD_CNT;
dma_info.buf = di.dma_pos;
dma_info.infiniteloop = DMA_PERIOD_CNT;
if (di.params->ch)
di.params->ops->prepare(di.params->ch, &dma_info);
out:
mutex_unlock(&di.mutex);
}
static void eax_adma_trigger(bool on)
{
spin_lock(&di.lock);
if (on) {
di.running = on;
lpass_dma_enable(true);
lpass_inc_dram_usage_count();
/* eax always uses dram */
lpass_update_lpclock(LPCLK_CTRLID_LEGACY, true);
if (di.params->ch)
di.params->ops->trigger(di.params->ch);
} else {
if (di.params->ch)
di.params->ops->stop(di.params->ch);
lpass_dma_enable(false);
lpass_dec_dram_usage_count();
di.prepare_done = false;
di.running = on;
lpass_update_lpclock(LPCLK_CTRLID_LEGACY, false);
}
spin_unlock(&di.lock);
}
static inline void eax_dma_xfer(struct runtime_data *prtd,
short *npcm_l, short *npcm_r, int *upcm_l, int *upcm_r)
{
dma_addr_t dma_pos;
#ifndef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
if (eax_dma_is_uhqa(prtd->dma_bytes)) {
#else
if (eax_dma_is_uhqa(prtd->format)) {
#endif
if (!prtd->uhqa_dma_mono || !upcm_l || !upcm_r) {
pr_err("%s : UHQA DMA MONO Pointer is NULL\n", __func__);
return;
}
*upcm_l = *prtd->uhqa_dma_mono++;
*upcm_r = *prtd->uhqa_dma_mono++;
dma_pos = prtd->dma_pos + 8;
if (dma_pos == prtd->dma_end) {
prtd->dma_pos = prtd->dma_start;
prtd->uhqa_dma_mono = (int *)prtd->dma_buf;
} else {
prtd->dma_pos = dma_pos;
}
if (prtd->running &&
((prtd->dma_pos - prtd->dma_start) % prtd->dma_period == 0))
snd_pcm_period_elapsed(prtd->substream);
} else {
if (!prtd->normal_dma_mono || !npcm_l || !npcm_r) {
pr_err("%s : NORMAL DMA MONO Pointer is NULL\n", __func__);
return;
}
*npcm_l = *prtd->normal_dma_mono++;
*npcm_r = *prtd->normal_dma_mono++;
dma_pos = prtd->dma_pos + 4;
if (dma_pos == prtd->dma_end) {
prtd->dma_pos = prtd->dma_start;
prtd->normal_dma_mono = (short *)prtd->dma_buf;
} else {
prtd->dma_pos = dma_pos;
}
if (prtd->dma_period > NMIXBUF_BYTE * 2) {
if (prtd->running &&
((prtd->dma_pos - prtd->dma_start) % prtd->dma_period == 0))
snd_pcm_period_elapsed(prtd->substream);
} else {
if (prtd->running &&
((prtd->dma_pos - prtd->dma_start) % prtd->dma_period == 0))
snd_pcm_period_elapsed(prtd->substream);
}
}
}
static int eax_dma_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct runtime_data *prtd = runtime->private_data;
unsigned long totbytes = params_buffer_bytes(params);
#ifdef EAX_DMA_PCM_DUMP
struct snd_soc_pcm_runtime *rtd = substream->private_data;
#endif
pr_debug("Entered %s\n", __func__);
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
runtime->dma_bytes = totbytes;
spin_lock_irq(&prtd->lock);
prtd->dma_period = params_period_bytes(params);
prtd->dma_start = runtime->dma_addr;
prtd->dma_pos = prtd->dma_start;
prtd->dma_end = prtd->dma_start + totbytes;
prtd->dma_buf = (u32 *)(runtime->dma_area);
prtd->dma_bytes = totbytes;
if (eax_dma_is_uhqa(totbytes)) {
prtd->uhqa_dma_mono = (int *)prtd->dma_buf;
prtd->normal_dma_mono = NULL;
} else {
prtd->normal_dma_mono = (short *)prtd->dma_buf;
prtd->uhqa_dma_mono = NULL;
}
#ifdef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
prtd->format = params_format(params);
prtd->rate = params_rate(params);
#endif
spin_unlock_irq(&prtd->lock);
spin_lock_irq(&mi.lock);
#ifndef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
if (mi.is_uhqa && !eax_dma_is_uhqa(prtd->dma_bytes))
return -EINVAL;
#endif
#ifndef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
if (eax_dma_is_uhqa(prtd->dma_bytes)) {
#else
if (eax_dma_is_uhqa(prtd->format)) {
#endif
mi.is_uhqa = true;
#ifndef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
mi.mixbuf_size = totbytes / DMA_PERIOD_CNT / 8;
mi.mixbuf_byte = totbytes / DMA_PERIOD_CNT;
#else
mi.mixbuf_size = UMIXBUF_SIZE;
mi.mixbuf_byte = UMIXBUF_BYTE;
#endif
} else {
mi.is_uhqa = false;
#ifndef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
mi.mixbuf_size = NMIXBUF_SIZE;
mi.mixbuf_byte = NMIXBUF_BYTE;
#else
if (prtd->rate == 44100) {
if (prtd->dma_period > NMIXBUF_441_110_BYTE) {
mi.mixbuf_size = NMIXBUF_441_441_SIZE;
mi.mixbuf_byte = NMIXBUF_441_441_BYTE;
} else {
mi.mixbuf_size = NMIXBUF_441_110_SIZE;
mi.mixbuf_byte = NMIXBUF_441_110_BYTE;
}
} else {
mi.mixbuf_size = NMIXBUF_SIZE;
mi.mixbuf_byte = NMIXBUF_BYTE;
}
#endif
}
spin_unlock_irq(&mi.lock);
#ifdef EAX_DMA_PCM_DUMP
snprintf(prtd->name, 50, "/data/pcm/%s_%s_%d_%s.raw",
(substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? "P" : "C",
rtd->dai_link->name, dump_count,
(mi.is_uhqa == true) ? "U" : "N");
open_file(prtd, prtd->name);
dump_count++;
#endif
pr_info("EAX:%s:DmaAddr=@%x Total=%d PrdSz=%d #Prds=%d area=0x%p\n",
(substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? "P" : "C",
(u32)prtd->dma_start, (int)runtime->dma_bytes,
params_period_bytes(params), params_periods(params),
runtime->dma_area);
return 0;
}
static int eax_dma_hw_free(struct snd_pcm_substream *substream)
{
#ifdef EAX_DMA_PCM_DUMP
struct snd_pcm_runtime *runtime = substream->runtime;
struct runtime_data *prtd = runtime->private_data;
#endif
pr_debug("Entered %s\n", __func__);
snd_pcm_set_runtime_buffer(substream, NULL);
#ifdef EAX_DMA_PCM_DUMP
close_file(prtd);
#endif
eax_adma_hw_free();
return 0;
}
static int eax_dma_prepare(struct snd_pcm_substream *substream)
{
struct runtime_data *prtd = substream->runtime->private_data;
int ret = 0;
pr_debug("Entered %s\n", __func__);
mutex_lock(&di.mutex);
di.set_params_cnt++;
mutex_unlock(&di.mutex);
prtd->dma_pos = prtd->dma_start;
#ifndef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
if (eax_dma_is_uhqa(prtd->dma_bytes)) {
#else
if (eax_dma_is_uhqa(prtd->format)) {
#endif
prtd->uhqa_dma_mono = (int *)prtd->dma_buf;
prtd->normal_dma_mono = NULL;
#ifndef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
eax_adma_hw_params(prtd->dma_bytes / DMA_PERIOD_CNT);
eax_adma_prepare(prtd->dma_bytes / DMA_PERIOD_CNT);
#else
eax_adma_hw_params(UMIXBUF_BYTE);
eax_adma_prepare(UMIXBUF_BYTE);
#endif
} else {
prtd->normal_dma_mono = (short *)prtd->dma_buf;
prtd->uhqa_dma_mono = NULL;
#ifndef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
eax_adma_hw_params(NMIXBUF_BYTE);
eax_adma_prepare(NMIXBUF_BYTE);
#else
eax_adma_hw_params(mi.mixbuf_byte);
eax_adma_prepare(mi.mixbuf_byte);
#endif
}
return ret;
}
static int eax_dma_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct runtime_data *prtd = substream->runtime->private_data;
int ret = 0;
pr_debug("Entered %s\n", __func__);
spin_lock(&prtd->lock);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
prtd->running = true;
eax_mixer_trigger(true);
break;
case SNDRV_PCM_TRIGGER_STOP:
prtd->running = false;
eax_mixer_trigger(false);
break;
default:
ret = -EINVAL;
break;
}
spin_unlock(&prtd->lock);
return ret;
}
static snd_pcm_uframes_t eax_dma_pointer(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct runtime_data *prtd = runtime->private_data;
unsigned long res;
pr_debug("Entered %s\n", __func__);
res = prtd->dma_pos - prtd->dma_start;
pr_debug("Pointer offset: %lu\n", res);
if (res >= snd_pcm_lib_buffer_bytes(substream)) {
if (res == snd_pcm_lib_buffer_bytes(substream))
res = 0;
}
return bytes_to_frames(substream->runtime, res);
}
static int eax_dma_open(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct runtime_data *prtd;
pr_debug("Entered %s\n", __func__);
snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
snd_soc_set_runtime_hwparams(substream, &dma_hardware);
prtd = kzalloc(sizeof(struct runtime_data), GFP_KERNEL);
if (prtd == NULL)
return -ENOMEM;
spin_lock_init(&prtd->lock);
runtime->private_data = prtd;
prtd->substream = substream;
eax_mixer_add(prtd);
#ifndef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
spin_lock(&mi.lock);
mi.is_uhqa= 0;
spin_unlock(&mi.lock);
#endif
return 0;
}
static int eax_dma_close(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct runtime_data *prtd = runtime->private_data;
pr_debug("Entered %s\n", __func__);
#ifndef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
spin_lock(&mi.lock);
mi.is_uhqa= 0;
spin_unlock(&mi.lock);
eax_mixer_remove(prtd);
#endif
if (!prtd)
pr_debug("dma_close called with prtd == NULL\n");
#ifdef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
eax_mixer_remove(prtd);
#endif
kfree(prtd);
return 0;
}
static int eax_dma_mmap(struct snd_pcm_substream *substream,
struct vm_area_struct *vma)
{
struct snd_pcm_runtime *runtime = substream->runtime;
pr_debug("Entered %s\n", __func__);
return dma_mmap_writecombine(substream->pcm->card->dev, vma,
runtime->dma_area,
runtime->dma_addr,
runtime->dma_bytes);
}
static struct snd_pcm_ops eax_dma_ops = {
.open = eax_dma_open,
.close = eax_dma_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = eax_dma_hw_params,
.hw_free = eax_dma_hw_free,
.prepare = eax_dma_prepare,
.trigger = eax_dma_trigger,
.pointer = eax_dma_pointer,
.mmap = eax_dma_mmap,
};
static int eax_prealloc_buffer(struct snd_pcm *pcm, int stream)
{
struct snd_pcm_substream *substream = pcm->streams[stream].substream;
struct snd_dma_buffer *buf = &substream->dma_buffer;
size_t size = dma_hardware.buffer_bytes_max;
pr_debug("Entered %s\n", __func__);
buf->dev.type = SNDRV_DMA_TYPE_DEV;
buf->dev.dev = pcm->card->dev;
buf->private_data = NULL;
buf->area = dma_alloc_coherent(pcm->card->dev, size,
&buf->addr, GFP_KERNEL);
if (!buf->area)
return -ENOMEM;
buf->bytes = size;
return 0;
}
static u64 eax_dma_mask = DMA_BIT_MASK(32);
static int eax_dma_new(struct snd_soc_pcm_runtime *rtd)
{
struct snd_card *card = rtd->card->snd_card;
struct snd_pcm *pcm = rtd->pcm;
int ret = 0;
pr_debug("Entered %s\n", __func__);
if (!card->dev->dma_mask)
card->dev->dma_mask = &eax_dma_mask;
if (!card->dev->coherent_dma_mask)
card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
#ifdef EAX_DMA_PCM_DUMP
if (!eax_mixer_kobj) {
eax_mixer_kobj = kobject_create_and_add("eax-mixer", NULL);
if (sysfs_create_file(eax_mixer_kobj, &pcm_dump_attribute.attr))
pr_err("%s: failed to create sysfs to control PCM dump\n", __func__);
}
#endif
#ifndef CONFIG_SND_SAMSUNG_SEIREN_OFFLOAD
mi.nmix_buf = kzalloc(NMIXBUF_BYTE, GFP_KERNEL);
#else
mi.nmix_buf = kzalloc(NMIXBUF_441_441_BYTE, GFP_KERNEL);
#endif
if (mi.nmix_buf == NULL)
return -ENOMEM;
mi.umix_buf = kzalloc(UMIXBUF_BYTE, GFP_KERNEL);
if (mi.umix_buf == NULL) {
kfree(mi.nmix_buf);
return -ENOMEM;
}
if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
ret = eax_prealloc_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK);
if (ret) {
kfree(mi.nmix_buf);
kfree(mi.umix_buf);
goto out;
}
}
if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
ret = eax_prealloc_buffer(pcm, SNDRV_PCM_STREAM_CAPTURE);
if (ret) {
kfree(mi.nmix_buf);
kfree(mi.umix_buf);
goto out;
}
}
out:
return ret;
}
static void eax_dma_free(struct snd_pcm *pcm)
{
struct snd_pcm_substream *substream;
struct snd_dma_buffer *buf;
int stream;
pr_debug("Entered %s\n", __func__);
for (stream = 0; stream < 2; stream++) {
substream = pcm->streams[stream].substream;
if (!substream)
continue;
buf = &substream->dma_buffer;
if (!buf->area)
continue;
kfree(buf->area);
buf->area = NULL;
}
}
static struct snd_soc_platform_driver eax_asoc_platform = {
.ops = &eax_dma_ops,
.pcm_new = eax_dma_new,
.pcm_free = eax_dma_free,
};
int eax_asoc_platform_register(struct device *dev)
{
return snd_soc_register_platform(dev, &eax_asoc_platform);
}
EXPORT_SYMBOL_GPL(eax_asoc_platform_register);
void eax_asoc_platform_unregister(struct device *dev)
{
snd_soc_unregister_platform(dev);
}
EXPORT_SYMBOL_GPL(eax_asoc_platform_unregister);
static void eax_mixer_prepare(void)
{
struct buf_info *bi;
short npcm_l, npcm_r;
int nmix_l, nmix_r;
short *nmix_buf;
int upcm_l, upcm_r;
long umix_l, umix_r;
int *umix_buf;
int n;
if (mi.buf_fill || !di.running)
return;
#ifdef EAX_DMA_PCM_DUMP
list_for_each_entry(bi, &buf_list, node) {
if (mi.is_uhqa) {
if (bi->prtd && bi->prtd->running && dump_enabled) {
vfs_write(bi->prtd->filp, (char *)bi->prtd->uhqa_dma_mono,
mi.mixbuf_byte, &bi->prtd->filp->f_pos);
}
} else {
if (bi->prtd && bi->prtd->running && dump_enabled) {
vfs_write(bi->prtd->filp, (char *)bi->prtd->normal_dma_mono,
mi.mixbuf_byte, &bi->prtd->filp->f_pos);
}
}
}
#endif
spin_lock(&mi.lock);
if (mi.is_uhqa) {
umix_buf = mi.umix_buf;
if (!umix_buf) {
spin_unlock(&mi.lock);
return;
}
for (n = 0; n < mi.mixbuf_size; n++) {
umix_l = 0;
umix_r = 0;
list_for_each_entry(bi, &buf_list, node) {
if (bi->prtd && bi->prtd->running) {
eax_dma_xfer(bi->prtd, NULL, NULL,
&upcm_l, &upcm_r);
umix_l += upcm_l;
umix_r += upcm_r;
}
}
/* check 24bit(UHQ) overflow */
if (umix_l > 0x007fffff)
umix_l = 0x007fffff;
else if (umix_l < -0x007fffff)
umix_l = -0x007fffff;
if (umix_r > 0x007fffff)
umix_r = 0x007fffff;
else if (umix_r < -0x007fffff)
umix_r = -0x007fffff;
*umix_buf++ = (int)umix_l;
*umix_buf++ = (int)umix_r;
}
} else {
nmix_buf = mi.nmix_buf;
if (!nmix_buf) {
spin_unlock(&mi.lock);
return;
}
for (n = 0; n < mi.mixbuf_size; n++) {
nmix_l = 0;
nmix_r = 0;
list_for_each_entry(bi, &buf_list, node) {
if (bi->prtd && bi->prtd->running) {
eax_dma_xfer(bi->prtd, &npcm_l, &npcm_r,
NULL, NULL);
nmix_l += npcm_l;
nmix_r += npcm_r;
}
}
if (nmix_l > 0x7fff)
nmix_l = 0x7fff;
else if (nmix_l < -0x7fff)
nmix_l = -0x7fff;
if (nmix_r > 0x7fff)
nmix_r = 0x7fff;
else if (nmix_r < -0x7fff)
nmix_r = -0x7fff;
*nmix_buf++ = (short)nmix_l;
*nmix_buf++ = (short)nmix_r;
}
}
mi.buf_fill = true;
spin_unlock(&mi.lock);
}
static void eax_mixer_write(void)
{
int ret;
spin_lock(&mi.lock);
if (!eax_mixer_any_buf_running() || !mi.running) {
spin_unlock(&mi.lock);
return;
}
spin_unlock(&mi.lock);
if (!di.running && di.buf_fill[DMA_START_THRESHOLD] &&
mi.running) {
if (!di.prepare_done) {
eax_adma_hw_params(mi.mixbuf_byte);
eax_adma_prepare(mi.mixbuf_byte);
}
eax_adma_trigger(true);
}
if (di.buf_fill[di.buf_wr_idx]) {
if (!di.running)
return;
di.buf_done = false;
ret = wait_event_interruptible_timeout(mixer_buf_wq,
di.buf_done, HZ / 50);
if (!ret)
return;
}
spin_lock(&mi.lock);
if (mi.is_uhqa) {
memcpy(di.buf_wr_p[di.buf_wr_idx], mi.umix_buf, mi.mixbuf_byte);
} else {
memcpy(di.buf_wr_p[di.buf_wr_idx], mi.nmix_buf, mi.mixbuf_byte);
}
mi.buf_fill = false;
spin_unlock(&mi.lock);
#ifdef EAX_DMA_PCM_DUMP
if (mi.is_uhqa) {
if (dump_enabled) {
vfs_write(mfilp_uhqa, di.buf_wr_p[di.buf_wr_idx],
mi.mixbuf_byte, &mfilp_uhqa->f_pos);
}
} else {
if (dump_enabled) {
vfs_write(mfilp_normal, di.buf_wr_p[di.buf_wr_idx],
mi.mixbuf_byte, &mfilp_normal->f_pos);
}
}
#endif
di.buf_fill[di.buf_wr_idx] = true;
di.buf_wr_idx++;
if (di.buf_wr_idx == DMA_PERIOD_CNT)
di.buf_wr_idx = 0;
}
static int eax_mixer_kthread(void *arg)
{
struct sched_param param_fifo = {.sched_priority = MAX_RT_PRIO >> 1};
sched_setscheduler_nocheck(current, SCHED_FIFO, &param_fifo);
while (!kthread_should_stop()) {
wait_event_interruptible(mixer_run_wq, mi.running);
eax_mixer_prepare();
eax_mixer_write();
}
return 0;
}
static int eax_mixer_add(struct runtime_data *prtd)
{
struct buf_info *bi;
unsigned long flags;
bi = kzalloc(sizeof(struct buf_info), GFP_KERNEL);
if (!bi) {
pr_err("%s: Memory alloc fails!\n", __func__);
return -ENOMEM;
}
bi->prtd = prtd;
spin_lock_irqsave(&mi.lock, flags);
list_add(&bi->node, &buf_list);
spin_unlock_irqrestore(&mi.lock, flags);
pr_debug("%s: prtd %p added\n", __func__, prtd);
return 0;
}
static int eax_mixer_remove(struct runtime_data *prtd)
{
struct buf_info *bi;
unsigned long flags;
bool node_found = false;
spin_lock_irqsave(&mi.lock, flags);
list_for_each_entry(bi, &buf_list, node) {
if (bi->prtd == prtd) {
node_found = true;
break;
}
}
if (!node_found) {
spin_unlock_irqrestore(&mi.lock, flags);
pr_err("%s: prtd %p not found\n", __func__, prtd);
return -EINVAL;
}
list_del(&bi->node);
kfree(bi);
spin_unlock_irqrestore(&mi.lock, flags);
pr_debug("%s: prtd %p removed\n", __func__, prtd);
return 0;
}
static void eax_mixer_trigger(bool on)
{
if (on) {
mi.running = true;
if (waitqueue_active(&mixer_run_wq))
wake_up_interruptible(&mixer_run_wq);
} else {
if (!eax_mixer_any_buf_running()) {
if (di.running)
eax_adma_trigger(false);
mi.running = false;
}
}
}
MODULE_AUTHOR("Yeongman Seo, <yman.seo@samsung.com>");
MODULE_DESCRIPTION("Samsung ASoC EAX-DMA Driver");
MODULE_LICENSE("GPL");