ALSA声卡驱动–7:ASoC架构中的Codec

“本文转载自:[DroidPhone]的Linux ALSA声卡驱动之七:ASoC架构中的Codec”

1.Codec简介

  在移动设备中,Codec的作用可以归结为4种,分别是:

  • 对PCM等信号进行D/A转换,把数字的音频信号转换为模拟信号;
  • 对Mic、Linein或者其他输入源的模拟信号进行A/D转换,把模拟的声音信号转变CPU能够处理的数字信号;
  • 对音频通路进行控制,列如播放音乐,收听调频收音机,又或者接听电话时,音频信号在codec内的流通路线是不一样的;
  • 对音频信号做出相应的处理,例如音量控制,功率放大,EQ控制等等。

  ASoC对Codec的这些功能都定义好了一些列相应的接口,以方便地对Codec进行控制。ASoC对Codec驱动的一个基本要求是:驱动程序的代码必须要做到平台无关性,以方便同一个Codec的代码不经修改即可用在不同的平台上。

2.ASoC中对Codec的数据抽象

  描述Codec的最主要的几个数据结构分别是:snd_soc_codec,snd_soc_codec_driver,snd_soc_dai,snd_soc_dai_driver,其中的snd_soc_dai和snd_soc_dai_driver在ASoC的Platform驱动中也会使用到,Platform和Codec的DAI通过snd_soc_dai_link结构,在Machine驱动中进行绑定连接。结构体定义在include/sound/soc.h。

2.1 snd_soc_codec

/* SoC Audio Codec device */
struct snd_soc_codec {
        struct device *dev; // 指向Codec设备的指针
        const struct snd_soc_codec_driver *driver; // 指向该codec的驱动的指针

        struct list_head list;

        /* runtime */
        unsigned int cache_init:1; /* codec cache has been initialized */

        /* codec IO */
        // 该指针指向的结构用于对codec的控制,一般和read,write字段联合使用
        void *control_data; /* codec control (i2c/3wire) data */
        hw_write_t hw_write;
        void *reg_cache;

        /* component */
        struct snd_soc_component component;
};

其中,component中也包含了许多的参数:

struct snd_soc_component {
        const char *name; // Codec的名字
        int id;
        const char *name_prefix;
        struct device *dev;
        struct snd_soc_card *card; // 指向Machine驱动的card实例

        unsigned int active;

        unsigned int ignore_pmdown_time:1; /* pmdown_time is ignored at stop */
        unsigned int registered_as_component:1;
        unsigned int suspended:1; /* is in suspend PM state */

        struct list_head list;
        struct list_head card_aux_list; /* for auxiliary bound components */
        struct list_head card_list;

        struct snd_soc_dai_driver *dai_drv;
        int num_dai;//Codec数字接口的个数,目前越来越多的Codec带有多个I2S或者是PCM接口

        const struct snd_soc_component_driver *driver;

        struct list_head dai_list;

        // 读取Codec寄存器的函数
        int (*read)(struct snd_soc_component *, unsigned int, unsigned int *);
        // 写入Codec寄存器的函数
        int (*write)(struct snd_soc_component *, unsigned int, unsigned int);

        struct regmap *regmap;
        int val_bytes;

        struct mutex io_mutex;

        /* attached dynamic objects */
        struct list_head dobj_list;

#ifdef CONFIG_DEBUG_FS
        struct dentry *debugfs_root;
#endif

        /*
        * DO NOT use any of the fields below in drivers, they are temporary and
        * are going to be removed again soon. If you use them in driver code the
        * driver will be marked as BROKEN when these fields are removed.
        */

        /* Don t use these, use snd_soc_component_get_dapm() */
        struct snd_soc_dapm_context dapm; // 用于DAPM控件

        struct snd_soc_codec *codec;

        int (*probe)(struct snd_soc_component *);
        void (*remove)(struct snd_soc_component *);
        int (*suspend)(struct snd_soc_component *);
        int (*resume)(struct snd_soc_component *);
        int (*pcm_new)(struct snd_soc_component *, struct snd_soc_pcm_runtime *);
        void (*pcm_free)(struct snd_soc_component *, struct snd_pcm *);

