Skip to content

Commit

Permalink
ASoC: SOF: assign link DMA channel at run-time
Browse files Browse the repository at this point in the history
The recommended HDA HW programming sequence for setting
the DMA format requires that the link DMA and host DMA
channels be coupled before setting the format. This
change means that host DMA or link DMA channels be
reserved even if only one is used.

Statically assigned link DMA channels would mean that
all the corresponding host DMA channels will need to be
reserved, leaving only a few channels available at run-time.
So, the suggestion here is to switch to dynamically assigning
both host DMA channels and link DMA channels are run-time.

The host DMA channel is assigned when the pcm
is opened as before. While choosing the link DMA channel,
if the host DMA channel corresponding to the link DMA channel
is already taken, the proposed method checks to make
sure that the BE is connected to the FE that has been assigned
this host DMA channel. Once the link DMA channel is assigned,
an IPC is sent to the DSP to set the link DMA channel.

The link DMA channel is freed during hw_free() and also in the
SUSPEND trigger callback. It will be re-assigned when hw_params
are set upon resume.

Signed-off-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
  • Loading branch information
ranj063 authored and plbossart committed May 29, 2019
1 parent 353419f commit 64632de
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 148 deletions.
284 changes: 183 additions & 101 deletions sound/soc/sof/intel/hda-dai.c
Expand Up @@ -30,62 +30,84 @@ struct hda_pipe_params {
};

