[Alsa]8, Machine驱动的编写(1)

上篇说到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-controllercompatible属性,就可以了。

2 probe流程

  1. 首先,card->dev = &pdev->dev; 实例化card
  2. 然后,board = devm_kzalloc(&pdev->dev, sizeof(*board), GFP_KERNEL);分配一块内存给&pdev->dev所指向的地址,用来存放board所存储的信息。
  3. 判断是否已经为platform_device创建节点,如果创建了,就给smdk_daicpu_dai_namecpu_of_nodeplatform_nameplatform_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;
  }

  1. 通过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);
  1. 把board赋给pdev->driver_data
platform_set_drvdata(pdev, board);
  1. 注册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)中详细介绍

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山猫Show

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值