mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-07 16:58:04 -04:00
966 lines
26 KiB
C
966 lines
26 KiB
C
/* sound/soc/samsung/compr.c
|
|
*
|
|
* ALSA SoC Audio Layer - Samsung Compress platform driver
|
|
*
|
|
* Copyright (c) 2014 Samsung Electronics Co. Ltd.
|
|
* Yeongman Seo <yman.seo@samsung.com>
|
|
* Lee Tae Ho <taeho07.lee@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/of.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/time.h>
|
|
#include <linux/pm_runtime.h>
|
|
|
|
#include <sound/soc.h>
|
|
#include <sound/pcm.h>
|
|
#include <sound/pcm_params.h>
|
|
#include <sound/tlv.h>
|
|
|
|
#include "compr.h"
|
|
#include "./seiren/seiren.h"
|
|
#ifdef CONFIG_SND_ESA_SA_EFFECT
|
|
#include "esa_sa_effect.h"
|
|
#endif
|
|
static struct snd_compr_caps compr_cap = {
|
|
.direction = SND_COMPRESS_PLAYBACK,
|
|
.min_fragment_size = 4 * 1024,
|
|
.max_fragment_size = 32 * 1024,
|
|
.min_fragments = 1,
|
|
.max_fragments = 5,
|
|
.num_codecs = 2,
|
|
.codecs[COMPR_MP3] = SND_AUDIOCODEC_MP3,
|
|
.codecs[COMPR_AAC] = SND_AUDIOCODEC_AAC,
|
|
};
|
|
|
|
struct audio_processor* compr_audio_processor_alloc(seiren_ops ops, void* priv)
|
|
{
|
|
struct audio_processor *ap;
|
|
|
|
ap = kzalloc(sizeof(struct audio_processor), GFP_KERNEL);
|
|
if (!ap)
|
|
return NULL;
|
|
ap->ops = ops;
|
|
ap->priv = priv;
|
|
ap->reg_ack = esa_compr_get_mem() + COMPR_ACK;
|
|
|
|
return ap;
|
|
}
|
|
|
|
#ifdef AUDIO_PERF
|
|
enum CHECK_TIMES {
|
|
OPEN_T = 0x0,
|
|
WRITE_T,
|
|
POINTER_T,
|
|
DRAIN_T,
|
|
ISR_T,
|
|
TOTAL_TIMES,
|
|
};
|
|
#endif
|
|
|
|
struct runtime_data {
|
|
struct snd_compr_stream *cstream;
|
|
struct snd_compr_caps *compr_cap;
|
|
struct snd_compr_params codec_param;
|
|
|
|
spinlock_t lock;
|
|
int state;
|
|
struct snd_soc_dai *cpu_dai;
|
|
struct snd_soc_dai *codec_dai;
|
|
struct snd_pcm_substream substream;
|
|
struct snd_pcm_hw_params hw_params;
|
|
|
|
uint32_t byte_offset;
|
|
u64 copied_total;
|
|
u64 received_total;
|
|
u64 app_pointer;
|
|
void *buffer;
|
|
|
|
#ifdef AUDIO_PERF
|
|
uint32_t start_time[TOTAL_TIMES];
|
|
uint32_t end_time[TOTAL_TIMES];
|
|
u64 total_time[TOTAL_TIMES];
|
|
#endif
|
|
atomic_t start;
|
|
atomic_t eos;
|
|
atomic_t created;
|
|
|
|
wait_queue_head_t flush_wait;
|
|
wait_queue_head_t exit_wait;
|
|
|
|
uint32_t stop_ack;
|
|
uint32_t exit_ack;
|
|
|
|
struct audio_processor* ap;
|
|
};
|
|
|
|
int compr_dai_cmd(struct runtime_data *prtd, int cmd);
|
|
static int compr_event_handler(uint32_t cmd, uint32_t size, void* priv)
|
|
{
|
|
struct runtime_data *prtd = priv;
|
|
struct snd_compr_runtime *runtime = prtd->cstream->runtime;
|
|
u64 bytes_available;
|
|
int ret;
|
|
|
|
pr_debug("%s: event handler cmd(%x)\n", __func__, cmd);
|
|
|
|
#ifdef AUDIO_PERF
|
|
prtd->start_time[ISR_T] = sched_clock();
|
|
#endif
|
|
switch(cmd) {
|
|
case INTR_CREATED:
|
|
pr_debug("%s: offload instance is created\n", __func__);
|
|
break;
|
|
case INTR_DECODED:
|
|
spin_lock(&prtd->lock);
|
|
|
|
/* update copied total bytes */
|
|
prtd->copied_total += size;
|
|
prtd->byte_offset += size;
|
|
if (prtd->byte_offset >= runtime->buffer_size)
|
|
prtd->byte_offset -= runtime->buffer_size;
|
|
|
|
snd_compr_fragment_elapsed(prtd->cstream);
|
|
|
|
if (!atomic_read(&prtd->start) &&
|
|
runtime->state != SNDRV_PCM_STATE_PAUSED) {
|
|
/* Writes must be restarted from _copy() */
|
|
pr_err("%s: write_done received while not started(%d)",
|
|
__func__, runtime->state);
|
|
spin_unlock(&prtd->lock);
|
|
return -EIO;
|
|
}
|
|
|
|
bytes_available = prtd->received_total -
|
|
prtd->copied_total;
|
|
|
|
pr_debug("%s: current free bufsize(%llu)\n", __func__,
|
|
runtime->buffer_size - bytes_available);
|
|
|
|
if (bytes_available < runtime->fragment_size) {
|
|
pr_debug("%s: WRITE_DONE Insufficient data to send.(avail:%llu)\n",
|
|
__func__, bytes_available);
|
|
}
|
|
spin_unlock(&prtd->lock);
|
|
break;
|
|
case INTR_FLUSH:
|
|
prtd->stop_ack = 1;
|
|
wake_up(&prtd->flush_wait);
|
|
break;
|
|
case INTR_PAUSED:
|
|
ret = compr_dai_cmd(prtd, cmd);
|
|
if (ret)
|
|
pr_err("%s: compr_dai_cmd fail(%d)\n", __func__, ret);
|
|
break;
|
|
case INTR_EOS:
|
|
if (atomic_read(&prtd->eos)) {
|
|
if (prtd->copied_total != prtd->received_total)
|
|
pr_err("%s: EOS is not sync!(%llu/%llu)\n", __func__,
|
|
prtd->copied_total, prtd->received_total);
|
|
/* ALSA Framework callback to notify drain complete */
|
|
snd_compr_drain_notify(prtd->cstream);
|
|
atomic_set(&prtd->eos, 0);
|
|
pr_info("%s: DATA_CMD_EOS wake up\n", __func__);
|
|
}
|
|
break;
|
|
case INTR_DESTROY:
|
|
prtd->exit_ack = 1;
|
|
wake_up(&prtd->exit_wait);
|
|
break;
|
|
default:
|
|
pr_err("%s: unknown command(%x)\n", __func__, cmd);
|
|
break;
|
|
}
|
|
#ifdef AUDIO_PERF
|
|
prtd->end_time[ISR_T] = sched_clock();
|
|
prtd->total_time[ISR_T] +=
|
|
prtd->end_time[ISR_T] - prtd->start_time[ISR_T];
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static int compr_config_substream(struct snd_compr_stream *cstream,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = cstream->private_data;
|
|
int ret;
|
|
|
|
pr_debug("%s\n", __func__);
|
|
|
|
substream->pid = get_task_pid(current, PIDTYPE_PID);
|
|
substream->private_data = rtd;
|
|
substream->stream = (cstream->direction == SND_COMPRESS_PLAYBACK) ?
|
|
SNDRV_PCM_STREAM_PLAYBACK : SNDRV_PCM_STREAM_CAPTURE;
|
|
substream->runtime = kzalloc(sizeof(struct snd_pcm_runtime), GFP_KERNEL);
|
|
if (substream->runtime == NULL) {
|
|
ret = -ENOMEM;
|
|
goto config_substream_runtime_err;
|
|
}
|
|
substream->runtime->hw_constraints.rules_num = 0;
|
|
substream->runtime->hw_constraints.rules_all = 1;
|
|
substream->runtime->hw_constraints.rules =
|
|
kzalloc(sizeof(struct snd_pcm_hw_rule), GFP_KERNEL);
|
|
if (substream->runtime->hw_constraints.rules == NULL) {
|
|
ret = -ENOMEM;
|
|
goto config_substream_runtime_rules_err;
|
|
}
|
|
return 0;
|
|
|
|
config_substream_runtime_rules_err:
|
|
kfree(substream->runtime);
|
|
config_substream_runtime_err:
|
|
return ret;
|
|
}
|
|
|
|
static int compr_dai_setup(struct runtime_data *prtd, struct snd_soc_pcm_runtime *rtd)
|
|
{
|
|
struct snd_pcm_substream *substream = &prtd->substream;
|
|
struct snd_soc_dai *cpu_dai = prtd->cpu_dai;
|
|
struct snd_soc_dai *codec_dai = prtd->codec_dai;
|
|
const struct snd_soc_dai_ops *cpu_dai_ops = cpu_dai->driver->ops;
|
|
const struct snd_soc_dai_ops *codec_dai_ops = codec_dai->driver->ops;
|
|
struct snd_pcm_hw_params *params = &prtd->hw_params;
|
|
int ret;
|
|
|
|
if (cpu_dai_ops->startup) {
|
|
ret = (*cpu_dai_ops->startup)(substream, cpu_dai);
|
|
if (ret < 0) {
|
|
dev_err(cpu_dai->dev, "can't open interface"
|
|
" %s: %d\n", cpu_dai->name, ret);
|
|
goto cpu_dai_err;
|
|
}
|
|
}
|
|
|
|
if (codec_dai_ops->startup) {
|
|
ret = (*codec_dai_ops->startup)(substream, codec_dai);
|
|
if (ret < 0) {
|
|
dev_err(codec_dai->dev, "can't open codec"
|
|
" %s: %d\n", codec_dai->name, ret);
|
|
goto codec_dai_err;
|
|
}
|
|
}
|
|
|
|
if (rtd->dai_link->ops->hw_params) {
|
|
ret = (*rtd->dai_link->ops->hw_params)(substream, params);
|
|
if (ret < 0) {
|
|
pr_err("%s: hw_params err(%d)\n", __func__, ret);
|
|
goto hw_params_err;
|
|
}
|
|
}
|
|
|
|
if (codec_dai_ops->hw_params) {
|
|
ret = (*codec_dai_ops->hw_params)(substream, params, codec_dai);
|
|
if (ret < 0) {
|
|
dev_err(codec_dai->dev, "can't set %s hw params:"
|
|
" %d\n", codec_dai->name, ret);
|
|
goto codec_dai_hw_param_err;
|
|
}
|
|
}
|
|
|
|
if (cpu_dai_ops->hw_params) {
|
|
ret = (*cpu_dai_ops->hw_params)(substream, params, cpu_dai);
|
|
if (ret < 0) {
|
|
dev_err(cpu_dai->dev, "can't set %s hw params:"
|
|
" %d\n", cpu_dai->name, ret);
|
|
goto cpu_dai_hw_param_err;
|
|
}
|
|
}
|
|
prtd->cpu_dai->rate = params_rate(params);
|
|
prtd->codec_dai->rate = params_rate(params);
|
|
|
|
return 0;
|
|
cpu_dai_hw_param_err:
|
|
if (cpu_dai_ops->hw_free)
|
|
(*cpu_dai_ops->hw_free)(substream, prtd->cpu_dai);
|
|
codec_dai_hw_param_err:
|
|
if (codec_dai_ops->hw_free)
|
|
(*codec_dai_ops->hw_free)(substream, prtd->codec_dai);
|
|
hw_params_err:
|
|
codec_dai_err:
|
|
if (codec_dai_ops->shutdown)
|
|
(*codec_dai_ops->shutdown)(substream, codec_dai);
|
|
cpu_dai_err:
|
|
if (cpu_dai_ops->shutdown)
|
|
(*cpu_dai_ops->shutdown)(substream, cpu_dai);
|
|
return ret;
|
|
}
|
|
|
|
int compr_dai_cmd(struct runtime_data *prtd, int cmd)
|
|
{
|
|
struct snd_pcm_substream *substream = &prtd->substream;
|
|
struct snd_soc_dai *cpu_dai = prtd->cpu_dai;
|
|
struct snd_soc_dai *codec_dai = prtd->codec_dai;
|
|
const struct snd_soc_dai_ops *cpu_dai_ops = cpu_dai->driver->ops;
|
|
const struct snd_soc_dai_ops *codec_dai_ops = codec_dai->driver->ops;
|
|
int ret;
|
|
|
|
if (codec_dai_ops->trigger) {
|
|
ret = (*codec_dai_ops->trigger)(substream, cmd, codec_dai);
|
|
if (ret < 0) {
|
|
pr_err("%s: error codec_dai trigger(%d)\n", __func__, cmd);
|
|
goto trigger_err;
|
|
}
|
|
}
|
|
|
|
if (cpu_dai_ops->trigger) {
|
|
ret = (*cpu_dai_ops->trigger)(substream, cmd, cpu_dai);
|
|
if (ret < 0) {
|
|
pr_err("%s: error cpu_dai trigger(%d)\n", __func__, cmd);
|
|
goto trigger_err;
|
|
}
|
|
}
|
|
return 0;
|
|
trigger_err:
|
|
return ret;
|
|
}
|
|
|
|
static int compr_dai_prepare(struct runtime_data *prtd)
|
|
{
|
|
struct snd_pcm_substream *substream = &prtd->substream;
|
|
struct snd_soc_dai *cpu_dai = prtd->cpu_dai;
|
|
struct snd_soc_dai *codec_dai = prtd->codec_dai;
|
|
const struct snd_soc_dai_ops *cpu_dai_ops = cpu_dai->driver->ops;
|
|
const struct snd_soc_dai_ops *codec_dai_ops = codec_dai->driver->ops;
|
|
int ret;
|
|
|
|
if (codec_dai_ops->prepare) {
|
|
ret = (*codec_dai_ops->prepare)(substream, codec_dai);
|
|
if (ret < 0) {
|
|
dev_err(codec_dai->dev, "DAI prepare error: %d\n",
|
|
ret);
|
|
goto prepare_err;
|
|
}
|
|
}
|
|
|
|
if (cpu_dai_ops->prepare) {
|
|
ret = (*cpu_dai_ops->prepare)(substream, cpu_dai);
|
|
if (ret < 0) {
|
|
dev_err(codec_dai->dev, "DAI prepare error: %d\n",
|
|
ret);
|
|
goto prepare_err;
|
|
}
|
|
}
|
|
return 0;
|
|
prepare_err:
|
|
return ret;
|
|
}
|
|
|
|
static void compr_config_hw_params(struct snd_pcm_hw_params *params,
|
|
struct snd_compr_params *compr_params)
|
|
{
|
|
u64 fmt;
|
|
int acodec_rate = 48000;
|
|
|
|
pr_debug("%s\n", __func__);
|
|
|
|
fmt = ffs(SNDRV_PCM_FMTBIT_S16_LE) - 1;
|
|
snd_mask_set(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), fmt);
|
|
|
|
hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min = 16;
|
|
hw_param_interval(params, SNDRV_PCM_HW_PARAM_FRAME_BITS)->min = 32;
|
|
hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS)->min = 2;
|
|
|
|
#ifdef CONFIG_SND_ESA_SA_EFFECT
|
|
acodec_rate = esa_compr_get_sample_rate();
|
|
if (!acodec_rate)
|
|
acodec_rate = 48000;
|
|
#endif
|
|
|
|
pr_info("%s input_SR %d PCM_HW_PARAM_RATE %d \n", __func__,
|
|
compr_params->codec.sample_rate, acodec_rate);
|
|
hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE)->min = acodec_rate;
|
|
}
|
|
|
|
static int compr_open(struct snd_compr_stream *cstream)
|
|
{
|
|
struct snd_compr_runtime *runtime = cstream->runtime;
|
|
struct snd_soc_pcm_runtime *rtd = cstream->private_data;
|
|
struct runtime_data *prtd;
|
|
struct snd_pcm_substream *substream;
|
|
int ret;
|
|
|
|
pr_debug("%s\n", __func__);
|
|
|
|
prtd = kzalloc(sizeof(struct runtime_data), GFP_KERNEL);
|
|
if (prtd == NULL)
|
|
return -ENOMEM;
|
|
|
|
spin_lock_init(&prtd->lock);
|
|
|
|
esa_compr_set_state(true);
|
|
prtd->ap = compr_audio_processor_alloc((seiren_ops)compr_event_handler,
|
|
prtd);
|
|
if(!prtd->ap) {
|
|
pr_err("%s: could not allocate memory\n", __func__);
|
|
ret = -ENOMEM;
|
|
goto compr_audio_processor_alloc_err;
|
|
}
|
|
#ifdef CONFIG_SND_ESA_SA_EFFECT
|
|
aud_vol.ap[COMPR_DAI_MULTIMEDIA_1] = prtd->ap;
|
|
#endif
|
|
runtime->private_data = prtd;
|
|
|
|
prtd->cpu_dai = rtd->cpu_dai;
|
|
prtd->codec_dai = rtd->codec_dai;
|
|
|
|
substream = &prtd->substream;
|
|
|
|
ret = compr_config_substream(cstream, substream);
|
|
if (ret) {
|
|
pr_err("%s: could not config substream(%d)\n", __func__, ret);
|
|
goto compr_audio_processor_alloc_err;
|
|
}
|
|
|
|
/* init runtime data */
|
|
prtd->cstream = cstream;
|
|
prtd->byte_offset = 0;
|
|
prtd->app_pointer = 0;
|
|
prtd->copied_total = 0;
|
|
prtd->received_total = 0;
|
|
prtd->compr_cap = &compr_cap;
|
|
prtd->ap->sample_rate = 44100;
|
|
prtd->ap->num_channels = 3; /* stereo channel */
|
|
|
|
atomic_set(&prtd->eos, 0);
|
|
atomic_set(&prtd->start, 0);
|
|
atomic_set(&prtd->created, 0);
|
|
|
|
init_waitqueue_head(&prtd->flush_wait);
|
|
init_waitqueue_head(&prtd->exit_wait);
|
|
#ifdef AUDIO_PERF
|
|
prtd->start_time[OPEN_T] = sched_clock();
|
|
#endif
|
|
esa_compr_open();
|
|
return 0;
|
|
|
|
compr_audio_processor_alloc_err:
|
|
kfree(prtd);
|
|
esa_compr_set_state(false);
|
|
return ret;
|
|
}
|
|
|
|
static int compr_free(struct snd_compr_stream *cstream)
|
|
{
|
|
struct snd_compr_runtime *runtime = cstream->runtime;
|
|
struct runtime_data *prtd = runtime->private_data;
|
|
struct snd_pcm_substream *substream;
|
|
struct snd_soc_dai *cpu_dai;
|
|
struct snd_soc_dai *codec_dai;
|
|
const struct snd_soc_dai_ops *cpu_dai_ops;
|
|
const struct snd_soc_dai_ops *codec_dai_ops;
|
|
unsigned long flags;
|
|
int ret;
|
|
#ifdef AUDIO_PERF
|
|
u64 playback_time, total_time = 0;
|
|
int idx;
|
|
#endif
|
|
pr_debug("%s\n", __func__);
|
|
|
|
if (!prtd) {
|
|
pr_info("compress dai has already freed.\n");
|
|
return 0;
|
|
}
|
|
|
|
substream = &prtd->substream;
|
|
cpu_dai = prtd->cpu_dai;
|
|
codec_dai = prtd->codec_dai;
|
|
cpu_dai_ops = cpu_dai->driver->ops;
|
|
codec_dai_ops = codec_dai->driver->ops;
|
|
|
|
if (atomic_read(&prtd->eos)) {
|
|
/* ALSA Framework callback to notify drain complete */
|
|
snd_compr_drain_notify(cstream);
|
|
atomic_set(&prtd->eos, 0);
|
|
pr_debug("%s Call Drain notify to wakeup\n", __func__);
|
|
}
|
|
|
|
if (atomic_read(&prtd->created)) {
|
|
spin_lock_irqsave(&prtd->lock, flags);
|
|
atomic_set(&prtd->created, 0);
|
|
prtd->exit_ack = 0;
|
|
ret = esa_compr_send_cmd(CMD_COMPR_DESTROY, prtd->ap);
|
|
if (ret) {
|
|
esa_err("%s: can't send CMD_COMPR_DESTROY (%d)\n",
|
|
__func__, ret);
|
|
spin_unlock_irqrestore(&prtd->lock, flags);
|
|
} else {
|
|
spin_unlock_irqrestore(&prtd->lock, flags);
|
|
ret = wait_event_interruptible_timeout(prtd->exit_wait,
|
|
prtd->exit_ack, 1 * HZ);
|
|
if (!ret)
|
|
pr_err("%s: CMD_DESTROY timed out!!!\n", __func__);
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_SND_ESA_SA_EFFECT
|
|
aud_vol.ap[COMPR_DAI_MULTIMEDIA_1] = NULL;
|
|
#endif
|
|
esa_compr_set_state(false);
|
|
/* codec hw_free -> cpu hw_free ->
|
|
cpu shutdown -> codec shutdown */
|
|
if (codec_dai_ops->hw_free)
|
|
(*codec_dai_ops->hw_free)(substream, codec_dai);
|
|
|
|
if (cpu_dai_ops->hw_free)
|
|
(*cpu_dai_ops->hw_free)(substream, cpu_dai);
|
|
|
|
if (cpu_dai_ops->shutdown)
|
|
(*cpu_dai_ops->shutdown)(substream, cpu_dai);
|
|
|
|
if (codec_dai_ops->shutdown)
|
|
(*codec_dai_ops->shutdown)(substream, codec_dai);
|
|
|
|
if (substream->runtime)
|
|
kfree(substream->runtime->hw_constraints.rules);
|
|
kfree(substream->runtime);
|
|
esa_compr_close();
|
|
#ifdef AUDIO_PERF
|
|
prtd->end_time[OPEN_T] = sched_clock();
|
|
playback_time = prtd->end_time[OPEN_T] - prtd->start_time[OPEN_T];
|
|
|
|
for (idx = 0; idx < TOTAL_TIMES; idx++) {
|
|
total_time += prtd->total_time[idx];
|
|
}
|
|
pr_debug("%s: measure the audio waken time : %llu\n", __func__,
|
|
total_time);
|
|
pr_debug("%s: may be the ap sleep time : (%llu/%llu)\n", __func__,
|
|
playback_time - total_time, playback_time);
|
|
#endif
|
|
kfree(prtd->ap);
|
|
kfree(prtd);
|
|
return 0;
|
|
}
|
|
|
|
static int compr_set_params(struct snd_compr_stream *cstream,
|
|
struct snd_compr_params *params)
|
|
{
|
|
struct snd_compr_runtime *runtime = cstream->runtime;
|
|
struct snd_soc_pcm_runtime *rtd = cstream->private_data;
|
|
struct runtime_data *prtd = runtime->private_data;
|
|
unsigned long flags;
|
|
int ret;
|
|
|
|
pr_debug("%s\n", __func__);
|
|
|
|
compr_config_hw_params(&prtd->hw_params, params);
|
|
|
|
pr_debug("%s, cpu_dai name = %s\n",
|
|
__func__, rtd->cpu_dai->name);
|
|
|
|
/* startup -> hw_params */
|
|
ret = compr_dai_setup(prtd, rtd);
|
|
if (ret) {
|
|
pr_err("%s: could not setup compr_dai(%d)\n", __func__, ret);
|
|
return -ENXIO;
|
|
}
|
|
|
|
ret = compr_dai_prepare(prtd);
|
|
if (ret) {
|
|
pr_err("%s: compr_dai_prepare() fail(%d)\n", __func__, ret);
|
|
return -ENXIO;
|
|
}
|
|
|
|
/* COMPR set_params */
|
|
memcpy(&prtd->codec_param, params, sizeof(struct snd_compr_params));
|
|
|
|
prtd->byte_offset = 0;
|
|
prtd->app_pointer = 0;
|
|
prtd->copied_total = 0;
|
|
prtd->ap->buffer_size = runtime->fragments * runtime->fragment_size;
|
|
prtd->ap->num_channels = prtd->codec_param.codec.ch_in;
|
|
prtd->ap->sample_rate = prtd->codec_param.codec.sample_rate;
|
|
|
|
if (prtd->ap->sample_rate == 0 ||
|
|
prtd->ap->num_channels == 0) {
|
|
pr_err("%s: invalid parameters: sample(%ld), ch(%ld)\n",
|
|
__func__, prtd->ap->sample_rate,
|
|
prtd->ap->num_channels);
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (prtd->codec_param.codec.id) {
|
|
case SND_AUDIOCODEC_MP3:
|
|
prtd->ap->codec_id = COMPR_MP3;
|
|
break;
|
|
case SND_AUDIOCODEC_AAC:
|
|
prtd->ap->codec_id = COMPR_AAC;
|
|
break;
|
|
default:
|
|
pr_err("%s: unknown codec id %d\n", __func__,
|
|
prtd->codec_param.codec.id);
|
|
break;
|
|
}
|
|
|
|
ret = esa_compr_set_param(prtd->ap, (uint8_t**)&prtd->buffer);
|
|
if (ret) {
|
|
pr_err("%s: esa_compr_set_param fail(%d)\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
spin_lock_irqsave(&prtd->lock, flags);
|
|
atomic_set(&prtd->created, 1);
|
|
spin_unlock_irqrestore(&prtd->lock, flags);
|
|
|
|
pr_info("%s: sample rate:%ld, channels:%ld\n", __func__,
|
|
prtd->ap->sample_rate, prtd->ap->num_channels);
|
|
return 0;
|
|
}
|
|
|
|
static int compr_set_metadata(struct snd_compr_stream *cstream,
|
|
struct snd_compr_metadata *metadata)
|
|
{
|
|
pr_debug("%s\n", __func__);
|
|
|
|
if (!metadata || !cstream)
|
|
return -EINVAL;
|
|
|
|
if (metadata->key == SNDRV_COMPRESS_ENCODER_PADDING) {
|
|
pr_debug("%s, got encoder padding %u", __func__, metadata->value[0]);
|
|
} else if (metadata->key == SNDRV_COMPRESS_ENCODER_DELAY) {
|
|
pr_debug("%s, got encoder delay %u", __func__, metadata->value[0]);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int compr_trigger(struct snd_compr_stream *cstream, int cmd)
|
|
{
|
|
struct snd_compr_runtime *runtime = cstream->runtime;
|
|
struct runtime_data *prtd = runtime->private_data;
|
|
unsigned long flags;
|
|
int ret;
|
|
|
|
pr_debug("%s: trigger cmd(%d)\n", __func__, cmd);
|
|
|
|
/* platform -> codec -> cpu */
|
|
if (cstream->direction != SND_COMPRESS_PLAYBACK) {
|
|
pr_err("%s: Unsupported stream type\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (cmd) {
|
|
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
|
pr_info("%s: SNDRV_PCM_TRIGGER_PAUSE_PUSH\n", __func__);
|
|
|
|
spin_lock_irqsave(&prtd->lock, flags);
|
|
ret = esa_compr_send_cmd(CMD_COMPR_PAUSE, prtd->ap);
|
|
if (ret) {
|
|
pr_err("%s: pause cmd failed(%d)\n", __func__,
|
|
ret);
|
|
spin_unlock_irqrestore(&prtd->lock, flags);
|
|
return ret;
|
|
}
|
|
spin_unlock_irqrestore(&prtd->lock, flags);
|
|
atomic_set(&prtd->start, 0);
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
pr_info("%s: SNDRV_PCM_TRIGGER_STOP\n", __func__);
|
|
|
|
spin_lock_irqsave(&prtd->lock, flags);
|
|
|
|
if (atomic_read(&prtd->eos)) {
|
|
/* ALSA Framework callback to notify drain complete */
|
|
snd_compr_drain_notify(cstream);
|
|
atomic_set(&prtd->eos, 0);
|
|
pr_debug("%s: interrupt drain and eos wait queues", __func__);
|
|
}
|
|
|
|
pr_debug("CMD_STOP\n");
|
|
prtd->stop_ack = 0;
|
|
ret = esa_compr_send_cmd(CMD_COMPR_STOP, prtd->ap);
|
|
if (ret) {
|
|
pr_err("%s: stop cmd failed (%d)\n",
|
|
__func__, ret);
|
|
spin_unlock_irqrestore(&prtd->lock, flags);
|
|
return ret;
|
|
}
|
|
spin_unlock_irqrestore(&prtd->lock, flags);
|
|
|
|
ret = wait_event_interruptible_timeout(prtd->flush_wait,
|
|
prtd->stop_ack, 1 * HZ);
|
|
if (!ret) {
|
|
pr_err("CMD_STOP cmd timeout(%d)\n", ret);
|
|
ret = -ETIMEDOUT;
|
|
} else
|
|
ret = 0;
|
|
|
|
ret = compr_dai_cmd(prtd, cmd);
|
|
if (ret) {
|
|
pr_err("%s: compr_dai_cmd fail(%d)\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
atomic_set(&prtd->start, 0);
|
|
|
|
/* reset */
|
|
prtd->stop_ack = 0;
|
|
prtd->byte_offset = 0;
|
|
prtd->app_pointer = 0;
|
|
prtd->copied_total = 0;
|
|
prtd->received_total = 0;
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
|
if (SNDRV_PCM_TRIGGER_START == cmd)
|
|
pr_info("%s: SNDRV_PCM_TRIGGER_START\n", __func__);
|
|
else if (SNDRV_PCM_TRIGGER_PAUSE_RELEASE == cmd)
|
|
pr_info("%s: SNDRV_PCM_TRIGGER_PAUSE_RELEASE\n", __func__);
|
|
|
|
ret = compr_dai_cmd(prtd, cmd);
|
|
if (ret) {
|
|
pr_err("%s: compr_dai_cmd fail(%d)\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
atomic_set(&prtd->start, 1);
|
|
spin_lock_irqsave(&prtd->lock, flags);
|
|
ret = esa_compr_send_cmd(CMD_COMPR_START, prtd->ap);
|
|
if (ret) {
|
|
pr_err("%s: start cmd failed\n", __func__);
|
|
spin_unlock_irqrestore(&prtd->lock, flags);
|
|
return ret;
|
|
}
|
|
spin_unlock_irqrestore(&prtd->lock, flags);
|
|
break;
|
|
case SND_COMPR_TRIGGER_NEXT_TRACK:
|
|
pr_info("%s: SND_COMPR_TRIGGER_NEXT_TRACK\n", __func__);
|
|
break;
|
|
case SND_COMPR_TRIGGER_PARTIAL_DRAIN:
|
|
pr_info("%s: SND_COMPR_TRIGGER_PARTIAL_DRAIN\n", __func__);
|
|
case SND_COMPR_TRIGGER_DRAIN:
|
|
if (SND_COMPR_TRIGGER_DRAIN == cmd)
|
|
pr_info("%s: SND_COMPR_TRIGGER_DRAIN\n", __func__);
|
|
/* Make sure all the data is sent to F/W before sending EOS */
|
|
spin_lock_irqsave(&prtd->lock, flags);
|
|
#ifdef AUDIO_PERF
|
|
prtd->start_time[DRAIN_T] = sched_clock();
|
|
#endif
|
|
if (!atomic_read(&prtd->start)) {
|
|
pr_err("%s: stream is not in started state\n",
|
|
__func__);
|
|
ret = -EPERM;
|
|
spin_unlock_irqrestore(&prtd->lock, flags);
|
|
break;
|
|
}
|
|
|
|
atomic_set(&prtd->eos, 1);
|
|
pr_debug("%s: CMD_EOS\n", __func__);
|
|
ret = esa_compr_send_cmd(CMD_COMPR_EOS, prtd->ap);
|
|
if (ret) {
|
|
pr_err("%s: can't send eos (%d)\n", __func__, ret);
|
|
spin_unlock_irqrestore(&prtd->lock, flags);
|
|
return ret;
|
|
}
|
|
spin_unlock_irqrestore(&prtd->lock, flags);
|
|
#ifdef AUDIO_PERF
|
|
prtd->end_time[DRAIN_T] = sched_clock();
|
|
prtd->total_time[DRAIN_T] +=
|
|
prtd->end_time[DRAIN_T] - prtd->start_time[DRAIN_T];
|
|
#endif
|
|
pr_info("%s: Out of %s Drain", __func__,
|
|
(cmd == SND_COMPR_TRIGGER_DRAIN ? "Full" : "Partial"));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int compr_pointer(struct snd_compr_stream *cstream,
|
|
struct snd_compr_tstamp *tstamp)
|
|
{
|
|
struct snd_compr_runtime *runtime = cstream->runtime;
|
|
struct runtime_data *prtd = runtime->private_data;
|
|
struct snd_compr_tstamp timestamp;
|
|
unsigned long flags;
|
|
int pcm_size, bytes_available;
|
|
int num_channel;
|
|
|
|
pr_debug("%s\n", __func__);
|
|
#ifdef AUDIO_PERF
|
|
prtd->start_time[POINTER_T] = sched_clock();
|
|
#endif
|
|
memset(×tamp, 0x0, sizeof(struct snd_compr_tstamp));
|
|
|
|
spin_lock_irqsave(&prtd->lock, flags);
|
|
timestamp.sampling_rate = prtd->ap->sample_rate;
|
|
timestamp.byte_offset = prtd->byte_offset;
|
|
timestamp.copied_total = prtd->copied_total;
|
|
pcm_size = esa_compr_pcm_size();
|
|
|
|
/* set the number of channels */
|
|
if (prtd->ap->num_channels == 1 || prtd->ap->num_channels == 2)
|
|
num_channel = 1;
|
|
else if (prtd->ap->num_channels == 3)
|
|
num_channel = 2;
|
|
else
|
|
num_channel = 2;
|
|
spin_unlock_irqrestore(&prtd->lock, flags);
|
|
|
|
if (pcm_size) {
|
|
bytes_available = prtd->received_total - prtd->copied_total;
|
|
|
|
timestamp.pcm_io_frames = (snd_pcm_uframes_t)div64_u64(pcm_size,
|
|
2 * num_channel);
|
|
pr_debug("%s: pcm_size(%u), frame_count(%u), copied_total(%llu), \
|
|
free_size(%llu)\n", __func__, pcm_size,
|
|
timestamp.pcm_io_frames, prtd->copied_total,
|
|
runtime->buffer_size - bytes_available);
|
|
|
|
}
|
|
memcpy(tstamp, ×tamp, sizeof(struct snd_compr_tstamp));
|
|
#ifdef AUDIO_PERF
|
|
prtd->end_time[POINTER_T] = sched_clock();
|
|
prtd->total_time[POINTER_T] +=
|
|
prtd->end_time[POINTER_T] - prtd->start_time[POINTER_T];
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static int compr_copy(struct snd_compr_stream *cstream, char __user* buf, size_t bytes)
|
|
{
|
|
struct snd_compr_runtime *runtime = cstream->runtime;
|
|
struct runtime_data *prtd = runtime->private_data;
|
|
u64 bytes_available;
|
|
unsigned long flags;
|
|
unsigned long copy;
|
|
void *dstn;
|
|
int ret;
|
|
|
|
pr_debug("%s\n", __func__);
|
|
#ifdef AUDIO_PERF
|
|
prtd->start_time[WRITE_T] = sched_clock();
|
|
#endif
|
|
if (!prtd->buffer) {
|
|
pr_err("%s: Buffer is not allocated yet ??", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* check the free area */
|
|
if (bytes <= 0) {
|
|
pr_err("%s: Buffer size is zero(%ld)\n", __func__, bytes);
|
|
return 0;
|
|
}
|
|
|
|
pr_debug("copying %ld at %lld\n",
|
|
(unsigned long)bytes, prtd->app_pointer);
|
|
|
|
dstn = prtd->buffer + prtd->app_pointer;
|
|
if (bytes < runtime->buffer_size - prtd->app_pointer) {
|
|
if (copy_from_user(dstn, buf, bytes))
|
|
return -EFAULT;
|
|
prtd->app_pointer += bytes;
|
|
} else {
|
|
copy = runtime->buffer_size - prtd->app_pointer;
|
|
if (copy_from_user(dstn, buf, copy))
|
|
return -EFAULT;
|
|
if (copy_from_user(prtd->buffer, buf + copy, bytes - copy))
|
|
return -EFAULT;
|
|
prtd->app_pointer = bytes - copy;
|
|
}
|
|
|
|
/*
|
|
* since the available bytes fits fragment_size, copy the data right away
|
|
*/
|
|
spin_lock_irqsave(&prtd->lock, flags);
|
|
prtd->received_total += bytes;
|
|
bytes_available = prtd->received_total - prtd->copied_total;
|
|
spin_unlock_irqrestore(&prtd->lock, flags);
|
|
|
|
pr_debug("%s: bytes_received(%llu), free_size(%llu)\n",
|
|
__func__, prtd->received_total,
|
|
runtime->buffer_size - bytes_available);
|
|
|
|
/* get the bytes to write */
|
|
if (bytes_available > 0) {
|
|
//TODO: issue: unknown mp3 fragment should be checked
|
|
#if 0
|
|
u64 pointer = div64_u64(prtd->copied_total,
|
|
runtime->buffer_size);
|
|
pointer = prtd->copied_total - (pointer * runtime->buffer_size);
|
|
pr_info("%s: bytes to write offset in buffer(%d/%llu)\n",
|
|
__func__, prtd->byte_offset, prtd->app_pointer);
|
|
pr_info("%s: [%2llx][%2llx][%2llx][%2llx] (%d)\n",
|
|
__func__, (u64)(((char*)prtd->buffer)[pointer]),
|
|
(u64)(((char*)prtd->buffer)[pointer + 1]),
|
|
(u64)(((char*)prtd->buffer)[pointer + 2]),
|
|
(u64)(((char*)prtd->buffer)[pointer + 3]),
|
|
bytes);
|
|
#endif
|
|
pr_debug("%s: needs to be copied to the buffer = %llu\n",
|
|
__func__, bytes_available);
|
|
|
|
ret = esa_compr_send_buffer(bytes, prtd->ap);
|
|
if (ret) {
|
|
pr_err("%s: can't send buffer %ld bytes (%d)",
|
|
__func__, bytes, ret);
|
|
return -EFAULT;
|
|
}
|
|
}
|
|
#ifdef AUDIO_PERF
|
|
prtd->end_time[WRITE_T] = sched_clock();
|
|
prtd->total_time[WRITE_T] +=
|
|
prtd->end_time[WRITE_T] - prtd->start_time[WRITE_T];
|
|
#endif
|
|
return bytes;
|
|
}
|
|
|
|
static int compr_get_caps(struct snd_compr_stream *cstream,
|
|
struct snd_compr_caps *caps)
|
|
{
|
|
struct snd_compr_runtime *runtime = cstream->runtime;
|
|
struct runtime_data *prtd = runtime->private_data;
|
|
|
|
pr_debug("%s\n", __func__);
|
|
|
|
memcpy(caps, prtd->compr_cap, sizeof(struct snd_compr_caps));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int compr_get_codec_caps(struct snd_compr_stream *cstream,
|
|
struct snd_compr_codec_caps *codec)
|
|
{
|
|
pr_debug("%s\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct snd_compr_ops compr_ops = {
|
|
.open = compr_open,
|
|
.free = compr_free,
|
|
.set_params = compr_set_params,
|
|
.set_metadata = compr_set_metadata,
|
|
.trigger = compr_trigger,
|
|
.pointer = compr_pointer,
|
|
.copy = compr_copy,
|
|
.get_caps = compr_get_caps,
|
|
.get_codec_caps = compr_get_codec_caps,
|
|
};
|
|
|
|
static struct snd_soc_platform_driver samsung_compr_platform = {
|
|
.compr_ops = &compr_ops,
|
|
};
|
|
|
|
int asoc_compr_platform_register(struct device *dev)
|
|
{
|
|
return snd_soc_register_platform(dev, &samsung_compr_platform);
|
|
}
|
|
EXPORT_SYMBOL_GPL(asoc_compr_platform_register);
|
|
|
|
void asoc_compr_platform_unregister(struct device *dev)
|
|
{
|
|
snd_soc_unregister_platform(dev);
|
|
}
|
|
EXPORT_SYMBOL_GPL(asoc_compr_platform_unregister);
|
|
|
|
MODULE_AUTHOR("Yeongman Seo, <yman.seo@samsung.com>");
|
|
MODULE_AUTHOR("Taeho Lee <taeho07.lee@samsung.com>");
|
|
MODULE_DESCRIPTION("Samsung ASoC Compress Driver");
|
|
MODULE_LICENSE("GPL");
|