上篇说到Machine驱动在纯净版的kernel里是找不到的,但是Alsa三驾马车,这个又是很重要的一环,下面就来说说怎么写Machine驱动。
Linux 4.9.123 可从以下地址获得
https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/
本文Codec基于wm8994。
1 从probe开始
由于一般来说Machine驱动自己写的情况很少,所以在这里主要还是以解释流程为主,至于为什么又换了codec这件事我也很无奈,关键是wm8994这个codec(sound/soc/codecs/wm8994.c
)和machine(sound/soc/samsung/smdk_wm8994.c
)在内核中都可以找到:
首先,从probe开始
sound/soc/samsung/smdk_wm8994.c
static const struct of_device_id samsung_wm8994_of_match[] = {
{ .compatible = "samsung,smdk-wm8994", .data = &smdk_board_data },
{},
};
MODULE_DEVICE_TABLE(of, samsung_wm8994_of_match);
static struct platform_driver smdk_audio_driver = {
.driver = {
.name = "smdk-audio-wm8994",
.of_match_table = of_match_ptr(samsung_wm8994_of_match),
.pm = &snd_soc_pm_ops,
},
.probe = smdk_audio_probe,
};
module_platform_driver(smdk_audio_driver);
static int smdk_audio_probe(struct platform_device *pdev)
{
int ret;
struct device_node *np = pdev->dev.of_node;
struct snd_soc_card *card = &smdk;
struct smdk_wm8994_data *board;
const struct of_device_id *id;
card->dev = &pdev->dev;
board = devm_kzalloc(&pdev->dev, sizeof(*board), GFP_KERNEL);
if (!board)
return -ENOMEM;
if (np) {
smdk_dai[0].cpu_dai_name = NULL;
smdk_dai[0].cpu_of_node = of_parse_phandle(np,
"samsung,i2s-controller", 0);
if (!smdk_dai[0].cpu_of_node) {
dev_err(&pdev->dev,
"Property 'samsung,i2s-controller' missing or invalid\n");
ret = -EINVAL;
}
smdk_dai[0].platform_name = NULL;
smdk_dai[0].platform_of_node = smdk_dai[0].cpu_of_node;
}
id = of_match_device(of_match_ptr(samsung_wm8994_of_match), &pdev->dev);
if (id)
*board = *((struct smdk_wm8994_data *)id->data);
platform_set_drvdata(pdev, board);
ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret)
dev_err(&pdev->dev, "snd_soc_register_card() failed:%d\n", ret);
return ret;
}
从probe可以看到,只需要最简单的一个samsung,i2s-controller
和compatible
属性,就可以了。
2 probe流程
- 首先,
card->dev = &pdev->dev;
实例化card - 然后,
board = devm_kzalloc(&pdev->dev, sizeof(*board), GFP_KERNEL);
分配一块内存给&pdev->dev所指向的地址,用来存放board所存储的信息。 - 判断是否已经为platform_device创建节点,如果创建了,就给
smdk_dai
的cpu_dai_name
,cpu_of_node
,platform_name
,platform_of_node
成员赋值,注意smdk_dai[0].platform_of_node = smdk_dai[0].cpu_of_node;
,platform_of_node是依赖于上面解析DTS解析出来的cpu_of_node成员。这一注册platform_device工作是在smdk_audio_init
中完成的,还有的是在soc-core.c中完成的。
if (np) {
smdk_dai[0].cpu_dai_name = NULL;
smdk_dai[0].cpu_of_node = of_parse_phandle(np,
"samsung,i2s-controller", 0);
if (!smdk_dai[0].cpu_of_node) {
dev_err(&pdev->dev,
"Property 'samsung,i2s-controller' missing or invalid\n");
ret = -EINVAL;
}
smdk_dai[0].platform_name = NULL;
smdk_dai[0].platform_of_node = smdk_dai[0].cpu_of_node;
}
- 通过of_match_device(
drivers/of/device.c
)匹配一个of_device_id id。
id = of_match_device(of_match_ptr(samsung_wm8994_of_match), &pdev->dev);
if (id)
*board = *((struct smdk_wm8994_data *)id->data);
- 把board赋给pdev->driver_data
platform_set_drvdata(pdev, board);
- 注册card到platform,完成。
ret = devm_snd_soc_register_card(&pdev->dev, card);
3 devm_snd_soc_register_card(&pdev->dev, card);详细分析
之前已经说过了devm_snd_soc_register_card是Machine驱动最核心的内容,这一个小节我们就来详细的探究一下这个函数。
/**
* devm_snd_soc_register_card - resource managed card registration
* @dev: Device used to manage card
* @card: Card to register
*
* Register a card with automatic unregistration when the device is
* unregistered.
*/
int devm_snd_soc_register_card(struct device *dev, struct snd_soc_card *card)
{
struct snd_soc_card **ptr;
int ret;
...
ret = snd_soc_register_card(card);
...
}
EXPORT_SYMBOL_GPL(devm_snd_soc_register_card);
其中又调用了snd_soc_register_card,其定义如下
/**
* snd_soc_register_card - Register a card with the ASoC core
*
* @card: Card to register
*
*/
int snd_soc_register_card(struct snd_soc_card *card)
{
int i, ret;
struct snd_soc_pcm_runtime *rtd;
if (!card->name || !card->dev)
return -EINVAL;
for (i = 0; i < card->num_links; i++) {
struct snd_soc_dai_link *link = &card->dai_link[i];
ret = soc_init_dai_link(card, link);
if (ret) {
dev_err(card->dev, "ASoC: failed to init link %s\n",
link->name);
return ret;
}
}
dev_set_drvdata(card->dev, card); //
snd_soc_initialize_card_lists(card); //
INIT_LIST_HEAD(&card->dai_link_list); //
card->num_dai_links = 0;
INIT_LIST_HEAD(&card->rtd_list);
card->num_rtd = 0;
INIT_LIST_HEAD(&card->dapm_dirty);
INIT_LIST_HEAD(&card->dobj_list);
card->instantiated = 0;
mutex_init(&card->mutex);
mutex_init(&card->dapm_mutex);
ret = snd_soc_instantiate_card(card);
if (ret != 0)
return ret;
/* deactivate pins to sleep state */
list_for_each_entry(rtd, &card->rtd_list, list) {
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
int j;
for (j = 0; j < rtd->num_codecs; j++) {
struct snd_soc_dai *codec_dai = rtd->codec_dais[j];
if (!codec_dai->active)
pinctrl_pm_select_sleep_state(codec_dai->dev);
}
if (!cpu_dai->active)
pinctrl_pm_select_sleep_state(cpu_dai->dev);
}
return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_register_card);
这一部分内容大家因该很熟悉了吧,前面花了大批时间说dai,dapm之类的概念,原理,在这里全都用上了。
为snd_soc_pcm_runtime数组申请内存,每一个dai_link对应snd_soc_pcm_runtime数组的一个单元,然后把snd_soc_card中的dai_link配置复制到相应的snd_soc_pcm_runtime中,初始化dai_link,初始化dai_link时还初始化了multicodec,最后,大部分的工作都在snd_soc_instantiate_card中实现,这部分由于内容太多,写在一篇文章里太多了,下篇继续。
从snd_soc_instantiate_card函数出来以后就比较简单了,设置codec_dai和cpu_dai的低功耗状态。
drivers/pinctrl/core.c
/**
* pinctrl_pm_select_sleep_state() - select sleep pinctrl state for PM
* @dev: device to select sleep state for
*/
int pinctrl_pm_select_sleep_state(struct device *dev)
{
if (!dev->pins)
return 0;
return pinctrl_pm_select_state(dev, dev->pins->sleep_state);
}
EXPORT_SYMBOL_GPL(pinctrl_pm_select_sleep_state);
至此,machine驱动注册完成。
本篇中的snd_soc_instantiate_card
将在[Alsa]8, wm8524 Machine驱动的编写(2)中详细介绍