Linux kernel ASoC DMA engine driver

The Linux kernel ASoC framework conceptually splits the embedded audio system into multiple reusable component drivers, including Codec class driver, platform class driver and machine class driver. In terms of implementation, machine class drivers are described by struct snd_soc_cardand struct snd_soc_dai_linkstructures, DMA engine drivers belonging to platform class drivers are struct snd_soc_component_driverdescribed by structures, codec class drivers and I2S drivers are described by structures such as struct snd_soc_component_driver, struct snd_soc_dai_driverand struct snd_soc_dai_ops. Various drivers except platform class drivers are organized together through component abstraction, that is, these drivers are registered struct snd_soc_component_driverto the Linux kernel ASoC framework struct snd_soc_componentas in sound/soc/soc-core.cthe global linked list component_list.

An example of a DMA driver is soc/pxa/pxa2xx-pcm.c:

static const struct snd_soc_component_driver pxa2xx_soc_platform = {
	.pcm_construct	= pxa2xx_soc_pcm_new,
	.pcm_destruct	= pxa2xx_soc_pcm_free,
	.open		= pxa2xx_soc_pcm_open,
	.close		= pxa2xx_soc_pcm_close,
	.hw_params	= pxa2xx_soc_pcm_hw_params,
	.hw_free	= pxa2xx_soc_pcm_hw_free,
	.prepare	= pxa2xx_soc_pcm_prepare,
	.trigger	= pxa2xx_soc_pcm_trigger,
	.pointer	= pxa2xx_soc_pcm_pointer,
	.mmap		= pxa2xx_soc_pcm_mmap,
};

static int pxa2xx_soc_platform_probe(struct platform_device *pdev)
{
	return devm_snd_soc_register_component(&pdev->dev, &pxa2xx_soc_platform,
					       NULL, 0);
}

static struct platform_driver pxa_pcm_driver = {
	.driver = {
		.name = "pxa-pcm-audio",
	},

	.probe = pxa2xx_soc_platform_probe,
};

module_platform_driver(pxa_pcm_driver);

The DMA engine driver is the same as the I2S or Codec driver. It is registered to the Linux kernel ASoC framework in the form of devm_snd_soc_register_component()function struct snd_soc_component_driver, but the special thing is that its dai driver parameter is empty.

struct snd_soc_dai_linkThe structure object defined by a machine class driver struct snd_soc_dai_link_componentdescribes the other types of drivers it references. For example, a machine class driver sound/soc/pxa/e800_wm9712.chas the following code snippet:

SND_SOC_DAILINK_DEFS(ac97,
	DAILINK_COMP_ARRAY(COMP_CPU("pxa2xx-ac97")),
	DAILINK_COMP_ARRAY(COMP_CODEC("wm9712-codec", "wm9712-hifi")),
	DAILINK_COMP_ARRAY(COMP_PLATFORM("pxa-pcm-audio")));

SND_SOC_DAILINK_DEFS(ac97_aux,
	DAILINK_COMP_ARRAY(COMP_CPU("pxa2xx-ac97-aux")),
	DAILINK_COMP_ARRAY(COMP_CODEC("wm9712-codec", "wm9712-aux")),
	DAILINK_COMP_ARRAY(COMP_PLATFORM("pxa-pcm-audio")));

static struct snd_soc_dai_link e800_dai[] = {
	{
		.name = "AC97",
		.stream_name = "AC97 HiFi",
		SND_SOC_DAILINK_REG(ac97),
	},
	{
		.name = "AC97 Aux",
		.stream_name = "AC97 Aux",
		SND_SOC_DAILINK_REG(ac97_aux),
	},
};

static struct snd_soc_card e800 = {
	.name = "Toshiba e800",
	.owner = THIS_MODULE,
	.dai_link = e800_dai,
	.num_links = ARRAY_SIZE(e800_dai),

	.dapm_widgets = e800_dapm_widgets,
	.num_dapm_widgets = ARRAY_SIZE(e800_dapm_widgets),
	.dapm_routes = audio_map,
	.num_dapm_routes = ARRAY_SIZE(audio_map),
};

