diff mbox series

[11/21] ASoC: SOF: Intel: Add Intel specific HDA stream operations

Message ID 20181211213029.28801-12-pierre-louis.bossart@linux.intel.com (mailing list archive)
State New, archived
Headers show
Series Sound Open Firmware (SOF) - Intel support | expand

Commit Message

Pierre-Louis Bossart Dec. 11, 2018, 9:30 p.m. UTC
From: Liam Girdwood <liam.r.girdwood@linux.intel.com>

Add support or HDA DSP stream operations for Intel HDA DSPs.

Signed-off-by: Keyon Jie <yang.jie@linux.intel.com>
Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Signed-off-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
---
 sound/soc/sof/intel/hda-stream.c | 661 +++++++++++++++++++++++++++++++
 1 file changed, 661 insertions(+)
 create mode 100644 sound/soc/sof/intel/hda-stream.c
diff mbox series

Patch

diff --git a/sound/soc/sof/intel/hda-stream.c b/sound/soc/sof/intel/hda-stream.c
new file mode 100644
index 000000000000..815a74994bbc
--- /dev/null
+++ b/sound/soc/sof/intel/hda-stream.c
@@ -0,0 +1,661 @@ 
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license.  When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2018 Intel Corporation. All rights reserved.
+//
+// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com>
+//	    Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
+//	    Rander Wang <rander.wang@intel.com>
+//          Keyon Jie <yang.jie@linux.intel.com>
+//
+
+/*
+ * Hardware interface for generic Intel audio DSP HDA IP
+ */
+
+#include <linux/pm_runtime.h>
+#include <sound/hdaudio_ext.h>
+#include <sound/hda_register.h>
+#include <sound/sof.h>
+#include "../ops.h"
+#include "hda.h"
+
+/*
+ * set up one of BDL entries for a stream
+ */
+static int hda_setup_bdle(struct snd_sof_dev *sdev,
+			  struct snd_dma_buffer *dmab,
+			  struct hdac_stream *stream,
+			  struct sof_intel_dsp_bdl **bdlp,
+			  int offset, int size, int ioc)
+{
+	struct hdac_bus *bus = sof_to_bus(sdev);
+	struct sof_intel_dsp_bdl *bdl = *bdlp;
+
+	while (size > 0) {
+		dma_addr_t addr;
+		int chunk;
+
+		if (stream->frags >= HDA_DSP_MAX_BDL_ENTRIES) {
+			dev_err(sdev->dev, "error: stream frags exceeded\n");
+			return -EINVAL;
+		}
+
+		addr = snd_sgbuf_get_addr(dmab, offset);
+		/* program BDL addr */
+		bdl->addr_l = cpu_to_le32(lower_32_bits(addr));
+		bdl->addr_h = cpu_to_le32(upper_32_bits(addr));
+		/* program BDL size */
+		chunk = snd_sgbuf_get_chunk_size(dmab, offset, size);
+		/* one BDLE should not cross 4K boundary */
+		if (bus->align_bdle_4k) {
+			u32 remain = 0x1000 - (offset & 0xfff);
+
+			if (chunk > remain)
+				chunk = remain;
+		}
+		bdl->size = cpu_to_le32(chunk);
+		/* only program IOC when the whole segment is processed */
+		size -= chunk;
+		bdl->ioc = (size || !ioc) ? 0 : cpu_to_le32(0x01);
+		bdl++;
+		stream->frags++;
+		offset += chunk;
+
+		dev_vdbg(sdev->dev, "bdl, frags:%d, chunk size:0x%x;\n",
+			 stream->frags, chunk);
+	}
+
+	*bdlp = bdl;
+	return offset;
+}
+
+/*
+ * set up Buffer Descriptor List (BDL) for host memory transfer
+ * BDL describes the location of the individual buffers and is little endian.
+ */
+int hda_dsp_stream_setup_bdl(struct snd_sof_dev *sdev,
+			     struct snd_dma_buffer *dmab,
+			     struct hdac_stream *stream)
+{
+	struct sof_intel_dsp_bdl *bdl;
+	int i, offset, period_bytes, periods;
+	int remain, ioc;
+
+	period_bytes = stream->period_bytes;
+	dev_dbg(sdev->dev, "period_bytes:0x%x\n", period_bytes);
+	if (!period_bytes)
+		period_bytes = stream->bufsize;
+
+	periods = stream->bufsize / period_bytes;
+
+	dev_dbg(sdev->dev, "periods:%d\n", periods);
+
+	remain = stream->bufsize % period_bytes;
+	if (remain)
+		periods++;
+
+	/* program the initial BDL entries */
+	bdl = (struct sof_intel_dsp_bdl *)stream->bdl.area;
+	offset = 0;
+	stream->frags = 0;
+
+	/*
+	 * set IOC if don't use position IPC
+	 * and period_wakeup needed.
+	 */
+	ioc = sdev->hda->no_ipc_position ?
+	      !stream->no_period_wakeup : 0;
+
+	for (i = 0; i < periods; i++) {
+		if (i == (periods - 1) && remain)
+			/* set the last small entry */
+			offset = hda_setup_bdle(sdev, dmab,
+						stream, &bdl, offset,
+						remain, 0);
+		else
+			offset = hda_setup_bdle(sdev, dmab,
+						stream, &bdl, offset,
+						period_bytes, ioc);
+	}
+
+	return offset;
+}
+
+int hda_dsp_stream_spib_config(struct snd_sof_dev *sdev,
+			       struct hdac_ext_stream *stream,
+			       int enable, u32 size)
+{
+	struct hdac_stream *hstream = &stream->hstream;
+	u32 mask = 0;
+
+	if (!sdev->bar[HDA_DSP_SPIB_BAR]) {
+		dev_err(sdev->dev, "error: address of spib capability is NULL\n");
+		return -EINVAL;
+	}
+
+	mask |= (1 << hstream->index);
+
+	/* enable/disable SPIB for the stream */
+	snd_sof_dsp_update_bits(sdev, HDA_DSP_SPIB_BAR,
+				SOF_HDA_ADSP_REG_CL_SPBFIFO_SPBFCCTL, mask,
+				enable << hstream->index);
+
+	/* set the SPIB value */
+	sof_io_write(sdev, stream->spib_addr, size);
+
+	return 0;
+}
+
+/* get next unused stream */
+struct hdac_ext_stream *
+hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction)
+{
+	struct hdac_bus *bus = sof_to_bus(sdev);
+	struct hdac_ext_stream *stream = NULL;
+	struct hdac_stream *s;
+
+	spin_lock_irq(&bus->reg_lock);
+
+	/* get an unused stream */
+	list_for_each_entry(s, &bus->stream_list, list) {
+		if (s->direction == direction && !s->opened) {
+			s->opened = true;
+			stream = stream_to_hdac_ext_stream(s);
+			break;
+		}
+	}
+
+	spin_unlock_irq(&bus->reg_lock);
+
+	/* stream found ? */
+	if (!stream)
+		dev_err(sdev->dev, "error: no free %s streams\n",
+			direction == SNDRV_PCM_STREAM_PLAYBACK ?
+			"playback" : "capture");
+
+	return stream;
+}
+
+/* free a stream */
+int hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int tag)
+{
+	struct hdac_bus *bus = sof_to_bus(sdev);
+	struct hdac_stream *s;
+
+	spin_lock_irq(&bus->reg_lock);
+
+	/* find used stream */
+	list_for_each_entry(s, &bus->stream_list, list) {
+		if (s->direction == direction &&
+		    s->opened && s->stream_tag == tag) {
+			s->opened = false;
+			spin_unlock_irq(&bus->reg_lock);
+			return 0;
+		}
+	}
+
+	spin_unlock_irq(&bus->reg_lock);
+
+	dev_dbg(sdev->dev, "tag %d not opened!\n", tag);
+	return -ENODEV;
+}
+
+int hda_dsp_stream_trigger(struct snd_sof_dev *sdev,
+			   struct hdac_ext_stream *stream, int cmd)
+{
+	struct hdac_stream *hstream = &stream->hstream;
+	int sd_offset = SOF_STREAM_SD_OFFSET(hstream);
+
+	/* cmd must be for audio stream */
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+	case SNDRV_PCM_TRIGGER_START:
+		snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL,
+					1 << hstream->index,
+					1 << hstream->index);
+
+		snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
+					sd_offset,
+					SOF_HDA_SD_CTL_DMA_START |
+					SOF_HDA_CL_DMA_SD_INT_MASK,
+					SOF_HDA_SD_CTL_DMA_START |
+					SOF_HDA_CL_DMA_SD_INT_MASK);
+
+		hstream->running = true;
+		break;
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_STOP:
+		snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
+					sd_offset,
+					SOF_HDA_SD_CTL_DMA_START |
+					SOF_HDA_CL_DMA_SD_INT_MASK, 0x0);
+
+		snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, sd_offset +
+				  SOF_HDA_ADSP_REG_CL_SD_STS,
+				  SOF_HDA_CL_DMA_SD_INT_MASK);
+
+		hstream->running = false;
+		snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL,
+					1 << hstream->index, 0x0);
+		break;
+	default:
+		dev_err(sdev->dev, "error: unknown command: %d\n", cmd);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/*
+ * prepare for common hdac registers settings, for both code loader
+ * and normal stream.
+ */
+int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev,
+			     struct hdac_ext_stream *stream,
+			     struct snd_dma_buffer *dmab,
+			     struct snd_pcm_hw_params *params)
+{
+	struct hdac_bus *bus = sof_to_bus(sdev);
+	struct hdac_stream *hstream = &stream->hstream;
+	int sd_offset = SOF_STREAM_SD_OFFSET(hstream);
+	int ret, timeout = HDA_DSP_STREAM_RESET_TIMEOUT;
+	u32 val, mask;
+
+	if (!stream) {
+		dev_err(sdev->dev, "error: no stream available\n");
+		return -ENODEV;
+	}
+
+	/* decouple host and link DMA */
+	mask = 0x1 << hstream->index;
+	snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL,
+				mask, mask);
+
+	if (!dmab) {
+		dev_err(sdev->dev, "error: no dma buffer allocated!\n");
+		return -ENODEV;
+	}
+
+	/* clear stream status */
+	snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset,
+				SOF_HDA_CL_DMA_SD_INT_MASK |
+				SOF_HDA_SD_CTL_DMA_START, 0);
+	snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
+				sd_offset + SOF_HDA_ADSP_REG_CL_SD_STS,
+				SOF_HDA_CL_DMA_SD_INT_MASK,
+				SOF_HDA_CL_DMA_SD_INT_MASK);
+
+	/* stream reset */
+	snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, 0x1,
+				0x1);
+	udelay(3);
+	do {
+		val = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR,
+				       sd_offset);
+		if (val & 0x1)
+			break;
+	} while (--timeout);
+	if (timeout == 0) {
+		dev_err(sdev->dev, "error: stream reset failed\n");
+		return -ETIMEDOUT;
+	}
+
+	timeout = HDA_DSP_STREAM_RESET_TIMEOUT;
+	snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, 0x1,
+				0x0);
+
+	/* wait for hardware to report that stream is out of reset */
+	udelay(3);
+	do {
+		val = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR,
+				       sd_offset);
+		if ((val & 0x1) == 0)
+			break;
+	} while (--timeout);
+	if (timeout == 0) {
+		dev_err(sdev->dev, "error: timeout waiting for stream reset\n");
+		return -ETIMEDOUT;
+	}
+
+	if (hstream->posbuf)
+		*hstream->posbuf = 0;
+
+	/* reset BDL address */
+	snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
+			  sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPL,
+			  0x0);
+	snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
+			  sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPU,
+			  0x0);
+
+	/* clear stream status */
+	snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset,
+				SOF_HDA_CL_DMA_SD_INT_MASK |
+				SOF_HDA_SD_CTL_DMA_START, 0);
+	snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
+				sd_offset + SOF_HDA_ADSP_REG_CL_SD_STS,
+				SOF_HDA_CL_DMA_SD_INT_MASK,
+				SOF_HDA_CL_DMA_SD_INT_MASK);
+
+	hstream->frags = 0;
+
+	ret = hda_dsp_stream_setup_bdl(sdev, dmab, hstream);
+	if (ret < 0) {
+		dev_err(sdev->dev, "error: set up of BDL failed\n");
+		return ret;
+	}
+
+	/* set up stream descriptor for DMA */
+	/* program stream tag */
+	snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset,
+				SOF_HDA_CL_SD_CTL_STREAM_TAG_MASK,
+				hstream->stream_tag <<
+				SOF_HDA_CL_SD_CTL_STREAM_TAG_SHIFT);
+
+	/* program cyclic buffer length */
+	snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
+			  sd_offset + SOF_HDA_ADSP_REG_CL_SD_CBL,
+			  hstream->bufsize);
+
+	/* program stream format */
+	snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
+				sd_offset +
+				SOF_HDA_ADSP_REG_CL_SD_FORMAT,
+				0xffff, hstream->format_val);
+
+	/* program last valid index */
+	snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
+				sd_offset + SOF_HDA_ADSP_REG_CL_SD_LVI,
+				0xffff, (hstream->frags - 1));
+
+	/* program BDL address */
+	snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
+			  sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPL,
+			  (u32)hstream->bdl.addr);
+	snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
+			  sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPU,
+			  upper_32_bits(hstream->bdl.addr));
+
+	/* enable position buffer */
+	if (!(snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_ADSP_DPLBASE)
+				& SOF_HDA_ADSP_DPLBASE_ENABLE)) {
+		snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_ADSP_DPUBASE,
+				  upper_32_bits(bus->posbuf.addr));
+		snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_ADSP_DPLBASE,
+				  (u32)bus->posbuf.addr |
+				  SOF_HDA_ADSP_DPLBASE_ENABLE);
+	}
+
+	/* set interrupt enable bits */
+	snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset,
+				SOF_HDA_CL_DMA_SD_INT_MASK,
+				SOF_HDA_CL_DMA_SD_INT_MASK);
+
+	/* read FIFO size */
+	if (hstream->direction == SNDRV_PCM_STREAM_PLAYBACK) {
+		hstream->fifo_size =
+			snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR,
+					 sd_offset +
+					 SOF_HDA_ADSP_REG_CL_SD_FIFOSIZE);
+		hstream->fifo_size &= 0xffff;
+		hstream->fifo_size += 1;
+	} else {
+		hstream->fifo_size = 0;
+	}
+
+	return ret;
+}
+
+irqreturn_t hda_dsp_stream_interrupt(int irq, void *context)
+{
+	struct hdac_bus *bus = (struct hdac_bus *)context;
+	u32 status;
+
+	if (!pm_runtime_active(bus->dev))
+		return IRQ_NONE;
+
+	spin_lock(&bus->reg_lock);
+
+	status = snd_hdac_chip_readl(bus, INTSTS);
+	if (status == 0 || status == 0xffffffff) {
+		spin_unlock(&bus->reg_lock);
+		return IRQ_NONE;
+	}
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
+	/* clear rirb int */
+	status = snd_hdac_chip_readb(bus, RIRBSTS);
+	if (status & RIRB_INT_MASK) {
+		if (status & RIRB_INT_RESPONSE)
+			snd_hdac_bus_update_rirb(bus);
+		snd_hdac_chip_writeb(bus, RIRBSTS, RIRB_INT_MASK);
+	}
+#endif
+
+	spin_unlock(&bus->reg_lock);
+
+	return snd_hdac_chip_readl(bus, INTSTS) ? IRQ_WAKE_THREAD : IRQ_HANDLED;
+}
+
+irqreturn_t hda_dsp_stream_threaded_handler(int irq, void *context)
+{
+	struct hdac_bus *bus = (struct hdac_bus *)context;
+	struct sof_intel_hda_dev *sof_hda = bus_to_sof_hda(bus);
+	struct hdac_stream *s;
+	u32 status = snd_hdac_chip_readl(bus, INTSTS);
+	u32 sd_status;
+
+	/* check streams */
+	list_for_each_entry(s, &bus->stream_list, list) {
+		if (status & (1 << s->index) && s->opened) {
+			sd_status = snd_hdac_stream_readb(s, SD_STS);
+
+			dev_vdbg(bus->dev, "stream %d status 0x%x\n",
+				 s->index, sd_status);
+
+			snd_hdac_stream_writeb(s, SD_STS, SD_INT_MASK);
+
+			if (!s->substream ||
+			    !s->running ||
+			    (sd_status & SOF_HDA_CL_DMA_SD_INT_COMPLETE) == 0)
+				continue;
+
+			/* Inform ALSA only in case not do that with IPC */
+			if (sof_hda->no_ipc_position)
+				snd_pcm_period_elapsed(s->substream);
+
+		}
+	}
+
+	return IRQ_HANDLED;
+}
+
+int hda_dsp_stream_init(struct snd_sof_dev *sdev)
+{
+	struct hdac_bus *bus = sof_to_bus(sdev);
+	struct hdac_ext_stream *stream;
+	struct hdac_stream *hstream;
+	struct pci_dev *pci = sdev->pci;
+	int sd_offset;
+	int i, num_playback, num_capture, num_total, ret;
+	u32 gcap;
+
+	gcap = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_GCAP);
+	dev_dbg(sdev->dev, "hda global caps = 0x%x\n", gcap);
+
+	/* get stream count from GCAP */
+	num_capture = (gcap >> 8) & 0x0f;
+	num_playback = (gcap >> 12) & 0x0f;
+	num_total = num_playback + num_capture;
+
+	dev_dbg(sdev->dev, "detected %d playback and %d capture streams\n",
+		num_playback, num_capture);
+
+	if (num_playback >= SOF_HDA_PLAYBACK_STREAMS) {
+		dev_err(sdev->dev, "error: too many playback streams %d\n",
+			num_playback);
+		return -EINVAL;
+	}
+
+	if (num_capture >= SOF_HDA_CAPTURE_STREAMS) {
+		dev_err(sdev->dev, "error: too many capture streams %d\n",
+			num_playback);
+		return -EINVAL;
+	}
+
+	/* mem alloc for the position buffer */
+	/* TODO: check position buffer update */
+	ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &pci->dev,
+				  SOF_HDA_DPIB_ENTRY_SIZE * num_total,
+				  &bus->posbuf);
+	if (ret < 0) {
+		dev_err(sdev->dev, "error: posbuffer dma alloc failed\n");
+		return -ENOMEM;
+	}
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
+	/* mem alloc for the CORB/RIRB ringbuffers */
+	ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &pci->dev,
+				  PAGE_SIZE, &bus->rb);
+	if (ret < 0) {
+		dev_err(sdev->dev, "error: RB alloc failed\n");
+		return -ENOMEM;
+	}
+#endif
+
+	/* create capture streams */
+	for (i = 0; i < num_capture; i++) {
+
+		stream = devm_kzalloc(sdev->dev, sizeof(*stream), GFP_KERNEL);
+		if (!stream)
+			return -ENOMEM;
+
+		stream->pphc_addr = sdev->bar[HDA_DSP_PP_BAR] +
+			SOF_HDA_PPHC_BASE + SOF_HDA_PPHC_INTERVAL * i;
+
+		stream->pplc_addr = sdev->bar[HDA_DSP_PP_BAR] +
+			SOF_HDA_PPLC_BASE + SOF_HDA_PPLC_MULTI * num_total +
+			SOF_HDA_PPLC_INTERVAL * i;
+
+		/* do we support SPIB */
+		if (sdev->bar[HDA_DSP_SPIB_BAR]) {
+			stream->spib_addr = sdev->bar[HDA_DSP_SPIB_BAR] +
+				SOF_HDA_SPIB_BASE + SOF_HDA_SPIB_INTERVAL * i +
+				SOF_HDA_SPIB_SPIB;
+
+			stream->fifo_addr = sdev->bar[HDA_DSP_SPIB_BAR] +
+				SOF_HDA_SPIB_BASE + SOF_HDA_SPIB_INTERVAL * i +
+				SOF_HDA_SPIB_MAXFIFO;
+		}
+
+		hstream = &stream->hstream;
+		hstream->bus = bus;
+		hstream->sd_int_sta_mask = 1 << i;
+		hstream->index = i;
+		sd_offset = SOF_STREAM_SD_OFFSET(hstream);
+		hstream->sd_addr = sdev->bar[HDA_DSP_HDA_BAR] + sd_offset;
+		hstream->stream_tag = i + 1;
+		hstream->opened = false;
+		hstream->running = false;
+		hstream->direction = SNDRV_PCM_STREAM_CAPTURE;
+
+		/* memory alloc for stream BDL */
+		ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &pci->dev,
+					  HDA_DSP_BDL_SIZE, &hstream->bdl);
+		if (ret < 0) {
+			dev_err(sdev->dev, "error: stream bdl dma alloc failed\n");
+			return -ENOMEM;
+		}
+		hstream->posbuf = (__le32 *)(bus->posbuf.area +
+			(hstream->index) * 8);
+
+		list_add_tail(&hstream->list, &bus->stream_list);
+	}
+
+	/* create playback streams */
+	for (i = num_capture; i < num_total; i++) {
+
+		stream = devm_kzalloc(sdev->dev, sizeof(*stream), GFP_KERNEL);
+		if (!stream)
+			return -ENOMEM;
+
+		/* we always have DSP support */
+		stream->pphc_addr = sdev->bar[HDA_DSP_PP_BAR] +
+			SOF_HDA_PPHC_BASE + SOF_HDA_PPHC_INTERVAL * i;
+
+		stream->pplc_addr = sdev->bar[HDA_DSP_PP_BAR] +
+			SOF_HDA_PPLC_BASE + SOF_HDA_PPLC_MULTI * num_total +
+			SOF_HDA_PPLC_INTERVAL * i;
+
+		/* do we support SPIB */
+		if (sdev->bar[HDA_DSP_SPIB_BAR]) {
+			stream->spib_addr = sdev->bar[HDA_DSP_SPIB_BAR] +
+				SOF_HDA_SPIB_BASE + SOF_HDA_SPIB_INTERVAL * i +
+				SOF_HDA_SPIB_SPIB;
+
+			stream->fifo_addr = sdev->bar[HDA_DSP_SPIB_BAR] +
+				SOF_HDA_SPIB_BASE + SOF_HDA_SPIB_INTERVAL * i +
+				SOF_HDA_SPIB_MAXFIFO;
+		}
+
+		hstream = &stream->hstream;
+		hstream->bus = bus;
+		hstream->sd_int_sta_mask = 1 << i;
+		hstream->index = i;
+		sd_offset = SOF_STREAM_SD_OFFSET(hstream);
+		hstream->sd_addr = sdev->bar[HDA_DSP_HDA_BAR] + sd_offset;
+		hstream->stream_tag = i - num_capture + 1;
+		hstream->opened = false;
+		hstream->running = false;
+		hstream->direction = SNDRV_PCM_STREAM_PLAYBACK;
+
+		/* mem alloc for stream BDL */
+		ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &pci->dev,
+					  HDA_DSP_BDL_SIZE, &hstream->bdl);
+		if (ret < 0) {
+			dev_err(sdev->dev, "error: stream bdl dma alloc failed\n");
+			return -ENOMEM;
+		}
+
+		hstream->posbuf = (__le32 *)(bus->posbuf.area +
+			(hstream->index) * 8);
+
+		list_add_tail(&hstream->list, &bus->stream_list);
+	}
+
+	return 0;
+}
+
+void hda_dsp_stream_free(struct snd_sof_dev *sdev)
+{
+	struct hdac_bus *bus = sof_to_bus(sdev);
+	struct hdac_stream *s, *_s;
+	struct hdac_ext_stream *stream;
+
+	/* free position buffer */
+	if (bus->posbuf.area)
+		snd_dma_free_pages(&bus->posbuf);
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
+	/* free position buffer */
+	if (bus->rb.area)
+		snd_dma_free_pages(&bus->rb);
+#endif
+
+	list_for_each_entry_safe(s, _s, &bus->stream_list, list) {
+		/* TODO: decouple */
+
+		/* free bdl buffer */
+		if (s->bdl.area)
+			snd_dma_free_pages(&s->bdl);
+		list_del(&s->list);
+		stream = stream_to_hdac_ext_stream(s);
+		devm_kfree(sdev->dev, stream);
+	}
+}
+