        // 时钟配置函数
        int (*set_sysclk)(struct snd_soc_component *component,
                          int clk_id, int source, unsigned int freq, int dir);
        // 锁相环配置函数
        int (*set_pll)(struct snd_soc_component *component, int pll_id,
                       int source, unsigned int freq_in, unsigned int freq_out);
        int (*set_jack)(struct snd_soc_component *component,
                        struct snd_soc_jack *jack,  void *data);
        int (*set_bias_level)(struct snd_soc_component *component,
                              enum snd_soc_bias_level level);

        /* machine specific init */
        int (*init)(struct snd_soc_component *component);

#ifdef CONFIG_DEBUG_FS
        void (*init_debugfs)(struct snd_soc_component *component);
        const char *debugfs_prefix;
#endif
};

2.2 snd_soc_codec_driver

/* codec driver */
struct snd_soc_codec_driver {

        /* driver ops */
        // codec驱动的probe函数,由snd_soc_instantiate_card回调
        int (*probe)(struct snd_soc_codec *);
        int (*remove)(struct snd_soc_codec *);
        int (*suspend)(struct snd_soc_codec *); // 电源管理
        int (*resume)(struct snd_soc_codec *);
        struct snd_soc_component_driver component_driver;

        /* codec wide operations */
        int (*set_sysclk)(struct snd_soc_codec *codec,
                          int clk_id, int source, unsigned int freq, int dir);
        int (*set_pll)(struct snd_soc_codec *codec, int pll_id, int source,
                unsigned int freq_in, unsigned int freq_out);
        int (*set_jack)(struct snd_soc_codec *codec,
                        struct snd_soc_jack *jack,  void *data);

        /* codec IO */
        struct regmap *(*get_regmap)(struct device *);
        unsigned int (*read)(struct snd_soc_codec *, unsigned int);
        int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);
        unsigned int reg_cache_size;
        short reg_cache_step;
        short reg_word_size;
        const void *reg_cache_default;

        /* codec bias level */
        int (*set_bias_level)(struct snd_soc_codec *,
                              enum snd_soc_bias_level level);
        bool idle_bias_off;
        bool suspend_bias_off;

        void (*seq_notifier)(struct snd_soc_dapm_context *,
                             enum snd_soc_dapm_type, int);

        bool ignore_pmdown_time;  /* Doesn t benefit from pmdown delay */
};

同样许多参数都包含在snd_soc_component_driver

/* component interface */
struct snd_soc_component_driver {
   const char *name;

   /* Default control and setup, added after probe() is run */
   const struct snd_kcontrol_new *controls; // 音频控件指针
   unsigned int num_controls;
   const struct snd_soc_dapm_widget *dapm_widgets;
   unsigned int num_dapm_widgets;
   const struct snd_soc_dapm_route *dapm_routes;
   unsigned int num_dapm_routes;

   int (*probe)(struct snd_soc_component *);
   void (*remove)(struct snd_soc_component *);
   int (*suspend)(struct snd_soc_component *);
   int (*resume)(struct snd_soc_component *);

   /* pcm creation and destruction */
   int (*pcm_new)(struct snd_soc_pcm_runtime *);
   void (*pcm_free)(struct snd_pcm *);

   /* component wide operations */
   int (*set_sysclk)(struct snd_soc_component *component,
           int clk_id, int source, unsigned int freq, int dir);
   int (*set_pll)(struct snd_soc_component *component, int pll_id,
             int source, unsigned int freq_in, unsigned int freq_out);
   int (*set_jack)(struct snd_soc_component *component,
         struct snd_soc_jack *jack,  void *data);

   /* DT */
   int (*of_xlate_dai_name)(struct snd_soc_component *component,
             struct of_phandle_args *args,
             const char **dai_name);
   int (*of_xlate_dai_id)(struct snd_soc_component *comment,
                struct device_node *endpoint);
   void (*seq_notifier)(struct snd_soc_component *, enum snd_soc_dapm_type,
      int subseq);
   int (*stream_event)(struct snd_soc_component *, int event);
   int (*set_bias_level)(struct snd_soc_component *component,
               enum snd_soc_bias_level level);