SND_SOC_DAILINK_DEFS()Macros are used to struct snd_soc_dai_linkconveniently define referenced cpus, codecsand platformsetc struct snd_soc_dai_link_componentarrays, where the used to define platformsreferences DAILINK_COMP_ARRAY(COMP_PLATFORM("pxa-pcm-audio"))the DMA engine driver we saw above.

The Linux kernel ASoC framework provides a generic DMA engine driver, located sound/soc/soc-generic-dmaengine-pcmin file . This driver itself will not actively register itself with the Linux kernel ASoC framework. Drivers that need to use the DMA engine to transfer data between the device and memory must proberegister it at time, such as sound/soc/rockchip/rockchip_pcm.c:

static const struct snd_pcm_hardware snd_rockchip_hardware = {
	.info			= SNDRV_PCM_INFO_MMAP |
				  SNDRV_PCM_INFO_MMAP_VALID |
				  SNDRV_PCM_INFO_PAUSE |
				  SNDRV_PCM_INFO_RESUME |
				  SNDRV_PCM_INFO_INTERLEAVED,
	.period_bytes_min	= 32,
	.period_bytes_max	= 8192,
	.periods_min		= 1,
	.periods_max		= 52,
	.buffer_bytes_max	= 64 * 1024,
	.fifo_size		= 32,
};

static const struct snd_dmaengine_pcm_config rk_dmaengine_pcm_config = {
	.pcm_hardware = &snd_rockchip_hardware,
	.prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
	.prealloc_buffer_size = 32 * 1024,
};

int rockchip_pcm_platform_register(struct device *dev)
{
	return devm_snd_dmaengine_pcm_register(dev, &rk_dmaengine_pcm_config,
		SND_DMAENGINE_PCM_FLAG_COMPAT);
}
EXPORT_SYMBOL_GPL(rockchip_pcm_platform_register);

The function here is called rockchip_pcm_platform_register()in the I2S driver's probeoperation (located sound/soc/rockchip/rockchip_i2s.cin:

static int rockchip_i2s_probe(struct platform_device *pdev)
{
 . . . . . .

	ret = devm_snd_soc_register_component(&pdev->dev,
					      &rockchip_i2s_component,
					      soc_dai, 1);

	if (ret) {
		dev_err(&pdev->dev, "Could not register DAI\n");
		goto err_suspend;
	}

	ret = rockchip_pcm_platform_register(&pdev->dev);
	if (ret) {
		dev_err(&pdev->dev, "Could not register PCM\n");
		goto err_suspend;
	}

	return 0;
 . . . . . .
	return ret;
}

rockchip_pcm_platform_register()Function Call devm_snd_dmaengine_pcm_register()Function registers the generic DMA engine driver. devm_snd_dmaengine_pcm_register()The function definition (located sound/soc/soc-devres.c) is as follows:

static void devm_dmaengine_pcm_release(struct device *dev, void *res)
{
	snd_dmaengine_pcm_unregister(*(struct device **)res);
}

/**
 * devm_snd_dmaengine_pcm_register - resource managed dmaengine PCM registration
 * @dev: The parent device for the PCM device
 * @config: Platform specific PCM configuration
 * @flags: Platform specific quirks
 *
 * Register a dmaengine based PCM device with automatic unregistration when the
 * device is unregistered.
 */
int devm_snd_dmaengine_pcm_register(struct device *dev,
	const struct snd_dmaengine_pcm_config *config, unsigned int flags)
{
	struct device **ptr;
	int ret;

	ptr = devres_alloc(devm_dmaengine_pcm_release, sizeof(*ptr), GFP_KERNEL);
	if (!ptr)
		return -ENOMEM;

	ret = snd_dmaengine_pcm_register(dev, config, flags);
	if (ret == 0) {
		*ptr = dev;
		devres_add(dev, ptr);
	} else {
		devres_free(ptr);
	}

	return ret;
}
EXPORT_SYMBOL_GPL(devm_snd_dmaengine_pcm_register);

#endif

