Linux カーネル ASoC フレームワークは、概念的に、組み込みオーディオ システムを、コーデック クラス ドライバー、プラットフォーム クラス ドライバー、マシン クラス ドライバーなどの複数の再利用可能なコンポーネント ドライバーに分割します。実装に関しては、マシン クラスのドライバーは 、struct snd_soc_card
のstruct snd_soc_dai_link
構造体で記述され、プラットフォーム クラスのドライバーに属する DMA エンジン ドライバーは のstruct snd_soc_component_driver
構造体で記述され、コーデック クラスのドライバーと I2S は 、その他のドライバーは 、struct snd_soc_component_driver
、struct snd_soc_dai_driver
のような構造体で記述されますstruct snd_soc_dai_ops
。プラットフォーム クラス ドライバーを除くさまざまなドライバーは、コンポーネント抽象化を通じてまとめて編成されます。つまり、これらのドライバーは、グローバル リンク リストstruct snd_soc_component_driver
Linux カーネル ASoC フレームワークにstruct snd_soc_component
と同様に。sound/soc/soc-core.c
component_list
DMA ドライバーの例は次のとおりです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);
devm_snd_soc_register_component()
DMA エンジン ドライバーは I2S ドライバーやコーデック ドライバーと同じで、 Linux カーネル ASoC フレームワークにfunction形式で登録されますが、特別struct snd_soc_component_driver
な点は、その dai ドライバー パラメーターが空であることです。
マシン クラス ドライバーによって定義されるstruct snd_soc_dai_link
構造オブジェクトは、struct snd_soc_dai_link_component
それが参照する他のタイプのドライバーを記述します。たとえば、マシン クラス ドライバーにはsound/soc/pxa/e800_wm9712.c
次のコード スニペットがあります。
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()
struct snd_soc_dai_link
マクロは、参照される および etc 配列を簡単に定義するcpus
ために使用されます。ここで、 は、上で見た DMA エンジン ドライバーの参照を定義するために使用されますcodecs
。platforms
struct snd_soc_dai_link_component
platforms
DAILINK_COMP_ARRAY(COMP_PLATFORM("pxa-pcm-audio"))
Linux カーネル ASoC フレームワークは、sound/soc/soc-generic-dmaengine-pcm
ファイル にある汎用 DMA エンジン ドライバーを提供します。このドライバー自体は、Linux カーネル ASoC フレームワークに積極的に登録されません。デバイスとメモリ間でデータを転送するために DMA エンジンを使用する必要があるドライバーは、次のように、同時に登録する必要がありprobe
ます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);
ここでの関数は、 rockchip_pcm_platform_register()
I2S ドライバーのprobe
操作で呼び出されます (次の場所sound/soc/rockchip/rockchip_i2s.c
にあります)。
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()
関数呼び出しdevm_snd_dmaengine_pcm_register()
関数は、汎用 DMA エンジン ドライバーを登録します。devm_snd_dmaengine_pcm_register()
関数定義 (located sound/soc/soc-devres.c
) は次のとおりです。
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()
と同じです。関数定義 (located ) は次のとおりです。devm_snd_soc_register_component()
snd_dmaengine_pcm_register()
snd_dmaengine_pcm_register()
sound/soc/soc-generic-dmaengine-pcm.c
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()
関数の実行プロセスは次のとおりです。
- 構造体オブジェクトを動的に割り当て
struct dmaengine_pcm
、そのフィールドconfig
とflags
フィールドを初期化します。struct dmaengine_pcm
構造体定義 (locatedinclude/sound/dmaengine_pcm.h
) は次のとおりです。
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;
};
この構造体にはstruct snd_soc_component
構造体メンバーがあります。
- データ送受信用の DMA チャネルを申請するには、デバイスツリーのデバイスノード定義に送受信で参照する DMA チャネルを次のように指定する必要があります。
i2s0_8ch: i2s@fe470000 {
. . . . . .
dmas = <&dmac0 0>, <&dmac0 1>;
dma-names = "tx", "rx";
. . . . . .
};
-
で渡されたパラメータによると
config
、選択と選択の唯一の違いは、前者ではもう 1 つの操作が定義されていることstruct snd_soc_component_driver
です。dmaengine_pcm_component_process
dmaengine_pcm_component
copy_user
-
struct オブジェクトを初期化して追加するには、 struct object 経由で取得できる
struct snd_soc_component
関数 ( にありますinclude/sound/dmaengine_pcm.h
)があります。struct snd_soc_component
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);
}
汎用 DMA エンジン ドライバーでサポートされる操作は、struct snd_soc_component_driver
によって定義されます。グローバル コンポーネント リストでは、対応するコンポーネントは、struct snd_soc_component
それを登録した開発者によって識別されます。
汎用 DMA エンジン ドライバーを使用する ASoC マシン ドライバーの例 ( にありますsound/soc/rockchip/rockchip_rt5645.c
) は次のとおりです。
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;
. . . . . .
}
ここで dai link に定義されている cpus dai 配列と platforms 配列には空の要素が 1 つだけありますが、操作ではprobe
デバイス ツリーのデバイス ノードの定義に従って対応する要素が見つかりますof_node
。cpus dai とプラットフォームは同じものを参照しますがof_node
、snd_soc_add_pcm_runtime()
関数内でstruct snd_soc_component
CPU DAI を追加するプロセスは、まず対応するものを見つけてから から取得することですstruct snd_soc_dai
。struct snd_soc_dai
つまりstruct snd_soc_component
、CPU DAI を検索しても一般的struct snd_soc_component
なものは見つかりません。 struct snd_soc_dai
DMA エンジンstruct snd_soc_component
。プラットフォームに追加するプロセスstruct snd_soc_component
では、一致するものをすべて直接検索しstruct snd_soc_component
て追加します。
PCM 用に作成するときstruct snd_soc_pcm_runtime
、つまりマシン クラス ドライバーの操作でprobe
、snd_soc_dai_link
CPU DAI とプラットフォームが同じを指すように、これと同様のプロセスが実行されると、of_node
対応するof_node
デバイス ドライバーはユニバーサル DMA エンジン ドライバーを登録します。 , 一般的な DMA エンジンはstruct snd_soc_component
コンポーネント リストに含まれます。
ユニバーサル DMA エンジン ドライバーのstruct snd_soc_component_driver
もう 1 つの特別な特徴は、probe_order
として指定されていることです。これSND_SOC_COMP_ORDER_LATE
により、そのprobe
およびinit
操作は相対的に後で実行され、 のsnd_soc_card
位置も少し遅くなります。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()
マクロ定義 (located )include/sound/soc-component.h
は次のとおりです。
#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++)
Linux カーネル ASoC ユニバーサル DMA エンジン ドライバーの動作
汎用 DMA エンジン ドライバーは次の操作を提供します。
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,
};
これらの操作のうち、最も早く呼び出されるのはpcm_construct
操作、つまりdmaengine_pcm_new()
関数です。マシンクラスドライバーでは、devm_snd_soc_register_card()
サウンドカードを登録する関数を呼び出しますが、この関数ではsnd_soc_register_card()
-> snd_soc_bind_card()
-> soc_init_pcm_runtime()
-> soc_new_pcm()
->という呼び出し処理がありsnd_soc_pcm_component_new()
、snd_soc_pcm_component_new()
関数が各種struct snd_soc_component_driver
操作を行いますpcm_construct
。snd_soc_pcm_component_new()
関数定義 (located sound/soc/soc-component.c
) は次のとおりです。
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;
}
ユニバーサル DMA エンジン ドライバーのpcm_construct
操作関数に戻るとdmaengine_pcm_new()
、この関数はsound/soc/soc-generic-dmaengine-pcm.c
次のように定義 (配置) されます。
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;
}
この関数は、再生および記録用の DMA チャネルにそれぞれ適用され、ユーザー空間アプリケーション、カーネル ALSA/ASoc フレームワーク、およびハードウェア デバイス間のデータ交換用に DMA バッファを割り当てます。snd_pcm_set_managed_buffer()
DMA バッファを割り当てる機能。snd_pcm_set_managed_buffer()
関数定義 (located sound/core/pcm_memory.c
) は次のとおりです。
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);
open
操作dmaengine_pcm_open()
関数は、データ送信前の準備作業を完了する関数であり、次のように定義されます。
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);
}
snd_soc_dai_get_dma_data()
ここでは、タイプ のstruct snd_dmaengine_dai_dma_data
DMA データを取得するために関数が呼び出されます。これは、ユニバーサル DMA エンジン ドライバーと I2S などの DAI ドライバーの間の規則です。つまり、DMAprobe
データは、次のように DAI ドライバーの操作で設定する必要があります。
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()
この関数は主に、ストリームのランタイム ハードウェア パラメーターを設定します。
その他の操作については、現時点ではあまり説明しません。
終わり。