   const struct snd_pcm_ops *ops;
   const struct snd_compr_ops *compr_ops;

   /* probe ordering - for components with runtime dependencies */
   int probe_order;
   int remove_order;

   /* bits */
   unsigned int idle_bias_on:1;
   unsigned int suspend_bias_off:1;
   unsigned int pmdown_time:1; /* care pmdown_time at stop */
   unsigned int endianness:1;
   unsigned int non_legacy_dai_naming:1;
};

2.3 snd_soc_dai

  • include/sound/soc-dai.h

/*
 * Digital Audio Interface runtime data.
 *
 * Holds runtime data for a DAI.
 */
struct snd_soc_dai {
        const char *name; // dai的名字
        int id;
        struct device *dev; // 设备指针

        /* driver ops */
        struct snd_soc_dai_driver *driver; // 指向dai驱动结构的指针

        /* DAI runtime info */
        unsigned int capture_active:1;                /* stream is in use */
        unsigned int playback_active:1;                /* stream is in use */
        unsigned int symmetric_rates:1;
        unsigned int symmetric_channels:1;
        unsigned int symmetric_samplebits:1;
        unsigned int probed:1;

        unsigned int active;

        struct snd_soc_dapm_widget *playback_widget;
        struct snd_soc_dapm_widget *capture_widget;

        /* DAI DMA data */
        void *playback_dma_data; // 用于管理playback dma
        void *capture_dma_data; // 用于管理capture dma

        /* Symmetry data - only valid if symmetry is being enforced */
        unsigned int rate;
        unsigned int channels;
        unsigned int sample_bits;

        /* parent platform/codec */
        struct snd_soc_codec *codec;
        struct snd_soc_component *component;

        /* CODEC TDM slot masks and params (for fixup) */
        unsigned int tx_mask;
        unsigned int rx_mask;

        struct list_head list;
};

2.4 snd_soc_dai_driver

struct snd_soc_dai_driver {
        /* DAI description */
        const char *name;
        unsigned int id;
        unsigned int base;
        struct snd_soc_dobj dobj;

        /* DAI driver callbacks */
        int (*probe)(struct snd_soc_dai *dai);
        int (*remove)(struct snd_soc_dai *dai);
        int (*suspend)(struct snd_soc_dai *dai);
        int (*resume)(struct snd_soc_dai *dai);
        /* compress dai */
        int (*compress_new)(struct snd_soc_pcm_runtime *rtd, int num);
        /* Optional Callback used at pcm creation*/
        int (*pcm_new)(struct snd_soc_pcm_runtime *rtd,
                       struct snd_soc_dai *dai);
        /* DAI is also used for the control bus */
        bool bus_control;

        /* ops */
        const struct snd_soc_dai_ops *ops;
        const struct snd_soc_cdai_ops *cops;

        /* DAI capabilities */
        struct snd_soc_pcm_stream capture;
        struct snd_soc_pcm_stream playback;
        unsigned int symmetric_rates:1;
        unsigned int symmetric_channels:1;
        unsigned int symmetric_samplebits:1;

        /* probe ordering - for components with runtime dependencies */
        int probe_order;
        int remove_order;
};

3.Codec的注册

  由于Codec驱动的代码要做到平台无关性,要使得Machine驱动能够使用该Codec,Codec驱动的首要任务就是确定snd_soc_codec和snd_soc_dai的实例,并把它们注册到系统中,注册后的codec和dai才能为Machine驱动所用。以WM8994为例,对应的代码位置:/sound/soc/codecs/wm8994.c,模块的入口函数注册了一个platform driver:


static struct platform_driver wm8994_codec_driver = {
   .driver = {
      .name = "wm8994-codec",
      .pm = &wm8994_pm_ops,
   },
   .probe = wm8994_probe,
   .remove = wm8994_remove,
};