devm_snd_dmaengine_pcm_register()devm_snd_soc_register_card()The function is the same as the function we saw before devm_snd_soc_register_component(), except that it encapsulates snd_dmaengine_pcm_register()a function. snd_dmaengine_pcm_register()The function definition (located sound/soc/soc-generic-dmaengine-pcm.c) is as follows:

static const struct snd_soc_component_driver dmaengine_pcm_component = {
	.name		= SND_DMAENGINE_PCM_DRV_NAME,
	.probe_order	= SND_SOC_COMP_ORDER_LATE,
	.open		= dmaengine_pcm_open,
	.close		= dmaengine_pcm_close,
	.hw_params	= dmaengine_pcm_hw_params,
	.trigger	= dmaengine_pcm_trigger,
	.pointer	= dmaengine_pcm_pointer,
	.pcm_construct	= dmaengine_pcm_new,
};

static const struct snd_soc_component_driver dmaengine_pcm_component_process = {
	.name		= SND_DMAENGINE_PCM_DRV_NAME,
	.probe_order	= SND_SOC_COMP_ORDER_LATE,
	.open		= dmaengine_pcm_open,
	.close		= dmaengine_pcm_close,
	.hw_params	= dmaengine_pcm_hw_params,
	.trigger	= dmaengine_pcm_trigger,
	.pointer	= dmaengine_pcm_pointer,
	.copy_user	= dmaengine_copy_user,
	.pcm_construct	= dmaengine_pcm_new,
};

static const char * const dmaengine_pcm_dma_channel_names[] = {
	[SNDRV_PCM_STREAM_PLAYBACK] = "tx",
	[SNDRV_PCM_STREAM_CAPTURE] = "rx",
};

static int dmaengine_pcm_request_chan_of(struct dmaengine_pcm *pcm,
	struct device *dev, const struct snd_dmaengine_pcm_config *config)
{
	unsigned int i;
	const char *name;
	struct dma_chan *chan;

	if ((pcm->flags & SND_DMAENGINE_PCM_FLAG_NO_DT) || (!dev->of_node &&
	    !(config && config->dma_dev && config->dma_dev->of_node)))
		return 0;

	if (config && config->dma_dev) {
		/*
		 * If this warning is seen, it probably means that your Linux
		 * device structure does not match your HW device structure.
		 * It would be best to refactor the Linux device structure to
		 * correctly match the HW structure.
		 */
		dev_warn(dev, "DMA channels sourced from device %s",
			 dev_name(config->dma_dev));
		dev = config->dma_dev;
	}

	for_each_pcm_streams(i) {
		if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX)
			name = "rx-tx";
		else
			name = dmaengine_pcm_dma_channel_names[i];
		if (config && config->chan_names[i])
			name = config->chan_names[i];
		chan = dma_request_chan(dev, name);
		if (IS_ERR(chan)) {
			/*
			 * Only report probe deferral errors, channels
			 * might not be present for devices that
			 * support only TX or only RX.
			 */
			if (PTR_ERR(chan) == -EPROBE_DEFER)
				return -EPROBE_DEFER;
			pcm->chan[i] = NULL;
		} else {
			pcm->chan[i] = chan;
		}
		if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX)
			break;
	}

	if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX)
		pcm->chan[1] = pcm->chan[0];

	return 0;
}

static void dmaengine_pcm_release_chan(struct dmaengine_pcm *pcm)
{
	unsigned int i;

	for_each_pcm_streams(i) {
		if (!pcm->chan[i])
			continue;
		dma_release_channel(pcm->chan[i]);
		if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX)
			break;
	}
}

/**
 * snd_dmaengine_pcm_register - Register a dmaengine based PCM device
 * @dev: The parent device for the PCM device
 * @config: Platform specific PCM configuration
 * @flags: Platform specific quirks
 */
