第17章Linux音频设备驱动之Linux ASoC音频设备驱动

匿名 (未验证) 提交于 2019-12-02 21:59:42

17.5 Linux ASoC音频设备驱动

17.5.1 ASoC(ALSA System on Chip) 驱动的组成

    ASoC(ALSA System on Chip)是 ALSA 在 SoC 方面的发展和演变,ASoC在本质上仍然属于ALSA,但是在 ALSA 架构基础上对 CPU 相关的代码和 Codec 相关的代码进行分离。其原因是,采用传统 ALSA 架构的情况下,同一型号的 Codec 工作于不同的 CPU 时,需要不同的驱动,这不符合代码重用的要求。

    对于目前嵌入式系统上的声卡驱动开发,建议读者尽量采用 ASoC 框架,ASoC 主要由 3 部分组成。

(1)Codec 驱动。这一部分只关心 Codec 本身,与 CPU 平台相关的特性不由此部分操作。

(2)平台驱动。这一部分只关心 CPU 本身,不关心 Codec。主要处理两个问题:DMA 引擎和 SoC 集成的 PCM、I 2 S 或 AC 97 数字接口控制。

(3)板驱动(称为 machine 驱动)。这一部分将平台驱动和 Codec 驱动绑定在一起,描述板一级的硬件特征。

    在以上 3 部分中,1 和 2 基本都可以仍然是通用的驱动,Codec 驱动认为自己可以连接任意 CPU,而 CPU 的 I 2 S、PCM 或 AC 97 接口对应的平台驱动则认为自己可以连接任意符合其接口类型的 Codec,只有 (3)是不通用的,由特定的电路板上具体的 CPU 和 Codec 确定,因此板驱动很像一个插座,上面插上了 Codec 和平台这两个插头。

    在以上三部分之上的是 ASoC 核心层,由内核源代码中的 sound/soc/soc-core.c 实现,查看其源代码发现它完全是一个传统的 ALSA 驱动。因此,对于基于 ASoC 架构的声卡驱动,alsa-lib以及 ALSA 的一系列 utility 仍然是可用的。而ASoC 的用户编程方法也与 ALSA 完全一致。

    内核源代码的 Documentation/sound/alsa/soc/目录包含ASoC 相关的文档。

17.5.2 ASoC Codec 驱动

在 ASoC 架构下,Codec 驱动负责如下工作。

(1)Codec DAI(Digital Audio Interfaces)和 PCM (脉冲编码调制)配置,由结构体 snd_soc_ dai(如代码清单 17.28)来描述,形容 playback、capture 的属性以及 DAI 接口的操作。

代码清单 17.28 DAI 结构体 snd_soc_dai 定义

include/sound/soc-dai.h

/*
 * Digital Audio Interface runtime data.
 *
 * Holds runtime data for a DAI.
 */

struct snd_soc_dai {

        /* DAI 的描述 */

        const char *name;
        int id;
        struct device *dev;
        void *ac97_pdata;       /* platform_data for the ac97 codec */

        /* driver ops */
        struct snd_soc_dai_driver *driver;

        /* DAI 运行时信息 */
        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 active;
        unsigned char probed:1;

        struct snd_soc_dapm_widget *playback_widget;
        struct snd_soc_dapm_widget *capture_widget;

        /* DAI DMA data */
        void *playback_dma_data;
        void *capture_dma_data;

        /* 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_platform *platform;
        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 snd_soc_card *card;

        struct list_head list;
};

(2)Codec IO 操作、动态音频电源管理以及时钟、PLL 等控制。

    代码清单 17.28 中的 snd_soc_codec 结构体是对 Codec 本身 I/O 控制以及动态音频电源管理Dynamic Audio Power Management,DAPM)的描述。描述 I 2 C、SPI 或 AC 97 如何读写 Codec 寄存器并容纳 DAPM 链表,其定义如代码清单 17.29。

代码清单 17.29 snd_soc_codec 结构体定义

include/sound/soc.h

/* SoC Audio Codec */
struct snd_soc_codec {
        char *name;
        struct module *owner;
        struct mutex mutex;

        /* callbacks */
        int (*dapm_event)(struct snd_soc_codec *codec, int event);

        /* runtime */
        struct snd_card *card;
        struct snd_ac97 *ac97;  /* for ad-hoc ac97 devices */
        unsigned int active;
        unsigned int pcm_devs;
        void *private_data;

