From b30658d4f7a2cea334b93d4f02b313cfe1c4978e Mon Sep 17 00:00:00 2001 From: Marcin Maka Date: Tue, 7 Aug 2018 14:30:02 +0200 Subject: [PATCH 1/3] comp: dai: dma channel acquired by stream tag Some DMA types (HD/A for insstance) require to synchronize selection of the channel between the host and the dsp. Stream tag is a common setting for both host and dai atm, so it is not adjusted in dai comp. Signed-off-by: Marcin Maka --- src/audio/dai.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/audio/dai.c b/src/audio/dai.c index 478cca77689e..b59694a29b17 100644 --- a/src/audio/dai.c +++ b/src/audio/dai.c @@ -213,18 +213,7 @@ static struct comp_dev *dai_new(struct sof_ipc_comp *comp) dd->xrun = 0; dd->pointer_init = 0; - /* get DMA channel from DMAC1 */ - dd->chan = dma_channel_get(dd->dma, 0); - if (dd->chan < 0){ - trace_dai_error("eDc"); - goto error; - } - - /* set up callback */ - dma_set_cb(dd->dma, dd->chan, DMA_IRQ_TYPE_BLOCK | - DMA_IRQ_TYPE_LLIST, dai_dma_cb, dev); dev->state = COMP_STATE_READY; - dev->is_dma_connected = 1; return dev; error: @@ -392,6 +381,18 @@ static int dai_params(struct comp_dev *dev) return -EINVAL; } + /* get DMA channel, once the stream_tag is known */ + dd->chan = dma_channel_get(dd->dma, dev->params.stream_tag); + if (dd->chan < 0) { + trace_dai_error("eDc"); + return -EINVAL; + } + + /* set up callback */ + dma_set_cb(dd->dma, dd->chan, DMA_IRQ_TYPE_BLOCK | + DMA_IRQ_TYPE_LLIST, dai_dma_cb, dev); + dev->is_dma_connected = 1; + /* for DAI, we should configure its frame_fmt from topology */ dev->params.frame_fmt = dconfig->frame_fmt; From f96878a8f4e50e5b8023ddcf8810ea211b1899c3 Mon Sep 17 00:00:00 2001 From: Marcin Maka Date: Tue, 7 Aug 2018 15:03:42 +0200 Subject: [PATCH 2/3] dai: support for hda cyclic mode dai added There are no buffer completion int's, so work queue timers are used by hda-dma in cyclic mode. Buffer segment completion programming removed. W/A to observed work queue timer inaccuracy applied. Signed-off-by: Marcin Maka --- src/audio/dai.c | 25 +++- src/audio/host.c | 3 +- src/drivers/Makefile.am | 1 + src/drivers/cavs-hda.c | 62 +++++++++ src/drivers/hda-dma.c | 241 +++++++++++++++++++++++++++++----- src/include/sof/dma.h | 9 +- src/ipc/dma-copy.c | 2 +- src/platform/intel/cavs/dai.c | 79 ++++++++++- 8 files changed, 379 insertions(+), 43 deletions(-) create mode 100644 src/drivers/cavs-hda.c diff --git a/src/audio/dai.c b/src/audio/dai.c index b59694a29b17..2c8597e80b4f 100644 --- a/src/audio/dai.c +++ b/src/audio/dai.c @@ -197,9 +197,21 @@ static struct comp_dev *dai_new(struct sof_ipc_comp *comp) /* TODO: hda: retrieve req'ed caps from the dai, * dmas are not cross-compatible. */ - dir = DMA_DIR_MEM_TO_DEV | DMA_DIR_DEV_TO_MEM; - caps = DMA_CAP_GP_LP | DMA_CAP_GP_HP; - dma_dev = DMA_DEV_SSP | DMA_DEV_DMIC; + switch (dai->type) { + case SOF_DAI_INTEL_HDA: + dir = dai->direction == SOF_IPC_STREAM_PLAYBACK ? + DMA_DIR_DEV_TO_MEM : DMA_DIR_MEM_TO_DEV; + caps = DMA_CAP_HDA; + dma_dev = DMA_DEV_HDA; + break; + case SOF_DAI_INTEL_SSP: + case SOF_DAI_INTEL_DMIC: + default: + dir = DMA_DIR_MEM_TO_DEV | DMA_DIR_DEV_TO_MEM; + caps = DMA_CAP_GP_LP | DMA_CAP_GP_HP; + dma_dev = DMA_DEV_SSP | DMA_DEV_DMIC; + break; + } dd->dma = dma_get(dir, caps, dma_dev, DMA_ACCESS_SHARED); if (dd->dma == NULL) { trace_dai_error("eDd"); @@ -702,6 +714,13 @@ static int dai_config(struct comp_dev *dev, struct sof_ipc_dai_config *config) trace_value(config->dmic.pdm[0].enable_mic_b); trace_value(dev->frame_bytes); break; + case SOF_DAI_INTEL_HDA: + /* set to some non-zero value to satisfy the condition below, + * it is recalculated in dai_params() later + * this is temp until dai/hda model is changed. + */ + dev->frame_bytes = 4; + break; default: /* other types of DAIs not handled for now */ trace_dai_error("de2"); diff --git a/src/audio/host.c b/src/audio/host.c index aeeff3329922..0d0d0861e13c 100644 --- a/src/audio/host.c +++ b/src/audio/host.c @@ -863,7 +863,8 @@ static int host_copy(struct comp_dev *dev) #if defined CONFIG_DMA_GW /* tell gateway to copy another period */ - ret = dma_copy(hd->dma, hd->chan, hd->period_bytes); + /* TODO: flags to be used by preload */ + ret = dma_copy(hd->dma, hd->chan, hd->period_bytes, 0); if (ret < 0) goto out; diff --git a/src/drivers/Makefile.am b/src/drivers/Makefile.am index 3a165103a8ac..5e74844e9b2f 100644 --- a/src/drivers/Makefile.am +++ b/src/drivers/Makefile.am @@ -27,6 +27,7 @@ if BUILD_APOLLOLAKE libdrivers_a_SOURCES += \ apl-ssp.c \ hda-dma.c \ + cavs-hda.c \ dmic.c endif diff --git a/src/drivers/cavs-hda.c b/src/drivers/cavs-hda.c new file mode 100644 index 000000000000..e61e832ebc30 --- /dev/null +++ b/src/drivers/cavs-hda.c @@ -0,0 +1,62 @@ +/* + * 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: Marcin Maka +#include + +static int hda_trigger(struct dai *dai, int cmd, int direction) +{ + return 0; +} + +static int hda_set_config(struct dai *dai, + struct sof_ipc_dai_config *config) +{ + return 0; +} + +static int hda_dummy(struct dai *dai) +{ + return 0; +} + +static int hda_set_loopback_mode(struct dai *dai, uint32_t lbm) +{ + return -EINVAL; +} + +const struct dai_ops hda_ops = { + .trigger = hda_trigger, + .set_config = hda_set_config, + .pm_context_store = hda_dummy, + .pm_context_restore = hda_dummy, + .probe = hda_dummy, + .set_loopback_mode = hda_set_loopback_mode +}; diff --git a/src/drivers/hda-dma.c b/src/drivers/hda-dma.c index f8fce18e8829..7711bded09a4 100644 --- a/src/drivers/hda-dma.c +++ b/src/drivers/hda-dma.c @@ -67,13 +67,14 @@ #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 */ +#define DGCS_SCS BIT(31) +#define DGCS_GEN BIT(26) +#define DGCS_FWCB BIT(23) +#define DGCS_BSC BIT(11) +#define DGCS_BOR BIT(10) /* buffer overrun */ +#define DGCS_BF BIT(9) /* buffer full */ +#define DGCS_BNE BIT(8) /* buffer not empty */ +#define DGCS_FIFORDY BIT(5) /* enable FIFO */ /* DGBBA */ #define DGBBA_MASK 0xffff80 @@ -83,12 +84,26 @@ #define HDA_DMA_MAX_CHANS 9 +/* TODO: 10% adjustment for inaccurate queue timer */ +#define HDA_LINK_1MS_US 1010 + struct hda_chan_data { + struct dma *dma; + uint32_t index; uint32_t stream_id; uint32_t status; uint32_t desc_count; uint32_t desc_avail; uint32_t direction; + + uint32_t period_bytes; + uint32_t buffer_bytes; + struct work dma_ch_work; + + void (*cb)(void *data, uint32_t type, + struct dma_sg_elem *next); /* client callback */ + void *cb_data; /* client callback data */ + int cb_type; /* callback type */ }; struct dma_pdata { @@ -97,6 +112,8 @@ struct dma_pdata { struct hda_chan_data chan[HDA_DMA_MAX_CHANS]; }; +static int hda_dma_stop(struct dma *dma, int channel); + static inline uint32_t host_dma_reg_read(struct dma *dma, uint32_t chan, uint32_t reg) { @@ -115,22 +132,109 @@ static inline void hda_update_bits(struct dma *dma, uint32_t chan, 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) +static inline void hda_dma_inc_fp(struct dma *dma, uint32_t chan, + uint32_t value) { - tracev_host("GwU"); + host_dma_reg_write(dma, chan, DGBFPI, value); + /* TODO: wp update, not rp should inc LLPI and LPIBI in the + * coupled input DMA + */ + host_dma_reg_write(dma, chan, DGLLPI, value); + host_dma_reg_write(dma, chan, DGLPIBI, value); +} - /* reset BSC before start next copy */ - hda_update_bits(dma, channel, DGCS, DGCS_BSC, DGCS_BSC); +static inline void hda_dma_inc_link_fp(struct dma *dma, uint32_t chan, + uint32_t value) +{ + host_dma_reg_write(dma, chan, DGBFPI, value); + /* TODO: wp update should inc LLPI and LPIBI in the input DMA */ +} - /* - * 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); +/* TODO: might be implemented as buffer_size - get_data_size() */ +static inline uint32_t hda_dma_get_free_size(struct dma *dma, uint32_t chan) +{ + const uint32_t cs = host_dma_reg_read(dma, chan, DGCS); + const uint32_t bs = host_dma_reg_read(dma, chan, DGBS); + const uint32_t rp = host_dma_reg_read(dma, chan, DGBRP); + const uint32_t wp = host_dma_reg_read(dma, chan, DGBRP); + int32_t fs; + + if (cs & DGCS_BF) + return 0; /* buffer is full */ + if (!(cs & DGCS_BNE)) + return bs; /* buffer is empty */ + fs = rp - wp; + if (wp >= rp) + fs += bs; + return fs; +} + +static inline uint32_t hda_dma_get_data_size(struct dma *dma, uint32_t chan) +{ + const uint32_t cs = host_dma_reg_read(dma, chan, DGCS); + const uint32_t bs = host_dma_reg_read(dma, chan, DGBS); + const uint32_t rp = host_dma_reg_read(dma, chan, DGBRP); + const uint32_t wp = host_dma_reg_read(dma, chan, DGBRP); + + uint32_t ds; - host_dma_reg_write(dma, channel, DGLLPI, bytes); - host_dma_reg_write(dma, channel, DGLPIBI, bytes); + if (!(cs & DGCS_BNE)) + return 0; /* buffer is empty */ + ds = wp - rp; + if (ds <= 0) + ds += bs; + + return ds; +} + +static int hda_dma_copy_ch(struct dma *dma, struct hda_chan_data *chan, + int bytes, uint32_t flags) +{ + struct dma_sg_elem next; + uint32_t dgcs = 0; + + tracev_host("GwU"); + if (flags & DMA_COPY_NO_COMMIT) { + /* delay to fill the buffer, only for the first time + * for _input_ dma. + */ + idelay(PLATFORM_DEFAULT_DELAY); + } else { + /* clear link xruns */ + dgcs = host_dma_reg_read(dma, chan->index, DGCS); + if (dgcs & DGCS_BOR) + hda_update_bits(dma, chan->index, + DGCS, DGCS_BOR, DGCS_BOR); + + /* make sure that previous transfer is complete */ + if (chan->direction == DMA_DIR_MEM_TO_DEV) { + while (hda_dma_get_free_size(dma, chan->index) < + bytes) + idelay(PLATFORM_DEFAULT_DELAY); + } + + /* + * set BFPI to let host gateway knows we have read size, + * which will trigger next copy start. + */ + if (chan->direction == DMA_DIR_MEM_TO_DEV) + hda_dma_inc_link_fp(dma, chan->index, bytes); + else + hda_dma_inc_fp(dma, chan->index, bytes); + } + + spin_lock_irq(&dma->lock, flags); + if (chan->cb) { + next.src = DMA_RELOAD_LLI; + next.dest = DMA_RELOAD_LLI; + next.size = DMA_RELOAD_LLI; + chan->cb(chan->cb_data, DMA_IRQ_TYPE_LLIST, &next); + if (next.size == DMA_RELOAD_END) { + /* disable channel, finished */ + hda_dma_stop(dma, chan->index); + } + } + spin_unlock_irq(&dma->lock, flags); /* Force Host DMA to exit L1 */ pm_runtime_put(PM_RUNTIME_HOST_DMA_L1); @@ -138,6 +242,23 @@ static int hda_dma_copy(struct dma *dma, int channel, int bytes) return 0; } +static uint64_t hda_dma_work(void *data, uint64_t delay) +{ + struct hda_chan_data *chan = (struct hda_chan_data *)data; + + hda_dma_copy_ch(chan->dma, chan, chan->period_bytes, 0); + /* next time to re-arm (TODO: should sub elapsed time?) */ + return HDA_LINK_1MS_US; +} + +/* notify DMA to copy bytes */ +static int hda_dma_copy(struct dma *dma, int channel, int bytes, uint32_t flags) +{ + struct dma_pdata *p = dma_get_drvdata(dma); + + return hda_dma_copy_ch(dma, &p->chan[channel], bytes, flags); +} + /* acquire the specific DMA channel */ static int hda_dma_channel_get(struct dma *dma, int channel) { @@ -172,6 +293,12 @@ static void hda_dma_channel_put_unlocked(struct dma *dma, int channel) /* set new state */ p->chan[channel].status = COMP_STATE_INIT; + p->chan[channel].period_bytes = 0; + p->chan[channel].buffer_bytes = 0; + p->chan[channel].cb = NULL; + p->chan[channel].cb_type = 0; + p->chan[channel].cb_data = NULL; + work_init(&p->chan[channel].dma_ch_work, NULL, NULL, 0); } /* channel must not be running when this is called */ @@ -216,6 +343,18 @@ static int hda_dma_start(struct dma *dma, int channel) p->chan[channel].desc_avail = p->chan[channel].desc_count; pm_runtime_put(PM_RUNTIME_HOST_DMA_L1); + + /* activate timer if configured in cyclic mode */ + if (p->chan[channel].dma_ch_work.cb) { + work_schedule_default(&p->chan[channel].dma_ch_work, + HDA_LINK_1MS_US); + } + + /* start link output transfer now */ + if (p->chan[channel].direction == DMA_DIR_MEM_TO_DEV) + hda_dma_inc_link_fp(dma, channel, + p->chan[channel].buffer_bytes); + out: spin_unlock_irq(&dma->lock, flags); return ret; @@ -223,6 +362,7 @@ static int hda_dma_start(struct dma *dma, int channel) static int hda_dma_release(struct dma *dma, int channel) { + /* TODO: to be removed, no longer called by anyone */ struct dma_pdata *p = dma_get_drvdata(dma); uint32_t flags; @@ -266,6 +406,9 @@ static int hda_dma_stop(struct dma *dma, int channel) trace_host("DDi"); + if (p->chan[channel].dma_ch_work.cb) + work_cancel_default(&p->chan[channel].dma_ch_work); + /* disable the channel */ hda_update_bits(dma, channel, DGCS, DGCS_GEN | DGCS_FIFORDY, 0); p->chan[channel].status = COMP_STATE_PREPARE; @@ -326,7 +469,8 @@ static int hda_dma_set_config(struct dma *dma, int channel, list_for_item(plist, &config->elem_list) { sg_elem = container_of(plist, struct dma_sg_elem, list); - if (config->direction == DMA_DIR_HMEM_TO_LMEM) + if (config->direction == DMA_DIR_HMEM_TO_LMEM || + config->direction == DMA_DIR_DEV_TO_MEM) addr = sg_elem->dest; else addr = sg_elem->src; @@ -352,27 +496,41 @@ static int hda_dma_set_config(struct dma *dma, int channel, if (buffer_addr == 0) buffer_addr = addr; } + p->chan[channel].period_bytes = period_bytes; + p->chan[channel].buffer_bytes = buffer_bytes; + + /* initialize timer */ + if (config->cyclic) { + work_init(&p->chan[channel].dma_ch_work, hda_dma_work, + &p->chan[channel], WORK_SYNC); + } + + /* init channel in HW */ + host_dma_reg_write(dma, channel, DGBBA, buffer_addr); + host_dma_reg_write(dma, channel, DGBS, buffer_bytes); + + if (config->direction == DMA_DIR_LMEM_TO_HMEM || + config->direction == DMA_DIR_HMEM_TO_LMEM) + host_dma_reg_write(dma, channel, DGMBS, + ALIGN_UP(period_bytes, + PLATFORM_HDA_BUFFER_ALIGNMENT)); /* firmware control buffer */ dgcs = DGCS_FWCB; /* set DGCS.SCS bit to 1 for 16bit(2B) container */ - if ((config->direction == DMA_DIR_HMEM_TO_LMEM && + if ((config->direction & (DMA_DIR_HMEM_TO_LMEM | DMA_DIR_DEV_TO_MEM) && config->dest_width <= 2) || - (config->direction == DMA_DIR_LMEM_TO_HMEM && + (config->direction & (DMA_DIR_LMEM_TO_HMEM | DMA_DIR_MEM_TO_DEV) && config->src_width <= 2)) dgcs |= DGCS_SCS; - /* init channel in HW */ + /* set DGCS.FIFORDY for output dma */ + if ((config->cyclic && config->direction == DMA_DIR_MEM_TO_DEV) || + (!config->cyclic && config->direction == DMA_DIR_LMEM_TO_HMEM)) + dgcs |= DGCS_FIFORDY; + 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, - ALIGN_UP(period_bytes, PLATFORM_HDA_BUFFER_ALIGNMENT)); - host_dma_reg_write(dma, channel, DGLLPI, 0); - host_dma_reg_write(dma, channel, DGLPIBI, 0); p->chan[channel].status = COMP_STATE_PREPARE; out: @@ -396,13 +554,23 @@ 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; + struct dma_pdata *p = dma_get_drvdata(dma); + uint32_t flags; + + spin_lock_irq(&dma->lock, flags); + p->chan[channel].cb = cb; + p->chan[channel].cb_data = data; + p->chan[channel].cb_type = type; + spin_unlock_irq(&dma->lock, flags); + + return 0; } static int hda_dma_probe(struct dma *dma) { struct dma_pdata *hda_pdata; int i; + struct hda_chan_data *chan; /* allocate private data */ hda_pdata = rzalloc(RZONE_SYS, SOF_MEM_CAPS_RAM, sizeof(*hda_pdata)); @@ -411,8 +579,13 @@ static int hda_dma_probe(struct dma *dma) spinlock_init(&dma->lock); /* init channel status */ - for (i = 0; i < HDA_DMA_MAX_CHANS; i++) - hda_pdata->chan[i].status = COMP_STATE_INIT; + chan = hda_pdata->chan; + + for (i = 0; i < HDA_DMA_MAX_CHANS; i++, chan++) { + chan->dma = dma; + chan->index = i; + chan->status = COMP_STATE_INIT; + } /* init number of channels draining */ atomic_init(&dma->num_channels_busy, 0); diff --git a/src/include/sof/dma.h b/src/include/sof/dma.h index 36b9bf3635c8..1e7412e414e8 100644 --- a/src/include/sof/dma.h +++ b/src/include/sof/dma.h @@ -80,6 +80,8 @@ #define DMA_IRQ_TYPE_BLOCK BIT(0) #define DMA_IRQ_TYPE_LLIST BIT(1) +/* DMA copy flags */ +#define DMA_COPY_NO_COMMIT BIT(0) /* We will use this macro in cb handler to inform dma that * we need to stop the reload for special purpose @@ -124,7 +126,7 @@ struct dma_ops { int (*start)(struct dma *dma, int channel); int (*stop)(struct dma *dma, int channel); - int (*copy)(struct dma *dma, int channel, int bytes); + int (*copy)(struct dma *dma, int channel, int bytes, uint32_t flags); int (*pause)(struct dma *dma, int channel); int (*release)(struct dma *dma, int channel); int (*status)(struct dma *dma, int channel, @@ -226,9 +228,10 @@ static inline int dma_stop(struct dma *dma, int channel) return dma->ops->stop(dma, channel); } -static inline int dma_copy(struct dma *dma, int channel, int bytes) +static inline int dma_copy(struct dma *dma, int channel, int bytes, + uint32_t flags) { - return dma->ops->copy(dma, channel, bytes); + return dma->ops->copy(dma, channel, bytes, flags); } static inline int dma_pause(struct dma *dma, int channel) diff --git a/src/ipc/dma-copy.c b/src/ipc/dma-copy.c index f5f22bf58b6b..c7e18b090ca0 100644 --- a/src/ipc/dma-copy.c +++ b/src/ipc/dma-copy.c @@ -182,7 +182,7 @@ int dma_copy_to_host_nowait(struct dma_copy *dc, struct dma_sg_config *host_sg, int ret; /* tell gateway to copy */ - ret = dma_copy(dc->dmac, dc->chan, size); + ret = dma_copy(dc->dmac, dc->chan, size, 0); if (ret < 0) return ret; diff --git a/src/platform/intel/cavs/dai.c b/src/platform/intel/cavs/dai.c index e76bc6a7c4f0..f8ec87c1506e 100644 --- a/src/platform/intel/cavs/dai.c +++ b/src/platform/intel/cavs/dai.c @@ -45,6 +45,9 @@ #include #include +/* TODO: ops should be declared by their respective dai headers */ +extern const struct dai_ops hda_ops; + static struct dai ssp[] = { { .type = SOF_DAI_INTEL_SSP, @@ -201,6 +204,75 @@ static struct dai dmic[2] = { #endif +/* TODO: numbers to be defined and shared with HD/A dmas */ +static struct dai hda[(6 + 7)] = { + { + .type = SOF_DAI_INTEL_HDA, + .index = 0, + .ops = &hda_ops + }, + { + .type = SOF_DAI_INTEL_HDA, + .index = 1, + .ops = &hda_ops + }, + { + .type = SOF_DAI_INTEL_HDA, + .index = 2, + .ops = &hda_ops + }, + { + .type = SOF_DAI_INTEL_HDA, + .index = 3, + .ops = &hda_ops + }, + { + .type = SOF_DAI_INTEL_HDA, + .index = 4, + .ops = &hda_ops + }, + { + .type = SOF_DAI_INTEL_HDA, + .index = 5, + .ops = &hda_ops + }, + { + .type = SOF_DAI_INTEL_HDA, + .index = 6, + .ops = &hda_ops + }, + { + .type = SOF_DAI_INTEL_HDA, + .index = 7, + .ops = &hda_ops + }, + { + .type = SOF_DAI_INTEL_HDA, + .index = 8, + .ops = &hda_ops + }, + { + .type = SOF_DAI_INTEL_HDA, + .index = 9, + .ops = &hda_ops + }, + { + .type = SOF_DAI_INTEL_HDA, + .index = 10, + .ops = &hda_ops + }, + { + .type = SOF_DAI_INTEL_HDA, + .index = 11, + .ops = &hda_ops + }, + { + .type = SOF_DAI_INTEL_HDA, + .index = 12, + .ops = &hda_ops + } +}; + struct dai *dai_get(uint32_t type, uint32_t index) { int i; @@ -220,6 +292,11 @@ struct dai *dai_get(uint32_t type, uint32_t index) } } #endif - + if (type == SOF_DAI_INTEL_HDA) { + for (i = 0; i < ARRAY_SIZE(hda); i++) { + if (hda[i].type == type && hda[i].index == index) + return &hda[i]; + } + } return NULL; } From b39b17ddf3ed1797c1eba8502777200f61c0e304 Mon Sep 17 00:00:00 2001 From: Marcin Maka Date: Tue, 7 Aug 2018 18:57:59 +0200 Subject: [PATCH 3/3] comp: host: hda/dw-dma flow unification Dedicated implementation of trigger handler and element creation merged. Hd/a version simplified, host and local sgl removed. Signed-off-by: Marcin Maka --- src/audio/host.c | 380 ++++++++++++++++------------------------------- 1 file changed, 126 insertions(+), 254 deletions(-) diff --git a/src/audio/host.c b/src/audio/host.c index 0d0d0861e13c..2622a23e8a38 100644 --- a/src/audio/host.c +++ b/src/audio/host.c @@ -30,6 +30,7 @@ */ #include +#include #include #include #include @@ -51,8 +52,6 @@ #define tracev_host(__e) tracev_event(TRACE_CLASS_HOST, __e) #define trace_host_error(__e) trace_error(TRACE_CLASS_HOST, __e) -static int host_copy(struct comp_dev *dev); - struct hc_buf { /* host buffer info */ struct list_item elem_list; @@ -60,6 +59,15 @@ struct hc_buf { uint32_t current_end; }; +/** + * \brief Host component data. + * + * Host reports local position in the host buffer every params.host_period_bytes + * if the latter is != 0. report_pos is used to track progress since the last + * multiple of host_period_bytes. + * + * host_size is the host buffer size (in bytes) specified in the IPC parameters. + */ struct host_data { /* local DMA config */ struct dma *dma; @@ -68,28 +76,34 @@ struct host_data { completion_t complete; struct comp_buffer *dma_buffer; + uint32_t period_bytes; /**< Size of a single period (in bytes) */ + uint32_t period_count; /**< Number of periods */ + uint32_t pointer_init; + + /* host position reporting related */ + uint32_t host_size; /**< Host buffer size (in bytes) */ + uint32_t report_pos; /**< Position in current report period */ + uint32_t local_pos; /**< Local position in host buffer */ + /* local and host DMA buffer info */ +#if !defined CONFIG_DMA_GW struct hc_buf host; struct hc_buf local; - uint32_t host_size; - /* host position reporting related */ - volatile uint32_t *host_pos; /* read/write pos, update to mailbox for host side */ - uint32_t report_pos; /* position in current report period */ - uint32_t local_pos; /* the host side buffer local read/write position, in bytes */ /* pointers set during params to host or local above */ struct hc_buf *source; struct hc_buf *sink; uint32_t split_remaining; uint32_t next_inc; - uint32_t period_bytes; - uint32_t period_count; - uint32_t pointer_init; +#endif /* stream info */ struct sof_ipc_stream_posn posn; /* TODO: update this */ }; static int host_stop(struct comp_dev *dev); +static int host_copy_int(struct comp_dev *dev, bool preload_run); + +#if !defined CONFIG_DMA_GW static inline struct dma_sg_elem *next_buffer(struct hc_buf *hc) { @@ -104,7 +118,7 @@ static inline struct dma_sg_elem *next_buffer(struct hc_buf *hc) return elem; } -#if !defined CONFIG_DMA_GW +#endif /* * Host period copy between DSP and host DMA completion. @@ -120,14 +134,16 @@ static void host_dma_cb(void *data, uint32_t type, struct dma_sg_elem *next) struct comp_dev *dev = (struct comp_dev *)data; struct host_data *hd = comp_get_drvdata(dev); struct dma_sg_elem *local_elem; +#if !defined CONFIG_DMA_GW struct dma_sg_elem *source_elem; struct dma_sg_elem *sink_elem; uint32_t next_size; uint32_t need_copy = 0; uint32_t period_bytes = hd->period_bytes; +#endif local_elem = list_first_item(&hd->config.elem_list, - struct dma_sg_elem, list); + struct dma_sg_elem, list); tracev_host("irq"); @@ -138,32 +154,34 @@ static void host_dma_cb(void *data, uint32_t type, struct dma_sg_elem *next) /* recalc available buffer space */ comp_update_buffer_consume(hd->dma_buffer, local_elem->size); - /* new local period, update host buffer position blks */ - hd->local_pos += local_elem->size; dev->position += local_elem->size; - /* buffer overlap ? */ + /* new local period, update host buffer position blks + * local_pos is queried by the ops.potision() API + */ + hd->local_pos += local_elem->size; + + /* buffer overlap, hard code host buffer size at the moment ? */ if (hd->local_pos >= hd->host_size) hd->local_pos = 0; - /* send IPC message to driver if needed */ - hd->report_pos += local_elem->size; - hd->posn.host_posn += local_elem->size; - /* NO_IRQ mode if host_period_size == 0 */ - if (dev->params.host_period_bytes != 0 && - hd->report_pos >= dev->params.host_period_bytes) { - hd->report_pos = 0; - /* update for host side */ - if (hd->host_pos) { - *hd->host_pos = hd->local_pos; + if (dev->params.host_period_bytes != 0) { + hd->report_pos += local_elem->size; + + /* send IPC message to driver if needed */ + if (hd->report_pos >= dev->params.host_period_bytes) { + hd->report_pos = 0; + + /* send timestamped position to host + * (updates position first, by calling ops.position()) + */ + pipeline_get_timestamp(dev->pipeline, dev, &hd->posn); + ipc_stream_send_position(dev, &hd->posn); } - - /* send timestamps to host */ - pipeline_get_timestamp(dev->pipeline, dev, &hd->posn); - ipc_stream_send_position(dev, &hd->posn); } +#if !defined CONFIG_DMA_GW /* update src and dest positions and check for overflow */ local_elem->src += local_elem->size; local_elem->dest += local_elem->size; @@ -216,6 +234,7 @@ static void host_dma_cb(void *data, uint32_t type, struct dma_sg_elem *next) /* let any waiters know we have completed */ wait_completed(&hd->complete); +#endif } static int create_local_elems(struct comp_dev *dev) @@ -241,208 +260,40 @@ static int create_local_elems(struct comp_dev *dev) i * hd->period_bytes; e->size = hd->period_bytes; - - list_item_append(&e->list, &hd->local.elem_list); - } - - return 0; - -unwind: - list_for_item_safe(elist, tlist, &hd->local.elem_list) { - e = container_of(elist, struct dma_sg_elem, list); - list_item_del(&e->list); - rfree(e); - } - - trace_host_error("el0"); - return -ENOMEM; -} - -/* used to pass standard and bespoke commands (with data) to component */ -static int host_trigger(struct comp_dev *dev, int cmd) -{ - int ret = 0; - - trace_host("cmd"); - - ret = comp_set_state(dev, cmd); - if (ret < 0) - return ret; - - switch (cmd) { - case COMP_TRIGGER_STOP: - ret = host_stop(dev); - break; - case COMP_TRIGGER_START: - /* preload first playback period for preloader task */ - if (dev->params.direction == SOF_IPC_STREAM_PLAYBACK) { - ret = host_copy(dev); - if (ret == dev->frames) - ret = 0; - // wait for completion ? - } - break; - default: - break; - } - - return ret; -} - +#if defined CONFIG_DMA_GW + list_item_append(&e->list, &hd->config.elem_list); #else - -static void host_gw_dma_update(struct comp_dev *dev) -{ - struct host_data *hd = comp_get_drvdata(dev); - struct dma_sg_elem *local_elem; - struct dma_sg_elem *source_elem; - struct dma_sg_elem *sink_elem; - - local_elem = list_first_item(&hd->config.elem_list, - struct dma_sg_elem, list); - - tracev_host("upd"); - - if (dev->params.direction == SOF_IPC_STREAM_PLAYBACK) - /* recalc available buffer space */ - comp_update_buffer_produce(hd->dma_buffer, local_elem->size); - else - /* recalc available buffer space */ - comp_update_buffer_consume(hd->dma_buffer, local_elem->size); - - dev->position += local_elem->size; - - /* new local period, update host buffer position blks */ - hd->local_pos += local_elem->size; - - /* buffer overlap, hard code host buffer size at the moment ? */ - if (hd->local_pos >= hd->host_size) - hd->local_pos = 0; - - /* send IPC message to driver if needed */ - hd->report_pos += local_elem->size; - /* update for host side */ - if (hd->host_pos) - *hd->host_pos = hd->local_pos; - - /* NO_IRQ mode if host_period_size == 0 */ - if (dev->params.host_period_bytes != 0 && - hd->report_pos >= dev->params.host_period_bytes) { - hd->report_pos = 0; - - /* send timestamps to host */ - pipeline_get_timestamp(dev->pipeline, dev, &hd->posn); - ipc_stream_send_position(dev, &hd->posn); - } - - /* update src/dest positions for local buf, and check for overflow */ - if (dev->params.direction == SOF_IPC_STREAM_PLAYBACK) { - local_elem->dest += local_elem->size; - if (local_elem->dest == hd->sink->current_end) { - /* end of elem, so use next */ - sink_elem = next_buffer(hd->sink); - hd->sink->current_end = sink_elem->dest + - sink_elem->size; - local_elem->dest = sink_elem->dest; - } - } else { - local_elem->src += local_elem->size; - if (local_elem->src == hd->source->current_end) { - /* end of elem, so use next */ - source_elem = next_buffer(hd->source); - hd->source->current_end = source_elem->src + - source_elem->size; - local_elem->src = source_elem->src; - } - } -} - -static int create_local_elems(struct comp_dev *dev) -{ - struct host_data *hd = comp_get_drvdata(dev); - struct dma_sg_elem *e; - struct dma_sg_elem *ec; - struct dma_sg_elem *local_elem; - struct list_item *elist; - struct list_item *tlist; - int i; - - /* TODO: simplify elem storage by using an array */ - for (i = 0; i < hd->period_count; i++) { - /* allocate new host DMA elem and add it to our list */ - e = rzalloc(RZONE_RUNTIME, SOF_MEM_CAPS_RAM, sizeof(*e)); - if (e == NULL) - goto unwind; - - if (dev->params.direction == SOF_IPC_STREAM_PLAYBACK) - e->dest = (uintptr_t)(hd->dma_buffer->addr) + - i * hd->period_bytes; - else - e->src = (uintptr_t)(hd->dma_buffer->addr) + - i * hd->period_bytes; - - e->size = hd->period_bytes; - list_item_append(&e->list, &hd->local.elem_list); - - /* - * for dma gateway, we don't allocate extra sg elements in - * host_buffer, so, we need create them here and add them - * to config.elem_list. - * And, as the first element has been added at host_new, so - * add from the 2nd element here. - */ - if (i == 0) - continue; - - /* allocate new host DMA elem and add it to our list */ - ec = rzalloc(RZONE_RUNTIME, SOF_MEM_CAPS_RAM, sizeof(*ec)); - if (!ec) - goto unwind; - - *ec = *e; - list_item_append(&ec->list, &hd->config.elem_list); - +#endif } return 0; unwind: +#if defined CONFIG_DMA_GW + list_for_item_safe(elist, tlist, &hd->config.elem_list) { +#else list_for_item_safe(elist, tlist, &hd->local.elem_list) { +#endif e = container_of(elist, struct dma_sg_elem, list); list_item_del(&e->list); rfree(e); } - local_elem = list_first_item(&hd->config.elem_list, - struct dma_sg_elem, list); - list_for_item_safe(elist, tlist, &local_elem->list) { - ec = container_of(elist, struct dma_sg_elem, list); - list_item_del(&ec->list); - rfree(ec); - } - trace_host_error("el0"); return -ENOMEM; } -/* - * Host DMA will copy the first period once it is started, automatically. - * Here update the pointers to reflect the real case. +/** + * \brief Command handler. + * \param[in,out] dev Device + * \param[in] cmd Command + * \return 0 if successful, error code otherwise. + * + * Used to pass standard and bespoke commands (with data) to component. + * This function is common for all dma types, with one exception: + * dw-dma is run on demand, so no start()/stop() is issued. */ -static void host_pointer_init(struct comp_dev *dev) -{ - struct host_data *hd = comp_get_drvdata(dev); - - /* not required for capture streams */ - if (dev->params.direction == SOF_IPC_STREAM_PLAYBACK) - comp_update_buffer_produce(hd->dma_buffer, - hd->period_bytes); - - hd->pointer_init = 1; -} - -/* used to pass standard and bespoke commands (with data) to component */ static int host_trigger(struct comp_dev *dev, int cmd) { struct host_data *hd = comp_get_drvdata(dev); @@ -459,18 +310,32 @@ static int host_trigger(struct comp_dev *dev, int cmd) ret = host_stop(dev); /* fall through */ case COMP_TRIGGER_XRUN: +/* TODO: add attribute to dma interface and do run-time if() here */ +#if defined CONFIG_DMA_GW ret = dma_stop(hd->dma, hd->chan); +#endif break; case COMP_TRIGGER_START: +#if defined CONFIG_DMA_GW ret = dma_start(hd->dma, hd->chan); if (ret < 0) { trace_host_error("TsF"); trace_error_value(ret); goto out; } +#endif + /* preload first playback period for preloader task */ + if (dev->params.direction == SOF_IPC_STREAM_PLAYBACK) { + if (!hd->pointer_init) { + ret = host_copy_int(dev, true); + + if (ret == dev->frames) + ret = 0; + // wait for completion ? - if (!hd->pointer_init) - host_pointer_init(dev); + hd->pointer_init = 1; + } + } break; default: break; @@ -480,16 +345,16 @@ static int host_trigger(struct comp_dev *dev, int cmd) return ret; } -#endif - static struct comp_dev *host_new(struct sof_ipc_comp *comp) { struct comp_dev *dev; struct host_data *hd; struct sof_ipc_comp_host *host; struct sof_ipc_comp_host *ipc_host = (struct sof_ipc_comp_host *)comp; - struct dma_sg_elem *elem; uint32_t dir, caps, dma_dev; +#if !defined CONFIG_DMA_GW + struct dma_sg_elem *elem = NULL; +#endif trace_host("new"); @@ -507,13 +372,6 @@ static struct comp_dev *host_new(struct sof_ipc_comp *comp) return NULL; } - elem = rzalloc(RZONE_RUNTIME, SOF_MEM_CAPS_RAM, sizeof(*elem)); - if (elem == NULL) { - rfree(dev); - rfree(hd); - return NULL; - } - comp_set_drvdata(dev, hd); /* request HDA DMA with shared access privilege */ @@ -532,20 +390,14 @@ static struct comp_dev *host_new(struct sof_ipc_comp *comp) /* init buffer elems */ list_init(&hd->config.elem_list); +#if !defined CONFIG_DMA_GW list_init(&hd->host.elem_list); list_init(&hd->local.elem_list); - list_item_prepend(&elem->list, &hd->config.elem_list); -#if !defined CONFIG_DMA_GW - /* get DMA channel from DMAC */ - hd->chan = dma_channel_get(hd->dma, 0); - if (hd->chan < 0) { - trace_host_error("eDC"); + elem = rzalloc(RZONE_RUNTIME, SOF_MEM_CAPS_RAM, sizeof(*elem)); + if (!elem) goto error; - } - - /* set up callback */ - dma_set_cb(hd->dma, hd->chan, DMA_IRQ_TYPE_LLIST, host_dma_cb, dev); + list_item_prepend(&elem->list, &hd->config.elem_list); #endif /* init posn data. TODO: other fields */ @@ -556,7 +408,9 @@ static struct comp_dev *host_new(struct sof_ipc_comp *comp) return dev; error: +#if !defined CONFIG_DMA_GW rfree(elem); +#endif rfree(hd); rfree(dev); return NULL; @@ -581,6 +435,7 @@ static void host_free(struct comp_dev *dev) rfree(dev); } +#if !defined CONFIG_DMA_GW static int host_elements_reset(struct comp_dev *dev) { struct host_data *hd = comp_get_drvdata(dev); @@ -610,6 +465,7 @@ static int host_elements_reset(struct comp_dev *dev) return 0; } +#endif /* configure the DMA params and descriptors for host buffer IO */ static int host_params(struct comp_dev *dev) @@ -627,18 +483,20 @@ static int host_params(struct comp_dev *dev) /* determine source and sink buffer elems */ if (dev->params.direction == SOF_IPC_STREAM_PLAYBACK) { - +#if !defined CONFIG_DMA_GW hd->source = &hd->host; hd->sink = &hd->local; +#endif hd->dma_buffer = list_first_item(&dev->bsink_list, struct comp_buffer, source_list); config->direction = DMA_DIR_HMEM_TO_LMEM; hd->period_count = cconfig->periods_sink; } else { - +#if !defined CONFIG_DMA_GW hd->source = &hd->local; hd->sink = &hd->host; +#endif hd->dma_buffer = list_first_item(&dev->bsource_list, struct comp_buffer, sink_list); @@ -686,16 +544,20 @@ static int host_params(struct comp_dev *dev) config->dest_width = comp_sample_bytes(dev); config->cyclic = 0; +#if !defined CONFIG_DMA_GW host_elements_reset(dev); +#endif -#if defined CONFIG_DMA_GW dev->params.stream_tag -= 1; - /* get DMA channel from DMAC */ + /* get DMA channel from DMAC + * note: stream_tag is ignored by dw-dma + */ hd->chan = dma_channel_get(hd->dma, dev->params.stream_tag); if (hd->chan < 0) { trace_host_error("eDC"); return -ENODEV; } +#if defined CONFIG_DMA_GW err = dma_set_config(hd->dma, hd->chan, &hd->config); if (err < 0) { trace_host_error("eDc"); @@ -703,7 +565,10 @@ static int host_params(struct comp_dev *dev) return err; } #endif - + /* set up callback */ + dma_set_cb(hd->dma, hd->chan, DMA_IRQ_TYPE_LLIST, + host_dma_cb, + dev); return 0; } @@ -719,10 +584,10 @@ static int host_prepare(struct comp_dev *dev) return ret; hd->local_pos = 0; - if (hd->host_pos) - *hd->host_pos = 0; hd->report_pos = 0; +#if !defined CONFIG_DMA_GW hd->split_remaining = 0; +#endif hd->pointer_init = 0; dev->position = 0; @@ -734,8 +599,6 @@ static int host_pointer_reset(struct comp_dev *dev) struct host_data *hd = comp_get_drvdata(dev); /* reset buffer pointers */ - if (hd->host_pos) - *hd->host_pos = 0; hd->local_pos = 0; hd->report_pos = 0; dev->position = 0; @@ -759,6 +622,7 @@ static int host_position(struct comp_dev *dev, return 0; } +#if !defined CONFIG_DMA_GW static int host_buffer(struct comp_dev *dev, struct dma_sg_elem *elem, uint32_t host_size) { @@ -775,6 +639,7 @@ static int host_buffer(struct comp_dev *dev, struct dma_sg_elem *elem, list_item_append(&e->list, &hd->host.elem_list); return 0; } +#endif static int host_reset(struct comp_dev *dev) { @@ -785,6 +650,7 @@ static int host_reset(struct comp_dev *dev) trace_host("res"); +#if !defined CONFIG_DMA_GW /* free all host DMA elements */ list_for_item_safe(elist, tlist, &hd->host.elem_list) { e = container_of(elist, struct dma_sg_elem, list); @@ -798,6 +664,7 @@ static int host_reset(struct comp_dev *dev) list_item_del(&e->list); rfree(e); } +#endif #if defined CONFIG_DMA_GW dma_stop(hd->dma, hd->chan); @@ -822,9 +689,10 @@ static int host_reset(struct comp_dev *dev) host_pointer_reset(dev); hd->pointer_init = 0; - hd->host_pos = NULL; +#if !defined CONFIG_DMA_GW hd->source = NULL; hd->sink = NULL; +#endif dev->state = COMP_STATE_READY; return 0; @@ -832,6 +700,11 @@ static int host_reset(struct comp_dev *dev) /* copy and process stream data from source to sink buffers */ static int host_copy(struct comp_dev *dev) +{ + return host_copy_int(dev, false); +} + +static int host_copy_int(struct comp_dev *dev, bool preload_run) { struct host_data *hd = comp_get_drvdata(dev); struct dma_sg_elem *local_elem; @@ -860,20 +733,17 @@ static int host_copy(struct comp_dev *dev) return 0; } } - +/* TODO: this could be run-time if() based on the same attribute + * as in the host_trigger(). + */ #if defined CONFIG_DMA_GW /* tell gateway to copy another period */ - /* TODO: flags to be used by preload */ - ret = dma_copy(hd->dma, hd->chan, hd->period_bytes, 0); + ret = dma_copy(hd->dma, hd->chan, hd->period_bytes, + preload_run ? DMA_COPY_NO_COMMIT : 0); if (ret < 0) goto out; - /* - * update host pointers for the new copied period. - * fixme: do we need wait and check to make sure - * the new copy is finished here? - */ - host_gw_dma_update(dev); + /* note: update() moved to callback */ #else /* do DMA transfer */ ret = dma_set_config(hd->dma, hd->chan, &hd->config); @@ -900,7 +770,9 @@ struct comp_driver comp_host = { .trigger = host_trigger, .copy = host_copy, .prepare = host_prepare, +#if !defined CONFIG_DMA_GW .host_buffer = host_buffer, +#endif .position = host_position, }, };