int snd_dmaengine_pcm_register(struct device *dev,
	const struct snd_dmaengine_pcm_config *config, unsigned int flags)
{
	const struct snd_soc_component_driver *driver;
	struct dmaengine_pcm *pcm;
	int ret;

	pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
	if (!pcm)
		return -ENOMEM;

#ifdef CONFIG_DEBUG_FS
	pcm->component.debugfs_prefix = "dma";
#endif
	pcm->config = config;
	pcm->flags = flags;

	ret = dmaengine_pcm_request_chan_of(pcm, dev, config);
	if (ret)
		goto err_free_dma;

	if (config && config->process)
		driver = &dmaengine_pcm_component_process;
	else
		driver = &dmaengine_pcm_component;

	ret = snd_soc_component_initialize(&pcm->component, driver, dev);
	if (ret)
		goto err_free_dma;

	ret = snd_soc_add_component(&pcm->component, NULL, 0);
	if (ret)
		goto err_free_dma;

	return 0;

err_free_dma:
	dmaengine_pcm_release_chan(pcm);
	kfree(pcm);
	return ret;
}
EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_register);

/**
 * snd_dmaengine_pcm_unregister - Removes a dmaengine based PCM device
 * @dev: Parent device the PCM was register with
 *
 * Removes a dmaengine based PCM device previously registered with
 * snd_dmaengine_pcm_register.
 */
void snd_dmaengine_pcm_unregister(struct device *dev)
{
	struct snd_soc_component *component;
	struct dmaengine_pcm *pcm;

	component = snd_soc_lookup_component(dev, SND_DMAENGINE_PCM_DRV_NAME);
	if (!component)
		return;

	pcm = soc_component_to_pcm(component);

	snd_soc_unregister_component_by_driver(dev, component->driver);
	dmaengine_pcm_release_chan(pcm);
	kfree(pcm);
}
EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_unregister);

MODULE_LICENSE("GPL");

snd_dmaengine_pcm_register()The execution process of the function is as follows:

  1. Dynamically allocate a struct dmaengine_pcmstructure object and initialize its configand flagsfields. struct dmaengine_pcmThe structure definition (located include/sound/dmaengine_pcm.h) is as follows:
struct dmaengine_pcm {
	struct dma_chan *chan[SNDRV_PCM_STREAM_LAST + 1];
	const struct snd_dmaengine_pcm_config *config;
	struct snd_soc_component component;
	unsigned int flags;
};

This structure has a struct snd_soc_componentstructure member;

  1. To apply for a DMA channel for data sending and receiving, you need to specify the DMA channel referenced by sending and receiving in the device node definition of the device tree, as follows:
	i2s0_8ch: i2s@fe470000 {
 . . . . . .
		dmas = <&dmac0 0>, <&dmac0 1>;
		dma-names = "tx", "rx";
 . . . . . .
	};
  1. According to the parameters passed in config, the only difference between selection and struct snd_soc_component_driveris that the former defines one more operation;dmaengine_pcm_component_processdmaengine_pcm_componentcopy_user

  2. To initialize and add struct snd_soc_componenta struct object, there is a function (located in include/sound/dmaengine_pcm.h) that can struct snd_soc_componentbe obtained via the struct object struct dmaengine_pcm:

static inline struct dmaengine_pcm *soc_component_to_pcm(struct snd_soc_component *p)
{
	return container_of(p, struct dmaengine_pcm, component);
}

The operations supported by the generic DMA engine driver are struct snd_soc_component_driverdefined by . In the global component list, the corresponding one struct snd_soc_componentis identified by the dev that registered it.

An ASoC machine driver using the generic DMA engine driver, an example (at sound/soc/rockchip/rockchip_rt5645.c) is as follows:

SND_SOC_DAILINK_DEFS(pcm,
	DAILINK_COMP_ARRAY(COMP_EMPTY()),
	DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "rt5645-aif1")),
	DAILINK_COMP_ARRAY(COMP_EMPTY()));

static struct snd_soc_dai_link rk_dailink = {
	.name = "rt5645",
	.stream_name = "rt5645 PCM",
	.init = rk_init,
	.ops = &rk_aif1_ops,
	/* set rt5645 as slave */
	.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
		SND_SOC_DAIFMT_CBS_CFS,
	SND_SOC_DAILINK_REG(pcm),
};