module_platform_driver(wm8994_codec_driver);

  有platform driver,必定会有相应的platform device,这个platform device的来源后面再说,显然,platform driver注册后,probe回调将会被调用,这里是wm8994_probe函数:

static int wm8994_probe(struct platform_device *pdev)
{
        struct wm8994_priv *wm8994;

        wm8994 = devm_kzalloc(&pdev->dev, sizeof(struct wm8994_priv),
                              GFP_KERNEL);
        if (wm8994 == NULL)
                return -ENOMEM;
        platform_set_drvdata(pdev, wm8994);

        mutex_init(&wm8994->fw_lock);

        wm8994->wm8994 = dev_get_drvdata(pdev->dev.parent);

        pm_runtime_enable(&pdev->dev);
        pm_runtime_idle(&pdev->dev);

        return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm8994,
                        wm8994_dai, ARRAY_SIZE(wm8994_dai));
}

其中,soc_codec_dev_wm8994和wm8994_dai的定义如下(代码中定义了3个dai,这里只列出第一个):

static const struct snd_soc_codec_driver soc_codec_dev_wm8994 = {
        .probe =        wm8994_codec_probe,
        .remove =        wm8994_codec_remove,
        .suspend =        wm8994_codec_suspend,
        .resume =        wm8994_codec_resume,
        .get_regmap =   wm8994_get_regmap,
        .set_bias_level = wm8994_set_bias_level,
};

static struct snd_soc_dai_driver wm8994_dai[] = {
        {
                .name = "wm8994-aif1",
                .id = 1,
                .playback = {
                        .stream_name = "AIF1 Playback",
                        .channels_min = 1,
                        .channels_max = 2,
                        .rates = WM8994_RATES,
                        .formats = WM8994_FORMATS,
                        .sig_bits = 24,
                },
                .capture = {
                        .stream_name = "AIF1 Capture",
                        .channels_min = 1,
                        .channels_max = 2,
                        .rates = WM8994_RATES,
                        .formats = WM8994_FORMATS,
                        .sig_bits = 24,
                 },
                .ops = &wm8994_aif1_dai_ops,
        },
......
}

  可见,Codec驱动的第一个步骤就是定义snd_soc_codec_driver和snd_soc_dai_driver的实例,然后调用 snd_soc_register_codec函数对Codec进行注册。进入snd_soc_register_codec函数看看:

第一,它申请了一个snd_soc_codec结构的实例:

  • sound/soc/soc-core.c

codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);

确定codec的名字,这个名字很重大,Machine驱动定义的snd_soc_dai_link中会指定每个link的codec和dai的名字,进行匹配绑定时就是通过和这里的名字比较,从而找到该Codec的!

--snd_soc_component_initialize()
component->name = fmt_single_name(dev, &component->id);

然后初始化它的各个字段,多数字段的值来自上面定义的snd_soc_codec_driver的实例 soc_codec_dev_wm8994:

        if (codec_drv->probe)
                codec->component.probe = snd_soc_codec_drv_probe;
        if (codec_drv->remove)
                codec->component.remove = snd_soc_codec_drv_remove;
        if (codec_drv->suspend)
                codec->component.suspend = snd_soc_codec_drv_suspend;
        if (codec_drv->resume)
                codec->component.resume = snd_soc_codec_drv_resume;
        if (codec_drv->write)
                codec->component.write = snd_soc_codec_drv_write;
        if (codec_drv->read)
                codec->component.read = snd_soc_codec_drv_read;
        if (codec_drv->set_sysclk)
                codec->component.set_sysclk = snd_soc_codec_set_sysclk_;
        if (codec_drv->set_pll)
                codec->component.set_pll = snd_soc_codec_set_pll_;
        if (codec_drv->set_jack)
                codec->component.set_jack = snd_soc_codec_set_jack_;
        codec->component.ignore_pmdown_time = codec_drv->ignore_pmdown_time;