/*
* Unlike GP dma, there is a set of stream registers in hda controller
* to control the link dma channels. Each register controls one link
* dma channel and the relation is fixed. To make sure FW uses correct
* link dma channels, host allocates stream registers and sends the
* corresponding link dma channels to FW to allocate link dma channel
*
* FIXME: this API is abused in the sense that tx_num and rx_num are
* passed as arguments, not returned. We need to find a better way to
* retrieve the stream tag allocated for the link DMA
* This function checks if the host dma channel corresponding
* to the link DMA stream_tag argument is assigned to one
* of the FEs connected to the BE DAI.
*/
static int hda_link_dma_get_channels(struct snd_soc_dai *dai,
unsigned int *tx_num,
unsigned int *tx_slot,
unsigned int *rx_num,
unsigned int *rx_slot)
static bool hda_check_fes(struct snd_soc_pcm_runtime *rtd,
int dir, int stream_tag)
{
struct hdac_bus *bus;
struct hdac_ext_stream *stream;
struct snd_pcm_substream substream;
struct snd_sof_dev *sdev =
snd_soc_component_get_drvdata(dai->component);

bus = sof_to_bus(sdev);

memset(&substream, 0, sizeof(substream));
if (*tx_num == 1) {
substream.stream = SNDRV_PCM_STREAM_PLAYBACK;
stream = snd_hdac_ext_stream_assign(bus, &substream,
HDAC_EXT_STREAM_TYPE_LINK);
if (!stream) {
dev_err(bus->dev, "error: failed to find a free hda ext stream for playback");
return -EBUSY;
}
struct snd_pcm_substream *fe_substream;
struct hdac_stream *fe_hstream;
struct snd_soc_dpcm *dpcm;

for_each_dpcm_fe(rtd, dir, dpcm) {
fe_substream = snd_soc_dpcm_get_substream(dpcm->fe, dir);
fe_hstream = fe_substream->runtime->private_data;
if (fe_hstream->stream_tag == stream_tag)
return true;
}

snd_soc_dai_set_dma_data(dai, &substream, stream);
*tx_slot = hdac_stream(stream)->stream_tag - 1;
return false;
}

static struct hdac_ext_stream *
hda_link_stream_assign(struct hdac_bus *bus,
struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct sof_intel_hda_stream *hda_stream;
struct hdac_ext_stream *res = NULL;
struct hdac_stream *stream = NULL;

dev_dbg(bus->dev, "link dma channel %d for playback", *tx_slot);
int stream_dir = substream->stream;

if (!bus->ppcap) {
dev_err(bus->dev, "stream type not supported\n");
return NULL;
}

if (*rx_num == 1) {
substream.stream = SNDRV_PCM_STREAM_CAPTURE;
stream = snd_hdac_ext_stream_assign(bus, &substream,
HDAC_EXT_STREAM_TYPE_LINK);
if (!stream) {
dev_err(bus->dev, "error: failed to find a free hda ext stream for capture");
return -EBUSY;
list_for_each_entry(stream, &bus->stream_list, list) {
struct hdac_ext_stream *hstream =
stream_to_hdac_ext_stream(stream);
if (stream->direction != substream->stream)
continue;

hda_stream = hstream_to_sof_hda_stream(hstream);

/* check if available */
if (!hstream->link_locked) {
if (stream->opened) {
/*
* check if the stream tag matches the stream
* tag of one of the connected FEs
*/
if (hda_check_fes(rtd, stream_dir,
stream->stream_tag)) {
res = hstream;
break;
}
} else {
res = hstream;
break;
}
}
}

snd_soc_dai_set_dma_data(dai, &substream, stream);
*rx_slot = hdac_stream(stream)->stream_tag - 1;

dev_dbg(bus->dev, "link dma channel %d for capture", *rx_slot);
if (res) {
/*
* Decouple host and link DMA. The decoupled flag
* is updated in snd_hdac_ext_stream_decouple().
*/
if (!res->decoupled)
snd_hdac_ext_stream_decouple(bus, res, true);
spin_lock_irq(&bus->reg_lock);
res->link_locked = 1;
res->link_substream = substream;
spin_unlock_irq(&bus->reg_lock);
}

return 0;
return res;
}

static int hda_link_dma_params(struct hdac_ext_stream *stream,
Expand Down Expand Up @@ -122,6 +144,51 @@ static int hda_link_dma_params(struct hdac_ext_stream *stream,
return 0;
}

/* Send DAI_CONFIG IPC to the DAI that matches the dai_name and direction */
static int hda_link_config_ipc(struct sof_intel_hda_stream *hda_stream,
const char *dai_name, int channel, int dir)
{
struct sof_ipc_dai_config *config;
struct snd_sof_dai *sof_dai;
struct sof_ipc_reply reply;
int ret = 0;

list_for_each_entry(sof_dai, &hda_stream->sdev->dai_list, list) {
if (!sof_dai->cpu_dai_name)
continue;

if (!strcmp(dai_name, sof_dai->cpu_dai_name) &&
dir == sof_dai->comp_dai.direction) {
config = sof_dai->dai_config;

if (!config) {
dev_err(hda_stream->sdev->dev,
"error: no config for DAI %s\n",
sof_dai->name);
return -EINVAL;
}

/* update config with stream tag */
config->hda.link_dma_ch = channel;

/* send IPC */
ret = sof_ipc_tx_message(hda_stream->sdev->ipc,
config->hdr.cmd,
config,
config->hdr.size,
&reply, sizeof(reply));

if (ret < 0)
dev_err(hda_stream->sdev->dev,
"error: failed to set dai config for %s\n",
sof_dai->name);
return ret;
}
}

return -EINVAL;
}

static int hda_link_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
Expand All @@ -135,20 +202,31 @@ static int hda_link_hw_params(struct snd_pcm_substream *substream,
struct hda_pipe_params p_params = {0};
struct hdac_ext_link *link;
int stream_tag;
int ret;

link_dev = snd_soc_dai_get_dma_data(dai, substream);
link_dev = hda_link_stream_assign(bus, substream);
if (!link_dev)
return -EBUSY;

stream_tag = hdac_stream(link_dev)->stream_tag;

hda_stream = hstream_to_sof_hda_stream(link_dev);

/* update the DSP with the new tag */
ret = hda_link_config_ipc(hda_stream, dai->name, stream_tag - 1,
substream->stream);
if (ret < 0)
return ret;

snd_soc_dai_set_dma_data(dai, substream, (void *)link_dev);

hda_stream = container_of(link_dev, struct sof_intel_hda_stream,
hda_stream);
hda_stream->hw_params_upon_resume = 0;

link = snd_hdac_ext_bus_get_link(bus, codec_dai->component->name);
if (!link)
return -EINVAL;

stream_tag = hdac_stream(link_dev)->stream_tag;

/* set the stream tag in the codec dai dma params */
/* set the stream tag in the codec dai dma params */
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
snd_soc_dai_set_tdm_slot(codec_dai, stream_tag, 0, 0, 0);
else
Expand Down Expand Up @@ -181,8 +259,7 @@ static int hda_link_pcm_prepare(struct snd_pcm_substream *substream,
struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream);
int stream = substream->stream;

hda_stream = container_of(link_dev, struct sof_intel_hda_stream,
hda_stream);
hda_stream = hstream_to_sof_hda_stream(link_dev);

/* setup hw_params again only if resuming from system suspend */
if (!hda_stream->hw_params_upon_resume)
Expand All @@ -199,8 +276,24 @@ static int hda_link_pcm_trigger(struct snd_pcm_substream *substream,
{
struct hdac_ext_stream *link_dev =
snd_soc_dai_get_dma_data(dai, substream);
struct sof_intel_hda_stream *hda_stream;
struct snd_soc_pcm_runtime *rtd;
struct hdac_ext_link *link;
struct hdac_stream *hstream;
struct hdac_bus *bus;
int stream_tag;
int ret;

hstream = substream->runtime->private_data;
bus = hstream->bus;
rtd = snd_pcm_substream_chip(substream);

link = snd_hdac_ext_bus_get_link(bus, rtd->codec_dai->component->name);
if (!link)
return -EINVAL;

hda_stream = hstream_to_sof_hda_stream(link_dev);

dev_dbg(dai->dev, "In %s cmd=%d\n", __func__, cmd);
switch (cmd) {
case SNDRV_PCM_TRIGGER_RESUME:
Expand All @@ -217,8 +310,22 @@ static int hda_link_pcm_trigger(struct snd_pcm_substream *substream,
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
snd_hdac_ext_link_stream_start(link_dev);
break;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
case SNDRV_PCM_TRIGGER_SUSPEND:
/*
* clear and release link DMA channel. It will be assigned when
* hw_params is set up again after resume.
*/
ret = hda_link_config_ipc(hda_stream, dai->name,
DMA_CHAN_INVALID, substream->stream);
if (ret < 0)
return ret;
stream_tag = hdac_stream(link_dev)->stream_tag;
snd_hdac_ext_link_clear_stream_id(link, stream_tag);
snd_hdac_ext_stream_release(link_dev,
HDAC_EXT_STREAM_TYPE_LINK);

/* fallthrough */
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
case SNDRV_PCM_TRIGGER_STOP:
snd_hdac_ext_link_stream_clear(link_dev);
break;
Expand All @@ -228,62 +335,38 @@ static int hda_link_pcm_trigger(struct snd_pcm_substream *substream,
return 0;
}

/*
* FIXME: This API is also abused since it's used for two purposes.
* when the substream argument is NULL this function is used for cleanups
* that aren't necessarily required, and called explicitly by handling
* ASoC core structures, which is not recommended.
* This part will be reworked in follow-up patches.
*/
static int hda_link_hw_free(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
const char *name;
unsigned int stream_tag;
struct sof_intel_hda_stream *hda_stream;
struct hdac_bus *bus;
struct hdac_ext_link *link;
struct hdac_stream *hstream;
struct hdac_ext_stream *stream;
struct snd_soc_pcm_runtime *rtd;
struct hdac_ext_stream *link_dev;
struct snd_pcm_substream pcm_substream;

memset(&pcm_substream, 0, sizeof(pcm_substream));
if (substream) {
hstream = substream->runtime->private_data;
bus = hstream->bus;
rtd = snd_pcm_substream_chip(substream);
link_dev = snd_soc_dai_get_dma_data(dai, substream);
snd_hdac_ext_stream_decouple(bus, link_dev, false);
name = rtd->codec_dai->component->name;
link = snd_hdac_ext_bus_get_link(bus, name);
if (!link)
return -EINVAL;

if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
stream_tag = hdac_stream(link_dev)->stream_tag;
snd_hdac_ext_link_clear_stream_id(link, stream_tag);
}
int ret;

link_dev->link_prepared = 0;
} else {
/* release all hda streams when dai link is unloaded */
pcm_substream.stream = SNDRV_PCM_STREAM_PLAYBACK;
stream = snd_soc_dai_get_dma_data(dai, &pcm_substream);
if (stream) {
snd_soc_dai_set_dma_data(dai, &pcm_substream, NULL);
snd_hdac_ext_stream_release(stream,
HDAC_EXT_STREAM_TYPE_LINK);
}
hstream = substream->runtime->private_data;
bus = hstream->bus;
rtd = snd_pcm_substream_chip(substream);
link_dev = snd_soc_dai_get_dma_data(dai, substream);
hda_stream = hstream_to_sof_hda_stream(link_dev);

pcm_substream.stream = SNDRV_PCM_STREAM_CAPTURE;
stream = snd_soc_dai_get_dma_data(dai, &pcm_substream);
if (stream) {
snd_soc_dai_set_dma_data(dai, &pcm_substream, NULL);
snd_hdac_ext_stream_release(stream,
HDAC_EXT_STREAM_TYPE_LINK);
}
}
/* free the link DMA channel in the FW */
ret = hda_link_config_ipc(hda_stream, dai->name, DMA_CHAN_INVALID,
substream->stream);
if (ret < 0)
return ret;

link = snd_hdac_ext_bus_get_link(bus, rtd->codec_dai->component->name);
if (!link)
return -EINVAL;

stream_tag = hdac_stream(link_dev)->stream_tag;
snd_hdac_ext_link_clear_stream_id(link, stream_tag);
snd_hdac_ext_stream_release(link_dev, HDAC_EXT_STREAM_TYPE_LINK);
link_dev->link_prepared = 0;

return 0;
}
Expand All @@ -293,7 +376,6 @@ static const struct snd_soc_dai_ops hda_link_dai_ops = {
.hw_free = hda_link_hw_free,
.trigger = hda_link_pcm_trigger,
.prepare = hda_link_pcm_prepare,
.get_channel_map = hda_link_dma_get_channels,
};
#endif

Expand Down
2 changes: 2 additions & 0 deletions sound/soc/sof/sof-priv.h
Expand Up @@ -60,6 +60,8 @@
(IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) || \
IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST))

#define DMA_CHAN_INVALID 0xFFFFFFFF

struct snd_sof_dev;
struct snd_sof_ipc_msg;
struct snd_sof_ipc;
Expand Down

0 comments on commit 64632de

Please sign in to comment.