        /* codec IO */
        void *control_data; /* codec control (i2c/3wire) data */
        unsigned int (*read)(struct snd_soc_codec *, unsigned int);
        int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);
        hw_write_t hw_write;
        hw_read_t hw_read;
        void *reg_cache;
        short reg_cache_size;
        short reg_cache_step;

        /* dapm */
        struct list_head dapm_widgets;
        struct list_head dapm_paths;
        unsigned int dapm_state;
        unsigned int suspend_dapm_state;
        struct delayed_work delayed_work;

        /* codec DAI's */
        struct snd_soc_codec_dai *dai;
        unsigned int num_dai;
};

 snd_soc_dai_ops 则描述该 Codec 的时钟、PLL 以及格式设置,其定义如代码清单 17.30。

代码清单 17.30 snd_soc_dai_ops 结构体定义

include/sound/soc-dai.h 

struct snd_soc_dai_ops {
        /*
         * DAI clocking configuration, all optional.
         * Called by soc_card drivers, normally in their hw_params.
         */
        int (*set_sysclk)(struct snd_soc_dai *dai,
                int clk_id, unsigned int freq, int dir);
        int (*set_pll)(struct snd_soc_dai *dai, int pll_id, int source,
                unsigned int freq_in, unsigned int freq_out);
        int (*set_clkdiv)(struct snd_soc_dai *dai, int div_id, int div);
        int (*set_bclk_ratio)(struct snd_soc_dai *dai, unsigned int ratio);

        /*
         * DAI format configuration
         * Called by soc_card drivers, normally in their hw_params.
         */
        int (*set_fmt)(struct snd_soc_dai *dai, unsigned int fmt);
        int (*xlate_tdm_slot_mask)(unsigned int slots,
                unsigned int *tx_mask, unsigned int *rx_mask);
        int (*set_tdm_slot)(struct snd_soc_dai *dai,
                unsigned int tx_mask, unsigned int rx_mask,
                int slots, int slot_width);
        int (*set_channel_map)(struct snd_soc_dai *dai,
                unsigned int tx_num, unsigned int *tx_slot,
                unsigned int rx_num, unsigned int *rx_slot);
        int (*set_tristate)(struct snd_soc_dai *dai, int tristate);
        int (*get_channel_map)(struct snd_soc_dai *dai,
                unsigned int *tx_num, unsigned int *tx_slot,
                unsigned int *rx_num, unsigned int *rx_slot);

        /*
         * DAI digital mute - optional.
         * Called by soc-core to minimise any pops.
         */
        int (*digital_mute)(struct snd_soc_dai *dai, int mute);
        int (*mute_stream)(struct snd_soc_dai *dai, int mute, int stream);

        /*
         * ALSA PCM audio operations - all optional.
         * Called by soc-core during audio PCM operations.
         */
        int (*startup)(struct snd_pcm_substream *,
                struct snd_soc_dai *);
        void (*shutdown)(struct snd_pcm_substream *,
                struct snd_soc_dai *);
        int (*hw_params)(struct snd_pcm_substream *,
                struct snd_pcm_hw_params *, struct snd_soc_dai *);
        int (*hw_free)(struct snd_pcm_substream *,
                struct snd_soc_dai *);
        int (*prepare)(struct snd_pcm_substream *,
                struct snd_soc_dai *);
        /*
         * NOTE: Commands passed to the trigger function are not necessarily
         * compatible with the current state of the dai. For example this
         * sequence of commands is possible: START STOP STOP.
         * So do not unconditionally use refcounting functions in the trigger
         * function, e.g. clk_enable/disable.
         */
        int (*trigger)(struct snd_pcm_substream *, int,
                struct snd_soc_dai *);
        int (*bespoke_trigger)(struct snd_pcm_substream *, int,
                struct snd_soc_dai *);
        /*
         * For hardware based FIFO caused delay reporting.
         * Optional.
         */
        snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *,
                struct snd_soc_dai *);
};

(3)Codec 的 mixer 控制。

    ASoC 中定义了一组宏来描述 Codec 的 mixer 控制,这组宏可以将 mixer 名和对应的寄存器进行绑定,主要包括:

include/sound/soc.h

SOC_SINGLE(xname, reg, shift, mask, invert)
SOC_DOUBLE(xname, reg, shift_left, shift_right, mask, invert)