在做了一些寄存器缓存的初始化和配置工作后,通过 snd_soc_register_dais函数对本Codec的dai进行注册:

        ret = snd_soc_register_dais(&codec->component, dai_drv, num_dai, false);
        if (ret < 0) {
                dev_err(dev, "ASoC: Failed to register DAIs: %d
", ret);
                goto err_cleanup;
        }

最后,它把codec实例链接到全局链表codec_list中,并且调用snd_soc_instantiate_cards是函数触发Machine驱动进行一次匹配绑定操作:

snd_soc_component_add_unlocked(&codec->component);
list_add(&codec->list, &codec_list);

4.mfd设备

  前面已经提到,codec驱动把自己注册为一个platform driver,那对应的platform device在哪里定义?答案是在以下代码文件中:/drivers/mfd/wm8994-core.c。

  WM8994本身具备多种功能,除了codec外,它还有作为LDO和GPIO使用,这几种功能共享一些IO和中断资源,linux为这种设备提供了一套标准的实现方法:mfd设备。其基本思想是为这些功能的公共部分实现一个父设备,以便共享某些系统资源和功能,然后每个子功能实现为它的子设备,这样既共享了资源和代码,又能实现合理的设备层次结构,主要利用到的API就是:mfd_add_devices(),mfd_remove_devices(),mfd_cell_enable(),mfd_cell_disable(),mfd_clone_cell()。

回到wm8994-core.c中,由于WM8994使用I2C进行内部寄存器的存取,它第一注册了一个I2C驱动:

static struct i2c_driver wm8994_i2c_driver = {
   .driver = {
      .name = "wm8994",
      .pm = &wm8994_pm_ops,
      .of_match_table = of_match_ptr(wm8994_of_match),
   },
   .probe = wm8994_i2c_probe,
   .remove = wm8994_i2c_remove,
   .id_table = wm8994_i2c_id,
};

module_i2c_driver(wm8994_i2c_driver);

  进入 wm8994_i2c_probe()函数,它先申请了一个wm8994结构的变量,该变量被作为这个I2C设备的driver_data使用,上面已经讲过,codec作为它的子设备,将会取出并使用这个driver_data。接下来,本函数利用devm_regmap_init_i2c()初始化并获得一个regmap结构,该结构主要用于后续基于regmap机制的寄存器I/O,关于regmap我们留在后面再讲。最后,通过wm8994_device_init()来添加mfd子设备:

  • drivers/mfd/wm8994-core.c

static int wm8994_i2c_probe(struct i2c_client *i2c,
                                      const struct i2c_device_id *id)
{
        const struct of_device_id *of_id;
        struct wm8994 *wm8994;
        int ret;

        wm8994 = devm_kzalloc(&i2c->dev, sizeof(struct wm8994), GFP_KERNEL);
        if (wm8994 == NULL)
                return -ENOMEM;

        i2c_set_clientdata(i2c, wm8994);
        wm8994->dev = &i2c->dev;
        wm8994->irq = i2c->irq;

        if (i2c->dev.of_node) {
                of_id = of_match_device(wm8994_of_match, &i2c->dev);
                if (of_id)
                        wm8994->type = (enum wm8994_type)of_id->data;
        } else {
                wm8994->type = id->driver_data;
        }

        wm8994->regmap = devm_regmap_init_i2c(i2c, &wm8994_base_regmap_config);
        if (IS_ERR(wm8994->regmap)) {
                ret = PTR_ERR(wm8994->regmap);
                dev_err(wm8994->dev, "Failed to allocate register map: %d
",
                        ret);
                return ret;
        }

        return wm8994_device_init(wm8994, i2c->irq);
}

继续进入wm8994_device_init()函数,它第一为两个LDO添加mfd子设备:

        /* Add the on-chip regulators first for bootstrapping */
        ret = mfd_add_devices(wm8994->dev, 0,
                              wm8994_regulator_devs,
                              ARRAY_SIZE(wm8994_regulator_devs),
                              NULL, 0, NULL);

由于WM1811,WM8994,WM8958三个芯片功能类似,因此这三个芯片都使用了WM8994的代码,所以 wm8994_device_init()接下来根据不同的芯片型号做了一些初始化动作,这部分的代码就不贴了。接着,从platform_data中获得部分配置信息:

        wm8994->irq_base = pdata->irq_base;
        wm8994->gpio_base = pdata->gpio_base;

        /* GPIO configuration is only applied if it s non-zero */
        for (i = 0; i < ARRAY_SIZE(pdata->gpio_defaults); i++) {
                if (pdata->gpio_defaults[i]) {
                        wm8994_set_bits(wm8994, WM8994_GPIO_1 + i,
                                        0xffff, pdata->gpio_defaults[i]);
                }
        }

最后,初始化irq,然后添加codec子设备和gpio子设备:

        wm8994_irq_init(wm8994);

        ret = mfd_add_devices(wm8994->dev, -1,
                              wm8994_devs, ARRAY_SIZE(wm8994_devs),
                              NULL, 0, NULL);

经过以上这些处理后,作为父设备的I2C设备已经准备就绪,它的下面挂着4个子设备:ldo-0,ldo-1,codec,gpio。其中,codec子设备的加入,它将会和前面所讲codec的platform driver匹配,触发probe回调完成下面所说的codec驱动的初始化工作。

5.Codec初始化

  Machine驱动的初始化,codec和dai的注册,都会调用snd_soc_instantiate_cards()进行一次声卡和codec,dai,platform的匹配绑定过程,这里所说的绑定,正如Machine驱动一文中所描述,就是通过3个全局链表,按名字进行匹配,把匹配的codec,dai和platform实例赋值给声卡每对dai的snd_soc_pcm_runtime变量中。一旦绑定成功,将会使得codec和dai驱动的probe回调被调用,codec的初始化工作就在该回调中完成。对于WM8994,该回调就是wm8994_codec_probe函数:

  • wm8994_codec_probe

ALSA声卡驱动--7:ASoC架构中的Codec

  • 取出父设备的driver_data,实则就是上一节的wm8994结构变量,取出其中的regmap字段,复制到codec的control_data字段中;
  • 申请一个wm8994_priv私有数据结构,并把它设为codec设备的driver_data;
  • 通过snd_soc_codec_set_cache_io初始化regmap io,完成这一步后,就可以使用API:snd_soc_read(),snd_soc_write()对codec的寄存器进行读写了;
  • 把父设备的driver_data(struct wm8994)和platform_data保存到私有结构wm8994_priv中;
  • 由于要同时支持3个芯片型号,这里要根据芯片的型号做一些特定的初始化工作;
  • 申请必要的几个中断;
  • 设置合适的偏置电平;
  • 通过snd_soc_update_bits修改某些寄存器;
  • 根据父设备的platform_data,完成特定于平台的初始化配置;
  • 添加必要的control,dapm部件进而dapm路由信息;

至此,codec驱动的初始化完成。

6.regmap-io

  我们知道,要想对codec进行控制,一般都是通过读写它的内部寄存器完成的,读写的接口一般是I2C或者是SPI接口,不过每个codec芯片寄存器的比特位组成都有所不同,寄存器地址的比特位也有所不同。例如WM8753的寄存器地址是7bits,数据是9bits,WM8993的寄存器地址是8bits,数据也是16bits,而WM8994的寄存器地址是16bits,数据也是16bits。在kernel3.1版本,内核引入了一套regmap机制和相关的API,这样就可以用统一的操作来实现对这些多样的寄存器的控制。regmap使用起来也相对简单:

  • 为codec定义一个regmap_config结构实例,指定codec寄存器的地址和数据位等信息;
  • 根据codec的控制总线类型,调用以下其中一个函数,得到一个指向regmap结构的指针:
    • struct regmap *regmap_init_i2c(struct i2c_client *i2c, const struct regmap_config *config);
    • struct regmap *regmap_init_spi(struct spi_device *dev, const struct regmap_config *config);
  • 把获得的regmap结构指针赋值给codec->control_data;
  • 调用soc-io的api:snd_soc_codec_set_cache_io使得soc-io和regmap进行关联;

  完成以上步骤后,codec驱动就可以使用诸如snd_soc_read、snd_soc_write、snd_soc_update_bits等API对codec的寄存器进行读写了。

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容