2012-08-16 11:40:41 +00:00
|
|
|
/*
|
|
|
|
* soc-compress.c -- ALSA SoC Compress
|
|
|
|
*
|
|
|
|
* Copyright (C) 2012 Intel Corp.
|
|
|
|
*
|
|
|
|
* Authors: Namarta Kohli <namartax.kohli@intel.com>
|
|
|
|
* Ramesh Babu K V <ramesh.babu@linux.intel.com>
|
|
|
|
* Vinod Koul <vinod.koul@linux.intel.com>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
|
|
* under the terms of the GNU General Public License as published by the
|
|
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
|
|
* option) any later version.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/workqueue.h>
|
|
|
|
#include <sound/core.h>
|
|
|
|
#include <sound/compress_params.h>
|
|
|
|
#include <sound/compress_driver.h>
|
|
|
|
#include <sound/soc.h>
|
|
|
|
#include <sound/initval.h>
|
|
|
|
|
|
|
|
static int soc_compr_open(struct snd_compr_stream *cstream)
|
|
|
|
{
|
|
|
|
struct snd_soc_pcm_runtime *rtd = cstream->private_data;
|
|
|
|
struct snd_soc_platform *platform = rtd->platform;
|
|
|
|
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
|
|
|
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
if (platform->driver->compr_ops && platform->driver->compr_ops->open) {
|
|
|
|
ret = platform->driver->compr_ops->open(cstream);
|
|
|
|
if (ret < 0) {
|
|
|
|
pr_err("compress asoc: can't open platform %s\n", platform->name);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rtd->dai_link->compr_ops && rtd->dai_link->compr_ops->startup) {
|
|
|
|
ret = rtd->dai_link->compr_ops->startup(cstream);
|
|
|
|
if (ret < 0) {
|
|
|
|
pr_err("compress asoc: %s startup failed\n", rtd->dai_link->name);
|
|
|
|
goto machine_err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cstream->direction == SND_COMPRESS_PLAYBACK) {
|
|
|
|
cpu_dai->playback_active++;
|
|
|
|
codec_dai->playback_active++;
|
|
|
|
} else {
|
|
|
|
cpu_dai->capture_active++;
|
|
|
|
codec_dai->capture_active++;
|
|
|
|
}
|
|
|
|
|
|
|
|
cpu_dai->active++;
|
|
|
|
codec_dai->active++;
|
|
|
|
rtd->codec->active++;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
machine_err:
|
|
|
|
if (platform->driver->compr_ops && platform->driver->compr_ops->free)
|
|
|
|
platform->driver->compr_ops->free(cstream);
|
|
|
|
out:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int soc_compr_free(struct snd_compr_stream *cstream)
|
|
|
|
{
|
|
|
|
struct snd_soc_pcm_runtime *rtd = cstream->private_data;
|
|
|
|
struct snd_soc_platform *platform = rtd->platform;
|
|
|
|
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
|
|
|
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
|
|
|
struct snd_soc_codec *codec = rtd->codec;
|
|
|
|
|
|
|
|
if (cstream->direction == SND_COMPRESS_PLAYBACK) {
|
|
|
|
cpu_dai->playback_active--;
|
|
|
|
codec_dai->playback_active--;
|
|
|
|
} else {
|
|
|
|
cpu_dai->capture_active--;
|
|
|
|
codec_dai->capture_active--;
|
|
|
|
}
|
|
|
|
|
|
|
|
snd_soc_dai_digital_mute(codec_dai, 1);
|
|
|
|
|
|
|
|
cpu_dai->active--;
|
|
|
|
codec_dai->active--;
|
|
|
|
codec->active--;
|
|
|
|
|
|
|
|
if (!cpu_dai->active)
|
|
|
|
cpu_dai->rate = 0;
|
|
|
|
|
|
|
|
if (!codec_dai->active)
|
|
|
|
codec_dai->rate = 0;
|
|
|
|
|
|
|
|
|
|
|
|
if (rtd->dai_link->compr_ops && rtd->dai_link->compr_ops->shutdown)
|
|
|
|
rtd->dai_link->compr_ops->shutdown(cstream);
|
|
|
|
|
|
|
|
if (platform->driver->compr_ops && platform->driver->compr_ops->free)
|
|
|
|
platform->driver->compr_ops->free(cstream);
|
2012-08-21 06:45:02 +00:00
|
|
|
cpu_dai->runtime = NULL;
|
2012-08-16 11:40:41 +00:00
|
|
|
|
|
|
|
if (cstream->direction == SND_COMPRESS_PLAYBACK) {
|
|
|
|
if (!rtd->pmdown_time || codec->ignore_pmdown_time ||
|
|
|
|
rtd->dai_link->ignore_pmdown_time) {
|
|
|
|
snd_soc_dapm_stream_event(rtd,
|
|
|
|
SNDRV_PCM_STREAM_PLAYBACK,
|
|
|
|
SND_SOC_DAPM_STREAM_STOP);
|
|
|
|
} else
|
ASoC: Prevent pop_wait overwrite
pop_wait is used to determine if a deferred playback close
needs to be cancelled when the a PCM is open or if after
the power-down delay expires it needs to run. pop_wait is
associated with the CODEC DAI, so the CODEC DAI must be
unique. This holds true for most CODECs, except for the
dummy CODEC and its DAI.
In DAI links with non-unique dummy CODECs (e.g. front-ends),
pop_wait can be overwritten by another DAI link using also a
dummy CODEC. Failure to cancel a deferred close can cause
mute due to the DAPM STOP event sent in the deferred work.
One scenario where pop_wait is overwritten and causing mute
is below (where hw:0,0 and hw:0,1 are two front-ends with
default pmdown_time = 5 secs):
aplay /dev/urandom -D hw:0,0 -c 2 -r 48000 -f S16_LE -d 1
sleep 1
aplay /dev/urandom -D hw:0,1 -c 2 -r 48000 -f S16_LE -d 3 &
aplay /dev/urandom -D hw:0,0 -c 2 -r 48000 -f S16_LE
Since CODECs may not be unique, pop_wait is moved to the PCM
runtime structure. Creating separate dummy CODECs for each
DAI link can also solve the problem, but at this point it's
only pop_wait variable in the CODEC DAI that has negative
effects by not being unique.
Signed-off-by: Misael Lopez Cruz <misael.lopez@ti.com>
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
2012-12-13 18:23:05 +00:00
|
|
|
rtd->pop_wait = 1;
|
2012-08-16 11:40:41 +00:00
|
|
|
schedule_delayed_work(&rtd->delayed_work,
|
|
|
|
msecs_to_jiffies(rtd->pmdown_time));
|
|
|
|
} else {
|
|
|
|
/* capture streams can be powered down now */
|
|
|
|
snd_soc_dapm_stream_event(rtd,
|
|
|
|
SNDRV_PCM_STREAM_CAPTURE,
|
|
|
|
SND_SOC_DAPM_STREAM_STOP);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int soc_compr_trigger(struct snd_compr_stream *cstream, int cmd)
|
|
|
|
{
|
|
|
|
|
|
|
|
struct snd_soc_pcm_runtime *rtd = cstream->private_data;
|
|
|
|
struct snd_soc_platform *platform = rtd->platform;
|
|
|
|
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
if (platform->driver->compr_ops && platform->driver->compr_ops->trigger) {
|
|
|
|
ret = platform->driver->compr_ops->trigger(cstream, cmd);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cmd == SNDRV_PCM_TRIGGER_START)
|
|
|
|
snd_soc_dai_digital_mute(codec_dai, 0);
|
|
|
|
else if (cmd == SNDRV_PCM_TRIGGER_STOP)
|
|
|
|
snd_soc_dai_digital_mute(codec_dai, 1);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int soc_compr_set_params(struct snd_compr_stream *cstream,
|
|
|
|
struct snd_compr_params *params)
|
|
|
|
{
|
|
|
|
struct snd_soc_pcm_runtime *rtd = cstream->private_data;
|
|
|
|
struct snd_soc_platform *platform = rtd->platform;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
/* first we call set_params for the platform driver
|
|
|
|
* this should configure the soc side
|
|
|
|
* if the machine has compressed ops then we call that as well
|
|
|
|
* expectation is that platform and machine will configure everything
|
|
|
|
* for this compress path, like configuring pcm port for codec
|
|
|
|
*/
|
|
|
|
if (platform->driver->compr_ops && platform->driver->compr_ops->set_params) {
|
|
|
|
ret = platform->driver->compr_ops->set_params(cstream, params);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rtd->dai_link->compr_ops && rtd->dai_link->compr_ops->set_params) {
|
|
|
|
ret = rtd->dai_link->compr_ops->set_params(cstream);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
snd_soc_dapm_stream_event(rtd, SNDRV_PCM_STREAM_PLAYBACK,
|
|
|
|
SND_SOC_DAPM_STREAM_START);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int soc_compr_get_params(struct snd_compr_stream *cstream,
|
|
|
|
struct snd_codec *params)
|
|
|
|
{
|
|
|
|
struct snd_soc_pcm_runtime *rtd = cstream->private_data;
|
|
|
|
struct snd_soc_platform *platform = rtd->platform;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
if (platform->driver->compr_ops && platform->driver->compr_ops->get_params)
|
|
|
|
ret = platform->driver->compr_ops->get_params(cstream, params);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int soc_compr_get_caps(struct snd_compr_stream *cstream,
|
|
|
|
struct snd_compr_caps *caps)
|
|
|
|
{
|
|
|
|
struct snd_soc_pcm_runtime *rtd = cstream->private_data;
|
|
|
|
struct snd_soc_platform *platform = rtd->platform;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
if (platform->driver->compr_ops && platform->driver->compr_ops->get_caps)
|
|
|
|
ret = platform->driver->compr_ops->get_caps(cstream, caps);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int soc_compr_get_codec_caps(struct snd_compr_stream *cstream,
|
|
|
|
struct snd_compr_codec_caps *codec)
|
|
|
|
{
|
|
|
|
struct snd_soc_pcm_runtime *rtd = cstream->private_data;
|
|
|
|
struct snd_soc_platform *platform = rtd->platform;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
if (platform->driver->compr_ops && platform->driver->compr_ops->get_codec_caps)
|
|
|
|
ret = platform->driver->compr_ops->get_codec_caps(cstream, codec);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int soc_compr_ack(struct snd_compr_stream *cstream, size_t bytes)
|
|
|
|
{
|
|
|
|
struct snd_soc_pcm_runtime *rtd = cstream->private_data;
|
|
|
|
struct snd_soc_platform *platform = rtd->platform;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
if (platform->driver->compr_ops && platform->driver->compr_ops->ack)
|
|
|
|
ret = platform->driver->compr_ops->ack(cstream, bytes);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int soc_compr_pointer(struct snd_compr_stream *cstream,
|
|
|
|
struct snd_compr_tstamp *tstamp)
|
|
|
|
{
|
|
|
|
struct snd_soc_pcm_runtime *rtd = cstream->private_data;
|
|
|
|
struct snd_soc_platform *platform = rtd->platform;
|
|
|
|
|
|
|
|
if (platform->driver->compr_ops && platform->driver->compr_ops->pointer)
|
|
|
|
platform->driver->compr_ops->pointer(cstream, tstamp);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ASoC Compress operations */
|
|
|
|
static struct snd_compr_ops soc_compr_ops = {
|
|
|
|
.open = soc_compr_open,
|
|
|
|
.free = soc_compr_free,
|
|
|
|
.set_params = soc_compr_set_params,
|
|
|
|
.get_params = soc_compr_get_params,
|
|
|
|
.trigger = soc_compr_trigger,
|
|
|
|
.pointer = soc_compr_pointer,
|
|
|
|
.ack = soc_compr_ack,
|
|
|
|
.get_caps = soc_compr_get_caps,
|
|
|
|
.get_codec_caps = soc_compr_get_codec_caps
|
|
|
|
};
|
|
|
|
|
|
|
|
/* create a new compress */
|
|
|
|
int soc_new_compress(struct snd_soc_pcm_runtime *rtd, int num)
|
|
|
|
{
|
|
|
|
struct snd_soc_codec *codec = rtd->codec;
|
|
|
|
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
|
|
|
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
|
|
|
struct snd_compr *compr;
|
|
|
|
char new_name[64];
|
|
|
|
int ret = 0, direction = 0;
|
|
|
|
|
|
|
|
/* check client and interface hw capabilities */
|
|
|
|
snprintf(new_name, sizeof(new_name), "%s %s-%d",
|
|
|
|
rtd->dai_link->stream_name, codec_dai->name, num);
|
|
|
|
direction = SND_COMPRESS_PLAYBACK;
|
|
|
|
compr = kzalloc(sizeof(*compr), GFP_KERNEL);
|
|
|
|
if (compr == NULL) {
|
|
|
|
snd_printk(KERN_ERR "Cannot allocate compr\n");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
compr->ops = &soc_compr_ops;
|
|
|
|
mutex_init(&compr->lock);
|
|
|
|
ret = snd_compress_new(rtd->card->snd_card, num, direction, compr);
|
|
|
|
if (ret < 0) {
|
|
|
|
pr_err("compress asoc: can't create compress for codec %s\n",
|
|
|
|
codec->name);
|
|
|
|
kfree(compr);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
rtd->compr = compr;
|
|
|
|
compr->private_data = rtd;
|
|
|
|
|
|
|
|
printk(KERN_INFO "compress asoc: %s <-> %s mapping ok\n", codec_dai->name,
|
|
|
|
cpu_dai->name);
|
|
|
|
return ret;
|
|
|
|
}
|