SOC_ENUM_SINGLE(xreg, xshift, xmask, xtexts)

    例如,对于宏 SOC_SINGLE,参数 xname 是 mixer 的名字(如“Playback Volume”),reg是控制该 mixer 的寄存器,shift 对应寄存器内的位,mask 是进行操作时的屏蔽位,invert 表明是否倒序或翻转。

(4)Codec 音频操作。

    在 ASoC 驱动的 Codec 部分,也需要关心音频流开始采集或播放时的一些动作,如hw_params()、hw_free()、prepare()、trigger()这些操作,不过与原始 ALSA 不同的是,在 Codec驱动的这些函数中,不关心 CPU 端,而只关心 Codec 本身,由结构体 snd_soc_ops 描述,如代码清单 17.31 所示。

代码清单 17.31 snd_soc_ops 结构体定义

include/sound/soc.h

/* SoC audio ops */
struct snd_soc_ops {
        int (*startup)(struct snd_pcm_substream *);
        void (*shutdown)(struct snd_pcm_substream *);
        int (*hw_params)(struct snd_pcm_substream *, struct snd_pcm_hw_params *);
        int (*hw_free)(struct snd_pcm_substream *);
        int (*prepare)(struct snd_pcm_substream *);
        int (*trigger)(struct snd_pcm_substream *, int);
};

17.5.3 ASoC 平台驱动

    首先,在 ASoC 平台驱动部分,同样存在着 Codec 驱动中的 snd_soc_daisnd_soc_dai_opssnd_soc_ops 这 3 个结构体的实例用于描述 DAI 和 DAI 上的操作,不同的是,在平台驱动中,它们只描述 CPU 相关的部分。除此之外,在 ASoC 平台驱动中,必须实现完整的DMA 驱动,即传统 ALSA 的 snd_pcm_ops 结构体成员函数 trigger()、pointer()等。因此 ASoC 平台驱动由 DAI 和 DMA 两部分组成,如代码清单 17.32 所示。(i2s数字音频数据传输总线)

代码清单 17.32 ASoC 平台驱动的组成

/* DAI 部分 */
 static int xxx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
 {
         ...
 }

 static int xxx_i2s_startup(struct snd_pcm_substream *substream)
 {
         ...
 }

 static int xxx_i2s_hw_params(struct snd_pcm_substream *substream,
 struct snd_pcm_hw_params *params)
 {
         ...
 }

static void xxx_i2s_shutdown(struct snd_pcm_substream *substream)
 {
         ...
 }

 static int xxx_i2s_probe(struct platform_device *pdev, struct snd_soc_dai *dai)
 {
             ...
 }

 static void xxx_i2s_remove(struct platform_device *pdev, struct snd_soc_dai *dai)
 {
         ...
 }

 static int xxx_i2s_suspend(struct platform_device *dev, struct snd_soc_dai *dai)
 {
         ...
 }

 static int xxx_i2s_resume(struct platform_device *pdev, struct snd_soc_dai *dai)
 {
         ...
 }

struct snd_soc_dai xxx_i2s_dai = {
         .name = "xxx-i2s",
         .id = 0,
         .type = SND_SOC_DAI_I2S,
         .probe = xxx_i2s_probe,
         .remove = xxx_i2s_remove,
         .suspend = xxx_i2s_suspend,
         .resume = xxx_i2s_resume,
         .playback = {
                 .channels_min = 1,
                 .channels_max = 2,
                 .rates = XXX_I2S_RATES,

                 .formats = XXX_I2S_FORMATS,

                },

                 .capture = {
                         .channels_min = 1,
                         .channels_max = 2,
                         .rates = XXX_I2S_RATES,

                         .formats = XXX_I2S_FORMATS,

                },

                 .ops = {
                         .startup = xxx_i2s_startup,
                         .shutdown = xxx_i2s_shutdown,

                         .hw_params = xxx_i2s_hw_params,

                 },

             .dai_ops = {
                     .set_fmt = xxx_i2s_set_dai_fmt,
             },
 };

/* DMA 部分 */
 static void bf5xx_dma_irq(void *data)
 {
         struct snd_pcm_substream *pcm = data;
         snd_pcm_period_elapsed(pcm);
 }

static const struct snd_pcm_hardware xxx_pcm_hardware = {
         ...
 };

static int xxx_pcm_hw_params(struct snd_pcm_substream *substream,
 struct snd_pcm_hw_params *params)
 {
         ...
         snd_pcm_lib_malloc_pages(substream, size);

         return 0;
 }