static struct snd_soc_card snd_soc_card_rk = {
	.name = "I2S-RT5650",
	.owner = THIS_MODULE,
	.dai_link = &rk_dailink,
	.num_links = 1,
	.dapm_widgets = rk_dapm_widgets,
	.num_dapm_widgets = ARRAY_SIZE(rk_dapm_widgets),
	.dapm_routes = rk_audio_map,
	.num_dapm_routes = ARRAY_SIZE(rk_audio_map),
	.controls = rk_mc_controls,
	.num_controls = ARRAY_SIZE(rk_mc_controls),
};
 . . . . . .
static int snd_rk_mc_probe(struct platform_device *pdev)
{
 . . . . . .
	rk_dailink.cpus->of_node = of_parse_phandle(np,
			"rockchip,i2s-controller", 0);
	if (!rk_dailink.cpus->of_node) {
		dev_err(&pdev->dev,
			"Property 'rockchip,i2s-controller' missing or invalid\n");
		ret = -EINVAL;
		goto put_codec_of_node;
	}

	rk_dailink.platforms->of_node = rk_dailink.cpus->of_node;
 . . . . . .
}

There is only one empty element in the cpus dai array and platforms array defined for dai link here, but in the probeoperation, the corresponding one is found according to the definition of the device node in the device tree of_node. Although cpus dai and platforms refer to the same of_node, in snd_soc_add_pcm_runtime()the function, struct snd_soc_componentthe process of adding for CPU DAI is to first find the corresponding one struct snd_soc_dai, and then struct snd_soc_daiobtain it from struct snd_soc_component, which means that when searching for CPU DAI struct snd_soc_component, no struct snd_soc_daigeneral will be found. DMA engine struct snd_soc_component. The process of adding for platforms struct snd_soc_componentis to directly find all matching ones struct snd_soc_componentand add them.

When creating for PCM struct snd_soc_pcm_runtime, that is, in the operation of the machine class driver probe, if snd_soc_dai_linka process similar to this is done for , so that the CPU DAI and platform point to the same of_node, and the corresponding of_nodedevice driver registers the universal DMA engine driver, The general DMA engine's struct snd_soc_componentwill be included in its component list.

struct snd_soc_component_driverAnother special feature of the universal DMA engine driver is that it is specified probe_orderas SND_SOC_COMP_ORDER_LATE, which makes its probeand initoperations, relatively speaking, executed later, and its snd_soc_cardposition in is also a little later. As in soc_probe_link_components()function(located sound/soc/soc-core.c):

static int soc_probe_component(struct snd_soc_card *card,
			       struct snd_soc_component *component)
{
	struct snd_soc_dapm_context *dapm =
		snd_soc_component_get_dapm(component);
	struct snd_soc_dai *dai;
	int probed = 0;
	int ret;

	if (!strcmp(component->name, "snd-soc-dummy"))
		return 0;

	if (component->card) {
		if (component->card != card) {
			dev_err(component->dev,
				"Trying to bind component to card \"%s\" but is already bound to card \"%s\"\n",
				card->name, component->card->name);
			return -ENODEV;
		}
		return 0;
	}

	ret = snd_soc_component_module_get_when_probe(component);
	if (ret < 0)
		return ret;

	component->card = card;
	soc_set_name_prefix(card, component);

	soc_init_component_debugfs(component);

	snd_soc_dapm_init(dapm, card, component);

	ret = snd_soc_dapm_new_controls(dapm,
					component->driver->dapm_widgets,
					component->driver->num_dapm_widgets);

	if (ret != 0) {
		dev_err(component->dev,
			"Failed to create new controls %d\n", ret);
		goto err_probe;
	}

	for_each_component_dais(component, dai) {
		ret = snd_soc_dapm_new_dai_widgets(dapm, dai);
		if (ret != 0) {
			dev_err(component->dev,
				"Failed to create DAI widgets %d\n", ret);
			goto err_probe;
		}
	}

	ret = snd_soc_component_probe(component);
	if (ret < 0) {
		dev_err(component->dev,
			"ASoC: failed to probe component %d\n", ret);
		goto err_probe;
	}
	WARN(dapm->idle_bias_off &&
	     dapm->bias_level != SND_SOC_BIAS_OFF,
	     "codec %s can not start from non-off bias with idle_bias_off==1\n",
	     component->name);
	probed = 1;

