[Sound-open-firmware] [PATCH 6/8] dma: hda dma: Add HDA host and link DMA driver

Keyon Jie yang.jie at linux.intel.com
Thu Feb 8 13:48:11 CET 2018


Add a host and link DMA driver for Intel HDA DMA gateway.

Signed-off-by: Liam Girdwood <liam.r.girdwood at linux.intel.com>
Signed-off-by: Keyon Jie <yang.jie at linux.intel.com>
---
 configure.ac                       |   2 +
 src/drivers/Makefile.am            |  10 +
 src/drivers/hda-dma.c              | 437 +++++++++++++++++++++++++++++++++++++
 src/platform/apollolake/platform.c |  25 ++-
 4 files changed, 466 insertions(+), 8 deletions(-)
 create mode 100644 src/drivers/hda-dma.c

diff --git a/configure.ac b/configure.ac
index 05a3bee..ab2e845 100644
--- a/configure.ac
+++ b/configure.ac
@@ -142,6 +142,7 @@ case "$with_platform" in
 
 	AC_DEFINE([CONFIG_APOLLOLAKE], [1], [Configure for Apololake])
 	AC_DEFINE([CONFIG_IRQ_MAP], [1], [Configure IRQ maps])
+	AC_DEFINE([CONFIG_DMA_GW], [1], [Configure DMA Gateway])
     ;;
     haswell*)
 
@@ -193,6 +194,7 @@ case "$with_platform" in
 
 	AC_DEFINE([CONFIG_CANNONLAKE], [1], [Configure for Cannonlake])
 	AC_DEFINE([CONFIG_IRQ_MAP], [1], [Configure IRQ maps])
+	AC_DEFINE([CONFIG_DMA_GW], [1], [Configure DMA Gateway])
     ;;
     *)
 	if test "$ARCH" = "host"; then
diff --git a/src/drivers/Makefile.am b/src/drivers/Makefile.am
index 8c507fa..0e8d84e 100644
--- a/src/drivers/Makefile.am
+++ b/src/drivers/Makefile.am
@@ -4,6 +4,16 @@ libdrivers_a_SOURCES = \
 	ssp.c \
 	dw-dma.c
 
+if BUILD_APOLLOLAKE
+libdrivers_a_SOURCES += \
+	hda-dma.c
+endif
+
+if BUILD_CANNONLAKE
+libdrivers_a_SOURCES += \
+	hda-dma.c
+endif
+
 libdrivers_a_CFLAGS = \
 	$(ARCH_CFLAGS) \
 	$(REEF_INCDIR) \