static int xxx_pcm_hw_free(struct snd_pcm_substream *substream)
 {
         snd_pcm_lib_free_pages(substream);

         return 0;
 }

static int xxx_pcm_prepare(struct snd_pcm_substream *substream)
 {
         ...
 }

 static int xxx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
 {
         ...
 }

 static snd_pcm_uframes_t xxx_pcm_pointer(struct snd_pcm_substream *substream)
 {
         ...
 }

 static int xxx_pcm_open(struct snd_pcm_substream *substream)
{
         ...
}

static int xxx_pcm_mmap(struct snd_pcm_substream *substream, struct vm_area_struct *vma)
 {
         ...
 }

struct snd_pcm_ops xxx_pcm_i2s_ops = {

          .open = xxx_pcm_open,
         .ioctl = snd_pcm_lib_ioctl,
         .hw_params = xxx_pcm_hw_params,
         .hw_free = xxx_pcm_hw_free,
         .prepare = xxx_pcm_prepare,
         .trigger = xxx_pcm_trigger,
         .pointer = xxx_pcm_pointer,
         .mmap = xxx_pcm_mmap,
 };

17.5.4 ASoC 板驱动

    ASoC 板驱动直接与板对应,对于一块确定的电路板,其 SoC 和 Codec 都是确定的,因此板驱动将 ASoC Codec 驱动和 CPU 端的平台驱动进行绑定,这个绑定用数据结构 snd_soc_dai_link描述,其定义如代码清单 17.33 所示。

代码清单 17.33 snd_soc_dai_link 结构体

include/sound/soc.h

struct snd_soc_dai_link {
         char *name; /* Codec name */
         char *stream_name; /* Stream name */

         /* DAI */
         struct snd_soc_dai *codec_dai;
         struct snd_soc_dai *cpu_dai;

         /* 板流操作 */
          struct snd_soc_ops *ops;

         /* codec/machine 特定的初始化 */
        int (*init)(struct snd_soc_codec *codec);

        /* DAI pcm */
         struct snd_pcm *pcm;
};

    除此之外,板驱动还关心一些板特定的硬件操作,也存在一个 snd_soc_ops 的实例。

    在板驱动的模块初始化函数中,会通过 platform_device_add()注册一个名为“soc-audio”的platform 设备,因为 soc-core.c 注册了一个名为“soc-audio”的 platform 驱动,因此,在板驱动中注册“soc-audio”设备会引起两者的匹配,从而引发一系列的初始化操作。尤其值得一提的是,“soc-audio”设备的私有数据需要为一个 snd_soc_device 的结构体实体,因此一个板驱动典型的模块加载函数将形如代码清单 17.34。代码清单 17.34 ASoC 板驱动模块加载函数及其访问的数据结构

static struct snd_soc_dai_link cpux_codecy_dai = {
         .name = "codecy",
         .stream_name = "CODECY",
         .cpu_dai = &cpux_i2s_dai,
         .codec_dai = &codecy_dai,
         .ops = &cpux_codecy_ops,
 };

static struct snd_soc_machine cpux_codecy = {
         .name = "cpux_codecy",
         .probe = cpux_probe,
         .dai_link = &cpux_codecy_dai,
         .num_links = 1,
 };

static struct snd_soc_device cpux_codecy_snd_devdata = {
         .machine = &cpux_codecy,
         .platform = &cpux_i2s_soc_platform,
         .codec_dev = &soc_codec_dev_codecy,
 };

 static struct platform_device *cpux_codecy_snd_device;

static int __init cpux_codecy_init(void)
 {
         int ret;

         cpux_codecy_snd_device = platform_device_alloc("soc-audio", -1);
         if (!cpux_codecy_snd_device)
                 return -ENOMEM;

         platform_set_drvdata(cpux_codecy_snd_device, &cpux_codecy_snd_devdata);
         cpux_codecy_snd_devdata.dev = &cpux_codecy_snd_device->dev;
         ret = platform_device_add(cpux_codecy_snd_device);

         if (ret)
             platform_device_put(cpux_codecy_snd_device);

         return ret;
 }
 module_init(cpux_codecy_init);

分析:

        ASoC 驱动的 Codec、平台和板驱动是 3 个独立的内核模块,在板驱动中,对 ASoC Codec 设备、ASoC平台设备实例的访问都通过被ASoC Codec驱动或ASoC平台驱动导出的全局变量执行,这使得 ASoC 难以同时支持两个以上的 Codec。



易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!