diff --git a/include/sound/sof/dai-intel.h b/include/sound/sof/dai-intel.h index 71f9b34d624427..e6ebd3ebac3c2c 100644 --- a/include/sound/sof/dai-intel.h +++ b/include/sound/sof/dai-intel.h @@ -82,8 +82,7 @@ struct sof_ipc_dai_ssp_params { /* HDA Configuration Request - SOF_IPC_DAI_HDA_CONFIG */ struct sof_ipc_dai_hda_params { - struct sof_ipc_hdr hdr; - /* TODO */ + uint32_t link_dma_ch; } __packed; /* DMIC Configuration Request - SOF_IPC_DAI_DMIC_CONFIG */ diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c index dd68cbf00014dd..b0f90ce4604c46 100644 --- a/sound/soc/sof/intel/hda-dai.c +++ b/sound/soc/sof/intel/hda-dai.c @@ -33,7 +33,59 @@ struct hda_pipe_params { unsigned int link_bps; }; -/* TODO: add hda dai params in tplg, and configure this in topology parsing */ +/* 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 the correct + * link dma channel, host allocates stream register and sends the + * corresponding link dma channel to FW to allocate link dma channel + */ +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) +{ + struct hdac_bus *bus; + struct hdac_ext_stream *stream; + struct snd_pcm_substream substream = {0}; + struct snd_sof_dev *sdev = + snd_soc_component_get_drvdata(dai->component); + + bus = sof_to_bus(sdev); + + 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, "failed to find a free hda ext stream for playback"); + return -EBUSY; + } + + snd_soc_dai_set_dma_data(dai, &substream, (void *)stream); + *tx_slot = hdac_stream(stream)->stream_tag - 1; + + dev_dbg(bus->dev, "link dma channel %d for playback", *tx_slot); + } + + 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, "failed to find a free hda ext stream for capture"); + return -EBUSY; + } + + snd_soc_dai_set_dma_data(dai, &substream, (void *)stream); + *rx_slot = hdac_stream(stream)->stream_tag - 1; + + dev_dbg(bus->dev, "link dma channel %d for capture", *rx_slot); + } + + return 0; +} + static int hda_link_dma_params(struct hdac_ext_stream *stream, struct hda_pipe_params *params) { @@ -78,12 +130,7 @@ static int hda_link_hw_params(struct snd_pcm_substream *substream, struct hdac_ext_link *link; int stream_tag; - link_dev = snd_hdac_ext_stream_assign(bus, substream, - HDAC_EXT_STREAM_TYPE_LINK); - if (!link_dev) - return -EBUSY; - - snd_soc_dai_set_dma_data(dai, substream, (void *)link_dev); + link_dev = snd_soc_dai_get_dma_data(dai, substream); link = snd_hdac_ext_bus_get_link(bus, codec_dai->component->name); if (!link) @@ -147,22 +194,50 @@ static int hda_link_pcm_trigger(struct snd_pcm_substream *substream, static int hda_link_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct hdac_stream *hstream = substream->runtime->private_data; - struct hdac_bus *bus = hstream->bus; - struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); - struct hdac_ext_stream *link_dev = - snd_soc_dai_get_dma_data(dai, substream); + const char *name; + unsigned int stream_tag; + struct hdac_bus *bus; struct hdac_ext_link *link; - - link = snd_hdac_ext_bus_get_link(bus, rtd->codec_dai->component->name); - if (!link) - return -EINVAL; - - snd_hdac_ext_link_clear_stream_id(link, - hdac_stream(link_dev)->stream_tag); - snd_hdac_ext_stream_release(link_dev, HDAC_EXT_STREAM_TYPE_LINK); - - link_dev->link_prepared = 0; + struct snd_sof_dev *sdev; + 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 = {0}; + + 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); + name = rtd->codec_dai->component->name; + link = snd_hdac_ext_bus_get_link(bus, name); + if (!link) + return -EINVAL; + + stream_tag = hdac_stream(link_dev)->stream_tag; + snd_hdac_ext_link_clear_stream_id(link, stream_tag); + + link_dev->link_prepared = 0; + } else { + /* release all hda streams when dai link is unloaded */ + sdev = snd_soc_component_get_drvdata(dai->component); + 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); + } + + 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); + } + } return 0; } @@ -171,6 +246,7 @@ static const struct snd_soc_dai_ops hda_link_dai_ops = { .hw_params = hda_link_hw_params, .hw_free = hda_link_hw_free, .trigger = hda_link_pcm_trigger, + .get_channel_map = hda_link_dma_get_channels, }; #endif diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index 9af530bb45dc9a..3016b9322c5fbe 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -2028,6 +2028,49 @@ static int sof_link_dmic_load(struct snd_soc_component *scomp, int index, return ret; } +static int sof_link_hda_process(struct snd_sof_dev *sdev, + struct snd_soc_dai_link *link, + struct sof_ipc_dai_config *config, + int slot, + int direction) +{ + struct sof_ipc_reply reply; + u32 size = sizeof(*config); + struct snd_sof_dai *dai; + int found = 0; + int ret; + + /* for hda link, playback and capture are supported by different + * dai in FW. Here get the dai_index of each dai and send config + * to FW. In FW, each dai sets config by dai_index + */ + list_for_each_entry(dai, &sdev->dai_list, list) { + if (!dai->name) + continue; + + if (strcmp(link->name, dai->name) == 0 && + dai->comp_dai.direction == direction) { + config->dai_index = dai->comp_dai.dai_index; + found = 1; + break; + } + } + + if (!found) { + dev_err(sdev->dev, "failed to find dai %s", link->name); + return -EINVAL; + } + + config->hda.link_dma_ch = slot; + + /* send message to DSP */ + ret = sof_ipc_tx_message(sdev->ipc, + config->hdr.cmd, config, size, &reply, + sizeof(reply)); + + return ret; +} + static int sof_link_hda_load(struct snd_soc_component *scomp, int index, struct snd_soc_dai_link *link, struct snd_soc_tplg_link_config *cfg, @@ -2035,9 +2078,15 @@ static int sof_link_hda_load(struct snd_soc_component *scomp, int index, struct sof_ipc_dai_config *config) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_dai_link_component dai_component = {0}; struct snd_soc_tplg_private *private = &cfg->priv; - struct sof_ipc_reply reply; + struct snd_soc_dai *dai; + u32 size = sizeof(*config); + u32 tx_num = 0; + u32 tx_slot = 0; + u32 rx_num = 0; + u32 rx_slot = 0; int ret; /* init IPC */ @@ -2054,18 +2103,50 @@ static int sof_link_hda_load(struct snd_soc_component *scomp, int index, return ret; } - dev_dbg(sdev->dev, "tplg: config HDA%d fmt 0x%x\n", - config->dai_index, config->format); + dai_component.dai_name = link->cpu_dai_name; + dai = snd_soc_find_dai(&dai_component); + if (!dai) { + dev_err(sdev->dev, "failed to find dai %s", dai->name); + return -EINVAL; + } - /* send message to DSP */ - ret = sof_ipc_tx_message(sdev->ipc, - config->hdr.cmd, config, size, &reply, - sizeof(reply)); + if (link->dpcm_playback) + tx_num = 1; - if (ret < 0) - dev_err(sdev->dev, "error: failed to set DAI config for HDA%d\n", + if (link->dpcm_capture) + rx_num = 1; + + ret = snd_soc_dai_get_channel_map(dai, &tx_num, &tx_slot, + &rx_num, &rx_slot); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to get dma channel for HDA%d\n", config->dai_index); + return ret; + } + + /* for hda link, playback and capture are supported by different + * dai in FW. Here send dai config according to capability of dai. + */ + if (link->dpcm_playback) { + ret = sof_link_hda_process(sdev, link, config, tx_slot, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to set DAI config for playback dai HDA%d\n", + config->dai_index); + + return ret; + } + } + + if (link->dpcm_capture) { + ret = sof_link_hda_process(sdev, link, config, rx_slot, + SNDRV_PCM_STREAM_CAPTURE); + if (ret < 0) + dev_err(sdev->dev, "error: failed to set DAI config for capture dai HDA%d\n", + config->dai_index); + } + return ret; } @@ -2152,10 +2233,68 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, return 0; } +static int sof_link_hda_unload(struct snd_sof_dev *sdev, + struct snd_soc_dai_link *link) +{ + struct snd_soc_dai_link_component dai_component = {0}; + struct snd_soc_dai *dai; + int ret = 0; + + dai_component.dai_name = link->cpu_dai_name; + dai = snd_soc_find_dai(&dai_component); + if (!dai) { + dev_err(sdev->dev, "failed to find dai %s", dai->name); + return -EINVAL; + } + + if (dai->driver->ops->hw_free) + ret = dai->driver->ops->hw_free(NULL, dai); + if (ret < 0) + dev_err(sdev->dev, "error: failed to free hda resource for %s\n", + link->name); + + return ret; +} + static int sof_link_unload(struct snd_soc_component *scomp, struct snd_soc_dobj *dobj) { - return 0; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_dai_link *link = + container_of(dobj, struct snd_soc_dai_link, dobj); + + struct snd_sof_dai *sof_dai = NULL; + int ret = 0; + + list_for_each_entry(sof_dai, &sdev->dai_list, list) { + if (!sof_dai->name) + continue; + + if (strcmp(link->name, sof_dai->name) == 0) + break; + } + + if (!sof_dai) { + dev_err(sdev->dev, "failed to find dai %s", link->name); + return -EINVAL; + } + + switch (sof_dai->dai_config.type) { + case SOF_DAI_INTEL_SSP: + case SOF_DAI_INTEL_DMIC: + /* no resource needs to be released for SSP and DMIC */ + break; + case SOF_DAI_INTEL_HDA: + ret = sof_link_hda_unload(sdev, link); + break; + default: + dev_err(sdev->dev, "error: invalid DAI type %d\n", + sof_dai->dai_config.type); + ret = -EINVAL; + break; + } + + return ret; } /* bind PCM ID to host component ID */