diff --git a/src/drivers/hda-dma.c b/src/drivers/hda-dma.c
new file mode 100644
index 0000000..a143528
--- /dev/null
+++ b/src/drivers/hda-dma.c
@@ -0,0 +1,437 @@
+/*
+ * Copyright (c) 2018, Intel Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of the Intel Corporation nor the
+ *     names of its contributors may be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Author: Keyon Jie <yang.jie at linux.intel.com>
+ *         Liam Girdwood <liam.r.girdwood at linux.intel.com>
+ */
+
+#include <stdint.h>
+#include <stddef.h>
+#include <errno.h>
+#include <reef/reef.h>
+#include <reef/lock.h>
+#include <reef/list.h>
+#include <reef/stream.h>
+#include <reef/alloc.h>
+#include <reef/trace.h>
+#include <reef/dma.h>
+#include <reef/io.h>
+#include <reef/ipc.h>
+#include <reef/wait.h>
+#include <platform/dma.h>
+#include <arch/cache.h>
+#include <uapi/ipc.h>
+
+#define trace_host(__e)	trace_event(TRACE_CLASS_HOST, __e)
+#define tracev_host(__e)	tracev_event(TRACE_CLASS_HOST, __e)
+#define trace_host_error(__e)	trace_error(TRACE_CLASS_HOST, __e)
+
+/* Gateway Stream Registers */
+#define DGCS		0x00
+#define DGBBA		0x04
+#define DGBS		0x08
+#define DGBFPI		0x0c /* firmware need to update this when DGCS.FWCB=1 */
+#define DGBRP		0x10 /* Read Only, read pointer */
+#define DGBWP		0x14 /* Read Only, write pointer */
+#define DGBSP		0x18
+#define DGMBS		0x1c
+#define DGLLPI		0x24
+#define DGLPIBI		0x28
+
+/* DGCS */
+#define DGCS_SCS	(1 << 31)
+#define DGCS_GEN	(1 << 26)
+#define DGCS_FWCB      (1 << 23)
+#define DGCS_BSC	(1 << 11)
+#define DGCS_BF		(1 << 9) /* buffer full */
+#define DGCS_BNE	(1 << 8) /* buffer not empty */
+#define DGCS_FIFORDY	(1 << 5) /* enable FIFO */
+
+/* DGBBA */
+#define DGBBA_MASK	0xffff80
+
+/* DGBS */
+#define DGBS_MASK	0xfffff0
+
+#define HDA_DMA_MAX_CHANS		8
+
+struct hda_chan_data {
+	uint32_t stream_id;
+	uint32_t status;
+	uint32_t desc_count;
+	uint32_t desc_avail;
+	uint32_t direction;
+};
+
+struct dma_pdata {
+	struct dma *dma;
+	int32_t num_channels;
+	struct hda_chan_data chan[HDA_DMA_MAX_CHANS];
+};
+
+static inline uint32_t host_dma_reg_read(struct dma *dma, uint32_t chan,
+	uint32_t reg)
+{
+	return io_reg_read(dma_chan_base(dma, chan) + reg);
+}
+
+static inline void host_dma_reg_write(struct dma *dma, uint32_t chan,
+	uint32_t reg, uint32_t value)
+{
+	io_reg_write(dma_chan_base(dma, chan) + reg, value);
+}
+
+static inline void hda_update_bits(struct dma *dma, uint32_t chan,
+	uint32_t reg, uint32_t mask, uint32_t value)
+{
+	io_reg_update_bits(dma_chan_base(dma, chan) + reg,  mask, value);
+}
+
+/* notify DMA to copy bytes */
+static int hda_dma_copy(struct dma *dma, int channel, int bytes)
+{
+	trace_host("GwU");
+
+	/* reset BSC before start next copy */
+	hda_update_bits(dma, channel, DGCS, DGCS_BSC, DGCS_BSC);
+
+	/*
+	 * set BFPI to let host gateway knows we have read size,
+	 * which will trigger next copy start.
+	 */
+	host_dma_reg_write(dma, channel, DGBFPI, bytes);
+
+	host_dma_reg_write(dma, channel, DGLLPI, bytes);
+	host_dma_reg_write(dma, channel, DGLPIBI, bytes);
+
+	return 0;
+}
+
+/* acquire the specific DMA channel */
+static int hda_dma_channel_get(struct dma *dma, int channel)
+{
+	struct dma_pdata *p = dma_get_drvdata(dma);
+	uint32_t flags;
+
+	spin_lock_irq(&dma->lock, flags);
+
+	trace_host("Dgt");
+
+	/* use channel if it's free */
+	if (p->chan[channel].status == COMP_STATE_INIT) {
+		p->chan[channel].status = COMP_STATE_READY;
+
+		/* return channel */
+		spin_unlock_irq(&dma->lock, flags);
+		return channel;
+	}
+
+	/* DMAC has no free channels */
+	spin_unlock_irq(&dma->lock, flags);
+	trace_host_error("eG0");
+	return -ENODEV;
+}
+
+/* channel must not be running when this is called */
+static void hda_dma_channel_put_unlocked(struct dma *dma, int channel)
+{
+	struct dma_pdata *p = dma_get_drvdata(dma);
+
+	/* set new state */
+	p->chan[channel].status = COMP_STATE_INIT;
+}
+
+/* channel must not be running when this is called */
+static void hda_dma_channel_put(struct dma *dma, int channel)
+{
+	uint32_t flags;
+
+	spin_lock_irq(&dma->lock, flags);
+	hda_dma_channel_put_unlocked(dma, channel);
+	spin_unlock_irq(&dma->lock, flags);
+}
+
+static int hda_dma_start(struct dma *dma, int channel)
+{
+	struct dma_pdata *p = dma_get_drvdata(dma);
+	uint32_t flags;
+	uint32_t dgcs;
+	int ret = 0;
+
+	spin_lock_irq(&dma->lock, flags);
+
+	trace_host("DEn");
+
+	/* is channel idle, disabled and ready ? */
+	dgcs = host_dma_reg_read(dma, channel, DGCS);
+	if (p->chan[channel].status != COMP_STATE_PREPARE ||
+	    (dgcs & DGCS_GEN)) {
+		ret = -EBUSY;
+		trace_host_error("eS0");
+		trace_value(dgcs);
+		trace_value(p->chan[channel].status);
+		goto out;
+	}
+
+	/* enable the channel */
+	hda_update_bits(dma, channel, DGCS, DGCS_GEN | DGCS_FIFORDY,
+			DGCS_GEN | DGCS_FIFORDY);
+
+	/* full buffer is copied at startup */
+	p->chan[channel].desc_avail = p->chan[channel].desc_count;
+out:
+	spin_unlock_irq(&dma->lock, flags);
+	return ret;
+}
+
+static int hda_dma_release(struct dma *dma, int channel)
+{
+	struct dma_pdata *p = dma_get_drvdata(dma);
+	uint32_t flags;
+
+	spin_lock_irq(&dma->lock, flags);
+
+	trace_host("Dpr");
+
+	/* resume and reload DMA */
+	p->chan[channel].status = COMP_STATE_ACTIVE;
+
+	spin_unlock_irq(&dma->lock, flags);
+	return 0;
+}
+
+static int hda_dma_pause(struct dma *dma, int channel)
+{
+	struct dma_pdata *p = dma_get_drvdata(dma);
+	uint32_t flags;
+
+	spin_lock_irq(&dma->lock, flags);
+
+	trace_host("Dpa");
+
+	if (p->chan[channel].status != COMP_STATE_ACTIVE)
+		goto out;
+
+	/* pause the channel */
+	p->chan[channel].status = COMP_STATE_PAUSED;
+
+out:
+	spin_unlock_irq(&dma->lock, flags);
+	return 0;
+}
+
+static int hda_dma_stop(struct dma *dma, int channel)
+{
+	struct dma_pdata *p = dma_get_drvdata(dma);
+	uint32_t flags;
+
+	spin_lock_irq(&dma->lock, flags);
+
+	trace_host("DDi");
+
+	/* disable the channel */
+	hda_update_bits(dma, channel, DGCS, DGCS_GEN | DGCS_FIFORDY, 0);
+	p->chan[channel].status = COMP_STATE_PREPARE;
+
+	spin_unlock_irq(&dma->lock, flags);
+	return 0;
+}
+
+/* fill in "status" with current DMA channel state and position */
+static int hda_dma_status(struct dma *dma, int channel,
+	struct dma_chan_status *status, uint8_t direction)
+{
+	struct dma_pdata *p = dma_get_drvdata(dma);
+
+	status->state = p->chan[channel].status;
+	status->r_pos =  host_dma_reg_read(dma, channel, DGBRP);
+	status->w_pos = host_dma_reg_read(dma, channel, DGBWP);
+	status->timestamp = timer_get_system(platform_timer);
+
+	return 0;
+}
+
+/* set the DMA channel configuration, source/target address, buffer sizes */
+static int hda_dma_set_config(struct dma *dma, int channel,
+	struct dma_sg_config *config)
+{
+	struct dma_pdata *p = dma_get_drvdata(dma);
+	struct list_item *plist;
+	struct dma_sg_elem *sg_elem;
+	uint32_t buffer_addr = 0;
+	uint32_t period_bytes = 0;
+	uint32_t buffer_bytes = 0;
+	uint32_t desc_count = 0;
+	uint32_t flags;
+	uint32_t addr;
+	uint32_t dgcs;
+	int ret = 0;
+
+	spin_lock_irq(&dma->lock, flags);
+
+	trace_host("Dsc");
+
+	/* get number of SG elems */
+	list_for_item(plist, &config->elem_list)
+		desc_count++;
+
+	if (desc_count == 0) {
+		trace_host_error("eD1");
+		ret = -EINVAL;
+		goto out;
+	}
+
+	/* default channel config */
+	p->chan[channel].direction = config->direction;
+	p->chan[channel].desc_count = desc_count;
+
+
+	/* validate - HDA only supports continuous elems of same size  */
+	list_for_item(plist, &config->elem_list) {
+		sg_elem = container_of(plist, struct dma_sg_elem, list);
+
+		if (config->direction == SOF_IPC_STREAM_PLAYBACK)
+			addr = sg_elem->dest;
+		else
+			addr = sg_elem->src;
+
+		/* make sure elem is continuous */
+		if (buffer_addr && (buffer_addr + buffer_bytes) != addr) {
+			trace_host_error("eD2");
+			ret = -EINVAL;
+			goto out;
+		}
+
+		/* make sure period_bytes are constant */
+		if (period_bytes && period_bytes != sg_elem->size) {
+			trace_host_error("eD3");
+			ret = -EINVAL;
+			goto out;
+		}
+
+		/* update counters */
+		period_bytes = sg_elem->size;
+		buffer_bytes += period_bytes;
+
+		if (buffer_addr == 0)
+			buffer_addr = addr;
+	}
+
+	/* firmware control buffer */
+	dgcs = DGCS_FWCB;
+	/* set DGCS.SCS bit to 0 for 32 bit container */
+	if ((config->direction == SOF_IPC_STREAM_PLAYBACK &&
+	    config->dest_width <= 16) ||
+	    (config->direction == SOF_IPC_STREAM_CAPTURE &&
+	    config->src_width <= 16))
+		dgcs |= DGCS_SCS;
+
+	/* init channel in HW */
+	host_dma_reg_write(dma, channel, DGCS, dgcs);
+	host_dma_reg_write(dma, channel, DGBBA,  buffer_addr);
+	host_dma_reg_write(dma, channel, DGBS,  buffer_bytes);
+	host_dma_reg_write(dma, channel, DGBFPI,  0);
+	host_dma_reg_write(dma, channel, DGBSP,  period_bytes);
+	host_dma_reg_write(dma, channel, DGMBS,  period_bytes);
+	host_dma_reg_write(dma, channel, DGLLPI,  0);
+	host_dma_reg_write(dma, channel, DGLPIBI,  0);
+
+	p->chan[channel].status = COMP_STATE_PREPARE;
+out:
+	spin_unlock_irq(&dma->lock, flags);
+	return ret;
+}
+
+/* restore DMA conext after leaving D3 */
+static int hda_dma_pm_context_restore(struct dma *dma)
+{
+	return 0;
+}
+
+/* store DMA conext after leaving D3 */
+static int hda_dma_pm_context_store(struct dma *dma)
+{
+	return 0;
+}
+
+static int hda_dma_set_cb(struct dma *dma, int channel, int type,
+	void (*cb)(void *data, uint32_t type, struct dma_sg_elem *next),
+	void *data)
+{
+	return -EINVAL;
+}
+
+static int hda_dma_probe(struct dma *dma)
+{
+	struct dma_pdata *hda_pdata;
+	int i;
+
+	/* allocate private data */
+	hda_pdata = rzalloc(RZONE_SYS, RFLAGS_NONE, sizeof(*hda_pdata));
+	dma_set_drvdata(dma, hda_pdata);
+
+	spinlock_init(&dma->lock);
+
+	/* init channel status */
+	for (i = 0; i < HDA_DMA_MAX_CHANS; i++)
+		hda_pdata->chan[i].status = COMP_STATE_INIT;
+
+	return 0;
+}
+
+const struct dma_ops hda_host_dma_ops = {
+	.channel_get	= hda_dma_channel_get,
+	.channel_put	= hda_dma_channel_put,
+	.start		= hda_dma_start,
+	.stop		= hda_dma_stop,
+	.copy		= hda_dma_copy,
+	.pause		= hda_dma_pause,
+	.release	= hda_dma_release,
+	.status		= hda_dma_status,
+	.set_config	= hda_dma_set_config,
+	.set_cb		= hda_dma_set_cb,
+	.pm_context_restore		= hda_dma_pm_context_restore,
+	.pm_context_store		= hda_dma_pm_context_store,
+	.probe		= hda_dma_probe,
+};
+
+const struct dma_ops hda_link_dma_ops = {
+	.channel_get	= hda_dma_channel_get,
+	.channel_put	= hda_dma_channel_put,
+	.start		= hda_dma_start,
+	.stop		= hda_dma_stop,
+	.copy		= hda_dma_copy,
+	.pause		= hda_dma_pause,
+	.release	= hda_dma_release,
+	.status		= hda_dma_status,
+	.set_config	= hda_dma_set_config,
+	.set_cb		= hda_dma_set_cb,
+	.pm_context_restore		= hda_dma_pm_context_restore,
+	.pm_context_store		= hda_dma_pm_context_store,
+	.probe		= hda_dma_probe,
+};
+
diff --git a/src/platform/apollolake/platform.c b/src/platform/apollolake/platform.c
index bb44634..555f14f 100644
--- a/src/platform/apollolake/platform.c
+++ b/src/platform/apollolake/platform.c
@@ -176,8 +176,7 @@ static struct timer platform_ext_timer = {
 
 int platform_init(struct reef *reef)
 {
-	struct dma *dmac0;
-	struct dma *dmac1;
+	struct dma *dmac;
 	struct dai *ssp2;
 
 	platform_interrupt_init();
@@ -232,15 +231,25 @@ int platform_init(struct reef *reef)
 
 	/* init DMACs */
 	trace_point(TRACE_BOOT_PLATFORM_DMA);
-	dmac0 = dma_get(DMA_GP_LP_DMAC0);
-	if (dmac0 == NULL)
+	dmac = dma_get(DMA_GP_LP_DMAC0);
+	if (dmac == NULL)
 		return -ENODEV;
-	dma_probe(dmac0);
+	dma_probe(dmac);
 
-	dmac1 = dma_get(DMA_GP_LP_DMAC1);
-	if (dmac1 == NULL)
+	dmac = dma_get(DMA_GP_LP_DMAC1);
+	if (dmac == NULL)
 		return -ENODEV;
-	dma_probe(dmac1);
+	dma_probe(dmac);
+
+	dmac = dma_get(DMA_HOST_OUT_DMAC);
+	if (dmac == NULL)
+		return -ENODEV;
+	dma_probe(dmac);
+
+	dmac = dma_get(DMA_HOST_IN_DMAC);
+	if (dmac == NULL)
+		return -ENODEV;
+	dma_probe(dmac);
 
 	/* init SSP ports */
 	trace_point(TRACE_BOOT_PLATFORM_SSP);
-- 
2.11.0



More information about the Sound-open-firmware mailing list