	/*
	 * machine specific init
	 * see
	 *	snd_soc_component_set_aux()
	 */
	ret = snd_soc_component_init(component);
	if (ret < 0)
		goto err_probe;

	ret = snd_soc_add_component_controls(component,
					     component->driver->controls,
					     component->driver->num_controls);
	if (ret < 0)
		goto err_probe;

	ret = snd_soc_dapm_add_routes(dapm,
				      component->driver->dapm_routes,
				      component->driver->num_dapm_routes);
	if (ret < 0) {
		if (card->disable_route_checks) {
			dev_info(card->dev,
				 "%s: disable_route_checks set, ignoring errors on add_routes\n",
				 __func__);
		} else {
			dev_err(card->dev,
				"%s: snd_soc_dapm_add_routes failed: %d\n",
				__func__, ret);
			goto err_probe;
		}
	}

	/* see for_each_card_components */
	list_add(&component->card_list, &card->component_dev_list);

err_probe:
	if (ret < 0)
		soc_remove_component(component, probed);

	return ret;
}
 . . . . . .
static int soc_probe_link_components(struct snd_soc_card *card)
{
	struct snd_soc_component *component;
	struct snd_soc_pcm_runtime *rtd;
	int i, ret, order;

	for_each_comp_order(order) {
		for_each_card_rtds(card, rtd) {
			for_each_rtd_components(rtd, i, component) {
				if (component->driver->probe_order != order)
					continue;

				ret = soc_probe_component(card, component);
				if (ret < 0)
					return ret;
			}
		}
	}

	return 0;
}

for_each_comp_order()The macro definition (located ) used here include/sound/soc-component.his as follows:

#define SND_SOC_COMP_ORDER_FIRST	-2
#define SND_SOC_COMP_ORDER_EARLY	-1
#define SND_SOC_COMP_ORDER_NORMAL	 0
#define SND_SOC_COMP_ORDER_LATE		 1
#define SND_SOC_COMP_ORDER_LAST		 2

#define for_each_comp_order(order)		\
	for (order  = SND_SOC_COMP_ORDER_FIRST;	\
	     order <= SND_SOC_COMP_ORDER_LAST;	\
	     order++)

Operation of the Linux kernel ASoC universal DMA engine driver

The generic DMA engine driver provides the following operations:

static const struct snd_soc_component_driver dmaengine_pcm_component = {
	.name		= SND_DMAENGINE_PCM_DRV_NAME,
	.probe_order	= SND_SOC_COMP_ORDER_LATE,
	.open		= dmaengine_pcm_open,
	.close		= dmaengine_pcm_close,
	.hw_params	= dmaengine_pcm_hw_params,
	.trigger	= dmaengine_pcm_trigger,
	.pointer	= dmaengine_pcm_pointer,
	.pcm_construct	= dmaengine_pcm_new,
};

static const struct snd_soc_component_driver dmaengine_pcm_component_process = {
	.name		= SND_DMAENGINE_PCM_DRV_NAME,
	.probe_order	= SND_SOC_COMP_ORDER_LATE,
	.open		= dmaengine_pcm_open,
	.close		= dmaengine_pcm_close,
	.hw_params	= dmaengine_pcm_hw_params,
	.trigger	= dmaengine_pcm_trigger,
	.pointer	= dmaengine_pcm_pointer,
	.copy_user	= dmaengine_copy_user,
	.pcm_construct	= dmaengine_pcm_new,
};

Among these operations, the earliest one to be called is pcm_constructthe operation, which is dmaengine_pcm_new()the function. In the machine class driver, call devm_snd_soc_register_card()the function to register the sound card. In this function, there is the following calling process, snd_soc_register_card()-> snd_soc_bind_card()-> soc_init_pcm_runtime()-> soc_new_pcm()-> snd_soc_pcm_component_new(), and snd_soc_pcm_component_new()the function performs various struct snd_soc_component_driveroperations pcm_construct. snd_soc_pcm_component_new()The function definition (located sound/soc/soc-component.c) is as follows:

