dai: atomic trigger operations

This change adds support for atomic trigger operations,
by grouping DAIs together with a common 'group_id' in
the configuration of each DAI.

When DAIs are in the same group and in the same DSP core,
they will process triggers atomically, i.e. wait for all
the other DAIs to receive the trigger command and pospone
the final, actual trigger operation.

This functionality might be required in case of stream aggregation,
where one stream is fed into multiple physical links.

Signed-off-by: Slawomir Blauciak <slawomir.blauciak@linux.intel.com>
This commit is contained in:
Slawomir Blauciak 2020-05-05 10:48:00 +02:00 committed by Liam Girdwood
parent 5b29dae9c8
commit 496bdd7cf0
5 changed files with 284 additions and 7 deletions

View File

@ -52,6 +52,7 @@ struct dai_data {
struct timestamp_cfg ts_config;
struct dai *dai;
struct dma *dma;
struct dai_group *group; /**< NULL if no group assigned */
int xrun; /* true if we are doing xrun recovery */
pcm_converter_func process; /* processing function */
@ -66,6 +67,41 @@ struct dai_data {
uint64_t wallclock; /* wall clock at stream start */
};
static void dai_atomic_trigger(void *arg, enum notify_id type, void *data);
/* Assign DAI to a group */
static int dai_assign_group(struct comp_dev *dev, uint32_t group_id)
{
struct dai_data *dd = comp_get_drvdata(dev);
if (dd->group) {
if (dd->group->group_id != group_id) {
comp_err(dev, "dai_assign_group(), DAI already in group %d, requested %d",
dd->group->group_id, group_id);
return -EINVAL;
}
/* No need to re-assign to the same group, do nothing */
return 0;
}
dd->group = dai_group_get(group_id, DAI_CREAT);
if (!dd->group) {
comp_err(dev, "dai_assign_group(), failed to assign group %d",
group_id);
return -EINVAL;
}
comp_dbg(dev, "dai_assign_group(), group %d num %d",
group_id, dd->group->num_dais);
/* Register for the atomic trigger event */
notifier_register(dev, dd->group, NOTIFIER_ID_DAI_TRIGGER,
dai_atomic_trigger, 0);
return 0;
}
/* this is called by DMA driver every time descriptor has completed */
static void dai_dma_cb(void *arg, enum notify_id type, void *data)
{
@ -205,6 +241,11 @@ static void dai_free(struct comp_dev *dev)
{
struct dai_data *dd = comp_get_drvdata(dev);
if (dd->group) {
notifier_unregister(dev, dd->group, NOTIFIER_ID_DAI_TRIGGER);
dai_group_put(dd->group);
}
if (dd->chan) {
notifier_unregister(dev, dd->chan, NOTIFIER_ID_DMA_COPY);
dma_channel_put(dd->chan);
@ -588,12 +629,12 @@ static void dai_update_start_position(struct comp_dev *dev)
}
/* used to pass standard and bespoke command (with data) to component */
static int dai_comp_trigger(struct comp_dev *dev, int cmd)
static int dai_comp_trigger_internal(struct comp_dev *dev, int cmd)
{
struct dai_data *dd = comp_get_drvdata(dev);
int ret;
comp_dbg(dev, "dai_comp_trigger(), command = %u", cmd);
comp_dbg(dev, "dai_comp_trigger_internal(), command = %u", cmd);
ret = comp_set_state(dev, cmd);
if (ret < 0)
@ -604,7 +645,7 @@ static int dai_comp_trigger(struct comp_dev *dev, int cmd)
switch (cmd) {
case COMP_TRIGGER_START:
comp_dbg(dev, "dai_comp_trigger(), START");
comp_dbg(dev, "dai_comp_trigger_internal(), START");
/* only start the DAI if we are not XRUN handling */
if (dd->xrun == 0) {
@ -646,17 +687,17 @@ static int dai_comp_trigger(struct comp_dev *dev, int cmd)
dai_update_start_position(dev);
break;
case COMP_TRIGGER_XRUN:
comp_info(dev, "dai_comp_trigger(), XRUN");
comp_info(dev, "dai_comp_trigger_internal(), XRUN");
dd->xrun = 1;
/* fallthrough */
case COMP_TRIGGER_STOP:
comp_dbg(dev, "dai_comp_trigger(), STOP");
comp_dbg(dev, "dai_comp_trigger_internal(), STOP");
ret = dma_stop(dd->chan);
dai_trigger(dd->dai, cmd, dev->direction);
break;
case COMP_TRIGGER_PAUSE:
comp_dbg(dev, "dai_comp_trigger(), PAUSE");
comp_dbg(dev, "dai_comp_trigger_internal(), PAUSE");
ret = dma_pause(dd->chan);
dai_trigger(dd->dai, cmd, dev->direction);
default:
@ -666,6 +707,72 @@ static int dai_comp_trigger(struct comp_dev *dev, int cmd)
return ret;
}
static int dai_comp_trigger(struct comp_dev *dev, int cmd)
{
struct dai_data *dd = comp_get_drvdata(dev);
struct dai_group *group = dd->group;
uint32_t irq_flags;
int ret = 0;
/* DAI not in a group, use normal trigger */
if (!group) {
comp_dbg(dev, "dai_comp_trigger(), non-atomic trigger");
return dai_comp_trigger_internal(dev, cmd);
}
/* DAI is grouped, so only trigger when the entire group is ready */
if (!group->trigger_counter) {
/* First DAI to receive the trigger command,
* prepare for atomic trigger
*/
comp_dbg(dev, "dai_comp_trigger(), begin atomic trigger for group %d",
group->group_id);
group->trigger_cmd = cmd;
group->trigger_counter = group->num_dais - 1;
} else if (group->trigger_cmd != cmd) {
/* Already processing a different trigger command */
comp_err(dev, "dai_comp_trigger(), already processing atomic trigger");
ret = -EAGAIN;
} else {
/* Count down the number of remaining DAIs required
* to receive the trigger command before atomic trigger
* takes place
*/
group->trigger_counter--;
comp_dbg(dev, "dai_comp_trigger(), trigger counter %d, group %d",
group->trigger_counter, group->group_id);
if (!group->trigger_counter) {
/* The counter has reached 0, which means
* all DAIs have received the same trigger command
* and we may begin the actual trigger process
* synchronously.
*/
irq_local_disable(irq_flags);
notifier_event(group, NOTIFIER_ID_DAI_TRIGGER,
BIT(cpu_get_id()), NULL, 0);
irq_local_enable(irq_flags);
/* return error of last trigger */
ret = group->trigger_ret;
}
}
return ret;
}
static void dai_atomic_trigger(void *arg, enum notify_id type, void *data)
{
struct comp_dev *dev = arg;
struct dai_data *dd = comp_get_drvdata(dev);
struct dai_group *group = dd->group;
/* Atomic context set by the last DAI to receive trigger command */
group->trigger_ret = dai_comp_trigger_internal(dev, group->trigger_cmd);
}
/* report xrun occurrence */
static void dai_report_xrun(struct comp_dev *dev, uint32_t bytes)
{
@ -763,6 +870,7 @@ static int dai_config(struct comp_dev *dev, struct sof_ipc_dai_config *config)
struct sof_ipc_comp_dai *dai = COMP_GET_IPC(dev, sof_ipc_comp_dai);
int channel = 0;
int handshake;
int ret = 0;
comp_info(dev, "dai_config() dai type = %d index = %d",
config->type, config->dai_index);
@ -773,6 +881,13 @@ static int dai_config(struct comp_dev *dev, struct sof_ipc_dai_config *config)
return -EINVAL;
}
if (config->group_id) {
ret = dai_assign_group(dev, config->group_id);
if (ret)
return ret;
}
switch (config->type) {
case SOF_DAI_INTEL_SSP:
/* set dma burst elems to slot number */

View File

@ -71,7 +71,8 @@ struct sof_ipc_dai_config {
/* physical protocol and clocking */
uint16_t format; /**< SOF_DAI_FMT_ */
uint16_t reserved16; /**< alignment */
uint8_t group_id; /**< group ID, 0 means no group (ABI 3.17) */
uint8_t reserved8; /**< alignment */
/* reserved for future use */
uint32_t reserved[8];

View File

@ -217,6 +217,65 @@ struct dai_type_info {
trace_dai_get_id, \
trace_dai_get_subid, dai_p, __e, ##__VA_ARGS__)
/**
* \brief API to request DAI group
*
* Returns a DAI group for the given ID and
* increments the counter of DAIs in the group.
*
* If a group for the given ID doesn't exist,
* it will either return NULL or allocate a new group structure
* if the CREATE flag is supplied.
*
* \param[in] group_id Group ID
* \param[in] flags Flags (CREATE)
*/
struct dai_group *dai_group_get(uint32_t group_id, uint32_t flags);
/**
* \brief API to release DAI group
*
* Decrements the DAI counter inside the group.
*
* \param[in] group Group
*/
void dai_group_put(struct dai_group *group);
/**
* \brief DAI group information
*/
struct dai_group {
/**
* Group ID
*/
uint32_t group_id;
/**
* Number of DAIs in this group
*/
uint32_t num_dais;
/**
* Number of DAIs to receive a trigger before processing begins
*/
uint32_t trigger_counter;
/**
* Trigger command to propagate
*/
int trigger_cmd;
/**
* Last trigger error
*/
int trigger_ret;
/**
* Group list
*/
struct list_item list;
};
/**
* \brief Holds information about array of DAIs grouped by type.
*/

View File

@ -33,6 +33,7 @@ enum notify_id {
NOTIFIER_ID_LL_PRE_RUN, /* NULL */
NOTIFIER_ID_LL_POST_RUN, /* NULL */
NOTIFIER_ID_DMA_IRQ, /* struct dma_chan_data * */
NOTIFIER_ID_DAI_TRIGGER, /* struct dai_group * */
NOTIFIER_ID_COUNT
};

View File

@ -5,10 +5,13 @@
// Author: Marcin Maka <marcin.maka@linux.intel.com>
#include <sof/lib/dai.h>
#include <sof/lib/alloc.h>
#include <sof/lib/cpu.h>
#include <sof/lib/memory.h>
#include <sof/lib/uuid.h>
#include <sof/spinlock.h>
#include <sof/trace/trace.h>
#include <ipc/topology.h>
#include <user/trace.h>
#include <errno.h>
#include <stddef.h>
@ -20,6 +23,104 @@ DECLARE_SOF_UUID("dai", dai_uuid, 0x06711c94, 0xd37d, 0x4a76,
DECLARE_TR_CTX(dai_tr, SOF_UUID(dai_uuid), LOG_LEVEL_INFO);
struct dai_group_list {
struct list_item list;
} __aligned(PLATFORM_DCACHE_ALIGN);
static struct dai_group_list *groups[PLATFORM_CORE_COUNT];
static struct dai_group_list *dai_group_list_get(int core_id)
{
struct dai_group_list *group_list = groups[core_id];
if (!group_list) {
group_list = rzalloc(SOF_MEM_ZONE_SYS, 0, SOF_MEM_CAPS_RAM,
sizeof(*group_list));
groups[core_id] = group_list;
list_init(&group_list->list);
}
return group_list;
}
static struct dai_group *dai_group_find(uint32_t group_id)
{
struct list_item *dai_groups;
struct list_item *group_item;
struct dai_group *group;
dai_groups = &dai_group_list_get(cpu_get_id())->list;
list_for_item(group_item, dai_groups) {
group = container_of(group_item, struct dai_group, list);
if (group->group_id == group_id)
break;
group = NULL;
}
return group;
}
static struct dai_group *dai_group_alloc()
{
struct list_item *dai_groups = &dai_group_list_get(cpu_get_id())->list;
struct dai_group *group;
group = rzalloc(SOF_MEM_ZONE_SYS, 0, SOF_MEM_CAPS_RAM,
sizeof(*group));
list_item_prepend(&group->list, dai_groups);
return group;
}
struct dai_group *dai_group_get(uint32_t group_id, uint32_t flags)
{
struct dai_group *group;
if (!group_id) {
tr_err(&dai_tr, "dai_group_get(): invalid group_id %u",
group_id);
return NULL;
}
/* Check if this group already exists */
group = dai_group_find(group_id);
/* Check if there's a released and now unused group */
if (!group)
group = dai_group_find(0);
/* Group doesn't exist, let's allocate and initialize it */
if (!group && flags & DAI_CREAT)
group = dai_group_alloc();
if (group) {
/* Group might've been previously unused */
if (!group->group_id)
group->group_id = group_id;
group->num_dais++;
} else {
tr_err(&dai_tr, "dai_group_get(): failed to get group_id %u",
group_id);
}
return group;
}
void dai_group_put(struct dai_group *group)
{
group->num_dais--;
/* Mark as unused if there are no more DAIs in this group */
if (!group->num_dais)
group->group_id = 0;
}
static inline const struct dai_type_info *dai_find_type(uint32_t type)
{
const struct dai_info *info = dai_info_get();