summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/sound/hda-mlink.h21
-rw-r--r--sound/soc/sof/intel/hda-ctrl.c11
-rw-r--r--sound/soc/sof/intel/hda-dai-ops.c76
-rw-r--r--sound/soc/sof/intel/hda-dai.c2
-rw-r--r--sound/soc/sof/intel/hda-mlink.c18
-rw-r--r--sound/soc/sof/intel/hda.h26
6 files changed, 146 insertions, 8 deletions
diff --git a/include/sound/hda-mlink.h b/include/sound/hda-mlink.h
index d9789b048c61..ba35f03576b9 100644
--- a/include/sound/hda-mlink.h
+++ b/include/sound/hda-mlink.h
@@ -9,6 +9,22 @@
struct hdac_bus;
struct hdac_ext_link;
+/**
+ * enum hda_bus_ml_link_type - mlink link type, used by SOF link DMA
+ * allocator constraints (see struct sof_intel_hda_dev).
+ *
+ * @HDA_BUS_ML_LINK_HDA: non-alt link, i.e. HDA codec or iDisp
+ * @HDA_BUS_ML_LINK_SDW: alt link, SoundWire
+ * @HDA_BUS_ML_LINK_UAOL: alt link, USB Audio Offload
+ * @HDA_BUS_ML_LINK_OTHER: alt link, SSP or DMIC
+ */
+enum hda_bus_ml_link_type {
+ HDA_BUS_ML_LINK_HDA,
+ HDA_BUS_ML_LINK_SDW,
+ HDA_BUS_ML_LINK_UAOL,
+ HDA_BUS_ML_LINK_OTHER,
+};
+
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_MLINK)
int hda_bus_ml_init(struct hdac_bus *bus);
@@ -53,6 +69,8 @@ void hda_bus_ml_reset_losidv(struct hdac_bus *bus);
int hda_bus_ml_resume(struct hdac_bus *bus);
int hda_bus_ml_suspend(struct hdac_bus *bus);
+enum hda_bus_ml_link_type hda_bus_ml_link_get_type(struct hdac_ext_link *hlink);
+
struct hdac_ext_link *hdac_bus_eml_ssp_get_hlink(struct hdac_bus *bus);
struct hdac_ext_link *hdac_bus_eml_dmic_get_hlink(struct hdac_bus *bus);
struct hdac_ext_link *hdac_bus_eml_sdw_get_hlink(struct hdac_bus *bus);
@@ -172,6 +190,9 @@ static inline void hda_bus_ml_reset_losidv(struct hdac_bus *bus) { }
static inline int hda_bus_ml_resume(struct hdac_bus *bus) { return 0; }
static inline int hda_bus_ml_suspend(struct hdac_bus *bus) { return 0; }
+static inline enum hda_bus_ml_link_type
+hda_bus_ml_link_get_type(struct hdac_ext_link *hlink) { return HDA_BUS_ML_LINK_HDA; }
+
static inline struct hdac_ext_link *
hdac_bus_eml_ssp_get_hlink(struct hdac_bus *bus) { return NULL; }
diff --git a/sound/soc/sof/intel/hda-ctrl.c b/sound/soc/sof/intel/hda-ctrl.c
index a9ead78d3fcb..aeb34310eebd 100644
--- a/sound/soc/sof/intel/hda-ctrl.c
+++ b/sound/soc/sof/intel/hda-ctrl.c
@@ -186,6 +186,7 @@ EXPORT_SYMBOL_NS(hda_dsp_ctrl_clock_power_gating, "SND_SOC_SOF_INTEL_HDA_COMMON"
int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool detect_codec)
{
struct hdac_bus *bus = sof_to_bus(sdev);
+ struct sof_intel_hda_dev *sof_hda = bus_to_sof_hda(bus);
struct hdac_stream *stream;
int sd_offset, ret = 0;
u32 gctl;
@@ -193,6 +194,16 @@ int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool detect_codec)
if (bus->chip_init)
return 0;
+ /*
+ * The controller reset clears the ACE2+ link DMA stream allocation
+ * constraints; reset the masks to reflect this.
+ */
+ memset(sof_hda->link_dma_active_sdw_mask, 0,
+ sizeof(sof_hda->link_dma_active_sdw_mask));
+ memset(sof_hda->link_dma_active_multi_mask, 0,
+ sizeof(sof_hda->link_dma_active_multi_mask));
+ sof_hda->link_dma_out_hda_used_mask = 0;
+
hda_codec_set_codec_wakeup(sdev, true);
hda_dsp_ctrl_misc_clock_gating(sdev, false);
diff --git a/sound/soc/sof/intel/hda-dai-ops.c b/sound/soc/sof/intel/hda-dai-ops.c
index b2c559559962..f0be42048db3 100644
--- a/sound/soc/sof/intel/hda-dai-ops.c
+++ b/sound/soc/sof/intel/hda-dai-ops.c
@@ -20,7 +20,7 @@
/* These ops are only applicable for the HDA DAI's in their current form */
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_LINK)
/*
- * This function checks if the host dma channel corresponding
+ * This function checks if the host DMA stream corresponding
* to the link DMA stream_tag argument is assigned to one
* of the FEs connected to the BE DAI.
*/
@@ -42,23 +42,53 @@ static bool hda_check_fes(struct snd_soc_pcm_runtime *rtd,
}
static struct hdac_ext_stream *
-hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream)
+hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream,
+ enum hda_bus_ml_link_type link_type)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct sof_intel_hda_dev *sof_hda = bus_to_sof_hda(bus);
struct sof_intel_hda_stream *hda_stream;
const struct sof_intel_dsp_desc *chip;
struct snd_sof_dev *sdev;
struct hdac_ext_stream *res = NULL;
struct hdac_stream *hstream = NULL;
-
int stream_dir = substream->stream;
+ bool is_multi = link_type == HDA_BUS_ML_LINK_HDA || link_type == HDA_BUS_ML_LINK_UAOL;
+ bool is_play = stream_dir == SNDRV_PCM_STREAM_PLAYBACK;
+ bool is_sdw = link_type == HDA_BUS_ML_LINK_SDW;
+ bool is_hda = link_type == HDA_BUS_ML_LINK_HDA;
+ u32 concur_block_mask = 0;
+ u32 seq_block_mask = 0;
+ unsigned int stream_idx;
if (!bus->ppcap) {
dev_err(bus->dev, "stream type not supported\n");
return NULL;
}
+ /*
+ * On ACE2+ the link DMA stream allocator must avoid two HW errata,
+ * see the comment on struct sof_intel_hda_dev.
+ *
+ * - Concurrent cross-direction: SoundWire conflicts with HDA, iDisp
+ * and UAOL on the same physical stream index; SSP and DMIC are safe.
+ * - Sequential playback: a stream index previously used by an HDA/iDisp
+ * link cannot drive any non-HDA/iDisp link in the same direction
+ * until the next controller reset.
+ *
+ * The masks are protected by bus->reg_lock; sample them inside the
+ * lock together with the stream walk to keep the decision atomic
+ * with concurrent allocations and releases.
+ */
guard(spinlock_irq)(&bus->reg_lock);
+
+ if (is_sdw)
+ concur_block_mask = sof_hda->link_dma_active_multi_mask[!stream_dir];
+ else if (is_multi)
+ concur_block_mask = sof_hda->link_dma_active_sdw_mask[!stream_dir];
+ if (is_play && !is_hda)
+ seq_block_mask = sof_hda->link_dma_out_hda_used_mask;
+
list_for_each_entry(hstream, &bus->stream_list, list) {
struct hdac_ext_stream *hext_stream =
stream_to_hdac_ext_stream(hstream);
@@ -69,6 +99,12 @@ hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream
sdev = hda_stream->sdev;
chip = get_chip_info(sdev->pdata);
+ stream_idx = hstream->stream_tag - 1;
+
+ /* skip streams blocked by the ACE2+ allocator constraints */
+ if ((concur_block_mask | seq_block_mask) & BIT(stream_idx))
+ continue;
+
/* check if link is available */
if (!hext_stream->link_locked) {
/*
@@ -95,7 +131,7 @@ hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream
/*
* This must be a hostless stream.
- * So reserve the host DMA channel.
+ * So reserve the host DMA stream.
*/
hda_stream->host_reserved = 1;
break;
@@ -109,6 +145,16 @@ hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream
res->link_locked = 1;
res->link_substream = substream;
+
+ stream_idx = res->hstream.stream_tag - 1;
+ if (is_sdw)
+ sof_hda->link_dma_active_sdw_mask[stream_dir] |= BIT(stream_idx);
+ else if (is_multi)
+ sof_hda->link_dma_active_multi_mask[stream_dir] |= BIT(stream_idx);
+
+ /* persistent OUT HDA/iDisp shadow, cleared only on CRST# */
+ if (is_hda && is_play)
+ sof_hda->link_dma_out_hda_used_mask |= BIT(stream_idx);
}
return res;
@@ -143,11 +189,13 @@ static struct hdac_ext_stream *hda_ipc4_get_hext_stream(struct snd_sof_dev *sdev
static struct hdac_ext_stream *hda_assign_hext_stream(struct snd_sof_dev *sdev,
struct snd_soc_dai *cpu_dai,
- struct snd_pcm_substream *substream)
+ struct snd_pcm_substream *substream,
+ struct hdac_ext_link *hlink)
{
struct hdac_ext_stream *hext_stream;
+ enum hda_bus_ml_link_type link_type = hda_bus_ml_link_get_type(hlink);
- hext_stream = hda_link_stream_assign(sof_to_bus(sdev), substream);
+ hext_stream = hda_link_stream_assign(sof_to_bus(sdev), substream, link_type);
if (!hext_stream)
return NULL;
@@ -160,6 +208,22 @@ static void hda_release_hext_stream(struct snd_sof_dev *sdev, struct snd_soc_dai
struct snd_pcm_substream *substream)
{
struct hdac_ext_stream *hext_stream = hda_get_hext_stream(sdev, cpu_dai, substream);
+ struct sof_intel_hda_dev *sof_hda = sdev->pdata->hw_pdata;
+ struct hdac_bus *bus = sof_to_bus(sdev);
+ int dir = substream->stream;
+ unsigned int stream_idx = hext_stream->hstream.stream_tag - 1;
+
+ /*
+ * Drop the stream index from the per-direction active concurrency masks.
+ * The two masks are mutually exclusive for a given stream/direction
+ * (and a stream of the SSP/DMIC kind appears in neither), so a blind
+ * clear of both is safe and lets us avoid having to remember the
+ * link type at allocation time.
+ */
+ scoped_guard(spinlock_irq, &bus->reg_lock) {
+ sof_hda->link_dma_active_sdw_mask[dir] &= ~BIT(stream_idx);
+ sof_hda->link_dma_active_multi_mask[dir] &= ~BIT(stream_idx);
+ }
snd_soc_dai_set_dma_data(cpu_dai, substream, NULL);
snd_hdac_ext_stream_release(hext_stream, HDAC_EXT_STREAM_TYPE_LINK);
diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c
index 15faedeec16d..bb44d4f8a4da 100644
--- a/sound/soc/sof/intel/hda-dai.c
+++ b/sound/soc/sof/intel/hda-dai.c
@@ -188,7 +188,7 @@ static int hda_link_dma_hw_params(struct snd_pcm_substream *substream,
if (!hext_stream) {
if (ops->assign_hext_stream)
- hext_stream = ops->assign_hext_stream(sdev, cpu_dai, substream);
+ hext_stream = ops->assign_hext_stream(sdev, cpu_dai, substream, hlink);
}
if (!hext_stream)
diff --git a/sound/soc/sof/intel/hda-mlink.c b/sound/soc/sof/intel/hda-mlink.c
index e0107cbd06e6..6f02fb5b70ce 100644
--- a/sound/soc/sof/intel/hda-mlink.c
+++ b/sound/soc/sof/intel/hda-mlink.c
@@ -894,6 +894,24 @@ void hda_bus_ml_reset_losidv(struct hdac_bus *bus)
}
EXPORT_SYMBOL_NS(hda_bus_ml_reset_losidv, "SND_SOC_SOF_HDA_MLINK");
+enum hda_bus_ml_link_type hda_bus_ml_link_get_type(struct hdac_ext_link *hlink)
+{
+ struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink);
+
+ if (!h2link->alt)
+ return HDA_BUS_ML_LINK_HDA;
+
+ switch (h2link->elid) {
+ case AZX_REG_ML_LEPTR_ID_SDW:
+ return HDA_BUS_ML_LINK_SDW;
+ case AZX_REG_ML_LEPTR_ID_INTEL_UAOL:
+ return HDA_BUS_ML_LINK_UAOL;
+ default:
+ return HDA_BUS_ML_LINK_OTHER;
+ }
+}
+EXPORT_SYMBOL_NS(hda_bus_ml_link_get_type, "SND_SOC_SOF_HDA_MLINK");
+
int hda_bus_ml_resume(struct hdac_bus *bus)
{
struct hdac_ext_link *hlink;
diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h
index 799e49539b4a..1609589929a1 100644
--- a/sound/soc/sof/intel/hda.h
+++ b/sound/soc/sof/intel/hda.h
@@ -523,6 +523,29 @@ struct sof_intel_hda_dev {
/* the maximum number of streams (playback + capture) supported */
u32 stream_max;
+ /*
+ * ACE2+ link DMA stream allocation constraints (stream index =
+ * stream_tag - 1, shared between input and output directions). All
+ * masks are cleared by hda_dsp_ctrl_init_chip() on controller reset
+ * (CRST#).
+ *
+ * - Concurrent (cross-direction) constraint: a SoundWire stream and
+ * a HDA/iDisp/UAOL stream cannot share a physical stream index
+ * across directions, the resulting LLP/timestamp values are wrong.
+ * link_dma_active_sdw_mask and link_dma_active_multi_mask
+ * (indexed by SNDRV_PCM_STREAM_*) track currently allocated
+ * streams per direction in each of the conflicting groups; SSP
+ * and DMIC do not participate. Bits are cleared on stream release.
+ *
+ * - Sequential (playback only) constraint: once a HDA/iDisp link
+ * has used a playback stream index, that index cannot drive a
+ * non-HDA/iDisp link in the same direction until the next CRST#.
+ * link_dma_out_hda_used_mask records this.
+ */
+ u32 link_dma_active_sdw_mask[SNDRV_PCM_STREAM_LAST + 1];
+ u32 link_dma_active_multi_mask[SNDRV_PCM_STREAM_LAST + 1];
+ u32 link_dma_out_hda_used_mask;
+
/* PM related */
bool l1_disabled;/* is DMI link L1 disabled? */
@@ -1031,7 +1054,8 @@ struct hda_dai_widget_dma_ops {
struct snd_pcm_substream *substream);
struct hdac_ext_stream *(*assign_hext_stream)(struct snd_sof_dev *sdev,
struct snd_soc_dai *cpu_dai,
- struct snd_pcm_substream *substream);
+ struct snd_pcm_substream *substream,
+ struct hdac_ext_link *hlink);
void (*release_hext_stream)(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
struct snd_pcm_substream *substream);
void (*setup_hext_stream)(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_stream,