int snd_soc_pcm_component_new(struct snd_soc_pcm_runtime *rtd)
{
	struct snd_soc_component *component;
	int ret;
	int i;

	for_each_rtd_components(rtd, i, component) {
		if (component->driver->pcm_construct) {
			ret = component->driver->pcm_construct(component, rtd);
			if (ret < 0)
				return soc_component_ret(component, ret);
		}
	}

	return 0;
}

pcm_constructReturning to the operation function of the universal DMA engine driver dmaengine_pcm_new(), this function is defined (located sound/soc/soc-generic-dmaengine-pcm.c) as follows:

static int dmaengine_pcm_new(struct snd_soc_component *component,
			     struct snd_soc_pcm_runtime *rtd)
{
	struct dmaengine_pcm *pcm = soc_component_to_pcm(component);
	const struct snd_dmaengine_pcm_config *config = pcm->config;
	struct device *dev = component->dev;
	struct snd_pcm_substream *substream;
	size_t prealloc_buffer_size;
	size_t max_buffer_size;
	unsigned int i;

	if (config && config->prealloc_buffer_size) {
		prealloc_buffer_size = config->prealloc_buffer_size;
		max_buffer_size = config->pcm_hardware->buffer_bytes_max;
	} else {
		prealloc_buffer_size = 512 * 1024;
		max_buffer_size = SIZE_MAX;
	}

	for_each_pcm_streams(i) {
		substream = rtd->pcm->streams[i].substream;
		if (!substream)
			continue;

		if (!pcm->chan[i] && config && config->chan_names[i])
			pcm->chan[i] = dma_request_slave_channel(dev,
				config->chan_names[i]);

		if (!pcm->chan[i] && (pcm->flags & SND_DMAENGINE_PCM_FLAG_COMPAT)) {
			pcm->chan[i] = dmaengine_pcm_compat_request_channel(
				component, rtd, substream);
		}

		if (!pcm->chan[i]) {
			dev_err(component->dev,
				"Missing dma channel for stream: %d\n", i);
			return -EINVAL;
		}

		snd_pcm_set_managed_buffer(substream,
				SNDRV_DMA_TYPE_DEV_IRAM,
				dmaengine_dma_dev(pcm, substream),
				prealloc_buffer_size,
				max_buffer_size);

		if (!dmaengine_pcm_can_report_residue(dev, pcm->chan[i]))
			pcm->flags |= SND_DMAENGINE_PCM_FLAG_NO_RESIDUE;

		if (rtd->pcm->streams[i].pcm->name[0] == '\0') {
			strscpy_pad(rtd->pcm->streams[i].pcm->name,
				    rtd->pcm->streams[i].pcm->id,
				    sizeof(rtd->pcm->streams[i].pcm->name));
		}
	}

	return 0;
}

This function applies for DMA channels for playback and recording respectively, and allocates DMA buffer for data exchange between user space applications, kernel ALSA/ASoc framework and hardware devices. snd_pcm_set_managed_buffer()Function allocates DMA buffer. snd_pcm_set_managed_buffer()The function definition (located sound/core/pcm_memory.c) is as follows:

static void preallocate_pages(struct snd_pcm_substream *substream,
			      int type, struct device *data,
			      size_t size, size_t max, bool managed)
{
	if (snd_BUG_ON(substream->dma_buffer.dev.type))
		return;

	substream->dma_buffer.dev.type = type;
	substream->dma_buffer.dev.dev = data;

	if (size > 0 && preallocate_dma && substream->number < maximum_substreams)
		preallocate_pcm_pages(substream, size);

	if (substream->dma_buffer.bytes > 0)
		substream->buffer_bytes_max = substream->dma_buffer.bytes;
	substream->dma_max = max;
	if (max > 0)
		preallocate_info_init(substream);
	if (managed)
		substream->managed_buffer_alloc = 1;
}
 . . . . . .
void snd_pcm_set_managed_buffer(struct snd_pcm_substream *substream, int type,
				struct device *data, size_t size, size_t max)
{
	preallocate_pages(substream, type, data, size, max, true);
}
EXPORT_SYMBOL(snd_pcm_set_managed_buffer);

openThe operation dmaengine_pcm_open()function completes the preparation work before data transmission. This function is defined as follows:

static int
dmaengine_pcm_set_runtime_hwparams(struct snd_soc_component *component,
				   struct snd_pcm_substream *substream)
{
	struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
	struct dmaengine_pcm *pcm = soc_component_to_pcm(component);
	struct device *dma_dev = dmaengine_dma_dev(pcm, substream);
	struct dma_chan *chan = pcm->chan[substream->stream];
	struct snd_dmaengine_dai_dma_data *dma_data;
	struct snd_pcm_hardware hw;

	if (rtd->num_cpus > 1) {
		dev_err(rtd->dev,
			"%s doesn't support Multi CPU yet\n", __func__);
		return -EINVAL;
	}

	if (pcm->config && pcm->config->pcm_hardware)
		return snd_soc_set_runtime_hwparams(substream,
				pcm->config->pcm_hardware);

	dma_data = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream);

	memset(&hw, 0, sizeof(hw));
	hw.info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
			SNDRV_PCM_INFO_INTERLEAVED;
	hw.periods_min = 2;
	hw.periods_max = UINT_MAX;
	hw.period_bytes_min = 256;
	hw.period_bytes_max = dma_get_max_seg_size(dma_dev);
	hw.buffer_bytes_max = SIZE_MAX;
	hw.fifo_size = dma_data->fifo_size;

	if (pcm->flags & SND_DMAENGINE_PCM_FLAG_NO_RESIDUE)
		hw.info |= SNDRV_PCM_INFO_BATCH;

	/**
	 * FIXME: Remove the return value check to align with the code
	 * before adding snd_dmaengine_pcm_refine_runtime_hwparams
	 * function.
	 */
	snd_dmaengine_pcm_refine_runtime_hwparams(substream,
						  dma_data,
						  &hw,
						  chan);

	return snd_soc_set_runtime_hwparams(substream, &hw);
}

static int dmaengine_pcm_open(struct snd_soc_component *component,
			      struct snd_pcm_substream *substream)
{
	struct dmaengine_pcm *pcm = soc_component_to_pcm(component);
	struct dma_chan *chan = pcm->chan[substream->stream];
	int ret;

	ret = dmaengine_pcm_set_runtime_hwparams(component, substream);
	if (ret)
		return ret;

	return snd_dmaengine_pcm_open(substream, chan);
}

The function is called here to obtain DMA data snd_soc_dai_get_dma_data()of type . struct snd_dmaengine_dai_dma_dataThis is a convention between the universal DMA engine driver and DAI drivers such as I2S, that is, the probeDMA data needs to be set in the operation of the DAI driver, similar to the following:

struct i2s_dev {
	struct device *dev;
 . . . . . .
	struct snd_dmaengine_dai_dma_data capture_dma_data;
	struct snd_dmaengine_dai_dma_data playback_dma_data;
 . . . . . .
}
 . . . . . .
static int i2s_dai_probe(struct snd_soc_dai *dai)
{
	struct i2s_dev *i2s = snd_soc_dai_get_drvdata(dai);

	snd_soc_dai_init_dma_data(dai,
		i2s->has_playback ? &i2s->playback_dma_data : NULL,
		i2s->has_capture  ? &i2s->capture_dma_data  : NULL);

	return 0;
}
 . . . . . .
static int i2s_init_dai(struct i2s_dev *i2s, struct resource *res,
								 struct snd_soc_dai_driver **dp)
{
 . . . . . .
		i2s->playback_dma_data.addr = res->start + I2S_TXFIFODATA;
		i2s->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
		i2s->playback_dma_data.maxburst = 8;

 . . . . . .
		i2s->capture_dma_data.addr = res->start + I2S_RXFIFODATA;
		i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
		i2s->capture_dma_data.maxburst = 8;
 . . . . . .
}

dmaengine_pcm_open()The function mainly sets the runtime hardware parameters for the stream.

For other operations, not much explanation will be given for the time being.

Done.

Guess you like

Origin blog.csdn.net/tq08g2z/article/details/131991448